12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205 |
- /*
- 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 Affero 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.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- SPDX-License-Identifier: AGPL3.0-or-later
- */
- /**
- * @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 <linux/if_tun.h>
- /**
- * 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
- #if !HAVE_DECL_STRUCT_IN6_IFREQ
- /**
- * 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 */
- #ifdef IPTABLES
- if (0 == access (IPTABLES, X_OK))
- sbin_iptables = IPTABLES;
- else
- #endif
- 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;
- }
- #ifdef IP6TABLES
- if (0 == access (IP6TABLES, X_OK))
- sbin_ip6tables = IP6TABLES;
- else
- #endif
- 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;
- }
- #ifdef PATH_TO_IP
- if (0 == access (PATH_TO_IP, X_OK))
- sbin_ip = PATH_TO_IP;
- else
- #endif
- 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;
- }
- #ifdef SYSCTL
- if (0 == access (SYSCTL, X_OK))
- sbin_sysctl = SYSCTL;
- else
- #endif
- 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 */
|