• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2020, 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 
ntlm_wb_cleanup(struct ntlmdata * ntlm)79 static void ntlm_wb_cleanup(struct ntlmdata *ntlm)
80 {
81   if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
82     sclose(ntlm->ntlm_auth_hlpr_socket);
83     ntlm->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
84   }
85 
86   if(ntlm->ntlm_auth_hlpr_pid) {
87     int i;
88     for(i = 0; i < 4; i++) {
89       pid_t ret = waitpid(ntlm->ntlm_auth_hlpr_pid, NULL, WNOHANG);
90       if(ret == ntlm->ntlm_auth_hlpr_pid || errno == ECHILD)
91         break;
92       switch(i) {
93       case 0:
94         kill(ntlm->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(ntlm->ntlm_auth_hlpr_pid, SIGKILL);
103         break;
104       case 3:
105         break;
106       }
107     }
108     ntlm->ntlm_auth_hlpr_pid = 0;
109   }
110 
111   Curl_safefree(ntlm->challenge);
112   Curl_safefree(ntlm->response);
113 }
114 
ntlm_wb_init(struct Curl_easy * data,struct ntlmdata * ntlm,const char * userp)115 static CURLcode ntlm_wb_init(struct Curl_easy *data, struct ntlmdata *ntlm,
116                              const char *userp)
117 {
118   curl_socket_t sockfds[2];
119   pid_t child_pid;
120   const char *username;
121   char *slash, *domain = NULL;
122   const char *ntlm_auth = NULL;
123   char *ntlm_auth_alloc = NULL;
124 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
125   struct passwd pw, *pw_res;
126   char pwbuf[1024];
127 #endif
128   char buffer[STRERROR_LEN];
129 
130 #if defined(CURL_DISABLE_VERBOSE_STRINGS)
131   (void) data;
132 #endif
133 
134   /* Return if communication with ntlm_auth already set up */
135   if(ntlm->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
136      ntlm->ntlm_auth_hlpr_pid)
137     return CURLE_OK;
138 
139   username = userp;
140   /* The real ntlm_auth really doesn't like being invoked with an
141      empty username. It won't make inferences for itself, and expects
142      the client to do so (mostly because it's really designed for
143      servers like squid to use for auth, and client support is an
144      afterthought for it). So try hard to provide a suitable username
145      if we don't already have one. But if we can't, provide the
146      empty one anyway. Perhaps they have an implementation of the
147      ntlm_auth helper which *doesn't* need it so we might as well try */
148   if(!username || !username[0]) {
149     username = getenv("NTLMUSER");
150     if(!username || !username[0])
151       username = getenv("LOGNAME");
152     if(!username || !username[0])
153       username = getenv("USER");
154 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
155     if((!username || !username[0]) &&
156        !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
157        pw_res) {
158       username = pw.pw_name;
159     }
160 #endif
161     if(!username || !username[0])
162       username = userp;
163   }
164   slash = strpbrk(username, "\\/");
165   if(slash) {
166     domain = strdup(username);
167     if(!domain)
168       return CURLE_OUT_OF_MEMORY;
169     slash = domain + (slash - username);
170     *slash = '\0';
171     username = username + (slash - domain) + 1;
172   }
173 
174   /* For testing purposes, when DEBUGBUILD is defined and environment
175      variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
176      NTLM challenge/response which only accepts commands and output
177      strings pre-written in test case definitions */
178 #ifdef DEBUGBUILD
179   ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
180   if(ntlm_auth_alloc)
181     ntlm_auth = ntlm_auth_alloc;
182   else
183 #endif
184     ntlm_auth = NTLM_WB_FILE;
185 
186   if(access(ntlm_auth, X_OK) != 0) {
187     failf(data, "Could not access ntlm_auth: %s errno %d: %s",
188           ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer)));
189     goto done;
190   }
191 
192   if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
193     failf(data, "Could not open socket pair. errno %d: %s",
194           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
195     goto done;
196   }
197 
198   child_pid = fork();
199   if(child_pid == -1) {
200     sclose(sockfds[0]);
201     sclose(sockfds[1]);
202     failf(data, "Could not fork. errno %d: %s",
203           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
204     goto done;
205   }
206   else if(!child_pid) {
207     /*
208      * child process
209      */
210 
211     /* Don't use sclose in the child since it fools the socket leak detector */
212     sclose_nolog(sockfds[0]);
213     if(dup2(sockfds[1], STDIN_FILENO) == -1) {
214       failf(data, "Could not redirect child stdin. errno %d: %s",
215             errno, Curl_strerror(errno, buffer, sizeof(buffer)));
216       exit(1);
217     }
218 
219     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
220       failf(data, "Could not redirect child stdout. errno %d: %s",
221             errno, Curl_strerror(errno, buffer, sizeof(buffer)));
222       exit(1);
223     }
224 
225     if(domain)
226       execl(ntlm_auth, ntlm_auth,
227             "--helper-protocol", "ntlmssp-client-1",
228             "--use-cached-creds",
229             "--username", username,
230             "--domain", domain,
231             NULL);
232     else
233       execl(ntlm_auth, ntlm_auth,
234             "--helper-protocol", "ntlmssp-client-1",
235             "--use-cached-creds",
236             "--username", username,
237             NULL);
238 
239     sclose_nolog(sockfds[1]);
240     failf(data, "Could not execl(). errno %d: %s",
241           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
242     exit(1);
243   }
244 
245   sclose(sockfds[1]);
246   ntlm->ntlm_auth_hlpr_socket = sockfds[0];
247   ntlm->ntlm_auth_hlpr_pid = child_pid;
248   free(domain);
249   free(ntlm_auth_alloc);
250   return CURLE_OK;
251 
252 done:
253   free(domain);
254   free(ntlm_auth_alloc);
255   return CURLE_REMOTE_ACCESS_DENIED;
256 }
257 
258 /* if larger than this, something is seriously wrong */
259 #define MAX_NTLM_WB_RESPONSE 100000
260 
ntlm_wb_response(struct Curl_easy * data,struct ntlmdata * ntlm,const char * input,curlntlm state)261 static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm,
262                                  const char *input, curlntlm state)
263 {
264   size_t len_in = strlen(input), len_out = 0;
265   struct dynbuf b;
266   char *ptr = NULL;
267   unsigned char *buf = (unsigned char *)data->state.buffer;
268   Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE);
269 
270   while(len_in > 0) {
271     ssize_t written = swrite(ntlm->ntlm_auth_hlpr_socket, input, len_in);
272     if(written == -1) {
273       /* Interrupted by a signal, retry it */
274       if(errno == EINTR)
275         continue;
276       /* write failed if other errors happen */
277       goto done;
278     }
279     input += written;
280     len_in -= written;
281   }
282   /* Read one line */
283   while(1) {
284     ssize_t size =
285       sread(ntlm->ntlm_auth_hlpr_socket, buf, data->set.buffer_size);
286     if(size == -1) {
287       if(errno == EINTR)
288         continue;
289       goto done;
290     }
291     else if(size == 0)
292       goto done;
293 
294     if(Curl_dyn_addn(&b, buf, size))
295       goto done;
296 
297     len_out = Curl_dyn_len(&b);
298     ptr = Curl_dyn_ptr(&b);
299     if(len_out && ptr[len_out - 1] == '\n') {
300       ptr[len_out - 1] = '\0';
301       break; /* done! */
302     }
303     /* loop */
304   }
305 
306   /* Samba/winbind installed but not configured */
307   if(state == NTLMSTATE_TYPE1 &&
308      len_out == 3 &&
309      ptr[0] == 'P' && ptr[1] == 'W')
310     goto done;
311   /* invalid response */
312   if(len_out < 4)
313     goto done;
314   if(state == NTLMSTATE_TYPE1 &&
315      (ptr[0]!='Y' || ptr[1]!='R' || ptr[2]!=' '))
316     goto done;
317   if(state == NTLMSTATE_TYPE2 &&
318      (ptr[0]!='K' || ptr[1]!='K' || ptr[2]!=' ') &&
319      (ptr[0]!='A' || ptr[1]!='F' || ptr[2]!=' '))
320     goto done;
321 
322   ntlm->response = strdup(ptr + 3);
323   Curl_dyn_free(&b);
324   if(!ntlm->response)
325     return CURLE_OUT_OF_MEMORY;
326   return CURLE_OK;
327 done:
328   Curl_dyn_free(&b);
329   return CURLE_REMOTE_ACCESS_DENIED;
330 }
331 
Curl_input_ntlm_wb(struct connectdata * conn,bool proxy,const char * header)332 CURLcode Curl_input_ntlm_wb(struct connectdata *conn,
333                             bool proxy,
334                             const char *header)
335 {
336   struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm;
337   curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state;
338 
339   if(!checkprefix("NTLM", header))
340     return CURLE_BAD_CONTENT_ENCODING;
341 
342   header += strlen("NTLM");
343   while(*header && ISSPACE(*header))
344     header++;
345 
346   if(*header) {
347     ntlm->challenge = strdup(header);
348     if(!ntlm->challenge)
349       return CURLE_OUT_OF_MEMORY;
350 
351     *state = NTLMSTATE_TYPE2; /* We got a type-2 message */
352   }
353   else {
354     if(*state == NTLMSTATE_LAST) {
355       infof(conn->data, "NTLM auth restarted\n");
356       Curl_http_auth_cleanup_ntlm_wb(conn);
357     }
358     else if(*state == NTLMSTATE_TYPE3) {
359       infof(conn->data, "NTLM handshake rejected\n");
360       Curl_http_auth_cleanup_ntlm_wb(conn);
361       *state = NTLMSTATE_NONE;
362       return CURLE_REMOTE_ACCESS_DENIED;
363     }
364     else if(*state >= NTLMSTATE_TYPE1) {
365       infof(conn->data, "NTLM handshake failure (internal error)\n");
366       return CURLE_REMOTE_ACCESS_DENIED;
367     }
368 
369     *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */
370   }
371 
372   return CURLE_OK;
373 }
374 
375 /*
376  * This is for creating ntlm header output by delegating challenge/response
377  * to Samba's winbind daemon helper ntlm_auth.
378  */
Curl_output_ntlm_wb(struct connectdata * conn,bool proxy)379 CURLcode Curl_output_ntlm_wb(struct connectdata *conn, bool proxy)
380 {
381   /* point to the address of the pointer that holds the string to send to the
382      server, which is for a plain host or for a HTTP proxy */
383   char **allocuserpwd;
384   /* point to the name and password for this */
385   const char *userp;
386   struct ntlmdata *ntlm;
387   curlntlm *state;
388   struct auth *authp;
389   struct Curl_easy *data = conn->data;
390 
391   CURLcode res = CURLE_OK;
392 
393   DEBUGASSERT(conn);
394   DEBUGASSERT(conn->data);
395 
396   if(proxy) {
397 #ifndef CURL_DISABLE_PROXY
398     allocuserpwd = &data->state.aptr.proxyuserpwd;
399     userp = conn->http_proxy.user;
400     ntlm = &conn->proxyntlm;
401     state = &conn->proxy_ntlm_state;
402     authp = &conn->data->state.authproxy;
403 #else
404     return CURLE_NOT_BUILT_IN;
405 #endif
406   }
407   else {
408     allocuserpwd = &data->state.aptr.userpwd;
409     userp = conn->user;
410     ntlm = &conn->ntlm;
411     state = &conn->http_ntlm_state;
412     authp = &conn->data->state.authhost;
413   }
414   authp->done = FALSE;
415 
416   /* not set means empty */
417   if(!userp)
418     userp = "";
419 
420   switch(*state) {
421   case NTLMSTATE_TYPE1:
422   default:
423     /* Use Samba's 'winbind' daemon to support NTLM authentication,
424      * by delegating the NTLM challenge/response protocol to a helper
425      * in ntlm_auth.
426      * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
427      * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
428      * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
429      * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
430      * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
431      * filename of ntlm_auth helper.
432      * If NTLM authentication using winbind fails, go back to original
433      * request handling process.
434      */
435     /* Create communication with ntlm_auth */
436     res = ntlm_wb_init(conn->data, ntlm, userp);
437     if(res)
438       return res;
439     res = ntlm_wb_response(conn->data, ntlm, "YR\n", *state);
440     if(res)
441       return res;
442 
443     free(*allocuserpwd);
444     *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
445                             proxy ? "Proxy-" : "",
446                             ntlm->response);
447     DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
448     Curl_safefree(ntlm->response);
449     if(!*allocuserpwd)
450       return CURLE_OUT_OF_MEMORY;
451     break;
452 
453   case NTLMSTATE_TYPE2: {
454     char *input = aprintf("TT %s\n", ntlm->challenge);
455     if(!input)
456       return CURLE_OUT_OF_MEMORY;
457     res = ntlm_wb_response(conn->data, ntlm, input, *state);
458     free(input);
459     if(res)
460       return res;
461 
462     free(*allocuserpwd);
463     *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
464                             proxy ? "Proxy-" : "",
465                             ntlm->response);
466     DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
467     *state = NTLMSTATE_TYPE3; /* we sent a type-3 */
468     authp->done = TRUE;
469     Curl_http_auth_cleanup_ntlm_wb(conn);
470     if(!*allocuserpwd)
471       return CURLE_OUT_OF_MEMORY;
472     break;
473   }
474   case NTLMSTATE_TYPE3:
475     /* connection is already authenticated,
476      * don't send a header in future requests */
477     *state = NTLMSTATE_LAST;
478     /* FALLTHROUGH */
479   case NTLMSTATE_LAST:
480     Curl_safefree(*allocuserpwd);
481     authp->done = TRUE;
482     break;
483   }
484 
485   return CURLE_OK;
486 }
487 
Curl_http_auth_cleanup_ntlm_wb(struct connectdata * conn)488 void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn)
489 {
490   ntlm_wb_cleanup(&conn->ntlm);
491   ntlm_wb_cleanup(&conn->proxyntlm);
492 }
493 
494 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */
495