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