Imported Upstream version 7.59.0
[platform/upstream/curl.git] / lib / curl_ntlm_wb.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2017, 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
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
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
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     failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
182           ntlm_auth, errno, Curl_strerror(conn, errno));
183     goto done;
184   }
185
186   if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
187     failf(conn->data, "Could not open socket pair. errno %d: %s",
188           errno, Curl_strerror(conn, errno));
189     goto done;
190   }
191
192   child_pid = fork();
193   if(child_pid == -1) {
194     sclose(sockfds[0]);
195     sclose(sockfds[1]);
196     failf(conn->data, "Could not fork. errno %d: %s",
197           errno, Curl_strerror(conn, errno));
198     goto done;
199   }
200   else if(!child_pid) {
201     /*
202      * child process
203      */
204
205     /* Don't use sclose in the child since it fools the socket leak detector */
206     sclose_nolog(sockfds[0]);
207     if(dup2(sockfds[1], STDIN_FILENO) == -1) {
208       failf(conn->data, "Could not redirect child stdin. errno %d: %s",
209             errno, Curl_strerror(conn, errno));
210       exit(1);
211     }
212
213     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
214       failf(conn->data, "Could not redirect child stdout. errno %d: %s",
215             errno, Curl_strerror(conn, errno));
216       exit(1);
217     }
218
219     if(domain)
220       execl(ntlm_auth, ntlm_auth,
221             "--helper-protocol", "ntlmssp-client-1",
222             "--use-cached-creds",
223             "--username", username,
224             "--domain", domain,
225             NULL);
226     else
227       execl(ntlm_auth, ntlm_auth,
228             "--helper-protocol", "ntlmssp-client-1",
229             "--use-cached-creds",
230             "--username", username,
231             NULL);
232
233     sclose_nolog(sockfds[1]);
234     failf(conn->data, "Could not execl(). errno %d: %s",
235           errno, Curl_strerror(conn, errno));
236     exit(1);
237   }
238
239   sclose(sockfds[1]);
240   conn->ntlm_auth_hlpr_socket = sockfds[0];
241   conn->ntlm_auth_hlpr_pid = child_pid;
242   free(domain);
243   free(ntlm_auth_alloc);
244   return CURLE_OK;
245
246 done:
247   free(domain);
248   free(ntlm_auth_alloc);
249   return CURLE_REMOTE_ACCESS_DENIED;
250 }
251
252 static CURLcode ntlm_wb_response(struct connectdata *conn,
253                                  const char *input, curlntlm state)
254 {
255   char *buf = malloc(NTLM_BUFSIZE);
256   size_t len_in = strlen(input), len_out = 0;
257
258   if(!buf)
259     return CURLE_OUT_OF_MEMORY;
260
261   while(len_in > 0) {
262     ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
263     if(written == -1) {
264       /* Interrupted by a signal, retry it */
265       if(errno == EINTR)
266         continue;
267       /* write failed if other errors happen */
268       goto done;
269     }
270     input += written;
271     len_in -= written;
272   }
273   /* Read one line */
274   while(1) {
275     ssize_t size;
276     char *newbuf;
277
278     size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
279     if(size == -1) {
280       if(errno == EINTR)
281         continue;
282       goto done;
283     }
284     else if(size == 0)
285       goto done;
286
287     len_out += size;
288     if(buf[len_out - 1] == '\n') {
289       buf[len_out - 1] = '\0';
290       break;
291     }
292     newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
293     if(!newbuf)
294       return CURLE_OUT_OF_MEMORY;
295
296     buf = newbuf;
297   }
298
299   /* Samba/winbind installed but not configured */
300   if(state == NTLMSTATE_TYPE1 &&
301      len_out == 3 &&
302      buf[0] == 'P' && buf[1] == 'W')
303     goto done;
304   /* invalid response */
305   if(len_out < 4)
306     goto done;
307   if(state == NTLMSTATE_TYPE1 &&
308      (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
309     goto done;
310   if(state == NTLMSTATE_TYPE2 &&
311      (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
312      (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
313     goto done;
314
315   conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3);
316   free(buf);
317   return CURLE_OK;
318 done:
319   free(buf);
320   return CURLE_REMOTE_ACCESS_DENIED;
321 }
322
323 /*
324  * This is for creating ntlm header output by delegating challenge/response
325  * to Samba's winbind daemon helper ntlm_auth.
326  */
327 CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
328                               bool proxy)
329 {
330   /* point to the address of the pointer that holds the string to send to the
331      server, which is for a plain host or for a HTTP proxy */
332   char **allocuserpwd;
333   /* point to the name and password for this */
334   const char *userp;
335   /* point to the correct struct with this */
336   struct ntlmdata *ntlm;
337   struct auth *authp;
338
339   CURLcode res = CURLE_OK;
340   char *input;
341
342   DEBUGASSERT(conn);
343   DEBUGASSERT(conn->data);
344
345   if(proxy) {
346     allocuserpwd = &conn->allocptr.proxyuserpwd;
347     userp = conn->http_proxy.user;
348     ntlm = &conn->proxyntlm;
349     authp = &conn->data->state.authproxy;
350   }
351   else {
352     allocuserpwd = &conn->allocptr.userpwd;
353     userp = conn->user;
354     ntlm = &conn->ntlm;
355     authp = &conn->data->state.authhost;
356   }
357   authp->done = FALSE;
358
359   /* not set means empty */
360   if(!userp)
361     userp = "";
362
363   switch(ntlm->state) {
364   case NTLMSTATE_TYPE1:
365   default:
366     /* Use Samba's 'winbind' daemon to support NTLM authentication,
367      * by delegating the NTLM challenge/response protocol to a helper
368      * in ntlm_auth.
369      * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
370      * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
371      * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
372      * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
373      * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
374      * filename of ntlm_auth helper.
375      * If NTLM authentication using winbind fails, go back to original
376      * request handling process.
377      */
378     /* Create communication with ntlm_auth */
379     res = ntlm_wb_init(conn, userp);
380     if(res)
381       return res;
382     res = ntlm_wb_response(conn, "YR\n", ntlm->state);
383     if(res)
384       return res;
385
386     free(*allocuserpwd);
387     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
388                             proxy ? "Proxy-" : "",
389                             conn->response_header);
390     DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
391     free(conn->response_header);
392     conn->response_header = NULL;
393     break;
394   case NTLMSTATE_TYPE2:
395     input = aprintf("TT %s\n", conn->challenge_header);
396     if(!input)
397       return CURLE_OUT_OF_MEMORY;
398     res = ntlm_wb_response(conn, input, ntlm->state);
399     free(input);
400     input = NULL;
401     if(res)
402       return res;
403
404     free(*allocuserpwd);
405     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
406                             proxy ? "Proxy-" : "",
407                             conn->response_header);
408     DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
409     ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */
410     authp->done = TRUE;
411     Curl_ntlm_wb_cleanup(conn);
412     break;
413   case NTLMSTATE_TYPE3:
414     /* connection is already authenticated,
415      * don't send a header in future requests */
416     free(*allocuserpwd);
417     *allocuserpwd = NULL;
418     authp->done = TRUE;
419     break;
420   }
421
422   return CURLE_OK;
423 }
424
425 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */