kore/src/utils.c

870 lines
16 KiB
C

/*
* Copyright (c) 2013-2021 Joris Vink <joris@coders.se>
*
* 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 <sys/types.h>
#include <sys/time.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
#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 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))
#if defined(KORE_DEBUG)
void
kore_debug_internal(char *file, int line, const char *fmt, ...)
{
va_list args;
char buf[2048];
va_start(args, fmt);
(void)vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
printf("[%d] %s:%d - %s\n", kore_pid, file, line, buf);
}
#endif
void
kore_log_init(void)
{
#if defined(KORE_SINGLE_BINARY)
extern const char *__progname;
const char *name = kore_strdup(__progname);
#else
const char *name = "kore";
#endif
if (!kore_foreground)
openlog(name, LOG_NDELAY | LOG_PID, LOG_DAEMON);
}
void
kore_log(int prio, const char *fmt, ...)
{
va_list args;
const char *name;
char buf[2048];
va_start(args, fmt);
(void)vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
if (worker != NULL) {
name = kore_worker_name(worker->id);
if (kore_foreground)
printf("%s: %s\n", name, buf);
else
syslog(prio, "%s: %s", name, buf);
} else {
if (kore_foreground)
printf("[parent]: %s\n", buf);
else
syslog(prio, "[parent]: %s", buf);
}
}
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(char *in, const char strip, char **out)
{
u_int32_t len;
char *s, *p;
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_debug("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_debug("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_debug("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_debug("misformed mday in http-date: '%s'", http_date);
goto out;
}
if (kore_split_string(args[4], ":", tbuf, 5) != 3) {
kore_debug("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_debug("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_debug("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_debug("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_debug("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)) {
kore_debug("strftime() gave us NULL (%ld)", now);
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);
}
EVP_PKEY *
kore_rsakey_load(const char *path)
{
FILE *fp;
EVP_PKEY *pkey;
if (access(path, R_OK) == -1)
return (NULL);
if ((fp = fopen(path, "r")) == NULL)
fatalx("%s(%s): %s", __func__, path, errno_s);
if ((pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL)
fatalx("PEM_read_PrivateKey: %s", ssl_errno_s);
fclose(fp);
return (pkey);
}
EVP_PKEY *
kore_rsakey_generate(const char *path)
{
FILE *fp;
EVP_PKEY_CTX *ctx;
EVP_PKEY *pkey;
if ((ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL)) == NULL)
fatalx("EVP_PKEY_CTX_new_id: %s", ssl_errno_s);
if (EVP_PKEY_keygen_init(ctx) <= 0)
fatalx("EVP_PKEY_keygen_init: %s", ssl_errno_s);
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, KORE_RSAKEY_BITS) <= 0)
fatalx("EVP_PKEY_CTX_set_rsa_keygen_bits: %s", ssl_errno_s);
pkey = NULL;
if (EVP_PKEY_keygen(ctx, &pkey) <= 0)
fatalx("EVP_PKEY_keygen: %s", ssl_errno_s);
if (path != NULL) {
if ((fp = fopen(path, "w")) == NULL)
fatalx("fopen(%s): %s", path, errno_s);
if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL))
fatalx("PEM_write_PrivateKey: %s", ssl_errno_s);
fclose(fp);
}
return (pkey);
}
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_subject_name(struct connection *c, char **out, int flags)
{
struct kore_buf buf;
u_int8_t *data;
ASN1_STRING *astr;
X509_NAME *name;
X509_NAME_ENTRY *entry;
const char *field;
int ret, idx, namelen, nid, len;
data = NULL;
ret = KORE_RESULT_ERROR;
kore_buf_init(&buf, 1024);
if (c->cert == NULL)
goto cleanup;
if ((name = X509_get_subject_name(c->cert)) == NULL)
goto cleanup;
namelen = X509_NAME_entry_count(name);
if (namelen == 0)
goto cleanup;
for (idx = 0; idx < namelen; idx++) {
entry = X509_NAME_get_entry(name, idx);
if (entry == NULL)
goto cleanup;
nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(entry));
if ((field = OBJ_nid2sn(nid)) == NULL)
goto cleanup;
if ((astr = X509_NAME_ENTRY_get_data(entry)) == NULL)
goto cleanup;
data = NULL;
if ((len = ASN1_STRING_to_UTF8(&data, astr)) < 0)
goto cleanup;
if (flags & KORE_X509_COMMON_NAME_ONLY) {
if (nid == NID_commonName) {
kore_buf_append(&buf, data, len);
break;
}
} else {
kore_buf_appendf(&buf, "%s=", field);
kore_buf_append(&buf, data, len);
if (idx != (namelen - 1))
kore_buf_appendf(&buf, " ");
}
OPENSSL_free(data);
data = NULL;
}
ret = KORE_RESULT_OK;
*out = kore_buf_stringify(&buf, NULL);
buf.offset = 0;
buf.data = NULL;
cleanup:
if (data != NULL)
OPENSSL_free(data);
kore_buf_cleanup(&buf);
return (ret);
}
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_SHUTDOWN, 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];
extern const char *kore_progname;
(void)vsnprintf(buf, sizeof(buf), fmt, args);
if (!kore_foreground)
kore_log(LOG_ERR, "%s", buf);
if (worker != NULL && worker->id == KORE_WORKER_KEYMGR)
kore_keymgr_cleanup(1);
printf("%s: %s\n", kore_progname, buf);
}
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_appendf(&buf, in, len);
kore_buf_appendf(&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);
}