/* pGina PAM Server - A PAM-Aware Unix Daemon for pGina Copyright (C) 2003 Nathan Yocom 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 Web: http://pgina.xpasystems.com Snail Mail: Nathan Yocom 9 Evergreen Farms Rd. Scarborough, ME 04074 Phone: 207-450-4948 */ /* $Log: actions.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.19 2003/06/11 16:57:43 nyocom Added PAM_AUTHTOK_EXPIRED and PAM_NEW_AUTHTOK_REQD notification Revision 1.18 2003/06/10 20:34:51 nyocom Added passing of hostname to pam Added check for expired authtok Revision 1.17 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.16 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.15 2003/05/01 03:47:49 xpasys fixed protocol problem with verify Revision 1.14 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.13 2003/04/29 17:00:12 xpasys Added #ifndef to eliminate warnings in Solaris Revision 1.12 2003/04/29 16:28:05 xpasys Added Log statement to new authasst code Added debug_out call to authorized() Revision 1.11 2003/04/29 16:11:46 xpasys added authasst code changed argument parsing move some of main() to parent_loop() added prefork num option initial commit of authasst.c authasst.h linkedlist.c linkedlist.h. initial commit of authasst.conf files initial commit of Jiho's client test code. Revision 1.10 2003/04/28 18:50:38 xpasys Drastic changes in move to fork model Prepared for adding auth_asst code Memory management enhancements Revision 1.9 2003/04/17 18:31:25 xpasys Added check for blank username Revision 1.8 2003/04/17 03:58:59 xpasys Memory audit completed, found 1 leak - patched. Revision 1.7 2003/04/16 05:44:50 xpasys Added solaris compile support (little) Revision 1.6 2003/04/16 05:38:56 xpasys Added -pthread and -D_REENTRANT to CFLAGS Added debug_out code (enabled with ./configure --enable-debug) Fixed potential memory issues with call to do_authenticate Better malloc/free handling Actually responds with an error to a bad password change request now Revision 1.5 2003/04/11 04:50:26 xpasys Refitted mutexing to allow for better performance, possible solve deadlock issues Added debug trace info Revision 1.4 2003/04/07 17:18:26 xpasys Changed line endings to Unix Added code to be a bit friendlier on bad arguments (thanks to Jiho Kim) Revision 1.3 2003/04/04 07:58:00 xpasys Made read_string() non_blocking, times out after some time Added -l for login service name Added -a for passwd service name Added -r for number of retries Added -s for sleep time (usecs) General security and stability improvements Revision 1.2 2003/04/03 04:42:45 xpasys Added better memory handling Added pthread_exit() instead of null returns Removed egads dependancy Revision 1.1.1.1 2003/03/22 06:02:20 xpasys Imported sources */ #include "pgina_pam_server.h" #include "actions.h" #include "access.h" #include #include #include #include #include /* JK: This function doesn't write to the file descriptor at all, just * returns if the user is verified on the fd inet connection. Returns * ALLOW if okay; returns DENY otherwise. * * The code for resolving the name should probably be in its own * function. */ int verify(const int fd, const char* user) { struct sockaddr_in addr; int sizeof_addr = sizeof(addr); char *ipAddrStr = NULL; int verified=DENY; //short out if authasst is turned off. if(! with_authasst) { //allow everyone from everywhere. return ALLOW; } //resolve the host name from the connection if(getpeername(fd, (struct sockaddr *) &addr, &sizeof_addr)==0) { ipAddrStr = strdup(inet_ntoa(addr.sin_addr)); debug_out("[VERIFY] Connection from %s", ipAddrStr); } else { debug_out("[VERIFY] Can't resolve name!"); return DENY; } verified = authorized(user, ipAddrStr); debug_out("[VERIFY] %s@%s %s", user, ipAddrStr, verified?"OK":"DENIED"); if(ipAddrStr) { free(ipAddrStr); ipAddrStr = NULL; } return verified; } /* JK: This function actually writes to the SSL connection, depending * on the result from the verify call. * */ void do_verify(SSL *mySSL) { char *user = NULL; //get the username; user = read_string(mySSL); //send SUCCESS or ERROR depending on verify. send_string(mySSL, (verify(SSL_get_fd(mySSL), user)==ALLOW)?SUCCESS:ERROR); //free up string. if(user) { free(user); user = NULL; } } /* Require an auth first! Otherwise any client could change passwords! :( */ void do_changepassword(SSL *mySSL) { struct auth_struct buffer; char *username; #ifndef SOLARIS pam_handle_t *pamh=NULL; int ret = 0, changed = 0; static struct pam_conv mychangeconv = { change_conv, NULL }; #endif debug_out("In do_changepassword"); // Added to try and reduce memory errors buffer.username = NULL; buffer.password = NULL; username = NULL; #ifdef SOLARIS // Solaris change password conv bombs?!? debug_out("Changing passwords in Solaris is not allowed, sending %s",ERROR); send_string(mySSL,ERROR); debug_out("Sent"); #else username = (char *)malloc(MAX_USERNAME_LENGTH); debug_out("Calling do_authentication to verify current password"); // We must first ensure they have the old password correct by auth if(do_authentication(mySSL,1,username)) { debug_out("Authentication succeeded for username: %s, attempting password change",username); buffer.username = username; buffer.password = read_string(mySSL); mychangeconv.appdata_ptr = &buffer; // If we are in solaris, we need to first 'expire' the users password? // new_pass = read_string(mySSL); // Get the new password // Init PAM and give it a shot if((ret = pam_start(password_service, NULL, &mychangeconv, &pamh)) == PAM_SUCCESS) if((ret = pam_chauthtok(pamh, PAM_SILENT)) == PAM_SUCCESS) changed = 1; pam_end(pamh,ret); if(changed) send_string(mySSL,SUCCESS); else send_string(mySSL,ERROR); debug_out("Call to pam_chauthtok returned: %d",changed); if(buffer.password) { free(buffer.password); buffer.password = NULL; } if(username) { free(username); username = NULL; buffer.username = NULL; } // buffer.username is just another reference to username } else { debug_out("Authentication failed for username: %s, password change failing",username); if(username) { free(username); username = NULL; } send_string(mySSL,ERROR); } #endif debug_out("Leaving do_changepassword"); } int do_authentication(SSL *mySSL,int for_pass_change, char *username) { struct auth_struct buffer; char *user; char *pass; struct sockaddr_in addr; int sizeof_addr = 0; int clientFd = 0; struct hostent *h; static struct pam_conv myauthconv = { auth_conv, NULL }; pam_handle_t *pamh=NULL; int ret = 0, expired = 0, authenticated = 0; char hostname[2048]={'\0'}; debug_out("In do_authentication, for_pass_change = %d",for_pass_change); // Get the clients IP address clientFd = SSL_get_fd(mySSL); sizeof_addr = sizeof(addr); getpeername(clientFd, (struct sockaddr *) &addr, &sizeof_addr); h=gethostbyaddr((char*)(&(addr.sin_addr.s_addr)),4,AF_INET); if(h != NULL) strcpy(hostname,h->h_name); // Added to avoid potential memory crap user = NULL; pass = NULL; buffer.username = NULL; buffer.password = NULL; user = read_string(mySSL); pass = read_string(mySSL); buffer.username = user; buffer.password = pass; myauthconv.appdata_ptr = &buffer; if(pass) debug_out("Attempting to authenticate username: %s password is not null",buffer.username); else debug_out("Attempting to authenticate username: %s (password is NULL)",buffer.username); //JK: added verify as a necessary condition for //authentication. if(user && pass && strlen(user) > 0 && verify(SSL_get_fd(mySSL), user)==ALLOW) { //JK: unnested "simpler" code authenticated = (ret = pam_start(login_service, NULL, &myauthconv, &pamh)) == PAM_SUCCESS && (ret = pam_authenticate(pamh, 0)) == PAM_SUCCESS && (ret = pam_set_item(pamh, PAM_RHOST, hostname)) == PAM_SUCCESS && (expired = pam_acct_mgmt(pamh, 0)) == PAM_SUCCESS; /* if( (ret = pam_start(login_service, NULL, &myauthconv, &pamh)) == PAM_SUCCESS ) if( (ret = pam_authenticate(pamh, 0)) == PAM_SUCCESS ) if( (ret = pam_acct_mgmt(pamh, 0)) == PAM_SUCCESS ) authenticated = 1; */ pam_end(pamh,ret); } else debug_out("User %s attempted to login from invalid host, or provided invalid information.",user); debug_out("expired=%d",expired); if(expired == PAM_AUTHTOK_EXPIRED || expired == PAM_NEW_AUTHTOK_REQD) { authenticated = 0; expired = 1; debug_out("This accounts password has expired"); } else expired = 0; if(authenticated) send_string(mySSL,SUCCESS); else { if(expired) send_string(mySSL,EXPIRED); else send_string(mySSL,ERROR); debug_out("expired(%d),authenticated(%d)",expired,authenticated); } debug_out("Call to pam_authenticate returned: %d",authenticated); if(for_pass_change && user) { strncpy(username,user,MAX_USERNAME_LENGTH); debug_out("Copying username for password change: %s = %s",buffer.username,username); } if(user) { free(user); buffer.username = NULL; user = NULL; } if(pass) { free(pass); buffer.password = NULL; pass = NULL; } if(expired) { debug_out("Leaving do_authenticate with return: 1"); return 1; } else { debug_out("Leaving do_authenticate with return: %d",authenticated); return authenticated; } } void do_get_group_authasst(SSL *mySSL) { char *username = NULL; char *group_name = NULL; int i; debug_out("In do_get_group_authasst"); username = read_string(mySSL); // Get the usernam if(username && groups) { //find a group that contains the username for(i=0; inum && group_name==NULL; i++) if(isInList((groups->members)[i], username)) group_name=(groups->groups)[i]; } if(group_name) { send_string(mySSL, group_name); debug_out("User '%s' is in group '%s'.", username, group_name); } else { send_string(mySSL,ERROR); debug_out("User info for %s was not to be found.", username); } debug_out("Leaving do_get_group_authasst"); } void do_get_group(SSL *mySSL) { char *username = NULL; struct passwd *users_info = NULL; struct group *group_info = NULL; char *group_name = NULL; debug_out("In do_get_group"); username = read_string(mySSL); // Get the username if(username) users_info = getpwnam(username); debug_out("Checking for info on: %s",username); if(users_info) { group_info = getgrgid(users_info->pw_gid); if(group_info) { group_name = group_info->gr_name; send_string(mySSL,group_name); debug_out("Sent client group=%s",group_name); } else { send_string(mySSL,ERROR); debug_out("Group info was not to be found"); } } else { send_string(mySSL,ERROR); debug_out("User info was not to be found"); } if(username) { free(username); username = NULL; } debug_out("Leaving do_get_group"); }