curl_ntlm_wb.c
Go to the documentation of this file.
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 
77 void 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 
115 static 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 
246 done:
247  free(domain);
248  free(ntlm_auth_alloc);
250 }
251 
252 static 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;
318 done:
319  free(buf);
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  */
327 CURLcode 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 
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 */
#define free(ptr)
Definition: curl_memory.h:130
#define state(x, y)
Definition: ftp.c:100
#define getenv
Definition: setup-vms.h:52
#define sclose(x)
#define CURL_SOCKET_BAD
Definition: curl.h:131
#define failf
Definition: sendf.h:48
#define strdup(ptr)
Definition: curl_memory.h:122
#define DEBUGASSERT(x)
CURLcode
Definition: curl.h:454
static int res
CURL_EXTERN char * curl_getenv(const char *variable)
Definition: getenv.c:51
#define malloc(size)
Definition: curl_memory.h:124
unsigned int i
Definition: unit1303.c:79
struct proxy_info http_proxy
Definition: urldata.h:839
Definition: urldata.h:1179
int Curl_wait_ms(int timeout_ms)
Definition: select.c:75
#define FALSE
const char * Curl_strerror(struct connectdata *conn, int err)
Definition: strerror.c:646
void * Curl_saferealloc(void *ptr, size_t size)
Definition: strdup.c:93
Definition: curl.h:455
struct UrlState state
Definition: urldata.h:1769
#define aprintf
Definition: curl_printf.h:46
#define STDOUT_FILENO
Definition: tool_main.h:36
#define ssize_t
Definition: config-win32.h:382
struct auth authhost
Definition: urldata.h:1289
bool done
Definition: urldata.h:1185
char buf[3]
Definition: unit1398.c:32
curlntlm
Definition: urldata.h:292
struct auth authproxy
Definition: urldata.h:1290
size_t size
Definition: unit1302.c:52
#define fprintf
Definition: curl_printf.h:41
#define STDIN_FILENO
Definition: tool_main.h:32
#define TRUE
struct connectdata::dynamically_allocated_data allocptr
int curl_socket_t
Definition: curl.h:130
char * user
Definition: urldata.h:761
char * user
Definition: urldata.h:865
struct Curl_easy * data
Definition: urldata.h:791


rc_tagdetect_client
Author(s): Monika Florek-Jasinska , Raphael Schaller
autogenerated on Sat Feb 13 2021 03:42:08