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
77void 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
115static 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
246done:
247  free(domain);
248  free(ntlm_auth_alloc);
249  return CURLE_REMOTE_ACCESS_DENIED;
250}
251
252static 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;
318done:
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 */
327CURLcode 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 protocal 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 */
426