284 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			284 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*	servxcheck() - Service access check.		Author: Kees J. Bot
 | |
|  *								8 Jan 1997
 | |
|  */
 | |
| #define nil 0
 | |
| #define ioctl _ioctl
 | |
| #define open _open
 | |
| #define write _write
 | |
| #define close _close
 | |
| #include <sys/types.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <syslog.h>
 | |
| #include <errno.h>
 | |
| #include <string.h>
 | |
| #include <fcntl.h>
 | |
| #include <unistd.h>
 | |
| #include <time.h>
 | |
| #include <sys/ioctl.h>
 | |
| #include <net/hton.h>
 | |
| #include <net/gen/in.h>
 | |
| #include <net/gen/tcp.h>
 | |
| #include <net/gen/tcp_io.h>
 | |
| #include <net/gen/inet.h>
 | |
| #include <net/gen/socket.h>
 | |
| #include <net/gen/netdb.h>
 | |
| 
 | |
| /* Default service access file. */
 | |
| static const char *path_servacces = _PATH_SERVACCES;
 | |
| 
 | |
| #define WLEN	256
 | |
| 
 | |
| static int getword(FILE *fp, char *word)
 | |
| /* Read a word from the file open by 'fp', skip whitespace and comments.
 | |
|  * Colon and semicolon are returned as a one character "word".  Returns
 | |
|  * word[0] or EOF.
 | |
|  */
 | |
| {
 | |
|     int c;
 | |
|     char *pw;
 | |
|     int wc;
 | |
| 
 | |
|     wc= 0;
 | |
|     for (;;) {
 | |
| 	if ((c= getc(fp)) == EOF) return EOF;
 | |
| 	if (c == '#') { wc= 1; continue; }
 | |
| 	if (c == '\n') { wc= 0; continue; }
 | |
| 	if (wc) continue;
 | |
| 	if (c <= ' ') continue;
 | |
| 	break;
 | |
|     }
 | |
| 
 | |
|     pw= word;
 | |
|     if (c == ':' || c == ';') {
 | |
| 	    *pw++ = c;
 | |
|     } else {
 | |
| 	do {
 | |
| 	    if (pw < word + WLEN-1) *pw++ = c;
 | |
| 	    c= getc(fp);
 | |
| 	} while (c != EOF && c > ' ' && c != ':' && c != ';');
 | |
| 	if (c != EOF) ungetc(c, fp);
 | |
|     }
 | |
|     *pw= 0;
 | |
|     return word[0];
 | |
| }
 | |
| 
 | |
| static int netspec(char *word, ipaddr_t *addr, ipaddr_t *mask)
 | |
| /* Try to interpret 'word' as an network spec, e.g. 172.16.102.64/27. */
 | |
