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
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
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>
43 * Ture if src is a header. This is just a basic check
44 * for the colon delimiter. Might eventually become more
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
) {
57 i
= strcspn(src
, ":");
59 return substr(src
, 0, i
);
65 * Extract the header value from the source.
67 char *get_http_header_value(const char *src
) {
70 if ((sub
= strchr(src
, ':'))) {
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
) {
97 i
= so_recvln(fd
, &buf
, &bsize
);
102 printf("HEAD: %s", buf
);
105 * Are we reading HTTP request (from client) or response (from server)?
110 tok
= strtok_r(buf
, " ", &s3
);
111 if (tok
&& (!strncasecmp(buf
, "HTTP/", 5) || !strncasecmp(tok
, "ICY", 3))) {
114 data
->http
= strdup(tok
);
117 tok
= strtok_r(NULL
, " ", &s3
);
121 tok
+= strlen(ccode
);
122 while (tok
< buf
+len
&& *tok
++ == ' ');
125 data
->msg
= strdup(tok
);
129 data
->msg
= strdup("");
131 if (!ccode
|| strlen(ccode
) != 3 || (data
->code
= atoi(ccode
)) == 0) {
135 } else if (strstr(orig
, " HTTP/") && tok
) {
140 data
->rel_url
= NULL
;
142 data
->hostname
= NULL
;
144 data
->method
= strdup(tok
);
146 tok
= strtok_r(NULL
, " ", &s3
);
148 data
->url
= strdup(tok
);
150 tok
= strtok_r(NULL
, " ", &s3
);
152 data
->http
= strdup(tok
);
154 if (!data
->url
|| !data
->http
) {
159 if ((tok
= strstr(data
->url
, "://"))) {
165 s3
= strchr(tok
, '/');
167 host
= substr(tok
, 0, s3
-tok
);
168 data
->rel_url
= strdup(s3
);
170 host
= substr(tok
, 0, strlen(tok
));
171 data
->rel_url
= strdup("/");
176 printf("headers_recv: Unknown header (%s).\n", orig
);
182 * Read in all headers, do not touch any possible HTTP body
185 i
= so_recvln(fd
, &buf
, &bsize
);
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);
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
);
202 printf("headers_recv: no host name (%s)\n", orig
);
208 * Remove port number from internal host name variable
210 if (data
->hostname
&& (tok
= strchr(data
->hostname
, ':'))) {
212 data
->port
= atoi(tok
+1);
213 } else if (data
->url
) {
214 if (!strncasecmp(data
->url
, "https", 5))
220 if (!strlen(data
->hostname
) || !data
->port
) {
227 if (orig
) free(orig
);
228 if (ccode
) free(ccode
);
229 if (host
) free(host
);
234 printf("headers_recv: fd %d error %d\n", fd
, i
);
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
) {
251 * First compute required buffer size (avoid realloc, etc)
254 len
= 20 + strlen(data
->method
) + strlen(data
->url
) + strlen(data
->http
);
256 len
= 20 + strlen(data
->http
) + strlen(data
->msg
);
260 len
+= 20 + strlen(t
->key
) + strlen(t
->value
);
265 * We know how much memory we need now...
270 * Prepare the first request/response line
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.
283 len
+= sprintf(buf
+len
, "%s: %s\r\n", t
->key
, t
->value
);
293 * Flush it all down the toilet
296 i
= write(fd
, buf
, len
+2);
302 if (i
<= 0 || i
!= len
+2) {
304 printf("headers_send: fd %d warning %d (connection closed)\n", fd
, i
);
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
) {
328 block
= (len
== -1 || len
-c
> BLOCK
? BLOCK
: len
-c
);
329 i
= read(src
, buf
, block
);
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
)) {
342 if (dst
>= 0 && i
> 0) {
343 j
= write(dst
, buf
, i
);
345 printf("data_send: wrote %d of %d\n", j
, i
);
348 } while (i
> 0 && j
> 0 && (len
== -1 || c
< len
));
352 if (i
<= 0 || j
<= 0) {
353 if (i
== 0 && j
> 0 && (len
== -1 || c
== len
))
357 printf("data_send: fds %d:%d warning %d (connection closed)\n", dst
, src
, i
);
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
) {
378 /* Take care of all chunks */
380 i
= so_recvln(src
, &buf
, &bsize
);
383 printf("chunked_data_send: aborting, read error\n");
388 csize
= strtol(buf
, &err
, 16);
390 if (!isspace(*err
) && *err
!= ';') {
392 printf("chunked_data_send: aborting, chunk size format error\n");
398 i
= write(dst
, buf
, strlen(buf
));
401 if (!data_send(dst
, src
, csize
+2)) {
403 printf("chunked_data_send: aborting, data_send failed\n");
408 } while (csize
!= 0);
410 /* Take care of possible trailer */
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');
422 * Full-duplex forwarding between proxy and client descriptors.
423 * Used for bidirectional HTTP CONNECT connection.
425 int tunnel(int cd
, int sd
) {
427 int from
, to
, ret
, sel
;
433 printf("tunnel: select cli: %d, srv: %d\n", cd
, sd
);
440 sel
= select(FD_SETSIZE
, &set
, NULL
, NULL
, NULL
);
442 if (FD_ISSET(cd
, &set
)) {
450 ret
= read(from
, buf
, BUFSIZE
);
452 ret
= write(to
, buf
, ret
);
457 } else if (sel
< 0) {
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
) {
478 * Are we checking a complete req+res conversation or just the
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);
494 nobody
= GET(request
) || HEAD(request
);
498 * Otherwise consult Content-Length. If present, we forward exaclty
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"))
518 length
= (tmp
== NULL
|| nobody
? 0 : atoll(tmp
));
520 if (current
== request
&& length
== -1)
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
) {
535 * Are we checking a complete req+res conversation or just the
538 current
= (response
->empty
? request
: response
);
541 * Ok, so do we expect any body?
543 bodylen
= http_has_body(request
, response
);
546 * Check for supported T-E.
548 if (hlist_subcmp(current
->headers
, "Transfer-Encoding", "chunked")) {
550 printf("Chunked body included.\n");
552 rc
= chunked_data_send(writefd
, readfd
);
554 printf(rc
? "Chunked body sent.\n" : "Could not chunk send whole body\n");
557 printf("Body included. Length: %lld\n", bodylen
);
559 rc
= data_send(writefd
, readfd
, bodylen
);
561 printf(rc
? "Body sent.\n" : "Could not send whole body\n");
564 printf("No body.\n");
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
) {
577 bodylen
= http_has_body(NULL
, response
);
579 if (hlist_subcmp(response
->headers
, "Transfer-Encoding", "chunked")) {
581 printf("Discarding chunked body.\n");
582 rc
= chunked_data_send(-1, fd
);
585 printf("Discarding %lld bytes.\n", bodylen
);
586 rc
= data_send(-1, fd
, bodylen
);
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
;
602 if (!hlist_subcmp(headers
, header
, "basic"))
605 tmp
= hlist_get(headers
, header
);
606 buf
= new(strlen(tmp
) + 1);
608 while (i
< strlen(tmp
) && tmp
[++i
] == ' ');
609 from_base64(buf
, tmp
+i
);
610 pos
= strchr(buf
, ':');
613 memset(buf
, 0, strlen(buf
)); /* clean password memory */
618 dom
= strchr(buf
, '\\');
620 auth_strcpy(tcreds
, user
, buf
);
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);
633 if (tcreds
->hashnt
) {
634 tmp
= ntlm_hash_nt_password(pos
+1);
635 auth_memcpy(tcreds
, passnt
, tmp
, 21);
639 if (tcreds
->hashlm
) {
640 tmp
= ntlm_hash_lm_password(pos
+1);
641 auth_memcpy(tcreds
, passlm
, tmp
, 21);
645 memset(buf
, 0, strlen(buf
));