kore/src/accesslog.c

341 lines
7.6 KiB
C
Raw Permalink Normal View History

2013-06-24 09:36:40 +02:00
/*
2022-01-31 22:02:06 +01:00
* Copyright (c) 2013-2022 Joris Vink <joris@coders.se>
2013-06-24 09:36:40 +02:00
*
* 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>
2013-06-24 09:36:40 +02:00
#include <sys/socket.h>
2016-01-07 09:24:45 +01:00
#include <time.h>
#include <inttypes.h>
#include <signal.h>
2013-06-24 09:36:40 +02:00
#include "kore.h"
#include "http.h"
/*
* The worker will write accesslogs to its worker data structure which is
* held in shared memory.
*
* Each accesslog is prefixed with the internal domain ID (2 bytes) and
* the length of the log entry (2 bytes) (packed in kore_alog_header).
*
* The parent will every 10ms fetch the produced accesslogs from the workers
* and copy them to its own log buffer. Once this log buffer becomes full
* or 1 second has passed the parent will parse the logs and append them
* to the correct domain logbuffer which is eventually flushed to disk.
*/
#define LOGBUF_SIZE (KORE_ACCESSLOG_BUFLEN * worker_count)
#define DOMAIN_LOGBUF_LEN (1024 * 1024)
#define LOG_ENTRY_MINSIZE_GUESS 90
static void accesslog_lock(struct kore_worker *);
static void accesslog_unlock(struct kore_worker *);
static void accesslog_flush_cb(struct kore_domain *);
static void accesslog_flush(struct kore_domain *, u_int64_t, int);
static u_int64_t time_cache = 0;
static char tbuf[128] = { '\0' };
2013-06-24 09:36:40 +02:00
static struct kore_buf *logbuf = NULL;
2013-06-24 09:36:40 +02:00
void
kore_accesslog_worker_init(void)
{
kore_domain_closelogs();
2013-06-24 09:36:40 +02:00
}
void
kore_accesslog(struct http_request *req)
2013-06-24 09:36:40 +02:00
{
struct timespec ts;
struct tm *tm;
u_int64_t now;
struct kore_alog_header *hdr;
size_t avail;
time_t curtime;
int len, attempts;
char addr[INET6_ADDRSTRLEN], *cn_value;
2022-02-20 21:19:44 +01:00
const char *ptr, *method, *http_version, *cn, *referer;
2013-06-24 09:36:40 +02:00
switch (req->method) {
case HTTP_METHOD_GET:
2013-06-24 09:36:40 +02:00
method = "GET";
break;
case HTTP_METHOD_POST:
2013-06-24 09:36:40 +02:00
method = "POST";
break;
case HTTP_METHOD_PUT:
method = "PUT";
break;
case HTTP_METHOD_DELETE:
method = "DELETE";
break;
case HTTP_METHOD_HEAD:
method = "HEAD";
break;
case HTTP_METHOD_PATCH:
method = "PATCH";
break;
default:
method = "UNKNOWN";
break;
}
if (req->flags & HTTP_VERSION_1_0)
http_version = "HTTP/1.0";
2022-02-20 21:19:44 +01:00
else
http_version = "HTTP/1.1";
if (req->referer != NULL)
referer = req->referer;
else
referer = "-";
if (req->agent == NULL)
req->agent = "-";
cn = "-";
cn_value = NULL;
2022-02-18 10:20:28 +01:00
if (req->owner->tls_cert != NULL) {
if (kore_x509_subject_name(req->owner, &cn_value,
KORE_X509_COMMON_NAME_ONLY))
cn = cn_value;
}
2013-06-24 09:36:40 +02:00
switch (req->owner->family) {
case AF_INET:
ptr = inet_ntop(req->owner->family,
&(req->owner->addr.ipv4.sin_addr), addr, sizeof(addr));
break;
case AF_INET6:
ptr = inet_ntop(req->owner->family,
&(req->owner->addr.ipv6.sin6_addr), addr, sizeof(addr));
break;
case AF_UNIX:
ptr = NULL;
break;
default:
fatal("unknown family %d", req->owner->family);
}
if (ptr == NULL) {
addr[0] = '-';
addr[1] = '\0';
}
now = kore_time_ms();
if ((now - time_cache) >= 1000) {
time(&curtime);
tm = localtime(&curtime);
(void)strftime(tbuf, sizeof(tbuf), "%d/%b/%Y:%H:%M:%S %z", tm);
time_cache = now;
2013-06-24 09:36:40 +02:00
}
attempts = 0;
ts.tv_sec = 0;
ts.tv_nsec = 1000000;
for (;;) {
if (attempts++ > 1000) {
if (getppid() == 1) {
if (kill(worker->pid, SIGQUIT) == -1)
fatal("failed to shutdown");
return;
}
attempts = 0;
}
accesslog_lock(worker);
2013-06-24 09:36:40 +02:00
avail = KORE_ACCESSLOG_BUFLEN - worker->lb.offset;
if (avail < sizeof(*hdr) + LOG_ENTRY_MINSIZE_GUESS) {
accesslog_unlock(worker);
nanosleep(&ts, NULL);
continue;
}
hdr = (struct kore_alog_header *)
(worker->lb.buf + worker->lb.offset);
worker->lb.offset += sizeof(*hdr);
len = snprintf(worker->lb.buf + worker->lb.offset, avail,
"%s - %s [%s] \"%s %s %s\" %d %" PRIu64" \"%s\" \"%s\"\n",
addr, cn, tbuf, method, req->path, http_version,
req->status, req->content_length, referer, req->agent);
if (len == -1)
fatal("failed to create log entry");
if ((size_t)len >= avail) {
worker->lb.offset -= sizeof(*hdr);
accesslog_unlock(worker);
nanosleep(&ts, NULL);
continue;
}
if ((size_t)len > USHRT_MAX) {
kore_log(LOG_WARNING,
"log entry length exceeds limit (%d)", len);
worker->lb.offset -= sizeof(*hdr);
break;
}
hdr->loglen = len;
hdr->domain = req->rt->dom->id;
worker->lb.offset += (size_t)len;
break;
}
kore_free(cn_value);
accesslog_unlock(worker);
2013-06-24 09:36:40 +02:00
}
void
kore_accesslog_gather(void *arg, u_int64_t now, int force)
2013-06-24 09:36:40 +02:00
{
int id;
struct kore_worker *kw;
struct kore_alog_header *hdr;
struct kore_domain *dom;
size_t off, remain;
2013-06-24 09:36:40 +02:00
if (logbuf == NULL)
logbuf = kore_buf_alloc(LOGBUF_SIZE);
Add acmev2 (RFC8555) support to Kore. A new acme process is created that communicates with the acme servers. This process does not hold any of your private keys (no account keys, no domain keys etc). Whenever the acme process requires a signed payload it will ask the keymgr process to do the signing with the relevant keys. This process is also sandboxed with pledge+unveil on OpenBSD and seccomp syscall filtering on Linux. The implementation only supports the tls-alpn-01 challenge. This means that you do not need to open additional ports on your machine. http-01 and dns-01 are currently not supported (no wildcard support). A new configuration option "acme_provider" is available and can be set to the acme server its directory. By default this will point to the live letsencrypt environment: https://acme-v02.api.letsencrypt.org/directory The acme process can be controlled via the following config options: - acme_root (where the acme process will chroot/chdir into). - acme_runas (the user the acme process will run as). If none are set, the values from 'root' and 'runas' are taken. If you want to turn on acme for domains you do it as follows: domain kore.io { acme yes } You do not need to specify certkey/certfile anymore, if they are present still they will be overwritten by the acme system. The keymgr will store all certificates and keys under its root (keymgr_root), the account key is stored as "/account-key.pem" and all obtained certificates go under "certificates/<domain>/fullchain.pem" while keys go under "certificates/<domain>/key.pem". Kore will automatically renew certificates if they will expire in 7 days or less.
2019-11-06 19:33:53 +01:00
for (id = KORE_WORKER_BASE; id < worker_count; id++) {
kw = kore_worker_data(id);
accesslog_lock(kw);
if (force || kw->lb.offset >= KORE_ACCESSLOG_SYNC) {
kore_buf_append(logbuf, kw->lb.buf, kw->lb.offset);
kw->lb.offset = 0;
}
accesslog_unlock(kw);
}
2013-06-24 09:36:40 +02:00
if (force || logbuf->offset >= LOGBUF_SIZE) {
off = 0;
remain = logbuf->offset;
while (remain > 0) {
if (remain < sizeof(*hdr)) {
kore_log(LOG_ERR,
"invalid log buffer: (%zu remain)", remain);
break;
}
hdr = (struct kore_alog_header *)(logbuf->data + off);
off += sizeof(*hdr);
remain -= sizeof(*hdr);
if (hdr->loglen > remain) {
kore_log(LOG_ERR,
"invalid log header: %u (%zu remain)",
hdr->loglen, remain);
break;
}
if ((dom = kore_domain_byid(hdr->domain)) == NULL)
fatal("unknown domain id %u", hdr->domain);
if (dom->logbuf == NULL)
dom->logbuf = kore_buf_alloc(DOMAIN_LOGBUF_LEN);
kore_buf_append(dom->logbuf, &logbuf->data[off],
hdr->loglen);
off += hdr->loglen;
remain -= hdr->loglen;
accesslog_flush(dom, now, force);
2018-06-29 22:37:48 +02:00
}
kore_buf_reset(logbuf);
2018-06-29 22:37:48 +02:00
}
if (force)
kore_domain_callback(accesslog_flush_cb);
}
void
kore_accesslog_run(void *arg, u_int64_t now)
{
static int ticks = 0;
kore_accesslog_gather(arg, now, ticks++ % 100 ? 0 : 1);
}
static void
accesslog_flush_cb(struct kore_domain *dom)
{
accesslog_flush(dom, 0, 1);
}
static void
accesslog_flush(struct kore_domain *dom, u_int64_t now, int force)
{
ssize_t written;
if (force && dom->logbuf == NULL)
return;
if (force || dom->logbuf->offset >= DOMAIN_LOGBUF_LEN) {
written = write(dom->accesslog, dom->logbuf->data,
dom->logbuf->offset);
if (written == -1) {
if (errno == EINTR)
return;
if (dom->logwarn == 0 ||
errno != dom->logerr) {
kore_log(LOG_NOTICE,
"error writing log for %s (%s)",
dom->domain, errno_s);
dom->logwarn = now;
dom->logerr = errno;
}
kore_buf_reset(dom->logbuf);
return;
}
if ((size_t)written != dom->logbuf->offset) {
kore_log(LOG_ERR, "partial accesslog write for %s",
dom->domain);
}
kore_buf_reset(dom->logbuf);
}
}
static void
accesslog_lock(struct kore_worker *kw)
{
for (;;) {
if (__sync_bool_compare_and_swap(&kw->lb.lock, 0, 1))
break;
}
}
static void
accesslog_unlock(struct kore_worker *kw)
{
if (!__sync_bool_compare_and_swap(&kw->lb.lock, 1, 0))
fatal("accesslog_unlock: failed to release");
2013-06-24 09:36:40 +02:00
}