/* * Copyright (c) 2013-2022 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "kore.h" static struct { char *name; int value; } month_names[] = { { "Jan", 0 }, { "Feb", 1 }, { "Mar", 2 }, { "Apr", 3 }, { "May", 4 }, { "Jun", 5 }, { "Jul", 6 }, { "Aug", 7 }, { "Sep", 8 }, { "Oct", 9 }, { "Nov", 10 }, { "Dec", 11 }, { NULL, 0 }, }; static void fatal_log(const char *, va_list); static int utils_base64_encode(const void *, size_t, char **, const char *, int); static int utils_base64_decode(const char *, u_int8_t **, size_t *, const char *, int); static int utils_x509name_tobuf(void *, int, int, const char *, const void *, size_t, int); static char b64_table[] = \ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static char b64url_table[] = \ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; /* b64_table and b64url_table are the same size. */ #define B64_TABLE_LEN (sizeof(b64_table)) size_t kore_strlcpy(char *dst, const char *src, const size_t len) { char *d = dst; const char *s = src; const char *end = dst + len - 1; if (len == 0) fatal("kore_strlcpy: len == 0"); while ((*d = *s) != '\0') { if (d == end) { *d = '\0'; break; } d++; s++; } while (*s != '\0') s++; return (s - src); } int kore_snprintf(char *str, size_t size, int *len, const char *fmt, ...) { int l; va_list args; va_start(args, fmt); l = vsnprintf(str, size, fmt, args); va_end(args); if (l == -1 || (size_t)l >= size) return (KORE_RESULT_ERROR); if (len != NULL) *len = l; return (KORE_RESULT_OK); } long long kore_strtonum(const char *str, int base, long long min, long long max, int *err) { long long l; char *ep; if (min > max) { *err = KORE_RESULT_ERROR; return (0); } errno = 0; l = strtoll(str, &ep, base); if (errno != 0 || str == ep || *ep != '\0') { *err = KORE_RESULT_ERROR; return (0); } if (l < min) { *err = KORE_RESULT_ERROR; return (0); } if (l > max) { *err = KORE_RESULT_ERROR; return (0); } *err = KORE_RESULT_OK; return (l); } u_int64_t kore_strtonum64(const char *str, int sign, int *err) { u_int64_t l; long long ll; char *ep; int check; l = 0; check = 1; ll = strtoll(str, &ep, 10); if ((errno == EINVAL || errno == ERANGE) && (ll == LLONG_MIN || ll == LLONG_MAX)) { if (sign) { *err = KORE_RESULT_ERROR; return (0); } check = 0; } if (!sign) { l = strtoull(str, &ep, 10); if ((errno == EINVAL || errno == ERANGE) && l == ULONG_MAX) { *err = KORE_RESULT_ERROR; return (0); } if (check && ll < 0) { *err = KORE_RESULT_ERROR; return (0); } } if (str == ep || *ep != '\0') { *err = KORE_RESULT_ERROR; return (0); } *err = KORE_RESULT_OK; return ((sign) ? (u_int64_t)ll : l); } double kore_strtodouble(const char *str, long double min, long double max, int *err) { double d; char *ep; if (min > max) { *err = KORE_RESULT_ERROR; return (0); } errno = 0; d = strtod(str, &ep); if (errno == ERANGE || str == ep || *ep != '\0') { *err = KORE_RESULT_ERROR; return (0); } if (d < min) { *err = KORE_RESULT_ERROR; return (0); } if (d > max) { *err = KORE_RESULT_ERROR; return (0); } *err = KORE_RESULT_OK; return (d); } int kore_split_string(char *input, const char *delim, char **out, size_t ele) { int count; char **ap; if (ele == 0) return (0); count = 0; for (ap = out; ap < &out[ele - 1] && (*ap = strsep(&input, delim)) != NULL;) { if (**ap != '\0') { ap++; count++; } } *ap = NULL; return (count); } void kore_strip_chars(const char *in, const char strip, char **out) { char *p; const char *s; u_int32_t len; len = strlen(in); *out = kore_malloc(len + 1); p = *out; for (s = in; s < (in + len); s++) { if (*s == strip) continue; *p++ = *s; } *p = '\0'; } time_t kore_date_to_time(const char *http_date) { time_t t; int err, i; struct tm tm, *ltm; char *args[7], *tbuf[5], *sdup; time(&t); ltm = localtime(&t); sdup = kore_strdup(http_date); t = KORE_RESULT_ERROR; if (kore_split_string(sdup, " ", args, 7) != 6) { kore_log(LOG_WARNING, "misformed http-date: '%s'", http_date); goto out; } memset(&tm, 0, sizeof(tm)); tm.tm_year = kore_strtonum(args[3], 10, 1900, 2068, &err) - 1900; if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed year in http-date: '%s'", http_date); goto out; } for (i = 0; month_names[i].name != NULL; i++) { if (!strcmp(month_names[i].name, args[2])) { tm.tm_mon = month_names[i].value; break; } } if (month_names[i].name == NULL) { kore_log(LOG_WARNING, "misformed month in http-date: '%s'", http_date); goto out; } tm.tm_mday = kore_strtonum(args[1], 10, 1, 31, &err); if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed mday in http-date: '%s'", http_date); goto out; } if (kore_split_string(args[4], ":", tbuf, 5) != 3) { kore_log(LOG_WARNING, "misformed HH:MM:SS in http-date: '%s'", http_date); goto out; } tm.tm_hour = kore_strtonum(tbuf[0], 10, 0, 23, &err); if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed hour in http-date: '%s'", http_date); goto out; } tm.tm_min = kore_strtonum(tbuf[1], 10, 0, 59, &err); if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed minutes in http-date: '%s'", http_date); goto out; } tm.tm_sec = kore_strtonum(tbuf[2], 10, 0, 60, &err); if (err == KORE_RESULT_ERROR) { kore_log(LOG_WARNING, "misformed seconds in http-date: '%s'", http_date); goto out; } tm.tm_isdst = ltm->tm_isdst; t = mktime(&tm) + ltm->tm_gmtoff; if (t == -1) { t = 0; kore_log(LOG_WARNING, "mktime() on '%s' failed", http_date); } out: kore_free(sdup); return (t); } char * kore_time_to_date(time_t now) { struct tm *tm; static time_t last = 0; static char tbuf[32]; if (now != last) { last = now; tm = gmtime(&now); if (!strftime(tbuf, sizeof(tbuf), "%a, %d %b %Y %T GMT", tm)) return (NULL); } return (tbuf); } u_int64_t kore_time_ms(void) { struct timespec ts; (void)clock_gettime(CLOCK_MONOTONIC, &ts); return ((u_int64_t)(ts.tv_sec * 1000 + (ts.tv_nsec / 1000000))); } int kore_base64url_encode(const void *data, size_t len, char **out, int flags) { return (utils_base64_encode(data, len, out, b64url_table, flags)); } int kore_base64_encode(const void *data, size_t len, char **out) { return (utils_base64_encode(data, len, out, b64_table, 0)); } int kore_base64url_decode(const char *in, u_int8_t **out, size_t *olen, int flags) { return (utils_base64_decode(in, out, olen, b64url_table, flags)); } int kore_base64_decode(const char *in, u_int8_t **out, size_t *olen) { return (utils_base64_decode(in, out, olen, b64_table, 0)); } void * kore_mem_find(void *src, size_t slen, const void *needle, size_t len) { size_t pos; for (pos = 0; pos < slen; pos++) { if ( *((u_int8_t *)src + pos) != *(const u_int8_t *)needle) continue; if ((slen - pos) < len) return (NULL); if (!memcmp((u_int8_t *)src + pos, needle, len)) return ((u_int8_t *)src + pos); } return (NULL); } char * kore_text_trim(char *string, size_t len) { char *end; if (len == 0) return (string); end = (string + len) - 1; while (isspace(*(unsigned char *)string) && string < end) string++; while (isspace(*(unsigned char *)end) && end > string) *(end)-- = '\0'; return (string); } char * kore_read_line(FILE *fp, char *in, size_t len) { char *p, *t; if (fgets(in, len, fp) == NULL) return (NULL); p = in; in[strcspn(in, "\n")] = '\0'; while (isspace(*(unsigned char *)p)) p++; if (p[0] == '#' || p[0] == '\0') { p[0] = '\0'; return (p); } for (t = p; *t != '\0'; t++) { if (*t == '\t') *t = ' '; } return (p); } const char * kore_worker_name(int id) { static char buf[64]; switch (id) { case KORE_WORKER_KEYMGR: (void)snprintf(buf, sizeof(buf), "[keymgr]"); break; case KORE_WORKER_ACME: (void)snprintf(buf, sizeof(buf), "[acme]"); break; default: (void)snprintf(buf, sizeof(buf), "[wrk %d]", id); break; } return (buf); } int kore_x509_issuer_name(struct connection *c, char **out, int flags) { struct kore_buf buf; KORE_X509_NAMES *name; if ((name = kore_tls_x509_issuer_name(c)) == NULL) return (KORE_RESULT_ERROR); kore_buf_init(&buf, 1024); if (!kore_tls_x509name_foreach(name, flags, &buf, utils_x509name_tobuf)) { kore_buf_cleanup(&buf); return (KORE_RESULT_ERROR); } *out = kore_buf_stringify(&buf, NULL); buf.offset = 0; buf.data = NULL; return (KORE_RESULT_OK); } int kore_x509_subject_name(struct connection *c, char **out, int flags) { struct kore_buf buf; KORE_X509_NAMES *name; if ((name = kore_tls_x509_subject_name(c)) == NULL) return (KORE_RESULT_ERROR); kore_buf_init(&buf, 1024); if (!kore_tls_x509name_foreach(name, flags, &buf, utils_x509name_tobuf)) { kore_buf_cleanup(&buf); return (KORE_RESULT_ERROR); } *out = kore_buf_stringify(&buf, NULL); buf.offset = 0; buf.data = NULL; return (KORE_RESULT_OK); } void fatal(const char *fmt, ...) { va_list args; va_start(args, fmt); fatal_log(fmt, args); va_end(args); exit(1); } void fatalx(const char *fmt, ...) { va_list args; /* In case people call fatalx() from the parent context. */ if (worker != NULL) kore_msg_send(KORE_MSG_PARENT, KORE_MSG_FATALX, NULL, 0); va_start(args, fmt); fatal_log(fmt, args); va_end(args); exit(1); } static void fatal_log(const char *fmt, va_list args) { char buf[2048]; (void)vsnprintf(buf, sizeof(buf), fmt, args); kore_log(LOG_ERR, "fatal: %s", buf); if (worker != NULL && worker->id == KORE_WORKER_KEYMGR) kore_keymgr_cleanup(1); } static int utils_x509name_tobuf(void *udata, int islast, int nid, const char *field, const void *data, size_t len, int flags) { struct kore_buf *buf = udata; if (flags & KORE_X509_COMMON_NAME_ONLY) { if (nid == KORE_X509_NAME_COMMON_NAME) kore_buf_append(buf, data, len); } else { kore_buf_appendf(buf, "%s=", field); kore_buf_append(buf, data, len); if (!islast) kore_buf_appendf(buf, " "); } return (KORE_RESULT_OK); } static int utils_base64_encode(const void *data, size_t len, char **out, const char *table, int flags) { u_int8_t n; size_t nb; const u_int8_t *ptr; u_int32_t bytes; struct kore_buf result; nb = 0; ptr = data; kore_buf_init(&result, (len / 3) * 4); while (len > 0) { if (len > 2) { nb = 3; bytes = *ptr++ << 16; bytes |= *ptr++ << 8; bytes |= *ptr++; } else if (len > 1) { nb = 2; bytes = *ptr++ << 16; bytes |= *ptr++ << 8; } else if (len == 1) { nb = 1; bytes = *ptr++ << 16; } else { kore_buf_cleanup(&result); return (KORE_RESULT_ERROR); } n = (bytes >> 18) & 0x3f; kore_buf_append(&result, &(table[n]), 1); n = (bytes >> 12) & 0x3f; kore_buf_append(&result, &(table[n]), 1); if (nb > 1) { n = (bytes >> 6) & 0x3f; kore_buf_append(&result, &(table[n]), 1); if (nb > 2) { n = bytes & 0x3f; kore_buf_append(&result, &(table[n]), 1); } } len -= nb; } if (!(flags & KORE_BASE64_RAW)) { switch (nb) { case 1: kore_buf_appendf(&result, "=="); break; case 2: kore_buf_appendf(&result, "="); break; case 3: break; default: kore_buf_cleanup(&result); return (KORE_RESULT_ERROR); } } /* result.data gets taken over so no need to cleanup result. */ *out = kore_buf_stringify(&result, NULL); return (KORE_RESULT_OK); } static int utils_base64_decode(const char *in, u_int8_t **out, size_t *olen, const char *table, int flags) { int i, c; u_int8_t d, n, o; struct kore_buf *res, buf; const char *ptr, *pad; u_int32_t b, len, plen, idx; i = 4; b = 0; d = 0; c = 0; len = strlen(in); memset(&buf, 0, sizeof(buf)); if (flags & KORE_BASE64_RAW) { switch (len % 4) { case 2: plen = 2; pad = "=="; break; case 3: plen = 1; pad = "="; break; default: return (KORE_RESULT_ERROR); } kore_buf_init(&buf, len + plen); kore_buf_append(&buf, in, len); kore_buf_append(&buf, pad, plen); len = len + plen; ptr = (const char *)buf.data; } else { ptr = in; } res = kore_buf_alloc(len); for (idx = 0; idx < len; idx++) { c = ptr[idx]; if (c == '=') break; for (o = 0; o < B64_TABLE_LEN; o++) { if (table[o] == c) { d = o; break; } } if (o == B64_TABLE_LEN) { *out = NULL; kore_buf_free(res); kore_buf_cleanup(&buf); return (KORE_RESULT_ERROR); } b |= (d & 0x3f) << ((i - 1) * 6); i--; if (i == 0) { for (i = 2; i >= 0; i--) { n = (b >> (8 * i)); kore_buf_append(res, &n, 1); } b = 0; i = 4; } } if (c == '=') { if (i > 2) { *out = NULL; kore_buf_free(res); kore_buf_cleanup(&buf); return (KORE_RESULT_ERROR); } o = i; for (i = 2; i >= o; i--) { n = (b >> (8 * i)); kore_buf_append(res, &n, 1); } } kore_buf_cleanup(&buf); *out = kore_buf_release(res, olen); return (KORE_RESULT_OK); }