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