From a4423189abc65a592301a7b161f5366bf6bfa501 Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Sun, 28 Oct 2018 12:10:07 +0100 Subject: [PATCH] Initial import Signed-off-by: David Oberhollenzer --- .gitignore | 19 ++++ Makefile.am | 11 ++ autogen.sh | 3 + configure.ac | 35 ++++++ klogd.c | 193 +++++++++++++++++++++++++++++++++ logfile.c | 242 ++++++++++++++++++++++++++++++++++++++++++ m4/compiler.m4 | 40 +++++++ mksock.c | 63 +++++++++++ proto.c | 194 ++++++++++++++++++++++++++++++++++ protomap.c | 81 ++++++++++++++ syslogd.c | 282 +++++++++++++++++++++++++++++++++++++++++++++++++ syslogd.h | 97 +++++++++++++++++ 12 files changed, 1260 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile.am create mode 100755 autogen.sh create mode 100644 configure.ac create mode 100644 klogd.c create mode 100644 logfile.c create mode 100644 m4/compiler.m4 create mode 100644 mksock.c create mode 100644 proto.c create mode 100644 protomap.c create mode 100644 syslogd.c create mode 100644 syslogd.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9635e08 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +.deps +Makefile +Makefile.in +aclocal.m4 +autom4te.cache +compile +config.h.in +config.log +config.status +configure +depcomp +install-sh +missing +stamp-h1 +config.h +*.o +*~ +klogd +usyslogd diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..99a455e --- /dev/null +++ b/Makefile.am @@ -0,0 +1,11 @@ +ACLOCAL_AMFLAGS = -I m4 + +AM_CPPFLAGS = -D_GNU_SOURCE +AM_CFLAGS = $(WARN_CFLAGS) + +usyslogd_SOURCES = syslogd.c syslogd.h proto.c logfile.c mksock.c protomap.c +klogd_SOURCES = klogd.c + +bin_PROGRAMS = +sbin_PROGRAMS = usyslogd klogd +# EXTRA_DIST = README LICENSE diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..c08fadf --- /dev/null +++ b/autogen.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +autoreconf --force --install --symlink diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..bf00548 --- /dev/null +++ b/configure.ac @@ -0,0 +1,35 @@ +AC_PREREQ([2.60]) +AC_INIT([usyslog], [0.1], [david.oberhollenzer@tele2.at], usyslog) +AC_CONFIG_MACRO_DIR([m4]) +AM_INIT_AUTOMAKE([foreign dist-xz]) +AM_SILENT_RULES([yes]) +AC_PROG_CC +AC_PROG_CC_C99 +AC_PROG_INSTALL + +UL_WARN_ADD([-Wall]) +UL_WARN_ADD([-Wextra]) +UL_WARN_ADD([-Wunused]) +UL_WARN_ADD([-Wmissing-prototypes]) +UL_WARN_ADD([-Wmissing-declarations]) +UL_WARN_ADD([-Wwrite-strings]) +UL_WARN_ADD([-Wjump-misses-init]) +UL_WARN_ADD([-Wuninitialized]) +UL_WARN_ADD([-Winit-self]) +UL_WARN_ADD([-Wlogical-op]) +UL_WARN_ADD([-Wunused-but-set-parameter]) +UL_WARN_ADD([-Wunused-but-set-variable]) +UL_WARN_ADD([-Wunused-parameter]) +UL_WARN_ADD([-Wunused-result]) +UL_WARN_ADD([-Wunused-variable]) +UL_WARN_ADD([-Wduplicated-cond]) +UL_WARN_ADD([-Wduplicated-branches]) +UL_WARN_ADD([-Wrestrict]) +UL_WARN_ADD([-Wnull-dereference]) +UL_WARN_ADD([-pedantic]) + +AC_SUBST([WARN_CFLAGS]) + +AC_CONFIG_HEADERS([config.h]) + +AC_OUTPUT([Makefile]) diff --git a/klogd.c b/klogd.c new file mode 100644 index 0000000..39f7454 --- /dev/null +++ b/klogd.c @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +enum { + KLOG_CLOSE = 0, + KLOG_OPEN = 1, + KLOG_READ = 2, + KLOG_CONSOLE_OFF = 6, + KLOG_CONSOLE_ON = 7, + KLOG_CONSOLE_LEVEL = 8, +}; + +static char log_buffer[4096]; +static sig_atomic_t running = 1; +static int level = 0; + +static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "level", required_argument, NULL, 'l' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *shortopt = "hVl:"; + +static const char *helptext = +"Usage: klogd [OPTION]... \n\n" +"Collect printk() messages from the kernel and forward them to syslogd.\n" +"\n" +"The following OPTIONSs can be used:\n" +" -l, --level Minimum log level that should be printed to console.\n" +" If not set, logging to console is turned off.\n" +" -h, --help Print this help text and exit\n" +" -V, --version Print version information and exit\n\n"; + +#define GPL_URL "https://gnu.org/licenses/gpl.html" + +static const char *version_string = +"klogd (usyslog) " PACKAGE_VERSION "\n" +"Copyright (C) 2018 David Oberhollenzer\n\n" +"License GPLv3+: GNU GPL version 3 or later <" GPL_URL ">.\n" +"This is free software: you are free to change and redistribute it.\n" +"There is NO WARRANTY, to the extent permitted by law.\n"; + +static void process_options(int argc, char **argv) +{ + int c; + + for (;;) { + c = getopt_long(argc, argv, shortopt, options, NULL); + if (c == -1) + break; + + switch (c) { + case 'l': + level = strtoul(optarg, NULL, 10); + break; + case 'h': + fputs(helptext, stdout); + exit(EXIT_SUCCESS); + case 'V': + fputs(version_string, stdout); + exit(EXIT_SUCCESS); + default: + fputs("Try `klogd --help' for more information\n", + stderr); + exit(EXIT_FAILURE); + } + } +} + +static void sighandler(int signo) +{ + if (signo == SIGTERM || signo == SIGINT) + running = 0; +} + +static void sigsetup(void) +{ + struct sigaction act; + sigset_t mask; + + memset(&act, 0, sizeof(act)); + act.sa_handler = sighandler; + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + + sigfillset(&mask); + sigdelset(&mask, SIGTERM); + sigdelset(&mask, SIGINT); + sigprocmask(SIG_SETMASK, &mask, NULL); +} + +static void log_open(void) +{ + klogctl(KLOG_OPEN, NULL, 0); + + if (level) { + klogctl(KLOG_CONSOLE_LEVEL, NULL, level); + } else { + klogctl(KLOG_CONSOLE_OFF, NULL, 0); + } + + openlog("kernel", 0, LOG_KERN); +} + +static void log_close(void) +{ + klogctl(KLOG_CONSOLE_ON, NULL, 0); + klogctl(KLOG_CLOSE, NULL, 0); + syslog(LOG_NOTICE, "-- klogd terminating --"); +} + +int main(int argc, char **argv) +{ + int diff, count = 0, priority, ret = EXIT_SUCCESS; + char *ptr, *end; + + process_options(argc, argv); + sigsetup(); + log_open(); + + /* TODO: seccomp lockdown? */ + + while (running) { + diff = klogctl(KLOG_READ, log_buffer + count, + sizeof(log_buffer) - 1 - count); + + if (diff < 0) { + if (errno == EINTR) + continue; + syslog(LOG_CRIT, "klogctl read: %s", strerror(errno)); + ret = EXIT_FAILURE; + break; + } + + count += diff; + log_buffer[count] = '\0'; + ptr = log_buffer; + + for (;;) { + end = strchr(ptr, '\n'); + if (end == NULL) { + count = strlen(ptr); + memmove(log_buffer, ptr, count); + break; + } + + *(end++) = '\0'; + priority = LOG_INFO; + + if (*ptr == '<') { + ++ptr; + if (*ptr) + priority = strtoul(ptr, &ptr, 10); + if (*ptr == '>') + ++ptr; + } + + if (*ptr) + syslog(priority, "%s", ptr); + ptr = end; + } + } + + log_close(); + return ret; +} diff --git a/logfile.c b/logfile.c new file mode 100644 index 0000000..ca8c983 --- /dev/null +++ b/logfile.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syslogd.h" + + +typedef struct logfile_t { + struct logfile_t *next; + size_t size; + int fd; + char filename[]; +} logfile_t; + + +typedef struct { + log_backend_t base; + logfile_t *list; + size_t maxsize; + int flags; +} log_backend_file_t; + + +static int logfile_open(logfile_t *file) +{ + struct stat sb; + + file->fd = open(file->filename, O_WRONLY | O_CREAT, 0640); + if (file->fd < 0) { + perror(file->filename); + return -1; + } + + if (lseek(file->fd, 0, SEEK_END)) + goto fail; + + if (fstat(file->fd, &sb)) + goto fail; + + file->size = sb.st_size; + return 0; +fail: + perror(file->filename); + close(file->fd); + file->fd = -1; + return -1; +} + +static logfile_t *logfile_create(const char *filename) +{ + logfile_t *file = calloc(1, sizeof(*file) + strlen(filename) + 1); + + if (file == NULL) { + perror("calloc"); + return NULL; + } + + strcpy(file->filename, filename); + + if (logfile_open(file)) { + free(file); + return NULL; + } + + return file; +} + +static int logfile_write(logfile_t *file, const syslog_msg_t *msg) +{ + const char *lvl_str, *fac_name; + char timebuf[32]; + struct tm tm; + int ret; + + if (file->fd < 0 && logfile_open(file) != 0) + return -1; + + lvl_str = level_id_to_string(msg->level); + if (lvl_str == NULL) + return -1; + + gmtime_r(&msg->timestamp, &tm); + strftime(timebuf, sizeof(timebuf), "%FT%T", &tm); + + if (msg->ident != NULL) { + fac_name = facility_id_to_string(msg->facility); + if (fac_name == NULL) + return -1; + + ret = dprintf(file->fd, "[%s][%s][%s][%u] %s\n", timebuf, + fac_name, lvl_str, msg->pid, msg->message); + } else { + ret = dprintf(file->fd, "[%s][%s][%u] %s\n", timebuf, lvl_str, + msg->pid, msg->message); + } + + fsync(file->fd); + + if (ret > 0) + file->size += ret; + return 0; +} + +static int logfile_rotate(logfile_t *f, int flags) +{ + char timebuf[32]; + char *filename; + struct tm tm; + time_t now; + + if (flags & LOG_ROTATE_OVERWRITE) { + strcpy(timebuf, "1"); + } else { + now = time(NULL); + gmtime_r(&now, &tm); + strftime(timebuf, sizeof(timebuf), "%FT%T", &tm); + } + + filename = alloca(strlen(f->filename) + strlen(timebuf) + 2); + sprintf(filename, "%s.%s", f->filename, timebuf); + + if (rename(f->filename, filename)) { + perror(filename); + return -1; + } + + close(f->fd); + logfile_open(f); + return 0; +} + +/*****************************************************************************/ + +static int file_backend_init(log_backend_t *backend, int flags, + size_t sizelimit) +{ + log_backend_file_t *log = (log_backend_file_t *)backend; + + log->flags = flags; + log->maxsize = sizelimit; + return 0; +} + +static void file_backend_cleanup(log_backend_t *backend) +{ + log_backend_file_t *log = (log_backend_file_t *)backend; + logfile_t *f; + + while (log->list != NULL) { + f = log->list; + log->list = f->next; + + close(f->fd); + free(f); + } +} + +static int file_backend_write(log_backend_t *backend, const syslog_msg_t *msg) +{ + log_backend_file_t *log = (log_backend_file_t *)backend; + const char *ident; + char *filename; + logfile_t *f; + size_t len; + + if (msg->ident != NULL) { + ident = msg->ident; + } else { + ident = facility_id_to_string(msg->facility); + if (ident == NULL) + return -1; + } + + len = strlen(ident) + strlen(".log") + 1; + filename = alloca(len); + strcpy(filename, ident); + strcat(filename, ".log"); + + for (f = log->list; f != NULL; f = f->next) { + if (strcmp(filename, f->filename) == 0) + break; + } + + if (f == NULL) { + f = logfile_create(filename); + if (f == NULL) + return -1; + f->next = log->list; + log->list = f; + } + + if (logfile_write(f, msg)) + return -1; + + if ((log->flags & LOG_ROTATE_SIZE_LIMIT) && f->size >= log->maxsize) + logfile_rotate(f, log->flags); + + return 0; +} + +static void file_backend_rotate(log_backend_t *backend) +{ + log_backend_file_t *log = (log_backend_file_t *)backend; + logfile_t *f; + + for (f = log->list; f != NULL; f = f->next) + logfile_rotate(f, log->flags); +} + +log_backend_file_t filebackend = { + .base = { + .init = file_backend_init, + .cleanup = file_backend_cleanup, + .write = file_backend_write, + .rotate = file_backend_rotate, + }, + .list = NULL, +}; + +log_backend_t *logmgr = (log_backend_t *)&filebackend; diff --git a/m4/compiler.m4 b/m4/compiler.m4 new file mode 100644 index 0000000..058c73f --- /dev/null +++ b/m4/compiler.m4 @@ -0,0 +1,40 @@ +dnl Copyright (C) 2008-2011 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Simon Josefsson +dnl -- derivated from coreutils m4/warnings.m4 + +# UL_AS_VAR_APPEND(VAR, VALUE) +# ---------------------------- +# Provide the functionality of AS_VAR_APPEND if Autoconf does not have it. +m4_ifdef([AS_VAR_APPEND], +[m4_copy([AS_VAR_APPEND], [UL_AS_VAR_APPEND])], +[m4_define([UL_AS_VAR_APPEND], +[AS_VAR_SET([$1], [AS_VAR_GET([$1])$2])])]) + +# UL_ADD_WARN(COMPILER_OPTION [, VARNAME]) +# ------------------------ +# Adds parameter to WARN_CFLAGS (or to $VARNAME) if the compiler supports it. +AC_DEFUN([UL_WARN_ADD], [ + m4_define([warnvarname], m4_default([$2],WARN_CFLAGS)) + AS_VAR_PUSHDEF([ul_Warn], [ul_cv_warn_$1])dnl + AC_CACHE_CHECK([whether compiler handles $1], m4_defn([ul_Warn]), [ + # store AC_LANG_WERROR status, then turn it on + save_ac_[]_AC_LANG_ABBREV[]_werror_flag="${ac_[]_AC_LANG_ABBREV[]_werror_flag}" + AC_LANG_WERROR + + ul_save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="-Werror ${CPPFLAGS} $1" + AC_PREPROC_IFELSE([AC_LANG_PROGRAM([])], + [AS_VAR_SET(ul_Warn, [yes])], + [AS_VAR_SET(ul_Warn, [no])]) + # restore AC_LANG_WERROR + ac_[]_AC_LANG_ABBREV[]_werror_flag="${save_ac_[]_AC_LANG_ABBREV[]_werror_flag}" + + CPPFLAGS="$ul_save_CPPFLAGS" + ]) + AS_VAR_IF(ul_Warn, [yes], [UL_AS_VAR_APPEND(warnvarname, [" $1"])]) +]) + diff --git a/mksock.c b/mksock.c new file mode 100644 index 0000000..f274608 --- /dev/null +++ b/mksock.c @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syslogd.h" + +int mksock(const char *path) +{ + struct sockaddr_un un; + const char *errmsg; + int fd; + + fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (fd < 0) { + perror("socket"); + return -1; + } + + memset(&un, 0, sizeof(un)); + un.sun_family = AF_UNIX; + + strcpy(un.sun_path, path); + + if (bind(fd, (struct sockaddr *)&un, sizeof(un))) { + errmsg ="bind"; + goto fail_errno; + } + + if (chmod(path, 0777)) { + errmsg = "chmod"; + goto fail_errno; + } + + return fd; +fail_errno: + fprintf(stderr, "%s: %s: %s\n", path, errmsg, strerror(errno)); + close(fd); + unlink(path); + return -1; +} diff --git a/proto.c b/proto.c new file mode 100644 index 0000000..4ab2f47 --- /dev/null +++ b/proto.c @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include + +#include "syslogd.h" + +static const char *months[] = { + "Jan", "Feb", "Mar", "Apr", + "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec" +}; + +static const int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +static int isleap(int year) +{ + return ((year % 4 == 0) && (year % 100 != 0)) || ((year % 400) == 0); +} + +static int mdays(int year, int month) +{ + return (isleap(year) && month == 2) ? 29 : days[month - 1]; +} + +static char *read_num(char *str, int *out, int maxval) +{ + if (str == NULL || !isdigit(*str)) + return NULL; + for (*out = 0; isdigit(*str); ++str) { + (*out) = (*out) * 10 + (*str) - '0'; + if ((*out) > maxval) + return NULL; + } + return str; +} + +static char *skip_space(char *str) +{ + if (str == NULL || !isspace(*str)) + return NULL; + while (isspace(*str)) + ++str; + return str; +} + +static char *read_date_bsd(char *str, struct tm *tm) +{ + int year, month, day, hour, minute, second; + time_t t; + + /* decode date */ + for (month = 0; month < 12; ++month) { + if (strncmp(str, months[month], 3) == 0) { + str = skip_space(str + 3); + break; + } + } + + str = read_num(str, &day, 31); + str = skip_space(str); + + t = time(NULL); + if (localtime_r(&t, tm) == NULL) + return NULL; + + year = tm->tm_year; + + /* sanity check */ + if (str == NULL || month >= 12 || day < 1) + return NULL; + if (month == 11 && tm->tm_mon == 0) + --year; + if (day > mdays(year + 1900, month + 1)) + return NULL; + + /* decode time */ + str = read_num(str, &hour, 23); + if (str == NULL || *(str++) != ':') + return NULL; + str = read_num(str, &minute, 59); + if (str == NULL || *(str++) != ':') + return NULL; + str = read_num(str, &second, 59); + str = skip_space(str); + + /* store result */ + memset(tm, 0, sizeof(*tm)); + tm->tm_sec = second; + tm->tm_min = minute; + tm->tm_hour = hour; + tm->tm_mday = day; + tm->tm_mon = month; + tm->tm_year = year; + return str; +} + +static char *decode_priority(char *str, int *priority) +{ + while (isspace(*str)) + ++str; + if (*(str++) != '<') + return NULL; + str = read_num(str, priority, 23 * 8 + 7); + if (str == NULL || *(str++) != '>') + return NULL; + while (isspace(*str)) + ++str; + return str; +} + +int syslog_msg_parse(syslog_msg_t *msg, char *str) +{ + char *ident, *ptr; + struct tm tstamp; + pid_t pid = 0; + int priority; + size_t len; + + memset(msg, 0, sizeof(*msg)); + + str = decode_priority(str, &priority); + if (str == NULL) + return -1; + + msg->facility = priority >> 3; + msg->level = priority & 0x07; + + str = read_date_bsd(str, &tstamp); + if (str == NULL) + return -1; + + ident = str; + while (*str != '\0' && *str != ':') + ++str; + + if (*str == ':') { + *(str++) = '\0'; + while (isspace(*str)) + ++str; + + ptr = ident; + while (*ptr != '[' && *ptr != '\0') + ++ptr; + + if (*ptr == '[') { + *(ptr++) = '\0'; + + while (isdigit(*ptr)) + pid = pid * 10 + *(ptr++) - '0'; + } + } else { + ident = NULL; + } + + if (ident != NULL && ident[0] == '\0') + ident = NULL; + + msg->timestamp = mktime(&tstamp); + msg->pid = pid; + msg->ident = ident; + msg->message = str; + + len = strlen(str); + while (len > 0 && isspace(str[len - 1])) + --len; + str[len] = '\0'; + + if (ident != NULL) { + for (ptr = ident; *ptr != '\0'; ++ptr) { + if (!isalnum(*ptr)) + *ptr = '_'; + } + } + + return 0; +} diff --git a/protomap.c b/protomap.c new file mode 100644 index 0000000..34dac7f --- /dev/null +++ b/protomap.c @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "syslogd.h" + +typedef struct { + const char *name; + int id; +} enum_map_t; + +static const enum_map_t levels[] = { + { "emergency", 0 }, + { "alert", 1 }, + { "critical", 2 }, + { "error", 3 }, + { "warning", 4 }, + { "notice", 5 }, + { "info", 6 }, + { "debug", 7 }, + { NULL, 0 }, +}; + +static const enum_map_t facilities[] = { + { "kernel", 0 }, + { "user", 1 }, + { "mail", 2 }, + { "daemon", 3 }, + { "auth", 4 }, + { "syslog", 5 }, + { "lpr", 6 }, + { "news", 7 }, + { "uucp", 8 }, + { "clock", 9 }, + { "authpriv", 10 }, + { "ftp", 11 }, + { "ntp", 12 }, + { "audit", 13 }, + { "alert", 14 }, + { "cron", 15 }, + { "local0", 16 }, + { "local1", 17 }, + { "local2", 18 }, + { "local3", 19 }, + { "local4", 20 }, + { "local5", 21 }, + { "local6", 22 }, + { "local7", 23 }, + { NULL, 0 }, +}; + +static const char *enum_to_name(const enum_map_t *map, int id) +{ + while (map->name != NULL && map->id != id) + ++map; + + return map->name; +} + +const char *level_id_to_string(int level) +{ + return enum_to_name(levels, level); +} + +const char *facility_id_to_string(int level) +{ + return enum_to_name(facilities, level); +} diff --git a/syslogd.c b/syslogd.c new file mode 100644 index 0000000..98c7aba --- /dev/null +++ b/syslogd.c @@ -0,0 +1,282 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syslogd.h" + +static const struct option long_opts[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "rotate-replace", no_argument, NULL, 'r' }, + { "chroot", no_argument, NULL, 'c' }, + { "max-size", required_argument, NULL, 'm' }, + { "user", required_argument, NULL, 'u' }, + { "group", required_argument, NULL, 'g' }, + { NULL, 0, NULL, 0 }, +}; + +static const char *short_opts = "hVcrm:u:g:"; + +const char *usage_string = +"Usage: usyslogd [OPTIONS..]\n\n" +"The following options are supported:\n" +" -h, --help Print this help text and exit\n" +" -V, --version Print version information and exit\n" +" -r, --rotate-replace Replace old log files when doing log rotation.\n" +" -m, --max-size Automatically rotate log files bigger than this.\n" +" -u, --user Run the syslog daemon as this user. If not set,\n" +" try to use the user '" DEFAULT_USER "'.\n" +" -g, --group Run the syslog daemon as this group. If not set,\n" +" try to use the group '" DEFAULT_GROUP "'.\n" +" -c, --chroot If set, do a chroot into the log file path.\n"; + + + +static volatile sig_atomic_t syslog_run = 1; +static volatile sig_atomic_t syslog_rotate = 0; +static int log_flags = 0; +static size_t max_size = 0; +static uid_t uid = 0; +static gid_t gid = 0; +static bool dochroot = false; + + + +static void sighandler(int signo) +{ + switch (signo) { + case SIGINT: + case SIGTERM: + syslog_run = 0; + break; + case SIGHUP: + syslog_rotate = 1; + break; + default: + break; + } +} + +static void signal_setup(void) +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = sighandler; + + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGHUP, &act, NULL); +} + +static int handle_data(int fd) +{ + char buffer[2048]; + syslog_msg_t msg; + ssize_t ret; + + memset(buffer, 0, sizeof(buffer)); + + ret = read(fd, buffer, sizeof(buffer)); + if (ret <= 0) + return -1; + + if (syslog_msg_parse(&msg, buffer)) + return -1; + + return logmgr->write(logmgr, &msg); +} + +#define GPL_URL "https://gnu.org/licenses/gpl.html" + +static const char *version_string = +"usyslogd (usyslog) " PACKAGE_VERSION "\n" +"Copyright (C) 2018 David Oberhollenzer\n\n" +"License GPLv3+: GNU GPL version 3 or later <" GPL_URL ">.\n" +"This is free software: you are free to change and redistribute it.\n" +"There is NO WARRANTY, to the extent permitted by law.\n"; + +static void process_options(int argc, char **argv) +{ + struct passwd *pw = getpwnam(DEFAULT_USER); + struct group *grp = getgrnam(DEFAULT_GROUP); + char *end; + int i; + + if (pw != NULL) + uid = pw->pw_uid; + + if (grp != NULL) + gid = grp->gr_gid; + + for (;;) { + i = getopt_long(argc, argv, short_opts, long_opts, NULL); + if (i == -1) + break; + + switch (i) { + case 'r': + log_flags |= LOG_ROTATE_OVERWRITE; + break; + case 'm': + log_flags |= LOG_ROTATE_SIZE_LIMIT; + max_size = strtol(optarg, &end, 10); + if (max_size == 0 || *end != '\0') { + fputs("Numeric argument > 0 expected for -m\n", + stderr); + goto fail; + } + break; + case 'u': + pw = getpwnam(optarg); + if (pw == NULL) { + fprintf(stderr, "Cannot get UID for user %s\n", + optarg); + goto fail; + } + uid = pw->pw_uid; + break; + case 'g': + grp = getgrnam(optarg); + if (grp == NULL) { + fprintf(stderr, + "Cannot get GID for group %s\n", + optarg); + goto fail; + } + gid = grp->gr_gid; + break; + case 'c': + dochroot = true; + break; + case 'h': + fputs(usage_string, stdout); + exit(EXIT_SUCCESS); + case 'V': + fputs(version_string, stdout); + exit(EXIT_SUCCESS); + default: + goto fail; + } + } + return; +fail: + fputs("Try `usyslogd --help' for more information\n", stderr); + exit(EXIT_FAILURE); +} + +static int chroot_setup(void) +{ + if (mkdir(SYSLOG_PATH, 0750)) { + if (errno != EEXIST) { + perror("mkdir " SYSLOG_PATH); + return -1; + } + } + + if (uid > 0 && gid > 0 && chown(SYSLOG_PATH, uid, gid) != 0) { + perror("chown " SYSLOG_PATH); + return -1; + } + + if (chmod(SYSLOG_PATH, 0750)) { + perror("chmod " SYSLOG_PATH); + return -1; + } + + if (chdir(SYSLOG_PATH)) { + perror("cd " SYSLOG_PATH); + return -1; + } + + if (dochroot && chroot(SYSLOG_PATH) != 0) { + perror("chroot " SYSLOG_PATH); + return -1; + } + + return 0; +} + +static int user_setup(void) +{ + if (gid > 0 && setresgid(gid, gid, gid) != 0) { + perror("setgid"); + return -1; + } + if (uid > 0 && setresuid(uid, uid, uid) != 0) { + perror("setuid"); + return -1; + } + return 0; +} + +int main(int argc, char **argv) +{ + int sfd, status = EXIT_FAILURE; + + process_options(argc, argv); + + signal_setup(); + + sfd = mksock(SYSLOG_SOCKET); + if (sfd < 0) + return EXIT_FAILURE; + + if (uid > 0 && gid > 0 && chown(SYSLOG_SOCKET, uid, gid) != 0) { + perror("chown " SYSLOG_SOCKET); + return -1; + } + + if (chroot_setup()) + return EXIT_FAILURE; + + if (user_setup()) + return EXIT_FAILURE; + + if (logmgr->init(logmgr, log_flags, max_size)) + goto out; + + while (syslog_run) { + if (syslog_rotate) { + logmgr->rotate(logmgr); + syslog_rotate = 0; + } + + handle_data(sfd); + } + + status = EXIT_SUCCESS; +out: + logmgr->cleanup(logmgr); + if (sfd > 0) + close(sfd); + unlink(SYSLOG_SOCKET); + return status; +} diff --git a/syslogd.h b/syslogd.h new file mode 100644 index 0000000..5ba3fd2 --- /dev/null +++ b/syslogd.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +/* + * Copyright (C) 2018 - David Oberhollenzer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef SYSLOGD_H +#define SYSLOGD_H + + +#include +#include +#include + +#include "config.h" + + +#define SYSLOG_SOCKET "/dev/log" +#define SYSLOG_PATH "/var/log" +#define DEFAULT_USER "syslogd" +#define DEFAULT_GROUP "syslogd" + + +/* + encapsulates the split up data from a message received + through the local syslog socket. + */ +typedef struct { + int facility; + int level; + time_t timestamp; + pid_t pid; + const char *ident; + const char *message; +} syslog_msg_t; + + +enum { + /* + Rotate log data in a way that we still generate a continuous stream + of log data. E.g. in the case of log files, move the current log file + to one suffixed with a timestamp. We don't lose any log data. + */ + LOG_ROTATE_CONTINUOUS = 0x00, + + /* + Rotate log data by overwriting old data with more recent data. + E.g. in the case of log files, move the current log file to one + with a constant prefix, overwriting any existing data. + */ + LOG_ROTATE_OVERWRITE = 0x01, + + /* + Automatically do a log rotatation if a log stream reaches a preset + size limit. + */ + LOG_ROTATE_SIZE_LIMIT = 0x10, +}; + +typedef struct log_backend_t { + int (*init)(struct log_backend_t *log, int flags, size_t sizelimit); + + void (*cleanup)(struct log_backend_t *log); + + int (*write)(struct log_backend_t *log, const syslog_msg_t *msg); + + void (*rotate)(struct log_backend_t *log); +} log_backend_t; + + +extern log_backend_t *logmgr; + +/* + Parse a message string received from the syslog socket and produce + a split up representation for the message. + */ +int syslog_msg_parse(syslog_msg_t *msg, char *str); + +/* Create a unix DGRAM socket. */ +int mksock(const char *path); + +const char *level_id_to_string(int level); + +const char *facility_id_to_string(int level); + +#endif /* LOGFILE_H */