Revert "Update to 7.40.1"
[platform/upstream/curl.git] / lib / curl_ntlm_wb.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2014, 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 http://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(USE_NTLM) && defined(NTLM_WB_ENABLED)
26
27 /*
28  * NTLM details:
29  *
30  * http://davenport.sourceforge.net/ntlm.html
31  * http://www.innovation.ch/java/ntlm.html
32  */
33
34 #define DEBUG_ME 0
35
36 #ifdef HAVE_SYS_WAIT_H
37 #include <sys/wait.h>
38 #endif
39 #ifdef HAVE_SIGNAL_H
40 #include <signal.h>
41 #endif
42
43 #include "urldata.h"
44 #include "sendf.h"
45 #include "select.h"
46 #include "curl_ntlm_msgs.h"
47 #include "curl_ntlm_wb.h"
48 #include "url.h"
49 #include "strerror.h"
50 #include "curl_memory.h"
51
52 #define _MPRINTF_REPLACE /* use our functions only */
53 #include <curl/mprintf.h>
54
55 /* The last #include file should be: */
56 #include "memdebug.h"
57
58 #if DEBUG_ME
59 # define DEBUG_OUT(x) x
60 #else
61 # define DEBUG_OUT(x) Curl_nop_stmt
62 #endif
63
64 /* Portable 'sclose_nolog' used only in child process instead of 'sclose'
65    to avoid fooling the socket leak detector */
66 #if defined(HAVE_CLOSESOCKET)
67 #  define sclose_nolog(x)  closesocket((x))
68 #elif defined(HAVE_CLOSESOCKET_CAMEL)
69 #  define sclose_nolog(x)  CloseSocket((x))
70 #else
71 #  define sclose_nolog(x)  close((x))
72 #endif
73
74 void Curl_ntlm_wb_cleanup(struct connectdata *conn)
75 {
76   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD) {
77     sclose(conn->ntlm_auth_hlpr_socket);
78     conn->ntlm_auth_hlpr_socket = CURL_SOCKET_BAD;
79   }
80
81   if(conn->ntlm_auth_hlpr_pid) {
82     int i;
83     for(i = 0; i < 4; i++) {
84       pid_t ret = waitpid(conn->ntlm_auth_hlpr_pid, NULL, WNOHANG);
85       if(ret == conn->ntlm_auth_hlpr_pid || errno == ECHILD)
86         break;
87       switch(i) {
88       case 0:
89         kill(conn->ntlm_auth_hlpr_pid, SIGTERM);
90         break;
91       case 1:
92         /* Give the process another moment to shut down cleanly before
93            bringing down the axe */
94         Curl_wait_ms(1);
95         break;
96       case 2:
97         kill(conn->ntlm_auth_hlpr_pid, SIGKILL);
98         break;
99       case 3:
100         break;
101       }
102     }
103     conn->ntlm_auth_hlpr_pid = 0;
104   }
105
106   Curl_safefree(conn->challenge_header);
107   conn->challenge_header = NULL;
108   Curl_safefree(conn->response_header);
109   conn->response_header = NULL;
110 }
111
112 static CURLcode ntlm_wb_init(struct connectdata *conn, const char *userp)
113 {
114   curl_socket_t sockfds[2];
115   pid_t child_pid;
116   const char *username;
117   char *slash, *domain = NULL;
118   const char *ntlm_auth = NULL;
119   char *ntlm_auth_alloc = NULL;
120   int error;
121
122   /* Return if communication with ntlm_auth already set up */
123   if(conn->ntlm_auth_hlpr_socket != CURL_SOCKET_BAD ||
124      conn->ntlm_auth_hlpr_pid)
125     return CURLE_OK;
126
127   username = userp;
128   slash = strpbrk(username, "\\/");
129   if(slash) {
130     if((domain = strdup(username)) == NULL)
131       return CURLE_OUT_OF_MEMORY;
132     slash = domain + (slash - username);
133     *slash = '\0';
134     username = username + (slash - domain) + 1;
135   }
136
137   /* For testing purposes, when DEBUGBUILD is defined and environment
138      variable CURL_NTLM_WB_FILE is set a fake_ntlm is used to perform
139      NTLM challenge/response which only accepts commands and output
140      strings pre-written in test case definitions */
141 #ifdef DEBUGBUILD
142   ntlm_auth_alloc = curl_getenv("CURL_NTLM_WB_FILE");
143   if(ntlm_auth_alloc)
144     ntlm_auth = ntlm_auth_alloc;
145   else
146 #endif
147     ntlm_auth = NTLM_WB_FILE;
148
149   if(access(ntlm_auth, X_OK) != 0) {
150     error = ERRNO;
151     failf(conn->data, "Could not access ntlm_auth: %s errno %d: %s",
152           ntlm_auth, error, Curl_strerror(conn, error));
153     goto done;
154   }
155
156   if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds)) {
157     error = ERRNO;
158     failf(conn->data, "Could not open socket pair. errno %d: %s",
159           error, Curl_strerror(conn, error));
160     goto done;
161   }
162
163   child_pid = fork();
164   if(child_pid == -1) {
165     error = ERRNO;
166     sclose(sockfds[0]);
167     sclose(sockfds[1]);
168     failf(conn->data, "Could not fork. errno %d: %s",
169           error, Curl_strerror(conn, error));
170     goto done;
171   }
172   else if(!child_pid) {
173     /*
174      * child process
175      */
176
177     /* Don't use sclose in the child since it fools the socket leak detector */
178     sclose_nolog(sockfds[0]);
179     if(dup2(sockfds[1], STDIN_FILENO) == -1) {
180       error = ERRNO;
181       failf(conn->data, "Could not redirect child stdin. errno %d: %s",
182             error, Curl_strerror(conn, error));
183       exit(1);
184     }
185
186     if(dup2(sockfds[1], STDOUT_FILENO) == -1) {
187       error = ERRNO;
188       failf(conn->data, "Could not redirect child stdout. errno %d: %s",
189             error, Curl_strerror(conn, error));
190       exit(1);
191     }
192
193     if(domain)
194       execl(ntlm_auth, ntlm_auth,
195             "--helper-protocol", "ntlmssp-client-1",
196             "--use-cached-creds",
197             "--username", username,
198             "--domain", domain,
199             NULL);
200     else
201       execl(ntlm_auth, ntlm_auth,
202             "--helper-protocol", "ntlmssp-client-1",
203             "--use-cached-creds",
204             "--username", username,
205             NULL);
206
207     error = ERRNO;
208     sclose_nolog(sockfds[1]);
209     failf(conn->data, "Could not execl(). errno %d: %s",
210           error, Curl_strerror(conn, error));
211     exit(1);
212   }
213
214   sclose(sockfds[1]);
215   conn->ntlm_auth_hlpr_socket = sockfds[0];
216   conn->ntlm_auth_hlpr_pid = child_pid;
217   Curl_safefree(domain);
218   Curl_safefree(ntlm_auth_alloc);
219   return CURLE_OK;
220
221 done:
222   Curl_safefree(domain);
223   Curl_safefree(ntlm_auth_alloc);
224   return CURLE_REMOTE_ACCESS_DENIED;
225 }
226
227 static CURLcode ntlm_wb_response(struct connectdata *conn,
228                                  const char *input, curlntlm state)
229 {
230   ssize_t size;
231   char buf[NTLM_BUFSIZE];
232   char *tmpbuf = buf;
233   size_t len_in = strlen(input);
234   size_t len_out = sizeof(buf);
235
236   while(len_in > 0) {
237     ssize_t written = swrite(conn->ntlm_auth_hlpr_socket, input, len_in);
238     if(written == -1) {
239       /* Interrupted by a signal, retry it */
240       if(errno == EINTR)
241         continue;
242       /* write failed if other errors happen */
243       goto done;
244     }
245     input += written;
246     len_in -= written;
247   }
248   /* Read one line */
249   while(len_out > 0) {
250     size = sread(conn->ntlm_auth_hlpr_socket, tmpbuf, len_out);
251     if(size == -1) {
252       if(errno == EINTR)
253         continue;
254       goto done;
255     }
256     else if(size == 0)
257       goto done;
258     else if(tmpbuf[size - 1] == '\n') {
259       tmpbuf[size - 1] = '\0';
260       goto wrfinish;
261     }
262     tmpbuf += size;
263     len_out -= size;
264   }
265   goto done;
266 wrfinish:
267   /* Samba/winbind installed but not configured */
268   if(state == NTLMSTATE_TYPE1 &&
269      size == 3 &&
270      buf[0] == 'P' && buf[1] == 'W')
271     return CURLE_REMOTE_ACCESS_DENIED;
272   /* invalid response */
273   if(size < 4)
274     goto done;
275   if(state == NTLMSTATE_TYPE1 &&
276      (buf[0]!='Y' || buf[1]!='R' || buf[2]!=' '))
277     goto done;
278   if(state == NTLMSTATE_TYPE2 &&
279      (buf[0]!='K' || buf[1]!='K' || buf[2]!=' ') &&
280      (buf[0]!='A' || buf[1]!='F' || buf[2]!=' '))
281     goto done;
282
283   conn->response_header = aprintf("NTLM %.*s", size - 4, buf + 3);
284   return CURLE_OK;
285 done:
286   return CURLE_REMOTE_ACCESS_DENIED;
287 }
288
289 /*
290  * This is for creating ntlm header output by delegating challenge/response
291  * to Samba's winbind daemon helper ntlm_auth.
292  */
293 CURLcode Curl_output_ntlm_wb(struct connectdata *conn,
294                               bool proxy)
295 {
296   /* point to the address of the pointer that holds the string to send to the
297      server, which is for a plain host or for a HTTP proxy */
298   char **allocuserpwd;
299   /* point to the name and password for this */
300   const char *userp;
301   /* point to the correct struct with this */
302   struct ntlmdata *ntlm;
303   struct auth *authp;
304
305   CURLcode res = CURLE_OK;
306   char *input;
307
308   DEBUGASSERT(conn);
309   DEBUGASSERT(conn->data);
310
311   if(proxy) {
312     allocuserpwd = &conn->allocptr.proxyuserpwd;
313     userp = conn->proxyuser;
314     ntlm = &conn->proxyntlm;
315     authp = &conn->data->state.authproxy;
316   }
317   else {
318     allocuserpwd = &conn->allocptr.userpwd;
319     userp = conn->user;
320     ntlm = &conn->ntlm;
321     authp = &conn->data->state.authhost;
322   }
323   authp->done = FALSE;
324
325   /* not set means empty */
326   if(!userp)
327     userp="";
328
329   switch(ntlm->state) {
330   case NTLMSTATE_TYPE1:
331   default:
332     /* Use Samba's 'winbind' daemon to support NTLM authentication,
333      * by delegating the NTLM challenge/response protocal to a helper
334      * in ntlm_auth.
335      * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html
336      * http://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html
337      * http://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html
338      * Preprocessor symbol 'NTLM_WB_ENABLED' is defined when this
339      * feature is enabled and 'NTLM_WB_FILE' symbol holds absolute
340      * filename of ntlm_auth helper.
341      * If NTLM authentication using winbind fails, go back to original
342      * request handling process.
343      */
344     /* Create communication with ntlm_auth */
345     res = ntlm_wb_init(conn, userp);
346     if(res)
347       return res;
348     res = ntlm_wb_response(conn, "YR\n", ntlm->state);
349     if(res)
350       return res;
351
352     Curl_safefree(*allocuserpwd);
353     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
354                             proxy ? "Proxy-" : "",
355                             conn->response_header);
356     DEBUG_OUT(fprintf(stderr, "**** Header %s\n ", *allocuserpwd));
357     Curl_safefree(conn->response_header);
358     conn->response_header = NULL;
359     break;
360   case NTLMSTATE_TYPE2:
361     input = aprintf("TT %s\n", conn->challenge_header);
362     if(!input)
363       return CURLE_OUT_OF_MEMORY;
364     res = ntlm_wb_response(conn, input, ntlm->state);
365     free(input);
366     input = NULL;
367     if(res)
368       return res;
369
370     Curl_safefree(*allocuserpwd);
371     *allocuserpwd = aprintf("%sAuthorization: %s\r\n",
372                             proxy ? "Proxy-" : "",
373                             conn->response_header);
374     DEBUG_OUT(fprintf(stderr, "**** %s\n ", *allocuserpwd));
375     ntlm->state = NTLMSTATE_TYPE3; /* we sent a type-3 */
376     authp->done = TRUE;
377     Curl_ntlm_wb_cleanup(conn);
378     break;
379   case NTLMSTATE_TYPE3:
380     /* connection is already authenticated,
381      * don't send a header in future requests */
382     if(*allocuserpwd) {
383       free(*allocuserpwd);
384       *allocuserpwd=NULL;
385     }
386     authp->done = TRUE;
387     break;
388   }
389
390   return CURLE_OK;
391 }
392
393 #endif /* USE_NTLM && NTLM_WB_ENABLED */