aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Oberhollenzer <david.oberhollenzer@tele2.at>2018-10-28 12:10:07 +0100
committerDavid Oberhollenzer <david.oberhollenzer@tele2.at>2018-10-28 12:31:03 +0100
commita4423189abc65a592301a7b161f5366bf6bfa501 (patch)
tree349ba487611c615e984098ffe832c07805b1216f
Initial import
Signed-off-by: David Oberhollenzer <david.oberhollenzer@tele2.at>
-rw-r--r--.gitignore19
-rw-r--r--Makefile.am11
-rwxr-xr-xautogen.sh3
-rw-r--r--configure.ac35
-rw-r--r--klogd.c193
-rw-r--r--logfile.c242
-rw-r--r--m4/compiler.m440
-rw-r--r--mksock.c63
-rw-r--r--proto.c194
-rw-r--r--protomap.c81
-rw-r--r--syslogd.c282
-rw-r--r--syslogd.h97
12 files changed, 1260 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>.
+ */
+#include <sys/klog.h>
+#include <syslog.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <errno.h>
+
+#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 <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 <https://www.gnu.org/licenses/>.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+#include <stddef.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+#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 <https://www.gnu.org/licenses/>.
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <grp.h>
+
+#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 <size> Automatically rotate log files bigger than this.\n"
+" -u, --user <name> Run the syslog daemon as this user. If not set,\n"
+" try to use the user '" DEFAULT_USER "'.\n"
+" -g, --group <name> 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 <https://www.gnu.org/licenses/>.
+ */
+#ifndef SYSLOGD_H
+#define SYSLOGD_H
+
+
+#include <sys/types.h>
+#include <stdbool.h>
+#include <time.h>
+
+#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 */