]>
Commit | Line | Data |
---|---|---|
6dbe3af9 KZ |
1 | /* |
2 | * Copyright (c) 1983, 1993 | |
3 | * The Regents of the University of California. All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * 3. All advertising materials mentioning features or use of this software | |
14 | * must display the following acknowledgement: | |
15 | * This product includes software developed by the University of | |
16 | * California, Berkeley and its contributors. | |
17 | * 4. Neither the name of the University nor the names of its contributors | |
18 | * may be used to endorse or promote products derived from this software | |
19 | * without specific prior written permission. | |
20 | * | |
21 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
24 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
31 | * SUCH DAMAGE. | |
7eda085c | 32 | * |
b50945d4 | 33 | * 1999-02-22 Arkadiusz MiĆkiewicz <misiek@pld.ORG.PL> |
7eda085c KZ |
34 | * - added Native Language Support |
35 | * Sun Mar 21 1999 - Arnaldo Carvalho de Melo <acme@conectiva.com.br> | |
36 | * - fixed strerr(errno) in gettext calls | |
6dbe3af9 KZ |
37 | */ |
38 | ||
6dbe3af9 | 39 | #include <errno.h> |
047e2888 | 40 | #include <limits.h> |
6dbe3af9 KZ |
41 | #include <unistd.h> |
42 | #include <stdlib.h> | |
5ec85227 | 43 | #include <sys/time.h> |
6dbe3af9 KZ |
44 | #include <stdio.h> |
45 | #include <ctype.h> | |
46 | #include <string.h> | |
7eda085c KZ |
47 | #include <sys/types.h> |
48 | #include <sys/socket.h> | |
66ee8158 | 49 | #include <sys/un.h> |
912d6b98 WJE |
50 | #include <arpa/inet.h> |
51 | #include <netdb.h> | |
b363e86d | 52 | #include <getopt.h> |
019b9702 | 53 | #include <pwd.h> |
b363e86d | 54 | |
633493be | 55 | #include "all-io.h" |
b363e86d | 56 | #include "c.h" |
c05a80ca | 57 | #include "closestream.h" |
7eda085c | 58 | #include "nls.h" |
52a49e9a | 59 | #include "pathnames.h" |
b363e86d | 60 | #include "strutils.h" |
4b670c01 | 61 | #include "xalloc.h" |
6dbe3af9 KZ |
62 | |
63 | #define SYSLOG_NAMES | |
64 | #include <syslog.h> | |
65 | ||
ebff016a | 66 | #ifdef HAVE_LIBSYSTEMD |
d77dc29e | 67 | # include <systemd/sd-daemon.h> |
4b670c01 SK |
68 | # include <systemd/sd-journal.h> |
69 | #endif | |
70 | ||
87ee2658 ST |
71 | #ifdef HAVE_SYS_TIMEX_H |
72 | # include <sys/timex.h> | |
73 | #endif | |
74 | ||
68265d07 SK |
75 | enum { |
76 | TYPE_UDP = (1 << 1), | |
77 | TYPE_TCP = (1 << 2), | |
78 | ALL_TYPES = TYPE_UDP | TYPE_TCP | |
79 | }; | |
80 | ||
d77dc29e SK |
81 | enum { |
82 | AF_UNIX_ERRORS_OFF = 0, | |
83 | AF_UNIX_ERRORS_ON, | |
84 | AF_UNIX_ERRORS_AUTO | |
85 | }; | |
86 | ||
98920f80 | 87 | enum { |
4b670c01 | 88 | OPT_PRIO_PREFIX = CHAR_MAX + 1, |
4de2e8a0 SK |
89 | OPT_JOURNALD, |
90 | OPT_RFC3164, | |
d77dc29e | 91 | OPT_RFC5424, |
3f51c10b | 92 | OPT_SOCKET_ERRORS, |
55f5bc66 | 93 | OPT_MSGID, |
fd343a05 | 94 | OPT_NOACT, |
3f51c10b | 95 | OPT_ID |
98920f80 DJ |
96 | }; |
97 | ||
cfa77d26 SK |
98 | struct logger_ctl { |
99 | int fd; | |
cfa77d26 | 100 | int pri; |
59c6ac0b | 101 | pid_t pid; /* zero when unwanted */ |
2b3f40c5 | 102 | char *hdr; /* the syslog header (based on protocol) */ |
cfa77d26 | 103 | char *tag; |
55f5bc66 | 104 | char *msgid; |
c95d3209 | 105 | char *unix_socket; /* -u <path> or default to _PATH_DEVLOG */ |
c68a1cb4 SK |
106 | char *server; |
107 | char *port; | |
108 | int socket_type; | |
f68b8aa7 | 109 | size_t max_message_size; |
2b3f40c5 | 110 | void (*syslogfp)(struct logger_ctl *ctl); |
4de2e8a0 | 111 | unsigned int |
d77dc29e | 112 | unix_socket_errors:1, /* whether to report or not errors */ |
fd343a05 | 113 | noact:1, /* do not write to sockets */ |
d77dc29e SK |
114 | prio_prefix:1, /* read priority from intput */ |
115 | stderr_printout:1, /* output message to stderr */ | |
116 | rfc5424_time:1, /* include time stamp */ | |
117 | rfc5424_tq:1, /* include time quality markup */ | |
ae6846b8 RG |
118 | rfc5424_host:1, /* include hostname */ |
119 | skip_empty_lines:1; /* do not send empty lines when processing files */ | |
cfa77d26 SK |
120 | }; |
121 | ||
ef5fb280 KZ |
122 | /* |
123 | * For tests we want to be able to control datetime outputs | |
124 | */ | |
125 | #ifdef TEST_LOGGER | |
126 | static inline int logger_gettimeofday(struct timeval *tv, struct timezone *tz) | |
127 | { | |
128 | char *str = getenv("LOGGER_TEST_TIMEOFDAY"); | |
129 | uintmax_t sec, usec; | |
130 | ||
131 | if (str && sscanf(str, "%ju.%ju", &sec, &usec) == 2) { | |
132 | tv->tv_sec = sec; | |
133 | tv->tv_usec = usec; | |
98e90a49 | 134 | return tv->tv_sec >= 0 && tv->tv_usec >= 0 ? 0 : -EINVAL; |
ef5fb280 KZ |
135 | } |
136 | ||
137 | return gettimeofday(tv, tz); | |
138 | } | |
139 | ||
140 | static inline char *logger_xgethostname(void) | |
141 | { | |
142 | char *str = getenv("LOGGER_TEST_HOSTNAME"); | |
143 | return str ? xstrdup(str) : xgethostname(); | |
144 | } | |
145 | ||
146 | static inline pid_t logger_getpid(void) | |
147 | { | |
148 | char *str = getenv("LOGGER_TEST_GETPID"); | |
149 | unsigned int pid; | |
150 | ||
151 | if (str && sscanf(str, "%u", &pid) == 1) | |
152 | return pid; | |
153 | return getpid(); | |
154 | } | |
155 | ||
156 | ||
157 | #undef HAVE_NTP_GETTIME /* force to default non-NTP */ | |
158 | ||
159 | #else /* !TEST_LOGGER */ | |
160 | # define logger_gettimeofday(x, y) gettimeofday(x, y) | |
161 | # define logger_xgethostname xgethostname | |
162 | # define logger_getpid getpid | |
163 | #endif | |
164 | ||
165 | ||
46ee14df | 166 | static int decode(const char *name, CODE *codetab) |
82054b1d DR |
167 | { |
168 | register CODE *c; | |
169 | ||
4d7d1af6 SK |
170 | if (name == NULL || *name == '\0') |
171 | return -1; | |
172 | if (isdigit(*name)) { | |
173 | int num; | |
174 | char *end = NULL; | |
175 | ||
8d341322 | 176 | errno = 0; |
4d7d1af6 SK |
177 | num = strtol(name, &end, 10); |
178 | if (errno || name == end || (end && *end)) | |
179 | return -1; | |
180 | for (c = codetab; c->c_name; c++) | |
181 | if (num == c->c_val) | |
182 | return num; | |
183 | return -1; | |
184 | } | |
82054b1d DR |
185 | for (c = codetab; c->c_name; c++) |
186 | if (!strcasecmp(name, c->c_name)) | |
187 | return (c->c_val); | |
188 | ||
189 | return -1; | |
190 | } | |
191 | ||
192 | static int pencode(char *s) | |
193 | { | |
2e0fd22d SK |
194 | int facility, level; |
195 | char *separator; | |
196 | ||
197 | separator = strchr(s, '.'); | |
198 | if (separator) { | |
199 | *separator = '\0'; | |
200 | facility = decode(s, facilitynames); | |
201 | if (facility < 0) | |
202 | errx(EXIT_FAILURE, _("unknown facility name: %s"), s); | |
203 | s = ++separator; | |
204 | } else | |
205 | facility = LOG_USER; | |
206 | level = decode(s, prioritynames); | |
207 | if (level < 0) | |
208 | errx(EXIT_FAILURE, _("unknown priority name: %s"), s); | |
9a13f968 SK |
209 | if (facility == LOG_KERN) |
210 | facility = LOG_USER; /* kern is forbidden */ | |
2e0fd22d | 211 | return ((level & LOG_PRIMASK) | (facility & LOG_FACMASK)); |
82054b1d DR |
212 | } |
213 | ||
d77dc29e | 214 | static int unix_socket(struct logger_ctl *ctl, const char *path, const int socket_type) |
fe6999da | 215 | { |
68265d07 | 216 | int fd, i; |
fe6999da | 217 | static struct sockaddr_un s_addr; /* AF_UNIX address of local logger */ |
7eda085c | 218 | |
68265d07 SK |
219 | if (strlen(path) >= sizeof(s_addr.sun_path)) |
220 | errx(EXIT_FAILURE, _("openlog %s: pathname too long"), path); | |
7eda085c | 221 | |
fe6999da | 222 | s_addr.sun_family = AF_UNIX; |
68265d07 SK |
223 | strcpy(s_addr.sun_path, path); |
224 | ||
225 | for (i = 2; i; i--) { | |
226 | int st = -1; | |
49999d6a | 227 | |
68265d07 SK |
228 | if (i == 2 && socket_type & TYPE_UDP) |
229 | st = SOCK_DGRAM; | |
230 | if (i == 1 && socket_type & TYPE_TCP) | |
231 | st = SOCK_STREAM; | |
232 | if (st == -1 || (fd = socket(AF_UNIX, st, 0)) == -1) | |
233 | continue; | |
fe6999da SK |
234 | if (connect(fd, (struct sockaddr *)&s_addr, sizeof(s_addr)) == -1) { |
235 | close(fd); | |
68265d07 | 236 | continue; |
fe6999da | 237 | } |
68265d07 | 238 | break; |
fe6999da | 239 | } |
7eda085c | 240 | |
d77dc29e SK |
241 | if (i == 0) { |
242 | if (ctl->unix_socket_errors) | |
243 | err(EXIT_FAILURE, _("socket %s"), path); | |
244 | else | |
245 | /* See --socket-errors manual page entry for | |
246 | * explanation of this strange exit. */ | |
247 | exit(EXIT_SUCCESS); | |
248 | } | |
fe6999da | 249 | return fd; |
7eda085c KZ |
250 | } |
251 | ||
195c3603 KZ |
252 | static int inet_socket(const char *servername, const char *port, |
253 | const int socket_type) | |
68265d07 SK |
254 | { |
255 | int fd, errcode, i; | |
24f4db69 | 256 | struct addrinfo hints, *res; |
68265d07 SK |
257 | const char *p = port; |
258 | ||
259 | for (i = 2; i; i--) { | |
260 | memset(&hints, 0, sizeof(hints)); | |
261 | if (i == 2 && socket_type & TYPE_UDP) { | |
262 | hints.ai_socktype = SOCK_DGRAM; | |
263 | if (port == NULL) | |
264 | p = "syslog"; | |
265 | } | |
266 | if (i == 1 && socket_type & TYPE_TCP) { | |
267 | hints.ai_socktype = SOCK_STREAM; | |
268 | if (port == NULL) | |
269 | p = "syslog-conn"; | |
270 | } | |
271 | if (hints.ai_socktype == 0) | |
272 | continue; | |
273 | hints.ai_family = AF_UNSPEC; | |
274 | errcode = getaddrinfo(servername, p, &hints, &res); | |
275 | if (errcode != 0) | |
4ce393f4 | 276 | errx(EXIT_FAILURE, _("failed to resolve name %s port %s: %s"), |
68265d07 SK |
277 | servername, p, gai_strerror(errcode)); |
278 | if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) { | |
279 | freeaddrinfo(res); | |
280 | continue; | |
281 | } | |
282 | if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) { | |
283 | freeaddrinfo(res); | |
284 | close(fd); | |
285 | continue; | |
286 | } | |
24f4db69 | 287 | |
68265d07 SK |
288 | freeaddrinfo(res); |
289 | break; | |
290 | } | |
912d6b98 | 291 | |
68265d07 | 292 | if (i == 0) |
4ce393f4 | 293 | errx(EXIT_FAILURE, _("failed to connect to %s port %s"), servername, p); |
49999d6a | 294 | |
912d6b98 WJE |
295 | return fd; |
296 | } | |
68265d07 | 297 | |
ebff016a | 298 | #ifdef HAVE_LIBSYSTEMD |
fd343a05 | 299 | static int journald_entry(struct logger_ctl *ctl, FILE *fp) |
4b670c01 SK |
300 | { |
301 | struct iovec *iovec; | |
302 | char *buf = NULL; | |
303 | ssize_t sz; | |
fd343a05 | 304 | int n, lines, vectors = 8, ret = 0; |
4b670c01 SK |
305 | size_t dummy = 0; |
306 | ||
307 | iovec = xmalloc(vectors * sizeof(struct iovec)); | |
308 | for (lines = 0; /* nothing */ ; lines++) { | |
309 | buf = NULL; | |
310 | sz = getline(&buf, &dummy, fp); | |
311 | if (sz == -1) | |
312 | break; | |
313 | if (0 < sz && buf[sz - 1] == '\n') { | |
314 | sz--; | |
315 | buf[sz] = '\0'; | |
316 | } | |
317 | if (lines == vectors) { | |
318 | vectors *= 2; | |
047e2888 SK |
319 | if (IOV_MAX < vectors) |
320 | errx(EXIT_FAILURE, _("maximum input lines (%d) exceeded"), IOV_MAX); | |
4b670c01 SK |
321 | iovec = xrealloc(iovec, vectors * sizeof(struct iovec)); |
322 | } | |
323 | iovec[lines].iov_base = buf; | |
324 | iovec[lines].iov_len = sz; | |
325 | } | |
fd343a05 KZ |
326 | |
327 | if (!ctl->noact) | |
328 | ret = sd_journal_sendv(iovec, lines); | |
329 | if (ctl->stderr_printout) { | |
330 | for (n = 0; n < lines; n++) | |
331 | fprintf(stderr, "%s\n", (char *) iovec[n].iov_base); | |
332 | } | |
4b670c01 SK |
333 | for (n = 0; n < lines; n++) |
334 | free(iovec[n].iov_base); | |
335 | free(iovec); | |
336 | return ret; | |
337 | } | |
338 | #endif | |
339 | ||
46ee14df | 340 | static char *xgetlogin(void) |
019b9702 SK |
341 | { |
342 | char *cp; | |
343 | struct passwd *pw; | |
344 | ||
345 | if (!(cp = getlogin()) || !*cp) | |
346 | cp = (pw = getpwuid(geteuid()))? pw->pw_name : "<someone>"; | |
347 | return cp; | |
348 | } | |
349 | ||
3070ca77 RG |
350 | /* this creates a timestamp based on current time according to the |
351 | * fine rules of RFC3164, most importantly it ensures in a portable | |
352 | * way that the month day is correctly written (with a SP instead | |
353 | * of a leading 0). The function uses a static buffer which is | |
354 | * overwritten on the next call (just like ctime() does). | |
355 | */ | |
9a13f968 | 356 | static const char *rfc3164_current_time(void) |
3070ca77 RG |
357 | { |
358 | static char time[32]; | |
359 | struct timeval tv; | |
360 | struct tm *tm; | |
9a13f968 SK |
361 | static char *monthnames[] = { |
362 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", | |
363 | "Sep", "Oct", "Nov", "Dec" | |
364 | }; | |
3070ca77 | 365 | |
ef5fb280 | 366 | logger_gettimeofday(&tv, NULL); |
3070ca77 RG |
367 | tm = localtime(&tv.tv_sec); |
368 | snprintf(time, sizeof(time),"%s %2d %2.2d:%2.2d:%2.2d", | |
369 | monthnames[tm->tm_mon], tm->tm_mday, | |
0f1c825b | 370 | tm->tm_hour, tm->tm_min, tm->tm_sec); |
3070ca77 RG |
371 | return time; |
372 | } | |
373 | ||
940a14a3 RG |
374 | /* writes generated buffer to desired destination. For TCP syslog, |
375 | * we use RFC6587 octet-stuffing. This is not great, but doing | |
376 | * full blown RFC5425 (TLS) looks like it is too much for the | |
377 | * logger utility. | |
378 | */ | |
2b3f40c5 | 379 | static void write_output(const struct logger_ctl *ctl, const char *const msg) |
4be84306 | 380 | { |
2b3f40c5 RG |
381 | char *buf; |
382 | const size_t len = xasprintf(&buf, "%s%s", ctl->hdr, msg); | |
fd343a05 KZ |
383 | |
384 | if (!ctl->noact) { | |
385 | if (write_all(ctl->fd, buf, len) < 0) | |
9a13f968 | 386 | warn(_("write failed")); |
fd343a05 KZ |
387 | else if (ctl->socket_type == TYPE_TCP) { |
388 | /* using an additional write seems like the best compromise: | |
389 | * - writev() is not yet supported by framework | |
390 | * - adding the \n to the buffer in formatters violates layers | |
391 | * - adding \n after the fact requires memory copy | |
392 | * - logger is not a high-performance app | |
393 | */ | |
394 | if (write_all(ctl->fd, "\n", 1) < 0) | |
395 | warn(_("write failed")); | |
396 | } | |
397 | } | |
398 | ||
4be84306 RG |
399 | if (ctl->stderr_printout) |
400 | fprintf(stderr, "%s\n", buf); | |
401 | } | |
402 | ||
d5f93061 | 403 | #define NILVALUE "-" |
2b3f40c5 | 404 | static void syslog_rfc3164_header(struct logger_ctl *const ctl) |
195c3603 | 405 | { |
d5f93061 | 406 | char pid[30], *hostname; |
d8b616c2 | 407 | |
77c3bd5b | 408 | *pid = '\0'; |
cfa77d26 | 409 | if (ctl->fd < 0) |
c462a8ca | 410 | return; |
59c6ac0b KZ |
411 | if (ctl->pid) |
412 | snprintf(pid, sizeof(pid), "[%d]", ctl->pid); | |
852feb72 | 413 | |
ef5fb280 | 414 | if ((hostname = logger_xgethostname())) { |
d5f93061 SK |
415 | char *dot = strchr(hostname, '.'); |
416 | if (dot) | |
417 | *dot = '\0'; | |
418 | } else | |
419 | hostname = xstrdup(NILVALUE); | |
852feb72 | 420 | |
2b3f40c5 RG |
421 | xasprintf(&ctl->hdr, "<%d>%.15s %s %.200s%s: ", |
422 | ctl->pri, rfc3164_current_time(), hostname, ctl->tag, pid); | |
852feb72 KZ |
423 | |
424 | free(hostname); | |
7eda085c KZ |
425 | } |
426 | ||
4826184b RG |
427 | /* Some field mappings may be controversal, thus I give the reason |
428 | * why this specific mapping was used: | |
429 | * APP-NAME <-- tag | |
430 | * Some may argue that "logger" is a better fit, but we think | |
431 | * this is better inline of what other implementations do. In | |
432 | * rsyslog, for example, the TAG value is populated from APP-NAME. | |
433 | * PROCID <-- pid | |
434 | * This is a relatively straightforward interpretation from | |
435 | * RFC5424, sect. 6.2.6. | |
55f5bc66 | 436 | * MSGID <-- msgid (from --msgid) |
4826184b RG |
437 | * One may argue that the string "logger" would be better suited |
438 | * here so that a receiver can identify the sender process. | |
439 | * However, this does not sound like a good match to RFC5424, | |
55f5bc66 | 440 | * sect. 6.2.7. |
4826184b RG |
441 | * Note that appendix A.1 of RFC5424 does not provide clear guidance |
442 | * of how these fields should be used. This is the case because the | |
443 | * IETF working group couldn't arrive at a clear agreement when we | |
444 | * specified RFC5424. The rest of the field mappings should be | |
445 | * pretty clear from RFC5424. -- Rainer Gerhards, 2015-03-10 | |
446 | */ | |
2b3f40c5 | 447 | static void syslog_rfc5424_header(struct logger_ctl *const ctl) |
4de2e8a0 | 448 | { |
9a13f968 SK |
449 | char *time; |
450 | char *hostname; | |
451 | char *const app_name = ctl->tag; | |
452 | char *procid; | |
453 | char *const msgid = xstrdup(ctl->msgid ? ctl->msgid : NILVALUE); | |
454 | char *structured_data; | |
455 | ||
4de2e8a0 SK |
456 | if (ctl->fd < 0) |
457 | return; | |
852feb72 | 458 | |
4de2e8a0 | 459 | if (ctl->rfc5424_time) { |
4826184b RG |
460 | struct timeval tv; |
461 | struct tm *tm; | |
9a13f968 | 462 | |
ef5fb280 | 463 | logger_gettimeofday(&tv, NULL); |
4de2e8a0 | 464 | if ((tm = localtime(&tv.tv_sec)) != NULL) { |
852feb72 | 465 | char fmt[64]; |
2f267611 | 466 | const size_t i = strftime(fmt, sizeof(fmt), |
9a13f968 | 467 | "%Y-%m-%dT%H:%M:%S.%%06u%z ", tm); |
2f267611 | 468 | /* patch TZ info to comply with RFC3339 (we left SP at end) */ |
9a13f968 SK |
469 | fmt[i - 1] = fmt[i - 2]; |
470 | fmt[i - 2] = fmt[i - 3]; | |
471 | fmt[i - 3] = ':'; | |
4826184b | 472 | xasprintf(&time, fmt, tv.tv_usec); |
4de2e8a0 SK |
473 | } else |
474 | err(EXIT_FAILURE, _("localtime() failed")); | |
4826184b | 475 | } else |
7ff6948e | 476 | time = xstrdup(NILVALUE); |
852feb72 | 477 | |
4de2e8a0 | 478 | if (ctl->rfc5424_host) { |
ef5fb280 | 479 | if (!(hostname = logger_xgethostname())) |
d5f93061 | 480 | hostname = xstrdup(NILVALUE); |
4de2e8a0 SK |
481 | /* Arbitrary looking 'if (var < strlen()) checks originate from |
482 | * RFC 5424 - 6 Syslog Message Format definition. */ | |
483 | if (255 < strlen(hostname)) | |
484 | errx(EXIT_FAILURE, _("hostname '%s' is too long"), | |
485 | hostname); | |
4826184b | 486 | } else |
7ff6948e | 487 | hostname = xstrdup(NILVALUE); |
852feb72 | 488 | |
2b3f40c5 RG |
489 | if (48 < strlen(ctl->tag)) |
490 | errx(EXIT_FAILURE, _("tag '%s' is too long"), ctl->tag); | |
852feb72 | 491 | |
59c6ac0b | 492 | if (ctl->pid) |
4826184b RG |
493 | xasprintf(&procid, "%d", ctl->pid); |
494 | else | |
7ff6948e | 495 | procid = xstrdup(NILVALUE); |
4826184b | 496 | |
4de2e8a0 | 497 | if (ctl->rfc5424_tq) { |
7d3a07d8 KZ |
498 | #ifdef HAVE_NTP_GETTIME |
499 | struct ntptimeval ntptv; | |
9a13f968 | 500 | |
4de2e8a0 | 501 | if (ntp_gettime(&ntptv) == TIME_OK) |
4826184b RG |
502 | xasprintf(&structured_data, |
503 | "[timeQuality tzKnown=\"1\" isSynced=\"1\" syncAccuracy=\"%ld\"]", | |
4de2e8a0 SK |
504 | ntptv.maxerror); |
505 | else | |
87ee2658 | 506 | #endif |
4826184b RG |
507 | xasprintf(&structured_data, |
508 | "[timeQuality tzKnown=\"1\" isSynced=\"0\"]"); | |
773df0fa KZ |
509 | } else |
510 | structured_data = xstrdup(NILVALUE); | |
852feb72 | 511 | |
4826184b RG |
512 | xasprintf(&ctl->hdr, "<%d>1 %s %s %s %s %s %s ", |
513 | ctl->pri, | |
514 | time, | |
515 | hostname, | |
516 | app_name, | |
517 | procid, | |
518 | msgid, | |
519 | structured_data); | |
852feb72 | 520 | |
4826184b | 521 | free(time); |
852feb72 | 522 | free(hostname); |
4826184b RG |
523 | /* app_name points to ctl->tag, do NOT free! */ |
524 | free(procid); | |
525 | free(msgid); | |
526 | free(structured_data); | |
4de2e8a0 SK |
527 | } |
528 | ||
529 | static void parse_rfc5424_flags(struct logger_ctl *ctl, char *optarg) | |
530 | { | |
531 | char *in, *tok; | |
532 | ||
533 | in = optarg; | |
534 | while ((tok = strtok(in, ","))) { | |
535 | in = NULL; | |
536 | if (!strcmp(tok, "notime")) { | |
537 | ctl->rfc5424_time = 0; | |
538 | ctl->rfc5424_tq = 0; | |
539 | } else if (!strcmp(tok, "notq")) | |
540 | ctl->rfc5424_tq = 0; | |
541 | else if (!strcmp(tok, "nohost")) | |
542 | ctl->rfc5424_host = 0; | |
543 | else | |
544 | warnx(_("ignoring unknown option argument: %s"), tok); | |
545 | } | |
546 | } | |
547 | ||
d77dc29e SK |
548 | static int parse_unix_socket_errors_flags(char *optarg) |
549 | { | |
550 | if (!strcmp(optarg, "off")) | |
551 | return AF_UNIX_ERRORS_OFF; | |
552 | if (!strcmp(optarg, "on")) | |
553 | return AF_UNIX_ERRORS_ON; | |
554 | if (!strcmp(optarg, "auto")) | |
555 | return AF_UNIX_ERRORS_AUTO; | |
556 | warnx(_("invalid argument: %s: using automatic errors"), optarg); | |
557 | return AF_UNIX_ERRORS_AUTO; | |
558 | } | |
559 | ||
2b3f40c5 | 560 | static void syslog_local_header(struct logger_ctl *const ctl) |
cfa77d26 | 561 | { |
3070ca77 | 562 | char pid[32]; |
1d575033 | 563 | |
59c6ac0b KZ |
564 | if (ctl->pid) |
565 | snprintf(pid, sizeof(pid), "[%d]", ctl->pid); | |
1d575033 SK |
566 | else |
567 | pid[0] = '\0'; | |
568 | ||
2b3f40c5 RG |
569 | xasprintf(&ctl->hdr, "<%d>%s %s%s: ", ctl->pri, rfc3164_current_time(), |
570 | ctl->tag, pid); | |
571 | } | |
572 | ||
573 | static void generate_syslog_header(struct logger_ctl *const ctl) | |
574 | { | |
575 | free(ctl->hdr); | |
576 | ctl->syslogfp(ctl); | |
cfa77d26 SK |
577 | } |
578 | ||
c68a1cb4 SK |
579 | static void logger_open(struct logger_ctl *ctl) |
580 | { | |
581 | if (ctl->server) { | |
582 | ctl->fd = inet_socket(ctl->server, ctl->port, ctl->socket_type); | |
4de2e8a0 | 583 | if (!ctl->syslogfp) |
2b3f40c5 | 584 | ctl->syslogfp = syslog_rfc5424_header; |
c68a1cb4 SK |
585 | return; |
586 | } | |
7dc20804 RG |
587 | if (!ctl->unix_socket) |
588 | ctl->unix_socket = _PATH_DEVLOG; | |
589 | ||
590 | ctl->fd = unix_socket(ctl, ctl->unix_socket, ctl->socket_type); | |
591 | if (!ctl->syslogfp) | |
2b3f40c5 | 592 | ctl->syslogfp = syslog_local_header; |
9a13f968 SK |
593 | if (!ctl->tag) |
594 | ctl->tag = xgetlogin(); | |
2b3f40c5 | 595 | generate_syslog_header(ctl); |
c68a1cb4 SK |
596 | } |
597 | ||
46ee14df | 598 | static void logger_command_line(const struct logger_ctl *ctl, char **argv) |
c68a1cb4 | 599 | { |
2b3f40c5 RG |
600 | /* note: we never re-generate the syslog header here, even if we |
601 | * generate multiple messages. If so, we think it is the right thing | |
602 | * to do to report them with the same timestamp, as the user actually | |
603 | * intended to send a single message. | |
604 | */ | |
f68b8aa7 | 605 | char *const buf = xmalloc(ctl->max_message_size + 1); |
c68a1cb4 | 606 | char *p = buf; |
f68b8aa7 | 607 | const char *endp = buf + ctl->max_message_size - 1; |
c68a1cb4 SK |
608 | size_t len; |
609 | ||
610 | while (*argv) { | |
611 | len = strlen(*argv); | |
612 | if (endp < p + len && p != buf) { | |
2b3f40c5 | 613 | write_output(ctl, buf); |
c68a1cb4 SK |
614 | p = buf; |
615 | } | |
f68b8aa7 RG |
616 | if (ctl->max_message_size < len) { |
617 | (*argv)[ctl->max_message_size] = '\0'; /* truncate */ | |
2b3f40c5 | 618 | write_output(ctl, *argv++); |
c68a1cb4 SK |
619 | continue; |
620 | } | |
621 | if (p != buf) | |
622 | *p++ = ' '; | |
623 | memmove(p, *argv++, len); | |
624 | *(p += len) = '\0'; | |
625 | } | |
626 | if (p != buf) | |
2b3f40c5 | 627 | write_output(ctl, buf); |
c68a1cb4 SK |
628 | } |
629 | ||
630 | static void logger_stdin(struct logger_ctl *ctl) | |
631 | { | |
c68a1cb4 | 632 | int default_priority = ctl->pri; |
b9ef27f5 RG |
633 | int last_pri = default_priority; |
634 | size_t max_usrmsg_size = ctl->max_message_size - strlen(ctl->hdr); | |
635 | char *const buf = xmalloc(max_usrmsg_size + 2 + 2); | |
636 | int pri; | |
637 | int c; | |
638 | size_t i; | |
639 | ||
640 | c = getchar(); | |
641 | while (c != EOF) { | |
642 | i = 0; | |
643 | if (ctl->prio_prefix) { | |
644 | if (c == '<') { | |
645 | pri = 0; | |
646 | buf[i++] = c; | |
9a13f968 SK |
647 | while (isdigit(c = getchar()) && pri <= 191) { |
648 | buf[i++] = c; | |
649 | pri = pri * 10 + c - '0'; | |
b9ef27f5 RG |
650 | } |
651 | if (c != EOF && c != '\n') | |
652 | buf[i++] = c; | |
653 | if (c == '>' && 0 <= pri && pri <= 191) { /* valid RFC PRI values */ | |
654 | i = 0; | |
655 | if (pri < 8) | |
656 | pri |= 8; /* kern facility is forbidden */ | |
657 | ctl->pri = pri; | |
658 | } else | |
659 | ctl->pri = default_priority; | |
660 | ||
661 | if (ctl->pri != last_pri) { | |
662 | generate_syslog_header(ctl); | |
663 | max_usrmsg_size = ctl->max_message_size - strlen(ctl->hdr); | |
664 | last_pri = ctl->pri; | |
665 | } | |
666 | if (c != EOF && c != '\n') | |
667 | c = getchar(); | |
668 | } | |
669 | } | |
670 | ||
671 | while (c != EOF && c != '\n' && i < max_usrmsg_size) { | |
672 | buf[i++] = c; | |
673 | c = getchar(); | |
674 | } | |
675 | buf[i] = '\0'; | |
676 | ||
9a13f968 | 677 | if (i > 0 || !ctl->skip_empty_lines) |
ae6846b8 | 678 | write_output(ctl, buf); |
b9ef27f5 | 679 | |
9a13f968 | 680 | if (c == '\n') /* discard line terminator */ |
b9ef27f5 | 681 | c = getchar(); |
c68a1cb4 SK |
682 | } |
683 | } | |
684 | ||
46ee14df | 685 | static void logger_close(const struct logger_ctl *ctl) |
c68a1cb4 | 686 | { |
1d575033 SK |
687 | if (close(ctl->fd) != 0) |
688 | err(EXIT_FAILURE, _("close failed")); | |
2b3f40c5 | 689 | free(ctl->hdr); |
c68a1cb4 SK |
690 | } |
691 | ||
b363e86d SK |
692 | static void __attribute__ ((__noreturn__)) usage(FILE *out) |
693 | { | |
925aa9e8 | 694 | fputs(USAGE_HEADER, out); |
4ce393f4 | 695 | fprintf(out, _(" %s [options] [<message>]\n"), program_invocation_short_name); |
2da49186 | 696 | |
451dbcfa BS |
697 | fputs(USAGE_SEPARATOR, out); |
698 | fputs(_("Enter messages into the system log.\n"), out); | |
699 | ||
925aa9e8 | 700 | fputs(USAGE_OPTIONS, out); |
3f51c10b SK |
701 | fputs(_(" -i log the logger command's PID\n"), out); |
702 | fputs(_(" --id[=<id>] log the given <id>, or otherwise the PID\n"), out); | |
d0e875ff | 703 | fputs(_(" -f, --file <file> log the contents of this file\n"), out); |
ae6846b8 | 704 | fputs(_(" -e, --skip-empty do not log empty lines when processing files\n"), out); |
fd343a05 | 705 | fputs(_(" --no-act do everything except the write the log\n"), out); |
d0e875ff KZ |
706 | fputs(_(" -p, --priority <prio> mark given message with this priority\n"), out); |
707 | fputs(_(" --prio-prefix look for a prefix on every line read from stdin\n"), out); | |
708 | fputs(_(" -s, --stderr output message to standard error as well\n"), out); | |
f68b8aa7 | 709 | fputs(_(" -S, --size <size> maximum size for a single message\n"), out); |
d0e875ff KZ |
710 | fputs(_(" -t, --tag <tag> mark every line with this tag\n"), out); |
711 | fputs(_(" -n, --server <name> write to this remote syslog server\n"), out); | |
712 | fputs(_(" -P, --port <number> use this UDP port\n"), out); | |
713 | fputs(_(" -T, --tcp use TCP only\n"), out); | |
714 | fputs(_(" -d, --udp use UDP only\n"), out); | |
715 | fputs(_(" --rfc3164 use the obsolete BSD syslog protocol\n"), out); | |
2cb40465 | 716 | fputs(_(" --rfc5424[=<snip>] use the syslog protocol (the default for remote);\n" |
d0e875ff | 717 | " <snip> can be notime, or notq, and/or nohost\n"), out); |
8fce3924 | 718 | fputs(_(" --msgid <msgid> set rfc5424 message id field\n"), out); |
d0e875ff | 719 | fputs(_(" -u, --socket <socket> write to this Unix socket\n"), out); |
d77dc29e SK |
720 | fputs(_(" --socket-errors[=<on|off|auto>]\n" |
721 | " print connection errors when using Unix sockets\n"), out); | |
ebff016a | 722 | #ifdef HAVE_LIBSYSTEMD |
4b670c01 SK |
723 | fputs(_(" --journald[=<file>] write journald entry\n"), out); |
724 | #endif | |
925aa9e8 KZ |
725 | |
726 | fputs(USAGE_SEPARATOR, out); | |
727 | fputs(USAGE_HELP, out); | |
728 | fputs(USAGE_VERSION, out); | |
729 | fprintf(out, USAGE_MAN_TAIL("logger(1)")); | |
b363e86d SK |
730 | |
731 | exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS); | |
732 | } | |
733 | ||
6dbe3af9 KZ |
734 | /* |
735 | * logger -- read and log utility | |
736 | * | |
737 | * Reads from an input and arranges to write the result on the system | |
738 | * log. | |
739 | */ | |
195c3603 KZ |
740 | int main(int argc, char **argv) |
741 | { | |
cfa77d26 SK |
742 | struct logger_ctl ctl = { |
743 | .fd = -1, | |
59c6ac0b | 744 | .pid = 0, |
d0b6c4bf | 745 | .pri = LOG_USER | LOG_NOTICE, |
c68a1cb4 | 746 | .prio_prefix = 0, |
cfa77d26 | 747 | .tag = NULL, |
c68a1cb4 | 748 | .unix_socket = NULL, |
d77dc29e | 749 | .unix_socket_errors = 0, |
c68a1cb4 SK |
750 | .server = NULL, |
751 | .port = NULL, | |
2b3f40c5 | 752 | .hdr = NULL, |
55f5bc66 | 753 | .msgid = NULL, |
4de2e8a0 | 754 | .socket_type = ALL_TYPES, |
f68b8aa7 | 755 | .max_message_size = 1024, |
4de2e8a0 SK |
756 | .rfc5424_time = 1, |
757 | .rfc5424_tq = 1, | |
758 | .rfc5424_host = 1, | |
ae6846b8 | 759 | .skip_empty_lines = 0 |
cfa77d26 | 760 | }; |
c68a1cb4 | 761 | int ch; |
3d9f4b1d | 762 | int stdout_reopened = 0; |
d77dc29e | 763 | int unix_socket_errors_mode = AF_UNIX_ERRORS_AUTO; |
ebff016a | 764 | #ifdef HAVE_LIBSYSTEMD |
4b670c01 SK |
765 | FILE *jfd = NULL; |
766 | #endif | |
b363e86d | 767 | static const struct option longopts[] = { |
9a13f968 SK |
768 | { "id", optional_argument, 0, OPT_ID }, |
769 | { "stderr", no_argument, 0, 's' }, | |
770 | { "file", required_argument, 0, 'f' }, | |
fd343a05 | 771 | { "no-act", no_argument, 0, OPT_NOACT, }, |
9a13f968 SK |
772 | { "priority", required_argument, 0, 'p' }, |
773 | { "tag", required_argument, 0, 't' }, | |
774 | { "socket", required_argument, 0, 'u' }, | |
d77dc29e | 775 | { "socket-errors", required_argument, 0, OPT_SOCKET_ERRORS }, |
9a13f968 SK |
776 | { "udp", no_argument, 0, 'd' }, |
777 | { "tcp", no_argument, 0, 'T' }, | |
778 | { "server", required_argument, 0, 'n' }, | |
779 | { "port", required_argument, 0, 'P' }, | |
780 | { "version", no_argument, 0, 'V' }, | |
781 | { "help", no_argument, 0, 'h' }, | |
782 | { "prio-prefix", no_argument, 0, OPT_PRIO_PREFIX }, | |
783 | { "rfc3164", no_argument, 0, OPT_RFC3164 }, | |
784 | { "rfc5424", optional_argument, 0, OPT_RFC5424 }, | |
785 | { "size", required_argument, 0, 'S' }, | |
786 | { "msgid", required_argument, 0, OPT_MSGID }, | |
787 | { "skip-empty", no_argument, 0, 'e' }, | |
ebff016a | 788 | #ifdef HAVE_LIBSYSTEMD |
9a13f968 | 789 | { "journald", optional_argument, 0, OPT_JOURNALD }, |
4b670c01 | 790 | #endif |
9a13f968 | 791 | { NULL, 0, 0, 0 } |
b363e86d | 792 | }; |
7eda085c KZ |
793 | |
794 | setlocale(LC_ALL, ""); | |
795 | bindtextdomain(PACKAGE, LOCALEDIR); | |
796 | textdomain(PACKAGE); | |
c05a80ca | 797 | atexit(close_stdout); |
6dbe3af9 | 798 | |
ae6846b8 | 799 | while ((ch = getopt_long(argc, argv, "ef:ip:S:st:u:dTn:P:Vh", |
49999d6a | 800 | longopts, NULL)) != -1) { |
98920f80 | 801 | switch (ch) { |
6dbe3af9 | 802 | case 'f': /* file to log */ |
49999d6a | 803 | if (freopen(optarg, "r", stdin) == NULL) |
3d9f4b1d SK |
804 | err(EXIT_FAILURE, _("file %s"), optarg); |
805 | stdout_reopened = 1; | |
6dbe3af9 | 806 | break; |
ae6846b8 RG |
807 | case 'e': |
808 | ctl.skip_empty_lines = 1; | |
809 | break; | |
6dbe3af9 | 810 | case 'i': /* log process id also */ |
ef5fb280 | 811 | ctl.pid = logger_getpid(); |
3f51c10b SK |
812 | break; |
813 | case OPT_ID: | |
aab5b444 | 814 | if (optarg) { |
e598686d KZ |
815 | const char *p = optarg; |
816 | ||
817 | if (*p == '=') | |
818 | p++; | |
59c6ac0b KZ |
819 | ctl.pid = strtoul_or_err(optarg, _("failed to parse id")); |
820 | } else | |
ef5fb280 | 821 | ctl.pid = logger_getpid(); |
6dbe3af9 KZ |
822 | break; |
823 | case 'p': /* priority */ | |
cfa77d26 | 824 | ctl.pri = pencode(optarg); |
6dbe3af9 KZ |
825 | break; |
826 | case 's': /* log to standard error */ | |
35d36197 | 827 | ctl.stderr_printout = 1; |
6dbe3af9 KZ |
828 | break; |
829 | case 't': /* tag */ | |
cfa77d26 | 830 | ctl.tag = optarg; |
6dbe3af9 | 831 | break; |
7eda085c | 832 | case 'u': /* unix socket */ |
c68a1cb4 | 833 | ctl.unix_socket = optarg; |
7eda085c | 834 | break; |
f68b8aa7 RG |
835 | case 'S': /* max message size */ |
836 | ctl.max_message_size = strtosize_or_err(optarg, | |
837 | _("failed to parse message size")); | |
838 | break; | |
66ee8158 | 839 | case 'd': |
c68a1cb4 | 840 | ctl.socket_type = TYPE_UDP; |
68265d07 SK |
841 | break; |
842 | case 'T': | |
c68a1cb4 | 843 | ctl.socket_type = TYPE_TCP; |
66ee8158 | 844 | break; |
68265d07 | 845 | case 'n': |
c68a1cb4 | 846 | ctl.server = optarg; |
912d6b98 | 847 | break; |
68265d07 | 848 | case 'P': |
c68a1cb4 | 849 | ctl.port = optarg; |
912d6b98 | 850 | break; |
b363e86d | 851 | case 'V': |
e421313d | 852 | printf(UTIL_LINUX_VERSION); |
b363e86d SK |
853 | exit(EXIT_SUCCESS); |
854 | case 'h': | |
855 | usage(stdout); | |
98920f80 | 856 | case OPT_PRIO_PREFIX: |
c68a1cb4 | 857 | ctl.prio_prefix = 1; |
98920f80 | 858 | break; |
4de2e8a0 | 859 | case OPT_RFC3164: |
2b3f40c5 | 860 | ctl.syslogfp = syslog_rfc3164_header; |
4de2e8a0 SK |
861 | break; |
862 | case OPT_RFC5424: | |
2b3f40c5 | 863 | ctl.syslogfp = syslog_rfc5424_header; |
4de2e8a0 SK |
864 | if (optarg) |
865 | parse_rfc5424_flags(&ctl, optarg); | |
866 | break; | |
55f5bc66 | 867 | case OPT_MSGID: |
9a13f968 | 868 | if (strchr(optarg, ' ')) |
8fce3924 | 869 | errx(EXIT_FAILURE, _("--msgid cannot contain space")); |
55f5bc66 RG |
870 | ctl.msgid = optarg; |
871 | break; | |
ebff016a | 872 | #ifdef HAVE_LIBSYSTEMD |
4b670c01 SK |
873 | case OPT_JOURNALD: |
874 | if (optarg) { | |
875 | jfd = fopen(optarg, "r"); | |
876 | if (!jfd) | |
877 | err(EXIT_FAILURE, _("cannot open %s"), | |
878 | optarg); | |
879 | } else | |
880 | jfd = stdin; | |
881 | break; | |
882 | #endif | |
d77dc29e SK |
883 | case OPT_SOCKET_ERRORS: |
884 | unix_socket_errors_mode = parse_unix_socket_errors_flags(optarg); | |
885 | break; | |
fd343a05 KZ |
886 | case OPT_NOACT: |
887 | ctl.noact = 1; | |
888 | break; | |
6dbe3af9 KZ |
889 | case '?': |
890 | default: | |
b363e86d | 891 | usage(stderr); |
6dbe3af9 | 892 | } |
49999d6a | 893 | } |
6dbe3af9 KZ |
894 | argc -= optind; |
895 | argv += optind; | |
3d9f4b1d SK |
896 | if (stdout_reopened && argc) |
897 | warnx(_("--file <file> and <message> are mutually exclusive, message is ignored")); | |
ebff016a | 898 | #ifdef HAVE_LIBSYSTEMD |
4b670c01 | 899 | if (jfd) { |
fd343a05 | 900 | int ret = journald_entry(&ctl, jfd); |
4b670c01 SK |
901 | if (stdin != jfd) |
902 | fclose(jfd); | |
047e2888 | 903 | if (ret) |
54fefa07 | 904 | errx(EXIT_FAILURE, _("journald entry could not be written")); |
047e2888 | 905 | return EXIT_SUCCESS; |
4b670c01 SK |
906 | } |
907 | #endif | |
d77dc29e SK |
908 | switch (unix_socket_errors_mode) { |
909 | case AF_UNIX_ERRORS_OFF: | |
910 | ctl.unix_socket_errors = 0; | |
911 | break; | |
912 | case AF_UNIX_ERRORS_ON: | |
913 | ctl.unix_socket_errors = 1; | |
914 | break; | |
915 | case AF_UNIX_ERRORS_AUTO: | |
916 | #ifdef HAVE_LIBSYSTEMD | |
917 | ctl.unix_socket_errors = sd_booted(); | |
918 | #else | |
919 | ctl.unix_socket_errors = 0; | |
920 | #endif | |
921 | break; | |
922 | default: | |
923 | abort(); | |
924 | } | |
c68a1cb4 SK |
925 | logger_open(&ctl); |
926 | if (0 < argc) | |
927 | logger_command_line(&ctl, argv); | |
7eda085c | 928 | else |
c68a1cb4 SK |
929 | /* Note. --file <arg> reopens stdin making the below |
930 | * function to be used for file inputs. */ | |
931 | logger_stdin(&ctl); | |
932 | logger_close(&ctl); | |
49999d6a | 933 | return EXIT_SUCCESS; |
6dbe3af9 | 934 | } |