diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 19:10:49 +0000 |
commit | cfe5e3905201349e9cf3f95d52ff4bd100bde37d (patch) | |
tree | d0baf160cbee3195249d095f85e52d20c21acf02 /misc-utils/logger.c | |
parent | Initial commit. (diff) | |
download | util-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.c | 1324 |
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; +} |