Imported Upstream version 7.44.0
[platform/upstream/curl.git] / lib / curl_ntlm_wb.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2015, 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 http://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  * http://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 "curl_ntlm_msgs.h"
51 #include "curl_ntlm_wb.h"
52 #include "url.h"
53 #include "strerror.h"
54 #include "curl_printf.h"
55
56 /* The last #include files should be: */
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
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
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
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 = realloc(buf, len_out + NTLM_BUFSIZE);
298     if(!newbuf) {
299       free(buf);
300       return CURLE_OUT_OF_MEMORY;
301     }
302     buf = newbuf;
303   }
304
305   /* Samba/winbind installed but not configured */
306   if(state == NTLMSTATE_TYPE1 &&
307      len_out == 3 &&
308      buf[0] == 'P' && buf[1] == 'W')
309     goto done;
310   /* invalid response */
311   if(len_out < 4)
312     goto done;
313   if(state == NTLMSTATE_TYPE1 &&
314      (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
315     goto done;
316   if(state == NTLMSTATE_TYPE2 &&
317      (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
318      (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
319     goto done;
320
321   conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3);
322   free(buf);
323   return CURLE_OK;
324 done:
325   free(buf);
326   return CURLE_REMOTE_ACCESS_DENIED;
327 }
328
329 /*
330  * This is for creating ntlm header output by delegating challenge/response
331  * to Samba's winbind daemon helper ntlm_auth.
332  */
333 CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
334                               bool proxy)
335 {
336   /* point to the address of the pointer that holds the string to send to the
337      server, which is for a plain host or for a HTTP proxy */
338   char **allocuserpwd;
339   /* point to the name and password for this */
340   const char *userp;
341   /* point to the correct struct with this */
342   struct ntlmdata *ntlm;
343   struct auth *authp;
344
345   CURLcode res = CURLE_OK;
346   char *input;
347
348   DEBUGASSERT(conn);
349   DEBUGASSERT(conn->data);
350
351   if(proxy) {
352     allocuserpwd = &conn->allocptr.proxyuserpwd;
353     userp = conn->proxyuser;
354     ntlm = &conn->proxyntlm;
355     authp = &conn->data->state.authproxy;
356   }
357   else {
358     allocuserpwd = &conn->allocptr.userpwd;
359     userp = conn->user;
360     ntlm = &conn->ntlm;
361     authp = &conn->data->state.authhost;
362   }
363   authp->done = FALSE;
364
365   /* not set means empty */
366   if(!userp)
367     userp="";
368
369   switch(ntlm->state) {
370   case NTLMSTATE_TYPE1:
371   default:
372     /* Use Samba's 'winbind' daemon to support NTLM authentication,
373      * by delegating the NTLM challenge/response protocal to a helper
374      * in ntlm_auth.
375      * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
376      * http://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
377      * http://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
378      * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
379      * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
380      * filename of ntlm_auth helper.
381      * If NTLM authentication using winbind fails, go back to original
382      * request handling process.
383      */
384     /* Create communication with ntlm_auth */
385     res = ntlm_wb_init(conn, userp);
386     if(res)
387       return res;
388     res = ntlm_wb_response(conn, "YR\n", ntlm->state);
389     if(res)
390       return res;
391
392     free(*allocuserpwd);
393     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
394                             proxy ? "Proxy-" : "",
395                             conn->response_header);
396     DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
397     free(conn->response_header);
398     conn->response_header = NULL;
399     break;
400   case NTLMSTATE_TYPE2:
401     input = aprintf("TT %s\n", conn->challenge_header);
402     if(!input)
403       return CURLE_OUT_OF_MEMORY;
404     res = ntlm_wb_response(conn, input, ntlm->state);
405     free(input);
406     input = NULL;
407     if(res)
408       return res;
409
410     free(*allocuserpwd);
411     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
412                             proxy ? "Proxy-" : "",
413                             conn->response_header);
414     DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
415     ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */
416     authp->done = TRUE;
417     Curl_ntlm_wb_cleanup(conn);
418     break;
419   case NTLMSTATE_TYPE3:
420     /* connection is already authenticated,
421      * don't send a header in future requests */
422     free(*allocuserpwd);
423     *allocuserpwd=NULL;
424     authp->done = TRUE;
425     break;
426   }
427
428   return CURLE_OK;
429 }
430
431 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */