Fix bug 901.
[gnupg.git] / tools / sockprox.c
1 /* sockprox - Proxy for local sockets with logging facilities
2  *      Copyright (C) 2007 g10 Code GmbH.
3  *
4  * sockprox is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * sockprox is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, see <http://www.gnu.org/licenses/>.
16  */
17
18 /* Hacked by Moritz Schulte <moritz@g10code.com>. 
19
20    Usage example:
21
22    Run a server which binds to a local socket.  For example,
23    gpg-agent.  gpg-agent's local socket is specified with --server.
24    sockprox opens a new local socket (here "mysock"); the whole
25    traffic between server and client is written to "/tmp/prot" in this
26    case.
27   
28      ./sockprox --server /tmp/gpg-PKdD8r/S.gpg-agent.ssh \
29                 --listen mysock --protocol /tmp/prot
30   
31    Then, redirect your ssh-agent client to sockprox by setting
32    SSH_AUTH_SOCK to "mysock".
33 */
34
35
36
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <getopt.h>
41 #include <stddef.h>
42 #include <errno.h>
43 #include <string.h>
44 #include <sys/socket.h>
45 #include <sys/un.h>
46 #include <fcntl.h>
47 #include <assert.h>
48 #include <pthread.h>
49
50 struct opt
51 {
52   char *protocol_file;
53   char *server_spec;
54   char *listen_spec;
55   int verbose;
56 };
57
58 struct opt opt = { NULL, NULL, NULL, 0 };
59
60 struct thread_data
61 {
62   int client_sock;
63   FILE *protocol_file;
64 };
65
66 \f
67
68 static int
69 create_server_socket (const char *filename, int *new_sock)
70 {
71   struct sockaddr_un name;
72   size_t size;
73   int sock;
74   int ret;
75   int err;
76
77   /* Create the socket. */
78   sock = socket (PF_LOCAL, SOCK_STREAM, 0);
79   if (sock < 0)
80     {
81       err = errno;
82       goto out;
83     }
84
85   /* Bind a name to the socket. */
86   name.sun_family = AF_LOCAL;
87   strncpy (name.sun_path, filename, sizeof (name.sun_path));
88   name.sun_path[sizeof (name.sun_path) - 1] = '\0';
89
90   size = SUN_LEN (&name);
91
92   remove (filename);
93
94   ret = bind (sock, (struct sockaddr *) &name, size);
95   if (ret < 0)
96     {
97       err = errno;
98       goto out;
99     }
100
101   ret = listen (sock, 2);
102   if (ret < 0)
103     {
104       err = errno;
105       goto out;
106     }
107
108   *new_sock = sock;
109   err = 0;
110
111  out:
112
113   return err;
114 }
115
116 static int
117 connect_to_socket (const char *filename, int *new_sock)
118 {
119   struct sockaddr_un srvr_addr;
120   size_t len;
121   int sock;
122   int ret;
123   int err;
124
125   sock = socket (PF_LOCAL, SOCK_STREAM, 0);
126   if (sock == -1)
127     {
128       err = errno;
129       goto out;
130     }
131
132   memset (&srvr_addr, 0, sizeof srvr_addr);
133   srvr_addr.sun_family = AF_LOCAL;
134   strncpy (srvr_addr.sun_path, filename, sizeof (srvr_addr.sun_path) - 1);
135   srvr_addr.sun_path[sizeof (srvr_addr.sun_path) - 1] = 0;
136   len = SUN_LEN (&srvr_addr);
137
138   ret = connect (sock, (struct sockaddr *) &srvr_addr, len);
139   if (ret == -1)
140     {
141       close (sock);
142       err = errno;
143       goto out;
144     }
145
146   *new_sock = sock;
147   err = 0;
148
149  out:
150
151   return err;
152 }
153
154 \f
155
156 static int
157 log_data (unsigned char *data, size_t length,
158           FILE *from, FILE *to, FILE *protocol)
159 {
160   unsigned int i;
161   int ret;
162   int err;
163
164   flockfile (protocol);
165   fprintf (protocol, "%i -> %i: ", fileno (from), fileno (to));
166   for (i = 0; i < length; i++)
167     fprintf (protocol, "%02X", data[i]);
168   fprintf (protocol, "\n");
169   funlockfile (protocol);
170
171   ret = fflush (protocol);
172   if (ret == EOF)
173     err = errno;
174   else
175     err = 0;
176
177   return err;
178 }
179
180 static int
181 transfer_data (FILE *from, FILE *to, FILE *protocol)
182 {
183   unsigned char buffer[BUFSIZ];
184   size_t len, written;
185   int err;
186   int ret;
187
188   err = 0;
189
190   while (1)
191     {
192       len = fread (buffer, 1, sizeof (buffer), from);
193       if (len == 0)
194         break;
195
196       err = log_data (buffer, len, from, to, protocol);
197       if (err)
198         break;
199
200       written = fwrite (buffer, 1, len, to);
201       if (written != len)
202         {
203           err = errno;
204           break;
205         }
206
207       ret = fflush (to);
208       if (ret == EOF)
209         {
210           err = errno;
211           break;
212         }
213
214       if (ferror (from))
215         break;
216     }
217
218   return err;
219 }
220
221
222 static int
223 io_loop (FILE *client, FILE *server, FILE *protocol)
224 {
225   fd_set active_fd_set, read_fd_set;
226   int ret;
227   int err;
228
229   FD_ZERO (&active_fd_set);
230   FD_SET (fileno (client), &active_fd_set);
231   FD_SET (fileno (server), &active_fd_set);
232
233   err = 0;
234
235   while (1)
236     {
237       read_fd_set = active_fd_set;
238
239       /* FIXME: eof?  */
240
241       ret = select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL);
242       if (ret < 0)
243         {
244           err = errno;
245           break;
246         }
247
248       if (FD_ISSET (fileno (client), &read_fd_set))
249         {
250           if (feof (client))
251             break;
252
253           /* Forward data from client to server.  */
254           err = transfer_data (client, server, protocol);
255         }
256       else if (FD_ISSET (fileno (server), &read_fd_set))
257         {
258           if (feof (server))
259             break;
260
261           /* Forward data from server to client.  */
262           err = transfer_data (server, client, protocol);
263         }
264
265       if (err)
266         break;
267     }
268
269   return err;
270 }
271
272 \f
273
274
275 /* Set the `O_NONBLOCK' flag of DESC if VALUE is nonzero,
276    or clear the flag if VALUE is 0.
277    Return 0 on success, or -1 on error with `errno' set. */
278
279 int
280 set_nonblock_flag (int desc, int value)
281 {
282   int oldflags = fcntl (desc, F_GETFL, 0);
283   int err;
284   int ret;
285
286   /* If reading the flags failed, return error indication now. */
287   if (oldflags == -1)
288     return -1;
289   /* Set just the flag we want to set. */
290   if (value != 0)
291     oldflags |= O_NONBLOCK;
292   else
293     oldflags &= ~O_NONBLOCK;
294   /* Store modified flag word in the descriptor. */
295
296   ret = fcntl (desc, F_SETFL, oldflags);
297   if (ret == -1)
298     err = errno;
299   else
300     err = 0;
301
302   return err;
303 }
304
305 \f
306
307 void *
308 serve_client (void *data)
309 {
310   struct thread_data *thread_data = data;
311   int client_sock = thread_data->client_sock;
312   int server_sock;
313   FILE *protocol = thread_data->protocol_file;
314   FILE *client;
315   FILE *server;
316   int err;
317
318   client = NULL;
319   server = NULL;
320
321   /* Connect to server.  */
322   err = connect_to_socket (opt.server_spec, &server_sock);
323   if (err)
324     goto out;
325
326   /* Set IO mode to nonblicking.  */
327   err = set_nonblock_flag (server_sock, 1);
328   if (err)
329     goto out;
330
331   client = fdopen (client_sock, "r+");
332   if (! client)
333     {
334       err = errno;
335       goto out;
336     }
337
338   server = fdopen (server_sock, "r+");
339   if (! server)
340     {
341       err = errno;
342       goto out;
343     }
344
345   err = io_loop (client, server, protocol);
346
347  out:
348
349   if (client)
350     fclose (client);
351   else
352     close (client_sock);
353
354   if (server)
355     fclose (server);
356   else
357     close (server_sock);
358
359   free (data);
360
361   return NULL;
362 }
363
364 static int
365 run_proxy (void)
366 {
367   int client_sock;
368   int my_sock;
369   int err;
370   struct sockaddr_un clientname;
371   size_t size;
372   pthread_t  mythread;
373   struct thread_data *thread_data;
374   FILE *protocol_file;
375   pthread_attr_t thread_attr;
376
377   protocol_file = NULL;
378
379   err = pthread_attr_init (&thread_attr);
380   if (err)
381     goto out;
382
383   err = pthread_attr_setdetachstate (&thread_attr, PTHREAD_CREATE_DETACHED);
384   if (err)
385     goto out;
386
387   if (opt.protocol_file)
388     {
389       protocol_file = fopen (opt.protocol_file, "a");
390       if (! protocol_file)
391         {
392           err = errno;
393           goto out;
394         }
395     }
396   else
397     protocol_file = stdout;
398
399   err = create_server_socket (opt.listen_spec, &my_sock);
400   if (err)
401     goto out;
402
403   while (1)
404     {
405       /* Accept new client.  */
406       size = sizeof (clientname);
407       client_sock = accept (my_sock,
408                             (struct sockaddr *) &clientname,
409                             &size);
410       if (client_sock < 0)
411         {
412           err = errno;
413           break;
414         }
415
416       /* Set IO mode to nonblicking.  */
417       err = set_nonblock_flag (client_sock, 1);
418       if (err)
419         {
420           close (client_sock);
421           break;
422         }
423
424       /* Got new client -> handle in new process.  */
425
426       thread_data = malloc (sizeof (*thread_data));
427       if (! thread_data)
428         {
429           err = errno;
430           break;
431         }
432       thread_data->client_sock = client_sock;
433       thread_data->protocol_file = protocol_file;
434
435       err = pthread_create (&mythread, &thread_attr, serve_client, thread_data);
436       if (err)
437         break;
438     }
439   if (err)
440     goto out;
441
442   /* ? */
443
444  out:
445   
446   pthread_attr_destroy (&thread_attr);
447   fclose (protocol_file);       /* FIXME, err checking.  */
448
449   return err;
450 }
451
452 \f
453
454 static int
455 print_help (int ret)
456 {
457   printf ("Usage: sockprox [options] "
458           "--server SERVER-SOCKET --listen PROXY-SOCKET\n");
459   exit (ret);
460 }
461
462 int
463 main (int argc, char **argv)
464 {
465   struct option long_options[] =
466     {
467       { "help",     no_argument,       0,            'h' },
468       { "verbose",  no_argument,       &opt.verbose, 1   },
469       { "protocol", required_argument, 0,            'p' },
470       { "server",   required_argument, 0,            's' },
471       { "listen",   required_argument, 0,            'l' },
472       { 0, 0, 0, 0 }
473     };
474   int ret;
475   int err;
476   int c;
477
478   while (1)
479     {
480       int opt_idx = 0;
481       c = getopt_long (argc, argv, "hvp:s:l:",
482                        long_options, &opt_idx);
483
484       if (c == -1)
485         break;
486
487       switch (c)
488         {
489         case 0:
490           if (long_options[opt_idx].flag)
491             break;
492           printf ("option %s", long_options[opt_idx].name);
493           if (optarg)
494             printf (" with arg %s", optarg);
495           printf ("\n");
496           break;
497
498         case 'p':
499           opt.protocol_file = optarg;
500           break;
501
502         case 's':
503           opt.server_spec = optarg;
504           break;
505
506         case 'l':
507           opt.listen_spec = optarg;
508           break;
509
510         case 'v':
511           opt.verbose = 1;
512           break;
513
514         case 'h':
515           print_help (EXIT_SUCCESS);
516           break;
517
518         default:
519           abort ();
520         }
521     }
522
523   if (opt.verbose)
524     {
525       printf ("server: %s\n", opt.server_spec ? opt.server_spec : "");
526       printf ("listen: %s\n", opt.listen_spec ? opt.listen_spec : "");
527       printf ("protocol: %s\n", opt.protocol_file ? opt.protocol_file : "");
528     }
529
530   if (! (opt.server_spec && opt.listen_spec))
531     print_help (EXIT_FAILURE);
532
533   err = run_proxy ();
534   if (err)
535     {
536       fprintf (stderr, "run_proxy() failed: %s\n", strerror (err));
537       ret = EXIT_FAILURE;
538     }
539   else
540     /* ? */
541     ret = EXIT_SUCCESS;
542
543   return ret;
544 }
545
546
547 /*
548 Local Variables:
549 compile-command: "cc -Wall -g -o sockprox sockprox.c -lpthread"
550 End:
551 */