| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "mod_session.h" | |
| #include "apr_lib.h" | |
| #include "apr_strings.h" | |
| #include "http_log.h" | |
| #include "util_cookies.h" | |
| #define MOD_SESSION_COOKIE "mod_session_cookie" | |
| module AP_MODULE_DECLARE_DATA session_cookie_module; | |
| /** | |
| * Structure to carry the per-dir session config. | |
| */ | |
| typedef struct { | |
| const char *name; | |
| int name_set; | |
| const char *name_attrs; | |
| const char *name2; | |
| int name2_set; | |
| const char *name2_attrs; | |
| int remove; | |
| int remove_set; | |
| } session_cookie_dir_conf; | |
| /** | |
| * Set the cookie and embed the session within it. | |
| * | |
| * This function adds an RFC2109 compliant Set-Cookie header for | |
| * the cookie specified in SessionCookieName, and an RFC2965 compliant | |
| * Set-Cookie2 header for the cookie specified in SessionCookieName2. | |
| * | |
| * If specified, the optional cookie attributes will be added to | |
| * each cookie. If defaults are not specified, DEFAULT_ATTRS | |
| * will be used. | |
| * | |
| * On success, this method will return APR_SUCCESS. | |
| * | |
| * @param r The request pointer. | |
| * @param z A pointer to where the session will be written. | |
| */ | |
| static apr_status_t session_cookie_save(request_rec * r, session_rec * z) | |
| { | |
| session_cookie_dir_conf *conf = ap_get_module_config(r->per_dir_config, | |
| &session_cookie_module); | |
| /* create RFC2109 compliant cookie */ | |
| if (conf->name_set) { | |
| if (z->encoded && z->encoded[0]) { | |
| ap_cookie_write(r, conf->name, z->encoded, conf->name_attrs, | |
| z->maxage, r->headers_out, r->err_headers_out, | |
| NULL); | |
| } | |
| else { | |
| ap_cookie_remove(r, conf->name, conf->name_attrs, r->headers_out, | |
| r->err_headers_out, NULL); | |
| } | |
| } | |
| /* create RFC2965 compliant cookie */ | |
| if (conf->name2_set) { | |
| if (z->encoded && z->encoded[0]) { | |
| ap_cookie_write2(r, conf->name2, z->encoded, conf->name2_attrs, | |
| z->maxage, r->headers_out, r->err_headers_out, | |
| NULL); | |
| } | |
| else { | |
| ap_cookie_remove2(r, conf->name2, conf->name2_attrs, | |
| r->headers_out, r->err_headers_out, NULL); | |
| } | |
| } | |
| if (conf->name_set || conf->name2_set) { | |
| return OK; | |
| } | |
| return DECLINED; | |
| } | |
| /** | |
| * Isolate the cookie with the name "name", and if present, extract | |
| * the payload from the cookie. | |
| * | |
| * If the cookie is found, the cookie and any other cookies with the | |
| * same name are removed from the cookies passed in the request, so | |
| * that credentials are not leaked to a backend server or process. | |
| * | |
| * A missing or malformed cookie will cause this function to return | |
| * APR_EGENERAL. | |
| * | |
| * On success, this returns APR_SUCCESS. | |
| */ | |
| static apr_status_t session_cookie_load(request_rec * r, session_rec ** z) | |
| { | |
| session_cookie_dir_conf *conf = ap_get_module_config(r->per_dir_config, | |
| &session_cookie_module); | |
| session_rec *zz = NULL; | |
| const char *val = NULL; | |
| const char *note = NULL; | |
| const char *name = NULL; | |
| request_rec *m = r; | |
| /* find the first redirect */ | |
| while (m->prev) { | |
| m = m->prev; | |
| } | |
| /* find the main request */ | |
| while (m->main) { | |
| m = m->main; | |
| } | |
| /* is our session in a cookie? */ | |
| if (conf->name2_set) { | |
| name = conf->name2; | |
| } | |
| else if (conf->name_set) { | |
| name = conf->name; | |
| } | |
| else { | |
| return DECLINED; | |
| } | |
| /* first look in the notes */ | |
| note = apr_pstrcat(m->pool, MOD_SESSION_COOKIE, name, NULL); | |
| zz = (session_rec *)apr_table_get(m->notes, note); | |
| if (zz) { | |
| *z = zz; | |
| return OK; | |
| } | |
| /* otherwise, try parse the cookie */ | |
| ap_cookie_read(r, name, &val, conf->remove); | |
| /* create a new session and return it */ | |
| zz = (session_rec *) apr_pcalloc(m->pool, sizeof(session_rec)); | |
| zz->pool = m->pool; | |
| zz->entries = apr_table_make(m->pool, 10); | |
| zz->encoded = val; | |
| *z = zz; | |
| /* put the session in the notes so we don't have to parse it again */ | |
| apr_table_setn(m->notes, note, (char *)zz); | |
| /* don't cache auth protected pages */ | |
| apr_table_addn(r->headers_out, "Cache-Control", "no-cache, private"); | |
| return OK; | |
| } | |
| static void *create_session_cookie_dir_config(apr_pool_t * p, char *dummy) | |
| { | |
| session_cookie_dir_conf *new = | |
| (session_cookie_dir_conf *) apr_pcalloc(p, sizeof(session_cookie_dir_conf)); | |
| return (void *) new; | |
| } | |
| static void *merge_session_cookie_dir_config(apr_pool_t * p, void *basev, | |
| void *addv) | |
| { | |
| session_cookie_dir_conf *new = (session_cookie_dir_conf *) | |
| apr_pcalloc(p, sizeof(session_cookie_dir_conf)); | |
| session_cookie_dir_conf *add = (session_cookie_dir_conf *) addv; | |
| session_cookie_dir_conf *base = (session_cookie_dir_conf *) basev; | |
| new->name = (add->name_set == 0) ? base->name : add->name; | |
| new->name_attrs = (add->name_set == 0) ? base->name_attrs : add->name_attrs; | |
| new->name_set = add->name_set || base->name_set; | |
| new->name2 = (add->name2_set == 0) ? base->name2 : add->name2; | |
| new->name2_attrs = (add->name2_set == 0) ? base->name2_attrs : add->name2_attrs; | |
| new->name2_set = add->name2_set || base->name2_set; | |
| new->remove = (add->remove_set == 0) ? base->remove : add->remove; | |
| new->remove_set = add->remove_set || base->remove_set; | |
| return new; | |
| } | |
| /** | |
| * Sanity check a given string that it exists, is not empty, | |
| * and does not contain special characters. | |
| */ | |
| static const char *check_string(cmd_parms * cmd, const char *string) | |
| { | |
| if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&')) { | |
| return apr_pstrcat(cmd->pool, cmd->directive->directive, | |
| " cannot be empty, or contain '=' or '&'.", | |
| NULL); | |
| } | |
| return NULL; | |
| } | |
| static const char *set_cookie_name(cmd_parms * cmd, void *config, | |
| const char *args) | |
| { | |
| char *last; | |
| char *line = apr_pstrdup(cmd->pool, args); | |
| session_cookie_dir_conf *conf = (session_cookie_dir_conf *) config; | |
| char *cookie = apr_strtok(line, " \t", &last); | |
| conf->name = cookie; | |
| conf->name_set = 1; | |
| while (apr_isspace(*last)) { | |
| last++; | |
| } | |
| conf->name_attrs = last; | |
| return check_string(cmd, cookie); | |
| } | |
| static const char *set_cookie_name2(cmd_parms * cmd, void *config, | |
| const char *args) | |
| { | |
| char *last; | |
| char *line = apr_pstrdup(cmd->pool, args); | |
| session_cookie_dir_conf *conf = (session_cookie_dir_conf *) config; | |
| char *cookie = apr_strtok(line, " \t", &last); | |
| conf->name2 = cookie; | |
| conf->name2_set = 1; | |
| while (apr_isspace(*last)) { | |
| last++; | |
| } | |
| conf->name2_attrs = last; | |
| return check_string(cmd, cookie); | |
| } | |
| static const char * | |
| set_remove(cmd_parms * parms, void *dconf, int flag) | |
| { | |
| session_cookie_dir_conf *conf = dconf; | |
| conf->remove = flag; | |
| conf->remove_set = 1; | |
| return NULL; | |
| } | |
| static const command_rec session_cookie_cmds[] = | |
| { | |
| AP_INIT_RAW_ARGS("SessionCookieName", set_cookie_name, NULL, RSRC_CONF|OR_AUTHCFG, | |
| "The name of the RFC2109 cookie carrying the session"), | |
| AP_INIT_RAW_ARGS("SessionCookieName2", set_cookie_name2, NULL, RSRC_CONF|OR_AUTHCFG, | |
| "The name of the RFC2965 cookie carrying the session"), | |
| AP_INIT_FLAG("SessionCookieRemove", set_remove, NULL, RSRC_CONF|OR_AUTHCFG, | |
| "Set to 'On' to remove the session cookie from the headers " | |
| "and hide the cookie from a backend server or process"), | |
| {NULL} | |
| }; | |
| static void register_hooks(apr_pool_t * p) | |
| { | |
| ap_hook_session_load(session_cookie_load, NULL, NULL, APR_HOOK_MIDDLE); | |
| ap_hook_session_save(session_cookie_save, NULL, NULL, APR_HOOK_MIDDLE); | |
| } | |
| AP_DECLARE_MODULE(session_cookie) = | |
| { | |
| STANDARD20_MODULE_STUFF, | |
| create_session_cookie_dir_config, /* dir config creater */ | |
| merge_session_cookie_dir_config, /* dir merger --- default is to | |
| * override */ | |
| NULL, /* server config */ | |
| NULL, /* merge server config */ | |
| session_cookie_cmds, /* command apr_table_t */ | |
| register_hooks /* register hooks */ | |
| }; |