From 0e727788cb90f386b9b1ba3416fc9b02b5470b68 Mon Sep 17 00:00:00 2001 From: David Bartley Date: Fri, 6 Jul 2007 01:36:29 -0400 Subject: [PATCH] Initial checkin. --- Makefile | 15 +++ debian/README | 6 ++ debian/README.Debian | 6 ++ debian/changelog | 5 + debian/compat | 1 + debian/control | 12 +++ debian/copyright | 26 +++++ debian/dirs | 1 + debian/docs | 1 + debian/rules | 99 ++++++++++++++++++ pam_csc.c | 239 +++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 411 insertions(+) create mode 100644 Makefile create mode 100644 debian/README create mode 100644 debian/README.Debian create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/dirs create mode 100644 debian/docs create mode 100755 debian/rules create mode 100644 pam_csc.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f08c234 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CC=gcc +CFLAGS=-O2 -fPIC -Wall +LDFLAGS=-shared -lpam -lldap +LD=ld + +all: pam_csc.so + +pam_csc.so: pam_csc.o + $(LD) -o $@ $(LDFLAGS) $< + +clean: + rm -f pam_csc.so pam_csc.o + +install: + cp pam_csc.so /lib/security/ diff --git a/debian/README b/debian/README new file mode 100644 index 0000000..7bf5923 --- /dev/null +++ b/debian/README @@ -0,0 +1,6 @@ +The Debian Package libpam-csc +---------------------------- + +Comments regarding the Package + + -- David Bartley Sun, 24 Jun 2007 23:18:22 -0400 diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..b892526 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,6 @@ +libpam-csc for Debian +--------------------- + + + + -- David Bartley Sun, 24 Jun 2007 23:18:22 -0400 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..b0bbc94 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +libpam-csc (1.0) unstable; urgency=low + + * Initial Release. + + -- David Bartley Sun, 24 Jun 2007 23:18:22 -0400 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..8ea3148 --- /dev/null +++ b/debian/control @@ -0,0 +1,12 @@ +Source: libpam-csc +Section: net +Priority: optional +Maintainer: David Bartley +Build-Depends: debhelper (>= 4.0.0), libldap-dev, libpam0g-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. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..53889ef --- /dev/null +++ b/debian/copyright @@ -0,0 +1,26 @@ +This is libpam-csc, written and maintained by David Bartley +on Sun, 24 Jun 2007 23:18:22 -0400. + +The original source can always be found at: + ftp://ftp.debian.org/dists/unstable/main/source/ + +Copyright Holder: David Bartley + +License: + + 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 package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +On Debian systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 0000000..d1f6515 --- /dev/null +++ b/debian/dirs @@ -0,0 +1 @@ +lib/security diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..1333ed7 --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ +TODO diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..0d37306 --- /dev/null +++ b/debian/rules @@ -0,0 +1,99 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + + + +CFLAGS = -Wall -g + +ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS))) + CFLAGS += -O0 +else + CFLAGS += -O2 +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + touch configure-stamp + + +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 $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + # Add here commands to clean up after the build process. + -$(MAKE) clean + + dh_clean + +install: build-stamp + dh_testdir + dh_testroot + dh_clean -k + 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 +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/pam_csc.c b/pam_csc.c new file mode 100644 index 0000000..79dd69d --- /dev/null +++ b/pam_csc.c @@ -0,0 +1,239 @@ +#define PAM_SM_ACCOUNT +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PAM_CSC_LDAP_URI \ + "ldap://caffeine.csclub.uwaterloo.ca ldap://perpugilliam.csclub.uwaterloo.ca" +#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 +#define PAM_CSC_EXPIRED_MSG \ + "*****************************************************************************\n" \ + "* *\n" \ + "* Your account has expired - please contact the Computer Science Club *\n" \ + "* *\n" \ + "*****************************************************************************\n" + +/* + * User terms are defined as (3 * year + term) where term is: + * 0 = Winter, 1 = Spring, 2 = Fall + * 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__); \ + retval = PAM_SUCCESS; \ + goto cleanup; \ +} + +#define WARN_ZERO(x) \ + if( (x) == 0 ) HANDLE_WARN + +#define WARN_NEG1(x) \ + if( (x) == -1 ) HANDLE_WARN + +#define WARN_PAM(x) \ + if( (x) != PAM_SUCCESS ) HANDLE_WARN + +#define WARN_LDAP(x) \ + if( (x) != LDAP_SUCCESS ) HANDLE_WARN + +char* escape_ldap_string(const char* src) +{ + char *dst, *dstPtr; + int i; + + if(!(dst = malloc(2 * strlen(src) + 1))) + return NULL; + dstPtr = dst; + + for(i = 0; i < strlen(src); i++) + { + if(src[i] == '*' || src[i] == '(' || src[i] == ')' || src[i] == '\\') + { + dstPtr[0] = '\\'; + dstPtr++; + } + dstPtr[0] = src[i]; + dstPtr++; + } + dstPtr[0] = '\0'; + + return dst; +} + +int check_user(const char* username, enum check_user_type_t checkType) +{ + 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_initialize(&ld, PAM_CSC_LDAP_URI) ) + 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 */ + 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) ) + +cleanup: + + return retval; +} + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t* pamh, int flags, int argc, const char* argv[]) +{ + const char* username; + struct passwd* pwd; + + /* determine username */ + if((pam_get_user(pamh, &username, NULL) != PAM_SUCCESS) || !username) + { + return PAM_USER_UNKNOWN; + } + + /* check uid */ + pwd = getpwnam(username); + if(pwd && pwd->pw_uid < PAM_CSC_MINIMUM_UID) + { + return PAM_SUCCESS; + } + + /* check if user exists in ldap */ + if(check_user(username, check_user_exists) == PAM_AUTH_ERR) + { + return PAM_SUCCESS; + } + + /* check if user is registered for the current term */ + if(check_user(username, check_user_cur_term) == PAM_SUCCESS) + { + return PAM_SUCCESS; + } + + /* check if user is registered for the previous term */ + if(check_user(username, check_user_prev_term) == PAM_SUCCESS) + { + /* 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; + } + + /* check if user is in allowed groups */ + if(check_user(username, check_user_groups) == PAM_SUCCESS) + { + /* 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; + } + + /* 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); + + return PAM_AUTH_ERR; +}