/* * 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 * - added Native Language Support * Sun Mar 21 1999 - Arnaldo Carvalho de Melo * - fixed strerr(errno) in gettext calls */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #ifdef HAVE_LIBSYSTEMD # define SD_JOURNAL_SUPPRESS_LOCATION # include # include #endif #ifdef HAVE_SYS_TIMEX_H # include #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 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; #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@, e.g., "ourSDID@32473" */ static int valid_structured_data_id(const char *str) { char *at = strchr(str, '@'); const char *p; /* standardized IDs without @ */ if (!at && (strcmp(str, "timeQuality") == 0 || strcmp(str, "origin") == 0 || strcmp(str, "meta") == 0)) return 1; if (!at || at == str || !*(at + 1)) return 0; /* or .[...] */ 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 */ 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 = ""; generate_syslog_header(ctl); } /* 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. * The initial header is generated by logger_open(). */ int default_priority = ctl->pri; int last_pri = default_priority; 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 (ctl->pri != last_pri) { generate_syslog_header(ctl); last_pri = ctl->pri; } 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) 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] []\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[=] log the given , or otherwise the PID\n"), out); fputs(_(" -f, --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 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 maximum size for a single message\n"), out); fputs(_(" -t, --tag mark every line with this tag\n"), out); fputs(_(" -n, --server write to this remote syslog server\n"), out); fputs(_(" -P, --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[=] use the syslog protocol (the default for remote);\n" " can be notime, or notq, and/or nohost\n"), out); fputs(_(" --sd-id rfc5424 structured data ID\n"), out); fputs(_(" --sd-param rfc5424 structured data name=value\n"), out); fputs(_(" --msgid set rfc5424 message id field\n"), out); fputs(_(" -u, --socket write to this Unix socket\n"), out); fputs(_(" --socket-errors[=]\n" " print connection errors when using Unix sockets\n"), out); #ifdef HAVE_LIBSYSTEMD fputs(_(" --journald[=] 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 and 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) logger_command_line(&ctl, argv); else /* Note. --file reopens stdin making the below * function to be used for file inputs. */ logger_stdin(&ctl); logger_close(&ctl); return EXIT_SUCCESS; }