diff options
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | docs/manual/mod/mod_session_cookie.xml | 156 | ||||
-rw-r--r-- | include/util_cookies.h | 120 | ||||
-rw-r--r-- | modules/session/config.m4 | 2 | ||||
-rw-r--r-- | modules/session/mod_session_cookie.c | 269 | ||||
-rw-r--r-- | server/Makefile.in | 2 | ||||
-rw-r--r-- | server/util_cookies.c | 256 |
7 files changed, 808 insertions, 2 deletions
@@ -2,6 +2,11 @@ Changes with Apache 2.3.0 [ When backported to 2.2.x, remove entry from this file ] + *) mod_session_cookie: Add a session implementation capable of storing + session information within cookies on the browser. Useful for high + volume sites where server bound sessions are too resource intensive. + [Graham Leggett] + *) mod_session: Add a generic session interface to unify the different attempts at saving persistent sessions across requests. [Graham Leggett] diff --git a/docs/manual/mod/mod_session_cookie.xml b/docs/manual/mod/mod_session_cookie.xml new file mode 100644 index 0000000000..e2b2112130 --- /dev/null +++ b/docs/manual/mod/mod_session_cookie.xml @@ -0,0 +1,156 @@ +<?xml version="1.0"?> +<!DOCTYPE modulesynopsis SYSTEM "../style/modulesynopsis.dtd"> +<?xml-stylesheet type="text/xsl" href="../style/manual.en.xsl"?> +<!-- $LastChangedRevision: 634760 $ --> + +<!-- + 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. +--> + +<modulesynopsis metafile="mod_session_cookie.xml.meta"> + +<name>mod_session_cookie</name> +<description>Cookie based session support</description> +<status>Extension</status> +<sourcefile>mod_session_cookie.c</sourcefile> +<identifier>session_cookie_module</identifier> + +<summary> + <note type="warning"><title>Warning</title> + <p>The session modules make use of HTTP cookies, and as such can fall + victim to Cross Site Scripting attacks, or expose potentially private + information to clients. Please ensure that the relevant risks have + been taken into account before enabling the session functionality on + your server.</p> + </note> + + <p>This submodule of <module>mod_session</module> provides support for the + storage of user sessions on the remote browser within HTTP cookies.</p> + + <p>Using cookies to store a session removes the need for the server or + a group of servers to store the session locally, or collaborate to share + a session, and can be useful for high traffic environments where a + server based session might be too resource intensive.</p> + + <p>If session privacy is required, the <module>mod_session_crypto</module> + module can be used to encrypt the contents of the session before writing + the session to the client.</p> + + <p>For more details on the session interface, see the documentation for + the <module>mod_session</module> module.</p> + +</summary> +<seealso><module>mod_session</module></seealso> +<seealso><module>mod_session_crypto</module></seealso> +<seealso><module>mod_session_dbd</module></seealso> + + <section id="basicexamples"><title>Basic Examples</title> + + <p>To create a simple session and store it in a cookie called + <var>session</var>, configure the session as follows:</p> + + <example><title>Browser based session</title> + Session On<br /> + SessionCookieName session path=/<br /> + </example> + + <p>For more examples on how the session can be configured to be read + from and written to by a CGI application, see the + <module>mod_session</module> examples section.</p> + + <p>For documentation on how the session can be used to store username + and password details, see the <module>mod_auth_form</module> module.</p> + + </section> + +<directivesynopsis> +<name>SessionCookieName</name> +<description>Name and attributes for the RFC2109 cookie storing the session</description> +<syntax>SessionCookieName <var>name</var> <var>attributes</var></syntax> +<default>none</default> +<contextlist><context>directory</context> +</contextlist> +<compatibility>Available in Apache 2.3.0 and later</compatibility> + +<usage> + <p>The <directive>SessionCookieName</directive> directive specifies the name and + optional attributes of an RFC2109 compliant cookie inside which the session will + be stored. RFC2109 cookies are set using the <code>Set-Cookie</code> HTTP header. + </p> + + <p>An optional list of cookie attributes can be specified, as per the example below. + These attributes are inserted into the cookie as is, and are not interpreted by + Apache. Ensure that your attributes are defined correctly as per the cookie specification. + </p> + + <example><title>Cookie with attributes</title> + Session On<br /> + SessionCookieName session path=/private;domain=example.com;httponly;secure;version=1;<br /> + </example> + +</usage> +</directivesynopsis> + +<directivesynopsis> +<name>SessionCookieName2</name> +<description>Name and attributes for the RFC2965 cookie storing the session</description> +<syntax>SessionCookieName2 <var>name</var> <var>attributes</var></syntax> +<default>none</default> +<contextlist><context>directory</context> +</contextlist> +<compatibility>Available in Apache 2.3.0 and later</compatibility> + +<usage> + <p>The <directive>SessionCookieName2</directive> directive specifies the name and + optional attributes of an RFC2965 compliant cookie inside which the session will + be stored. RFC2965 cookies are set using the <code>Set-Cookie2</code> HTTP header. + </p> + + <p>An optional list of cookie attributes can be specified, as per the example below. + These attributes are inserted into the cookie as is, and are not interpreted by + Apache. Ensure that your attributes are defined correctly as per the cookie specification. + </p> + + <example><title>Cookie2 with attributes</title> + Session On<br /> + SessionCookieName2 session path=/private;domain=example.com;httponly;secure;version=1;<br /> + </example> + +</usage> +</directivesynopsis> + +<directivesynopsis> +<name>SessionCookieRemove</name> +<description>Control for whether session cookies should be removed from incoming HTTP headers</description> +<syntax>SessionCookieRemove On|Off</syntax> +<default>SessionCookieRemove Off</default> +<contextlist><context>directory</context> +</contextlist> +<compatibility>Available in Apache 2.3.0 and later</compatibility> + +<usage> + <p>The <directive>SessionCookieRemove</directive> flag controls whether the cookies + containing the session will be removed from the headers during request processing.</p> + + <p>In a reverse proxy situation where the Apache server acts as a server frontend for + a backend origin server, revealing the contents of the session cookie to the backend + could be a potential privacy violation. When set to on, the session cookie will be + removed from the incoming HTTP headers.</p> + +</usage> +</directivesynopsis> + +</modulesynopsis> diff --git a/include/util_cookies.h b/include/util_cookies.h new file mode 100644 index 0000000000..c01b5f4436 --- /dev/null +++ b/include/util_cookies.h @@ -0,0 +1,120 @@ +/* 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. + */ + +/** + * @file util_cookies.h + * @brief Apache cookie library + */ + +#ifndef UTIL_COOKIES_H +#define UTIL_COOKIES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup APACHE_CORE_COOKIE Cookies + * @ingroup APACHE_CORE + * + * RFC2109 and RFC2965 compliant HTTP cookies can be read from and written + * to using this set of functions. + * + */ + +#include "apr_errno.h" +#include "httpd.h" + +#define SET_COOKIE "Set-Cookie" +#define SET_COOKIE2 "Set-Cookie2" +#define DEFAULT_ATTRS "HttpOnly;Secure;Version=1" +#define CLEAR_ATTRS "Max-Age=0;Version=1" + +typedef struct { + request_rec *r; + const char *name; + const char *encoded; + apr_table_t *new_cookies; + int duplicated; +} ap_cookie_do; + +/** + * Write an RFC2109 compliant cookie. + * + * @param r The request + * @param name The name of the cookie. + * @param val The value to place in the cookie. + * @param attrs The string containing additional cookie attributes. If NULL, the + * DEFAULT_ATTRS will be used. + * @param maxage If non zero, a Max-Age header will be added to the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_write(request_rec * r, const char *name, const char *val, + const char *attrs, long maxage); + +/** + * Write an RFC2965 compliant cookie. + * + * @param r The request + * @param name2 The name of the cookie. + * @param val The value to place in the cookie. + * @param attrs2 The string containing additional cookie attributes. If NULL, the + * DEFAULT_ATTRS will be used. + * @param maxage If non zero, a Max-Age header will be added to the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_write2(request_rec * r, const char *name2, const char *val, + const char *attrs2, long maxage); + +/** + * Remove an RFC2109 compliant cookie. + * + * @param r The request + * @param name The name of the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_remove(request_rec * r, const char *name); + +/** + * Remove an RFC2965 compliant cookie. + * + * @param r The request + * @param name2 The name of the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_remove2(request_rec * r, const char *name2); + +/** + * Read a cookie called name, placing its value in val. + * + * Both the Cookie and Cookie2 headers are scanned for the cookie. + * + * If the cookie is duplicated, this function returns APR_EGENERAL. If found, + * and if remove is non zero, the cookie will be removed from the headers, and + * thus kept private from the backend. + */ +AP_DECLARE(apr_status_t) ap_cookie_read(request_rec * r, const char *name, const char **val, + int remove); + +/** + * Sanity check a given string that it exists, is not empty, + * and does not contain the special characters '=', ';' and '&'. + * + * It is used to sanity check the cookie names. + */ +AP_DECLARE(apr_status_t) ap_cookie_check_string(const char *string); + +#ifdef __cplusplus +} +#endif + +#endif /* !UTIL_COOKIES_H */ diff --git a/modules/session/config.m4 b/modules/session/config.m4 index f346bfda71..53548cf076 100644 --- a/modules/session/config.m4 +++ b/modules/session/config.m4 @@ -10,7 +10,7 @@ dnl Session modules; modules that are capable of storing key value pairs in dnl various places, such as databases, LDAP, or cookies. dnl APACHE_MODULE(session, session module, , , most) -dnl APACHE_MODULE(session_cookie, session cookie module, , , most) +APACHE_MODULE(session_cookie, session cookie module, , , most) dnl APACHE_MODULE(session_crypto, session crypto module, , , most) dnl APACHE_MODULE(session_dbd, session dbd module, , , most) dnl APACHE_MODULE(session_ldap, session ldap module, , , most) diff --git a/modules/session/mod_session_cookie.c b/modules/session/mod_session_cookie.c new file mode 100644 index 0000000000..b69ef2833e --- /dev/null +++ b/modules/session/mod_session_cookie.c @@ -0,0 +1,269 @@ +/* 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. + */ + +#define CORE_PRIVATE + +#include "mod_session.h" +#include "apr_lib.h" +#include "apr_strings.h" +#include "http_log.h" +#include "util_cookies.h" + +#define LOG_PREFIX "mod_session_cookie: " +#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. + */ +AP_DECLARE(int) ap_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); + + /* don't cache auth protected pages */ + apr_table_addn(r->headers_out, "Cache-Control", "no-cache"); + + /* 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); + } + else { + ap_cookie_remove(r, conf->name); + } + } + + /* 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); + } + else { + ap_cookie_remove2(r, conf->name2); + } + } + + 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. + */ +AP_DECLARE(int) ap_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->main ? r->main : r; + + /* 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(r->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(r->pool, sizeof(session_rec)); + zz->pool = r->pool; + zz->entries = apr_table_make(r->pool, 10); + zz->encoded = val; + zz->uuid = (apr_uuid_t *) apr_pcalloc(r->pool, sizeof(apr_uuid_t)); + *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); + + 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, OR_AUTHCFG, + "The name of the RFC2109 cookie carrying the session"), + AP_INIT_RAW_ARGS("SessionCookieName2", set_cookie_name2, NULL, OR_AUTHCFG, + "The name of the RFC2965 cookie carrying the session"), + AP_INIT_FLAG("SessionCookieRemove", set_remove, NULL, 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(ap_session_cookie_load, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_session_save(ap_session_cookie_save, NULL, NULL, APR_HOOK_MIDDLE); +} + +module AP_MODULE_DECLARE_DATA session_cookie_module = +{ + 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 */ +}; diff --git a/server/Makefile.in b/server/Makefile.in index 5fc286a571..3c96c4061b 100644 --- a/server/Makefile.in +++ b/server/Makefile.in @@ -11,7 +11,7 @@ LTLIBRARY_SOURCES = \ config.c log.c main.c vhost.c util.c \ util_script.c util_md5.c util_cfgtree.c util_ebcdic.c util_time.c \ connection.c listen.c util_mutex.c \ - mpm_common.c util_charset.c util_debug.c util_xml.c \ + mpm_common.c util_charset.c util_cookies.c util_debug.c util_xml.c \ util_expr.c util_filter.c util_pcre.c exports.c \ scoreboard.c error_bucket.c protocol.c core.c request.c provider.c \ eoc_bucket.c eor_bucket.c core_filters.c diff --git a/server/util_cookies.c b/server/util_cookies.c new file mode 100644 index 0000000000..dc04b5bbe6 --- /dev/null +++ b/server/util_cookies.c @@ -0,0 +1,256 @@ +/* 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. + */ + +#define CORE_PRIVATE + +#include "util_cookies.h" +#include "apr_lib.h" +#include "apr_strings.h" +#include "http_log.h" + +#define LOG_PREFIX "ap_cookie: " + +/** + * Write an RFC2109 compliant cookie. + * + * @param r The request + * @param name The name of the cookie. + * @param val The value to place in the cookie. + * @param attrs The string containing additional cookie attributes. If NULL, the + * DEFAULT_ATTRS will be used. + * @param maxage If non zero, a Max-Age header will be added to the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_write(request_rec * r, const char *name, const char *val, + const char *attrs, long maxage) +{ + + char *buffer; + + /* handle expiry */ + buffer = ""; + if (maxage) { + buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";", NULL); + } + + /* create RFC2109 compliant cookie */ + char *rfc2109 = apr_pstrcat(r->pool, name, "=", val, ";", + buffer, + attrs && strlen(attrs) > 0 ? + attrs : DEFAULT_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, LOG_PREFIX + "user '%s' set cookie: '%s'", r->user, rfc2109); + apr_table_addn(r->headers_out, SET_COOKIE, rfc2109); + + return APR_SUCCESS; + +} + +/** + * Write an RFC2965 compliant cookie. + * + * @param r The request + * @param name2 The name of the cookie. + * @param val The value to place in the cookie. + * @param attrs2 The string containing additional cookie attributes. If NULL, the + * DEFAULT_ATTRS will be used. + * @param maxage If non zero, a Max-Age header will be added to the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_write2(request_rec * r, const char *name2, const char *val, + const char *attrs2, long maxage) +{ + + char *buffer; + + /* handle expiry */ + buffer = ""; + if (maxage) { + buffer = apr_pstrcat(r->pool, "Max-Age=", apr_ltoa(r->pool, maxage), ";"); + } + + /* create RFC2965 compliant cookie */ + char *rfc2965 = apr_pstrcat(r->pool, name2, "=", val, ";", + buffer, + attrs2 && strlen(attrs2) > 0 ? + attrs2 : DEFAULT_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, LOG_PREFIX + "user '%s' set cookie2: '%s'", r->user, rfc2965); + apr_table_addn(r->headers_out, SET_COOKIE2, rfc2965); + + return APR_SUCCESS; + +} + +/** + * Remove an RFC2109 compliant cookie. + * + * @param r The request + * @param name The name of the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_remove(request_rec * r, const char *name) +{ + + /* create RFC2109 compliant cookie */ + char *rfc2109 = apr_pstrcat(r->pool, name, "=;", + CLEAR_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, LOG_PREFIX + "user '%s' removed cookie: '%s'", r->user, rfc2109); + apr_table_addn(r->headers_out, SET_COOKIE, rfc2109); + + return APR_SUCCESS; + +} + +/** + * Remove an RFC2965 compliant cookie. + * + * @param r The request + * @param name2 The name of the cookie. + */ +AP_DECLARE(apr_status_t) ap_cookie_remove2(request_rec * r, const char *name2) +{ + + /* create RFC2965 compliant cookie */ + char *rfc2965 = apr_pstrcat(r->pool, name2, "=;", + CLEAR_ATTRS, NULL); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, LOG_PREFIX + "user '%s' removed cookie2: '%s'", r->user, rfc2965); + apr_table_addn(r->headers_out, SET_COOKIE2, rfc2965); + + return APR_SUCCESS; + +} + +/* Iterate through the cookies, isolate our cookie and then remove it. + * + * If our cookie appears two or more times, but with different values, + * remove it twice and set the duplicated flag to true. Remove any + * $path or other attributes following our cookie if present. If we end + * up with an empty cookie, remove the whole header. + */ +static int extract_cookie_line(ap_cookie_do * v, const char *key, const char *val) +{ + char *last1, *last2; + char *cookie = apr_pstrdup(v->r->pool, val); + const char *name = apr_pstrcat(v->r->pool, v->name ? v->name : "", "=", NULL); + size_t len = strlen(name); + char *new_cookie = ""; + const char *comma = ","; + char *next1; + const char *semi = ";"; + char *next2; + const char *sep = ""; + int cookies = 0; + + /* find the cookie called name */ + int eat = 0; + next1 = apr_strtok(cookie, comma, &last1); + while (next1) { + next2 = apr_strtok(next1, semi, &last2); + while (next2) { + char *trim = next2; + while (apr_isspace(*trim)) { + trim++; + } + if (!strncmp(trim, name, len)) { + if (v->encoded) { + if (strcmp(v->encoded, trim + len)) { + v->duplicated = 1; + } + } + v->encoded = apr_pstrdup(v->r->pool, trim + len); + eat = 1; + } + else { + if (*trim != '$') { + cookies++; + eat = 0; + } + if (!eat) { + new_cookie = apr_pstrcat(v->r->pool, new_cookie, sep, next2, NULL); + } + } + next2 = apr_strtok(NULL, semi, &last2); + sep = semi; + } + + next1 = apr_strtok(NULL, comma, &last1); + sep = comma; + } + + /* any cookies left over? */ + if (cookies) { + apr_table_addn(v->new_cookies, key, new_cookie); + } + + return 1; +} + +/** + * Read a cookie called name, placing its value in val. + * + * Both the Cookie and Cookie2 headers are scanned for the cookie. + * + * If the cookie is duplicated, this function returns APR_EGENERAL. If found, + * and if remove is non zero, the cookie will be removed from the headers, and + * thus kept private from the backend. + */ +AP_DECLARE(apr_status_t) ap_cookie_read(request_rec * r, const char *name, const char **val, + int remove) +{ + + ap_cookie_do v; + v.r = r; + v.encoded = NULL; + v.new_cookies = apr_table_make(r->pool, 10); + v.duplicated = 0; + v.name = name; + + apr_table_do((int (*) (void *, const char *, const char *)) + extract_cookie_line, (void *) &v, r->headers_in, + "Cookie", "Cookie2", NULL); + if (v.duplicated) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, LOG_PREFIX + "client submitted cookie '%s' more than once: %s", v.name, r->uri); + return APR_EGENERAL; + } + + /* remove our cookie(s), and replace them */ + if (remove) { + apr_table_unset(r->headers_in, "Cookie"); + apr_table_unset(r->headers_in, "Cookie2"); + r->headers_in = apr_table_overlay(r->pool, r->headers_in, v.new_cookies); + } + + *val = v.encoded; + + return APR_SUCCESS; + +} + +/** + * Sanity check a given string that it exists, is not empty, + * and does not contain the special characters '=', ';' and '&'. + * + * It is used to sanity check the cookie names. + */ +AP_DECLARE(apr_status_t) ap_cookie_check_string(const char *string) +{ + if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&') || + ap_strchr_c(string, ';')) { + return APR_EGENERAL; + } + return APR_SUCCESS; +} |