Support logging via TCP
[gnupg.git] / tools / watchgnupg.c
1 /* watchgnupg.c - Socket server for GnuPG logs
2  *      Copyright (C) 2003, 2004 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <stddef.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <stdarg.h>
29 #include <assert.h>
30 #include <unistd.h>
31 #include <sys/socket.h>
32 #include <sys/un.h>
33 #include <netinet/in.h>
34 #include <arpa/inet.h>
35 #include <fcntl.h>
36 #include <time.h>
37
38 #define PGM "watchgnupg"
39
40 /* Allow for a standalone build on most systems. */
41 #ifdef VERSION
42 #define MYVERSION_LINE PGM " (GnuPG) " VERSION
43 #define BUGREPORT_LINE "\nReport bugs to <bug-gnupg@gnu.org>.\n"
44 #else
45 #define MYVERSION_LINE PGM " (standalone build) " __DATE__
46 #define BUGREPORT_LINE ""
47 #endif
48 #if !defined(SUN_LEN) || !defined(PF_LOCAL) || !defined(AF_LOCAL)
49 #define JNLIB_NEED_AFLOCAL
50 #include "../jnlib/mischelp.h"
51 #endif
52
53
54 static int verbose;
55
56
57 static void
58 die (const char *format, ...)
59 {
60   va_list arg_ptr;
61
62   fflush (stdout);
63   fprintf (stderr, "%s: ", PGM);
64
65   va_start (arg_ptr, format);
66   vfprintf (stderr, format, arg_ptr);
67   va_end (arg_ptr);
68   putc ('\n', stderr);
69
70   exit (1);
71 }
72
73
74 /* static void */
75 /* err (const char *format, ...) */
76 /* { */
77 /*   va_list arg_ptr; */
78
79 /*   fflush (stdout); */
80 /*   fprintf (stderr, "%s: ", PGM); */
81
82 /*   va_start (arg_ptr, format); */
83 /*   vfprintf (stderr, format, arg_ptr); */
84 /*   va_end (arg_ptr); */
85 /*   putc ('\n', stderr); */
86 /* } */
87
88 static void *
89 xmalloc (size_t n)
90 {
91   void *p = malloc (n);
92   if (!p)
93     die ("out of core");
94   return p;
95 }
96
97 static void *
98 xcalloc (size_t n, size_t m)
99 {
100   void *p = calloc (n, m);
101   if (!p)
102     die ("out of core");
103   return p;
104 }
105
106 static void *
107 xrealloc (void *old, size_t n)
108 {
109   void *p = realloc (old, n);
110   if (!p)
111     die ("out of core");
112   return p;
113 }
114     
115
116 struct client_s {
117   struct client_s *next;
118   int fd;
119   size_t size;  /* Allocated size of buffer. */
120   size_t len;   /* Current length of buffer. */
121   unsigned char *buffer; /* Buffer to with data already read. */
122   
123 };
124 typedef struct client_s *client_t;
125
126
127
128 static void
129 print_fd_and_time (int fd)
130 {
131   struct tm *tp;
132   time_t atime = time (NULL);
133   
134   tp = localtime (&atime);
135   printf ("%3d - %04d-%02d-%02d %02d:%02d:%02d ",
136           fd,
137           1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
138           tp->tm_hour, tp->tm_min, tp->tm_sec );
139 }
140
141
142 /* Print LINE for the client identified by C.  Calling this function
143    witgh LINE set to NULL, will flush the internal buffer. */
144 static void
145 print_line (client_t c, const char *line)
146 {
147   const char *s;
148   size_t n;
149
150   if (!line)
151     {
152       if (c->buffer && c->len)
153         {
154           print_fd_and_time (c->fd);
155           fwrite (c->buffer, c->len, 1, stdout); 
156           putc ('\n', stdout);
157           c->len = 0;
158         }
159       return;
160     }
161
162   while ((s = strchr (line, '\n')))
163     {
164       print_fd_and_time (c->fd);
165       if (c->buffer && c->len)
166         {
167           fwrite (c->buffer, c->len, 1, stdout); 
168           c->len = 0;
169         }
170       fwrite (line, s - line + 1, 1, stdout); 
171       line = s + 1;
172     }
173   n = strlen (line);
174   if (n)
175     {
176       if (c->len + n >= c->size)
177         {
178           c->size += ((n + 255) & ~255);
179           c->buffer = (c->buffer
180                        ? xrealloc (c->buffer, c->size)
181                        : xmalloc (c->size));
182         }
183       memcpy (c->buffer + c->len, line, n);
184       c->len += n;
185     }
186 }
187
188
189 static void
190 print_version (int with_help)
191 {
192   fputs (MYVERSION_LINE "\n"
193          "Copyright (C) 2004 Free Software Foundation, Inc.\n"
194          "License GPLv3+: "
195          "GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n"
196          "This is free software: you are free to change and redistribute it.\n"
197          "There is NO WARRANTY, to the extent permitted by law.\n",
198          stdout);
199   if (with_help)
200     fputs ("\n"
201           "Usage: " PGM " [OPTIONS] SOCKETNAME|PORT\n"
202           "Open the local socket SOCKETNAME (or the TCP port PORT)\n"
203           "and display log messages\n"
204           "\n"
205           "  --force     delete an already existing socket file\n"
206           "  --tcp       listen on a TCP port instead of a local socket\n"
207           "  --verbose   enable extra informational output\n"
208           "  --version   print version of the program and exit\n"
209           "  --help      display this help and exit\n"
210           BUGREPORT_LINE, stdout );
211   
212   exit (0);
213 }
214
215 int 
216 main (int argc, char **argv)
217 {
218   int last_argc = -1;
219   int force = 0;
220   int tcp = 0;
221
222   struct sockaddr_un srvr_addr_un;
223   struct sockaddr_in srvr_addr_in;
224   struct sockaddr *srvr_addr = NULL;
225   socklen_t addrlen;
226   unsigned short port;
227   int server;
228   int flags;
229   client_t client_list = NULL;
230  
231   if (argc)
232     {
233       argc--; argv++;
234     }
235   while (argc && last_argc != argc )
236     {
237       last_argc = argc;
238       if (!strcmp (*argv, "--"))
239         {
240           argc--; argv++;
241           break;
242         }
243       else if (!strcmp (*argv, "--version"))
244         print_version (0);
245       else if (!strcmp (*argv, "--help"))
246         print_version (1);
247       else if (!strcmp (*argv, "--verbose"))
248         {
249           verbose = 1;
250           argc--; argv++;
251         }
252       else if (!strcmp (*argv, "--force"))
253         {
254           force = 1;
255           argc--; argv++;
256         }
257       else if (!strcmp (*argv, "--tcp"))
258         {
259           tcp = 1;
260           argc--; argv++;
261         }
262     }          
263  
264   if (argc != 1)
265     {
266       fprintf (stderr, "usage: " PGM " socketname\n");
267       exit (1);
268     }
269
270   port = tcp? atoi (*argv) : 0;
271
272   if (verbose)
273     {
274       if (tcp)
275         fprintf (stderr, "listening on port %hu\n", port);
276       else
277         fprintf (stderr, "opening socket `%s'\n", *argv);
278     }
279
280   setvbuf (stdout, NULL, _IOLBF, 0);
281
282   server = socket (tcp? PF_INET : PF_LOCAL, SOCK_STREAM, 0);
283   if (server == -1)
284     die ("socket() failed: %s\n", strerror (errno));
285
286   /* We better set the listening socket to non-blocking so that we
287      don't get bitten by race conditions in accept.  The should not
288      happen for Unix Domain sockets but well, shit happens. */
289   flags = fcntl (server, F_GETFL, 0);
290   if (flags == -1)
291     die ("fcntl (F_GETFL) failed: %s\n", strerror (errno));
292   if ( fcntl (server, F_SETFL, (flags | O_NONBLOCK)) == -1)
293     die ("fcntl (F_SETFL) failed: %s\n", strerror (errno));
294   
295   if (tcp)
296     {
297       memset (&srvr_addr_in, 0, sizeof srvr_addr_in);
298       srvr_addr_in.sin_family = AF_INET;
299       srvr_addr_in.sin_port = htons (port);
300       srvr_addr_in.sin_addr.s_addr = htonl (INADDR_ANY);
301       srvr_addr = (struct sockaddr *)&srvr_addr_in;
302       addrlen = sizeof srvr_addr_in;
303     }
304   else
305     {
306       memset (&srvr_addr_un, 0, sizeof srvr_addr_un);
307       srvr_addr_un.sun_family = AF_LOCAL;
308       strncpy (srvr_addr_un.sun_path, *argv, sizeof (srvr_addr_un.sun_path)-1);
309       srvr_addr_un.sun_path[sizeof (srvr_addr_un.sun_path) - 1] = 0;
310       srvr_addr = (struct sockaddr *)&srvr_addr_un;
311       addrlen = SUN_LEN (&srvr_addr_un);
312     }
313   
314  again:
315   if (bind (server, srvr_addr, addrlen))
316     { 
317       if (!tcp && errno == EADDRINUSE && force)
318         {
319           force = 0;
320           remove (srvr_addr_un.sun_path);
321           goto again;
322         }
323       if (tcp)
324         die ("bind to port %hu failed: %s\n", port, strerror (errno));
325       else
326         die ("bind to `%s' failed: %s\n", *argv, strerror (errno));
327     }
328
329   if (listen (server, 5))
330     die ("listen failed: %s\n", strerror (errno));
331
332   for (;;)
333     {
334       fd_set rfds;
335       int max_fd;
336       client_t client;
337
338       /* Usually we don't have that many connections, thus it is okay
339          to set them allways from scratch and don't maintain an active
340          fd_set. */
341       FD_ZERO (&rfds);
342       FD_SET (server, &rfds);
343       max_fd = server;
344       for (client = client_list; client; client = client->next)
345         if (client->fd != -1)
346           {
347             FD_SET (client->fd, &rfds);
348             if (client->fd > max_fd)
349               max_fd = client->fd;
350           }
351
352       if (select (max_fd + 1, &rfds, NULL, NULL, NULL) <= 0)
353         continue;  /* Ignore any errors. */
354
355       if (FD_ISSET (server, &rfds)) /* New connection. */
356         { 
357           struct sockaddr_un clnt_addr;
358           int fd;
359
360           addrlen = sizeof clnt_addr;
361           fd = accept (server, (struct sockaddr *) &clnt_addr, &addrlen);
362           if (fd == -1)
363             {
364               printf ("[accepting connection failed: %s]\n", strerror (errno));
365             }
366           else if (fd >= FD_SETSIZE)
367             {
368               close (fd);
369               printf ("[connection request denied: too many connections]\n");
370             }
371           else 
372             {
373               for (client = client_list; client && client->fd != -1;
374                    client = client->next)
375                 ;
376               if (!client)
377                 {
378                   client = xcalloc (1, sizeof *client);
379                   client->next = client_list;
380                   client_list = client;
381                 }
382               client->fd = fd;
383               printf ("[client at fd %d connected]\n", client->fd);
384             }
385         }
386       for (client = client_list; client; client = client->next)
387         if (client->fd != -1 && FD_ISSET (client->fd, &rfds))
388           {
389             char line[256];
390             int n;
391             
392             n = read (client->fd, line, sizeof line - 1);
393             if (n < 0)
394               {
395                 int save_errno = errno;
396                 print_line (client, NULL); /* flush */
397                 printf ("[client at fd %d read error: %s]\n",
398                         client->fd, strerror (save_errno));
399                 close (client->fd);
400                 client->fd = -1;
401               }
402             else if (!n) 
403               {
404                 print_line (client, NULL); /* flush */
405                 close (client->fd);
406                 printf ("[client at fd %d disconnected]\n", client->fd);
407                 client->fd = -1;
408               }
409             else
410               {
411                 line[n] = 0;
412                 print_line (client, line);
413               }
414           }
415     }
416
417   return 0;
418 }
419
420
421 /*
422 Local Variables:
423 compile-command: "gcc -Wall -g -o watchgnupg watchgnupg.c"
424 End:
425 */