/* VERSIONS Author of the original perl version: btv@odin.net (Fitos Imre) C translation: rzm@pdi.net (Rafal Maszkowski) 96.07.27 97.01 DEBUG added 97.02.09 integrated perl ppplogin.dynamic translation # btv -> btv@odin.net (Fitos Imre) 19950628 v. 1.0a # Dynamization: rzm@pdi.net 19960318 v. 2.0 # 19960320 allocating blocks of >32 addresses # 19960329 corrected permissions # addresses generation # creating sems only if needed (create_sems) # v. 2.0.1 alpha # ToDo: # - permissions, sems owned by root.ppp always? must be run as ppp.ppp 6555 97.02 tests 97.02.12 deny in 3rd field 97.02.24 named it ppplogin-2.1.c.{gz,lsm} and uploaded to sunsite 2000.07.21 trying (with Irek Janas) to make v. 2.2 running on RH 6.2 cleanup and spell checking noauth for ppp-2.3 2000.07.24 final report from Irek configuration: /etc/ -> /etc/slip/ more about debugging REQUIREMENTS - SYSV IPC compiled in, usually it is - noauth option in /etc/ppp/options for ppp-2.3 or newer - running ppplogin suid/sgid to special user, e.g. ppp:ppp Even if my code isn't 100% secure ppplogin should be safe because it is not taking any input from the user. Special user is needed because somebody must own the semaphores. COMPILING AND INSTALLING cc -o ppplogin ppplogin-*.c You can add -O and -DDEBUG=n (n=1..3). chown ppp:ppp ppplogin chmod ug+s ppplogin mv ppplogin /sbin For ppp-2.3 or newer you may need to add noauth option to /etc/ppp/options . We are using Unix password authentication instead of pppd authentication. Typical message when there is NO noauth option: Jul 20 07:58:47 ich pppd[21660]: (because this system has a default route to the internet) Jul 20 07:58:47 ich pppd[21660]: but I couldn't find any suitable secret (password) for it to use to do so. DEBUGGING compile with -DDEBUG=n where n is 1..3. With this modification YOU CANNOT RUN IT IN NORMAL WAY. Run it supplying two arguments: user and tty. The configuration files slip.hosts and slip.tty will be taken from the CURRENT directory. ./ppplogin rzm /dev/ttyC4 When you are allowed to run pppd you will get a shell with ppp:ppp user as an owner so you can run pppd with the arguments shown by ppplogin or just check if you are getting through. CONFIGURATION My pppd setup (2.0.x): -rwsr-sr-x 1 ppp ppp 6670 Jul 31 1996 /sbin/ppplogin ppplogin needs to have some special user privileges for creating and maintaining semaphores and at least special group to be able to run pppd which should be suid root but shouldn't be directly runnable by the users: -rws--x--- 1 root ppp 89736 Mar 20 1996 /usr/sbin/pppd only ppp group can run it - everybody else can be allowed to by ppplogin. Configuration is like sliplogin's (and perl ppplogin's) but with some extensions and restrictions. ppplogin uses only 1st and 3rd column, using 2nd could be implemented if somebody really needs to. /etc/slip.hosts: rzm rymunda 158.75.28.29 0xffffffff auto -1 user1 rymunda DYNAMIC 0xffffff00 auto user2 rymunda DYNAMIC 0xffffff00i auto # # this one is not allowed - NOT IMPLEMENTED YET but may work.. marbo rymunda deny # # will use slip.ttya instead of slip.tty - NOT IMPLEMENTED YET # now is interpreted as DYNAMIC sal rymunda DYNAMICa 0xffffff00 auto # # everybody else is allowed * rymunda DYNAMIC 0xffffff00 auto # ttys definitions for pseudo-dynamic and truly-dynamic assignment # 1st field may be a specific tty or *. NOT IMPLEMENTED YET - any regular # expression matching ttys # /etc/slip.tty: /dev/ttyC1 158.75.28.19 /dev/ttyC3 158.75.28.20 /dev/ttyS1 158.75.28.20 # # disallowed - NOT IMPLEMENTED but may work /dev/tty3 deny # # dynamic * 158.75.28.129 32 up The fields are: 1. * is 'any'. The other lines can be for specific ttys which will override the default *. Only one * (the last one) makes sense. 2. The lowest address 3. Number of addresses 4. The way they are used: up (first free, default), down (starting from the last one), rand CREDITS Thanks to Irek Janas for testing perl version 2.0.1 then running faithfully the C version for years and testing v. 2.2. C version 2.1 tested by Mariusz Boroński. TODO - generating addresses, random mode using always up to the last free addr. would need checking all possible addresses 1st - expansive? - implement regex in 1st field - implement multiple DYNAMIC (DYNAMICa, DYNAMICb etc.) in 3rd field - use 2nd field (local address) */ #include /* getuid, ttyname, execl */ #include /* getpwuid */ #include /* getpwuid */ #include /* fopen, FILE* */ #include /* strtok */ #include /* div */ #include #include #ifndef SEMMSL /* for RH 6.2 */ #define SEMMSL 32 /* linux/sem.h has 512, let's be humble, braver when tested */ #endif #define MAXBLOCKS 16 /* blocks of SEMMSL */ #define TOTALDYNIP (MAXBLOCKS*SEMMSL) #define VERSION "2.2 (tested)" /* you can use cc -g -DDEBUG=3 ppplogin ppplogin.c or uncomment #define below when DEBUG>0 ppplogin must be run as e.g. ./ppplogin rzm /dev/ttyC4 */ /* #define DEBUG 3 /* */ #if DEBUG>=2 #include #endif #define LINE_LEN 200 #define TOKEN_LEN 30 #define TOKEN_SEP " \r\n" #if DEBUG<1 #define CONF_PATH "/etc/slip/" /* formerly /etc/, RH 6.x uses /etc/slip/ */ #define PPP "/usr/sbin/pppd" #else #define CONF_PATH "" /* current directory when debugging */ #define PPP "/bin/bash" /* to be able to run id and such */ #endif #define SLIP_HOSTS CONF_PATH "slip.hosts" #define SLIP_TTY CONF_PATH "slip.tty" #define METHOD_UP 0 #define METHOD_DOWN 1 #define METHOD_RAND 2 /* the last-1 argument will be filled with :ip_address the first argument can be changed to anything - pppd path is in PPP */ char *args[] = { "pppd", "modem", "crtscts", "proxyarp", "lock", "passive", "asyncmap", "0", NULL, NULL }; char * find_in_file(char *key, char *file) { FILE *filep; static char buff[LINE_LEN]; int found=0; if ( (filep=fopen(file, "r")) == NULL) { printf("Can't open file %s .\n", file); exit(1); } while ( fgets(buff, LINE_LEN-2, filep) ) { strtok(buff, TOKEN_SEP); /* find exact match or any *, should be extended to regcomp/regexec match */ #if DEBUG>=3 printf("buff: <%s>\n", buff); #endif if ( (strncmp(key, buff, TOKEN_LEN) == 0) || (strncmp("*", buff, TOKEN_LEN) == 0) ) { found = 1; break; } } fclose(filep); if (!found) return NULL; else return buff; } int ruid, euid, rgid, egid; void seteid() { setuid(euid); setgid(egid); } void setrid() { setuid(ruid); setgid(rgid); } /* works for C (sub)class only */ char *add_address(char *ip, int i) { int last; char *address = malloc(16), *ind; if (address==NULL) { printf("add_address: malloc failed\n"); exit(41); } ind = rindex(ip, '.'); if (ind==NULL) { printf("add_address: ip doesn't have any dots\n"); exit(42); } last = atoi(ind+1) + i; /* copy 1st 3 octets and dot */ strncpy(address, ip, ind-ip+1); snprintf(address+ (ind-ip+1), 16, "%d", last); return address; } /* the caller should check if the result is in range 0 .. $nip-1 */ int generate_address(int i, int nip, int imethod) { switch (imethod) { case METHOD_UP: return i; case METHOD_DOWN: return nip-i-1; case METHOD_RAND: return 1+(int) ( (double)nip*rand()/(RAND_MAX+1.0) ); } return -1; } key_t make_ipc_key(char *tmp) { int sum = 0, i; for (i=0; (*tmp!='\0') && (i MAXBLOCKS ) { printf("IPC keys table too small\n"); exit(31); } /* after this mapping is 31->0,31 32->0,0 33->1,1 so we'll get the right # of blocks */ if (blocks.rem==0) blocks.quot--; for ( block=0; block <= blocks.quot; block++) { int number, key; key_t ipc_key; char tmp[TOKEN_LEN]; snprintf(tmp, sizeof(tmp)-1, "%s%04d", keyinp, block); ipc_key = make_ipc_key(tmp); if ( (blocks.rem!=0) && (block==blocks.quot)) number = blocks.rem; else number = SEMMSL; /* get them */ seteid(); key = semget(ipc_key, 0, 0); /* they don't exist? - create */ if (key == -1) key = semget(ipc_key, number, 0660 | IPC_CREAT | IPC_EXCL); if (key == -1) { printf("semget(): no key\n"); exit(32); } setrid(); /* global keys (sem ids) table */ keys[block]=key; } } int check_address(int daddress) { int key, res; /* max # of sems for sem-id, 32 on Linux */ div_t blocks = div(daddress, SEMMSL); struct sembuf sops[2]; /* get global key */ key=keys[blocks.quot]; /* (no)wait for 0 */ sops[0].sem_num = blocks.rem; sops[0].sem_op = 0; sops[0].sem_flg = IPC_NOWAIT; /* Increment the semaphore count, vanishing after pppd exit */ sops[1].sem_num = blocks.rem; sops[1].sem_op = 1; sops[1].sem_flg = SEM_UNDO; seteid(); res = semop(key, sops, 2); setrid(); #if DEBUG>=2 printf("semop: <%d>, errno: <%d>\n", res, errno); #endif return res; } char * get_address(char *keyinp, char *ip, char *nip, char *method) { int inip, maxi, i, daddress, imethod; char **endptr, *address; /* inip = strtol(nip, endptr, 10); */ inip = atoi(nip); #if DEBUG>=2 printf("inip: <%d>\n", inip); #endif /* if ( **endptr != '\0' ) { printf("<%s> is not valid number\n", nip); exit(21); } */ if ( strncmp(method, "up", 2) == 0 ) imethod = METHOD_UP; else if ( strncmp(method, "down", 4) == 0 ) imethod = METHOD_DOWN; else if ( strncmp(method, "rand", 4) == 0 ) imethod = METHOD_RAND; else { printf("unknown address generation method <%s>\n", method); exit(22); } /* create as many sem blocks as needed if needed */ create_sems(keyinp, inip); /* usually checking just as many addresses as needed, slightly more for random mode - ugly */ maxi = inip; if ( strncmp(method, "rand", 4) == 0) maxi = 3*maxi/2; for (i=0; i<= maxi; i++) { daddress=generate_address(i, inip, imethod); #if DEBUG>=1 printf("daddr: %d\n", daddress); #endif /* check if there is any sem with 0 value and inc it immediately */ if ( check_address(daddress) == 0 ) break; } if ( (i>=maxi) || (daddress<0) || (daddress>=inip) ) { printf("Can't find any free dynamic address.\n"); exit(23); } #if DEBUG>=2 printf("i: %d, maxi: %d\n", i, maxi); #endif address = add_address(ip, daddress); #if DEBUG>=2 printf("addr: %s\n", address); #endif return address; } void go_ppp(char *ppp, char *ip, char **args) { char colonip[TOKEN_LEN+1]=":", **last; strncat(colonip, ip, TOKEN_LEN); printf("Your IP address is: %s\n", ip); for (last=args; *last != NULL; last++) ; *last = colonip; #if DEBUG>=1 { int i; printf("Args: "); for (i=0; args[i] != NULL; i++) printf("%s ", args[i]); printf("\n"); args[1] = NULL; } #endif printf("Executing %s.\n", PPP); seteid(); if ( execv(PPP, args) == -1 ) { printf("exec %s failed\n", PPP); exit(3); } } int main(argc, argv) int argc; char **argv; { struct passwd *pw; char *tty, *line, *ip, *nip, *method, keyinp[TOKEN_LEN]; printf("%s version %s compiled %s %s, up to %d IPs\n", __FILE__, VERSION, __DATE__, __TIME__, TOTALDYNIP); /* whoami? */ ruid = getuid(); euid = geteuid(); rgid = getgid(); egid = getegid(); pw = getpwuid(ruid); /* pw->pw_name */ /* security */ setrid(); /* tty? */ tty = ttyname(0); #if DEBUG>=1 if (argc!=3) { printf("Wrong number of arguments in debugging mode. Use:\n ppplogin user /dev/ttyXY\n"); exit(1); } tty = argv[2]; #endif printf("Welcome %s on %s.\n", pw->pw_name, tty); #if DEBUG>=1 printf("errr... welcome %s...\n", argv[1]); line = find_in_file(argv[1], SLIP_HOSTS); #else line = find_in_file(pw->pw_name, SLIP_HOSTS); #endif if ( line == NULL ) { printf("You have no permission to use PPP. Go away.\n"); exit(2); } /* We can use PPP - what's the assigned address? */ ip = strtok(NULL, TOKEN_SEP); ip = strtok(NULL, TOKEN_SEP); /* cmp only 7 chars, so we can use DYNAMICt and such for multiple sets in v. 2.3 */ if (strncmp(ip, "DYNAMIC", strlen("DYNAMIC")) != 0) { /* getting back privileges and running pppd */ go_ppp(PPP, ip, args); } /* we may use DYNAMICxxx string for generating unique IPC key have to hurry, before next strtok call */ strncpy(keyinp, ip, sizeof(keyinp)-1); #if DEBUG>=2 printf("keyinp: <%s>\n", keyinp); #endif /* DYNAMIC - convert tty to IP */ line = find_in_file(tty, SLIP_TTY); if ( line == NULL ) { printf("There is no line configured as %s. Sorry.\n", tty); exit(4); } /* the IP # or starting (the lowest) IP # */ ip = strtok(NULL, TOKEN_SEP); #if DEBUG>=2 printf("ip: <%s>\n", ip); #endif if (strncmp(ip, "deny", 4) == 0) { printf("You are explicitly denied PPP access.\nContact the administrator if it is wrong.\n"); exit(66); } /* how many */ nip = strtok(NULL, TOKEN_SEP); #if DEBUG>=2 printf("nip: <%s>\n", nip); #endif /* up, down (from ip+nip-1 down), random */ method = strtok(NULL, TOKEN_SEP); #if DEBUG>=2 printf("method: <%s>\n", method); #endif /* try to allocate dynamic */ srand(0); /* just in case method could be random */ if ( (nip!=NULL) && (strlen(nip)!=0) ) ip = get_address(keyinp, ip, nip, method); /* getting back privileges and running pppd */ go_ppp(PPP, ip, args); return -1; }