Initial checkin.
[public/libpam-csc.git] / pam_csc.c
1 #define PAM_SM_ACCOUNT
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/time.h>
5 #include <time.h>
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <security/pam_modules.h>
10 #include <security/pam_appl.h>
11 #include <ldap.h>
12 #include <syslog.h>
13 #include <pwd.h>
14
15 #define PAM_CSC_LDAP_URI \
16     "ldap://caffeine.csclub.uwaterloo.ca ldap://perpugilliam.csclub.uwaterloo.ca"
17 #define PAM_CSC_LDAP_USER_BASE_DN       "ou=People,dc=csclub,dc=uwaterloo,dc=ca"
18 #define PAM_CSC_LDAP_GROUP_BASE_DN      "ou=Group,dc=csclub,dc=uwaterloo,dc=ca"
19 #define PAM_CSC_LDAP_TIMEOUT            5
20 #define PAM_CSC_ALLOWED_GROUPS          "cn=staff"
21 #define PAM_CSC_MINIMUM_UID             1000
22 #define PAM_CSC_EXPIRED_MSG \
23     "*****************************************************************************\n" \
24     "*                                                                           *\n" \
25     "*    Your account has expired - please contact the Computer Science Club    *\n" \
26     "*                                                                           *\n" \
27     "*****************************************************************************\n"
28
29 /*
30  * User terms are defined as (3 * year + term) where term is:
31  *   0 = Winter, 1 = Spring, 2 = Fall
32  * Term is a string in the form [f|w|s][year]
33  */
34
35 enum check_user_type_t
36 {
37     check_user_exists,
38     check_user_cur_term,
39     check_user_prev_term,
40     check_user_groups
41 };
42
43 #define HANDLE_WARN \
44 { \
45     syslog(LOG_AUTHPRIV | LOG_WARNING, "pam_csc generated a warning on line %d of %s\n", __LINE__, __FILE__); \
46     retval = PAM_SUCCESS; \
47     goto cleanup; \
48 }
49
50 #define WARN_ZERO(x) \
51     if( (x) == 0 ) HANDLE_WARN
52
53 #define WARN_NEG1(x) \
54     if( (x) == -1 ) HANDLE_WARN
55
56 #define WARN_PAM(x) \
57     if( (x) != PAM_SUCCESS ) HANDLE_WARN
58
59 #define WARN_LDAP(x) \
60     if( (x) != LDAP_SUCCESS ) HANDLE_WARN
61
62 char* escape_ldap_string(const char* src)
63 {
64     char *dst, *dstPtr;
65     int i;
66
67     if(!(dst = malloc(2 * strlen(src) + 1)))
68         return NULL;
69     dstPtr = dst;
70
71     for(i = 0; i < strlen(src); i++)
72     {
73         if(src[i] == '*' || src[i] == '(' || src[i] == ')' || src[i] == '\\')
74         {
75             dstPtr[0] = '\\';
76             dstPtr++;
77         }
78         dstPtr[0] = src[i];
79         dstPtr++;
80     }
81     dstPtr[0] = '\0';
82
83     return dst;
84 }
85
86 int check_user(const char* username, enum check_user_type_t checkType)
87 {
88     int retval = PAM_SUCCESS;
89     time_t curTime;
90     struct tm* localTime;
91     int longTerm, year, term;
92     LDAP* ld = NULL;
93     static const char termChars[] = {'w', 's', 'f'};
94     char* usernameEscaped = NULL;
95     char* filter = NULL;
96     char* attr[] = {"objectClass", NULL};
97     struct timeval timeout = {PAM_CSC_LDAP_TIMEOUT, 0};
98     LDAPMessage* res = NULL;
99     char* baseDN = NULL;
100
101     /* fail-safe for root */
102     if(strcmp(username, "root") == 0)
103     {
104         return PAM_SUCCESS;
105     }
106
107     /* connect and bind */
108     WARN_LDAP( ldap_initialize(&ld, PAM_CSC_LDAP_URI) )
109     WARN_NEG1( ldap_simple_bind(ld, NULL, NULL) )
110
111     WARN_ZERO( usernameEscaped = escape_ldap_string(username) );
112     switch(checkType)
113     {
114     case check_user_exists:
115
116         /* format filter */
117         WARN_ZERO( filter = malloc(50 + strlen(usernameEscaped)) )
118         sprintf(filter, "(uid=%s)", usernameEscaped);
119         baseDN = PAM_CSC_LDAP_USER_BASE_DN;
120         break;
121
122     case check_user_prev_term:
123     case check_user_cur_term:
124
125         /* get term info and compute current and previous term */
126         WARN_NEG1( curTime = time(NULL) )
127         WARN_ZERO( localTime = localtime(&curTime) )
128         longTerm = 3 * (1900 + localTime->tm_year) + (localTime->tm_mon / 4);
129         if(checkType == check_user_prev_term)
130             longTerm--;
131         term = termChars[longTerm % 3];
132         year = longTerm / 3;
133
134         /* format filter */
135         WARN_ZERO( filter = malloc(100 + strlen(usernameEscaped)) )
136         sprintf(filter, "(&(uid=%s)(|(&(objectClass=member)(term=%c%d))(!(objectClass=member))))", 
137             usernameEscaped, term, year);
138         baseDN = PAM_CSC_LDAP_USER_BASE_DN;
139         break;
140
141     case check_user_groups:
142
143         /* format filter */
144         WARN_ZERO( filter = malloc(50 + strlen(PAM_CSC_ALLOWED_GROUPS) + strlen(usernameEscaped)) )
145         sprintf(filter, "(&(objectClass=posixGroup)(%s)(memberUid=%s))", PAM_CSC_ALLOWED_GROUPS, usernameEscaped);
146         baseDN = PAM_CSC_LDAP_GROUP_BASE_DN;
147         break;
148     }
149
150     /* search */
151     WARN_LDAP( ldap_search_st(ld, baseDN, LDAP_SCOPE_SUBTREE, filter, attr, 1, &timeout, &res) )
152     if((term = ldap_count_entries(ld, res)) == 0)
153         retval = PAM_AUTH_ERR;
154
155 cleanup:
156
157     if(usernameEscaped) free(usernameEscaped);
158     if(res) ldap_msgfree(res);
159     if(filter) free(filter);
160     if(ld) ldap_unbind(ld);
161
162     return retval;
163 }
164
165 int print_pam_message(pam_handle_t* pamh, char* msg, int style)
166 {
167     int retval = PAM_SUCCESS;
168     struct pam_conv* pamConv;
169     struct pam_message pamMessage;
170     struct pam_message* pamMessages[1];
171     struct pam_response* pamResponse;
172
173     /* output message */
174     WARN_PAM( pam_get_item(pamh, PAM_CONV, (const void**)&pamConv) )
175     pamMessages[0] = &pamMessage;
176     pamMessage.msg_style = style;
177     pamMessage.msg = msg;
178     WARN_PAM( pamConv->conv(1, (const struct pam_message**)pamMessages, 
179         &pamResponse, pamConv->appdata_ptr) )
180
181 cleanup:
182
183     return retval;
184 }
185
186 PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t* pamh, int flags, int argc, const char* argv[])
187 {
188     const char* username;
189     struct passwd* pwd;
190
191     /* determine username */
192     if((pam_get_user(pamh, &username, NULL) != PAM_SUCCESS) || !username)
193     {
194         return PAM_USER_UNKNOWN;
195     }
196
197     /* check uid */
198     pwd = getpwnam(username);
199     if(pwd && pwd->pw_uid < PAM_CSC_MINIMUM_UID)
200     {
201         return PAM_SUCCESS;
202     }
203
204     /* check if user exists in ldap */
205     if(check_user(username, check_user_exists) == PAM_AUTH_ERR)
206     {
207         return PAM_SUCCESS;
208     }
209
210     /* check if user is registered for the current term */
211     if(check_user(username, check_user_cur_term) == PAM_SUCCESS)
212     {
213         return PAM_SUCCESS;
214     }
215
216     /* check if user is registered for the previous term */
217     if(check_user(username, check_user_prev_term) == PAM_SUCCESS)
218     {
219         /* show warning */
220         syslog(LOG_AUTHPRIV | LOG_NOTICE, "(pam_csc): %s was not registered for current term but was registered for previous term - permitting login\n", username);
221         print_pam_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_TEXT_INFO);
222         return PAM_SUCCESS;
223     }
224
225     /* check if user is in allowed groups */
226     if(check_user(username, check_user_groups) == PAM_SUCCESS)
227     {
228         /* show warning */
229         print_pam_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_TEXT_INFO);
230         syslog(LOG_AUTHPRIV | LOG_NOTICE, "(pam_csc): %s was not registered but was in allowed groups - permitting login\n", username);
231         return PAM_SUCCESS;
232     }
233
234     /* account has expired - show prompt */
235     print_pam_message(pamh, PAM_CSC_EXPIRED_MSG, PAM_ERROR_MSG);
236     syslog(LOG_AUTHPRIV | LOG_NOTICE, "(pam_csc): %s was not registered and was not in allowed groups - denying login\n", username);
237
238     return PAM_AUTH_ERR;
239 }