1 /*  Copyright 2004-2007 Thimo Eichstaedt <apache-mod@digithi.de>
2  *
3  *  Licensed under the Apache License, Version 2.0 (the "License");
4  *  you may not use this file except in compliance with the License.
5  *  You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  *  Unless required by applicable law or agreed to in writing, software
10  *  distributed under the License is distributed on an "AS IS" BASIS,
11  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  *  See the License for the specific language governing permissions and
13  *  limitations under the License.
14  *
15  *  Based on code from
16  *      Dirk.vanGulik@jrc.it  - original module
17  *      mjd@pobox.com         - bug fixes, conversion from mSQL to MySQL
18  *
19  *
20  *  Version history:
21  *  Version 0.2 - 28.07.2004  - first release - Thimo Eichstaedt <apache-mod@digithi.de>
22  *  Version 0.3 - 03.08.2004  - code fixup - Thimo Eichstaedt <apache-mod@digithi.de>
23  *  Version 0.4 - 05.04.2005  - bug fixes and redirect modified - Thimo Eichstaedt <apache-mod@digithi.de>
24  *  Version 0.5 - 01.06.2005  - mysql_close() fix - Thimo Eichstaedt <apache-mod@digithi.de>
25  *  Version 0.6 - 20.12.2005  - mysql_otions() added, mysql query modification - Thimo Eichstaedt <apache.mod@digithi.de>
26  *  Version 0.7 - 04.01.2006  - load return codes with default values - Thimo Eichstaedt <apache-mod@digithi.de>
27  *  Version 0.8 - 26.03.2007  - code cleanup, small memory leak fixed - Thimo Eichstaedt <apache-mod@digithi.de>
28  *  Version 0.9 - 23.04.2007  - code/documentation cleanup, persistent database connections added, rename of config vars - Thimo Eichstaedt <apache-mod@digithi.de>
29  *  Version 1.0 - 18.06.2009  - AuthCookieSQL_AdditionalSQL added, minor code cleanup - Thimo Eichstaedt <apache-mod@digithi.de>
30  *
31  */
32 
33 /********************************************************************************
34  *                                includes                                      *
35  ********************************************************************************/
36 
37 #include "apr_strings.h"
38 #include "httpd.h"
39 #include "http_config.h"
40 #include "http_core.h"
41 #include "http_request.h"
42 #include "http_log.h"
43 #include "mod_auth_cookie_sql2.h"
44 
45 #include <time.h>               /* for time */
46 #include <string.h>             /* for strncmp */
47 
48 module AP_MODULE_DECLARE_DATA MODULE_NAME_module;
49 
50 /********************************************************************************
51  *                      public function declarations                            *
52  ********************************************************************************/
53 static int auth_cookie_sql2_authenticate_user (request_rec *);
54 static void *auth_cookie_sql2_create_auth_dir_config(apr_pool_t *, char *);
55 
56 /********************************************************************************
57  *                      local function declarations                             *
58  ********************************************************************************/
59 static int check_valid_cookie(request_rec *, auth_cookie_sql2_config_rec *);
60 static int do_redirect(request_rec *);
61 
62 /********************************************************************************
63  *                            local variables                                   *
64  ********************************************************************************/
65 static auth_cookie_sql2_config_rec default_config_rec = {
66     0,		/* are we activated ? */
67     NULL,	/* Cookie Name */
68     NULL,	/* Database host */
69     NULL,	/* Database user */
70     NULL,	/* Database password */
71     NULL,	/* Database name */
72     NULL,	/* Database table */
73     0,		/* Persistent connection */
74     NULL,	/* Database username field */
75     NULL,	/* Database sessname field */
76     NULL,	/* Database sessval field */
77     NULL,	/* Database expiry information field */
78     NULL,	/* Database remote ip information field */
79     NULL,	/* SQL addon */
80     NULL,	/* goto URL if failure */
81 };
82 
83 /********************************************************************************
84  *                               functions                                      *
85  ********************************************************************************/
86 
auth_cookie_sql2_create_auth_dir_config(apr_pool_t * p,char * d)87 static void *auth_cookie_sql2_create_auth_dir_config(apr_pool_t *p, char *d) {
88     auth_cookie_sql2_config_rec *conf = apr_pcalloc(p, sizeof(*conf));
89 
90     if (conf) {
91 	// Set default values
92 	*conf=default_config_rec;
93     }
94     return conf;
95 }
96 
97 /* this function is called when the child or module is cleared */
auth_cookie_sql2_child_exit(void * data)98 static apr_status_t auth_cookie_sql2_child_exit(void *data) {
99 #ifdef AUTH_COOKIE_SQL2_DEBUG
100     server_rec *s = (server_rec *) data;
101 #endif
102 
103 #ifdef AUTH_COOKIE_SQL2_DEBUG
104     ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s, ERRTAG "child exit called.");
105 #endif
106 
107     close_db(NULL, NULL, 1);
108     return APR_SUCCESS;
109 }
110 
111 /* this function is called the the module is initialized by apache */
auth_cookie_sql2_child_init(apr_pool_t * p,server_rec * s)112 static void auth_cookie_sql2_child_init(apr_pool_t *p, server_rec *s) {
113 
114 #ifdef AUTH_COOKIE_SQL2_DEBUG
115     ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s, ERRTAG "child init called.");
116 #endif
117 
118     apr_pool_cleanup_register(p,
119 			      s,
120 			      auth_cookie_sql2_child_exit,
121 			      auth_cookie_sql2_child_exit
122 			      );
123 }
124 
125 /* send a redirect to the browser */
do_redirect(request_rec * r)126 static int do_redirect(request_rec *r) {
127     auth_cookie_sql2_config_rec *conf = ap_get_module_config(r->per_dir_config, &MODULE_NAME_module);
128     char *redirect = apr_psprintf(r->pool, "%s?r=%s", conf->failureurl, r->uri);
129 
130     if (redirect) {
131 	apr_table_setn(r->headers_out, "Location", redirect);
132 	return HTTP_MOVED_TEMPORARILY;
133     } else {
134 	return HTTP_INTERNAL_SERVER_ERROR;
135     }
136 }
137 
138 /* check for a valid cookie */
check_valid_cookie(request_rec * r,auth_cookie_sql2_config_rec * conf)139 static int check_valid_cookie(request_rec *r, auth_cookie_sql2_config_rec *conf) {
140     const char *cookieptr;
141     char *cookies, *value;
142     char username[MAX_USERNAME_LEN + 1];
143     time_t tc;
144     int db_ret=RET_UNAUTHORIZED; //default: unauthorized
145 
146     // grab pointer to cookie informations
147     if ((cookieptr = apr_table_get(r->headers_in, "Cookie")) == NULL) {
148 
149 	// no cookies found at all, so return with failure/unauthorized
150 	if (conf->failureurl) {
151 		return do_redirect(r);
152 	} else {
153 		return HTTP_UNAUTHORIZED;
154 	}
155     }
156 
157     // make a copy of cookies, so we can do what we want (strtok, ...)
158     if ((cookies = apr_palloc(r->pool, strlen(cookieptr) + 2)) == NULL) {
159 	// error
160 	return HTTP_INTERNAL_SERVER_ERROR;
161     }
162     strcpy (cookies, cookieptr);
163     cookies[0 + strlen(cookieptr)] = ';';
164     cookies[1 + strlen(cookieptr)] = '\0';
165 
166     // fetch current time;
167     tc = time(NULL);
168 
169 	// now check for cookies
170     if (conf->cookiename) {
171 	// a cookiename is given, we are searching only for THIS cookie in the DB
172 
173 	// find cookie we are searching for
174 	if ((cookies = strstr(cookies, conf->cookiename)) != NULL) {
175 	    // found cookie
176 
177 	    if ((value = strchr (cookies, '=')) != NULL) {
178 		// found beginning of cookieval
179 		if ((value = strtok(value+1, " ;\n\r\t\f")) != NULL) {
180 
181 #ifdef AUTH_COOKIE_SQL2_DEBUG
182 		    ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "valid cookie found, data: %s", cookies);
183 #endif
184 
185 #if MODULE_MAGIC_NUMBER_MAJOR >= 20111130
186 		    db_ret=check_against_db(conf, r, conf->cookiename, value, username,r->useragent_ip, conf->sql_addon, tc);
187 #else
188 		    db_ret=check_against_db(conf, r, conf->cookiename, value, username,r->connection->remote_ip, conf->sql_addon, tc);
189 #endif
190 		}
191 	    }
192 	}
193     } else {
194 	// no cookiename is given, lets try all cookies the client gave us
195 	for (cookies = strtok(cookies, " ;\n\r\t\f"); (cookies); cookies = strtok(NULL, " ;\n\r\t\f")) {
196 
197 	    /* content of cookies should now be like "aaa=bbb"
198 	       check this and, if not, continue search for a valid cookie */
199 	    if ((value = strchr (cookies, '=')) == NULL) {
200 		continue;
201 	    }
202 
203 #ifdef AUTH_COOKIE_SQL2_DEBUG
204 	    ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "testing against valid cookie: %s", cookies);
205 #endif
206 
207 	    /* modify aaa=bbb\0 to aaa\0bbb\0, so cookie contains aaa\0
208 	       and value contains bbb\0 */
209 	    *value = '\0';
210 	    value++;
211 
212 	    if ((db_ret=check_against_db(conf, r, cookies, value, username,
213 #if MODULE_MAGIC_NUMBER_MAJOR >= 20111130
214 		    r->useragent_ip,
215 #else
216 		    r->connection->remote_ip,
217 #endif
218 		    conf->sql_addon, tc)) == RET_AUTHORIZED) {
219 		// found valid cookie
220 
221 #ifdef AUTH_COOKIE_SQL2_DEBUG
222 		ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "valid cookie found");
223 #endif
224 
225 		break;
226 	    }
227 	}
228     }
229 
230     // handle return codes
231     switch (db_ret) {
232         case RET_AUTHORIZED:
233     	    // valid cookie found
234 	    r->user = apr_pstrdup(r->pool,username);
235 	    r->ap_auth_type="Cookie";
236 	    return OK;
237 	    break;
238 	case RET_UNAUTHORIZED:
239 	    // no valid information in database found
240 	    if (conf->failureurl) {
241 		return do_redirect(r);
242 	    } else {
243 		return HTTP_UNAUTHORIZED;
244 	    }
245 	    break;
246 	default:
247 	    return DECLINED;
248 	    break;
249     }
250 }
251 
252 /* try to authenticate the user */
auth_cookie_sql2_authenticate_user(request_rec * r)253 static int auth_cookie_sql2_authenticate_user (request_rec *r) {
254     auth_cookie_sql2_config_rec *conf = ap_get_module_config(r->per_dir_config, &MODULE_NAME_module);
255 
256     if (! conf->activated) {
257 	// not active
258 	return DECLINED;
259     }
260 
261     if ( !conf->dbhost || !conf->dbuser || !conf->dbpassword || !conf->dbname || !conf->dbtable ) {
262 	ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "please check database connect information, some are missing");
263 	return DECLINED;
264     }
265 
266     if ( !conf->dbusername_field || !conf->dbsessname_field || !conf->dbsessval_field) {
267 	ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "please check database field names, some are missing");
268 	return DECLINED;
269     }
270 
271 #ifdef AUTH_COOKIE_SQL2_DEBUG
272     ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, r, ERRTAG "module started.");
273 #endif
274 
275     return check_valid_cookie(r,conf);
276 
277 }
278 
279 
280 /* Module data */
281 static const command_rec auth_commands[] = {
282 
283   AP_INIT_FLAG("AuthCookieSql", ap_set_flag_slot,
284     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, activated),
285     WHERE_ALLOWED, "Set to on to activate Cookie Auth"),
286 
287 
288   AP_INIT_TAKE1("AuthCookieSql_CookieName", ap_set_string_slot,
289     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, cookiename),
290     WHERE_ALLOWED, "Name of the cookie"),
291 
292   AP_INIT_TAKE1("AuthCookieSql_DBHost", ap_set_string_slot,
293     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbhost),
294     WHERE_ALLOWED, "Host on which DB server resides"),
295 
296   AP_INIT_TAKE1("AuthCookieSql_DBUser", ap_set_string_slot,
297     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbuser),
298     WHERE_ALLOWED, "Username for DB access"),
299 
300   AP_INIT_TAKE1("AuthCookieSql_DBPassword", ap_set_string_slot,
301     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbpassword),
302     WHERE_ALLOWED, "Password for DB access"),
303 
304   AP_INIT_TAKE1("AuthCookieSql_DBName", ap_set_string_slot,
305     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbname),
306     WHERE_ALLOWED, "Database name"),
307 
308   AP_INIT_TAKE1("AuthCookieSql_DBTable", ap_set_string_slot,
309     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbtable),
310     WHERE_ALLOWED, "Table name in database"),
311 
312   AP_INIT_FLAG("AuthCookieSql_DBPersistent", ap_set_flag_slot,
313     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbpersistent),
314     WHERE_ALLOWED, "If enabled connection to database is held open during requests"),
315 
316   AP_INIT_TAKE1("AuthCookieSql_UsernameField", ap_set_string_slot,
317     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbusername_field),
318     WHERE_ALLOWED, "Field name in database where username is hold"),
319 
320   AP_INIT_TAKE1("AuthCookieSql_SessnameField", ap_set_string_slot,
321     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbsessname_field),
322     WHERE_ALLOWED, "Field name in database where session name is hold"),
323 
324   AP_INIT_TAKE1("AuthCookieSql_SessvalField", ap_set_string_slot,
325     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbsessval_field),
326     WHERE_ALLOWED, "Field name in database where session key is hold"),
327 
328   AP_INIT_TAKE1("AuthCookieSql_ExpiryField", ap_set_string_slot,
329     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbexpiry_field),
330     WHERE_ALLOWED, "Field name in database where expiry information is hold"),
331 
332   AP_INIT_TAKE1("AuthCookieSql_RemoteIPField", ap_set_string_slot,
333     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, dbremoteip_field),
334     WHERE_ALLOWED, "Field name in database where remote ip information is hold"),
335 
336   AP_INIT_TAKE1("AuthCookieSql_AdditionalSql", ap_set_string_slot,
337     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, sql_addon),
338     WHERE_ALLOWED, "SQL clause which is appended after the SQL statement"),
339 
340   AP_INIT_TAKE1("AuthCookieSql_FailureURL", ap_set_string_slot,
341     (void *)APR_OFFSETOF(auth_cookie_sql2_config_rec, failureurl),
342     WHERE_ALLOWED, "URL where to go to when auth not successful"),
343 
344   { NULL }
345 };
346 
347 /* register hooks at the apache server */
auth_cookie_sql2_register_hooks(apr_pool_t * p)348 static void auth_cookie_sql2_register_hooks(apr_pool_t *p) {
349 
350     /* Hook in and great, we are the first :) */
351     ap_hook_check_user_id(auth_cookie_sql2_authenticate_user,NULL,NULL,APR_HOOK_FIRST);
352 
353     /* hook in for initialisation */
354     ap_hook_child_init(auth_cookie_sql2_child_init, NULL, NULL, APR_HOOK_MIDDLE);
355 }
356 
357 /* module data */
358 module AP_MODULE_DECLARE_DATA MODULE_NAME_module = {
359     STANDARD20_MODULE_STUFF,
360     auth_cookie_sql2_create_auth_dir_config,	/* per-directory config creater */
361     NULL,                       		/* dir merger --- default is to override */
362     NULL,                       		/* server config creator */
363     NULL,                       		/* server config merger */
364     auth_commands,              		/* command table */
365     auth_cookie_sql2_register_hooks              /* set up other request processing hooks */
366 };
367