missing check
[oweals/gnunet.git] / src / exit / gnunet-helper-exit.c
1 /*
2      This file is part of GNUnet.
3      (C) 2010, 2011, 2012 Christian Grothoff
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file exit/gnunet-helper-exit.c 
23  *
24  * @brief the helper for exit nodes. Opens a virtual
25  * network-interface, sends data received on the if to stdout, sends
26  * data received on stdin to the interface.  The code also enables
27  * IPv4/IPv6 forwarding and NAT on the current system (the latter on
28  * an interface specified on the command-line); these changes to the
29  * network configuration are NOT automatically undone when the program
30  * is stopped (this is because we cannot be sure that some other
31  * application didn't enable them before or after us; also, these
32  * changes should be mostly harmless as it simply turns the system
33  * into a router).
34  *
35  * @author Philipp Tölke
36  * @author Christian Grothoff
37  *
38  * The following list of people have reviewed this code and considered
39  * it safe since the last modification (if you reviewed it, please
40  * have your name added to the list):
41  *
42  * - Philipp Tölke
43  */
44 #include "platform.h"
45 #include <linux/if_tun.h>
46
47 /**
48  * Need 'struct GNUNET_MessageHeader'.
49  */
50 #include "gnunet_common.h"
51
52 /**
53  * Need VPN message types.
54  */
55 #include "gnunet_protocols.h"
56
57 /**
58  * Should we print (interesting|debug) messages that can happen during
59  * normal operation?
60  */
61 #define DEBUG GNUNET_NO
62
63 /**
64  * Maximum size of a GNUnet message (GNUNET_SERVER_MAX_MESSAGE_SIZE)
65  */
66 #define MAX_SIZE 65536
67
68 /**
69  * Path to 'sysctl' binary.
70  */
71 static const char *sbin_sysctl;
72
73 /**
74  * Path to 'iptables' binary.
75  */
76 static const char *sbin_iptables;
77
78
79 #ifndef _LINUX_IN6_H
80 /**
81  * This is in linux/include/net/ipv6.h, but not always exported...
82  */
83 struct in6_ifreq
84 {
85   struct in6_addr ifr6_addr;
86   uint32_t ifr6_prefixlen; /* __u32 in the original */
87   int ifr6_ifindex;
88 };
89 #endif
90
91
92 /**
93  * Open '/dev/null' and make the result the given
94  * file descriptor.
95  *
96  * @param target_fd desired FD to point to /dev/null
97  * @param flags open flags (O_RDONLY, O_WRONLY)
98  */
99 static void
100 open_dev_null (int target_fd,
101                int flags)
102 {
103   int fd;
104
105   fd = open ("/dev/null", flags);
106   if (-1 == fd)
107     abort ();
108   if (fd == target_fd)
109     return;
110   if (-1 == dup2 (fd, target_fd))
111   {    
112     (void) close (fd);
113     abort ();
114   }
115   (void) close (fd);
116 }
117
118
119 /**
120  * Run the given command and wait for it to complete.
121  * 
122  * @param file name of the binary to run
123  * @param cmd command line arguments (as given to 'execv')
124  * @return 0 on success, 1 on any error
125  */
126 static int
127 fork_and_exec (const char *file, 
128                char *const cmd[])
129 {
130   int status;
131   pid_t pid;
132   pid_t ret;
133
134   pid = fork ();
135   if (-1 == pid)
136   {
137     fprintf (stderr, 
138              "fork failed: %s\n", 
139              strerror (errno));
140     return 1;
141   }
142   if (0 == pid)
143   {
144     /* we are the child process */
145     /* close stdin/stdout to not cause interference
146        with the helper's main protocol! */
147     (void) close (0); 
148     open_dev_null (0, O_RDONLY);
149     (void) close (1); 
150     open_dev_null (1, O_WRONLY);
151     (void) execv (file, cmd);
152     /* can only get here on error */
153     fprintf (stderr, 
154              "exec `%s' failed: %s\n", 
155              file,
156              strerror (errno));
157     _exit (1);
158   }
159   /* keep running waitpid as long as the only error we get is 'EINTR' */
160   while ( (-1 == (ret = waitpid (pid, &status, 0))) &&
161           (errno == EINTR) ); 
162   if (-1 == ret)
163   {
164     fprintf (stderr, 
165              "waitpid failed: %s\n", 
166              strerror (errno));
167     return 1;
168   }
169   if (! (WIFEXITED (status) && (0 == WEXITSTATUS (status))))
170     return 1;
171   /* child process completed and returned success, we're happy */
172   return 0;
173 }
174
175
176 /**
177  * Creates a tun-interface called dev;
178  *
179  * @param dev is asumed to point to a char[IFNAMSIZ]
180  *        if *dev == '\\0', uses the name supplied by the kernel;
181  * @return the fd to the tun or -1 on error
182  */
183 static int
184 init_tun (char *dev)
185 {
186   struct ifreq ifr;
187   int fd;
188
189   if (NULL == dev)
190   {
191     errno = EINVAL;
192     return -1;
193   }
194
195   if (-1 == (fd = open ("/dev/net/tun", O_RDWR)))
196   {
197     fprintf (stderr, "Error opening `%s': %s\n", "/dev/net/tun",
198              strerror (errno));
199     return -1;
200   }
201
202   if (fd >= FD_SETSIZE)
203   {
204     fprintf (stderr, "File descriptor to large: %d", fd);
205     (void) close (fd);
206     return -1;
207   }
208
209   memset (&ifr, 0, sizeof (ifr));
210   ifr.ifr_flags = IFF_TUN;
211
212   if ('\0' != *dev)
213     strncpy (ifr.ifr_name, dev, IFNAMSIZ);
214
215   if (-1 == ioctl (fd, TUNSETIFF, (void *) &ifr))
216   {
217     fprintf (stderr, 
218              "Error with ioctl on `%s': %s\n", "/dev/net/tun",
219              strerror (errno));
220     (void) close (fd);
221     return -1;
222   }
223   strcpy (dev, ifr.ifr_name);
224   return fd;
225 }
226
227
228 /**
229  * @brief Sets the IPv6-Address given in address on the interface dev
230  *
231  * @param dev the interface to configure
232  * @param address the IPv6-Address
233  * @param prefix_len the length of the network-prefix
234  */
235 static void
236 set_address6 (const char *dev, const char *address, unsigned long prefix_len)
237 {
238   struct ifreq ifr;
239   struct sockaddr_in6 sa6;
240   int fd;
241   struct in6_ifreq ifr6;
242
243   /*
244    * parse the new address
245    */
246   memset (&sa6, 0, sizeof (struct sockaddr_in6));
247   sa6.sin6_family = AF_INET6;
248   if (1 != inet_pton (AF_INET6, address, &sa6.sin6_addr))
249   {
250     fprintf (stderr, "Failed to parse address `%s': %s\n", address,
251              strerror (errno));
252     exit (1);
253   }
254
255   if (-1 == (fd = socket (PF_INET6, SOCK_DGRAM, 0)))
256   {
257     fprintf (stderr, "Error creating socket: %s\n", strerror (errno));    
258     exit (1);
259   }
260
261   memset (&ifr, 0, sizeof (struct ifreq));
262   /*
263    * Get the index of the if
264    */
265   strncpy (ifr.ifr_name, dev, IFNAMSIZ);
266   if (-1 == ioctl (fd, SIOGIFINDEX, &ifr))
267   {
268     fprintf (stderr, "ioctl failed at %d: %s\n", __LINE__, strerror (errno));
269     (void) close (fd);
270     exit (1);
271   }
272
273   memset (&ifr6, 0, sizeof (struct in6_ifreq));
274   ifr6.ifr6_addr = sa6.sin6_addr;
275   ifr6.ifr6_ifindex = ifr.ifr_ifindex;
276   ifr6.ifr6_prefixlen = prefix_len;
277
278   /*
279    * Set the address
280    */
281   if (-1 == ioctl (fd, SIOCSIFADDR, &ifr6))
282   {
283     fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
284              strerror (errno));
285     (void) close (fd);
286     exit (1);
287   }
288
289   /*
290    * Get the flags
291    */
292   if (-1 == ioctl (fd, SIOCGIFFLAGS, &ifr))
293   {
294     fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
295              strerror (errno));
296     (void) close (fd);
297     exit (1);
298   }
299
300   /*
301    * Add the UP and RUNNING flags
302    */
303   ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
304   if (-1 == ioctl (fd, SIOCSIFFLAGS, &ifr))
305   {
306     fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
307              strerror (errno));
308     (void) close (fd);
309     exit (1);
310   }
311
312   if (0 != close (fd))
313   {
314     fprintf (stderr, "close failed: %s\n", strerror (errno));
315     exit (1);
316   }
317 }
318
319
320 /**
321  * @brief Sets the IPv4-Address given in address on the interface dev
322  *
323  * @param dev the interface to configure
324  * @param address the IPv4-Address
325  * @param mask the netmask
326  */
327 static void
328 set_address4 (const char *dev, const char *address, const char *mask)
329 {
330   int fd;
331   struct sockaddr_in *addr;
332   struct ifreq ifr;
333
334   memset (&ifr, 0, sizeof (struct ifreq));
335   addr = (struct sockaddr_in *) &(ifr.ifr_addr);
336   addr->sin_family = AF_INET;
337
338   /*
339    * Parse the address
340    */
341   if (1 != inet_pton (AF_INET, address, &addr->sin_addr.s_addr))
342   {
343     fprintf (stderr, "Failed to parse address `%s': %s\n", address,
344              strerror (errno));
345     exit (1);
346   }
347
348   if (-1 == (fd = socket (PF_INET, SOCK_DGRAM, 0)))
349   {
350     fprintf (stderr, "Error creating socket: %s\n", strerror (errno));
351     exit (1);
352   }
353
354   strncpy (ifr.ifr_name, dev, IFNAMSIZ);
355
356   /*
357    * Set the address
358    */
359   if (-1 == ioctl (fd, SIOCSIFADDR, &ifr))
360   {
361     fprintf (stderr, "ioctl failed at %d: %s\n", __LINE__, strerror (errno));
362     (void) close (fd);
363     exit (1);
364   }
365
366   /*
367    * Parse the netmask
368    */
369   addr = (struct sockaddr_in *) &(ifr.ifr_netmask);
370   if (1 != inet_pton (AF_INET, mask, &addr->sin_addr.s_addr))
371   {
372     fprintf (stderr, "Failed to parse address `%s': %s\n", mask,
373              strerror (errno));
374     (void) close (fd);
375     exit (1);
376   }
377
378   /*
379    * Set the netmask
380    */
381   if (-1 == ioctl (fd, SIOCSIFNETMASK, &ifr))
382   {
383     fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
384              strerror (errno));
385     (void) close (fd);
386     exit (1);
387   }
388
389   /*
390    * Get the flags
391    */
392   if (-1 == ioctl (fd, SIOCGIFFLAGS, &ifr))
393   {
394     fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
395              strerror (errno));
396     (void) close (fd);
397     exit (1);
398   }
399
400   /*
401    * Add the UP and RUNNING flags
402    */
403   ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
404   if (-1 == ioctl (fd, SIOCSIFFLAGS, &ifr))
405   {
406     fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__,
407              strerror (errno));
408     (void) close (fd);
409     exit (1);
410   }
411
412   if (0 != close (fd))
413   {
414     fprintf (stderr, "close failed: %s\n", strerror (errno));
415     (void) close (fd);
416     exit (1);
417   }
418 }
419
420
421 /**
422  * Start forwarding to and from the tunnel.
423  *
424  * @param fd_tun tunnel FD
425  */
426 static void
427 run (int fd_tun)
428 {
429   /*
430    * The buffer filled by reading from fd_tun
431    */
432   unsigned char buftun[MAX_SIZE];
433   ssize_t buftun_size = 0;
434   unsigned char *buftun_read = NULL;
435
436   /*
437    * The buffer filled by reading from stdin
438    */
439   unsigned char bufin[MAX_SIZE];
440   ssize_t bufin_size = 0;
441   size_t bufin_rpos = 0;
442   unsigned char *bufin_read = NULL;
443
444   fd_set fds_w;
445   fd_set fds_r;
446
447   /* read refers to reading from fd_tun, writing to stdout */
448   int read_open = 1;
449
450   /* write refers to reading from stdin, writing to fd_tun */
451   int write_open = 1;
452
453   while ((1 == read_open) || (1 == write_open))
454   {
455     FD_ZERO (&fds_w);
456     FD_ZERO (&fds_r);
457
458     /*
459      * We are supposed to read and the buffer is empty
460      * -> select on read from tun
461      */
462     if (read_open && (0 == buftun_size))
463       FD_SET (fd_tun, &fds_r);
464
465     /*
466      * We are supposed to read and the buffer is not empty
467      * -> select on write to stdout
468      */
469     if (read_open && (0 != buftun_size))
470       FD_SET (1, &fds_w);
471
472     /*
473      * We are supposed to write and the buffer is empty
474      * -> select on read from stdin
475      */
476     if (write_open && (NULL == bufin_read))
477       FD_SET (0, &fds_r);
478
479     /*
480      * We are supposed to write and the buffer is not empty
481      * -> select on write to tun
482      */
483     if (write_open && (NULL != bufin_read))
484       FD_SET (fd_tun, &fds_w);
485
486     int r = select (fd_tun + 1, &fds_r, &fds_w, NULL, NULL);
487
488     if (-1 == r)
489     {
490       if (EINTR == errno)
491         continue;
492       fprintf (stderr, "select failed: %s\n", strerror (errno));
493       exit (1);
494     }
495
496     if (r > 0)
497     {
498       if (FD_ISSET (fd_tun, &fds_r))
499       {
500         buftun_size =
501             read (fd_tun, buftun + sizeof (struct GNUNET_MessageHeader),
502                   MAX_SIZE - sizeof (struct GNUNET_MessageHeader));
503         if (-1 == buftun_size)
504         {
505           fprintf (stderr, "read-error: %s\n", strerror (errno));
506           shutdown (fd_tun, SHUT_RD);
507           shutdown (1, SHUT_WR);
508           read_open = 0;
509           buftun_size = 0;
510         }
511         else if (0 == buftun_size)
512         {
513 #if DEBUG
514           fprintf (stderr, "EOF on tun\n");
515 #endif
516           shutdown (fd_tun, SHUT_RD);
517           shutdown (1, SHUT_WR);
518           read_open = 0;
519           buftun_size = 0;
520         }
521         else
522         {
523           buftun_read = buftun;
524           struct GNUNET_MessageHeader *hdr =
525               (struct GNUNET_MessageHeader *) buftun;
526           buftun_size += sizeof (struct GNUNET_MessageHeader);
527           hdr->type = htons (GNUNET_MESSAGE_TYPE_VPN_HELPER);
528           hdr->size = htons (buftun_size);
529         }
530       }
531       else if (FD_ISSET (1, &fds_w))
532       {
533         ssize_t written = write (1, buftun_read, buftun_size);
534
535         if (-1 == written)
536         {
537 #if !DEBUG
538           if (errno != EPIPE)
539 #endif
540             fprintf (stderr, "write-error to stdout: %s\n", strerror (errno));
541           shutdown (fd_tun, SHUT_RD);
542           shutdown (1, SHUT_WR);
543           read_open = 0;
544           buftun_size = 0;
545         }
546         else if (0 == written)
547         {
548           fprintf (stderr, "write returned 0!?\n");
549           exit (1);
550         }
551         else
552         {
553           buftun_size -= written;
554           buftun_read += written;
555         }
556       }
557
558       if (FD_ISSET (0, &fds_r))
559       {
560         bufin_size = read (0, bufin + bufin_rpos, MAX_SIZE - bufin_rpos);
561         if (-1 == bufin_size)
562         {
563           fprintf (stderr, "read-error: %s\n", strerror (errno));
564           shutdown (0, SHUT_RD);
565           shutdown (fd_tun, SHUT_WR);
566           write_open = 0;
567           bufin_size = 0;
568         }
569         else if (0 == bufin_size)
570         {
571 #if DEBUG
572           fprintf (stderr, "EOF on stdin\n");
573 #endif
574           shutdown (0, SHUT_RD);
575           shutdown (fd_tun, SHUT_WR);
576           write_open = 0;
577           bufin_size = 0;
578         }
579         else
580         {
581           struct GNUNET_MessageHeader *hdr;
582
583 PROCESS_BUFFER:
584           bufin_rpos += bufin_size;
585           if (bufin_rpos < sizeof (struct GNUNET_MessageHeader))
586             continue;
587           hdr = (struct GNUNET_MessageHeader *) bufin;
588           if (ntohs (hdr->type) != GNUNET_MESSAGE_TYPE_VPN_HELPER)
589           {
590             fprintf (stderr, "protocol violation!\n");
591             exit (1);
592           }
593           if (ntohs (hdr->size) > bufin_rpos)
594             continue;
595           bufin_read = bufin + sizeof (struct GNUNET_MessageHeader);
596           bufin_size = ntohs (hdr->size) - sizeof (struct GNUNET_MessageHeader);
597           bufin_rpos -= bufin_size + sizeof (struct GNUNET_MessageHeader);
598         }
599       }
600       else if (FD_ISSET (fd_tun, &fds_w))
601       {
602         ssize_t written = write (fd_tun, bufin_read, bufin_size);
603
604         if (-1 == written)
605         {
606           fprintf (stderr, "write-error to tun: %s\n", strerror (errno));
607           shutdown (0, SHUT_RD);
608           shutdown (fd_tun, SHUT_WR);
609           write_open = 0;
610           bufin_size = 0;
611         }
612         else if (0 == written)
613         {
614           fprintf (stderr, "write returned 0!?\n");
615           exit (1);
616         }
617         else
618         {
619           bufin_size -= written;
620           bufin_read += written;
621           if (0 == bufin_size)
622           {
623             memmove (bufin, bufin_read, bufin_rpos);
624             bufin_read = NULL;  /* start reading again */
625             bufin_size = 0;
626             goto PROCESS_BUFFER;
627           }
628         }
629       }
630     }
631   }
632 }
633
634
635 /**
636  * Open VPN tunnel interface.
637  *
638  * @param argc must be 6
639  * @param argv 0: binary name ("gnunet-helper-exit")
640  *             1: tunnel interface name ("gnunet-exit")
641  *             2: IPv4 "physical" interface name ("eth0"), or "-" to not do IPv4 NAT
642  *             3: IPv6 address ("::1"), or "-" to skip IPv6
643  *             4: IPv6 netmask length in bits ("64") [ignored if #4 is "-"]
644  *             5: IPv4 address ("1.2.3.4"), or "-" to skip IPv4
645  *             6: IPv4 netmask ("255.255.0.0") [ignored if #4 is "-"]
646  */
647 int
648 main (int argc, char **argv)
649 {
650   char dev[IFNAMSIZ];
651   int fd_tun;
652   int global_ret;
653
654   if (7 != argc)
655   {
656     fprintf (stderr, "Fatal: must supply 6 arguments!\n");
657     return 1;
658   }
659   if ( (0 == strcmp (argv[3], "-")) &&
660        (0 == strcmp (argv[5], "-")) )
661   {
662     fprintf (stderr, "Fatal: disabling both IPv4 and IPv6 makes no sense.\n");
663     return 1;
664   }
665   if (0 == access ("/sbin/iptables", X_OK))
666     sbin_iptables = "/sbin/iptables";
667   else if (0 == access ("/usr/sbin/iptables", X_OK))
668     sbin_iptables = "/usr/sbin/iptables";
669   else
670   {
671     fprintf (stderr, 
672              "Fatal: executable iptables not found in approved directories: %s\n",
673              strerror (errno));
674     return 1;
675   }
676   if (0 == access ("/sbin/sysctl", X_OK))
677     sbin_sysctl = "/sbin/sysctl";
678   else if (0 == access ("/usr/sbin/sysctl", X_OK))
679     sbin_sysctl = "/usr/sbin/sysctl";
680   else
681   {
682     fprintf (stderr,
683              "Fatal: executable sysctl not found in approved directories: %s\n",
684              strerror (errno));
685     return 1;
686   }
687
688   strncpy (dev, argv[1], IFNAMSIZ);
689   dev[IFNAMSIZ - 1] = '\0';
690
691   if (-1 == (fd_tun = init_tun (dev)))
692   {
693     fprintf (stderr, 
694              "Fatal: could not initialize tun-interface `%s' with IPv6 %s/%s and IPv4 %s/%s\n",
695              dev,
696              argv[3],
697              argv[4],
698              argv[5],
699              argv[6]);
700     return 1;
701   }
702
703   if (0 != strcmp (argv[3], "-"))
704   {
705     {
706       const char *address = argv[3];
707       long prefix_len = atol (argv[4]);
708       
709       if ((prefix_len < 1) || (prefix_len > 127))
710       {
711         fprintf (stderr, "Fatal: prefix_len out of range\n");
712         return 1;
713       }      
714       set_address6 (dev, address, prefix_len);    
715     }
716     {
717       char *const sysctl_args[] =
718         {
719           "sysctl", "-w", "net.ipv6.conf.all.forwarding=1", NULL
720         };
721       if (0 != fork_and_exec (sbin_sysctl,
722                               sysctl_args))
723       {
724         fprintf (stderr,
725                  "Failed to enable IPv6 forwarding.  Will continue anyway.\n");
726       }    
727     }
728   }
729
730   if (0 != strcmp (argv[5], "-"))
731   {
732     {
733       const char *address = argv[5];
734       const char *mask = argv[6];
735       
736       set_address4 (dev, address, mask);
737     }
738     {
739       char *const sysctl_args[] =
740         {
741           "sysctl", "-w", "net.ipv4.ip_forward=1", NULL
742         };
743       if (0 != fork_and_exec (sbin_sysctl,
744                               sysctl_args))
745       {
746         fprintf (stderr,
747                  "Failed to enable IPv4 forwarding.  Will continue anyway.\n");
748       }    
749     }
750     if (0 != strcmp (argv[2], "-"))
751     {
752       char *const iptables_args[] =
753         {
754           "iptables", "-t", "nat", "-A", "POSTROUTING", "-o", argv[2], "-j", "MASQUERADE", NULL
755         };
756       if (0 != fork_and_exec (sbin_iptables,
757                               iptables_args))
758       {
759         fprintf (stderr,
760                  "Failed to enable IPv4 masquerading (NAT).  Will continue anyway.\n");
761       }    
762     }
763   }
764   
765   uid_t uid = getuid ();
766 #ifdef HAVE_SETRESUID
767   if (0 != setresuid (uid, uid, uid))
768   {
769     fprintf (stderr, "Failed to setresuid: %s\n", strerror (errno));
770     global_ret = 2;
771     goto cleanup;
772   }
773 #else
774   if (0 != (setuid (uid) | seteuid (uid)))
775   {
776     fprintf (stderr, "Failed to setuid: %s\n", strerror (errno));
777     global_ret = 2;
778     goto cleanup;
779   }
780 #endif
781
782   if (SIG_ERR == signal (SIGPIPE, SIG_IGN))
783   {
784     fprintf (stderr, "Failed to protect against SIGPIPE: %s\n",
785              strerror (errno));
786     /* no exit, we might as well die with SIGPIPE should it ever happen */
787   }
788   run (fd_tun);
789   global_ret = 0;
790  cleanup:
791   (void) close (fd_tun);
792   return global_ret;
793 }
794
795 /* end of gnunet-helper-exit.c */