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