wdvi

network DVI viewer
Log | Files | Refs

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 }