]> granicus.if.org Git - apache/blob - modules/session/mod_session_dbd.c
8e8e2f054a7380f492175cb97b6493cb5f12f0a5
[apache] / modules / session / mod_session_dbd.c
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "mod_session.h"
18 #include "apr_lib.h"
19 #include "apr_strings.h"
20 #include "http_log.h"
21 #include "util_cookies.h"
22 #include "apr_dbd.h"
23 #include "mod_dbd.h"
24 #include "mpm_common.h"
25
26 #define MOD_SESSION_DBD "mod_session_dbd"
27
28 module AP_MODULE_DECLARE_DATA session_dbd_module;
29
30 /**
31  * Structure to carry the per-dir session config.
32  */
33 typedef struct {
34     const char *name;
35     int name_set;
36     const char *name_attrs;
37     const char *name2;
38     int name2_set;
39     const char *name2_attrs;
40     int peruser;
41     int peruser_set;
42     int remove;
43     int remove_set;
44     const char *selectlabel;
45     const char *insertlabel;
46     const char *updatelabel;
47     const char *deletelabel;
48 } session_dbd_dir_conf;
49
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;
53
54 /**
55  * Initialise the database.
56  *
57  * If the mod_dbd module is missing, this method will return APR_EGENERAL.
58  */
59 static apr_status_t dbd_init(request_rec *r, const char *query, ap_dbd_t **dbdp,
60                              apr_dbd_prepared_t **statementp)
61 {
62     ap_dbd_t *dbd;
63     apr_dbd_prepared_t *statement;
64
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");
71             return APR_EGENERAL;
72         }
73     }
74
75     dbd = session_dbd_acquire_fn(r);
76     if (!dbd) {
77         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
78                       "failed to acquire database connection");
79         return APR_EGENERAL;
80     }
81
82     statement = apr_hash_get(dbd->prepared, query, APR_HASH_KEY_STRING);
83     if (!statement) {
84         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
85                       "failed to find the prepared statement called '%s'", query);
86         return APR_EGENERAL;
87     }
88
89     *dbdp = dbd;
90     *statementp = statement;
91
92     return APR_SUCCESS;
93 }
94
95 /**
96  * Load the session by the key specified.
97  */
98 static apr_status_t dbd_load(request_rec * r, const char *key, const char **val)
99 {
100
101     apr_status_t rv;
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();
107
108     session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
109                                                       &session_dbd_module);
110
111     if (conf->selectlabel == NULL) {
112         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
113                       "no SessionDBDselectlabel has been specified");
114         return APR_EGENERAL;
115     }
116
117     rv = dbd_init(r, conf->selectlabel, &dbd, &statement);
118     if (rv) {
119         return rv;
120     }
121     rv = apr_dbd_pvbselect(dbd->driver, r->pool, dbd->handle, &res, statement,
122                           0, key, &expiry, NULL);
123     if (rv) {
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));
128         return APR_EGENERAL;
129     }
130     for (rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1);
131          rv != -1;
132          rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, -1)) {
133         if (rv != 0) {
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));
138             return APR_EGENERAL;
139         }
140         if (*val == NULL) {
141             *val = apr_dbd_get_entry(dbd->driver, row, 0);
142         }
143         /* we can't break out here or row won't get cleaned up */
144     }
145
146     return APR_SUCCESS;
147
148 }
149
150 /**
151  * Load the session by firing off a dbd query.
152  *
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.
156  *
157  * If the session is keyed by the username, the session will be extracted
158  * by that.
159  *
160  * If no session is found, an empty session will be created.
161  *
162  * On success, this returns OK.
163  */
164 static apr_status_t session_dbd_load(request_rec * r, session_rec ** z)
165 {
166
167     session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
168                                                       &session_dbd_module);
169
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;
177
178     /* is our session in a cookie? */
179     if (conf->name2_set) {
180         name = conf->name2;
181     }
182     else if (conf->name_set) {
183         name = conf->name;
184     }
185     else if (conf->peruser_set && r->user) {
186         name = r->user;
187     }
188     else {
189         return DECLINED;
190     }
191
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);
195     if (zz) {
196         *z = zz;
197         return OK;
198     }
199
200     /* load anonymous sessions */
201     if (conf->name_set || conf->name2_set) {
202
203         /* load an RFC2109 or RFC2965 compliant cookie */
204         ap_cookie_read(r, name, &key, conf->remove);
205         if (key) {
206             ret = dbd_load(r, key, &val);
207             if (ret != APR_SUCCESS) {
208                 return ret;
209             }
210         }
211
212     }
213
214     /* load named session */
215     else if (conf->peruser) {
216         if (r->user) {
217             ret = dbd_load(r, r->user, &val);
218             if (ret != APR_SUCCESS) {
219                 return ret;
220             }
221         }
222     }
223
224     /* otherwise not for us */
225     else {
226         return DECLINED;
227     }
228
229     /* create a new session and return it */
230     zz = (session_rec *) apr_pcalloc(r->pool, sizeof(session_rec));
231     zz->pool = r->pool;
232     zz->entries = apr_table_make(zz->pool, 10);
233     zz->uuid = (apr_uuid_t *) apr_pcalloc(zz->pool, sizeof(apr_uuid_t));
234     if (key) {
235         apr_uuid_parse(zz->uuid, key);
236     }
237     else {
238         apr_uuid_get(zz->uuid);
239     }
240     zz->encoded = val;
241     *z = zz;
242
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);
245
246     return OK;
247
248 }
249
250 /**
251  * Save the session by the key specified.
252  */
253 static apr_status_t dbd_save(request_rec * r, const char *key, const char *val,
254                              apr_int64_t expiry)
255 {
256
257     apr_status_t rv;
258     ap_dbd_t *dbd = NULL;
259     apr_dbd_prepared_t *statement;
260     int rows = 0;
261
262     session_dbd_dir_conf *conf = ap_get_module_config(r->per_dir_config,
263                                                       &session_dbd_module);
264
265     if (conf->updatelabel == NULL) {
266         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
267                       "no SessionDBDupdatelabel has been specified");
268         return APR_EGENERAL;
269     }
270
271     rv = dbd_init(r, conf->updatelabel, &dbd, &statement);
272     if (rv) {
273         return rv;
274     }
275     rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement,
276                           val, &expiry, key, NULL);
277     if (rv) {
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));
282         return APR_EGENERAL;
283     }
284
285     /*
286      * if some rows were updated it means a session existed and was updated,
287      * so we are done.
288      */
289     if (rows != 0) {
290         return APR_SUCCESS;
291     }
292
293     if (conf->insertlabel == NULL) {
294         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
295                       "no SessionDBDinsertlabel has been specified");
296         return APR_EGENERAL;
297     }
298
299     rv = dbd_init(r, conf->insertlabel, &dbd, &statement);
300     if (rv) {
301         return rv;
302     }
303     rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement,
304                           val, &expiry, key, NULL);
305     if (rv) {
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));
310         return APR_EGENERAL;
311     }
312
313     /*
314      * if some rows were inserted it means a session was inserted, so we are
315      * done.
316      */
317     if (rows != 0) {
318         return APR_SUCCESS;
319     }
320
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);
324
325     return APR_EGENERAL;
326
327 }
328
329 /**
330  * Remove the session by the key specified.
331  */
332 static apr_status_t dbd_remove(request_rec * r, const char *key)
333 {
334
335     apr_status_t rv;
336     apr_dbd_prepared_t *statement;
337     int rows = 0;
338
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);
342     if (dbd == NULL) {
343         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
344                       "failed to acquire database connection to remove "
345                       "session with key '%s'", key);
346         return APR_EGENERAL;
347     }
348
349     if (conf->deletelabel == NULL) {
350         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
351                       "no SessionDBDdeletelabel has been specified");
352         return APR_EGENERAL;
353     }
354
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'",
361                       conf->deletelabel);
362         return APR_EGENERAL;
363     }
364     rv = apr_dbd_pvbquery(dbd->driver, r->pool, dbd->handle, &rows, statement,
365                           key, NULL);
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);
370         return rv;
371     }
372
373     return APR_SUCCESS;
374
375 }
376
377 /**
378  * Clean out expired sessions.
379  *
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.
383  */
384 static apr_status_t dbd_clean(apr_pool_t *p, server_rec *s)
385 {
386
387     return APR_ENOTIMPL;
388
389 }
390
391 /**
392  * Save the session by firing off a dbd query.
393  *
394  * If the session is anonymous, save the session and write a cookie
395  * containing the uuid.
396  *
397  * If the session is keyed to the username, save the session using
398  * the username as a key.
399  *
400  * On success, this method will return APR_SUCCESS.
401  *
402  * @param r The request pointer.
403  * @param z A pointer to where the session will be written.
404  */
405 static apr_status_t session_dbd_save(request_rec * r, session_rec * z)
406 {
407
408     char *buffer;
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);
412
413     /* support anonymous sessions */
414     if (conf->name_set || conf->name2_set) {
415
416         /* don't cache pages with a session */
417         apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
418
419         /* must we create a uuid? */
420         buffer = apr_pcalloc(r->pool, APR_UUID_FORMATTED_LENGTH + 1);
421         apr_uuid_format(buffer, z->uuid);
422
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);
426         }
427         else {
428             ret = dbd_remove(r, buffer);
429         }
430         if (ret != APR_SUCCESS) {
431             return ret;
432         }
433
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);
438         }
439
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);
444         }
445
446         return OK;
447
448     }
449
450     /* save named session */
451     else if (conf->peruser) {
452
453         /* don't cache pages with a session */
454         apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
455
456         if (r->user) {
457             ret = dbd_save(r, r->user, z->encoded, z->expiry);
458             if (ret != APR_SUCCESS) {
459                 return ret;
460             }
461             return OK;
462         }
463         else {
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);
467         }
468     }
469
470     return DECLINED;
471
472 }
473
474 /**
475  * This function performs housekeeping on the database, deleting expired
476  * sessions.
477  */
478 static int session_dbd_monitor(apr_pool_t *p, server_rec *s)
479 {
480     /* TODO handle housekeeping */
481     dbd_clean(p, s);
482     return OK;
483 }
484
485
486 static void *create_session_dbd_dir_config(apr_pool_t * p, char *dummy)
487 {
488     session_dbd_dir_conf *new =
489     (session_dbd_dir_conf *) apr_pcalloc(p, sizeof(session_dbd_dir_conf));
490
491     new->remove = 1;
492
493     new->selectlabel = "selectsession";
494     new->insertlabel = "insertsession";
495     new->updatelabel = "updatesession";
496     new->deletelabel = "deletesession";
497
498     return (void *) new;
499 }
500
501 static void *merge_session_dbd_dir_config(apr_pool_t * p, void *basev, void *addv)
502 {
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;
506
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;
521
522     return new;
523 }
524
525 /**
526  * Sanity check a given string that it exists, is not empty,
527  * and does not contain special characters.
528  */
529 static const char *check_string(cmd_parms * cmd, const char *string)
530 {
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 '&'.",
534                            NULL);
535     }
536     return NULL;
537 }
538
539 static const char *
540      set_dbd_peruser(cmd_parms * parms, void *dconf, int flag)
541 {
542     session_dbd_dir_conf *conf = dconf;
543
544     conf->peruser = flag;
545     conf->peruser_set = 1;
546
547     return NULL;
548 }
549
550 static const char *
551      set_dbd_cookie_remove(cmd_parms * parms, void *dconf, int flag)
552 {
553     session_dbd_dir_conf *conf = dconf;
554
555     conf->remove = flag;
556     conf->remove_set = 1;
557
558     return NULL;
559 }
560
561 static const char *set_cookie_name(cmd_parms * cmd, void *config, const char *args)
562 {
563     char *last;
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);
567     conf->name = cookie;
568     conf->name_set = 1;
569     while (apr_isspace(*last)) {
570         last++;
571     }
572     conf->name_attrs = last;
573     return check_string(cmd, cookie);
574 }
575
576 static const char *set_cookie_name2(cmd_parms * cmd, void *config, const char *args)
577 {
578     char *last;
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;
583     conf->name2_set = 1;
584     while (apr_isspace(*last)) {
585         last++;
586     }
587     conf->name2_attrs = last;
588     return check_string(cmd, cookie);
589 }
590
591 static const command_rec session_dbd_cmds[] =
592 {
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"),
613     {NULL}
614 };
615
616 static void register_hooks(apr_pool_t * p)
617 {
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);
621 }
622
623 AP_DECLARE_MODULE(session_dbd) =
624 {
625     STANDARD20_MODULE_STUFF,
626     create_session_dbd_dir_config, /* dir config creater */
627     merge_session_dbd_dir_config,  /* dir merger --- default is to
628                                     * override */
629     NULL,                          /* server config */
630     NULL,                          /* merge server config */
631     session_dbd_cmds,              /* command apr_table_t */
632     register_hooks                 /* register hooks */
633 };