1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 1998 - 2017, Daniel Stenberg, <daniel@haxx.se>, et al.
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.
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.
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
21 ***************************************************************************/
23 #include "curl_setup.h"
25 #if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \
26 defined(NTLM_WB_ENABLED)
31 * https://davenport.sourceforge.io/ntlm.html
32 * https://www.innovation.ch/java/ntlm.html
37 #ifdef HAVE_SYS_WAIT_H
50 #include "vauth/ntlm.h"
51 #include "curl_ntlm_core.h"
52 #include "curl_ntlm_wb.h"
56 /* The last 3 #include files should be in this order */
57 #include "curl_printf.h"
58 #include "curl_memory.h"
62 # define DEBUG_OUT(x) x
64 # define DEBUG_OUT(x) Curl_nop_stmt
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))
74 # define sclose_nolog(x) close((x))
77 void Curl_ntlm_wb_cleanup(struct connectdata *conn)
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;
84 if(conn->ntlm_auth_hlpr_pid) {
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)
92 kill(conn->ntlm_auth_hlpr_pid, SIGTERM);
95 /* Give the process another moment to shut down cleanly before
96 bringing down the axe */
100 kill(conn->ntlm_auth_hlpr_pid, SIGKILL);
106 conn->ntlm_auth_hlpr_pid = 0;
109 free(conn->challenge_header);
110 conn->challenge_header = NULL;
111 free(conn->response_header);
112 conn->response_header = NULL;
115 static CURLcode ntlm_wb_init(struct connectdata *conn, const char *userp)
117 curl_socket_t sockfds[2];
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;
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)
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) &&
152 username = pw.pw_name;
155 if(!username || !username[0])
158 slash = strpbrk(username, "\\/");
160 domain = strdup(username);
162 return CURLE_OUT_OF_MEMORY;
163 slash = domain + (slash - username);
165 username = username + (slash - domain) + 1;
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 */
173 ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
175 ntlm_auth = ntlm_auth_alloc;
178 ntlm_auth = NTLM_WB_FILE;
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));
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));
193 if(child_pid == -1) {
196 failf(conn->data, "Could not fork. errno %d: %s",
197 errno, Curl_strerror(conn, errno));
200 else if(!child_pid) {
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));
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));
220 execl(ntlm_auth, ntlm_auth,
221 "--helper-protocol", "ntlmssp-client-1",
222 "--use-cached-creds",
223 "--username", username,
227 execl(ntlm_auth, ntlm_auth,
228 "--helper-protocol", "ntlmssp-client-1",
229 "--use-cached-creds",
230 "--username", username,
233 sclose_nolog(sockfds[1]);
234 failf(conn->data, "Could not execl(). errno %d: %s",
235 errno, Curl_strerror(conn, errno));
240 conn->ntlm_auth_hlpr_socket = sockfds[0];
241 conn->ntlm_auth_hlpr_pid = child_pid;
243 free(ntlm_auth_alloc);
248 free(ntlm_auth_alloc);
249 return CURLE_REMOTE_ACCESS_DENIED;
252 static CURLcode ntlm_wb_response(struct connectdata *conn,
253 const char *input, curlntlm state)
255 char *buf = malloc(NTLM_BUFSIZE);
256 size_t len_in = strlen(input), len_out = 0;
259 return CURLE_OUT_OF_MEMORY;
262 ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
264 /* Interrupted by a signal, retry it */
267 /* write failed if other errors happen */
278 size = sread(conn->ntlm_auth_hlpr_socket, buf + len_out, NTLM_BUFSIZE);
288 if(buf[len_out - 1] == '\n') {
289 buf[len_out - 1] = '\0';
292 newbuf = Curl_saferealloc(buf, len_out + NTLM_BUFSIZE);
294 return CURLE_OUT_OF_MEMORY;
299 /* Samba/winbind installed but not configured */
300 if(state == NTLMSTATE_TYPE1 &&
302 buf[0] == 'P' && buf[1] == 'W')
304 /* invalid response */
307 if(state == NTLMSTATE_TYPE1 &&
308 (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
310 if(state == NTLMSTATE_TYPE2 &&
311 (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
312 (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
315 conn->response_header = aprintf("NTLM %.*s", len_out - 4, buf + 3);
320 return CURLE_REMOTE_ACCESS_DENIED;
324 * This is for creating ntlm header output by delegating challenge/response
325 * to Samba's winbind daemon helper ntlm_auth.
327 CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
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 */
333 /* point to the name and password for this */
335 /* point to the correct struct with this */
336 struct ntlmdata *ntlm;
339 CURLcode res = CURLE_OK;
343 DEBUGASSERT(conn->data);
346 allocuserpwd = &conn->allocptr.proxyuserpwd;
347 userp = conn->http_proxy.user;
348 ntlm = &conn->proxyntlm;
349 authp = &conn->data->state.authproxy;
352 allocuserpwd = &conn->allocptr.userpwd;
355 authp = &conn->data->state.authhost;
359 /* not set means empty */
363 switch(ntlm->state) {
364 case NTLMSTATE_TYPE1:
366 /* Use Samba's 'winbind' daemon to support NTLM authentication,
367 * by delegating the NTLM challenge/response protocol to a helper
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.
378 /* Create communication with ntlm_auth */
379 res = ntlm_wb_init(conn, userp);
382 res = ntlm_wb_response(conn, "YR\n", ntlm->state);
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;
394 case NTLMSTATE_TYPE2:
395 input = aprintf("TT %s\n", conn->challenge_header);
397 return CURLE_OUT_OF_MEMORY;
398 res = ntlm_wb_response(conn, input, ntlm->state);
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 */
411 Curl_ntlm_wb_cleanup(conn);
413 case NTLMSTATE_TYPE3:
414 /* connection is already authenticated,
415 * don't send a header in future requests */
417 *allocuserpwd = NULL;
425 #endif /* !CURL_DISABLE_HTTP && USE_NTLM && NTLM_WB_ENABLED */