Add POST handling and URLDecode POST/GET request data. Switch Login to use POST inste...
[matches/MCTX3420.git] / server / login.c
1 /**
2  * @file login.c
3  * @brief Implementation of Login related functionality
4  */
5
6
7
8
9 #include "login.h"
10 #include "options.h"
11 #include <ctype.h>
12 #include <unistd.h>
13
14 #define LDAP_DEPRECATED 1 // Required to use ldap_simple_bind_s
15 #include <ldap.h>
16
17 /**
18  * Attempt to login using a file formatted like /etc/shadow
19  * This is here... because all better options have been exhausted
20  * @param user - The username
21  * @param pass - The password
22  * @returns Privelage level of the user or USER_UNAUTH for failure to authenticate
23  */
24 UserType Login_Shadow(const char * user, const char * pass, const char * shadow)
25 {
26         if (strlen(user) + strlen(pass) >= BUFSIZ-1)
27         {
28                 Log(LOGERR, "User/Password too long!\n");
29                 return USER_UNAUTH;
30         }
31
32         FILE * f = fopen(shadow, "r");
33         if (f == NULL)
34         {
35                 Log(LOGERR,"Can't open %s - %s\n", shadow, strerror(errno));
36                 return USER_UNAUTH;
37         }
38
39         char buffer[BUFSIZ];
40         int passwd_index = -1;
41
42         while (fgets(buffer, BUFSIZ, f) != NULL) // NOTE: Restrict username+password strings to BUFSIZ... what could possibly go wrong?
43         {
44
45                 //Log(LOGDEBUG,"Scanning %d: %s", strlen(buffer), buffer);
46         
47                 for (int i = 0; i < BUFSIZ-1 && buffer[i] != '\0'; ++i)
48                 {
49                         if (buffer[i] == ':')
50                         {
51                                 buffer[i] = '\0';
52                                 passwd_index = i+1;
53                                 break;
54                         }
55                 }
56
57                 if (strcmp(user,buffer) == 0)
58                 {
59                         //Log(LOGDEBUG,"User matches! %s\n", buffer);
60                         break;
61                 } 
62                 passwd_index = -1;
63         }
64
65         if (passwd_index <= 0)
66         {
67                 //Log(LOGDEBUG,"No user found matching %s\n", user);
68                 return USER_UNAUTH;
69         }
70
71         int gid_index = -1;
72         for (int i = passwd_index; i < BUFSIZ-1 && buffer[i] != '\0'; ++i)
73         {
74                 if (buffer[i] == ':')
75                 {
76                         gid_index = i+1;
77                         buffer[i] = '\0';
78                 }
79                 if (buffer[i] == '\n')
80                         buffer[i] = '\0';
81         }
82         char * end = buffer+gid_index;
83         UserType user_type = USER_NORMAL;
84         if (gid_index > passwd_index && gid_index < BUFSIZ-1)
85         {
86                 int gid = strtol(buffer+gid_index, &end,10);
87                 Log(LOGDEBUG, "Usertype %d %s", gid, buffer+gid_index);
88                 if (*end == '\0' && gid == 0)
89                 {
90                         Log(LOGDEBUG, "Admin");
91                         user_type = USER_ADMIN;
92                 }
93         }
94         
95         // Determine the salt
96         char salt[BUFSIZ];
97         int s = 0; int count = 0;
98         for (int i = passwd_index; i < BUFSIZ-1; ++i)
99         {
100                 salt[s++] = buffer[i];
101                 if (salt[s] == '$' && ++count >= 3)
102                         break;
103         }
104
105 //      Log(LOGDEBUG,"Salted Entry: %s\n", buffer+passwd_index);
106 //      Log(LOGDEBUG,"Salted Attempt: %s\n", crypt(pass, salt));
107         
108         if (strcmp(crypt(pass, salt), buffer+passwd_index) == 0)
109         {
110                 return user_type;
111         }
112         return USER_UNAUTH;
113 }
114
115 /**
116  * Attempt to bind to a LDAP uri
117  * @param uri - The uri
118  * @param dn - The DN
119  * @param pass - The password
120  * @returns An error code according to libldap; LDAP_SUCCESS if everything worked
121  */
122 int Login_LDAP_Bind(const char * uri, const char * dn, const char * pass)
123 {
124         Log(LOGDEBUG, "Bind to %s with dn %s and pass %s", uri, dn, pass);
125
126         // Initialise LDAP; prepares to connect to the server
127         LDAP * ld = NULL;
128         int err = ldap_initialize(&ld, uri);
129         if (err != LDAP_SUCCESS || ld == NULL)
130         {
131                 Log(LOGERR,"ldap_initialize failed - %s (ld = %p)", ldap_err2string(err), ld);
132                 return err;
133         }
134
135         // Set the LDAP version...
136         int version = LDAP_VERSION3;
137         err = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version); // specify the version
138         if (err != LDAP_SUCCESS)
139         {
140                 Log(LOGERR,"ldap_set_option failed - %s", ldap_err2string(err));
141                 return err;
142         }
143
144         // Attempt to bind using the supplied credentials.
145         // NOTE: ldap_simple_bind_s is "deprecated" in <ldap.h>, but not listed as such in the man pages :S
146         err = ldap_simple_bind_s(ld, dn, pass);
147         if (err != LDAP_SUCCESS)
148         {
149                 Log(LOGERR, "ldap_simple_bind_s failed - %s", ldap_err2string(err));
150         }
151         else
152         {
153                 Log(LOGDEBUG, "Successfully bound to %s with dn %s", uri, dn);
154         }
155
156         int err2 = ldap_unbind_s(ld);
157         if (err2 != LDAP_SUCCESS)
158         {
159                 Log(LOGERR, "ldap_unbind_s failed - %s", ldap_err2string(err2));
160                 err = err2;
161         }
162         return err;
163 }
164
165 /**
166  * Logout
167  * @param context - The context. The key will be cleared.
168  * @param params - Parameter string, UNUSED
169  */
170 void Logout_Handler(FCGIContext * context, char * params)
171 {
172         FCGI_ReleaseControl(context);
173         FCGI_SendControlCookie(context, false); //Unset the cookie
174         FCGI_AcceptJSON(context, "Logged out");
175 }
176
177
178 /**
179  * Handle a Login Request
180  * @param context - The context
181  * @param params - Parameter string, should contain username and password.
182  *                                 NOTE: Care should be taken when using params, as it is
183  *                                 completely unescaped. Do not log or use it without
184  *                 suitable escaping.
185  */
186 void Login_Handler(FCGIContext * context, char * params)
187 {
188         char * user; // The username supplied through CGI
189         char * pass; // The password supplied through CGI
190
191         FCGIValue values[] = {
192                 {"user", &user, FCGI_REQUIRED(FCGI_STRING_T)},
193                 {"pass", &pass, FCGI_REQUIRED(FCGI_STRING_T)},
194         };
195
196         //enum to avoid the use of magic numbers
197         typedef enum {
198                 USER,
199                 PASS,
200                 LOGOUT
201         } LoginParams;
202
203         // Fill values appropriately
204         if (!FCGI_ParseRequest(context, params, values, sizeof(values)/sizeof(FCGIValue)))
205         {
206                 // Error occured; FCGI_RejectJSON already called
207                 return;
208         }
209
210         // Trim leading whitespace
211         int i = 0;
212         for (i = 0; isspace(user[0]) && user[0] != '\0'; ++i, ++user);
213
214         // Truncate string at first non alphanumeric character
215         for (i = 0; isalnum(user[i]) && user[i] != '\0'; ++i);
216         user[i] = '\0';
217
218         
219         UserType user_type = USER_UNAUTH;
220         
221         switch (g_options.auth_method)
222         {
223
224                 case AUTH_LDAP:
225                 {
226                         if (*pass == '\0')
227                         {
228                                 FCGI_RejectJSON(context, "No password supplied.");
229                                 return;
230                         }
231
232                         //TODO: Generate the DN in some sane way
233                         char dn[BUFSIZ];
234                 
235                         // On a simple LDAP server:
236                         //int len = sprintf(dn, "uid=%s,%s", user, g_options.ldap_base_dn);
237         
238                         // At UWA (hooray)
239                         char * user_group = "Students";
240                         if (user[0] == '0')
241                                 user_group = "Staff";
242                         int len = sprintf(dn, "cn=%s,ou=%s,%s", user, user_group, g_options.ldap_base_dn);
243                 
244
245                         if (len >= BUFSIZ)
246                         {
247                                 FCGI_RejectJSON(context, "DN too long! Recompile with increased BUFSIZ");
248                                 return;
249                         }
250                 
251                         if (Login_LDAP_Bind(g_options.auth_uri, dn, pass) == LDAP_SUCCESS)
252                         {
253                                 if (user[0] == '0')
254                                         user_type = USER_ADMIN;
255                                 else
256                                         user_type = USER_NORMAL;
257                         }
258                         break;
259                 }
260                 case AUTH_SHADOW:
261                 {
262                         user_type = Login_Shadow(user, pass, g_options.auth_uri);
263                         break;
264                 }
265                 default:
266                 {
267                         Log(LOGWARN, "No authentication!");
268                         user_type = USER_ADMIN;
269                         break;
270                 }
271         }
272                 
273         // error check  
274         
275         if (user_type == USER_UNAUTH)
276         {
277                 Log(LOGDEBUG, "Authentication failure for %s", user);
278                 FCGI_RejectJSONEx(context, STATUS_UNAUTHORIZED, "Authentication failure.");
279         }
280         else
281         {
282                 // Try and gain control over the system
283                 if (FCGI_LockControl(context, user, user_type))
284                 {
285                         FCGI_EscapeText(context->user_name); //Don't break javascript pls
286                         // Give the user a cookie
287                         FCGI_SendControlCookie(context, true); //Send the control key
288                         FCGI_AcceptJSON(context, "Logged in");
289                         Log(LOGDEBUG, "Successful authentication for %s", user);
290                 }
291                 else
292                 {
293                         Log(LOGDEBUG, "%s successfully authenticated but system was in use by %s", user, context->user_name);
294                         FCGI_RejectJSON(context, "Someone else is already logged in (and you are not an admin)");
295                 }
296         }
297 }

UCC git Repository :: git.ucc.asn.au