1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 1998 - 2019, 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
Curl_ntlm_wb_cleanup(struct connectdata * conn)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
ntlm_wb_init(struct connectdata * conn,const char * userp)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 char buffer[STRERROR_LEN];
128
129 /* Return if communication with ntlm_auth already set up */
130 if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
131 conn->ntlm_auth_hlpr_pid)
132 return CURLE_OK;
133
134 username = userp;
135 /* The real ntlm_auth really doesn't like being invoked with an
136 empty username. It won't make inferences for itself, and expects
137 the client to do so (mostly because it's really designed for
138 servers like squid to use for auth, and client support is an
139 afterthought for it). So try hard to provide a suitable username
140 if we don't already have one. But if we can't, provide the
141 empty one anyway. Perhaps they have an implementation of the
142 ntlm_auth helper which *doesn't* need it so we might as well try */
143 if(!username || !username[0]) {
144 username = getenv("NTLMUSER");
145 if(!username || !username[0])
146 username = getenv("LOGNAME");
147 if(!username || !username[0])
148 username = getenv("USER");
149 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
150 if((!username || !username[0]) &&
151 !getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
152 pw_res) {
153 username = pw.pw_name;
154 }
155 #endif
156 if(!username || !username[0])
157 username = userp;
158 }
159 slash = strpbrk(username, "\\/");
160 if(slash) {
161 domain = strdup(username);
162 if(!domain)
163 return CURLE_OUT_OF_MEMORY;
164 slash = domain + (slash - username);
165 *slash = '\0';
166 username = username + (slash - domain) + 1;
167 }
168
169 /* For testing purposes, when DEBUGBUILD is defined and environment
170 variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
171 NTLM challenge/response which only accepts commands and output
172 strings pre-written in test case definitions */
173 #ifdef DEBUGBUILD
174 ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
175 if(ntlm_auth_alloc)
176 ntlm_auth = ntlm_auth_alloc;
177 else
178 #endif
179 ntlm_auth = NTLM_WB_FILE;
180
181 if(access(ntlm_auth, X_OK) != 0) {
182 failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
183 ntlm_auth, errno, Curl_strerror(errno, buffer, sizeof(buffer)));
184 goto done;
185 }
186
187 if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
188 failf(conn->data, "Could not open socket pair. errno %d: %s",
189 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
190 goto done;
191 }
192
193 child_pid = fork();
194 if(child_pid == -1) {
195 sclose(sockfds[0]);
196 sclose(sockfds[1]);
197 failf(conn->data, "Could not fork. errno %d: %s",
198 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
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 failf(conn->data, "Could not redirect child stdin. errno %d: %s",
210 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
211 exit(1);
212 }
213
214 if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
215 failf(conn->data, "Could not redirect child stdout. errno %d: %s",
216 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
217 exit(1);
218 }
219
220 if(domain)
221 execl(ntlm_auth, ntlm_auth,
222 "--helper-protocol", "ntlmssp-client-1",
223 "--use-cached-creds",
224 "--username", username,
225 "--domain", domain,
226 NULL);
227 else
228 execl(ntlm_auth, ntlm_auth,
229 "--helper-protocol", "ntlmssp-client-1",
230 "--use-cached-creds",
231 "--username", username,
232 NULL);
233
234 sclose_nolog(sockfds[1]);
235 failf(conn->data, "Could not execl(). errno %d: %s",
236 errno, Curl_strerror(errno, buffer, sizeof(buffer)));
237 exit(1);
238 }
239
240 sclose(sockfds[1]);
241 conn->ntlm_auth_hlpr_socket = sockfds[0];
242 conn->ntlm_auth_hlpr_pid = child_pid;
243 free(domain);
244 free(ntlm_auth_alloc);
245 return CURLE_OK;
246
247 done:
248 free(domain);
249 free(ntlm_auth_alloc);
250 return CURLE_REMOTE_ACCESS_DENIED;
251 }
252
253 /* if larger than this, something is seriously wrong */
254 #define MAX_NTLM_WB_RESPONSE 100000
255
ntlm_wb_response(struct connectdata * conn,const char * input,curlntlm state)256 static 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
297 if(len_out > MAX_NTLM_WB_RESPONSE) {
298 failf(conn->data, "too large ntlm_wb response!");
299 free(buf);
300 return CURLE_OUT_OF_MEMORY;
301 }
302
303 newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
304 if(!newbuf)
305 return CURLE_OUT_OF_MEMORY;
306
307 buf = newbuf;
308 }
309
310 /* Samba/winbind installed but not configured */
311 if(state == NTLMSTATE_TYPE1 &&
312 len_out == 3 &&
313 buf[0] == 'P' && buf[1] == 'W')
314 goto done;
315 /* invalid response */
316 if(len_out < 4)
317 goto done;
318 if(state == NTLMSTATE_TYPE1 &&
319 (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
320 goto done;
321 if(state == NTLMSTATE_TYPE2 &&
322 (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
323 (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
324 goto done;
325
326 conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3);
327 free(buf);
328 if(!conn->response_header)
329 return CURLE_OUT_OF_MEMORY;
330 return CURLE_OK;
331 done:
332 free(buf);
333 return CURLE_REMOTE_ACCESS_DENIED;
334 }
335
336 /*
337 * This is for creating ntlm header output by delegating challenge/response
338 * to Samba's winbind daemon helper ntlm_auth.
339 */
Curl_output_ntlm_wb(struct connectdata * conn,bool proxy)340 CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
341 bool proxy)
342 {
343 /* point to the address of the pointer that holds the string to send to the
344 server, which is for a plain host or for a HTTP proxy */
345 char **allocuserpwd;
346 /* point to the name and password for this */
347 const char *userp;
348 /* point to the correct struct with this */
349 struct ntlmdata *ntlm;
350 struct auth *authp;
351
352 CURLcode res = CURLE_OK;
353 char *input;
354
355 DEBUGASSERT(conn);
356 DEBUGASSERT(conn->data);
357
358 if(proxy) {
359 allocuserpwd = &conn->allocptr.proxyuserpwd;
360 userp = conn->http_proxy.user;
361 ntlm = &conn->proxyntlm;
362 authp = &conn->data->state.authproxy;
363 }
364 else {
365 allocuserpwd = &conn->allocptr.userpwd;
366 userp = conn->user;
367 ntlm = &conn->ntlm;
368 authp = &conn->data->state.authhost;
369 }
370 authp->done = FALSE;
371
372 /* not set means empty */
373 if(!userp)
374 userp = "";
375
376 switch(ntlm->state) {
377 case NTLMSTATE_TYPE1:
378 default:
379 /* Use Samba's 'winbind' daemon to support NTLM authentication,
380 * by delegating the NTLM challenge/response protocol to a helper
381 * in ntlm_auth.
382 * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
383 * https://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
384 * https://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
385 * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
386 * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
387 * filename of ntlm_auth helper.
388 * If NTLM authentication using winbind fails, go back to original
389 * request handling process.
390 */
391 /* Create communication with ntlm_auth */
392 res = ntlm_wb_init(conn, userp);
393 if(res)
394 return res;
395 res = ntlm_wb_response(conn, "YR\n", ntlm->state);
396 if(res)
397 return res;
398
399 free(*allocuserpwd);
400 *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
401 proxy ? "Proxy-" : "",
402 conn->response_header);
403 DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
404 free(conn->response_header);
405 if(!*allocuserpwd)
406 return CURLE_OUT_OF_MEMORY;
407 conn->response_header = NULL;
408 break;
409 case NTLMSTATE_TYPE2:
410 input = aprintf("TT %s\n", conn->challenge_header);
411 if(!input)
412 return CURLE_OUT_OF_MEMORY;
413 res = ntlm_wb_response(conn, input, ntlm->state);
414 free(input);
415 input = NULL;
416 if(res)
417 return res;
418
419 free(*allocuserpwd);
420 *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
421 proxy ? "Proxy-" : "",
422 conn->response_header);
423 DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
424 ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */
425 authp->done = TRUE;
426 Curl_ntlm_wb_cleanup(conn);
427 if(!*allocuserpwd)
428 return CURLE_OUT_OF_MEMORY;
429 break;
430 case NTLMSTATE_TYPE3:
431 /* connection is already authenticated,
432 * don't send a header in future requests */
433 free(*allocuserpwd);
434 *allocuserpwd = NULL;
435 authp->done = TRUE;
436 break;
437 }
438
439 return CURLE_OK;
440 }
441
442 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */
443