1 /* ====================================================================
2  * Copyright (c) 1995 The Apache Group.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in
13  *    the documentation and/or other materials provided with the
14  *    distribution.
15  *
16  * 3. All advertising materials mentioning features or use of this
17  *    software must display the following acknowledgment:
18  *    "This product includes software developed by the Apache Group
19  *    for use in the Apache HTTP server project (http://www.apache.org/)."
20  *
21  * 4. The names "Apache Server" and "Apache Group" must not be used to
22  *    endorse or promote products derived from this software without
23  *    prior written permission.
24  *
25  * 5. Redistributions of any form whatsoever must retain the following
26  *    acknowledgment:
27  *    "This product includes software developed by the Apache Group
28  *    for use in the Apache HTTP server project (http://www.apache.org/)."
29  *
30  * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
31  * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
33  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
34  * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
38  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
41  * OF THE POSSIBILITY OF SUCH DAMAGE.
42  * ====================================================================
43  *
44  * This software consists of voluntary contributions made by many
45  * individuals on behalf of the Apache Group and was originally based
46  * on public domain software written at the National Center for
47  * Supercomputing Applications, University of Illinois, Urbana-Champaign.
48  * For more information on the Apache Group and the Apache HTTP server
49  * project, please see <http://www.apache.org/>.
50  *
51  */
52 
53 
54 /*
55  * Module definition information - the part between the -START and -END
56  * lines below is used by Configure. This could be stored in a separate
57  * instead.
58  *
59  * MODULE-DEFINITION-START
60  * Name: mysql_auth_module
61  * ConfigStart
62      MYSQL_LIB="-L/usr/local/lib/mysql -lmysqlclient -lm -lz"
63      if [ "X$MYSQL_LIB" != "X" ]; then
64          LIBS="$LIBS $MYSQL_LIB"
65          echo " + using $MYSQL_LIB for MySQL support"
66      fi
67  * ConfigEnd
68  * MODULE-DEFINITION-END
69  */
70 
71 #define STRING(x) STR(x)		/* Used to build strings from compile options */
72 #define STR(x) #x
73 
74 #include "ap_mmn.h"			/* For MODULE_MAGIC_NUMBER */
75 /* Use the MODULE_MAGIC_NUMBER to check if at least Apache 2.0 */
76 #if AP_MODULE_MAGIC_AT_LEAST(20010223,0)
77   #define APACHE2
78 #endif
79 
80 /* Compile time options for code generation */
81 #ifdef AES
82   #define _AES 1
83 #else
84   #define _AES 0
85 #endif
86 /* set any defaults not specified at compile time */
87 #ifdef HOST				/* Host to use */
88   #define _HOST STRING(HOST)
89 #else
90   #define _HOST 0			/* Will default to localhost */
91 #endif
92 
93 /* Apache 1.x defines the port as a string, but Apache 2.x uses an integer */
94 #ifdef PORT				/* The port to use */
95   #ifdef APACHE2
96     #define _PORT PORT
97   #else
98     #define _PORT STRING(PORT)
99   #endif
100 #else
101   /* MariaDB as of 10.2 does not declare MYSQL_PORT for the client */
102   #ifndef MYSQL_PORT
103     #define MYSQL_PORT 3306
104   #endif
105   #ifdef APACHE2
106     #define _PORT MYSQL_PORT		/* Use the one from MySQL */
107   #else
108     #define _PORT STRING(MYSQL_PORT)
109   #endif
110 #endif
111 
112 #ifdef SOCKET				/* UNIX socket */
113   #define _SOCKET STRING(SOCKET)
114 #else
115   /* MariaDB as of 10.2 does not declare MYSQL_UNIX_ADDR for the client */
116   #ifndef MYSQL_UNIX_ADDR
117     #define MYSQL_UNIX_ADDR STRING(/tmp/mysql.sock)
118   #endif
119   #define _SOCKET MYSQL_UNIX_ADDR
120 #endif
121 
122 #ifdef USER				/* Authorized user */
123   #define _USER STRING(USER)
124 #else
125   #define _USER 0			/* User must be specified in config */
126 #endif
127 
128 #ifdef PASSWORD				/* Default password */
129   #define _PASSWORD STRING(PASSWORD)
130 #else
131   #define _PASSWORD 0			/* Password must be specified in config */
132 #endif
133 
134 #ifdef DB				/* Default database */
135   #define _DB STRING(DB)
136 #else
137   #define _DB "test"			/* Test database */
138 #endif
139 
140 #ifdef PWTABLE				/* Password table */
141   #define _PWTABLE STRING(PWTABLE)
142 #else
143   #define _PWTABLE "user_info" 		/* Default is user_info */
144 #endif
145 
146 #ifdef NAMEFIELD			/* Name column in password table */
147   #define _NAMEFIELD STRING(NAMEFIELD)
148 #else
149   #define _NAMEFIELD "user_name"	/* Default is "user_name" */
150 #endif
151 
152 #ifdef PASSWORDFIELD			/* Password column in password table */
153   #define _PASSWORDFIELD STRING(PASSWORDFIELD)
154 #else
155   #define _PASSWORDFIELD "user_password" /* Default is user_password */
156 #endif
157 
158 #ifdef GROUPUSERNAMEFIELD
159   #define _GROUPUSERNAMEFIELD STRING(GROUPUSERNAMEFIELD)
160 #else
161   #define _GROUPUSERNAMEFIELD NULL
162 #endif
163 
164 #ifdef ENCRYPTION			/* Encryption type */
165   #define _ENCRYPTION STRING(ENCRYPTION)
166 #else
167   #define _ENCRYPTION 0			/* Will default to "crypt" in code */
168 #endif
169 
170 #ifdef SALTFIELD			/* If a salt column is not defined */
171   #define _SALTFIELD STRING(SALTFIELD)
172 #else
173   #define _SALTFIELD "<>"		/* Default is no salt */
174 #endif
175 
176 #ifdef KEEPALIVE			/* Keep the connection alive */
177   #define _KEEPALIVE KEEPALIVE
178 #else
179   #define _KEEPALIVE 0			/* Do not keep it alive */
180 #endif
181 
182 #ifdef AUTHORITATIVE			/* If we are the last word */
183   #define _AUTHORITATIVE AUTHORITATIVE
184 #else
185   #define _AUTHORITATIVE 1 		/* Yes, we are */
186 #endif
187 
188 #ifdef NOPASSWORD			/* If password not needed */
189   #define _NOPASSWORD NOPASSWORD
190 #else
191   #define _NOPASSWORD 0			/* It is required */
192 #endif
193 
194 #ifdef ENABLE				/* If we are to be enabled */
195   #define _ENABLE ENABLE
196 #else
197   #define _ENABLE 1			/* Assume we are */
198 #endif
199 
200 #ifdef CHARACTERSET
201   #define _CHARACTERSET STRING(CHARACTERSET)
202 #else
203   #define _CHARACTERSET NULL		/* Default is no character set */
204 #endif
205 
206 #include "httpd.h"
207 #include "http_config.h"
208 #include "http_core.h"
209 #include "http_log.h"
210 #include "http_protocol.h"
211 
212 #ifdef APACHE2
213   #define PCALLOC apr_pcalloc
214   #define SNPRINTF apr_snprintf
215   #define PSTRDUP apr_pstrdup
216   #define PSTRNDUP apr_pstrndup
217   #define STRCAT apr_pstrcat
218   #define POOL apr_pool_t
219   #include "http_request.h"   /* for ap_hook_(check_user_id | auth_checker)*/
220   #include "ap_compat.h"
221   #include "apr_strings.h"
222   #include "apr_sha1.h"
223   #include "apr_base64.h"
224   #include "apr_lib.h"
225   #define ISSPACE apr_isspace
226   #ifdef CRYPT
227     #include "crypt.h"
228   #else
229     #include "unistd.h"
230   #endif
231   #define LOG_ERROR(lvl, stat, rqst, msg)  \
232 	  ap_log_rerror (APLOG_MARK, lvl, stat, rqst, msg)
233   #define LOG_ERROR_1(lvl, stat, rqst, msg, parm)  \
234 	  ap_log_rerror (APLOG_MARK, lvl, stat, rqst, msg, parm)
235   #define LOG_ERROR_2(lvl, stat, rqst, msg, parm1, parm2)  \
236 	  ap_log_rerror (APLOG_MARK, lvl, stat, rqst, msg, parm1, parm2)
237   #define LOG_ERROR_3(lvl, stat, rqst, msg, parm1, parm2, parm3)  \
238 	  ap_log_rerror (APLOG_MARK, lvl, stat, rqst, msg, parm1, parm2, parm3)
239   #define APACHE_FUNC static apr_status_t
240   #define APACHE_FUNC_RETURN(rc) return rc
241   #define NOT_AUTHORIZED HTTP_UNAUTHORIZED
242   #define TABLE_GET apr_table_get
243 #else
244   #define PCALLOC ap_pcalloc
245   #define SNPRINTF ap_snprintf
246   #define PSTRDUP ap_pstrdup
247   #define PSTRNDUP ap_pstrndup
248   #define STRCAT apr_pstrcat
249   #define POOL pool
250   #include <stdlib.h>
251   #include "ap_sha1.h"
252   #include "ap_ctype.h"
253   #define LOG_ERROR(lvl, stat, rqst, msg) \
254 	  ap_log_error(APLOG_MARK, lvl, rqst->server, msg)
255   #define LOG_ERROR_1(lvl, stat, rqst, msg, parm) \
256 	  ap_log_error(APLOG_MARK, lvl, rqst->server, msg, parm)
257   #define LOG_ERROR_2(lvl, stat, rqst, msg, parm1, parm2) \
258 	  ap_log_error(APLOG_MARK, lvl, rqst->server, msg, parm1, parm2)
259   #define LOG_ERROR_3(lvl, stat, rqst, msg, parm1, parm2, parm3) \
260 	  ap_log_error(APLOG_MARK, lvl, rqst->server, msg, parm1, parm2, parm3)
261   #define APACHE_FUNC static void
262   #define APACHE_FUNC_RETURN(rc) return
263   #define NOT_AUTHORIZED AUTH_REQUIRED
264   #define TABLE_GET ap_table_get
265   #define ISSPACE ap_isspace
266 #endif
267 
268 #include "util_md5.h"
269 #ifndef APACHE2
270 /* Both Apache 1's ap_config.h and my_global.h define closesocket (to the same value) */
271 /* This gets rid of a warning message.  It's OK because we don't use it anyway */
272   #undef closesocket
273 #endif
274 #if _AES  /* Only needed if AES encryption desired */
275   #include <my_global.h>
276 #endif
277 #include <mysql.h>
278 #if _AES
279   #include <my_aes.h>
280 #endif
281 
282 #ifndef SCRAMBLED_PASSWORD_CHAR_LENGTH /* Ensure it is defined for older MySQL releases */
283   #define SCRAMBLED_PASSWORD_CHAR_LENGTH 32 /* Big enough for the old method of scrambling */
284 #endif
285 
286 /* salt flags */
287 #define NO_SALT		      0
288 #define SALT_OPTIONAL	      1
289 #define SALT_REQUIRED	      2
290 
291 /* forward function declarations */
292 static short pw_scrambled(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt);
293 static short pw_md5(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt);
294 static short pw_crypted(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt);
295 #if _AES
296 static short pw_aes(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt);
297 #endif
298 static short pw_sha1(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt);
299 static short pw_plain(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt);
300 
301 static char * format_remote_host(request_rec * r, char ** parm);
302 static char * format_remote_ip(request_rec * r, char ** parm);
303 static char * format_filename(request_rec * r, char ** parm);
304 static char * format_server_name(request_rec * r, char ** parm);
305 static char * format_server_hostname(request_rec * r, char ** parm);
306 static char * format_protocol(request_rec * r, char ** parm);
307 static char * format_method(request_rec * r, char ** parm);
308 static char * format_args(request_rec * r, char ** parm);
309 static char * format_request(request_rec * r, char ** parm);
310 static char * format_uri(request_rec * r, char ** parm);
311 static char * format_percent(request_rec * r, char ** parm);
312 static char * format_cookie(request_rec * r, char ** parm);
313 
314 
315 typedef struct {	      /* Encryption methods */
316   char * string; 	      /* Identifing string */
317   short salt_status;	      /* If a salt is required, optional or unused */
318   short (*func)(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt);
319 } encryption ;
320 
321 /* Encryption methods used.  The first entry is the default entry */
322 static encryption encryptions[] = {{"crypt", SALT_OPTIONAL, pw_crypted},
323 					   {"none", NO_SALT, pw_plain},
324 					   {"scrambled", NO_SALT, pw_scrambled},
325 					   {"md5", NO_SALT, pw_md5},
326 #if _AES
327 					   {"aes", SALT_REQUIRED, pw_aes},
328 #endif
329 					   {"sha1", NO_SALT, pw_sha1}};
330 typedef struct {		/* User formatting patterns */
331   char pattern;			/* Pattern to match */
332   char * (*func)(request_rec * r, char ** parm);
333 } format;
334 
335 format formats[] = {{'h', format_remote_host},
336 	            {'a', format_remote_ip},
337 		    {'f', format_filename},
338 		    {'V', format_server_name},
339 		    {'v', format_server_hostname},
340 		    {'H', format_protocol},
341 		    {'m', format_method},
342 		    {'q', format_args},
343 		    {'r', format_request},
344 		    {'U', format_uri},
345 		    {'%', format_percent},
346 		    {'C', format_cookie}};
347 /*
348  * structure to hold the configuration details for the request
349  */
350 typedef struct  {
351   char *mysqlhost;		/* host name of db server */
352 #ifdef APACHE2
353   int  mysqlport;		/* port number of db server */
354 #else
355   char * mysqlport;		/* port number of db server */
356 #endif
357   char *mysqlsocket;		/* socket path of db server */
358   char *mysqluser;		/* user ID to connect to db server */
359   char *mysqlpasswd;		/* password to connect to db server */
360   char *mysqlDB;		/* DB name */
361   char *mysqlpwtable;		/* user password table */
362   char *mysqlgrptable;		/* user group table */
363   char *mysqlNameField;		/* field in password table with username */
364   char *mysqlPasswordField;	/* field in password table with password */
365   char *mysqlGroupField;	/* field in group table with group name */
366   char *mysqlGroupUserNameField;/* field in group table with username */
367   char *mysqlEncryptionField;   /* encryption type for passwords */
368   char *mysqlSaltField;		/* salt for scrambled password */
369   int  mysqlKeepAlive;		/* keep connection persistent? */
370   int  mysqlAuthoritative;	/* are we authoritative? */
371   int  mysqlNoPasswd;		/* do we ignore password? */
372   int  mysqlEnable;		/* do we bother trying to auth at all? */
373   char *mysqlUserCondition; 	/* Condition to add to the user where-clause in select query */
374   char *mysqlGroupCondition; 	/* Condition to add to the group where-clause in select query */
375   char *mysqlCharacterSet;	/* MySQL character set to use */
376 } mysql_auth_config_rec;
377 
378 /*
379  * Global information for the database connection.  Contains
380  * the host name, userid and database name used to open the
381  * connection.  If handle is not null, assume it is
382  * still valid.  MySQL in recent incarnations will re-connect
383  * automaticaly if the connection is closed, so we don't have
384  * to worry about that here.
385  */
386 typedef struct {
387   MYSQL * handle;
388   char host [255];
389   char user [255];
390   char db [255];
391   time_t last_used;
392 } mysql_connection;
393 
394 static mysql_connection connection = {NULL, "", "", ""};
395 
396 /*
397  * Global handle to db.  If not null, assume it is still valid.
398  * MySQL in recent incarnations will re-connect automatically if the
399  * connection is closed, so we don't worry about that here.
400  */
401 /* static MYSQL *mysql_handle = NULL; */
402 
close_connection()403 static void close_connection() {
404   if (connection.handle)
405     mysql_close(connection.handle);
406   connection.handle = NULL;		/* make sure we don't try to use it later */
407   return;
408 }
409 
410 /*
411  * Callback to close mysql handle when necessary.  Also called when a
412  * child httpd process is terminated.
413  */
414 APACHE_FUNC
mod_auth_mysql_cleanup(void * notused)415 mod_auth_mysql_cleanup (void *notused)
416 {
417   close_connection();
418   APACHE_FUNC_RETURN(0);
419 }
420 
421 /*
422  * empty function necessary because register_cleanup requires it as one
423  * of its parameters
424  */
425 APACHE_FUNC
mod_auth_mysql_cleanup_child(void * data)426 mod_auth_mysql_cleanup_child (void *data)
427 {
428   /* nothing */
429   APACHE_FUNC_RETURN(0);
430 }
431 
432 
433 #ifndef APACHE2
434 /*
435  * handler to do cleanup on child exit
436  */
437 static void
child_exit(server_rec * s,pool * p)438 child_exit(server_rec *s, pool *p)
439 {
440   mod_auth_mysql_cleanup(NULL);
441 }
442 #endif
443 
444 
445 
446 #ifndef TRUE
447 #define TRUE 1
448 #endif
449 #ifndef FALSE
450 #define FALSE 0
451 #endif
452 
453 /*
454  * open connection to DB server if necessary.  Return TRUE if connection
455  * is good, FALSE if not able to connect.  If false returned, reason
456  * for failure has been logged to error_log file already.
457  */
458 static int
open_db_handle(request_rec * r,mysql_auth_config_rec * m)459 open_db_handle(request_rec *r, mysql_auth_config_rec *m)
460 {
461   static MYSQL mysql_conn;
462   char query[MAX_STRING_LEN];
463   short host_match = FALSE;
464   short user_match = FALSE;
465 
466   if (connection.handle) {
467 
468     /* See if the host has changed */
469     if (!m->mysqlhost || (strcmp(m->mysqlhost, "localhost") == 0)) {
470       if (connection.host[0] == '\0')
471         host_match = TRUE;
472     }
473     else
474       if (m->mysqlhost && (strcmp(m->mysqlhost, connection.host) == 0))
475 	host_match = TRUE;
476 
477     /* See if the user has changed */
478     if (m->mysqluser) {
479       if (strcmp(m->mysqluser, connection.user) == 0)
480 	user_match = TRUE;
481     }
482     else
483       if (connection.user[0] == '\0')
484         user_match = TRUE;
485 
486     /* if the host, or user have changed, need to close and reopen database connection */
487     if (host_match && user_match) {
488       /* If the database hasn't changed, we can just return */
489       if (m->mysqlDB && strcmp(m->mysqlDB, connection.db) == 0)
490 	return TRUE; /* already open */
491 
492       /* Otherwise we need to reselect the database */
493       else {
494 	if (mysql_select_db(connection.handle,m->mysqlDB) != 0) {
495 	  LOG_ERROR_1(APLOG_ERR, 0, r, "MySQL ERROR: %s", mysql_error(connection.handle));
496 	  return FALSE;
497 	}
498 	else {
499 	  strcpy (connection.db, m->mysqlDB);
500 	  return TRUE;
501 	}
502       }
503     }
504     else
505       close_connection();
506   }
507 
508   connection.handle = mysql_init(&mysql_conn);
509   if (! connection.handle) {
510     LOG_ERROR_1(APLOG_ERR, 0, r, "MySQL ERROR: %s", mysql_error(&mysql_conn));
511   }
512 
513   if (!m->mysqlhost || strcmp(m->mysqlhost,"localhost") == 0) {
514     connection.host[0] = '\0';
515   } else {
516     strcpy(connection.host, m->mysqlhost);
517   }
518 
519 #ifdef APACHE2
520   connection.handle=mysql_real_connect(&mysql_conn,connection.host,m->mysqluser,
521 		  		  m->mysqlpasswd, NULL, m->mysqlport,
522 				  m->mysqlsocket, 0);
523 #else
524   connection.handle=mysql_real_connect(&mysql_conn,connection.host,m->mysqluser,
525 		  		  m->mysqlpasswd, NULL, atoi(m->mysqlport),
526 				  m->mysqlsocket, 0);
527 #endif
528   if (!connection.handle) {
529     LOG_ERROR_1(APLOG_ERR, 0, r, "MySQL ERROR: %s", mysql_error(&mysql_conn));
530     return FALSE;
531   }
532 
533   if (!m->mysqlKeepAlive) {
534     /* close when request done */
535 #ifdef APACHE2
536     apr_pool_cleanup_register(r->pool, (void *)NULL, mod_auth_mysql_cleanup, mod_auth_mysql_cleanup_child);
537 #else
538     ap_register_cleanup(r->pool, (void *)NULL, mod_auth_mysql_cleanup, mod_auth_mysql_cleanup_child);
539 #endif
540   }
541 
542   if (m->mysqluser)
543     strcpy(connection.user, m->mysqluser);
544   else
545     connection.user[0] = '\0';
546 
547   if (mysql_select_db(connection.handle,m->mysqlDB) != 0) {
548     LOG_ERROR_1(APLOG_ERR, 0, r, "MySQL ERROR: %s", mysql_error(connection.handle));
549     return FALSE;
550   }
551   else {
552     strcpy (connection.db, m->mysqlDB);
553   }
554   if (m->mysqlCharacterSet) {	/* If a character set was specified */
555     SNPRINTF(query, sizeof(query)-1, "SET CHARACTER SET %s", m->mysqlCharacterSet);
556     if (mysql_query(connection.handle, query) != 0) {
557       LOG_ERROR_2(APLOG_ERR, 0, r, "MySQL ERROR: %s: %s", mysql_error(connection.handle), r->uri);
558       return FALSE;
559     }
560   }
561 
562   return TRUE;
563 }
564 
create_mysql_auth_dir_config(POOL * p,char * d)565 static void * create_mysql_auth_dir_config (POOL *p, char *d)
566 {
567   mysql_auth_config_rec *m = PCALLOC(p, sizeof(mysql_auth_config_rec));
568   if (!m) return NULL;		/* failure to get memory is a bad thing */
569 
570   /* default values */
571   m->mysqlhost = _HOST;
572   m->mysqlport = _PORT;
573   m->mysqlsocket = _SOCKET;
574   m->mysqluser = _USER;
575   m->mysqlpasswd = _PASSWORD;
576   m->mysqlDB = _DB;
577   m->mysqlpwtable = _PWTABLE;
578   m->mysqlgrptable = 0;                             /* user group table */
579   m->mysqlNameField = _NAMEFIELD;		    /* default user name field */
580   m->mysqlPasswordField = _PASSWORDFIELD;	    /* default user password field */
581   m->mysqlGroupUserNameField = _GROUPUSERNAMEFIELD; /* user name field in group table */
582   m->mysqlEncryptionField = _ENCRYPTION;  	    /* default encryption is encrypted */
583   m->mysqlSaltField = _SALTFIELD;	    	    /* default is scramble password against itself */
584   m->mysqlKeepAlive = _KEEPALIVE;         	    /* do not keep persistent connection */
585   m->mysqlAuthoritative = _AUTHORITATIVE; 	    /* we are authoritative source for users */
586   m->mysqlNoPasswd = _NOPASSWORD;         	    /* we require password */
587   m->mysqlEnable = _ENABLE;		    	    /* authorization on by default */
588   m->mysqlUserCondition = 0;             	    /* No condition to add to the user
589 						       where-clause in select query */
590   m->mysqlGroupCondition = 0;            	    /* No condition to add to the group
591 						       where-clause in select query */
592   m->mysqlCharacterSet = _CHARACTERSET;		    /* default characterset to use */
593   return (void *)m;
594 }
595 
596 #ifdef APACHE2
597 static
598 command_rec mysql_auth_cmds[] = {
599 	AP_INIT_TAKE1("AuthMySQLHost", ap_set_string_slot,
600 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlhost),
601 	OR_AUTHCFG, "mysql server host name"),
602 
603 	AP_INIT_TAKE1("AuthMySQLPort", ap_set_int_slot,
604 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlport),
605 	OR_AUTHCFG, "mysql server port number"),
606 
607 	AP_INIT_TAKE1("AuthMySQLSocket", ap_set_string_slot,
608 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlsocket),
609 	OR_AUTHCFG, "mysql server socket path"),
610 
611 	AP_INIT_TAKE1("AuthMySQLUser", ap_set_string_slot,
612 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqluser),
613 	OR_AUTHCFG, "mysql server user name"),
614 
615 	AP_INIT_TAKE1("AuthMySQLPassword", ap_set_string_slot,
616 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlpasswd),
617 	OR_AUTHCFG, "mysql server user password"),
618 
619 	AP_INIT_TAKE1("AuthMySQLDB", ap_set_string_slot,
620 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlDB),
621 	OR_AUTHCFG, "mysql database name"),
622 
623 	AP_INIT_TAKE1("AuthMySQLUserTable", ap_set_string_slot,
624 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlpwtable),
625 	OR_AUTHCFG, "mysql user table name"),
626 
627 	AP_INIT_TAKE1("AuthMySQLGroupTable", ap_set_string_slot,
628 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlgrptable),
629 	OR_AUTHCFG, "mysql group table name"),
630 
631 	AP_INIT_TAKE1("AuthMySQLNameField", ap_set_string_slot,
632 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlNameField),
633 	OR_AUTHCFG, "mysql User ID field name within User table"),
634 
635 	AP_INIT_TAKE1("AuthMySQLGroupField", ap_set_string_slot,
636 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlGroupField),
637 	OR_AUTHCFG, "mysql Group field name within table"),
638 
639 	AP_INIT_TAKE1("AuthMySQLGroupUserNameField", ap_set_string_slot,
640 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlGroupUserNameField),
641 	OR_AUTHCFG, "mysql User ID field name within Group table"),
642 
643 	AP_INIT_TAKE1("AuthMySQLPasswordField", ap_set_string_slot,
644 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlPasswordField),
645 	OR_AUTHCFG, "mysql Password field name within table"),
646 
647 	AP_INIT_TAKE1("AuthMySQLPwEncryption", ap_set_string_slot,
648 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlEncryptionField),
649 	OR_AUTHCFG, "mysql password encryption method"),
650 
651 	AP_INIT_TAKE1("AuthMySQLSaltField", ap_set_string_slot,
652 	(void*) APR_OFFSETOF(mysql_auth_config_rec, mysqlSaltField),
653 	OR_AUTHCFG, "mysql salfe field name within table"),
654 
655 /*	AP_INIT_FLAG("AuthMySQLKeepAlive", ap_set_flag_slot,
656 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlKeepAlive),
657 	OR_AUTHCFG, "mysql connection kept open across requests if On"),
658 */
659 	AP_INIT_FLAG("AuthMySQLAuthoritative", ap_set_flag_slot,
660 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlAuthoritative),
661 	OR_AUTHCFG, "mysql lookup is authoritative if On"),
662 
663 	AP_INIT_FLAG("AuthMySQLNoPasswd", ap_set_flag_slot,
664 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlNoPasswd),
665 	OR_AUTHCFG, "If On, only check if user exists; ignore password"),
666 
667 	AP_INIT_FLAG("AuthMySQLEnable", ap_set_flag_slot,
668 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlEnable),
669 	OR_AUTHCFG, "enable mysql authorization"),
670 
671 	AP_INIT_TAKE1("AuthMySQLUserCondition", ap_set_string_slot,
672 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlUserCondition),
673 	OR_AUTHCFG, "condition to add to user where-clause"),
674 
675 	AP_INIT_TAKE1("AuthMySQLGroupCondition", ap_set_string_slot,
676 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlGroupCondition),
677 	OR_AUTHCFG, "condition to add to group where-clause"),
678 
679 	AP_INIT_TAKE1("AuthMySQLCharacterSet", ap_set_string_slot,
680 	(void *) APR_OFFSETOF(mysql_auth_config_rec, mysqlCharacterSet),
681 	OR_AUTHCFG, "mysql character set to be used"),
682 
683   { NULL }
684 };
685 #else
686 static
687 command_rec mysql_auth_cmds[] = {
688   { "AuthMySQLHost", ap_set_string_slot,
689     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlhost),
690     OR_AUTHCFG, TAKE1, "mysql server host name" },
691 
692   { "AuthMySQLSocket", ap_set_string_slot,
693     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlsocket),
694     OR_AUTHCFG, TAKE1, "mysql server socket path" },
695 
696   { "AuthMySQLPort", ap_set_string_slot,
697     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlport),
698     OR_AUTHCFG, TAKE1, "mysql server port number" },
699 
700   { "AuthMySQLUser", ap_set_string_slot,
701     (void*)XtOffsetOf(mysql_auth_config_rec, mysqluser),
702     OR_AUTHCFG, TAKE1, "mysql server user name" },
703 
704   { "AuthMySQLPassword", ap_set_string_slot,
705     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlpasswd),
706     OR_AUTHCFG, TAKE1, "mysql server user password" },
707 
708   { "AuthMySQLDB", ap_set_string_slot,
709     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlDB),
710     OR_AUTHCFG, TAKE1, "mysql database name" },
711 
712   { "AuthMySQLUserTable", ap_set_string_slot,
713     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlpwtable),
714     OR_AUTHCFG, TAKE1, "mysql user table name" },
715 
716   { "AuthMySQLGroupTable", ap_set_string_slot,
717     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlgrptable),
718     OR_AUTHCFG, TAKE1, "mysql group table name" },
719 
720   { "AuthMySQLNameField", ap_set_string_slot,
721     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlNameField),
722     OR_AUTHCFG, TAKE1, "mysql User ID field name within User table" },
723 
724   { "AuthMySQLGroupField", ap_set_string_slot,
725     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlGroupField),
726     OR_AUTHCFG, TAKE1, "mysql Group field name within table" },
727 
728   { "AuthMySQLGroupUserNameField", ap_set_string_slot,
729     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlGroupUserNameField),
730     OR_AUTHCFG, TAKE1, "mysql User ID field name within Group table" },
731 
732   { "AuthMySQLPasswordField", ap_set_string_slot,
733     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlPasswordField),
734     OR_AUTHCFG, TAKE1, "mysql Password field name within table" },
735 
736   { "AuthMySQLPwEncryption", ap_set_string_slot,
737     (void *)XtOffsetOf(mysql_auth_config_rec, mysqlEncryptionField),
738     OR_AUTHCFG, TAKE1, "mysql password encryption method" },
739 
740   { "AuthMySQLSaltField", ap_set_string_slot,
741     (void *)XtOffsetOf(mysql_auth_config_rec, mysqlSaltField),
742     OR_AUTHCFG, TAKE1, "mysql salt field name within table" },
743 
744 /*  { "AuthMySQLKeepAlive", ap_set_flag_slot,
745     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlKeepAlive),
746     OR_AUTHCFG, FLAG, "mysql connection kept open across requests if On" },
747 */
748   { "AuthMySQLAuthoritative", ap_set_flag_slot,
749     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlAuthoritative),
750     OR_AUTHCFG, FLAG, "mysql lookup is authoritative if On" },
751 
752   { "AuthMySQLNoPasswd", ap_set_flag_slot,
753     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlNoPasswd),
754     OR_AUTHCFG, FLAG, "If On, only check if user exists; ignore password" },
755 
756   { "AuthMySQLEnable", ap_set_flag_slot,
757     (void *)XtOffsetOf(mysql_auth_config_rec, mysqlEnable),
758     OR_AUTHCFG, FLAG, "enable mysql authorization"},
759 
760   { "AuthMySQLUserCondition", ap_set_string_slot,
761     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlUserCondition),
762     OR_AUTHCFG, TAKE1, "condition to add to user where-clause" },
763 
764   { "AuthMySQLGroupCondition", ap_set_string_slot,
765     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlGroupCondition),
766     OR_AUTHCFG, TAKE1, "condition to add to group where-clause" },
767 
768   { "AuthMySQLCharacterSet", ap_set_string_slot,
769     (void*)XtOffsetOf(mysql_auth_config_rec, mysqlCharacterSet),
770     OR_AUTHCFG, TAKE1, "mysql character set to use" },
771 
772   { NULL }
773 };
774 #endif
775 
776 module mysql_auth_module;
777 
778 /*
779  * Convert binary to hex
780  */
bin2hex(POOL * pool,const char * bin,short len)781 static char * bin2hex (POOL *pool, const char * bin, short len) {
782   int i = 0;
783   static char hexchars [] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
784   char * buffer = PCALLOC(pool, len * 2 + 1);
785   for (i = 0; i < len; i++) {
786     buffer[i*2] = hexchars[bin[i] >> 4 & 0x0f];
787     buffer[i*2+1] = hexchars[bin[i] & 0x0f];
788   }
789   buffer[len * 2] = '\0';
790   return buffer;
791 }
792 
793 /*
794  * Convert hexadecimal characters to character
795  */
796 
hex2chr(char * in)797 static char hex2chr(char * in) {
798   static const char * data = "0123456789ABCDEF";
799   const char * offset;
800   char val = 0;
801   int i;
802 
803   for (i = 0; i < 2; i++) {
804     val <<= 4;
805     offset = strchr(data, toupper(in[i]));
806     if (offset == NULL)
807       return '\0';
808     val += offset - data;
809   }
810   return val;
811 }
812 
813 
814 /* Checks scrambled passwords */
pw_scrambled(POOL * pool,const char * real_pw,const char * sent_pw,const char * salt)815 static short pw_scrambled(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt) {
816   char * encrypted_sent_pw = PCALLOC(pool, SCRAMBLED_PASSWORD_CHAR_LENGTH+1);
817 #ifdef SCRAMBLED_PASSWORD_CHAR_LENGTH_323   /* If we may need to use old password */
818   if (real_pw[0] != '*')
819     make_scrambled_password_323(encrypted_sent_pw, sent_pw);
820   else
821 #endif
822     make_scrambled_password(encrypted_sent_pw, sent_pw);
823   return strcmp(real_pw, encrypted_sent_pw) == 0;
824 }
825 
826 /* checks md5 hashed passwords */
pw_md5(POOL * pool,const char * real_pw,const char * sent_pw,const char * salt)827 static short pw_md5(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt) {
828   return strcmp(real_pw,ap_md5(pool, (const unsigned char *) sent_pw)) == 0;
829 }
830 
831 /* Checks crypt()ed passwords */
pw_crypted(POOL * pool,const char * real_pw,const char * sent_pw,const char * salt)832 static short pw_crypted(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt) {
833   /* salt will contain either the salt or real_pw */
834   return strcmp(real_pw, crypt(sent_pw, salt)) == 0;
835 }
836 
837 #if _AES
838 /* checks aes passwords */
pw_aes(POOL * pool,const char * real_pw,const char * sent_pw,const char * salt)839 static short pw_aes(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt) {
840   /* salt will contain the salt value */
841   /* Encryption is in 16 byte blocks */
842   char * encrypted_sent_pw = PCALLOC(pool, 16 * ((strlen(sent_pw) / 16) + 1));
843   short enc_len = my_aes_encrypt(sent_pw, strlen(sent_pw), encrypted_sent_pw, salt, strlen(salt));
844   return enc_len > 0 && memcmp(real_pw, encrypted_sent_pw, enc_len) == 0;
845 }
846 #endif
847 
848 /* checks SHA1 passwords */
pw_sha1(POOL * pool,const char * real_pw,const char * sent_pw,const char * salt)849 static short pw_sha1(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt) {
850   char *scrambled_sent_pw, *buffer=PCALLOC(pool, 128);
851   short enc_len = 0;
852 #ifdef APACHE2
853   apr_sha1_base64(sent_pw, strlen(sent_pw), buffer);
854   buffer += 5;   /* go past {SHA1} eyecatcher */
855   scrambled_sent_pw = PCALLOC(pool, apr_base64_decode_len(buffer) + 1);
856   enc_len = apr_base64_decode(scrambled_sent_pw, buffer);
857 #else
858   ap_sha1_base64(sent_pw, strlen(sent_pw), buffer);
859   buffer += 5;   /* go past {SHA1} eyecatcher */
860   scrambled_sent_pw = PCALLOC(pool, ap_base64decode_len(buffer) + 1);
861   enc_len = ap_base64decode(scrambled_sent_pw, buffer);
862 #endif
863   scrambled_sent_pw[enc_len] = '\0';
864   return  strcasecmp(bin2hex(pool, scrambled_sent_pw, enc_len), real_pw) == 0;
865 }
866 
867 /* checks plain text passwords */
pw_plain(POOL * pool,const char * real_pw,const char * sent_pw,const char * salt)868 static short pw_plain(POOL * pool, const char * real_pw, const char * sent_pw, const char * salt) {
869   return strcmp(real_pw, sent_pw) == 0;
870 }
871 
str_format(request_rec * r,char * input)872 static char * str_format(request_rec * r, char * input) {
873   char * output = input, *pos, *start = input, *data, *temp;
874   int i, len, found;
875 
876   while ((pos = strchr(start, '%')) != NULL) {
877     len = pos - output;			/* Length of string to copy */
878     pos++;				/* Point at formatting character */
879     found = 0;
880     for (i = 0; i < sizeof(formats)/sizeof(formats[0]); i++) {
881       if (*pos == formats[i].pattern) {
882 	pos++;				/* Data following format char */
883 	data = formats[i].func(r, &pos);
884 	temp = PCALLOC(r->pool, len + strlen(data) + strlen(pos) + 1);
885 	if (temp == NULL) {
886           LOG_ERROR_1(APLOG_ERR|APLOG_NOERRNO, 0, r, "MySQL ERROR: Insufficient storage to expand format %c", *(pos-1));
887 	  return input;
888 	}
889 	strncpy(temp, output, len);
890 	strcat (temp, data);
891 	start = temp + strlen(temp);
892 	strcat (temp, pos);
893 	output = temp;
894 	found = 1;
895 	break;
896       }
897     }
898     if (!found) {
899       LOG_ERROR_2(APLOG_ERR|APLOG_NOERRNO, 0, r, "MySQL ERROR: Invalid formatting character at position %d: \"%s\"",
900 		      pos-output, output);
901       return input;
902     }
903   }
904   return output;
905 }
906 
format_remote_host(request_rec * r,char ** parm)907 static char * format_remote_host(request_rec * r, char ** parm) {
908 #ifdef APACHE2
909   return  ap_escape_logitem(r->pool, ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME, NULL));
910 #else
911   return ap_escape_logitem(r->pool, ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME));
912 #endif
913 }
914 
format_remote_ip(request_rec * r,char ** parm)915 static char * format_remote_ip(request_rec * r, char ** parm) {
916 /*
917  from ap_mmn.h
918  20111130.0 (2.4.0-dev)  c->remote_ip becomes c->peer_ip and r->client_ip,
919                          c->remote_addr becomes c->peer_addr and r->client_addr
920 */
921 #if AP_MODULE_MAGIC_AT_LEAST(20111130,0)
922   return r->connection->client_ip;
923 #else
924    return r->connection->remote_ip;
925 #endif
926 }
927 
format_filename(request_rec * r,char ** parm)928 static char * format_filename(request_rec * r, char ** parm) {
929   return ap_escape_logitem(r->pool, r->filename);
930 }
931 
format_server_name(request_rec * r,char ** parm)932 static char * format_server_name(request_rec * r, char ** parm) {
933   return ap_escape_logitem(r->pool, ap_get_server_name(r));
934 }
935 
format_server_hostname(request_rec * r,char ** parm)936 static char * format_server_hostname(request_rec * r, char ** parm) {
937   return ap_escape_logitem(r->pool, r->server->server_hostname);
938 }
939 
format_protocol(request_rec * r,char ** parm)940 static char * format_protocol(request_rec * r, char ** parm) {
941   return ap_escape_logitem(r->pool, r->protocol);
942 }
943 
format_method(request_rec * r,char ** parm)944 static char * format_method(request_rec * r, char ** parm) {
945   return ap_escape_logitem(r->pool, r->method);
946 }
947 
format_args(request_rec * r,char ** parm)948 static char * format_args(request_rec * r, char ** parm) {
949   if (r->args)
950     return ap_escape_logitem(r->pool, r->args);
951   else
952     return "";
953 }
954 
format_request(request_rec * r,char ** parm)955 static char * format_request(request_rec * r, char ** parm) {
956   return ap_escape_logitem(r->pool,
957     (r->parsed_uri.password) ? STRCAT(r->pool, r->method, " ",
958 #ifdef APACHE2
959 	apr_uri_unparse(r->pool, &r->parsed_uri, 0),
960 #else
961 	ap_unparse_uri_components(r->pool, &r->parsed_uri, 0),
962 #endif
963 	r->assbackwards ? NULL : " ", r->protocol, NULL) :
964     r->the_request);
965 }
966 
format_uri(request_rec * r,char ** parm)967 static char * format_uri(request_rec * r, char ** parm) {
968   return ap_escape_logitem(r->pool, r->uri);
969 }
970 
format_percent(request_rec * r,char ** parm)971 static char * format_percent(request_rec * r, char ** parm) {
972   return "%";
973 }
974 
format_cookie(request_rec * r,char ** parm)975 static char * format_cookie(request_rec * r, char ** parm) {
976   const char * cookies;
977   char * start = *parm;
978   char delim;
979   char * end;
980   char * cookiename;
981   const char * cookiestart, *cookieend;
982   char * cookieval;
983   int len;
984 
985   delim = *start;
986   end = strchr(++start, delim);
987   if (end == NULL) {
988     LOG_ERROR_1(APLOG_NOERRNO | APLOG_ERR, 0, r, "No ending delimiter found for cookie starting at %s", *parm -2);
989     return "";
990   }
991   *parm = end + 1;   /* Point past end of data */
992   if ((cookies = TABLE_GET(r->headers_in, "Cookie")) == NULL) {
993     LOG_ERROR(APLOG_NOERRNO|APLOG_ERR, 0, r, "No cookies found");
994     return "";
995   }
996   len = end - start;
997   cookiename = PCALLOC(r->pool, len + 2);
998   strncpy(cookiename, start, len);
999   strcat(cookiename, "=");
1000   len++;
1001 
1002   cookiestart = cookies;
1003   while (cookiestart != NULL) {
1004     while (*cookiestart != '\0' && ISSPACE(*cookiestart))
1005       cookiestart++;
1006     if (strncmp(cookiestart, cookiename, len) == 0) {
1007       cookiestart += len;
1008       cookieend = strchr(cookiestart, ';');		/* Search for end of cookie data */
1009       if (cookieend == NULL)			/* NULL means this was the last cookie */
1010 	cookieend = cookiestart + strlen(cookiestart);
1011       len = cookieend - cookiestart;
1012       cookieval = PSTRNDUP(r->pool, cookiestart, len);
1013 
1014       start = cookieval;
1015       while ((start = strchr(start, '%')) != NULL) {  /* Convert any hex data to char */
1016          *start = hex2chr(start+1);
1017 	 strcpy (start+1, start+3);
1018 	 start++;
1019       }
1020 
1021       return cookieval;
1022     }
1023     cookiestart = strchr(cookiestart, ';');
1024     if (cookiestart != NULL)
1025       cookiestart++;
1026   }
1027   return "";
1028 }
1029 
1030 
1031 /*
1032  * Fetch and return password string from database for named user.
1033  * If we are in NoPasswd mode, returns user name instead.
1034  * If user or password not found, returns NULL
1035  */
get_mysql_pw(request_rec * r,char * user,mysql_auth_config_rec * m,const char * salt_column,const char ** psalt)1036 static char * get_mysql_pw(request_rec *r, char *user, mysql_auth_config_rec *m, const char *salt_column, const char ** psalt) {
1037   MYSQL_RES *result;
1038   char *pw = NULL;		/* password retrieved */
1039   char *sql_safe_user = NULL;
1040   int ulen;
1041   char query[MAX_STRING_LEN];
1042 
1043   if(!open_db_handle(r,m)) {
1044     return NULL;		/* failure reason already logged */
1045   }
1046 
1047   /*
1048    * If we are not checking for passwords, there may not be a password field
1049    * in the database.  We just look up the name field value in this case
1050    * since it is guaranteed to exist.
1051    */
1052   if (m->mysqlNoPasswd) {
1053     m->mysqlPasswordField = m->mysqlNameField;
1054   }
1055 
1056   ulen = strlen(user);
1057   sql_safe_user = PCALLOC(r->pool, ulen*2+1);
1058   mysql_escape_string(sql_safe_user,user,ulen);
1059 
1060   if (salt_column) {	/* If a salt was requested */
1061     if (m->mysqlUserCondition) {
1062       SNPRINTF(query,sizeof(query)-1,"SELECT %s, length(%s), %s FROM %s WHERE %s='%s' AND %s",
1063 		m->mysqlPasswordField, m->mysqlPasswordField, salt_column, m->mysqlpwtable,
1064 		m->mysqlNameField, sql_safe_user, str_format(r, m->mysqlUserCondition));
1065     } else {
1066       SNPRINTF(query,sizeof(query)-1,"SELECT %s, length(%s), %s FROM %s WHERE %s='%s'",
1067 		m->mysqlPasswordField, m->mysqlPasswordField, salt_column, m->mysqlpwtable,
1068 		m->mysqlNameField, sql_safe_user);
1069     }
1070   } else {
1071     if (m->mysqlUserCondition) {
1072       SNPRINTF(query,sizeof(query)-1,"SELECT %s, length(%s) FROM %s WHERE %s='%s' AND %s",
1073 		m->mysqlPasswordField, m->mysqlPasswordField, m->mysqlpwtable,
1074 		m->mysqlNameField, sql_safe_user, str_format(r, m->mysqlUserCondition));
1075     } else {
1076       SNPRINTF(query,sizeof(query)-1,"SELECT %s, length(%s) FROM %s WHERE %s='%s'",
1077 		m->mysqlPasswordField, m->mysqlPasswordField, m->mysqlpwtable,
1078 		m->mysqlNameField, sql_safe_user);
1079     }
1080   }
1081   if (mysql_query(connection.handle, query) != 0) {
1082     LOG_ERROR_2(APLOG_ERR, 0, r, "MySQL ERROR: %s: %s", mysql_error(connection.handle), r->uri);
1083     return NULL;
1084   }
1085 
1086   result = mysql_store_result(connection.handle);
1087   /* if (result && (mysql_num_rows(result) == 1)) */
1088   if (result && (mysql_num_rows(result) >= 1)) {
1089     MYSQL_ROW data = mysql_fetch_row(result);
1090     if (data[0]) {
1091       int len = atoi(data[1]);
1092       pw = (char *) PCALLOC(r->pool, len + 1);
1093       memcpy(pw, data[0], len);
1094 /*      pw = (char *) PSTRDUP(r->pool, data[0]); */
1095     } else {		/* no password in mysql table returns NULL */
1096       /* this should never happen, but test for it anyhow */
1097       LOG_ERROR_2(APLOG_NOERRNO|APLOG_ERR, 0, r, "MySQL user %s has no valid password: %s", user, r->uri);
1098       mysql_free_result(result);
1099       return NULL;
1100     }
1101 
1102     if (salt_column) {
1103       if (data[2]) {
1104         *psalt = (char *) PSTRDUP(r->pool, data[2]);
1105       } else {		/* no alt in mysql table returns NULL */
1106         *psalt = 0;
1107       }
1108     }
1109   }
1110 
1111   if (result) mysql_free_result(result);
1112 
1113   return pw;
1114 }
1115 
1116 /*
1117  * get list of groups from database.  Returns array of pointers to strings
1118  * the last of which is NULL.  returns NULL pointer if user is not member
1119  * of any groups.
1120  */
get_mysql_groups(request_rec * r,char * user,mysql_auth_config_rec * m)1121 static char ** get_mysql_groups(request_rec *r, char *user, mysql_auth_config_rec *m)
1122 {
1123   MYSQL_RES *result;
1124   char **list = NULL;
1125   char query[MAX_STRING_LEN];
1126   char *sql_safe_user;
1127   int ulen;
1128 
1129   if(!open_db_handle(r,m)) {
1130     return NULL;		/* failure reason already logged */
1131   }
1132 
1133   ulen = strlen(user);
1134   sql_safe_user = PCALLOC(r->pool, ulen*2+1);
1135   mysql_escape_string(sql_safe_user,user,ulen);
1136 
1137   if (m->mysqlGroupUserNameField == NULL)
1138     m->mysqlGroupUserNameField = m->mysqlNameField;
1139   if (m->mysqlGroupCondition) {
1140     SNPRINTF(query,sizeof(query)-1,"SELECT %s FROM %s WHERE %s='%s' AND %s",
1141 	      m->mysqlGroupField, m->mysqlgrptable,
1142 	      m->mysqlGroupUserNameField, sql_safe_user, str_format(r, m->mysqlGroupCondition));
1143   } else {
1144     SNPRINTF(query,sizeof(query)-1,"SELECT %s FROM %s WHERE %s='%s'",
1145 	      m->mysqlGroupField, m->mysqlgrptable,
1146 	      m->mysqlGroupUserNameField, sql_safe_user);
1147   }
1148   if (mysql_query(connection.handle, query) != 0) {
1149     LOG_ERROR_2(APLOG_ERR, 0, r, "MySQL error %s: %s", mysql_error(connection.handle),r->uri);
1150     return NULL;
1151   }
1152 
1153   result = mysql_store_result(connection.handle);
1154   if (result && (mysql_num_rows(result) > 0)) {
1155     int i = mysql_num_rows(result);
1156     list = (char **) PCALLOC(r->pool, sizeof(char *) * (i+1));
1157     list[i] = NULL;		/* last element in array is NULL */
1158     while (i--) {		/* populate the array elements */
1159       MYSQL_ROW data = mysql_fetch_row(result);
1160       if (data[0])
1161 	list[i] = (char *) PSTRDUP(r->pool, data[0]);
1162       else
1163 	list[i] = "";		/* if no data, make it empty, not NULL */
1164     }
1165   }
1166 
1167   if (result) mysql_free_result(result);
1168 
1169   return list;
1170 }
1171 
1172 /*
1173  * callback from Apache to do the authentication of the user to his
1174  * password.
1175  */
mysql_authenticate_basic_user(request_rec * r)1176 static int mysql_authenticate_basic_user (request_rec *r)
1177 {
1178   int passwords_match = 0;	/* Assume no match */
1179   encryption * enc_data = 0;
1180   int i = 0;
1181 
1182   char *user;
1183   mysql_auth_config_rec *sec =
1184     (mysql_auth_config_rec *)ap_get_module_config (r->per_dir_config,
1185 						   &mysql_auth_module);
1186 
1187   const char *sent_pw, *real_pw, *salt = 0, *salt_column = 0;
1188   int res;
1189 
1190   if (!sec->mysqlEnable)	/* no mysql authorization */
1191     return DECLINED;
1192 
1193   if ((res = ap_get_basic_auth_pw (r, &sent_pw)) != OK)
1194     return res;
1195 
1196 /* Determine the encryption method */
1197   if (sec->mysqlEncryptionField) {
1198     for (i = 0; i < sizeof(encryptions) / sizeof(encryptions[0]); i++) {
1199       if (strcasecmp(sec->mysqlEncryptionField, encryptions[i].string) == 0) {
1200 	enc_data = &(encryptions[i]);
1201 	break;
1202       }
1203     }
1204     if (!enc_data) {  /* Entry was not found in the list */
1205       LOG_ERROR_1(APLOG_NOERRNO|APLOG_ERR, 0, r, "mysql invalid encryption method %s", sec->mysqlEncryptionField);
1206       ap_note_basic_auth_failure(r);
1207       return NOT_AUTHORIZED;
1208     }
1209   }
1210   else
1211     enc_data = &encryptions[0];
1212 
1213 #ifdef APACHE2
1214   user = r->user;
1215 #else
1216   user = r->connection->user;
1217 #endif
1218 
1219   if (enc_data->salt_status == NO_SALT || !sec->mysqlSaltField)
1220     salt = salt_column = 0;
1221   else { 			/* Parse the mysqlSaltField */
1222     short salt_length = strlen(sec->mysqlSaltField);
1223 
1224     if (strcasecmp(sec->mysqlSaltField, "<>") == 0) { /* Salt against self */
1225       salt = salt_column = 0;
1226     } else if (sec->mysqlSaltField[0] == '<' && sec->mysqlSaltField[salt_length-1] == '>') {
1227       salt =  PSTRNDUP(r->pool, sec->mysqlSaltField+1, salt_length - 2);
1228       salt_column = 0;
1229     } else {
1230       salt = 0;
1231       salt_column = sec->mysqlSaltField;
1232     }
1233   }
1234 
1235   if (enc_data->salt_status == SALT_REQUIRED && !salt && !salt_column) {
1236     LOG_ERROR_1(APLOG_NOERRNO | APLOG_ERR, 0, r, "MySQL Salt field not specified for encryption %s", sec->mysqlEncryptionField);
1237     return DECLINED;
1238   }
1239 
1240   real_pw = get_mysql_pw(r, user, sec, salt_column, &salt ); /* Get a salt if one was specified */
1241 
1242   if(!real_pw)
1243   {
1244     /* user not found in database */
1245     LOG_ERROR_2(APLOG_NOERRNO|APLOG_ERR, 0, r, "MySQL user %s not found: %s", user, r->uri);
1246     ap_note_basic_auth_failure (r);
1247     if (!sec->mysqlAuthoritative)
1248       return DECLINED;		/* let other schemes find user */
1249     else
1250       return NOT_AUTHORIZED;
1251   }
1252 
1253   if (!salt)
1254     salt = real_pw;
1255 
1256   /* if we don't require password, just return ok since they exist */
1257   if (sec->mysqlNoPasswd) {
1258     return OK;
1259   }
1260 
1261   passwords_match = enc_data->func(r->pool, real_pw, sent_pw, salt);
1262 
1263   if(passwords_match) {
1264 	  return OK;
1265   } else {
1266     LOG_ERROR_2(APLOG_NOERRNO|APLOG_ERR, 0, r,
1267 	       "user %s: password mismatch: %s", user, r->uri);
1268 
1269     ap_note_basic_auth_failure (r);
1270     return NOT_AUTHORIZED;
1271   }
1272 }
1273 
1274 /*
1275  * check if user is member of at least one of the necessary group(s)
1276  */
mysql_check_auth(request_rec * r)1277 static int mysql_check_auth(request_rec *r)
1278 {
1279   mysql_auth_config_rec *sec =
1280     (mysql_auth_config_rec *)ap_get_module_config(r->per_dir_config,
1281 						  &mysql_auth_module);
1282 #ifdef APACHE2
1283   char *user = r->user;
1284 #else
1285   char *user = r->connection->user;
1286 #endif
1287   int method = r->method_number;
1288 
1289 #ifdef APACHE2
1290   const apr_array_header_t *reqs_arr = NULL;
1291 #else
1292   const array_header *reqs_arr = ap_requires(r);
1293 #endif
1294 
1295   require_line *reqs = reqs_arr ? (require_line *)reqs_arr->elts : NULL;
1296 
1297   register int x;
1298   char **groups = NULL;
1299 
1300   if (!sec->mysqlGroupField) return DECLINED; /* not doing groups here */
1301   if (!reqs_arr) return DECLINED; /* no "require" line in access config */
1302 
1303   /* if the group table is not specified, use the same as for password */
1304   if (!sec->mysqlgrptable) sec->mysqlgrptable = sec->mysqlpwtable;
1305 
1306   for(x = 0; x < reqs_arr->nelts; x++) {
1307     const char *t, *want;
1308 
1309     if (!(reqs[x].method_mask & (1 << method))) continue;
1310 
1311     t = reqs[x].requirement;
1312     want = ap_getword_conf(r->pool, &t);
1313 
1314     if (!strcmp(want, "valid-user")) {
1315       return OK;
1316     }
1317 
1318     if (!strcmp(want, "user")) {
1319       while (t[0]) {
1320 	want = ap_getword_conf(r->pool, &t);
1321 	if (strcmp(user, want) == 0) {
1322 	  return OK;
1323 	}
1324       }
1325     }
1326     else if(!strcmp(want,"group")) {
1327       /* check for list of groups from database only first time thru */
1328       if (groups || (groups = get_mysql_groups(r, user, sec))) {
1329 
1330 	/* loop through list of groups specified in htaccess file */
1331 	while(t[0]) {
1332 	  int i = 0;
1333 	  want = ap_getword_conf(r->pool, &t);
1334 	  /* compare against each group to which this user belongs */
1335 	  while(groups[i]) {	/* last element is NULL */
1336 	    if(!strcmp(groups[i],want)) {
1337 	      return OK;		/* we found the user! */
1338 	    }
1339 	    ++i;
1340 	  }
1341 	}
1342       }
1343     }
1344   }
1345   if (sec->mysqlAuthoritative) {
1346       LOG_ERROR_2(APLOG_NOERRNO|APLOG_ERR, 0, r,
1347 		 "mysql user %s failed authorization to access %s",user,r->uri);
1348       ap_note_basic_auth_failure(r);
1349       return NOT_AUTHORIZED;
1350   }
1351   return DECLINED;
1352 }
1353 
1354 #ifdef APACHE2
register_hooks(POOL * p)1355 static void register_hooks(POOL *p)
1356 {
1357 	ap_hook_check_user_id(mysql_authenticate_basic_user, NULL, NULL, APR_HOOK_MIDDLE);
1358 	ap_hook_auth_checker(mysql_check_auth, NULL, NULL, APR_HOOK_MIDDLE);
1359 }
1360 #endif
1361 
1362 #ifdef APACHE2
1363 module AP_MODULE_DECLARE_DATA mysql_auth_module =
1364 {
1365 STANDARD20_MODULE_STUFF,
1366 create_mysql_auth_dir_config, /* dir config creater */
1367 NULL,                       /* dir merger --- default is to override */
1368 NULL,                       /* server config */
1369 NULL,                       /* merge server config */
1370 mysql_auth_cmds,              /* command apr_table_t */
1371 register_hooks              /* register hooks */
1372 };
1373 
1374 #else
1375 module mysql_auth_module = {
1376    STANDARD_MODULE_STUFF,
1377    NULL,			/* initializer */
1378    create_mysql_auth_dir_config, /* dir config creater */
1379    NULL,			/* dir merger --- default is to override */
1380    NULL,			/* server config */
1381    NULL,			/* merge server config */
1382    mysql_auth_cmds,		/* command table */
1383    NULL,			/* handlers */
1384    NULL,			/* filename translation */
1385    mysql_authenticate_basic_user, /* check_user_id */
1386    mysql_check_auth,		/* check auth */
1387    NULL,			/* check access */
1388    NULL,			/* type_checker */
1389    NULL,			/* fixups */
1390    NULL,			/* logger */
1391    NULL,			/* header parser */
1392    NULL,			/* child_init */
1393    child_exit,			/* child_exit */
1394    NULL				/* post read-request */
1395 };
1396 #endif
1397 
1398 
1399