This tiny daemon enables to pull journal entries and push to a UDP multicast address in syslog RFC 5424 format. systemd-journal-syslogd runs with own user systemd-journal-syslog. It starts running after the network is up.
V2: Address Zbigniew's comments 1. Rename binary systemd-journal-syslogd 2. Fixed up man and added example 3. Error code check sd_event_add_signal 4. remove +User=systemd-journal-network from service file 5. remove opterr=0 6. assignment into declaration of mh V3: Address Lennart's comments 1. add unicast events in the man 2. use fopen_temporary and fflush_and_check(). 3. remove if (m->event_journal_input) { 4. use sigprocmask_many 5. fix facility and priority 6. remove IP_MULTICAST_TTL and IP_PKTINFO 7. use safe_atoi --- Makefile-man.am | 8 + Makefile.am | 37 ++ man/systemd-journal-syslogd.service.xml | 84 +++++ man/systemd-journal-syslogd.xml | 156 ++++++++ src/journal-remote/journal-syslog-conf.c | 61 ++++ src/journal-remote/journal-syslog-conf.h | 39 ++ src/journal-remote/journal-syslog-gperf.gperf | 18 + src/journal-remote/journal-syslog-manager.c | 501 ++++++++++++++++++++++++++ src/journal-remote/journal-syslog-manager.h | 70 ++++ src/journal-remote/journal-syslog-network.c | 201 +++++++++++ src/journal-remote/journal-syslogd.c | 217 +++++++++++ src/journal-remote/journal-syslogd.conf.in | 2 + units/systemd-journal-syslogd.service | 18 + 13 files changed, 1412 insertions(+) create mode 100644 man/systemd-journal-syslogd.service.xml create mode 100644 man/systemd-journal-syslogd.xml create mode 100644 src/journal-remote/journal-syslog-conf.c create mode 100644 src/journal-remote/journal-syslog-conf.h create mode 100644 src/journal-remote/journal-syslog-gperf.gperf create mode 100644 src/journal-remote/journal-syslog-manager.c create mode 100644 src/journal-remote/journal-syslog-manager.h create mode 100644 src/journal-remote/journal-syslog-network.c create mode 100644 src/journal-remote/journal-syslogd.c create mode 100644 src/journal-remote/journal-syslogd.conf.in create mode 100644 units/systemd-journal-syslogd.service diff --git a/Makefile-man.am b/Makefile-man.am index 2f3e5f2..437d488 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -1380,6 +1380,14 @@ man/systemd-journal-gatewayd.socket.html: man/systemd-journal-gatewayd.service.h endif +MANPAGES += \ + man/systemd-journal-syslogd.service.8 \ + man/systemd-journal-syslogd.8 +MANPAGES_ALIAS += \ + man/systemd-journal-syslogd.8 +man/systemd-journal-syslogd.8: man/systemd-journal-syslogd.service.8 +man/systemd-journal-syslogd.html: man/systemd-journal-syslogd.service.html + if HAVE_MYHOSTNAME MANPAGES += \ man/nss-myhostname.8 diff --git a/Makefile.am b/Makefile.am index 0a57389..0b843ac 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4361,6 +4361,43 @@ EXTRA_DIST += \ src/journal-remote/journal-upload.conf.in endif +systemd_journal_syslogd_SOURCES = \ + src/journal-remote/journal-syslog-manager.h \ + src/journal-remote/journal-syslog-manager.c \ + src/journal-remote/journal-syslog-conf.h \ + src/journal-remote/journal-syslog-conf.c \ + src/journal-remote/journal-syslog-network.c \ + src/journal-remote/journal-syslogd.c + +nodist_systemd_journal_syslogd_SOURCES = \ + src/journal-remote/journal-syslog-gperf.c + +EXTRA_DIST += \ + src/journal-remote/journal-syslog-gperf.gperf + +CLEANFILES += \ + src/journal-remote/journal-syslog-gperf.c + +systemd_journal_syslogd_LDADD = \ + libsystemd-internal.la \ + libsystemd-journal-internal.la \ + libsystemd-shared.la + +rootlibexec_PROGRAMS += \ + systemd-journal-syslogd + +nodist_systemunit_DATA += \ + units/systemd-journal-syslogd.service + +EXTRA_DIST += \ + units/systemd-journal-syslogd.service.in + +nodist_pkgsysconf_DATA += \ + src/journal-remote/journal-syslogd.conf + +EXTRA_DIST += \ + src/journal-remote/journal-syslogd.conf.in + # using _CFLAGS = in the conditional below would suppress AM_CFLAGS journalctl_CFLAGS = \ $(AM_CFLAGS) diff --git a/man/systemd-journal-syslogd.service.xml b/man/systemd-journal-syslogd.service.xml new file mode 100644 index 0000000..e837153 --- /dev/null +++ b/man/systemd-journal-syslogd.service.xml @@ -0,0 +1,84 @@ +<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" +"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2015 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="systemd-journal-syslogd.service" xmlns:xi="http://www.w3.org/2001/XInclude"> + + <refentryinfo> + <title>systemd-journal-syslogd.service</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Susant</firstname> + <surname>Sahani</surname> + <email>ssah...@gmail.com</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-journal-syslogd.service</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-journal-syslogd.service</refname> + <refname>systemd-journal-syslogd</refname> + <refpurpose>Forward journal events using syslog network procotol</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>systemd-journal-syslogd.service</filename></para> + <cmdsynopsis> + <command>/usr/lib/systemd/systemd-journal-syslogd</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><command>systemd-journal-syslogd</command> serves journal + events over the network. It unicasts and multicasts journal event to Syslog RFC 5424 format. + </para> + + <para>The program is started by + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> + . Use + <command>systemctl start systemd-journal-syslogd.service</command> to start + the service, and <command>systemctl enable systemd-journal-syslogd.service</command> + to have it started on boot.</para> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>, + </para> + </refsect1> + +</refentry> diff --git a/man/systemd-journal-syslogd.xml b/man/systemd-journal-syslogd.xml new file mode 100644 index 0000000..5497b2e --- /dev/null +++ b/man/systemd-journal-syslogd.xml @@ -0,0 +1,156 @@ +<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" +"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> + +<!-- + This file is part of systemd. + + Copyright 2015 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +--> + +<refentry id="systemd-journal-syslogd" xmlns:xi="http://www.w3.org/2001/XInclude"> + <refentryinfo> + <title>systemd-journal-syslogd</title> + <productname>systemd</productname> + + <authorgroup> + <author> + <contrib>Developer</contrib> + <firstname>Susant</firstname> + <surname>Sahani</surname> + <email>ssah...@gmail.com</email> + </author> + </authorgroup> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-journal-syslogd</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-journal-syslogd</refname> + <refpurpose>Send journal messages over the network in RFC 5424 format</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis> + <command>systemd-journal-syslogd</command> + <arg choice="opt" rep="repeat">OPTIONS</arg> + <arg choice="opt" rep="norepeat">--save-state=<replaceable>state file</replaceable></arg> + <arg choice="opt" rep="norepeat">--cursor=<replaceable>journal cursor</replaceable></arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para> + <command>systemd-journal-syslogd</command> will send journal + entries to the UDP Unicast and Multicast address . Unless + limited by one of the options specified below, all journal + entries accessible to the user the program is running as will be + sent, and then the program will wait and send new entries + as they become available. + </para> + </refsect1> + + <refsect1> + <title>Options</title> + + <variablelist> + <varlistentry> + <term><option>--cursor=</option></term> + + <listitem><para>Send entries from the location in the + journal specified by the passed cursor. This has the same + meaning as <option>--cursor</option> option for + <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para></listitem> + </varlistentry> + + <varlistentry> + <term><option>--save-state</option><optional>=<replaceable>PATH</replaceable></optional></term> + + <listitem><para>Send entries from the location in the + journal <emphasis>after</emphasis> the location specified by + the cursor saved in file at <replaceable>PATH</replaceable> + (<filename>/var/lib/systemd/journal-syslogd/state</filename> by default). + After an entry is successfully uploaded, update this file + with the cursor of that entry. + </para></listitem> + </varlistentry> + + <xi:include href="standard-options.xml" xpointer="help" /> + <xi:include href="standard-options.xml" xpointer="version" /> + </variablelist> + </refsect1> + + <refsect1> + <title>Exit status</title> + + <para>On success, 0 is returned; otherwise, a non-zero + failure code is returned.</para> + </refsect1> + + <refsect1> + <title>[Network] Section Options</title> + + <para>The <literal>[Network]</literal> section only applies for + UDP multicast address and Port:</para> + + <variablelist class='network-directives'> + <varlistentry> + <term><varname>Address=</varname></term> + <listitem><para>Controls whether log messages received by the + journal daemon shall be forwarded to a unicast UDP address or multicast UDP network + group in syslog RFC 5424 format.</para> + + <para>The the address string format is similar to socket units. See + <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>1</manvolnum></citerefentry> + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>Example</title> + <example> + <title>/etc/systemd/journal-syslogd.conf</title> + <programlisting>[Network] +Address=239.0.0.1:6000 + </programlisting> + </example> + </refsect1> + + <refsect1> + <title>Example</title> + <example> + <title>/etc/systemd/journal-syslogd.conf</title> + <programlisting>[Network] +Address=192.168.8.101:514 + </programlisting> + </example> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-journal-syslogd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + </para> + </refsect1> +</refentry> diff --git a/src/journal-remote/journal-syslog-conf.c b/src/journal-remote/journal-syslog-conf.c new file mode 100644 index 0000000..c357189 --- /dev/null +++ b/src/journal-remote/journal-syslog-conf.c @@ -0,0 +1,61 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "in-addr-util.h" +#include "journal-syslog-conf.h" + +int config_parse_syslog_broadcast_address(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = socket_address_parse(&m->address, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, -r, + "Failed to parse address value, ignoring: %s", rvalue); + return 0; + } + + return 0; +} + +int manager_parse_config_file(Manager *m) { + assert(m); + + return config_parse_many("/etc/systemd/journal-syslogd.conf", + CONF_DIRS_NULSTR("systemd/journal-syslogd.conf"), + "Network\0", + config_item_perf_lookup, journal_syslog_gperf_lookup, + false, m); +} diff --git a/src/journal-remote/journal-syslog-conf.h b/src/journal-remote/journal-syslog-conf.h new file mode 100644 index 0000000..ca9ef05 --- /dev/null +++ b/src/journal-remote/journal-syslog-conf.h @@ -0,0 +1,39 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#pragma once + +#include "in-addr-util.h" +#include "conf-parser.h" +#include "journal-syslog-manager.h" + +const struct ConfigPerfItem* journal_syslog_gperf_lookup(const char *key, unsigned length); +int config_parse_syslog_broadcast_address(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata); +int manager_parse_config_file(Manager *m); diff --git a/src/journal-remote/journal-syslog-gperf.gperf b/src/journal-remote/journal-syslog-gperf.gperf new file mode 100644 index 0000000..e0f364f --- /dev/null +++ b/src/journal-remote/journal-syslog-gperf.gperf @@ -0,0 +1,18 @@ +%{ +#include <stddef.h> +#include "conf-parser.h" +#include "journal-syslog-conf.h" +#include "journal-syslog-manager.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name journal_syslog_gperf_hash +%define lookup-function-name journal_syslog_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Network.Address, config_parse_syslog_broadcast_address, 0, 0 diff --git a/src/journal-remote/journal-syslog-manager.c b/src/journal-remote/journal-syslog-manager.c new file mode 100644 index 0000000..1051f2d --- /dev/null +++ b/src/journal-remote/journal-syslog-manager.c @@ -0,0 +1,501 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" +#include "socket-util.h" +#include "conf-parser.h" +#include "sd-daemon.h" +#include "network-util.h" +#include "capability.h" +#include "mkdir.h" +#include "fileio.h" +#include "journal-internal.h" +#include "journal-syslog-manager.h" + +#define JOURNAL_SEND_POLL_TIMEOUT (10 * USEC_PER_SEC) + +/* Default severity LOG_NOTICE */ +#define JOURNAL_DEFAULT_SEVERITY LOG_PRI(LOG_NOTICE) + +/* Default facility LOG_USER */ +#define JOURNAL_DEFAULT_FACILITY LOG_FAC(LOG_USER) + +static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { + size_t fl, nl; + void *buf; + + assert(data); + assert(field); + assert(target); + assert(target_size); + + fl = strlen(field); + if (length < fl) + return 0; + + if (memcmp(data, field, fl)) + return 0; + + nl = length - fl; + buf = malloc(nl+1); + if (!buf) + return -ENOMEM; + + memcpy(buf, (const char*) data + fl, nl); + ((char*)buf)[nl] = 0; + + free(*target); + *target = buf; + *target_size = nl; + + return 1; +} + +static int manager_read_journal_input(Manager *m) { + _cleanup_free_ char *facility = NULL, *identifier = NULL, + *priority = NULL, *message = NULL, *pid = NULL, + *hostname = NULL; + int sev = JOURNAL_DEFAULT_SEVERITY; + int fac = JOURNAL_DEFAULT_FACILITY; + struct timeval tv; + usec_t realtime; + const void *data; + size_t length; + size_t n = 0; + int r; + + assert(m); + assert(m->journal); + + JOURNAL_FOREACH_DATA_RETVAL(m->journal, data, length, r) { + + r = parse_field(data, length, "PRIORITY=", &priority, &n); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_FACILITY=", &facility, &n); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_HOSTNAME=", &hostname, &n); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &n); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_PID=", &pid, &n); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "MESSAGE=", &message, &n); + if (r < 0) + return r; + } + + r = sd_journal_get_realtime_usec(m->journal, &realtime); + if (r < 0) + log_warning_errno(r, "Failed to rerieve realtime from journal: %m"); + else { + tv.tv_sec = realtime / USEC_PER_SEC; + tv.tv_usec = realtime % USEC_PER_SEC; + } + + if (facility) { + r = safe_atoi(facility, &fac); + if (r < 0) + log_debug("Failed to parse syslog facility: %s", facility); + + if (fac < LOG_KERN || fac >= LOG_NFACILITIES) + fac = JOURNAL_DEFAULT_FACILITY; + } + + if (priority) { + r = safe_atoi(priority, &sev); + if (r < 0) + log_debug("Failed to parse syslog priority: %s", priority); + + if (sev < LOG_EMERG || sev > LOG_DEBUG) + sev = JOURNAL_DEFAULT_SEVERITY; + } + + return manager_push_to_network(m, sev, fac, identifier, + message, hostname, pid, r >= 0 ? &tv : NULL); +} + +static int update_cursor_state(Manager *m) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + + if (!m->state_file || !m->last_cursor) + return 0; + + r = fopen_temporary(m->state_file, &f, &temp_path); + if (r < 0) + goto finish; + + fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "LAST_CURSOR=%s\n", + m->last_cursor); + + r = fflush_and_check(f); + if (r < 0) + goto finish; + + if (rename(temp_path, m->state_file) < 0) { + r = -errno; + goto finish; + } + + free(temp_path); + temp_path = NULL; + + finish: + if (r < 0) + log_error_errno(r, "Failed to save state %s: %m", m->state_file); + + return r; +} + +static int load_cursor_state(Manager *m) { + int r; + + assert(m); + + if (!m->state_file) + return 0; + + r = parse_env_file(m->state_file, NEWLINE, "LAST_CURSOR", &m->last_cursor, NULL); + if (r < 0 && r != -ENOENT) + return r; + + log_debug("Last cursor was %s.", m->last_cursor ? m->last_cursor : "Not available"); + + return 0; +} + +static int process_journal_input(Manager *m) { + int r; + + assert(m); + assert(m->journal); + + while (true) { + r = sd_journal_next(m->journal); + if (r < 0) { + log_error_errno(r, "Failed to get next entry: %m"); + return r; + } + + if (r == 0) + break; + + r = manager_read_journal_input(m); + if (r < 0) { + /* Can't send the message. Seek one entry back. */ + r = sd_journal_previous(m->journal); + if (r < 0) + log_error_errno(r, "Failed to iterate through journal: %m"); + + break; + } + } + + r = sd_journal_get_cursor(m->journal, &m->current_cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + free(m->last_cursor); + m->last_cursor = m->current_cursor; + m->current_cursor = NULL; + + return update_cursor_state(m); +} + +static int manager_journal_event_handler(sd_event_source *event, int fd, uint32_t revents, void *userp) { + Manager *m = userp; + int r; + + if (revents & EPOLLHUP) { + log_debug("Received HUP"); + return 0; + } + + if (!(revents & EPOLLIN)) { + log_warning("Unexpected poll event %"PRIu32".", revents); + return -EINVAL; + } + + r = sd_journal_process(m->journal); + if (r < 0) { + log_error_errno(r, "Failed to process journal: %m"); + manager_disconnect(m); + return r; + } + + if (r == SD_JOURNAL_NOP) + return 0; + + return process_journal_input(m); +} + +static void close_journal_input(Manager *m) { + assert(m); + + if (m->journal) { + log_debug("Closing journal input."); + + sd_journal_close(m->journal); + m->journal = NULL; + } + + m->timeout = 0; +} + +static int manager_signal_event_handler(sd_event_source *event, const struct signalfd_siginfo *si, void *userdata) { + Manager *m = userdata; + + assert(m); + + log_received_signal(LOG_INFO, si); + + manager_disconnect(m); + + sd_event_exit(m->event, 0); + + return 0; +} + +static int manager_journal_monitor_listen(Manager *m) { + int r, events; + + assert(m); + + r = sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY); + if (r < 0) { + log_error_errno(r, "Failed to open journal: %m"); + return r; + } + + sd_journal_set_data_threshold(m->journal, 0); + + m->journal_watch_fd = sd_journal_get_fd(m->journal); + if (m->journal_watch_fd < 0) + return log_error_errno(m->journal_watch_fd, "sd_journal_get_fd failed: %m"); + + events = sd_journal_get_events(m->journal); + + r = sd_journal_reliable_fd(m->journal); + assert(r >= 0); + if (r > 0) + m->timeout = -1; + else + m->timeout = JOURNAL_SEND_POLL_TIMEOUT; + + r = sd_event_add_io(m->event, &m->event_journal_input , + m->journal_watch_fd , events, manager_journal_event_handler, m); + if (r < 0) + return log_error_errno(r, "Failed to register input event: %m"); + + /* ignore failure */ + if (!m->last_cursor) + (void) load_cursor_state(m); + + if (m->last_cursor) { + r = sd_journal_seek_cursor(m->journal, m->last_cursor); + if (r < 0) + return log_error_errno(r, "Failed to seek to cursor %s: %m", + m->last_cursor); + } + + return 0; +} + +int manager_connect(Manager *m) { + int r; + + assert(m); + + manager_disconnect(m); + + r = manager_open_network_socket(m); + if (r < 0) + return r; + + r = manager_journal_monitor_listen(m); + if (r < 0) + return r; + + return 0; +} + +void manager_disconnect(Manager *m) { + assert(m); + + close_journal_input(m); + + manager_close_network_socket(m); + + m->event_journal_input = sd_event_source_unref(m->event_journal_input); + + sd_notifyf(false, "STATUS=Idle."); +} + +static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + bool connected, online; + int r; + + assert(m); + + sd_network_monitor_flush(m->network_monitor); + + /* check if the machine is online */ + online = network_is_online(); + + /* check if the socket is currently open*/ + connected = m->socket >= 0; + + if (connected && !online) { + log_info("No network connectivity, watching for changes."); + manager_disconnect(m); + + } else if (!connected && online) { + log_info("Network configuration changed, trying to establish connection."); + + r = manager_connect(m); + if (r < 0) + return r; + } + + return 0; +} + +static int manager_network_monitor_listen(Manager *m) { + int r, fd, events; + + assert(m); + + r = sd_network_monitor_new(&m->network_monitor, NULL); + if (r < 0) + return r; + + fd = sd_network_monitor_get_fd(m->network_monitor); + if (fd < 0) + return fd; + + events = sd_network_monitor_get_events(m->network_monitor); + if (events < 0) + return events; + + r = sd_event_add_io(m->event, &m->network_event_source, fd, events, manager_network_event_handler, m); + if (r < 0) + return r; + + return 0; +} + +void manager_free(Manager *m) { + if (!m) + return; + + manager_disconnect(m); + + free(m->last_cursor); + free(m->current_cursor); + + free(m->state_file); + + sd_event_source_unref(m->network_event_source); + sd_network_monitor_unref(m->network_monitor); + + sd_event_source_unref(m->sigterm_event); + sd_event_source_unref(m->sigint_event); + + sd_event_unref(m->event); + + free(m); +} + +int manager_new(Manager **ret, const char *state_file, const char *cursor) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + assert(ret); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + m->socket = m->journal_watch_fd = -1; + + m->state_file = strdup(state_file); + if (!m->state_file) + return -ENOMEM; + + if (cursor) { + m->last_cursor = strdup(cursor); + if (!m->last_cursor) + return -ENOMEM; + } + + r = sd_event_default(&m->event); + if (r < 0) + return log_error_errno(r, "sd_event_default failed: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0); + + r = sd_event_add_signal(m->event, NULL, SIGTERM, manager_signal_event_handler, m); + if (r < 0) + return r; + + r = sd_event_add_signal(m->event, NULL, SIGINT, manager_signal_event_handler, m); + if (r < 0) + return r; + + sd_event_set_watchdog(m->event, true); + + r = manager_network_monitor_listen(m); + if (r < 0) + return r; + + *ret = m; + m = NULL; + + return 0; +} diff --git a/src/journal-remote/journal-syslog-manager.h b/src/journal-remote/journal-syslog-manager.h new file mode 100644 index 0000000..aeb553b --- /dev/null +++ b/src/journal-remote/journal-syslog-manager.h @@ -0,0 +1,70 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#pragma once + +#include "sd-event.h" +#include "sd-network.h" +#include "socket-util.h" +#include "sd-journal.h" + +typedef struct Manager Manager; + +struct Manager { + sd_event *event; + sd_event_source *event_journal_input; + uint64_t timeout; + + sd_event_source *sigint_event, *sigterm_event; + + /* network */ + sd_event_source *network_event_source; + sd_network_monitor *network_monitor; + + int socket; + + /* Multicast UDP address */ + SocketAddress address; + + /* journal */ + int journal_watch_fd; + sd_journal *journal; + + char *state_file; + + char *last_cursor, *current_cursor; +}; + +int manager_new(Manager **ret, const char *state_file, const char *cursor); +void manager_free(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +int manager_connect(Manager *m); +void manager_disconnect(Manager *m); + +void manager_close_network_socket(Manager *m); +int manager_open_network_socket(Manager *m); + +int manager_push_to_network(Manager *m, int severity, int facility, + const char *identifier, const char *message, + const char *hostname, const char *pid, + const struct timeval *tv); diff --git a/src/journal-remote/journal-syslog-network.c b/src/journal-remote/journal-syslog-network.c new file mode 100644 index 0000000..26ac362 --- /dev/null +++ b/src/journal-remote/journal-syslog-network.c @@ -0,0 +1,201 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <unistd.h> +#include <stddef.h> +#include <poll.h> + +#include "journal-syslog-manager.h" + +#define RFC_5424_NILVALUE "-" +#define RFC_5424_PROTOCOL 1 + +#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) + +static int sendmsg_loop(Manager *m, struct msghdr *mh) { + int r; + + assert(m); + assert(mh); + + for (;;) { + if (sendmsg(m->socket, mh, MSG_NOSIGNAL) >= 0) + return 0; + + if (errno == EINTR) + continue; + + if (errno != EAGAIN) + return -errno; + + r = fd_wait_for_event(m->socket, POLLOUT, SEND_TIMEOUT_USEC); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; + } + + return 0; +} + +static int network_send(Manager *m, struct iovec *iovec, unsigned n_iovec) { + struct msghdr mh = { + .msg_iov = iovec, + .msg_iovlen = n_iovec, + }; + + assert(m); + assert(iovec); + assert(n_iovec > 0); + + if (m->address.sockaddr.sa.sa_family == AF_INET) { + mh.msg_name = &m->address.sockaddr.sa; + mh.msg_namelen = sizeof(m->address.sockaddr.sa); + } else if (m->address.sockaddr.sa.sa_family == AF_INET6) { + mh.msg_name = &m->address.sockaddr.in6; + mh.msg_namelen = sizeof(m->address.sockaddr.in6); + } else + return -EAFNOSUPPORT; + + return sendmsg_loop(m, &mh); +} + +/* rfc3339 timestamp format: yyyy-mm-ddthh:mm:ss[.frac]<+/->zz:zz */ +static void format_rfc3339_timestamp(const struct timeval *tv, char *header_time, size_t header_size) { + char gm_buf[sizeof("+0530") + 1]; + struct tm tm; + time_t t; + + assert(header_time); + + t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC)); + localtime_r(&t, &tm); + + strftime(header_time, header_size, "%Y-%m-%dT%T", &tm); + + /* add fractional part */ + if (tv) + snprintf(header_time + strlen(header_time), header_size, ".%06ld", tv->tv_usec); + + /* format the timezone according to RFC */ + xstrftime(gm_buf, "%z", &tm); + snprintf(header_time + strlen(header_time), header_size, "%.3s:%.2s ", gm_buf, gm_buf + 3); +} + +/* The Syslog Protocol RFC5424 format : + * <pri>version sp timestamp sp hostname sp app-name sp procid sp msgid sp [sd-id]s sp msg + */ +int manager_push_to_network(Manager *m, + int severity, + int facility, + const char *identifier, + const char *message, + const char *hostname, + const char *pid, + const struct timeval *tv) { + char header_priority[sizeof("< >1 ") + 1]; + char header_time[FORMAT_TIMESTAMP_MAX]; + uint16_t makepri; + struct iovec iov[13]; + int n = 0; + + assert(m); + assert(message); + + makepri = (facility << 3) + severity; + + /* First: priority field Second: Version '<pri>version' */ + snprintf(header_priority, sizeof(header_priority), "<%i>%i ", makepri, RFC_5424_PROTOCOL); + IOVEC_SET_STRING(iov[n++], header_priority); + + /* Third: timestamp */ + format_rfc3339_timestamp(tv, header_time, sizeof(header_time)); + IOVEC_SET_STRING(iov[n++], header_time); + + /* Fourth: hostname */ + if (hostname) + IOVEC_SET_STRING(iov[n++], hostname); + else + IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE); + + IOVEC_SET_STRING(iov[n++], " "); + + /* Fifth: identifier */ + if (identifier) + IOVEC_SET_STRING(iov[n++], identifier); + else + IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE); + + IOVEC_SET_STRING(iov[n++], " "); + + /* Sixth: procid */ + if (pid) + IOVEC_SET_STRING(iov[n++], pid); + else + IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE); + + IOVEC_SET_STRING(iov[n++], " "); + + /* Seventh: msgid */ + IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE); + IOVEC_SET_STRING(iov[n++], " "); + + /* Eighth: [structured-data] */ + IOVEC_SET_STRING(iov[n++], RFC_5424_NILVALUE); + IOVEC_SET_STRING(iov[n++], " "); + + /* Ninth: message */ + IOVEC_SET_STRING(iov[n++], message); + + return network_send(m, iov, n); +} + +void manager_close_network_socket(Manager *m) { + assert(m); + + m->socket = safe_close(m->socket); +} + +int manager_open_network_socket(Manager *m) { + const int one = 1; + int r; + + assert(m); + + if (!IN_SET(m->address.sockaddr.sa.sa_family, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + m->socket = socket(m->address.sockaddr.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->socket < 0) + return -errno; + + r = setsockopt(m->socket, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + return m->socket; + + fail: + m->socket = safe_close(m->socket); + return r; +} diff --git a/src/journal-remote/journal-syslogd.c b/src/journal-remote/journal-syslogd.c new file mode 100644 index 0000000..fd8e770 --- /dev/null +++ b/src/journal-remote/journal-syslogd.c @@ -0,0 +1,217 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Susant Sahani + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdio.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <getopt.h> + +#include "sd-daemon.h" +#include "util.h" +#include "build.h" +#include "mkdir.h" +#include "capability.h" +#include "network-util.h" +#include "journal-syslog-conf.h" +#include "journal-syslog-manager.h" + +#define STATE_FILE "/var/lib/systemd/journal-syslogd/state" + +static const char *arg_cursor = NULL; +static const char *arg_save_state = STATE_FILE; + +static int setup_cursor_state_file(Manager *m, uid_t uid, gid_t gid) { + _cleanup_close_ int fd = -1; + int r; + + assert(m); + + r = mkdir_parents(m->state_file, 0755); + if (r < 0) + return log_error_errno(r, "Cannot create parent directory of state file %s: %m", + m->state_file); + + fd = open(m->state_file, O_RDWR|O_CLOEXEC, 0644); + if (fd >= 0) { + + /* Try to fix the access mode, so that we can still + touch the file after dropping priviliges */ + fchmod(fd, 0644); + fchown(fd, uid, gid); + } else + /* create stamp file with the compiled-in date */ + return touch_file(m->state_file, true, USEC_INFINITY, uid, gid, 0644); + + return 0; +} + +static void help(void) { + printf("%s ..\n\n" + "Send journal events to a UDP multicast group in RFC 5424 syslog format.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --cursor=CURSOR Start at the specified cursor\n" + " --save-state[=FILE] Save uploaded cursors (default \n" + " " STATE_FILE ")\n" + " -h --help Show this help and exit\n" + " --version Print version string and exit\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_CURSOR, + ARG_SAVE_STATE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "cursor", required_argument, NULL, ARG_CURSOR }, + { "save-state", optional_argument, NULL, ARG_SAVE_STATE }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0 /* done */; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0 /* done */; + case ARG_CURSOR: + if (arg_cursor) { + log_error("cannot use more than one --cursor/--after-cursor"); + return -EINVAL; + } + + arg_cursor = optarg; + break; + case ARG_SAVE_STATE: + arg_save_state = optarg ?: STATE_FILE; + break; + + case '?': + log_error("Unknown option %s.", argv[optind-1]); + return -EINVAL; + + case ':': + log_error("Missing argument to %s.", argv[optind-1]); + return -EINVAL; + + default: + assert_not_reached("Unhandled option code."); + } + + + if (optind < argc) { + log_error("Input arguments make no sense with journal input."); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char **argv) { + _cleanup_(manager_freep) Manager *m = NULL; + const char *user = "systemd-journal-syslog"; + uid_t uid; + gid_t gid; + int r; + + log_show_color(true); + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Cannot resolve user name %s: %m", user); + goto finish; + } + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = manager_new(&m, arg_save_state, arg_cursor); + if (r < 0) { + log_error_errno(r, "Failed to allocate manager: %m"); + goto finish; + } + + r = manager_parse_config_file(m); + if (r < 0) { + log_error_errno(r, "Failed to parse configuration file: %m"); + goto finish; + } + + r = setup_cursor_state_file(m, uid, gid); + if (r < 0) + goto cleanup; + + r = drop_privileges(uid, gid, + (1ULL << CAP_NET_ADMIN) | + (1ULL << CAP_NET_BIND_SERVICE) | + (1ULL << CAP_NET_BROADCAST)); + if (r < 0) + goto finish; + + log_debug("%s running as pid "PID_FMT, + program_invocation_short_name, getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing input..."); + + if (network_is_online()) { + r = manager_connect(m); + if (r < 0) + goto finish; + } + + r = sd_event_loop(m->event); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + + sd_event_get_exit_code(m->event, &r); + + cleanup: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down..."); + + finish: + return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/journal-remote/journal-syslogd.conf.in b/src/journal-remote/journal-syslogd.conf.in new file mode 100644 index 0000000..b567a46 --- /dev/null +++ b/src/journal-remote/journal-syslogd.conf.in @@ -0,0 +1,2 @@ +[Network] +#Address=239.0.0.1:6000 diff --git a/units/systemd-journal-syslogd.service b/units/systemd-journal-syslogd.service new file mode 100644 index 0000000..6e8bfc5 --- /dev/null +++ b/units/systemd-journal-syslogd.service @@ -0,0 +1,18 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +[Unit] +Description=Journal Syslog Unicast and Multicast Daemon +After=network.target + +[Service] +ExecStart=/usr/lib/systemd/systemd-journal-syslogd +PrivateTmp=yes +PrivateDevices=yes +WatchdogSec=20min + +[Install] +WantedBy=multi-user.target -- 2.3.5 _______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel