1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "mod_session.h"
19 #include "apr_strings.h"
21 #include "util_cookies.h"
24 #include "mpm_common.h"
26 #define MOD_SESSION_DBD "mod_session_dbd"
28 module AP_MODULE_DECLARE_DATA session_dbd_module;
31 * Structure to carry the per-dir session config.
36 const char *name_attrs;
39 const char *name2_attrs;
44 const char *selectlabel;
45 const char *insertlabel;
46 const char *updatelabel;
47 const char *deletelabel;
48 } session_dbd_dir_conf;
50 /* optional function - look it up once in post_config */
51 static ap_dbd_t *(*session_dbd_acquire_fn) (request_rec *) = NULL;
52 static void (*session_dbd_prepare_fn) (server_rec *, const char *, const char *) = NULL;
55 * Initialise the database.
57 * If the mod_dbd module is missing, this method will return APR_EGENERAL.
59 static apr_status_t dbd_init(request_rec *r, const char *query, ap_dbd_t **dbdp,
60 apr_dbd_prepared_t **statementp)
63 apr_dbd_prepared_t *statement;
65 if (!session_dbd_prepare_fn || !session_dbd_acquire_fn) {
66 session_dbd_prepare_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare);
67 session_dbd_acquire_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire);
68 if (!session_dbd_prepare_fn || !session_dbd_acquire_fn) {
69 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
70 "You must load mod_dbd to enable AuthDBD functions");
75 dbd = session_dbd_acquire_fn(r);
77 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
78 "failed to acquire database connection");
82 statement = apr_hash_get(dbd->prepared, query, APR_HASH_KEY_STRING);
84 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
85 "failed to find the prepared statement called '%s'", query);
90 *statementp = statement;
96 * Load the session by the key specified.
98 static apr_status_t dbd_load(request_rec * r, const char *key, const char **val)
102 ap_dbd_t *dbd = NULL;
103 apr_dbd_prepared_t *statement = NULL;
104 apr_dbd_results_t *res = NULL;
105 apr_dbd_row_t *row = NULL;
106 apr_int64_t expiry = (apr_int64_t) apr_time_now();
108 session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
109 &session_dbd_module);
111 if (conf->selectlabel == NULL) {
112 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
113 "no SessionDBDselectlabel has been specified");
117 rv = dbd_init(r, conf->selectlabel, &dbd, &statement);
121 rv = apr_dbd_pvbselect(dbd->driver, r->pool, dbd->handle, &res, statement,
122 0, key, &expiry, NULL);
124 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
125 "query execution error saving session '%s' "
126 "in database using query '%s': %s", key, conf->selectlabel,
127 apr_dbd_error(dbd->driver, dbd->handle, rv));
130 for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1);
132 rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) {
134 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
135 "error retrieving results while saving '%s' "
136 "in database using query '%s': %s", key, conf->selectlabel,
137 apr_dbd_error(dbd->driver, dbd->handle, rv));
141 *val = apr_dbd_get_entry(dbd->driver, row, 0);
143 /* we can't break out here or row won't get cleaned up */
151 * Load the session by firing off a dbd query.
153 * If the session is anonymous, the session key will be extracted from
154 * the cookie specified. Failing that, the session key will be extracted
155 * from the GET parameters.
157 * If the session is keyed by the username, the session will be extracted
160 * If no session is found, an empty session will be created.
162 * On success, this returns OK.
164 static apr_status_t session_dbd_load(request_rec * r, session_rec ** z)
167 session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
168 &session_dbd_module);
170 apr_status_t ret = APR_SUCCESS;
171 session_rec *zz = NULL;
172 const char *name = NULL;
173 const char *note = NULL;
174 const char *val = NULL;
175 const char *key = NULL;
176 request_rec *m = r->main ? r->main : r;
178 /* is our session in a cookie? */
179 if (conf->name2_set) {
182 else if (conf->name_set) {
185 else if (conf->peruser_set && r->user) {
192 /* first look in the notes */
193 note = apr_pstrcat(r->pool, MOD_SESSION_DBD, name, NULL);
194 zz = (session_rec *)apr_table_get(m->notes, note);
200 /* load anonymous sessions */
201 if (conf->name_set || conf->name2_set) {
203 /* load an RFC2109 or RFC2965 compliant cookie */
204 ap_cookie_read(r, name, &key, conf->remove);
206 ret = dbd_load(r, key, &val);
207 if (ret != APR_SUCCESS) {
214 /* load named session */
215 else if (conf->peruser) {
217 ret = dbd_load(r, r->user, &val);
218 if (ret != APR_SUCCESS) {
224 /* otherwise not for us */
229 /* create a new session and return it */
230 zz = (session_rec *) apr_pcalloc(r->pool, sizeof(session_rec));
232 zz->entries = apr_table_make(zz->pool, 10);
233 zz->uuid = (apr_uuid_t *) apr_pcalloc(zz->pool, sizeof(apr_uuid_t));
235 apr_uuid_parse(zz->uuid, key);
238 apr_uuid_get(zz->uuid);
243 /* put the session in the notes so we don't have to parse it again */
244 apr_table_setn(m->notes, note, (char *)zz);
251 * Save the session by the key specified.
253 static apr_status_t dbd_save(request_rec * r, const char *key, const char *val,
258 ap_dbd_t *dbd = NULL;
259 apr_dbd_prepared_t *statement;
262 session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
263 &session_dbd_module);
265 if (conf->updatelabel == NULL) {
266 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
267 "no SessionDBDupdatelabel has been specified");
271 rv = dbd_init(r, conf->updatelabel, &dbd, &statement);
275 rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement,
276 val, &expiry, key, NULL);
278 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
279 "query execution error updating session '%s' "
280 "using database query '%s': %s", key, conf->updatelabel,
281 apr_dbd_error(dbd->driver, dbd->handle, rv));
286 * if some rows were updated it means a session existed and was updated,
293 if (conf->insertlabel == NULL) {
294 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
295 "no SessionDBDinsertlabel has been specified");
299 rv = dbd_init(r, conf->insertlabel, &dbd, &statement);
303 rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement,
304 val, &expiry, key, NULL);
306 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
307 "query execution error inserting session '%s' "
308 "in database with '%s': %s", key, conf->insertlabel,
309 apr_dbd_error(dbd->driver, dbd->handle, rv));
314 * if some rows were inserted it means a session was inserted, so we are
321 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
322 "the session insert query did not cause any rows to be added "
323 "to the database for session '%s', session not inserted", key);
330 * Remove the session by the key specified.
332 static apr_status_t dbd_remove(request_rec * r, const char *key)
336 apr_dbd_prepared_t *statement;
339 session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
340 &session_dbd_module);
341 ap_dbd_t *dbd = session_dbd_acquire_fn(r);
343 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
344 "failed to acquire database connection to remove "
345 "session with key '%s'", key);
349 if (conf->deletelabel == NULL) {
350 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
351 "no SessionDBDdeletelabel has been specified");
355 statement = apr_hash_get(dbd->prepared, conf->deletelabel,
356 APR_HASH_KEY_STRING);
357 if (statement == NULL) {
358 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
359 "prepared statement could not be found for "
360 "SessionDBDdeletelabel with the label '%s'",
364 rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement,
366 if (rv != APR_SUCCESS) {
367 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
368 "query execution error removing session '%s' "
369 "from database", key);
378 * Clean out expired sessions.
380 * TODO: We need to figure out a way to clean out expired sessions from the database.
381 * The monitor hook doesn't help us that much, as we have no handle into the
382 * server, and so we need to come up with a way to do this safely.
384 static apr_status_t dbd_clean(apr_pool_t *p, server_rec *s)
392 * Save the session by firing off a dbd query.
394 * If the session is anonymous, save the session and write a cookie
395 * containing the uuid.
397 * If the session is keyed to the username, save the session using
398 * the username as a key.
400 * On success, this method will return APR_SUCCESS.
402 * @param r The request pointer.
403 * @param z A pointer to where the session will be written.
405 static apr_status_t session_dbd_save(request_rec * r, session_rec * z)
409 apr_status_t ret = APR_SUCCESS;
410 session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
411 &session_dbd_module);
413 /* support anonymous sessions */
414 if (conf->name_set || conf->name2_set) {
416 /* don't cache pages with a session */
417 apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
419 /* must we create a uuid? */
420 buffer = apr_pcalloc(r->pool, APR_UUID_FORMATTED_LENGTH + 1);
421 apr_uuid_format(buffer, z->uuid);
423 /* save the session with the uuid as key */
424 if (z->encoded && z->encoded[0]) {
425 ret = dbd_save(r, buffer, z->encoded, z->expiry);
428 ret = dbd_remove(r, buffer);
430 if (ret != APR_SUCCESS) {
434 /* create RFC2109 compliant cookie */
435 if (conf->name_set) {
436 ap_cookie_write(r, conf->name, buffer, conf->name_attrs, z->maxage,
437 r->headers_out, r->err_headers_out, NULL);
440 /* create RFC2965 compliant cookie */
441 if (conf->name2_set) {
442 ap_cookie_write2(r, conf->name2, buffer, conf->name2_attrs, z->maxage,
443 r->headers_out, r->err_headers_out, NULL);
450 /* save named session */
451 else if (conf->peruser) {
453 /* don't cache pages with a session */
454 apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
457 ret = dbd_save(r, r->user, z->encoded, z->expiry);
458 if (ret != APR_SUCCESS) {
464 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
465 "peruser sessions can only be saved if a user is logged in, "
466 "session not saved: %s", r->uri);
475 * This function performs housekeeping on the database, deleting expired
478 static int session_dbd_monitor(apr_pool_t *p, server_rec *s)
480 /* TODO handle housekeeping */
486 static void *create_session_dbd_dir_config(apr_pool_t * p, char *dummy)
488 session_dbd_dir_conf *new =
489 (session_dbd_dir_conf *) apr_pcalloc(p, sizeof(session_dbd_dir_conf));
493 new->selectlabel = "selectsession";
494 new->insertlabel = "insertsession";
495 new->updatelabel = "updatesession";
496 new->deletelabel = "deletesession";
501 static void *merge_session_dbd_dir_config(apr_pool_t * p, void *basev, void *addv)
503 session_dbd_dir_conf *new = (session_dbd_dir_conf *) apr_pcalloc(p, sizeof(session_dbd_dir_conf));
504 session_dbd_dir_conf *add = (session_dbd_dir_conf *) addv;
505 session_dbd_dir_conf *base = (session_dbd_dir_conf *) basev;
507 new->name = (add->name_set == 0) ? base->name : add->name;
508 new->name_attrs = (add->name_set == 0) ? base->name_attrs : add->name_attrs;
509 new->name_set = add->name_set || base->name_set;
510 new->name2 = (add->name2_set == 0) ? base->name2 : add->name2;
511 new->name2_attrs = (add->name2_set == 0) ? base->name2_attrs : add->name2_attrs;
512 new->name2_set = add->name2_set || base->name2_set;
513 new->peruser = (add->peruser_set == 0) ? base->peruser : add->peruser;
514 new->peruser_set = add->peruser_set || base->peruser_set;
515 new->remove = (add->remove_set == 0) ? base->remove : add->remove;
516 new->remove_set = add->remove_set || base->remove_set;
517 new->selectlabel = (!add->selectlabel) ? base->selectlabel : add->selectlabel;
518 new->updatelabel = (!add->updatelabel) ? base->updatelabel : add->updatelabel;
519 new->insertlabel = (!add->insertlabel) ? base->insertlabel : add->insertlabel;
520 new->deletelabel = (!add->deletelabel) ? base->deletelabel : add->deletelabel;
526 * Sanity check a given string that it exists, is not empty,
527 * and does not contain special characters.
529 static const char *check_string(cmd_parms * cmd, const char *string)
531 if (APR_SUCCESS != ap_cookie_check_string(string)) {
532 return apr_pstrcat(cmd->pool, cmd->directive->directive,
533 " cannot be empty, or contain '=', ';' or '&'.",
540 set_dbd_peruser(cmd_parms * parms, void *dconf, int flag)
542 session_dbd_dir_conf *conf = dconf;
544 conf->peruser = flag;
545 conf->peruser_set = 1;
551 set_dbd_cookie_remove(cmd_parms * parms, void *dconf, int flag)
553 session_dbd_dir_conf *conf = dconf;
556 conf->remove_set = 1;
561 static const char *set_cookie_name(cmd_parms * cmd, void *config, const char *args)
564 char *line = apr_pstrdup(cmd->pool, args);
565 session_dbd_dir_conf *conf = (session_dbd_dir_conf *) config;
566 char *cookie = apr_strtok(line, " \t", &last);
569 while (apr_isspace(*last)) {
572 conf->name_attrs = last;
573 return check_string(cmd, cookie);
576 static const char *set_cookie_name2(cmd_parms * cmd, void *config, const char *args)
579 char *line = apr_pstrdup(cmd->pool, args);
580 session_dbd_dir_conf *conf = (session_dbd_dir_conf *) config;
581 char *cookie = apr_strtok(line, " \t", &last);
582 conf->name2 = cookie;
584 while (apr_isspace(*last)) {
587 conf->name2_attrs = last;
588 return check_string(cmd, cookie);
591 static const command_rec session_dbd_cmds[] =
593 AP_INIT_TAKE1("SessionDBDSelectLabel", ap_set_string_slot,
594 (void *) APR_OFFSETOF(session_dbd_dir_conf, selectlabel), RSRC_CONF|OR_AUTHCFG,
595 "Query label used to select a new session"),
596 AP_INIT_TAKE1("SessionDBDInsertLabel", ap_set_string_slot,
597 (void *) APR_OFFSETOF(session_dbd_dir_conf, insertlabel), RSRC_CONF|OR_AUTHCFG,
598 "Query label used to insert a new session"),
599 AP_INIT_TAKE1("SessionDBDUpdateLabel", ap_set_string_slot,
600 (void *) APR_OFFSETOF(session_dbd_dir_conf, updatelabel), RSRC_CONF|OR_AUTHCFG,
601 "Query label used to update an existing session"),
602 AP_INIT_TAKE1("SessionDBDDeleteLabel", ap_set_string_slot,
603 (void *) APR_OFFSETOF(session_dbd_dir_conf, deletelabel), RSRC_CONF|OR_AUTHCFG,
604 "Query label used to delete an existing session"),
605 AP_INIT_FLAG("SessionDBDPerUser", set_dbd_peruser, NULL, RSRC_CONF|OR_AUTHCFG,
606 "Save the session per user"),
607 AP_INIT_FLAG("SessionDBDCookieRemove", set_dbd_cookie_remove, NULL, RSRC_CONF|OR_AUTHCFG,
608 "Remove the session cookie after session load. On by default."),
609 AP_INIT_RAW_ARGS("SessionDBDCookieName", set_cookie_name, NULL, RSRC_CONF|OR_AUTHCFG,
610 "The name of the RFC2109 cookie carrying the session key"),
611 AP_INIT_RAW_ARGS("SessionDBDCookieName2", set_cookie_name2, NULL, RSRC_CONF|OR_AUTHCFG,
612 "The name of the RFC2965 cookie carrying the session key"),
616 static void register_hooks(apr_pool_t * p)
618 ap_hook_session_load(session_dbd_load, NULL, NULL, APR_HOOK_MIDDLE);
619 ap_hook_session_save(session_dbd_save, NULL, NULL, APR_HOOK_MIDDLE);
620 ap_hook_monitor(session_dbd_monitor, NULL, NULL, APR_HOOK_MIDDLE);
623 AP_DECLARE_MODULE(session_dbd) =
625 STANDARD20_MODULE_STUFF,
626 create_session_dbd_dir_config, /* dir config creater */
627 merge_session_dbd_dir_config, /* dir merger --- default is to
629 NULL, /* server config */
630 NULL, /* merge server config */
631 session_dbd_cmds, /* command apr_table_t */
632 register_hooks /* register hooks */