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
This commit is contained in:
David Bartley 2007-08-08 18:59:10 -04:00
parent 015bb49c08
commit d6c554d5de
6 changed files with 241 additions and 148 deletions

View File

@ -1,12 +1,11 @@
CC=gcc CC=gcc
CFLAGS=-O2 -fPIC -Wall CFLAGS=-g -O2 -fPIC -Wall
LDFLAGS=-shared -lpam -lldap LDFLAGS=-g -shared -lpam -lldap
LD=ld
all: pam_csc.so all: pam_csc.so
pam_csc.so: pam_csc.o pam_csc.so: pam_csc.o
$(LD) -o $@ $(LDFLAGS) $< $(CC) -o $@ $(LDFLAGS) $<
clean: clean:
rm -f pam_csc.so pam_csc.o rm -f pam_csc.so pam_csc.o

10
debian/changelog vendored
View File

@ -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 <dtbartle@csclub.uwaterloo.ca> Fri, 27 Jul 2007 13:17:35 -0400
libpam-csc (1.1) unstable; urgency=low libpam-csc (1.1) unstable; urgency=low
* Removed hardcoding of URI. * Removed hardcoding of URI.

6
debian/control vendored
View File

@ -2,11 +2,11 @@ Source: libpam-csc
Section: net Section: net
Priority: optional Priority: optional
Maintainer: David Bartley <dtbartle@csclub.uwaterloo.ca> Maintainer: David Bartley <dtbartle@csclub.uwaterloo.ca>
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 Standards-Version: 3.7.2
Package: libpam-csc Package: libpam-csc
Architecture: any Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends} Depends: ${shlibs:Depends}, ${misc:Depends}
Description: Custom CSC PAM module to handle account expiration. Description: CSC PAM module to handle account expiration.
Custom CSC PAM module to handle account expiration. CSC PAM module to handle account expiration.

1
debian/docs vendored
View File

@ -1 +0,0 @@
TODO

8
debian/rules vendored
View File

@ -33,9 +33,7 @@ build: build-stamp
build-stamp: configure-stamp build-stamp: configure-stamp
dh_testdir dh_testdir
# Add here commands to compile the package.
$(MAKE) $(MAKE)
#docbook-to-man debian/libpam-csc.sgml > libpam-csc.1
touch $@ touch $@
@ -44,8 +42,7 @@ clean:
dh_testroot dh_testroot
rm -f build-stamp configure-stamp rm -f build-stamp configure-stamp
# Add here commands to clean up after the build process. $(MAKE) clean
-$(MAKE) clean
dh_clean dh_clean
@ -56,9 +53,6 @@ install: build-stamp
dh_installdirs dh_installdirs
install -m 644 pam_csc.so $(CURDIR)/debian/libpam-csc/lib/security 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. # Build architecture-independent files here.
binary-indep: build install binary-indep: build install

357
pam_csc.c
View File

