Annotation of embedaddon/curl/lib/curl_ntlm_wb.c, revision 1.1.1.1
1.1 misho 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:
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:
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:
261: static CURLcode ntlm_wb_response(struct Curl_easy *data, struct ntlmdata *ntlm,
262: const char *input, curlntlm state)
263: {
264: char *buf = malloc(NTLM_BUFSIZE);
265: size_t len_in = strlen(input), len_out = 0;
266:
267: #if defined(CURL_DISABLE_VERBOSE_STRINGS)
268: (void) data;
269: #endif
270:
271: if(!buf)
272: return CURLE_OUT_OF_MEMORY;
273:
274: while(len_in > 0) {
275: ssize_t written = swrite(ntlm->ntlm_auth_hlpr_socket, input, len_in);
276: if(written == -1) {
277: /* Interrupted by a signal, retry it */
278: if(errno == EINTR)
279: continue;
280: /* write failed if other errors happen */
281: goto done;
282: }
283: input += written;
284: len_in -= written;
285: }
286: /* Read one line */
287: while(1) {
288: ssize_t size;
289: char *newbuf;
290:
291: size = sread(ntlm->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
292: if(size == -1) {
293: if(errno == EINTR)
294: continue;
295: goto done;
296: }
297: else if(size == 0)
298: goto done;
299:
300: len_out += size;
301: if(buf[len_out - 1] == '\n') {
302: buf[len_out - 1] = '\0';
303: break;
304: }
305:
306: if(len_out > MAX_NTLM_WB_RESPONSE) {
307: failf(data, "too large ntlm_wb response!");
308: free(buf);
309: return CURLE_OUT_OF_MEMORY;
310: }
311:
312: newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
313: if(!newbuf)
314: return CURLE_OUT_OF_MEMORY;
315:
316: buf = newbuf;
317: }
318:
319: /* Samba/winbind installed but not configured */
320: if(state == NTLMSTATE_TYPE1 &&
321: len_out == 3 &&
322: buf[0] == 'P' && buf[1] == 'W')
323: goto done;
324: /* invalid response */
325: if(len_out < 4)
326: goto done;
327: if(state == NTLMSTATE_TYPE1 &&
328: (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
329: goto done;
330: if(state == NTLMSTATE_TYPE2 &&
331: (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
332: (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
333: goto done;
334:
335: ntlm->response = aprintf("%.*s", len_out - 4, buf + 3);
336: free(buf);
337: if(!ntlm->response)
338: return CURLE_OUT_OF_MEMORY;
339: return CURLE_OK;
340: done:
341: free(buf);
342: return CURLE_REMOTE_ACCESS_DENIED;
343: }
344:
345: CURLcode Curl_input_ntlm_wb(struct connectdata *conn,
346: bool proxy,
347: const char *header)
348: {
349: struct ntlmdata *ntlm = proxy ? &conn->proxyntlm : &conn->ntlm;
350: curlntlm *state = proxy ? &conn->proxy_ntlm_state : &conn->http_ntlm_state;
351:
352: if(!checkprefix("NTLM", header))
353: return CURLE_BAD_CONTENT_ENCODING;
354:
355: header += strlen("NTLM");
356: while(*header && ISSPACE(*header))
357: header++;
358:
359: if(*header) {
360: ntlm->challenge = strdup(header);
361: if(!ntlm->challenge)
362: return CURLE_OUT_OF_MEMORY;
363:
364: *state = NTLMSTATE_TYPE2; /* We got a type-2 message */
365: }
366: else {
367: if(*state == NTLMSTATE_LAST) {
368: infof(conn->data, "NTLM auth restarted\n");
369: Curl_http_auth_cleanup_ntlm_wb(conn);
370: }
371: else if(*state == NTLMSTATE_TYPE3) {
372: infof(conn->data, "NTLM handshake rejected\n");
373: Curl_http_auth_cleanup_ntlm_wb(conn);
374: *state = NTLMSTATE_NONE;
375: return CURLE_REMOTE_ACCESS_DENIED;
376: }
377: else if(*state >= NTLMSTATE_TYPE1) {
378: infof(conn->data, "NTLM handshake failure (internal error)\n");
379: return CURLE_REMOTE_ACCESS_DENIED;
380: }
381:
382: *state = NTLMSTATE_TYPE1; /* We should send away a type-1 */
383: }
384:
385: return CURLE_OK;
386: }
387:
388: /*
389: * This is for creating ntlm header output by delegating challenge/response
390: * to Samba's winbind daemon helper ntlm_auth.
391: */
392: CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
393: bool proxy)
394: {
395: /* point to the address of the pointer that holds the string to send to the
396: server, which is for a plain host or for a HTTP proxy */
397: char **allocuserpwd;
398: /* point to the name and password for this */
399: const char *userp;
400: struct ntlmdata *ntlm;
401: curlntlm *state;
402: struct auth *authp;
403:
404: CURLcode res = CURLE_OK;
405:
406: DEBUGASSERT(conn);
407: DEBUGASSERT(conn->data);
408:
409: if(proxy) {
410: allocuserpwd = &conn->allocptr.proxyuserpwd;
411: userp = conn->http_proxy.user;
412: ntlm = &conn->proxyntlm;
413: state = &conn->proxy_ntlm_state;
414: authp = &conn->data->state.authproxy;
415: }
416: else {
417: allocuserpwd = &conn->allocptr.userpwd;
418: userp = conn->user;
419: ntlm = &conn->ntlm;
420: state = &conn->http_ntlm_state;
421: authp = &conn->data->state.authhost;
422: }
423: authp->done = FALSE;
424:
425: /* not set means empty */
426: if(!userp)
427: userp = "";
428:
429: switch(*state) {
430: case NTLMSTATE_TYPE1:
431: default:
432: /* Use Samba's 'winbind' daemon to support NTLM authentication,
433: * by delegating the NTLM challenge/response protocol to a helper
434: * in ntlm_auth.
435: * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
436: * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
437: * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
438: * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
439: * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
440: * filename of ntlm_auth helper.
441: * If NTLM authentication using winbind fails, go back to original
442: * request handling process.
443: */
444: /* Create communication with ntlm_auth */
445: res = ntlm_wb_init(conn->data, ntlm, userp);
446: if(res)
447: return res;
448: res = ntlm_wb_response(conn->data, ntlm, "YR\n", *state);
449: if(res)
450: return res;
451:
452: free(*allocuserpwd);
453: *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
454: proxy ? "Proxy-" : "",
455: ntlm->response);
456: DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
457: Curl_safefree(ntlm->response);
458: if(!*allocuserpwd)
459: return CURLE_OUT_OF_MEMORY;
460: break;
461:
462: case NTLMSTATE_TYPE2: {
463: char *input = aprintf("TT %s\n", ntlm->challenge);
464: if(!input)
465: return CURLE_OUT_OF_MEMORY;
466: res = ntlm_wb_response(conn->data, ntlm, input, *state);
467: free(input);
468: if(res)
469: return res;
470:
471: free(*allocuserpwd);
472: *allocuserpwd = aprintf("%sAuthorization: NTLM %s\r\n",
473: proxy ? "Proxy-" : "",
474: ntlm->response);
475: DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
476: *state = NTLMSTATE_TYPE3; /* we sent a type-3 */
477: authp->done = TRUE;
478: Curl_http_auth_cleanup_ntlm_wb(conn);
479: if(!*allocuserpwd)
480: return CURLE_OUT_OF_MEMORY;
481: break;
482: }
483: case NTLMSTATE_TYPE3:
484: /* connection is already authenticated,
485: * don't send a header in future requests */
486: *state = NTLMSTATE_LAST;
487: /* FALLTHROUGH */
488: case NTLMSTATE_LAST:
489: Curl_safefree(*allocuserpwd);
490: authp->done = TRUE;
491: break;
492: }
493:
494: return CURLE_OK;
495: }
496:
497: void Curl_http_auth_cleanup_ntlm_wb(struct connectdata *conn)
498: {
499: ntlm_wb_cleanup(&conn->ntlm);
500: ntlm_wb_cleanup(&conn->proxyntlm);
501: }
502:
503: #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>