2005-07-04 Marcus Brinkmann <marcus@g10code.de>
[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 2 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, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stddef.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <stdarg.h>
30 #include <assert.h>
31 #include <unistd.h>
32 #include <sys/socket.h>
33 #include <sys/un.h>
34 #include <fcntl.h>
35 #include <time.h>
36
37 #define PGM "watchgnupg"
38
39 /* Allow for a standalone build. */
40 #ifdef VERSION
41 #define MYVERSION_LINE PGM " (GnuPG) " VERSION
42 #define BUGREPORT_LINE "\nReport bugs to <bug-gnupg@gnu.org>.\n"
43 #else
44 #define MYVERSION_LINE PGM 
45 #define BUGREPORT_LINE ""
46 #endif
47
48 #ifndef PF_LOCAL
49 # ifdef PF_UNIX
50 #  define PF_LOCAL PF_UNIX
51 # else
52 #  define PF_LOCAL AF_UNIX
53 # endif
54 # ifndef AF_LOCAL
55 #  define AF_LOCAL AF_UNIX
56 # endif
57 #endif
58
59
60 static int verbose;
61
62
63 static void
64 die (const char *format, ...)
65 {
66   va_list arg_ptr;
67
68   fflush (stdout);
69   fprintf (stderr, "%s: ", PGM);
70
71   va_start (arg_ptr, format);
72   vfprintf (stderr, format, arg_ptr);
73   va_end (arg_ptr);
74   putc ('\n', stderr);
75
76   exit (1);
77 }
78
79
80 /* static void */
81 /* err (const char *format, ...) */
82 /* { */
83 /*   va_list arg_ptr; */
84
85 /*   fflush (stdout); */
86 /*   fprintf (stderr, "%s: ", PGM); */
87
88 /*   va_start (arg_ptr, format); */
89 /*   vfprintf (stderr, format, arg_ptr); */
90 /*   va_end (arg_ptr); */
91 /*   putc ('\n', stderr); */
92 /* } */
93
94 static void *
95 xmalloc (size_t n)
96 {
97   void *p = malloc (n);
98   if (!p)
99     die ("out of core");
100   return p;
101 }
102
103 static void *
104 xcalloc (size_t n, size_t m)
105 {
106   void *p = calloc (n, m);
107   if (!p)
108     die ("out of core");
109   return p;
110 }
111
112 static void *
113 xrealloc (void *old, size_t n)
114 {
115   void *p = realloc (old, n);
116   if (!p)
117     die ("out of core");
118   return p;
119 }
120     
121
122 struct client_s {
123   struct client_s *next;
124   int fd;
125   size_t size;  /* Allocated size of buffer. */
126   size_t len;   /* Current length of buffer. */
127   unsigned char *buffer; /* Buffer to with data already read. */
128   
129 };
130 typedef struct client_s *client_t;
131
132
133
134 static void
135 print_fd_and_time (int fd)
136 {
137   struct tm *tp;
138   time_t atime = time (NULL);
139   
140   tp = localtime (&atime);
141   printf ("%3d - %04d-%02d-%02d %02d:%02d:%02d ",
142           fd,
143           1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
144           tp->tm_hour, tp->tm_min, tp->tm_sec );
145 }
146
147
148 /* Print LINE for the client identified by C.  Calling this function
149    witgh LINE set to NULL, will flush the internal buffer. */
150 static void
151 print_line (client_t c, const char *line)
152 {
153   const char *s;
154   size_t n;
155
156   if (!line)
157     {
158       if (c->buffer && c->len)
159         {
160           print_fd_and_time (c->fd);
161           fwrite (c->buffer, c->len, 1, stdout); 
162           putc ('\n', stdout);
163           c->len = 0;
164         }
165       return;
166     }
167
168   while ((s = strchr (line, '\n')))
169     {
170       print_fd_and_time (c->fd);
171       if (c->buffer && c->len)
172         {
173           fwrite (c->buffer, c->len, 1, stdout); 
174           c->len = 0;
175         }
176       fwrite (line, s - line + 1, 1, stdout); 
177       line = s + 1;
178     }
179   n = strlen (line);
180   if (n)
181     {
182       if (c->len + n >= c->size)
183         {
184           c->size += ((n + 255) & ~255);
185           c->buffer = (c->buffer
186                        ? xrealloc (c->buffer, c->size)
187                        : xmalloc (c->size));
188         }
189       memcpy (c->buffer + c->len, line, n);
190       c->len += n;
191     }
192 }
193
194
195 static void
196 print_version (int with_help)
197 {
198   fputs (MYVERSION_LINE "\n"
199          "Copyright (C) 2004 Free Software Foundation, Inc.\n"
200          "This program comes with ABSOLUTELY NO WARRANTY.\n"
201          "This is free software, and you are welcome to redistribute it\n"
202          "under certain conditions. See the file COPYING for details.\n",
203          stdout);
204         
205   if (with_help)
206     fputs ("\n"
207           "Usage: " PGM " [OPTIONS] SOCKETNAME\n"
208           "Open the local socket SOCKETNAME and display log messages\n"
209           "\n"
210           "  --force     delete an already existing socket file\n"
211           "  --verbose   enable extra informational output\n"
212           "  --version   print version of the program and exit\n"
213           "  --help      display this help and exit\n"
214           BUGREPORT_LINE, stdout );
215   
216   exit (0);
217 }
218
219 int 
220 main (int argc, char **argv)
221 {
222   int last_argc = -1;
223   int force = 0;
224
225   struct sockaddr_un srvr_addr;
226   socklen_t addrlen;
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     }          
258  
259   if (argc != 1)
260     {
261       fprintf (stderr, "usage: " PGM " socketname\n");
262       exit (1);
263     }
264
265
266   if (verbose)
267     fprintf (stderr, "opening socket `%s'\n", *argv);
268
269   setvbuf (stdout, NULL, _IOLBF, 0);
270
271   server = socket (PF_LOCAL, SOCK_STREAM, 0);
272   if (server == -1)
273     die ("socket() failed: %s\n", strerror (errno));
274
275   /* We better set the listening socket to non-blocking so that we
276      don't get bitten by race conditions in accept.  The should not
277      happen for Unix Domain sockets but well, shit happens. */
278   flags = fcntl (server, F_GETFL, 0);
279   if (flags == -1)
280     die ("fcntl (F_GETFL) failed: %s\n", strerror (errno));
281   if ( fcntl (server, F_SETFL, (flags | O_NONBLOCK)) == -1)
282     die ("fcntl (F_SETFL) failed: %s\n", strerror (errno));
283   
284
285   memset (&srvr_addr, 0, sizeof srvr_addr);
286   srvr_addr.sun_family = AF_LOCAL;
287   strncpy (srvr_addr.sun_path, *argv, sizeof (srvr_addr.sun_path) - 1);
288   srvr_addr.sun_path[sizeof (srvr_addr.sun_path) - 1] = 0;
289   addrlen = (offsetof (struct sockaddr_un, sun_path)
290              + strlen (srvr_addr.sun_path) + 1);
291
292   
293  again:
294   if (bind (server, (struct sockaddr *) &srvr_addr, addrlen))
295     { 
296       if (errno == EADDRINUSE && force)
297         {
298           force = 0;
299           remove (srvr_addr.sun_path);
300           goto again;
301         }
302       die ("bind to `%s' failed: %s\n", *argv, strerror (errno));
303     }
304
305   if (listen (server, 5))
306     die ("listen failed: %s\n", strerror (errno));
307
308   for (;;)
309     {
310       fd_set rfds;
311       int max_fd;
312       client_t client;
313
314       /* Usually we don't have that many connections, thus it is okay
315          to set them allways from scratch and don't maintain an active
316          fd_set. */
317       FD_ZERO (&rfds);
318       FD_SET (server, &rfds);
319       max_fd = server;
320       for (client = client_list; client; client = client->next)
321         if (client->fd != -1)
322           {
323             FD_SET (client->fd, &rfds);
324             if (client->fd > max_fd)
325               max_fd = client->fd;
326           }
327
328       if (select (max_fd + 1, &rfds, NULL, NULL, NULL) <= 0)
329         continue;  /* Ignore any errors. */
330
331       if (FD_ISSET (server, &rfds)) /* New connection. */
332         { 
333           struct sockaddr_un clnt_addr;
334           int fd;
335
336           addrlen = sizeof clnt_addr;
337           fd = accept (server, (struct sockaddr *) &clnt_addr, &addrlen);
338           if (fd == -1)
339             {
340               printf ("[accepting connection failed: %s]\n", strerror (errno));
341             }
342           else if (fd >= FD_SETSIZE)
343             {
344               close (fd);
345               printf ("[connection request denied: too many connections]\n");
346             }
347           else 
348             {
349               for (client = client_list; client && client->fd != -1;
350                    client = client->next)
351                 ;
352               if (!client)
353                 {
354                   client = xcalloc (1, sizeof *client);
355                   client->next = client_list;
356                   client_list = client;
357                 }
358               client->fd = fd;
359               printf ("[client at fd %d connected]\n", client->fd);
360             }
361         }
362       for (client = client_list; client; client = client->next)
363         if (client->fd != -1 && FD_ISSET (client->fd, &rfds))
364           {
365             char line[256];
366             int n;
367             
368             n = read (client->fd, line, sizeof line - 1);
369             if (n < 0)
370               {
371                 int save_errno = errno;
372                 print_line (client, NULL); /* flush */
373                 printf ("[client at fd %d read error: %s]\n",
374                         client->fd, strerror (save_errno));
375                 close (client->fd);
376                 client->fd = -1;
377               }
378             else if (!n) 
379               {
380                 print_line (client, NULL); /* flush */
381                 close (client->fd);
382                 printf ("[client at fd %d disconnected]\n", client->fd);
383                 client->fd = -1;
384               }
385             else
386               {
387                 line[n] = 0;
388                 print_line (client, line);
389               }
390           }
391     }
392
393   return 0;
394 }
395
396
397 /*
398 Local Variables:
399 compile-command: "gcc -Wall -g -o watchgnupg watchgnupg.c"
400 End:
401 */