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 /* The last 3 #include files should be in this order */
57 #include "curl_printf.h"
58 #include "curl_memory.h"
59 #include "memdebug.h"
60 
61 #if DEBUG_ME
62 # define DEBUG_OUT(x) x
63 #else
64 # define DEBUG_OUT(x) Curl_nop_stmt
65 #endif
66 
67 /* Portable 'sclose_nolog' used only in child process instead of 'sclose'
68    to avoid fooling the socket leak detector */
69 #if defined(HAVE_CLOSESOCKET)
70 #  define sclose_nolog(x)  closesocket((x))
71 #elif defined(HAVE_CLOSESOCKET_CAMEL)
72 #  define sclose_nolog(x)  CloseSocket((x))
73 #else
74 #  define sclose_nolog(x)  close((x))
75 #endif
76 
Curl_ntlm_wb_cleanup(struct connectdata * conn)77 void Curl_ntlm_wb_cleanup(struct connectdata *conn)
78 {
79   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
80     sclose(conn->ntlm_auth_hlpr_socket);
81     conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
82   }
83 
84   if(conn->ntlm_auth_hlpr_pid) {
85     int i;
86     for(i = 0; i < 4; i++) {
87       pid_t ret = waitpid(conn->ntlm_auth_hlpr_pid, NULL, WNOHANG);
88       if(ret == conn->ntlm_auth_hlpr_pid || errno == ECHILD)
89         break;
90       switch(i) {
91       case 0:
92         kill(conn->ntlm_auth_hlpr_pid, SIGTERM);
93         break;
94       case 1:
95         /* Give the process another moment to shut down cleanly before
96            bringing down the axe */
97         Curl_wait_ms(1);
98         break;
99       case 2:
100         kill(conn->ntlm_auth_hlpr_pid, SIGKILL);
101         break;
102       case 3:
103         break;
104       }
105     }
106     conn->ntlm_auth_hlpr_pid = 0;
107   }
108 
109   free(conn->challenge_header);
110   conn->challenge_header = NULL;
111   free(conn->response_header);
112   conn->response_header = NULL;
113 }
114 
ntlm_wb_init(struct connectdata * conn,const char * userp)115 static CURLcode ntlm_wb_init(struct connectdata *conn, const char *userp)
116 {
117   curl_socket_t sockfds[2];
118   pid_t child_pid;
119   const char *username;
120   char *slash, *domain = NULL;
121   const char *ntlm_auth = NULL;
122   char *ntlm_auth_alloc = NULL;
123 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
124   struct passwd pw, *pw_res;
125   char pwbuf[1024];
126 #endif
127   char buffer[STRERROR_LEN];
128 
129   /* Return if communication with ntlm_auth already set up */
130   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
131      conn->ntlm_auth_hlpr_pid)
132     return CURLE_OK;
133 
134   username = userp;
135   /* The real ntlm_auth really doesn't like being invoked with an
136      empty username. It won't make inferences for itself, and expects
137      the client to do so (mostly because it's really designed for
138      servers like squid to use for auth, and client support is an
139      afterthought for it). So try hard to provide a suitable username
140      if we don't already have one. But if we can't, provide the
141      empty one anyway. Perhaps they have an implementation of the
142      ntlm_auth helper which *doesn't* need it so we might as well try */
143   if(!username || !username[0]) {
144     username = getenv("NTLMUSER");
145     if(!username || !username[0])
146       username = getenv("LOGNAME");
147     if(!username || !username[0])
148       username = getenv("USER");
149 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
150     if((!username || !username[0]) &&
151        !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
152        pw_res) {
153       username = pw.pw_name;
154     }
155 #endif
156     if(!username || !username[0])
157       username = userp;
158   }
159   slash = strpbrk(username, "\\/");
160   if(slash) {
161     domain = strdup(username);
162     if(!domain)
163       return CURLE_OUT_OF_MEMORY;
164     slash = domain + (slash - username);
165     *slash = '\0';
166     username = username + (slash - domain) + 1;
167   }
168 
169   /* For testing purposes, when DEBUGBUILD is defined and environment
170      variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
171      NTLM challenge/response which only accepts commands and output
172      strings pre-written in test case definitions */
173 #ifdef DEBUGBUILD
174   ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
175   if(ntlm_auth_alloc)
176     ntlm_auth = ntlm_auth_alloc;
177   else
178 #endif
179     ntlm_auth = NTLM_WB_FILE;
180 
181   if(access(ntlm_auth, X_OK) != 0) {
182     failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
183           ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer)));
184     goto done;
185   }
186 
187   if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
188     failf(conn->data, "Could not open socket pair. errno %d: %s",
189           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
190     goto done;
191   }
192 
193   child_pid = fork();
194   if(child_pid == -1) {
195     sclose(sockfds[0]);
196     sclose(sockfds[1]);
197     failf(conn->data, "Could not fork. errno %d: %s",
198           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
199     goto done;
200   }
201   else if(!child_pid) {
202     /*
203      * child process
204      */
205 
206     /* Don't use sclose in the child since it fools the socket leak detector */
207     sclose_nolog(sockfds[0]);
208     if(dup2(sockfds[1], STDIN_FILENO) == -1) {
209       failf(conn->data, "Could not redirect child stdin. errno %d: %s",
210             errno, Curl_strerror(errno, buffer, sizeof(buffer)));
211       exit(1);
212     }
213 
214     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
215       failf(conn->data, "Could not redirect child stdout. errno %d: %s",
216             errno, Curl_strerror(errno, buffer, sizeof(buffer)));
217       exit(1);
218     }
219 
220     if(domain)
221       execl(ntlm_auth, ntlm_auth,
222             "--helper-protocol", "ntlmssp-client-1",
223             "--use-cached-creds",
224             "--username", username,
225             "--domain", domain,
226             NULL);
227     else
228       execl(ntlm_auth, ntlm_auth,
229             "--helper-protocol", "ntlmssp-client-1",
230             "--use-cached-creds",
231             "--username", username,
232             NULL);
233 
234     sclose_nolog(sockfds[1]);
235     failf(conn->data, "Could not execl(). errno %d: %s",
236           errno, Curl_strerror(errno, buffer, sizeof(buffer)));
237     exit(1);
238   }
239 
240   sclose(sockfds[1]);
241   conn->ntlm_auth_hlpr_socket = sockfds[0];
242   conn->ntlm_auth_hlpr_pid = child_pid;
243   free(domain);
244   free(ntlm_auth_alloc);
245   return CURLE_OK;
246 
247 done:
248   free(domain);
249   free(ntlm_auth_alloc);
250   return CURLE_REMOTE_ACCESS_DENIED;
251 }
252 
253 /* if larger than this, something is seriously wrong */
254 #define MAX_NTLM_WB_RESPONSE 100000
255 
ntlm_wb_response(struct connectdata * conn,const char * input,curlntlm state)256 static CURLcode ntlm_wb_response(struct connectdata *conn,
257                                  const char *input, curlntlm state)
258 {
259   char *buf = malloc(NTLM_BUFSIZE);
260   size_t len_in = strlen(input), len_out = 0;
261 
262   if(!buf)
263     return CURLE_OUT_OF_MEMORY;
264 
265   while(len_in > 0) {
266     ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
267     if(written == -1) {
268       /* Interrupted by a signal, retry it */
269       if(errno == EINTR)
270         continue;
271       /* write failed if other errors happen */
272       goto done;
273     }
274     input += written;
275     len_in -= written;
276   }
277   /* Read one line */
278   while(1) {
279     ssize_t size;
280     char *newbuf;
281 
282     size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
283     if(size == -1) {
284       if(errno == EINTR)
285         continue;
286       goto done;
287     }
288     else if(size == 0)
289       goto done;
290 
291     len_out += size;
292     if(buf[len_out - 1] == '\n') {
293       buf[len_out - 1] = '\0';
294       break;
295     }
296 
297     if(len_out > MAX_NTLM_WB_RESPONSE) {
298       failf(conn->data, "too large ntlm_wb response!");
299       free(buf);
300       return CURLE_OUT_OF_MEMORY;
301     }
302 
303     newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
304     if(!newbuf)
305       return CURLE_OUT_OF_MEMORY;
306 
307     buf = newbuf;
308   }
309 
310   /* Samba/winbind installed but not configured */
311   if(state == NTLMSTATE_TYPE1 &&
312      len_out == 3 &&
313      buf[0] == 'P' && buf[1] == 'W')
314     goto done;
315   /* invalid response */
316   if(len_out < 4)
317     goto done;
318   if(state == NTLMSTATE_TYPE1 &&
319      (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
320     goto done;
321   if(state == NTLMSTATE_TYPE2 &&
322      (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
323      (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
324     goto done;
325 
326   conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3);
327   free(buf);
328   if(!conn->response_header)
329     return CURLE_OUT_OF_MEMORY;
330   return CURLE_OK;
331 done:
332   free(buf);
333   return CURLE_REMOTE_ACCESS_DENIED;
334 }
335 
336 /*
337  * This is for creating ntlm header output by delegating challenge/response
338  * to Samba's winbind daemon helper ntlm_auth.
339  */
Curl_output_ntlm_wb(struct connectdata * conn,bool proxy)340 CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
341                               bool proxy)
342 {
343   /* point to the address of the pointer that holds the string to send to the
344      server, which is for a plain host or for a HTTP proxy */
345   char **allocuserpwd;
346   /* point to the name and password for this */
347   const char *userp;
348   /* point to the correct struct with this */
349   struct ntlmdata *ntlm;
350   struct auth *authp;
351 
352   CURLcode res = CURLE_OK;
353   char *input;
354 
355   DEBUGASSERT(conn);
356   DEBUGASSERT(conn->data);
357 
358   if(proxy) {
359     allocuserpwd = &conn->allocptr.proxyuserpwd;
360     userp = conn->http_proxy.user;
361     ntlm = &conn->proxyntlm;
362     authp = &conn->data->state.authproxy;
363   }
364   else {
365     allocuserpwd = &conn->allocptr.userpwd;
366     userp = conn->user;
367     ntlm = &conn->ntlm;
368     authp = &conn->data->state.authhost;
369   }
370   authp->done = FALSE;
371 
372   /* not set means empty */
373   if(!userp)
374     userp = "";
375 
376   switch(ntlm->state) {
377   case NTLMSTATE_TYPE1:
378   default:
379     /* Use Samba's 'winbind' daemon to support NTLM authentication,
380      * by delegating the NTLM challenge/response protocol to a helper
381      * in ntlm_auth.
382      * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
383      * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
384      * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
385      * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
386      * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
387      * filename of ntlm_auth helper.
388      * If NTLM authentication using winbind fails, go back to original
389      * request handling process.
390      */
391     /* Create communication with ntlm_auth */
392     res = ntlm_wb_init(conn, userp);
393     if(res)
394       return res;
395     res = ntlm_wb_response(conn, "YR\n", ntlm->state);
396     if(res)
397       return res;
398 
399     free(*allocuserpwd);
400     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
401                             proxy ? "Proxy-" : "",
402                             conn->response_header);
403     DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
404     free(conn->response_header);
405     if(!*allocuserpwd)
406       return CURLE_OUT_OF_MEMORY;
407     conn->response_header = NULL;
408     break;
409   case NTLMSTATE_TYPE2:
410     input = aprintf("TT %s\n", conn->challenge_header);
411     if(!input)
412       return CURLE_OUT_OF_MEMORY;
413     res = ntlm_wb_response(conn, input, ntlm->state);
414     free(input);
415     input = NULL;
416     if(res)
417       return res;
418 
419     free(*allocuserpwd);
420     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
421                             proxy ? "Proxy-" : "",
422                             conn->response_header);
423     DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
424     ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */
425     authp->done = TRUE;
426     Curl_ntlm_wb_cleanup(conn);
427     if(!*allocuserpwd)
428       return CURLE_OUT_OF_MEMORY;
429     break;
430   case NTLMSTATE_TYPE3:
431     /* connection is already authenticated,
432      * don't send a header in future requests */
433     free(*allocuserpwd);
434     *allocuserpwd = NULL;
435     authp->done = TRUE;
436     break;
437   }
438 
439   return CURLE_OK;
440 }
441 
442 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */
443