- LRN's patch
[oweals/gnunet.git] / src / exit / gnunet-helper-exit.c
index 84b6e6e65ba6d131d16e66c374b9e6f47c8ea3ce..573bb7a502ebb5a8c49b41fbf87ebc887defd2d4 100644 (file)
 */
 
 /**
- * @file exit/gnunet-helper-exit.c
- * @brief the helper for exit nodes. Opens a virtual network-interface,
- * sends data received on the if to stdout, sends data received on stdin to the
- * interface
- * @author Philipp Tölke
+ * @file exit/gnunet-helper-exit.c 
+ *
+ * @brief the helper for exit nodes. Opens a virtual
+ * network-interface, sends data received on the if to stdout, sends
+ * data received on stdin to the interface.  The code also enables
+ * IPv4/IPv6 forwarding and NAT on the current system (the latter on
+ * an interface specified on the command-line); these changes to the
+ * network configuration are NOT automatically undone when the program
+ * is stopped (this is because we cannot be sure that some other
+ * application didn't enable them before or after us; also, these
+ * changes should be mostly harmless as it simply turns the system
+ * into a router).
  *
- * TODO:
- * - need to add code to setup ip_forwarding and NAT (for IPv4) so that
- *   users don't need to ALSO do admin work; this is what will set
- *   gnunet-helper-exit.c apart from gnunet-helper-vpn.c
+ * @author Philipp Tölke
+ * @author Christian Grothoff
  *
  * The following list of people have reviewed this code and considered
  * it safe since the last modification (if you reviewed it, please
  */
 #include "gnunet_protocols.h"
 
+/**
+ * Should we print (interesting|debug) messages that can happen during
+ * normal operation?
+ */
+#define DEBUG GNUNET_NO
+
 /**
  * Maximum size of a GNUnet message (GNUNET_SERVER_MAX_MESSAGE_SIZE)
  */
 #define MAX_SIZE 65536
 
+/**
+ * Path to 'sysctl' binary.
+ */
+static const char *sbin_sysctl;
+
+/**
+ * Path to 'iptables' binary.
+ */
+static const char *sbin_iptables;
+
+
 #ifndef _LINUX_IN6_H
 /**
  * This is in linux/include/net/ipv6.h, but not always exported...
 struct in6_ifreq
 {
   struct in6_addr ifr6_addr;
-  uint32_t ifr6_prefixlen;
-  unsigned int ifr6_ifindex;
+  __u32 ifr6_prefixlen;
+  int ifr6_ifindex;
 };
 #endif
 
 
+
+/**
+ * Run the given command and wait for it to complete.
+ * 
+ * @param file name of the binary to run
+ * @param cmd command line arguments (as given to 'execv')
+ * @return 0 on success, 1 on any error
+ */
+static int
+fork_and_exec (const char *file, 
+              char *const cmd[])
+{
+  int status;
+  pid_t pid;
+  pid_t ret;
+
+  pid = fork ();
+  if (-1 == pid)
+  {
+    fprintf (stderr, 
+            "fork failed: %s\n", 
+            strerror (errno));
+    return 1;
+  }
+  if (0 == pid)
+  {
+    /* we are the child process */
+    /* close stdin/stdout to not cause interference
+       with the helper's main protocol! */
+    (void) close (0); 
+    (void) close (1); 
+    (void) execv (file, cmd);
+    /* can only get here on error */
+    fprintf (stderr, 
+            "exec `%s' failed: %s\n", 
+            file,
+            strerror (errno));
+    _exit (1);
+  }
+  /* keep running waitpid as long as the only error we get is 'EINTR' */
+  while ( (-1 == (ret = waitpid (pid, &status, 0))) &&
+         (errno == EINTR) ); 
+  if (-1 == ret)
+  {
+    fprintf (stderr, 
+            "waitpid failed: %s\n", 
+            strerror (errno));
+    return 1;
+  }
+  if (! (WIFEXITED (status) && (0 == WEXITSTATUS (status))))
+    return 1;
+  /* child process completed and returned success, we're happy */
+  return 0;
+}
+
+
 /**
  * Creates a tun-interface called dev;
  *
@@ -96,6 +174,7 @@ init_tun (char *dev)
   if (fd >= FD_SETSIZE)
   {
     fprintf (stderr, "File descriptor to large: %d", fd);
+    (void) close (fd);
     return -1;
   }
 
@@ -107,7 +186,8 @@ init_tun (char *dev)
 
   if (-1 == ioctl (fd, TUNSETIFF, (void *) &ifr))
   {
-    fprintf (stderr, "Error with ioctl on `%s': %s\n", "/dev/net/tun",
+    fprintf (stderr, 
+            "Error with ioctl on `%s': %s\n", "/dev/net/tun",
              strerror (errno));
     (void) close (fd);
     return -1;
@@ -128,16 +208,16 @@ static void
 set_address6 (const char *dev, const char *address, unsigned long prefix_len)
 {
   struct ifreq ifr;
-  struct in6_ifreq ifr6;
   struct sockaddr_in6 sa6;
   int fd;
+  struct in6_ifreq ifr6;
 
   /*
    * parse the new address
    */
   memset (&sa6, 0, sizeof (struct sockaddr_in6));
   sa6.sin6_family = AF_INET6;
