release 0.92.3
[cntlm.git] / direct.c
blobc0f86efd003ff242585ddd35bdf7011b18806828
1 /*
2 * CNTLM is free software; you can redistribute it and/or modify it under the
3 * terms of the GNU General Public License as published by the Free Software
4 * Foundation; either version 2 of the License, or (at your option) any later
5 * version.
7 * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY
8 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
10 * details.
12 * You should have received a copy of the GNU General Public License along with
13 * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
14 * St, Fifth Floor, Boston, MA 02110-1301, USA.
16 * Copyright (c) 2007 David Kubicek
20 #include <sys/types.h>
21 #include <pthread.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <syslog.h>
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 #include <strings.h>
30 #include <errno.h>
31 #include <netdb.h>
32 #include <sys/socket.h>
34 extern int h_errno;
36 #include "utils.h"
37 #include "globals.h"
38 #include "auth.h"
39 #include "http.h"
40 #include "socket.h"
41 #include "ntlm.h"
42 #include "direct.h"
43 #include "pages.h"
45 int host_connect(const char *hostname, int port) {
46 struct in_addr addr;
48 errno = 0;
49 if (!so_resolv(&addr, hostname)) {
50 //if (debug)
51 // printf("so_resolv: %s failed (%d: %s)\n", hostname, h_errno, hstrerror(h_errno));
52 return -1;
55 return so_connect(addr, port);
59 int www_authenticate(int sd, rr_data_t request, rr_data_t response, struct auth_s *creds) {
60 char *tmp, *buf, *challenge;
61 rr_data_t auth;
62 int len;
64 int rc = 0;
66 buf = new(BUFSIZE);
68 strcpy(buf, "NTLM ");
69 len = ntlm_request(&tmp, creds);
70 if (len) {
71 to_base64(MEM(buf, uint8_t, 5), MEM(tmp, uint8_t, 0), len, BUFSIZE-5);
72 free(tmp);
75 auth = dup_rr_data(request);
76 auth->headers = hlist_mod(auth->headers, "Connection", "keep-alive", 1);
77 auth->headers = hlist_mod(auth->headers, "Authorization", buf, 1);
78 auth->headers = hlist_mod(auth->headers, "Content-Length", "0", 1);
79 auth->headers = hlist_del(auth->headers, "Transfer-Encoding");
82 * Drop whatever error page server returned
84 if (!http_body_drop(sd, response))
85 goto bailout;
87 if (debug) {
88 printf("\nSending WWW auth request...\n");
89 hlist_dump(auth->headers);
92 if (!headers_send(sd, auth))
93 goto bailout;
95 if (debug)
96 printf("\nReading WWW auth response...\n");
99 * Get NTLM challenge
101 reset_rr_data(auth);
102 if (!headers_recv(sd, auth)) {
103 goto bailout;
106 if (debug)
107 hlist_dump(auth->headers);
110 * Auth required?
112 if (auth->code == 401) {
113 if (!http_body_drop(sd, auth))
114 goto bailout;
116 tmp = hlist_get(auth->headers, "WWW-Authenticate");
117 if (tmp && strlen(tmp) > 6 + 8) {
118 challenge = new(strlen(tmp) + 5 + 1);
119 len = from_base64(challenge, tmp + 5);
120 if (len > NTLM_CHALLENGE_MIN) {
121 len = ntlm_response(&tmp, challenge, len, creds);
122 if (len > 0) {
123 strcpy(buf, "NTLM ");
124 to_base64(MEM(buf, uint8_t, 5), MEM(tmp, uint8_t, 0), len, BUFSIZE-5);
125 request->headers = hlist_mod(request->headers, "Authorization", buf, 1);
126 free(tmp);
127 } else {
128 syslog(LOG_ERR, "No target info block. Cannot do NTLMv2!\n");
129 response->errmsg = "Invalid NTLM challenge from web server";
130 free(challenge);
131 goto bailout;
133 } else {
134 syslog(LOG_ERR, "Server returning invalid challenge!\n");
135 response->errmsg = "Invalid NTLM challenge from web server";
136 free(challenge);
137 goto bailout;
140 free(challenge);
141 } else {
142 syslog(LOG_WARNING, "No challenge in WWW-Authenticate!\n");
143 response->errmsg = "Web server reply missing NTLM challenge";
144 goto bailout;
146 } else {
147 goto bailout;
150 if (debug)
151 printf("\nSending WWW auth...\n");
153 if (!headers_send(sd, request)) {
154 goto bailout;
157 if (debug)
158 printf("\nReading final server response...\n");
160 reset_rr_data(auth);
161 if (!headers_recv(sd, auth)) {
162 goto bailout;
165 rc = 1;
167 if (debug)
168 hlist_dump(auth->headers);
170 bailout:
171 if (rc)
172 response = copy_rr_data(response, auth);
173 free_rr_data(auth);
174 free(buf);
176 return rc;
179 rr_data_t direct_request(void *cdata, rr_data_t request) {
180 rr_data_t data[2], rc = NULL;
181 struct auth_s *tcreds = NULL;
182 int *rsocket[2], *wsocket[2];
183 int w, loop, sd;
184 char *tmp;
186 char *hostname = NULL;
187 int port = 0;
188 int conn_alive = 0;
190 int cd = ((struct thread_arg_s *)cdata)->fd;
191 struct sockaddr_in caddr = ((struct thread_arg_s *)cdata)->addr;
193 if (debug)
194 printf("Direct thread processing...\n");
196 sd = host_connect(request->hostname, request->port);
197 if (sd < 0) {
198 syslog(LOG_WARNING, "Connection failed for %s:%d (%s)", request->hostname, request->port, strerror(errno));
199 tmp = gen_502_page(request->http, strerror(errno));
200 w = write(cd, tmp, strlen(tmp));
201 free(tmp);
203 rc = (void *)-1;
204 goto bailout;
208 * Now save NTLM credentials for purposes of this thread.
209 * If web auth fails, we'll rewrite them like with NTLM-to-Basic in proxy mode.
211 tcreds = dup_auth(g_creds, /* fullcopy */ 1);
213 if (request->hostname) {
214 hostname = strdup(request->hostname);
215 port = request->port;
216 } else {
217 tmp = gen_502_page(request->http, "Invalid request URL");
218 w = write(cd, tmp, strlen(tmp));
219 free(tmp);
221 rc = (void *)-1;
222 goto bailout;
225 do {
226 if (request) {
227 data[0] = dup_rr_data(request);
228 request = NULL;
229 } else {
230 data[0] = new_rr_data();
232 data[1] = new_rr_data();
234 rsocket[0] = wsocket[1] = &cd;
235 rsocket[1] = wsocket[0] = &sd;
237 conn_alive = 0;
239 for (loop = 0; loop < 2; ++loop) {
240 if (data[loop]->empty) { // Isn't this the first loop with request supplied by caller?
241 if (debug) {
242 printf("\n******* Round %d C: %d, S: %d *******\n", loop+1, cd, sd);
243 printf("Reading headers (%d)...\n", *rsocket[loop]);
245 if (!headers_recv(*rsocket[loop], data[loop])) {
246 free_rr_data(data[0]);
247 free_rr_data(data[1]);
248 rc = (void *)-1;
249 goto bailout;
254 * Check whether this new request still talks to the same server as previous.
255 * If no, return request to caller, he must decide on forward or direct
256 * approach.
258 if (loop == 0 && hostname && data[0]->hostname
259 && (strcasecmp(hostname, data[0]->hostname) || port != data[0]->port)) {
260 if (debug)
261 printf("\n******* D RETURN: %s *******\n", data[0]->url);
263 rc = dup_rr_data(data[0]);
264 free_rr_data(data[0]);
265 free_rr_data(data[1]);
266 goto bailout;
269 if (debug)
270 hlist_dump(data[loop]->headers);
272 if (loop == 0 && data[0]->req) {
273 syslog(LOG_DEBUG, "%s %s %s", inet_ntoa(caddr.sin_addr), data[0]->method, data[0]->url);
276 * Convert full proxy request URL into a relative URL
277 * Host header is already inserted by headers_recv()
279 if (data[0]->rel_url) {
280 if (data[0]->url)
281 free(data[0]->url);
282 data[0]->url = strdup(data[0]->rel_url);
285 data[0]->headers = hlist_mod(data[0]->headers, "Connection", "keep-alive", 1);
286 data[0]->headers = hlist_del(data[0]->headers, "Proxy-Authorization");
289 * Try to get auth from client if present
291 if (http_parse_basic(data[0]->headers, "Authorization", tcreds) > 0 && debug)
292 printf("NTLM-to-basic: Credentials parsed: %s\\%s at %s\n", tcreds->domain, tcreds->user, tcreds->workstation);
296 * Is this a CONNECT request?
298 if (loop == 0 && CONNECT(data[0])) {
299 if (debug)
300 printf("CONNECTing...\n");
302 data[1]->empty = 0;
303 data[1]->req = 0;
304 data[1]->code = 200;
305 data[1]->msg = strdup("Connection established");
306 data[1]->http = strdup(data[0]->http);
308 if (headers_send(cd, data[1]))
309 tunnel(cd, sd);
311 free_rr_data(data[0]);
312 free_rr_data(data[1]);
313 rc = (void *)-1;
314 goto bailout;
317 if (loop == 1 && data[1]->code == 401 && hlist_subcmp_all(data[1]->headers, "WWW-Authenticate", "NTLM")) {
319 * Server closing the connection after 401?
320 * Should never happen.
322 if (hlist_subcmp(data[1]->headers, "Connection", "close")) {
323 if (debug)
324 printf("Reconnect before WWW auth\n");
325 close(sd);
326 sd = host_connect(data[0]->hostname, data[0]->port);
327 if (sd < 0) {
328 tmp = gen_502_page(data[0]->http, "WWW authentication reconnect failed");
329 w = write(cd, tmp, strlen(tmp));
330 free(tmp);
332 rc = (void *)-1;
333 goto bailout;
336 if (!www_authenticate(*wsocket[0], data[0], data[1], tcreds)) {
337 if (debug)
338 printf("WWW auth connection error.\n");
340 tmp = gen_502_page(data[1]->http, data[1]->errmsg ? data[1]->errmsg : "Error during WWW-Authenticate");
341 w = write(cd, tmp, strlen(tmp));
342 free(tmp);
344 free_rr_data(data[0]);
345 free_rr_data(data[1]);
347 rc = (void *)-1;
348 goto bailout;
349 } else if (data[1]->code == 401) {
351 * Server giving 401 after auth?
352 * Request basic auth
354 tmp = gen_401_page(data[1]->http, data[0]->hostname, data[0]->port);
355 w = write(cd, tmp, strlen(tmp));
356 free(tmp);
358 free_rr_data(data[0]);
359 free_rr_data(data[1]);
361 rc = (void *)-1;
362 goto bailout;
367 * Check if we should loop for another request. Required for keep-alive
368 * connections, client might really need a non-interrupted conversation.
370 * We default to keep-alive server connections, unless server explicitly
371 * flags closing the connection or we detect a body with unknown size
372 * (end marked by server closing).
374 if (loop == 1) {
375 conn_alive = !hlist_subcmp(data[1]->headers, "Connection", "close")
376 && http_has_body(data[0], data[1]) != -1;
377 if (conn_alive) {
378 data[1]->headers = hlist_mod(data[1]->headers, "Proxy-Connection", "keep-alive", 1);
379 data[1]->headers = hlist_mod(data[1]->headers, "Connection", "keep-alive", 1);
380 } else {
381 data[1]->headers = hlist_mod(data[1]->headers, "Proxy-Connection", "close", 1);
382 rc = (void *)-1;
386 if (debug)
387 printf("Sending headers (%d)...\n", *wsocket[loop]);
390 * Send headers
392 if (!headers_send(*wsocket[loop], data[loop])) {
393 free_rr_data(data[0]);
394 free_rr_data(data[1]);
395 rc = (void *)-1;
396 goto bailout;
399 if (!http_body_send(*wsocket[loop], *rsocket[loop], data[0], data[1])) {
400 free_rr_data(data[0]);
401 free_rr_data(data[1]);
402 rc = (void *)-1;
403 goto bailout;
407 free_rr_data(data[0]);
408 free_rr_data(data[1]);
410 } while (conn_alive && !so_closed(sd) && !so_closed(cd) && !serialize);
412 bailout:
413 if (tcreds)
414 free(tcreds);
415 if (hostname)
416 free(hostname);
418 close(sd);
420 return rc;
423 void direct_tunnel(void *thread_data) {
424 int sd, port = 0;
425 char *pos, *hostname;
427 int cd = ((struct thread_arg_s *)thread_data)->fd;
428 char *thost = ((struct thread_arg_s *)thread_data)->target;
429 struct sockaddr_in caddr = ((struct thread_arg_s *)thread_data)->addr;
431 hostname = strdup(thost);
432 if ((pos = strchr(hostname, ':')) != NULL) {
433 *pos = 0;
434 port = atoi(++pos);
437 sd = host_connect(hostname, port);
438 if (sd <= 0)
439 goto bailout;
441 syslog(LOG_DEBUG, "%s FORWARD %s", inet_ntoa(caddr.sin_addr), thost);
442 if (debug)
443 printf("Portforwarding to %s for client %d...\n", thost, cd);
445 tunnel(cd, sd);
447 bailout:
448 free(hostname);
449 close(sd);
450 close(cd);
452 return;