diff options
author | Christian Grothoff <christian@grothoff.org> | 2012-01-02 12:23:13 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2012-01-02 12:23:13 +0000 |
commit | 7d12168098849a5296a41661263131c1fec2ca8b (patch) | |
tree | 9f547a40b932c284d1750249c29b520ffd24356d /src/dns/gnunet-helper-dns.c | |
parent | f5bee1c9c607cf39a3c22f9c8a7f52db447c1ee9 (diff) | |
download | gnunet-7d12168098849a5296a41661263131c1fec2ca8b.tar.gz gnunet-7d12168098849a5296a41661263131c1fec2ca8b.zip |
DNS helper for DNS redesign
Diffstat (limited to 'src/dns/gnunet-helper-dns.c')
-rw-r--r-- | src/dns/gnunet-helper-dns.c | 949 |
1 files changed, 949 insertions, 0 deletions
diff --git a/src/dns/gnunet-helper-dns.c b/src/dns/gnunet-helper-dns.c new file mode 100644 index 000000000..3660871d8 --- /dev/null +++ b/src/dns/gnunet-helper-dns.c | |||
@@ -0,0 +1,949 @@ | |||
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-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). We then | ||
26 | * allow interacting with our virtual interface via stdin/stdout. | ||
27 | * @author Philipp Tölke | ||
28 | * @author Christian Grothoff | ||
29 | * | ||
30 | * This program alters the Linux firewall rules so that DNS traffic | ||
31 | * that ordinarily exits the system can be intercepted and managed by | ||
32 | * a virtual interface. In order to achieve this, DNS traffic is | ||
33 | * marked with the DNS_MARK given in below and re-routed to a custom | ||
34 | * table with the DNS_TABLE ID given below. Systems and | ||
35 | * administrators must take care to not cause conflicts with these | ||
36 | * values (it was deemed safest to hardcode them as passing these | ||
37 | * values as arguments might permit messing with arbitrary firewall | ||
38 | * rules, which would be dangerous). | ||
39 | * | ||
40 | * The code first sets up the virtual interface, then begins to | ||
41 | * redirect the DNS traffic to it, and then on errors or SIGTERM shuts | ||
42 | * down the virtual interface and removes the rules for the traffic | ||
43 | * redirection. | ||
44 | * | ||
45 | * | ||
46 | * Note that having this binary SUID is only partially safe: it will | ||
47 | * allow redirecting (and intercepting / mangling) of all DNS traffic | ||
48 | * originating from this system by any user who is able to run it. | ||
49 | * Furthermore, this code will make it trivial to DoS all DNS traffic | ||
50 | * originating from the current system, simply by sending it to | ||
51 | * nowhere (redirect stdout to /dev/null). | ||
52 | * | ||
53 | * Naturally, neither of these problems can be helped as this is the | ||
54 | * fundamental purpose of the binary. Certifying that this code is | ||
55 | * "safe" thus only means that it doesn't allow anything else (such | ||
56 | * as local priv. escalation, etc.). | ||
57 | * | ||
58 | * The following list of people have reviewed this code and considered | ||
59 | * it safe (within specifications) since the last modification (if you | ||
60 | * reviewed it, please have your name added to the list): | ||
61 | * | ||
62 | * - Christian Grothoff | ||
63 | */ | ||
64 | #include "platform.h" | ||
65 | |||
66 | #include <linux/if_tun.h> | ||
67 | |||
68 | /** | ||
69 | * Need 'struct GNUNET_MessageHeader'. | ||
70 | */ | ||
71 | #include "gnunet_common.h" | ||
72 | |||
73 | /** | ||
74 | * Need DNS message types. | ||
75 | */ | ||
76 | #include "gnunet_protocols.h" | ||
77 | |||
78 | /** | ||
79 | * Maximum size of a GNUnet message (GNUNET_SERVER_MAX_MESSAGE_SIZE) | ||
80 | */ | ||
81 | #define MAX_SIZE 65536 | ||
82 | |||
83 | #ifndef _LINUX_IN6_H | ||
84 | /** | ||
85 | * This is in linux/include/net/ipv6.h, but not always exported... | ||
86 | */ | ||
87 | struct in6_ifreq | ||
88 | { | ||
89 | struct in6_addr ifr6_addr; | ||
90 | uint32_t ifr6_prefixlen; | ||
91 | unsigned int ifr6_ifindex; | ||
92 | }; | ||
93 | #endif | ||
94 | |||
95 | /** | ||
96 | * Name and full path of IPTABLES binary. | ||
97 | */ | ||
98 | #define SBIN_IPTABLES "/sbin/iptables" | ||
99 | |||
100 | /** | ||
101 | * Name and full path of IPTABLES binary. | ||
102 | */ | ||
103 | #define SBIN_IP "/sbin/ip" | ||
104 | |||
105 | /** | ||
106 | * Port for DNS traffic. | ||
107 | */ | ||
108 | #define DNS_PORT "53" | ||
109 | |||
110 | /** | ||
111 | * Marker we set for our hijacked DNS traffic. We use GNUnet's | ||
112 | * port (2086) plus the DNS port (53) in HEX to make a 32-bit mark | ||
113 | * (which is hopefully long enough to not collide); so | ||
114 | * 0x08260035 = 136708149 (hopefully unique enough...). | ||
115 | */ | ||
116 | #define DNS_MARK "136708149" | ||
117 | |||
118 | /** | ||
119 | * Table we use for our DNS rules. 0-255 is the range and | ||
120 | * 0, 253, 254 and 255 are already reserved. As this is about | ||
121 | * DNS and as "53" is likely (fingers crossed!) high enough to | ||
122 | * not usually conflict with a normal user's setup, we use 53 | ||
123 | * to give a hint that this has something to do with DNS. | ||
124 | */ | ||
125 | #define DNS_TABLE "53" | ||
126 | |||
127 | |||
128 | /** | ||
129 | * Control pipe for shutdown via signal. [0] is the read end, | ||
130 | * [1] is the write end. | ||
131 | */ | ||
132 | static int cpipe[2]; | ||
133 | |||
134 | |||
135 | /** | ||
136 | * Signal handler called to initiate "nice" shutdown. Signals select | ||
137 | * loop via non-bocking pipe 'cpipe'. | ||
138 | * | ||
139 | * @param signal signal number of the signal (not used) | ||
140 | */ | ||
141 | static void | ||
142 | signal_handler (int signal) | ||
143 | { | ||
144 | /* ignore return value, as the signal handler could theoretically | ||
145 | be called many times before the shutdown can actually happen */ | ||
146 | (void) write (cpipe[1], "K", 1); | ||
147 | } | ||
148 | |||
149 | |||
150 | /** | ||
151 | * Run the given command and wait for it to complete. | ||
152 | * | ||
153 | * @param file name of the binary to run | ||
154 | * @param cmd command line arguments (as given to 'execv') | ||
155 | * @return 0 on success, 1 on any error | ||
156 | */ | ||
157 | static int | ||
158 | fork_and_exec (const char *file, | ||
159 | char *const cmd[]) | ||
160 | { | ||
161 | int status; | ||
162 | pid_t pid; | ||
163 | pid_t ret; | ||
164 | |||
165 | pid = fork (); | ||
166 | if (-1 == pid) | ||
167 | { | ||
168 | fprintf (stderr, | ||
169 | "fork failed: %s\n", | ||
170 | strerror (errno)); | ||
171 | return 1; | ||
172 | } | ||
173 | if (0 == pid) | ||
174 | { | ||
175 | /* we are the child process */ | ||
176 | (void) execv (file, cmd); | ||
177 | /* can only get here on error */ | ||
178 | fprintf (stderr, | ||
179 | "exec `%s' failed: %s\n", | ||
180 | file, | ||
181 | strerror (errno)); | ||
182 | _exit (1); | ||
183 | } | ||
184 | /* keep running waitpid as long as the only error we get is 'EINTR' */ | ||
185 | while ( (-1 == (ret = waitpid (pid, &status, 0))) && | ||
186 | (errno == EINTR) ); | ||
187 | if (-1 == ret) | ||
188 | { | ||
189 | fprintf (stderr, | ||
190 | "waitpid failed: %s\n", | ||
191 | strerror (errno)); | ||
192 | return 1; | ||
193 | } | ||
194 | if (! (WIFEXITED (status) && (0 == WEXITSTATUS (status)))) | ||
195 | return 1; | ||
196 | /* child process completed and returned success, we're happy */ | ||
197 | return 0; | ||
198 | } | ||
199 | |||
200 | |||
201 | /** | ||
202 | * Creates a tun-interface called dev; | ||
203 | * | ||
204 | * @param dev is asumed to point to a char[IFNAMSIZ] | ||
205 | * if *dev == '\\0', uses the name supplied by the kernel; | ||
206 | * @return the fd to the tun or -1 on error | ||
207 | */ | ||
208 | static int | ||
209 | init_tun (char *dev) | ||
210 | { | ||
211 | struct ifreq ifr; | ||
212 | int fd; | ||
213 | |||
214 | if (NULL == dev) | ||
215 | { | ||
216 | errno = EINVAL; | ||
217 | return -1; | ||
218 | } | ||
219 | |||
220 | if (-1 == (fd = open ("/dev/net/tun", O_RDWR))) | ||
221 | { | ||
222 | fprintf (stderr, "Error opening `%s': %s\n", "/dev/net/tun", | ||
223 | strerror (errno)); | ||
224 | return -1; | ||
225 | } | ||
226 | |||
227 | if (fd >= FD_SETSIZE) | ||
228 | { | ||
229 | fprintf (stderr, "File descriptor to large: %d", fd); | ||
230 | return -1; | ||
231 | } | ||
232 | |||
233 | memset (&ifr, 0, sizeof (ifr)); | ||
234 | ifr.ifr_flags = IFF_TUN; | ||
235 | |||
236 | if ('\0' != *dev) | ||
237 | strncpy (ifr.ifr_name, dev, IFNAMSIZ); | ||
238 | |||
239 | if (-1 == ioctl (fd, TUNSETIFF, (void *) &ifr)) | ||
240 | { | ||
241 | fprintf (stderr, "Error with ioctl on `%s': %s\n", "/dev/net/tun", | ||
242 | strerror (errno)); | ||
243 | (void) close (fd); | ||
244 | return -1; | ||
245 | } | ||
246 | strcpy (dev, ifr.ifr_name); | ||
247 | return fd; | ||
248 | } | ||
249 | |||
250 | |||
251 | /** | ||
252 | * @brief Sets the IPv6-Address given in address on the interface dev | ||
253 | * | ||
254 | * @param dev the interface to configure | ||
255 | * @param address the IPv6-Address | ||
256 | * @param prefix_len the length of the network-prefix | ||
257 | */ | ||
258 | static void | ||
259 | set_address6 (const char *dev, const char *address, unsigned long prefix_len) | ||
260 | { | ||
261 | struct ifreq ifr; | ||
262 | struct in6_ifreq ifr6; | ||
263 | struct sockaddr_in6 sa6; | ||
264 | int fd; | ||
265 | |||
266 | /* | ||
267 | * parse the new address | ||
268 | */ | ||
269 | memset (&sa6, 0, sizeof (struct sockaddr_in6)); | ||
270 | sa6.sin6_family = AF_INET6; | ||
271 | if (1 != inet_pton (AF_INET6, address, sa6.sin6_addr.s6_addr)) | ||
272 | { | ||
273 | fprintf (stderr, "Failed to parse address `%s': %s\n", address, | ||
274 | strerror (errno)); | ||
275 | exit (1); | ||
276 | } | ||
277 | |||
278 | if (-1 == (fd = socket (PF_INET6, SOCK_DGRAM, 0))) | ||
279 | { | ||
280 | fprintf (stderr, "Error creating socket: %s\n", strerror (errno)); | ||
281 | exit (1); | ||
282 | } | ||
283 | |||
284 | memset (&ifr, 0, sizeof (struct ifreq)); | ||
285 | /* | ||
286 | * Get the index of the if | ||
287 | */ | ||
288 | strncpy (ifr.ifr_name, dev, IFNAMSIZ); | ||
289 | if (-1 == ioctl (fd, SIOGIFINDEX, &ifr)) | ||
290 | { | ||
291 | fprintf (stderr, "ioctl failed at %d: %s\n", __LINE__, strerror (errno)); | ||
292 | (void) close (fd); | ||
293 | exit (1); | ||
294 | } | ||
295 | |||
296 | memset (&ifr6, 0, sizeof (struct in6_ifreq)); | ||
297 | ifr6.ifr6_addr = sa6.sin6_addr; | ||
298 | ifr6.ifr6_ifindex = ifr.ifr_ifindex; | ||
299 | ifr6.ifr6_prefixlen = prefix_len; | ||
300 | |||
301 | /* | ||
302 | * Set the address | ||
303 | */ | ||
304 | if (-1 == ioctl (fd, SIOCSIFADDR, &ifr6)) | ||
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 | /* | ||
313 | * Get the flags | ||
314 | */ | ||
315 | if (-1 == ioctl (fd, SIOCGIFFLAGS, &ifr)) | ||
316 | { | ||
317 | fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, | ||
318 | strerror (errno)); | ||
319 | (void) close (fd); | ||
320 | exit (1); | ||
321 | } | ||
322 | |||
323 | /* | ||
324 | * Add the UP and RUNNING flags | ||
325 | */ | ||
326 | ifr.ifr_flags |= IFF_UP | IFF_RUNNING; | ||
327 | if (-1 == ioctl (fd, SIOCSIFFLAGS, &ifr)) | ||
328 | { | ||
329 | fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, | ||
330 | strerror (errno)); | ||
331 | (void) close (fd); | ||
332 | exit (1); | ||
333 | } | ||
334 | |||
335 | if (0 != close (fd)) | ||
336 | { | ||
337 | fprintf (stderr, "close failed: %s\n", strerror (errno)); | ||
338 | exit (1); | ||
339 | } | ||
340 | } | ||
341 | |||
342 | |||
343 | /** | ||
344 | * @brief Sets the IPv4-Address given in address on the interface dev | ||
345 | * | ||
346 | * @param dev the interface to configure | ||
347 | * @param address the IPv4-Address | ||
348 | * @param mask the netmask | ||
349 | */ | ||
350 | static void | ||
351 | set_address4 (const char *dev, const char *address, const char *mask) | ||
352 | { | ||
353 | int fd; | ||
354 | struct sockaddr_in *addr; | ||
355 | struct ifreq ifr; | ||
356 | |||
357 | memset (&ifr, 0, sizeof (struct ifreq)); | ||
358 | addr = (struct sockaddr_in *) &(ifr.ifr_addr); | ||
359 | addr->sin_family = AF_INET; | ||
360 | |||
361 | /* | ||
362 | * Parse the address | ||
363 | */ | ||
364 | if (1 != inet_pton (AF_INET, address, &addr->sin_addr.s_addr)) | ||
365 | { | ||
366 | fprintf (stderr, "Failed to parse address `%s': %s\n", address, | ||
367 | strerror (errno)); | ||
368 | exit (1); | ||
369 | } | ||
370 | |||
371 | if (-1 == (fd = socket (PF_INET, SOCK_DGRAM, 0))) | ||
372 | { | ||
373 | fprintf (stderr, "Error creating socket: %s\n", strerror (errno)); | ||
374 | exit (1); | ||
375 | } | ||
376 | |||
377 | strncpy (ifr.ifr_name, dev, IFNAMSIZ); | ||
378 | |||
379 | /* | ||
380 | * Set the address | ||
381 | */ | ||
382 | if (-1 == ioctl (fd, SIOCSIFADDR, &ifr)) | ||
383 | { | ||
384 | fprintf (stderr, "ioctl failed at %d: %s\n", __LINE__, strerror (errno)); | ||
385 | (void) close (fd); | ||
386 | exit (1); | ||
387 | } | ||
388 | |||
389 | /* | ||
390 | * Parse the netmask | ||
391 | */ | ||
392 | addr = (struct sockaddr_in *) &(ifr.ifr_netmask); | ||
393 | if (1 != inet_pton (AF_INET, mask, &addr->sin_addr.s_addr)) | ||
394 | { | ||
395 | fprintf (stderr, "Failed to parse address `%s': %s\n", mask, | ||
396 | strerror (errno)); | ||
397 | (void) close (fd); | ||
398 | exit (1); | ||
399 | } | ||
400 | |||
401 | /* | ||
402 | * Set the netmask | ||
403 | */ | ||
404 | if (-1 == ioctl (fd, SIOCSIFNETMASK, &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 | /* | ||
413 | * Get the flags | ||
414 | */ | ||
415 | if (-1 == ioctl (fd, SIOCGIFFLAGS, &ifr)) | ||
416 | { | ||
417 | fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, | ||
418 | strerror (errno)); | ||
419 | (void) close (fd); | ||
420 | exit (1); | ||
421 | } | ||
422 | |||
423 | /* | ||
424 | * Add the UP and RUNNING flags | ||
425 | */ | ||
426 | ifr.ifr_flags |= IFF_UP | IFF_RUNNING; | ||
427 | if (-1 == ioctl (fd, SIOCSIFFLAGS, &ifr)) | ||
428 | { | ||
429 | fprintf (stderr, "ioctl failed at line %d: %s\n", __LINE__, | ||
430 | strerror (errno)); | ||
431 | (void) close (fd); | ||
432 | exit (1); | ||
433 | } | ||
434 | |||
435 | if (0 != close (fd)) | ||
436 | { | ||
437 | fprintf (stderr, "close failed: %s\n", strerror (errno)); | ||
438 | (void) close (fd); | ||
439 | exit (1); | ||
440 | } | ||
441 | } | ||
442 | |||
443 | |||
444 | /** | ||
445 | * Start forwarding to and from the tunnel. This function runs with | ||
446 | * "reduced" priviledges (saved UID is still 0, but effective UID is | ||
447 | * the real user ID). | ||
448 | * | ||
449 | * @param fd_tun tunnel FD | ||
450 | */ | ||
451 | static void | ||
452 | run (int fd_tun) | ||
453 | { | ||
454 | /* | ||
455 | * The buffer filled by reading from fd_tun | ||
456 | */ | ||
457 | unsigned char buftun[MAX_SIZE]; | ||
458 | ssize_t buftun_size = 0; | ||
459 | unsigned char *buftun_read = NULL; | ||
460 | |||
461 | /* | ||
462 | * The buffer filled by reading from stdin | ||
463 | */ | ||
464 | unsigned char bufin[MAX_SIZE]; | ||
465 | ssize_t bufin_size = 0; | ||
466 | size_t bufin_rpos = 0; | ||
467 | unsigned char *bufin_read = NULL; | ||
468 | fd_set fds_w; | ||
469 | fd_set fds_r; | ||
470 | int max; | ||
471 | |||
472 | while (1) | ||
473 | { | ||
474 | FD_ZERO (&fds_w); | ||
475 | FD_ZERO (&fds_r); | ||
476 | |||
477 | /* | ||
478 | * We are supposed to read and the buffer is empty | ||
479 | * -> select on read from tun | ||
480 | */ | ||
481 | if (0 == buftun_size) | ||
482 | FD_SET (fd_tun, &fds_r); | ||
483 | |||
484 | /* | ||
485 | * We are supposed to read and the buffer is not empty | ||
486 | * -> select on write to stdout | ||
487 | */ | ||
488 | if (0 != buftun_size) | ||
489 | FD_SET (1, &fds_w); | ||
490 | |||
491 | /* | ||
492 | * We are supposed to write and the buffer is empty | ||
493 | * -> select on read from stdin | ||
494 | */ | ||
495 | if (NULL == bufin_read) | ||
496 | FD_SET (0, &fds_r); | ||
497 | |||
498 | /* | ||
499 | * We are supposed to write and the buffer is not empty | ||
500 | * -> select on write to tun | ||
501 | */ | ||
502 | if (NULL != bufin_read) | ||
503 | FD_SET (fd_tun, &fds_w); | ||
504 | |||
505 | FD_SET (cpipe[0], &fds_r); | ||
506 | max = (fd_tun > cpipe[0]) ? fd_tun : cpipe[0]; | ||
507 | |||
508 | int r = select (max + 1, &fds_r, &fds_w, NULL, NULL); | ||
509 | |||
510 | if (-1 == r) | ||
511 | { | ||
512 | if (EINTR == errno) | ||
513 | continue; | ||
514 | fprintf (stderr, "select failed: %s\n", strerror (errno)); | ||
515 | return; | ||
516 | } | ||
517 | |||
518 | if (r > 0) | ||
519 | { | ||
520 | if (FD_ISSET (cpipe[0], &fds_r)) | ||
521 | return; /* aborted by signal */ | ||
522 | |||
523 | if (FD_ISSET (fd_tun, &fds_r)) | ||
524 | { | ||
525 | buftun_size = | ||
526 | read (fd_tun, buftun + sizeof (struct GNUNET_MessageHeader), | ||
527 | MAX_SIZE - sizeof (struct GNUNET_MessageHeader)); | ||
528 | if (-1 == buftun_size) | ||
529 | { | ||
530 | if ( (errno == EINTR) || | ||
531 | (errno == EAGAIN) ) | ||
532 | continue; | ||
533 | fprintf (stderr, "read-error: %s\n", strerror (errno)); | ||
534 | return; | ||
535 | } | ||
536 | if (0 == buftun_size) | ||
537 | { | ||
538 | fprintf (stderr, "EOF on tun\n"); | ||
539 | return; | ||
540 | } | ||
541 | buftun_read = buftun; | ||
542 | { | ||
543 | struct GNUNET_MessageHeader *hdr = | ||
544 | (struct GNUNET_MessageHeader *) buftun; | ||
545 | buftun_size += sizeof (struct GNUNET_MessageHeader); | ||
546 | hdr->type = htons (GNUNET_MESSAGE_TYPE_DNS_HELPER); | ||
547 | hdr->size = htons (buftun_size); | ||
548 | } | ||
549 | } | ||
550 | else if (FD_ISSET (1, &fds_w)) | ||
551 | { | ||
552 | ssize_t written = write (1, buftun_read, buftun_size); | ||
553 | |||
554 | if (-1 == written) | ||
555 | { | ||
556 | if ( (errno == EINTR) || | ||
557 | (errno == EAGAIN) ) | ||
558 | continue; | ||
559 | fprintf (stderr, "write-error to stdout: %s\n", strerror (errno)); | ||
560 | return; | ||
561 | } | ||
562 | if (0 == written) | ||
563 | { | ||
564 | fprintf (stderr, "write returned 0\n"); | ||
565 | return; | ||
566 | } | ||
567 | buftun_size -= written; | ||
568 | buftun_read += written; | ||
569 | } | ||
570 | |||
571 | if (FD_ISSET (0, &fds_r)) | ||
572 | { | ||
573 | bufin_size = read (0, bufin + bufin_rpos, MAX_SIZE - bufin_rpos); | ||
574 | if (-1 == bufin_size) | ||
575 | { | ||
576 | if ( (errno == EINTR) || | ||
577 | (errno == EAGAIN) ) | ||
578 | continue; | ||
579 | fprintf (stderr, "read-error: %s\n", strerror (errno)); | ||
580 | return; | ||
581 | } | ||
582 | if (0 == bufin_size) | ||
583 | { | ||
584 | fprintf (stderr, "EOF on stdin\n"); | ||
585 | return; | ||
586 | } | ||
587 | { | ||
588 | struct GNUNET_MessageHeader *hdr; | ||
589 | |||
590 | PROCESS_BUFFER: | ||
591 | bufin_rpos += bufin_size; | ||
592 | if (bufin_rpos < sizeof (struct GNUNET_MessageHeader)) | ||
593 | continue; | ||
594 | hdr = (struct GNUNET_MessageHeader *) bufin; | ||
595 | if (ntohs (hdr->type) != GNUNET_MESSAGE_TYPE_DNS_HELPER) | ||
596 | { | ||
597 | fprintf (stderr, "protocol violation!\n"); | ||
598 | return; | ||
599 | } | ||
600 | if (ntohs (hdr->size) > bufin_rpos) | ||
601 | continue; | ||
602 | bufin_read = bufin + sizeof (struct GNUNET_MessageHeader); | ||
603 | bufin_size = ntohs (hdr->size) - sizeof (struct GNUNET_MessageHeader); | ||
604 | bufin_rpos -= bufin_size + sizeof (struct GNUNET_MessageHeader); | ||
605 | } | ||
606 | } | ||
607 | else if (FD_ISSET (fd_tun, &fds_w)) | ||
608 | { | ||
609 | ssize_t written = write (fd_tun, bufin_read, bufin_size); | ||
610 | |||
611 | if (-1 == written) | ||
612 | { | ||
613 | if ( (errno == EINTR) || | ||
614 | (errno == EAGAIN) ) | ||
615 | continue; | ||
616 | fprintf (stderr, "write-error to tun: %s\n", strerror (errno)); | ||
617 | return; | ||
618 | } | ||
619 | if (0 == written) | ||
620 | { | ||
621 | fprintf (stderr, "write returned 0\n"); | ||
622 | return; | ||
623 | } | ||
624 | { | ||
625 | bufin_size -= written; | ||
626 | bufin_read += written; | ||
627 | if (0 == bufin_size) | ||
628 | { | ||
629 | memmove (bufin, bufin_read, bufin_rpos); | ||
630 | bufin_read = NULL; /* start reading again */ | ||
631 | bufin_size = 0; | ||
632 | goto PROCESS_BUFFER; | ||
633 | } | ||
634 | } | ||
635 | } | ||
636 | } | ||
637 | } | ||
638 | } | ||
639 | |||
640 | |||
641 | /** | ||
642 | * Main function of "gnunet-helper-dns", which opens a VPN tunnel interface, | ||
643 | * redirects all outgoing DNS traffic (except from the specified port) to that | ||
644 | * interface and then passes traffic from and to the interface via stdin/stdout. | ||
645 | * | ||
646 | * Once stdin/stdout close or have other errors, the tunnel is closed and the | ||
647 | * DNS traffic redirection is stopped. | ||
648 | * | ||
649 | * @param argc number of arguments | ||
650 | * @param argv 0: binary name (should be "gnunet-helper-vpn") | ||
651 | * 1: tunnel interface name (typically "gnunet-dns") | ||
652 | * 2: IPv6 address for the tunnel ("FE80::1") | ||
653 | * 3: IPv6 netmask length in bits ("64") | ||
654 | * 4: IPv4 address for the tunnel ("1.2.3.4") | ||
655 | * 5: IPv4 netmask ("255.255.0.0") | ||
656 | * 6: PORT to not hijack ("55533") | ||
657 | * @return 0 on success, otherwise code indicating type of error: | ||
658 | * 1 wrong number of arguments | ||
659 | * 2 invalid arguments (i.e. port number / prefix length wrong) | ||
660 | * 3 iptables not executable | ||
661 | * 4 ip not executable | ||
662 | * 5 failed to initialize tunnel interface | ||
663 | * 6 failed to initialize control pipe | ||
664 | * 8 failed to change routing table, cleanup successful | ||
665 | * 9-23 failed to undo some changes to routing table | ||
666 | * 24 failed to drop privs | ||
667 | * 25-39 failed to drop privs and then failed to undo some changes to routing table | ||
668 | * 40 failed to regain privs | ||
669 | * 41-55 failed to regain prisv and then failed to undo some changes to routing table | ||
670 | * 255 failed to handle kill signal properly | ||
671 | */ | ||
672 | int | ||
673 | main (int argc, char *const*argv) | ||
674 | { | ||
675 | unsigned int port; | ||
676 | char localport[6]; | ||
677 | int r; | ||
678 | char dev[IFNAMSIZ]; | ||
679 | int fd_tun; | ||
680 | |||
681 | if (7 != argc) | ||
682 | { | ||
683 | fprintf (stderr, "Fatal: must supply 6 arguments!\n"); | ||
684 | return 1; | ||
685 | } | ||
686 | |||
687 | /* verify that the binaries were care about are executable */ | ||
688 | if (0 != access (SBIN_IPTABLES, X_OK)) | ||
689 | { | ||
690 | fprintf (stderr, | ||
691 | "`%s' is not executable: %s\n", | ||
692 | SBIN_IPTABLES, | ||
693 | strerror (errno)); | ||
694 | return 3; | ||
695 | } | ||
696 | if (0 != access (SBIN_IP, X_OK)) | ||
697 | { | ||
698 | fprintf (stderr, | ||
699 | "`%s' is not executable: %s\n", | ||
700 | SBIN_IP, | ||
701 | strerror (errno)); | ||
702 | return 4; | ||
703 | } | ||
704 | |||
705 | /* validate port number */ | ||
706 | port = atoi (argv[6]); | ||
707 | if ( (port == 0) || (port >= 65536) ) | ||
708 | { | ||
709 | fprintf (stderr, | ||
710 | "Port `%u' is invalid\n", | ||
711 | port); | ||
712 | return 2; | ||
713 | } | ||
714 | /* print port number to string for command-line use*/ | ||
715 | (void) snprintf (localport, | ||
716 | sizeof (localport), | ||
717 | "%u", | ||
718 | port); | ||
719 | |||
720 | /* do not die on SIGPIPE */ | ||
721 | if (SIG_ERR == signal (SIGPIPE, SIG_IGN)) | ||
722 | { | ||
723 | fprintf (stderr, "Failed to protect against SIGPIPE: %s\n", | ||
724 | strerror (errno)); | ||
725 | return 7; | ||
726 | } | ||
727 | |||
728 | /* setup pipe to shutdown nicely on SIGINT */ | ||
729 | if (0 != pipe (cpipe)) | ||
730 | { | ||
731 | fprintf (stderr, | ||
732 | "Fatal: could not setup control pipe: %s\n", | ||
733 | strerror (errno)); | ||
734 | return 6; | ||
735 | } | ||
736 | if (cpipe[0] >= FD_SETSIZE) | ||
737 | { | ||
738 | fprintf (stderr, "Pipe file descriptor to large: %d", cpipe[0]); | ||
739 | (void) close (cpipe[0]); | ||
740 | (void) close (cpipe[1]); | ||
741 | return 6; | ||
742 | } | ||
743 | { | ||
744 | /* make pipe non-blocking, as we theoretically could otherwise block | ||
745 | in the signal handler */ | ||
746 | int flags = fcntl (cpipe[1], F_GETFL); | ||
747 | if (-1 == flags) | ||
748 | { | ||
749 | fprintf (stderr, "Failed to read flags for pipe: %s", strerror (errno)); | ||
750 | (void) close (cpipe[0]); | ||
751 | (void) close (cpipe[1]); | ||
752 | return 6; | ||
753 | } | ||
754 | flags |= O_NONBLOCK; | ||
755 | if (0 != fcntl (cpipe[1], F_SETFL, flags)) | ||
756 | { | ||
757 | fprintf (stderr, "Failed to make pipe non-blocking: %s", strerror (errno)); | ||
758 | (void) close (cpipe[0]); | ||
759 | (void) close (cpipe[1]); | ||
760 | return 6; | ||
761 | } | ||
762 | } | ||
763 | if (SIG_ERR == signal (SIGINT, &signal_handler)) | ||
764 | { | ||
765 | fprintf (stderr, | ||
766 | "Fatal: could not initialize signal handler: %s\n", | ||
767 | strerror (errno)); | ||
768 | (void) close (cpipe[0]); | ||
769 | (void) close (cpipe[1]); | ||
770 | return 7; | ||
771 | } | ||
772 | |||
773 | |||
774 | /* get interface name */ | ||
775 | strncpy (dev, argv[1], IFNAMSIZ); | ||
776 | dev[IFNAMSIZ - 1] = '\0'; | ||
777 | |||
778 | /* now open virtual interface (first part that requires root) */ | ||
779 | if (-1 == (fd_tun = init_tun (dev))) | ||
780 | { | ||
781 | fprintf (stderr, "Fatal: could not initialize tun-interface\n"); | ||
782 | (void) signal (SIGINT, SIG_IGN); | ||
783 | (void) close (cpipe[0]); | ||
784 | (void) close (cpipe[1]); | ||
785 | return 5; | ||
786 | } | ||
787 | |||
788 | /* now set interface addresses */ | ||
789 | { | ||
790 | const char *address = argv[2]; | ||
791 | long prefix_len = atol (argv[3]); | ||
792 | |||
793 | if ((prefix_len < 1) || (prefix_len > 127)) | ||
794 | { | ||
795 | fprintf (stderr, "Fatal: prefix_len out of range\n"); | ||
796 | (void) signal (SIGINT, SIG_IGN); | ||
797 | (void) close (cpipe[0]); | ||
798 | (void) close (cpipe[1]); | ||
799 | return 2; | ||
800 | } | ||
801 | set_address6 (dev, address, prefix_len); | ||
802 | } | ||
803 | |||
804 | { | ||
805 | const char *address = argv[4]; | ||
806 | const char *mask = argv[5]; | ||
807 | |||
808 | set_address4 (dev, address, mask); | ||
809 | } | ||
810 | |||
811 | /* update routing tables -- next part why we need SUID! */ | ||
812 | /* Forward everything from the given local port (with destination | ||
813 | to port 53, and only for UDP) without hijacking */ | ||
814 | r = 8; /* failed to fully setup routing table */ | ||
815 | { | ||
816 | char *const mangle_args[] = | ||
817 | { | ||
818 | "iptables", "-t", "mangle", "-I", "OUTPUT", "1", "-p", | ||
819 | "udp", "--sport", localport, "--dport", DNS_PORT, "-j", | ||
820 | "ACCEPT", NULL | ||
821 | }; | ||
822 | if (0 != fork_and_exec (SBIN_IPTABLES, mangle_args)) | ||
823 | goto cleanup_mangle_1; | ||
824 | } | ||
825 | /* Mark all of the other DNS traffic using our mark DNS_MARK */ | ||
826 | { | ||
827 | char *const mark_args[] = | ||
828 | { | ||
829 | "iptables", "-t", "mangle", "-I", "OUTPUT", DNS_TABLE, "-p", | ||
830 | "udp", "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, | ||
831 | NULL | ||
832 | }; | ||
833 | if (0 != fork_and_exec (SBIN_IPTABLES, mark_args)) | ||
834 | goto cleanup_mark_2; | ||
835 | } | ||
836 | /* Forward all marked DNS traffic to our DNS_TABLE */ | ||
837 | { | ||
838 | char *const forward_args[] = | ||
839 | { | ||
840 | "ip", "rule", "add", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL | ||
841 | }; | ||
842 | if (0 != fork_and_exec (SBIN_IP, forward_args)) | ||
843 | goto cleanup_forward_3; | ||
844 | } | ||
845 | /* Finally, add rule in our forwarding table to pass to our virtual interface */ | ||
846 | { | ||
847 | char *const route_args[] = | ||
848 | { | ||
849 | "ip", "route", "add", "default", "via", dev, | ||
850 | "table", DNS_TABLE, NULL | ||
851 | }; | ||
852 | if (0 != fork_and_exec (SBIN_IP, route_args)) | ||
853 | goto cleanup_route_4; | ||
854 | } | ||
855 | |||
856 | /* drop privs *except* for the saved UID; this is not perfect, but better | ||
857 | than doing nothing */ | ||
858 | uid_t uid = getuid (); | ||
859 | #ifdef HAVE_SETRESUID | ||
860 | if (0 != setresuid (uid, uid, 0)) | ||
861 | { | ||
862 | fprintf (stderr, "Failed to setresuid: %s\n", strerror (errno)); | ||
863 | r = 24; | ||
864 | goto cleanup_route_4; | ||
865 | } | ||
866 | #else | ||
867 | /* Note: no 'setuid' here as we must keep our saved UID as root */ | ||
868 | if (0 != seteuid (uid)) | ||
869 | { | ||
870 | fprintf (stderr, "Failed to seteuid: %s\n", strerror (errno)); | ||
871 | r = 24; | ||
872 | goto cleanup_route_4; | ||
873 | } | ||
874 | #endif | ||
875 | |||
876 | r = 0; /* did fully setup routing table (if nothing else happens, we were successful!) */ | ||
877 | |||
878 | /* now forward until we hit a problem */ | ||
879 | run (fd_tun); | ||
880 | (void) close (fd_tun); | ||
881 | |||
882 | /* now need to regain privs so we can remove the firewall rules we added! */ | ||
883 | #ifdef HAVE_SETRESUID | ||
884 | if (0 != setresuid (uid, 0, 0)) | ||
885 | { | ||
886 | fprintf (stderr, "Failed to setresuid back to root: %s\n", strerror (errno)); | ||
887 | r = 40; | ||
888 | goto cleanup_route_4; | ||
889 | } | ||
890 | #else | ||
891 | if (0 != seteuid (0)) | ||
892 | { | ||
893 | fprintf (stderr, "Failed to seteuid back to root: %s\n", strerror (errno)); | ||
894 | r = 40; | ||
895 | goto cleanup_route_4; | ||
896 | } | ||
897 | #endif | ||
898 | |||
899 | /* update routing tables again -- this is why we could not fully drop privs */ | ||
900 | /* now undo updating of routing tables; normal exit or clean-up-on-error case */ | ||
901 | cleanup_route_4: | ||
902 | { | ||
903 | char *const route_clean_args[] = | ||
904 | { | ||
905 | "ip", "route", "del", "default", "via", dev, | ||
906 | "table", DNS_TABLE, NULL | ||
907 | }; | ||
908 | if (0 != fork_and_exec (SBIN_IP, route_clean_args)) | ||
909 | r += 1; | ||
910 | } | ||
911 | cleanup_forward_3: | ||
912 | { | ||
913 | char *const forward_clean_args[] = | ||
914 | { | ||
915 | "ip", "rule", "del", "fwmark", DNS_MARK, "table", DNS_TABLE, NULL | ||
916 | }; | ||
917 | if (0 != fork_and_exec (SBIN_IP, forward_clean_args)) | ||
918 | r += 2; | ||
919 | } | ||
920 | cleanup_mark_2: | ||
921 | { | ||
922 | char *const mark_clean_args[] = | ||
923 | { | ||
924 | "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", | ||
925 | "--dport", DNS_PORT, "-j", "MARK", "--set-mark", DNS_MARK, NULL | ||
926 | }; | ||
927 | if (0 != fork_and_exec (SBIN_IPTABLES, mark_clean_args)) | ||
928 | r += 4; | ||
929 | } | ||
930 | cleanup_mangle_1: | ||
931 | { | ||
932 | char *const mangle_clean_args[] = | ||
933 | { | ||
934 | "iptables", "-t", "mangle", "-D", "OUTPUT", "-p", "udp", | ||
935 | "--sport", localport, "--dport", DNS_PORT, "-j", "ACCEPT", | ||
936 | NULL | ||
937 | }; | ||
938 | if (0 != fork_and_exec (SBIN_IPTABLES, mangle_clean_args)) | ||
939 | r += 8; | ||
940 | } | ||
941 | |||
942 | /* remove SIGINT handler so we can close the pipes */ | ||
943 | (void) signal (SIGINT, SIG_IGN); | ||
944 | (void) close (cpipe[0]); | ||
945 | (void) close (cpipe[1]); | ||
946 | return r; | ||
947 | } | ||
948 | |||
949 | /* end of gnunet-helper-hijack-dns.c */ | ||