1*30a08502Stb /* $OpenBSD: tal.c,v 1.41 2024/11/13 12:51:04 tb Exp $ */ 29a7e9e7fSjob /* 39a7e9e7fSjob * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 49a7e9e7fSjob * 59a7e9e7fSjob * Permission to use, copy, modify, and distribute this software for any 69a7e9e7fSjob * purpose with or without fee is hereby granted, provided that the above 79a7e9e7fSjob * copyright notice and this permission notice appear in all copies. 89a7e9e7fSjob * 99a7e9e7fSjob * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 109a7e9e7fSjob * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 119a7e9e7fSjob * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 129a7e9e7fSjob * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 139a7e9e7fSjob * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 149a7e9e7fSjob * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 159a7e9e7fSjob * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 169a7e9e7fSjob */ 179a7e9e7fSjob 189a7e9e7fSjob #include <assert.h> 199a7e9e7fSjob #include <err.h> 20a382efa2Sclaudio #include <libgen.h> 219a7e9e7fSjob #include <stdio.h> 229a7e9e7fSjob #include <stdlib.h> 239a7e9e7fSjob #include <string.h> 249a7e9e7fSjob 259a7e9e7fSjob #include "extern.h" 269a7e9e7fSjob 27e7f33a00Sclaudio static int 2808c1ca85Sclaudio tal_cmp(const void *a, const void *b) 2908c1ca85Sclaudio { 3008c1ca85Sclaudio char * const *sa = a; 3108c1ca85Sclaudio char * const *sb = b; 3208c1ca85Sclaudio 3308c1ca85Sclaudio return strcmp(*sa, *sb); 3408c1ca85Sclaudio } 3508c1ca85Sclaudio 369a7e9e7fSjob /* 3708c1ca85Sclaudio * Inner function for parsing RFC 8630 from a buffer. 389a7e9e7fSjob * Returns a valid pointer on success, NULL otherwise. 399a7e9e7fSjob * The pointer must be freed with tal_free(). 409a7e9e7fSjob */ 419a7e9e7fSjob static struct tal * 4241edc670Sclaudio tal_parse_buffer(const char *fn, char *buf, size_t len) 439a7e9e7fSjob { 4408c1ca85Sclaudio char *nl, *line, *f, *file = NULL; 45e7f33a00Sclaudio unsigned char *der; 46aebfeadbSclaudio size_t dersz; 47e7f33a00Sclaudio int rc = 0; 489a7e9e7fSjob struct tal *tal = NULL; 499a7e9e7fSjob EVP_PKEY *pkey = NULL; 5041edc670Sclaudio int optcomment = 1; 519a7e9e7fSjob 529a7e9e7fSjob if ((tal = calloc(1, sizeof(struct tal))) == NULL) 5363749b15Sbenno err(1, NULL); 549a7e9e7fSjob 55397f8a58Sclaudio /* Begin with the URI section, comment section already removed. */ 5641edc670Sclaudio while ((nl = memchr(buf, '\n', len)) != NULL) { 57397f8a58Sclaudio line = buf; 5841edc670Sclaudio 59397f8a58Sclaudio /* advance buffer to next line */ 6041edc670Sclaudio len -= nl + 1 - buf; 61397f8a58Sclaudio buf = nl + 1; 629a7e9e7fSjob 637cc8496aStb /* replace LF and optional CR with NUL, point nl at first NUL */ 647cc8496aStb *nl = '\0'; 657cc8496aStb if (nl > line && nl[-1] == '\r') { 667cc8496aStb nl[-1] = '\0'; 677cc8496aStb nl--; 687cc8496aStb } 697cc8496aStb 7041edc670Sclaudio if (optcomment) { 7141edc670Sclaudio /* if this is a comment, just eat the line */ 7241edc670Sclaudio if (line[0] == '#') 7341edc670Sclaudio continue; 7441edc670Sclaudio optcomment = 0; 7541edc670Sclaudio } 7641edc670Sclaudio 779a7e9e7fSjob /* Zero-length line is end of section. */ 78397f8a58Sclaudio if (*line == '\0') 799a7e9e7fSjob break; 809a7e9e7fSjob 813e23e2bcSclaudio /* make sure only US-ASCII chars are in the URL */ 828c2eb288Sclaudio if (!valid_uri(line, nl - line, NULL)) { 833e23e2bcSclaudio warnx("%s: invalid URI", fn); 843e23e2bcSclaudio goto out; 853e23e2bcSclaudio } 8608c1ca85Sclaudio /* Check that the URI is sensible */ 870610060dSjob if (!(strncasecmp(line, HTTPS_PROTO, HTTPS_PROTO_LEN) == 0 || 880610060dSjob strncasecmp(line, RSYNC_PROTO, RSYNC_PROTO_LEN) == 0)) { 8908c1ca85Sclaudio warnx("%s: unsupported URL schema: %s", fn, line); 9008c1ca85Sclaudio goto out; 9108c1ca85Sclaudio } 9208c1ca85Sclaudio if (strcasecmp(nl - 4, ".cer")) { 9308c1ca85Sclaudio warnx("%s: not a certificate URL: %s", fn, line); 9408c1ca85Sclaudio goto out; 9571d558daSclaudio } 9671d558daSclaudio 979a7e9e7fSjob /* Append to list of URIs. */ 989a7e9e7fSjob tal->uri = reallocarray(tal->uri, 99*30a08502Stb tal->num_uris + 1, sizeof(char *)); 1009a7e9e7fSjob if (tal->uri == NULL) 10163749b15Sbenno err(1, NULL); 1029a7e9e7fSjob 103*30a08502Stb tal->uri[tal->num_uris] = strdup(line); 104*30a08502Stb if (tal->uri[tal->num_uris] == NULL) 10563749b15Sbenno err(1, NULL); 106*30a08502Stb tal->num_uris++; 1079a7e9e7fSjob 10808c1ca85Sclaudio f = strrchr(line, '/') + 1; /* can not fail */ 10908c1ca85Sclaudio if (file) { 11008c1ca85Sclaudio if (strcmp(file, f)) { 11108c1ca85Sclaudio warnx("%s: URL with different file name %s, " 11208c1ca85Sclaudio "instead of %s", fn, f, file); 1139a7e9e7fSjob goto out; 114397f8a58Sclaudio } 11508c1ca85Sclaudio } else 11608c1ca85Sclaudio file = f; 117397f8a58Sclaudio } 1189a7e9e7fSjob 119*30a08502Stb if (tal->num_uris == 0) { 120ae92bda2Sjob warnx("%s: no URIs in TAL file", fn); 1219a7e9e7fSjob goto out; 12208c1ca85Sclaudio } 12308c1ca85Sclaudio 12408c1ca85Sclaudio /* sort uri lexicographically so https:// is preferred */ 125*30a08502Stb qsort(tal->uri, tal->num_uris, sizeof(tal->uri[0]), tal_cmp); 1269a7e9e7fSjob 127aebfeadbSclaudio /* Now the Base64-encoded public key. */ 12841edc670Sclaudio if ((base64_decode(buf, len, &der, &dersz)) == -1) { 12980272c49Sderaadt warnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: " 130aebfeadbSclaudio "bad public key", fn); 1319a7e9e7fSjob goto out; 1329a7e9e7fSjob } 1339a7e9e7fSjob 134e7f33a00Sclaudio tal->pkey = der; 135e7f33a00Sclaudio tal->pkeysz = dersz; 1369a7e9e7fSjob 1379a7e9e7fSjob /* Make sure it's a valid public key. */ 138e7f33a00Sclaudio pkey = d2i_PUBKEY(NULL, (const unsigned char **)&der, dersz); 1399a7e9e7fSjob if (pkey == NULL) { 140c0528901Stb warnx("%s: RFC 7730 section 2.1: subjectPublicKeyInfo: " 1419a7e9e7fSjob "failed public key parse", fn); 1429a7e9e7fSjob goto out; 1439a7e9e7fSjob } 1449a7e9e7fSjob rc = 1; 1459a7e9e7fSjob out: 1469a7e9e7fSjob if (rc == 0) { 1479a7e9e7fSjob tal_free(tal); 1489a7e9e7fSjob tal = NULL; 1499a7e9e7fSjob } 1509a7e9e7fSjob EVP_PKEY_free(pkey); 1519a7e9e7fSjob return tal; 1529a7e9e7fSjob } 1539a7e9e7fSjob 1549a7e9e7fSjob /* 155fec153cdSclaudio * Parse a TAL from "buf" conformant to RFC 7730 originally from a file 156fec153cdSclaudio * named "fn". 157fec153cdSclaudio * Returns the encoded data or NULL on syntax failure. 1589a7e9e7fSjob */ 1599a7e9e7fSjob struct tal * 16041edc670Sclaudio tal_parse(const char *fn, char *buf, size_t len) 1619a7e9e7fSjob { 1629a7e9e7fSjob struct tal *p; 163b55f176aSderaadt const char *d; 164a382efa2Sclaudio size_t dlen; 1659a7e9e7fSjob 16641edc670Sclaudio p = tal_parse_buffer(fn, buf, len); 1676978176aSclaudio if (p == NULL) 1686978176aSclaudio return NULL; 169a382efa2Sclaudio 170a382efa2Sclaudio /* extract the TAL basename (without .tal suffix) */ 171b55f176aSderaadt d = strrchr(fn, '/'); 172a382efa2Sclaudio if (d == NULL) 173b55f176aSderaadt d = fn; 174b55f176aSderaadt else 175b55f176aSderaadt d++; 176a382efa2Sclaudio dlen = strlen(d); 177b6929da3Sclaudio if (dlen > 4 && strcasecmp(d + dlen - 4, ".tal") == 0) 178a382efa2Sclaudio dlen -= 4; 179cf76813dSclaudio if ((p->descr = strndup(d, dlen)) == NULL) 18063749b15Sbenno err(1, NULL); 181a382efa2Sclaudio 1829a7e9e7fSjob return p; 1839a7e9e7fSjob } 1849a7e9e7fSjob 185fec153cdSclaudio /* 1869a7e9e7fSjob * Free a TAL pointer. 1879a7e9e7fSjob * Safe to call with NULL. 1889a7e9e7fSjob */ 1899a7e9e7fSjob void 1909a7e9e7fSjob tal_free(struct tal *p) 1919a7e9e7fSjob { 1929a7e9e7fSjob size_t i; 1939a7e9e7fSjob 1949a7e9e7fSjob if (p == NULL) 1959a7e9e7fSjob return; 1969a7e9e7fSjob 1979a7e9e7fSjob if (p->uri != NULL) 198*30a08502Stb for (i = 0; i < p->num_uris; i++) 1999a7e9e7fSjob free(p->uri[i]); 2009a7e9e7fSjob 2019a7e9e7fSjob free(p->pkey); 2029a7e9e7fSjob free(p->uri); 203a382efa2Sclaudio free(p->descr); 2049a7e9e7fSjob free(p); 2059a7e9e7fSjob } 2069a7e9e7fSjob 2079a7e9e7fSjob /* 2089a7e9e7fSjob * Buffer TAL parsed contents for writing. 2099a7e9e7fSjob * See tal_read() for the other side of the pipe. 2109a7e9e7fSjob */ 2119a7e9e7fSjob void 21208db1177Sclaudio tal_buffer(struct ibuf *b, const struct tal *p) 2139a7e9e7fSjob { 2149a7e9e7fSjob size_t i; 2159a7e9e7fSjob 216dc508150Sclaudio io_simple_buffer(b, &p->id, sizeof(p->id)); 21708db1177Sclaudio io_buf_buffer(b, p->pkey, p->pkeysz); 21808db1177Sclaudio io_str_buffer(b, p->descr); 219*30a08502Stb io_simple_buffer(b, &p->num_uris, sizeof(p->num_uris)); 2209a7e9e7fSjob 221*30a08502Stb for (i = 0; i < p->num_uris; i++) 22208db1177Sclaudio io_str_buffer(b, p->uri[i]); 2239a7e9e7fSjob } 2249a7e9e7fSjob 2259a7e9e7fSjob /* 2269a7e9e7fSjob * Read parsed TAL contents from descriptor. 2279a7e9e7fSjob * See tal_buffer() for the other side of the pipe. 2289a7e9e7fSjob * A returned pointer must be freed with tal_free(). 2299a7e9e7fSjob */ 2309a7e9e7fSjob struct tal * 2317eb79a4aSclaudio tal_read(struct ibuf *b) 2329a7e9e7fSjob { 2339a7e9e7fSjob size_t i; 2349a7e9e7fSjob struct tal *p; 2359a7e9e7fSjob 2369a7e9e7fSjob if ((p = calloc(1, sizeof(struct tal))) == NULL) 23763749b15Sbenno err(1, NULL); 2389a7e9e7fSjob 239dc508150Sclaudio io_read_buf(b, &p->id, sizeof(p->id)); 2407eb79a4aSclaudio io_read_buf_alloc(b, (void **)&p->pkey, &p->pkeysz); 2417eb79a4aSclaudio io_read_str(b, &p->descr); 242*30a08502Stb io_read_buf(b, &p->num_uris, sizeof(p->num_uris)); 2439a7e9e7fSjob assert(p->pkeysz > 0); 24452c8fec2Sclaudio assert(p->descr); 245*30a08502Stb assert(p->num_uris > 0); 2469a7e9e7fSjob 247*30a08502Stb if ((p->uri = calloc(p->num_uris, sizeof(char *))) == NULL) 24863749b15Sbenno err(1, NULL); 2499a7e9e7fSjob 250*30a08502Stb for (i = 0; i < p->num_uris; i++) { 2517eb79a4aSclaudio io_read_str(b, &p->uri[i]); 25252c8fec2Sclaudio assert(p->uri[i]); 25352c8fec2Sclaudio } 2549a7e9e7fSjob 2559a7e9e7fSjob return p; 2569a7e9e7fSjob } 257