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