xref: /openbsd/usr.sbin/acme-client/http.c (revision 4cfece93)
1 /*	$Id: http.c,v 1.29 2020/01/20 22:10:27 sthen Exp $ */
2 /*
3  * Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 
21 #include <arpa/inet.h>
22 #include <netinet/in.h>
23 
24 #include <ctype.h>
25 #include <err.h>
26 #include <limits.h>
27 #include <netdb.h>
28 #include <stdio.h>
29 #include <stdint.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <tls.h>
33 #include <unistd.h>
34 
35 #include "http.h"
36 #include "extern.h"
37 
38 /*
39  * A buffer for transferring HTTP/S data.
40  */
41 struct	httpxfer {
42 	char		*hbuf;    /* header transfer buffer */
43 	size_t		 hbufsz;  /* header buffer size */
44 	int		 headok;  /* header has been parsed */
45 	char		*bbuf;    /* body transfer buffer */
46 	size_t		 bbufsz;  /* body buffer size */
47 	int		 bodyok;  /* body has been parsed */
48 	char		*headbuf; /* lookaside buffer for headers */
49 	struct httphead	*head;    /* parsed headers */
50 	size_t		 headsz;  /* number of headers */
51 };
52 
53 /*
54  * An HTTP/S connection object.
55  */
56 struct	http {
57 	int		   fd;     /* connected socket */
58 	short		   port;   /* port number */
59 	struct source	   src;    /* endpoint (raw) host */
60 	char		  *path;   /* path to request */
61 	char		  *host;   /* name of endpoint host */
62 	struct tls	  *ctx;    /* if TLS */
63 	writefp		   writer; /* write function */
64 	readfp		   reader; /* read function */
65 };
66 
67 struct tls_config *tlscfg;
68 
69 static ssize_t
70 dosysread(char *buf, size_t sz, const struct http *http)
71 {
72 	ssize_t	 rc;
73 
74 	rc = read(http->fd, buf, sz);
75 	if (rc == -1)
76 		warn("%s: read", http->src.ip);
77 	return rc;
78 }
79 
80 static ssize_t
81 dosyswrite(const void *buf, size_t sz, const struct http *http)
82 {
83 	ssize_t	 rc;
84 
85 	rc = write(http->fd, buf, sz);
86 	if (rc == -1)
87 		warn("%s: write", http->src.ip);
88 	return rc;
89 }
90 
91 static ssize_t
92 dotlsread(char *buf, size_t sz, const struct http *http)
93 {
94 	ssize_t	 rc;
95 
96 	do {
97 		rc = tls_read(http->ctx, buf, sz);
98 	} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
99 
100 	if (rc == -1)
101 		warnx("%s: tls_read: %s", http->src.ip,
102 		    tls_error(http->ctx));
103 	return rc;
104 }
105 
106 static ssize_t
107 dotlswrite(const void *buf, size_t sz, const struct http *http)
108 {
109 	ssize_t	 rc;
110 
111 	do {
112 		rc = tls_write(http->ctx, buf, sz);
113 	} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
114 
115 	if (rc == -1)
116 		warnx("%s: tls_write: %s", http->src.ip,
117 		    tls_error(http->ctx));
118 	return rc;
119 }
120 
121 int
122 http_init()
123 {
124 	if (tlscfg != NULL)
125 		return 0;
126 
127 	if (tls_init() == -1) {
128 		warn("tls_init");
129 		goto err;
130 	}
131 
132 	tlscfg = tls_config_new();
133 	if (tlscfg == NULL) {
134 		warn("tls_config_new");
135 		goto err;
136 	}
137 
138 	if (tls_config_set_ca_file(tlscfg, tls_default_ca_cert_file()) == -1) {
139 		warn("tls_config_set_ca_file: %s", tls_config_error(tlscfg));
140 		goto err;
141 	}
142 
143 	return 0;
144 
145  err:
146 	tls_config_free(tlscfg);
147 	tlscfg = NULL;
148 
149 	return -1;
150 }
151 
152 static ssize_t
153 http_read(char *buf, size_t sz, const struct http *http)
154 {
155 	ssize_t	 ssz, xfer;
156 
157 	xfer = 0;
158 	do {
159 		if ((ssz = http->reader(buf, sz, http)) < 0)
160 			return -1;
161 		if (ssz == 0)
162 			break;
163 		xfer += ssz;
164 		sz -= ssz;
165 		buf += ssz;
166 	} while (ssz > 0 && sz > 0);
167 
168 	return xfer;
169 }
170 
171 static int
172 http_write(const char *buf, size_t sz, const struct http *http)
173 {
174 	ssize_t	 ssz, xfer;
175 
176 	xfer = sz;
177 	while (sz > 0) {
178 		if ((ssz = http->writer(buf, sz, http)) < 0)
179 			return -1;
180 		sz -= ssz;
181 		buf += (size_t)ssz;
182 	}
183 	return xfer;
184 }
185 
186 void
187 http_disconnect(struct http *http)
188 {
189 	int rc;
190 
191 	if (http->ctx != NULL) {
192 		/* TLS connection. */
193 		do {
194 			rc = tls_close(http->ctx);
195 		} while (rc == TLS_WANT_POLLIN || rc == TLS_WANT_POLLOUT);
196 
197 		tls_free(http->ctx);
198 	}
199 	if (http->fd != -1) {
200 		if (close(http->fd) == -1)
201 			warn("%s: close", http->src.ip);
202 	}
203 
204 	http->fd = -1;
205 	http->ctx = NULL;
206 }
207 
208 void
209 http_free(struct http *http)
210 {
211 
212 	if (http == NULL)
213 		return;
214 	http_disconnect(http);
215 	free(http->host);
216 	free(http->path);
217 	free(http->src.ip);
218 	free(http);
219 }
220 
221 struct http *
222 http_alloc(const struct source *addrs, size_t addrsz,
223     const char *host, short port, const char *path)
224 {
225 	struct sockaddr_storage ss;
226 	int		 family, fd, c;
227 	socklen_t	 len;
228 	size_t		 cur, i = 0;
229 	struct http	*http;
230 
231 	/* Do this while we still have addresses to connect. */
232 again:
233 	if (i == addrsz)
234 		return NULL;
235 	cur = i++;
236 
237 	/* Convert to PF_INET or PF_INET6 address from string. */
238 
239 	memset(&ss, 0, sizeof(struct sockaddr_storage));
240 
241 	if (addrs[cur].family == 4) {
242 		family = PF_INET;
243 		((struct sockaddr_in *)&ss)->sin_family = AF_INET;
244 		((struct sockaddr_in *)&ss)->sin_port = htons(port);
245 		c = inet_pton(AF_INET, addrs[cur].ip,
246 		    &((struct sockaddr_in *)&ss)->sin_addr);
247 		len = sizeof(struct sockaddr_in);
248 	} else if (addrs[cur].family == 6) {
249 		family = PF_INET6;
250 		((struct sockaddr_in6 *)&ss)->sin6_family = AF_INET6;
251 		((struct sockaddr_in6 *)&ss)->sin6_port = htons(port);
252 		c = inet_pton(AF_INET6, addrs[cur].ip,
253 		    &((struct sockaddr_in6 *)&ss)->sin6_addr);
254 		len = sizeof(struct sockaddr_in6);
255 	} else {
256 		warnx("%s: unknown family", addrs[cur].ip);
257 		goto again;
258 	}
259 
260 	if (c < 0) {
261 		warn("%s: inet_ntop", addrs[cur].ip);
262 		goto again;
263 	} else if (c == 0) {
264 		warnx("%s: inet_ntop", addrs[cur].ip);
265 		goto again;
266 	}
267 
268 	/* Create socket and connect. */
269 
270 	fd = socket(family, SOCK_STREAM, 0);
271 	if (fd == -1) {
272 		warn("%s: socket", addrs[cur].ip);
273 		goto again;
274 	} else if (connect(fd, (struct sockaddr *)&ss, len) == -1) {
275 		warn("%s: connect", addrs[cur].ip);
276 		close(fd);
277 		goto again;
278 	}
279 
280 	/* Allocate the communicator. */
281 
282 	http = calloc(1, sizeof(struct http));
283 	if (http == NULL) {
284 		warn("calloc");
285 		close(fd);
286 		return NULL;
287 	}
288 	http->fd = fd;
289 	http->port = port;
290 	http->src.family = addrs[cur].family;
291 	http->src.ip = strdup(addrs[cur].ip);
292 	http->host = strdup(host);
293 	http->path = strdup(path);
294 	if (http->src.ip == NULL || http->host == NULL || http->path == NULL) {
295 		warn("strdup");
296 		goto err;
297 	}
298 
299 	/* If necessary, do our TLS setup. */
300 
301 	if (port != 443) {
302 		http->writer = dosyswrite;
303 		http->reader = dosysread;
304 		return http;
305 	}
306 
307 	http->writer = dotlswrite;
308 	http->reader = dotlsread;
309 
310 	if ((http->ctx = tls_client()) == NULL) {
311 		warn("tls_client");
312 		goto err;
313 	} else if (tls_configure(http->ctx, tlscfg) == -1) {
314 		warnx("%s: tls_configure: %s",
315 			http->src.ip, tls_error(http->ctx));
316 		goto err;
317 	}
318 
319 	if (tls_connect_socket(http->ctx, http->fd, http->host) != 0) {
320 		warnx("%s: tls_connect_socket: %s, %s", http->src.ip,
321 		    http->host, tls_error(http->ctx));
322 		goto err;
323 	}
324 
325 	return http;
326 err:
327 	http_free(http);
328 	return NULL;
329 }
330 
331 struct httpxfer *
332 http_open(const struct http *http, int headreq, const void *p, size_t psz)
333 {
334 	char		*req;
335 	int		 c;
336 	struct httpxfer	*trans;
337 
338 	if (p == NULL) {
339 		if (headreq)
340 			c = asprintf(&req,
341 			    "HEAD %s HTTP/1.0\r\n"
342 			    "Host: %s\r\n"
343 			    "User-Agent: OpenBSD-acme-client\r\n"
344 			    "\r\n",
345 			    http->path, http->host);
346 		else
347 			c = asprintf(&req,
348 			    "GET %s HTTP/1.0\r\n"
349 			    "Host: %s\r\n"
350 			    "User-Agent: OpenBSD-acme-client\r\n"
351 			    "\r\n",
352 			    http->path, http->host);
353 	} else {
354 		c = asprintf(&req,
355 		    "POST %s HTTP/1.0\r\n"
356 		    "Host: %s\r\n"
357 		    "Content-Length: %zu\r\n"
358 		    "Content-Type: application/jose+json\r\n"
359 		    "User-Agent: OpenBSD-acme-client\r\n"
360 		    "\r\n",
361 		    http->path, http->host, psz);
362 	}
363 
364 	if (c == -1) {
365 		warn("asprintf");
366 		return NULL;
367 	} else if (!http_write(req, c, http)) {
368 		free(req);
369 		return NULL;
370 	} else if (p != NULL && !http_write(p, psz, http)) {
371 		free(req);
372 		return NULL;
373 	}
374 
375 	free(req);
376 
377 	trans = calloc(1, sizeof(struct httpxfer));
378 	if (trans == NULL)
379 		warn("calloc");
380 	return trans;
381 }
382 
383 void
384 http_close(struct httpxfer *x)
385 {
386 
387 	if (x == NULL)
388 		return;
389 	free(x->hbuf);
390 	free(x->bbuf);
391 	free(x->headbuf);
392 	free(x->head);
393 	free(x);
394 }
395 
396 /*
397  * Read the HTTP body from the wire.
398  * If invoked multiple times, this will return the same pointer with the
399  * same data (or NULL, if the original invocation returned NULL).
400  * Returns NULL if read or allocation errors occur.
401  * You must not free the returned pointer.
402  */
403 char *
404 http_body_read(const struct http *http, struct httpxfer *trans, size_t *sz)
405 {
406 	char		 buf[BUFSIZ];
407 	ssize_t		 ssz;
408 	void		*pp;
409 	size_t		 szp;
410 
411 	if (sz == NULL)
412 		sz = &szp;
413 
414 	/* Have we already parsed this? */
415 
416 	if (trans->bodyok > 0) {
417 		*sz = trans->bbufsz;
418 		return trans->bbuf;
419 	} else if (trans->bodyok < 0)
420 		return NULL;
421 
422 	*sz = 0;
423 	trans->bodyok = -1;
424 
425 	do {
426 		/* If less than sizeof(buf), at EOF. */
427 		if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
428 			return NULL;
429 		else if (ssz == 0)
430 			break;
431 		pp = recallocarray(trans->bbuf,
432 		    trans->bbufsz, trans->bbufsz + ssz, 1);
433 		if (pp == NULL) {
434 			warn("recallocarray");
435 			return NULL;
436 		}
437 		trans->bbuf = pp;
438 		memcpy(trans->bbuf + trans->bbufsz, buf, ssz);
439 		trans->bbufsz += ssz;
440 	} while (ssz == sizeof(buf));
441 
442 	trans->bodyok = 1;
443 	*sz = trans->bbufsz;
444 	return trans->bbuf;
445 }
446 
447 struct httphead *
448 http_head_get(const char *v, struct httphead *h, size_t hsz)
449 {
450 	size_t	 i;
451 
452 	for (i = 0; i < hsz; i++) {
453 		if (strcasecmp(h[i].key, v) == 0)
454 			return &h[i];
455 	}
456 	return NULL;
457 }
458 
459 /*
460  * Look through the headers and determine our HTTP code.
461  * This will return -1 on failure, otherwise the code.
462  */
463 int
464 http_head_status(const struct http *http, struct httphead *h, size_t sz)
465 {
466 	int		 rc;
467 	unsigned int	 code;
468 	struct httphead *st;
469 
470 	if ((st = http_head_get("Status", h, sz)) == NULL) {
471 		warnx("%s: no status header", http->src.ip);
472 		return -1;
473 	}
474 
475 	rc = sscanf(st->val, "%*s %u %*s", &code);
476 	if (rc < 0) {
477 		warn("sscanf");
478 		return -1;
479 	} else if (rc != 1) {
480 		warnx("%s: cannot convert status header", http->src.ip);
481 		return -1;
482 	}
483 	return code;
484 }
485 
486 /*
487  * Parse headers from the transfer.
488  * Malformed headers are skipped.
489  * A special "Status" header is added for the HTTP status line.
490  * This can only happen once http_head_read has been called with
491  * success.
492  * This can be invoked multiple times: it will only parse the headers
493  * once and after that it will just return the cache.
494  * You must not free the returned pointer.
495  * If the original header parse failed, or if memory allocation fails
496  * internally, this returns NULL.
497  */
498 struct httphead *
499 http_head_parse(const struct http *http, struct httpxfer *trans, size_t *sz)
500 {
501 	size_t		 hsz, szp;
502 	struct httphead	*h;
503 	char		*cp, *ep, *ccp, *buf;
504 
505 	if (sz == NULL)
506 		sz = &szp;
507 
508 	/*
509 	 * If we've already parsed the headers, return the
510 	 * previously-parsed buffer now.
511 	 * If we have errors on the stream, return NULL now.
512 	 */
513 
514 	if (trans->head != NULL) {
515 		*sz = trans->headsz;
516 		return trans->head;
517 	} else if (trans->headok <= 0)
518 		return NULL;
519 
520 	if ((buf = strdup(trans->hbuf)) == NULL) {
521 		warn("strdup");
522 		return NULL;
523 	}
524 	hsz = 0;
525 	cp = buf;
526 
527 	do {
528 		if ((cp = strstr(cp, "\r\n")) != NULL)
529 			cp += 2;
530 		hsz++;
531 	} while (cp != NULL);
532 
533 	/*
534 	 * Allocate headers, then step through the data buffer, parsing
535 	 * out headers as we have them.
536 	 * We know at this point that the buffer is NUL-terminated in
537 	 * the usual way.
538 	 */
539 
540 	h = calloc(hsz, sizeof(struct httphead));
541 	if (h == NULL) {
542 		warn("calloc");
543 		free(buf);
544 		return NULL;
545 	}
546 
547 	*sz = hsz;
548 	hsz = 0;
549 	cp = buf;
550 
551 	do {
552 		if ((ep = strstr(cp, "\r\n")) != NULL) {
553 			*ep = '\0';
554 			ep += 2;
555 		}
556 		if (hsz == 0) {
557 			h[hsz].key = "Status";
558 			h[hsz++].val = cp;
559 			continue;
560 		}
561 
562 		/* Skip bad headers. */
563 		if ((ccp = strchr(cp, ':')) == NULL) {
564 			warnx("%s: header without separator", http->src.ip);
565 			continue;
566 		}
567 
568 		*ccp++ = '\0';
569 		while (isspace((int)*ccp))
570 			ccp++;
571 		h[hsz].key = cp;
572 		h[hsz++].val = ccp;
573 	} while ((cp = ep) != NULL);
574 
575 	trans->headbuf = buf;
576 	trans->head = h;
577 	trans->headsz = hsz;
578 	return h;
579 }
580 
581 /*
582  * Read the HTTP headers from the wire.
583  * If invoked multiple times, this will return the same pointer with the
584  * same data (or NULL, if the original invocation returned NULL).
585  * Returns NULL if read or allocation errors occur.
586  * You must not free the returned pointer.
587  */
588 char *
589 http_head_read(const struct http *http, struct httpxfer *trans, size_t *sz)
590 {
591 	char		 buf[BUFSIZ];
592 	ssize_t		 ssz;
593 	char		*ep;
594 	void		*pp;
595 	size_t		 szp;
596 
597 	if (sz == NULL)
598 		sz = &szp;
599 
600 	/* Have we already parsed this? */
601 
602 	if (trans->headok > 0) {
603 		*sz = trans->hbufsz;
604 		return trans->hbuf;
605 	} else if (trans->headok < 0)
606 		return NULL;
607 
608 	*sz = 0;
609 	ep = NULL;
610 	trans->headok = -1;
611 
612 	/*
613 	 * Begin by reading by BUFSIZ blocks until we reach the header
614 	 * termination marker (two CRLFs).
615 	 * We might read into our body, but that's ok: we'll copy out
616 	 * the body parts into our body buffer afterward.
617 	 */
618 
619 	do {
620 		/* If less than sizeof(buf), at EOF. */
621 		if ((ssz = http_read(buf, sizeof(buf), http)) < 0)
622 			return NULL;
623 		else if (ssz == 0)
624 			break;
625 		pp = recallocarray(trans->hbuf,
626 		    trans->hbufsz, trans->hbufsz + ssz, 1);
627 		if (pp == NULL) {
628 			warn("recallocarray");
629 			return NULL;
630 		}
631 		trans->hbuf = pp;
632 		memcpy(trans->hbuf + trans->hbufsz, buf, ssz);
633 		trans->hbufsz += ssz;
634 		/* Search for end of headers marker. */
635 		ep = memmem(trans->hbuf, trans->hbufsz, "\r\n\r\n", 4);
636 	} while (ep == NULL && ssz == sizeof(buf));
637 
638 	if (ep == NULL) {
639 		warnx("%s: partial transfer", http->src.ip);
640 		return NULL;
641 	}
642 	*ep = '\0';
643 
644 	/*
645 	 * The header data is invalid if it has any binary characters in
646 	 * it: check that now.
647 	 * This is important because we want to guarantee that all
648 	 * header keys and pairs are properly NUL-terminated.
649 	 */
650 
651 	if (strlen(trans->hbuf) != (uintptr_t)(ep - trans->hbuf)) {
652 		warnx("%s: binary data in header", http->src.ip);
653 		return NULL;
654 	}
655 
656 	/*
657 	 * Copy remaining buffer into body buffer.
658 	 */
659 
660 	ep += 4;
661 	trans->bbufsz = (trans->hbuf + trans->hbufsz) - ep;
662 	trans->bbuf = malloc(trans->bbufsz);
663 	if (trans->bbuf == NULL) {
664 		warn("malloc");
665 		return NULL;
666 	}
667 	memcpy(trans->bbuf, ep, trans->bbufsz);
668 
669 	trans->headok = 1;
670 	*sz = trans->hbufsz;
671 	return trans->hbuf;
672 }
673 
674 void
675 http_get_free(struct httpget *g)
676 {
677 
678 	if (g == NULL)
679 		return;
680 	http_close(g->xfer);
681 	http_free(g->http);
682 	free(g);
683 }
684 
685 struct httpget *
686 http_get(const struct source *addrs, size_t addrsz, const char *domain,
687     short port, const char *path, int headreq, const void *post, size_t postsz)
688 {
689 	struct http	*h;
690 	struct httpxfer	*x;
691 	struct httpget	*g;
692 	struct httphead	*head;
693 	size_t		 headsz, bodsz, headrsz;
694 	int		 code;
695 	char		*bod, *headr;
696 
697 	h = http_alloc(addrs, addrsz, domain, port, path);
698 	if (h == NULL)
699 		return NULL;
700 
701 	if ((x = http_open(h, headreq, post, postsz)) == NULL) {
702 		http_free(h);
703 		return NULL;
704 	} else if ((headr = http_head_read(h, x, &headrsz)) == NULL) {
705 		http_close(x);
706 		http_free(h);
707 		return NULL;
708 	} else if ((bod = http_body_read(h, x, &bodsz)) == NULL) {
709 		http_close(x);
710 		http_free(h);
711 		return NULL;
712 	}
713 
714 	http_disconnect(h);
715 
716 	if ((head = http_head_parse(h, x, &headsz)) == NULL) {
717 		http_close(x);
718 		http_free(h);
719 		return NULL;
720 	} else if ((code = http_head_status(h, head, headsz)) < 0) {
721 		http_close(x);
722 		http_free(h);
723 		return NULL;
724 	}
725 
726 	if ((g = calloc(1, sizeof(struct httpget))) == NULL) {
727 		warn("calloc");
728 		http_close(x);
729 		http_free(h);
730 		return NULL;
731 	}
732 
733 	g->headpart = headr;
734 	g->headpartsz = headrsz;
735 	g->bodypart = bod;
736 	g->bodypartsz = bodsz;
737 	g->head = head;
738 	g->headsz = headsz;
739 	g->code = code;
740 	g->xfer = x;
741 	g->http = h;
742 	return g;
743 }
744