-  if (1 != inet_pton (AF_INET6, address, sa6.sin6_addr.s6_addr))
+  if (1 != inet_pton (AF_INET6, address, &sa6.sin6_addr))
   {
     fprintf (stderr, "Failed to parse address `%s': %s\n", address,
              strerror (errno));
@@ -146,7 +226,7 @@ set_address6 (const char *dev, const char *address, unsigned long prefix_len)
 
   if (-1 == (fd = socket (PF_INET6, SOCK_DGRAM, 0)))
   {
-    fprintf (stderr, "Error creating socket: %s\n", strerror (errno));
+    fprintf (stderr, "Error creating socket: %s\n", strerror (errno));    
     exit (1);
   }
 
@@ -402,7 +482,9 @@ run (int fd_tun)
         }
         else if (0 == buftun_size)
         {
+#if DEBUG
           fprintf (stderr, "EOF on tun\n");
+#endif
           shutdown (fd_tun, SHUT_RD);
           shutdown (1, SHUT_WR);
           read_open = 0;
@@ -424,7 +506,10 @@ run (int fd_tun)
 
         if (-1 == written)
         {
-          fprintf (stderr, "write-error to stdout: %s\n", strerror (errno));
+#if !DEBUG
+         if (errno != EPIPE)
+#endif
+           fprintf (stderr, "write-error to stdout: %s\n", strerror (errno));
           shutdown (fd_tun, SHUT_RD);
           shutdown (1, SHUT_WR);
           read_open = 0;
@@ -455,7 +540,9 @@ run (int fd_tun)
         }
         else if (0 == bufin_size)
         {
+#if DEBUG
           fprintf (stderr, "EOF on stdin\n");
+#endif
           shutdown (0, SHUT_RD);
           shutdown (fd_tun, SHUT_WR);
           write_open = 0;
@@ -521,12 +608,13 @@ PROCESS_BUFFER:
  * Open VPN tunnel interface.
  *
  * @param argc must be 6
- * @param argv 0: binary name (gnunet-helper-vpn)
- *             1: tunnel interface name (gnunet-vpn)
- *             2: IPv6 address (::1)
- *             3: IPv6 netmask length in bits (64)
- *             4: IPv4 address (1.2.3.4)
- *             5: IPv4 netmask (255.255.0.0)
+ * @param argv 0: binary name ("gnunet-helper-exit")
+ *             1: tunnel interface name ("gnunet-exit")
+ *             2: IPv4 "physical" interface name ("eth0"), or "%" to not do IPv4 NAT
+ *             3: IPv6 address ("::1"), or "-" to skip IPv6
+ *             4: IPv6 netmask length in bits ("64") [ignored if #4 is "-"]
+ *             5: IPv4 address ("1.2.3.4"), or "-" to skip IPv4
+ *             6: IPv4 netmask ("255.255.0.0") [ignored if #4 is "-"]
  */
 int
 main (int argc, char **argv)
@@ -535,9 +623,37 @@ main (int argc, char **argv)
   int fd_tun;
   int global_ret;
 
-  if (6 != argc)
+  if (7 != argc)
+  {
+    fprintf (stderr, "Fatal: must supply 6 arguments!\n");
+    return 1;
+  }
+  if ( (0 == strcmp (argv[3], "-")) &&
+       (0 == strcmp (argv[5], "-")) )
+  {
+    fprintf (stderr, "Fatal: disabling both IPv4 and IPv6 makes no sense.\n");
+    return 1;
+  }
+  if (0 == access ("/sbin/iptables", X_OK))
+    sbin_iptables = "/sbin/iptables";
+  else if (0 == access ("/usr/sbin/iptables", X_OK))
+    sbin_iptables = "/usr/sbin/iptables";
+  else
   {
-    fprintf (stderr, "Fatal: must supply 5 arguments!\n");
+    fprintf (stderr, 
+            "Fatal: executable iptables not found in approved directories: %s\n",
+            strerror (errno));
+    return 1;
+  }
+  if (0 == access ("/sbin/sysctl", X_OK))
+    sbin_sysctl = "/sbin/sysctl";
+  else if (0 == access ("/usr/sbin/sysctl", X_OK))
+    sbin_sysctl = "/usr/sbin/sysctl";
+  else
+  {
+    fprintf (stderr,
+            "Fatal: executable sysctl not found in approved directories: %s\n",
+            strerror (errno));
     return 1;
   }
 
@@ -546,28 +662,76 @@ main (int argc, char **argv)
 
   if (-1 == (fd_tun = init_tun (dev)))
   {
-    fprintf (stderr, "Fatal: could not initialize tun-interface\n");
+    fprintf (stderr, 
+            "Fatal: could not initialize tun-interface `%s' with IPv6 %s/%s and IPv4 %s/%s\n",
+            dev,
+            argv[3],
+            argv[4],
+            argv[5],
+            argv[6]);
     return 1;
   }
 
+  if (0 != strcmp (argv[3], "-"))
   {
-    const char *address = argv[2];
-    long prefix_len = atol (argv[3]);
-
-    if ((prefix_len < 1) || (prefix_len > 127))
     {
-      fprintf (stderr, "Fatal: prefix_len out of range\n");
-      return 1;
+      const char *address = argv[3];
+      long prefix_len = atol (argv[4]);
+      
+      if ((prefix_len < 1) || (prefix_len > 127))
+      {
+       fprintf (stderr, "Fatal: prefix_len out of range\n");
+       return 1;
+      }      
+      set_address6 (dev, address, prefix_len);    
+    }
+    {
+      char *const sysctl_args[] =
+       {
+         "sysctl", "-w", "net.ipv6.conf.all.forwarding=1", NULL
+       };
+      if (0 != fork_and_exec (sbin_sysctl,
+                             sysctl_args))
+      {
+       fprintf (stderr,
+                "Failed to enable IPv6 forwarding.  Will continue anyway.\n");
+      }    
     }
-
-    set_address6 (dev, address, prefix_len);
   }
 
+  if (0 != strcmp (argv[5], "-"))
   {
-    const char *address = argv[4];
-    const char *mask = argv[5];
-
-    set_address4 (dev, address, mask);
+    {
+      const char *address = argv[5];
+      const char *mask = argv[6];
+      
+      set_address4 (dev, address, mask);
+    }
+    {
+      char *const sysctl_args[] =
+       {
+         "sysctl", "-w", "net.ipv4.ip_forward=1", NULL
+       };
+      if (0 != fork_and_exec (sbin_sysctl,
+                             sysctl_args))
+      {
+       fprintf (stderr,
+                "Failed to enable IPv4 forwarding.  Will continue anyway.\n");
+      }    
+    }
+    if (0 != strcmp (argv[2], "%"))
+    {
+      char *const iptables_args[] =
+       {
+         "iptables", "-t", "nat", "-A", "POSTROUTING", "-o", argv[2], "-j", "MASQUERADE", NULL
+       };
+      if (0 != fork_and_exec (sbin_iptables,
+                             iptables_args))
+      {
+       fprintf (stderr,
+                "Failed to enable IPv4 masquerading (NAT).  Will continue anyway.\n");
+      }    
+    }
   }
   
   uid_t uid = getuid ();
@@ -599,3 +763,5 @@ main (int argc, char **argv)
   close (fd_tun);
   return global_ret;
 }
+
+/* end of gnunet-helper-exit.c */