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