From d6c554d5de98c8ced9cab13c36e492a1fcf2041e Mon Sep 17 00:00:00 2001 From: David Bartley Date: Wed, 8 Aug 2007 18:59:10 -0400 Subject: [PATCH] Optimized querying by using single query and using asynch functions Added preliminary code for detecting CS accounts Refactored variable names to use underscores instead of camel-case Prefixed helper functions with pam_csc_ Added -g to compiler flags --- Makefile | 7 +- debian/changelog | 10 ++ debian/control | 6 +- debian/docs | 1 - debian/rules | 8 +- pam_csc.c | 357 +++++++++++++++++++++++++++++------------------ 6 files changed, 241 insertions(+), 148 deletions(-) diff --git a/Makefile b/Makefile index f08c234..2db17f8 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,11 @@ CC=gcc -CFLAGS=-O2 -fPIC -Wall -LDFLAGS=-shared -lpam -lldap -LD=ld +CFLAGS=-g -O2 -fPIC -Wall +LDFLAGS=-g -shared -lpam -lldap all: pam_csc.so pam_csc.so: pam_csc.o - $(LD) -o $@ $(LDFLAGS) $< + $(CC) -o $@ $(LDFLAGS) $< clean: rm -f pam_csc.so pam_csc.o diff --git a/debian/changelog b/debian/changelog index 82eff95..1e08b6a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +libpam-csc (1.2) stable testing; urgency=low + + * Optimized querying by using single query and using asynch functions + * Added preliminary code for detecting CS accounts + * Refactored variable names to use underscores instead of camel-case + * Prefixed helper functions with pam_csc_ + * Added -g to compiler flags + + -- David Bartley Fri, 27 Jul 2007 13:17:35 -0400 + libpam-csc (1.1) unstable; urgency=low * Removed hardcoding of URI. diff --git a/debian/control b/debian/control index 8ea3148..bd5c0c5 100644 --- a/debian/control +++ b/debian/control @@ -2,11 +2,11 @@ Source: libpam-csc Section: net Priority: optional Maintainer: David Bartley -Build-Depends: debhelper (>= 4.0.0), libldap-dev, libpam0g-dev +Build-Depends: debhelper (>= 4.0.0), libldap2-dev, libpam0g-dev, libsasl2-dev Standards-Version: 3.7.2 Package: libpam-csc Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} -Description: Custom CSC PAM module to handle account expiration. - Custom CSC PAM module to handle account expiration. +Description: CSC PAM module to handle account expiration. + CSC PAM module to handle account expiration. diff --git a/debian/docs b/debian/docs index 1333ed7..e69de29 100644 --- a/debian/docs +++ b/debian/docs @@ -1 +0,0 @@ -TODO diff --git a/debian/rules b/debian/rules index 0d37306..40542fc 100755 --- a/debian/rules +++ b/debian/rules @@ -33,9 +33,7 @@ build: build-stamp build-stamp: configure-stamp dh_testdir - # Add here commands to compile the package. $(MAKE) - #docbook-to-man debian/libpam-csc.sgml > libpam-csc.1 touch $@ @@ -44,8 +42,7 @@ clean: dh_testroot rm -f build-stamp configure-stamp - # Add here commands to clean up after the build process. - -$(MAKE) clean + $(MAKE) clean dh_clean @@ -56,9 +53,6 @@ install: build-stamp dh_installdirs install -m 644 pam_csc.so $(CURDIR)/debian/libpam-csc/lib/security - # Add here commands to install the package into debian/libpam-csc. -# $(MAKE) DESTDIR=$(CURDIR)/debian/libpam-csc install - # Build architecture-independent files here. binary-indep: build install diff --git a/pam_csc.c b/pam_csc.c index d0c026e..c7274dc 100644 --- a/pam_csc.c +++ b/pam_csc.c @@ -5,24 +5,54 @@ #include #include #include +#include #include #include #include #include +#include #include #include -#define PAM_CSC_LDAP_USER_BASE_DN "ou=People,dc=csclub,dc=uwaterloo,dc=ca" -#define PAM_CSC_LDAP_GROUP_BASE_DN "ou=Group,dc=csclub,dc=uwaterloo,dc=ca" -#define PAM_CSC_LDAP_TIMEOUT 5 -#define PAM_CSC_ALLOWED_GROUPS "cn=staff" -#define PAM_CSC_MINIMUM_UID 1000 +/* TODO + * + * We need to create a cronjob (that runs on logon and every hour) to run + * 'kinit -p TODO@STUDENT.CS.UWATERLOO.CA -k -c /tmp/krb5cc_pam_csc' + * + * We also need to add a keytab entry for TODO@STUDENT.CS.UWATERLOO.CA + * + */ + +#define PAM_CSC_CSC_BASE_DN "ou=People,dc=csclub,dc=uwaterloo,dc=ca" +#define PAM_CSC_CSCF_URI \ + "ldaps://eponina.student.cs.uwaterloo.ca" \ + "ldaps://canadenis.student.cs.uwaterloo.ca" +#define PAM_CSC_CSCF_BASE_DN "dc=student,dc=cs,dc=uwateloo,dc=ca" +#define PAM_CSC_CSCF_BIND_DN \ + "uid=TODO,dc=student,dc=cs,dc=uwaterloo,dc=ca" +#define PAM_CSC_KRB5CCNAME "/tmp/krb5cc_pam_csc" +#define PAM_CSC_CSCF_SASL_USER \ + "dn:uid=TODO,cn=STUDENT.CS.UWATERLOO.CA,cn=GSSAPI,cn=auth" +#define PAM_CSC_CSCF_SASL_REALM "STUDENT.CS.UWATERLOO.CA" +#define PAM_CSC_LDAP_TIMEOUT 5 +#define PAM_CSC_MINIMUM_UID 1000 #define PAM_CSC_EXPIRED_MSG \ "*****************************************************************************\n" \ "* *\n" \ "* Your account has expired - please contact the Computer Science Club *\n" \ "* *\n" \ "*****************************************************************************\n" +#define PAM_CSC_CSCF_DISALLOWED_MSG \ + "You are not registered as a CS student - login denied." + +#define PAM_CSC_SYSLOG_EXPIRED_WARNING \ + "(pam_csc): %s was not registered for current term or previous term - denying login\n" +#define PAM_CSC_SYSLOG_EXPIRED_ERROR \ + "(pam_csc): %s was not registered for current term but was registered for previous term - permitting login\n" +#define PAM_CSC_SYSLOG_CSCF_DISALLOWED \ + "(pam_csc): %s is using a CSCF machine but is not enrolled in CS - denying login\n" +#define PAM_CSC_SYSLOG_SASL_UNRECOGNIZED_CALLBACK \ + "(pam_csc): %ld is not a recognized SASL callback option\n" /* * User terms are defined as (3 * year + term) where term is: @@ -30,14 +60,6 @@ * Term is a string in the form [f|w|s][year] */ -enum check_user_type_t -{ - check_user_exists, - check_user_cur_term, - check_user_prev_term, - check_user_groups -}; - #define HANDLE_WARN \ { \ syslog(LOG_AUTHPRIV | LOG_WARNING, "pam_csc generated a warning on line %d of %s\n", __LINE__, __FILE__); \ @@ -57,124 +79,78 @@ enum check_user_type_t #define WARN_LDAP(x) \ if( (x) != LDAP_SUCCESS ) HANDLE_WARN -char* escape_ldap_string(const char* src) +struct pam_csc_sasl_interact_param { - char *dst, *dstPtr; + const char* realm; + const char* user; +}; +typedef struct pam_csc_sasl_interact_param pam_csc_sasl_interact_param_t; + +int pam_csc_sasl_interact(LDAP* ld, unsigned flags, void* def, void* inter) +{ + pam_csc_sasl_interact_param_t* param = (pam_csc_sasl_interact_param_t*)def; + sasl_interact_t* interact = (sasl_interact_t*)interact; + while(interact->id != SASL_CB_LIST_END) + { + switch(interact->id) + { + case SASL_CB_GETREALM: + interact->result = param->realm; + interact->len = strlen(param->realm); + case SASL_CB_USER: + interact->result = param->user; + interact->len = strlen(param->user); + break; + default: + syslog(LOG_AUTHPRIV | LOG_NOTICE, + PAM_CSC_SYSLOG_SASL_UNRECOGNIZED_CALLBACK, interact->id); + interact->result = ""; + interact->len = 0; + } + } + + return LDAP_SUCCESS; +} + +char* pam_csc_escape_ldap_string(const char* src) +{ + char *dst, *dst_ptr; int i; if(!(dst = malloc(2 * strlen(src) + 1))) return NULL; - dstPtr = dst; + dst_ptr = dst; for(i = 0; i < strlen(src); i++) { if(src[i] == '*' || src[i] == '(' || src[i] == ')' || src[i] == '\\') { - dstPtr[0] = '\\'; - dstPtr++; + dst_ptr[0] = '\\'; + dst_ptr++; } - dstPtr[0] = src[i]; - dstPtr++; + dst_ptr[0] = src[i]; + dst_ptr++; } - dstPtr[0] = '\0'; + dst_ptr[0] = '\0'; return dst; } -int check_user(const char* username, enum check_user_type_t checkType) +int pam_csc_print_message(pam_handle_t* pamh, char* msg, int style) { int retval = PAM_SUCCESS; - time_t curTime; - struct tm* localTime; - int longTerm, year, term; - LDAP* ld = NULL; - static const char termChars[] = {'w', 's', 'f'}; - char* usernameEscaped = NULL; - char* filter = NULL; - char* attr[] = {"objectClass", NULL}; - struct timeval timeout = {PAM_CSC_LDAP_TIMEOUT, 0}; - LDAPMessage* res = NULL; - char* baseDN = NULL; - - /* fail-safe for root */ - if(strcmp(username, "root") == 0) - { - return PAM_SUCCESS; - } - - /* connect and bind */ - WARN_LDAP( ldap_create(&ld) ) - WARN_NEG1( ldap_simple_bind(ld, NULL, NULL) ) - - WARN_ZERO( usernameEscaped = escape_ldap_string(username) ); - switch(checkType) - { - case check_user_exists: - - /* format filter */ - WARN_ZERO( filter = malloc(50 + strlen(usernameEscaped)) ) - sprintf(filter, "(uid=%s)", usernameEscaped); - baseDN = PAM_CSC_LDAP_USER_BASE_DN; - break; - - case check_user_prev_term: - case check_user_cur_term: - - /* get term info and compute current and previous term */ - WARN_NEG1( curTime = time(NULL) ) - WARN_ZERO( localTime = localtime(&curTime) ) - longTerm = 3 * (1900 + localTime->tm_year) + (localTime->tm_mon / 4); - if(checkType == check_user_prev_term) - longTerm--; - term = termChars[longTerm % 3]; - year = longTerm / 3; - - /* format filter */ - WARN_ZERO( filter = malloc(100 + strlen(usernameEscaped)) ) - sprintf(filter, "(&(uid=%s)(|(&(objectClass=member)(term=%c%d))(!(objectClass=member))))", - usernameEscaped, term, year); - baseDN = PAM_CSC_LDAP_USER_BASE_DN; - break; - - case check_user_groups: - - /* format filter */ - WARN_ZERO( filter = malloc(50 + strlen(PAM_CSC_ALLOWED_GROUPS) + strlen(usernameEscaped)) ) - sprintf(filter, "(&(objectClass=posixGroup)(%s)(memberUid=%s))", PAM_CSC_ALLOWED_GROUPS, usernameEscaped); - baseDN = PAM_CSC_LDAP_GROUP_BASE_DN; - break; - } - - /* search */ - WARN_LDAP( ldap_search_st(ld, baseDN, LDAP_SCOPE_SUBTREE, filter, attr, 1, &timeout, &res) ) - if((term = ldap_count_entries(ld, res)) == 0) - retval = PAM_AUTH_ERR; - -cleanup: - - if(usernameEscaped) free(usernameEscaped); - if(res) ldap_msgfree(res); - if(filter) free(filter); - if(ld) ldap_unbind(ld); - - return retval; -} - -int print_pam_message(pam_handle_t* pamh, char* msg, int style) -{ - int retval = PAM_SUCCESS; - struct pam_conv* pamConv; - struct pam_message pamMessage; - struct pam_message* pamMessages[1]; - struct pam_response* pamResponse; + struct pam_conv* conv; + struct pam_message message; + struct pam_message* messages[1]; + struct pam_response* response; /* output message */ - WARN_PAM( pam_get_item(pamh, PAM_CONV, (const void**)&pamConv) ) - pamMessages[0] = &pamMessage; - pamMessage.msg_style = style; - pamMessage.msg = msg; - WARN_PAM( pamConv->conv(1, (const struct pam_message**)pamMessages, - &pamResponse, pamConv->appdata_ptr) ) + WARN_PAM( pam_get_item(pamh, PAM_CONV, (void**)&conv) ) + messages[0] = &message; + message.msg_style = style; + message.msg = msg; + WARN_PAM( conv->conv(1, (const struct pam_message**)messages, + &response, conv->appdata_ptr) ) cleanup: @@ -183,8 +159,27 @@ cleanup: PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t* pamh, int flags, int argc, const char* argv[]) { + int retval = PAM_SUCCESS; const char* username; struct passwd* pwd; + time_t cur_time; + struct tm* local_time; + int long_term; + static const char term_chars[] = {'w', 's', 'f'}; + char cur_term[6], prev_term[6]; + LDAP *ld_csc = NULL, *ld_cscf = NULL; + bool cscf; + char* username_escaped = NULL; + char *filter_csc = NULL, *filter_cscf = NULL; + char *attrs_csc[] = {"objectClass", "term", NULL}, + *attrs_cscf[] = {"objectClass", NULL}; + bool expired; + const char* pam_rhost; + int msg_csc, msg_cscf; + LDAPMessage *res_csc = NULL, *res_cscf = NULL; + struct timeval timeout = {PAM_CSC_LDAP_TIMEOUT, 0}; + LDAPMessage* entry = NULL; + char **values = NULL, **values_iter = NULL; /* determine username */ if((pam_get_user(pamh, &username, NULL) != PAM_SUCCESS) || !username) @@ -199,39 +194,135 @@ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t* pamh, int flags, int argc, const c return PAM_SUCCESS; } - /* check if user exists in ldap */ - if(check_user(username, check_user_exists) == PAM_AUTH_ERR) + /* escape username */ + WARN_ZERO( username_escaped = pam_csc_escape_ldap_string(username) ); + + /* get term info and compute current and previous term */ + WARN_NEG1( cur_time = time(NULL) ) + WARN_ZERO( local_time = localtime(&cur_time) ) + long_term = 3 * (1900 + local_time->tm_year) + (local_time->tm_mon / 4); + sprintf(cur_term, "%c%d", term_chars[long_term % 3], long_term / 3); + long_term--; + sprintf(prev_term, "%c%d", term_chars[long_term % 3], long_term / 3); + + /* connect to CSC */ + WARN_LDAP( ldap_create(&ld_csc) ) + WARN_NEG1( ldap_simple_bind(ld_csc, NULL, NULL) ) + + /* check if we are logging in from a CSCF teaching thin client */ + cscf = false; + if(pam_get_item(pamh, PAM_RHOST, (void**)&pam_rhost) && pam_rhost) { - return PAM_SUCCESS; + /* TODO: check pam_rhost + * It appears that the thin clients all have hostnames of the form + * tc[0-9]+\.cs + */ } - /* check if user is registered for the current term */ - if(check_user(username, check_user_cur_term) == PAM_SUCCESS) + if(cscf) { - return PAM_SUCCESS; + /* set krb5 cache location */ + setenv("KRB5CCNAME", PAM_CSC_KRB5CCNAME, 1); + + /* connect to CSCF */ + pam_csc_sasl_interact_param_t interact_param = { + PAM_CSC_CSCF_SASL_REALM, + PAM_CSC_CSCF_SASL_USER, + }; + WARN_LDAP( ldap_initialize(&ld_cscf, PAM_CSC_CSCF_URI) ) + WARN_NEG1( ldap_sasl_interactive_bind_s(ld_cscf, PAM_CSC_CSCF_BIND_DN, + "GSSAPI", NULL, NULL, LDAP_SASL_INTERACTIVE | LDAP_SASL_QUIET, + pam_csc_sasl_interact, &interact_param) ) } - /* check if user is registered for the previous term */ - if(check_user(username, check_user_prev_term) == PAM_SUCCESS) + /* create CSC request string */ + WARN_ZERO( filter_csc = malloc(100 + strlen(username_escaped)) ) + sprintf(filter_csc, "(&(uid=%s)(|(&(objectClass=member)(|(term=%s)(term=%s)))(!(objectClass=member))))", username_escaped, cur_term, prev_term); + + /* issue CSC request */ + WARN_NEG1( msg_csc = ldap_search(ld_csc, PAM_CSC_CSC_BASE_DN, + LDAP_SCOPE_SUBTREE, filter_csc, attrs_csc, 0) ) + + if(cscf) { - /* show warning */ - syslog(LOG_AUTHPRIV | LOG_NOTICE, "(pam_csc): %s was not registered for current term but was registered for previous term - permitting login\n", username); - print_pam_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_TEXT_INFO); - return PAM_SUCCESS; + /* create CSCF request string */ + WARN_ZERO( filter_cscf = malloc(100 + strlen(username_escaped)) ) + sprintf(filter_csc, "TODO %s", username_escaped); + + /* issue CSCF request */ + WARN_NEG1( msg_cscf = ldap_search(ld_cscf, PAM_CSC_CSCF_BASE_DN, + LDAP_SCOPE_SUBTREE, filter_cscf, attrs_cscf, 1) ) } - /* check if user is in allowed groups */ - if(check_user(username, check_user_groups) == PAM_SUCCESS) + /* wait for CSC response */ + WARN_NEG1( ldap_result(ld_csc, msg_csc, 1, &timeout, &res_csc) ) + + /* check if we received an entry from CSC */ + if(ldap_count_entries(ld_csc, res_csc) == 0) { - /* show warning */ - print_pam_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_TEXT_INFO); - syslog(LOG_AUTHPRIV | LOG_NOTICE, "(pam_csc): %s was not registered but was in allowed groups - permitting login\n", username); - return PAM_SUCCESS; + /* show notice and disallow login */ + pam_csc_print_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_ERROR_MSG); + syslog(LOG_AUTHPRIV | LOG_NOTICE, PAM_CSC_SYSLOG_EXPIRED_WARNING, + username); + retval = PAM_AUTH_ERR; + goto cleanup; } - /* account has expired - show prompt */ - print_pam_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_ERROR_MSG); - syslog(LOG_AUTHPRIV | LOG_NOTICE, "(pam_csc): %s was not registered and was not in allowed groups - denying login\n", username); + /* get CSC entry */ + WARN_ZERO( entry = ldap_first_entry(ld_csc, res_csc) ) + WARN_ZERO( values = ldap_get_values(ld_csc, entry, "term") ) - return PAM_AUTH_ERR; + /* iterate through term attributes */ + expired = true; + values_iter = values; + while(*values_iter) + { + if(strcmp(*values_iter, cur_term) == 0) + { + /* user is registered in current term */ + expired = false; + break; + } + values_iter++; + } + + /* check if account is expired */ + if(expired) + { + /* show notice and continue */ + pam_csc_print_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_TEXT_INFO); + syslog(LOG_AUTHPRIV | LOG_NOTICE, PAM_CSC_SYSLOG_EXPIRED_ERROR, + username); + } + + if(cscf) + { + /* wait for CSCF response */ + WARN_NEG1( ldap_result(ld_cscf, msg_cscf, 1, &timeout, &res_cscf) ) + + /* check if we got an entry back from CSCF */ + if(ldap_count_entries(ld_cscf, res_cscf) == 0) + { + /* output CSCF disallowed message */ + pam_csc_print_message(pamh, PAM_CSC_CSCF_DISALLOWED_MSG, + PAM_ERROR_MSG); + syslog(LOG_AUTHPRIV | LOG_NOTICE, PAM_CSC_SYSLOG_CSCF_DISALLOWED, + username); + retval = PAM_AUTH_ERR; + goto cleanup; + } + } + +cleanup: + + if(values) ldap_value_free(values); + if(res_csc) ldap_msgfree(res_csc); + if(res_cscf) ldap_msgfree(res_cscf); + if(ld_csc) ldap_unbind(ld_csc); + if(ld_cscf) ldap_unbind(ld_cscf); + if(filter_csc) free(filter_csc); + if(filter_cscf) free(filter_cscf); + if(username_escaped) free(username_escaped); + + return retval; }