2 #define LDAP_DEPRECATED 1
11 #include <security/pam_appl.h>
12 #include <security/pam_modules.h>
14 #include <sasl/sasl.h>
19 #ifndef LDAP_SASL_QUIET
20 # define LDAP_SASL_QUIET 0
24 # define LOG_AUTHPRIV LOG_AUTH
28 # define PAM_EXTERN extern
31 #define PAM_CSC_CSC_BASE_DN "ou=People,dc=csclub,dc=uwaterloo,dc=ca"
32 #define PAM_CSC_LDAP_TIMEOUT 5
33 #define PAM_CSC_ALLOWED_USERNAMES {"nobody"}
34 #define PAM_CSC_EXPIRED_MSG \
35 "*****************************************************************************\n" \
37 "* Your account has expired - please contact the Computer Science Club *\n" \
39 "*****************************************************************************\n"
41 #define PAM_CSC_SYSLOG_EXPIRED_NO_TERMS \
42 "(pam_csc): %s was not registered for current term or previous term - denying login\n"
43 #define PAM_CSC_SYSLOG_EXPIRED_LAST_TERM \
44 "(pam_csc): %s was not registered for current term but was registered for previous term - permitting login\n"
45 #define PAM_CSC_SYSLOG_NOT_A_MEMBER \
46 "(pam_csc): %s is not a member account - permitting login\n"
47 #define PAM_CSC_SYSLOG_SASL_UNRECOGNIZED_CALLBACK \
48 "(pam_csc): %ld is not a recognized SASL callback option\n"
51 * User terms are defined as (3 * year + term) where term is:
52 * 0 = Winter, 1 = Spring, 2 = Fall
53 * Term is a string in the form [f|w|s][year]
58 syslog(LOG_AUTHPRIV | LOG_WARNING, "pam_csc generated a warning on line %d of %s\n", __LINE__, __FILE__); \
59 retval = PAM_SUCCESS; \
63 #define WARN_ZERO(x) \
64 if( (x) == 0 ) HANDLE_WARN
66 #define WARN_NEG1(x) \
67 if( (x) == -1 ) HANDLE_WARN
70 if( (x) != PAM_SUCCESS ) HANDLE_WARN
72 #define WARN_LDAP(x) \
73 if( (x) != LDAP_SUCCESS ) HANDLE_WARN
75 struct pam_csc_sasl_interact_param
81 typedef struct pam_csc_sasl_interact_param pam_csc_sasl_interact_param_t;
83 int pam_csc_sasl_interact(LDAP* ld, unsigned flags, void* def, void* inter)
85 pam_csc_sasl_interact_param_t* param = (pam_csc_sasl_interact_param_t*)def;
86 sasl_interact_t* interact = (sasl_interact_t*)interact;
87 while(interact->id != SASL_CB_LIST_END)
91 case SASL_CB_GETREALM:
92 interact->result = param->realm;
95 interact->result = param->user;
98 interact->result = param->pass;
101 syslog(LOG_AUTHPRIV | LOG_NOTICE,
102 PAM_CSC_SYSLOG_SASL_UNRECOGNIZED_CALLBACK, interact->id);
103 interact->result = "";
106 interact->len = strlen(interact->result);
112 char* pam_csc_escape_ldap_string(const char* src)
117 if(!(dst = malloc(2 * strlen(src) + 1)))
121 for(i = 0; i < strlen(src); i++)
123 if(src[i] == '*' || src[i] == '(' || src[i] == ')' || src[i] == '\\')
136 int pam_csc_print_message(pam_handle_t* pamh, char* msg, int style)
138 int retval = PAM_SUCCESS;
139 const struct pam_conv* conv;
140 struct pam_message message;
141 struct pam_message* messages[1];
142 struct pam_response* response;
145 WARN_PAM( pam_get_item(pamh, PAM_CONV, (const void**)&conv) )
146 if(!conv || !conv->conv)
148 messages[0] = &message;
149 message.msg_style = style;
151 WARN_PAM( conv->conv(1, (const struct pam_message**)messages,
152 &response, conv->appdata_ptr) )
159 PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t* pamh, int flags, int argc, const char* argv[])
161 int retval = PAM_SUCCESS;
162 const char* username;
165 const char* allowed_usernames[] = PAM_CSC_ALLOWED_USERNAMES;
168 struct tm* local_time;
169 int long_term, term_month;
170 static const char term_chars[] = {'w', 's', 'f'};
171 char cur_term[6], prev_term[6];
173 char* username_escaped = NULL;
174 char *filter_csc = NULL;
175 char *attrs_csc[] = {"objectClass", "term", "nonMemberTerm", NULL};
176 bool expired, syscom = 0;
177 const char* pam_rhost;
179 LDAPMessage *res_csc = NULL;
180 struct timeval timeout = {PAM_CSC_LDAP_TIMEOUT, 0};
181 LDAPMessage* entry = NULL;
182 char **values = NULL, **nmvalues = NULL, **values_iter = NULL;
184 /* determine username */
185 if((pam_get_user(pamh, &username, NULL) != PAM_SUCCESS) || !username)
187 return PAM_USER_UNKNOWN;
190 /* check uid range */
191 pwd = getpwnam(username);
194 /* these ranges are taken from puppet/documents/id-range */
195 if(pwd->pw_uid < 500 || (pwd->pw_uid >= 1000 && pwd->pw_uid < 10000))
201 /* check to see if user is in group syscom, if yes, still print message but allow login even if user expired */
203 grp = getgrnam("syscom");
204 while(grp->gr_mem[i] != NULL) {
205 if(!strcmp(grp->gr_mem[i], username)) {
213 for(i = 0; i < sizeof(allowed_usernames) / sizeof(char*); i++)
215 if(strcmp(allowed_usernames[i], username) == 0)
221 /* escape username */
222 WARN_ZERO( username_escaped = pam_csc_escape_ldap_string(username) );
224 /* get term info and compute current and previous term */
225 WARN_NEG1( cur_time = time(NULL) )
226 WARN_ZERO( local_time = localtime(&cur_time) )
227 long_term = 3 * (1900 + local_time->tm_year) + (local_time->tm_mon / 4);
228 sprintf(cur_term, "%c%d", term_chars[long_term % 3], long_term / 3);
230 sprintf(prev_term, "%c%d", term_chars[long_term % 3], long_term / 3);
231 term_month = local_time->tm_mon % 4;
234 WARN_LDAP( ldap_create(&ld_csc) )
235 WARN_NEG1( ldap_simple_bind(ld_csc, NULL, NULL) )
237 /* check if we are logging in from a CSCF teaching thin client */
238 if(pam_get_item(pamh, PAM_RHOST, (const void**)&pam_rhost) && pam_rhost)
240 /* TODO: check if pam_rhost is tcNNN.student.cs */
243 /* create CSC request string */
244 WARN_ZERO( filter_csc = malloc(140 + strlen(username_escaped)) )
245 sprintf(filter_csc, "(&(uid=%s)(|(&(objectClass=member)(|(term=%s)(term=%s)(nonMemberTerm=%s)(nonMemberTerm=%s)))(!(objectClass=member))))", username_escaped, cur_term, prev_term, cur_term, prev_term);
247 /* issue CSC request */
248 WARN_NEG1( msg_csc = ldap_search(ld_csc, PAM_CSC_CSC_BASE_DN,
249 LDAP_SCOPE_SUBTREE, filter_csc, attrs_csc, 0) )
251 /* wait for CSC response */
252 WARN_NEG1( ldap_result(ld_csc, msg_csc, 1, &timeout, &res_csc) )
254 /* check if we received an entry from CSC */
255 if(ldap_count_entries(ld_csc, res_csc) == 0)
257 /* show notice and disallow login */
258 pam_csc_print_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_ERROR_MSG);
259 syslog(LOG_AUTHPRIV | LOG_NOTICE, PAM_CSC_SYSLOG_EXPIRED_NO_TERMS,
261 retval = (syscom ? PAM_SUCCESS : PAM_AUTH_ERR);
266 WARN_ZERO( entry = ldap_first_entry(ld_csc, res_csc) )
267 values = ldap_get_values(ld_csc, entry, "term");
268 nmvalues = ldap_get_values(ld_csc, entry, "nonMemberTerm");
270 if(!values && !nmvalues)
272 syslog(LOG_AUTHPRIV | LOG_NOTICE, PAM_CSC_SYSLOG_NOT_A_MEMBER,
274 retval = PAM_SUCCESS;
278 /* iterate through term attributes */
281 values_iter = values;
284 if(strcmp(*values_iter, cur_term) == 0)
286 /* user is registered in current term */
294 values_iter = nmvalues;
295 while (*values_iter) {
296 if (strcmp(*values_iter, cur_term) == 0) {
304 /* check if account is expired */
307 /* we allow once month grace-period */
310 /* show notice and continue */
311 pam_csc_print_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_TEXT_INFO);
312 syslog(LOG_AUTHPRIV | LOG_NOTICE, PAM_CSC_SYSLOG_EXPIRED_LAST_TERM,
317 /* show notice and disallow login */
318 pam_csc_print_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_ERROR_MSG);
319 syslog(LOG_AUTHPRIV | LOG_NOTICE, PAM_CSC_SYSLOG_EXPIRED_NO_TERMS,
321 retval = (syscom ? PAM_SUCCESS : PAM_AUTH_ERR);
326 if(values) ldap_value_free(values);
327 if(nmvalues) ldap_value_free(nmvalues);
328 if(res_csc) ldap_msgfree(res_csc);
329 if(ld_csc) ldap_unbind(ld_csc);
330 if(filter_csc) free(filter_csc);
331 if(username_escaped) free(username_escaped);