/* * userv service (or standalone program) for per-user IP subranges. * * This is the service program, which is invoked as root from userv (or may * be invoked firectly). * * Its arguments are supposed to be, in order, as follows: * * The first two arguments are usually supplied by the userv * configuration. See the file `ipif/ipif' in the source tree, which * is installed in /etc/userv/services.d/ipif by `make install': * * * * Specifies address ranges and gids which own them. The default * configuration supplies /etc/userv/ipif-networks, which is then read * for a list of entries, one per line. * * -- * Serves to separate the user-supplied and therefore untrusted * arguments from the trusted first argument. * * The remaining arguments are supplied by the (untrusted) caller: * * ,,[,[][,[]]] * * As for slattach. The only supported protocol is slip. * Alternatively, set to `debug' to print debugging info and * exit. is address of the interface to be created * on the local system; is the address of the * point-to-point peer. They must be actual addresses (not * hostnames). * * /,/,... * * List of additional routes to add for this interface. routes will * be set up on the local system arranging for packets for those * networks to be sent via the created interface. must be an * IPv4 address, and mask must be an integer (dotted-quad masks are * not supported). If no additional routes are to be set up, use `-' * or supply an empty argument. * * Each item - whether a line in a file such as * /etc/userv/ipif-networks, or the single trusted argument supplied * on the service program command line - is one of: * * / * ./ * ../ * * Reads a file which contains lines which are each * items. * * ,[=][-|+]/(-|+/...)[,] * * Indicates that may allocate addresses in the relevant address * range ( is ignored). must be numeric. To specify a * single host address, you must specify a mask of /32. If `=' is * specified then the specific subrange is only allowed for the local * endpoint address, but not for remote addresses. * * More than one range may be given, with each range prefixed * by + or -. In this case each address range in the rule will * scanned in order, and the first range in the rule that matches * any desired rule will count: if that first matching range is * prefixed by `+' (or nothing) then the rule applies, if it * is prefixed by `-' (or nothing matches), the rule does not. * * * * Means that anything is to be permitted. This should not appear in * /etc/userv/ipif-networks, as that would permit any user on the * system to create any interfaces with any addresses and routes * attached. It is provided so that root can usefully invoke the ipif * service program directly (not via userv), without needing to set up * permissions in /etc/userv/ipif-networks. * * Only `*' permits interface name patterns other than the default * value of `userv%d'. * * #... * * Comment. Blank lines are also ignored. * * NB: Permission is granted if _any_ config entry matches the request. * * The service program should be run from userv with no-disconnect-hup. */ /* * This file is part of ipif, part of userv-utils * * Copyright 1996-2013 Ian Jackson * Copyright 1998 David Damerell * Copyright 1999,2003 * Chancellor Masters and Scholars of the University of Cambridge * Copyright 2010 Tony Finch * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with userv-utils; if not, see http://www.gnu.org/licenses/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NARGS 4 #define MAXEXROUTES 50 #define ATXTLEN 16 static const unsigned long gidmaxval= (unsigned long)((gid_t)-2); static const char *const protos_ok[]= { "slip", 0 }; static const char default_ifnamepat[]= "userv%d"; static const char *configstr, *proto; static unsigned long localaddr, peeraddr, mtu; static int localpming, peerpming; static int localallow, peerallow, ifnameallow, allallow; static char *ifnamepat; static int nexroutes; static struct exroute { unsigned long prefix, mask; int allow, pming; char prefixtxt[ATXTLEN], masktxt[ATXTLEN]; } exroutes[MAXEXROUTES]; static char localtxt[ATXTLEN]; static char peertxt[ATXTLEN]; static struct pplace { struct pplace *parent; const char *filename; int lineno; } *cpplace; static int tunfd; static char *ifname; static void terminate(int estatus) { exit(estatus); } static void fatal(const char *fmt, ...) __attribute__((format(printf,1,2))); static void fatal(const char *fmt, ...) { va_list al; va_start(al,fmt); fputs("userv-ipif service: fatal error: ",stderr); vfprintf(stderr, fmt, al); putc('\n',stderr); terminate(8); } static void sysfatal(const char *fmt, ...) __attribute__((format(printf,1,2))); static void sysfatal(const char *fmt, ...) { va_list al; int e; e= errno; va_start(al,fmt); fputs("userv-ipif service: fatal system error: ",stderr); vfprintf(stderr, fmt, al); fprintf(stderr,": %s\n", strerror(e)); terminate(12); } static void badusage(const char *fmt, ...) __attribute__((format(printf,1,2))); static void badusage(const char *fmt, ...) { va_list al; struct pplace *cpp; if (cpplace) { fprintf(stderr, "userv-ipif service: %s:%d: ", cpplace->filename, cpplace->lineno); } else { fputs("userv-ipif service: invalid usage: ",stderr); } va_start(al,fmt); vfprintf(stderr, fmt, al); putc('\n',stderr); if (cpplace) { for (cpp=cpplace->parent; cpp; cpp=cpp->parent) { fprintf(stderr, "userv-ipif service: %s:%d: ... in file included from here\n", cpp->filename, cpp->lineno); } } terminate(16); } static char *ip2txt(unsigned long addr, char *buf) { sprintf(buf, "%lu.%lu.%lu.%lu", (addr>>24) & 0x0ff, (addr>>16) & 0x0ff, (addr>>8) & 0x0ff, (addr) & 0x0ff); return buf; } static unsigned long eat_number(const char **argp, const char *what, unsigned long min, unsigned long max, const char *endchars, int *endchar_r) { /* If !endchars then the endchar must be a nul, otherwise it may be * a nul (resulting in *argp set to 0) or something else (*argp set * to point to after delim, *endchar_r set to delim). * *endchar_r may be 0. */ unsigned long rv; char *ep; int endchar; if (!*argp) { badusage("missing number %s",what); } rv= strtoul(*argp,&ep,0); if ((endchar= *ep)) { if (!endchars) badusage("junk after number %s",what); if (!strchr(endchars,endchar)) badusage("invalid character or delimiter `%c' in or after number, %s:" " expected %s (or none?)", endchar,what,endchars); *argp= ep+1; } else { *argp= 0; } if (endchar_r) *endchar_r= endchar; if (rv < min || rv > max) badusage("number %s value %lu out of range %lu..%lu", what, rv, min, max); return rv; } static int addrnet_overlap(unsigned long p1, unsigned long m1, unsigned long p2, unsigned long m2) { unsigned long mask; mask= m1&m2; return (p1 & mask) == (p2 & mask); } static void addrnet_mustdiffer(const char *w1, unsigned long p1, unsigned long m1, const char *w2, unsigned long p2, unsigned long m2) { if (!addrnet_overlap(p1,m1,p2,m2)) return; badusage("%s %08lx/%08lx overlaps/clashes with %s %08lx/%08lx", w1,p1,m1, w2,p2,m2); } static unsigned long eat_addr(const char **argp, const char *what, const char *endchars, int *endchar_r) { char whatbuf[100]; unsigned long rv; int i; for (rv=0, i=0; i<4; i++) { rv <<= 8; sprintf(whatbuf,"%s byte #%d",what,i); rv |= eat_number(argp,whatbuf, 0,255, i<3 ? "." : endchars, endchar_r); } return rv; } static void eat_prefixmask(const char **argp, const char *what, const char *endchars, int *endchar_r, unsigned long *prefix_r, unsigned long *mask_r, int *len_r) { /* mask_r and len_r may be 0 */ char whatbuf[100]; int len; unsigned long prefix, mask; prefix= eat_addr(argp,what, "/",0); sprintf(whatbuf,"%s length",what); len= eat_number(argp,whatbuf, 0,32, endchars,endchar_r); mask= len ? (~0UL << (32-len)) : 0UL; if (prefix & ~mask) badusage("%s prefix %08lx not fully contained in mask %08lx", what,prefix,mask); *prefix_r= prefix; if (mask_r) *mask_r= mask; if (len_r) *len_r= len; } static char *eat_optionalstr(const char **argp, const char *what, const char *def) { ptrdiff_t len; const char *start= *argp; if (!start) { len = 0; } else { const char *comma= strchr(start, ','); if (comma) { len= comma - start; *argp= comma + 1; } else { len= strlen(start); *argp= 0; } } if (!len) { start= def; len= strlen(def); } char *r = malloc(len+1); if (!r) sysfatal("malloc for command line string"); memcpy(r,start,len); r[len]= 0; return r; } static int addrnet_isin(unsigned long prefix, unsigned long mask, unsigned long mprefix, unsigned long mmask) { return !(~mask & mmask) && (prefix & mmask) == mprefix; } /* Totally hideous algorithm for parsing the config file lines. * For each config file line, we first see if its gid applies. If not * we skip it. Otherwise, we do * permit_begin * which sets pming to 1 * for each range. pming may be 0 if we've determined that * this line does not apply to . * permit_range * which calls permit_range_thing for each * which checks to see if is inside the relevant * range (for +) or overlaps it (for -) and updates * allow and pming. */ static void permit_begin(void) { int i; localpming= peerpming= 1; for (i=0; iparent) { if (!strcmp(cpp->filename,filename)) badusage("recursive configuration file `%s'",filename); } file= fopen(filename,"r"); if (!file) badusage("cannot open configuration file `%s': %s", filename, strerror(errno)); if (!proto) printf("config file `%s':\n",filename); npp.parent= cpplace; npp.filename= filename; npp.lineno= 0; cpplace= &npp; while (fgets(buf, sizeof(buf), file)) { npp.lineno++; l= strlen(buf); if (!l) continue; truncated= (buf[l-1] != '\n'); while (l>0 && isspace((unsigned char) buf[l-1])) l--; if (!l) continue; buf[l]= 0; if (truncated) { while ((c= getc(file)) != EOF && c != '\n'); if (c == EOF) break; } pconfig(buf,truncated); } if (ferror(file)) badusage("failed while reading configuration file: %s", strerror(errno)); cpplace= npp.parent; fclose(file); } static void pconfig(const char *configstr, int truncated) { unsigned long fgid, tgid, pprefix, pmask; int plen, localonly, plus, rangeix, delim; char ptxt[ATXTLEN]; char whattxt[100]; const char *gidlist; switch (configstr[0]) { case '*': permit_begin(); permit_range(0UL,0UL,1,0); ifnameallow= 1; return; case '#': return; case '/': case '.': if (truncated) badusage("filename too long (`%.100s...')",configstr); pfile(configstr); return; default: if (!isdigit((unsigned char)configstr[0])) badusage("unknown configuration directive"); fgid= eat_number(&configstr,"gid", 0,gidmaxval, ",",0); if (!proto) printf(" %5lu", fgid); gidlist= getenv("USERV_GID"); if (!gidlist) fatal("USERV_GID not set"); for (;;) { if (!gidlist) { if (!proto) printf(" no matching gid\n"); return; } tgid= eat_number(&gidlist,"userv-gid", 0,gidmaxval, " ",0); if (tgid == fgid) break; } if (configstr[0] == '=') { localonly= 1; configstr++; } else { localonly= 0; } permit_begin(); rangeix= 0; plus= 1; switch (configstr[0]) { case '-': plus= 0; /* fall through */ case '+': configstr++; default:; } for (;;) { sprintf(whattxt, "%s-prefix#%d", plus ? "permitted" : "notpermitted", rangeix); eat_prefixmask(&configstr,whattxt, ",+-",&delim, &pprefix,&pmask,&plen); if (!configstr && truncated) badusage("gid,prefix/len,... spec too long"); if (!proto) printf(" %c%s/%d:", plus?'+':'-',ip2txt(pprefix,ptxt), plen); permit_range(pprefix,pmask,plus,localonly); if (delim==',') break; plus= delim=='-' ? 0 : 1; rangeix++; } putchar('\n'); return; } } static void checkallow(int allow, const char *what, const char *prefixtxt, const char *masktxt) { if (allow) return; fprintf(stderr,"userv-ipif service: access denied for %s, %s/%s\n", what, prefixtxt, masktxt); allallow= 0; } static void parseargs(int argc, const char *const *argv) { unsigned long routeaddr, routemask; const char *carg; const char *const *cprotop; int i; char erwhatbuf[100], erwhatbuf2[100]; if (argc < NARGS+1) { badusage("too few arguments"); } if (argc > NARGS+1) { badusage("too many arguments"); } configstr= *++argv; carg= *++argv; if (strcmp(carg,"--")) badusage("separator argument `--' not found, got `%s'",carg); carg= *++argv; localaddr= eat_addr(&carg,"local-addr", ",",0); peeraddr= eat_addr(&carg,"peer-addr", ",",0); mtu= eat_number(&carg,"mtu", 576,65536, ",",0); localallow= peerallow= 0; char *protostr= eat_optionalstr(&carg,"protocol","slip"); if (!strcmp(protostr,"debug")) { proto= 0; } else { for (cprotop= protos_ok; (proto= *cprotop) && strcmp(proto,protostr); cprotop++); if (!proto) fatal("invalid protocol"); } ifnamepat= eat_optionalstr(&carg,"ifname pattern",default_ifnamepat); addrnet_mustdiffer("local-addr",localaddr,~0UL, "peer-addr",peeraddr,~0UL); carg= *++argv; if (strcmp(carg,"-")) { for (nexroutes=0; carg && *carg; nexroutes++) { if (nexroutes == MAXEXROUTES) fatal("too many extra routes (only %d allowed)",MAXEXROUTES); sprintf(erwhatbuf,"route#%d",nexroutes); eat_prefixmask(&carg,erwhatbuf, ",",0, &routeaddr,&routemask,0); if (routemask == ~0UL) { addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "local-addr",localaddr,~0UL); addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "peer-addr",peeraddr,~0UL); } for (i=0; i= strlen(ifnamepat)+1); strcpy(ifr.ifr_name, ifnamepat); tunfd= open("/dev/net/tun", O_RDWR); if (!tunfd) sysfatal("open /dev/net/tun"); r= fcntl(tunfd, F_GETFD); if (r==-1) sysfatal("fcntl(tunfd,F_GETFD)"); r= fcntl(tunfd, F_SETFD, r|FD_CLOEXEC); if (r==-1) sysfatal("fcntl(tunfd,F_SETFD,|FD_CLOEXEC)"); r= ioctl(tunfd, TUNSETIFF, (void*)&ifr); if (r) sysfatal("ioctl TUNSETIFF"); /* ifr.ifr_name might not be null-terminated. crazy abi. */ ifname= malloc(sizeof(ifr.ifr_name)+1); if (!ifname) sysfatal("malloc for interface name"); memcpy(ifname, ifr.ifr_name, sizeof(ifr.ifr_name)); ifname[sizeof(ifr.ifr_name)]= 0; } static void netconfigure(void) { char mtutxt[100]; int i; if (task("ifconfig")) { sprintf(mtutxt,"%lu",mtu); execlp("ifconfig", "ifconfig", ifname, localtxt, "netmask","255.255.255.255", "pointopoint",peertxt, "-broadcast", "mtu",mtutxt, "up", (char*)0); sysfatal("cannot exec ifconfig"); } for (i=0; i=ip_end) break; uint8_t c= *ip++; if (c==SLIP_END) { rx_packet(output_buf, op-output_buf); op= output_buf; eaten= ip - input_buf; continue; } if (c==SLIP_ESC) { if (ip>=ip_end) { /* rescan this when there's more */ ip--; break; } c= *ip++; if (c==SLIP_ESC_END) c=SLIP_END; else if (c==SLIP_ESC_ESC) c=SLIP_ESC; else fatal("unexpected byte 0%o after SLIP_ESC",c); } if (op == output_buf+mtu) fatal("SLIP packet exceeds mtu"); *op++= c; } output_len= op - output_buf; scanned= ip - input_buf; input_waiting -= eaten; memmove(input_buf, input_buf+eaten, input_waiting); scanned -= eaten; } static void tx_packet(uint8_t *output_buf, const uint8_t *ip, int inlen) { /* output_buf is passed as a parameter since it's in copydata's stack frame */ assert(!output_waiting); uint8_t *op= output_buf; *op++= SLIP_END; while (inlen-- >0) { uint8_t c= *ip++; if (c==SLIP_END) { *op++= SLIP_ESC; *op++= SLIP_ESC_END; } else if (c==SLIP_ESC) { *op++= SLIP_ESC; *op++= SLIP_ESC_ESC; } else *op++= c; } *op++= SLIP_END; assert(op <= output_buf + mtu*2+2); output_waiting= op - output_buf; } static void copydata(void) __attribute__((noreturn)); static void copydata(void) { uint8_t output_buf[mtu*2+2]; uint8_t input_buf[mtu*2+2]; uint8_t rx_packet_buf[mtu]; int r, i; struct pollfd polls[3]; memset(polls, 0, sizeof(polls)); polls[0].fd= 0; polls[0].events= POLLIN; polls[1].fd= 1; polls[2].fd= tunfd; /* We don't do flow control on input packets; instead, we just throw * away ones which the kernel doesn't accept. So we always poll for * those. * * Output packets we buffer, so we poll only as appropriate for those. */ /* Start by transmitting one END byte to say we're ready. */ output_buf[0]= SLIP_END; output_waiting= 1; for (;;) { if (output_waiting) { r= write(1, output_buf, output_waiting); if (r<0) { if (errno==EINTR) continue; if (errno!=EAGAIN) sysfatal("error writing SLIP output (packets being received)"); } else { assert(r>0); output_waiting -= r; memmove(output_buf, output_buf+r, output_waiting); } } if (output_waiting) { polls[1].events |= POLLOUT; polls[2].events &= ~POLLIN; } else { polls[1].events &= ~POLLOUT; polls[2].events |= POLLIN; } r= poll(polls,3,-1); if (r<0) { if (errno==EINTR) continue; sysfatal("poll() failed"); } assert(r>0); /* we used an infinite timeout */ for (i=0; i0) { input_waiting += r; assert(input_waiting <= sizeof(input_buf)); more_rx_data(input_buf, rx_packet_buf); } else if (r==0) { terminate(0); } else { if (!(errno==EINTR || errno==EAGAIN)) sysfatal("error reading input SLIP data (packets to transmit)"); } } /* We handle what would be (polls[1].events & POLLOUT) above, * unconditionally. That eliminates the need to poll in the usual case */ if (polls[2].events & POLLIN) { uint8_t packet_buf[mtu]; r= read(tunfd, packet_buf, mtu); if (r>0) { tx_packet(output_buf, packet_buf, r); } else { assert(r<0); if (!(errno==EAGAIN || errno==EWOULDBLOCK)) sysfatal("error reading packet (being transmitted) from tun"); } } } } int main(int argc, const char *const *argv) { parseargs(argc,argv); pconfig(configstr,0); checkpermit(); if (!proto) dumpdebug(); createif(); netconfigure(); setnonblock(tunfd); setnonblock(0); setnonblock(1); copydata(); }