/* * Copyright (c) 2021 Omar Polo * * 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 "gmid.h" #include #include #include #include #include #include #include #include #include #include #include #include static struct event imsgev; static FILE *log; static void handle_imsg_quit(struct imsgbuf*, struct imsg*, size_t); static void handle_imsg_log(struct imsgbuf*, struct imsg*, size_t); static void handle_imsg_log_type(struct imsgbuf*, struct imsg*, size_t); static void handle_dispatch_imsg(int, short, void*); static imsg_handlerfn *handlers[] = { [IMSG_QUIT] = handle_imsg_quit, [IMSG_LOG] = handle_imsg_log, [IMSG_LOG_REQUEST] = handle_imsg_log, [IMSG_LOG_TYPE] = handle_imsg_log_type, }; static inline void print_date(FILE *f) { struct tm tminfo; time_t t; char buf[20]; time(&t); strftime(buf, sizeof(buf), "%F %T", localtime_r(&t, &tminfo)); fprintf(f, "[%s] ", buf); } static inline int should_log(int priority) { switch (priority) { case LOG_ERR: return 1; case LOG_WARNING: return 1; case LOG_NOTICE: return conf.verbose >= 1; case LOG_INFO: return conf.verbose >= 2; case LOG_DEBUG: return conf.verbose >= 3; default: return 0; } } static inline void send_log(int type, int priority, const char *msg, size_t len) { imsg_compose(&logibuf, type, priority, 0, -1, msg, len); imsg_flush(&logibuf); } void fatal(const char *fmt, ...) { struct pollfd pfd; va_list ap; int r; char *fmted; va_start(ap, fmt); if ((r = vasprintf(&fmted, fmt, ap)) != -1) { send_log(IMSG_LOG, LOG_CRIT, fmted, r+1); free(fmted); /* wait for the logger process to shut down */ pfd.fd = logibuf.fd; pfd.events = POLLIN; poll(&pfd, 1, 1000); } va_end(ap); exit(1); } static inline void vlog(int priority, struct client *c, const char *fmt, va_list ap) { char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; char *fmted, *s; size_t len; int ec; if (!should_log(priority)) return; if (c != NULL) { len = sizeof(c->addr); ec = getnameinfo((struct sockaddr*)&c->addr, len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); if (ec != 0) fatal("getnameinfo: %s: %s", gai_strerror(ec), strerror(errno)); } if (vasprintf(&fmted, fmt, ap) == -1) fatal("vasprintf: %s", strerror(errno)); if (c == NULL) ec = asprintf(&s, "internal: %s", fmted); else ec = asprintf(&s, "%s:%s %s", hbuf, sbuf, fmted); if (ec < 0) fatal("asprintf: %s", strerror(errno)); send_log(IMSG_LOG, priority, s, ec+1); free(fmted); free(s); } void log_err(struct client *c, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(LOG_ERR, c, fmt, ap); va_end(ap); } void log_warn(struct client *c, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(LOG_WARNING, c, fmt, ap); va_end(ap); } void log_notice(struct client *c, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(LOG_NOTICE, c, fmt, ap); va_end(ap); } void log_info(struct client *c, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(LOG_INFO, c, fmt, ap); va_end(ap); } void log_debug(struct client *c, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vlog(LOG_DEBUG, c, fmt, ap); va_end(ap); } /* strchr, but with a bound */ static char * gmid_strnchr(char *s, int c, size_t len) { size_t i; for (i = 0; i < len; ++i) if (s[i] == c) return &s[i]; return NULL; } void log_request(struct client *c, char *meta, size_t l) { char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV], b[GEMINI_URL_LEN]; char *fmted; const char *t; size_t len; int ec; len = sizeof(c->addr); ec = getnameinfo((struct sockaddr*)&c->addr, len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); if (ec != 0) fatal("getnameinfo: %s", gai_strerror(ec)); if (c->iri.schema != NULL) { /* serialize the IRI */ strlcpy(b, c->iri.schema, sizeof(b)); strlcat(b, "://", sizeof(b)); /* log the decoded host name, but if it was invalid * use the raw one. */ if (*c->domain != '\0') strlcat(b, c->domain, sizeof(b)); else strlcat(b, c->iri.host, sizeof(b)); if (*c->iri.path != '/') strlcat(b, "/", sizeof(b)); strlcat(b, c->iri.path, sizeof(b)); /* TODO: sanitize UTF8 */ if (*c->iri.query != '\0') { /* TODO: sanitize UTF8 */ strlcat(b, "?", sizeof(b)); strlcat(b, c->iri.query, sizeof(b)); } } else { if ((t = c->req) == NULL) t = ""; strlcpy(b, t, sizeof(b)); } if ((t = gmid_strnchr(meta, '\r', l)) == NULL) t = meta + len; ec = asprintf(&fmted, "%s:%s GET %s %.*s", hbuf, sbuf, b, (int)(t-meta), meta); if (ec < 0) err(1, "asprintf"); send_log(IMSG_LOG_REQUEST, LOG_NOTICE, fmted, ec+1); free(fmted); } static void do_log(int type, int priority, const char *msg) { int quit = 0; if (priority == LOG_CRIT) { quit = 1; priority = LOG_ERR; } if (log != NULL) { if (type != IMSG_LOG_REQUEST) print_date(log); fprintf(log, "%s\n", msg); } else syslog(LOG_DAEMON | priority, "%s", msg); if (quit) exit(1); } static void handle_imsg_quit(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen) { event_loopbreak(); } static void handle_imsg_log(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen) { int priority; char *msg; msg = imsg->data; msg[datalen-1] = '\0'; priority = imsg->hdr.peerid; do_log(imsg->hdr.type, priority, msg); } static void handle_imsg_log_type(struct imsgbuf *ibuf, struct imsg *imsg, size_t datalen) { if (log != NULL && log != stderr) { fflush(log); fclose(log); } log = NULL; if (imsg->fd != -1) { if ((log = fdopen(imsg->fd, "a")) == NULL) { syslog(LOG_DAEMON | LOG_ERR, "fdopen: %s", strerror(errno)); exit(1); } } } static void handle_dispatch_imsg(int fd, short ev, void *d) { struct imsgbuf *ibuf = d; dispatch_imsg(ibuf, handlers, sizeof(handlers)); } int logger_main(int fd, struct imsgbuf *ibuf) { log = stderr; openlog(getprogname(), LOG_NDELAY, LOG_DAEMON); event_init(); event_set(&imsgev, fd, EV_READ | EV_PERSIST, &handle_dispatch_imsg, ibuf); event_add(&imsgev, NULL); sandbox_logger_process(); event_dispatch(); closelog(); return 0; }