@ -5,24 +5,54 @@
#include <time.h> #include <time.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h>
#include <string.h> #include <string.h>
#include <security/pam_modules.h> #include <security/pam_modules.h>
#include <security/pam_appl.h> #include <security/pam_appl.h>
#include <ldap.h> #include <ldap.h>
#include <sasl/sasl.h>
#include <syslog.h> #include <syslog.h>
#include <pwd.h> #include <pwd.h>
#define PAM_CSC_LDAP_USER_BASE_DN "ou=People,dc=csclub,dc=uwaterloo,dc=ca" /* TODO
#define PAM_CSC_LDAP_GROUP_BASE_DN "ou=Group,dc=csclub,dc=uwaterloo,dc=ca" *
#define PAM_CSC_LDAP_TIMEOUT 5 * We need to create a cronjob (that runs on logon and every hour) to run
#define PAM_CSC_ALLOWED_GROUPS "cn=staff" * 'kinit -p TODO@STUDENT.CS.UWATERLOO.CA -k -c /tmp/krb5cc_pam_csc'
#define PAM_CSC_MINIMUM_UID 1000 *
* 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 \ #define PAM_CSC_EXPIRED_MSG \
"*****************************************************************************\n" \ "*****************************************************************************\n" \
"* *\n" \ "* *\n" \
"* Your account has expired - please contact the Computer Science Club *\n" \ "* Your account has expired - please contact the Computer Science Club *\n" \
"* *\n" \ "* *\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: * 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] * 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 \ #define HANDLE_WARN \
{ \ { \
syslog(LOG_AUTHPRIV | LOG_WARNING, "pam_csc generated a warning on line %d of %s\n", __LINE__, __FILE__); \ 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) \ #define WARN_LDAP(x) \
if( (x) != LDAP_SUCCESS ) HANDLE_WARN 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; int i;
if(!(dst = malloc(2 * strlen(src) + 1))) if(!(dst = malloc(2 * strlen(src) + 1)))
return NULL; return NULL;
dstPtr = dst; dst_ptr = dst;
for(i = 0; i < strlen(src); i++) for(i = 0; i < strlen(src); i++)
{ {
if(src[i] == '*' || src[i] == '(' || src[i] == ')' || src[i] == '\\') if(src[i] == '*' || src[i] == '(' || src[i] == ')' || src[i] == '\\')
{ {
dstPtr[0] = '\\'; dst_ptr[0] = '\\';
dstPtr++; dst_ptr++;
} }
dstPtr[0] = src[i]; dst_ptr[0] = src[i];
dstPtr++; dst_ptr++;
} }
dstPtr[0] = '\0'; dst_ptr[0] = '\0';
return dst; 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; int retval = PAM_SUCCESS;
time_t curTime; struct pam_conv* conv;
struct tm* localTime; struct pam_message message;
int longTerm, year, term; struct pam_message* messages[1];
LDAP* ld = NULL; struct pam_response* response;
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;
/* output message */ /* output message */
WARN_PAM( pam_get_item(pamh, PAM_CONV, (const void**)&pamConv) ) WARN_PAM( pam_get_item(pamh, PAM_CONV, (void**)&conv) )
pamMessages[0] = &pamMessage; messages[0] = &message;
pamMessage.msg_style = style; message.msg_style = style;
pamMessage.msg = msg; message.msg = msg;
WARN_PAM( pamConv->conv(1, (const struct pam_message**)pamMessages, WARN_PAM( conv->conv(1, (const struct pam_message**)messages,
&pamResponse, pamConv->appdata_ptr) ) &response, conv->appdata_ptr) )
cleanup: cleanup:
@ -183,8 +159,27 @@ cleanup:
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t* pamh, int flags, int argc, const char* argv[]) 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; const char* username;
struct passwd* pwd; 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 */ /* determine username */
if((pam_get_user(pamh, &username, NULL) != PAM_SUCCESS) || !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; return PAM_SUCCESS;
} }
/* check if user exists in ldap */ /* escape username */
if(check_user(username, check_user_exists) == PAM_AUTH_ERR) 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(cscf)
if(check_user(username, check_user_cur_term) == PAM_SUCCESS)
{ {
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 */ /* create CSC request string */
if(check_user(username, check_user_prev_term) == PAM_SUCCESS) 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 */ /* create CSCF request string */
syslog(LOG_AUTHPRIV | LOG_NOTICE, "(pam_csc): %s was not registered for current term but was registered for previous term - permitting login\n", username); WARN_ZERO( filter_cscf = malloc(100 + strlen(username_escaped)) )
print_pam_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_TEXT_INFO); sprintf(filter_csc, "TODO %s", username_escaped);
return PAM_SUCCESS;
/* 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 */ /* wait for CSC response */
if(check_user(username, check_user_groups) == PAM_SUCCESS) 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 */ /* show notice and disallow login */
print_pam_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_TEXT_INFO); pam_csc_print_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_ERROR_MSG);
syslog(LOG_AUTHPRIV | LOG_NOTICE, "(pam_csc): %s was not registered but was in allowed groups - permitting login\n", username); syslog(LOG_AUTHPRIV | LOG_NOTICE, PAM_CSC_SYSLOG_EXPIRED_WARNING,
return PAM_SUCCESS; username);
retval = PAM_AUTH_ERR;
goto cleanup;
} }
/* account has expired - show prompt */ /* get CSC entry */
print_pam_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_ERROR_MSG); WARN_ZERO( entry = ldap_first_entry(ld_csc, res_csc) )
syslog(LOG_AUTHPRIV | LOG_NOTICE, "(pam_csc): %s was not registered and was not in allowed groups - denying login\n", username); 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;
} }