Imported Upstream version 7.53.1
[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  * 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_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
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     domain = strdup(username);
161     if(!domain)
162       return CURLE_OUT_OF_MEMORY;
163     slash = domain + (slash - username);
164     *slash = '\0';
165     username = username + (slash - domain) + 1;
166   }
167
168   /* For testing purposes, when DEBUGBUILD is defined and environment
169      variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
170      NTLM challenge/response which only accepts commands and output
171      strings pre-written in test case definitions */
172 #ifdef DEBUGBUILD
173   ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
174   if(ntlm_auth_alloc)
175     ntlm_auth = ntlm_auth_alloc;
176   else
177 #endif
178     ntlm_auth = NTLM_WB_FILE;
179
180   if(access(ntlm_auth, X_OK) != 0) {
181     error = ERRNO;
182     failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
183           ntlm_auth, error, Curl_strerror(conn, error));
184     goto done;
185   }
186
187   if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
188     error = ERRNO;
189     failf(conn->data, "Could not open socket pair. errno %d: %s",
190           error, Curl_strerror(conn, error));
191     goto done;
192   }
193
194   child_pid = fork();
195   if(child_pid == -1) {
196     error = ERRNO;
197     sclose(sockfds[0]);
198     sclose(sockfds[1]);
199     failf(conn->data, "Could not fork. errno %d: %s",
200           error, Curl_strerror(conn, error));
201     goto done;
202   }
203   else if(!child_pid) {
204     /*
205      * child process
206      */
207
208     /* Don't use sclose in the child since it fools the socket leak detector */
209     sclose_nolog(sockfds[0]);
210     if(dup2(sockfds[1], STDIN_FILENO) == -1) {
211       error = ERRNO;
212       failf(conn->data, "Could not redirect child stdin. errno %d: %s",
213             error, Curl_strerror(conn, error));
214       exit(1);
215     }
216
217     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
218       error = ERRNO;
219       failf(conn->data, "Could not redirect child stdout. errno %d: %s",
220             error, Curl_strerror(conn, error));
221       exit(1);
222     }
223
224     if(domain)
225       execl(ntlm_auth, ntlm_auth,
226             "--helper-protocol", "ntlmssp-client-1",
227             "--use-cached-creds",
228             "--username", username,
229             "--domain", domain,
230             NULL);
231     else
232       execl(ntlm_auth, ntlm_auth,
233             "--helper-protocol", "ntlmssp-client-1",
234             "--use-cached-creds",
235             "--username", username,
236             NULL);
237
238     error = ERRNO;
239     sclose_nolog(sockfds[1]);
240     failf(conn->data, "Could not execl(). errno %d: %s",
241           error, Curl_strerror(conn, error));
242     exit(1);
243   }
244
245   sclose(sockfds[1]);
246   conn->ntlm_auth_hlpr_socket = sockfds[0];
247   conn->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 static CURLcode ntlm_wb_response(struct connectdata *conn,
259                                  const char *input, curlntlm state)
260 {
261   char *buf = malloc(NTLM_BUFSIZE);
262   size_t len_in = strlen(input), len_out = 0;
263
264   if(!buf)
265     return CURLE_OUT_OF_MEMORY;
266
267   while(len_in > 0) {
268     ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
269     if(written == -1) {
270       /* Interrupted by a signal, retry it */
271       if(errno == EINTR)
272         continue;
273       /* write failed if other errors happen */
274       goto done;
275     }
276     input += written;
277     len_in -= written;
278   }
279   /* Read one line */
280   while(1) {
281     ssize_t size;
282     char *newbuf;
283
284     size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
285     if(size == -1) {
286       if(errno == EINTR)
287         continue;
288       goto done;
289     }
290     else if(size == 0)
291       goto done;
292
293     len_out += size;
294     if(buf[len_out - 1] == '\n') {
295       buf[len_out - 1] = '\0';
296       break;
297     }
298     newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
299     if(!newbuf)
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->http_proxy.user;
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      * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
377      * https://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 */