/* * netplan access lists: read and write the access list file and verify * whether a file is accessible or not based on the access list. The * functions in this file are called from netplan.c. The functions are: * * acl_read read an access list file into memory * acl_write write memory to an access list file * acl_verify verify the rights a client has on a file based on the list * acl_exit shut down, release the list * ident_id get user ID from remote identd server * * The syntax for the access list file is a sequence of rules like this: * * name | owner | * : * [permit | deny] [read] [write] [delete] [netmask n.n.n.n] * [[user | group | host] data [data ...]] * * is the file the rule applies to; "owner" applies if the file name * matches the user name (derived from uid), an asterisk (*) applies to all * files. Permit is the default. If none of read,write,delete are specified, * all three are the default. The netmask applies to the client's IP address, * and is ffffffff if missing. * is one or more numerical UIDs or user names, numerical GIDs or group * names, or numerical n.n.n.n IP addresses or host names, for user, group, * and host rules, respectively. User is the default. Trailing n=0 IP address * components are not assumed to denote nets, use the netmask specifier for * subnet masking. All whitespace is ignored. * Pound signs (#) introduce comments that extend to the end of the line. */ #include #include #include #include #include #include #include #include #ifdef IBM #include #endif #include #include #include #include #include #ifdef NEWSOS4 #include #endif #include #include #include #include #include "netplan.h" typedef enum {aUSER=0, aGROUP, aHOST, aUSERHOST} Mode; typedef struct Acl { struct Acl *next; /* next access list entry in chain */ char *name; /* applies to this file, 0=all */ BOOL deny; /* TRUE=deny, FALSE=permit */ BOOL read; /* permit/deny reading */ BOOL write; /* permit/deny writing */ BOOL delete; /* permit/deny deleting */ Mode mode; /* check user or group? */ unsigned int netmask; /* if a(USER)HOST, netmask, 0=default*/ int ndata; /* # of data elems, -1 is wildcard */ int maxdata; /* allocated # of elems in data list */ unsigned long *ugid, *host; /* uid/gid and host/net data */ } Acl; extern char *progname; /* name of this program, argv[0] */ extern BOOL verbose; /* debug mode, don't daemonize */ extern BOOL to_syslog; /* print to syslog, not stderr */ extern struct client *client_list; /* array of clients (entry 0 unused) */ static Acl *acl_root; /* root of current access list */ static char *word; /* current word from input stream */ static char *nextword; /* next word from input (readahead) */ static BOOL getword(FILE *); static unsigned int ipaddr_atoi(const char *); static char *ip_itoa (unsigned int); static void acl_add_data(Acl *acl, unsigned int ugid, unsigned int host); extern void *allocate(int n); extern void *reallocate(void *o, int n); extern void release(void *p); extern char *mystrdup(const char *s); extern void logger(char *fmt, ...); extern char *ip_addr(struct sockaddr_in *); /* * forget the previous access list and read a new one ffrom the given file. * This must be done before the first verify because an empty access list * means deny everything. Returns FALSE if reading fails; in this case the * access list is left empty even if the file was ok partially, on the * theory that it's better to deny everything than permitting too much. * * phase 0: name : * phase 1: permid deny read write delete user group host netmask * phase 2: data data data ... */ int acl_read( const char *fname) /* access list file name to read */ { FILE *fp; /* open file */ Acl *tail = 0; /* previous access list entry */ Acl *acl = 0; /* current new access list entry */ int phase = 0; /* 0=name, 1=mode, 2=data */ int ret = TRUE; /* return code */ struct hostent *h; /* for adding hosts to ACL */ struct passwd *u; /* for adding users to ACL */ struct group *g; /* for adding groups to ACL */ int **ipp; char *c=0; unsigned int usr; acl_exit(); if (!(fp = fopen(fname, "r"))) { if (to_syslog) syslog(LOG_NOTICE, "%s: cannot open ACL file %s. " "This is ok; access control disabled.\n", progname, fname); else { perror(fname); fprintf(stderr, "this is ok; access control disabled.\n"); } return(FALSE); } while (ret && getword(fp)) { if (*nextword == ':') /* new rule */ phase = 0; if (phase == 1) { /* read, ... */ if (!strcmp(word, "permit")) acl->deny = FALSE; else if (!strcmp(word, "deny")) acl->deny = TRUE; else if (!strcmp(word, "read")) acl->read = TRUE; else if (!strcmp(word, "write")) acl->write = TRUE; else if (!strcmp(word, "delete")) acl->delete = TRUE; else if (!strcmp(word, "user")) { acl->mode = aUSER; phase = 2; continue; } else if (!strcmp(word, "group")) { acl->mode = aGROUP; phase = 2; continue; } else if (!strcmp(word, "host")) { acl->mode = aHOST; phase = 2; continue; } else if (!strcmp(word, "netmask")) { getword(fp); acl->netmask = ipaddr_atoi(word); } else phase = 2; } if (phase == 2) { /* data */ if (acl->mode == aUSER) { for (c=word; *c && *c != '@'; c++); if (*c) acl->mode = aUSERHOST; } if (*word>='0' && *word<='9' && acl->mode != aUSERHOST) if (acl->mode == aHOST) acl_add_data(acl, 0,ipaddr_atoi(word)); else acl_add_data(acl, atoi(word), 0); else switch(acl->mode) { case aUSER: if ((u = getpwnam(word))) acl_add_data(acl, u->pw_uid, 0); else logger("%s: unknown user %s, ign" "ored\n", fname, word); break; case aGROUP: if ((g = getgrnam(word))) acl_add_data(acl, g->gr_gid, 0); else logger("%s: unknown group %s, ignored" "\n", fname, word); break; case aHOST: if ((h = gethostbyname(word))) { for (ipp=(int**)h->h_addr_list; *ipp;ipp++) acl_add_data(acl, 0, ntohl(**ipp)); } else logger("%s: unknown host %s, ignored\n" ,fname, word); break; case aUSERHOST: acl->mode = aUSER; /* GU: prep for next word */ *c++ = 0; if (*word >= '0' && *word <= '9') usr = atoi(word); else if (!(u = getpwnam(word))) { logger("%s: unknown user %s, ignored\n" ,fname, word); break; } else usr = u->pw_uid; if (*c >= '0' && *c <= '9') acl_add_data(acl, usr, ipaddr_atoi(c)); else if ((h = gethostbyname(c))) for (ipp=(int**)h->h_addr_list; *ipp;ipp++) acl_add_data(acl, usr, ntohl(**ipp)); else logger("%s: unknown host %s, ignored\n" ,fname, word); } } if (phase == 0) { /* name : */ acl = (Acl *)allocate(sizeof(Acl)); memset(acl, 0, sizeof(Acl)); if (tail) tail = tail->next = acl; else tail = acl_root = acl; if (strcmp(word, "*") && !(acl->name=mystrdup(word))) { perror(fname); ret = FALSE; } else if (getword(fp) && *word != ':') { ret = FALSE; logger("%s: no ':' after \"%s\"\n", fname, acl->name ? acl->name : "*"); } else phase = 1; } } for (acl=acl_root; acl; acl=acl->next) if (!acl->read && !acl->write && !acl->delete) acl->read = acl->write = acl->delete = TRUE; fclose(fp); if (!fname) { perror(fname); ret = FALSE; } if (!ret) acl_exit(); return(ret); } /* * add one word of data payload to the Acl structure. Return FALSE on error. */ static void acl_add_data( Acl *acl, unsigned int ugid, unsigned int host) { if (acl->ndata >= acl->maxdata) { acl->ugid = acl->maxdata ? reallocate(acl->ugid, sizeof(ugid) * (acl->maxdata *= 2)) : allocate(sizeof(ugid) * (acl->maxdata = 16)); acl->host = acl->maxdata ? reallocate(acl->host, sizeof(host) * (acl->maxdata *= 2)) : allocate(sizeof(host) * (acl->maxdata = 16)); } acl->ugid[acl->ndata] = ugid; acl->host[acl->ndata++] = host; } /* * a subfunction of the previous, which reads the access list file word by * word. A word is either a sequence of one or more characters of the char * set [a-zA-Z0-9_.], or a single character that is not in the set and is * not a comma or a minus. Whitespace, comma, and minus are ignored. This * makes the access list file free-format, newlines are treated like spaces. * Pound signs (#) introduce comments that extend to the end of the line. * This function always reads one word ahead so the parser can check when * the next word is ':', which means that the current word begins a new * file rule and is not another data word. */ #define ISBLANK(c) strchr(", \t\n\r", c) #define ISWORD(c) ((c >= 'a' && c <= 'z') ||\ (c >= 'A' && c <= 'Z') ||\ (c >= '0' && c <= '9') ||\ c == '_' || c == '.' || c == '-' || c == '@') static BOOL getword( FILE *fp) /* file to read from */ { static char buf1[256]; /* current/next word buffer */ static char buf2[256]; /* current/next word buffer */ char *p; /* next free char in nextword buffer */ int c; /* current char read from file */ int max = sizeof(buf1); if (!nextword) { /* startup init */ word = buf1; nextword = buf2; getword(fp); } p = word; /* swap buffers, word=next */ word = nextword; nextword = p; for (;;) { /* readahead next word */ while (ISBLANK(c = fgetc(fp))); if (c != '#') break; while ((c = fgetc(fp)) != EOF && c != '\n'); } if (c != EOF) *p++ = c; while (--max > 1 && (c = fgetc(fp)) != EOF && ISWORD(c)) *p++ = c; *p = 0; ungetc(c, fp); return(*word); } /* * write an access list back to a given file, in the same format expected * by acl_read. If writing fails (probably because the file is not writable), * return FALSE. This function is currently not used because there is no way * to modify the list as it was read from the file originally, so why bother? */ int acl_write( const char *fname) /* access list file name to write */ { FILE *fp; /* open file */ Acl *acl; /* current new access list entry */ int i; /* for scanning data list */ static const char fstderr[] = "stderr"; if (!fname) { fname = fstderr; fp = stderr; } else if (!(fp = fopen(fname, "w"))) { perror(fname); return(FALSE); } fprintf(fp, "# netplan database access permissions file\n"); fprintf(fp, "#\n"); fprintf(fp, "# file|'*' ':' permit|deny\n"); fprintf(fp, "# [read] [write] [delete] [netmask n.n.n.n]\n"); fprintf(fp, "# [user|group|host *|'*']\n"); fprintf(fp, "#\n"); for (acl=acl_root; acl; acl=acl->next) { fprintf(fp, "%s: %s", acl->name ? acl->name : "*", acl->deny ? "deny" : "permit"); if (acl->read) fprintf(fp, " read"); if (acl->write) fprintf(fp, " write"); if (acl->delete) fprintf(fp, " delete"); if (acl->netmask) fprintf(fp, " netmask %s", ip_itoa(acl->netmask)); if (acl->ndata) switch(acl->mode) { case aUSER: fprintf(fp, "\n\tuser"); break; case aGROUP: fprintf(fp, "\n\tgroup"); break; case aUSERHOST: case aHOST: fprintf(fp, "\n\thost"); break; } for (i=0; i < acl->ndata; i++) if (acl->mode == aHOST) fprintf(fp, " %s", ip_itoa(acl->host[i])); else if (acl->mode == aUSERHOST) fprintf(fp, " %d@%s", (int)acl->ugid[i], ip_itoa(acl->host[i])); else fprintf(fp, " %d", (int)acl->ugid[i]); fprintf(fp, "\n"); } if (fname != fstderr) fclose(fp); return(TRUE); } /* * simple conversion functions for IP addresses: 32-bit unsigned ints to and * from four-part dotted-decimal notation. Symbolic IP addresses are not * supported. */ static unsigned int ipaddr_atoi( const char *addr) /* n.n.n.n string to convert */ { unsigned int a=0, b=0, c=0, d=0; sscanf(addr, "%d.%d.%d.%d", &a, &b, &c, &d); return((a&255) << 24 | (b&255) << 16 | (c&255) << 8 | (d&255)); } static char *ip_itoa( unsigned int ip) /* IP address to convert */ { static char addr[16]; /* n.n.n.n address string buffer */ sprintf(addr, "%d.%d.%d.%d", ip>>24, ip>>16&255, ip>>8&255, ip&255); return(addr); } /* * the reason why all of the above is done: return the rights that the user * described by the client structure (containing uid, gid, IP address, and * name), has for . The right to read, write, delete are distinguished. * The access list is scanned for rules matching the file name and for default * rules (marked by * in the access list file). This is called whenever a * client opens a file. A client who failed authentication (-a was specified * but identd didn't confirm */ void acl_verify( int *r, /* set to TRUE if reading is ok */ int *w, /* set to TRUE if writing is ok */ int *d, /* set to TRUE if deleting is ok */ char *name, /* file name to verify */ struct client *c) /* client structure */ { Acl *acl; /* current acl entry */ unsigned int nm; /* current netmask for */ int i=0, j; /* for searching data[] and gids[]*/ BOOL found; *r = *w = *d = FALSE; for (acl=acl_root; acl; acl=acl->next) { if (acl->name && !c->auth_fail && (strcmp(acl->name, "owner") || strcmp(c->user, name)) && strcmp(acl->name, name)) continue; switch(acl->mode) { case aUSER: if (c->auth_fail) continue; for (i=0; i < acl->ndata; i++) if (acl->ugid[i] == c->uid) break; break; case aGROUP: if (c->auth_fail) continue; for (found=FALSE, i=0; !found && i < acl->ndata; !found && i++) for (j=0; jgids[j]>=0; j++) if ((found = (int)acl->ugid[i] == (int)c->gids[j])) break; break; case aHOST: nm = acl->netmask ? acl->netmask : 0xffffffff; for (i=0; i < acl->ndata; i++) if ((nm & acl->host[i]) == (nm & ntohl(c->addr.sin_addr.s_addr))) break; break; case aUSERHOST: nm = acl->netmask ? acl->netmask : 0xffffffff; for (i=0; i < acl->ndata; i++) if ((acl->ugid[i] == c->uid) && ((nm & acl->host[i]) == (nm & ntohl(c->addr.sin_addr.s_addr)))) break; } if (!acl->ndata || i < acl->ndata) { if (acl->read) *r = !acl->deny; if (acl->write) *w = !acl->deny; if (acl->delete) *d = !acl->deny; } } } /* * shut down access lists. Release all memory. This is also done when the * access list file is re-read to keep rules from accumulating. */ void acl_exit(void) { Acl *acl, *next; /* current and next acl entry */ for (acl=acl_root; acl; acl=next) { next = acl->next; release(acl->name); release(acl->ugid); release(acl->host); release(acl); } acl_root = 0; word = nextword = 0; /* make sure to restart readahead */ } /* * ask an identd server for the user name at the other end of a connection. * This is not actually part of ACL but is used to see whether we can trust * the claimed user ID before it is applied to ACL verification. netplan * will use the identd name if available and ignore who the client said * he is. Return 0 if authentication failed, or the remote user name if ok. */ char *ident_id( int fd) /* client ID and socket descriptor */ { struct client *c = &client_list[fd]; char *hostname; int port; struct sockaddr_in addr; /* IP address */ int idfd; /* temporary socket descriptor */ int i, on; /* for setting socket options */ char buf[1024], *p; /* banner reply from server */ struct servent *serv = getservbyname("auth", "tcp"); port = serv ? serv->s_port : htons(113); hostname = ip_addr(&c->addr); if (verbose) logger("client %d: authenticating, connecting to identd on " "%.100s\n", fd, hostname); if ((idfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { logger("client %d: cannot open socket to authentication server" "on %.100s\n", fd, hostname); return(0); } addr.sin_family = AF_INET; addr.sin_port = port; memcpy(&addr.sin_addr, &c->addr.sin_addr, sizeof(addr.sin_addr)); if (connect(idfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(idfd); logger("client %d: cannot connect to authentication server on " "%.100s\n", fd, hostname); return(0); } on = 1; setsockopt(idfd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); on = 1; setsockopt(idfd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); sprintf(buf, "%d,%d\n", ntohs(c->addr.sin_port), c->port); write(idfd, buf, strlen(buf)); if ((i = read(idfd, buf, sizeof(buf)-1)) < 0) { close(idfd); logger("client %d: authentication server on %.100s does not " "reply\n", fd, hostname); return(0); } close(idfd); buf[i > 2 ? i-2 : 0] = 0; if (verbose) logger("client %d: identd replied \"%.500s\"\n", fd, buf); if (!strstr(buf, "USERID")) { logger("client %d: authentication failure on %.100s, identd " "replied \"%.500s\"\n", fd, hostname, buf); return(0); } if ((p = strrchr(buf, ':'))) while (*++p == ' '); return(p ? mystrdup(p) : 0); }