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 #include "strdup.h"
55 /* The last 3 #include files should be in this order */
56 #include "curl_printf.h"
57 #include "curl_memory.h"
58 #include "memdebug.h"
59
60 #if DEBUG_ME
61 # define DEBUG_OUT(x) x
62 #else
63 # define DEBUG_OUT(x) Curl_nop_stmt
64 #endif
65
66 /* Portable 'sclose_nolog' used only in child process instead of 'sclose'
67 to avoid fooling the socket leak detector */
68 #if defined(HAVE_CLOSESOCKET)
69 # define sclose_nolog(x) closesocket((x))
70 #elif defined(HAVE_CLOSESOCKET_CAMEL)
71 # define sclose_nolog(x) CloseSocket((x))
72 #else
73 # define sclose_nolog(x) close((x))
74 #endif
75
Curl_ntlm_wb_cleanup(struct connectdata * conn)76 void Curl_ntlm_wb_cleanup(struct connectdata *conn)
77 {
78 if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
79 sclose(conn->ntlm_auth_hlpr_socket);
80 conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
81 }
82
83 if(conn->ntlm_auth_hlpr_pid) {
84 int i;
85 for(i = 0; i < 4; i++) {
86 pid_t ret = waitpid(conn->ntlm_auth_hlpr_pid, NULL, WNOHANG);
87 if(ret == conn->ntlm_auth_hlpr_pid || errno == ECHILD)
88 break;
89 switch(i) {
90 case 0:
91 kill(conn->ntlm_auth_hlpr_pid, SIGTERM);
92 break;
93 case 1:
94 /* Give the process another moment to shut down cleanly before
95 bringing down the axe */
96 Curl_wait_ms(1);
97 break;
98 case 2:
99 kill(conn->ntlm_auth_hlpr_pid, SIGKILL);
100 break;
101 case 3:
102 break;
103 }
104 }
105 conn->ntlm_auth_hlpr_pid = 0;
106 }
107
108 free(conn->challenge_header);
109 conn->challenge_header = NULL;
110 free(conn->response_header);
111 conn->response_header = NULL;
112 }
113
ntlm_wb_init(struct connectdata * conn,const char * userp)114 static CURLcode ntlm_wb_init(struct connectdata *conn, const char *userp)
115 {
116 curl_socket_t sockfds[2];
117 pid_t child_pid;
118 const char *username;
119 char *slash, *domain = NULL;
120 const char *ntlm_auth = NULL;
121 char *ntlm_auth_alloc = NULL;
122 #if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
123 struct passwd pw, *pw_res;
124 char pwbuf[1024];
125 #endif
126 int error;
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 if((domain = strdup(username)) == NULL)
161 return CURLE_OUT_OF_MEMORY;
162 slash = domain + (slash - username);
163 *slash = '\0';
164 username = username + (slash - domain) + 1;
165 }
166
167 /* For testing purposes, when DEBUGBUILD is defined and environment
168 variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
169 NTLM challenge/response which only accepts commands and output
170 strings pre-written in test case definitions */
171 #ifdef DEBUGBUILD
172 ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
173 if(ntlm_auth_alloc)
174 ntlm_auth = ntlm_auth_alloc;
175 else
176 #endif
177 ntlm_auth = NTLM_WB_FILE;
178
179 if(access(ntlm_auth, X_OK) != 0) {
180 error = ERRNO;
181 failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
182 ntlm_auth, error, Curl_strerror(conn, error));
183 goto done;
184 }
185
186 if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
187 error = ERRNO;
188 failf(conn->data, "Could not open socket pair. errno %d: %s",
189 error, Curl_strerror(conn, error));
190 goto done;
191 }
192
193 child_pid = fork();
194 if(child_pid == -1) {
195 error = ERRNO;
196 sclose(sockfds[0]);
197 sclose(sockfds[1]);
198 failf(conn->data, "Could not fork. errno %d: %s",
199 error, Curl_strerror(conn, error));
200 goto done;
201 }
202 else if(!child_pid) {
203 /*
204 * child process
205 */
206
207 /* Don't use sclose in the child since it fools the socket leak detector */
208 sclose_nolog(sockfds[0]);
209 if(dup2(sockfds[1], STDIN_FILENO) == -1) {
210 error = ERRNO;
211 failf(conn->data, "Could not redirect child stdin. errno %d: %s",
212 error, Curl_strerror(conn, error));
213 exit(1);
214 }
215
216 if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
217 error = ERRNO;
218 failf(conn->data, "Could not redirect child stdout. errno %d: %s",
219 error, Curl_strerror(conn, error));
220 exit(1);
221 }
222
223 if(domain)
224 execl(ntlm_auth, ntlm_auth,
225 "--helper-protocol", "ntlmssp-client-1",
226 "--use-cached-creds",
227 "--username", username,
228 "--domain", domain,
229 NULL);
230 else
231 execl(ntlm_auth, ntlm_auth,
232 "--helper-protocol", "ntlmssp-client-1",
233 "--use-cached-creds",
234 "--username", username,
235 NULL);
236
237 error = ERRNO;
238 sclose_nolog(sockfds[1]);
239 failf(conn->data, "Could not execl(). errno %d: %s",
240 error, Curl_strerror(conn, error));
241 exit(1);
242 }
243
244 sclose(sockfds[1]);
245 conn->ntlm_auth_hlpr_socket = sockfds[0];
246 conn->ntlm_auth_hlpr_pid = child_pid;
247 free(domain);
248 free(ntlm_auth_alloc);
249 return CURLE_OK;
250
251 done:
252 free(domain);
253 free(ntlm_auth_alloc);
254 return CURLE_REMOTE_ACCESS_DENIED;
255 }
256
ntlm_wb_response(struct connectdata * conn,const char * input,curlntlm state)257 static CURLcode ntlm_wb_response(struct connectdata *conn,
258 const char *input, curlntlm state)
259 {
260 char *buf = malloc(NTLM_BUFSIZE);
261 size_t len_in = strlen(input), len_out = 0;
262
263 if(!buf)
264 return CURLE_OUT_OF_MEMORY;
265
266 while(len_in > 0) {
267 ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
268 if(written == -1) {
269 /* Interrupted by a signal, retry it */
270 if(errno == EINTR)
271 continue;
272 /* write failed if other errors happen */
273 goto done;
274 }
275 input += written;
276 len_in -= written;
277 }
278 /* Read one line */
279 while(1) {
280 ssize_t size;
281 char *newbuf;
282
283 size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
284 if(size == -1) {
285 if(errno == EINTR)
286 continue;
287 goto done;
288 }
289 else if(size == 0)
290 goto done;
291
292 len_out += size;
293 if(buf[len_out - 1] == '\n') {
294 buf[len_out - 1] = '\0';
295 break;
296 }
297 newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
298 if(!newbuf)
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;
323 done:
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 */
Curl_output_ntlm_wb(struct connectdata * conn,bool proxy)332 CURLcode 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->http_proxy.user;
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