dns hijacker code review
[oweals/gnunet.git] / src / dns / gnunet-helper-hijack-dns.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 dns/gnunet-helper-hijack-dns.c
23  * @brief helper to install firewall rules to hijack all DNS traffic
24  *        and send it to our virtual interface except for DNS traffic
25  *        that originates on the specified port. 
26  * @author Philipp Tölke
27  * @author Christian Grothoff
28  *
29  * This program alters the Linux firewall rules so that DNS traffic
30  * that ordinarily exits the system can be intercepted and managed by
31  * a virtual interface.  In order to achieve this, DNS traffic is
32  * marked with the DNS_MARK given in below and re-routed to a custom
33  * table with the DNS_TABLE ID given below.  Systems and
34  * administrators must take care to not cause conflicts with these
35  * values (it was deemed safest to hardcode them as passing these
36  * values as arguments might permit messing with arbitrary firewall
37  * rules, which would be dangerous).
38  *
39  * Note that having this binary SUID is only partially safe: it will
40  * allow redirecting (and intercepting / mangling) of all DNS traffic
41  * originating from this system by any user who can create a virtual
42  * interface (and this is again enabled by other GNUnet SUID
43  * binaries).  Furthermore, even without the ability to create a
44  * tunnel interface, this code will make it possible to DoS all DNS
45  * traffic originating from the current system, simply by sending it
46  * to nowhere.  
47  *
48  * Naturally, neither of these problems can be helped as this is the
49  * fundamental purpose of the binary.  Certifying that this code is
50  * "safe" thus only means that it doesn't allow anything else (such
51  * as local priv. escalation, etc.). 
52  *
53  * The following list of people have reviewed this code and considered
54  * it safe (within specifications) since the last modification (if you
55  * reviewed it, please have your name added to the list):
56  *
57  * - Christian Grothoff 
58  */
59 #include "platform.h"
60
61 /**
62  * Name and full path of IPTABLES binary.
63  */
64 #define SBIN_IPTABLES "/sbin/iptables"
65
66 /**
67  * Name and full path of IPTABLES binary.
68  */
69 #define SBIN_IP "/sbin/ip"
70
71 /**
72  * Port for DNS traffic.
73  */
74 #define DNS_PORT "53"
75
76 /**
77  * Marker we set for our hijacked DNS traffic.  We use GNUnet's
78  * port (2086) plus the DNS port (53) in HEX to make a 32-bit mark
79  * (which is hopefully long enough to not collide); so
80  * 0x08260035 = 136708149 (hopefully unique enough...).
81  */
82 #define DNS_MARK "136708149"
83
84 /**
85  * Table we use for our DNS rules.  0-255 is the range and
86  * 0, 253, 254 and 255 are already reserved.  As this is about
87  * DNS and as "53" is likely (fingers crossed!) high enough to
88  * not usually conflict with a normal user's setup, we use 53
89  * to give a hint that this has something to do with DNS.
90  */
91 #define DNS_TABLE "53"
92
93
94 /**
95  * Run the given command and wait for it to complete.
96  * 
97  * @param file name of the binary to run
98  * @param cmd command line arguments (as given to 'execv')
99  * @return 0 on success, 1 on any error
100  */
101 static int
102 fork_and_exec (const char *file, 
103                char *const cmd[])
104 {
105   int status;
106   pid_t pid;
107   pid_t ret;
108
109   pid = fork ();
110   if (-1 == pid)
111   {
112     fprintf (stderr, 
113              "fork failed: %s\n", 
114              strerror (errno));
115     return 1;
116   }
117   if (0 == pid)
118   {
119     /* we are the child process */
120     (void) execv (file, cmd);
121     /* can only get here on error */
122     fprintf (stderr, 
123              "exec `%s' failed: %s\n", 
124              file,
125              strerror (errno));
126     _exit (1);
127   }
128   /* keep running waitpid as long as the only error we get is 'EINTR' */
129   while ( (-1 == (ret = waitpid (pid, &status, 0))) &&
130           (errno == EINTR) ); 
131   if (-1 == ret)
132   {
133     fprintf (stderr, 
134              "waitpid failed: %s\n", 
135              strerror (errno));
136     return 1;
137   }
138   if (! (WIFEXITED (status) && (0 == WEXITSTATUS (status))))
139     return 1;
140   /* child process completed and returned success, we're happy */
141   return 0;
142 }
143
144
145 /**
146  * Main function of "gnunet-helper-hijack-dns".  
147  * Use "-d" as the first argument to remove the firewall rules.
148  * The other arguments are the DNS source port to NOT affect
149  * by the rules, followed by the name of the virtual interface
150  * to redirect all of the remaining DNS traffic to.
151  *
152  * @param argc number of arguments
153  * @param argv ["-d"] PORT VTUN
154  * @return 0 on success, otherwise code indicating type of error:
155  *         1 wrong number of arguments
156  *         2 invalid port number
157  *         3 iptables not executable
158  *         4 ip not executable
159  *         8 failed to change routing table, cleanup successfull
160  *         16-31 failed to undo some changes to routing table
161  *         31-47 failed to fully change routing table and then might have failed to undo everything
162  */
163 int
164 main (int argc, char *const*argv)
165 {
166   int delete;
167   unsigned int port;
168   char *virt_dns;
169   char localport[6];
170   int r;
171
172   /* check command-line arguments */
173   if (argc < 3)
174   {
175     fprintf (stderr, 
176              "Syntax: gnunet-helper-hijack-dns [-d] PORT INTERFACENAME\n");
177     return 1;
178   }
179   if (0 == strcmp (argv[1], "-d"))
180     delete = 1;
181   else
182     delete = 0;
183   if (argc != 3 + delete)
184   {
185     fprintf (stderr, 
186              "Syntax: gnunet-helper-hijack-dns [-d] PORT INTERFACENAME\n");
187     return 1;
188   }  
189   port = atoi (argv[1 + delete]);
190   virt_dns = argv[2 + delete];
191   if ( (port == 0) || (port >= 65536) )
192   {
193     fprintf (stderr, 
194              "Port `%u' is invalid\n",
195              port);
196     return 2;
197   }
198   /* verify that the binaries were care about are executable */
199   if (0 != access (SBIN_IPTABLES, X_OK))
200   {
201     fprintf (stderr, 
202              "`%s' is not executable: %s\n", 
203              SBIN_IPTABLES,
204              strerror (errno));
205     return 3;
206   }
207   if (0 != access (SBIN_IP, X_OK))
208   {
209     fprintf (stderr, 
210              "`%s' is not executable: %s\n", 
211              SBIN_IP,
212              strerror (errno));
213     return 4;
214   }
215
216   /* print port number to string for command-line use*/
217   (void) snprintf (localport,
218                    sizeof (localport), 
219                    "%u", 
220                    port);
221
222   /* update routing tables -- this is why we are SUID! */
223   if (! delete)
224   {
225     /* Forward everything from the given local port (with destination
226        to port 53, and only for UDP) without hijacking */
227     {
228       char *const mangle_args[] = 
229         {
230           "iptables", "-t", "mangle", "-I", "OUTPUT", "1", "-p",
231           "udp", "--sport", localport, "--dport", DNS_PORT, "-j",
232           "ACCEPT", NULL
233         };
234       if (0 != fork_and_exec (SBIN_IPTABLES, mangle_args))
235         goto cleanup_mangle_1;
236     }    
237     /* Mark all of the other DNS traffic using our mark DNS_MARK */
238     {
239       char *const mark_args[] =
240         {
241           "iptables", "-t", "mangle", "-I", "OUTPUT", DNS_TABLE, "-p",
242           "udp", "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK,
243           NULL
244         };
245       if (0 != fork_and_exec (SBIN_IPTABLES, mark_args))
246         goto cleanup_mark_2;
247     }
248     /* Forward all marked DNS traffic to our DNS_TABLE */
249     {
250       char *const forward_args[] =
251         {
252           "ip", "rule", "add", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL
253         };
254       if (0 != fork_and_exec (SBIN_IP, forward_args))
255         goto cleanup_forward_3;
256     }
257     /* Finally, add rule in our forwarding table to pass to our virtual interface */
258     {
259       char *const route_args[] =
260         {
261           "ip", "route", "add", "default", "via", virt_dns,
262           "table", DNS_TABLE, NULL
263         };
264       if (0 != fork_and_exec (SBIN_IP, route_args))
265         goto cleanup_route_4;
266     }
267   }
268   else
269   {
270     r = 0;
271     /* delete or clean-up-on-error case */
272 cleanup_route_4:
273     {
274       char *const route_clean_args[] =                   
275         {
276           "ip", "route", "del", "default", "via", virt_dns,
277           "table", DNS_TABLE, NULL
278         };
279       if (0 != fork_and_exec (SBIN_IP, route_clean_args))
280         r += 1;
281     }
282 cleanup_forward_3:
283     {
284       char *const forward_clean_args[] =
285         {
286           "ip", "rule", "del", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL
287         };
288       if (0 != fork_and_exec (SBIN_IP, forward_clean_args))
289         r += 2; 
290     }
291 cleanup_mark_2:
292     {
293       char *const mark_clean_args[] =
294         {
295           "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp",
296           "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, NULL
297         };
298       if (0 != fork_and_exec (SBIN_IPTABLES, mark_clean_args))
299         r += 4;
300     }   
301 cleanup_mangle_1:
302     {
303       char *const mangle_clean_args[] =
304         {
305           "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp",
306           "--sport", localport, "--dport", DNS_PORT, "-j", "ACCEPT",
307           NULL
308         };
309       if (0 != fork_and_exec (SBIN_IPTABLES, mangle_clean_args))
310         r += 8;
311     }
312     if (r != 0)
313     {
314       if (delete)
315         return 16 + r; /* failed to delete */
316       return 32 + r; /* first failed to install, then also failed to clean up! */
317     }
318     if (! delete)
319     {
320       /* got here via goto to clean up handler, failed to install, succeeded with clean up */
321       return 8;
322     }
323   } 
324   /* success ! */
325   return 0;
326 }
327
328 /* end of gnunet-helper-hijack-dns.c */