chiark / gitweb /
journal: Fix syslog forwarding without CAP_SYS_ADMIN
[elogind.git] / src / journal / journald-syslog.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2011 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <unistd.h>
23 #include <stddef.h>
24 #include <sys/epoll.h>
25
26 #include "systemd/sd-messages.h"
27 #include "socket-util.h"
28 #include "selinux-util.h"
29 #include "journald-server.h"
30 #include "journald-syslog.h"
31 #include "journald-kmsg.h"
32 #include "journald-console.h"
33 #include "journald-wall.h"
34
35 /* Warn once every 30s if we missed syslog message */
36 #define WARN_FORWARD_SYSLOG_MISSED_USEC (30 * USEC_PER_SEC)
37
38 static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, const struct ucred *ucred, const struct timeval *tv) {
39
40         static const union sockaddr_union sa = {
41                 .un.sun_family = AF_UNIX,
42                 .un.sun_path = "/run/systemd/journal/syslog",
43         };
44         struct msghdr msghdr = {
45                 .msg_iov = (struct iovec *) iovec,
46                 .msg_iovlen = n_iovec,
47                 .msg_name = (struct sockaddr*) &sa.sa,
48                 .msg_namelen = offsetof(union sockaddr_union, un.sun_path)
49                                + strlen("/run/systemd/journal/syslog"),
50         };
51         struct cmsghdr *cmsg;
52         union {
53                 struct cmsghdr cmsghdr;
54                 uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
55         } control;
56
57         assert(s);
58         assert(iovec);
59         assert(n_iovec > 0);
60
61         if (ucred) {
62                 zero(control);
63                 msghdr.msg_control = &control;
64                 msghdr.msg_controllen = sizeof(control);
65
66                 cmsg = CMSG_FIRSTHDR(&msghdr);
67                 cmsg->cmsg_level = SOL_SOCKET;
68                 cmsg->cmsg_type = SCM_CREDENTIALS;
69                 cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred));
70                 memcpy(CMSG_DATA(cmsg), ucred, sizeof(struct ucred));
71                 msghdr.msg_controllen = cmsg->cmsg_len;
72         }
73
74         /* Forward the syslog message we received via /dev/log to
75          * /run/systemd/syslog. Unfortunately we currently can't set
76          * the SO_TIMESTAMP auxiliary data, and hence we don't. */
77
78         if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0)
79                 return;
80
81         /* The socket is full? I guess the syslog implementation is
82          * too slow, and we shouldn't wait for that... */
83         if (errno == EAGAIN) {
84                 s->n_forward_syslog_missed++;
85                 return;
86         }
87
88         if (ucred && (errno == ESRCH || errno == EPERM)) {
89                 struct ucred u;
90
91                 /* Hmm, presumably the sender process vanished
92                  * by now, or we don't have CAP_SYS_AMDIN, so
93                  * let's fix it as good as we can, and retry */
94
95                 u = *ucred;
96                 u.pid = getpid();
97                 memcpy(CMSG_DATA(cmsg), &u, sizeof(struct ucred));
98
99                 if (sendmsg(s->syslog_fd, &msghdr, MSG_NOSIGNAL) >= 0)
100                         return;
101
102                 if (errno == EAGAIN) {
103                         s->n_forward_syslog_missed++;
104                         return;
105                 }
106         }
107
108         if (errno != ENOENT)
109                 log_debug_errno(errno, "Failed to forward syslog message: %m");
110 }
111
112 static void forward_syslog_raw(Server *s, int priority, const char *buffer, const struct ucred *ucred, const struct timeval *tv) {
113         struct iovec iovec;
114
115         assert(s);
116         assert(buffer);
117
118         if (LOG_PRI(priority) > s->max_level_syslog)
119                 return;
120
121         IOVEC_SET_STRING(iovec, buffer);
122         forward_syslog_iovec(s, &iovec, 1, ucred, tv);
123 }
124
125 void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv) {
126         struct iovec iovec[5];
127         char header_priority[6], header_time[64], header_pid[16];
128         int n = 0;
129         time_t t;
130         struct tm *tm;
131         char *ident_buf = NULL;
132
133         assert(s);
134         assert(priority >= 0);
135         assert(priority <= 999);
136         assert(message);
137
138         if (LOG_PRI(priority) > s->max_level_syslog)
139                 return;
140
141         /* First: priority field */
142         snprintf(header_priority, sizeof(header_priority), "<%i>", priority);
143         char_array_0(header_priority);
144         IOVEC_SET_STRING(iovec[n++], header_priority);
145
146         /* Second: timestamp */
147         t = tv ? tv->tv_sec : ((time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC));
148         tm = localtime(&t);
149         if (!tm)
150                 return;
151         if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0)
152                 return;
153         IOVEC_SET_STRING(iovec[n++], header_time);
154
155         /* Third: identifier and PID */
156         if (ucred) {
157                 if (!identifier) {
158                         get_process_comm(ucred->pid, &ident_buf);
159                         identifier = ident_buf;
160                 }
161
162                 snprintf(header_pid, sizeof(header_pid), "["PID_FMT"]: ", ucred->pid);
163                 char_array_0(header_pid);
164
165                 if (identifier)
166                         IOVEC_SET_STRING(iovec[n++], identifier);
167
168                 IOVEC_SET_STRING(iovec[n++], header_pid);
169         } else if (identifier) {
170                 IOVEC_SET_STRING(iovec[n++], identifier);
171                 IOVEC_SET_STRING(iovec[n++], ": ");
172         }
173
174         /* Fourth: message */
175         IOVEC_SET_STRING(iovec[n++], message);
176
177         forward_syslog_iovec(s, iovec, n, ucred, tv);
178
179         free(ident_buf);
180 }
181
182 int syslog_fixup_facility(int priority) {
183
184         if ((priority & LOG_FACMASK) == 0)
185                 return (priority & LOG_PRIMASK) | LOG_USER;
186
187         return priority;
188 }
189
190 size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid) {
191         const char *p;
192         char *t;
193         size_t l, e;
194
195         assert(buf);
196         assert(identifier);
197         assert(pid);
198
199         p = *buf;
200
201         p += strspn(p, WHITESPACE);
202         l = strcspn(p, WHITESPACE);
203
204         if (l <= 0 ||
205             p[l-1] != ':')
206                 return 0;
207
208         e = l;
209         l--;
210
211         if (p[l-1] == ']') {
212                 size_t k = l-1;
213
214                 for (;;) {
215
216                         if (p[k] == '[') {
217                                 t = strndup(p+k+1, l-k-2);
218                                 if (t)
219                                         *pid = t;
220
221                                 l = k;
222                                 break;
223                         }
224
225                         if (k == 0)
226                                 break;
227
228                         k--;
229                 }
230         }
231
232         t = strndup(p, l);
233         if (t)
234                 *identifier = t;
235
236         e += strspn(p + e, WHITESPACE);
237         *buf = p + e;
238         return e;
239 }
240
241 void syslog_parse_priority(const char **p, int *priority, bool with_facility) {
242         int a = 0, b = 0, c = 0;
243         int k;
244
245         assert(p);
246         assert(*p);
247         assert(priority);
248
249         if ((*p)[0] != '<')
250                 return;
251
252         if (!strchr(*p, '>'))
253                 return;
254
255         if ((*p)[2] == '>') {
256                 c = undecchar((*p)[1]);
257                 k = 3;
258         } else if ((*p)[3] == '>') {
259                 b = undecchar((*p)[1]);
260                 c = undecchar((*p)[2]);
261                 k = 4;
262         } else if ((*p)[4] == '>') {
263                 a = undecchar((*p)[1]);
264                 b = undecchar((*p)[2]);
265                 c = undecchar((*p)[3]);
266                 k = 5;
267         } else
268                 return;
269
270         if (a < 0 || b < 0 || c < 0 ||
271             (!with_facility && (a || b || c > 7)))
272                 return;
273
274         if (with_facility)
275                 *priority = a*100 + b*10 + c;
276         else
277                 *priority = (*priority & LOG_FACMASK) | c;
278         *p += k;
279 }
280
281 static void syslog_skip_date(char **buf) {
282         enum {
283                 LETTER,
284                 SPACE,
285                 NUMBER,
286                 SPACE_OR_NUMBER,
287                 COLON
288         } sequence[] = {
289                 LETTER, LETTER, LETTER,
290                 SPACE,
291                 SPACE_OR_NUMBER, NUMBER,
292                 SPACE,
293                 SPACE_OR_NUMBER, NUMBER,
294                 COLON,
295                 SPACE_OR_NUMBER, NUMBER,
296                 COLON,
297                 SPACE_OR_NUMBER, NUMBER,
298                 SPACE
299         };
300
301         char *p;
302         unsigned i;
303
304         assert(buf);
305         assert(*buf);
306
307         p = *buf;
308
309         for (i = 0; i < ELEMENTSOF(sequence); i++, p++) {
310
311                 if (!*p)
312                         return;
313
314                 switch (sequence[i]) {
315
316                 case SPACE:
317                         if (*p != ' ')
318                                 return;
319                         break;
320
321                 case SPACE_OR_NUMBER:
322                         if (*p == ' ')
323                                 break;
324
325                         /* fall through */
326
327                 case NUMBER:
328                         if (*p < '0' || *p > '9')
329                                 return;
330
331                         break;
332
333                 case LETTER:
334                         if (!(*p >= 'A' && *p <= 'Z') &&
335                             !(*p >= 'a' && *p <= 'z'))
336                                 return;
337
338                         break;
339
340                 case COLON:
341                         if (*p != ':')
342                                 return;
343                         break;
344
345                 }
346         }
347
348         *buf = p;
349 }
350
351 void server_process_syslog_message(
352         Server *s,
353         const char *buf,
354         const struct ucred *ucred,
355         const struct timeval *tv,
356         const char *label,
357         size_t label_len) {
358
359         char syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)],
360              syslog_facility[sizeof("SYSLOG_FACILITY") + DECIMAL_STR_MAX(int)];
361         const char *message = NULL, *syslog_identifier = NULL, *syslog_pid = NULL;
362         struct iovec iovec[N_IOVEC_META_FIELDS + 6];
363         unsigned n = 0;
364         int priority = LOG_USER | LOG_INFO;
365         _cleanup_free_ char *identifier = NULL, *pid = NULL;
366         const char *orig;
367
368         assert(s);
369         assert(buf);
370
371         orig = buf;
372         syslog_parse_priority(&buf, &priority, true);
373
374         if (s->forward_to_syslog)
375                 forward_syslog_raw(s, priority, orig, ucred, tv);
376
377         syslog_skip_date((char**) &buf);
378         syslog_parse_identifier(&buf, &identifier, &pid);
379
380         if (s->forward_to_kmsg)
381                 server_forward_kmsg(s, priority, identifier, buf, ucred);
382
383         if (s->forward_to_console)
384                 server_forward_console(s, priority, identifier, buf, ucred);
385
386         if (s->forward_to_wall)
387                 server_forward_wall(s, priority, identifier, buf, ucred);
388
389         IOVEC_SET_STRING(iovec[n++], "_TRANSPORT=syslog");
390
391         sprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK);
392         IOVEC_SET_STRING(iovec[n++], syslog_priority);
393
394         if (priority & LOG_FACMASK) {
395                 sprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority));
396                 IOVEC_SET_STRING(iovec[n++], syslog_facility);
397         }
398
399         if (identifier) {
400                 syslog_identifier = strappenda("SYSLOG_IDENTIFIER=", identifier);
401                 if (syslog_identifier)
402                         IOVEC_SET_STRING(iovec[n++], syslog_identifier);
403         }
404
405         if (pid) {
406                 syslog_pid = strappenda("SYSLOG_PID=", pid);
407                 if (syslog_pid)
408                         IOVEC_SET_STRING(iovec[n++], syslog_pid);
409         }
410
411         message = strappenda("MESSAGE=", buf);
412         if (message)
413                 IOVEC_SET_STRING(iovec[n++], message);
414
415         server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), ucred, tv, label, label_len, NULL, priority, 0);
416 }
417
418 int server_open_syslog_socket(Server *s) {
419         static const int one = 1;
420         int r;
421
422         assert(s);
423
424         if (s->syslog_fd < 0) {
425                 static const union sockaddr_union sa = {
426                         .un.sun_family = AF_UNIX,
427                         .un.sun_path = "/run/systemd/journal/dev-log",
428                 };
429
430                 s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
431                 if (s->syslog_fd < 0)
432                         return log_error_errno(errno, "socket() failed: %m");
433
434                 unlink(sa.un.sun_path);
435
436                 r = bind(s->syslog_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
437                 if (r < 0)
438                         return log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
439
440                 chmod(sa.un.sun_path, 0666);
441         } else
442                 fd_nonblock(s->syslog_fd, 1);
443
444         r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
445         if (r < 0)
446                 return log_error_errno(errno, "SO_PASSCRED failed: %m");
447
448 #ifdef HAVE_SELINUX
449         if (mac_selinux_use()) {
450                 r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one));
451                 if (r < 0)
452                         log_warning_errno(errno, "SO_PASSSEC failed: %m");
453         }
454 #endif
455
456         r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
457         if (r < 0)
458                 return log_error_errno(errno, "SO_TIMESTAMP failed: %m");
459
460         r = sd_event_add_io(s->event, &s->syslog_event_source, s->syslog_fd, EPOLLIN, server_process_datagram, s);
461         if (r < 0)
462                 return log_error_errno(r, "Failed to add syslog server fd to event loop: %m");
463
464         return 0;
465 }
466
467 void server_maybe_warn_forward_syslog_missed(Server *s) {
468         usec_t n;
469         assert(s);
470
471         if (s->n_forward_syslog_missed <= 0)
472                 return;
473
474         n = now(CLOCK_MONOTONIC);
475         if (s->last_warn_forward_syslog_missed + WARN_FORWARD_SYSLOG_MISSED_USEC > n)
476                 return;
477
478         server_driver_message(s, SD_MESSAGE_FORWARD_SYSLOG_MISSED, "Forwarding to syslog missed %u messages.", s->n_forward_syslog_missed);
479
480         s->n_forward_syslog_missed = 0;
481         s->last_warn_forward_syslog_missed = n;
482 }