/* pGina PAM Server - A PAM-Aware Unix Daemon for pGina Copyright (C) 2003 Nathan Yocom, Jiho Kim This program 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 2 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Email: nate.yocom@xpasystems.com Email: jiho.kim@xpasystems.com Web: http://pgina.xpasystems.com Snail Mail: Nathan Yocom 9 Evergreen Farms Rd. Scarborough, ME 04074 Phone: 207-450-4948 */ /* $Log: access.c,v $ Revision 1.2 2003/09/12 12:32:46 nyocom Removed unused variable in access.c Cleaned up some extranious code in actions.c Fixed 'too many file descriptors' open bug, thanks to Joseph Tombrello for the diff. Revision 1.1 2003/08/06 04:58:33 nyocom Initial Import Revision 1.10 2003/05/16 01:52:18 jkim -with-authasst-group option added header dependency problem in compilation fixed in pgina_pam_misc.h importing authasst.groups config file Revision 1.9 2003/05/07 21:49:16 jkim Reorganized some header dependencies Moved some misc function to pgina_pam_misc.c Changed syslog to debug_out in authasst code Reformatted usage info to fit in 80 cols. Revision 1.8 2003/05/01 23:32:57 xpasys Fixed confusion about ALL in access.c Revision 1.7 2003/05/01 03:47:49 xpasys fixed protocol problem with verify Revision 1.6 2003/05/01 02:40:27 xpasys Changed authasst to work with IP/FQDN/hostname. Imported source of Jiho's client into CVS Revision 1.5 2003/04/30 00:16:38 xpasys Added GPL to jiho's code Added --enable-debug notes to README and upgrade info Revision 1.4 2003/04/29 16:53:43 xpasys Several solaris compilation fixes added Revision 1.3 2003/04/29 16:46:31 xpasys Added install target for authasst.conf files */ #include "access.h" #include "pgina_pam_server.h" #include #include #include #include #include //gets rid of leading and trailing whitespaces in a string char * trim(char * s) { int p=0; int len; if(s==NULL || strlen(s)==0) return s; len = strlen(s); for(p=0; p=0 && (s[p]=='\0' || isspace((int) s[p])); p--) s[p]='\0'; return s; } //returns a linked list of relevant "logical" lines in a file. //Continued lines are concatenated. LL* parseConfigFile(const char* filename) { // temporary storage for line int bufsize=1000; char buf[bufsize+1]; //file FILE * configFile; //flag during parsing int continuation=0; //variables used in various places char * str=NULL; char * temp=NULL; int len; LLnode* walker; //There's two linked lists involved. strbufLL is used as a string //buffer to be better about reading in an arbitrarily large //string. lineLL is the list (of strings) to be returned from //this function. LL* strbufLL=NULL; LL* lineLL=NULL; //open file configFile = fopen (filename,"rt"); if(! configFile) { syslog(LOG_ERR, "[PARSE_CONFIG_FILE] Could not open %s! There's no reason to go on. Bang, I'm dead.", filename); exit(2); } lineLL=new_LL(); do { //clean up string buffer list if necessary. if(strbufLL) { freelist(strbufLL); strbufLL=NULL; } strbufLL = new_LL(); //loop to get whole string (until \n) into a linked list with at //most bufsize chunks per node. do { memset(buf, '\0', bufsize); fgets(buf, bufsize/2, configFile); len = strlen(buf); addtoLL(strbufLL, buf); } while(len!=0 && buf[len-1]!='\n' && ! feof(configFile)); //allocate enough space for this line if(! continuation) { if(str) { free(str); str=NULL; } str = malloc(sizeOfLL(strbufLL)*bufsize); str[0]='\0'; len = 0; } else { len = strlen(str); temp = str; str = malloc(sizeOfLL(strbufLL)*bufsize+len); strcpy(str, temp); if(temp) { free(temp); temp=NULL; } } //walk through the buffer linked list and copy each chunk into the string for(walker=strbufLL->head; walker!=NULL; walker=walker->next) { strcpy(str+len, (char *) walker->pl); len+=strlen((char*) walker->pl); } //get rid of trailing and leading white spaces trim(str); //if the lines wasn't a comment, then either set continuation flag //or add the line to the linked list to be returned. if(strlen(str)!=0 && str[0]!='#') { if(str[strlen(str)-1]=='\\') { continuation=1; str[strlen(str)-1]=' '; } else { continuation=0; // printf("/ %s /\n", str); addtoLL(lineLL, str); if(str) { free(str); str=NULL; } } } } while(!feof(configFile)); fclose (configFile); if(str) { free(str); str=NULL; } if(strbufLL) { freelist(strbufLL); strbufLL=NULL; } return lineLL; } groupdef* read_group_file(const char * filename) { LL* token_list=NULL; LL* file_list=NULL; LLnode* walker=NULL; char * temp; int num, size; int i; LL** group_array=NULL; char** name_array=NULL; groupdef *group_defs; //get the file in a pre-processed list file_list = parseConfigFile(filename); //useless file if(file_list==NULL) return NULL; num = sizeOfLL(file_list); name_array = calloc(num, sizeof(char*)); group_array = calloc(num, sizeof(LL*)); size=0; temp=NULL; for(walker=file_list->head; walker!=NULL; walker=walker->next) { //get the next line temp = strdup((char*) walker->pl); //break it up into tokens. token_list=get_tokens(temp, DELIMS); //free up the string if(temp) { free(temp); temp=NULL; } //get the group name temp = strdup((char *) (token_list->head->pl)); //see if the group name is already in the list for(i=0; temp!=NULL && ihead->pl))) { munge(group_array[i], token_list); free(token_list); temp=NULL; } } //if not, place it at the end. if(temp!=NULL) { //get rid of the group name from the list. remove_head(token_list); name_array[size]=temp; group_array[size]=token_list; size++; } } //cleanup freelist(file_list); //allocate memory for the group definition group_defs = malloc(sizeof(groupdef)); //put stuff into the struct group_defs->num = size; group_defs->groups = name_array; group_defs->members = group_array; return group_defs; } //reads a config file that contains group information (e.g. hostgroup, //usergroup) groupdef* read_group_config_file(const char * filename) { int all_mentioned=0; int num_of_groups = 0; LLnode * walker=NULL; char * temp=NULL; char * groupname=NULL; int i,j; LL* completelist; char ** item_list; LL ** members_list; LL *tokens; unsigned int n, oldn; groupdef* gd; //get the file in a pre-processed list LL* file_list = parseConfigFile(filename); if(file_list==NULL) return NULL; //holds the list of items completelist = new_LL(); //find all the items, generate the complete list. for(walker=file_list->head; walker!=NULL; walker=walker->next) { temp = strdup((char *) walker->pl); tokens=get_tokens(temp, DELIMS); munge(completelist, tokens); free(temp); freelist(tokens); } //make sure "ALL" is not used. all_mentioned = isInList(completelist, "ALL"); //find the number of groups we have num_of_groups = sizeOfLL(completelist); //allocate some memory to store pointers item_list = calloc(num_of_groups, sizeof(char *)); members_list = calloc(num_of_groups, sizeof(LL *)); //copy the linked list into the array. i=0; for(walker=completelist->head; walker!=NULL; walker=walker->next) { item_list[i]=strdup((char *) walker->pl); if(!strcmp(item_list[i], "ALL")) members_list[i] = completelist; else members_list[i] = new_LL(); i++; } //iterate through the lines in the file again and create the initial //membership lists for(walker=file_list->head; walker!=NULL; walker=walker->next) { LL * tokens = get_tokens((char*) walker->pl, " \t,"); groupname = strdup((char *) tokens->head->pl); //remove group names remove_head(tokens); for(i=0; ihead; walker!=NULL; walker=walker->next) { for(j=0; strcmp(item_list[j], walker->pl) && jnum = num_of_groups; gd->groups = item_list; gd->members = members_list; return gd; } ruledef * read_rule_config_file(const char* filename) { LLnode* walker; int i, num; // get the pertinent lines the config file LL* file_list = parseConfigFile(filename); LL* rule_list; ruledef* defs; if(file_list==NULL) { //file couldn't be opened. return NULL; } rule_list = new_LL(); //iterate through the lines for(walker=file_list->head; walker!=NULL; walker=walker->next) { char * temp = strdup(walker->pl); LL* tokens = get_tokens(temp, DELIMS); num = sizeOfLL(tokens); //if there are two tokens on the line, do something, else do nothing. if(num==2) { rule* newrule = malloc(sizeof(rule)); newrule->usergroup = strdup(tokens->head->pl); newrule->hostgroup = strdup(tokens->tail->pl); add_void_to_LL(rule_list, (void*) newrule); } else { //ignore } free(temp); freelist(tokens); } //make returnable struct defs = malloc(sizeof(ruledef)); defs->num = sizeOfLL(rule_list); defs->rules = calloc(defs->num, sizeof(rule*)); //copy the linked list into the array. i=0; for(walker=rule_list->head; walker!=NULL; walker=walker->next) { (defs->rules)[i]=(rule*) walker->pl; i++; } freelist(file_list); free_LL(rule_list); return defs; } //clean up a groupdef struct void free_groupdef(groupdef * gd) { int i; if(gd) { for(i=0; i<(gd->num); i++) { free((gd->groups)[i]); freelist((gd->members)[i]); } if(gd->groups) { free(gd->groups); } if(gd->members) { free(gd->members); } free(gd); } } //clean up a ruledef struct void free_ruledef(ruledef* rd) { int i; rule* R=NULL; if(rd) { for(i=0; i<(rd->num); i++) { R = (rd->rules)[i]; free(R->usergroup); free(R->hostgroup); free(R); } free(rd->rules); free(rd); } } //clean up a config void cleanup_config() { //debug code //printf("Cleaning up...\n"); free_groupdef(users_defs); free_groupdef(hosts_defs); free_ruledef(allow_defs); free_ruledef(deny_defs); users_defs=NULL; hosts_defs=NULL; allow_defs=NULL; deny_defs=NULL; } int init_access() { //initialize from files. IO errors exits. users_defs = read_group_config_file(userfile); hosts_defs = read_group_config_file(hostfile); deny_defs = read_rule_config_file(denyfile); allow_defs = read_rule_config_file(allowfile); groups = read_group_file(groupfile); //return error code? return -1; } //checks the username/host pairs against the array of rules in the //ruledef structure. Returns NULL or an applicable rule. rule* checkrules(ruledef * rd, const char * username, const char * host) { int i; int u,h; // for usergroup and hostgroup, respectively. for(i=0; i<(rd->num); i++) { rule* r = rd->rules[i]; //find the usergroup of the rule in the list for(u=0; u<(users_defs->num); u++) if(!strcmp((users_defs->groups)[u],r->usergroup)) break; //find the hostgroup of the rule in the list for(h=0; h<(hosts_defs->num); h++) if(!strcmp((hosts_defs->groups)[h],r->hostgroup)) break; //if the usergroup and hostgroup are in the lists, see if rule //applies to the username/host pair. If so, return the rule. if(u<(users_defs->num) && h<(hosts_defs->num)) { //debug code #if 0 printf("\t%s %d\t %s %d\n", r->usergroup, isInList((users_defs->members)[u],username), r->hostgroup, isInList((hosts_defs->members)[h],host )); #endif if(isInList((users_defs->members)[u],username) && isInList_case((hosts_defs->members)[h],host)) { return r; } } else { // rule don't match user/host lists. This means this is an // extraneous rule that won't ever get applied. syslog(LOG_ERR, "[CHECKRULES] Unusable rule: %s:%s\n", r->usergroup, r->hostgroup); } } return NULL; } //checks if a username/host is authorized. int check_pair(const char * username, const char * host) { rule* r; if(username==NULL || host==NULL) return DENY; // deal with deny rules. r = checkrules(deny_defs, username, host); if(r!=NULL) { debug_out("[CHECK_PAIR] DENY %s@%s: due to rule %s:%s", username, host, r->usergroup, r->hostgroup); return DENY; } // if you pass the deny phase, try the allow stuff r = checkrules(allow_defs, username, host); if(r!=NULL) { debug_out("[CHECK_PAIR] ALLOW %s@%s: due to rule %s:%s", username, host, r->usergroup, r->hostgroup); return ALLOW; } //no rules apply. debug_out("[CHECK_PAIR] UNDEFINED %s@%s: no rules apply", username, host); return UNDEFINED; } char* get_fqdn(const char* host_ip) { struct in_addr* ipAddrNum; struct hostent * hp; ipAddrNum = malloc(sizeof(struct in_addr)); inet_aton(host_ip, ipAddrNum); hp = gethostbyaddr((char*)&(ipAddrNum->s_addr), sizeof(ipAddrNum), AF_INET); if(ipAddrNum) { free(ipAddrNum); ipAddrNum = NULL; } return strdup(hp->h_name); } /* Implements a state machine for authorized logic. * * Algorithm: * * Start in state=1, check username/ipaddress combo. If DENY, go to * state 4; else go to state 2 and 3 for ALLOW and UNDEFINED * respectively. * * State 2 or 3. Find the FQDN (fully qualified domain name). * * State 2. We're here because we found an ALLOW rule (but no DENY) * for the IP address. If the check for username/fqdn combo is DENY, * go to state 4; else go to state 5. * * State 3. We're here because we found no rule applicable to the * username/ipaddress combo. If the check for username/fqdn combo is * DENY, go to state 4. If the check is ALLOW go to state 5; else go * to state 6 * * State 4. This is Hell. We've arrived here because you've been * rejected by the config files. The crazy cycle shall soon end. * * State 5. We're here because we found a ALLOW rule (but no DENY) * for this host. We still need to make sure there are no denies when * we strip the FQDN of several suffixes. If there is one, we go to * state 4. Otherwise we stay, advance the pointer and loop. * * State 6. We're here because we found no rule applicable to the * username/ipaddress or the username/FQDN combos. We still need to * make sure there are no denies when we strip the FQDN of several * suffixes. If there is one, we go to state 4. Otherwise we stay, * advance the pointer and loop * * State 7. Congratulations. You have just made it through the state * machine with at least one ALLOW and no DENY. Collect $200 from the * nearest monopoly set. The loop terminates */ int authorized(const char * username, const char * host_ip) { const int THE_END=3; int state=1; //start state int result=-10; //nonsense number int i; char * fqdn=NULL; char * suffix=NULL; static int delta[8][4]={{0,0,0,0}, //state 0 (space filler) {4,2,3,0}, //state 1 {4,5,5,0}, //state 2 {4,5,6,0}, //state 3 {0,0,0,0}, //state 4F (DENY state) {4,5,5,7}, //state 5 {4,5,6,4}, //state 6 {0,0,0,0}}; //state 7F (ALLOW state) LLnode* walker = dns_suffixes->head; do { debug_out("[AUTHORIZED] State machine status: %d",state); switch(state) { case 1: //Start state. IP check result=check_pair(username, host_ip); state=delta[state][result]; break; case 2: case 3: //second phase. FQDN check fqdn = get_fqdn(host_ip); result=check_pair(username, fqdn); state=delta[state][result]; break; case 5: case 6: //third phase. hostname check (by stripping off suffixes). if(walker==NULL) { state=delta[state][THE_END]; debug_LL(dns_suffixes); debug_out("[AUTHORIZED] We've reached the end of the suffix list"); } else { suffix = (char*) (walker->pl); debug_out("[AUTHORIZED] Attempting to strip %s from fqdn", suffix); // calculate the difference in lengths i = strlen(fqdn)-strlen(suffix); // if the name is longer than the suffix if(i>0) { if(!strcasecmp(fqdn+i,suffix)) { fqdn[i]='\0'; //truncate string result= check_pair(username, fqdn); fqdn[i]=suffix[0]; //fix string. } } state=delta[state][result]; walker=walker->next; } break; default: syslog(LOG_ERR, "[AUTHORIZED] Impossible state [%d] reached in state machine. This shouldn't happen. Submit a bug report!", state); state=4; } } while(state!=7 && state!=4); if(fqdn) { free(fqdn); fqdn=NULL; } return(state==7)?ALLOW:DENY; } void debug_groupdef(groupdef* gd) { #ifdef DEBUG int i,n=0; n = gd->num; for(i=0; igroups)[i]); debug_LL((gd->members)[i]); } #endif } void debug_ruledef(ruledef* rd) { #ifdef DEBUG int i,n=0; n = rd->num; for(i=0; irules)[i]->usergroup,(rd->rules)[i]->hostgroup); } #endif } void debug_config() { #ifdef DEBUG printf("\n\n\nUsers\n"); debug_groupdef(users_defs); printf("\n\n\nHosts\n"); debug_groupdef(hosts_defs); printf("\n\n\nAllows\n"); debug_ruledef(allow_defs); printf("\n\n\nDenies\n"); debug_ruledef(deny_defs); #endif }