Imported Upstream version 7.50.2
[platform/upstream/curl.git] / lib / curl_ntlm_wb.c
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 /* The last 3 #include files should be in this order */
55 #include "curl_printf.h"
56 #include "curl_memory.h"
57 #include "memdebug.h"
58
59 #if DEBUG_ME
60 # define DEBUG_OUT(x) x
61 #else
62 # define DEBUG_OUT(x) Curl_nop_stmt
63 #endif
64
65 /* Portable 'sclose_nolog' used only in child process instead of 'sclose'
66    to avoid fooling the socket leak detector */
67 #if defined(HAVE_CLOSESOCKET)
68 #  define sclose_nolog(x)  closesocket((x))
69 #elif defined(HAVE_CLOSESOCKET_CAMEL)
70 #  define sclose_nolog(x)  CloseSocket((x))
71 #else
72 #  define sclose_nolog(x)  close((x))
73 #endif
74
75 void Curl_ntlm_wb_cleanup(struct connectdata *conn)
76 {
77   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
78     sclose(conn->ntlm_auth_hlpr_socket);
79     conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
80   }
81
82   if(conn->ntlm_auth_hlpr_pid) {
83     int i;
84     for(i = 0; i < 4; i++) {
85       pid_t ret = waitpid(conn->ntlm_auth_hlpr_pid, NULL, WNOHANG);
86       if(ret == conn->ntlm_auth_hlpr_pid || errno == ECHILD)
87         break;
88       switch(i) {
89       case 0:
90         kill(conn->ntlm_auth_hlpr_pid, SIGTERM);
91         break;
92       case 1:
93         /* Give the process another moment to shut down cleanly before
94            bringing down the axe */
95         Curl_wait_ms(1);
96         break;
97       case 2:
98         kill(conn->ntlm_auth_hlpr_pid, SIGKILL);
99         break;
100       case 3:
101         break;
102       }
103     }
104     conn->ntlm_auth_hlpr_pid = 0;
105   }
106
107   free(conn->challenge_header);
108   conn->challenge_header = NULL;
109   free(conn->response_header);
110   conn->response_header = NULL;
111 }
112
113 static CURLcode ntlm_wb_init(struct connectdata *conn, const char *userp)
114 {
115   curl_socket_t sockfds[2];
116   pid_t child_pid;
117   const char *username;
118   char *slash, *domain = NULL;
119   const char *ntlm_auth = NULL;
120   char *ntlm_auth_alloc = NULL;
121 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
122   struct passwd pw, *pw_res;
123   char pwbuf[1024];
124 #endif
125   int error;
126
127   /* Return if communication with ntlm_auth already set up */
128   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
129      conn->ntlm_auth_hlpr_pid)
130     return CURLE_OK;
131
132   username = userp;
133   /* The real ntlm_auth really doesn't like being invoked with an
134      empty username. It won't make inferences for itself, and expects
135      the client to do so (mostly because it's really designed for
136      servers like squid to use for auth, and client support is an
137      afterthought for it). So try hard to provide a suitable username
138      if we don't already have one. But if we can't, provide the
139      empty one anyway. Perhaps they have an implementation of the
140      ntlm_auth helper which *doesn't* need it so we might as well try */
141   if(!username || !username[0]) {
142     username = getenv("NTLMUSER");
143     if(!username || !username[0])
144       username = getenv("LOGNAME");
145     if(!username || !username[0])
146       username = getenv("USER");
147 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
148     if((!username || !username[0]) &&
149        !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
150        pw_res) {
151       username = pw.pw_name;
152     }
153 #endif
154     if(!username || !username[0])
155       username = userp;
156   }
157   slash = strpbrk(username, "\\/");
158   if(slash) {
159     if((domain = strdup(username)) == NULL)
160       return CURLE_OUT_OF_MEMORY;
161     slash = domain + (slash - username);
162     *slash = '\0';
163     username = username + (slash - domain) + 1;
164   }
165
166   /* For testing purposes, when DEBUGBUILD is defined and environment
167      variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
168      NTLM challenge/response which only accepts commands and output
169      strings pre-written in test case definitions */
170 #ifdef DEBUGBUILD
171   ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
172   if(ntlm_auth_alloc)
173     ntlm_auth = ntlm_auth_alloc;
174   else
175 #endif
176     ntlm_auth = NTLM_WB_FILE;
177
178   if(access(ntlm_auth, X_OK) != 0) {
179     error = ERRNO;
180     failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
181           ntlm_auth, error, Curl_strerror(conn, error));
182     goto done;
183   }
184
185   if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
186     error = ERRNO;
187     failf(conn->data, "Could not open socket pair. errno %d: %s",
188           error, Curl_strerror(conn, error));
189     goto done;
190   }
191
192   child_pid = fork();
193   if(child_pid == -1) {
194     error = ERRNO;
195     sclose(sockfds[0]);
196     sclose(sockfds[1]);
197     failf(conn->data, "Could not fork. errno %d: %s",
198           error, Curl_strerror(conn, error));
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       error = ERRNO;
210       failf(conn->data, "Could not redirect child stdin. errno %d: %s",
211             error, Curl_strerror(conn, error));
212       exit(1);
213     }
214
215     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
216       error = ERRNO;
217       failf(conn->data, "Could not redirect child stdout. errno %d: %s",
218             error, Curl_strerror(conn, error));
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     error = ERRNO;
237     sclose_nolog(sockfds[1]);
238     failf(conn->data, "Could not execl(). errno %d: %s",
239           error, Curl_strerror(conn, error));
240     exit(1);
241   }
242
243   sclose(sockfds[1]);
244   conn->ntlm_auth_hlpr_socket = sockfds[0];
245   conn->ntlm_auth_hlpr_pid = child_pid;
246   free(domain);
247   free(ntlm_auth_alloc);
248   return CURLE_OK;
249
250 done:
251   free(domain);
252   free(ntlm_auth_alloc);
253   return CURLE_REMOTE_ACCESS_DENIED;
254 }
255
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     newbuf = realloc(buf, len_out + NTLM_BUFSIZE);
297     if(!newbuf) {
298       free(buf);
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  */
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->proxyuser;
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 */