• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2019, Daniel Stenberg, <daniel@haxx.se>, et al.
9  *
10  * This software is licensed as described in the file COPYING, which
11  * you should have received as part of this distribution. The terms
12  * are also available at https://curl.haxx.se/docs/copyright.html.
13  *
14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15  * copies of the Software, and permit persons to whom the Software is
16  * furnished to do so, under the terms of the COPYING file.
17  *
18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19  * KIND, either express or implied.
20  *
21  ***************************************************************************/
22 
23 #include "curl_setup.h"
24 
25 #if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \
26     defined(NTLM_WB_ENABLED)
27 
28 /*
29  * NTLM details:
30  *
31  * https://davenport.sourceforge.io/ntlm.html
32  * https://www.innovation.ch/java/ntlm.html
33  */
34 
35 #define DEBUG_ME 0
36 
37 #ifdef HAVE_SYS_WAIT_H
38 #include <sys/wait.h>
39 #endif
40 #ifdef HAVE_SIGNAL_H
41 #include <signal.h>
42 #endif
43 #ifdef HAVE_PWD_H
44 #include <pwd.h>
45 #endif
46 
47 #include "urldata.h"
48 #include "sendf.h"
49 #include "select.h"
50 #include "vauth/ntlm.h"
51 #include "curl_ntlm_core.h"
52 #include "curl_ntlm_wb.h"
53 #include "url.h"
54 #include "strerror.h"
55 #include "strdup.h"
56 #include "strcase.h"
57 
58 /* The last 3 #include files should be in this order */
59 #include "curl_printf.h"
60 #include "curl_memory.h"
61 #include "memdebug.h"
62 
63 #if DEBUG_ME
64 # define DEBUG_OUT(x) x
65 #else
66 # define DEBUG_OUT(x) Curl_nop_stmt
67 #endif
68 
69 /* Portable 'sclose_nolog' used only in child process instead of 'sclose'
70    to avoid fooling the socket leak detector */
71 #if defined(HAVE_CLOSESOCKET)
72 #  define sclose_nolog(x)  closesocket((x))
73 #elif defined(HAVE_CLOSESOCKET_CAMEL)
74 #  define sclose_nolog(x)  CloseSocket((x))
75 #else
76 #  define sclose_nolog(x)  close((x))
77 #endif
78 
Curl_http_auth_cleanup_ntlm_wb(struct connectdata * conn)79 void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn)
80 {
81   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
82     sclose(conn->ntlm_auth_hlpr_socket);
83     conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
84   }
85 
86   if(conn->ntlm_auth_hlpr_pid) {
87     int i;
88     for(i = 0; i < 4; i++) {
89       pid_t ret = waitpid(conn->ntlm_auth_hlpr_pid, NULL, WNOHANG);
90       if(ret == conn->ntlm_auth_hlpr_pid || errno == ECHILD)
91         break;
92       switch(i) {
93       case 0:
94         kill(conn->ntlm_auth_hlpr_pid, SIGTERM);
95         break;
96       case 1:
97         /* Give the process another moment to shut down cleanly before
98            bringing down the axe */
99         Curl_wait_ms(1);
100         break;
101       case 2:
102         kill(conn->ntlm_auth_hlpr_pid, SIGKILL);
103         break;
104       case 3:
105         break;
106       }
107     }
108     conn->ntlm_auth_hlpr_pid = 0;
109   }
110 
111   free(conn->challenge_header);
112   conn->challenge_header = NULL;
113   free(conn->response_header);
114   conn->response_header = NULL;
115 }
116 
ntlm_wb_init(struct connectdata * conn,const char * userp)117 static CURLcode ntlm_wb_init(struct connectdata *conn, const char *userp)
118 {
119   curl_socket_t sockfds[2];
120   pid_t child_pid;
121   const char *username;
122   char *slash, *domain = NULL;
123   const char *ntlm_auth = NULL;
124   char *ntlm_auth_alloc = NULL;
125 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
126   struct passwd pw, *pw_res;
127   char pwbuf[1024];
128 #endif
129   char buffer[STRERROR_LEN];
130 
131   /* Return if communication with ntlm_auth already set up */
132   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
133      conn->ntlm_auth_hlpr_pid)
134     return CURLE_OK;
135 
136   username = userp;
137   /* The real ntlm_auth really doesn't like being invoked with an
138      empty username. It won't make inferences for itself, and expects
139      the client to do so (mostly because it's really designed for
140      servers like squid to use for auth, and client support is an
141      afterthought for it). So try hard to provide a suitable username
142      if we don't already have one. But if we can't, provide the
143      empty one anyway. Perhaps they have an implementation of the
144      ntlm_auth helper which *doesn't* need it so we might as well try */
145   if(!username || !username[0]) {
146     username = getenv("NTLMUSER");
147     if(!username || !username[0])
148       username = getenv("LOGNAME");
149     if(!username || !username[0])
150       username = getenv("USER");
151 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
152     if((!username || !username[0]) &&
153        !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
154        pw_res) {
155       username = pw.pw_name;
156     }
157 #endif
158     if(!username || !username[0])
159       username = userp;
160   }
161   slash = strpbrk(username, "\\/");
162   if(slash) {
163     domain = strdup(username);
164     if(!domain)
165       return CURLE_OUT_OF_MEMORY;
166     slash = domain + (slash - username);
167     *slash = '\0';
168     username = username + (slash - domain) + 1;
169   }
170 
171   /* For testing purposes, when DEBUGBUILD is defined and environment
172      variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
173      NTLM challenge/response which only accepts commands and output
174      strings pre-written in test case definitions */
175 #ifdef DEBUGBUILD
176   ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
177   if(ntlm_auth_alloc)
178     ntlm_auth = ntlm_auth_alloc;
179   else
180 #endif
181     ntlm_auth = NTLM_WB_FILE;
182 
183   if(access(ntlm_auth, X_OK) != 0) {
184     failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
185           ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer)));
186     goto done;
187   }
188 
189   if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
190     failf(conn->data, "Could not open socket pair. errno %d: %s",
191           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
192     goto done;
193   }
194 
195   child_pid = fork();
196   if(child_pid == -1) {
197     sclose(sockfds[0]);
198     sclose(sockfds[1]);
199     failf(conn->data, "Could not fork. errno %d: %s",
200           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
201     goto done;
202   }
203   else if(!child_pid) {
204     /*
205      * child process
206      */
207 
208     /* Don't use sclose in the child since it fools the socket leak detector */
209     sclose_nolog(sockfds[0]);
210     if(dup2(sockfds[1], STDIN_FILENO) == -1) {
211       failf(conn->data, "Could not redirect child stdin. errno %d: %s",
212             errno, Curl_strerror(errno, buffer, sizeof(buffer)));
213       exit(1);
214     }
215 
216     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
217       failf(conn->data, "Could not redirect child stdout. errno %d: %s",
218             errno, Curl_strerror(errno, buffer, sizeof(buffer)));
219       exit(1);
220     }
221 
222     if(domain)
223       execl(ntlm_auth, ntlm_auth,
224             "--helper-protocol", "ntlmssp-client-1",
225             "--use-cached-creds",
226             "--username", username,
227             "--domain", domain,
228             NULL);
229     else
230       execl(ntlm_auth, ntlm_auth,
231             "--helper-protocol", "ntlmssp-client-1",
232             "--use-cached-creds",
233             "--username", username,
234             NULL);
235 
236     sclose_nolog(sockfds[1]);
237     failf(conn->data, "Could not execl(). errno %d: %s",
238           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
239     exit(1);
240   }
241 
242   sclose(sockfds[1]);
243   conn->ntlm_auth_hlpr_socket = sockfds[0];
244   conn->ntlm_auth_hlpr_pid = child_pid;
245   free(domain);
246   free(ntlm_auth_alloc);
247   return CURLE_OK;
248 
249 done:
250   free(domain);
251   free(ntlm_auth_alloc);
252   return CURLE_REMOTE_ACCESS_DENIED;
253 }
254 
255 /* if larger than this, something is seriously wrong */
256 #define MAX_NTLM_WB_RESPONSE 100000
257 
ntlm_wb_response(struct connectdata * conn,const char * input,curlntlm state)258 static CURLcode ntlm_wb_response(struct connectdata *conn,
259                                  const char *input, curlntlm state)
260 {
261   char *buf = malloc(NTLM_BUFSIZE);
262   size_t len_in = strlen(input), len_out = 0;
263 
264   if(!buf)
265     return CURLE_OUT_OF_MEMORY;
266 
267   while(len_in > 0) {
268     ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
269     if(written == -1) {
270       /* Interrupted by a signal, retry it */
271       if(errno == EINTR)
272         continue;
273       /* write failed if other errors happen */
274       goto done;
275     }
276     input += written;
277     len_in -= written;
278   }
279   /* Read one line */
280   while(1) {
281     ssize_t size;
282     char *newbuf;
283 
284     size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
285     if(size == -1) {
286       if(errno == EINTR)
287         continue;
288       goto done;
289     }
290     else if(size == 0)
291       goto done;
292 
293     len_out += size;
294     if(buf[len_out - 1] == '\n') {
295       buf[len_out - 1] = '\0';
296       break;
297     }
298 
299     if(len_out > MAX_NTLM_WB_RESPONSE) {
300       failf(conn->data, "too large ntlm_wb response!");
301       free(buf);
302       return CURLE_OUT_OF_MEMORY;
303     }
304 
305     newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
306     if(!newbuf)
307       return CURLE_OUT_OF_MEMORY;
308 
309     buf = newbuf;
310   }
311 
312   /* Samba/winbind installed but not configured */
313   if(state == NTLMSTATE_TYPE1 &&
314      len_out == 3 &&
315      buf[0] == 'P' && buf[1] == 'W')
316     goto done;
317   /* invalid response */
318   if(len_out < 4)
319     goto done;
320   if(state == NTLMSTATE_TYPE1 &&
321      (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
322     goto done;
323   if(state == NTLMSTATE_TYPE2 &&
324      (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
325      (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
326     goto done;
327 
328   conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3);
329   free(buf);
330   if(!conn->response_header)
331     return CURLE_OUT_OF_MEMORY;
332   return CURLE_OK;
333 done:
334   free(buf);
335   return CURLE_REMOTE_ACCESS_DENIED;
336 }
337 
Curl_input_ntlm_wb(struct connectdata * conn,bool proxy,const char * header)338 CURLcode Curl_input_ntlm_wb(struct connectdata *conn,
339                             bool proxy,
340                             const char *header)
341 {
342   curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state;
343 
344   if(!checkprefix("NTLM", header))
345     return CURLE_BAD_CONTENT_ENCODING;
346 
347   header += strlen("NTLM");
348   while(*header && ISSPACE(*header))
349     header++;
350 
351   if(*header) {
352     conn->challenge_header = strdup(header);
353     if(!conn->challenge_header)
354       return CURLE_OUT_OF_MEMORY;
355 
356     *state = NTLMSTATE_TYPE2; /* We got a type-2 message */
357   }
358   else {
359     if(*state == NTLMSTATE_LAST) {
360       infof(conn->data, "NTLM auth restarted\n");
361       Curl_http_auth_cleanup_ntlm_wb(conn);
362     }
363     else if(*state == NTLMSTATE_TYPE3) {
364       infof(conn->data, "NTLM handshake rejected\n");
365       Curl_http_auth_cleanup_ntlm_wb(conn);
366       *state = NTLMSTATE_NONE;
367       return CURLE_REMOTE_ACCESS_DENIED;
368     }
369     else if(*state >= NTLMSTATE_TYPE1) {
370       infof(conn->data, "NTLM handshake failure (internal error)\n");
371       return CURLE_REMOTE_ACCESS_DENIED;
372     }
373 
374     *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */
375   }
376 
377   return CURLE_OK;
378 }
379 
380 /*
381  * This is for creating ntlm header output by delegating challenge/response
382  * to Samba's winbind daemon helper ntlm_auth.
383  */
Curl_output_ntlm_wb(struct connectdata * conn,bool proxy)384 CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
385                               bool proxy)
386 {
387   /* point to the address of the pointer that holds the string to send to the
388      server, which is for a plain host or for a HTTP proxy */
389   char **allocuserpwd;
390   /* point to the name and password for this */
391   const char *userp;
392   curlntlm *state;
393   struct auth *authp;
394 
395   CURLcode res = CURLE_OK;
396   char *input;
397 
398   DEBUGASSERT(conn);
399   DEBUGASSERT(conn->data);
400 
401   if(proxy) {
402     allocuserpwd = &conn->allocptr.proxyuserpwd;
403     userp = conn->http_proxy.user;
404     state = &conn->proxy_ntlm_state;
405     authp = &conn->data->state.authproxy;
406   }
407   else {
408     allocuserpwd = &conn->allocptr.userpwd;
409     userp = conn->user;
410     state = &conn->http_ntlm_state;
411     authp = &conn->data->state.authhost;
412   }
413   authp->done = FALSE;
414 
415   /* not set means empty */
416   if(!userp)
417     userp = "";
418 
419   switch(*state) {
420   case NTLMSTATE_TYPE1:
421   default:
422     /* Use Samba's 'winbind' daemon to support NTLM authentication,
423      * by delegating the NTLM challenge/response protocol to a helper
424      * in ntlm_auth.
425      * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
426      * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
427      * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
428      * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
429      * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
430      * filename of ntlm_auth helper.
431      * If NTLM authentication using winbind fails, go back to original
432      * request handling process.
433      */
434     /* Create communication with ntlm_auth */
435     res = ntlm_wb_init(conn, userp);
436     if(res)
437       return res;
438     res = ntlm_wb_response(conn, "YR\n", *state);
439     if(res)
440       return res;
441 
442     free(*allocuserpwd);
443     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
444                             proxy ? "Proxy-" : "",
445                             conn->response_header);
446     DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
447     free(conn->response_header);
448     if(!*allocuserpwd)
449       return CURLE_OUT_OF_MEMORY;
450     conn->response_header = NULL;
451     break;
452 
453   case NTLMSTATE_TYPE2:
454     input = aprintf("TT %s\n", conn->challenge_header);
455     if(!input)
456       return CURLE_OUT_OF_MEMORY;
457     res = ntlm_wb_response(conn, input, *state);
458     free(input);
459     input = NULL;
460     if(res)
461       return res;
462 
463     free(*allocuserpwd);
464     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
465                             proxy ? "Proxy-" : "",
466                             conn->response_header);
467     DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
468     *state = NTLMSTATE_TYPE3; /* we sent a type-3 */
469     authp->done = TRUE;
470     Curl_http_auth_cleanup_ntlm_wb(conn);
471     if(!*allocuserpwd)
472       return CURLE_OUT_OF_MEMORY;
473     break;
474 
475   case NTLMSTATE_TYPE3:
476     /* connection is already authenticated,
477      * don't send a header in future requests */
478     *state = NTLMSTATE_LAST;
479     /* FALLTHROUGH */
480   case NTLMSTATE_LAST:
481     Curl_safefree(*allocuserpwd);
482     authp->done = TRUE;
483     break;
484   }
485 
486   return CURLE_OK;
487 }
488 
489 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */
490