summaryrefslogtreecommitdiffstats
path: root/misc-utils/logger.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:10:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 19:10:49 +0000
commitcfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch)
treed0baf160cbee3195249d095f85e52d20c21acf02 /misc-utils/logger.c
parentInitial commit. (diff)
downloadutil-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.tar.xz
util-linux-cfe5e3905201349e9cf3f95d52ff4bd100bde37d.zip
Adding upstream version 2.39.3.upstream/2.39.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'misc-utils/logger.c')
-rw-r--r--misc-utils/logger.c1324
1 files changed, 1324 insertions, 0 deletions
diff --git a/misc-utils/logger.c b/misc-utils/logger.c
new file mode 100644
index 0000000..8174d55
--- /dev/null
+++ b/misc-utils/logger.c
@@ -0,0 +1,1324 @@
+/*
+ * Copyright (c) 1983, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * 1999-02-22 Arkadiusz Miƛkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ * Sun Mar 21 1999 - Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ * - fixed strerr(errno) in gettext calls
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <getopt.h>
+#include <pwd.h>
+#include <signal.h>
+#include <sys/uio.h>
+
+#include "all-io.h"
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "strv.h"
+#include "list.h"
+#include "pwdutils.h"
+
+#define SYSLOG_NAMES
+#include <syslog.h>
+
+#ifdef HAVE_LIBSYSTEMD
+# define SD_JOURNAL_SUPPRESS_LOCATION
+# include <systemd/sd-daemon.h>
+# include <systemd/sd-journal.h>
+#endif
+
+#ifdef HAVE_SYS_TIMEX_H
+# include <sys/timex.h>
+#endif
+
+enum {
+ TYPE_UDP = (1 << 1),
+ TYPE_TCP = (1 << 2),
+ ALL_TYPES = TYPE_UDP | TYPE_TCP
+};
+
+enum {
+ AF_UNIX_ERRORS_OFF = 0,
+ AF_UNIX_ERRORS_ON,
+ AF_UNIX_ERRORS_AUTO
+};
+
+enum {
+ OPT_PRIO_PREFIX = CHAR_MAX + 1,
+ OPT_JOURNALD,
+ OPT_RFC3164,
+ OPT_RFC5424,
+ OPT_SOCKET_ERRORS,
+ OPT_MSGID,
+ OPT_NOACT,
+ OPT_ID,
+ OPT_STRUCTURED_DATA_ID,
+ OPT_STRUCTURED_DATA_PARAM,
+ OPT_OCTET_COUNT
+};
+
+/* rfc5424 structured data */
+struct structured_data {
+ char *id; /* SD-ID */
+ char **params; /* array with SD-PARAMs */
+
+ struct list_head sds;
+};
+
+struct logger_ctl {
+ int fd;
+ int pri;
+ pid_t pid; /* zero when unwanted */
+ char *hdr; /* the syslog header (based on protocol) */
+ char const *tag;
+ char *login;
+ char *msgid;
+ char *unix_socket; /* -u <path> or default to _PATH_DEVLOG */
+ char *server;
+ char *port;
+ int socket_type;
+ size_t max_message_size;
+ struct list_head user_sds; /* user defined rfc5424 structured data */
+ struct list_head reserved_sds; /* standard rfc5424 structured data */
+
+ void (*syslogfp)(struct logger_ctl *ctl);
+
+ unsigned int
+ unix_socket_errors:1, /* whether to report or not errors */
+ noact:1, /* do not write to sockets */
+ prio_prefix:1, /* read priority from input */
+ stderr_printout:1, /* output message to stderr */
+ rfc5424_time:1, /* include time stamp */
+ rfc5424_tq:1, /* include time quality markup */
+ rfc5424_host:1, /* include hostname */
+ skip_empty_lines:1, /* do not send empty lines when processing files */
+ octet_count:1; /* use RFC6587 octet counting */
+};
+
+#define is_connected(_ctl) ((_ctl)->fd >= 0)
+static void logger_reopen(struct logger_ctl *ctl);
+
+/*
+ * For tests we want to be able to control datetime outputs
+ */
+#ifdef TEST_LOGGER
+static inline int logger_gettimeofday(struct timeval *tv, struct timezone *tz)
+{
+ char *str = getenv("LOGGER_TEST_TIMEOFDAY");
+ uintmax_t sec, usec;
+
+ if (str && sscanf(str, "%ju.%ju", &sec, &usec) == 2) {
+ tv->tv_sec = sec;
+ tv->tv_usec = usec;
+ return tv->tv_sec >= 0 && tv->tv_usec >= 0 ? 0 : -EINVAL;
+ }
+
+ return gettimeofday(tv, tz);
+}
+
+static inline char *logger_xgethostname(void)
+{
+ char *str = getenv("LOGGER_TEST_HOSTNAME");
+ return str ? xstrdup(str) : xgethostname();
+}
+
+static inline pid_t logger_getpid(void)
+{
+ char *str = getenv("LOGGER_TEST_GETPID");
+ unsigned int pid;
+
+ if (str && sscanf(str, "%u", &pid) == 1)
+ return pid;
+ return getpid();
+}
+
+
+#undef HAVE_NTP_GETTIME /* force to default non-NTP */
+
+#else /* !TEST_LOGGER */
+# define logger_gettimeofday(x, y) gettimeofday(x, y)
+# define logger_xgethostname xgethostname
+# define logger_getpid getpid
+#endif
+
+
+static int decode(const char *name, const CODE *codetab)
+{
+ register const CODE *c;
+
+ if (name == NULL || *name == '\0')
+ return -1;
+ if (isdigit(*name)) {
+ int num;
+ char *end = NULL;
+
+ errno = 0;
+ num = strtol(name, &end, 10);
+ if (errno || name == end || (end && *end))
+ return -1;
+ for (c = codetab; c->c_name; c++)
+ if (num == c->c_val)
+ return num;
+ return -1;
+ }
+ for (c = codetab; c->c_name; c++)
+ if (!strcasecmp(name, c->c_name))
+ return (c->c_val);
+
+ return -1;
+}
+
+static int pencode(char *s)
+{
+ int facility, level;
+ char *separator;
+
+ assert(s);
+
+ separator = strchr(s, '.');
+ if (separator) {
+ *separator = '\0';
+ facility = decode(s, facilitynames);
+ if (facility < 0)
+ errx(EXIT_FAILURE, _("unknown facility name: %s"), s);
+ s = ++separator;
+ } else
+ facility = LOG_USER;
+ level = decode(s, prioritynames);
+ if (level < 0)
+ errx(EXIT_FAILURE, _("unknown priority name: %s"), s);
+ if (facility == LOG_KERN)
+ facility = LOG_USER; /* kern is forbidden */
+ return ((level & LOG_PRIMASK) | (facility & LOG_FACMASK));
+}
+
+static int unix_socket(struct logger_ctl *ctl, const char *path, int *socket_type)
+{
+ int fd = -1, i, type = -1;
+ static struct sockaddr_un s_addr; /* AF_UNIX address of local logger */
+
+ if (strlen(path) >= sizeof(s_addr.sun_path))
+ errx(EXIT_FAILURE, _("openlog %s: pathname too long"), path);
+
+ s_addr.sun_family = AF_UNIX;
+ strcpy(s_addr.sun_path, path);
+
+ for (i = 2; i; i--) {
+ int st = -1;
+
+ if (i == 2 && *socket_type & TYPE_UDP) {
+ st = SOCK_DGRAM;
+ type = TYPE_UDP;
+ }
+ if (i == 1 && *socket_type & TYPE_TCP) {
+ st = SOCK_STREAM;
+ type = TYPE_TCP;
+ }
+ if (st == -1 || (fd = socket(AF_UNIX, st, 0)) == -1)
+ continue;
+ if (connect(fd, (struct sockaddr *)&s_addr, sizeof(s_addr)) == -1) {
+ close(fd);
+ continue;
+ }
+ break;
+ }
+
+ if (i == 0) {
+ if (ctl->unix_socket_errors)
+ err(EXIT_FAILURE, _("socket %s"), path);
+
+ /* write_output() will try to reconnect */
+ return -1;
+ }
+
+ /* replace ALL_TYPES with the real TYPE_* */
+ if (type > 0 && type != *socket_type)
+ *socket_type = type;
+ return fd;
+}
+
+static int inet_socket(const char *servername, const char *port, int *socket_type)
+{
+ int fd, errcode, i, type = -1;
+ struct addrinfo hints, *res;
+ const char *p = port;
+
+ for (i = 2; i; i--) {
+ memset(&hints, 0, sizeof(hints));
+ if (i == 2 && *socket_type & TYPE_UDP) {
+ hints.ai_socktype = SOCK_DGRAM;
+ type = TYPE_UDP;
+ if (port == NULL)
+ p = "syslog";
+ }
+ if (i == 1 && *socket_type & TYPE_TCP) {
+ hints.ai_socktype = SOCK_STREAM;
+ type = TYPE_TCP;
+ if (port == NULL)
+ p = "syslog-conn";
+ }
+ if (hints.ai_socktype == 0)
+ continue;
+ hints.ai_family = AF_UNSPEC;
+ errcode = getaddrinfo(servername, p, &hints, &res);
+ if (errcode != 0)
+ errx(EXIT_FAILURE, _("failed to resolve name %s port %s: %s"),
+ servername, p, gai_strerror(errcode));
+ if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
+ freeaddrinfo(res);
+ continue;
+ }
+ if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) {
+ freeaddrinfo(res);
+ close(fd);
+ continue;
+ }
+
+ freeaddrinfo(res);
+ break;
+ }
+
+ if (i == 0)
+ errx(EXIT_FAILURE, _("failed to connect to %s port %s"), servername, p);
+
+ /* replace ALL_TYPES with the real TYPE_* */
+ if (type > 0 && type != *socket_type)
+ *socket_type = type;
+ return fd;
+}
+
+#ifdef HAVE_LIBSYSTEMD
+static int journald_entry(struct logger_ctl *ctl, FILE *fp)
+{
+ struct iovec *iovec;
+ char *buf = NULL;
+ ssize_t sz;
+ int n, lines = 0, vectors = 8, ret = 0, msgline = -1;
+ size_t dummy = 0;
+
+ iovec = xmalloc(vectors * sizeof(struct iovec));
+ while (1) {
+ buf = NULL;
+ sz = getline(&buf, &dummy, fp);
+ if (sz == -1 ||
+ (sz = rtrim_whitespace((unsigned char *) buf)) == 0) {
+ free(buf);
+ break;
+ }
+
+ if (strncmp(buf, "MESSAGE=", 8) == 0) {
+ if (msgline == -1)
+ msgline = lines; /* remember the first message */
+ else {
+ char *p = xrealloc(iovec[msgline].iov_base,
+ iovec[msgline].iov_len + sz - 8 + 2);
+
+ iovec[msgline].iov_base = p;
+ p += iovec[msgline].iov_len;
+ *p++ = '\n';
+ memcpy(p, buf + 8, sz - 8);
+ free(buf);
+
+ iovec[msgline].iov_len += sz - 8 + 1;
+ continue;
+ }
+ }
+
+ if (lines == vectors) {
+ vectors *= 2;
+ if (IOV_MAX < vectors)
+ errx(EXIT_FAILURE, _("maximum input lines (%d) exceeded"), IOV_MAX);
+ iovec = xrealloc(iovec, vectors * sizeof(struct iovec));
+ }
+ iovec[lines].iov_base = buf;
+ iovec[lines].iov_len = sz;
+ ++lines;
+ }
+
+ if (!ctl->noact)
+ ret = sd_journal_sendv(iovec, lines);
+ if (ctl->stderr_printout) {
+ for (n = 0; n < lines; n++)
+ fprintf(stderr, "%s\n", (char *) iovec[n].iov_base);
+ }
+ for (n = 0; n < lines; n++)
+ free(iovec[n].iov_base);
+ free(iovec);
+ return ret;
+}
+#endif
+
+/* this creates a timestamp based on current time according to the
+ * fine rules of RFC3164, most importantly it ensures in a portable
+ * way that the month day is correctly written (with a SP instead
+ * of a leading 0). The function uses a static buffer which is
+ * overwritten on the next call (just like ctime() does).
+ */
+static char const *rfc3164_current_time(void)
+{
+ static char time[32];
+ struct timeval tv;
+ struct tm tm;
+ static char const * const monthnames[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
+ "Sep", "Oct", "Nov", "Dec"
+ };
+
+ logger_gettimeofday(&tv, NULL);
+ localtime_r(&tv.tv_sec, &tm);
+ snprintf(time, sizeof(time),"%s %2d %2.2d:%2.2d:%2.2d",
+ monthnames[tm.tm_mon], tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ return time;
+}
+
+#define next_iovec(ary, idx) __extension__ ({ \
+ assert(ARRAY_SIZE(ary) > (size_t)idx); \
+ assert(idx >= 0); \
+ &ary[idx++]; \
+})
+
+#define iovec_add_string(ary, idx, str, len) \
+ do { \
+ struct iovec *v = next_iovec(ary, idx); \
+ v->iov_base = (void *) str; \
+ v->iov_len = len ? len : strlen(str); \
+ } while (0)
+
+#define iovec_memcmp(ary, idx, str, len) \
+ memcmp((ary)[(idx) - 1].iov_base, str, len)
+
+/* writes generated buffer to desired destination. For TCP syslog,
+ * we use RFC6587 octet-stuffing (unless octet-counting is selected).
+ * This is not great, but doing full blown RFC5425 (TLS) looks like
+ * it is too much for the logger utility. If octet-counting is
+ * selected, we use that.
+ */
+static void write_output(struct logger_ctl *ctl, const char *const msg)
+{
+ struct iovec iov[4];
+ int iovlen = 0;
+ char *octet = NULL;
+
+ /* initial connect failed? */
+ if (!ctl->noact && !is_connected(ctl))
+ logger_reopen(ctl);
+
+ /* 1) octen count */
+ if (ctl->octet_count) {
+ size_t len = xasprintf(&octet, "%zu ", strlen(ctl->hdr) + strlen(msg));
+ iovec_add_string(iov, iovlen, octet, len);
+ }
+
+ /* 2) header */
+ iovec_add_string(iov, iovlen, ctl->hdr, 0);
+
+ /* 3) message */
+ iovec_add_string(iov, iovlen, msg, 0);
+
+ if (!ctl->noact && is_connected(ctl)) {
+ struct msghdr message = { 0 };
+#ifdef SCM_CREDENTIALS
+ struct cmsghdr *cmhp;
+ struct ucred *cred;
+ union {
+ struct cmsghdr cmh;
+ char control[CMSG_SPACE(sizeof(struct ucred))];
+ } cbuf = { .control = { 0 } };
+#endif
+
+ /* 4) add extra \n to make sure message is terminated */
+ if ((ctl->socket_type == TYPE_TCP) && !ctl->octet_count)
+ iovec_add_string(iov, iovlen, "\n", 1);
+
+ message.msg_iov = iov;
+ message.msg_iovlen = iovlen;
+
+#ifdef SCM_CREDENTIALS
+ /* syslog/journald may follow local socket credentials rather
+ * than in the message PID. If we use --id as root than we can
+ * force kernel to accept another valid PID than the real logger(1)
+ * PID.
+ */
+ if (ctl->pid && !ctl->server && ctl->pid != getpid()
+ && geteuid() == 0 && kill(ctl->pid, 0) == 0) {
+
+ message.msg_control = cbuf.control;
+ message.msg_controllen = CMSG_SPACE(sizeof(struct ucred));
+
+ cmhp = CMSG_FIRSTHDR(&message);
+ cmhp->cmsg_len = CMSG_LEN(sizeof(struct ucred));
+ cmhp->cmsg_level = SOL_SOCKET;
+ cmhp->cmsg_type = SCM_CREDENTIALS;
+ cred = (struct ucred *) CMSG_DATA(cmhp);
+
+ cred->pid = ctl->pid;
+ }
+#endif
+ /* Note that logger(1) maybe executed for long time (as pipe
+ * reader) and connection endpoint (syslogd) may be restarted.
+ *
+ * The libc syslog() function reconnects on failed send().
+ * Let's do the same to be robust. [kzak -- Oct 2017]
+ *
+ * MSG_NOSIGNAL is POSIX.1-2008 compatible, but it for example
+ * not supported by apple-darwin15.6.0.
+ */
+#ifndef MSG_NOSIGNAL
+# define MSG_NOSIGNAL 0
+#endif
+ if (sendmsg(ctl->fd, &message, MSG_NOSIGNAL) < 0) {
+ logger_reopen(ctl);
+ if (sendmsg(ctl->fd, &message, MSG_NOSIGNAL) < 0)
+ warn(_("send message failed"));
+ }
+ }
+
+ if (ctl->stderr_printout) {
+ /* make sure it's terminated for stderr */
+ if (iovec_memcmp(iov, iovlen, "\n", 1) != 0)
+ iovec_add_string(iov, iovlen, "\n", 1);
+
+ ignore_result( writev(STDERR_FILENO, iov, iovlen) );
+ }
+
+ free(octet);
+}
+
+#define NILVALUE "-"
+static void syslog_rfc3164_header(struct logger_ctl *const ctl)
+{
+ char pid[30], *hostname;
+
+ *pid = '\0';
+ if (ctl->pid)
+ snprintf(pid, sizeof(pid), "[%d]", ctl->pid);
+
+ if ((hostname = logger_xgethostname())) {
+ char *dot = strchr(hostname, '.');
+ if (dot)
+ *dot = '\0';
+ } else
+ hostname = xstrdup(NILVALUE);
+
+ xasprintf(&ctl->hdr, "<%d>%.15s %s %.200s%s: ",
+ ctl->pri, rfc3164_current_time(), hostname, ctl->tag, pid);
+
+ free(hostname);
+}
+
+static inline struct list_head *get_user_structured_data(struct logger_ctl *ctl)
+{
+ return &ctl->user_sds;
+}
+
+static inline struct list_head *get_reserved_structured_data(struct logger_ctl *ctl)
+{
+ return &ctl->reserved_sds;
+}
+
+static int has_structured_data_id(struct list_head *ls, const char *id)
+{
+ struct list_head *p;
+
+ if (!ls || list_empty(ls))
+ return 0;
+
+ list_for_each(p, ls) {
+ struct structured_data *sd = list_entry(p, struct structured_data, sds);
+ if (sd->id && strcmp(sd->id, id) == 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+static void add_structured_data_id(struct list_head *ls, const char *id)
+{
+ struct structured_data *sd;
+
+ assert(id);
+
+ if (has_structured_data_id(ls, id))
+ errx(EXIT_FAILURE, _("structured data ID '%s' is not unique"), id);
+
+ sd = xcalloc(1, sizeof(*sd));
+ INIT_LIST_HEAD(&sd->sds);
+ sd->id = xstrdup(id);
+
+ list_add_tail(&sd->sds, ls);
+}
+
+static void add_structured_data_param(struct list_head *ls, const char *param)
+{
+ struct structured_data *sd;
+
+ if (list_empty(ls))
+ errx(EXIT_FAILURE, _("--sd-id was not specified for --sd-param %s"), param);
+
+ assert(param);
+
+ sd = list_last_entry(ls, struct structured_data, sds);
+
+ if (strv_extend(&sd->params, param))
+ err_oom();
+}
+
+static void __attribute__ ((__format__ (__printf__, 2, 3)))
+ add_structured_data_paramf(struct list_head *ls, const char *fmt, ...)
+{
+ struct structured_data *sd;
+ va_list ap;
+ int x;
+
+ assert(!list_empty(ls));
+ assert(fmt);
+
+ sd = list_last_entry(ls, struct structured_data, sds);
+ va_start(ap, fmt);
+ x = strv_extendv(&sd->params, fmt, ap);
+ va_end(ap);
+
+ if (x)
+ err_oom();
+}
+
+static char *strdup_structured_data(struct structured_data *sd)
+{
+ char *res, *tmp;
+
+ if (strv_isempty(sd->params))
+ return NULL;
+
+ xasprintf(&res, "[%s %s]", sd->id,
+ (tmp = strv_join(sd->params, " ")));
+ free(tmp);
+ return res;
+}
+
+static char *strdup_structured_data_list(struct list_head *ls)
+{
+ struct list_head *p;
+ char *res = NULL;
+
+ list_for_each(p, ls) {
+ struct structured_data *sd = list_entry(p, struct structured_data, sds);
+ char *one = strdup_structured_data(sd);
+ char *tmp = res;
+
+ if (!one)
+ continue;
+ res = strconcat(tmp, one);
+ free(tmp);
+ free(one);
+ }
+
+ return res;
+}
+
+static char *get_structured_data_string(struct logger_ctl *ctl)
+{
+ char *sys = NULL, *usr = NULL, *res;
+
+ if (!list_empty(&ctl->reserved_sds))
+ sys = strdup_structured_data_list(&ctl->reserved_sds);
+ if (!list_empty(&ctl->user_sds))
+ usr = strdup_structured_data_list(&ctl->user_sds);
+
+ if (sys && usr) {
+ res = strconcat(sys, usr);
+ free(sys);
+ free(usr);
+ } else
+ res = sys ? sys : usr;
+
+ return res;
+}
+
+static int valid_structured_data_param(const char *str)
+{
+ char *s;
+ char *eq = strchr(str, '='),
+ *qm1 = strchr(str, '"'),
+ *qm2 = qm1 ? ul_strchr_escaped(qm1 + 1, '"') : NULL;
+
+ /* something is missing */
+ if (!eq || !qm1 || !qm2)
+ return 0;
+
+ /* ']' need to be escaped */
+ for (s = qm1 + 1; s && *s; ) {
+ char *p = strchr(s, ']');
+ if (!p)
+ break;
+ if (p > qm2 || p == ul_strchr_escaped(s, ']'))
+ return 0;
+ s = p + 1;
+ }
+
+ /* '\' is allowed only before '[]"\' chars */
+ for (s = qm1 + 1; s && *s; ) {
+ char *p = strchr(s, '\\');
+ if (!p)
+ break;
+ if (!strchr("[]\"\\", *(p + 1)))
+ return 0;
+ s = p + 1;
+ if (*s == '\\')
+ s++;
+ }
+
+ /* foo="bar" */
+ return eq > str && eq < qm1 && eq + 1 == qm1 && qm1 < qm2 && *(qm2 + 1) == '\0';
+}
+
+/* SD-ID format:
+ * name@<private enterprise number>, e.g., "ourSDID@32473"
+ */
+static int valid_structured_data_id(const char *str)
+{
+ char *at = strchr(str, '@');
+ const char *p;
+
+ /* standardized IDs without @<digits> */
+ if (!at && (strcmp(str, "timeQuality") == 0 ||
+ strcmp(str, "origin") == 0 ||
+ strcmp(str, "meta") == 0))
+ return 1;
+
+ if (!at || at == str || !*(at + 1))
+ return 0;
+
+ /* <digits> or <digits>.<digits>[...] */
+ for (p = at + 1; p && *p; p++) {
+ const char *end;
+
+ if (isdigit_strend(p, &end))
+ break; /* only digits in the string */
+
+ if (end == NULL || end == p ||
+ *end != '.' || *(end + 1) == '\0')
+ return 0;
+ p = end;
+ }
+
+ /* check for forbidden chars in the <name> */
+ for (p = str; p < at; p++) {
+ if (*p == '[' || *p == '=' || *p == '"' || *p == '@')
+ return 0;
+ if (isblank((unsigned char) *p) || iscntrl((unsigned char) *p))
+ return 0;
+ }
+ return 1;
+}
+
+
+/* Some field mappings may be controversial, thus I give the reason
+ * why this specific mapping was used:
+ * APP-NAME <-- tag
+ * Some may argue that "logger" is a better fit, but we think
+ * this is better inline of what other implementations do. In
+ * rsyslog, for example, the TAG value is populated from APP-NAME.
+ * PROCID <-- pid
+ * This is a relatively straightforward interpretation from
+ * RFC5424, sect. 6.2.6.
+ * MSGID <-- msgid (from --msgid)
+ * One may argue that the string "logger" would be better suited
+ * here so that a receiver can identify the sender process.
+ * However, this does not sound like a good match to RFC5424,
+ * sect. 6.2.7.
+ * Note that appendix A.1 of RFC5424 does not provide clear guidance
+ * of how these fields should be used. This is the case because the
+ * IETF working group couldn't arrive at a clear agreement when we
+ * specified RFC5424. The rest of the field mappings should be
+ * pretty clear from RFC5424. -- Rainer Gerhards, 2015-03-10
+ */
+static void syslog_rfc5424_header(struct logger_ctl *const ctl)
+{
+ char *time;
+ char *hostname;
+ char const *app_name = ctl->tag;
+ char *procid;
+ char *const msgid = xstrdup(ctl->msgid ? ctl->msgid : NILVALUE);
+ char *structured = NULL;
+ struct list_head *sd;
+
+ if (ctl->rfc5424_time) {
+ struct timeval tv;
+ struct tm tm;
+
+ logger_gettimeofday(&tv, NULL);
+ if (localtime_r(&tv.tv_sec, &tm) != NULL) {
+ char fmt[64];
+ const size_t i = strftime(fmt, sizeof(fmt),
+ "%Y-%m-%dT%H:%M:%S.%%06u%z ", &tm);
+ /* patch TZ info to comply with RFC3339 (we left SP at end) */
+ fmt[i - 1] = fmt[i - 2];
+ fmt[i - 2] = fmt[i - 3];
+ fmt[i - 3] = ':';
+ xasprintf(&time, fmt, tv.tv_usec);
+ } else
+ err(EXIT_FAILURE, _("localtime() failed"));
+ } else
+ time = xstrdup(NILVALUE);
+
+ if (ctl->rfc5424_host) {
+ if (!(hostname = logger_xgethostname()))
+ hostname = xstrdup(NILVALUE);
+ /* Arbitrary looking 'if (var < strlen()) checks originate from
+ * RFC 5424 - 6 Syslog Message Format definition. */
+ if (255 < strlen(hostname))
+ errx(EXIT_FAILURE, _("hostname '%s' is too long"),
+ hostname);
+ } else
+ hostname = xstrdup(NILVALUE);
+
+ if (48 < strlen(ctl->tag))
+ errx(EXIT_FAILURE, _("tag '%s' is too long"), ctl->tag);
+
+ if (ctl->pid)
+ xasprintf(&procid, "%d", ctl->pid);
+ else
+ procid = xstrdup(NILVALUE);
+
+ sd = get_reserved_structured_data(ctl);
+
+ /* time quality structured data (maybe overwritten by --sd-id timeQuality) */
+ if (ctl->rfc5424_tq && !has_structured_data_id(sd, "timeQuality")) {
+
+ add_structured_data_id(sd, "timeQuality");
+ add_structured_data_param(sd, "tzKnown=\"1\"");
+
+#ifdef HAVE_NTP_GETTIME
+ struct ntptimeval ntptv;
+
+ if (ntp_gettime(&ntptv) == TIME_OK) {
+ add_structured_data_param(sd, "isSynced=\"1\"");
+ add_structured_data_paramf(sd, "syncAccuracy=\"%ld\"", ntptv.maxerror);
+ } else
+#endif
+ add_structured_data_paramf(sd, "isSynced=\"0\"");
+ }
+
+ /* convert all structured data to string */
+ structured = get_structured_data_string(ctl);
+ if (!structured)
+ structured = xstrdup(NILVALUE);
+
+ xasprintf(&ctl->hdr, "<%d>1 %s %s %s %s %s %s ",
+ ctl->pri,
+ time,
+ hostname,
+ app_name,
+ procid,
+ msgid,
+ structured);
+
+ free(time);
+ free(hostname);
+ /* app_name points to ctl->tag, do NOT free! */
+ free(procid);
+ free(msgid);
+ free(structured);
+}
+
+static void parse_rfc5424_flags(struct logger_ctl *ctl, char *s)
+{
+ char *in, *tok;
+
+ in = s;
+ while ((tok = strtok(in, ","))) {
+ in = NULL;
+ if (!strcmp(tok, "notime")) {
+ ctl->rfc5424_time = 0;
+ ctl->rfc5424_tq = 0;
+ } else if (!strcmp(tok, "notq"))
+ ctl->rfc5424_tq = 0;
+ else if (!strcmp(tok, "nohost"))
+ ctl->rfc5424_host = 0;
+ else
+ warnx(_("ignoring unknown option argument: %s"), tok);
+ }
+}
+
+static int parse_unix_socket_errors_flags(char *s)
+{
+ if (!strcmp(s, "off"))
+ return AF_UNIX_ERRORS_OFF;
+ if (!strcmp(s, "on"))
+ return AF_UNIX_ERRORS_ON;
+ if (!strcmp(s, "auto"))
+ return AF_UNIX_ERRORS_AUTO;
+ warnx(_("invalid argument: %s: using automatic errors"), s);
+ return AF_UNIX_ERRORS_AUTO;
+}
+
+static void syslog_local_header(struct logger_ctl *const ctl)
+{
+ char pid[32];
+
+ if (ctl->pid)
+ snprintf(pid, sizeof(pid), "[%d]", ctl->pid);
+ else
+ pid[0] = '\0';
+
+ xasprintf(&ctl->hdr, "<%d>%s %s%s: ", ctl->pri, rfc3164_current_time(),
+ ctl->tag, pid);
+}
+
+static void generate_syslog_header(struct logger_ctl *const ctl)
+{
+ free(ctl->hdr);
+ ctl->hdr = NULL;
+ ctl->syslogfp(ctl);
+}
+
+/* just open, nothing else */
+static void __logger_open(struct logger_ctl *ctl)
+{
+ if (ctl->server) {
+ ctl->fd = inet_socket(ctl->server, ctl->port, &ctl->socket_type);
+ } else {
+ if (!ctl->unix_socket)
+ ctl->unix_socket = _PATH_DEVLOG;
+
+ ctl->fd = unix_socket(ctl, ctl->unix_socket, &ctl->socket_type);
+ }
+}
+
+/* open and initialize relevant @ctl tuff */
+static void logger_open(struct logger_ctl *ctl)
+{
+ __logger_open(ctl);
+
+ if (!ctl->syslogfp)
+ ctl->syslogfp = ctl->server ? syslog_rfc5424_header :
+ syslog_local_header;
+ if (!ctl->tag)
+ ctl->tag = ctl->login = xgetlogin();
+ if (!ctl->tag)
+ ctl->tag = "<someone>";
+}
+
+/* re-open; usually after failed connection */
+static void logger_reopen(struct logger_ctl *ctl)
+{
+ if (ctl->fd != -1)
+ close(ctl->fd);
+ ctl->fd = -1;
+
+ __logger_open(ctl);
+}
+
+static void logger_command_line(struct logger_ctl *ctl, char **argv)
+{
+ /* note: we never re-generate the syslog header here, even if we
+ * generate multiple messages. If so, we think it is the right thing
+ * to do to report them with the same timestamp, as the user actually
+ * intended to send a single message.
+ */
+ char *const buf = xmalloc(ctl->max_message_size + 1);
+ char *p = buf;
+ const char *endp = buf + ctl->max_message_size - 1;
+ size_t len;
+
+ while (*argv) {
+ len = strlen(*argv);
+ if (endp < p + len && p != buf) {
+ write_output(ctl, buf);
+ p = buf;
+ }
+ if (ctl->max_message_size < len) {
+ (*argv)[ctl->max_message_size] = '\0'; /* truncate */
+ write_output(ctl, *argv++);
+ continue;
+ }
+ if (p != buf)
+ *p++ = ' ';
+ memmove(p, *argv++, len);
+ *(p += len) = '\0';
+ }
+ if (p != buf)
+ write_output(ctl, buf);
+ free(buf);
+}
+
+static void logger_stdin(struct logger_ctl *ctl)
+{
+ /* note: we re-generate the syslog header for each log message to
+ * update header timestamps and to reflect possible priority changes.
+ */
+ int default_priority = ctl->pri;
+ char *buf = xmalloc(ctl->max_message_size + 2 + 2);
+ int pri;
+ int c;
+ size_t i;
+
+ c = getchar();
+ while (c != EOF) {
+ i = 0;
+ if (ctl->prio_prefix && c == '<') {
+ pri = 0;
+ buf[i++] = c;
+ while (isdigit(c = getchar()) && pri <= 191) {
+ buf[i++] = c;
+ pri = pri * 10 + c - '0';
+ }
+ if (c != EOF && c != '\n')
+ buf[i++] = c;
+ if (c == '>' && 0 <= pri && pri <= 191) {
+ /* valid RFC PRI values */
+ i = 0;
+ if ((pri & LOG_FACMASK) == 0)
+ pri |= (default_priority & LOG_FACMASK);
+ ctl->pri = pri;
+ } else
+ ctl->pri = default_priority;
+
+ if (c != EOF && c != '\n')
+ c = getchar();
+ }
+
+ while (c != EOF && c != '\n' && i < ctl->max_message_size) {
+ buf[i++] = c;
+ c = getchar();
+ }
+ buf[i] = '\0';
+
+ if (i > 0 || !ctl->skip_empty_lines) {
+ generate_syslog_header(ctl);
+ write_output(ctl, buf);
+ }
+
+ if (c == '\n') /* discard line terminator */
+ c = getchar();
+ }
+
+ free(buf);
+}
+
+static void logger_close(const struct logger_ctl *ctl)
+{
+ if (ctl->fd != -1 && close(ctl->fd) != 0)
+ err(EXIT_FAILURE, _("close failed"));
+ free(ctl->hdr);
+ free(ctl->login);
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [<message>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Enter messages into the system log.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -i log the logger command's PID\n"), out);
+ fputs(_(" --id[=<id>] log the given <id>, or otherwise the PID\n"), out);
+ fputs(_(" -f, --file <file> log the contents of this file\n"), out);
+ fputs(_(" -e, --skip-empty do not log empty lines when processing files\n"), out);
+ fputs(_(" --no-act do everything except the write the log\n"), out);
+ fputs(_(" -p, --priority <prio> mark given message with this priority\n"), out);
+ fputs(_(" --octet-count use rfc6587 octet counting\n"), out);
+ fputs(_(" --prio-prefix look for a prefix on every line read from stdin\n"), out);
+ fputs(_(" -s, --stderr output message to standard error as well\n"), out);
+ fputs(_(" -S, --size <size> maximum size for a single message\n"), out);
+ fputs(_(" -t, --tag <tag> mark every line with this tag\n"), out);
+ fputs(_(" -n, --server <name> write to this remote syslog server\n"), out);
+ fputs(_(" -P, --port <port> use this port for UDP or TCP connection\n"), out);
+ fputs(_(" -T, --tcp use TCP only\n"), out);
+ fputs(_(" -d, --udp use UDP only\n"), out);
+ fputs(_(" --rfc3164 use the obsolete BSD syslog protocol\n"), out);
+ fputs(_(" --rfc5424[=<snip>] use the syslog protocol (the default for remote);\n"
+ " <snip> can be notime, or notq, and/or nohost\n"), out);
+ fputs(_(" --sd-id <id> rfc5424 structured data ID\n"), out);
+ fputs(_(" --sd-param <data> rfc5424 structured data name=value\n"), out);
+ fputs(_(" --msgid <msgid> set rfc5424 message id field\n"), out);
+ fputs(_(" -u, --socket <socket> write to this Unix socket\n"), out);
+ fputs(_(" --socket-errors[=<on|off|auto>]\n"
+ " print connection errors when using Unix sockets\n"), out);
+#ifdef HAVE_LIBSYSTEMD
+ fputs(_(" --journald[=<file>] write journald entry\n"), out);
+#endif
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(26));
+ printf(USAGE_MAN_TAIL("logger(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * logger -- read and log utility
+ *
+ * Reads from an input and arranges to write the result on the system
+ * log.
+ */
+int main(int argc, char **argv)
+{
+ struct logger_ctl ctl = {
+ .fd = -1,
+ .pid = 0,
+ .pri = LOG_USER | LOG_NOTICE,
+ .prio_prefix = 0,
+ .tag = NULL,
+ .unix_socket = NULL,
+ .unix_socket_errors = 0,
+ .server = NULL,
+ .port = NULL,
+ .hdr = NULL,
+ .msgid = NULL,
+ .socket_type = ALL_TYPES,
+ .max_message_size = 1024,
+ .rfc5424_time = 1,
+ .rfc5424_tq = 1,
+ .rfc5424_host = 1,
+ .skip_empty_lines = 0
+ };
+ int ch;
+ int stdout_reopened = 0;
+ int unix_socket_errors_mode = AF_UNIX_ERRORS_AUTO;
+#ifdef HAVE_LIBSYSTEMD
+ FILE *jfd = NULL;
+#endif
+ static const struct option longopts[] = {
+ { "id", optional_argument, 0, OPT_ID },
+ { "stderr", no_argument, 0, 's' },
+ { "file", required_argument, 0, 'f' },
+ { "no-act", no_argument, 0, OPT_NOACT, },
+ { "priority", required_argument, 0, 'p' },
+ { "tag", required_argument, 0, 't' },
+ { "socket", required_argument, 0, 'u' },
+ { "socket-errors", required_argument, 0, OPT_SOCKET_ERRORS },
+ { "udp", no_argument, 0, 'd' },
+ { "tcp", no_argument, 0, 'T' },
+ { "server", required_argument, 0, 'n' },
+ { "port", required_argument, 0, 'P' },
+ { "version", no_argument, 0, 'V' },
+ { "help", no_argument, 0, 'h' },
+ { "octet-count", no_argument, 0, OPT_OCTET_COUNT },
+ { "prio-prefix", no_argument, 0, OPT_PRIO_PREFIX },
+ { "rfc3164", no_argument, 0, OPT_RFC3164 },
+ { "rfc5424", optional_argument, 0, OPT_RFC5424 },
+ { "size", required_argument, 0, 'S' },
+ { "msgid", required_argument, 0, OPT_MSGID },
+ { "skip-empty", no_argument, 0, 'e' },
+ { "sd-id", required_argument, 0, OPT_STRUCTURED_DATA_ID },
+ { "sd-param", required_argument, 0, OPT_STRUCTURED_DATA_PARAM },
+#ifdef HAVE_LIBSYSTEMD
+ { "journald", optional_argument, 0, OPT_JOURNALD },
+#endif
+ { NULL, 0, 0, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ INIT_LIST_HEAD(&ctl.user_sds);
+ INIT_LIST_HEAD(&ctl.reserved_sds);
+
+ while ((ch = getopt_long(argc, argv, "ef:ip:S:st:u:dTn:P:Vh",
+ longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'f': /* file to log */
+ if (freopen(optarg, "r", stdin) == NULL)
+ err(EXIT_FAILURE, _("file %s"), optarg);
+ stdout_reopened = 1;
+ break;
+ case 'e':
+ ctl.skip_empty_lines = 1;
+ break;
+ case 'i': /* log process id also */
+ ctl.pid = logger_getpid();
+ break;
+ case OPT_ID:
+ if (optarg) {
+ const char *p = optarg;
+
+ if (*p == '=')
+ p++;
+ ctl.pid = strtoul_or_err(optarg, _("failed to parse id"));
+ } else
+ ctl.pid = logger_getpid();
+ break;
+ case 'p': /* priority */
+ ctl.pri = pencode(optarg);
+ break;
+ case 's': /* log to standard error */
+ ctl.stderr_printout = 1;
+ break;
+ case 't': /* tag */
+ ctl.tag = optarg;
+ break;
+ case 'u': /* unix socket */
+ ctl.unix_socket = optarg;
+ break;
+ case 'S': /* max message size */
+ ctl.max_message_size = strtosize_or_err(optarg,
+ _("failed to parse message size"));
+ break;
+ case 'd':
+ ctl.socket_type = TYPE_UDP;
+ break;
+ case 'T':
+ ctl.socket_type = TYPE_TCP;
+ break;
+ case 'n':
+ ctl.server = optarg;
+ break;
+ case 'P':
+ ctl.port = optarg;
+ break;
+ case OPT_OCTET_COUNT:
+ ctl.octet_count = 1;
+ break;
+ case OPT_PRIO_PREFIX:
+ ctl.prio_prefix = 1;
+ break;
+ case OPT_RFC3164:
+ ctl.syslogfp = syslog_rfc3164_header;
+ break;
+ case OPT_RFC5424:
+ ctl.syslogfp = syslog_rfc5424_header;
+ if (optarg)
+ parse_rfc5424_flags(&ctl, optarg);
+ break;
+ case OPT_MSGID:
+ if (strchr(optarg, ' '))
+ errx(EXIT_FAILURE, _("--msgid cannot contain space"));
+ ctl.msgid = optarg;
+ break;
+#ifdef HAVE_LIBSYSTEMD
+ case OPT_JOURNALD:
+ if (optarg) {
+ jfd = fopen(optarg, "r");
+ if (!jfd)
+ err(EXIT_FAILURE, _("cannot open %s"),
+ optarg);
+ } else
+ jfd = stdin;
+ break;
+#endif
+ case OPT_SOCKET_ERRORS:
+ unix_socket_errors_mode = parse_unix_socket_errors_flags(optarg);
+ break;
+ case OPT_NOACT:
+ ctl.noact = 1;
+ break;
+ case OPT_STRUCTURED_DATA_ID:
+ if (!valid_structured_data_id(optarg))
+ errx(EXIT_FAILURE, _("invalid structured data ID: '%s'"), optarg);
+ add_structured_data_id(get_user_structured_data(&ctl), optarg);
+ break;
+ case OPT_STRUCTURED_DATA_PARAM:
+ if (!valid_structured_data_param(optarg))
+ errx(EXIT_FAILURE, _("invalid structured data parameter: '%s'"), optarg);
+ add_structured_data_param(get_user_structured_data(&ctl), optarg);
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (stdout_reopened && argc)
+ warnx(_("--file <file> and <message> are mutually exclusive, message is ignored"));
+#ifdef HAVE_LIBSYSTEMD
+ if (jfd) {
+ int ret = journald_entry(&ctl, jfd);
+ if (stdin != jfd)
+ fclose(jfd);
+ if (ret)
+ errx(EXIT_FAILURE, _("journald entry could not be written"));
+ return EXIT_SUCCESS;
+ }
+#endif
+
+ /* user overwrites built-in SD-ELEMENT */
+ if (has_structured_data_id(get_user_structured_data(&ctl), "timeQuality"))
+ ctl.rfc5424_tq = 0;
+
+ switch (unix_socket_errors_mode) {
+ case AF_UNIX_ERRORS_OFF:
+ ctl.unix_socket_errors = 0;
+ break;
+ case AF_UNIX_ERRORS_ON:
+ ctl.unix_socket_errors = 1;
+ break;
+ case AF_UNIX_ERRORS_AUTO:
+ ctl.unix_socket_errors = ctl.noact || ctl.stderr_printout;
+#ifdef HAVE_LIBSYSTEMD
+ ctl.unix_socket_errors |= !!sd_booted();
+#endif
+ break;
+ default:
+ abort();
+ }
+ logger_open(&ctl);
+ if (0 < argc) {
+ generate_syslog_header(&ctl);
+ logger_command_line(&ctl, argv);
+ } else
+ /* Note. --file <arg> reopens stdin making the below
+ * function to be used for file inputs. */
+ logger_stdin(&ctl);
+
+ logger_close(&ctl);
+ return EXIT_SUCCESS;
+}