]> granicus.if.org Git - apache/blob - modules/session/mod_session.c
ff4c9a6f2dbeb8756fd1b58bb3398e911c09b05c
[apache] / modules / session / mod_session.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 "util_filter.h"
21 #include "http_log.h"
22 #include "http_request.h"
23 #include "http_protocol.h"
24
25 #define SESSION_EXPIRY "expiry"
26 #define HTTP_SESSION "HTTP_SESSION"
27
28 APR_HOOK_STRUCT(
29                 APR_HOOK_LINK(session_load)
30                 APR_HOOK_LINK(session_save)
31                 APR_HOOK_LINK(session_encode)
32                 APR_HOOK_LINK(session_decode)
33 )
34 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_load,
35                       (request_rec * r, session_rec ** z), (r, z), DECLINED)
36 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_save,
37                        (request_rec * r, session_rec * z), (r, z), DECLINED)
38 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_encode,
39                    (request_rec * r, session_rec * z), (r, z), OK, DECLINED)
40 APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_decode,
41                    (request_rec * r, session_rec * z), (r, z), OK, DECLINED)
42
43 static int session_identity_encode(request_rec * r, session_rec * z);
44 static int session_identity_decode(request_rec * r, session_rec * z);
45 static int session_fixups(request_rec * r);
46
47 /**
48  * Should the session be included within this URL.
49  *
50  * This function tests whether a session is valid for this URL. It uses the
51  * include and exclude arrays to determine whether they should be included.
52  */
53 static int session_included(request_rec * r, session_dir_conf * conf)
54 {
55
56     const char **includes = (const char **) conf->includes->elts;
57     const char **excludes = (const char **) conf->excludes->elts;
58     int included = 1;                /* defaults to included */
59     int i;
60
61     if (conf->includes->nelts) {
62         included = 0;
63         for (i = 0; !included && i < conf->includes->nelts; i++) {
64             const char *include = includes[i];
65             if (strncmp(r->uri, include, strlen(include)) == 0) {
66                 included = 1;
67             }
68         }
69     }
70
71     if (conf->excludes->nelts) {
72         for (i = 0; included && i < conf->excludes->nelts; i++) {
73             const char *exclude = excludes[i];
74             if (strncmp(r->uri, exclude, strlen(exclude)) == 0) {
75                 included = 0;
76             }
77         }
78     }
79
80     return included;
81 }
82
83 /**
84  * Load the session.
85  *
86  * If the session doesn't exist, a blank one will be created.
87  *
88  * @param r The request
89  * @param z A pointer to where the session will be written.
90  */
91 static apr_status_t ap_session_load(request_rec * r, session_rec ** z)
92 {
93
94     session_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
95                                                    &session_module);
96     apr_time_t now;
97     session_rec *zz = NULL;
98     int rv = 0;
99
100     /* is the session enabled? */
101     if (!dconf || !dconf->enabled) {
102         return APR_SUCCESS;
103     }
104
105     /* should the session be loaded at all? */
106     if (!session_included(r, dconf)) {
107         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01814)
108                       "excluded by configuration for: %s", r->uri);
109         return APR_SUCCESS;
110     }
111
112     /* load the session from the session hook */
113     rv = ap_run_session_load(r, &zz);
114     if (DECLINED == rv) {
115         ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01815)
116                       "session is enabled but no session modules have been configured, "
117                       "session not loaded: %s", r->uri);
118         return APR_EGENERAL;
119     }
120     else if (OK != rv) {
121         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01816)
122                       "error while loading the session, "
123                       "session not loaded: %s", r->uri);
124         return rv;
125     }
126
127     /* found a session that hasn't expired? */
128     now = apr_time_now();
129     if (zz) {
130         if (zz->expiry && zz->expiry < now) {
131             zz = NULL;
132         }
133         else {
134             /* having a session we cannot decode is just as good as having
135                none at all */
136             rv = ap_run_session_decode(r, zz);
137             if (OK != rv) {
138                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01817)
139                               "error while decoding the session, "
140                               "session not loaded: %s", r->uri);
141                 zz = NULL;
142             }
143         }
144     }
145
146     /* no luck, create a blank session */
147     if (!zz) {
148         zz = (session_rec *) apr_pcalloc(r->pool, sizeof(session_rec));
149         zz->pool = r->pool;
150         zz->entries = apr_table_make(zz->pool, 10);
151     }
152
153     /* make sure the expiry and maxage are set, if present */
154     if (dconf->maxage) {
155         if (!zz->expiry) {
156             zz->expiry = now + dconf->maxage * APR_USEC_PER_SEC;
157         }
158         zz->maxage = dconf->maxage;
159     }
160
161     *z = zz;
162
163     return APR_SUCCESS;
164
165 }
166
167 /**
168  * Save the session.
169  *
170  * In most implementations the session is only saved if the dirty flag is
171  * true. This prevents the session being saved unnecessarily.
172  *
173  * @param r The request
174  * @param z A pointer to where the session will be written.
175  */
176 static apr_status_t ap_session_save(request_rec * r, session_rec * z)
177 {
178     if (z) {
179         apr_time_t now = apr_time_now();
180         int rv = 0;
181
182         session_dir_conf *dconf = ap_get_module_config(r->per_dir_config,
183                                                        &session_module);
184
185         /* sanity checks, should we try save at all? */
186         if (z->written) {
187             ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01818)
188                           "attempt made to save the session twice, "
189                           "session not saved: %s", r->uri);
190             return APR_EGENERAL;
191         }
192         if (z->expiry && z->expiry < now) {
193             ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01819)
194                           "attempt made to save a session when the session had already expired, "
195                           "session not saved: %s", r->uri);
196             return APR_EGENERAL;
197         }
198
199         /* reset the expiry back to maxage, if the expiry is present */
200         if (dconf->maxage) {
201             z->expiry = now + dconf->maxage * APR_USEC_PER_SEC;
202             z->maxage = dconf->maxage;
203         }
204
205         /* reset the expiry before saving if present */
206         if (z->dirty && z->maxage) {
207             z->expiry = now + z->maxage * APR_USEC_PER_SEC;
208         } 
209
210         /* encode the session */
211         rv = ap_run_session_encode(r, z);
212         if (OK != rv) {
213             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01820)
214                           "error while encoding the session, "
215                           "session not saved: %s", r->uri);
216             return rv;
217         }
218
219         /* try the save */
220         rv = ap_run_session_save(r, z);
221         if (DECLINED == rv) {
222             ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01821)
223                           "session is enabled but no session modules have been configured, "
224                           "session not saved: %s", r->uri);
225             return APR_EGENERAL;
226         }
227         else if (OK != rv) {
228             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01822)
229                           "error while saving the session, "
230                           "session not saved: %s", r->uri);
231             return rv;
232         }
233         else {
234             z->written = 1;
235         }
236     }
237
238     return APR_SUCCESS;
239
240 }
241
242 /**
243  * Get a particular value from the session.
244  * @param r The current request.
245  * @param z The current session. If this value is NULL, the session will be
246  * looked up in the request, created if necessary, and saved to the request
247  * notes.
248  * @param key The key to get.
249  * @param value The buffer to write the value to.
250  */
251 static apr_status_t ap_session_get(request_rec * r, session_rec * z,
252         const char *key, const char **value)
253 {
254     if (!z) {
255         apr_status_t rv;
256         rv = ap_session_load(r, &z);
257         if (APR_SUCCESS != rv) {
258             return rv;
259         }
260     }
261     if (z && z->entries) {
262         *value = apr_table_get(z->entries, key);
263     }
264
265     return OK;
266 }
267
268 /**
269  * Set a particular value to the session.
270  *
271  * Using this method ensures that the dirty flag is set correctly, so that
272  * the session can be saved efficiently.
273  * @param r The current request.
274  * @param z The current session. If this value is NULL, the session will be
275  * looked up in the request, created if necessary, and saved to the request
276  * notes.
277  * @param key The key to set. The existing key value will be replaced.
278  * @param value The value to set.
279  */
280 static apr_status_t ap_session_set(request_rec * r, session_rec * z,
281         const char *key, const char *value)
282 {
283     if (!z) {
284         apr_status_t rv;
285         rv = ap_session_load(r, &z);
286         if (APR_SUCCESS != rv) {
287             return rv;
288         }
289     }
290     if (z) {
291         if (value) {
292             apr_table_set(z->entries, key, value);
293         }
294         else {
295             apr_table_unset(z->entries, key);
296         }
297         z->dirty = 1;
298     }
299     return APR_SUCCESS;
300 }
301
302 static int identity_count(void *v, const char *key, const char *val)
303 {
304     int *count = v;
305     *count += strlen(key) * 3 + strlen(val) * 3 + 1;
306     return 1;
307 }
308
309 static int identity_concat(void *v, const char *key, const char *val)
310 {
311     char *slider = v;
312     int length = strlen(slider);
313     slider += length;
314     if (length) {
315         *slider = '&';
316         slider++;
317     }
318     ap_escape_urlencoded_buffer(slider, key);
319     slider += strlen(slider);
320     *slider = '=';
321     slider++;
322     ap_escape_urlencoded_buffer(slider, val);
323     return 1;
324 }
325
326 /**
327  * Default identity encoding for the session.
328  *
329  * By default, the name value pairs in the session are URLEncoded, separated
330  * by equals, and then in turn separated by ampersand, in the format of an
331  * html form.
332  *
333  * This was chosen to make it easy for external code to unpack a session,
334  * should there be a need to do so.
335  *
336  * @param r The request pointer.
337  * @param z A pointer to where the session will be written.
338  */
339 static apr_status_t session_identity_encode(request_rec * r, session_rec * z)
340 {
341
342     char *buffer = NULL;
343     int length = 0;
344     if (z->expiry) {
345         char *expiry = apr_psprintf(z->pool, "%" APR_INT64_T_FMT, z->expiry);
346         apr_table_setn(z->entries, SESSION_EXPIRY, expiry);
347     }
348     apr_table_do(identity_count, &length, z->entries, NULL);
349     buffer = apr_pcalloc(r->pool, length + 1);
350     apr_table_do(identity_concat, buffer, z->entries, NULL);
351     z->encoded = buffer;
352     return OK;
353
354 }
355
356 /**
357  * Default identity decoding for the session.
358  *
359  * By default, the name value pairs in the session are URLEncoded, separated
360  * by equals, and then in turn separated by ampersand, in the format of an
361  * html form.
362  *
363  * This was chosen to make it easy for external code to unpack a session,
364  * should there be a need to do so.
365  *
366  * This function reverses that process, and populates the session table.
367  *
368  * Name / value pairs that are not encoded properly are ignored.
369  *
370  * @param r The request pointer.
371  * @param z A pointer to where the session will be written.
372  */
373 static apr_status_t session_identity_decode(request_rec * r, session_rec * z)
374 {
375
376     char *last = NULL;
377     char *encoded, *pair;
378     const char *sep = "&";
379
380     /* sanity check - anything to decode? */
381     if (!z->encoded) {
382         return OK;
383     }
384
385     /* decode what we have */
386     encoded = apr_pstrdup(r->pool, z->encoded);
387     pair = apr_strtok(encoded, sep, &last);
388     while (pair && pair[0]) {
389         char *plast = NULL;
390         const char *psep = "=";
391         char *key = apr_strtok(pair, psep, &plast);
392         char *val = apr_strtok(NULL, psep, &plast);
393         if (key && *key) {
394             if (!val || !*val) {
395                 apr_table_unset(z->entries, key);
396             }
397             else if (!ap_unescape_urlencoded(key) && !ap_unescape_urlencoded(val)) {
398                 if (!strcmp(SESSION_EXPIRY, key)) {
399                     z->expiry = (apr_time_t) apr_atoi64(val);
400                 }
401                 else {
402                     apr_table_set(z->entries, key, val);
403                 }
404             }
405         }
406         pair = apr_strtok(NULL, sep, &last);
407     }
408     z->encoded = NULL;
409     return OK;
410
411 }
412
413 /**
414  * Ensure any changes to the session are committed.
415  *
416  * This is done in an output filter so that our options for where to
417  * store the session can include storing the session within a cookie:
418  * As an HTTP header, the cookie must be set before the output is
419  * written, but after the handler is run.
420  *
421  * NOTE: It is possible for internal redirects to cause more than one
422  * request to be present, and each request might have a session
423  * defined. We need to go through each session in turn, and save each
424  * one.
425  *
426  * The same session might appear in more than one request. The first
427  * attempt to save the session will be called
428  */
429 static apr_status_t session_output_filter(ap_filter_t * f,
430         apr_bucket_brigade * in)
431 {
432
433     /* save all the sessions in all the requests */
434     request_rec *r = f->r->main;
435     if (!r) {
436         r = f->r;
437     }
438     while (r) {
439         session_rec *z = NULL;
440         session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
441                                                       &session_module);
442
443         /* load the session, or create one if necessary */
444         /* when unset or on error, z will be NULL */
445         ap_session_load(r, &z);
446         if (!z || z->written) {
447             r = r->next;
448             continue;
449         }
450
451         /* if a header was specified, insert the new values from the header */
452         if (conf->header_set) {
453             const char *override = apr_table_get(r->err_headers_out, conf->header);
454             if (!override) {
455                 override = apr_table_get(r->headers_out, conf->header);
456             }
457             if (override) {
458                 apr_table_unset(r->err_headers_out, conf->header);
459                 apr_table_unset(r->headers_out, conf->header);
460                 z->encoded = override;
461                 z->dirty = 1;
462                 session_identity_decode(r, z);
463             }
464         }
465
466         /* save away the session, and we're done */
467         /* when unset or on error, we've complained to the log */
468         ap_session_save(r, z);
469
470         r = r->next;
471     }
472
473     /* remove ourselves from the filter chain */
474     ap_remove_output_filter(f);
475
476     /* send the data up the stack */
477     return ap_pass_brigade(f->next, in);
478
479 }
480
481 /**
482  * Insert the output filter.
483  */
484 static void session_insert_output_filter(request_rec * r)
485 {
486     ap_add_output_filter("MOD_SESSION_OUT", NULL, r, r->connection);
487 }
488
489 /**
490  * Fixups hook.
491  *
492  * Load the session within a fixup - this ensures that the session is
493  * properly loaded prior to the handler being called.
494  *
495  * The fixup is also responsible for injecting the session into the CGI
496  * environment, should the admin have configured it so.
497  *
498  * @param r The request
499  */
500 static int session_fixups(request_rec * r)
501 {
502     session_dir_conf *conf = ap_get_module_config(r->per_dir_config,
503                                                   &session_module);
504
505     session_rec *z = NULL;
506
507     /* if an error occurs or no session has been configured, we ignore
508      * the broken session and allow it to be recreated from scratch on save
509      * if necessary.
510      */
511     ap_session_load(r, &z);
512
513     if (z && conf->env) {
514         session_identity_encode(r, z);
515         if (z->encoded) {
516             apr_table_set(r->subprocess_env, HTTP_SESSION, z->encoded);
517             z->encoded = NULL;
518         }
519     }
520
521     return OK;
522
523 }
524
525
526 static void *create_session_dir_config(apr_pool_t * p, char *dummy)
527 {
528     session_dir_conf *new =
529     (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf));
530
531     new->includes = apr_array_make(p, 10, sizeof(const char **));
532     new->excludes = apr_array_make(p, 10, sizeof(const char **));
533
534     return (void *) new;
535 }
536
537 static void *merge_session_dir_config(apr_pool_t * p, void *basev, void *addv)
538 {
539     session_dir_conf *new = (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf));
540     session_dir_conf *add = (session_dir_conf *) addv;
541     session_dir_conf *base = (session_dir_conf *) basev;
542
543     new->enabled = (add->enabled_set == 0) ? base->enabled : add->enabled;
544     new->enabled_set = add->enabled_set || base->enabled_set;
545     new->maxage = (add->maxage_set == 0) ? base->maxage : add->maxage;
546     new->maxage_set = add->maxage_set || base->maxage_set;
547     new->header = (add->header_set == 0) ? base->header : add->header;
548     new->header_set = add->header_set || base->header_set;
549     new->env = (add->env_set == 0) ? base->env : add->env;
550     new->env_set = add->env_set || base->env_set;
551     new->includes = apr_array_append(p, base->includes, add->includes);
552     new->excludes = apr_array_append(p, base->excludes, add->excludes);
553
554     return new;
555 }
556
557
558 static const char *
559      set_session_enable(cmd_parms * parms, void *dconf, int flag)
560 {
561     session_dir_conf *conf = dconf;
562
563     conf->enabled = flag;
564     conf->enabled_set = 1;
565
566     return NULL;
567 }
568
569 static const char *
570      set_session_maxage(cmd_parms * parms, void *dconf, const char *arg)
571 {
572     session_dir_conf *conf = dconf;
573
574     conf->maxage = atol(arg);
575     conf->maxage_set = 1;
576
577     return NULL;
578 }
579
580 static const char *
581      set_session_header(cmd_parms * parms, void *dconf, const char *arg)
582 {
583     session_dir_conf *conf = dconf;
584
585     conf->header = arg;
586     conf->header_set = 1;
587
588     return NULL;
589 }
590
591 static const char *
592      set_session_env(cmd_parms * parms, void *dconf, int flag)
593 {
594     session_dir_conf *conf = dconf;
595
596     conf->env = flag;
597     conf->env_set = 1;
598
599     return NULL;
600 }
601
602 static const char *add_session_include(cmd_parms * cmd, void *dconf, const char *f)
603 {
604     session_dir_conf *conf = dconf;
605
606     const char **new = apr_array_push(conf->includes);
607     *new = f;
608
609     return NULL;
610 }
611
612 static const char *add_session_exclude(cmd_parms * cmd, void *dconf, const char *f)
613 {
614     session_dir_conf *conf = dconf;
615
616     const char **new = apr_array_push(conf->excludes);
617     *new = f;
618
619     return NULL;
620 }
621
622
623 static const command_rec session_cmds[] =
624 {
625     AP_INIT_FLAG("Session", set_session_enable, NULL, RSRC_CONF|OR_AUTHCFG,
626                  "on if a session should be maintained for these URLs"),
627     AP_INIT_TAKE1("SessionMaxAge", set_session_maxage, NULL, RSRC_CONF|OR_AUTHCFG,
628                   "length of time for which a session should be valid. Zero to disable"),
629     AP_INIT_TAKE1("SessionHeader", set_session_header, NULL, RSRC_CONF|OR_AUTHCFG,
630                   "output header, if present, whose contents will be injected into the session."),
631     AP_INIT_FLAG("SessionEnv", set_session_env, NULL, RSRC_CONF|OR_AUTHCFG,
632                  "on if a session should be written to the CGI environment. Defaults to off"),
633     AP_INIT_TAKE1("SessionInclude", add_session_include, NULL, RSRC_CONF|OR_AUTHCFG,
634                   "URL prefixes to include in the session. Defaults to all URLs"),
635     AP_INIT_TAKE1("SessionExclude", add_session_exclude, NULL, RSRC_CONF|OR_AUTHCFG,
636                   "URL prefixes to exclude from the session. Defaults to no URLs"),
637     {NULL}
638 };
639
640 static void register_hooks(apr_pool_t * p)
641 {
642     ap_register_output_filter("MOD_SESSION_OUT", session_output_filter,
643                               NULL, AP_FTYPE_CONTENT_SET);
644     ap_hook_insert_filter(session_insert_output_filter, NULL, NULL,
645                           APR_HOOK_MIDDLE);
646     ap_hook_insert_error_filter(session_insert_output_filter,
647                                 NULL, NULL, APR_HOOK_MIDDLE);
648     ap_hook_fixups(session_fixups, NULL, NULL, APR_HOOK_MIDDLE);
649     ap_hook_session_encode(session_identity_encode, NULL, NULL,
650                            APR_HOOK_REALLY_FIRST);
651     ap_hook_session_decode(session_identity_decode, NULL, NULL,
652                            APR_HOOK_REALLY_LAST);
653     APR_REGISTER_OPTIONAL_FN(ap_session_get);
654     APR_REGISTER_OPTIONAL_FN(ap_session_set);
655     APR_REGISTER_OPTIONAL_FN(ap_session_load);
656     APR_REGISTER_OPTIONAL_FN(ap_session_save);
657 }
658
659 AP_DECLARE_MODULE(session) =
660 {
661     STANDARD20_MODULE_STUFF,
662     create_session_dir_config,   /* dir config creater */
663     merge_session_dir_config,    /* dir merger --- default is to override */
664     NULL,                        /* server config */
665     NULL,                        /* merge server config */
666     session_cmds,                /* command apr_table_t */
667     register_hooks               /* register hooks */
668 };