diff --git a/man/logger.1 b/man/logger.1 index 49614e1..f7b44a6 100644 --- a/man/logger.1 +++ b/man/logger.1 @@ -33,11 +33,15 @@ .Nd Send messages to system log, or a log file .Sh SYNOPSIS .Nm -.Op Fl chiknsv +.Op Fl 46bchiknsv .Op Fl d Ar SD .Op Fl f Ar FILE +.Op Fl h Ar HOST +.Op Fl H Ar HOSTNAME +.Op Fl I Ar PID .Op Fl m Ar MSGID .Op Fl p Ar PRIO +.Op Fl P Ar PORT .Op Fl r Ar SIZE:NUM .Op Fl t Ar TAG .Op Fl u Ar SOCK @@ -61,6 +65,16 @@ reads input from .Sh OPTIONS This program follows the usual UNIX command line syntax: .Bl -tag -width Ds +.It Fl 4 +Force +.Nm +to use IPv4 addresses only. +.It Fl 6 +Force +.Nm +to use IPv6 addresses only. +.It Fl b +Use RFC3164 (BSD) style format, default: RFC5424. .It Fl c Log to console .Ql ( LOG_CONS ) @@ -83,6 +97,25 @@ accepts .Fl f- as an alias for .Ar stdout . +.It Fl H Ar hostname +Set the hostname in the header of the message to specified value. +If not specified, host part of +.Xr gethostname 3 +will be used. +.It Fl h Ar host +Send the message to the remote system +.Ar host +instead of logging it locally. +.It Fl I Ar PID +Like +.Fl i , +but uses +.Ar PID . +Useful when logging from shell scripts that send multiple messages. +E.g., the following arguments might be a useful template: +.Bd -literal -offset indent +logger -t $(basename $0) -I $$ +.Ed .It Fl i Log the process id of the logger process with each line .Ql ( LOG_PID ) . @@ -112,6 +145,17 @@ or sending remote in correctly formatted RFC5424 style. .It Fl n Open log file immediately .Ql ( LOG_NDELAY ) . +.It Fl P Ar port +Send the message to the specified +.Ar port +number on a remote system, +which can be specified as a service name +or as a decimal number. +The default is +.Dq Li syslog . +If an unknown service name is used, +.Nm +prints a warning and falls back to port 514. .It Fl p Ar PRIO Priority, numeric or .Ar facility.severity diff --git a/src/logger.c b/src/logger.c index 5e0b449..342b8f6 100644 --- a/src/logger.c +++ b/src/logger.c @@ -35,11 +35,14 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #define SYSLOG_NAMES #include "compat.h" @@ -61,8 +64,8 @@ static int create(char *path, mode_t mode, uid_t uid, gid_t gid) */ static int logrotate(char *file, int num, off_t sz) { - int cnt; struct stat st; + int cnt; if (stat(file, &st)) return 1; @@ -122,6 +125,39 @@ static void log_kmsg(FILE *fp, char *ident, int pri, int opts, char *buf) fprintf(fp, "<%d>%s[%d]:%s\n", pri, ident, getpid(), buf); } +static int nslookup(const char *host, const char *svcname, int family, struct sockaddr *sa) +{ + struct addrinfo hints, *ai, *result; + int error; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = !host ? AI_PASSIVE : 0; + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + + error = getaddrinfo(host, svcname, &hints, &result); + if (error == EAI_SERVICE) { + warnx("%s/udp: unknown service, trying syslog port 514", svcname); + svcname = "514"; + error = getaddrinfo(host, svcname, &hints, &result); + } + if (error) { + warnx("%s (%s:%s)", gai_strerror(error), host, svcname); + return 1; + } + + for (ai = result; ai; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) + continue; + + memcpy(sa, ai->ai_addr, ai->ai_addrlen); + break; + } + freeaddrinfo(result); + + return 0; +} + static int checksz(FILE *fp, off_t sz) { struct stat st; @@ -193,16 +229,23 @@ static int usage(int code) "\n" "Write MESSAGE (or line-by-line stdin) to syslog, or file (with logrotate).\n" "\n" + " -4 Prefer IPv4 address when sending remote, see -h\n" + " -6 Prefer IPv6 address when sending remote, see -h\n" + " -b Use RFC3164 (BSD) style format, default: RFC5424\n" " -c Log to console (LOG_CONS) on failure\n" " -d SD Log SD as RFC5424 style 'structured data' in message\n" " -f FILE Log file to write messages to, instead of syslog daemon\n" + " -h HOST Send (UDP) message to this remote syslog server (IP or DNS name)\n" + " -H NAME Use NAME instead of system hostname in message header\n" " -i Log process ID of the logger process with each line (LOG_PID)\n" + " -I PID Log process ID using PID, recommed using PID $$ for shell scripts\n" #ifdef __linux__ " -k Log to kernel /dev/kmsg if /dev/log doesn't exist yet\n" #endif " -m MSGID Log message using this RFC5424 style MSGID\n" " -n Open log file immediately (LOG_NDELAY)\n" " -p PRIO Log message priority (numeric or facility.severity pair)\n" + " -P PORT Use PORT (or named UDP service) for remote server, default: syslog\n" " -r S[:R] Enable log file rotation, default: 200 kB \e[4ms\e[0mize, 5 \e[4mr\e[0motations\n" " -s Log to stderr as well as the system log\n" " -t TAG Log using the specified tag (defaults to user name)\n" @@ -220,21 +263,36 @@ static int usage(int code) int main(int argc, char *argv[]) { - FILE *fp = NULL; - int c, num = 5; + char *ident = NULL, *logfile = NULL; + char *host = NULL, *sockpath = NULL; + char *msgid = NULL, *sd = NULL; + char *svcname = "syslog"; + off_t size = 200 * 1024; int facility = LOG_USER; int severity = LOG_INFO; - int log_opts = 0; - int rotate = 0; + int family = AF_UNSPEC; + struct sockaddr sa; int allow_kmsg = 0; - off_t size = 200 * 1024; - char *ident = NULL, *logfile = NULL; - char *msgid = NULL, *sd = NULL; - char *sockpath = NULL; char buf[512] = ""; + int log_opts = 0; + FILE *fp = NULL; + int c, num = 5; + int rotate = 0; - while ((c = getopt(argc, argv, "?cd:f:ikm:np:r:st:u:v")) != EOF) { + while ((c = getopt(argc, argv, "46?bcd:f:h:H:iI:km:np:P:r:st:u:v")) != EOF) { switch (c) { + case '4': + family = AF_INET; + break; + + case '6': + family = AF_INET6; + break; + + case 'b': + log_opts |= LOG_RFC3154; + break; + case 'c': log_opts |= LOG_CONS; break; @@ -247,10 +305,23 @@ int main(int argc, char *argv[]) logfile = optarg; break; + case 'h': + host = optarg; + break; + + case 'H': + strlcpy(log.log_hostname, optarg, sizeof(log.log_hostname)); + break; + case 'i': log_opts |= LOG_PID; break; + case 'I': + log_opts |= LOG_PID; + log.log_pid = atoi(optarg); + break; + case 'k': #ifdef __linux__ allow_kmsg = 1; @@ -272,6 +343,10 @@ int main(int argc, char *argv[]) return usage(1); break; + case 'P': + svcname = optarg; + break; + case 'r': parse_rotation(optarg, &size, &num); if (size > 0 && num > 0) @@ -351,6 +426,11 @@ int main(int argc, char *argv[]) return fclose(fp); } + } else if (host) { + log.log_host = &sa; + if (nslookup(host, svcname, family, &sa)) + return 1; + log_opts |= LOG_NDELAY; } openlog_r(ident, log_opts, facility, &log); diff --git a/src/syslog.c b/src/syslog.c index 9f6f4ce..125ab3a 100644 --- a/src/syslog.c +++ b/src/syslog.c @@ -96,6 +96,20 @@ is_socket(int fd) return 1; } +/* + * Used on systems that don't have sa->sa_len + */ +#ifndef HAVE_SA_LEN +static socklen_t sa_len(struct sockaddr *sa) +{ + if (sa->sa_family == AF_INET6) + return sizeof(struct sockaddr_in6); + if (sa->sa_family == AF_INET) + return sizeof(struct sockaddr_in); + return 0; +} +#endif + /* * syslog, vsyslog -- * print message on log file; output is intended for syslogd(8). @@ -190,6 +204,8 @@ vsyslogp_r(int pri, struct syslog_data *data, const char *msgid, { static const char BRCOSP[] = "]: "; static const char CRLF[] = "\r\n"; + struct sockaddr *sa = NULL; + socklen_t len = 0; size_t cnt, prlen, tries; char ch, *p, *t; struct timeval tv; @@ -225,6 +241,17 @@ vsyslogp_r(int pri, struct syslog_data *data, const char *msgid, if ((pri & LOG_FACMASK) == 0) pri |= data->log_fac; + /* Get system time, wallclock, fall back to UNIX time */ + if (gettimeofday(&tv, NULL) == -1) { + tv.tv_sec = time(NULL); + tv.tv_usec = 0; + } + + /* strftime() implies tzset(), localtime_r() doesn't. */ + tzset(); + now = (time_t) tv.tv_sec; + localtime_r(&now, &tmnow); + /* Build the message. */ p = tbuf; tbuf_left = TBUF_LEN; @@ -237,23 +264,59 @@ vsyslogp_r(int pri, struct syslog_data *data, const char *msgid, tbuf_left -= prlen; \ } while (/*CONSTCOND*/0) + /* Default log format is RFC5424, continues below BSD format */ + if (data->log_stat & LOG_RFC3154) { + if (!(data->log_stat & LOG_NLOG)) { + prlen = snprintf(p, tbuf_left, "<%d>", pri); + DEC(); + } else + prlen = 0; + + prlen = strftime(dbuf, sizeof(dbuf), "%b %d %T ", &tmnow); + + if (data->log_stat & (LOG_PERROR|LOG_CONS|LOG_NLOG)) { + iov[iovcnt].iov_base = dbuf; + iov[iovcnt].iov_len = strlen(dbuf); + iovcnt++; + } + if (data->log_host) { + memcpy(p, dbuf, prlen); + DEC(); + } + + if (data->log_hostname[0] == '\0' && gethostname(data->log_hostname, + sizeof(data->log_hostname)) == -1) { + /* can this really happen? */ + data->log_hostname[0] = '-'; + data->log_hostname[1] = '\0'; + } + prlen = snprintf(p, tbuf_left, "%s ", data->log_hostname); + DEC(); + + if (data->log_tag == NULL) + data->log_tag = getprogname(); + prlen = snprintf(p, tbuf_left, "%s", data->log_tag); + DEC(); + + if (data->log_stat & LOG_PID) { + if (data->log_pid == -1) + data->log_pid = getpid(); + prlen = snprintf(p, tbuf_left, "[%d]", data->log_pid); + DEC(); + } + strlcat(p, ":", tbuf_left); + prlen = 1; + DEC(); + goto output; + } + if (!(data->log_stat & LOG_NLOG)) { prlen = snprintf(p, tbuf_left, "<%d>1 ", pri); DEC(); } else prlen = 0; - if (gettimeofday(&tv, NULL) == -1) { - tv.tv_sec = time(NULL); - tv.tv_usec = 0; - } - { - /* strftime() implies tzset(), localtime_r() doesn't. */ - tzset(); - now = (time_t) tv.tv_sec; - localtime_r(&now, &tmnow); - prlen = strftime(p, tbuf_left, "%FT%T", &tmnow); DEC(); prlen = snprintf(p, tbuf_left, ".%06ld", (long)tv.tv_usec); @@ -307,7 +370,9 @@ vsyslogp_r(int pri, struct syslog_data *data, const char *msgid, DEC(); if (data->log_stat & LOG_PID) { - prlen = snprintf(p, tbuf_left, "%d ", getpid()); + if (data->log_pid == -1) + data->log_pid = getpid(); + prlen = snprintf(p, tbuf_left, "%d ", data->log_pid); if (data->log_stat & (LOG_PERROR|LOG_CONS|LOG_NLOG)) { iov[iovcnt].iov_base = __UNCONST("["); iov[iovcnt].iov_len = 1; @@ -343,6 +408,7 @@ vsyslogp_r(int pri, struct syslog_data *data, const char *msgid, } else strlcat(fmt_cat, "-", FMT_LEN); +output: if (data->log_stat & (LOG_PERROR|LOG_CONS|LOG_NLOG)) msgsdlen = strlen(fmt_cat) + 1; else @@ -425,8 +491,17 @@ vsyslogp_r(int pri, struct syslog_data *data, const char *msgid, goto done; } + if (data->log_host) { + sa = data->log_host; +#ifdef HAVE_SA_LEN + len = sa->sa_len; +#else + len = sa_len(sa); +#endif + } + /* - * If the send() failed, there are two likely scenarios: + * If the send() fails, there are two likely scenarios: * 1) syslogd was restarted * 2) /dev/log is out of socket buffer space * We attempt to reconnect to /dev/log to take care of @@ -434,7 +509,7 @@ vsyslogp_r(int pri, struct syslog_data *data, const char *msgid, * to give syslogd a chance to empty its socket buffer. */ for (tries = 0; tries < MAXTRIES; tries++) { - if (send(data->log_file, tbuf, cnt, 0) != -1) + if (sendto(data->log_file, tbuf, cnt, 0, sa, len) != -1) break; if (errno != ENOBUFS) { disconnectlog_r(data); @@ -487,7 +562,7 @@ disconnectlog_r(struct syslog_data *data) static void connectlog_r(struct syslog_data *data) { - /* AF_UNIX address of local logger */ + struct sockaddr *sa = data->log_host; static struct sockaddr_un sun = { .sun_family = AF_LOCAL, #ifdef HAVE_SA_LEN @@ -495,28 +570,48 @@ connectlog_r(struct syslog_data *data) #endif .sun_path = _PATH_LOG, }; + socklen_t len; + int family; char *path; - path = getenv("SYSLOG_UNIX_PATH"); - if (!data->log_sockpath && path) - data->log_sockpath = path; - if (data->log_sockpath && !access(data->log_sockpath, W_OK)) - strlcpy(sun.sun_path, data->log_sockpath, sizeof(sun.sun_path)); + if (sa) { + family = sa->sa_family; +#ifdef HAVE_SA_LEN + len = sa->sa_len; +#else + len = sa_len(sa); +#endif + } else { + sa = (struct sockaddr *)&sun; + family = AF_UNIX; +#ifdef HAVE_SA_LEN + len = sa->sa_len; +#else + len = sizeof(sun); +#endif + + path = getenv("SYSLOG_UNIX_PATH"); + if (!data->log_sockpath && path) + data->log_sockpath = path; + + if (data->log_sockpath && !access(data->log_sockpath, W_OK)) + strlcpy(sun.sun_path, data->log_sockpath, sizeof(sun.sun_path)); + } if (data->log_file == -1 || fcntl(data->log_file, F_GETFL, 0) == -1) { - data->log_file = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + data->log_file = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (data->log_file == -1) return; data->log_connected = 0; } + if (!data->log_connected) { if (!is_socket(data->log_file)) { data->log_connected = 1; return; } - if (connect(data->log_file, (const struct sockaddr *)&sun, - sizeof(sun)) == -1) { + if (connect(data->log_file, sa, len) == -1) { (void)close(data->log_file); data->log_file = -1; } else @@ -534,10 +629,12 @@ openlog_unlocked_r(const char *ident, int logstat, int logfac, if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0) data->log_fac = logfac; - if (data->log_stat & LOG_NDELAY) /* open immediately */ + if (data->log_stat & LOG_NDELAY) { /* open immediately */ connectlog_r(data); - - data->log_opened = 1; + if (data->log_connected) + data->log_opened = 1; + } else + data->log_opened = 1; } void diff --git a/src/syslog.h b/src/syslog.h index ff19b12..01e3a55 100644 --- a/src/syslog.h +++ b/src/syslog.h @@ -190,6 +190,7 @@ CODE facilitynames[] = { #define LOG_PTRIM 0x040 /* trim tag and pid from messages to stderr */ #define LOG_NLOG 0x080 /* don't write to the system log */ #define LOG_STDOUT 0x100 /* like nlog, for debugging syslogp() API */ +#define LOG_RFC3154 0x200 /* Log to remote/ipc socket in old BSD format */ #ifndef __KERNEL__ @@ -206,6 +207,8 @@ struct syslog_data { char log_hostname[256]; /* MAXHOSTNAMELEN */ int log_fac; int log_mask; + void *log_host; /* struct sockaddr* */ + int log_pid; }; #define SYSLOG_DATA_INIT { \ @@ -219,6 +222,8 @@ struct syslog_data { .log_hostname = { '\0' }, \ .log_fac = LOG_USER, \ .log_mask = 0xff, \ + .log_host = NULL, \ + .log_pid = -1, \ } #ifdef __cplusplus diff --git a/test/notify.sh b/test/notify.sh index 637fed7..a048da0 100755 --- a/test/notify.sh +++ b/test/notify.sh @@ -30,6 +30,7 @@ MSG=$MSG$MSG$MSG$MSG$MSG$MSG$MSG$MSG$MSG$MSG logger ${MSG} logger 1${MSG} logger 2${MSG} +sleep 1 if [ -f "${LOG}.0" ] && grep 'script 1' "${NOT1STAMP}" &&