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