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; | |
332 | const char *comma= strchr(start, ','); | |
333 | if (comma) { | |
334 | len= comma - start; | |
335 | *argp= comma + 1; | |
336 | } else { | |
337 | len= strlen(start); | |
338 | *argp= start + len; | |
339 | } | |
340 | if (!len) { | |
341 | start= def; | |
342 | len= strlen(def); | |
343 | } | |
344 | char *r = malloc(len+1); | |
345 | if (!r) sysfatal("malloc for command line string"); | |
346 | memcpy(r,start,len); | |
347 | r[len]= 0; | |
348 | return r; | |
349 | } | |
350 | ||
1e963473 | 351 | static int addrnet_isin(unsigned long prefix, unsigned long mask, |
352 | unsigned long mprefix, unsigned long mmask) { | |
353 | return !(~mask & mmask) && (prefix & mmask) == mprefix; | |
354 | } | |
e6a01344 | 355 | |
356 | /* Totally hideous algorithm for parsing the config file lines. | |
357 | * For each config file line, we first see if its gid applies. If not | |
358 | * we skip it. Otherwise, we do | |
359 | * permit_begin | |
360 | * which sets <foo>pming to 1 | |
361 | * for each range. <foo>pming may be 0 if we've determined that | |
362 | * this line does not apply to <foo>. | |
363 | * permit_range | |
364 | * which calls permit_range_thing for each <foo> | |
365 | * which checks to see if <foo> is inside the relevant | |
366 | * range (for +) or overlaps it (for -) and updates | |
367 | * <foo>allow and <foo>pming. | |
368 | */ | |
369 | ||
370 | static void permit_begin(void) { | |
371 | int i; | |
1c1a9fa1 | 372 | |
e6a01344 | 373 | localpming= peerpming= 1; |
374 | for (i=0; i<nexroutes; i++) exroutes[i].pming= 1; | |
375 | } | |
376 | ||
377 | static void permit_range_thing(unsigned long tprefix, unsigned long tmask, | |
378 | const char *what, int *tallow, int *tpming, | |
379 | unsigned long pprefix, unsigned long pmask, | |
380 | int plus, int *any) { | |
381 | if (plus) { | |
382 | if (!addrnet_isin(tprefix,tmask, pprefix,pmask)) return; | |
383 | if (*tpming) *tallow= 1; | |
384 | } else { | |
385 | if (!addrnet_overlap(tprefix,tmask, pprefix,pmask)) return; | |
386 | *tpming= 0; | |
387 | } | |
388 | if (!proto) printf(" %c%s", plus?'+':'-', what); | |
389 | *any= 1; | |
390 | } | |
1c1a9fa1 | 391 | |
e6a01344 | 392 | static void permit_range(unsigned long prefix, unsigned long mask, |
393 | int plus, int localonly) { | |
1e963473 | 394 | int i, any; |
e6a01344 | 395 | char idbuf[40]; |
1c1a9fa1 | 396 | |
e6a01344 | 397 | assert(!(prefix & ~mask)); |
2c310400 | 398 | any= 0; |
1c1a9fa1 | 399 | |
e6a01344 | 400 | permit_range_thing(localaddr,~0UL,"local", &localallow,&localpming, |
401 | prefix,mask, plus,&any); | |
402 | ||
0f91e874 | 403 | if (!localonly) { |
e6a01344 | 404 | permit_range_thing(peeraddr,~0UL, "peer-addr", &peerallow,&peerpming, |
405 | prefix,mask, plus,&any); | |
0f91e874 | 406 | for (i=0; i<nexroutes; i++) { |
e6a01344 | 407 | sprintf(idbuf,"route#%d",i); |
408 | permit_range_thing(exroutes[i].prefix,exroutes[i].mask, idbuf, | |
409 | &exroutes[i].allow,&exroutes[i].pming, | |
410 | prefix,mask, plus,&any); | |
1e963473 | 411 | } |
412 | } | |
e6a01344 | 413 | if (!proto) |
a48a8f0d | 414 | if (!any) fputs(" nothing",stdout); |
1e963473 | 415 | } |
1c1a9fa1 | 416 | |
1e963473 | 417 | static void pconfig(const char *configstr, int truncated); |
418 | ||
419 | static void pfile(const char *filename) { | |
420 | FILE *file; | |
421 | char buf[PATH_MAX]; | |
422 | int l, truncated, c; | |
423 | struct pplace npp, *cpp; | |
424 | ||
425 | for (cpp=cpplace; cpp; cpp=cpp->parent) { | |
426 | if (!strcmp(cpp->filename,filename)) | |
427 | badusage("recursive configuration file `%s'",filename); | |
428 | } | |
429 | ||
430 | file= fopen(filename,"r"); | |
431 | if (!file) | |
432 | badusage("cannot open configuration file `%s': %s", filename, strerror(errno)); | |
433 | ||
434 | if (!proto) printf("config file `%s':\n",filename); | |
435 | ||
436 | npp.parent= cpplace; | |
437 | npp.filename= filename; | |
438 | npp.lineno= 0; | |
439 | cpplace= &npp; | |
440 | ||
441 | while (fgets(buf, sizeof(buf), file)) { | |
442 | npp.lineno++; | |
443 | l= strlen(buf); | |
444 | if (!l) continue; | |
445 | ||
446 | truncated= (buf[l-1] != '\n'); | |
447 | while (l>0 && isspace((unsigned char) buf[l-1])) l--; | |
448 | if (!l) continue; | |
449 | buf[l]= 0; | |
450 | ||
451 | if (truncated) { | |
452 | while ((c= getc(file)) != EOF && c != '\n'); | |
453 | if (c == EOF) break; | |
454 | } | |
455 | ||
456 | pconfig(buf,truncated); | |
457 | } | |
458 | if (ferror(file)) | |
459 | badusage("failed while reading configuration file: %s", strerror(errno)); | |
460 | ||
461 | cpplace= npp.parent; | |
462 | } | |
463 | ||
464 | static void pconfig(const char *configstr, int truncated) { | |
465 | unsigned long fgid, tgid, pprefix, pmask; | |
e6a01344 | 466 | int plen, localonly, plus, rangeix, delim; |
1e963473 | 467 | char ptxt[ATXTLEN]; |
e6a01344 | 468 | char whattxt[100]; |
1e963473 | 469 | const char *gidlist; |
470 | ||
471 | switch (configstr[0]) { | |
472 | case '*': | |
e6a01344 | 473 | permit_begin(); |
474 | permit_range(0UL,0UL,1,0); | |
1e963473 | 475 | return; |
476 | ||
477 | case '#': | |
478 | return; | |
479 | ||
480 | case '/': case '.': | |
481 | if (truncated) badusage("filename too long (`%.100s...')",configstr); | |
482 | pfile(configstr); | |
483 | return; | |
484 | ||
485 | default: | |
486 | if (!isdigit((unsigned char)configstr[0])) | |
487 | badusage("unknown configuration directive"); | |
488 | ||
489 | fgid= eat_number(&configstr,"gid", 0,gidmaxval, ",",0); | |
1e963473 | 490 | |
e6a01344 | 491 | if (!proto) printf(" %5lu", fgid); |
1e963473 | 492 | |
493 | gidlist= getenv("USERV_GID"); | |
494 | if (!gidlist) fatal("USERV_GID not set"); | |
1c1a9fa1 | 495 | for (;;) { |
1e963473 | 496 | if (!gidlist) { |
e6a01344 | 497 | if (!proto) printf(" no matching gid\n"); |
1e963473 | 498 | return; |
1c1a9fa1 | 499 | } |
1e963473 | 500 | tgid= eat_number(&gidlist,"userv-gid", 0,gidmaxval, " ",0); |
501 | if (tgid == fgid) break; | |
1c1a9fa1 | 502 | } |
e6a01344 | 503 | |
504 | if (configstr[0] == '=') { | |
505 | localonly= 1; | |
506 | configstr++; | |
507 | } else { | |
508 | localonly= 0; | |
509 | } | |
510 | ||
511 | permit_begin(); | |
512 | ||
513 | rangeix= 0; | |
514 | plus= 1; | |
515 | switch (configstr[0]) { | |
516 | case '-': plus= 0; /* fall through */ | |
517 | case '+': configstr++; | |
518 | default:; | |
519 | } | |
520 | ||
521 | for (;;) { | |
522 | sprintf(whattxt, "%s-prefix#%d", | |
523 | plus ? "permitted" : "notpermitted", | |
524 | rangeix); | |
525 | eat_prefixmask(&configstr,whattxt, ",+-",&delim, | |
526 | &pprefix,&pmask,&plen); | |
527 | if (!configstr && truncated) | |
528 | badusage("gid,prefix/len,... spec too long"); | |
529 | ||
530 | if (!proto) | |
531 | printf(" %c%s/%d:", plus?'+':'-',ip2txt(pprefix,ptxt), plen); | |
532 | ||
533 | permit_range(pprefix,pmask,plus,localonly); | |
534 | if (delim==',') break; | |
535 | ||
536 | plus= delim=='-' ? 0 : 1; | |
537 | rangeix++; | |
538 | } | |
539 | ||
540 | putchar('\n'); | |
1e963473 | 541 | return; |
1c1a9fa1 | 542 | } |
1e963473 | 543 | } |
544 | ||
545 | static void checkallow(int allow, const char *what, | |
546 | const char *prefixtxt, const char *masktxt) { | |
547 | if (allow) return; | |
548 | fprintf(stderr,"userv-ipif service: access denied for %s, %s/%s\n", | |
549 | what, prefixtxt, masktxt); | |
550 | allallow= 0; | |
551 | } | |
552 | ||
553 | static void parseargs(int argc, const char *const *argv) { | |
554 | unsigned long routeaddr, routemask; | |
555 | const char *carg; | |
556 | const char *const *cprotop; | |
557 | int i; | |
558 | char erwhatbuf[100], erwhatbuf2[100]; | |
559 | ||
560 | if (argc < NARGS+1) { badusage("too few arguments"); } | |
561 | if (argc > NARGS+1) { badusage("too many arguments"); } | |
562 | ||
563 | configstr= *++argv; | |
1c1a9fa1 | 564 | |
565 | carg= *++argv; | |
1e963473 | 566 | if (strcmp(carg,"--")) badusage("separator argument `--' not found, got `%s'",carg); |
1c1a9fa1 | 567 | |
1e963473 | 568 | carg= *++argv; |
569 | localaddr= eat_addr(&carg,"local-addr", ",",0); | |
570 | peeraddr= eat_addr(&carg,"peer-addr", ",",0); | |
1c1a9fa1 | 571 | mtu= eat_number(&carg,"mtu", 576,65536, ",",0); |
1e963473 | 572 | localallow= peerallow= 0; |
4f937f54 IJ |
573 | |
574 | char *protostr = eat_optionalstr(&carg,"protocol","slip"); | |
575 | if (!strcmp(protostr,"debug")) { | |
1c1a9fa1 | 576 | proto= 0; |
577 | } else { | |
578 | for (cprotop= protos_ok; | |
4f937f54 | 579 | (proto= *cprotop) && strcmp(proto,protostr); |
1c1a9fa1 | 580 | cprotop++); |
581 | if (!proto) fatal("invalid protocol"); | |
582 | } | |
583 | ||
584 | addrnet_mustdiffer("local-addr",localaddr,~0UL, "peer-addr",peeraddr,~0UL); | |
585 | ||
586 | carg= *++argv; | |
2ed30784 | 587 | if (strcmp(carg,"-")) { |
588 | for (nexroutes=0; | |
589 | carg && *carg; | |
590 | nexroutes++) { | |
591 | if (nexroutes == MAXEXROUTES) | |
592 | fatal("too many extra routes (only %d allowed)",MAXEXROUTES); | |
593 | sprintf(erwhatbuf,"route#%d",nexroutes); | |
1c1a9fa1 | 594 | |
2ed30784 | 595 | eat_prefixmask(&carg,erwhatbuf, ",",0, &routeaddr,&routemask,0); |
596 | if (routemask == ~0UL) { | |
597 | addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "local-addr",localaddr,~0UL); | |
598 | addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "peer-addr",peeraddr,~0UL); | |
599 | } | |
600 | for (i=0; i<nexroutes; i++) { | |
601 | sprintf(erwhatbuf2,"route#%d",i); | |
602 | addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, | |
603 | erwhatbuf2,exroutes[i].prefix,exroutes[i].mask); | |
604 | } | |
605 | exroutes[nexroutes].prefix= routeaddr; | |
606 | exroutes[nexroutes].mask= routemask; | |
607 | exroutes[nexroutes].allow= 0; | |
608 | ip2txt(routeaddr,exroutes[nexroutes].prefixtxt); | |
609 | ip2txt(routemask,exroutes[nexroutes].masktxt); | |
1c1a9fa1 | 610 | } |
1c1a9fa1 | 611 | } |
2ed30784 | 612 | |
1c1a9fa1 | 613 | ip2txt(localaddr,localtxt); |
614 | ip2txt(peeraddr,peertxt); | |
1e963473 | 615 | } |
616 | ||
617 | static void checkpermit(void) { | |
618 | int i; | |
619 | char erwhatbuf[100]; | |
1c1a9fa1 | 620 | |
1e963473 | 621 | allallow= 1; |
622 | checkallow(localallow,"local-addr", localtxt,"32"); | |
623 | checkallow(peerallow,"peer-addr", peertxt,"32"); | |
624 | for (i=0; i<nexroutes; i++) { | |
625 | sprintf(erwhatbuf, "route#%d", i); | |
626 | checkallow(exroutes[i].allow, erwhatbuf, exroutes[i].prefixtxt, exroutes[i].masktxt); | |
627 | } | |
628 | if (!allallow) fatal("access denied"); | |
629 | } | |
630 | ||
631 | static void dumpdebug(void) __attribute__((noreturn)); | |
632 | static void dumpdebug(void) { | |
633 | int i; | |
634 | char erwhatbuf[100]; | |
635 | ||
636 | printf("protocol: debug\n" | |
637 | "local: %08lx == %s\n" | |
638 | "peer: %08lx == %s\n" | |
639 | "mtu: %ld\n" | |
640 | "routes: %d\n", | |
641 | localaddr, localtxt, | |
642 | peeraddr, peertxt, | |
643 | mtu, | |
644 | nexroutes); | |
645 | for (i=0; i<nexroutes; i++) { | |
646 | sprintf(erwhatbuf, "route#%d:", i); | |
647 | printf("%-9s %08lx/%08lx == %s/%s\n", | |
648 | erwhatbuf, | |
649 | exroutes[i].prefix, exroutes[i].mask, | |
650 | exroutes[i].prefixtxt, exroutes[i].masktxt); | |
1c1a9fa1 | 651 | } |
1e963473 | 652 | if (ferror(stdout) || fclose(stdout)) sysfatal("flush stdout"); |
653 | exit(0); | |
654 | } | |
655 | ||
6d360a53 | 656 | |
22fd0ebe IJ |
657 | static int task(const char *desc) { |
658 | pid_t pid, pidr; | |
659 | int status; | |
02b2392d | 660 | |
661 | pid= fork(); | |
6d360a53 | 662 | if (pid == (pid_t)-1) sysfatal("fork for task"); |
22fd0ebe | 663 | if (!pid) return 1; |
6d360a53 | 664 | |
665 | for (;;) { | |
0ae8e686 | 666 | pidr= waitpid(pid,&status,0); |
22fd0ebe IJ |
667 | if (pidr!=(pid_t)-1) break; |
668 | if (errno==EINTR) continue; | |
669 | sysfatal("waitpid for task"); | |
6d360a53 | 670 | } |
22fd0ebe | 671 | assert(pidr==pid); |
6d360a53 | 672 | |
22fd0ebe | 673 | if (WIFEXITED(status)) { |
0ae8e686 IJ |
674 | if (WEXITSTATUS(status)) |
675 | fatal("userv-ipif service: %s exited with error exit status %d\n", | |
22fd0ebe IJ |
676 | desc, WEXITSTATUS(status)); |
677 | } else if (WIFSIGNALED(status)) { | |
0ae8e686 IJ |
678 | fatal("userv-ipif service: %s died due to signal %s%s\n", |
679 | desc, strsignal(WTERMSIG(status)), | |
680 | WCOREDUMP(status) ? " (core dumped)" : ""); | |
22fd0ebe | 681 | } else { |
0ae8e686 IJ |
682 | fatal("userv-ipif service: %s unexpectedly terminated" |
683 | " with unknown status code %d\n", desc, status); | |
02b2392d | 684 | } |
685 | ||
22fd0ebe | 686 | return 0; |
02b2392d | 687 | } |
688 | ||
22fd0ebe IJ |
689 | static void createif(void) { |
690 | static const char ifnamepat[]= "userv%d"; | |
691 | struct ifreq ifr; | |
02b2392d | 692 | int r; |
02b2392d | 693 | |
22fd0ebe IJ |
694 | memset(&ifr,0,sizeof(ifr)); |
695 | ifr.ifr_flags= IFF_TUN | IFF_NO_PI; | |
6d360a53 | 696 | |
22fd0ebe IJ |
697 | assert(sizeof(ifr.ifr_name) >= sizeof(ifnamepat)); |
698 | strcpy(ifr.ifr_name, ifnamepat); | |
02b2392d | 699 | |
22fd0ebe IJ |
700 | tunfd= open("/dev/net/tun", O_RDWR); |
701 | if (!tunfd) sysfatal("open /dev/net/tun"); | |
02b2392d | 702 | |
22fd0ebe IJ |
703 | r= fcntl(tunfd, F_GETFD); |
704 | if (r==-1) sysfatal("fcntl(tunfd,F_GETFD)"); | |
705 | r= fcntl(tunfd, F_SETFD, r|FD_CLOEXEC); | |
706 | if (r==-1) sysfatal("fcntl(tunfd,F_SETFD,|FD_CLOEXEC)"); | |
02b2392d | 707 | |
22fd0ebe IJ |
708 | r= ioctl(tunfd, TUNSETIFF, (void*)&ifr); |
709 | if (r) sysfatal("ioctl TUNSETIFF"); | |
710 | ||
711 | /* ifr.ifr_name might not be null-terminated. crazy abi. */ | |
712 | ifname= malloc(sizeof(ifr.ifr_name)+1); | |
713 | if (!ifname) sysfatal("malloc for interface name"); | |
714 | memcpy(ifname, ifr.ifr_name, sizeof(ifr.ifr_name)); | |
715 | ifname[sizeof(ifr.ifr_name)]= 0; | |
02b2392d | 716 | } |
717 | ||
718 | static void netconfigure(void) { | |
719 | char mtutxt[100]; | |
720 | int i; | |
721 | ||
22fd0ebe | 722 | if (task("ifconfig")) { |
02b2392d | 723 | sprintf(mtutxt,"%lu",mtu); |
724 | ||
725 | execlp("ifconfig", "ifconfig", ifname, localtxt, | |
0ae8e686 | 726 | "netmask","255.255.255.255", "pointopoint",peertxt, "-broadcast", |
02b2392d | 727 | "mtu",mtutxt, "up", (char*)0); |
728 | sysfatal("cannot exec ifconfig"); | |
729 | } | |
730 | ||
02b2392d | 731 | for (i=0; i<nexroutes; i++) { |
22fd0ebe | 732 | if (task("route")) { |
6d360a53 | 733 | execlp("route","route", "add", "-net",exroutes[i].prefixtxt, |
02b2392d | 734 | "netmask",exroutes[i].masktxt, |
735 | "gw",peertxt, "dev",ifname, (char*)0); | |
736 | sysfatal("cannot exec route (for route)"); | |
737 | } | |
738 | } | |
739 | } | |
740 | ||
22fd0ebe | 741 | static void setnonblock(int fd) { |
5f1c67ff | 742 | int r; |
22fd0ebe IJ |
743 | r= fcntl(fd,F_GETFL); |
744 | if (r==-1) sysfatal("fcntl F_GETFL"); | |
745 | r= fcntl(fd,F_SETFL, r|O_NONBLOCK); | |
746 | if (r==-1) sysfatal("fcntl F_SETFL O_NONBLOCK"); | |
747 | } | |
748 | ||
749 | static void rx_packet(const uint8_t *packet, int len) { | |
03203c8a IJ |
750 | if (!len) |
751 | return; | |
22fd0ebe IJ |
752 | for (;;) { |
753 | int r= write(tunfd, packet, len); | |
754 | if (r<0) { | |
755 | if (errno==EINTR) continue; | |
03203c8a | 756 | if (errno==EAGAIN || errno==ENOMEM) return; /* oh well */ |
22fd0ebe IJ |
757 | sysfatal("error writing packet to tun (transmitting)"); |
758 | } | |
759 | assert(r==len); | |
760 | return; | |
761 | } | |
762 | } | |
763 | ||
764 | static int output_waiting, input_waiting; | |
765 | ||
766 | #define SLIP_END 0300 | |
767 | #define SLIP_ESC 0333 | |
768 | #define SLIP_ESC_END 0334 | |
769 | #define SLIP_ESC_ESC 0335 | |
770 | ||
771 | static void more_rx_data(uint8_t *input_buf, uint8_t *output_buf) { | |
772 | /* we make slip_data never contain continuation of a packet */ | |
773 | /* input_buf is passed as a parameter since it's in copydata's stack frame */ | |
774 | static int scanned; | |
775 | static int output_len; | |
776 | ||
777 | uint8_t *op= output_buf + output_len; | |
778 | const uint8_t *ip= input_buf + scanned; | |
779 | const uint8_t *ip_end= input_buf + input_waiting; | |
780 | int eaten= 0; | |
5f1c67ff | 781 | |
5f1c67ff | 782 | for (;;) { |
22fd0ebe IJ |
783 | if (ip>=ip_end) break; |
784 | uint8_t c= *ip++; | |
785 | if (c==SLIP_END) { | |
786 | rx_packet(output_buf, op-output_buf); | |
787 | op= output_buf; | |
788 | eaten= ip - input_buf; | |
789 | continue; | |
790 | } | |
791 | if (c==SLIP_ESC) { | |
792 | if (ip>=ip_end) { /* rescan this when there's more */ ip--; break; } | |
793 | c= *ip++; | |
794 | if (c==SLIP_ESC_END) c=SLIP_END; | |
795 | else if (c==SLIP_ESC_ESC) c=SLIP_ESC; | |
796 | else fatal("unexpected byte 0%o after SLIP_ESC",c); | |
797 | } | |
798 | if (op == output_buf+mtu) | |
799 | fatal("SLIP packet exceeds mtu"); | |
800 | *op++= c; | |
801 | } | |
802 | ||
803 | output_len= op - output_buf; | |
804 | scanned= ip - input_buf; | |
805 | ||
806 | input_waiting -= eaten; | |
807 | memmove(input_buf, input_buf+eaten, input_waiting); | |
808 | scanned -= eaten; | |
809 | } | |
810 | ||
811 | static void tx_packet(uint8_t *output_buf, const uint8_t *ip, int inlen) { | |
812 | /* output_buf is passed as a parameter since it's in copydata's stack frame */ | |
813 | assert(!output_waiting); | |
814 | uint8_t *op= output_buf; | |
815 | ||
816 | *op++= SLIP_END; | |
817 | while (inlen-- >0) { | |
818 | uint8_t c= *ip++; | |
819 | if (c==SLIP_END) { *op++= SLIP_ESC; *op++= SLIP_ESC_END; } | |
820 | else if (c==SLIP_ESC) { *op++= SLIP_ESC; *op++= SLIP_ESC_ESC; } | |
821 | else *op++= c; | |
5f1c67ff | 822 | } |
0ae8e686 IJ |
823 | *op++= SLIP_END; |
824 | assert(op <= output_buf + mtu*2+2); | |
825 | ||
22fd0ebe IJ |
826 | output_waiting= op - output_buf; |
827 | } | |
828 | ||
829 | static void copydata(void) __attribute__((noreturn)); | |
830 | static void copydata(void) { | |
831 | uint8_t output_buf[mtu*2+2]; | |
832 | uint8_t input_buf[mtu*2+2]; | |
833 | uint8_t rx_packet_buf[mtu]; | |
834 | ||
835 | int r, i; | |
02b2392d | 836 | |
22fd0ebe IJ |
837 | struct pollfd polls[3]; |
838 | memset(polls, 0, sizeof(polls)); | |
839 | ||
840 | polls[0].fd= 0; polls[0].events= POLLIN; | |
841 | polls[1].fd= 1; | |
842 | polls[2].fd= tunfd; | |
843 | ||
844 | /* We don't do flow control on input packets; instead, we just throw | |
845 | * away ones which the kernel doesn't accept. So we always poll for | |
846 | * those. | |
847 | * | |
848 | * Output packets we buffer, so we poll only as appropriate for those. | |
849 | */ | |
850 | ||
0ae8e686 IJ |
851 | /* Start by transmitting one END byte to say we're ready. */ |
852 | output_buf[0]= SLIP_END; | |
853 | output_waiting= 1; | |
854 | ||
22fd0ebe IJ |
855 | for (;;) { |
856 | if (output_waiting) { | |
857 | r= write(1, output_buf, output_waiting); | |
858 | if (r<0) { | |
859 | if (errno==EINTR) continue; | |
860 | if (errno!=EAGAIN) | |
861 | sysfatal("error writing SLIP output (packets being received)"); | |
862 | } else { | |
863 | assert(r>0); | |
864 | output_waiting -= r; | |
865 | memmove(output_buf, output_buf+r, output_waiting); | |
866 | } | |
867 | } | |
868 | if (output_waiting) { | |
869 | polls[1].events |= POLLOUT; | |
870 | polls[2].events &= ~POLLIN; | |
871 | } else { | |
872 | polls[1].events &= ~POLLOUT; | |
873 | polls[2].events |= POLLIN; | |
874 | } | |
875 | r= poll(polls,3,-1); | |
876 | ||
877 | if (r<0) { | |
878 | if (errno==EINTR) continue; | |
879 | sysfatal("poll() failed"); | |
880 | } | |
881 | assert(r>0); /* we used an infinite timeout */ | |
882 | ||
883 | for (i=0; i<sizeof(polls)/sizeof(polls[0]); i++) | |
884 | if (polls[i].revents & ~polls[i].events) | |
885 | fatal("unexpected revents 0x%x for fd=%d", | |
886 | polls[i].revents, polls[i].fd); | |
887 | ||
888 | if (polls[0].events & POLLIN) { | |
889 | int want= sizeof(input_buf) - input_waiting; | |
890 | if (want<0) fatal("incoming packet necessarily exceeds MTU"); | |
891 | r= read(0, input_buf + input_waiting, want); | |
892 | if (r>0) { | |
893 | input_waiting += r; | |
66b873f0 | 894 | assert(input_waiting <= sizeof(input_buf)); |
22fd0ebe IJ |
895 | more_rx_data(input_buf, rx_packet_buf); |
896 | } else if (r==0) { | |
897 | terminate(0); | |
898 | } else { | |
899 | if (!(errno==EINTR || errno==EAGAIN)) | |
900 | sysfatal("error reading input SLIP data (packets to transmit)"); | |
901 | } | |
902 | } | |
903 | ||
904 | /* We handle what would be (polls[1].events & POLLOUT) above, | |
905 | * unconditionally. That eliminates the need to poll in the usual case */ | |
906 | ||
907 | if (polls[2].events & POLLIN) { | |
908 | uint8_t packet_buf[mtu]; | |
909 | r= read(tunfd, packet_buf, mtu); | |
910 | if (r>0) { | |
911 | tx_packet(output_buf, packet_buf, r); | |
912 | } else { | |
913 | assert(r<0); | |
914 | if (!(errno==EAGAIN || errno==EWOULDBLOCK)) | |
915 | sysfatal("error reading packet (being transmitted) from tun"); | |
916 | } | |
917 | } | |
918 | } | |
02b2392d | 919 | } |
920 | ||
1e963473 | 921 | int main(int argc, const char *const *argv) { |
922 | parseargs(argc,argv); | |
923 | pconfig(configstr,0); | |
924 | checkpermit(); | |
925 | if (!proto) dumpdebug(); | |
926 | ||
22fd0ebe | 927 | createif(); |
02b2392d | 928 | netconfigure(); |
22fd0ebe IJ |
929 | setnonblock(tunfd); |
930 | setnonblock(0); | |
931 | setnonblock(1); | |
02b2392d | 932 | copydata(); |
1c1a9fa1 | 933 | } |