1*b5fa5d51Sclaudio /* $OpenBSD: http.c,v 1.92 2024/11/21 13:32:27 claudio Exp $ */ 21ef5b48aSclaudio /* 31ef5b48aSclaudio * Copyright (c) 2020 Nils Fisher <nils_fisher@hotmail.com> 4c0c0266aSclaudio * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org> 51ef5b48aSclaudio * 61ef5b48aSclaudio * Permission to use, copy, modify, and distribute this software for any 71ef5b48aSclaudio * purpose with or without fee is hereby granted, provided that the above 81ef5b48aSclaudio * copyright notice and this permission notice appear in all copies. 91ef5b48aSclaudio * 101ef5b48aSclaudio * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 111ef5b48aSclaudio * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 121ef5b48aSclaudio * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 131ef5b48aSclaudio * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 141ef5b48aSclaudio * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 151ef5b48aSclaudio * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 161ef5b48aSclaudio * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 171ef5b48aSclaudio */ 181ef5b48aSclaudio 191ef5b48aSclaudio /*- 201ef5b48aSclaudio * Copyright (c) 1997 The NetBSD Foundation, Inc. 211ef5b48aSclaudio * All rights reserved. 221ef5b48aSclaudio * 231ef5b48aSclaudio * This code is derived from software contributed to The NetBSD Foundation 241ef5b48aSclaudio * by Jason Thorpe and Luke Mewburn. 251ef5b48aSclaudio * 261ef5b48aSclaudio * Redistribution and use in source and binary forms, with or without 271ef5b48aSclaudio * modification, are permitted provided that the following conditions 281ef5b48aSclaudio * are met: 291ef5b48aSclaudio * 1. Redistributions of source code must retain the above copyright 301ef5b48aSclaudio * notice, this list of conditions and the following disclaimer. 311ef5b48aSclaudio * 2. Redistributions in binary form must reproduce the above copyright 321ef5b48aSclaudio * notice, this list of conditions and the following disclaimer in the 331ef5b48aSclaudio * documentation and/or other materials provided with the distribution. 341ef5b48aSclaudio * 351ef5b48aSclaudio * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 361ef5b48aSclaudio * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 371ef5b48aSclaudio * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 381ef5b48aSclaudio * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 391ef5b48aSclaudio * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 401ef5b48aSclaudio * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 411ef5b48aSclaudio * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 421ef5b48aSclaudio * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 431ef5b48aSclaudio * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 441ef5b48aSclaudio * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 451ef5b48aSclaudio * POSSIBILITY OF SUCH DAMAGE. 461ef5b48aSclaudio */ 471ef5b48aSclaudio #include <sys/types.h> 481ef5b48aSclaudio #include <sys/queue.h> 491ef5b48aSclaudio #include <sys/socket.h> 501ef5b48aSclaudio 5175c55a07Sclaudio #include <assert.h> 521ef5b48aSclaudio #include <ctype.h> 531ef5b48aSclaudio #include <err.h> 541ef5b48aSclaudio #include <errno.h> 5578bab303Sclaudio #include <imsg.h> 561ef5b48aSclaudio #include <limits.h> 571ef5b48aSclaudio #include <netdb.h> 581ef5b48aSclaudio #include <poll.h> 590ce80415Sclaudio #include <signal.h> 601ef5b48aSclaudio #include <stdio.h> 611ef5b48aSclaudio #include <stdlib.h> 621ef5b48aSclaudio #include <string.h> 631ef5b48aSclaudio #include <unistd.h> 641ef5b48aSclaudio #include <vis.h> 6578bab303Sclaudio #include <zlib.h> 661ef5b48aSclaudio 671ef5b48aSclaudio #include <tls.h> 681ef5b48aSclaudio 691ef5b48aSclaudio #include "extern.h" 701ef5b48aSclaudio 711ef5b48aSclaudio #define HTTP_USER_AGENT "OpenBSD rpki-client" 721ef5b48aSclaudio #define HTTP_BUF_SIZE (32 * 1024) 7375c55a07Sclaudio #define HTTP_IDLE_TIMEOUT 10 74af6f8e1cSclaudio #define MAX_CONTENTLEN (2 * 1024 * 1024 * 1024UL) 756e7e5289Sclaudio #define NPFDS (MAX_HTTP_REQUESTS + 1) 761ef5b48aSclaudio 7775c55a07Sclaudio enum res { 7875c55a07Sclaudio DONE, 7975c55a07Sclaudio WANT_POLLIN, 8075c55a07Sclaudio WANT_POLLOUT, 8175c55a07Sclaudio }; 821ef5b48aSclaudio 831ef5b48aSclaudio enum http_state { 841ef5b48aSclaudio STATE_FREE, 851ef5b48aSclaudio STATE_CONNECT, 861ef5b48aSclaudio STATE_TLSCONNECT, 876f704872Sclaudio STATE_PROXY_REQUEST, 886f704872Sclaudio STATE_PROXY_STATUS, 896f704872Sclaudio STATE_PROXY_RESPONSE, 901ef5b48aSclaudio STATE_REQUEST, 911ef5b48aSclaudio STATE_RESPONSE_STATUS, 921ef5b48aSclaudio STATE_RESPONSE_HEADER, 931ef5b48aSclaudio STATE_RESPONSE_DATA, 9475c55a07Sclaudio STATE_RESPONSE_CHUNKED_HEADER, 95fd522c34Sclaudio STATE_RESPONSE_CHUNKED_CRLF, 9675c55a07Sclaudio STATE_RESPONSE_CHUNKED_TRAILER, 971ef5b48aSclaudio STATE_WRITE_DATA, 9875c55a07Sclaudio STATE_IDLE, 9975c55a07Sclaudio STATE_CLOSE, 1001ef5b48aSclaudio }; 1011ef5b48aSclaudio 1021ef5b48aSclaudio struct http_proxy { 1031ef5b48aSclaudio char *proxyhost; 1046f704872Sclaudio char *proxyport; 1056f704872Sclaudio char *proxyauth; 1066f704872Sclaudio } proxy; 1071ef5b48aSclaudio 10878bab303Sclaudio struct http_zlib { 10978bab303Sclaudio z_stream zs; 11078bab303Sclaudio char *zbuf; 11178bab303Sclaudio size_t zbufsz; 11278bab303Sclaudio size_t zbufpos; 11378bab303Sclaudio size_t zinsz; 11478bab303Sclaudio int zdone; 11578bab303Sclaudio }; 11678bab303Sclaudio 1171ef5b48aSclaudio struct http_connection { 11875c55a07Sclaudio LIST_ENTRY(http_connection) entry; 1191ef5b48aSclaudio char *host; 1201ef5b48aSclaudio char *port; 1211ef5b48aSclaudio char *last_modified; 12275c55a07Sclaudio char *redir_uri; 12375c55a07Sclaudio struct http_request *req; 12475c55a07Sclaudio struct pollfd *pfd; 1251ef5b48aSclaudio struct addrinfo *res0; 1261ef5b48aSclaudio struct addrinfo *res; 1271ef5b48aSclaudio struct tls *tls; 1281ef5b48aSclaudio char *buf; 12978bab303Sclaudio struct http_zlib *zlibctx; 1301ef5b48aSclaudio size_t bufsz; 1311ef5b48aSclaudio size_t bufpos; 132af6f8e1cSclaudio size_t iosz; 133af6f8e1cSclaudio size_t totalsz; 13475c55a07Sclaudio time_t idle_time; 1356f704872Sclaudio time_t io_time; 1361ef5b48aSclaudio int status; 1371ef5b48aSclaudio int fd; 13875c55a07Sclaudio int chunked; 13978bab303Sclaudio int gzipped; 14075c55a07Sclaudio int keep_alive; 1411ef5b48aSclaudio short events; 1421ef5b48aSclaudio enum http_state state; 1431ef5b48aSclaudio }; 1441ef5b48aSclaudio 14575c55a07Sclaudio LIST_HEAD(http_conn_list, http_connection); 1461ef5b48aSclaudio 14775c55a07Sclaudio struct http_request { 14875c55a07Sclaudio TAILQ_ENTRY(http_request) entry; 14975c55a07Sclaudio char *uri; 15075c55a07Sclaudio char *modified_since; 15175c55a07Sclaudio char *host; 15275c55a07Sclaudio char *port; 15375c55a07Sclaudio const char *path; /* points into uri */ 154b6884e9fSclaudio unsigned int id; 15575c55a07Sclaudio int outfd; 15675c55a07Sclaudio int redirect_loop; 15775c55a07Sclaudio }; 15875c55a07Sclaudio 15975c55a07Sclaudio TAILQ_HEAD(http_req_queue, http_request); 16075c55a07Sclaudio 16175c55a07Sclaudio static struct http_conn_list active = LIST_HEAD_INITIALIZER(active); 16275c55a07Sclaudio static struct http_conn_list idle = LIST_HEAD_INITIALIZER(idle); 16375c55a07Sclaudio static struct http_req_queue queue = TAILQ_HEAD_INITIALIZER(queue); 164b6884e9fSclaudio static unsigned int http_conn_count; 16575c55a07Sclaudio 16625d36c5cSclaudio static struct msgbuf *msgq; 16775c55a07Sclaudio static struct sockaddr_storage http_bindaddr; 16875c55a07Sclaudio static struct tls_config *tls_config; 16975c55a07Sclaudio static uint8_t *tls_ca_mem; 17075c55a07Sclaudio static size_t tls_ca_size; 17175c55a07Sclaudio 17275c55a07Sclaudio /* HTTP request API */ 173b6884e9fSclaudio static void http_req_new(unsigned int, char *, char *, int, int); 17475c55a07Sclaudio static void http_req_free(struct http_request *); 175b6884e9fSclaudio static void http_req_done(unsigned int, enum http_result, const char *); 176b6884e9fSclaudio static void http_req_fail(unsigned int); 17775c55a07Sclaudio static int http_req_schedule(struct http_request *); 17875c55a07Sclaudio 17978bab303Sclaudio /* HTTP decompression helper */ 18078bab303Sclaudio static int http_inflate_new(struct http_connection *); 18178bab303Sclaudio static void http_inflate_free(struct http_connection *); 18278bab303Sclaudio static void http_inflate_done(struct http_connection *); 18378bab303Sclaudio static int http_inflate_data(struct http_connection *); 18478bab303Sclaudio static enum res http_inflate_advance(struct http_connection *); 18578bab303Sclaudio 18675c55a07Sclaudio /* HTTP connection API */ 18775c55a07Sclaudio static void http_new(struct http_request *); 188c8a1112eSclaudio static void http_free(struct http_connection *); 189c8a1112eSclaudio 19075c55a07Sclaudio static enum res http_done(struct http_connection *, enum http_result); 19175c55a07Sclaudio static enum res http_failed(struct http_connection *); 19275c55a07Sclaudio 19375c55a07Sclaudio /* HTTP connection FSM functions */ 19475c55a07Sclaudio static void http_do(struct http_connection *, 19575c55a07Sclaudio enum res (*)(struct http_connection *)); 19675c55a07Sclaudio 19775c55a07Sclaudio /* These functions can be used with http_do() */ 19875c55a07Sclaudio static enum res http_connect(struct http_connection *); 19975c55a07Sclaudio static enum res http_request(struct http_connection *); 20075c55a07Sclaudio static enum res http_close(struct http_connection *); 20175c55a07Sclaudio static enum res http_handle(struct http_connection *); 20275c55a07Sclaudio 20375c55a07Sclaudio /* Internal state functions used by the above functions */ 20475c55a07Sclaudio static enum res http_finish_connect(struct http_connection *); 2056f704872Sclaudio static enum res proxy_connect(struct http_connection *); 20675c55a07Sclaudio static enum res http_tls_connect(struct http_connection *); 20775c55a07Sclaudio static enum res http_tls_handshake(struct http_connection *); 20875c55a07Sclaudio static enum res http_read(struct http_connection *); 20975c55a07Sclaudio static enum res http_write(struct http_connection *); 2106f704872Sclaudio static enum res proxy_read(struct http_connection *); 2116f704872Sclaudio static enum res proxy_write(struct http_connection *); 21275c55a07Sclaudio static enum res data_write(struct http_connection *); 21378bab303Sclaudio static enum res data_inflate_write(struct http_connection *); 21475c55a07Sclaudio 2151ef5b48aSclaudio /* 2161ef5b48aSclaudio * Return a string that can be used in error message to identify the 2171ef5b48aSclaudio * connection. 2181ef5b48aSclaudio */ 2191ef5b48aSclaudio static const char * 22075c55a07Sclaudio http_info(const char *uri) 2211ef5b48aSclaudio { 222760e3f75Sclaudio static char buf[80]; 2231ef5b48aSclaudio 22475c55a07Sclaudio if (strnvis(buf, uri, sizeof buf, VIS_SAFE) >= (int)sizeof buf) { 2251ef5b48aSclaudio /* overflow, add indicator */ 2261ef5b48aSclaudio memcpy(buf + sizeof buf - 4, "...", 4); 2271ef5b48aSclaudio } 2281ef5b48aSclaudio 2291ef5b48aSclaudio return buf; 2301ef5b48aSclaudio } 2311ef5b48aSclaudio 2321ef5b48aSclaudio /* 233310fe3abSjob * Return IP address in presentation format. 234310fe3abSjob */ 235310fe3abSjob static const char * 236310fe3abSjob ip_info(const struct http_connection *conn) 237310fe3abSjob { 238eee9c28cSclaudio static char ipbuf[NI_MAXHOST]; 239310fe3abSjob 240310fe3abSjob if (conn->res == NULL) 241eee9c28cSclaudio return "unknown"; 242310fe3abSjob 243310fe3abSjob if (getnameinfo(conn->res->ai_addr, conn->res->ai_addrlen, ipbuf, 244310fe3abSjob sizeof(ipbuf), NULL, 0, NI_NUMERICHOST) != 0) 245eee9c28cSclaudio return "unknown"; 246310fe3abSjob 247eee9c28cSclaudio return ipbuf; 248eee9c28cSclaudio } 249310fe3abSjob 250eee9c28cSclaudio static const char * 251eee9c28cSclaudio conn_info(const struct http_connection *conn) 252eee9c28cSclaudio { 253eee9c28cSclaudio static char buf[100 + NI_MAXHOST]; 254eee9c28cSclaudio const char *uri; 255eee9c28cSclaudio 256eee9c28cSclaudio if (conn->req == NULL) 257eee9c28cSclaudio uri = conn->host; 258eee9c28cSclaudio else 259eee9c28cSclaudio uri = conn->req->uri; 260eee9c28cSclaudio 261eee9c28cSclaudio snprintf(buf, sizeof(buf), "%s (%s)", http_info(uri), ip_info(conn)); 262310fe3abSjob return buf; 263310fe3abSjob } 264310fe3abSjob 265310fe3abSjob /* 26664247d16Sclaudio * Determine whether the character needs encoding, per RFC2396. 2671ef5b48aSclaudio */ 2681ef5b48aSclaudio static int 26964247d16Sclaudio to_encode(const char *c0) 2701ef5b48aSclaudio { 27164247d16Sclaudio /* 2.4.3. Excluded US-ASCII Characters */ 27264247d16Sclaudio const char *excluded_chars = 27364247d16Sclaudio " " /* space */ 27464247d16Sclaudio "<>#\"" /* delims (modulo "%", see below) */ 27564247d16Sclaudio "{}|\\^[]`" /* unwise */ 27664247d16Sclaudio ; 2771ef5b48aSclaudio const unsigned char *c = (const unsigned char *)c0; 2781ef5b48aSclaudio 2791ef5b48aSclaudio /* 2801ef5b48aSclaudio * No corresponding graphic US-ASCII. 2811ef5b48aSclaudio * Control characters and octets not used in US-ASCII. 2821ef5b48aSclaudio */ 2831ef5b48aSclaudio return (iscntrl(*c) || !isascii(*c) || 2841ef5b48aSclaudio 2851ef5b48aSclaudio /* 28664247d16Sclaudio * '%' is also reserved, if is not followed by two 2871ef5b48aSclaudio * hexadecimal digits. 2881ef5b48aSclaudio */ 28964247d16Sclaudio strchr(excluded_chars, *c) != NULL || 2907810f2e0Sderaadt (*c == '%' && (!isxdigit(c[1]) || !isxdigit(c[2])))); 2911ef5b48aSclaudio } 2921ef5b48aSclaudio 2931ef5b48aSclaudio /* 29464247d16Sclaudio * Encode given URL, per RFC2396. 2951ef5b48aSclaudio * Allocate and return string to the caller. 2961ef5b48aSclaudio */ 2971ef5b48aSclaudio static char * 2981ef5b48aSclaudio url_encode(const char *path) 2991ef5b48aSclaudio { 3001ef5b48aSclaudio size_t i, length, new_length; 3011ef5b48aSclaudio char *epath, *epathp; 3021ef5b48aSclaudio 3031ef5b48aSclaudio length = new_length = strlen(path); 3041ef5b48aSclaudio 3051ef5b48aSclaudio /* 3061ef5b48aSclaudio * First pass: 3071ef5b48aSclaudio * Count unsafe characters, and determine length of the 3081ef5b48aSclaudio * final URL. 3091ef5b48aSclaudio */ 3101ef5b48aSclaudio for (i = 0; i < length; i++) 31164247d16Sclaudio if (to_encode(path + i)) 3121ef5b48aSclaudio new_length += 2; 3131ef5b48aSclaudio 3141ef5b48aSclaudio epath = epathp = malloc(new_length + 1); /* One more for '\0'. */ 3151ef5b48aSclaudio if (epath == NULL) 3161ef5b48aSclaudio err(1, NULL); 3171ef5b48aSclaudio 3181ef5b48aSclaudio /* 3191ef5b48aSclaudio * Second pass: 3201ef5b48aSclaudio * Encode, and copy final URL. 3211ef5b48aSclaudio */ 3221ef5b48aSclaudio for (i = 0; i < length; i++) 32364247d16Sclaudio if (to_encode(path + i)) { 3241ef5b48aSclaudio snprintf(epathp, 4, "%%" "%02x", 3251ef5b48aSclaudio (unsigned char)path[i]); 3261ef5b48aSclaudio epathp += 3; 3271ef5b48aSclaudio } else 3281ef5b48aSclaudio *(epathp++) = path[i]; 3291ef5b48aSclaudio 3301ef5b48aSclaudio *epathp = '\0'; 3311ef5b48aSclaudio return (epath); 3321ef5b48aSclaudio } 3331ef5b48aSclaudio 3346f704872Sclaudio static char 3356f704872Sclaudio hextochar(const char *str) 3366f704872Sclaudio { 3376f704872Sclaudio unsigned char c, ret; 3386f704872Sclaudio 3396f704872Sclaudio c = str[0]; 3406f704872Sclaudio ret = c; 3416f704872Sclaudio if (isalpha(c)) 3426f704872Sclaudio ret -= isupper(c) ? 'A' - 10 : 'a' - 10; 3436f704872Sclaudio else 3446f704872Sclaudio ret -= '0'; 3456f704872Sclaudio ret *= 16; 3466f704872Sclaudio 3476f704872Sclaudio c = str[1]; 3486f704872Sclaudio ret += c; 3496f704872Sclaudio if (isalpha(c)) 3506f704872Sclaudio ret -= isupper(c) ? 'A' - 10 : 'a' - 10; 3516f704872Sclaudio else 3526f704872Sclaudio ret -= '0'; 3536f704872Sclaudio return ret; 3546f704872Sclaudio } 3556f704872Sclaudio 3566f704872Sclaudio static char * 3576f704872Sclaudio url_decode(const char *str) 3586f704872Sclaudio { 3596f704872Sclaudio char *ret, c; 3606f704872Sclaudio int i, reallen; 3616f704872Sclaudio 3626f704872Sclaudio if (str == NULL) 3636f704872Sclaudio return NULL; 3646f704872Sclaudio if ((ret = malloc(strlen(str) + 1)) == NULL) 3656f704872Sclaudio err(1, "Can't allocate memory for URL decoding"); 3666f704872Sclaudio for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) { 3676f704872Sclaudio c = str[i]; 3686f704872Sclaudio if (c == '+') { 3696f704872Sclaudio *ret = ' '; 3706f704872Sclaudio continue; 3716f704872Sclaudio } 3726f704872Sclaudio /* 3736f704872Sclaudio * Cannot use strtol here because next char 3746f704872Sclaudio * after %xx may be a digit. 3756f704872Sclaudio */ 3766f704872Sclaudio if (c == '%' && isxdigit((unsigned char)str[i + 1]) && 3776f704872Sclaudio isxdigit((unsigned char)str[i + 2])) { 3786f704872Sclaudio *ret = hextochar(&str[i + 1]); 3796f704872Sclaudio i += 2; 3806f704872Sclaudio continue; 3816f704872Sclaudio } 3826f704872Sclaudio *ret = c; 3836f704872Sclaudio } 3846f704872Sclaudio *ret = '\0'; 3856f704872Sclaudio return ret - reallen; 3866f704872Sclaudio } 3876f704872Sclaudio 3886f704872Sclaudio static char * 3896f704872Sclaudio recode_credentials(const char *userinfo) 3906f704872Sclaudio { 3916f704872Sclaudio char *ui, *creds; 3926f704872Sclaudio size_t ulen; 3936f704872Sclaudio 3946f704872Sclaudio /* url-decode the user and pass */ 3956f704872Sclaudio ui = url_decode(userinfo); 3966f704872Sclaudio 3976f704872Sclaudio ulen = strlen(ui); 3986f704872Sclaudio if (base64_encode(ui, ulen, &creds) == -1) 3996f704872Sclaudio errx(1, "error in base64 encoding"); 4006f704872Sclaudio free(ui); 4016f704872Sclaudio return (creds); 4026f704872Sclaudio } 4036f704872Sclaudio 4046f704872Sclaudio /* 4056f704872Sclaudio * Parse a proxy URI and split it up into host, port and userinfo. 4066f704872Sclaudio */ 4076f704872Sclaudio static void 4086f704872Sclaudio proxy_parse_uri(char *uri) 4096f704872Sclaudio { 410fe8e8d49Stb char *fullhost, *host, *port = NULL, *cred, *cookie = NULL; 4116f704872Sclaudio 4126f704872Sclaudio if (uri == NULL) 4136f704872Sclaudio return; 4146f704872Sclaudio 4150610060dSjob if (strncasecmp(uri, HTTP_PROTO, HTTP_PROTO_LEN) != 0) 4166f704872Sclaudio errx(1, "%s: http_proxy not using http schema", http_info(uri)); 4176f704872Sclaudio 418b4964d69Stb host = uri + HTTP_PROTO_LEN; 419fe8e8d49Stb if ((fullhost = strndup(host, strcspn(host, "/"))) == NULL) 4206f704872Sclaudio err(1, NULL); 4216f704872Sclaudio 422fe8e8d49Stb cred = fullhost; 4236f704872Sclaudio host = strchr(cred, '@'); 4246f704872Sclaudio if (host != NULL) 4256f704872Sclaudio *host++ = '\0'; 4266f704872Sclaudio else { 4276f704872Sclaudio host = cred; 4286f704872Sclaudio cred = NULL; 4296f704872Sclaudio } 4306f704872Sclaudio 4316f704872Sclaudio if (*host == '[') { 4326f704872Sclaudio char *hosttail; 4336f704872Sclaudio 4346f704872Sclaudio if ((hosttail = strrchr(host, ']')) == NULL) 4356f704872Sclaudio errx(1, "%s: unmatched opening bracket", 4366f704872Sclaudio http_info(uri)); 4376f704872Sclaudio if (hosttail[1] == '\0' || hosttail[1] == ':') 4386f704872Sclaudio host++; 4396f704872Sclaudio if (hosttail[1] == ':') 4406f704872Sclaudio port = hosttail + 2; 4416f704872Sclaudio *hosttail = '\0'; 4426f704872Sclaudio } else { 4436f704872Sclaudio if ((port = strrchr(host, ':')) != NULL) 4446f704872Sclaudio *port++ = '\0'; 4456f704872Sclaudio } 4466f704872Sclaudio 4476f704872Sclaudio if (port == NULL) 4486f704872Sclaudio port = "443"; 4496f704872Sclaudio 4506f704872Sclaudio if (cred != NULL) { 4516f704872Sclaudio if (strchr(cred, ':') == NULL) 4526f704872Sclaudio errx(1, "%s: malformed proxy url", http_info(uri)); 4536f704872Sclaudio cred = recode_credentials(cred); 4546f704872Sclaudio if (asprintf(&cookie, "Proxy-Authorization: Basic %s\r\n", 4556f704872Sclaudio cred) == -1) 4566f704872Sclaudio err(1, NULL); 4576f704872Sclaudio free(cred); 4586f704872Sclaudio } else 4596f704872Sclaudio if ((cookie = strdup("")) == NULL) 4606f704872Sclaudio err(1, NULL); 4616f704872Sclaudio 462fe8e8d49Stb if ((proxy.proxyhost = strdup(host)) == NULL) 463fe8e8d49Stb err(1, NULL); 464ec013d45Sclaudio if ((proxy.proxyport = strdup(port)) == NULL) 465ec013d45Sclaudio err(1, NULL); 4666f704872Sclaudio proxy.proxyauth = cookie; 467fe8e8d49Stb 468fe8e8d49Stb free(fullhost); 4696f704872Sclaudio } 4706f704872Sclaudio 47175c55a07Sclaudio /* 47275c55a07Sclaudio * Parse a URI and split it up into host, port and path. 47375c55a07Sclaudio * Does some basic URI validation. Both host and port need to be freed 47475c55a07Sclaudio * by the caller whereas path points into the uri. 47575c55a07Sclaudio */ 4761ef5b48aSclaudio static int 4771ef5b48aSclaudio http_parse_uri(char *uri, char **ohost, char **oport, char **opath) 4781ef5b48aSclaudio { 4791ef5b48aSclaudio char *host, *port = NULL, *path; 4801ef5b48aSclaudio char *hosttail; 4811ef5b48aSclaudio 4820610060dSjob if (strncasecmp(uri, HTTPS_PROTO, HTTPS_PROTO_LEN) != 0) { 4831ef5b48aSclaudio warnx("%s: not using https schema", http_info(uri)); 4841ef5b48aSclaudio return -1; 4851ef5b48aSclaudio } 486b4964d69Stb host = uri + HTTPS_PROTO_LEN; 4871ef5b48aSclaudio if ((path = strchr(host, '/')) == NULL) { 4881ef5b48aSclaudio warnx("%s: missing https path", http_info(uri)); 4891ef5b48aSclaudio return -1; 4901ef5b48aSclaudio } 4911ef5b48aSclaudio if (path - uri > INT_MAX - 1) { 4921ef5b48aSclaudio warnx("%s: preposterous host length", http_info(uri)); 4931ef5b48aSclaudio return -1; 4941ef5b48aSclaudio } 4956f704872Sclaudio 4966f704872Sclaudio if (memchr(host, '@', path - host) != NULL) { 4976f704872Sclaudio warnx("%s: URI with userinfo not supported", http_info(uri)); 4986f704872Sclaudio return -1; 4996f704872Sclaudio } 5006f704872Sclaudio 5011ef5b48aSclaudio if (*host == '[') { 502cf09f547Stb if ((hosttail = memrchr(host, ']', path - host)) == NULL) { 503cf09f547Stb warnx("%s: unmatched opening bracket", http_info(uri)); 504cf09f547Stb return -1; 505cf09f547Stb } 506cf09f547Stb if (hosttail[1] == '/' || hosttail[1] == ':') 5071ef5b48aSclaudio host++; 5081ef5b48aSclaudio if (hosttail[1] == ':') 509b4b44febStb port = hosttail + 2; 5101ef5b48aSclaudio } else { 5111ef5b48aSclaudio if ((hosttail = memrchr(host, ':', path - host)) != NULL) 5121ef5b48aSclaudio port = hosttail + 1; 5131ef5b48aSclaudio else 5141ef5b48aSclaudio hosttail = path; 5151ef5b48aSclaudio } 5161ef5b48aSclaudio 5171ef5b48aSclaudio if ((host = strndup(host, hosttail - host)) == NULL) 5186735b9d8Sclaudio err(1, NULL); 5191ef5b48aSclaudio if (port != NULL) { 5201ef5b48aSclaudio if ((port = strndup(port, path - port)) == NULL) 5216735b9d8Sclaudio err(1, NULL); 5221ef5b48aSclaudio } else { 5231ef5b48aSclaudio if ((port = strdup("443")) == NULL) 5246735b9d8Sclaudio err(1, NULL); 5251ef5b48aSclaudio } 5261ef5b48aSclaudio /* do not include the initial / in path */ 5271ef5b48aSclaudio path++; 5281ef5b48aSclaudio 5291ef5b48aSclaudio *ohost = host; 5301ef5b48aSclaudio *oport = port; 5311ef5b48aSclaudio *opath = path; 5321ef5b48aSclaudio 5331ef5b48aSclaudio return 0; 5341ef5b48aSclaudio } 5351ef5b48aSclaudio 53675c55a07Sclaudio /* 53775c55a07Sclaudio * Lookup the IP addresses for host:port. 53875c55a07Sclaudio * Returns 0 on success and -1 on failure. 53975c55a07Sclaudio */ 540c8a1112eSclaudio static int 54175c55a07Sclaudio http_resolv(struct addrinfo **res, const char *host, const char *port) 542c8a1112eSclaudio { 543c8a1112eSclaudio struct addrinfo hints; 544c8a1112eSclaudio int error; 545c8a1112eSclaudio 546c8a1112eSclaudio memset(&hints, 0, sizeof(hints)); 547c8a1112eSclaudio hints.ai_family = PF_UNSPEC; 548c8a1112eSclaudio hints.ai_socktype = SOCK_STREAM; 54975c55a07Sclaudio error = getaddrinfo(host, port, &hints, res); 550c8a1112eSclaudio /* 551c8a1112eSclaudio * If the services file is corrupt/missing, fall back 552c8a1112eSclaudio * on our hard-coded defines. 553c8a1112eSclaudio */ 554c8a1112eSclaudio if (error == EAI_SERVICE) 55575c55a07Sclaudio error = getaddrinfo(host, "443", &hints, res); 556c8a1112eSclaudio if (error != 0) { 557c8a1112eSclaudio warnx("%s: %s", host, gai_strerror(error)); 558c8a1112eSclaudio return -1; 559c8a1112eSclaudio } 560c8a1112eSclaudio 561c8a1112eSclaudio return 0; 562c8a1112eSclaudio } 563c8a1112eSclaudio 56475c55a07Sclaudio /* 56575c55a07Sclaudio * Create and queue a new request. 56675c55a07Sclaudio */ 567c8a1112eSclaudio static void 568b6884e9fSclaudio http_req_new(unsigned int id, char *uri, char *modified_since, int count, 569b6884e9fSclaudio int outfd) 57075c55a07Sclaudio { 57175c55a07Sclaudio struct http_request *req; 57275c55a07Sclaudio char *host, *port, *path; 57375c55a07Sclaudio 57475c55a07Sclaudio if (http_parse_uri(uri, &host, &port, &path) == -1) { 57575c55a07Sclaudio free(uri); 57675c55a07Sclaudio free(modified_since); 57775c55a07Sclaudio close(outfd); 57875c55a07Sclaudio http_req_fail(id); 57975c55a07Sclaudio return; 58075c55a07Sclaudio } 58175c55a07Sclaudio 58275c55a07Sclaudio if ((req = calloc(1, sizeof(*req))) == NULL) 58375c55a07Sclaudio err(1, NULL); 58475c55a07Sclaudio 58575c55a07Sclaudio req->id = id; 58675c55a07Sclaudio req->outfd = outfd; 58775c55a07Sclaudio req->host = host; 58875c55a07Sclaudio req->port = port; 58975c55a07Sclaudio req->path = path; 59075c55a07Sclaudio req->uri = uri; 59175c55a07Sclaudio req->modified_since = modified_since; 59206792369Sclaudio req->redirect_loop = count; 59375c55a07Sclaudio 59475c55a07Sclaudio TAILQ_INSERT_TAIL(&queue, req, entry); 59575c55a07Sclaudio } 59675c55a07Sclaudio 59775c55a07Sclaudio /* 59875c55a07Sclaudio * Free a request, request is not allowed to be on the req queue. 59975c55a07Sclaudio */ 60075c55a07Sclaudio static void 60175c55a07Sclaudio http_req_free(struct http_request *req) 60275c55a07Sclaudio { 60375c55a07Sclaudio if (req == NULL) 60475c55a07Sclaudio return; 60575c55a07Sclaudio 60675c55a07Sclaudio free(req->host); 60775c55a07Sclaudio free(req->port); 60875c55a07Sclaudio /* no need to free req->path it points into req->uri */ 60975c55a07Sclaudio free(req->uri); 61075c55a07Sclaudio free(req->modified_since); 61175c55a07Sclaudio 61275c55a07Sclaudio if (req->outfd != -1) 61375c55a07Sclaudio close(req->outfd); 61475c55a07Sclaudio } 61575c55a07Sclaudio 61675c55a07Sclaudio /* 61775c55a07Sclaudio * Enqueue request response 61875c55a07Sclaudio */ 61975c55a07Sclaudio static void 620b6884e9fSclaudio http_req_done(unsigned int id, enum http_result res, const char *last_modified) 621c8a1112eSclaudio { 622c8a1112eSclaudio struct ibuf *b; 623c8a1112eSclaudio 62425f7afeeSclaudio b = io_new_buffer(); 625c8a1112eSclaudio io_simple_buffer(b, &id, sizeof(id)); 626c8a1112eSclaudio io_simple_buffer(b, &res, sizeof(res)); 627c8a1112eSclaudio io_str_buffer(b, last_modified); 62825d36c5cSclaudio io_close_buffer(msgq, b); 629c8a1112eSclaudio } 630c8a1112eSclaudio 63175c55a07Sclaudio /* 63275c55a07Sclaudio * Enqueue request failure response 63375c55a07Sclaudio */ 634c8a1112eSclaudio static void 635b6884e9fSclaudio http_req_fail(unsigned int id) 636c8a1112eSclaudio { 637c8a1112eSclaudio struct ibuf *b; 638c8a1112eSclaudio enum http_result res = HTTP_FAILED; 639c8a1112eSclaudio 64025f7afeeSclaudio b = io_new_buffer(); 641c8a1112eSclaudio io_simple_buffer(b, &id, sizeof(id)); 642c8a1112eSclaudio io_simple_buffer(b, &res, sizeof(res)); 643c8a1112eSclaudio io_str_buffer(b, NULL); 64425d36c5cSclaudio io_close_buffer(msgq, b); 645c8a1112eSclaudio } 6461ef5b48aSclaudio 64775c55a07Sclaudio /* 64875c55a07Sclaudio * Schedule new requests until maximum number of connections is reached. 64975c55a07Sclaudio * Try to reuse an idle connection if one exists that matches host and port. 65075c55a07Sclaudio */ 65175c55a07Sclaudio static int 65275c55a07Sclaudio http_req_schedule(struct http_request *req) 6531ef5b48aSclaudio { 6541ef5b48aSclaudio struct http_connection *conn; 6551ef5b48aSclaudio 65675c55a07Sclaudio TAILQ_REMOVE(&queue, req, entry); 65775c55a07Sclaudio 65875c55a07Sclaudio /* check list of idle connections first */ 65975c55a07Sclaudio LIST_FOREACH(conn, &idle, entry) { 66075c55a07Sclaudio if (strcmp(conn->host, req->host) != 0) 66175c55a07Sclaudio continue; 66275c55a07Sclaudio if (strcmp(conn->port, req->port) != 0) 66375c55a07Sclaudio continue; 66475c55a07Sclaudio 66575c55a07Sclaudio LIST_REMOVE(conn, entry); 66675c55a07Sclaudio LIST_INSERT_HEAD(&active, conn, entry); 66775c55a07Sclaudio 66875c55a07Sclaudio /* use established connection */ 66975c55a07Sclaudio conn->req = req; 67075c55a07Sclaudio conn->idle_time = 0; 67175c55a07Sclaudio 67275c55a07Sclaudio /* start request */ 67375c55a07Sclaudio http_do(conn, http_request); 67475c55a07Sclaudio if (conn->state == STATE_FREE) 67575c55a07Sclaudio http_free(conn); 67675c55a07Sclaudio return 1; 6771ef5b48aSclaudio } 6781ef5b48aSclaudio 6796e7e5289Sclaudio if (http_conn_count < MAX_HTTP_REQUESTS) { 68075c55a07Sclaudio http_new(req); 68175c55a07Sclaudio return 1; 68275c55a07Sclaudio } 68375c55a07Sclaudio 68475c55a07Sclaudio /* no more slots free, requeue */ 68575c55a07Sclaudio TAILQ_INSERT_HEAD(&queue, req, entry); 68675c55a07Sclaudio return 0; 68775c55a07Sclaudio } 68875c55a07Sclaudio 68975c55a07Sclaudio /* 69078bab303Sclaudio * Allocate everything to allow inline decompression during write out. 69178bab303Sclaudio * Returns 0 on success, -1 on failure. 69278bab303Sclaudio */ 69378bab303Sclaudio static int 69478bab303Sclaudio http_inflate_new(struct http_connection *conn) 69578bab303Sclaudio { 69678bab303Sclaudio struct http_zlib *zctx; 69778bab303Sclaudio 69878bab303Sclaudio if (conn->zlibctx != NULL) 69978bab303Sclaudio return 0; 70078bab303Sclaudio 70178bab303Sclaudio if ((zctx = calloc(1, sizeof(*zctx))) == NULL) 70278bab303Sclaudio goto fail; 70378bab303Sclaudio zctx->zbufsz = HTTP_BUF_SIZE; 70478bab303Sclaudio if ((zctx->zbuf = malloc(zctx->zbufsz)) == NULL) 70578bab303Sclaudio goto fail; 70678bab303Sclaudio if (inflateInit2(&zctx->zs, MAX_WBITS + 32) != Z_OK) 70778bab303Sclaudio goto fail; 70878bab303Sclaudio conn->zlibctx = zctx; 70978bab303Sclaudio return 0; 71078bab303Sclaudio 71178bab303Sclaudio fail: 712f452fe1cStb warnx("%s: decompression initialisation failed", conn_info(conn)); 71378bab303Sclaudio if (zctx != NULL) 71478bab303Sclaudio free(zctx->zbuf); 71578bab303Sclaudio free(zctx); 71678bab303Sclaudio return -1; 71778bab303Sclaudio } 71878bab303Sclaudio 71978bab303Sclaudio /* Free all memory used by the decompression API */ 72078bab303Sclaudio static void 72178bab303Sclaudio http_inflate_free(struct http_connection *conn) 72278bab303Sclaudio { 72378bab303Sclaudio if (conn->zlibctx == NULL) 72478bab303Sclaudio return; 72578bab303Sclaudio inflateEnd(&conn->zlibctx->zs); 72678bab303Sclaudio free(conn->zlibctx->zbuf); 72778bab303Sclaudio free(conn->zlibctx); 72878bab303Sclaudio conn->zlibctx = NULL; 72978bab303Sclaudio } 73078bab303Sclaudio 73178bab303Sclaudio /* Reset the decompression state to allow a new request to use it */ 73278bab303Sclaudio static void 73378bab303Sclaudio http_inflate_done(struct http_connection *conn) 73478bab303Sclaudio { 73578bab303Sclaudio if (inflateReset(&conn->zlibctx->zs) != Z_OK) 73678bab303Sclaudio http_inflate_free(conn); 73778bab303Sclaudio } 73878bab303Sclaudio 73978bab303Sclaudio /* 74078bab303Sclaudio * Inflate the data from conn->buf into zctx->zbuf. The number of bytes 74178bab303Sclaudio * available in zctx->zbuf is stored in zctx->zbufpos. 74278bab303Sclaudio * Returns -1 on failure. 74378bab303Sclaudio */ 74478bab303Sclaudio static int 74578bab303Sclaudio http_inflate_data(struct http_connection *conn) 74678bab303Sclaudio { 74778bab303Sclaudio struct http_zlib *zctx = conn->zlibctx; 74878bab303Sclaudio size_t bsz = conn->bufpos; 74978bab303Sclaudio int rv; 75078bab303Sclaudio 751af6f8e1cSclaudio if (conn->iosz < bsz) 75278bab303Sclaudio bsz = conn->iosz; 75378bab303Sclaudio 75478bab303Sclaudio zctx->zdone = 0; 75578bab303Sclaudio zctx->zbufpos = 0; 75678bab303Sclaudio zctx->zinsz = bsz; 75778bab303Sclaudio zctx->zs.next_in = conn->buf; 75878bab303Sclaudio zctx->zs.avail_in = bsz; 75978bab303Sclaudio zctx->zs.next_out = zctx->zbuf; 76078bab303Sclaudio zctx->zs.avail_out = zctx->zbufsz; 76178bab303Sclaudio 76278bab303Sclaudio switch ((rv = inflate(&zctx->zs, Z_NO_FLUSH))) { 76378bab303Sclaudio case Z_OK: 76478bab303Sclaudio break; 76578bab303Sclaudio case Z_STREAM_END: 76678bab303Sclaudio zctx->zdone = 1; 76778bab303Sclaudio break; 76878bab303Sclaudio default: 76978bab303Sclaudio if (zctx->zs.msg != NULL) 77078bab303Sclaudio warnx("%s: inflate failed: %s", conn_info(conn), 77178bab303Sclaudio zctx->zs.msg); 77278bab303Sclaudio else 77378bab303Sclaudio warnx("%s: inflate failed error %d", conn_info(conn), 77478bab303Sclaudio rv); 77578bab303Sclaudio return -1; 77678bab303Sclaudio } 77778bab303Sclaudio 77878bab303Sclaudio /* calculate how much can be written out */ 77978bab303Sclaudio zctx->zbufpos = zctx->zbufsz - zctx->zs.avail_out; 78078bab303Sclaudio return 0; 78178bab303Sclaudio } 78278bab303Sclaudio 78378bab303Sclaudio /* 78478bab303Sclaudio * Advance the input buffer after the output buffer has been fully written. 78578bab303Sclaudio * If compression is done finish the transaction else read more data. 78678bab303Sclaudio */ 78778bab303Sclaudio static enum res 78878bab303Sclaudio http_inflate_advance(struct http_connection *conn) 78978bab303Sclaudio { 79078bab303Sclaudio struct http_zlib *zctx = conn->zlibctx; 79178bab303Sclaudio size_t bsz = zctx->zinsz - zctx->zs.avail_in; 79278bab303Sclaudio 79378bab303Sclaudio /* adjust compressed input buffer */ 79478bab303Sclaudio conn->bufpos -= bsz; 79578bab303Sclaudio conn->iosz -= bsz; 79678bab303Sclaudio memmove(conn->buf, conn->buf + bsz, conn->bufpos); 79778bab303Sclaudio 79878bab303Sclaudio if (zctx->zdone) { 79978bab303Sclaudio /* all compressed data processed */ 80078bab303Sclaudio conn->gzipped = 0; 80178bab303Sclaudio http_inflate_done(conn); 80278bab303Sclaudio 80378bab303Sclaudio if (conn->iosz == 0) { 80478bab303Sclaudio if (!conn->chunked) { 80578bab303Sclaudio return http_done(conn, HTTP_OK); 80678bab303Sclaudio } else { 80778bab303Sclaudio conn->state = STATE_RESPONSE_CHUNKED_CRLF; 80878bab303Sclaudio return http_read(conn); 80978bab303Sclaudio } 81078bab303Sclaudio } else { 81178bab303Sclaudio warnx("%s: inflate extra data after end", 81278bab303Sclaudio conn_info(conn)); 81378bab303Sclaudio return http_failed(conn); 81478bab303Sclaudio } 81578bab303Sclaudio } 81678bab303Sclaudio 81778bab303Sclaudio if (conn->chunked && conn->iosz == 0) 81878bab303Sclaudio conn->state = STATE_RESPONSE_CHUNKED_CRLF; 81978bab303Sclaudio else 82078bab303Sclaudio conn->state = STATE_RESPONSE_DATA; 82178bab303Sclaudio return http_read(conn); 82278bab303Sclaudio } 82378bab303Sclaudio 82478bab303Sclaudio /* 82575c55a07Sclaudio * Create a new HTTP connection which will be used for the HTTP request req. 826f452fe1cStb * On errors a req failure is issued and both connection and request are freed. 82775c55a07Sclaudio */ 82875c55a07Sclaudio static void 82975c55a07Sclaudio http_new(struct http_request *req) 83075c55a07Sclaudio { 83175c55a07Sclaudio struct http_connection *conn; 83275c55a07Sclaudio 8331ef5b48aSclaudio if ((conn = calloc(1, sizeof(*conn))) == NULL) 8341ef5b48aSclaudio err(1, NULL); 8351ef5b48aSclaudio 8361ef5b48aSclaudio conn->fd = -1; 83775c55a07Sclaudio conn->req = req; 83875c55a07Sclaudio if ((conn->host = strdup(req->host)) == NULL) 83975c55a07Sclaudio err(1, NULL); 84075c55a07Sclaudio if ((conn->port = strdup(req->port)) == NULL) 84175c55a07Sclaudio err(1, NULL); 84275c55a07Sclaudio 84375c55a07Sclaudio LIST_INSERT_HEAD(&active, conn, entry); 84475c55a07Sclaudio http_conn_count++; 8451ef5b48aSclaudio 8462d3967cbSclaudio if (proxy.proxyhost != NULL) { 8476f704872Sclaudio if (http_resolv(&conn->res0, proxy.proxyhost, 8486f704872Sclaudio proxy.proxyport) == -1) { 8496f704872Sclaudio http_req_fail(req->id); 8506f704872Sclaudio http_free(conn); 8516f704872Sclaudio return; 8526f704872Sclaudio } 8536f704872Sclaudio } else { 85475c55a07Sclaudio if (http_resolv(&conn->res0, conn->host, conn->port) == -1) { 85575c55a07Sclaudio http_req_fail(req->id); 8561ef5b48aSclaudio http_free(conn); 85775c55a07Sclaudio return; 8581ef5b48aSclaudio } 8596f704872Sclaudio } 8601ef5b48aSclaudio 86175c55a07Sclaudio /* connect and start request */ 86275c55a07Sclaudio http_do(conn, http_connect); 86375c55a07Sclaudio if (conn->state == STATE_FREE) 86475c55a07Sclaudio http_free(conn); 8651ef5b48aSclaudio } 8661ef5b48aSclaudio 86775c55a07Sclaudio /* 86875c55a07Sclaudio * Free a no longer active connection, releasing all memory and closing 86975c55a07Sclaudio * any open file descriptor. 87075c55a07Sclaudio */ 871c8a1112eSclaudio static void 872c8a1112eSclaudio http_free(struct http_connection *conn) 873c8a1112eSclaudio { 87475c55a07Sclaudio assert(conn->state == STATE_FREE); 87575c55a07Sclaudio 87675c55a07Sclaudio LIST_REMOVE(conn, entry); 87775c55a07Sclaudio http_conn_count--; 87875c55a07Sclaudio 87975c55a07Sclaudio http_req_free(conn->req); 88078bab303Sclaudio http_inflate_free(conn); 881c8a1112eSclaudio free(conn->host); 882c8a1112eSclaudio free(conn->port); 883c8a1112eSclaudio free(conn->last_modified); 88475c55a07Sclaudio free(conn->redir_uri); 885c8a1112eSclaudio free(conn->buf); 886c8a1112eSclaudio 887c8a1112eSclaudio if (conn->res0 != NULL) 888c8a1112eSclaudio freeaddrinfo(conn->res0); 889c8a1112eSclaudio 890c8a1112eSclaudio tls_free(conn->tls); 891c8a1112eSclaudio 892c8a1112eSclaudio if (conn->fd != -1) 893c8a1112eSclaudio close(conn->fd); 894c8a1112eSclaudio free(conn); 895c8a1112eSclaudio } 896c8a1112eSclaudio 89775c55a07Sclaudio /* 89875c55a07Sclaudio * Called when a request on this connection is finished. 89975c55a07Sclaudio * Move connection into idle state and onto idle queue. 90075c55a07Sclaudio * If there is a request connected to it send back a response 90175c55a07Sclaudio * with http_result res, else ignore the res. 90275c55a07Sclaudio */ 90375c55a07Sclaudio static enum res 90475c55a07Sclaudio http_done(struct http_connection *conn, enum http_result res) 90575c55a07Sclaudio { 90675c55a07Sclaudio assert(conn->bufpos == 0); 90775c55a07Sclaudio assert(conn->iosz == 0); 90875c55a07Sclaudio assert(conn->chunked == 0); 90975c55a07Sclaudio assert(conn->redir_uri == NULL); 910c8a1112eSclaudio 91178bab303Sclaudio if (conn->gzipped) { 91278bab303Sclaudio conn->gzipped = 0; 91378bab303Sclaudio http_inflate_done(conn); 91478bab303Sclaudio } 91578bab303Sclaudio 91675c55a07Sclaudio conn->state = STATE_IDLE; 91775c55a07Sclaudio conn->idle_time = getmonotime() + HTTP_IDLE_TIMEOUT; 91875c55a07Sclaudio 91975c55a07Sclaudio if (conn->req) { 92075c55a07Sclaudio http_req_done(conn->req->id, res, conn->last_modified); 92175c55a07Sclaudio http_req_free(conn->req); 92275c55a07Sclaudio conn->req = NULL; 92375c55a07Sclaudio } 92475c55a07Sclaudio 92575c55a07Sclaudio if (!conn->keep_alive) 92675c55a07Sclaudio return http_close(conn); 92775c55a07Sclaudio 92875c55a07Sclaudio LIST_REMOVE(conn, entry); 92975c55a07Sclaudio LIST_INSERT_HEAD(&idle, conn, entry); 93075c55a07Sclaudio 93175c55a07Sclaudio /* reset status and keep-alive for good measures */ 93275c55a07Sclaudio conn->status = 0; 93375c55a07Sclaudio conn->keep_alive = 0; 93475c55a07Sclaudio 93575c55a07Sclaudio return WANT_POLLIN; 93675c55a07Sclaudio } 93775c55a07Sclaudio 93875c55a07Sclaudio /* 93975c55a07Sclaudio * Called in case of error, moves connection into free state. 94075c55a07Sclaudio * This will skip proper shutdown of the TLS session. 94175c55a07Sclaudio * If a request is pending fail and free the request. 94275c55a07Sclaudio */ 94375c55a07Sclaudio static enum res 94475c55a07Sclaudio http_failed(struct http_connection *conn) 94575c55a07Sclaudio { 94675c55a07Sclaudio conn->state = STATE_FREE; 94775c55a07Sclaudio 94875c55a07Sclaudio if (conn->req) { 94975c55a07Sclaudio http_req_fail(conn->req->id); 95075c55a07Sclaudio http_req_free(conn->req); 95175c55a07Sclaudio conn->req = NULL; 95275c55a07Sclaudio } 95375c55a07Sclaudio 95475c55a07Sclaudio return DONE; 95575c55a07Sclaudio } 95675c55a07Sclaudio 95775c55a07Sclaudio /* 9589170c2daSclaudio * Called in case of connect timeout, try an alternate connection. 9599170c2daSclaudio */ 9609170c2daSclaudio static enum res 9619170c2daSclaudio http_connect_failed(struct http_connection *conn) 9629170c2daSclaudio { 9639170c2daSclaudio assert(conn->state == STATE_CONNECT); 9649170c2daSclaudio close(conn->fd); 9659170c2daSclaudio conn->fd = -1; 9669170c2daSclaudio 9679170c2daSclaudio return http_connect(conn); 9689170c2daSclaudio } 9699170c2daSclaudio 9709170c2daSclaudio /* 97175c55a07Sclaudio * Call the function f and update the connection events based 97275c55a07Sclaudio * on the return value. 97375c55a07Sclaudio */ 97475c55a07Sclaudio static void 97575c55a07Sclaudio http_do(struct http_connection *conn, enum res (*f)(struct http_connection *)) 97675c55a07Sclaudio { 97775c55a07Sclaudio switch (f(conn)) { 97875c55a07Sclaudio case DONE: 97975c55a07Sclaudio conn->events = 0; 98075c55a07Sclaudio break; 98175c55a07Sclaudio case WANT_POLLIN: 98275c55a07Sclaudio conn->events = POLLIN; 98375c55a07Sclaudio break; 98475c55a07Sclaudio case WANT_POLLOUT: 98575c55a07Sclaudio conn->events = POLLOUT; 98675c55a07Sclaudio break; 98775c55a07Sclaudio default: 988eee9c28cSclaudio errx(1, "%s: unexpected function return", conn_info(conn)); 98975c55a07Sclaudio } 99075c55a07Sclaudio } 99175c55a07Sclaudio 99275c55a07Sclaudio /* 9936f704872Sclaudio * Connection successfully establish, initiate TLS handshake or proxy request. 99475c55a07Sclaudio */ 99575c55a07Sclaudio static enum res 996bc49575fSclaudio http_connect_done(struct http_connection *conn) 997bc49575fSclaudio { 9982d3967cbSclaudio if (proxy.proxyhost != NULL) 9996f704872Sclaudio return proxy_connect(conn); 100075c55a07Sclaudio return http_tls_connect(conn); 1001bc49575fSclaudio } 1002bc49575fSclaudio 100375c55a07Sclaudio /* 100475c55a07Sclaudio * Start an asynchronous connect. 100575c55a07Sclaudio */ 100675c55a07Sclaudio static enum res 10071ef5b48aSclaudio http_connect(struct http_connection *conn) 10081ef5b48aSclaudio { 10090abf3cc6Sclaudio const char *cause = NULL; 1010eee9c28cSclaudio struct addrinfo *res; 10111ef5b48aSclaudio 101275c55a07Sclaudio assert(conn->fd == -1); 1013f12b699fSclaudio conn->state = STATE_CONNECT; 1014f12b699fSclaudio 10151ef5b48aSclaudio /* start the loop below with first or next address */ 10161ef5b48aSclaudio if (conn->res == NULL) 10171ef5b48aSclaudio conn->res = conn->res0; 10181ef5b48aSclaudio else 10191ef5b48aSclaudio conn->res = conn->res->ai_next; 10201ef5b48aSclaudio for (; conn->res != NULL; conn->res = conn->res->ai_next) { 10210abf3cc6Sclaudio int fd, save_errno; 10221ef5b48aSclaudio 1023eee9c28cSclaudio res = conn->res; 10241ef5b48aSclaudio fd = socket(res->ai_family, 10251ef5b48aSclaudio res->ai_socktype | SOCK_NONBLOCK, res->ai_protocol); 10261ef5b48aSclaudio if (fd == -1) { 10271ef5b48aSclaudio cause = "socket"; 10281ef5b48aSclaudio continue; 10291ef5b48aSclaudio } 10301ef5b48aSclaudio conn->fd = fd; 10311ef5b48aSclaudio 10321ef5b48aSclaudio if (http_bindaddr.ss_family == res->ai_family) { 10331ef5b48aSclaudio if (bind(conn->fd, (struct sockaddr *)&http_bindaddr, 1034dc8fd1cbSclaudio res->ai_addrlen) == -1) { 1035dc8fd1cbSclaudio save_errno = errno; 1036dc8fd1cbSclaudio close(conn->fd); 1037dc8fd1cbSclaudio conn->fd = -1; 1038dc8fd1cbSclaudio errno = save_errno; 1039dc8fd1cbSclaudio cause = "bind"; 1040dc8fd1cbSclaudio continue; 1041dc8fd1cbSclaudio } 10421ef5b48aSclaudio } 10431ef5b48aSclaudio 10440abf3cc6Sclaudio if (connect(conn->fd, res->ai_addr, res->ai_addrlen) == -1) { 10451ef5b48aSclaudio if (errno == EINPROGRESS) { 10460abf3cc6Sclaudio /* wait for async connect to finish. */ 10471ef5b48aSclaudio return WANT_POLLOUT; 10481ef5b48aSclaudio } else { 10491ef5b48aSclaudio save_errno = errno; 10501ef5b48aSclaudio close(conn->fd); 10511ef5b48aSclaudio conn->fd = -1; 10521ef5b48aSclaudio errno = save_errno; 10531ef5b48aSclaudio cause = "connect"; 10541ef5b48aSclaudio continue; 10551ef5b48aSclaudio } 10561ef5b48aSclaudio } 10571ef5b48aSclaudio 10580abf3cc6Sclaudio break; /* okay we got one */ 10591ef5b48aSclaudio } 10600abf3cc6Sclaudio 10610abf3cc6Sclaudio if (conn->fd == -1) { 1062eee9c28cSclaudio if (cause != NULL) { 1063eee9c28cSclaudio conn->res = res; 1064eee9c28cSclaudio warn("%s: %s", conn_info(conn), cause); 1065eee9c28cSclaudio } 106675c55a07Sclaudio return http_failed(conn); 10671ef5b48aSclaudio } 1068beb64842Sclaudio 1069bc49575fSclaudio return http_connect_done(conn); 10701ef5b48aSclaudio } 10711ef5b48aSclaudio 107275c55a07Sclaudio /* 1073f452fe1cStb * Called once an asynchronous connect request finished. 107475c55a07Sclaudio */ 107575c55a07Sclaudio static enum res 1076c8a1112eSclaudio http_finish_connect(struct http_connection *conn) 10771ef5b48aSclaudio { 1078c8a1112eSclaudio int error = 0; 1079c8a1112eSclaudio socklen_t len; 1080c8a1112eSclaudio 1081c8a1112eSclaudio len = sizeof(error); 1082c8a1112eSclaudio if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) { 1083eee9c28cSclaudio warn("%s: getsockopt SO_ERROR", conn_info(conn)); 1084d8c93623Sclaudio return http_connect_failed(conn); 10851ef5b48aSclaudio } 1086c8a1112eSclaudio if (error != 0) { 1087c8a1112eSclaudio errno = error; 1088eee9c28cSclaudio warn("%s: connect", conn_info(conn)); 1089d8c93623Sclaudio return http_connect_failed(conn); 1090c8a1112eSclaudio } 1091c8a1112eSclaudio 1092c8a1112eSclaudio return http_connect_done(conn); 1093c8a1112eSclaudio } 10941ef5b48aSclaudio 109575c55a07Sclaudio /* 109675c55a07Sclaudio * Initiate TLS session on a new connection. 109775c55a07Sclaudio */ 109875c55a07Sclaudio static enum res 10991ef5b48aSclaudio http_tls_connect(struct http_connection *conn) 11001ef5b48aSclaudio { 110175c55a07Sclaudio assert(conn->state == STATE_CONNECT); 110275c55a07Sclaudio conn->state = STATE_TLSCONNECT; 110375c55a07Sclaudio 11041ef5b48aSclaudio if ((conn->tls = tls_client()) == NULL) { 11051ef5b48aSclaudio warn("tls_client"); 110675c55a07Sclaudio return http_failed(conn); 11071ef5b48aSclaudio } 11081ef5b48aSclaudio if (tls_configure(conn->tls, tls_config) == -1) { 110978bab303Sclaudio warnx("%s: TLS configuration: %s", conn_info(conn), 11101ef5b48aSclaudio tls_error(conn->tls)); 111175c55a07Sclaudio return http_failed(conn); 11121ef5b48aSclaudio } 11131ef5b48aSclaudio if (tls_connect_socket(conn->tls, conn->fd, conn->host) == -1) { 111478bab303Sclaudio warnx("%s: TLS connect: %s", conn_info(conn), 11151ef5b48aSclaudio tls_error(conn->tls)); 111675c55a07Sclaudio return http_failed(conn); 11171ef5b48aSclaudio } 111875c55a07Sclaudio 11191ef5b48aSclaudio return http_tls_handshake(conn); 11201ef5b48aSclaudio } 11211ef5b48aSclaudio 112275c55a07Sclaudio /* 112375c55a07Sclaudio * Do the tls_handshake and then send out the HTTP request. 112475c55a07Sclaudio */ 112575c55a07Sclaudio static enum res 1126c8a1112eSclaudio http_tls_handshake(struct http_connection *conn) 1127c8a1112eSclaudio { 1128c8a1112eSclaudio switch (tls_handshake(conn->tls)) { 112975c55a07Sclaudio case -1: 1130eee9c28cSclaudio warnx("%s: TLS handshake: %s", conn_info(conn), 113175c55a07Sclaudio tls_error(conn->tls)); 113275c55a07Sclaudio return http_failed(conn); 1133c8a1112eSclaudio case TLS_WANT_POLLIN: 1134c8a1112eSclaudio return WANT_POLLIN; 1135c8a1112eSclaudio case TLS_WANT_POLLOUT: 1136c8a1112eSclaudio return WANT_POLLOUT; 1137c8a1112eSclaudio } 113875c55a07Sclaudio 113975c55a07Sclaudio return http_request(conn); 1140c8a1112eSclaudio } 1141c8a1112eSclaudio 11426f704872Sclaudio static enum res 11436f704872Sclaudio proxy_connect(struct http_connection *conn) 11446f704872Sclaudio { 11456f704872Sclaudio char *host; 11466f704872Sclaudio int r; 11476f704872Sclaudio 11486f704872Sclaudio assert(conn->state == STATE_CONNECT); 11496f704872Sclaudio conn->state = STATE_PROXY_REQUEST; 11506f704872Sclaudio 11516f704872Sclaudio /* Construct the Host header from host and port info */ 11526f704872Sclaudio if (strchr(conn->host, ':')) { 11536f704872Sclaudio if (asprintf(&host, "[%s]:%s", conn->host, conn->port) == -1) 11546f704872Sclaudio err(1, NULL); 11556f704872Sclaudio 11566f704872Sclaudio } else { 11576f704872Sclaudio if (asprintf(&host, "%s:%s", conn->host, conn->port) == -1) 11586f704872Sclaudio err(1, NULL); 11596f704872Sclaudio } 11606f704872Sclaudio 11616f704872Sclaudio free(conn->buf); 11626f704872Sclaudio conn->bufpos = 0; 11636f704872Sclaudio /* XXX handle auth */ 11646f704872Sclaudio if ((r = asprintf(&conn->buf, "CONNECT %s HTTP/1.1\r\n" 1165f3de69f6Stb "Host: %s\r\n" 1166c222e9aeStb "User-Agent: " HTTP_USER_AGENT "\r\n%s\r\n", host, host, 11676f704872Sclaudio proxy.proxyauth)) == -1) 11686f704872Sclaudio err(1, NULL); 11696f704872Sclaudio conn->bufsz = r; 11706f704872Sclaudio 11716f704872Sclaudio free(host); 11726f704872Sclaudio 11736f704872Sclaudio return proxy_write(conn); 11746f704872Sclaudio } 11756f704872Sclaudio 117675c55a07Sclaudio /* 117775c55a07Sclaudio * Build the HTTP request and send it out. 117875c55a07Sclaudio */ 117975c55a07Sclaudio static enum res 11801ef5b48aSclaudio http_request(struct http_connection *conn) 11811ef5b48aSclaudio { 11821ef5b48aSclaudio char *host, *epath, *modified_since; 11835d2a5cd6Sclaudio int r, with_port = 0; 11841ef5b48aSclaudio 118575c55a07Sclaudio assert(conn->state == STATE_IDLE || conn->state == STATE_TLSCONNECT); 118675c55a07Sclaudio conn->state = STATE_REQUEST; 118775c55a07Sclaudio 11881ef5b48aSclaudio /* 11891ef5b48aSclaudio * Send port number only if it's specified and does not equal 11901ef5b48aSclaudio * the default. Some broken HTTP servers get confused if you explicitly 11911ef5b48aSclaudio * send them the port number. 11921ef5b48aSclaudio */ 119375c55a07Sclaudio if (strcmp(conn->port, "443") != 0) 11941ef5b48aSclaudio with_port = 1; 11951ef5b48aSclaudio 11961ef5b48aSclaudio /* Construct the Host header from host and port info */ 11971ef5b48aSclaudio if (strchr(conn->host, ':')) { 11981ef5b48aSclaudio if (asprintf(&host, "[%s]%s%s", conn->host, 11991ef5b48aSclaudio with_port ? ":" : "", with_port ? conn->port : "") == -1) 12001ef5b48aSclaudio err(1, NULL); 12011ef5b48aSclaudio 12021ef5b48aSclaudio } else { 12031ef5b48aSclaudio if (asprintf(&host, "%s%s%s", conn->host, 12041ef5b48aSclaudio with_port ? ":" : "", with_port ? conn->port : "") == -1) 12051ef5b48aSclaudio err(1, NULL); 12061ef5b48aSclaudio } 12071ef5b48aSclaudio 12081ef5b48aSclaudio /* 12091ef5b48aSclaudio * Construct and send the request. Proxy requests don't want leading /. 12101ef5b48aSclaudio */ 121175c55a07Sclaudio epath = url_encode(conn->req->path); 12121ef5b48aSclaudio 12131ef5b48aSclaudio modified_since = NULL; 121475c55a07Sclaudio if (conn->req->modified_since != NULL) { 12151ef5b48aSclaudio if (asprintf(&modified_since, "If-Modified-Since: %s\r\n", 121675c55a07Sclaudio conn->req->modified_since) == -1) 12171ef5b48aSclaudio err(1, NULL); 12181ef5b48aSclaudio } 12191ef5b48aSclaudio 12201ef5b48aSclaudio free(conn->buf); 12211ef5b48aSclaudio conn->bufpos = 0; 12225d2a5cd6Sclaudio if ((r = asprintf(&conn->buf, 12231ef5b48aSclaudio "GET /%s HTTP/1.1\r\n" 1224b1ebb9f1Sjob "Host: %s\r\n" 1225fd3f1878Sclaudio "Accept: */*\r\n" 122678bab303Sclaudio "Accept-Encoding: gzip, deflate\r\n" 12271ef5b48aSclaudio "User-Agent: " HTTP_USER_AGENT "\r\n" 1228b1ebb9f1Sjob "%s\r\n", 12291ef5b48aSclaudio epath, host, 12301ef5b48aSclaudio modified_since ? modified_since : "")) == -1) 12311ef5b48aSclaudio err(1, NULL); 12325d2a5cd6Sclaudio conn->bufsz = r; 12331ef5b48aSclaudio 12341ef5b48aSclaudio free(epath); 12351ef5b48aSclaudio free(host); 12361ef5b48aSclaudio free(modified_since); 12371ef5b48aSclaudio 123875c55a07Sclaudio return http_write(conn); 12391ef5b48aSclaudio } 12401ef5b48aSclaudio 124175c55a07Sclaudio /* 124275c55a07Sclaudio * Parse the HTTP status line. 1243eb6e4c97Sclaudio * Return 0 for status codes 100, 103, 200, 203, 301-304, 307-308. 1244eb6e4c97Sclaudio * The other 1xx and 2xx status codes are explicitly not handled and are 1245eb6e4c97Sclaudio * considered an error. 124675c55a07Sclaudio * Failure codes and other errors return -1. 124775c55a07Sclaudio * The redirect loop limit is enforced here. 124875c55a07Sclaudio */ 12491ef5b48aSclaudio static int 12501ef5b48aSclaudio http_parse_status(struct http_connection *conn, char *buf) 12511ef5b48aSclaudio { 12529ffee665Sclaudio #define HTTP_11 "HTTP/1.1 " 12531ef5b48aSclaudio const char *errstr; 12541ef5b48aSclaudio char *cp, ststr[4]; 12551ef5b48aSclaudio char gerror[200]; 12561ef5b48aSclaudio int status; 12571ef5b48aSclaudio 12589ffee665Sclaudio /* Check if the protocol is 1.1 and enable keep-alive in that case */ 12599ffee665Sclaudio if (strncmp(buf, HTTP_11, strlen(HTTP_11)) == 0) 12609ffee665Sclaudio conn->keep_alive = 1; 12619ffee665Sclaudio 12621ef5b48aSclaudio cp = strchr(buf, ' '); 12631ef5b48aSclaudio if (cp == NULL) { 1264eee9c28cSclaudio warnx("Improper response from %s", conn_info(conn)); 12651ef5b48aSclaudio return -1; 12661ef5b48aSclaudio } else 12671ef5b48aSclaudio cp++; 12681ef5b48aSclaudio 12691ef5b48aSclaudio strlcpy(ststr, cp, sizeof(ststr)); 1270eb6e4c97Sclaudio status = strtonum(ststr, 100, 599, &errstr); 12711ef5b48aSclaudio if (errstr != NULL) { 12721ef5b48aSclaudio strnvis(gerror, cp, sizeof gerror, VIS_SAFE); 1273eee9c28cSclaudio warnx("Error retrieving %s: %s", conn_info(conn), 127475c55a07Sclaudio gerror); 12751ef5b48aSclaudio return -1; 12761ef5b48aSclaudio } 12771ef5b48aSclaudio 12781ef5b48aSclaudio switch (status) { 1279eb6e4c97Sclaudio case 301: /* Redirect: moved permanently */ 1280eb6e4c97Sclaudio case 302: /* Redirect: found / moved temporarily */ 1281eb6e4c97Sclaudio case 303: /* Redirect: see other */ 1282eb6e4c97Sclaudio case 307: /* Redirect: temporary redirect */ 1283eb6e4c97Sclaudio case 308: /* Redirect: permanent redirect */ 128475c55a07Sclaudio if (conn->req->redirect_loop++ > 10) { 12851ef5b48aSclaudio warnx("%s: Too many redirections requested", 1286eee9c28cSclaudio conn_info(conn)); 12871ef5b48aSclaudio return -1; 12881ef5b48aSclaudio } 12891ef5b48aSclaudio /* FALLTHROUGH */ 1290eb6e4c97Sclaudio case 100: /* Informational: continue (ignored) */ 1291eb6e4c97Sclaudio case 103: /* Informational: early hints (ignored) */ 1292eb6e4c97Sclaudio /* FALLTHROUGH */ 1293eb6e4c97Sclaudio case 200: /* Success: OK */ 1294eb6e4c97Sclaudio case 203: /* Success: non-authoritative information (proxy) */ 1295eb6e4c97Sclaudio case 304: /* Redirect: not modified */ 12961ef5b48aSclaudio conn->status = status; 12971ef5b48aSclaudio break; 12981ef5b48aSclaudio default: 12991ef5b48aSclaudio strnvis(gerror, cp, sizeof gerror, VIS_SAFE); 1300eee9c28cSclaudio warnx("Error retrieving %s: %s", conn_info(conn), 130175c55a07Sclaudio gerror); 130275c55a07Sclaudio return -1; 13031ef5b48aSclaudio } 13041ef5b48aSclaudio 13051ef5b48aSclaudio return 0; 13061ef5b48aSclaudio } 13071ef5b48aSclaudio 130875c55a07Sclaudio /* 130975c55a07Sclaudio * Returns true if the connection status is any of the redirect codes. 131075c55a07Sclaudio */ 13111ef5b48aSclaudio static inline int 13121ef5b48aSclaudio http_isredirect(struct http_connection *conn) 13131ef5b48aSclaudio { 13141ef5b48aSclaudio if ((conn->status >= 301 && conn->status <= 303) || 131509b708f5Sclaudio conn->status == 307 || conn->status == 308) 13161ef5b48aSclaudio return 1; 13171ef5b48aSclaudio return 0; 13181ef5b48aSclaudio } 13191ef5b48aSclaudio 1320eb6e4c97Sclaudio static inline int 1321eb6e4c97Sclaudio http_isok(struct http_connection *conn) 1322eb6e4c97Sclaudio { 1323eb6e4c97Sclaudio if (conn->status >= 200 && conn->status < 300) 1324eb6e4c97Sclaudio return 1; 1325eb6e4c97Sclaudio return 0; 1326eb6e4c97Sclaudio } 1327eb6e4c97Sclaudio 132875c55a07Sclaudio static void 132975c55a07Sclaudio http_redirect(struct http_connection *conn) 133047d3b88aSclaudio { 133175c55a07Sclaudio char *uri, *mod_since = NULL; 133275c55a07Sclaudio int outfd; 133375c55a07Sclaudio 133475c55a07Sclaudio /* move uri and fd out for new request */ 133575c55a07Sclaudio outfd = conn->req->outfd; 133675c55a07Sclaudio conn->req->outfd = -1; 133775c55a07Sclaudio 133875c55a07Sclaudio uri = conn->redir_uri; 133975c55a07Sclaudio conn->redir_uri = NULL; 134075c55a07Sclaudio 134175c55a07Sclaudio if (conn->req->modified_since) 134275c55a07Sclaudio if ((mod_since = strdup(conn->req->modified_since)) == NULL) 134375c55a07Sclaudio err(1, NULL); 134447d3b88aSclaudio 134547d3b88aSclaudio logx("redirect to %s", http_info(uri)); 134606792369Sclaudio http_req_new(conn->req->id, uri, mod_since, conn->req->redirect_loop, 134706792369Sclaudio outfd); 134847d3b88aSclaudio 134975c55a07Sclaudio /* clear request before moving connection to idle */ 135075c55a07Sclaudio http_req_free(conn->req); 135175c55a07Sclaudio conn->req = NULL; 135247d3b88aSclaudio } 135347d3b88aSclaudio 135447d3b88aSclaudio static int 13551ef5b48aSclaudio http_parse_header(struct http_connection *conn, char *buf) 13561ef5b48aSclaudio { 13571ef5b48aSclaudio #define CONTENTLEN "Content-Length:" 13581ef5b48aSclaudio #define LOCATION "Location:" 135975c55a07Sclaudio #define CONNECTION "Connection:" 13601ef5b48aSclaudio #define TRANSFER_ENCODING "Transfer-Encoding:" 136178bab303Sclaudio #define CONTENT_ENCODING "Content-Encoding:" 13621ef5b48aSclaudio #define LAST_MODIFIED "Last-Modified:" 13631ef5b48aSclaudio const char *errstr; 13641ef5b48aSclaudio char *cp, *redirurl; 13651ef5b48aSclaudio char *locbase, *loctail; 13661ef5b48aSclaudio 13671ef5b48aSclaudio cp = buf; 13681ef5b48aSclaudio /* empty line, end of header */ 13691ef5b48aSclaudio if (*cp == '\0') 13701ef5b48aSclaudio return 0; 13711ef5b48aSclaudio else if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) { 13721ef5b48aSclaudio cp += sizeof(CONTENTLEN) - 1; 1373380deba5Sclaudio cp += strspn(cp, " \t"); 137474d62246Sclaudio conn->iosz = strtonum(cp, 0, MAX_CONTENTLEN, &errstr); 13751ef5b48aSclaudio if (errstr != NULL) { 1376d7bb5489Sclaudio warnx("Content-Length of %s is %s", 1377eee9c28cSclaudio conn_info(conn), errstr); 13781ef5b48aSclaudio return -1; 13791ef5b48aSclaudio } 13801ef5b48aSclaudio } else if (http_isredirect(conn) && 13811ef5b48aSclaudio strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) { 13821ef5b48aSclaudio cp += sizeof(LOCATION) - 1; 1383380deba5Sclaudio cp += strspn(cp, " \t"); 13841ef5b48aSclaudio /* 13851ef5b48aSclaudio * If there is a colon before the first slash, this URI 13861ef5b48aSclaudio * is not relative. RFC 3986 4.2 13871ef5b48aSclaudio */ 13881ef5b48aSclaudio if (cp[strcspn(cp, ":/")] != ':') { 13891ef5b48aSclaudio /* XXX doesn't handle protocol-relative URIs */ 13901ef5b48aSclaudio if (*cp == '/') { 13911ef5b48aSclaudio locbase = NULL; 13921ef5b48aSclaudio cp++; 13931ef5b48aSclaudio } else { 139475c55a07Sclaudio locbase = strdup(conn->req->path); 13951ef5b48aSclaudio if (locbase == NULL) 13966735b9d8Sclaudio err(1, NULL); 13971ef5b48aSclaudio loctail = strchr(locbase, '#'); 13981ef5b48aSclaudio if (loctail != NULL) 13991ef5b48aSclaudio *loctail = '\0'; 14001ef5b48aSclaudio loctail = strchr(locbase, '?'); 14011ef5b48aSclaudio if (loctail != NULL) 14021ef5b48aSclaudio *loctail = '\0'; 14031ef5b48aSclaudio loctail = strrchr(locbase, '/'); 14041ef5b48aSclaudio if (loctail == NULL) { 14051ef5b48aSclaudio free(locbase); 14061ef5b48aSclaudio locbase = NULL; 14071ef5b48aSclaudio } else 14081ef5b48aSclaudio loctail[1] = '\0'; 14091ef5b48aSclaudio } 14101ef5b48aSclaudio /* Construct URL from relative redirect */ 14111ef5b48aSclaudio if (asprintf(&redirurl, "%.*s/%s%s", 141275c55a07Sclaudio (int)(conn->req->path - conn->req->uri), 141375c55a07Sclaudio conn->req->uri, locbase ? locbase : "", cp) == -1) 14141ef5b48aSclaudio err(1, "Cannot build redirect URL"); 14151ef5b48aSclaudio free(locbase); 14161ef5b48aSclaudio } else if ((redirurl = strdup(cp)) == NULL) 14171ef5b48aSclaudio err(1, "Cannot build redirect URL"); 14181ef5b48aSclaudio loctail = strchr(redirurl, '#'); 14191ef5b48aSclaudio if (loctail != NULL) 14201ef5b48aSclaudio *loctail = '\0'; 142175c55a07Sclaudio conn->redir_uri = redirurl; 1422608bb3c7Sjob if (!valid_origin(redirurl, conn->req->uri)) { 1423608bb3c7Sjob warnx("%s: cross origin redirect to %s", conn->req->uri, 1424608bb3c7Sjob http_info(redirurl)); 1425608bb3c7Sjob return -1; 1426608bb3c7Sjob } 14271ef5b48aSclaudio } else if (strncasecmp(cp, TRANSFER_ENCODING, 14281ef5b48aSclaudio sizeof(TRANSFER_ENCODING) - 1) == 0) { 14291ef5b48aSclaudio cp += sizeof(TRANSFER_ENCODING) - 1; 1430380deba5Sclaudio cp += strspn(cp, " \t"); 14311ef5b48aSclaudio if (strcasecmp(cp, "chunked") == 0) 14321ef5b48aSclaudio conn->chunked = 1; 143378bab303Sclaudio } else if (strncasecmp(cp, CONTENT_ENCODING, 143478bab303Sclaudio sizeof(CONTENT_ENCODING) - 1) == 0) { 143578bab303Sclaudio cp += sizeof(CONTENT_ENCODING) - 1; 143678bab303Sclaudio cp += strspn(cp, " \t"); 143778bab303Sclaudio if (strcasecmp(cp, "gzip") == 0 || 143878bab303Sclaudio strcasecmp(cp, "deflate") == 0) { 143978bab303Sclaudio if (http_inflate_new(conn) == -1) 144078bab303Sclaudio return -1; 144178bab303Sclaudio conn->gzipped = 1; 144278bab303Sclaudio } 144375c55a07Sclaudio } else if (strncasecmp(cp, CONNECTION, sizeof(CONNECTION) - 1) == 0) { 144475c55a07Sclaudio cp += sizeof(CONNECTION) - 1; 1445380deba5Sclaudio cp += strspn(cp, " \t"); 14469ffee665Sclaudio if (strcasecmp(cp, "close") == 0) 14479ffee665Sclaudio conn->keep_alive = 0; 14489ffee665Sclaudio else if (strcasecmp(cp, "keep-alive") == 0) 144975c55a07Sclaudio conn->keep_alive = 1; 14501ef5b48aSclaudio } else if (strncasecmp(cp, LAST_MODIFIED, 14511ef5b48aSclaudio sizeof(LAST_MODIFIED) - 1) == 0) { 14521ef5b48aSclaudio cp += sizeof(LAST_MODIFIED) - 1; 1453380deba5Sclaudio cp += strspn(cp, " \t"); 14544b0eaf5cStb free(conn->last_modified); 145562764b10Stb if ((conn->last_modified = strdup(cp)) == NULL) 145662764b10Stb err(1, NULL); 14571ef5b48aSclaudio } 14581ef5b48aSclaudio 14591ef5b48aSclaudio return 1; 14601ef5b48aSclaudio } 14611ef5b48aSclaudio 146275c55a07Sclaudio /* 146375c55a07Sclaudio * Return one line from the HTTP response. 146475c55a07Sclaudio * The line returned has any possible '\r' and '\n' at the end stripped. 146575c55a07Sclaudio * The buffer is advanced to the start of the next line. 146675c55a07Sclaudio * If there is currently no full line in the buffer NULL is returned. 146775c55a07Sclaudio */ 14681ef5b48aSclaudio static char * 14691ef5b48aSclaudio http_get_line(struct http_connection *conn) 14701ef5b48aSclaudio { 14711ef5b48aSclaudio char *end, *line; 14721ef5b48aSclaudio size_t len; 14731ef5b48aSclaudio 14741ef5b48aSclaudio end = memchr(conn->buf, '\n', conn->bufpos); 14751ef5b48aSclaudio if (end == NULL) 14761ef5b48aSclaudio return NULL; 14771ef5b48aSclaudio 14781ef5b48aSclaudio len = end - conn->buf; 1479d8670119Sclaudio while (len > 0 && (conn->buf[len - 1] == '\r' || 1480d8670119Sclaudio conn->buf[len - 1] == ' ' || conn->buf[len - 1] == '\t')) 14811ef5b48aSclaudio --len; 1482d8670119Sclaudio 14831ef5b48aSclaudio if ((line = strndup(conn->buf, len)) == NULL) 14846735b9d8Sclaudio err(1, NULL); 14851ef5b48aSclaudio 14861ef5b48aSclaudio /* consume line including \n */ 14871ef5b48aSclaudio end++; 14881ef5b48aSclaudio conn->bufpos -= end - conn->buf; 14891ef5b48aSclaudio memmove(conn->buf, end, conn->bufpos); 14901ef5b48aSclaudio 14911ef5b48aSclaudio return line; 14921ef5b48aSclaudio } 14931ef5b48aSclaudio 149475c55a07Sclaudio /* 149575c55a07Sclaudio * Parse the header between data chunks during chunked transfers. 149675c55a07Sclaudio * Returns 0 if a new chunk size could be correctly read. 149775c55a07Sclaudio * If the chuck size could not be converted properly -1 is returned. 149875c55a07Sclaudio */ 14991ef5b48aSclaudio static int 15001ef5b48aSclaudio http_parse_chunked(struct http_connection *conn, char *buf) 15011ef5b48aSclaudio { 15021ef5b48aSclaudio char *header = buf; 15031ef5b48aSclaudio char *end; 1504d7bb5489Sclaudio unsigned long chunksize; 15051ef5b48aSclaudio 1506d8670119Sclaudio /* strip any optional chunk extension */ 1507d8670119Sclaudio header[strcspn(header, "; \t")] = '\0'; 15081ef5b48aSclaudio errno = 0; 15091ef5b48aSclaudio chunksize = strtoul(header, &end, 16); 1510d7bb5489Sclaudio if (header[0] == '\0' || *end != '\0' || (errno == ERANGE && 1511af6f8e1cSclaudio chunksize == ULONG_MAX) || chunksize > MAX_CONTENTLEN) 15121ef5b48aSclaudio return -1; 15131ef5b48aSclaudio 151475c55a07Sclaudio conn->iosz = chunksize; 15151ef5b48aSclaudio return 0; 15161ef5b48aSclaudio } 15171ef5b48aSclaudio 151875c55a07Sclaudio static enum res 15191ef5b48aSclaudio http_read(struct http_connection *conn) 15201ef5b48aSclaudio { 15211ef5b48aSclaudio ssize_t s; 15221ef5b48aSclaudio char *buf; 1523cec4d91cSclaudio int done; 15241ef5b48aSclaudio 15250fec2b2fSanton if (conn->bufpos > 0) 15260fec2b2fSanton goto again; 15270fec2b2fSanton 1528cec4d91cSclaudio read_more: 15291ef5b48aSclaudio s = tls_read(conn->tls, conn->buf + conn->bufpos, 15301ef5b48aSclaudio conn->bufsz - conn->bufpos); 15311ef5b48aSclaudio if (s == -1) { 1532eee9c28cSclaudio warnx("%s: TLS read: %s", conn_info(conn), 15331ef5b48aSclaudio tls_error(conn->tls)); 153475c55a07Sclaudio return http_failed(conn); 15351ef5b48aSclaudio } else if (s == TLS_WANT_POLLIN) { 15361ef5b48aSclaudio return WANT_POLLIN; 15371ef5b48aSclaudio } else if (s == TLS_WANT_POLLOUT) { 15381ef5b48aSclaudio return WANT_POLLOUT; 15391ef5b48aSclaudio } 15401ef5b48aSclaudio 1541cb5cc1c3Sclaudio if (s == 0) { 154275c55a07Sclaudio if (conn->req) 15431ef5b48aSclaudio warnx("%s: short read, connection closed", 1544eee9c28cSclaudio conn_info(conn)); 154575c55a07Sclaudio return http_failed(conn); 15461ef5b48aSclaudio } 15471ef5b48aSclaudio 15481ef5b48aSclaudio conn->bufpos += s; 15491ef5b48aSclaudio 1550cec4d91cSclaudio again: 15511ef5b48aSclaudio switch (conn->state) { 15526f704872Sclaudio case STATE_PROXY_STATUS: 15536f704872Sclaudio buf = http_get_line(conn); 15546f704872Sclaudio if (buf == NULL) 15556f704872Sclaudio goto read_more; 15566f704872Sclaudio if (http_parse_status(conn, buf) == -1) { 15576f704872Sclaudio free(buf); 15586f704872Sclaudio return http_failed(conn); 15596f704872Sclaudio } 15606f704872Sclaudio free(buf); 15616f704872Sclaudio conn->state = STATE_PROXY_RESPONSE; 15626f704872Sclaudio goto again; 15636f704872Sclaudio case STATE_PROXY_RESPONSE: 15646f704872Sclaudio while (1) { 15656f704872Sclaudio buf = http_get_line(conn); 15666f704872Sclaudio if (buf == NULL) 15676f704872Sclaudio goto read_more; 15686f704872Sclaudio /* empty line, end of header */ 156959b3ab32Stb if (*buf == '\0') { 157059b3ab32Stb free(buf); 15716f704872Sclaudio break; 15726f704872Sclaudio } 157359b3ab32Stb free(buf); 157459b3ab32Stb } 15756f704872Sclaudio /* proxy is ready to take connection */ 15766f704872Sclaudio if (conn->status == 200) { 15776f704872Sclaudio conn->state = STATE_CONNECT; 15786f704872Sclaudio return http_tls_connect(conn); 15796f704872Sclaudio } 15806f704872Sclaudio return http_failed(conn); 15811ef5b48aSclaudio case STATE_RESPONSE_STATUS: 15821ef5b48aSclaudio buf = http_get_line(conn); 15831ef5b48aSclaudio if (buf == NULL) 1584cec4d91cSclaudio goto read_more; 15856f704872Sclaudio 15861ef5b48aSclaudio if (http_parse_status(conn, buf) == -1) { 15871ef5b48aSclaudio free(buf); 158875c55a07Sclaudio return http_failed(conn); 15891ef5b48aSclaudio } 15901ef5b48aSclaudio free(buf); 15911ef5b48aSclaudio conn->state = STATE_RESPONSE_HEADER; 1592cec4d91cSclaudio goto again; 15931ef5b48aSclaudio case STATE_RESPONSE_HEADER: 1594cec4d91cSclaudio done = 0; 1595cec4d91cSclaudio while (!done) { 15961ef5b48aSclaudio int rv; 15971ef5b48aSclaudio 1598cec4d91cSclaudio buf = http_get_line(conn); 1599cec4d91cSclaudio if (buf == NULL) 1600cec4d91cSclaudio goto read_more; 1601cec4d91cSclaudio 16021ef5b48aSclaudio rv = http_parse_header(conn, buf); 16031ef5b48aSclaudio free(buf); 160475c55a07Sclaudio 1605cec4d91cSclaudio if (rv == -1) 160675c55a07Sclaudio return http_failed(conn); 1607cec4d91cSclaudio if (rv == 0) 1608cec4d91cSclaudio done = 1; 16091ef5b48aSclaudio } 1610cec4d91cSclaudio 1611cec4d91cSclaudio /* Check status header and decide what to do next */ 1612eb6e4c97Sclaudio if (http_isok(conn) || http_isredirect(conn)) { 161375c55a07Sclaudio if (http_isredirect(conn)) 161475c55a07Sclaudio http_redirect(conn); 161575c55a07Sclaudio 161674d62246Sclaudio conn->totalsz = 0; 1617cec4d91cSclaudio if (conn->chunked) 161875c55a07Sclaudio conn->state = STATE_RESPONSE_CHUNKED_HEADER; 1619cec4d91cSclaudio else 1620cec4d91cSclaudio conn->state = STATE_RESPONSE_DATA; 1621cec4d91cSclaudio goto again; 1622eb6e4c97Sclaudio } else if (conn->status == 100 || conn->status == 103) { 1623eb6e4c97Sclaudio conn->state = STATE_RESPONSE_STATUS; 1624cec4d91cSclaudio } else if (conn->status == 304) { 162575c55a07Sclaudio return http_done(conn, HTTP_NOT_MOD); 1626cec4d91cSclaudio } 1627cec4d91cSclaudio 162875c55a07Sclaudio return http_failed(conn); 16291ef5b48aSclaudio case STATE_RESPONSE_DATA: 163075c55a07Sclaudio if (conn->bufpos != conn->bufsz && 1631af6f8e1cSclaudio conn->iosz > conn->bufpos) 1632cec4d91cSclaudio goto read_more; 163375c55a07Sclaudio 16342e80dbe3Sclaudio /* got a buffer full of data */ 163575c55a07Sclaudio if (conn->req == NULL) { 163675c55a07Sclaudio /* 163775c55a07Sclaudio * After redirects all data needs to be discarded. 163875c55a07Sclaudio */ 1639af6f8e1cSclaudio if (conn->iosz < conn->bufpos) { 164075c55a07Sclaudio conn->bufpos -= conn->iosz; 164175c55a07Sclaudio conn->iosz = 0; 164275c55a07Sclaudio } else { 164375c55a07Sclaudio conn->iosz -= conn->bufpos; 164475c55a07Sclaudio conn->bufpos = 0; 164575c55a07Sclaudio } 164675c55a07Sclaudio if (conn->chunked) 1647fd522c34Sclaudio conn->state = STATE_RESPONSE_CHUNKED_CRLF; 164875c55a07Sclaudio else 164975c55a07Sclaudio conn->state = STATE_RESPONSE_DATA; 165075c55a07Sclaudio goto read_more; 165175c55a07Sclaudio } 165275c55a07Sclaudio 165375c55a07Sclaudio conn->state = STATE_WRITE_DATA; 165475c55a07Sclaudio return WANT_POLLOUT; 165575c55a07Sclaudio case STATE_RESPONSE_CHUNKED_HEADER: 165675c55a07Sclaudio assert(conn->iosz == 0); 165775c55a07Sclaudio 16581ef5b48aSclaudio buf = http_get_line(conn); 16591ef5b48aSclaudio if (buf == NULL) 1660cec4d91cSclaudio goto read_more; 166175c55a07Sclaudio if (http_parse_chunked(conn, buf) != 0) { 1662eee9c28cSclaudio warnx("%s: bad chunk encoding", conn_info(conn)); 16631ef5b48aSclaudio free(buf); 166475c55a07Sclaudio return http_failed(conn); 16651ef5b48aSclaudio } 166659b3ab32Stb free(buf); 16671ef5b48aSclaudio 166875c55a07Sclaudio /* 166975c55a07Sclaudio * check if transfer is done, in which case the last trailer 167075c55a07Sclaudio * still needs to be processed. 167175c55a07Sclaudio */ 1672fd522c34Sclaudio if (conn->iosz == 0) 167375c55a07Sclaudio conn->state = STATE_RESPONSE_CHUNKED_TRAILER; 1674fd522c34Sclaudio else 167575c55a07Sclaudio conn->state = STATE_RESPONSE_DATA; 167675c55a07Sclaudio goto again; 1677fd522c34Sclaudio case STATE_RESPONSE_CHUNKED_CRLF: 167875c55a07Sclaudio buf = http_get_line(conn); 167975c55a07Sclaudio if (buf == NULL) 1680cec4d91cSclaudio goto read_more; 1681fd522c34Sclaudio /* expect empty line to finish a chunk of data */ 1682fd522c34Sclaudio if (*buf != '\0') { 1683eee9c28cSclaudio warnx("%s: bad chunk encoding", conn_info(conn)); 168475c55a07Sclaudio free(buf); 168575c55a07Sclaudio return http_failed(conn); 168675c55a07Sclaudio } 168775c55a07Sclaudio free(buf); 168875c55a07Sclaudio conn->state = STATE_RESPONSE_CHUNKED_HEADER; 168975c55a07Sclaudio goto again; 1690fd522c34Sclaudio case STATE_RESPONSE_CHUNKED_TRAILER: 1691fd522c34Sclaudio buf = http_get_line(conn); 1692fd522c34Sclaudio if (buf == NULL) 1693fd522c34Sclaudio goto read_more; 1694fd522c34Sclaudio /* the trailer may include extra headers, just ignore them */ 1695fd522c34Sclaudio if (*buf != '\0') { 1696fd522c34Sclaudio free(buf); 1697fd522c34Sclaudio goto again; 1698fd522c34Sclaudio } 1699fd522c34Sclaudio free(buf); 1700fd522c34Sclaudio conn->chunked = 0; 1701fd522c34Sclaudio return http_done(conn, HTTP_OK); 17021ef5b48aSclaudio default: 17031ef5b48aSclaudio errx(1, "unexpected http state"); 17041ef5b48aSclaudio } 17051ef5b48aSclaudio } 17061ef5b48aSclaudio 170775c55a07Sclaudio /* 170875c55a07Sclaudio * Send out the HTTP request. When done, replace buffer with the read buffer. 170975c55a07Sclaudio */ 171075c55a07Sclaudio static enum res 171147d3b88aSclaudio http_write(struct http_connection *conn) 171247d3b88aSclaudio { 171347d3b88aSclaudio ssize_t s; 171447d3b88aSclaudio 171575c55a07Sclaudio assert(conn->state == STATE_REQUEST); 171675c55a07Sclaudio 171775c55a07Sclaudio while (conn->bufpos < conn->bufsz) { 171847d3b88aSclaudio s = tls_write(conn->tls, conn->buf + conn->bufpos, 171947d3b88aSclaudio conn->bufsz - conn->bufpos); 172047d3b88aSclaudio if (s == -1) { 1721eee9c28cSclaudio warnx("%s: TLS write: %s", conn_info(conn), 172247d3b88aSclaudio tls_error(conn->tls)); 172375c55a07Sclaudio return http_failed(conn); 172447d3b88aSclaudio } else if (s == TLS_WANT_POLLIN) { 172547d3b88aSclaudio return WANT_POLLIN; 172647d3b88aSclaudio } else if (s == TLS_WANT_POLLOUT) { 172747d3b88aSclaudio return WANT_POLLOUT; 172847d3b88aSclaudio } 172947d3b88aSclaudio 173047d3b88aSclaudio conn->bufpos += s; 173147d3b88aSclaudio } 173247d3b88aSclaudio 173375c55a07Sclaudio /* done writing, first thing we need the status */ 173475c55a07Sclaudio conn->state = STATE_RESPONSE_STATUS; 173575c55a07Sclaudio 173675c55a07Sclaudio /* free write buffer and allocate the read buffer */ 173775c55a07Sclaudio free(conn->buf); 173875c55a07Sclaudio conn->bufpos = 0; 173975c55a07Sclaudio conn->bufsz = HTTP_BUF_SIZE; 174075c55a07Sclaudio if ((conn->buf = malloc(conn->bufsz)) == NULL) 174175c55a07Sclaudio err(1, NULL); 174275c55a07Sclaudio 174375c55a07Sclaudio return http_read(conn); 174475c55a07Sclaudio } 174575c55a07Sclaudio 17466f704872Sclaudio static enum res 17476f704872Sclaudio proxy_read(struct http_connection *conn) 17486f704872Sclaudio { 17496f704872Sclaudio ssize_t s; 17506f704872Sclaudio char *buf; 17516f704872Sclaudio int done; 17526f704872Sclaudio 17536f704872Sclaudio s = read(conn->fd, conn->buf + conn->bufpos, 17546f704872Sclaudio conn->bufsz - conn->bufpos); 17556f704872Sclaudio if (s == -1) { 1756eee9c28cSclaudio warn("%s: read", conn_info(conn)); 17576f704872Sclaudio return http_failed(conn); 17586f704872Sclaudio } 17596f704872Sclaudio 17606f704872Sclaudio if (s == 0) { 17616f704872Sclaudio if (conn->req) 17626f704872Sclaudio warnx("%s: short read, connection closed", 1763eee9c28cSclaudio conn_info(conn)); 17646f704872Sclaudio return http_failed(conn); 17656f704872Sclaudio } 17666f704872Sclaudio 17676f704872Sclaudio conn->bufpos += s; 17686f704872Sclaudio 17696f704872Sclaudio again: 17706f704872Sclaudio switch (conn->state) { 17716f704872Sclaudio case STATE_PROXY_STATUS: 17726f704872Sclaudio buf = http_get_line(conn); 17736f704872Sclaudio if (buf == NULL) 17746f704872Sclaudio return WANT_POLLIN; 17756f704872Sclaudio if (http_parse_status(conn, buf) == -1) { 17766f704872Sclaudio free(buf); 17776f704872Sclaudio return http_failed(conn); 17786f704872Sclaudio } 17796f704872Sclaudio free(buf); 17806f704872Sclaudio conn->state = STATE_PROXY_RESPONSE; 17816f704872Sclaudio goto again; 17826f704872Sclaudio case STATE_PROXY_RESPONSE: 17836f704872Sclaudio done = 0; 17846f704872Sclaudio while (!done) { 17856f704872Sclaudio buf = http_get_line(conn); 17866f704872Sclaudio if (buf == NULL) 17876f704872Sclaudio return WANT_POLLIN; 17886f704872Sclaudio /* empty line, end of header */ 17896f704872Sclaudio if (*buf == '\0') 17906f704872Sclaudio done = 1; 17916f704872Sclaudio free(buf); 17926f704872Sclaudio } 17936f704872Sclaudio /* proxy is ready, connect to remote */ 17946f704872Sclaudio if (conn->status == 200) { 17956f704872Sclaudio conn->state = STATE_CONNECT; 17966f704872Sclaudio return http_tls_connect(conn); 17976f704872Sclaudio } 17986f704872Sclaudio return http_failed(conn); 17996f704872Sclaudio default: 18006f704872Sclaudio errx(1, "unexpected http state"); 18016f704872Sclaudio } 18026f704872Sclaudio } 18036f704872Sclaudio 18046f704872Sclaudio /* 18056f704872Sclaudio * Send out the proxy request. When done, replace buffer with the read buffer. 18066f704872Sclaudio */ 18076f704872Sclaudio static enum res 18086f704872Sclaudio proxy_write(struct http_connection *conn) 18096f704872Sclaudio { 18106f704872Sclaudio ssize_t s; 18116f704872Sclaudio 18126f704872Sclaudio assert(conn->state == STATE_PROXY_REQUEST); 18136f704872Sclaudio 18146f704872Sclaudio s = write(conn->fd, conn->buf + conn->bufpos, 18156f704872Sclaudio conn->bufsz - conn->bufpos); 18166f704872Sclaudio if (s == -1) { 1817eee9c28cSclaudio warn("%s: write", conn_info(conn)); 18186f704872Sclaudio return http_failed(conn); 18196f704872Sclaudio } 18206f704872Sclaudio conn->bufpos += s; 18216f704872Sclaudio if (conn->bufpos < conn->bufsz) 18226f704872Sclaudio return WANT_POLLOUT; 18236f704872Sclaudio 18246f704872Sclaudio /* done writing, first thing we need the status */ 18256f704872Sclaudio conn->state = STATE_PROXY_STATUS; 18266f704872Sclaudio 18276f704872Sclaudio /* free write buffer and allocate the read buffer */ 18286f704872Sclaudio free(conn->buf); 18296f704872Sclaudio conn->bufpos = 0; 18306f704872Sclaudio conn->bufsz = HTTP_BUF_SIZE; 18316f704872Sclaudio if ((conn->buf = malloc(conn->bufsz)) == NULL) 18326f704872Sclaudio err(1, NULL); 18336f704872Sclaudio 18346f704872Sclaudio return WANT_POLLIN; 18356f704872Sclaudio } 18366f704872Sclaudio 183775c55a07Sclaudio /* 183875c55a07Sclaudio * Properly shutdown the TLS session else move connection into free state. 183975c55a07Sclaudio */ 184075c55a07Sclaudio static enum res 184147d3b88aSclaudio http_close(struct http_connection *conn) 184247d3b88aSclaudio { 184375c55a07Sclaudio assert(conn->state == STATE_IDLE || conn->state == STATE_CLOSE); 184475c55a07Sclaudio 184575c55a07Sclaudio conn->state = STATE_CLOSE; 184629ff95dfSclaudio LIST_REMOVE(conn, entry); 184729ff95dfSclaudio LIST_INSERT_HEAD(&active, conn, entry); 184875c55a07Sclaudio 184947d3b88aSclaudio if (conn->tls != NULL) { 185047d3b88aSclaudio switch (tls_close(conn->tls)) { 185147d3b88aSclaudio case TLS_WANT_POLLIN: 185247d3b88aSclaudio return WANT_POLLIN; 185347d3b88aSclaudio case TLS_WANT_POLLOUT: 185447d3b88aSclaudio return WANT_POLLOUT; 185547d3b88aSclaudio case 0: 185647d3b88aSclaudio case -1: 185747d3b88aSclaudio break; 185847d3b88aSclaudio } 185947d3b88aSclaudio } 186047d3b88aSclaudio 186175c55a07Sclaudio conn->state = STATE_FREE; 186275c55a07Sclaudio return DONE; 186347d3b88aSclaudio } 186447d3b88aSclaudio 186575c55a07Sclaudio /* 186675c55a07Sclaudio * Write data into provided file descriptor. If all data got written 186775c55a07Sclaudio * the connection may change into idle state. 186875c55a07Sclaudio */ 186975c55a07Sclaudio static enum res 18701ef5b48aSclaudio data_write(struct http_connection *conn) 18711ef5b48aSclaudio { 18721ef5b48aSclaudio ssize_t s; 18731ef5b48aSclaudio size_t bsz = conn->bufpos; 18741ef5b48aSclaudio 187575c55a07Sclaudio assert(conn->state == STATE_WRITE_DATA); 187675c55a07Sclaudio 1877af6f8e1cSclaudio if (conn->iosz < bsz) 1878d7bb5489Sclaudio bsz = conn->iosz; 18791ef5b48aSclaudio 188075c55a07Sclaudio s = write(conn->req->outfd, conn->buf, bsz); 18811ef5b48aSclaudio if (s == -1) { 1882eee9c28cSclaudio warn("%s: data write", conn_info(conn)); 188375c55a07Sclaudio return http_failed(conn); 18841ef5b48aSclaudio } 18851ef5b48aSclaudio 188674d62246Sclaudio conn->totalsz += s; 188774d62246Sclaudio if (conn->totalsz > MAX_CONTENTLEN) { 1888eee9c28cSclaudio warn("%s: too much data offered", conn_info(conn)); 188974d62246Sclaudio return http_failed(conn); 189074d62246Sclaudio } 189174d62246Sclaudio 18921ef5b48aSclaudio conn->bufpos -= s; 1893d7bb5489Sclaudio conn->iosz -= s; 18941ef5b48aSclaudio memmove(conn->buf, conn->buf + s, conn->bufpos); 18951ef5b48aSclaudio 1896d7bb5489Sclaudio /* check if regular file transfer is finished */ 189775c55a07Sclaudio if (!conn->chunked && conn->iosz == 0) 189875c55a07Sclaudio return http_done(conn, HTTP_OK); 18991ef5b48aSclaudio 1900d7bb5489Sclaudio /* all data written, switch back to read */ 1901cec4d91cSclaudio if (conn->bufpos == 0 || conn->iosz == 0) { 190203816d72Sclaudio if (conn->chunked && conn->iosz == 0) 1903fd522c34Sclaudio conn->state = STATE_RESPONSE_CHUNKED_CRLF; 1904cec4d91cSclaudio else 1905cec4d91cSclaudio conn->state = STATE_RESPONSE_DATA; 1906cec4d91cSclaudio return http_read(conn); 1907cec4d91cSclaudio } 19081ef5b48aSclaudio 1909d7bb5489Sclaudio /* still more data to write in buffer */ 19101ef5b48aSclaudio return WANT_POLLOUT; 19111ef5b48aSclaudio } 19121ef5b48aSclaudio 1913f12b699fSclaudio /* 191478bab303Sclaudio * Inflate and write data into provided file descriptor. 191578bab303Sclaudio * This is a simplified version of data_write() that just writes out the 191678bab303Sclaudio * decompressed file stream. All the buffer handling is done by 191778bab303Sclaudio * http_inflate_data() and http_inflate_advance(). 191878bab303Sclaudio */ 191978bab303Sclaudio static enum res 192078bab303Sclaudio data_inflate_write(struct http_connection *conn) 192178bab303Sclaudio { 192278bab303Sclaudio struct http_zlib *zctx = conn->zlibctx; 192378bab303Sclaudio ssize_t s; 192478bab303Sclaudio 192578bab303Sclaudio assert(conn->state == STATE_WRITE_DATA); 192678bab303Sclaudio 192778bab303Sclaudio /* no decompressed data, get more */ 192878bab303Sclaudio if (zctx->zbufpos == 0) 192978bab303Sclaudio if (http_inflate_data(conn) == -1) 193078bab303Sclaudio return http_failed(conn); 193178bab303Sclaudio 193278bab303Sclaudio s = write(conn->req->outfd, zctx->zbuf, zctx->zbufpos); 193378bab303Sclaudio if (s == -1) { 193478bab303Sclaudio warn("%s: data write", conn_info(conn)); 193578bab303Sclaudio return http_failed(conn); 193678bab303Sclaudio } 193778bab303Sclaudio 193878bab303Sclaudio conn->totalsz += s; 193978bab303Sclaudio if (conn->totalsz > MAX_CONTENTLEN) { 194078bab303Sclaudio warn("%s: too much decompressed data offered", conn_info(conn)); 194178bab303Sclaudio return http_failed(conn); 194278bab303Sclaudio } 194378bab303Sclaudio 194478bab303Sclaudio /* adjust output buffer */ 194578bab303Sclaudio zctx->zbufpos -= s; 194678bab303Sclaudio memmove(zctx->zbuf, zctx->zbuf + s, zctx->zbufpos); 194778bab303Sclaudio 194878bab303Sclaudio /* all decompressed data written, progress input */ 194978bab303Sclaudio if (zctx->zbufpos == 0) 195078bab303Sclaudio return http_inflate_advance(conn); 195178bab303Sclaudio 195278bab303Sclaudio /* still more data to write in buffer */ 195378bab303Sclaudio return WANT_POLLOUT; 195478bab303Sclaudio } 195578bab303Sclaudio 195678bab303Sclaudio /* 1957f12b699fSclaudio * Do one IO call depending on the connection state. 1958f12b699fSclaudio * Return WANT_POLLIN or WANT_POLLOUT to poll for more data. 1959f12b699fSclaudio * If 0 is returned this stage is finished and the protocol should move 1960f12b699fSclaudio * to the next stage by calling http_nextstep(). On error return -1. 1961f12b699fSclaudio */ 196275c55a07Sclaudio static enum res 196375c55a07Sclaudio http_handle(struct http_connection *conn) 19641ef5b48aSclaudio { 196575c55a07Sclaudio assert(conn->pfd != NULL && conn->pfd->revents != 0); 196675c55a07Sclaudio 19676f704872Sclaudio conn->io_time = 0; 19686f704872Sclaudio 19691ef5b48aSclaudio switch (conn->state) { 19701ef5b48aSclaudio case STATE_CONNECT: 197175c55a07Sclaudio return http_finish_connect(conn); 19721ef5b48aSclaudio case STATE_TLSCONNECT: 19731ef5b48aSclaudio return http_tls_handshake(conn); 19741ef5b48aSclaudio case STATE_REQUEST: 19751ef5b48aSclaudio return http_write(conn); 19766f704872Sclaudio case STATE_PROXY_REQUEST: 19776f704872Sclaudio return proxy_write(conn); 19786f704872Sclaudio case STATE_PROXY_STATUS: 19796f704872Sclaudio case STATE_PROXY_RESPONSE: 19806f704872Sclaudio return proxy_read(conn); 19811ef5b48aSclaudio case STATE_RESPONSE_STATUS: 19821ef5b48aSclaudio case STATE_RESPONSE_HEADER: 19831ef5b48aSclaudio case STATE_RESPONSE_DATA: 198475c55a07Sclaudio case STATE_RESPONSE_CHUNKED_HEADER: 1985fd522c34Sclaudio case STATE_RESPONSE_CHUNKED_CRLF: 198675c55a07Sclaudio case STATE_RESPONSE_CHUNKED_TRAILER: 19871ef5b48aSclaudio return http_read(conn); 19881ef5b48aSclaudio case STATE_WRITE_DATA: 198978bab303Sclaudio if (conn->gzipped) 199078bab303Sclaudio return data_inflate_write(conn); 199178bab303Sclaudio else 19921ef5b48aSclaudio return data_write(conn); 199375c55a07Sclaudio case STATE_CLOSE: 19941ef5b48aSclaudio return http_close(conn); 199575c55a07Sclaudio case STATE_IDLE: 199675c55a07Sclaudio conn->state = STATE_RESPONSE_HEADER; 199729ff95dfSclaudio LIST_REMOVE(conn, entry); 199829ff95dfSclaudio LIST_INSERT_HEAD(&active, conn, entry); 199975c55a07Sclaudio return http_read(conn); 20001ef5b48aSclaudio case STATE_FREE: 20011ef5b48aSclaudio errx(1, "bad http state"); 20021ef5b48aSclaudio } 20035d2a5cd6Sclaudio errx(1, "unknown http state"); 20041ef5b48aSclaudio } 20051ef5b48aSclaudio 2006f12b699fSclaudio /* 200775c55a07Sclaudio * Initialisation done before pledge() call to load certificates. 2008f12b699fSclaudio */ 2009c8a1112eSclaudio static void 2010c8a1112eSclaudio http_setup(void) 2011c8a1112eSclaudio { 20126f704872Sclaudio char *httpproxy; 20136f704872Sclaudio 2014c8a1112eSclaudio tls_config = tls_config_new(); 2015c8a1112eSclaudio if (tls_config == NULL) 2016c8a1112eSclaudio errx(1, "tls config failed"); 201775c55a07Sclaudio 2018c8a1112eSclaudio #if 0 2019c8a1112eSclaudio /* TODO Should we allow extra protos and ciphers? */ 2020c8a1112eSclaudio if (tls_config_set_protocols(tls_config, TLS_PROTOCOLS_ALL) == -1) 2021c8a1112eSclaudio errx(1, "tls set protocols failed: %s", 2022c8a1112eSclaudio tls_config_error(tls_config)); 2023c8a1112eSclaudio if (tls_config_set_ciphers(tls_config, "legacy") == -1) 2024c8a1112eSclaudio errx(1, "tls set ciphers failed: %s", 2025c8a1112eSclaudio tls_config_error(tls_config)); 2026c8a1112eSclaudio #endif 2027c8a1112eSclaudio 2028c8a1112eSclaudio /* load cert file from disk now */ 2029c8a1112eSclaudio tls_ca_mem = tls_load_file(tls_default_ca_cert_file(), 2030c8a1112eSclaudio &tls_ca_size, NULL); 2031c8a1112eSclaudio if (tls_ca_mem == NULL) 2032c8a1112eSclaudio err(1, "tls_load_file: %s", tls_default_ca_cert_file()); 2033c8a1112eSclaudio tls_config_set_ca_mem(tls_config, tls_ca_mem, tls_ca_size); 2034c8a1112eSclaudio 20356f704872Sclaudio if ((httpproxy = getenv("http_proxy")) != NULL && *httpproxy == '\0') 20366f704872Sclaudio httpproxy = NULL; 20376f704872Sclaudio 20386f704872Sclaudio proxy_parse_uri(httpproxy); 2039c8a1112eSclaudio } 2040c8a1112eSclaudio 20411ef5b48aSclaudio void 20421ef5b48aSclaudio proc_http(char *bind_addr, int fd) 20431ef5b48aSclaudio { 204475c55a07Sclaudio struct pollfd pfds[NPFDS]; 204575c55a07Sclaudio struct http_connection *conn, *nc; 204675c55a07Sclaudio struct http_request *req, *nr; 2047*b5fa5d51Sclaudio struct ibuf *b; 20481ef5b48aSclaudio 20491db5fd2bSclaudio if (pledge("stdio rpath inet dns recvfd", NULL) == -1) 20501db5fd2bSclaudio err(1, "pledge"); 20511db5fd2bSclaudio 20521ef5b48aSclaudio if (bind_addr != NULL) { 20531ef5b48aSclaudio struct addrinfo hints, *res; 20541ef5b48aSclaudio 20551ef5b48aSclaudio bzero(&hints, sizeof(hints)); 20561ef5b48aSclaudio hints.ai_family = AF_UNSPEC; 20571ef5b48aSclaudio hints.ai_socktype = SOCK_DGRAM; /*dummy*/ 20581ef5b48aSclaudio hints.ai_flags = AI_NUMERICHOST; 20591ef5b48aSclaudio if (getaddrinfo(bind_addr, NULL, &hints, &res) == 0) { 20601ef5b48aSclaudio memcpy(&http_bindaddr, res->ai_addr, res->ai_addrlen); 20611ef5b48aSclaudio freeaddrinfo(res); 20621ef5b48aSclaudio } 20631ef5b48aSclaudio } 20641ef5b48aSclaudio http_setup(); 20651ef5b48aSclaudio 20661ef5b48aSclaudio if (pledge("stdio inet dns recvfd", NULL) == -1) 20671ef5b48aSclaudio err(1, "pledge"); 20681ef5b48aSclaudio 2069*b5fa5d51Sclaudio if ((msgq = msgbuf_new_reader(sizeof(size_t), io_parse_hdr, NULL)) == 2070*b5fa5d51Sclaudio NULL) 207125d36c5cSclaudio err(1, NULL); 20721ef5b48aSclaudio 20731ef5b48aSclaudio for (;;) { 207475c55a07Sclaudio time_t now; 207575c55a07Sclaudio int timeout; 207695b65f7cSderaadt size_t i; 207795b65f7cSderaadt 207840604d84Sclaudio memset(&pfds, 0, sizeof(pfds)); 207975c55a07Sclaudio pfds[0].fd = fd; 208075c55a07Sclaudio pfds[0].events = POLLIN; 208125d36c5cSclaudio if (msgbuf_queuelen(msgq) > 0) 208275c55a07Sclaudio pfds[0].events |= POLLOUT; 20831549bb4dSderaadt 208475c55a07Sclaudio i = 1; 208575c55a07Sclaudio timeout = INFTIM; 208675c55a07Sclaudio now = getmonotime(); 208775c55a07Sclaudio LIST_FOREACH(conn, &active, entry) { 2088bd2dbcbdSclaudio if (i >= NPFDS) 2089bd2dbcbdSclaudio errx(1, "too many connections"); 2090bd2dbcbdSclaudio 20919170c2daSclaudio if (conn->io_time == 0) { 20929170c2daSclaudio if (conn->state == STATE_CONNECT) 20939170c2daSclaudio conn->io_time = now + MAX_CONN_TIMEOUT; 20949170c2daSclaudio else 20952778dc25Sjob conn->io_time = now + MAX_IO_TIMEOUT; 20969170c2daSclaudio } 20976f704872Sclaudio 20986f704872Sclaudio if (conn->io_time <= now) 20996f704872Sclaudio timeout = 0; 21006f704872Sclaudio else { 21016f704872Sclaudio int diff = conn->io_time - now; 21026f704872Sclaudio diff *= 1000; 21036f704872Sclaudio if (timeout == INFTIM || diff < timeout) 21046f704872Sclaudio timeout = diff; 21056f704872Sclaudio } 21061ef5b48aSclaudio if (conn->state == STATE_WRITE_DATA) 210775c55a07Sclaudio pfds[i].fd = conn->req->outfd; 21081ef5b48aSclaudio else 21091ef5b48aSclaudio pfds[i].fd = conn->fd; 21101ef5b48aSclaudio 21111ef5b48aSclaudio pfds[i].events = conn->events; 211275c55a07Sclaudio conn->pfd = &pfds[i]; 211375c55a07Sclaudio i++; 21141ef5b48aSclaudio } 211575c55a07Sclaudio LIST_FOREACH(conn, &idle, entry) { 2116bd2dbcbdSclaudio if (i >= NPFDS) 2117bd2dbcbdSclaudio errx(1, "too many connections"); 2118bd2dbcbdSclaudio 211975c55a07Sclaudio if (conn->idle_time <= now) 212075c55a07Sclaudio timeout = 0; 212175c55a07Sclaudio else { 212275c55a07Sclaudio int diff = conn->idle_time - now; 212375c55a07Sclaudio diff *= 1000; 212475c55a07Sclaudio if (timeout == INFTIM || diff < timeout) 212575c55a07Sclaudio timeout = diff; 212675c55a07Sclaudio } 212775c55a07Sclaudio pfds[i].fd = conn->fd; 212875c55a07Sclaudio pfds[i].events = POLLIN; 212975c55a07Sclaudio conn->pfd = &pfds[i]; 213075c55a07Sclaudio i++; 213175c55a07Sclaudio } 21321ef5b48aSclaudio 21337ba5db23Sclaudio if (poll(pfds, i, timeout) == -1) { 21347ba5db23Sclaudio if (errno == EINTR) 21357ba5db23Sclaudio continue; 21361ef5b48aSclaudio err(1, "poll"); 21377ba5db23Sclaudio } 21381ef5b48aSclaudio 213975c55a07Sclaudio if (pfds[0].revents & POLLHUP) 21401ef5b48aSclaudio break; 214175c55a07Sclaudio if (pfds[0].revents & POLLOUT) { 214225d36c5cSclaudio if (msgbuf_write(fd, msgq) == -1) { 21439aadc625Sclaudio if (errno == EPIPE) 21441cedc9bdSclaudio errx(1, "write: connection closed"); 21459aadc625Sclaudio else 21461cedc9bdSclaudio err(1, "write"); 21471cedc9bdSclaudio } 21481cedc9bdSclaudio } 214975c55a07Sclaudio if (pfds[0].revents & POLLIN) { 2150*b5fa5d51Sclaudio switch (msgbuf_read(fd, msgq)) { 2151*b5fa5d51Sclaudio case -1: 2152*b5fa5d51Sclaudio err(1, "msgbuf_read"); 2153*b5fa5d51Sclaudio case 0: 2154*b5fa5d51Sclaudio errx(1, "msgbuf_read: connection closed"); 2155*b5fa5d51Sclaudio } 2156*b5fa5d51Sclaudio while ((b = io_buf_get(msgq)) != NULL) { 2157b6884e9fSclaudio unsigned int id; 21581ef5b48aSclaudio char *uri; 21591ef5b48aSclaudio char *mod; 21601ef5b48aSclaudio 21617eb79a4aSclaudio io_read_buf(b, &id, sizeof(id)); 21627eb79a4aSclaudio io_read_str(b, &uri); 21637eb79a4aSclaudio io_read_str(b, &mod); 21641ef5b48aSclaudio 216575c55a07Sclaudio /* queue up new requests */ 2166cf954fffSclaudio http_req_new(id, uri, mod, 0, ibuf_fd_get(b)); 21677eb79a4aSclaudio ibuf_free(b); 21687eb79a4aSclaudio } 216975c55a07Sclaudio } 217075c55a07Sclaudio 217175c55a07Sclaudio now = getmonotime(); 217275c55a07Sclaudio /* process idle connections */ 217375c55a07Sclaudio LIST_FOREACH_SAFE(conn, &idle, entry, nc) { 217475c55a07Sclaudio if (conn->pfd != NULL && conn->pfd->revents != 0) 217575c55a07Sclaudio http_do(conn, http_handle); 2176e8a0bc21Sclaudio else if (conn->idle_time <= now) { 2177e8a0bc21Sclaudio conn->io_time = 0; 217875c55a07Sclaudio http_do(conn, http_close); 2179e8a0bc21Sclaudio } 218075c55a07Sclaudio 218175c55a07Sclaudio if (conn->state == STATE_FREE) 218275c55a07Sclaudio http_free(conn); 218375c55a07Sclaudio } 218475c55a07Sclaudio 218575c55a07Sclaudio /* then active http requests */ 218675c55a07Sclaudio LIST_FOREACH_SAFE(conn, &active, entry, nc) { 218775c55a07Sclaudio /* check if event is ready */ 218875c55a07Sclaudio if (conn->pfd != NULL && conn->pfd->revents != 0) 218975c55a07Sclaudio http_do(conn, http_handle); 2190e8a0bc21Sclaudio else if (conn->io_time != 0 && conn->io_time <= now) { 21913029ed57Sclaudio conn->io_time = 0; 21929170c2daSclaudio if (conn->state == STATE_CONNECT) { 2193eee9c28cSclaudio warnx("%s: connect timeout", 2194eee9c28cSclaudio conn_info(conn)); 21959170c2daSclaudio http_do(conn, http_connect_failed); 21969170c2daSclaudio } else { 21976f704872Sclaudio warnx("%s: timeout, connection closed", 2198eee9c28cSclaudio conn_info(conn)); 21996f704872Sclaudio http_do(conn, http_failed); 22006f704872Sclaudio } 22019170c2daSclaudio } 220275c55a07Sclaudio 220375c55a07Sclaudio if (conn->state == STATE_FREE) 220475c55a07Sclaudio http_free(conn); 220575c55a07Sclaudio } 220675c55a07Sclaudio 220775c55a07Sclaudio TAILQ_FOREACH_SAFE(req, &queue, entry, nr) 220875c55a07Sclaudio if (!http_req_schedule(req)) 22091ef5b48aSclaudio break; 22101ef5b48aSclaudio } 22111ef5b48aSclaudio 22121ef5b48aSclaudio exit(0); 22131ef5b48aSclaudio } 2214