http.c (11362B)
1 /* 2 * Copyright (c) 2020 Kyle Milz <krwmilz@gmail.com> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 #include <sys/types.h> /* getaddrinfo() */ 18 #include <sys/socket.h> /* connect(), socket() */ 19 20 #include <netdb.h> /* getaddrinfo() */ 21 22 #include <assert.h> 23 #include <err.h> /* err() */ 24 #include <string.h> /* strtok(), strsep(), strstr() */ 25 #include <unistd.h> /* close() */ 26 27 #include <openssl/err.h> /* ERR_load_crypto_strings() */ 28 #include <openssl/evp.h> /* OpenSSL_add_all_algorithms() */ 29 #include <openssl/ssl.h> /* SSL_read(), SSL_write() */ 30 31 #include "http.h" 32 #include "popups.h" /* warning_popup_long() */ 33 #include "util.h" /* xmalloc() */ 34 35 #define SSL_BUF_SIZE 16 * 1024 36 #define HTTP_DELIM "\r\n" 37 38 39 struct http_headers { 40 char ver[15]; /* Should be enough, RFC2616 3.1 */ 41 int stat; /* 200, 404, etc */ 42 char stat_text[32]; /* 416 may be the longest? */ 43 unsigned int cont_len; /* Content-Length: */ 44 }; 45 46 47 /* 48 * Takes a struct url and finds all addresses related to url->hostname. 49 * Then tries connecting to each of them, returning a connected socket on 50 * success. 51 * 52 * Returns -1 on failure. 53 */ 54 static int 55 xconnect(struct url *url) 56 { 57 struct addrinfo hints, *res, *res0; 58 int error; 59 int save_errno; 60 int s; 61 const char *cause = NULL; 62 63 memset(&hints, 0, sizeof(hints)); 64 hints.ai_family = AF_UNSPEC; 65 hints.ai_socktype = SOCK_STREAM; 66 67 error = getaddrinfo(url->hostname, url->port, &hints, &res0); 68 if (error) { 69 warning_popup_long("%s: %s", "OK", NULL, url->hostname, 70 gai_strerror(error)); 71 return -1; 72 } 73 74 s = -1; 75 for (res = res0; res; res = res->ai_next) { 76 s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 77 if (s == -1) { 78 cause = "socket"; 79 continue; 80 } 81 82 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { 83 cause = "connect"; 84 save_errno = errno; 85 close(s); 86 errno = save_errno; 87 s = -1; 88 continue; 89 } 90 91 /* connect() succeeded */ 92 break; 93 } 94 95 freeaddrinfo(res0); 96 97 if (s == -1) { 98 warning_popup_long("%s %s: %s\n", "OK", NULL, 99 cause, url->hostname, strerror(errno)); 100 return -1; 101 } 102 103 return s; 104 } 105 106 107 /* 108 * Map some SSL error codes to real messages. This should exist somewhere 109 * already? 110 * 111 * Returns a message and never fails. 112 */ 113 static const char * 114 SSL_strerror(const SSL *tls, int ret) 115 { 116 switch (SSL_get_error(tls, ret)) { 117 case SSL_ERROR_NONE: 118 return "No error"; 119 case SSL_ERROR_ZERO_RETURN: 120 return "TLS connection has been closed"; 121 case SSL_ERROR_WANT_READ: 122 case SSL_ERROR_WANT_WRITE: 123 case SSL_ERROR_WANT_CONNECT: 124 case SSL_ERROR_WANT_ACCEPT: 125 case SSL_ERROR_WANT_X509_LOOKUP: 126 return "TLS operation did not complete"; 127 case SSL_ERROR_SYSCALL: 128 return "I/O error occurred"; 129 case SSL_ERROR_SSL: 130 return "Failure in SSL library"; 131 } 132 133 return "Unknown TLS error"; 134 } 135 136 137 /* 138 * Connect to the host using TLS. 139 * 140 * Returns an SSL pointer on success or NULL on failure. 141 */ 142 static SSL * 143 tls_connect(int server, struct url *url) 144 { 145 const SSL_METHOD *method; 146 SSL_CTX *ctx; 147 SSL *tls; 148 X509 *cert = NULL; 149 unsigned int ret, fprint_size; 150 const EVP_MD *fprint_type; 151 unsigned char fprint[EVP_MAX_MD_SIZE]; 152 EVP_PKEY *pkey = NULL; 153 154 /* According to LibreSSL manual pages none of these are necessary. */ 155 OpenSSL_add_all_algorithms(); 156 ERR_load_BIO_strings(); 157 ERR_load_crypto_strings(); 158 SSL_load_error_strings(); 159 160 /* Not needed for newer (> 1.1.0) library versions? */ 161 assert(SSL_library_init() == 1); 162 163 method = TLS_client_method(); 164 if ((ctx = SSL_CTX_new(method)) == NULL) 165 errx(1, "Unable to create new SSL context structure.\n"); 166 if ((tls = SSL_new(ctx)) == NULL) 167 errx(1, "Unable to create new SSL structure.\n"); 168 169 SSL_set_fd(tls, server); 170 if ((ret = SSL_connect(tls)) != 1) { 171 warning_popup_long("%s:%s: Cannot make TLS connection", 172 "OK", NULL, url->hostname, url->port); 173 174 /* Print SSL_strerror(tls, ret)); too? */ 175 return NULL; 176 } 177 178 if ((cert = SSL_get_peer_certificate(tls)) == NULL) { 179 warning_popup_long("%s:%s: No TLS certificate was presented by peer", 180 "OK", NULL, url->hostname, url->port); 181 return NULL; 182 } 183 184 #ifdef notyet 185 X509_NAME *certname = NULL; 186 certname = X509_NAME_new(); 187 certname = X509_get_subject_name(cert); 188 189 BIO outbio = BIO_new_fp(stdout, BIO_NOCLOSE); 190 BIO_printf(outbio, "Certificate subject data: "); 191 X509_NAME_print_ex(outbio, certname, 0, 0); 192 BIO_printf(outbio, "\n"); 193 #endif 194 195 /* Calculate certificate fingerprint. */ 196 fprint_type = EVP_sha256(); 197 198 if (!X509_digest(cert, fprint_type, fprint, &fprint_size)) { 199 warning_popup_long("%s:%s: Cannot create TLS certificate fingerprint", 200 "OK", NULL, url->hostname, url->port); 201 return NULL; 202 } 203 204 #ifdef notyet 205 BIO_printf(outbio, "Fingerprint (method = %s, size = %d): ", 206 OBJ_nid2sn(EVP_MD_type(fprint_type)), fprint_size); 207 208 for (int j = 0; j < fprint_size; j++) { 209 BIO_printf(outbio, "%02X%c", fprint[j], 210 (j + 1 == fprint_size) ? '\n' : ':'); 211 } 212 BIO_printf(outbio, "\n"); 213 #endif 214 215 /* Get certificate public key. */ 216 if ((pkey = X509_get_pubkey(cert)) == NULL) { 217 warning_popup_long("%s:%s: No public key in certificate.", 218 "OK", NULL, url->hostname, url->port); 219 return NULL; 220 } 221 #ifdef notyet 222 PEM_write_bio_PUBKEY(outbio, pkey); 223 BIO_free_all(outbio); 224 #endif 225 226 X509_free(cert); 227 228 return tls; 229 } 230 231 232 /* 233 * Creates an HTTP GET request and writes it to tls. 234 * Returns 0 on success and -1 on failure. 235 */ 236 static int 237 send_request(SSL *tls, struct url *url) 238 { 239 const char *req_fmt; 240 char *req; 241 int ret; 242 243 /* RFC7230 3.1.1 */ 244 req_fmt = "GET /%s HTTP/1.1" HTTP_DELIM 245 "Host: %s" HTTP_DELIM 246 HTTP_DELIM; 247 248 if (asprintf(&req, req_fmt, url->document, url->hostname) == -1) 249 err(1, "asprintf"); 250 251 if ((ret = SSL_write(tls, req, strlen(req))) < 1) { 252 warning_popup_long("%s: %s", "OK", NULL, url->hostname, 253 SSL_strerror(tls, ret)); 254 free(req); 255 return -1; 256 } 257 258 free(req); 259 return 0; 260 } 261 262 263 /* 264 * Parse a buffer that supposedly contains HTTP headers. 265 * 266 * On success: 267 * - *hdr_len contains the length of the header found in hdr 268 * - return a pointer to an allocated struct http_headers that the caller 269 * must free 270 * 271 * Return NULL on failure. 272 */ 273 static struct http_headers * 274 parse_headers(char *hdr, int *hdr_len) 275 { 276 struct http_headers *res; 277 char *hdr_end; 278 char *line; 279 280 if ((hdr_end = strstr(hdr, HTTP_DELIM HTTP_DELIM)) == NULL) 281 return NULL; 282 *hdr_len = hdr_end - hdr + strlen(HTTP_DELIM HTTP_DELIM); 283 284 if ((res = calloc(1, sizeof(struct http_headers))) == NULL) 285 err(1, NULL); 286 287 /* Let our string processing functions below know when they're done */ 288 *(hdr_end + strlen(HTTP_DELIM)) = '\0'; 289 290 if ((line = strtok(hdr, HTTP_DELIM)) == NULL) 291 goto free_res; 292 293 /* Status line of response is 'HTTP/1.1 200 OK' for example. */ 294 if (sscanf(line, "%15s %i %31[^\n]", res->ver, &res->stat, 295 res->stat_text) != 3) 296 goto free_res; 297 298 /* We only care about Content-Length right now. */ 299 while ((line = strtok(NULL, HTTP_DELIM)) != NULL) 300 sscanf(line, "Content-Length: %u", &res->cont_len); 301 302 return res; 303 304 free_res: 305 free(res); 306 return NULL; 307 } 308 309 310 /* 311 * Read tls for the response sent by the server after url was requested. 312 * The read is split in two parts: 313 * 1) the first gets the header, parses it and sizes recieve buffers 314 * 2) read at max Content-Length: into the receive buffers 315 * 316 * Returns a FILE* containing the response body on success or NULL on failure. 317 */ 318 static FILE * 319 read_response(SSL *tls, struct url *url) 320 { 321 char *buf; 322 struct http_headers *resp; 323 int nbytes; 324 int bytes_left; 325 int hdr_len; 326 FILE *dvi = NULL; 327 328 if ((buf = calloc(1, SSL_BUF_SIZE)) == NULL) 329 err(1, "calloc"); 330 331 /* 332 * The first read should yield AT LEAST the HTTP header. The following 333 * code does not handle the case where it does not. 334 */ 335 if ((nbytes = SSL_read(tls, buf, SSL_BUF_SIZE)) < 1) { 336 warning_popup_long("SSL_read: %s: %s", "OK", NULL, 337 url->hostname, SSL_strerror(tls, nbytes)); 338 goto free_buf; 339 } 340 341 if ((resp = parse_headers(buf, &hdr_len)) == NULL) { 342 warning_popup_long("%s: Malformed HTTP header", "OK", NULL, 343 url->hostname); 344 goto free_buf; 345 } 346 347 /* 348 * Do not handle anything other than 200. We must have 349 * Content-Length: as well. 350 */ 351 if (resp->stat != 200) { 352 warning_popup_long("%s: %i %s", "OK", NULL, url->orig_url, 353 resp->stat, resp->stat_text); 354 goto free_resp; 355 } 356 else if (resp->cont_len < 1) { 357 warning_popup_long("%s: Content-Length invalid", "OK", NULL, 358 url->orig_url); 359 goto free_resp; 360 } 361 362 /* 363 * XDvi is set up to do all its processing on FILE's, so we use that 364 * here to minimize changes over there. 365 */ 366 if ((dvi = fmemopen(NULL, resp->cont_len, "r+")) == NULL) 367 err(1, "fmemopen"); 368 369 /* Write out any remaining data in buf from the first SSL_read(). */ 370 if (hdr_len < nbytes) { 371 bytes_left = nbytes - hdr_len; 372 373 /* Check for buffer overflow. */ 374 if (bytes_left > resp->cont_len) { 375 warnx("truncating extra http data"); 376 bytes_left = resp->cont_len; 377 } 378 379 if (fwrite(buf + hdr_len, bytes_left, 1, dvi) < 1) 380 err(1, "fwrite"); 381 } 382 383 /* Second (or more) reads are the HTTP response body. */ 384 while (ftell(dvi) < resp->cont_len) { 385 if ((nbytes = SSL_read(tls, buf, SSL_BUF_SIZE)) < 1) { 386 warning_popup_long("SSL_read: %s: %s", "OK", NULL, 387 url->hostname, SSL_strerror(tls, nbytes)); 388 fclose(dvi); 389 dvi = NULL; 390 goto free_resp; 391 } 392 393 /* Check for buffer overflow. */ 394 if (ftell(dvi) + nbytes > resp->cont_len) { 395 warnx("truncating extra http data"); 396 nbytes = resp->cont_len - ftell(dvi); 397 } 398 399 if (fwrite(buf, nbytes, 1, dvi) < 1) 400 err(1, "fwrite"); 401 } 402 403 if (ftell(dvi) != resp->cont_len) 404 warning_popup_long("HTTP body len: expected %i but read %i\n", 405 "OK", NULL, resp->cont_len, ftell(dvi)); 406 407 if (fseek(dvi, 0L, SEEK_SET) < 0) 408 err(1, "fseek"); 409 410 free_resp: 411 free(resp); 412 free_buf: 413 free(buf); 414 415 return dvi; 416 } 417 418 419 /* 420 * Parses the url, resolves and connects to the host, creates a tls 421 * connection to the host, sends an HTTP GET request then reads the 422 * response. 423 * 424 * Returns a FILE* buffer containing a DVI file on success, or NULL on 425 * failure. 426 */ 427 FILE * 428 http_get(const char dest_url[]) 429 { 430 struct url *url; 431 int server; 432 SSL *tls; 433 FILE *dvi = NULL; 434 435 if ((url = parse_url(dest_url)) == NULL) 436 return NULL; 437 438 if ((server = xconnect(url)) < 0) 439 goto free_url; 440 441 if ((tls = tls_connect(server, url)) == NULL) 442 goto free_server; 443 444 if (send_request(tls, url) != 0) 445 goto free_tls; 446 447 if ((dvi = read_response(tls, url)) == NULL) 448 goto free_tls; 449 450 free_tls: 451 SSL_CTX_free(SSL_get_SSL_CTX(tls)); 452 SSL_free(tls); 453 free_server: 454 close(server); 455 free_url: 456 free(url); 457 458 return dvi; 459 }