| {
 | |
|     char *slash;
 | |
|     int r;
 | |
|     static char S32[]= "/32";
 | |
| 
 | |
|     if (*word == 0) return 0;
 | |
| 
 | |
|     if ((slash= strchr(word, '/')) == NULL) slash= S32;
 | |
| 
 | |
|     *slash= 0;
 | |
|     r= inet_aton(word, addr);
 | |
|     *slash++= '/';
 | |
|     if (!r) return 0;
 | |
| 
 | |
|     r= 0;
 | |
|     while ((*slash - '0') < 10u) {
 | |
| 	r= 10*r + (*slash++ - '0');
 | |
| 	if (r > 32) return 0;
 | |
|     }
 | |
|     if (*slash != 0 || slash[-1] == '/') return 0;
 | |
|     *mask= htonl(r == 0 ? 0L : (0xFFFFFFFFUL >> (32 - r)) << (32 - r));
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| static int match(const char *word, const char *pattern)
 | |
| /* Match word onto a pattern.  Pattern may contain the * wildcard. */
 | |
| {
 | |
|     unsigned cw, cp;
 | |
| #define lc(c, d) ((((c)= (d)) - 'A') <= ('Z' - 'A') ? (c)+= ('a' - 'A') : 0)
 | |
| 
 | |
|     for (;;) {
 | |
| 	lc(cw, *word);
 | |
| 	lc(cp, *pattern);
 | |
| 
 | |
| 	if (cp == '*') {
 | |
| 	    do pattern++; while (*pattern == '*');
 | |
| 	    lc(cp, *pattern);
 | |
| 	    if (cp == 0) return 1;
 | |
| 
 | |
| 	    while (cw != 0) {
 | |
| 		if (cw == cp && match(word+1, pattern+1)) return 1;
 | |
| 		word++;
 | |
| 		lc(cw, *word);
 | |
| 	    }
 | |
| 	    return 0;
 | |
| 	} else
 | |
| 	if (cw == 0 || cp == 0) {
 | |
| 	    return cw == cp;
 | |
| 	} else
 | |
| 	if (cw == cp) {
 | |
| 	    word++;
 | |
| 	    pattern++;
 | |
| 	} else {
 | |
| 	    return 0;
 | |
| 	}
 | |
|     }
 | |
| #undef lc
 | |
| }
 | |
| 
 | |
| static int get_name(ipaddr_t addr, char *name)
 | |
| /* Do a reverse lookup on the remote IP address followed by a forward lookup
 | |
|  * to check if the host has that address.  Return true if this is so, return
 | |
|  * either the true name or the ascii IP address in name[].
 | |
|  */
 | |
| {
 | |
|     struct hostent *he;
 | |
|     int i;
 | |
| 
 | |
|     he= gethostbyaddr((char *) &addr, sizeof(addr), AF_INET);
 | |
|     if (he != NULL) {
 | |
| 	strcpy(name, he->h_name);
 | |
| 	he= gethostbyname(name);
 | |
| 
 | |
| 	if (he != NULL && he->h_addrtype == AF_INET) {
 | |
| 	    for (i= 0; he->h_addr_list[i] != NULL; i++) {
 | |
| 		if (memcmp(he->h_addr_list[i], &addr, sizeof(addr)) == 0) {
 | |
| 		    strcpy(name, he->h_name);
 | |
| 		    return 1;
 | |
| 		}
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|     strcpy(name, inet_ntoa(addr));
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /* "state" and "log" flags, made to be bitwise comparable. */
 | |
| #define DEFFAIL		 0x01
 | |
| #define FAIL		(0x02 | DEFFAIL)
 | |
| #define PASS		 0x04
 | |
| 
 | |
| int servxcheck(ipaddr_t peer, const char *service,
 | |
| 		void (*logf)(int pass, const char *name))
 | |
| {
 | |
|     FILE *fp;
 | |
|     char word[WLEN];
 | |
|     char name[WLEN];
 | |
|     int c;
 | |
|     int got_name, slist, seen, explicit, state, log;
 | |
|     ipaddr_t addr, mask;
 | |
| 
 | |
|     /* Localhost? */
 | |
|     if ((peer & HTONL(0xFF000000)) == HTONL(0x7F000000)) return 1;
 | |
| 
 | |
|     if ((fp= fopen(path_servacces, "r")) == nil) {
 | |
| 	/* Succeed on error, fail if simply nonexistent. */
 | |
| 	return (errno != ENOENT);
 | |
|     }
 | |
| 
 | |
|     slist= 1;		/* Services list (before the colon.) */
 | |
|     seen= 0;		/* Given service not yet seen. */
 | |
|     explicit= 0;	/* Service mentioned explicitly. */
 | |
|     got_name= -1;	/* No reverse lookup done yet. */
 | |
|     log= FAIL;		/* By default log failures only. */
 | |
|     state= DEFFAIL;	/* Access denied until we know better. */
 | |
| 
 | |
|     while ((c= getword(fp, word)) != EOF) {
 | |
| 	if (c == ':') {
 | |
| 	    slist= 0;		/* Switch to access list. */
 | |
| 	} else
 | |
| 	if (c == ';') {
 | |
| 	    slist= 1;		/* Back to list of services. */
 | |
| 	    seen= 0;
 | |
| 	} else
 | |
| 	if (slist) {
 | |
| 	    /* Traverse services list. */
 | |
| 
 | |
| 	    if (match(service, word)) {
 | |
| 		/* Service has been spotted! */
 | |
| 		if (match(word, service)) {
 | |
| 		    /* Service mentioned without wildcards. */
 | |
| 		    seen= explicit= 1;
 | |
| 		} else {
 | |
| 		    /* Matched by a wildcard. */
 | |
| 		    if (!explicit) seen= 1;
 | |
| 		}
 | |
| 	    }
 | |
| 	} else {
 | |
| 	    /* Traverse access list. */
 | |
| 
 | |
| 	    if (c == 'l' && strcmp(word, "log") == 0) {
 | |
| 		if (seen) {
 | |
| 		    /* Log failures and successes. */
 | |
| 		    log= FAIL|PASS;
 | |
| 		}
 | |
| 		continue;
 | |
| 	    }
 | |
| 
 | |
| 	    if (c != '-' && c != '+') {
 | |
| 		if (logf == nil) {
 | |
| 		    syslog(LOG_ERR, "%s: strange check word '%s'\n",
 | |
| 			path_servacces, word);
 | |
| 		}
 | |
| 		continue;
 | |
| 	    }
 | |
| 
 | |
| 	    if (seen) {
 | |
| 		if (state == DEFFAIL) {
 | |
| 		    /* First check determines the default. */
 | |
| 		    state= c == '+' ? FAIL : PASS;
 | |
| 		}
 | |
| 
 | |
| 		if ((state == PASS) == (c == '+')) {
 | |
| 		    /* This check won't change state. */
 | |
| 		} else
 | |
| 		if (word[1] == 0) {
 | |
| 		    /* Lone + or - allows all or none. */
 | |
| 		    state= c == '-' ? FAIL : PASS;
 | |
| 		} else
 | |
| 		if (netspec(word+1, &addr, &mask)) {
 | |
| 		    /* Remote host is on the specified network? */
 | |
| 		    if (((peer ^ addr) & mask) == 0) {
 | |
| 			state= c == '-' ? FAIL : PASS;
 | |
| 		    }
 | |
| 		} else {
 | |
| 		    /* Name check. */
 | |
| 		    if (got_name == -1) {
 | |
| 			got_name= get_name(peer, name);
 | |
| 		    }
 | |
| 
 | |
| 		    /* Remote host name matches the word? */
 | |
| 		    if (!got_name) {
 | |
| 			state= FAIL;
 | |
| 		    } else
 | |
| 		    if (match(name, word+1)) {
 | |
| 			state= c == '-' ? FAIL : PASS;
 | |
| 		    }
 | |
| 		}
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|     fclose(fp);
 | |
| 
 | |
|     if ((log & state) != 0) {
 | |
| 	/* Log the result of the check. */
 | |
| 	if (got_name == -1) (void) get_name(peer, name);
 | |
| 
 | |
| 	if (logf != nil) {
 | |
| 	    (*logf)(state == PASS, name);
 | |
| 	} else {
 | |
| 	    syslog(LOG_NOTICE, "service '%s' %s to %s\n",
 | |
| 		service, state == PASS ? "granted" : "denied", name);
 | |
| 	}
 | |
|     }
 | |
|     return state == PASS;
 | |
| }
 | |
| 
 | |
| char *servxfile(const char *file)
 | |
| /* Specify a file to use for the access checks other than the default.  Return
 | |
|  * the old path.
 | |
|  */
 | |
| {
 | |
|     const char *oldpath= path_servacces;
 | |
|     path_servacces= file;
 | |
|     return (char *) oldpath;	/* (avoid const poisoning) */
 | |
| }
 | 
