|
1 /* Copyright 1999-2005 The Apache Software Foundation or its licensors, as |
|
2 * applicable. |
|
3 * |
|
4 * Licensed under the Apache License, Version 2.0 (the "License"); |
|
5 * you may not use this file except in compliance with the License. |
|
6 * You may obtain a copy of the License at |
|
7 * |
|
8 * http://www.apache.org/licenses/LICENSE-2.0 |
|
9 * |
|
10 * Unless required by applicable law or agreed to in writing, software |
|
11 * distributed under the License is distributed on an "AS IS" BASIS, |
|
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
13 * See the License for the specific language governing permissions and |
|
14 * limitations under the License. |
|
15 */ |
|
16 |
|
17 /* |
|
18 * http_auth_form: authentication |
|
19 * |
|
20 * maintained by Aaron Arthurs <ajarthu@uark.edu> |
|
21 * |
|
22 * Logs are located in 'NEWS' and detailed in 'ChangeLog'. |
|
23 */ |
|
24 |
|
25 /* |
|
26 * $Id: mod_auth_form.c,v 2.04 Sat Jun 24 14:48:34 CDT 2006 ueli Exp $ |
|
27 */ |
|
28 #define MAF_VERSION "mod_auth_form 2.04" |
|
29 #define MAF_DESC "Form-based authentication using session management" |
|
30 |
|
31 #include <httpd.h> |
|
32 #include <http_config.h> |
|
33 #include <http_core.h> |
|
34 #include <http_log.h> |
|
35 #include <http_protocol.h> |
|
36 #include <http_request.h> // for ap_hook_(check_user_id | auth_checker) |
|
37 #include <apr_strings.h> // for apr_pstrdup prototype |
|
38 #include <apr_reslist.h> |
|
39 #include <mysql.h> |
|
40 |
|
41 #define PCALLOC apr_pcalloc |
|
42 #define PSPRINTF apr_psprintf |
|
43 #define PSTRCAT apr_pstrcat |
|
44 #define PSTRDUP apr_pstrdup |
|
45 #define SNPRINTF apr_snprintf |
|
46 #define VSNPRINTF apr_vsnprintf |
|
47 |
|
48 #ifndef TRUE |
|
49 #define TRUE 1 |
|
50 #endif |
|
51 #ifndef FALSE |
|
52 #define FALSE 0 |
|
53 #endif |
|
54 #define MAX_VAR_LEN 20 // used by 'parse_condition_vars' |
|
55 |
|
56 |
|
57 // |
|
58 // Query result structure |
|
59 // |
|
60 typedef struct { |
|
61 unsigned char ***records; // rows and columns of character arrays |
|
62 unsigned int num_records; // how many rows are in this result |
|
63 unsigned int num_fields; // how many columns are in each row |
|
64 } q_result; |
|
65 |
|
66 // |
|
67 // Structures to hold the mod_auth_form's configuration directives |
|
68 // |
|
69 // Per-Directory Configuration |
|
70 // |
|
71 typedef struct { |
|
72 unsigned char *dbHost; // host name of db server |
|
73 unsigned int dbPort; // port number of db server |
|
74 unsigned char *dbSocket; // socket file of db server (takes precedence) |
|
75 #ifdef MAF_MYSQL_SSL |
|
76 int dbSsl; // enable SSL? |
|
77 unsigned char *dbSslKey; // path to client key |
|
78 unsigned char *dbSslCert; // path to client certificate |
|
79 unsigned char *dbSslCa; // path to file listing trusted certificates |
|
80 unsigned char *dbSslCaPath; // path to directory containing trusted PEM certificates |
|
81 unsigned char *dbSslCipherList; // cipher list in the format of 'openssl ciphers' |
|
82 #endif // #ifdef MAF_MYSQL_SSL |
|
83 unsigned char *dbUsername; // username to connect to db server |
|
84 unsigned char *dbPassword; // password to connect to db server |
|
85 unsigned char *dbName; // DB name |
|
86 unsigned char *dbTableSID; // session table |
|
87 unsigned char *dbTableGID; // group table |
|
88 unsigned char *dbTableTracking; // tracking table (optional) |
|
89 unsigned char *dbFieldUID; // field in group, session, and tracking tables |
|
90 // with username |
|
91 unsigned char *dbFieldGID; // field in group table with group names |
|
92 unsigned char *dbFieldTimeout; // field in session table with session |
|
93 // timeout date |
|
94 unsigned char *dbFieldExpiration; // field in session table with sessionr |
|
95 // expiration date |
|
96 unsigned char *dbFieldIPAddress; // field in tracking table with client's |
|
97 // IP address |
|
98 unsigned char *dbFieldDownloadDate; // field in tracking table with date |
|
99 // of download |
|
100 unsigned char *dbFieldDownloadPath; // field in tracking table with path |
|
101 // of download |
|
102 unsigned char *dbFieldDownloadSize; // field in tracking table with size |
|
103 // (in bytes) of download |
|
104 unsigned char *dbTableSIDCondition; // condition to add to the where-clause |
|
105 // in the session table |
|
106 unsigned char *dbTableGIDCondition; // condition to add to the where-clause |
|
107 // in the group table |
|
108 unsigned char *dbTableTrackingCondition; // condition to add to the where-clause |
|
109 // in the tracking table |
|
110 int sessionTimeout; // session inactivity timeout in minutes |
|
111 int sessionAutoRefresh; // how often in seconds to refresh a |
|
112 // current page |
|
113 int sessionCookies; // read session keys from cookies instead |
|
114 // of the URL query string? |
|
115 int sessionDelete; // remove all expired sessions per request |
|
116 int trackingLifetime; // life-span (in days) of each tracking record |
|
117 unsigned char *pageLogin; // URL (absolute or relative) to the login page |
|
118 unsigned char *pageExpired; // URL (absolute or relative) to the |
|
119 //'session expired' page |
|
120 unsigned char *pageNotAllowed; // URL (absolute or relative) to the |
|
121 // 'user not allowed' page |
|
122 unsigned char *pageAutoRefresh; // URL (absolute or relative) for the |
|
123 // Refresh header |
|
124 unsigned char *lastPageKey; // Query-string key containing the last |
|
125 // unauthorized URL |
|
126 int authoritative; // are we authoritative? |
|
127 } auth_form_dir_config; |
|
128 |
|
129 // Module-specific functions |
|
130 static void set_cgi_env_directory(request_rec *r, const unsigned char *uid); |
|
131 static void *create_auth_form_dir_config(apr_pool_t *p, char *d); |
|
132 static void register_hooks(apr_pool_t *p); |
|
133 static int form_authenticator(request_rec *r); |
|
134 static int form_session_checker(request_rec *r); |
|
135 static unsigned char *form_check_session(request_rec *r, auth_form_dir_config *config, MYSQL *db_handle, |
|
136 int *expired); |
|
137 static int form_check_required(request_rec *r, auth_form_dir_config *config, MYSQL *db_handle, |
|
138 const unsigned char *uid); |
|
139 static unsigned char *get_gids(request_rec *r, auth_form_dir_config *config, MYSQL *db_handle, |
|
140 const unsigned char *uid); |
|
141 static void track_request(request_rec *r, auth_form_dir_config *config, MYSQL *db_handle, |
|
142 const unsigned char *uid); |
|
143 |
|
144 // Database functions |
|
145 static int open_db(request_rec *r, auth_form_dir_config *config, MYSQL **db_handle); |
|
146 static void close_db(MYSQL **db_handle); |
|
147 static q_result *send_query(request_rec *r, MYSQL *db_handle, unsigned char *query_format, ...); |
|
148 |
|
149 // Utility functions |
|
150 static int redirect(request_rec *r, auth_form_dir_config *config, const unsigned char *page, |
|
151 int log_level, const unsigned char *reason_format, ...); |
|
152 static unsigned char *parse_condition_vars(request_rec *r, const unsigned char *condition, int cookies); |
|
153 static unsigned char *get_value(request_rec *r, const unsigned char *key_values, |
|
154 const unsigned char *key, unsigned char terminator, const unsigned char *padding); |
|
155 static unsigned char *construct_full_uri(request_rec *r); |
|
156 static unsigned char *url_encode(request_rec *r, const unsigned char *uri); |
|
157 static unsigned char *url_decode(request_rec *r, const unsigned char *uri_enc); |
|
158 |
|
159 static command_rec auth_form_cmds[] = { |
|
160 AP_INIT_TAKE1("AuthFormMySQLHost", ap_set_string_slot, |
|
161 (void *) APR_OFFSETOF(auth_form_dir_config, dbHost), |
|
162 OR_AUTHCFG, "mysql server host name"), |
|
163 |
|
164 AP_INIT_TAKE1("AuthFormMySQLPort", ap_set_int_slot, |
|
165 (void *) APR_OFFSETOF(auth_form_dir_config, dbPort), |
|
166 OR_AUTHCFG, "mysql server port number"), |
|
167 |
|
168 AP_INIT_TAKE1("AuthFormMySQLSocket", ap_set_string_slot, |
|
169 (void *) APR_OFFSETOF(auth_form_dir_config, dbSocket), |
|
170 OR_AUTHCFG, "mysql server socket file"), |
|
171 |
|
172 #ifdef MAF_MYSQL_SSL |
|
173 AP_INIT_FLAG("AuthFormMySQLSSL", ap_set_flag_slot, |
|
174 (void *) APR_OFFSETOF(auth_form_dir_config, dbSsl), |
|
175 OR_AUTHCFG, "enable SSL for mysql connection"), |
|
176 |
|
177 AP_INIT_TAKE1("AuthFormMySQLSSLKey", ap_set_string_slot, |
|
178 (void *) APR_OFFSETOF(auth_form_dir_config, dbSslKey), |
|
179 OR_AUTHCFG, "mysql client certificate key file"), |
|
180 |
|
181 AP_INIT_TAKE1("AuthFormMySQLSSLCert", ap_set_string_slot, |
|
182 (void *) APR_OFFSETOF(auth_form_dir_config, dbSslCert), |
|
183 OR_AUTHCFG, "mysql client certificate file"), |
|
184 |
|
185 AP_INIT_TAKE1("AuthFormMySQLSSLCA", ap_set_string_slot, |
|
186 (void *) APR_OFFSETOF(auth_form_dir_config, dbSslCa), |
|
187 OR_AUTHCFG, "path to file listing trusted certificates"), |
|
188 |
|
189 AP_INIT_TAKE1("AuthFormMySQLSSLCAPath", ap_set_string_slot, |
|
190 (void *) APR_OFFSETOF(auth_form_dir_config, dbSslCaPath), |
|
191 OR_AUTHCFG, "path to directory containing PEM-formatted, trusted certificates"), |
|
192 |
|
193 AP_INIT_TAKE1("AuthFormMySQLSSLCipherList", ap_set_string_slot, |
|
194 (void *) APR_OFFSETOF(auth_form_dir_config, dbSslCipherList), |
|
195 OR_AUTHCFG, "list of SSL ciphers to allow (in 'openssl ciphers' format)"), |
|
196 #endif // #ifdef MAF_MYSQL_SSL |
|
197 |
|
198 AP_INIT_TAKE1("AuthFormMySQLUsername", ap_set_string_slot, |
|
199 (void *) APR_OFFSETOF(auth_form_dir_config, dbUsername), |
|
200 OR_AUTHCFG, "mysql server user name"), |
|
201 |
|
202 AP_INIT_TAKE1("AuthFormMySQLPassword", ap_set_string_slot, |
|
203 (void *) APR_OFFSETOF(auth_form_dir_config, dbPassword), |
|
204 OR_AUTHCFG, "mysql server user password"), |
|
205 |
|
206 AP_INIT_TAKE1("AuthFormMySQLDB", ap_set_string_slot, |
|
207 (void *) APR_OFFSETOF(auth_form_dir_config, dbName), |
|
208 OR_AUTHCFG, "mysql database name"), |
|
209 |
|
210 AP_INIT_TAKE1("AuthFormMySQLTableSID", ap_set_string_slot, |
|
211 (void *) APR_OFFSETOF(auth_form_dir_config, dbTableSID), |
|
212 OR_AUTHCFG, "mysql session table"), |
|
213 |
|
214 AP_INIT_TAKE1("AuthFormMySQLTableSIDCondition", ap_set_string_slot, |
|
215 (void *) APR_OFFSETOF(auth_form_dir_config, dbTableSIDCondition), |
|
216 OR_AUTHCFG, "condition used in session validation"), |
|
217 |
|
218 AP_INIT_TAKE1("AuthFormMySQLTableGID", ap_set_string_slot, |
|
219 (void *) APR_OFFSETOF(auth_form_dir_config, dbTableGID), |
|
220 OR_AUTHCFG, "mysql group table"), |
|
221 |
|
222 AP_INIT_TAKE1("AuthFormMySQLTableGIDCondition", ap_set_string_slot, |
|
223 (void *) APR_OFFSETOF(auth_form_dir_config, dbTableGIDCondition), |
|
224 OR_AUTHCFG, "condition to add to where-clause in group table queries"), |
|
225 |
|
226 AP_INIT_TAKE1("AuthFormMySQLTableTracking", ap_set_string_slot, |
|
227 (void *) APR_OFFSETOF(auth_form_dir_config, dbTableTracking), |
|
228 OR_AUTHCFG, "mysql tracking table"), |
|
229 |
|
230 AP_INIT_TAKE1("AuthFormMySQLTableTrackingCondition", ap_set_string_slot, |
|
231 (void *) APR_OFFSETOF(auth_form_dir_config, dbTableTrackingCondition), |
|
232 OR_AUTHCFG, "condition to add to where-clause in tracking table queries"), |
|
233 |
|
234 AP_INIT_TAKE1("AuthFormMySQLFieldUID", ap_set_string_slot, |
|
235 (void *) APR_OFFSETOF(auth_form_dir_config, dbFieldUID), |
|
236 OR_AUTHCFG, "mysql username field within group, session, and " |
|
237 "tracking tables"), |
|
238 |
|
239 AP_INIT_TAKE1("AuthFormMySQLFieldGID", ap_set_string_slot, |
|
240 (void *) APR_OFFSETOF(auth_form_dir_config, dbFieldGID), |
|
241 OR_AUTHCFG, "mysql group field within group table"), |
|
242 |
|
243 AP_INIT_TAKE1("AuthFormMySQLFieldTimeout", ap_set_string_slot, |
|
244 (void *) APR_OFFSETOF(auth_form_dir_config, dbFieldTimeout), |
|
245 OR_AUTHCFG, "mysql session timeout date field within session table"), |
|
246 |
|
247 AP_INIT_TAKE1("AuthFormMySQLFieldExpiration", ap_set_string_slot, |
|
248 (void *) APR_OFFSETOF(auth_form_dir_config, dbFieldExpiration), |
|
249 OR_AUTHCFG, "mysql session expiration date field within session table"), |
|
250 |
|
251 AP_INIT_TAKE1("AuthFormMySQLFieldIPAddress", ap_set_string_slot, |
|
252 (void *) APR_OFFSETOF(auth_form_dir_config, dbFieldIPAddress), |
|
253 OR_AUTHCFG, "mysql client IP address field within tracking table"), |
|
254 |
|
255 AP_INIT_TAKE1("AuthFormMySQLFieldDownloadDate", ap_set_string_slot, |
|
256 (void *) APR_OFFSETOF(auth_form_dir_config, dbFieldDownloadDate), |
|
257 OR_AUTHCFG, "mysql download date field within tracking table"), |
|
258 |
|
259 AP_INIT_TAKE1("AuthFormMySQLFieldDownloadPath", ap_set_string_slot, |
|
260 (void *) APR_OFFSETOF(auth_form_dir_config, dbFieldDownloadPath), |
|
261 OR_AUTHCFG, "mysql download path field within tracking table"), |
|
262 |
|
263 AP_INIT_TAKE1("AuthFormMySQLFieldDownloadSize", ap_set_string_slot, |
|
264 (void *) APR_OFFSETOF(auth_form_dir_config, dbFieldDownloadSize), |
|
265 OR_AUTHCFG, "mysql download size (in bytes) field within tracking table"), |
|
266 |
|
267 AP_INIT_TAKE1("AuthFormSessionTimeout", ap_set_int_slot, |
|
268 (void *) APR_OFFSETOF(auth_form_dir_config, sessionTimeout), |
|
269 OR_AUTHCFG, "session inactivity timeout in minutes"), |
|
270 |
|
271 AP_INIT_TAKE1("AuthFormSessionAutoRefresh", ap_set_int_slot, |
|
272 (void *) APR_OFFSETOF(auth_form_dir_config, sessionAutoRefresh), |
|
273 OR_AUTHCFG, "how often in seconds to refresh a current page"), |
|
274 |
|
275 AP_INIT_FLAG("AuthFormSessionCookies", ap_set_flag_slot, |
|
276 (void *) APR_OFFSETOF(auth_form_dir_config, sessionCookies), |
|
277 OR_AUTHCFG, "If On, read from cookies for sessions, else read from " |
|
278 "the URL query string"), |
|
279 |
|
280 AP_INIT_FLAG("AuthFormSessionDelete", ap_set_flag_slot, |
|
281 (void *) APR_OFFSETOF(auth_form_dir_config, sessionDelete), |
|
282 OR_AUTHCFG, "If On, remove expired sessions."), |
|
283 |
|
284 AP_INIT_TAKE1("AuthFormTrackingLifetime", ap_set_int_slot, |
|
285 (void *) APR_OFFSETOF(auth_form_dir_config, trackingLifetime), |
|
286 OR_AUTHCFG, "life-span (in days) of each tracking record in the " |
|
287 "tracking table"), |
|
288 |
|
289 AP_INIT_TAKE1("AuthFormPageLogin", ap_set_string_slot, |
|
290 (void *) APR_OFFSETOF(auth_form_dir_config, pageLogin), |
|
291 OR_AUTHCFG, "(Absolute | Relative) URL location of the login page"), |
|
292 |
|
293 AP_INIT_TAKE1("AuthFormPageExpired", ap_set_string_slot, |
|
294 (void *) APR_OFFSETOF(auth_form_dir_config, pageExpired), |
|
295 OR_AUTHCFG, "(Absolute | Relative) URL location of the 'session " |
|
296 "expired' page"), |
|
297 |
|
298 AP_INIT_TAKE1("AuthFormPageNotAllowed", ap_set_string_slot, |
|
299 (void *) APR_OFFSETOF(auth_form_dir_config, pageNotAllowed), |
|
300 OR_AUTHCFG, "(Absolute | Relative) URL location of the 'user not " |
|
301 "allowed' page"), |
|
302 |
|
303 AP_INIT_TAKE1("AuthFormPageAutoRefresh", ap_set_string_slot, |
|
304 (void *) APR_OFFSETOF(auth_form_dir_config, pageAutoRefresh), |
|
305 OR_AUTHCFG, "(Absolute | Relative) URL location for the Refresh " |
|
306 "HTTP header (if applicable)"), |
|
307 |
|
308 AP_INIT_TAKE1("AuthFormLastPageKey", ap_set_string_slot, |
|
309 (void *) APR_OFFSETOF(auth_form_dir_config, lastPageKey), |
|
310 OR_AUTHCFG, "Query-string key containing the last unauthorized URL"), |
|
311 |
|
312 AP_INIT_FLAG("AuthFormAuthoritative", ap_set_flag_slot, |
|
313 (void *) APR_OFFSETOF(auth_form_dir_config, authoritative), |
|
314 OR_AUTHCFG, "Whether or not this module handles authorization"), |
|
315 |
|
316 { NULL } |
|
317 }; // auth_form_cmds |
|
318 |
|
319 module AP_MODULE_DECLARE_DATA auth_form_module = |
|
320 { |
|
321 STANDARD20_MODULE_STUFF, |
|
322 create_auth_form_dir_config, // dir config creater |
|
323 NULL, // dir merger --- default is to override |
|
324 NULL, // server config creater |
|
325 NULL, // merge server config |
|
326 auth_form_cmds, // command apr_table_t |
|
327 register_hooks // register hooks |
|
328 }; |
|
329 |
|
330 |
|
331 // FUNCTION IMPLEMENTATIONS // |
|
332 |
|
333 // Module-specific Functions // |
|
334 |
|
335 // |
|
336 // Set mod_auth_form's environment variables for CGI scripts |
|
337 // for the current request. |
|
338 // |
|
339 static void |
|
340 set_cgi_env_directory(request_rec *r, const unsigned char *uid) { |
|
341 apr_table_set(r->subprocess_env, "AP_MAF_VERSION", MAF_VERSION); |
|
342 apr_table_set(r->subprocess_env, "AP_MAF_DESCRIPTION", MAF_DESC); |
|
343 apr_table_set(r->subprocess_env, "AP_MAF_ENABLED", "true"); |
|
344 apr_table_set(r->subprocess_env, "AP_MAF_UID", uid); |
|
345 } |
|
346 |
|
347 // |
|
348 // CALLBACK: |
|
349 // Create the per-directory configuration and its defaults. |
|
350 // |
|
351 static void * |
|
352 create_auth_form_dir_config(apr_pool_t *p, char *d) { |
|
353 auth_form_dir_config *config = (auth_form_dir_config *) |
|
354 PCALLOC(p, sizeof(auth_form_dir_config)); |
|
355 if (!config) return NULL; // failure to get memory is a bad thing |
|
356 |
|
357 // |
|
358 // default values |
|
359 // |
|
360 config->dbHost = NULL; // connect to localhost |
|
361 config->dbPort = 3306; |
|
362 config->dbSocket = NULL; // no socket |
|
363 #ifdef MAF_MYSQL_SSL |
|
364 config->dbSsl = 0; // no SSL |
|
365 config->dbSslCipherList = "!ADH:RC4+RSA:HIGH:MEDIUM:LOW:EXP:+SSLv2:+EXP"; |
|
366 #endif // #ifdef MAF_MYSQL_SSL |
|
367 config->dbTableSID = "sessions"; |
|
368 config->dbTableSIDCondition = "sid=$sid AND uid=$uid"; |
|
369 config->dbFieldGID = "gid"; |
|
370 config->dbFieldUID = "uid"; |
|
371 config->dbFieldTimeout = "timeout_date"; |
|
372 config->dbFieldIPAddress = "client_ip_address"; |
|
373 config->dbFieldDownloadDate = "download_date"; |
|
374 config->dbFieldDownloadPath = "download_path"; |
|
375 config->dbFieldDownloadSize = "download_size"; |
|
376 config->sessionTimeout = 0; // no session inactivity timeout |
|
377 config->sessionAutoRefresh = -1; // refresh whenever session expires |
|
378 config->sessionCookies = 0; // read from the URL query string |
|
379 config->sessionDelete = 0; // leave expired sessions in table |
|
380 config->trackingLifetime = 30; // keep tracking records as old as 30 days |
|
381 config->authoritative = 1; // we should be the authoritative source |
|
382 return (void *)config; |
|
383 } // create_auth_form_dir_config |
|
384 |
|
385 // |
|
386 // CALLBACK: |
|
387 // Tell Apache which function does what. |
|
388 // |
|
389 static void |
|
390 register_hooks(apr_pool_t *p) { |
|
391 ap_hook_check_user_id(form_authenticator, NULL, NULL, |
|
392 APR_HOOK_REALLY_FIRST); |
|
393 ap_hook_auth_checker(form_session_checker, NULL, NULL, |
|
394 APR_HOOK_REALLY_FIRST); |
|
395 } // register_hooks |
|
396 |
|
397 // |
|
398 // CALLBACK: |
|
399 // This function does nothing more than return OK. The reason |
|
400 // behind this is that Apache's authorization |
|
401 // is based on 'Basic Authentication'. |
|
402 // |
|
403 // Usually, the 'check_user_id' hook will send the 'WWW-Authenticate' |
|
404 // challenge, causing the login box to pop-up on the client's screen. |
|
405 // However, we want a form-based login script (e.g. a PHP script) to do the |
|
406 // verification for us; all this module does is verify, control, and log access |
|
407 // to restricted areas (from the 'auth_checker' hook). |
|
408 // |
|
409 static int |
|
410 form_authenticator(request_rec *r) { |
|
411 auth_form_dir_config *config = (auth_form_dir_config *)ap_get_module_config(r->per_dir_config, |
|
412 &auth_form_module); |
|
413 // |
|
414 // Of course, if we're not authoritative, we should |
|
415 // pass authorization to other modules. |
|
416 // |
|
417 if(config->authoritative && config->pageLogin) |
|
418 return OK; |
|
419 else |
|
420 return DECLINED; |
|
421 } // form_authenticator |
|
422 |
|
423 // |
|
424 // CALLBACK: |
|
425 // Check the user's group membership and session. |
|
426 // |
|
427 static int |
|
428 form_session_checker(request_rec *r) { |
|
429 MYSQL *db_handle = NULL; |
|
430 unsigned char *uid = NULL; |
|
431 int expired = FALSE, status; |
|
432 auth_form_dir_config *config = (auth_form_dir_config *)ap_get_module_config(r->per_dir_config, |
|
433 &auth_form_module); |
|
434 |
|
435 // |
|
436 // Check if we are authoritative before doing anything else. |
|
437 // |
|
438 if(!(config->authoritative && config->pageLogin)) |
|
439 return DECLINED; |
|
440 |
|
441 // |
|
442 // See if we can open a connection to the database if one is not |
|
443 // already opened. |
|
444 // If not, send a FORBIDDEN message to the client. |
|
445 // |
|
446 if(!open_db(r, config, &db_handle)) |
|
447 return HTTP_FORBIDDEN; |
|
448 |
|
449 r->unparsed_uri = construct_full_uri(r); |
|
450 |
|
451 uid = form_check_session(r, config, db_handle, &expired); |
|
452 if(!expired && uid != NULL) |
|
453 status = form_check_required(r, config, db_handle, uid); |
|
454 else if(expired) |
|
455 status = redirect(r, config, config->pageExpired, APLOG_INFO, "session expired"); |
|
456 else // uid == NULL |
|
457 status = redirect(r, config, config->pageLogin, APLOG_INFO, "invalid session"); |
|
458 |
|
459 close_db(&db_handle); |
|
460 |
|
461 if(status == OK) |
|
462 set_cgi_env_directory(r, uid); |
|
463 |
|
464 return status; |
|
465 } // form_session_checker |
|
466 |
|
467 // |
|
468 // Check the session keys against the database using a specified condition. |
|
469 // |
|
470 static unsigned char * |
|
471 form_check_session(request_rec *r, auth_form_dir_config *config, MYSQL *db_handle, |
|
472 int *expired) { |
|
473 unsigned char *uid = NULL, *query, |
|
474 *condition = parse_condition_vars(r, config->dbTableSIDCondition, |
|
475 config->sessionCookies); |
|
476 int autorefresh = config->sessionAutoRefresh, |
|
477 autor_expire_enabled = (autorefresh == -1 && config->dbFieldExpiration), |
|
478 s_timeout_enabled = (config->sessionTimeout > 0); |
|
479 q_result *rows = NULL; |
|
480 |
|
481 *expired = FALSE; |
|
482 query = PSTRCAT(r->pool, |
|
483 "SELECT ", config->dbFieldUID, |
|
484 (autor_expire_enabled)? // grab the difference |
|
485 ",TIME_TO_SEC(TIMEDIFF(":"", |
|
486 (autor_expire_enabled)? |
|
487 (char *)config->dbFieldExpiration:"", |
|
488 (autor_expire_enabled)? |
|
489 ", NOW())) exp_diff":"", |
|
490 " FROM ", config->dbTableSID, " WHERE (", condition, ")", |
|
491 (s_timeout_enabled)? // using session inactivity timeout |
|
492 " AND NOW()<":"", |
|
493 (s_timeout_enabled)? |
|
494 (char *)config->dbFieldTimeout:"", |
|
495 (config->dbFieldExpiration)? // using session expiration |
|
496 " AND NOW()<":"", |
|
497 (config->dbFieldExpiration)? |
|
498 (char *)config->dbFieldExpiration:"", |
|
499 NULL); |
|
500 |
|
501 rows = send_query(r, db_handle, query); |
|
502 |
|
503 if(rows) { // session condition satisfied and un-expired |
|
504 int autoref_sav = autorefresh, |
|
505 use_page_expired = (!config->pageAutoRefresh && autoref_sav == -1), |
|
506 add_last_page_key = ((config->pageAutoRefresh || autoref_sav == -1) && config->lastPageKey); |
|
507 uid = rows->records[0][0]; |
|
508 if(autorefresh == -1) { |
|
509 autorefresh = 0; |
|
510 if(config->dbFieldExpiration) |
|
511 autorefresh = strtol(rows->records[0][1], NULL, 10); |
|
512 if(config->sessionTimeout > 0 && |
|
513 (autorefresh > 60*config->sessionTimeout || |
|
514 !config->dbFieldExpiration)) |
|
515 autorefresh = 60*config->sessionTimeout; |
|
516 } |
|
517 if(autorefresh > 0) { |
|
518 apr_table_set(r->headers_out, "Refresh", |
|
519 PSTRCAT(r->pool, |
|
520 PSPRINTF(r->pool, "%d", autorefresh+1), |
|
521 (config->pageAutoRefresh)? |
|
522 ";url=":"", |
|
523 (config->pageAutoRefresh)? |
|
524 (char *)config->pageAutoRefresh:"", |
|
525 (use_page_expired)? |
|
526 ";url=":"", |
|
527 (use_page_expired)? |
|
528 (char *)config->pageExpired:"", |
|
529 (add_last_page_key)? |
|
530 "?":"", |
|
531 (add_last_page_key)? |
|
532 (char *)config->lastPageKey:"", |
|
533 (add_last_page_key)? |
|
534 "=":"", |
|
535 (add_last_page_key)? |
|
536 (char *)url_encode(r, r->unparsed_uri):"", |
|
537 NULL)); |
|
538 } |
|
539 if(config->sessionTimeout > 0) |
|
540 send_query(r, db_handle, "UPDATE %s SET %s=DATE_ADD(NOW(), INTERVAL %d MINUTE) " |
|
541 "WHERE %s", config->dbTableSID, config->dbFieldTimeout, |
|
542 config->sessionTimeout, condition); |
|
543 |
|
544 if(config->dbTableTracking) |
|
545 track_request(r, config, db_handle, uid); |
|
546 } |
|
547 else if(config->sessionTimeout > 0 || config->dbFieldExpiration) { |
|
548 // either the session has expired and/or the condition is not met |
|
549 rows = send_query(r, db_handle, "SELECT %s FROM %s WHERE (%s)", config->dbFieldUID, |
|
550 config->dbTableSID, condition); |
|
551 if(rows) |
|
552 *expired = TRUE; |
|
553 } |
|
554 |
|
555 if(config->sessionDelete) // remove all expired sessions |
|
556 send_query(r, db_handle, "DELETE FROM %s WHERE %s", |
|
557 config->dbTableSID, |
|
558 PSTRCAT(r->pool, |
|
559 (s_timeout_enabled)? |
|
560 (char *)config->dbFieldTimeout:"", |
|
561 (s_timeout_enabled)? |
|
562 "<NOW()":"", |
|
563 (config->sessionTimeout > 0 && config->dbFieldExpiration)? |
|
564 " OR ":"", |
|
565 (config->dbFieldExpiration)? |
|
566 (char *)config->dbFieldExpiration:"", |
|
567 (config->dbFieldExpiration)? |
|
568 "<NOW()":"", |
|
569 NULL)); |
|
570 |
|
571 return uid; |
|
572 } // form_check_session |
|
573 |
|
574 // |
|
575 // Check if user is member of at least one of the necessary user(s)/group(s). |
|
576 // If so, return TRUE; otherwise, return FALSE. |
|
577 // |
|
578 static int |
|
579 form_check_required(request_rec *r, auth_form_dir_config *config, MYSQL *db_handle, |
|
580 const unsigned char *uid) { |
|
581 const apr_array_header_t *reqs_arr = ap_requires(r); |
|
582 require_line *reqs = reqs_arr ? (require_line *)reqs_arr->elts : NULL; |
|
583 unsigned char *require = (reqs && reqs->requirement) ? reqs->requirement : NULL, |
|
584 *last_word = NULL; |
|
585 |
|
586 // |
|
587 // See if we need to check for user/group membership. |
|
588 // (Check for both for an existing 'Require' line, and |
|
589 // that the 'Require' line doesn't contain 'valid-user'). |
|
590 // |
|
591 if(!require || (strncmp(require, "valid-user", 10) == 0)) |
|
592 return OK; |
|
593 |
|
594 // |
|
595 // Do we require certain users or certain groups? |
|
596 // |
|
597 last_word = strrchr(require, ' '); |
|
598 last_word++; |
|
599 if(strncmp(require, "group", 5) == 0) { // it's groups |
|
600 unsigned char *gids = get_gids(r, config, db_handle, uid); |
|
601 if(gids) { // user has group membership(s) |
|
602 unsigned char *cur_gid = strtok(gids, " \t,"); |
|
603 require += 5; |
|
604 while(cur_gid) { // match each GID of the user |
|
605 unsigned char sp_gid_sp[MAX_STRING_LEN]; |
|
606 SNPRINTF(sp_gid_sp, sizeof(sp_gid_sp)-1, " %s ", cur_gid); |
|
607 if(strstr(require, sp_gid_sp) || |
|
608 (strcmp(last_word, cur_gid) == 0)) // found a group match! |
|
609 return OK; |
|
610 cur_gid = strtok(NULL, " \t,"); |
|
611 } |
|
612 // |
|
613 // No group matched |
|
614 // |
|
615 return redirect(r, config, config->pageNotAllowed, APLOG_INFO, |
|
616 "user '%s' has a non-matching group membership", uid); |
|
617 } |
|
618 else // no group membership found |
|
619 return redirect(r, config, config->pageNotAllowed, APLOG_INFO, |
|
620 "user '%s' does not have group membership", uid); |
|
621 } |
|
622 else if(strncmp(require, "user", 4) == 0) { // it's users |
|
623 // |
|
624 // Generate the phrase " user " where 'user' is the |
|
625 // name of the user. Use " user " as the matching substring. |
|
626 // Also, match "user" with the last word in the list. |
|
627 // (The same is applied above for group matching). |
|
628 // |
|
629 unsigned char sp_uid_sp[MAX_STRING_LEN]; |
|
630 SNPRINTF(sp_uid_sp, sizeof(sp_uid_sp)-1, " %s ", uid); |
|
631 require += 4; |
|
632 if(strstr(require, sp_uid_sp) || |
|
633 (strcmp(last_word, uid) == 0)) // found a user match! |
|
634 return OK; |
|
635 else |
|
636 return redirect(r, config, config->pageNotAllowed, APLOG_INFO, |
|
637 "user '%s' not listed under Require", uid); |
|
638 } |
|
639 |
|
640 // |
|
641 // If we're here, we have a bad Require line |
|
642 // |
|
643 return redirect(r, config, config->pageNotAllowed, APLOG_ERR, |
|
644 "invalid Require directive"); |
|
645 } // form_check_required |
|
646 |
|
647 // |
|
648 // Get list of groups from database (space, tab, and/or comma delimited). |
|
649 // If successful, the list of groups is returned; |
|
650 // otherwise NULL is returned. |
|
651 // |
|
652 static unsigned char * |
|
653 get_gids(request_rec *r, auth_form_dir_config *config, MYSQL *db_handle, |
|
654 const unsigned char *uid) { |
|
655 q_result *rows = NULL; |
|
656 unsigned char gids[MAX_STRING_LEN]; |
|
657 |
|
658 if(!config->dbTableGID) |
|
659 return NULL; |
|
660 |
|
661 // |
|
662 // Get the user's GIDs |
|
663 // |
|
664 if(config->dbTableGIDCondition) |
|
665 rows = send_query(r, db_handle, "SELECT %s FROM %s WHERE %s='%s' AND (%s)", |
|
666 config->dbFieldGID, config->dbTableGID, |
|
667 config->dbFieldUID, uid, |
|
668 parse_condition_vars(r, config->dbTableGIDCondition, |
|
669 config->sessionCookies)); |
|
670 else |
|
671 rows = send_query(r, db_handle, "SELECT %s FROM %s WHERE %s='%s'", |
|
672 config->dbFieldGID, config->dbTableGID, |
|
673 config->dbFieldUID, uid); |
|
674 |
|
675 if(rows) { // user has group membership(s) |
|
676 unsigned int i, ulen; |
|
677 SNPRINTF(gids, sizeof(gids)-1, "%s", rows->records[0][0]); |
|
678 ulen = strlen(gids); |
|
679 for(i = 1; i < rows->num_records; i++) { |
|
680 SNPRINTF(&gids[ulen], sizeof(gids)-ulen-1, ",%s", rows->records[i][0]); |
|
681 ulen += strlen(rows->records[i][0]) + 1; |
|
682 } |
|
683 } |
|
684 #ifdef MAF_DEBUG |
|
685 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "'%s' belongs to groups " |
|
686 "'%s'", uid, gids); |
|
687 #endif |
|
688 |
|
689 return (unsigned char *)PSTRDUP(r->pool, gids); |
|
690 } // get_gids |
|
691 |
|
692 // |
|
693 // Track the request into the database. |
|
694 // |
|
695 static void |
|
696 track_request(request_rec *r, auth_form_dir_config *config, MYSQL *db_handle, |
|
697 const unsigned char *uid) { |
|
698 q_result *rows = NULL; |
|
699 unsigned char *trackingCondition_parsed = NULL; |
|
700 |
|
701 if(config->dbTableTrackingCondition) |
|
702 trackingCondition_parsed = |
|
703 parse_condition_vars(r, config->dbTableTrackingCondition, |
|
704 config->sessionCookies); |
|
705 |
|
706 // |
|
707 // First, remove all the user's expired tracking records |
|
708 // (Based on download date). |
|
709 // |
|
710 if(config->trackingLifetime > 0) { |
|
711 if(trackingCondition_parsed) |
|
712 send_query(r, db_handle, "DELETE FROM %s WHERE %s='%s' AND DATE_ADD(%s, " |
|
713 "INTERVAL %d DAY)<NOW() AND (%s)", config->dbTableTracking, |
|
714 config->dbFieldUID, uid, config->dbFieldDownloadDate, |
|
715 config->trackingLifetime, trackingCondition_parsed); |
|
716 else |
|
717 send_query(r, db_handle, "DELETE FROM %s WHERE %s='%s' AND DATE_ADD(%s, " |
|
718 "INTERVAL %d DAY)<NOW()", config->dbTableTracking, |
|
719 config->dbFieldUID, uid, config->dbFieldDownloadDate, |
|
720 config->trackingLifetime); |
|
721 } |
|
722 // |
|
723 // Check for an existing tracking record and update it. |
|
724 // (Both UID and download path must match). |
|
725 // |
|
726 if(trackingCondition_parsed) |
|
727 rows = send_query(r, db_handle, "SELECT %s FROM %s WHERE %s='%s' AND %s='%s' AND (%s)", |
|
728 config->dbFieldUID, config->dbTableTracking, |
|
729 config->dbFieldUID, uid, config->dbFieldDownloadPath, |
|
730 r->unparsed_uri, trackingCondition_parsed); |
|
731 else |
|
732 rows = send_query(r, db_handle, "SELECT %s FROM %s WHERE %s='%s' AND %s='%s'", |
|
733 config->dbFieldUID, config->dbTableTracking, config->dbFieldUID, |
|
734 uid, config->dbFieldDownloadPath, r->unparsed_uri); |
|
735 if(rows) { // existing tracking record (update) |
|
736 if(trackingCondition_parsed) |
|
737 send_query(r, db_handle, "UPDATE %s SET %s='%s',%s=NOW(),%s='%ld' WHERE %s='%s' " |
|
738 "AND %s='%s' AND (%s)", config->dbTableTracking, |
|
739 config->dbFieldIPAddress, r->connection->remote_ip, |
|
740 config->dbFieldDownloadDate, config->dbFieldDownloadSize, |
|
741 (long int)r->finfo.size, config->dbFieldUID, uid, |
|
742 config->dbFieldDownloadPath, r->unparsed_uri, |
|
743 trackingCondition_parsed); |
|
744 else |
|
745 send_query(r, db_handle, "UPDATE %s SET %s='%s',%s=NOW(),%s='%ld' WHERE %s='%s' " |
|
746 "AND %s='%s'", config->dbTableTracking, config->dbFieldIPAddress, |
|
747 r->connection->remote_ip, config->dbFieldDownloadDate, |
|
748 config->dbFieldDownloadSize, (long int)r->finfo.size, |
|
749 config->dbFieldUID, uid, config->dbFieldDownloadPath, |
|
750 r->unparsed_uri); |
|
751 } |
|
752 else // create a new tracking record |
|
753 send_query(r, db_handle, "INSERT INTO %s (%s, %s, %s, %s, %s) VALUES ('%s', '%s', " |
|
754 "NOW(), '%s', '%ld')", config->dbTableTracking, config->dbFieldUID, |
|
755 config->dbFieldIPAddress, config->dbFieldDownloadDate, |
|
756 config->dbFieldDownloadPath, config->dbFieldDownloadSize, uid, |
|
757 r->connection->remote_ip, r->unparsed_uri, (long int)r->finfo.size); |
|
758 } // track_request |
|
759 |
|
760 |
|
761 // Database Functions // |
|
762 |
|
763 // |
|
764 // Open connection to DB server and select database. Return TRUE if |
|
765 // successful, FALSE if not able to connect or select database. If FALSE |
|
766 // is returned, the reason for failure is logged to error_log file. |
|
767 // Also, if a connection is made, but the database cannot be selected, |
|
768 // the opened connection will be closed. |
|
769 // |
|
770 // Upon successful completion, 'db_handle' is set. |
|
771 // |
|
772 static int |
|
773 open_db(request_rec *r, auth_form_dir_config *config, MYSQL **db_handle) { |
|
774 if(*db_handle && mysql_ping(*db_handle) != 0) // already open |
|
775 return TRUE; |
|
776 |
|
777 *db_handle = (MYSQL *)PCALLOC(r->pool, sizeof(MYSQL)); |
|
778 mysql_init(*db_handle); |
|
779 #ifdef MAF_MYSQL_SSL |
|
780 if(config->dbSsl) { |
|
781 mysql_ssl_set(*db_handle, config->dbSslKey, config->dbSslCert, |
|
782 config->dbSslCa, config->dbSslCaPath, config->dbSslCipherList); |
|
783 } |
|
784 #endif // #ifdef MAF_MYSQL_SSL |
|
785 if(config->dbSocket != NULL) { |
|
786 if(!mysql_real_connect(*db_handle, NULL, config->dbUsername, |
|
787 config->dbPassword, NULL, 0, config->dbSocket, 0)) { |
|
788 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
|
789 "MySQL ERROR: %s: %s", mysql_error(*db_handle), |
|
790 r->unparsed_uri); |
|
791 return FALSE; |
|
792 } |
|
793 } |
|
794 else { |
|
795 if(!mysql_real_connect(*db_handle, config->dbHost, config->dbUsername, |
|
796 config->dbPassword, NULL, config->dbPort, NULL, 0)) { |
|
797 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
|
798 "MySQL ERROR: %s: %s", mysql_error(*db_handle), |
|
799 r->unparsed_uri); |
|
800 return FALSE; |
|
801 } |
|
802 } |
|
803 |
|
804 // |
|
805 // Try to select the database. If not successful, |
|
806 // close the 'mysql_handle'. |
|
807 // |
|
808 if(mysql_select_db(*db_handle, config->dbName) != 0) { |
|
809 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
|
810 "MySQL ERROR %s: %s", mysql_error(*db_handle), |
|
811 r->unparsed_uri); |
|
812 close_db(db_handle); |
|
813 return FALSE; |
|
814 } |
|
815 return TRUE; |
|
816 } |
|
817 |
|
818 // |
|
819 // Close the database handle if it's not already closed. |
|
820 // |
|
821 static void |
|
822 close_db(MYSQL **db_handle) { |
|
823 if(*db_handle) mysql_close(*db_handle); |
|
824 *db_handle = NULL; |
|
825 } // close_db |
|
826 |
|
827 // |
|
828 // Send a database query and, if present, |
|
829 // return the resulting rows (NULL otherwise). |
|
830 // |
|
831 static q_result * |
|
832 send_query(request_rec *r, MYSQL *db_handle, unsigned char *query_format, ...) { |
|
833 q_result *rows = NULL; |
|
834 MYSQL_RES *result; |
|
835 unsigned char query[MAX_STRING_LEN]; |
|
836 va_list arg_list; |
|
837 |
|
838 va_start(arg_list, query_format); |
|
839 VSNPRINTF(query, sizeof(query)-1, query_format, arg_list); |
|
840 va_end(arg_list); |
|
841 |
|
842 #ifdef MAF_DEBUG |
|
843 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Sending query " |
|
844 "'%s'", query); |
|
845 #endif |
|
846 if(mysql_real_query(db_handle, query, strlen(query)) != 0) { |
|
847 ap_log_rerror(APLOG_MARK, APLOG_CRIT, 0, r, |
|
848 "MySQL ERROR: %s: %s: %s", query, mysql_error(db_handle), |
|
849 r->unparsed_uri); |
|
850 return NULL; |
|
851 } |
|
852 |
|
853 if((result = mysql_store_result(db_handle)) != NULL) { |
|
854 unsigned int num_rows = (unsigned int)mysql_num_rows(result), |
|
855 num_fields = mysql_num_fields(result); |
|
856 register unsigned int i; |
|
857 |
|
858 if(num_rows >= 1) { |
|
859 rows = (q_result *)PCALLOC(r->pool, sizeof(q_result)); |
|
860 rows->num_records = num_rows; |
|
861 rows->num_fields = num_fields; |
|
862 rows->records = (unsigned char ***)PCALLOC(r->pool, sizeof(unsigned int **) * rows->num_records); |
|
863 for(i = 0; i < rows->num_records; i++) { |
|
864 register unsigned int j; |
|
865 unsigned char **cur_row = (unsigned char **)mysql_fetch_row(result); |
|
866 rows->records[i] = (unsigned char **)PCALLOC(r->pool, sizeof(unsigned int *) * rows->num_fields); |
|
867 for(j = 0; j < rows->num_fields; j++) |
|
868 rows->records[i][j] = (unsigned char *)PSTRDUP(r->pool, cur_row[j]); |
|
869 } |
|
870 } |
|
871 mysql_free_result(result); |
|
872 } |
|
873 |
|
874 return rows; |
|
875 } // send_query |
|
876 |
|
877 |
|
878 // Utility Functions // |
|
879 |
|
880 // |
|
881 // Redirect to a specified page |
|
882 // |
|
883 static int |
|
884 redirect(request_rec *r, auth_form_dir_config *config, const unsigned char *page, int log_level, |
|
885 const unsigned char *reason_format, ...) { |
|
886 va_list arg_list; |
|
887 unsigned char reason[MAX_STRING_LEN]; |
|
888 va_start(arg_list, reason_format); |
|
889 VSNPRINTF(reason,sizeof(reason)-1,reason_format,arg_list); |
|
890 va_end(arg_list); |
|
891 if(page) { |
|
892 unsigned char *new_url, *fragment; |
|
893 fragment = strchr(page, '#'); |
|
894 if(config->lastPageKey) { |
|
895 unsigned char *qora; |
|
896 if(strchr(page, '?')) |
|
897 qora = "&"; |
|
898 else |
|
899 qora = "?"; |
|
900 new_url = PSTRCAT(r->pool, page, |
|
901 qora, config->lastPageKey, "=", |
|
902 url_encode(r, r->unparsed_uri), |
|
903 (fragment)? |
|
904 (char *)fragment:"", |
|
905 NULL); |
|
906 } |
|
907 else |
|
908 new_url = PSTRDUP(r->pool, page); |
|
909 if(!fragment) |
|
910 new_url = PSTRCAT(r->pool, new_url, "##", NULL); |
|
911 #ifdef MAF_DEBUG |
|
912 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Redirect from " |
|
913 "%s to %s", r->unparsed_uri, new_url); |
|
914 #endif |
|
915 apr_table_set(r->headers_out, "Location", new_url); |
|
916 ap_log_rerror(APLOG_MARK, log_level, 0, r, |
|
917 "AuthForm: %s - %s: redirect to %s", |
|
918 r->the_request, reason, page); |
|
919 return HTTP_MOVED_TEMPORARILY; |
|
920 } |
|
921 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, |
|
922 "AuthForm: %s - %s: redirecting from %s: Target" |
|
923 "page not specified", |
|
924 r->the_request, reason, page); |
|
925 return HTTP_FORBIDDEN; |
|
926 } // redirect |
|
927 |
|
928 // |
|
929 // Parse out the variables (formatted as $var) in the condition string |
|
930 // (i.e. replace the variables with the values sent by the client). |
|
931 // |
|
932 // Returns the parsed boolean expression. |
|
933 // If unsuccessful, NULL is returned. |
|
934 // |
|
935 static unsigned char * |
|
936 parse_condition_vars(request_rec *r, const unsigned char *condition, int cookies) { |
|
937 const unsigned char *key_values = NULL; |
|
938 unsigned char parsed_str[MAX_STRING_LEN], var[MAX_VAR_LEN], terminator, ender, |
|
939 padding[2]; |
|
940 int len = strlen(condition), chr, pchr = 0, vchr = 0, |
|
941 mode = 0, escaping = FALSE; |
|
942 |
|
943 if(cookies) { |
|
944 key_values = apr_table_get(r->headers_in, "Cookie"); |
|
945 terminator = ';'; |
|
946 padding[0] = ' '; |
|
947 } |
|
948 else { |
|
949 key_values = r->args; |
|
950 terminator = '&'; |
|
951 padding[0] = '\0'; |
|
952 } |
|
953 padding[1] = '\0'; |
|
954 |
|
955 for(chr = 0; chr < len; chr++) { |
|
956 switch(mode) { |
|
957 case 0: // Normal mode |
|
958 if(condition[chr] != '$') { |
|
959 parsed_str[pchr] = condition[chr]; pchr++; |
|
960 if(condition[chr] == '\'' || condition[chr] == '\"') { |
|
961 mode = 1; // switch to String mode |
|
962 ender = condition[chr]; |
|
963 } |
|
964 } |
|
965 else { |
|
966 vchr = 0; |
|
967 mode = 2; // switch to Variable mode |
|
968 } |
|
969 break; |
|
970 |
|
971 case 1: // String mode |
|
972 parsed_str[pchr] = condition[chr]; pchr++; |
|
973 if(condition[chr] == ender && !escaping) |
|
974 mode = 0; // switch to Normal mode |
|
975 if(condition[chr] == '\\' && !escaping) |
|
976 escaping = TRUE; |
|
977 else |
|
978 escaping = FALSE; |
|
979 break; |
|
980 |
|
981 case 2: // Variable mode |
|
982 if(condition[chr] != ' ' && chr != (len - 1)) { |
|
983 var[vchr] = condition[chr]; vchr++; |
|
984 } |
|
985 else { |
|
986 unsigned char *value = NULL; |
|
987 unsigned int val_len = 0, i; |
|
988 if(condition[chr] != ' ') { |
|
989 var[vchr] = condition[chr]; vchr++; |
|
990 } |
|
991 var[vchr] = '\0'; |
|
992 |
|
993 if(key_values != NULL) |
|
994 value = get_value(r, key_values, var, terminator, padding); |
|
995 if(value == NULL) |
|
996 value = ""; |
|
997 else |
|
998 val_len = strlen(value); |
|
999 |
|
1000 // |
|
1001 // Replace the variable with its single-quoted value |
|
1002 // |
|
1003 SNPRINTF(&parsed_str[pchr], 2, "'"); pchr++; |
|
1004 for(i=0; i<val_len; i++) { // copy the value while escaping apostrophes |
|
1005 if(value[i] == '\\') { // skip escapes (including apostrophes) |
|
1006 parsed_str[pchr] = value[i]; |
|
1007 parsed_str[pchr+1] = value[i+1]; |
|
1008 pchr += 2; |
|
1009 i++; |
|
1010 } |
|
1011 else if(value[i] == '\'') { // escape the apostrophe |
|
1012 parsed_str[pchr] = '\\'; |
|
1013 parsed_str[pchr+1] = '\''; |
|
1014 pchr += 2; |
|
1015 } |
|
1016 else { // copy the character |
|
1017 parsed_str[pchr] = value[i]; |
|
1018 pchr++; |
|
1019 } |
|
1020 } |
|
1021 if(condition[chr] != ' ') { |
|
1022 SNPRINTF(&parsed_str[pchr], 2, "'"); pchr++; |
|
1023 } |
|
1024 else { |
|
1025 SNPRINTF(&parsed_str[pchr], 3, "' "); pchr += 2; |
|
1026 } |
|
1027 |
|
1028 mode = 0; // switch to Normal mode |
|
1029 } |
|
1030 break; |
|
1031 } |
|
1032 } |
|
1033 parsed_str[pchr] = '\0'; |
|
1034 |
|
1035 return (unsigned char *)PSTRDUP(r->pool, parsed_str); |
|
1036 } // parse_condition_vars |
|
1037 |
|
1038 // |
|
1039 // Extract a value from a key given |
|
1040 // a string, a terminating character, and the name |
|
1041 // of the key |
|
1042 // |
|
1043 // If successful, return the key's value; |
|
1044 // otherwise, NULL is returned. |
|
1045 // |
|
1046 static unsigned char * |
|
1047 get_value(request_rec *r, const unsigned char *key_values, const unsigned char *key, |
|
1048 unsigned char terminator, const unsigned char *padding) { |
|
1049 if(key_values && key) { |
|
1050 unsigned int pad_len = strlen(padding); |
|
1051 unsigned char key_equal[MAX_STRING_LEN]; |
|
1052 unsigned const char *key_begin = NULL, *value_begin = NULL; |
|
1053 SNPRINTF(key_equal, sizeof(key_equal)-1, "%c%s%s=", terminator, padding, key); |
|
1054 |
|
1055 key_begin = strstr(key_values, key_equal); |
|
1056 |
|
1057 if(key_begin == NULL && |
|
1058 strncmp(&key_equal[pad_len+1], &key_values[0], |
|
1059 strlen(&key_equal[pad_len+1])) == 0) { |
|
1060 key_begin = key_values; |
|
1061 value_begin = key_begin + strlen(key_equal) - pad_len - 1; |
|
1062 } |
|
1063 else |
|
1064 value_begin = key_begin + strlen(key_equal); |
|
1065 if(key_begin != NULL) { |
|
1066 unsigned char *value = (unsigned char *)PSTRDUP(r->pool, value_begin), |
|
1067 *value_end = strchr(value, terminator); |
|
1068 if(value_end) |
|
1069 value_end[0] = '\0'; |
|
1070 return url_decode(r, value); |
|
1071 } |
|
1072 } |
|
1073 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, |
|
1074 "AuthForm: %s - Could not find value for client key" |
|
1075 " '%s'", r->the_request, key); |
|
1076 |
|
1077 return NULL; |
|
1078 } // get_value |
|
1079 |
|
1080 // |
|
1081 // Build a full URI for the current request. |
|
1082 // |
|
1083 static unsigned char * |
|
1084 construct_full_uri(request_rec *r) { |
|
1085 unsigned int port = ap_get_server_port(r); |
|
1086 const unsigned char *xforwarded = apr_table_get(r->headers_in, "X-Forwarded-Server"); |
|
1087 |
|
1088 return PSTRCAT(r->pool, |
|
1089 #ifdef ap_http_scheme |
|
1090 ap_http_scheme(r), |
|
1091 #else |
|
1092 ap_http_method(r), |
|
1093 #endif |
|
1094 "://", (xforwarded == NULL)?ap_get_server_name(r):(char *)xforwarded, |
|
1095 (!ap_is_default_port(port, r))? |
|
1096 PSPRINTF(r->pool, ":%u", port):"", |
|
1097 r->parsed_uri.path, |
|
1098 (r->parsed_uri.query)? |
|
1099 "?":"", |
|
1100 (r->parsed_uri.query)? |
|
1101 r->parsed_uri.query:"", |
|
1102 (r->parsed_uri.fragment)? |
|
1103 "#":"", |
|
1104 (r->parsed_uri.fragment)? |
|
1105 r->parsed_uri.fragment:"", |
|
1106 NULL); |
|
1107 } // construct_full_uri |
|
1108 |
|
1109 // |
|
1110 // URL-Encoder |
|
1111 // |
|
1112 static unsigned char * |
|
1113 url_encode(request_rec *r, const unsigned char *uri) { |
|
1114 unsigned char uri_enc[MAX_STRING_LEN]; |
|
1115 unsigned int cchar = 0, cchare = 0; |
|
1116 while(uri[cchar] != '\0' && cchar < MAX_STRING_LEN) { |
|
1117 if(uri[cchar] <= 32 || |
|
1118 (uri[cchar] >= 34 && uri[cchar] <= 38) || |
|
1119 uri[cchar] == 43 || |
|
1120 uri[cchar] == 44 || |
|
1121 uri[cchar] == 47 || |
|
1122 (uri[cchar] >= 58 && uri[cchar] <= 64) || |
|
1123 (uri[cchar] >= 91 && uri[cchar] <= 94) || |
|
1124 uri[cchar] == 96 || |
|
1125 (uri[cchar] >= 123 && uri[cchar] <= 126) || |
|
1126 (uri[cchar] >= 128 && uri[cchar] <= 225)) { |
|
1127 SNPRINTF(&uri_enc[cchare], sizeof(uri_enc)-cchare-1, "%%%02.2X", |
|
1128 uri[cchar]); |
|
1129 cchare += 3; |
|
1130 } |
|
1131 else { |
|
1132 uri_enc[cchare] = uri[cchar]; |
|
1133 uri_enc[cchare+1] = '\0'; |
|
1134 cchare++; |
|
1135 } |
|
1136 cchar++; |
|
1137 } |
|
1138 return (unsigned char *)PSTRDUP(r->pool, uri_enc); |
|
1139 } // url_encode |
|
1140 |
|
1141 // |
|
1142 // URL-Decoder |
|
1143 // |
|
1144 static unsigned char * |
|
1145 url_decode(request_rec *r, const unsigned char *uri_enc) { |
|
1146 unsigned char uri[MAX_STRING_LEN]; |
|
1147 unsigned int cchar = 0, cchard = 0; |
|
1148 while(uri_enc[cchar] != '\0' && cchar < MAX_STRING_LEN) { |
|
1149 if(uri_enc[cchar] == '%') { |
|
1150 unsigned char hex_str[3] = {uri_enc[cchar+1], uri_enc[cchar+2], '\0'}; |
|
1151 uri[cchard] = (unsigned char)strtol(hex_str, NULL, 16); |
|
1152 uri[cchard+1] = '\0'; |
|
1153 cchard++; |
|
1154 cchar += 3; |
|
1155 } |
|
1156 else { |
|
1157 uri[cchard] = uri_enc[cchar]; |
|
1158 uri[cchard+1] = '\0'; |
|
1159 cchard++; |
|
1160 cchar++; |
|
1161 } |
|
1162 } |
|
1163 return (unsigned char *)PSTRDUP(r->pool, uri); |
|
1164 } // url_decode |