/* ======================================================================= * Copyright (c) 1996,1997 Vidya Media Ventures, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of this source code or a derived source code must * retain the above copyright notice, this list of conditions and the * following disclaimer. * * 2. Redistributions of this module or a derived module in binary form * must reproduce the above copyright notice, this list of conditions * and the following disclaimer in the documentation, packaging and/or * other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY VIDYA MEDIA VENTURES, INC. ``AS IS'' AND * ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL VIDYA MEDIA VENTURES, INC. * OR ITS EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ======================================================================= * * This software is a contribution to and makes use of the Apache HTTP * server which is written, maintained and copywritten by the Apache Group. * See http://www.apache.org/ for more information. * */ /* * Session key maintenance module * * Version 0.5 (December 1996) * * Adam Sussman (asussman@vidya.com) * * Requires Apache 1.2 or later. * * Outline: * * This module does its best to insure that a consistent identifier is * maintained by the client no matter what kind of browser that client is * and no matter what path they take through the session controlled areas * of the site. This identifier, refered to as a "session key", is always * made present as an environment variable (r->subprocess_env internaly * and the actual process enviroment for CGI, etc). This session key is * always held in the SESSION_KEY environment. * * Session keys can be logged by adding a %{SESSION_KEY}n to the log format. * * * Basic Theory of Operation: * * There are a few ways to make a browser maintain and retransmit a session key. * The perferable way is to use HTTP cookies. However, many browsers do not support * cookies (and even older versions of browsers that do don't, and are still out there). * In this case, the session key can be stored in the URLs that the browsers see in * the HTML documents. Doing it this way is a little more complicated as we have * to make sure that the URLs in a given document always have this session key information. * There are additional complications using imagemaps in this scenario. This module * attempts to deal with all of these cases. * * There are also a few other benefits and desirable qualities in a session * controlled environment. One of these that this module provides is the * ability to force entry only at at certain URLs. Thus, if a browser * makes a request for a URL without providing a session key, they can be * redirected to a specific entrance point where a new session key will be * created just for them. * * * The Session Algorithm: * * The flow of operation in this module is as follows. Most parts of this * process can be altered to suit the user's needs. * * 1) Optional Point of Entry Control and Session Key Demand * * + enabled with 'SessionTop' and 'SessionValidEntry' * + modified with 'SessionExemptTypes' and 'SessionExemptLocations' * * If a request is made for a URL without providing a session * key, the URL is checked against a list of valid entry points. * If it does not match one, the browser is redirected to the URL * defined by 'SessionTop'. * * Optionaly, certain mime-types, locations and handlers can be made exempt * from this redirection. The reason for this is that when session * key information is appended to a URL, it can interfere with * caching mechanisms. This would hinder the caching of images by * a proxy server independant of session keys. By default, only * mime types image/.* are exempted, but a more complete list can be * substituted with the 'SessionExemptTypes' directive. * The 'SessionExemptLocations' command can be used to exempt portions * of the directory structure. * * In certain cases, type exemption may not produce the * results you are looking for because the final type of a requested * object can't always be determined by this module. For example, if * you are exempting image/.* from session control but the only way to * get at an image is a via a type map file (.var), this module doesn't * know that the end result is an image type. It only sees the .var file. * If you group all you .var files in a common area however, you can exempt * them explicitly by speicifying that directory with 'SessionExemptLocations'. * * Certain clients can be exempted from this process as well. * It may not be desirable for search engine bots to be forced * to have session keys (and they almost never support cookies). * When these bots are given URL based session keys, these same keys * are often stored in the index that the search engines use to * respond to queries! * * You can use the BrowserMatch command with the argument * 'nosessioncontrol' to disable session control for selected * clients. * * Note that clients exempted with 'nosessioncontrol' will never be issued * session keys or cookies. * * Note also that objects exempted from session control may not have any * session key information to make available to the logging. * * If you opt not to use Point of Entry Control at all, original * requests will be issued a new session key and a new cookie without * being redirected anywhere first. * * * 2) Session Key Detection * * + controlled by 'SessionCookieName' and 'SessionUrlSidName' * + modified by 'SessionCookieExpire' and 'SessionUrlExpire' * * To find the session key, the cookie header and the URL * are examined. When the key is found, it is placed into the * SESSION_KEY environment variable. Another environment variable, * SESSION_KEY_METHOD is set to COOKIE or URL to indicate where * the key was found. These values are also set in the requests * internal notes table and can be logged by adding these items * to the log format: %{SESSION_KEY}n and %{SESSION_KEY_METHOD}n * * If the key was not found and the URL is a valid entry point, * a new session key is generated. The browser will be sent * a Set-Cookie header. The SESSION_KEY_METHOD variable will * always be set to URL in this case because it takes two requests * from the browser to determine that it has a cookie mechanism. * The first response should always be processed to include URL * session keys, just in case. Session keys in cookie headers * always take precedence over URL keys. * * Session keys can also be forcibly expired after a certain * amount of time using the 'SessionCookieExpire' and 'SessionUrlExpire' * command. When this happens, the module will create a brand * new session key. * * 3) Special Case Requests when using URL session keys * * + 'SessionFilter' and 'SessionFilterTypes' * * When the session key is being stored in the URL there are two * tasks that need to be performed. * * Firstly, any HTML files should be post processed to append the * session key information in their URLs. It is not currently * possible to do this within the server and it must therefore * be done externaly. This module employs a mechanism similar to * mod_actions. All requests of a certain mime type(s) (controlled * by 'SessionFilterTypes') are passed through an external CGI filter * (defined by 'SessionFilter'). This filter must do two things: * * 1) Append the session key information to any HREFs' and * ACTIONs' query string that is not an imagemap call * in the form: * * = * * 2) Append the session key information to any imagemap HREF's * path_info (*not* query string) in the same form. This is * only necessary for links to the map file, not SHAPE HREFs. * (see next point on why) * * Secondly, special provisions must be made for imagemaps. * As a general rule, browsers treat query strings on .map URLs * inconsistently and most just don't pass it on to the server. * The only consistent way to preserve session key information in * an imagemap call is to put it in the path info of that call. * This browser will recognize that information and make sure that * it gets appended to the new URL generated by the imagemap module. * * * Directives: * * (cookie related commands) * * SessionCookieName The name to give the HTTP cookie. * * SessionCookieDomain The domain in which the cookie is valid * * SessionCookiePath The path this cookie is valid in. * * SessionCookieExpire An interval, in seconds after which the cookie * should expire. This expiration date is set in the * cookie and it will also be forced by this module. * A new session key will be generated and if set, the * client will be redirected to SessionTop. * * SessionMillenialCookies Normally, cookie expiration dates use two digit years. * This won't work beyond 1999. This flag will make * the expiration date a four digit one. Be warned however, * not all browsers will recognize this yet and, at worst, * might ignore the cookie altogether. * * SessionDisableCookies Disable the use of cookies by this module entirely. This * is usefull for debugging filter scripts and exemptions. * * (url session key commands) * * SessionUrlSidName Variable name used to hold the session key within URLs. * * SessionUrlExpire An interval, in seconds after which the session key * embeded in a URL. A new session key will be generated and * if set, the client will be redirected to SessionTop. * * (url and session key commands) * * SessionMoreDigits Use more time digits (usec) in the session key. * * * (Entry Control commands) * * SessionTop Full URL to redirect browsers to when they try to enter * the site without a session key at the wrong place. * * SessionValidEntry A space seperated list of valid entry URIs where browsers * can safely enter and where new session_keys can be * generated. You can use regular expressions here. * * SessionExemptLocations A space seperated list of URIs and URI regular expressions * which are to be exempted from session and entry control. * * SessionExemptTypes A space seperated list of mime types and handlers which * are exmpted from session and entry control. You can * use regular expressions here. The default type * is image/.*. * * (URL session key filtering rules) * * SessionFilter The URI of the CGI script to use as a filter to put URL * based session keys in the URLs of a document. * * SessionFilterTypes A space seperated list of mime-types which must be passed * through the above filter. You can use regular expressions here. * The default is text/html. * * * Sample Configuration: * * * * SessionCookieName mysession * SessionCookiePath /Some/Place/ * SessionCookieDomain .mydomain.com * SessionCookieExpire 8400 * * SessionUrlSidName sid * SessionUrlExpire 8400 * * SessionTop http://www.mydomain.com/Some/Place/ * SessionValidEntry /Some/Place/ /Some/Place/index.html /Some/Place/else/ /cgi-bin/neat-script * * SessionExemptLocations /Some/Place/images/.* * SessionExemptTypes image/.* audio/.* proxy_handler * * SessionFilter /cgi-bin/session_php_filter * SessionFilterTypes text/html text/html3 application/x-httpd-php text/phtml * * * * BrowserMatchNoCase infoseek/.* nosessioncontrol * BrowserMatchNoCase .*spider.* nosessioncontrol * BrowserMatchNoCase .*spyder.* nosessioncontrol * BrowserMatchNoCase .*bot.* nosessioncontrol * BrowserMatchNoCase .*harvest.* nosessioncontrol * BrowserMatchNoCase .*crawler.* nosessioncontrol * BrowserMatchNoCase .*yahoo.* nosessioncontrol * * Note that the filter configured in this example parses the output of a php script. * * Note that you can collapse some of the lengthy lists above by making more complicated * regular expressions. For example, if you wanted these three locations as valid * entry points: /Some/Place, /Some/Place/, and /Some/Place/index.html you could use * this regular expression: /Some/Place(/(index.html)?)? * * * Compilation Notes: * * This module has to be placed in a certain order within the list of modules * in Configuration for the best results. It should always be listed BEFORE * mod_browser and mod_usertrack. * * * Trouble Shooting * * + The session key cookie is never sent to the client! * * 1) Make sure that 'SessionDisableCookies' is not set to 'on' * * 2) mod_usertrack tends to stomp on other module's Set-Cookie headers. * Mod_session can interact nicely with mod_usertrack but not vice-versa. * If you think your clients are not getting sent cookies and you use * mod_usertrack, this might be your problem. To fix this, make sure * that mod_session is listed before mod_usertrack in the Configuration * file before compilation. * * + mod_session seems to be ignoring my BrowserMatch 'nosessioncontrol' commands! * * 1) mod_browser needs to run before mod_session if you want your 'nosessioncontrol' * directives to work. The only way to do this is to make sure that mod_session * is listed before mod_browser in the Configuration file before compilation. * * + I keep getting broken image icons on my page! * * 1) Check to see if the client is trying to access the image without a * session key (easy to see of you have cookies turned off). If it is, * make sure that you are exempting image/.* with 'SessionExemptTypes'. * * 2) If that didn't help and you are using multiViews (either automatic or * with .var files), you should exempt these files by location with * 'SessionExemptLocations'. Be carefull not to exempt html and other * filtered files at the same time though! * * * Adam Sussman (asussman@vidya.com) Jun, 1996 * * Version 0.1 (Jun 1996) Inital imagemap handling. * * 0.2 (Aug 1996) Full conversion from perl libs with the exception of * URL post parsing which is still implemented externaly. * * 0.3 (Sep 1996) Altered key checks to search upwards along r->prev. * Added more digits option. * Added SessionUrlExpire and key expiration mechanisms * * 0.4 (Oct 1996) Altered format of session key to put a '_' between the * time stamp and the host name to make parsing easier. * This is necessary because we can't necessarily match * the session key to the remote host name in a proxy * environment (especialy AOL's). * * 0.5 (Dec 1996) Some cosmetic changes to make apache 1.2 happy. * Restricted module to 1.2 or later. * Added SESSION_KEY and SESSION_KEY_METHOD to the * request's notes table so that they can be logged * by mod_log_config. * Added ability to disable session control for certain * user agents via mod_browser settings. 'nosessioncontrol' * Regexed everything, including defaults. * Changed SessionSkipTypes to SessionExemptTypes. * Changed SessionSkipLocations to SessionExemptLocations. * session_imap_handler tended to get its own handler back * from sub_req_lookup_uri. This is a departure from pre 1.2 * behaviour. :( Changed the fixup not to hijack imap in the * presence of r->main. * Needed to look for session info in r->main for mod_dir, among * others. * */ #include "httpd.h" #include "http_config.h" #include "http_request.h" #include "http_core.h" #include "http_log.h" #include #include #if (MODULE_MAGIC_NUMBER < 19960725) #error "This module is for Apache 1.2 or greater!" #endif #define BROWSER_EXEMPT "nosessioncontrol" /* * Todo: * session_check_valid_entry should be smart enough to know that * a uri of /dir/ is the same thing as /dir/index.html. Can't * really do this without access to mod_dir and its handler though :( * */ module session_module; typedef struct rlist { regex_t *expression; struct rlist *next; } rlist; typedef struct { char *path; /* used to determine applicability in the fixup */ char *cookie_name; char *cookie_domain; char *cookie_path; long cookie_expire; /* interval in seconds */ int use_millenial_dates; int more_digits; int disable_cookies; char *url_sid_name; long url_expire; char *session_top; rlist *valid_entry; rlist *exempt_locations; rlist *exempt_types; regex_t *default_exempt_types; char *external_filter; rlist *filter_types; regex_t *default_filter_types; } session_config_rec; /* Configuration Support */ static int session_isnum(char *str) { register int i, j; j = strlen(str); for (i = 0; i < j; i++) if (!isdigit(str[i])) return 0; return 1; } static const char *session_set_cookie_expires(cmd_parms *cmd, session_config_rec * conf, char *arg) { if (!session_isnum(arg)) return "argument must be a positive integer."; conf->cookie_expire = atol(arg); if (conf->cookie_expire <= 0) return "argument must be a positive integer."; return NULL; } static const char *session_set_url_expires(cmd_parms *cmd, session_config_rec * conf, char *arg) { if (!session_isnum(arg)) return "argument must be a positive integer."; conf->url_expire = atol(arg); if (conf->url_expire <= 0) return "argument must be a positive integer."; return NULL; } static const char *session_add_regex_slot(cmd_parms *cmd, char *conf, char *arg) { rlist *tmp; rlist **rr; int offset; char pat[256]; offset = (int) cmd->info; rr = (rlist **) (conf + offset); tmp = *rr; if (tmp == NULL) { tmp = (rlist *) ap_pcalloc(cmd->pool, sizeof(rlist)); if (!tmp) return "Memory allocation error."; *rr = tmp; } else { /* skip to end of list */ while (tmp->next != NULL) tmp = tmp->next; tmp->next = (rlist *) ap_pcalloc(cmd->pool, sizeof(rlist)); if (!tmp->next) return "Memory allocation error."; tmp = tmp->next; } /* force all expressions to match -entire- argument */ sprintf(pat, "^%s$", arg); tmp->expression = ap_pregcomp(cmd->pool, pat, REG_EXTENDED | REG_ICASE | REG_NOSUB); if (!tmp->expression) return ap_pstrcat(cmd->pool, "Error in regular expression: ", arg); return NULL; } /* Per dir configuration */ static void *session_create_dir_config(pool *p, char *d) { session_config_rec *new; new = (session_config_rec *) ap_pcalloc(p, sizeof(session_config_rec)); new->path = ap_pstrdup(p, d); new->cookie_expire = -1; new->use_millenial_dates = 0; new->disable_cookies = 0; new->url_expire = -1; new->exempt_locations = NULL; new->valid_entry = NULL; /* set defaults */ /* we can do this conveniently with the command handler, but * we need to fake a cmd_parms structure. */ new->default_exempt_types = ap_pregcomp(p, "^image/.*$", REG_EXTENDED | REG_ICASE | REG_NOSUB); new->default_filter_types = ap_pregcomp(p, "^text/html$", REG_EXTENDED | REG_ICASE | REG_NOSUB); return new; } /* The configuration commands */ static command_rec session_cmds[] = { {"SessionCookieName", ap_set_string_slot, (void *) XtOffsetOf(session_config_rec, cookie_name), OR_AUTHCFG, TAKE1, "the name to give the session cookie."}, {"SessionCookieDomain", ap_set_string_slot, (void *) XtOffsetOf(session_config_rec, cookie_domain), OR_AUTHCFG, TAKE1, "the domain to assign to the session cookie."}, {"SessionCookiePath", ap_set_string_slot, (void *) XtOffsetOf(session_config_rec, cookie_path), OR_AUTHCFG, TAKE1, "the path to assign to the session cookie."}, {"SessionCookieExpire", session_set_cookie_expires, NULL, OR_AUTHCFG, TAKE1, "the amount of time (in seconds) after which the session cookie will expire."}, {"SessionMillenialCookies", ap_set_flag_slot, (void *) XtOffsetOf(session_config_rec, use_millenial_dates), OR_AUTHCFG, FLAG, "on or off."}, {"SessionMoreDigits", ap_set_flag_slot, (void *) XtOffsetOf(session_config_rec, more_digits), OR_AUTHCFG, FLAG, "on or off."}, {"SessionDisableCookies", ap_set_flag_slot, (void *) XtOffsetOf(session_config_rec, disable_cookies), OR_AUTHCFG, FLAG, "on or off."}, {"SessionUrlSidName", ap_set_string_slot, (void *) XtOffsetOf(session_config_rec, url_sid_name), OR_AUTHCFG, TAKE1, "the name of the session identifier to use in the URL cookie."}, {"SessionUrlExpire", session_set_url_expires, NULL, OR_AUTHCFG, TAKE1, "the amount of time (in seconds) after which the session url cookie will expire."}, {"SessionTop", ap_set_string_slot, (void *) XtOffsetOf(session_config_rec, session_top), OR_AUTHCFG, TAKE1, "the url to redirect to when someone tries to come in through an invalid entry point without a cookie."}, {"SessionValidEntry", session_add_regex_slot, (void *) XtOffsetOf(session_config_rec, valid_entry), OR_AUTHCFG, ITERATE, "a list of valid entry points without a session identifier"}, {"SessionFilter", ap_set_string_slot, (void *) XtOffsetOf(session_config_rec, external_filter), OR_AUTHCFG, TAKE1, "a cgi program to use as a URL SID filter."}, {"SessionFilterTypes", session_add_regex_slot, (void *) XtOffsetOf(session_config_rec, filter_types), OR_AUTHCFG, ITERATE, "a cgi program to use as a URL SID filter."}, {"SessionExemptLocations", session_add_regex_slot, (void *) XtOffsetOf(session_config_rec, exempt_locations), OR_AUTHCFG, ITERATE, "a cgi program to use as a URL SID filter."}, {"SessionExemptTypes", session_add_regex_slot, (void *) XtOffsetOf(session_config_rec, exempt_types), OR_AUTHCFG, ITERATE, "a cgi program to use as a URL SID filter."}, {NULL}, }; /* session_filter_handler * Handle post parsing of certain documents when SESSION_KEY_METHOD is URL. * This handler should never be set anywhere but in session_fixup. * * Post parsing is currently outsourced to a CGI like in mod_actions. Eventually, * this will be done internally. * */ static int session_filter_handler(request_rec *r) { session_config_rec *conf; conf = (session_config_rec *) ap_get_module_config(r->per_dir_config, &session_module); if (!conf->external_filter) { ap_log_error_old("No external filter defined. Do not use the session-postparse handler directly.", r->server); return SERVER_ERROR; } if (!ap_table_get(r->subprocess_env, "SESSION_KEY_METHOD") || strcmp(ap_table_get(r->subprocess_env, "SESSION_KEY_METHOD"), "URL")) return DECLINED; /* some sanity checking */ if (r->finfo.st_mode == 0) { ap_log_reason("File does not exist", r->filename, r); return NOT_FOUND; } /* avoid looping */ if (r->prev && r->prev->prev) return DECLINED; /* redirect to the cgi script */ ap_internal_redirect(ap_pstrcat(r->pool, conf->external_filter, ap_escape_uri(r->pool, r->uri), r->args ? "?" : NULL, r->args, NULL), r); return OK; } /* session_imap_handler * When SESSION_KEY_METHOD is URL, we need to hijack the imagemap process. As a general * rule, client handling of imagemap urls with a query string attached is spotty at best. * PATH_INFO on the other hand is always preserved, so this module expects to see the * SESSION_KEY embeded in the url's path info. All of this is handled by session_fixup * and session_filter_handler. The task of this handler is to run the actual image map * request through mod_imap and then append the SESSION_KEY to the redirect that results. * */ static int session_imap_handler(request_rec *r) { request_rec *rr; char work[MAX_STRING_LEN]; int ret; session_config_rec *conf; conf = (session_config_rec *) ap_get_module_config(r->per_dir_config, &session_module); /* build the real imagemap request */ /* we don't want the subrequest to hijack imap this time */ ap_table_set(r->notes, "session_imap_subreq", ""); /* do the subrequest */ rr = ap_sub_req_lookup_uri(r->uri, r); if (rr->status != 200) { ret = rr->status; ap_destroy_sub_req(rr); return ret; } /* set up the sub_request to be run by transfering * the coordinate args. The appropriate content * type should have been set by the sub_req_lookup_uri * function, and no overriding handler should be present. */ if ((rr->handler != NULL) && strcmp(rr->handler, "imap-file")) { sprintf(work, "session imap sub-request got wrong handler: %s", rr->handler); ap_log_error_old(work, r->server); return SERVER_ERROR; } /* run the imap handler */ rr->args = ap_pstrdup(rr->pool, r->args); ret = ap_run_sub_req(rr); /* if a successfull redirect was returned, tack the path_info onto it. * otherwise just pass the results on. */ if (ret == REDIRECT) { /* transfer results to parent request */ r->status = REDIRECT; /* add path_info as query string to Location header */ strcpy(work, ap_table_get(rr->headers_out, "Location")); if (strchr(work, '?')) sprintf(work, "%s&%s=%s", work, conf->url_sid_name, ap_table_get(r->subprocess_env, "SESSION_KEY")); else sprintf(work, "%s?%s=%s", work, conf->url_sid_name, ap_table_get(r->subprocess_env, "SESSION_KEY")); ap_table_set(r->headers_out, "Location", work); } ap_destroy_sub_req(rr); return ret; } /* key management functions */ /* * create_key * * Create the session identifier as well as the Set-Cookie header. * * Session identifiers have the general form of: * * unix_timestamp + [extra usec digits] + '_' + first part of hostname/IP number * */ static void session_create_key(request_rec *r, session_config_rec * conf) { struct timeval tv; time_t expire; struct tm *gtime; char *dot; char buffer[MAX_STRING_LEN]; char new_key[40]; char *rname; struct timezone tz = {0, 0}; rname = ap_pstrdup(r->pool, ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME)); /* * create an identifier. The current unix time * plus a portion of the remote host name is usualy * sufficient. u_secs can be added for even better odds. */ if ((dot = strchr(rname, '.'))) /* get just the first part */ *dot = '\0'; gettimeofday(&tv, &tz); if (conf->more_digits) sprintf(new_key, "%ld%03d_%s", (long) tv.tv_sec, (int) tv.tv_usec / 1000, rname); else sprintf(new_key, "%ld_%s", (long) tv.tv_sec, rname); /* * set the session key in the environment. We set the method * to URL since we cannot be 100% sure that a cookie mechanism * is available on the other end until the next request. */ ap_table_set(r->subprocess_env, "SESSION_KEY", new_key); ap_table_set(r->subprocess_env, "SESSION_KEY_METHOD", "URL"); ap_table_set(r->subprocess_env, "SESSION_KEY_NAME", conf->cookie_name); /* set these values in the notes table as well so that they * can be logged my mod_log_config */ ap_table_set(r->notes, "SESSION_KEY", new_key); ap_table_set(r->notes, "SESSION_KEY_METHOD", "URL"); ap_table_set(r->notes, "SESSION_KEY_NAME", conf->cookie_name); /* Now create the Set-Cookie token */ if (conf->disable_cookies) return; /* Basic header */ sprintf(buffer, "%s=%s", conf->cookie_name, new_key); /* optional domain */ if (conf->cookie_domain) { /* we have to make sure that the domain starts with a . otherwise * some versions of Netscape (at least) will not take it. */ if (*(conf->cookie_domain) != '.') sprintf(buffer, "%s; domain=.%s", buffer, conf->cookie_domain); else sprintf(buffer, "%s; domain=%s", buffer, conf->cookie_domain); } /* optional expiration date */ if (conf->cookie_expire > 0) { static const char *const days[7] = {"Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"}; expire = time(NULL) + conf->cookie_expire; gtime = gmtime(&expire); if (conf->use_millenial_dates) { /* use a 4 digit year. Also need to make sure * that the year is properly digited. */ if ((expire >= 946684800) && (gtime->tm_year < 100)) gtime->tm_year += 2000; if ((expire < 946684800) && (gtime->tm_year < 100)) gtime->tm_year += 1900; sprintf(buffer, "%s; expires=%s, %02d-%s-%04d %02d:%02d:%02d GMT", buffer, days[gtime->tm_wday], gtime->tm_mday, ap_month_snames[gtime->tm_mon], gtime->tm_year, gtime->tm_hour, gtime->tm_min, gtime->tm_sec); } else sprintf(buffer, "%s; expires=%s, %02d-%s-%02d %02d:%02d:%02d GMT", buffer, days[gtime->tm_wday], gtime->tm_mday, ap_month_snames[gtime->tm_mon], gtime->tm_year, gtime->tm_hour, gtime->tm_min, gtime->tm_sec); } /* need to have a path at the end */ sprintf(buffer, "%s; path=%s", buffer, conf->cookie_path); ap_table_merge(r->headers_out, "Set-Cookie", buffer); } /* * detect_key * * There are five places a session key created by this module could be: * * 1) In the subprocess_env of a parent request * (case of an internal redirect from the filter handler) * 2) In r->main of a parent request * 3) In a cookie header in r->headers_in * 4) In r->args (query string) * 5) In r->path_info (special case imagemap handling) * * Additionaly, if this is an internal redirect, the parent might have * called session_create_cookie, in which case we must propagate * the Set-Cookie header. * * We also need to propagate the notes settings just like subprocess_env * and Set-Cookie headers so that no matter when logging happens, the * information will be there. * */ static int session_detect_key(request_rec *r, session_config_rec * conf) { const char *str; char *ptr; char *val; char buffer[MAX_STRING_LEN]; request_rec *rp; /* 1) Look at parent requests */ rp = r->prev; while (rp) { /* Propagate any parent request Set-Cookie headers */ if (ap_table_get(rp->headers_out, "Set-Cookie")) ap_table_set(r->headers_out, "Set-Cookie", ap_table_get(rp->headers_out, "Set-Cookie")); /* Look for existing session key info */ if (ap_table_get(rp->subprocess_env, "SESSION_KEY") && ap_table_get(rp->subprocess_env, "SESSION_KEY_METHOD")) { ap_table_set(r->subprocess_env, "SESSION_KEY", ap_table_get(rp->subprocess_env, "SESSION_KEY")); ap_table_set(r->subprocess_env, "SESSION_KEY_METHOD", ap_table_get(rp->subprocess_env, "SESSION_KEY_METHOD")); ap_table_set(r->subprocess_env, "SESSION_KEY_NAME", ap_table_get(rp->subprocess_env, "SESSION_KEY_NAME")); /* propagate notes settings as well */ ap_table_set(r->notes, "SESSION_KEY", ap_table_get(rp->notes, "SESSION_KEY")); ap_table_set(r->notes, "SESSION_KEY_METHOD", ap_table_get(rp->notes, "SESSION_KEY_METHOD")); ap_table_set(r->notes, "SESSION_KEY_NAME", ap_table_get(rp->notes, "SESSION_KEY_NAME")); return 1; } rp = rp->prev; } /* 2) r->main */ if (r->main) { /* Propagate any parent request Set-Cookie headers */ if (ap_table_get(r->main->headers_out, "Set-Cookie")) ap_table_set(r->headers_out, "Set-Cookie", ap_table_get(r->main->headers_out, "Set-Cookie")); /* Look for existing session key info */ if (ap_table_get(r->main->subprocess_env, "SESSION_KEY") && ap_table_get(r->main->subprocess_env, "SESSION_KEY_METHOD")) { ap_table_set(r->subprocess_env, "SESSION_KEY", ap_table_get(r->main->subprocess_env, "SESSION_KEY")); ap_table_set(r->subprocess_env, "SESSION_KEY_METHOD", ap_table_get(r->main->subprocess_env, "SESSION_KEY_METHOD")); ap_table_set(r->subprocess_env, "SESSION_KEY_NAME", ap_table_get(r->main->subprocess_env, "SESSION_KEY_NAME")); /* propagate notes settings as well */ ap_table_set(r->notes, "SESSION_KEY", ap_table_get(r->main->notes, "SESSION_KEY")); ap_table_set(r->notes, "SESSION_KEY_METHOD", ap_table_get(r->main->notes, "SESSION_KEY_METHOD")); ap_table_set(r->notes, "SESSION_KEY_NAME", ap_table_get(r->main->notes, "SESSION_KEY_NAME")); return 1; } } /* 3) Look for a HTTP Cookie header */ if (!conf->disable_cookies) if ((str = ap_table_get(r->headers_in, "Cookie"))) { sprintf(buffer, "%s=", conf->cookie_name); if ((val = strstr(str, buffer))) { val += strlen(buffer); ptr = val + 1; /* skip forward to end of cookie value */ while ((*ptr != ';') && (*ptr != '\0')) ptr++; strncpy(buffer, val, ptr - val); buffer[ptr - val] = '\0'; ap_table_set(r->subprocess_env, "SESSION_KEY", buffer); ap_table_set(r->subprocess_env, "SESSION_KEY_METHOD", "COOKIE"); ap_table_set(r->subprocess_env, "SESSION_KEY_NAME", conf->cookie_name); /* set notes for logging */ ap_table_set(r->notes, "SESSION_KEY", buffer); ap_table_set(r->notes, "SESSION_KEY_METHOD", "COOKIE"); ap_table_set(r->notes, "SESSION_KEY_NAME", conf->cookie_name); return 1; } } /* 4) Look in the query string */ if (r->args) { sprintf(buffer, "%s=", conf->url_sid_name); if ((val = strstr(r->args, buffer))) { val += strlen(buffer); ptr = val + 1; /* skip forward to end of cookie value */ while ((*ptr != '&') && (*ptr != '\0')) ptr++; strncpy(buffer, val, ptr - val); buffer[ptr - val] = '\0'; ap_table_set(r->subprocess_env, "SESSION_KEY", buffer); ap_table_set(r->subprocess_env, "SESSION_KEY_METHOD", "URL"); ap_table_set(r->subprocess_env, "SESSION_KEY_NAME", conf->url_sid_name); /* set notes for logging */ ap_table_set(r->notes, "SESSION_KEY", buffer); ap_table_set(r->notes, "SESSION_KEY_METHOD", "URL"); ap_table_set(r->notes, "SESSION_KEY_NAME", conf->url_sid_name); return 1; } } /* 5) Look in the path info (special case imagemap) */ if (r->path_info) { sprintf(buffer, "%s=", conf->url_sid_name); if ((val = strstr(r->path_info, buffer))) { val += strlen(buffer); ptr = val + 1; /* skip forward to end of cookie value */ while ((*ptr != '&') && (*ptr != '/') && (*ptr != '\0')) ptr++; strncpy(buffer, val, ptr - val); buffer[ptr - val] = '\0'; ap_table_set(r->subprocess_env, "SESSION_KEY", buffer); ap_table_set(r->subprocess_env, "SESSION_KEY_METHOD", "URL"); ap_table_set(r->subprocess_env, "SESSION_KEY_NAME", conf->url_sid_name); /* set notes for logging */ ap_table_set(r->notes, "SESSION_KEY", buffer); ap_table_set(r->notes, "SESSION_KEY_METHOD", "URL"); ap_table_set(r->notes, "SESSION_KEY_NAME", conf->url_sid_name); return 1; } } return 0; } /* * session_match_iterate * * Try to find a matching regexp in an rlist * */ static int session_match_iterate(rlist * regexps, const char *arg) { if (regexps == NULL) return 0; while (regexps != NULL) { if (regexps->expression == NULL) continue; if (!regexec(regexps->expression, arg, 0, NULL, 0)) return 1; regexps = regexps->next; } return 0; } /* * session_check_valid_entry: * * Is the current request for one of the locations where an up * front session key is not absolutly required? * */ static int session_check_valid_entry(request_rec *r, session_config_rec * conf) { return session_match_iterate(conf->valid_entry, r->uri); } /* * is_exempt * * exempt certain things from session control and top level redirection * when a session key is *not* present. * * This is usefull for things we really want cached on the client/proxy * end (like images). Forcing a session key in the URL would impede this. * * It is also usefull when we don't want to force session control or keys * onto seach engine robots. * */ static int session_is_exempt(request_rec *r, session_config_rec * conf) { /* exempt content types or handlers */ if (conf->exempt_types) { if (r->content_type && session_match_iterate(conf->exempt_types, r->content_type)) return 1; if (r->handler && session_match_iterate(conf->exempt_types, r->handler)) return 1; } else { if (r->content_type && !regexec(conf->default_exempt_types, r->content_type, 0, NULL, 0)) return 1; if (r->handler && !regexec(conf->default_exempt_types, r->handler, 0, NULL, 0)) return 1; } /* exempt locations */ if ((conf->exempt_locations) && (session_match_iterate(conf->exempt_locations, r->uri))) return 1; return 0; } /* * session_must_filter: * * Check to see if the mime type of the file in this request mandates a * pass through our external filter. * */ static int session_must_filter(request_rec *r, session_config_rec * conf) { /* check mime type */ if (r->content_type) { if (conf->filter_types) return session_match_iterate(conf->filter_types, r->content_type); else return !regexec(conf->default_filter_types, r->content_type, 0, NULL, 0); } /* check handlers */ if (r->handler) { if (conf->filter_types) return session_match_iterate(conf->filter_types, r->handler); else return !regexec(conf->default_filter_types, r->handler, 0, NULL, 0); } return 0; } /* * session_has_expired * * Check to see if a session key has expired and needs to be forcibly * re-issued. This function has a side effect of forcefully expiring * the sessionkey when the hostname of the client does not match the one in the * recieved session key (that's on purpose too). * */ static int session_has_expired(request_rec *r, session_config_rec * conf) { const char *skey; char *digits; char *ptr; char *rname; rname = ap_pstrdup(r->pool, ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME)); /* get creation time of session key */ /* time stamp is everything up to first '_' character */ skey = ap_table_get(r->subprocess_env, "SESSION_KEY"); ptr = strchr(skey, '_'); if (ptr == NULL) /* can't get the time, force expiration */ return 1; if (conf->more_digits) digits = ap_pstrndup(r->pool, skey, ptr - skey - 3); else digits = ap_pstrndup(r->pool, skey, ptr - skey); if (!strcmp(ap_table_get(r->subprocess_env, "SESSION_KEY_METHOD"), "COOKIE") && (conf->cookie_expire > 0)) { if (atol(digits) + conf->cookie_expire < time(NULL)) return 1; } else if (!strcmp(ap_table_get(r->subprocess_env, "SESSION_KEY_METHOD"), "URL") && (conf->url_expire > 0)) if (atol(digits) + conf->url_expire < time(NULL)) return 1; return 0; } /* * session_fixup: * * The main event. * */ static int session_fixup(request_rec *r) { request_rec *rp; session_config_rec *conf = NULL; /* user-agent exemptions set with BrowserMatch */ if (ap_table_get(r->subprocess_env, BROWSER_EXEMPT)) return DECLINED; /* * First we need to decide if we are relevant here. There are three * cases in which we are: * * 1) some parent request via r->prev has session key info in it. * * 2) conf->path matches r->uri. This happens when this module * was configured in a directive. * * 3) conf->path matches r->filename. This happens when this module * was configured in a .htaccess file. * */ /* case 1 */ if (r->prev) { rp = r->prev; while (rp) { if (ap_table_get(rp->subprocess_env, "SESSION_KEY") && ap_table_get(rp->subprocess_env, "SESSION_KEY_METHOD")) { /* get the configuration we need from the parent request */ conf = (session_config_rec *) ap_get_module_config(rp->per_dir_config, &session_module); break; } rp = rp->prev; } } /* cases 2) and 3) */ if (!conf) { conf = (session_config_rec *) ap_get_module_config(r->per_dir_config, &session_module); if (!conf->path || ((strncmp(r->uri, conf->path, strlen(conf->path)) != 0) && (strncmp(r->filename, conf->path, strlen(conf->path)) != 0))) return DECLINED; } /* Now check that the required configs are set */ if (!conf->cookie_name) { ap_log_reason("missing SessionCookieName directive", r->filename, r); return SERVER_ERROR; } if (!conf->cookie_path) { ap_log_reason("missing SessionCookiePath directive", r->filename, r); return SERVER_ERROR; } if (!conf->url_sid_name) { ap_log_reason("missing SessionUrlSidName directive", r->filename, r); return SERVER_ERROR; } if (!conf->external_filter) { ap_log_reason("missing SessionFilter directive", r->filename, r); return SERVER_ERROR; } /* required pairs */ if ((conf->valid_entry && !conf->session_top) || (!conf->valid_entry && conf->session_top)) { ap_log_reason("both or none of SessionTop and SessionValidEntry must be set", r->filename, r); return SERVER_ERROR; } /* Now the hocus pocus */ if (!session_detect_key(r, conf) || session_has_expired(r, conf)) { /* If under entry point control and this isn't a valid entry point */ if (conf->valid_entry && conf->session_top && !session_check_valid_entry(r, conf)) { /* special types, handlers and locations are exempt */ if (session_is_exempt(r, conf)) return OK; else { ap_table_set(r->headers_out, "Location", conf->session_top); return REDIRECT; } } /* create the session key */ session_create_key(r, conf); } /* set up special handling of imagemaps and external filtering if the * session key came from a URL. */ if (!strcmp(ap_table_get(r->subprocess_env, "SESSION_KEY_METHOD"), "URL")) { /* imagemap (check for looped subrequest first) */ if ((!r->main || (!ap_table_get(r->main->notes, "session_imap_subreq"))) && ((r->handler && !strcmp(r->handler, "imap-file")) || (r->content_type && !strcmp(r->content_type, "application/x-httpd-imap")))) /* hijack this imagemap request */ r->handler = ap_pstrdup(r->pool, "session-imap-file"); /* post filtered types */ else if (session_must_filter(r, conf)) /* hijack this request */ r->handler = ap_pstrdup(r->pool, "session-postparse"); } return OK; } static handler_rec session_handlers[] = { {"session-imap-file", session_imap_handler}, {"session-postparse", session_filter_handler}, {NULL} }; module session_module = { STANDARD_MODULE_STUFF, NULL, /* initializer */ session_create_dir_config, /* dir config creater */ NULL, /* dir merger --- default is to override */ NULL, /* server config */ NULL, /* merge server config */ session_cmds, /* command table */ session_handlers, /* handlers */ NULL, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ session_fixup, /* fixups */ NULL, /* logger */ NULL, /* header parse */ NULL, /* child_init */ NULL, /* child_exit */ NULL /* post read-request */ };