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