mod_auth_form-2.05/src/mod_auth_form.c
changeset 11 022ee48c7409
equal deleted inserted replaced
10:1c27769b8435 11:022ee48c7409
       
     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