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