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