/* This file is part of GNUnet. Copyright (C) 2010, 2011, 2012 Christian Grothoff GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. */ /** * @file dns/gnunet-helper-dns.c * @brief helper to install firewall rules to hijack all DNS traffic * and send it to our virtual interface (except for DNS traffic * that originates on the specified port). We then * allow interacting with our virtual interface via stdin/stdout. * @author Philipp Tölke * @author Christian Grothoff * * This program alters the Linux firewall rules so that DNS traffic * that ordinarily exits the system can be intercepted and managed by * a virtual interface. In order to achieve this, DNS traffic is * marked with the DNS_MARK given in below and re-routed to a custom * table with the DNS_TABLE ID given below. Systems and * administrators must take care to not cause conflicts with these * values (it was deemed safest to hardcode them as passing these * values as arguments might permit messing with arbitrary firewall * rules, which would be dangerous). Traffic coming from the same * group ID as the effective group ID that this process is running * as is not intercepted. * * The code first sets up the virtual interface, then begins to * redirect the DNS traffic to it, and then on errors or SIGTERM shuts * down the virtual interface and removes the rules for the traffic * redirection. * * * Note that having this binary SUID is only partially safe: it will * allow redirecting (and intercepting / mangling) of all DNS traffic * originating from this system by any user who is able to run it. * Furthermore, this code will make it trivial to DoS all DNS traffic * originating from the current system, simply by sending it to * nowhere (redirect stdout to /dev/null). * * Naturally, neither of these problems can be helped as this is the * fundamental purpose of the binary. Certifying that this code is * "safe" thus only means that it doesn't allow anything else (such * as local priv. escalation, etc.). * * The following list of people have reviewed this code and considered * it safe (within specifications) since the last modification (if you * reviewed it, please have your name added to the list): * * - Christian Grothoff */ #include "platform.h" #include /** * Need 'struct GNUNET_MessageHeader'. */ #include "gnunet_crypto_lib.h" #include "gnunet_common.h" /** * Need DNS message types. */ #include "gnunet_protocols.h" /** * Maximum size of a GNUnet message (GNUNET_MAX_MESSAGE_SIZE) */ #define MAX_SIZE 65536 #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; }; #endif /** * Name and full path of IPTABLES binary. */ static const char *sbin_iptables; /** * Name and full path of IPTABLES binary. */ static const char *sbin_ip6tables; /** * Name and full path of sysctl binary */ static const char *sbin_sysctl; /** * Name and full path of IPTABLES binary. */ static const char *sbin_ip; /** * Port for DNS traffic. */ #define DNS_PORT "53" /** * Marker we set for our hijacked DNS traffic. We use GNUnet's * port (2086) plus the DNS port (53) in HEX to make a 32-bit mark * (which is hopefully long enough to not collide); so * 0x08260035 = 136708149 (hopefully unique enough...). */ #define DNS_MARK "136708149" /** * Table we use for our DNS rules. 0-255 is the range and * 0, 253, 254 and 255 are already reserved. As this is about * DNS and as "53" is likely (fingers crossed!) high enough to * not usually conflict with a normal user's setup, we use 53 * to give a hint that this has something to do with DNS. */ #define DNS_TABLE "53" /** * Control pipe for shutdown via signal. [0] is the read end, * [1] is the write end. */ static int cpipe[2]; /** * Signal handler called to initiate "nice" shutdown. Signals select * loop via non-bocking pipe 'cpipe'. * * @param signal signal number of the signal (not used) */ static void signal_handler (int signal) { /* ignore return value, as the signal handler could theoretically be called many times before the shutdown can actually happen */ (void) write (cpipe[1], "K", 1); } /** * Open '/dev/null' and make the result the given * file descriptor. * * @param target_fd desired FD to point to /dev/null * @param flags open flags (O_RDONLY, O_WRONLY) */ static void open_dev_null (int target_fd, int flags) { int fd; fd = open ("/dev/null", flags); if (-1 == fd) abort (); if (fd == target_fd) return; if (-1 == dup2 (fd, target_fd)) { (void) close (fd); abort (); } (void) close (fd); } /** * 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); open_dev_null (0, O_RDONLY); (void) close (1); open_dev_null (1, O_WRONLY); (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 @a dev; * * @param dev is asumed to point to a char[IFNAMSIZ] * if *dev == '\\0', uses the name supplied by the kernel; * @return the fd to the tun or -1 on error */ static int init_tun (char *dev) { struct ifreq ifr; int fd; if (NULL == dev) { errno = EINVAL; return -1; } if (-1 == (fd = open ("/dev/net/tun", O_RDWR))) { fprintf (stderr, "Error opening `%s': %s\n", "/dev/net/tun", strerror (errno)); return -1; } if (fd >= FD_SETSIZE) { fprintf (stderr, "File descriptor to large: %d", fd); (void) close (fd); return -1; } memset (&ifr, 0, sizeof (ifr)); ifr.ifr_flags = IFF_TUN; if ('\0' != *dev) strncpy (ifr.ifr_name, dev, IFNAMSIZ); if (-1 == ioctl (fd, TUNSETIFF, (void *) &ifr)) { fprintf (stderr, "Error with ioctl on `%s': %s\n", "/dev/net/tun", strerror (errno)); (void) close (fd); return -1; } strcpy (dev, ifr.ifr_name); return fd; } /** * @brief Sets the IPv6-Address given in @a address on the interface @a dev * * @param dev the interface to configure * @param address the IPv6-Address * @param prefix_len the length of the network-prefix */ 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; /* * 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)) { fprintf (stderr, "Failed to parse IPv6 address `%s': %s\n", address, strerror (errno)); exit (1); } if (-1 == (fd = socket (PF_INET6, SOCK_DGRAM, 0))) { fprintf (stderr, "Error creating IPv6 socket: %s (ignored)\n", strerror (errno)); /* ignore error, maybe only IPv4 works on this system! */ return; } memset (&ifr, 0, sizeof (struct ifreq)); /* * Get the index of the if */ strncpy (ifr.ifr_name, dev, IFNAMSIZ); if (-1 == ioctl (fd, SIOGIFINDEX, &ifr)) { fprintf (stderr, "ioctl failed at %d: %s\n", __LINE__, strerror (errno)); (void) close (fd); exit (1); } memset (&ifr6, 0, sizeof (struct in6_ifreq)); ifr6.ifr6_addr = sa6.sin6_addr; ifr6.ifr6_ifindex = ifr.ifr_ifindex; ifr6.ifr6_prefixlen = prefix_len; /* * Set the address */ if (-1 == ioctl (fd, SIOCSIFADDR, &ifr6)) { fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, strerror (errno)); (void) close (fd); exit (1); } /* * Get the flags */ if (-1 == ioctl (fd, SIOCGIFFLAGS, &ifr)) { fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, strerror (errno)); (void) close (fd); exit (1); } /* * Add the UP and RUNNING flags */ ifr.ifr_flags |= IFF_UP | IFF_RUNNING; if (-1 == ioctl (fd, SIOCSIFFLAGS, &ifr)) { fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, strerror (errno)); (void) close (fd); exit (1); } if (0 != close (fd)) { fprintf (stderr, "close failed: %s\n", strerror (errno)); exit (1); } } /** * @brief Sets the IPv4-Address given in @a address on the interface @a dev * * @param dev the interface to configure * @param address the IPv4-Address * @param mask the netmask */ static void set_address4 (const char *dev, const char *address, const char *mask) { int fd; struct sockaddr_in *addr; struct ifreq ifr; memset (&ifr, 0, sizeof (struct ifreq)); addr = (struct sockaddr_in *) &(ifr.ifr_addr); addr->sin_family = AF_INET; /* * Parse the address */ if (1 != inet_pton (AF_INET, address, &addr->sin_addr.s_addr)) { fprintf (stderr, "Failed to parse IPv4 address `%s': %s\n", address, strerror (errno)); exit (1); } if (-1 == (fd = socket (PF_INET, SOCK_DGRAM, 0))) { fprintf (stderr, "Error creating IPv4 socket: %s\n", strerror (errno)); exit (1); } strncpy (ifr.ifr_name, dev, IFNAMSIZ); /* * Set the address */ if (-1 == ioctl (fd, SIOCSIFADDR, &ifr)) { fprintf (stderr, "ioctl failed at %d: %s\n", __LINE__, strerror (errno)); (void) close (fd); exit (1); } /* * Parse the netmask */ addr = (struct sockaddr_in *) &(ifr.ifr_netmask); if (1 != inet_pton (AF_INET, mask, &addr->sin_addr.s_addr)) { fprintf (stderr, "Failed to parse address `%s': %s\n", mask, strerror (errno)); (void) close (fd); exit (1); } /* * Set the netmask */ if (-1 == ioctl (fd, SIOCSIFNETMASK, &ifr)) { fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, strerror (errno)); (void) close (fd); exit (1); } /* * Get the flags */ if (-1 == ioctl (fd, SIOCGIFFLAGS, &ifr)) { fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, strerror (errno)); (void) close (fd); exit (1); } /* * Add the UP and RUNNING flags */ ifr.ifr_flags |= IFF_UP | IFF_RUNNING; if (-1 == ioctl (fd, SIOCSIFFLAGS, &ifr)) { fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, strerror (errno)); (void) close (fd); exit (1); } if (0 != close (fd)) { fprintf (stderr, "close failed: %s\n", strerror (errno)); (void) close (fd); exit (1); } } /** * Start forwarding to and from the tunnel. This function runs with * "reduced" priviledges (saved UID is still 0, but effective UID is * the real user ID). * * @param fd_tun tunnel FD */ static void run (int fd_tun) { /* * The buffer filled by reading from fd_tun */ unsigned char buftun[MAX_SIZE]; ssize_t buftun_size = 0; unsigned char *buftun_read = NULL; /* * The buffer filled by reading from stdin */ unsigned char bufin[MAX_SIZE]; ssize_t bufin_size = 0; size_t bufin_rpos = 0; unsigned char *bufin_read = NULL; fd_set fds_w; fd_set fds_r; int max; while (1) { FD_ZERO (&fds_w); FD_ZERO (&fds_r); /* * We are supposed to read and the buffer is empty * -> select on read from tun */ if (0 == buftun_size) FD_SET (fd_tun, &fds_r); /* * We are supposed to read and the buffer is not empty * -> select on write to stdout */ if (0 < buftun_size) FD_SET (1, &fds_w); /* * We are supposed to write and the buffer is empty * -> select on read from stdin */ if (NULL == bufin_read) FD_SET (0, &fds_r); /* * We are supposed to write and the buffer is not empty * -> select on write to tun */ if (NULL != bufin_read) FD_SET (fd_tun, &fds_w); FD_SET (cpipe[0], &fds_r); max = (fd_tun > cpipe[0]) ? fd_tun : cpipe[0]; int r = select (max + 1, &fds_r, &fds_w, NULL, NULL); if (-1 == r) { if (EINTR == errno) continue; fprintf (stderr, "select failed: %s\n", strerror (errno)); return; } if (r > 0) { if (FD_ISSET (cpipe[0], &fds_r)) return; /* aborted by signal */ if (FD_ISSET (fd_tun, &fds_r)) { buftun_size = read (fd_tun, buftun + sizeof (struct GNUNET_MessageHeader), MAX_SIZE - sizeof (struct GNUNET_MessageHeader)); if (-1 == buftun_size) { if ( (errno == EINTR) || (errno == EAGAIN) ) { buftun_size = 0; continue; } fprintf (stderr, "read-error: %s\n", strerror (errno)); return; } if (0 == buftun_size) { fprintf (stderr, "EOF on tun\n"); return; } buftun_read = buftun; { struct GNUNET_MessageHeader *hdr = (struct GNUNET_MessageHeader *) buftun; buftun_size += sizeof (struct GNUNET_MessageHeader); hdr->type = htons (GNUNET_MESSAGE_TYPE_DNS_HELPER); hdr->size = htons (buftun_size); } } else if (FD_ISSET (1, &fds_w)) { ssize_t written = write (1, buftun_read, buftun_size); if (-1 == written) { if ( (errno == EINTR) || (errno == EAGAIN) ) continue; fprintf (stderr, "write-error to stdout: %s\n", strerror (errno)); return; } if (0 == written) { fprintf (stderr, "write returned 0\n"); return; } buftun_size -= written; buftun_read += written; } if (FD_ISSET (0, &fds_r)) { bufin_size = read (0, bufin + bufin_rpos, MAX_SIZE - bufin_rpos); if (-1 == bufin_size) { bufin_read = NULL; if ( (errno == EINTR) || (errno == EAGAIN) ) continue; fprintf (stderr, "read-error: %s\n", strerror (errno)); return; } if (0 == bufin_size) { bufin_read = NULL; fprintf (stderr, "EOF on stdin\n"); return; } { struct GNUNET_MessageHeader *hdr; PROCESS_BUFFER: bufin_rpos += bufin_size; if (bufin_rpos < sizeof (struct GNUNET_MessageHeader)) continue; hdr = (struct GNUNET_MessageHeader *) bufin; if (ntohs (hdr->type) != GNUNET_MESSAGE_TYPE_DNS_HELPER) { fprintf (stderr, "protocol violation!\n"); return; } if (ntohs (hdr->size) > bufin_rpos) continue; bufin_read = bufin + sizeof (struct GNUNET_MessageHeader); bufin_size = ntohs (hdr->size) - sizeof (struct GNUNET_MessageHeader); bufin_rpos -= bufin_size + sizeof (struct GNUNET_MessageHeader); } } else if (FD_ISSET (fd_tun, &fds_w)) { ssize_t written = write (fd_tun, bufin_read, bufin_size); if (-1 == written) { if ( (errno == EINTR) || (errno == EAGAIN) ) continue; fprintf (stderr, "write-error to tun: %s\n", strerror (errno)); return; } if (0 == written) { fprintf (stderr, "write returned 0\n"); return; } { bufin_size -= written; bufin_read += written; if (0 == bufin_size) { memmove (bufin, bufin_read, bufin_rpos); bufin_read = NULL; /* start reading again */ bufin_size = 0; goto PROCESS_BUFFER; } } } } } } /** * Main function of "gnunet-helper-dns", which opens a VPN tunnel interface, * redirects all outgoing DNS traffic (except from the specified port) to that * interface and then passes traffic from and to the interface via stdin/stdout. * * Once stdin/stdout close or have other errors, the tunnel is closed and the * DNS traffic redirection is stopped. * * @param argc number of arguments * @param argv 0: binary name (should be "gnunet-helper-vpn") * 1: tunnel interface name (typically "gnunet-dns") * 2: IPv6 address for the tunnel ("FE80::1") * 3: IPv6 netmask length in bits ("64") * 4: IPv4 address for the tunnel ("1.2.3.4") * 5: IPv4 netmask ("255.255.0.0") * 6: skip sysctl, routing and iptables setup ("0") * @return 0 on success, otherwise code indicating type of error: * 1 wrong number of arguments * 2 invalid arguments (i.e. port number / prefix length wrong) * 3 iptables not executable * 4 ip not executable * 5 failed to initialize tunnel interface * 6 failed to initialize control pipe * 8 failed to change routing table, cleanup successful * 9-23 failed to change routing table and failed to undo some changes to routing table * 24 failed to drop privs * 25-39 failed to drop privs and then failed to undo some changes to routing table * 40 failed to regain privs * 41-55 failed to regain prisv and then failed to undo some changes to routing table * 254 insufficient priviledges * 255 failed to handle kill signal properly */ int main (int argc, char *const*argv) { int r; char dev[IFNAMSIZ]; char mygid[32]; int fd_tun; uid_t uid; int nortsetup = 0; if (7 != argc) { fprintf (stderr, "Fatal: must supply 6 arguments!\n"); return 1; } /* assert privs so we can modify the firewall rules! */ uid = getuid (); #ifdef HAVE_SETRESUID if (0 != setresuid (uid, 0, 0)) { fprintf (stderr, "Failed to setresuid to root: %s\n", strerror (errno)); return 254; } #else if (0 != seteuid (0)) { fprintf (stderr, "Failed to seteuid back to root: %s\n", strerror (errno)); return 254; } #endif if (0 == strncmp (argv[6], "1", 2)) nortsetup = 1; if (0 == nortsetup) { /* verify that the binaries we care about are executable */ 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: executable iptables not found in approved directories: %s\n", strerror (errno)); return 3; } if (0 == access ("/sbin/ip6tables", X_OK)) sbin_ip6tables = "/sbin/ip6tables"; else if (0 == access ("/usr/sbin/ip6tables", X_OK)) sbin_ip6tables = "/usr/sbin/ip6tables"; else { fprintf (stderr, "Fatal: executable ip6tables not found in approved directories: %s\n", strerror (errno)); return 3; } if (0 == access ("/sbin/ip", X_OK)) sbin_ip = "/sbin/ip"; else if (0 == access ("/usr/sbin/ip", X_OK)) sbin_ip = "/usr/sbin/ip"; else if (0 == access ("/bin/ip", X_OK)) /* gentoo has it there */ sbin_ip = "/bin/ip"; else { fprintf (stderr, "Fatal: executable ip not found in approved directories: %s\n", strerror (errno)); return 4; } 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 5; } } /* setup 'mygid' string */ snprintf (mygid, sizeof (mygid), "%d", (int) getegid()); /* do not die on SIGPIPE */ if (SIG_ERR == signal (SIGPIPE, SIG_IGN)) { fprintf (stderr, "Failed to protect against SIGPIPE: %s\n", strerror (errno)); return 7; } /* setup pipe to shutdown nicely on SIGINT */ if (0 != pipe (cpipe)) { fprintf (stderr, "Fatal: could not setup control pipe: %s\n", strerror (errno)); return 6; } if (cpipe[0] >= FD_SETSIZE) { fprintf (stderr, "Pipe file descriptor to large: %d", cpipe[0]); (void) close (cpipe[0]); (void) close (cpipe[1]); return 6; } { /* make pipe non-blocking, as we theoretically could otherwise block in the signal handler */ int flags = fcntl (cpipe[1], F_GETFL); if (-1 == flags) { fprintf (stderr, "Failed to read flags for pipe: %s", strerror (errno)); (void) close (cpipe[0]); (void) close (cpipe[1]); return 6; } flags |= O_NONBLOCK; if (0 != fcntl (cpipe[1], F_SETFL, flags)) { fprintf (stderr, "Failed to make pipe non-blocking: %s", strerror (errno)); (void) close (cpipe[0]); (void) close (cpipe[1]); return 6; } } if ( (SIG_ERR == signal (SIGTERM, &signal_handler)) || #if (SIGTERM != GNUNET_TERM_SIG) (SIG_ERR == signal (GNUNET_TERM_SIG, &signal_handler)) || #endif (SIG_ERR == signal (SIGINT, &signal_handler)) || (SIG_ERR == signal (SIGHUP, &signal_handler)) ) { fprintf (stderr, "Fatal: could not initialize signal handler: %s\n", strerror (errno)); (void) close (cpipe[0]); (void) close (cpipe[1]); return 7; } /* get interface name */ strncpy (dev, argv[1], IFNAMSIZ); dev[IFNAMSIZ - 1] = '\0'; /* Disable rp filtering */ if (0 == nortsetup) { char *const sysctl_args[] = {"sysctl", "-w", "net.ipv4.conf.all.rp_filter=0", NULL}; char *const sysctl_args2[] = {"sysctl", "-w", "net.ipv4.conf.default.rp_filter=0", NULL}; if ((0 != fork_and_exec (sbin_sysctl, sysctl_args)) || (0 != fork_and_exec (sbin_sysctl, sysctl_args2))) { fprintf (stderr, "Failed to disable rp filtering.\n"); return 5; } } /* now open virtual interface (first part that requires root) */ if (-1 == (fd_tun = init_tun (dev))) { fprintf (stderr, "Fatal: could not initialize tun-interface\n"); (void) signal (SIGTERM, SIG_IGN); #if (SIGTERM != GNUNET_TERM_SIG) (void) signal (GNUNET_TERM_SIG, SIG_IGN); #endif (void) signal (SIGINT, SIG_IGN); (void) signal (SIGHUP, SIG_IGN); (void) close (cpipe[0]); (void) close (cpipe[1]); return 5; } /* now set interface addresses */ { 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"); (void) signal (SIGTERM, SIG_IGN); #if (SIGTERM != GNUNET_TERM_SIG) (void) signal (GNUNET_TERM_SIG, SIG_IGN); #endif (void) signal (SIGINT, SIG_IGN); (void) signal (SIGHUP, SIG_IGN); (void) close (cpipe[0]); (void) close (cpipe[1]); return 2; } set_address6 (dev, address, prefix_len); } { const char *address = argv[4]; const char *mask = argv[5]; set_address4 (dev, address, mask); } /* update routing tables -- next part why we need SUID! */ /* Forward everything from our EGID (which should only be held by the 'gnunet-service-dns') and with destination to port 53 on UDP, without hijacking */ if (0 == nortsetup) { r = 8; /* failed to fully setup routing table */ { char *const mangle_args[] = { "iptables", "-m", "owner", "-t", "mangle", "-I", "OUTPUT", "1", "-p", "udp", "--gid-owner", mygid, "--dport", DNS_PORT, "-j", "ACCEPT", NULL }; if (0 != fork_and_exec (sbin_iptables, mangle_args)) goto cleanup_rest; } { char *const mangle_args[] = { "ip6tables", "-m", "owner", "-t", "mangle", "-I", "OUTPUT", "1", "-p", "udp", "--gid-owner", mygid, "--dport", DNS_PORT, "-j", "ACCEPT", NULL }; if (0 != fork_and_exec (sbin_ip6tables, mangle_args)) goto cleanup_mangle_1b; } /* Mark all of the other DNS traffic using our mark DNS_MARK, unless it is on a link-local IPv6 address, which we cannot support. */ { char *const mark_args[] = { "iptables", "-t", "mangle", "-I", "OUTPUT", "2", "-p", "udp", "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, NULL }; if (0 != fork_and_exec (sbin_iptables, mark_args)) goto cleanup_mangle_1; } { char *const mark_args[] = { "ip6tables", "-t", "mangle", "-I", "OUTPUT", "2", "-p", "udp", "--dport", DNS_PORT, "!", "-s", "fe80::/10", /* this line excludes link-local traffic */ "-j", "MARK", "--set-mark", DNS_MARK, NULL }; if (0 != fork_and_exec (sbin_ip6tables, mark_args)) goto cleanup_mark_2b; } /* Forward all marked DNS traffic to our DNS_TABLE */ { char *const forward_args[] = { "ip", "rule", "add", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL }; if (0 != fork_and_exec (sbin_ip, forward_args)) goto cleanup_mark_2; } { char *const forward_args[] = { "ip", "-6", "rule", "add", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL }; if (0 != fork_and_exec (sbin_ip, forward_args)) goto cleanup_forward_3b; } /* Finally, add rule in our forwarding table to pass to our virtual interface */ { char *const route_args[] = { "ip", "route", "add", "default", "dev", dev, "table", DNS_TABLE, NULL }; if (0 != fork_and_exec (sbin_ip, route_args)) goto cleanup_forward_3; } { char *const route_args[] = { "ip", "-6", "route", "add", "default", "dev", dev, "table", DNS_TABLE, NULL }; if (0 != fork_and_exec (sbin_ip, route_args)) goto cleanup_route_4b; } } /* drop privs *except* for the saved UID; this is not perfect, but better than doing nothing */ #ifdef HAVE_SETRESUID if (0 != setresuid (uid, uid, 0)) { fprintf (stderr, "Failed to setresuid: %s\n", strerror (errno)); r = 24; goto cleanup_route_4; } #else /* Note: no 'setuid' here as we must keep our saved UID as root */ if (0 != seteuid (uid)) { fprintf (stderr, "Failed to seteuid: %s\n", strerror (errno)); r = 24; goto cleanup_route_4; } #endif r = 0; /* did fully setup routing table (if nothing else happens, we were successful!) */ /* now forward until we hit a problem */ run (fd_tun); /* now need to regain privs so we can remove the firewall rules we added! */ #ifdef HAVE_SETRESUID if (0 != setresuid (uid, 0, 0)) { fprintf (stderr, "Failed to setresuid back to root: %s\n", strerror (errno)); r = 40; goto cleanup_route_4; } #else if (0 != seteuid (0)) { fprintf (stderr, "Failed to seteuid back to root: %s\n", strerror (errno)); r = 40; goto cleanup_route_4; } #endif /* update routing tables again -- this is why we could not fully drop privs */ /* now undo updating of routing tables; normal exit or clean-up-on-error case */ cleanup_route_4: if (0 == nortsetup) { char *const route_clean_args[] = { "ip", "-6", "route", "del", "default", "dev", dev, "table", DNS_TABLE, NULL }; if (0 != fork_and_exec (sbin_ip, route_clean_args)) r += 1; } cleanup_route_4b: if (0 == nortsetup) { char *const route_clean_args[] = { "ip", "route", "del", "default", "dev", dev, "table", DNS_TABLE, NULL }; if (0 != fork_and_exec (sbin_ip, route_clean_args)) r += 1; } cleanup_forward_3: if (0 == nortsetup) { char *const forward_clean_args[] = { "ip", "-6", "rule", "del", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL }; if (0 != fork_and_exec (sbin_ip, forward_clean_args)) r += 2; } cleanup_forward_3b: if (0 == nortsetup) { char *const forward_clean_args[] = { "ip", "rule", "del", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL }; if (0 != fork_and_exec (sbin_ip, forward_clean_args)) r += 2; } cleanup_mark_2: if (0 == nortsetup) { char *const mark_clean_args[] = { "ip6tables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", "--dport", DNS_PORT, "!", "-s", "fe80::/10", /* this line excludes link-local traffic */ "-j", "MARK", "--set-mark", DNS_MARK, NULL }; if (0 != fork_and_exec (sbin_ip6tables, mark_clean_args)) r += 4; } cleanup_mark_2b: if (0 == nortsetup) { char *const mark_clean_args[] = { "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, NULL }; if (0 != fork_and_exec (sbin_iptables, mark_clean_args)) r += 4; } cleanup_mangle_1: if (0 == nortsetup) { char *const mangle_clean_args[] = { "ip6tables", "-m", "owner", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", "--gid-owner", mygid, "--dport", DNS_PORT, "-j", "ACCEPT", NULL }; if (0 != fork_and_exec (sbin_ip6tables, mangle_clean_args)) r += 8; } cleanup_mangle_1b: if (0 == nortsetup) { char *const mangle_clean_args[] = { "iptables", "-m", "owner", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", "--gid-owner", mygid, "--dport", DNS_PORT, "-j", "ACCEPT", NULL }; if (0 != fork_and_exec (sbin_iptables, mangle_clean_args)) r += 8; } cleanup_rest: /* close virtual interface */ (void) close (fd_tun); /* remove signal handler so we can close the pipes */ (void) signal (SIGTERM, SIG_IGN); #if (SIGTERM != GNUNET_TERM_SIG) (void) signal (GNUNET_TERM_SIG, SIG_IGN); #endif (void) signal (SIGINT, SIG_IGN); (void) signal (SIGHUP, SIG_IGN); (void) close (cpipe[0]); (void) close (cpipe[1]); return r; } /* end of gnunet-helper-dns.c */