| /* |
| * 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" |
| |
| #define SYSLOG_NAMES |
| #include <syslog.h> |
| |
| #ifdef HAVE_LIBSYSTEMD |
| # 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 *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; |
| |
| 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, 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, vectors = 8, ret = 0; |
| size_t dummy = 0; |
| |
| iovec = xmalloc(vectors * sizeof(struct iovec)); |
| for (lines = 0; /* nothing */ ; lines++) { |
| buf = NULL; |
| sz = getline(&buf, &dummy, fp); |
| if (sz == -1 || |
| (sz = rtrim_whitespace((unsigned char *) buf)) == 0) { |
| free(buf); |
| break; |
| } |
| 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; |
| } |
| |
| 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 |
| |
| static char const *xgetlogin(void) |
| { |
| char const *cp; |
| struct passwd *pw; |
| |
| if (!(cp = getlogin()) || !*cp) |
| cp = (pw = getpwuid(geteuid()))? pw->pw_name : "<someone>"; |
| return cp; |
| } |
| |
| /* 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); |
| tm = localtime(&tv.tv_sec); |
| 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 |
| * no suported 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 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 = strappend(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 = strappend(sys, usr); |
| free(sys); |
| free(usr); |
| } else |
| res = sys ? sys : usr; |
| |
| return res; |
| } |
| |
| static int valid_structured_data_param(const char *str) |
| { |
| char *eq = strchr(str, '='), |
| *qm1 = strchr(str, '"'), |
| *qm2 = qm1 ? strchr(qm1 + 1, '"') : NULL; |
| |
| if (!eq || !qm1 || !qm2) /* something is missing */ |
| return 0; |
| |
| /* 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 ((tm = localtime(&tv.tv_sec)) != 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 = xgetlogin(); |
| |
| 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 has_header = 1; |
| int default_priority = ctl->pri; |
| int last_pri = default_priority; |
| size_t max_usrmsg_size = ctl->max_message_size - strlen(ctl->hdr); |
| char *const buf = xmalloc(max_usrmsg_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 < 8) /* kern facility is forbidden */ |
| pri |= 8; |
| ctl->pri = pri; |
| } else |
| ctl->pri = default_priority; |
| |
| if (ctl->pri != last_pri) { |
| has_header = 0; |
| max_usrmsg_size = |
| ctl->max_message_size - strlen(ctl->hdr); |
| last_pri = ctl->pri; |
| } |
| if (c != EOF && c != '\n') |
| c = getchar(); |
| } |
| |
| while (c != EOF && c != '\n' && i < max_usrmsg_size) { |
| buf[i++] = c; |
| c = getchar(); |
| } |
| buf[i] = '\0'; |
| |
| if (i > 0 || !ctl->skip_empty_lines) { |
| if (!has_header) |
| generate_syslog_header(ctl); |
| write_output(ctl, buf); |
| has_header = 0; |
| } |
| |
| 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); |
| } |
| |
| 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); |
| atexit(close_stdout); |
| |
| 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 'V': |
| printf(UTIL_LINUX_VERSION); |
| exit(EXIT_SUCCESS); |
| case 'h': |
| usage(); |
| 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; |
| 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 build-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 <arg> reopens stdin making the below |
| * function to be used for file inputs. */ |
| logger_stdin(&ctl); |
| logger_close(&ctl); |
| return EXIT_SUCCESS; |
| } |