Commit | Line | Data |
---|---|---|
1c1a9fa1 | 1 | /* |
5124214b | 2 | * userv service (or standalone program) for per-user IP subranges. |
3 | * | |
4 | * When invoked appropriately, it creates a point-to-point network | |
5 | * interface with specified parameters. It arranges for packets sent out | |
6 | * via that interface by the kernel to appear on its own stdout in SLIP or | |
7 | * CSLIP encoding, and packets injected into its own stdin to be given to | |
8 | * the kernel as if received on that interface. Optionally, additional | |
9 | * routes can be set up to arrange for traffic for other address ranges to | |
10 | * be routed through the new interface. | |
11 | * | |
12 | * This is the service program, which is invoked as root from userv (or may | |
13 | * be invoked firectly). | |
14 | * | |
15 | * Its arguments are supposed to be, in order, as follows: | |
16 | * | |
17 | * The first two arguments are usually supplied by the userv | |
18 | * configuration. See the file `ipif/ipif' in the source tree, which | |
19 | * is installed in /etc/userv/services.d/ipif by `make install': | |
1c1a9fa1 | 20 | * |
1e963473 | 21 | * <config> |
5124214b | 22 | * |
23 | * Specifies address ranges and gids which own them. The default | |
24 | * configuration supplies /etc/userv/ipif-networks, which is then read | |
25 | * for a list of entries, one per line. | |
26 | * | |
27 | * -- | |
28 | * Serves to separate the user-supplied and therefore untrusted | |
29 | * arguments from the trusted first argument. | |
30 | * | |
31 | * The remaining arguments are supplied by the (untrusted) caller: | |
32 | * | |
4f937f54 | 33 | * <local-addr>,<peer-addr>,<mtu>[,[<proto>]] |
5124214b | 34 | * |
22410bfa IJ |
35 | * As for slattach. The only supported protocol is slip. |
36 | * Alternatively, set to `debug' to print debugging info and | |
37 | * exit. <local-addr> is address of the interface to be created | |
5124214b | 38 | * on the local system; <peer-addr> is the address of the |
39 | * point-to-point peer. They must be actual addresses (not | |
40 | * hostnames). | |
41 | * | |
1c1a9fa1 | 42 | * <prefix>/<mask>,<prefix>/<mask>,... |
5124214b | 43 | * |
44 | * List of additional routes to add for this interface. routes will | |
45 | * be set up on the local system arranging for packets for those | |
46 | * networks to be sent via the created interface. <prefix> must be an | |
47 | * IPv4 address, and mask must be an integer (dotted-quad masks are | |
48 | * not supported). If no additional routes are to be set up, use `-' | |
49 | * or supply an empty argument. | |
50 | * | |
447f36af IJ |
51 | * Each <config> item - whether a line in a file such as |
52 | * /etc/userv/ipif-networks, or the single trusted argument supplied | |
53 | * on the service program command line - is one of: | |
5124214b | 54 | * |
55 | * /<config-file-name> | |
56 | * ./<config-file-name> | |
57 | * ../<config-file-name> | |
58 | * | |
59 | * Reads a file which contains lines which are each <config> | |
60 | * items. | |
61 | * | |
e6a01344 | 62 | * <gid>,[=][-|+]<prefix>/<len>(-|+<prefix>/<len>...)[,<junk>] |
5124214b | 63 | * |
64 | * Indicates that <gid> may allocate addresses in the relevant address | |
65 | * range (<junk> is ignored). <gid> must be numeric. To specify a | |
66 | * single host address, you must specify a mask of /32. If `=' is | |
67 | * specified then the specific subrange is only allowed for the local | |
68 | * endpoint address, but not for remote addresses. | |
69 | * | |
e6a01344 | 70 | * More than one range may be given, with each range prefixed |
71 | * by + or -. In this case each address range in the rule will | |
72 | * scanned in order, and the first range in the rule that matches | |
73 | * any desired rule will count: if that first matching range is | |
74 | * prefixed by `+' (or nothing) then the rule applies, if it | |
75 | * is prefixed by `-' (or nothing matches), the rule does not. | |
76 | * | |
5124214b | 77 | * * |
78 | * Means that anything is to be permitted. This should not appear in | |
79 | * /etc/userv/ipif-networks, as that would permit any user on the | |
80 | * system to create any interfaces with any addresses and routes | |
81 | * attached. It is provided so that root can usefully invoke the ipif | |
82 | * service program directly (not via userv), without needing to set up | |
83 | * permissions in /etc/userv/ipif-networks. | |
84 | * | |
85 | * #... | |
86 | * | |
87 | * Comment. Blank lines are also ignored. | |
88 | * | |
89 | * NB: Permission is granted if _any_ config entry matches the request. | |
90 | * | |
91 | * The service program should be run from userv with no-disconnect-hup. | |
1c1a9fa1 | 92 | */ |
caa68336 | 93 | /* |
c07be359 | 94 | * This file is part of ipif, part of userv-utils |
caa68336 | 95 | * |
9028e234 IJ |
96 | * Copyright 1996-2013 Ian Jackson <ijackson@chiark.greenend.org.uk> |
97 | * Copyright 1998 David Damerell <damerell@chiark.greenend.org.uk> | |
98 | * Copyright 1999,2003 | |
99 | * Chancellor Masters and Scholars of the University of Cambridge | |
100 | * Copyright 2010 Tony Finch <fanf@dotat.at> | |
101 | * | |
caa68336 | 102 | * This is free software; you can redistribute it and/or modify it |
103 | * under the terms of the GNU General Public License as published by | |
9028e234 | 104 | * the Free Software Foundation; either version 3 of the License, or |
caa68336 | 105 | * (at your option) any later version. |
106 | * | |
107 | * This program is distributed in the hope that it will be useful, but | |
108 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
109 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
110 | * General Public License for more details. | |
111 | * | |
112 | * You should have received a copy of the GNU General Public License | |
9028e234 | 113 | * along with userv-utils; if not, see http://www.gnu.org/licenses/. |
caa68336 | 114 | */ |
1c1a9fa1 | 115 | |
116 | #include <stdio.h> | |
117 | #include <string.h> | |
118 | #include <stdlib.h> | |
119 | #include <assert.h> | |
120 | #include <errno.h> | |
1e963473 | 121 | #include <stdarg.h> |
122 | #include <ctype.h> | |
123 | #include <limits.h> | |
02b2392d | 124 | #include <signal.h> |
125 | #include <unistd.h> | |
22fd0ebe IJ |
126 | #include <stdint.h> |
127 | #include <poll.h> | |
4f937f54 | 128 | #include <stddef.h> |
02b2392d | 129 | |
130 | #include <sys/types.h> | |
131 | #include <sys/wait.h> | |
132 | #include <sys/stat.h> | |
1c1a9fa1 | 133 | |
22fd0ebe IJ |
134 | #include <sys/types.h> |
135 | #include <sys/ioctl.h> | |
136 | #include <sys/socket.h> | |
137 | ||
138 | #include <sys/stat.h> | |
139 | #include <fcntl.h> | |
140 | ||
141 | #include <linux/if.h> | |
142 | #include <linux/if_tun.h> | |
143 | ||
1e963473 | 144 | #define NARGS 4 |
415964dd | 145 | #define MAXEXROUTES 50 |
a48a8f0d | 146 | #define ATXTLEN 16 |
1c1a9fa1 | 147 | |
1e963473 | 148 | static const unsigned long gidmaxval= (unsigned long)((gid_t)-2); |
0ae8e686 | 149 | static const char *const protos_ok[]= { "slip", 0 }; |
02b2392d | 150 | static const int signals[]= { SIGHUP, SIGINT, SIGTERM, 0 }; |
1e963473 | 151 | |
152 | static const char *configstr, *proto; | |
1c1a9fa1 | 153 | static unsigned long localaddr, peeraddr, mtu; |
e6a01344 | 154 | static int localpming, peerpming; |
1e963473 | 155 | static int localallow, peerallow, allallow; |
1c1a9fa1 | 156 | static int nexroutes; |
157 | static struct exroute { | |
158 | unsigned long prefix, mask; | |
e6a01344 | 159 | int allow, pming; |
1c1a9fa1 | 160 | char prefixtxt[ATXTLEN], masktxt[ATXTLEN]; |
161 | } exroutes[MAXEXROUTES]; | |
162 | ||
1e963473 | 163 | static char localtxt[ATXTLEN]; |
164 | static char peertxt[ATXTLEN]; | |
165 | ||
166 | static struct pplace { | |
167 | struct pplace *parent; | |
168 | const char *filename; | |
169 | int lineno; | |
170 | } *cpplace; | |
171 | ||
02b2392d | 172 | |
22fd0ebe IJ |
173 | static int tunfd; |
174 | static char *ifname; | |
02b2392d | 175 | |
176 | ||
177 | static void terminate(int estatus) { | |
02b2392d | 178 | exit(estatus); |
179 | } | |
180 | ||
181 | ||
1e963473 | 182 | static void fatal(const char *fmt, ...) |
183 | __attribute__((format(printf,1,2))); | |
184 | static void fatal(const char *fmt, ...) { | |
185 | va_list al; | |
186 | va_start(al,fmt); | |
1c1a9fa1 | 187 | |
1e963473 | 188 | fputs("userv-ipif service: fatal error: ",stderr); |
189 | vfprintf(stderr, fmt, al); | |
190 | putc('\n',stderr); | |
02b2392d | 191 | terminate(8); |
1c1a9fa1 | 192 | } |
193 | ||
1e963473 | 194 | static void sysfatal(const char *fmt, ...) |
195 | __attribute__((format(printf,1,2))); | |
196 | static void sysfatal(const char *fmt, ...) { | |
197 | va_list al; | |
198 | int e; | |
199 | ||
200 | e= errno; | |
201 | va_start(al,fmt); | |
202 | ||
6d360a53 | 203 | fputs("userv-ipif service: fatal system error: ",stderr); |
1e963473 | 204 | vfprintf(stderr, fmt, al); |
6d360a53 | 205 | fprintf(stderr,": %s\n", strerror(e)); |
02b2392d | 206 | terminate(12); |
1c1a9fa1 | 207 | } |
1e963473 | 208 | |
209 | ||
210 | static void badusage(const char *fmt, ...) | |
211 | __attribute__((format(printf,1,2))); | |
212 | static void badusage(const char *fmt, ...) { | |
213 | va_list al; | |
214 | struct pplace *cpp; | |
1c1a9fa1 | 215 | |
1e963473 | 216 | if (cpplace) { |
217 | fprintf(stderr, | |
218 | "userv-ipif service: %s:%d: ", | |
219 | cpplace->filename, cpplace->lineno); | |
220 | } else { | |
221 | fputs("userv-ipif service: invalid usage: ",stderr); | |
222 | } | |
223 | va_start(al,fmt); | |
224 | vfprintf(stderr, fmt, al); | |
225 | putc('\n',stderr); | |
226 | ||
227 | if (cpplace) { | |
228 | for (cpp=cpplace->parent; cpp; cpp=cpp->parent) { | |
229 | fprintf(stderr, | |
230 | "userv-ipif service: %s:%d: ... in file included from here\n", | |
231 | cpp->filename, cpp->lineno); | |
232 | } | |
233 | } | |
02b2392d | 234 | terminate(16); |
1c1a9fa1 | 235 | } |
236 | ||
237 | static char *ip2txt(unsigned long addr, char *buf) { | |
238 | sprintf(buf, "%lu.%lu.%lu.%lu", | |
239 | (addr>>24) & 0x0ff, | |
240 | (addr>>16) & 0x0ff, | |
241 | (addr>>8) & 0x0ff, | |
242 | (addr) & 0x0ff); | |
243 | return buf; | |
244 | } | |
245 | ||
246 | static unsigned long eat_number(const char **argp, const char *what, | |
247 | unsigned long min, unsigned long max, | |
248 | const char *endchars, int *endchar_r) { | |
249 | /* If !endchars then the endchar must be a nul, otherwise it may be | |
250 | * a nul (resulting in *argp set to 0) or something else (*argp set | |
251 | * to point to after delim, *endchar_r set to delim). | |
252 | * *endchar_r may be 0. | |
253 | */ | |
254 | unsigned long rv; | |
255 | char *ep; | |
256 | int endchar; | |
257 | ||
baba1099 | 258 | if (!*argp) { badusage("missing number %s",what); } |
1c1a9fa1 | 259 | rv= strtoul(*argp,&ep,0); |
260 | if ((endchar= *ep)) { | |
baba1099 | 261 | if (!endchars) badusage("junk after number %s",what); |
1e963473 | 262 | if (!strchr(endchars,endchar)) |
263 | badusage("invalid character or delimiter `%c' in or after number, %s:" | |
baba1099 | 264 | " expected %s (or none?)", endchar,what,endchars); |
1c1a9fa1 | 265 | *argp= ep+1; |
266 | } else { | |
267 | *argp= 0; | |
268 | } | |
269 | if (endchar_r) *endchar_r= endchar; | |
1e963473 | 270 | if (rv < min || rv > max) badusage("number %s value %lu out of range %lu..%lu", |
271 | what, rv, min, max); | |
1c1a9fa1 | 272 | return rv; |
273 | } | |
274 | ||
e6a01344 | 275 | static int addrnet_overlap(unsigned long p1, unsigned long m1, |
276 | unsigned long p2, unsigned long m2) { | |
1c1a9fa1 | 277 | unsigned long mask; |
278 | ||
279 | mask= m1&m2; | |
e6a01344 | 280 | return (p1 & mask) == (p2 & mask); |
281 | } | |
282 | ||
283 | static void addrnet_mustdiffer(const char *w1, unsigned long p1, unsigned long m1, | |
284 | const char *w2, unsigned long p2, unsigned long m2) { | |
285 | if (!addrnet_overlap(p1,m1,p2,m2)) return; | |
1e963473 | 286 | badusage("%s %08lx/%08lx overlaps/clashes with %s %08lx/%08lx", |
287 | w1,p1,m1, w2,p2,m2); | |
1c1a9fa1 | 288 | } |
289 | ||
290 | static unsigned long eat_addr(const char **argp, const char *what, | |
1c1a9fa1 | 291 | const char *endchars, int *endchar_r) { |
292 | char whatbuf[100]; | |
293 | unsigned long rv; | |
294 | int i; | |
295 | ||
1c1a9fa1 | 296 | for (rv=0, i=0; |
297 | i<4; | |
298 | i++) { | |
299 | rv <<= 8; | |
300 | sprintf(whatbuf,"%s byte #%d",what,i); | |
301 | rv |= eat_number(argp,whatbuf, 0,255, i<3 ? "." : endchars, endchar_r); | |
302 | } | |
303 | ||
1c1a9fa1 | 304 | return rv; |
305 | } | |
306 | ||
307 | static void eat_prefixmask(const char **argp, const char *what, | |
1c1a9fa1 | 308 | const char *endchars, int *endchar_r, |
309 | unsigned long *prefix_r, unsigned long *mask_r, int *len_r) { | |
310 | /* mask_r and len_r may be 0 */ | |
311 | char whatbuf[100]; | |
312 | int len; | |
313 | unsigned long prefix, mask; | |
314 | ||
1e963473 | 315 | prefix= eat_addr(argp,what, "/",0); |
1c1a9fa1 | 316 | sprintf(whatbuf,"%s length",what); |
317 | len= eat_number(argp,whatbuf, 0,32, endchars,endchar_r); | |
318 | ||
f56780b7 | 319 | mask= len ? (~0UL << (32-len)) : 0UL; |
baba1099 | 320 | if (prefix & ~mask) badusage("%s prefix %08lx not fully contained in mask %08lx", |
1e963473 | 321 | what,prefix,mask); |
1c1a9fa1 | 322 | *prefix_r= prefix; |
323 | if (mask_r) *mask_r= mask; | |
324 | if (len_r) *len_r= len; | |
325 | } | |
1e963473 | 326 | |
4f937f54 IJ |
327 | static char *eat_optionalstr(const char **argp, |
328 | const char *what, | |
329 | const char *def) { | |
330 | ptrdiff_t len; | |
331 | const char *start= *argp; | |
345c35a6 IJ |
332 | if (!start) { |
333 | len = 0; | |
4f937f54 | 334 | } else { |
345c35a6 IJ |
335 | const char *comma= strchr(start, ','); |
336 | if (comma) { | |
337 | len= comma - start; | |
338 | *argp= comma + 1; | |
339 | } else { | |
340 | len= strlen(start); | |
eccfa510 | 341 | *argp= 0; |
345c35a6 | 342 | } |
4f937f54 IJ |
343 | } |
344 | if (!len) { | |
345 | start= def; | |
346 | len= strlen(def); | |
347 | } | |
348 | char *r = malloc(len+1); | |
349 | if (!r) sysfatal("malloc for command line string"); | |
350 | memcpy(r,start,len); | |
351 | r[len]= 0; | |
352 | return r; | |
353 | } | |
354 | ||
1e963473 | 355 | static int addrnet_isin(unsigned long prefix, unsigned long mask, |
356 | unsigned long mprefix, unsigned long mmask) { | |
357 | return !(~mask & mmask) && (prefix & mmask) == mprefix; | |
358 | } | |
e6a01344 | 359 | |
360 | /* Totally hideous algorithm for parsing the config file lines. | |
361 | * For each config file line, we first see if its gid applies. If not | |
362 | * we skip it. Otherwise, we do | |
363 | * permit_begin | |
364 | * which sets <foo>pming to 1 | |
365 | * for each range. <foo>pming may be 0 if we've determined that | |
366 | * this line does not apply to <foo>. | |
367 | * permit_range | |
368 | * which calls permit_range_thing for each <foo> | |
369 | * which checks to see if <foo> is inside the relevant | |
370 | * range (for +) or overlaps it (for -) and updates | |
371 | * <foo>allow and <foo>pming. | |
372 | */ | |
373 | ||
374 | static void permit_begin(void) { | |
375 | int i; | |
1c1a9fa1 | 376 | |
e6a01344 | 377 | localpming= peerpming= 1; |
378 | for (i=0; i<nexroutes; i++) exroutes[i].pming= 1; | |
379 | } | |
380 | ||
381 | static void permit_range_thing(unsigned long tprefix, unsigned long tmask, | |
382 | const char *what, int *tallow, int *tpming, | |
383 | unsigned long pprefix, unsigned long pmask, | |
384 | int plus, int *any) { | |
385 | if (plus) { | |
386 | if (!addrnet_isin(tprefix,tmask, pprefix,pmask)) return; | |
387 | if (*tpming) *tallow= 1; | |
388 | } else { | |
389 | if (!addrnet_overlap(tprefix,tmask, pprefix,pmask)) return; | |
390 | *tpming= 0; | |
391 | } | |
392 | if (!proto) printf(" %c%s", plus?'+':'-', what); | |
393 | *any= 1; | |
394 | } | |
1c1a9fa1 | 395 | |
e6a01344 | 396 | static void permit_range(unsigned long prefix, unsigned long mask, |
397 | int plus, int localonly) { | |
1e963473 | 398 | int i, any; |
e6a01344 | 399 | char idbuf[40]; |
1c1a9fa1 | 400 | |
e6a01344 | 401 | assert(!(prefix & ~mask)); |
2c310400 | 402 | any= 0; |
1c1a9fa1 | 403 | |
e6a01344 | 404 | permit_range_thing(localaddr,~0UL,"local", &localallow,&localpming, |
405 | prefix,mask, plus,&any); | |
406 | ||
0f91e874 | 407 | if (!localonly) { |
e6a01344 | 408 | permit_range_thing(peeraddr,~0UL, "peer-addr", &peerallow,&peerpming, |
409 | prefix,mask, plus,&any); | |
0f91e874 | 410 | for (i=0; i<nexroutes; i++) { |
e6a01344 | 411 | sprintf(idbuf,"route#%d",i); |
412 | permit_range_thing(exroutes[i].prefix,exroutes[i].mask, idbuf, | |
413 | &exroutes[i].allow,&exroutes[i].pming, | |
414 | prefix,mask, plus,&any); | |
1e963473 | 415 | } |
416 | } | |
e6a01344 | 417 | if (!proto) |
a48a8f0d | 418 | if (!any) fputs(" nothing",stdout); |
1e963473 | 419 | } |
1c1a9fa1 | 420 | |
1e963473 | 421 | static void pconfig(const char *configstr, int truncated); |
422 | ||
423 | static void pfile(const char *filename) { | |
424 | FILE *file; | |
425 | char buf[PATH_MAX]; | |
426 | int l, truncated, c; | |
427 | struct pplace npp, *cpp; | |
428 | ||
429 | for (cpp=cpplace; cpp; cpp=cpp->parent) { | |
430 | if (!strcmp(cpp->filename,filename)) | |
431 | badusage("recursive configuration file `%s'",filename); | |
432 | } | |
433 | ||
434 | file= fopen(filename,"r"); | |
435 | if (!file) | |
436 | badusage("cannot open configuration file `%s': %s", filename, strerror(errno)); | |
437 | ||
438 | if (!proto) printf("config file `%s':\n",filename); | |
439 | ||
440 | npp.parent= cpplace; | |
441 | npp.filename= filename; | |
442 | npp.lineno= 0; | |
443 | cpplace= &npp; | |
444 | ||
445 | while (fgets(buf, sizeof(buf), file)) { | |
446 | npp.lineno++; | |
447 | l= strlen(buf); | |
448 | if (!l) continue; | |
449 | ||
450 | truncated= (buf[l-1] != '\n'); | |
451 | while (l>0 && isspace((unsigned char) buf[l-1])) l--; | |
452 | if (!l) continue; | |
453 | buf[l]= 0; | |
454 | ||
455 | if (truncated) { | |
456 | while ((c= getc(file)) != EOF && c != '\n'); | |
457 | if (c == EOF) break; | |
458 | } | |
459 | ||
460 | pconfig(buf,truncated); | |
461 | } | |
462 | if (ferror(file)) | |
463 | badusage("failed while reading configuration file: %s", strerror(errno)); | |
464 | ||
465 | cpplace= npp.parent; | |
466 | } | |
467 | ||
468 | static void pconfig(const char *configstr, int truncated) { | |
469 | unsigned long fgid, tgid, pprefix, pmask; | |
e6a01344 | 470 | int plen, localonly, plus, rangeix, delim; |
1e963473 | 471 | char ptxt[ATXTLEN]; |
e6a01344 | 472 | char whattxt[100]; |
1e963473 | 473 | const char *gidlist; |
474 | ||
475 | switch (configstr[0]) { | |
476 | case '*': | |
e6a01344 | 477 | permit_begin(); |
478 | permit_range(0UL,0UL,1,0); | |
1e963473 | 479 | return; |
480 | ||
481 | case '#': | |
482 | return; | |
483 | ||
484 | case '/': case '.': | |
485 | if (truncated) badusage("filename too long (`%.100s...')",configstr); | |
486 | pfile(configstr); | |
487 | return; | |
488 | ||
489 | default: | |
490 | if (!isdigit((unsigned char)configstr[0])) | |
491 | badusage("unknown configuration directive"); | |
492 | ||
493 | fgid= eat_number(&configstr,"gid", 0,gidmaxval, ",",0); | |
1e963473 | 494 | |
e6a01344 | 495 | if (!proto) printf(" %5lu", fgid); |
1e963473 | 496 | |
497 | gidlist= getenv("USERV_GID"); | |
498 | if (!gidlist) fatal("USERV_GID not set"); | |
1c1a9fa1 | 499 | for (;;) { |
1e963473 | 500 | if (!gidlist) { |
e6a01344 | 501 | if (!proto) printf(" no matching gid\n"); |
1e963473 | 502 | return; |
1c1a9fa1 | 503 | } |
1e963473 | 504 | tgid= eat_number(&gidlist,"userv-gid", 0,gidmaxval, " ",0); |
505 | if (tgid == fgid) break; | |
1c1a9fa1 | 506 | } |
e6a01344 | 507 | |
508 | if (configstr[0] == '=') { | |
509 | localonly= 1; | |
510 | configstr++; | |
511 | } else { | |
512 | localonly= 0; | |
513 | } | |
514 | ||
515 | permit_begin(); | |
516 | ||
517 | rangeix= 0; | |
518 | plus= 1; | |
519 | switch (configstr[0]) { | |
520 | case '-': plus= 0; /* fall through */ | |
521 | case '+': configstr++; | |
522 | default:; | |
523 | } | |
524 | ||
525 | for (;;) { | |
526 | sprintf(whattxt, "%s-prefix#%d", | |
527 | plus ? "permitted" : "notpermitted", | |
528 | rangeix); | |
529 | eat_prefixmask(&configstr,whattxt, ",+-",&delim, | |
530 | &pprefix,&pmask,&plen); | |
531 | if (!configstr && truncated) | |
532 | badusage("gid,prefix/len,... spec too long"); | |
533 | ||
534 | if (!proto) | |
535 | printf(" %c%s/%d:", plus?'+':'-',ip2txt(pprefix,ptxt), plen); | |
536 | ||
537 | permit_range(pprefix,pmask,plus,localonly); | |
538 | if (delim==',') break; | |
539 | ||
540 | plus= delim=='-' ? 0 : 1; | |
541 | rangeix++; | |
542 | } | |
543 | ||
544 | putchar('\n'); | |
1e963473 | 545 | return; |
1c1a9fa1 | 546 | } |
1e963473 | 547 | } |
548 | ||
549 | static void checkallow(int allow, const char *what, | |
550 | const char *prefixtxt, const char *masktxt) { | |
551 | if (allow) return; | |
552 | fprintf(stderr,"userv-ipif service: access denied for %s, %s/%s\n", | |
553 | what, prefixtxt, masktxt); | |
554 | allallow= 0; | |
555 | } | |
556 | ||
557 | static void parseargs(int argc, const char *const *argv) { | |
558 | unsigned long routeaddr, routemask; | |
559 | const char *carg; | |
560 | const char *const *cprotop; | |
561 | int i; | |
562 | char erwhatbuf[100], erwhatbuf2[100]; | |
563 | ||
564 | if (argc < NARGS+1) { badusage("too few arguments"); } | |
565 | if (argc > NARGS+1) { badusage("too many arguments"); } | |
566 | ||
567 | configstr= *++argv; | |
1c1a9fa1 | 568 | |
569 | carg= *++argv; | |
1e963473 | 570 | if (strcmp(carg,"--")) badusage("separator argument `--' not found, got `%s'",carg); |
1c1a9fa1 | 571 | |
1e963473 | 572 | carg= *++argv; |
573 | localaddr= eat_addr(&carg,"local-addr", ",",0); | |
574 | peeraddr= eat_addr(&carg,"peer-addr", ",",0); | |
1c1a9fa1 | 575 | mtu= eat_number(&carg,"mtu", 576,65536, ",",0); |
1e963473 | 576 | localallow= peerallow= 0; |
4f937f54 IJ |
577 | |
578 | char *protostr = eat_optionalstr(&carg,"protocol","slip"); | |
579 | if (!strcmp(protostr,"debug")) { | |
1c1a9fa1 | 580 | proto= 0; |
581 | } else { | |
582 | for (cprotop= protos_ok; | |
4f937f54 | 583 | (proto= *cprotop) && strcmp(proto,protostr); |
1c1a9fa1 | 584 | cprotop++); |
585 | if (!proto) fatal("invalid protocol"); | |
586 | } | |
587 | ||
588 | addrnet_mustdiffer("local-addr",localaddr,~0UL, "peer-addr",peeraddr,~0UL); | |
589 | ||
590 | carg= *++argv; | |
2ed30784 | 591 | if (strcmp(carg,"-")) { |
592 | for (nexroutes=0; | |
593 | carg && *carg; | |
594 | nexroutes++) { | |
595 | if (nexroutes == MAXEXROUTES) | |
596 | fatal("too many extra routes (only %d allowed)",MAXEXROUTES); | |
597 | sprintf(erwhatbuf,"route#%d",nexroutes); | |
1c1a9fa1 | 598 | |
2ed30784 | 599 | eat_prefixmask(&carg,erwhatbuf, ",",0, &routeaddr,&routemask,0); |
600 | if (routemask == ~0UL) { | |
601 | addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "local-addr",localaddr,~0UL); | |
602 | addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "peer-addr",peeraddr,~0UL); | |
603 | } | |
604 | for (i=0; i<nexroutes; i++) { | |
605 | sprintf(erwhatbuf2,"route#%d",i); | |
606 | addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, | |
607 | erwhatbuf2,exroutes[i].prefix,exroutes[i].mask); | |
608 | } | |
609 | exroutes[nexroutes].prefix= routeaddr; | |
610 | exroutes[nexroutes].mask= routemask; | |
611 | exroutes[nexroutes].allow= 0; | |
612 | ip2txt(routeaddr,exroutes[nexroutes].prefixtxt); | |
613 | ip2txt(routemask,exroutes[nexroutes].masktxt); | |
1c1a9fa1 | 614 | } |
1c1a9fa1 | 615 | } |
2ed30784 | 616 | |
1c1a9fa1 | 617 | ip2txt(localaddr,localtxt); |
618 | ip2txt(peeraddr,peertxt); | |
1e963473 | 619 | } |
620 | ||
621 | static void checkpermit(void) { | |
622 | int i; | |
623 | char erwhatbuf[100]; | |
1c1a9fa1 | 624 | |
1e963473 | 625 | allallow= 1; |
626 | checkallow(localallow,"local-addr", localtxt,"32"); | |
627 | checkallow(peerallow,"peer-addr", peertxt,"32"); | |
628 | for (i=0; i<nexroutes; i++) { | |
629 | sprintf(erwhatbuf, "route#%d", i); | |
630 | checkallow(exroutes[i].allow, erwhatbuf, exroutes[i].prefixtxt, exroutes[i].masktxt); | |
631 | } | |
632 | if (!allallow) fatal("access denied"); | |
633 | } | |
634 | ||
635 | static void dumpdebug(void) __attribute__((noreturn)); | |
636 | static void dumpdebug(void) { | |
637 | int i; | |
638 | char erwhatbuf[100]; | |
639 | ||
640 | printf("protocol: debug\n" | |
641 | "local: %08lx == %s\n" | |
642 | "peer: %08lx == %s\n" | |
643 | "mtu: %ld\n" | |
644 | "routes: %d\n", | |
645 | localaddr, localtxt, | |
646 | peeraddr, peertxt, | |
647 | mtu, | |
648 | nexroutes); | |
649 | for (i=0; i<nexroutes; i++) { | |
650 | sprintf(erwhatbuf, "route#%d:", i); | |
651 | printf("%-9s %08lx/%08lx == %s/%s\n", | |
652 | erwhatbuf, | |
653 | exroutes[i].prefix, exroutes[i].mask, | |
654 | exroutes[i].prefixtxt, exroutes[i].masktxt); | |
1c1a9fa1 | 655 | } |
1e963473 | 656 | if (ferror(stdout) || fclose(stdout)) sysfatal("flush stdout"); |
657 | exit(0); | |
658 | } | |
659 | ||
6d360a53 | 660 | |
22fd0ebe IJ |
661 | static int task(const char *desc) { |
662 | pid_t pid, pidr; | |
663 | int status; | |
02b2392d | 664 | |
665 | pid= fork(); | |
6d360a53 | 666 | if (pid == (pid_t)-1) sysfatal("fork for task"); |
22fd0ebe | 667 | if (!pid) return 1; |
6d360a53 | 668 | |
669 | for (;;) { | |
0ae8e686 | 670 | pidr= waitpid(pid,&status,0); |
22fd0ebe IJ |
671 | if (pidr!=(pid_t)-1) break; |
672 | if (errno==EINTR) continue; | |
673 | sysfatal("waitpid for task"); | |
6d360a53 | 674 | } |
22fd0ebe | 675 | assert(pidr==pid); |
6d360a53 | 676 | |
22fd0ebe | 677 | if (WIFEXITED(status)) { |
0ae8e686 IJ |
678 | if (WEXITSTATUS(status)) |
679 | fatal("userv-ipif service: %s exited with error exit status %d\n", | |
22fd0ebe IJ |
680 | desc, WEXITSTATUS(status)); |
681 | } else if (WIFSIGNALED(status)) { | |
0ae8e686 IJ |
682 | fatal("userv-ipif service: %s died due to signal %s%s\n", |
683 | desc, strsignal(WTERMSIG(status)), | |
684 | WCOREDUMP(status) ? " (core dumped)" : ""); | |
22fd0ebe | 685 | } else { |
0ae8e686 IJ |
686 | fatal("userv-ipif service: %s unexpectedly terminated" |
687 | " with unknown status code %d\n", desc, status); | |
02b2392d | 688 | } |
689 | ||
22fd0ebe | 690 | return 0; |
02b2392d | 691 | } |
692 | ||
22fd0ebe IJ |
693 | static void createif(void) { |
694 | static const char ifnamepat[]= "userv%d"; | |
695 | struct ifreq ifr; | |
02b2392d | 696 | int r; |
02b2392d | 697 | |
22fd0ebe IJ |
698 | memset(&ifr,0,sizeof(ifr)); |
699 | ifr.ifr_flags= IFF_TUN | IFF_NO_PI; | |
6d360a53 | 700 | |
22fd0ebe IJ |
701 | assert(sizeof(ifr.ifr_name) >= sizeof(ifnamepat)); |
702 | strcpy(ifr.ifr_name, ifnamepat); | |
02b2392d | 703 | |
22fd0ebe IJ |
704 | tunfd= open("/dev/net/tun", O_RDWR); |
705 | if (!tunfd) sysfatal("open /dev/net/tun"); | |
02b2392d | 706 | |
22fd0ebe IJ |
707 | r= fcntl(tunfd, F_GETFD); |
708 | if (r==-1) sysfatal("fcntl(tunfd,F_GETFD)"); | |
709 | r= fcntl(tunfd, F_SETFD, r|FD_CLOEXEC); | |
710 | if (r==-1) sysfatal("fcntl(tunfd,F_SETFD,|FD_CLOEXEC)"); | |
02b2392d | 711 | |
22fd0ebe IJ |
712 | r= ioctl(tunfd, TUNSETIFF, (void*)&ifr); |
713 | if (r) sysfatal("ioctl TUNSETIFF"); | |
714 | ||
715 | /* ifr.ifr_name might not be null-terminated. crazy abi. */ | |
716 | ifname= malloc(sizeof(ifr.ifr_name)+1); | |
717 | if (!ifname) sysfatal("malloc for interface name"); | |
718 | memcpy(ifname, ifr.ifr_name, sizeof(ifr.ifr_name)); | |
719 | ifname[sizeof(ifr.ifr_name)]= 0; | |
02b2392d | 720 | } |
721 | ||
722 | static void netconfigure(void) { | |
723 | char mtutxt[100]; | |
724 | int i; | |
725 | ||
22fd0ebe | 726 | if (task("ifconfig")) { |
02b2392d | 727 | sprintf(mtutxt,"%lu",mtu); |
728 | ||
729 | execlp("ifconfig", "ifconfig", ifname, localtxt, | |
0ae8e686 | 730 | "netmask","255.255.255.255", "pointopoint",peertxt, "-broadcast", |
02b2392d | 731 | "mtu",mtutxt, "up", (char*)0); |
732 | sysfatal("cannot exec ifconfig"); | |
733 | } | |
734 | ||
02b2392d | 735 | for (i=0; i<nexroutes; i++) { |
22fd0ebe | 736 | if (task("route")) { |
6d360a53 | 737 | execlp("route","route", "add", "-net",exroutes[i].prefixtxt, |
02b2392d | 738 | "netmask",exroutes[i].masktxt, |
739 | "gw",peertxt, "dev",ifname, (char*)0); | |
740 | sysfatal("cannot exec route (for route)"); | |
741 | } | |
742 | } | |
743 | } | |
744 | ||
22fd0ebe | 745 | static void setnonblock(int fd) { |
5f1c67ff | 746 | int r; |
22fd0ebe IJ |
747 | r= fcntl(fd,F_GETFL); |
748 | if (r==-1) sysfatal("fcntl F_GETFL"); | |
749 | r= fcntl(fd,F_SETFL, r|O_NONBLOCK); | |
750 | if (r==-1) sysfatal("fcntl F_SETFL O_NONBLOCK"); | |
751 | } | |
752 | ||
753 | static void rx_packet(const uint8_t *packet, int len) { | |
03203c8a IJ |
754 | if (!len) |
755 | return; | |
22fd0ebe IJ |
756 | for (;;) { |
757 | int r= write(tunfd, packet, len); | |
758 | if (r<0) { | |
759 | if (errno==EINTR) continue; | |
03203c8a | 760 | if (errno==EAGAIN || errno==ENOMEM) return; /* oh well */ |
22fd0ebe IJ |
761 | sysfatal("error writing packet to tun (transmitting)"); |
762 | } | |
763 | assert(r==len); | |
764 | return; | |
765 | } | |
766 | } | |
767 | ||
768 | static int output_waiting, input_waiting; | |
769 | ||
770 | #define SLIP_END 0300 | |
771 | #define SLIP_ESC 0333 | |
772 | #define SLIP_ESC_END 0334 | |
773 | #define SLIP_ESC_ESC 0335 | |
774 | ||
775 | static void more_rx_data(uint8_t *input_buf, uint8_t *output_buf) { | |
776 | /* we make slip_data never contain continuation of a packet */ | |
777 | /* input_buf is passed as a parameter since it's in copydata's stack frame */ | |
778 | static int scanned; | |
779 | static int output_len; | |
780 | ||
781 | uint8_t *op= output_buf + output_len; | |
782 | const uint8_t *ip= input_buf + scanned; | |
783 | const uint8_t *ip_end= input_buf + input_waiting; | |
784 | int eaten= 0; | |
5f1c67ff | 785 | |
5f1c67ff | 786 | for (;;) { |
22fd0ebe IJ |
787 | if (ip>=ip_end) break; |
788 | uint8_t c= *ip++; | |
789 | if (c==SLIP_END) { | |
790 | rx_packet(output_buf, op-output_buf); | |
791 | op= output_buf; | |
792 | eaten= ip - input_buf; | |
793 | continue; | |
794 | } | |
795 | if (c==SLIP_ESC) { | |
796 | if (ip>=ip_end) { /* rescan this when there's more */ ip--; break; } | |
797 | c= *ip++; | |
798 | if (c==SLIP_ESC_END) c=SLIP_END; | |
799 | else if (c==SLIP_ESC_ESC) c=SLIP_ESC; | |
800 | else fatal("unexpected byte 0%o after SLIP_ESC",c); | |
801 | } | |
802 | if (op == output_buf+mtu) | |
803 | fatal("SLIP packet exceeds mtu"); | |
804 | *op++= c; | |
805 | } | |
806 | ||
807 | output_len= op - output_buf; | |
808 | scanned= ip - input_buf; | |
809 | ||
810 | input_waiting -= eaten; | |
811 | memmove(input_buf, input_buf+eaten, input_waiting); | |
812 | scanned -= eaten; | |
813 | } | |
814 | ||
815 | static void tx_packet(uint8_t *output_buf, const uint8_t *ip, int inlen) { | |
816 | /* output_buf is passed as a parameter since it's in copydata's stack frame */ | |
817 | assert(!output_waiting); | |
818 | uint8_t *op= output_buf; | |
819 | ||
820 | *op++= SLIP_END; | |
821 | while (inlen-- >0) { | |
822 | uint8_t c= *ip++; | |
823 | if (c==SLIP_END) { *op++= SLIP_ESC; *op++= SLIP_ESC_END; } | |
824 | else if (c==SLIP_ESC) { *op++= SLIP_ESC; *op++= SLIP_ESC_ESC; } | |
825 | else *op++= c; | |
5f1c67ff | 826 | } |
0ae8e686 IJ |
827 | *op++= SLIP_END; |
828 | assert(op <= output_buf + mtu*2+2); | |
829 | ||
22fd0ebe IJ |
830 | output_waiting= op - output_buf; |
831 | } | |
832 | ||
833 | static void copydata(void) __attribute__((noreturn)); | |
834 | static void copydata(void) { | |
835 | uint8_t output_buf[mtu*2+2]; | |
836 | uint8_t input_buf[mtu*2+2]; | |
837 | uint8_t rx_packet_buf[mtu]; | |
838 | ||
839 | int r, i; | |
02b2392d | 840 | |
22fd0ebe IJ |
841 | struct pollfd polls[3]; |
842 | memset(polls, 0, sizeof(polls)); | |
843 | ||
844 | polls[0].fd= 0; polls[0].events= POLLIN; | |
845 | polls[1].fd= 1; | |
846 | polls[2].fd= tunfd; | |
847 | ||
848 | /* We don't do flow control on input packets; instead, we just throw | |
849 | * away ones which the kernel doesn't accept. So we always poll for | |
850 | * those. | |
851 | * | |
852 | * Output packets we buffer, so we poll only as appropriate for those. | |
853 | */ | |
854 | ||
0ae8e686 IJ |
855 | /* Start by transmitting one END byte to say we're ready. */ |
856 | output_buf[0]= SLIP_END; | |
857 | output_waiting= 1; | |
858 | ||
22fd0ebe IJ |
859 | for (;;) { |
860 | if (output_waiting) { | |
861 | r= write(1, output_buf, output_waiting); | |
862 | if (r<0) { | |
863 | if (errno==EINTR) continue; | |
864 | if (errno!=EAGAIN) | |
865 | sysfatal("error writing SLIP output (packets being received)"); | |
866 | } else { | |
867 | assert(r>0); | |
868 | output_waiting -= r; | |
869 | memmove(output_buf, output_buf+r, output_waiting); | |
870 | } | |
871 | } | |
872 | if (output_waiting) { | |
873 | polls[1].events |= POLLOUT; | |
874 | polls[2].events &= ~POLLIN; | |
875 | } else { | |
876 | polls[1].events &= ~POLLOUT; | |
877 | polls[2].events |= POLLIN; | |
878 | } | |
879 | r= poll(polls,3,-1); | |
880 | ||
881 | if (r<0) { | |
882 | if (errno==EINTR) continue; | |
883 | sysfatal("poll() failed"); | |
884 | } | |
885 | assert(r>0); /* we used an infinite timeout */ | |
886 | ||
887 | for (i=0; i<sizeof(polls)/sizeof(polls[0]); i++) | |
888 | if (polls[i].revents & ~polls[i].events) | |
889 | fatal("unexpected revents 0x%x for fd=%d", | |
890 | polls[i].revents, polls[i].fd); | |
891 | ||
892 | if (polls[0].events & POLLIN) { | |
893 | int want= sizeof(input_buf) - input_waiting; | |
894 | if (want<0) fatal("incoming packet necessarily exceeds MTU"); | |
895 | r= read(0, input_buf + input_waiting, want); | |
896 | if (r>0) { | |
897 | input_waiting += r; | |
66b873f0 | 898 | assert(input_waiting <= sizeof(input_buf)); |
22fd0ebe IJ |
899 | more_rx_data(input_buf, rx_packet_buf); |
900 | } else if (r==0) { | |
901 | terminate(0); | |
902 | } else { | |
903 | if (!(errno==EINTR || errno==EAGAIN)) | |
904 | sysfatal("error reading input SLIP data (packets to transmit)"); | |
905 | } | |
906 | } | |
907 | ||
908 | /* We handle what would be (polls[1].events & POLLOUT) above, | |
909 | * unconditionally. That eliminates the need to poll in the usual case */ | |
910 | ||
911 | if (polls[2].events & POLLIN) { | |
912 | uint8_t packet_buf[mtu]; | |
913 | r= read(tunfd, packet_buf, mtu); | |
914 | if (r>0) { | |
915 | tx_packet(output_buf, packet_buf, r); | |
916 | } else { | |
917 | assert(r<0); | |
918 | if (!(errno==EAGAIN || errno==EWOULDBLOCK)) | |
919 | sysfatal("error reading packet (being transmitted) from tun"); | |
920 | } | |
921 | } | |
922 | } | |
02b2392d | 923 | } |
924 | ||
1e963473 | 925 | int main(int argc, const char *const *argv) { |
926 | parseargs(argc,argv); | |
927 | pconfig(configstr,0); | |
928 | checkpermit(); | |
929 | if (!proto) dumpdebug(); | |
930 | ||
22fd0ebe | 931 | createif(); |
02b2392d | 932 | netconfigure(); |
22fd0ebe IJ |
933 | setnonblock(tunfd); |
934 | setnonblock(0); | |
935 | setnonblock(1); | |
02b2392d | 936 | copydata(); |
1c1a9fa1 | 937 | } |