release 0.92.3
[cntlm.git] / http.c
bloba6f03e705d9a0d73835f9ba2160819da57587c5c
1 /*
2 * HTTP handling routines and related socket stuff for CNTLM
4 * CNTLM is free software; you can redistribute it and/or modify it under the
5 * terms of the GNU General Public License as published by the Free Software
6 * Foundation; either version 2 of the License, or (at your option) any later
7 * version.
9 * CNTLM is distributed in the hope that it will be useful, but WITHOUT ANY
10 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12 * details.
14 * You should have received a copy of the GNU General Public License along with
15 * this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
16 * St, Fifth Floor, Boston, MA 02110-1301, USA.
18 * Copyright (c) 2007 David Kubicek
22 #include <sys/types.h>
23 #include <sys/select.h>
24 #include <sys/time.h>
25 #include <ctype.h>
26 #include <string.h>
27 #include <strings.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <errno.h>
33 #include "utils.h"
34 #include "socket.h"
35 #include "ntlm.h"
36 #include "http.h"
38 #define BLOCK 2048
40 extern int debug;
43 * Ture if src is a header. This is just a basic check
44 * for the colon delimiter. Might eventually become more
45 * sophisticated. :)
47 int is_http_header(const char *src) {
48 return strcspn(src, ":") != strlen(src);
52 * Extract the header name from the source.
54 char *get_http_header_name(const char *src) {
55 int i;
57 i = strcspn(src, ":");
58 if (i != strlen(src))
59 return substr(src, 0, i);
60 else
61 return NULL;
65 * Extract the header value from the source.
67 char *get_http_header_value(const char *src) {
68 char *sub;
70 if ((sub = strchr(src, ':'))) {
71 sub++;
72 while (*sub == ' ')
73 sub++;
75 return strdup(sub);
76 } else
77 return NULL;
81 * Receive HTTP request/response from the given socket. Fill in pre-allocated
82 * rr_data_t structure.
83 * Returns: 1 if OK, 0 in case of socket EOF or other error
85 int headers_recv(int fd, rr_data_t data) {
86 int i, bsize;
87 int len;
88 char *buf;
89 char *tok, *s3 = 0;
90 char *orig = NULL;
91 char *ccode = NULL;
92 char *host = NULL;
94 bsize = BUFSIZE;
95 buf = new(bsize);
97 i = so_recvln(fd, &buf, &bsize);
98 if (i <= 0)
99 goto bailout;
101 if (debug)
102 printf("HEAD: %s", buf);
105 * Are we reading HTTP request (from client) or response (from server)?
107 trimr(buf);
108 orig = strdup(buf);
109 len = strlen(buf);
110 tok = strtok_r(buf, " ", &s3);
111 if (tok && (!strncasecmp(buf, "HTTP/", 5) || !strncasecmp(tok, "ICY", 3))) {
112 data->req = 0;
113 data->empty = 0;
114 data->http = strdup(tok);
115 data->msg = NULL;
117 tok = strtok_r(NULL, " ", &s3);
118 if (tok) {
119 ccode = strdup(tok);
121 tok += strlen(ccode);
122 while (tok < buf+len && *tok++ == ' ');
124 if (strlen(tok))
125 data->msg = strdup(tok);
128 if (!data->msg)
129 data->msg = strdup("");
131 if (!ccode || strlen(ccode) != 3 || (data->code = atoi(ccode)) == 0) {
132 i = -2;
133 goto bailout;
135 } else if (strstr(orig, " HTTP/") && tok) {
136 data->req = 1;
137 data->empty = 0;
138 data->method = NULL;
139 data->url = NULL;
140 data->rel_url = NULL;
141 data->http = NULL;
142 data->hostname = NULL;
144 data->method = strdup(tok);
146 tok = strtok_r(NULL, " ", &s3);
147 if (tok)
148 data->url = strdup(tok);
150 tok = strtok_r(NULL, " ", &s3);
151 if (tok)
152 data->http = strdup(tok);
154 if (!data->url || !data->http) {
155 i = -3;
156 goto bailout;
159 if ((tok = strstr(data->url, "://"))) {
160 tok += 3;
161 } else {
162 tok = data->url;
165 s3 = strchr(tok, '/');
166 if (s3) {
167 host = substr(tok, 0, s3-tok);
168 data->rel_url = strdup(s3);
169 } else {
170 host = substr(tok, 0, strlen(tok));
171 data->rel_url = strdup("/");
174 } else {
175 if (debug)
176 printf("headers_recv: Unknown header (%s).\n", orig);
177 i = -4;
178 goto bailout;
182 * Read in all headers, do not touch any possible HTTP body
184 do {
185 i = so_recvln(fd, &buf, &bsize);
186 trimr(buf);
187 if (i > 0 && is_http_header(buf)) {
188 data->headers = hlist_add(data->headers, get_http_header_name(buf), get_http_header_value(buf), HLIST_NOALLOC, HLIST_NOALLOC);
190 } while (strlen(buf) != 0 && i > 0);
192 if (data->req) {
194 * Fix requests, make sure the Host: header is present
196 if (host && strlen(host)) {
197 data->hostname = strdup(host);
198 if (!hlist_get(data->headers, "Host"))
199 data->headers = hlist_add(data->headers, "Host", host, HLIST_ALLOC, HLIST_ALLOC);
200 } else {
201 if (debug)
202 printf("headers_recv: no host name (%s)\n", orig);
203 i = -6;
204 goto bailout;
208 * Remove port number from internal host name variable
210 if (data->hostname && (tok = strchr(data->hostname, ':'))) {
211 *tok = 0;
212 data->port = atoi(tok+1);
213 } else if (data->url) {
214 if (!strncasecmp(data->url, "https", 5))
215 data->port = 443;
216 else
217 data->port = 80;
220 if (!strlen(data->hostname) || !data->port) {
221 i = -5;
222 goto bailout;
226 bailout:
227 if (orig) free(orig);
228 if (ccode) free(ccode);
229 if (host) free(host);
230 free(buf);
232 if (i <= 0) {
233 if (debug)
234 printf("headers_recv: fd %d error %d\n", fd, i);
235 return 0;
238 return 1;
242 * Send HTTP request/response to the given socket based on what's in "data".
243 * Returns: 1 if OK, 0 in case of socket error
245 int headers_send(int fd, rr_data_t data) {
246 hlist_t t;
247 char *buf;
248 int i, len;
251 * First compute required buffer size (avoid realloc, etc)
253 if (data->req)
254 len = 20 + strlen(data->method) + strlen(data->url) + strlen(data->http);
255 else
256 len = 20 + strlen(data->http) + strlen(data->msg);
258 t = data->headers;
259 while (t) {
260 len += 20 + strlen(t->key) + strlen(t->value);
261 t = t->next;
265 * We know how much memory we need now...
267 buf = new(len);
270 * Prepare the first request/response line
272 len = 0;
273 if (data->req)
274 len = sprintf(buf, "%s %s %s\r\n", data->method, data->url, data->http);
275 else if (!data->skip_http)
276 len = sprintf(buf, "%s %03d %s\r\n", data->http, data->code, data->msg);
279 * Now add all headers.
281 t = data->headers;
282 while (t) {
283 len += sprintf(buf+len, "%s: %s\r\n", t->key, t->value);
284 t = t->next;
288 * Terminate headers
290 strcat(buf, "\r\n");
293 * Flush it all down the toilet
295 if (!so_closed(fd))
296 i = write(fd, buf, len+2);
297 else
298 i = -999;
300 free(buf);
302 if (i <= 0 || i != len+2) {
303 if (debug)
304 printf("headers_send: fd %d warning %d (connection closed)\n", fd, i);
305 return 0;
308 return 1;
312 * Forward "size" of data from "src" to "dst". If size == -1 then keep
313 * forwarding until src reaches EOF.
314 * If dst == -1, data is discarded.
316 int data_send(int dst, int src, length_t len) {
317 char *buf;
318 int i, block;
319 int c = 0;
320 int j = 1;
322 if (!len)
323 return 1;
325 buf = new(BLOCK);
327 do {
328 block = (len == -1 || len-c > BLOCK ? BLOCK : len-c);
329 i = read(src, buf, block);
331 if (i > 0)
332 c += i;
334 if (dst >= 0 && debug)
335 printf("data_send: read %d of %d / %d of %lld (errno = %s)\n", i, block, c, len, i < 0 ? strerror(errno) : "ok");
337 if (dst >= 0 && so_closed(dst)) {
338 i = -999;
339 break;
342 if (dst >= 0 && i > 0) {
343 j = write(dst, buf, i);
344 if (debug)
345 printf("data_send: wrote %d of %d\n", j, i);
348 } while (i > 0 && j > 0 && (len == -1 || c < len));
350 free(buf);
352 if (i <= 0 || j <= 0) {
353 if (i == 0 && j > 0 && (len == -1 || c == len))
354 return 1;
356 if (debug)
357 printf("data_send: fds %d:%d warning %d (connection closed)\n", dst, src, i);
358 return 0;
361 return 1;
365 * Forward chunked HTTP body from "src" descriptor to "dst".
366 * If dst == -1, data is discarded.
368 int chunked_data_send(int dst, int src) {
369 char *buf;
370 int bsize;
371 int i, w, csize;
373 char *err = NULL;
375 bsize = BUFSIZE;
376 buf = new(bsize);
378 /* Take care of all chunks */
379 do {
380 i = so_recvln(src, &buf, &bsize);
381 if (i <= 0) {
382 if (debug)
383 printf("chunked_data_send: aborting, read error\n");
384 free(buf);
385 return 0;
388 csize = strtol(buf, &err, 16);
390 if (!isspace(*err) && *err != ';') {
391 if (debug)
392 printf("chunked_data_send: aborting, chunk size format error\n");
393 free(buf);
394 return 0;
397 if (dst >= 0)
398 i = write(dst, buf, strlen(buf));
400 if (csize)
401 if (!data_send(dst, src, csize+2)) {
402 if (debug)
403 printf("chunked_data_send: aborting, data_send failed\n");
405 free(buf);
406 return 0;
408 } while (csize != 0);
410 /* Take care of possible trailer */
411 do {
412 i = so_recvln(src, &buf, &bsize);
413 if (dst >= 0 && i > 0)
414 w = write(dst, buf, strlen(buf));
415 } while (i > 0 && buf[0] != '\r' && buf[0] != '\n');
417 free(buf);
418 return 1;
422 * Full-duplex forwarding between proxy and client descriptors.
423 * Used for bidirectional HTTP CONNECT connection.
425 int tunnel(int cd, int sd) {
426 fd_set set;
427 int from, to, ret, sel;
428 char *buf;
430 buf = new(BUFSIZE);
432 if (debug)
433 printf("tunnel: select cli: %d, srv: %d\n", cd, sd);
435 do {
436 FD_ZERO(&set);
437 FD_SET(cd, &set);
438 FD_SET(sd, &set);
440 sel = select(FD_SETSIZE, &set, NULL, NULL, NULL);
441 if (sel > 0) {
442 if (FD_ISSET(cd, &set)) {
443 from = cd;
444 to = sd;
445 } else {
446 from = sd;
447 to = cd;
450 ret = read(from, buf, BUFSIZE);
451 if (ret > 0) {
452 ret = write(to, buf, ret);
453 } else {
454 free(buf);
455 return (ret == 0);
457 } else if (sel < 0) {
458 free(buf);
459 return 0;
461 } while (1);
463 free(buf);
464 return 1;
468 * Return 0 if no body, -1 if body until EOF, number if size known
469 * One of request/response can be NULL
471 length_t http_has_body(rr_data_t request, rr_data_t response) {
472 rr_data_t current;
473 length_t length;
474 int nobody;
475 char *tmp;
478 * Are we checking a complete req+res conversation or just the
479 * request body?
481 current = (!response || response->empty ? request : response);
484 * HTTP body length decisions. There MUST NOT be any body from
485 * server if the request was HEAD or reply is 1xx, 204 or 304.
486 * No body can be in GET request if direction is from client.
488 if (current == response) {
489 nobody = (HEAD(request) ||
490 (response->code >= 100 && response->code < 200) ||
491 response->code == 204 ||
492 response->code == 304);
493 } else {
494 nobody = GET(request) || HEAD(request);
498 * Otherwise consult Content-Length. If present, we forward exaclty
499 * that many bytes.
501 * If not present, but there is Transfer-Encoding or Content-Type
502 * (or a request to close connection, that is, end of data is signaled
503 * by remote close), we will forward until EOF.
505 * No C-L, no T-E, no C-T == no body.
507 tmp = hlist_get(current->headers, "Content-Length");
508 if (!nobody && tmp == NULL && (hlist_in(current->headers, "Content-Type")
509 || hlist_in(current->headers, "Transfer-Encoding")
510 || hlist_subcmp(current->headers, "Connection", "close"))) {
511 // || (response->code == 200)
512 if (hlist_in(current->headers, "Transfer-Encoding")
513 && hlist_subcmp(current->headers, "Transfer-Encoding", "chunked"))
514 length = 1;
515 else
516 length = -1;
517 } else
518 length = (tmp == NULL || nobody ? 0 : atoll(tmp));
520 if (current == request && length == -1)
521 length = 0;
523 return length;
527 * Send a HTTP body (if any) between descriptors readfd and writefd
529 int http_body_send(int writefd, int readfd, rr_data_t request, rr_data_t response) {
530 length_t bodylen;
531 int rc = 1;
532 rr_data_t current;
535 * Are we checking a complete req+res conversation or just the
536 * request body?
538 current = (response->empty ? request : response);
541 * Ok, so do we expect any body?
543 bodylen = http_has_body(request, response);
544 if (bodylen) {
546 * Check for supported T-E.
548 if (hlist_subcmp(current->headers, "Transfer-Encoding", "chunked")) {
549 if (debug)
550 printf("Chunked body included.\n");
552 rc = chunked_data_send(writefd, readfd);
553 if (debug)
554 printf(rc ? "Chunked body sent.\n" : "Could not chunk send whole body\n");
555 } else {
556 if (debug)
557 printf("Body included. Length: %lld\n", bodylen);
559 rc = data_send(writefd, readfd, bodylen);
560 if (debug)
561 printf(rc ? "Body sent.\n" : "Could not send whole body\n");
563 } else if (debug)
564 printf("No body.\n");
566 return rc;
570 * Connection cleanup - C-L or chunked body
571 * Return 0 if connection closed or EOF, 1 if OK to continue
573 int http_body_drop(int fd, rr_data_t response) {
574 length_t bodylen;
575 int rc = 1;
577 bodylen = http_has_body(NULL, response);
578 if (bodylen) {
579 if (hlist_subcmp(response->headers, "Transfer-Encoding", "chunked")) {
580 if (debug)
581 printf("Discarding chunked body.\n");
582 rc = chunked_data_send(-1, fd);
583 } else {
584 if (debug)
585 printf("Discarding %lld bytes.\n", bodylen);
586 rc = data_send(-1, fd, bodylen);
590 return rc;
594 * Parse headers for BASIC auth credentials
596 * Return 1 = creds parsed OK, 0 = no creds, -1 = invalid creds
598 int http_parse_basic(hlist_t headers, const char *header, struct auth_s *tcreds) {
599 char *tmp = NULL, *pos = NULL, *buf = NULL, *dom = NULL;
600 int i;
602 if (!hlist_subcmp(headers, header, "basic"))
603 return 0;
605 tmp = hlist_get(headers, header);
606 buf = new(strlen(tmp) + 1);
607 i = 5;
608 while (i < strlen(tmp) && tmp[++i] == ' ');
609 from_base64(buf, tmp+i);
610 pos = strchr(buf, ':');
612 if (pos == NULL) {
613 memset(buf, 0, strlen(buf)); /* clean password memory */
614 free(buf);
615 return -1;
616 } else {
617 *pos = 0;
618 dom = strchr(buf, '\\');
619 if (dom == NULL) {
620 auth_strcpy(tcreds, user, buf);
621 } else {
622 *dom = 0;
623 auth_strcpy(tcreds, domain, buf);
624 auth_strcpy(tcreds, user, dom+1);
627 if (tcreds->hashntlm2) {
628 tmp = ntlm2_hash_password(tcreds->user, tcreds->domain, pos+1);
629 auth_memcpy(tcreds, passntlm2, tmp, 16);
630 free(tmp);
633 if (tcreds->hashnt) {
634 tmp = ntlm_hash_nt_password(pos+1);
635 auth_memcpy(tcreds, passnt, tmp, 21);
636 free(tmp);
639 if (tcreds->hashlm) {
640 tmp = ntlm_hash_lm_password(pos+1);
641 auth_memcpy(tcreds, passlm, tmp, 21);
642 free(tmp);
645 memset(buf, 0, strlen(buf));
646 free(buf);
649 return 1;