1 /* This file is part of GNU Pies.
2 Copyright (C) 2007-2020 Sergey Poznyakoff
3
4 GNU Pies is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 GNU Pies is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with GNU Pies. If not, see <http://www.gnu.org/licenses/>. */
16 #include <config.h>
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <stdarg.h>
20 #include <syslog.h>
21 #include <errno.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <limits.h>
26 #include <time.h>
27 #include <sys/types.h>
28 #include <sys/time.h>
29 #include <sys/socket.h>
30 #include <sys/un.h>
31 #include <netinet/in.h>
32 #include <netdb.h>
33 #include "pies.h"
34 #include "pies_syslog.h"
35
36 struct log_message
37 {
38 char *text; /* Message text (no terminating nul). */
39 size_t len; /* Number of bytes in text */
40 size_t drop_count; /* Count of messages dropped so far (first message only) */
41 int trunc;
42 struct log_message *next;
43 };
44
45 struct log_message_in
46 {
47 struct log_message msg;
48 char buf[PIES_LOG_BUF_SIZE];
49 };
50
51 /* Global variables */
52 /* Fallback log file is used to log critical messages when syslog
53 daemon is unavailable. If NULL, stderr will be used. */
54 char *pies_fallback_file = "/tmp/pies_logger.log";
55 /* Log tag */
56 char *pies_log_tag = "pies";
57 /* Log facility */
58 int pies_log_facility = LOG_USER;
59 /* Maximum capacity of the log message queue */
60 size_t pies_log_max_queue = PIES_LOG_MAX_QUEUE;
61
62 /* Name of the syslog device. If starts with a slash, it is assumed
63 to be a UNIX socket name. Otherwise, it is assumed to be a host name
64 or IPv4 address of the syslog daemon, optionally followed by a colon
65 and port number or service name.
66 If NULL, PIES_LOG_DEV is assumed.
67 The user can modify this value using the pies_syslog_set_dev call.
68 */
69 static char *log_dev;
70 /* Log socket descriptor. */
71 static int log_fd = -1;
72
73 /* Socket address */
74 static union {
75 struct sockaddr_in s_in;
76 struct sockaddr_un s_un;
77 } log_sa;
78
79 /* Socket address length. */
80 static socklen_t log_salen = 0;
81
82 /* Socked address family. */
83 static int log_family;
84
85 static inline int
pri_facility(int pri)86 pri_facility (int pri)
87 {
88 return pri & ~0x7;
89 }
90
91 static inline int
pri_severity(int pri)92 pri_severity (int pri)
93 {
94 return pri & 0x7;
95 }
96
97 /* Fallback logger */
98 static void
fallback_log(char const * fmt,...)99 fallback_log (char const *fmt, ...)
100 {
101 FILE *fp = NULL;
102 va_list ap;
103
104 if (pies_fallback_file)
105 fp = fopen (pies_fallback_file, "a");
106 if (!fp)
107 fp = stderr;
108 fprintf (fp, "pies[%lu]: ", (unsigned long) getpid());
109 va_start (ap, fmt);
110 vfprintf (fp, fmt, ap);
111 va_end (ap);
112 fputc ('\n', fp);
113 if (fp != stderr)
114 fclose (fp);
115 }
116
117 static int
reopen_logger(void)118 reopen_logger (void)
119 {
120 int fd;
121 int flags;
122
123 if (log_salen == 0)
124 {
125 char *dev = log_dev ? log_dev : PIES_LOG_DEV;
126 if (dev[0] == '/')
127 {
128 size_t len = strlen (dev);
129 if (len >= sizeof log_sa.s_un.sun_path)
130 {
131 fallback_log ("%s: UNIX socket name too long", dev);
132 return -1;
133 }
134 strcpy (log_sa.s_un.sun_path, dev);
135 log_sa.s_un.sun_family = AF_UNIX;
136 log_family = PF_UNIX;
137 log_salen = sizeof (log_sa.s_un);
138 }
139 else
140 {
141 struct addrinfo hints;
142 struct addrinfo *res;
143 int rc;
144 char *node;
145 char *service;
146
147 node = strdup (dev);
148 if (!node)
149 return -1;
150
151 service = strchr (node, ':');
152 if (service)
153 *service++ = 0;
154 else
155 service = "syslog";
156
157 memset (&hints, 0, sizeof (hints));
158 hints.ai_family = AF_INET;
159 hints.ai_socktype = SOCK_DGRAM;
160 hints.ai_protocol = IPPROTO_UDP;
161
162 rc = getaddrinfo (node, service, &hints, &res);
163 free(node);
164 if (rc)
165 {
166 fallback_log ("%s: invalid socket address", dev);
167 return -1;
168 }
169
170 memcpy (&log_sa, res->ai_addr, res->ai_addrlen);
171 log_family = PF_INET;
172 log_salen = res->ai_addrlen;
173 freeaddrinfo (res);
174 }
175 }
176
177 fd = socket (log_family, SOCK_DGRAM, 0);
178
179 if (fd == -1)
180 {
181 fallback_log ("socket: %s", strerror (errno));
182 return -1;
183 }
184
185 if ((flags = fcntl (fd, F_GETFL)) == -1 ||
186 fcntl (fd, F_SETFL, flags | O_NONBLOCK) == -1 ||
187 (flags = fcntl (fd, F_GETFD)) == -1 ||
188 fcntl (fd, F_SETFD, flags | FD_CLOEXEC) == -1)
189 {
190 close (fd);
191 return -1;
192 }
193
194 if (connect(fd, (struct sockaddr*)&log_sa, log_salen))
195 {
196 fallback_log("socket: %s", strerror(errno));
197 close(fd);
198 return -1;
199 }
200
201 log_fd = fd;
202
203 return 0;
204 }
205
206 static struct log_message_in *
log_message_in_create(void)207 log_message_in_create (void)
208 {
209 struct log_message_in *msg;
210
211 if ((msg = malloc (sizeof (*msg))) != NULL)
212 {
213 msg->msg.text = msg->buf;
214 msg->msg.len = 0;
215 msg->msg.drop_count = 0;
216 msg->msg.trunc = 0;
217 msg->msg.next = NULL;
218 }
219 return msg;
220 }
221
222 static void
log_message_in_format(struct log_message_in * msg,int prio,char const * msgtext,char const * tag,pid_t pid)223 log_message_in_format (struct log_message_in *msg,
224 int prio, char const *msgtext, char const *tag,
225 pid_t pid)
226 {
227 char tbuf[sizeof ("Jan 1 00:00:00")];
228 char hostbuf[256];
229 struct timeval tv;
230 struct tm tm;
231 int n;
232
233 gettimeofday (&tv, NULL);
234 localtime_r (&tv.tv_sec, &tm);
235 strftime (tbuf, sizeof (tbuf), "%b %d %H:%M:%S", &tm);
236
237 /* Supply default facility, unless prio already contains one.
238 Note: this means that we cannot use LOG_KERN, but that doesn't
239 really matter as we're not a kernel, anyway. */
240 if (pri_facility (prio) == 0)
241 prio |= pies_log_facility;
242
243 if (log_family == PF_UNIX)
244 {
245 n = snprintf (msg->buf, sizeof (msg->buf),
246 "<%d>%s %s[%lu]: %s",
247 prio,
248 tbuf,
249 tag,
250 (unsigned long)pid,
251 msgtext);
252 }
253 else
254 {
255 gethostname (hostbuf, sizeof (hostbuf));
256 n = snprintf (msg->buf, sizeof (msg->buf),
257 "<%d>%s %s %s[%lu]: %s",
258 prio,
259 tbuf,
260 hostbuf,
261 tag,
262 (unsigned long)pid,
263 msgtext);
264 }
265 if (n >= sizeof (msg->buf))
266 {
267 /* Overflow */
268 n = sizeof (msg->buf);
269 msg->msg.trunc = 1;
270 }
271 msg->msg.len = n;
272 }
273
274 static struct log_message *
log_message_create(int prio,char const * msgtext,char const * tag,pid_t pid)275 log_message_create (int prio, char const *msgtext, char const *tag, pid_t pid)
276 {
277 struct log_message_in *msg;
278
279 if ((msg = log_message_in_create ()) != NULL)
280 log_message_in_format (msg, prio, msgtext, tag, pid);
281 return &msg->msg;
282 }
283
284 /* Log message queue */
285 static struct log_message *log_queue_head, *log_queue_tail;
286 static size_t log_queue_length;
287
288 static void
log_message_putback(struct log_message * msg)289 log_message_putback (struct log_message *msg)
290 {
291 msg->next = log_queue_head;
292 log_queue_head = msg;
293 if (!log_queue_tail)
294 log_queue_tail = msg;
295 log_queue_length++;
296 }
297
298 static struct log_message *
log_message_dequeue(void)299 log_message_dequeue (void)
300 {
301 struct log_message *msg = log_queue_head;
302 if (msg)
303 {
304 log_queue_head = msg->next;
305 if (!log_queue_head)
306 log_queue_tail = log_queue_head;
307 msg->next = NULL;
308 log_queue_length--;
309 }
310 return msg;
311 }
312
313 static void
log_message_enqueue(struct log_message * inmsg)314 log_message_enqueue (struct log_message *inmsg)
315 {
316 int truncated = 0;
317
318 do
319 {
320 if (log_queue_length == pies_log_max_queue)
321 {
322 struct log_message *msg;
323 struct log_message_in *tmp;
324 char buf[PIES_LOG_BUF_SIZE];
325
326 /* Dequeue first message */
327 msg = log_message_dequeue ();
328
329 if (msg->drop_count == 0)
330 {
331 /* If it is not a drop message, free it and create a new
332 drop message */
333 free (msg);
334 tmp = log_message_in_create ();
335 tmp->msg.drop_count = 1;
336 }
337 else
338 /* Otherwise, cast it to log_message_in */
339 tmp = (struct log_message_in *)msg;
340
341 /* Dequeue and drop the first message */
342 free (log_message_dequeue ());
343 tmp->msg.drop_count++;
344
345 /* Reformat the message text */
346 snprintf (buf, sizeof (buf), "%zu messages dropped",
347 tmp->msg.drop_count);
348 log_message_in_format (tmp,
349 pies_log_facility|LOG_CRIT,
350 buf,
351 pies_log_tag,
352 getpid ());
353
354 log_message_putback (&tmp->msg);
355 }
356
357 if (log_queue_tail)
358 log_queue_tail->next = inmsg;
359 else
360 log_queue_head = inmsg;
361 log_queue_tail = inmsg;
362 log_queue_length++;
363
364 if (truncated)
365 /* Make sure at most one truncation message is added */
366 truncated = 0;
367 else if (inmsg->trunc)
368 {
369 inmsg = log_message_create (pies_log_facility|LOG_CRIT,
370 "-- message truncated --",
371 pies_log_tag, getpid ());
372 truncated = 1;
373 }
374 }
375 while (truncated);
376
377 pies_syslog_flush ();
378 }
379
380 /*
381 * Flush the message queue to the socket.
382 * Some fragments borrowed from the excellent syslog_async written by
383 * Simon Kelley (http://www.thekelleys.org.uk/syslog-async).
384 */
385 void
pies_syslog_flush(void)386 pies_syslog_flush (void)
387 {
388 struct log_message *msg;
389 int rc;
390
391 while ((msg = log_message_dequeue ()) != NULL)
392 {
393 if (log_fd == -1)
394 reopen_logger ();
395
396 rc = send (log_fd, msg->text, msg->len, MSG_NOSIGNAL);
397 if (rc != -1)
398 {
399 free (msg);
400 continue;
401 }
402 log_message_putback (msg);
403
404 if (errno == EINTR)
405 continue;//Should not happen??
406 if (errno == EAGAIN)
407 break;
408
409 /* *BSD, returns this instead of blocking? */
410 if (errno == ENOBUFS)
411 break;
412
413 /* A stream socket closed at the other end goes into EPIPE
414 forever, close and re-open. */
415 if (errno == EPIPE)
416 {
417 close (log_fd);
418 log_fd = -1;
419 continue;
420 }
421
422 if (errno == ECONNREFUSED || /* connection went down */
423 errno == ENOTCONN || /* nobody listening */
424 errno == EDESTADDRREQ || /* BSD equivalents of the above */
425 errno == ECONNRESET)
426 {
427 /* The reader is gone. Try reconnecting. If failed,
428 retry when called next time. */
429
430 if (connect (log_fd, (struct sockaddr *)&log_sa, log_salen) != -1)
431 /* Connected successfully: retry now */
432 continue;
433
434 if (errno == ENOENT ||
435 errno == EALREADY ||
436 errno == ECONNREFUSED ||
437 errno == EISCONN ||
438 errno == EINTR ||
439 errno == EAGAIN)
440 /* try again when woken up again */
441 break;
442 }
443
444 /* Else ? */
445 break;
446 }
447 }
448
449 void
logger_log(int prio,char const * msgtext,char const * tag,pid_t pid)450 logger_log (int prio, char const *msgtext, char const *tag, pid_t pid)
451 {
452 struct log_message *msg;
453
454 msg = log_message_create (prio, msgtext, tag, pid);
455 if (msg)
456 log_message_enqueue (msg);
457 }
458
459 /* Upper level logger API */
460 void
pies_vsyslog(int pri,char const * fmt,va_list ap)461 pies_vsyslog (int pri, char const *fmt, va_list ap)
462 {
463 char buf[PIES_LOG_BUF_SIZE];
464 vsnprintf (buf, sizeof(buf), fmt, ap);
465 logger_log (pri, buf, pies_log_tag, getpid ());
466 }
467
468 void
pies_syslog(int pri,char const * fmt,...)469 pies_syslog (int pri, char const *fmt, ...)
470 {
471 va_list ap;
472 va_start (ap, fmt);
473 pies_vsyslog (pri, fmt, ap);
474 va_end (ap);
475 }
476
477 int
pies_syslog_open(void)478 pies_syslog_open (void)
479 {
480 return reopen_logger ();
481 }
482
483 void
pies_syslog_close(void)484 pies_syslog_close (void)
485 {
486 if (log_fd != -1)
487 {
488 close (log_fd);
489 log_fd = -1;
490 }
491 }
492
493 void
pies_syslog_message(int prio,char const * text,char const * tag,pid_t pid)494 pies_syslog_message (int prio, char const *text, char const *tag, pid_t pid)
495 {
496 log_message_enqueue (log_message_create (prio, text, tag, pid));
497 }
498
499 int
pies_syslog_set_dev(char const * dev)500 pies_syslog_set_dev (char const *dev)
501 {
502 char *p = strdup (dev);
503 if (!p)
504 return -1;
505 pies_syslog_close ();
506 free (log_dev);
507 log_dev = p;
508 log_salen = 0;
509 return 0;
510 }
511