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