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 28APR_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) 34APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_load, 35 (request_rec * r, session_rec ** z), (r, z), DECLINED) 36APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ap, SESSION, int, session_save, 37 (request_rec * r, session_rec * z), (r, z), DECLINED) 38APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_encode, 39 (request_rec * r, session_rec * z), (r, z), OK, DECLINED) 40APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ap, SESSION, int, session_decode, 41 (request_rec * r, session_rec * z), (r, z), OK, DECLINED) 42 43static int session_identity_encode(request_rec * r, session_rec * z); 44static int session_identity_decode(request_rec * r, session_rec * z); 45static 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 */ 53static 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 */ 91static 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 */ 176static 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 */ 251static 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 */ 280static 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 302static int identity_count(int *count, const char *key, const char *val) 303{ 304 *count += strlen(key) * 3 + strlen(val) * 3 + 1; 305 return 1; 306} 307 308static int identity_concat(char *buffer, const char *key, const char *val) 309{ 310 char *slider = buffer; 311 int length = strlen(slider); 312 slider += length; 313 if (length) { 314 *slider = '&'; 315 slider++; 316 } 317 ap_escape_urlencoded_buffer(slider, key); 318 slider += strlen(slider); 319 *slider = '='; 320 slider++; 321 ap_escape_urlencoded_buffer(slider, val); 322 return 1; 323} 324 325/** 326 * Default identity encoding for the session. 327 * 328 * By default, the name value pairs in the session are URLEncoded, separated 329 * by equals, and then in turn separated by ampersand, in the format of an 330 * html form. 331 * 332 * This was chosen to make it easy for external code to unpack a session, 333 * should there be a need to do so. 334 * 335 * @param r The request pointer. 336 * @param z A pointer to where the session will be written. 337 */ 338static apr_status_t session_identity_encode(request_rec * r, session_rec * z) 339{ 340 341 char *buffer = NULL; 342 int length = 0; 343 if (z->expiry) { 344 char *expiry = apr_psprintf(z->pool, "%" APR_INT64_T_FMT, z->expiry); 345 apr_table_setn(z->entries, SESSION_EXPIRY, expiry); 346 } 347 apr_table_do((int (*) (void *, const char *, const char *)) 348 identity_count, &length, z->entries, NULL); 349 buffer = apr_pcalloc(r->pool, length + 1); 350 apr_table_do((int (*) (void *, const char *, const char *)) 351 identity_concat, buffer, z->entries, NULL); 352 z->encoded = buffer; 353 return OK; 354 355} 356 357/** 358 * Default identity decoding for the session. 359 * 360 * By default, the name value pairs in the session are URLEncoded, separated 361 * by equals, and then in turn separated by ampersand, in the format of an 362 * html form. 363 * 364 * This was chosen to make it easy for external code to unpack a session, 365 * should there be a need to do so. 366 * 367 * This function reverses that process, and populates the session table. 368 * 369 * Name / value pairs that are not encoded properly are ignored. 370 * 371 * @param r The request pointer. 372 * @param z A pointer to where the session will be written. 373 */ 374static apr_status_t session_identity_decode(request_rec * r, session_rec * z) 375{ 376 377 char *last = NULL; 378 char *encoded, *pair; 379 const char *sep = "&"; 380 381 /* sanity check - anything to decode? */ 382 if (!z->encoded) { 383 return OK; 384 } 385 386 /* decode what we have */ 387 encoded = apr_pstrdup(r->pool, z->encoded); 388 pair = apr_strtok(encoded, sep, &last); 389 while (pair && pair[0]) { 390 char *plast = NULL; 391 const char *psep = "="; 392 char *key = apr_strtok(pair, psep, &plast); 393 char *val = apr_strtok(NULL, psep, &plast); 394 if (key && *key) { 395 if (!val || !*val) { 396 apr_table_unset(z->entries, key); 397 } 398 else if (!ap_unescape_urlencoded(key) && !ap_unescape_urlencoded(val)) { 399 if (!strcmp(SESSION_EXPIRY, key)) { 400 z->expiry = (apr_time_t) apr_atoi64(val); 401 } 402 else { 403 apr_table_set(z->entries, key, val); 404 } 405 } 406 } 407 pair = apr_strtok(NULL, sep, &last); 408 } 409 z->encoded = NULL; 410 return OK; 411 412} 413 414/** 415 * Ensure any changes to the session are committed. 416 * 417 * This is done in an output filter so that our options for where to 418 * store the session can include storing the session within a cookie: 419 * As an HTTP header, the cookie must be set before the output is 420 * written, but after the handler is run. 421 * 422 * NOTE: It is possible for internal redirects to cause more than one 423 * request to be present, and each request might have a session 424 * defined. We need to go through each session in turn, and save each 425 * one. 426 * 427 * The same session might appear in more than one request. The first 428 * attempt to save the session will be called 429 */ 430static apr_status_t session_output_filter(ap_filter_t * f, 431 apr_bucket_brigade * in) 432{ 433 434 /* save all the sessions in all the requests */ 435 request_rec *r = f->r->main; 436 if (!r) { 437 r = f->r; 438 } 439 while (r) { 440 session_rec *z = NULL; 441 session_dir_conf *conf = ap_get_module_config(r->per_dir_config, 442 &session_module); 443 444 /* load the session, or create one if necessary */ 445 /* when unset or on error, z will be NULL */ 446 ap_session_load(r, &z); 447 if (!z || z->written) { 448 r = r->next; 449 continue; 450 } 451 452 /* if a header was specified, insert the new values from the header */ 453 if (conf->header_set) { 454 const char *override = apr_table_get(r->err_headers_out, conf->header); 455 if (!override) { 456 override = apr_table_get(r->headers_out, conf->header); 457 } 458 if (override) { 459 apr_table_unset(r->err_headers_out, conf->header); 460 apr_table_unset(r->headers_out, conf->header); 461 z->encoded = override; 462 z->dirty = 1; 463 session_identity_decode(r, z); 464 } 465 } 466 467 /* save away the session, and we're done */ 468 /* when unset or on error, we've complained to the log */ 469 ap_session_save(r, z); 470 471 r = r->next; 472 } 473 474 /* remove ourselves from the filter chain */ 475 ap_remove_output_filter(f); 476 477 /* send the data up the stack */ 478 return ap_pass_brigade(f->next, in); 479 480} 481 482/** 483 * Insert the output filter. 484 */ 485static void session_insert_output_filter(request_rec * r) 486{ 487 ap_add_output_filter("MOD_SESSION_OUT", NULL, r, r->connection); 488} 489 490/** 491 * Fixups hook. 492 * 493 * Load the session within a fixup - this ensures that the session is 494 * properly loaded prior to the handler being called. 495 * 496 * The fixup is also responsible for injecting the session into the CGI 497 * environment, should the admin have configured it so. 498 * 499 * @param r The request 500 */ 501static int session_fixups(request_rec * r) 502{ 503 session_dir_conf *conf = ap_get_module_config(r->per_dir_config, 504 &session_module); 505 506 session_rec *z = NULL; 507 508 /* if an error occurs or no session has been configured, we ignore 509 * the broken session and allow it to be recreated from scratch on save 510 * if necessary. 511 */ 512 ap_session_load(r, &z); 513 514 if (z && conf->env) { 515 session_identity_encode(r, z); 516 if (z->encoded) { 517 apr_table_set(r->subprocess_env, HTTP_SESSION, z->encoded); 518 z->encoded = NULL; 519 } 520 } 521 522 return OK; 523 524} 525 526 527static void *create_session_dir_config(apr_pool_t * p, char *dummy) 528{ 529 session_dir_conf *new = 530 (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf)); 531 532 new->includes = apr_array_make(p, 10, sizeof(const char **)); 533 new->excludes = apr_array_make(p, 10, sizeof(const char **)); 534 535 return (void *) new; 536} 537 538static void *merge_session_dir_config(apr_pool_t * p, void *basev, void *addv) 539{ 540 session_dir_conf *new = (session_dir_conf *) apr_pcalloc(p, sizeof(session_dir_conf)); 541 session_dir_conf *add = (session_dir_conf *) addv; 542 session_dir_conf *base = (session_dir_conf *) basev; 543 544 new->enabled = (add->enabled_set == 0) ? base->enabled : add->enabled; 545 new->enabled_set = add->enabled_set || base->enabled_set; 546 new->maxage = (add->maxage_set == 0) ? base->maxage : add->maxage; 547 new->maxage_set = add->maxage_set || base->maxage_set; 548 new->header = (add->header_set == 0) ? base->header : add->header; 549 new->header_set = add->header_set || base->header_set; 550 new->env = (add->env_set == 0) ? base->env : add->env; 551 new->env_set = add->env_set || base->env_set; 552 new->includes = apr_array_append(p, base->includes, add->includes); 553 new->excludes = apr_array_append(p, base->excludes, add->excludes); 554 555 return new; 556} 557 558 559static const char * 560 set_session_enable(cmd_parms * parms, void *dconf, int flag) 561{ 562 session_dir_conf *conf = dconf; 563 564 conf->enabled = flag; 565 conf->enabled_set = 1; 566 567 return NULL; 568} 569 570static const char * 571 set_session_maxage(cmd_parms * parms, void *dconf, const char *arg) 572{ 573 session_dir_conf *conf = dconf; 574 575 conf->maxage = atol(arg); 576 conf->maxage_set = 1; 577 578 return NULL; 579} 580 581static const char * 582 set_session_header(cmd_parms * parms, void *dconf, const char *arg) 583{ 584 session_dir_conf *conf = dconf; 585 586 conf->header = arg; 587 conf->header_set = 1; 588 589 return NULL; 590} 591 592static const char * 593 set_session_env(cmd_parms * parms, void *dconf, int flag) 594{ 595 session_dir_conf *conf = dconf; 596 597 conf->env = flag; 598 conf->env_set = 1; 599 600 return NULL; 601} 602 603static const char *add_session_include(cmd_parms * cmd, void *dconf, const char *f) 604{ 605 session_dir_conf *conf = dconf; 606 607 const char **new = apr_array_push(conf->includes); 608 *new = f; 609 610 return NULL; 611} 612 613static const char *add_session_exclude(cmd_parms * cmd, void *dconf, const char *f) 614{ 615 session_dir_conf *conf = dconf; 616 617 const char **new = apr_array_push(conf->excludes); 618 *new = f; 619 620 return NULL; 621} 622 623 624static const command_rec session_cmds[] = 625{ 626 AP_INIT_FLAG("Session", set_session_enable, NULL, RSRC_CONF|OR_AUTHCFG, 627 "on if a session should be maintained for these URLs"), 628 AP_INIT_TAKE1("SessionMaxAge", set_session_maxage, NULL, RSRC_CONF|OR_AUTHCFG, 629 "length of time for which a session should be valid. Zero to disable"), 630 AP_INIT_TAKE1("SessionHeader", set_session_header, NULL, RSRC_CONF|OR_AUTHCFG, 631 "output header, if present, whose contents will be injected into the session."), 632 AP_INIT_FLAG("SessionEnv", set_session_env, NULL, RSRC_CONF|OR_AUTHCFG, 633 "on if a session should be written to the CGI environment. Defaults to off"), 634 AP_INIT_TAKE1("SessionInclude", add_session_include, NULL, RSRC_CONF|OR_AUTHCFG, 635 "URL prefixes to include in the session. Defaults to all URLs"), 636 AP_INIT_TAKE1("SessionExclude", add_session_exclude, NULL, RSRC_CONF|OR_AUTHCFG, 637 "URL prefixes to exclude from the session. Defaults to no URLs"), 638 {NULL} 639}; 640 641static void register_hooks(apr_pool_t * p) 642{ 643 ap_register_output_filter("MOD_SESSION_OUT", session_output_filter, 644 NULL, AP_FTYPE_CONTENT_SET); 645 ap_hook_insert_filter(session_insert_output_filter, NULL, NULL, 646 APR_HOOK_MIDDLE); 647 ap_hook_insert_error_filter(session_insert_output_filter, 648 NULL, NULL, APR_HOOK_MIDDLE); 649 ap_hook_fixups(session_fixups, NULL, NULL, APR_HOOK_MIDDLE); 650 ap_hook_session_encode(session_identity_encode, NULL, NULL, 651 APR_HOOK_REALLY_FIRST); 652 ap_hook_session_decode(session_identity_decode, NULL, NULL, 653 APR_HOOK_REALLY_LAST); 654 APR_REGISTER_OPTIONAL_FN(ap_session_get); 655 APR_REGISTER_OPTIONAL_FN(ap_session_set); 656 APR_REGISTER_OPTIONAL_FN(ap_session_load); 657 APR_REGISTER_OPTIONAL_FN(ap_session_save); 658} 659 660AP_DECLARE_MODULE(session) = 661{ 662 STANDARD20_MODULE_STUFF, 663 create_session_dir_config, /* dir config creater */ 664 merge_session_dir_config, /* dir merger --- default is to override */ 665 NULL, /* server config */ 666 NULL, /* merge server config */ 667 session_cmds, /* command apr_table_t */ 668 register_hooks /* register hooks */ 669}; 670