chiark / gitweb /
journald: split off native protocol support into its own .c file
[elogind.git] / src / journal / journald-native.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 <sys/epoll.h>
24
25 #include "socket-util.h"
26 #include "journald.h"
27 #include "journald-native.h"
28 #include "journald-kmsg.h"
29 #include "journald-console.h"
30 #include "journald-syslog.h"
31
32 #define ENTRY_SIZE_MAX (1024*1024*32)
33
34 static bool valid_user_field(const char *p, size_t l) {
35         const char *a;
36
37         /* We kinda enforce POSIX syntax recommendations for
38            environment variables here, but make a couple of additional
39            requirements.
40
41            http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html */
42
43         /* No empty field names */
44         if (l <= 0)
45                 return false;
46
47         /* Don't allow names longer than 64 chars */
48         if (l > 64)
49                 return false;
50
51         /* Variables starting with an underscore are protected */
52         if (p[0] == '_')
53                 return false;
54
55         /* Don't allow digits as first character */
56         if (p[0] >= '0' && p[0] <= '9')
57                 return false;
58
59         /* Only allow A-Z0-9 and '_' */
60         for (a = p; a < p + l; a++)
61                 if (!((*a >= 'A' && *a <= 'Z') ||
62                       (*a >= '0' && *a <= '9') ||
63                       *a == '_'))
64                         return false;
65
66         return true;
67 }
68
69 void server_process_native_message(
70                 Server *s,
71                 const void *buffer, size_t buffer_size,
72                 struct ucred *ucred,
73                 struct timeval *tv,
74                 const char *label, size_t label_len) {
75
76         struct iovec *iovec = NULL;
77         unsigned n = 0, m = 0, j, tn = (unsigned) -1;
78         const char *p;
79         size_t remaining;
80         int priority = LOG_INFO;
81         char *identifier = NULL, *message = NULL;
82
83         assert(s);
84         assert(buffer || buffer_size == 0);
85
86         p = buffer;
87         remaining = buffer_size;
88
89         while (remaining > 0) {
90                 const char *e, *q;
91
92                 e = memchr(p, '\n', remaining);
93
94                 if (!e) {
95                         /* Trailing noise, let's ignore it, and flush what we collected */
96                         log_debug("Received message with trailing noise, ignoring.");
97                         break;
98                 }
99
100                 if (e == p) {
101                         /* Entry separator */
102                         server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority);
103                         n = 0;
104                         priority = LOG_INFO;
105
106                         p++;
107                         remaining--;
108                         continue;
109                 }
110
111                 if (*p == '.' || *p == '#') {
112                         /* Ignore control commands for now, and
113                          * comments too. */
114                         remaining -= (e - p) + 1;
115                         p = e + 1;
116                         continue;
117                 }
118
119                 /* A property follows */
120
121                 if (n+N_IOVEC_META_FIELDS >= m) {
122                         struct iovec *c;
123                         unsigned u;
124
125                         u = MAX((n+N_IOVEC_META_FIELDS+1) * 2U, 4U);
126                         c = realloc(iovec, u * sizeof(struct iovec));
127                         if (!c) {
128                                 log_oom();
129                                 break;
130                         }
131
132                         iovec = c;
133                         m = u;
134                 }
135
136                 q = memchr(p, '=', e - p);
137                 if (q) {
138                         if (valid_user_field(p, q - p)) {
139                                 size_t l;
140
141                                 l = e - p;
142
143                                 /* If the field name starts with an
144                                  * underscore, skip the variable,
145                                  * since that indidates a trusted
146                                  * field */
147                                 iovec[n].iov_base = (char*) p;
148                                 iovec[n].iov_len = l;
149                                 n++;
150
151                                 /* We need to determine the priority
152                                  * of this entry for the rate limiting
153                                  * logic */
154                                 if (l == 10 &&
155                                     memcmp(p, "PRIORITY=", 9) == 0 &&
156                                     p[9] >= '0' && p[9] <= '9')
157                                         priority = (priority & LOG_FACMASK) | (p[9] - '0');
158
159                                 else if (l == 17 &&
160                                          memcmp(p, "SYSLOG_FACILITY=", 16) == 0 &&
161                                          p[16] >= '0' && p[16] <= '9')
162                                         priority = (priority & LOG_PRIMASK) | ((p[16] - '0') << 3);
163
164                                 else if (l == 18 &&
165                                          memcmp(p, "SYSLOG_FACILITY=", 16) == 0 &&
166                                          p[16] >= '0' && p[16] <= '9' &&
167                                          p[17] >= '0' && p[17] <= '9')
168                                         priority = (priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3);
169
170                                 else if (l >= 19 &&
171                                          memcmp(p, "SYSLOG_IDENTIFIER=", 18) == 0) {
172                                         char *t;
173
174                                         t = strndup(p + 18, l - 18);
175                                         if (t) {
176                                                 free(identifier);
177                                                 identifier = t;
178                                         }
179                                 } else if (l >= 8 &&
180                                            memcmp(p, "MESSAGE=", 8) == 0) {
181                                         char *t;
182
183                                         t = strndup(p + 8, l - 8);
184                                         if (t) {
185                                                 free(message);
186                                                 message = t;
187                                         }
188                                 }
189                         }
190
191                         remaining -= (e - p) + 1;
192                         p = e + 1;
193                         continue;
194                 } else {
195                         le64_t l_le;
196                         uint64_t l;
197                         char *k;
198
199                         if (remaining < e - p + 1 + sizeof(uint64_t) + 1) {
200                                 log_debug("Failed to parse message, ignoring.");
201                                 break;
202                         }
203
204                         memcpy(&l_le, e + 1, sizeof(uint64_t));
205                         l = le64toh(l_le);
206
207                         if (remaining < e - p + 1 + sizeof(uint64_t) + l + 1 ||
208                             e[1+sizeof(uint64_t)+l] != '\n') {
209                                 log_debug("Failed to parse message, ignoring.");
210                                 break;
211                         }
212
213                         k = malloc((e - p) + 1 + l);
214                         if (!k) {
215                                 log_oom();
216                                 break;
217                         }
218
219                         memcpy(k, p, e - p);
220                         k[e - p] = '=';
221                         memcpy(k + (e - p) + 1, e + 1 + sizeof(uint64_t), l);
222
223                         if (valid_user_field(p, e - p)) {
224                                 iovec[n].iov_base = k;
225                                 iovec[n].iov_len = (e - p) + 1 + l;
226                                 n++;
227                         } else
228                                 free(k);
229
230                         remaining -= (e - p) + 1 + sizeof(uint64_t) + l + 1;
231                         p = e + 1 + sizeof(uint64_t) + l + 1;
232                 }
233         }
234
235         if (n <= 0)
236                 goto finish;
237
238         tn = n++;
239         IOVEC_SET_STRING(iovec[tn], "_TRANSPORT=journal");
240
241         if (message) {
242                 if (s->forward_to_syslog)
243                         server_forward_syslog(s, priority, identifier, message, ucred, tv);
244
245                 if (s->forward_to_kmsg)
246                         server_forward_kmsg(s, priority, identifier, message, ucred);
247
248                 if (s->forward_to_console)
249                         server_forward_console(s, priority, identifier, message, ucred);
250         }
251
252         server_dispatch_message(s, iovec, n, m, ucred, tv, label, label_len, NULL, priority);
253
254 finish:
255         for (j = 0; j < n; j++)  {
256                 if (j == tn)
257                         continue;
258
259                 if (iovec[j].iov_base < buffer ||
260                     (const uint8_t*) iovec[j].iov_base >= (const uint8_t*) buffer + buffer_size)
261                         free(iovec[j].iov_base);
262         }
263
264         free(iovec);
265         free(identifier);
266         free(message);
267 }
268
269 void server_process_native_file(
270                 Server *s,
271                 int fd,
272                 struct ucred *ucred,
273                 struct timeval *tv,
274                 const char *label, size_t label_len) {
275
276         struct stat st;
277         void *p;
278         ssize_t n;
279
280         assert(s);
281         assert(fd >= 0);
282
283         /* Data is in the passed file, since it didn't fit in a
284          * datagram. We can't map the file here, since clients might
285          * then truncate it and trigger a SIGBUS for us. So let's
286          * stupidly read it */
287
288         if (fstat(fd, &st) < 0) {
289                 log_error("Failed to stat passed file, ignoring: %m");
290                 return;
291         }
292
293         if (!S_ISREG(st.st_mode)) {
294                 log_error("File passed is not regular. Ignoring.");
295                 return;
296         }
297
298         if (st.st_size <= 0)
299                 return;
300
301         if (st.st_size > ENTRY_SIZE_MAX) {
302                 log_error("File passed too large. Ignoring.");
303                 return;
304         }
305
306         p = malloc(st.st_size);
307         if (!p) {
308                 log_oom();
309                 return;
310         }
311
312         n = pread(fd, p, st.st_size, 0);
313         if (n < 0)
314                 log_error("Failed to read file, ignoring: %s", strerror(-n));
315         else if (n > 0)
316                 server_process_native_message(s, p, n, ucred, tv, label, label_len);
317
318         free(p);
319 }
320
321 int server_open_native_socket(Server*s) {
322         union sockaddr_union sa;
323         int one, r;
324         struct epoll_event ev;
325
326         assert(s);
327
328         if (s->native_fd < 0) {
329
330                 s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
331                 if (s->native_fd < 0) {
332                         log_error("socket() failed: %m");
333                         return -errno;
334                 }
335
336                 zero(sa);
337                 sa.un.sun_family = AF_UNIX;
338                 strncpy(sa.un.sun_path, "/run/systemd/journal/socket", sizeof(sa.un.sun_path));
339
340                 unlink(sa.un.sun_path);
341
342                 r = bind(s->native_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path) + strlen(sa.un.sun_path));
343                 if (r < 0) {
344                         log_error("bind() failed: %m");
345                         return -errno;
346                 }
347
348                 chmod(sa.un.sun_path, 0666);
349         } else
350                 fd_nonblock(s->native_fd, 1);
351
352         one = 1;
353         r = setsockopt(s->native_fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
354         if (r < 0) {
355                 log_error("SO_PASSCRED failed: %m");
356                 return -errno;
357         }
358
359 #ifdef HAVE_SELINUX
360         one = 1;
361         r = setsockopt(s->syslog_fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one));
362         if (r < 0)
363                 log_warning("SO_PASSSEC failed: %m");
364 #endif
365
366         one = 1;
367         r = setsockopt(s->native_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one));
368         if (r < 0) {
369                 log_error("SO_TIMESTAMP failed: %m");
370                 return -errno;
371         }
372
373         zero(ev);
374         ev.events = EPOLLIN;
375         ev.data.fd = s->native_fd;
376         if (epoll_ctl(s->epoll_fd, EPOLL_CTL_ADD, s->native_fd, &ev) < 0) {
377                 log_error("Failed to add native server fd to epoll object: %m");
378                 return -errno;
379         }
380
381         return 0;
382 }