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