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 "http_log.h"
21#include "util_cookies.h"
22
23#define MOD_SESSION_COOKIE "mod_session_cookie"
24
25module AP_MODULE_DECLARE_DATA session_cookie_module;
26
27/**
28 * Structure to carry the per-dir session config.
29 */
30typedef struct {
31    const char *name;
32    int name_set;
33    const char *name_attrs;
34    const char *name2;
35    int name2_set;
36    const char *name2_attrs;
37    int remove;
38    int remove_set;
39} session_cookie_dir_conf;
40
41/**
42 * Set the cookie and embed the session within it.
43 *
44 * This function adds an RFC2109 compliant Set-Cookie header for
45 * the cookie specified in SessionCookieName, and an RFC2965 compliant
46 * Set-Cookie2 header for the cookie specified in SessionCookieName2.
47 *
48 * If specified, the optional cookie attributes will be added to
49 * each cookie. If defaults are not specified, DEFAULT_ATTRS
50 * will be used.
51 *
52 * On success, this method will return APR_SUCCESS.
53 *
54 * @param r The request pointer.
55 * @param z A pointer to where the session will be written.
56 */
57static apr_status_t session_cookie_save(request_rec * r, session_rec * z)
58{
59
60    session_cookie_dir_conf *conf = ap_get_module_config(r->per_dir_config,
61                                                    &session_cookie_module);
62
63    /* don't cache auth protected pages */
64    apr_table_addn(r->headers_out, "Cache-Control", "no-cache");
65
66    /* create RFC2109 compliant cookie */
67    if (conf->name_set) {
68        if (z->encoded && z->encoded[0]) {
69            ap_cookie_write(r, conf->name, z->encoded, conf->name_attrs,
70                            z->maxage, r->headers_out, r->err_headers_out,
71                            NULL);
72        }
73        else {
74            ap_cookie_remove(r, conf->name, conf->name_attrs, r->headers_out,
75                             r->err_headers_out, NULL);
76        }
77    }
78
79    /* create RFC2965 compliant cookie */
80    if (conf->name2_set) {
81        if (z->encoded && z->encoded[0]) {
82            ap_cookie_write2(r, conf->name2, z->encoded, conf->name2_attrs,
83                             z->maxage, r->headers_out, r->err_headers_out,
84                             NULL);
85        }
86        else {
87            ap_cookie_remove2(r, conf->name2, conf->name2_attrs,
88                              r->headers_out, r->err_headers_out, NULL);
89        }
90    }
91
92    if (conf->name_set || conf->name2_set) {
93        return OK;
94    }
95    return DECLINED;
96
97}
98
99/**
100 * Isolate the cookie with the name "name", and if present, extract
101 * the payload from the cookie.
102 *
103 * If the cookie is found, the cookie and any other cookies with the
104 * same name are removed from the cookies passed in the request, so
105 * that credentials are not leaked to a backend server or process.
106 *
107 * A missing or malformed cookie will cause this function to return
108 * APR_EGENERAL.
109 *
110 * On success, this returns APR_SUCCESS.
111 */
112static apr_status_t session_cookie_load(request_rec * r, session_rec ** z)
113{
114
115    session_cookie_dir_conf *conf = ap_get_module_config(r->per_dir_config,
116                                                    &session_cookie_module);
117
118    session_rec *zz = NULL;
119    const char *val = NULL;
120    const char *note = NULL;
121    const char *name = NULL;
122    request_rec *m = r;
123
124    /* find the first redirect */
125    while (m->prev) {
126        m = m->prev;
127    }
128    /* find the main request */
129    while (m->main) {
130        m = m->main;
131    }
132
133    /* is our session in a cookie? */
134    if (conf->name2_set) {
135        name = conf->name2;
136    }
137    else if (conf->name_set) {
138        name = conf->name;
139    }
140    else {
141        return DECLINED;
142    }
143
144    /* first look in the notes */
145    note = apr_pstrcat(m->pool, MOD_SESSION_COOKIE, name, NULL);
146    zz = (session_rec *)apr_table_get(m->notes, note);
147    if (zz) {
148        *z = zz;
149        return OK;
150    }
151
152    /* otherwise, try parse the cookie */
153    ap_cookie_read(r, name, &val, conf->remove);
154
155    /* create a new session and return it */
156    zz = (session_rec *) apr_pcalloc(m->pool, sizeof(session_rec));
157    zz->pool = m->pool;
158    zz->entries = apr_table_make(m->pool, 10);
159    zz->encoded = val;
160    *z = zz;
161
162    /* put the session in the notes so we don't have to parse it again */
163    apr_table_setn(m->notes, note, (char *)zz);
164
165    return OK;
166
167}
168
169
170
171static void *create_session_cookie_dir_config(apr_pool_t * p, char *dummy)
172{
173    session_cookie_dir_conf *new =
174    (session_cookie_dir_conf *) apr_pcalloc(p, sizeof(session_cookie_dir_conf));
175
176    return (void *) new;
177}
178
179static void *merge_session_cookie_dir_config(apr_pool_t * p, void *basev,
180                                             void *addv)
181{
182    session_cookie_dir_conf *new = (session_cookie_dir_conf *)
183                                apr_pcalloc(p, sizeof(session_cookie_dir_conf));
184    session_cookie_dir_conf *add = (session_cookie_dir_conf *) addv;
185    session_cookie_dir_conf *base = (session_cookie_dir_conf *) basev;
186
187    new->name = (add->name_set == 0) ? base->name : add->name;
188    new->name_attrs = (add->name_set == 0) ? base->name_attrs : add->name_attrs;
189    new->name_set = add->name_set || base->name_set;
190    new->name2 = (add->name2_set == 0) ? base->name2 : add->name2;
191    new->name2_attrs = (add->name2_set == 0) ? base->name2_attrs : add->name2_attrs;
192    new->name2_set = add->name2_set || base->name2_set;
193    new->remove = (add->remove_set == 0) ? base->remove : add->remove;
194    new->remove_set = add->remove_set || base->remove_set;
195
196    return new;
197}
198
199/**
200 * Sanity check a given string that it exists, is not empty,
201 * and does not contain special characters.
202 */
203static const char *check_string(cmd_parms * cmd, const char *string)
204{
205    if (!string || !*string || ap_strchr_c(string, '=') || ap_strchr_c(string, '&')) {
206        return apr_pstrcat(cmd->pool, cmd->directive->directive,
207                           " cannot be empty, or contain '=' or '&'.",
208                           NULL);
209    }
210    return NULL;
211}
212
213static const char *set_cookie_name(cmd_parms * cmd, void *config,
214                                   const char *args)
215{
216    char *last;
217    char *line = apr_pstrdup(cmd->pool, args);
218    session_cookie_dir_conf *conf = (session_cookie_dir_conf *) config;
219    char *cookie = apr_strtok(line, " \t", &last);
220    conf->name = cookie;
221    conf->name_set = 1;
222    while (apr_isspace(*last)) {
223        last++;
224    }
225    conf->name_attrs = last;
226    return check_string(cmd, cookie);
227}
228
229static const char *set_cookie_name2(cmd_parms * cmd, void *config,
230                                    const char *args)
231{
232    char *last;
233    char *line = apr_pstrdup(cmd->pool, args);
234    session_cookie_dir_conf *conf = (session_cookie_dir_conf *) config;
235    char *cookie = apr_strtok(line, " \t", &last);
236    conf->name2 = cookie;
237    conf->name2_set = 1;
238    while (apr_isspace(*last)) {
239        last++;
240    }
241    conf->name2_attrs = last;
242    return check_string(cmd, cookie);
243}
244
245static const char *
246     set_remove(cmd_parms * parms, void *dconf, int flag)
247{
248    session_cookie_dir_conf *conf = dconf;
249
250    conf->remove = flag;
251    conf->remove_set = 1;
252
253    return NULL;
254}
255
256static const command_rec session_cookie_cmds[] =
257{
258    AP_INIT_RAW_ARGS("SessionCookieName", set_cookie_name, NULL, RSRC_CONF|OR_AUTHCFG,
259                     "The name of the RFC2109 cookie carrying the session"),
260    AP_INIT_RAW_ARGS("SessionCookieName2", set_cookie_name2, NULL, RSRC_CONF|OR_AUTHCFG,
261                     "The name of the RFC2965 cookie carrying the session"),
262    AP_INIT_FLAG("SessionCookieRemove", set_remove, NULL, RSRC_CONF|OR_AUTHCFG,
263                 "Set to 'On' to remove the session cookie from the headers "
264                 "and hide the cookie from a backend server or process"),
265    {NULL}
266};
267
268static void register_hooks(apr_pool_t * p)
269{
270    ap_hook_session_load(session_cookie_load, NULL, NULL, APR_HOOK_MIDDLE);
271    ap_hook_session_save(session_cookie_save, NULL, NULL, APR_HOOK_MIDDLE);
272}
273
274AP_DECLARE_MODULE(session_cookie) =
275{
276    STANDARD20_MODULE_STUFF,
277    create_session_cookie_dir_config, /* dir config creater */
278    merge_session_cookie_dir_config,  /* dir merger --- default is to
279                                       * override */
280    NULL,                             /* server config */
281    NULL,                             /* merge server config */
282    session_cookie_cmds,              /* command apr_table_t */
283    register_hooks                    /* register hooks */
284};
285