xref: /third_party/curl/lib/curl_ntlm_wb.c (revision 13498266)
1/***************************************************************************
2 *                                  _   _ ____  _
3 *  Project                     ___| | | |  _ \| |
4 *                             / __| | | | |_) | |
5 *                            | (__| |_| |  _ <| |___
6 *                             \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 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#include <signal.h>
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#ifdef HAVE_PIPE
72#  define sclose_nolog(x)  close((x))
73#elif 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
81static 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
117static 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(wakeup_create(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    wakeup_close(sockfds[0]);
203    wakeup_close(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
254done:
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
263static 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[1024];
270  Curl_dyn_init(&b, MAX_NTLM_WB_RESPONSE);
271
272  while(len_in > 0) {
273    ssize_t written = wakeup_write(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      wakeup_read(ntlm->ntlm_auth_hlpr_socket, buf, sizeof(buf));
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;
329done:
330  Curl_dyn_free(&b);
331  return CURLE_REMOTE_ACCESS_DENIED;
332}
333
334CURLcode 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 */
384CURLcode 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 an 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
494void 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 */
501