/*=======================================================================
* Copyright (c) 2000 MPL Communications Inc. Author: Justin Wells
* http://www.carlsononline.com justin@semiotek.com
* ======================================================================
*
* Module Name: mod_ticket.c
*
* Version: 1.0 ($Id: mod_ticket.c,v 1.2 2000/01/25 20:17:42 justin Exp $)
*
* Contributor: MPL Communications Inc. (http://www.carlsononline.com)
*
* Author: Justin Wells (justin@semiotek.com)
*
* Description: Check for a a digitally signed ticket in the URI, and
* if found make it available in the variable $TICKET
*
* Motivation: Allow passing authenticated sessions from one domain
* to another in a secure fashion by way of a shared secret;
* track an http session through a site without using cookies
* in a manner which survives relative URL links.
*
* URI Format: http://servername/$ticketname$ticketvalue$md5sum/URI
* where md5sum == md5sum(strcat(secret,ticketvalue,remote_ip))
*
* Directives : TicketKey NAME SECRET define a ticketname
* TicketDelim CHAR use CHAR instead of $
* TicketSumLength NUMBER minimum length of md5sum
* TicketCryptIP on|off include remote IP in md5sum?
*
* Environment: $TICKET the value of the ticket
* $TICKET_NAME the name of the ticket
* $TICKET_ERROR reason for ignoring ticket
* $TICKET_SUM the string used to generate md5sum
*
* mod_ticket will only set the environment variables $TICKET and
* $TICKET_NAME if the md5sum in the ticket is valid. If a ticket is
* rejected an explanation will appear in the variable $TICKET_ERROR.
*
* The TicketKey directive is used to specify ticket names. Each ticket
* name has a secret phrase. When checking a request, the ticket value
* is appended to the ticket secret and an md5sum is computed. The
* computed sum must match the md5sum stored in the ticket.
*
* The TicketDelim directive allows you to specify a different delimiter
* other than the default $. Any URI beginning with this delimiter will
* be inspected to see if it begins with a ticket.
*
* The TicketCryptIP directive allows you to turn on and off inclusion
* of the remote IP in the generated md5sum. Including the IP results
* in tickets that are valid for only one client.
*
* The TicketSumLength directive allows you to specify only a subset
* of the actual md5sum in the URL. This shortens the URL, and reduces
* the cryptographic security of the scheme. If you specify a value of
* 12, for example, then you only need the 12 rightmost digits of the
* md5sum in the ticket (though you are allowed to use more). Setting
* TicketSumLength to zero allows tickets with no authentication,
* though if a sum is specified it must still be valid.
*
* =======================================================================
* Copyright (c) 2000 MPL Communications Inc. All rights reserved.
* This software was written for MPL Communications Inc. by Justin Wells
* and is hereby contributed to the Apache Software Foundation for
* distribution under the Apache license, as follows.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* 4. The names "Apache Server" and "Apache Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``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 THE APACHE GROUP OR
* ITS CONTRIBUTORS 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 consists of voluntary contributions made by many
* individuals on behalf of the Apache Group and was originally based
* on public domain software written at the National Center for
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
* For more information on the Apache Group and the Apache HTTP server
* project, please see .
*
*/
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_main.h"
#include "http_protocol.h"
#include "util_script.h"
#include "util_md5.h"
#include
/*
* Declare ourselves so the configuration routines can find and know us.
* We'll fill it in at the end of the module.
*/
module ticket_module;
/*--------------------------------------------------------------------------*/
/* */
/* CONFIGURATION HANDLING--build up the key_node list */
/* */
/*--------------------------------------------------------------------------*/
#define TICKET_DELIM '$'
#define SUM_LENGTH 32
/*
* Configuration for this module consists of a list of ticket_records. Each
* record supplies the data for one passphrase, along with a pointer to
* the next ticket record. This is a linked list. The first node in the
* list is a dummy record. The subsequent nodes are the config data.
* New data is added to the start of the list. Data is merged from a parent
* config by sharing the tail of a list, and then appending the override
* data to the start of the list.
*
* Our main hook is URI rewriting, which is called before per-directory
* configuration steps are performed, therefore we can only use server conf.
*/
typedef struct key_node
{
char *name;
char *phrase;
struct key_node *next;
}
key_node;
typedef struct ticket_conf
{
key_node *head;
char delimiter;
int md5length;
int cryptip;
}
ticket_conf;
/**
* Internal method--create a new key node
*/
key_node *new_key_node(pool * p, char *name, char *phrase)
{
key_node *new = (key_node *) ap_palloc(p, sizeof(key_node));;
new->name = name;
new->phrase = phrase;
new->next = NULL;
return new;
}
/**
* Internal method--insert a key node into a key node list
*/
void insert_key_node(key_node * head, key_node * new)
{
new->next = head->next;
head->next = new;
}
/**
* Create a new config structure, being a list of key_node. This
* list begins with a dummy node. This is the server config.
*/
static void *ticket_create_sconfig(pool * p, server_rec * s)
{
ticket_conf *conf = ap_palloc(p, sizeof(ticket_conf));
conf->delimiter = TICKET_DELIM;
conf->md5length = SUM_LENGTH;
conf->head = new_key_node(p, NULL, NULL);
conf->cryptip = 1;
return conf;
}
/**
* Merge the config structure for this location with its parents. This
* means attaching the parents list to the end of the childs list.
*/
static void *ticket_merge_sconfig(pool * p, void *parent_conf, void *sub_conf)
{
ticket_conf *par = (ticket_conf *) parent_conf;
ticket_conf *sub = (ticket_conf *) sub_conf;
ticket_conf *conf = ap_palloc(p, sizeof(ticket_conf));
key_node *phead = ((ticket_conf *) parent_conf)->head;
key_node *shead = ((ticket_conf *) sub_conf)->head;
key_node *head = new_key_node(p, NULL, NULL);
key_node *n = NULL;
conf->delimiter = sub->delimiter;
conf->md5length = sub->md5length;
conf->cryptip = sub->cryptip;
/* parent list at end, unmodified, sub list inserted ahead of that */
conf->head->next = phead->next;
while (shead->next != NULL) {
shead = shead->next;
n = new_key_node(p, shead->name, shead->phrase);
insert_key_node(conf->head, n);
}
return conf;
}
/**
* Add a new key to the list of ticket records, at the beginning, after
* the initial dummy node.
*/
static const char *ticket_handle_key(cmd_parms * cmd, void *mconfig,
char *keyname, char *keyphrase)
{
server_rec *s = cmd->server;
ticket_conf *conf =
(ticket_conf *) ap_get_module_config(s->module_config,
&ticket_module);
key_node *n = new_key_node(cmd->pool, keyname, keyphrase);
if (!keyname || !keyphrase) {
return "You must specify both a keyname and a keyphrase";
}
insert_key_node(conf->head, n);
return NULL;
}
/**
* Set the delimiter character used to separate the tickets. By default
* this is the $ character, but you can set this to another character
* if you prefer something different. Do not use / or ~ or something
* meaningful to another module or in a URL.
*/
static const char *ticket_set_delim(cmd_parms * cmd, void *mconfig,
char *delimiter)
{
server_rec *s = cmd->server;
char delim;
ticket_conf *conf =
(ticket_conf *) ap_get_module_config(s->module_config,
&ticket_module);
if (delimiter) {
delim = delimiter[0];
switch (delim) {
case '\0':
case '/':
case '\\':
case ':':
case ' ':
case ' ':
case '#':
return "Illegal character specified as ticket delimiter";
}
conf->delimiter = delim;
}
else {
"Ticket delimiter directive incorrectly specified";
}
return NULL;
}
/**
* Set the length of the MD5SUM used to generate tickets. A full MD5SUM
* will be generated, but you may wish to reduce the length of the
* resulting URLs (and thus reduce the security of the scheme) by
* using only part of the computed sum. mod_ticket will only use this
* many characters from the __end__ of the md5sum. By default it is
* a 12 character checksum. It can range from 0 to 32. Setting it
* to zero turns off authentication checking.
*/
static const char *ticket_set_sumlength(cmd_parms * cmd, void *mconfig,
char *length)
{
int len = atoi(length);
server_rec *s = cmd->server;
ticket_conf *conf =
(ticket_conf *) ap_get_module_config(s->module_config,
&ticket_module);
if ((len >= 0) && (len <= 32)) {
conf->md5length = len;
}
else {
return "Ticket MD5 length must be between 0 and 32";
}
return NULL;
}
/**
* Set whether or not the IP number is crypted into the md5sum or not.
* The default is that it is.
*/
static const char *ticket_set_cryptip(cmd_parms * cmd, void *mconfig,
int bool)
{
server_rec *s = cmd->server;
ticket_conf *conf =
(ticket_conf *) ap_get_module_config(s->module_config,
&ticket_module);
conf->cryptip = bool;
return NULL;
}
/*--------------------------------------------------------------------------*/
/* */
/* REQUEST PROCESSING -- Handle requests and extract our ticket from the */
/* URL if it is there */
/* */
/*--------------------------------------------------------------------------*/
/*
* This routine gives our module an opportunity to translate the URI into an
* actual filename. If we don't do anything special, the server's default
* rules (Alias directives and the like) will continue to be followed.
*
* The return value is OK, DECLINED, or HTTP_mumble. If we return OK, no
* further modules are called for this phase.
*/
static int ticket_translate_handler(request_rec * r)
{
void *sconf = r->server->module_config;
char *ticket, *ticket_value, *ticket_name, *ticket_sum;
char *sum, *md5string;
ticket_conf *conf =
(ticket_conf *) ap_get_module_config(sconf, &ticket_module);
key_node *keynode = conf->head;
char *name = r->uri;
const char *filename;
char *newfilename;
int len = 0;
/*
* If the name doesn't match our pattern, we ignore it
*/
if ((keynode == NULL) || (keynode->next == NULL) ||
(name[0] != '/') || (name[1] != conf->delimiter)) {
return DECLINED;
}
filename = name + 2;
/*
* Advance filename so that the / following our ticket, if there was one
* is at the head of filename.
*/
ticket = ap_getword(r->pool, &filename, '/');
if (filename[-1] == '/') {
--filename;
}
/*
* Check whether we got a ticket
*/
if (ticket[0] == '\0') {
return DECLINED;
}
/**
* Fixup the URI and filename so that the ticket was never there
*/
newfilename = ap_pstrdup(r->pool, filename);
r->filename = newfilename;
r->uri = ap_pstrdup(r->pool, newfilename);
ticket_name = ticket;
ticket_value = strchr(ticket, conf->delimiter);
if (!ticket_value) {
ap_table_setn(r->subprocess_env, "TICKET_ERROR",
"Supplied ticket does not have a value!");
return DECLINED;
}
ticket_value[0] = '\0';
ticket_value++;
/* If we need an md5sum (conf length > 0) make sure we got one */
ticket_sum = strchr(ticket_value, conf->delimiter);
if (ticket_sum) {
ticket_sum[0] = '\0';
ticket_sum++;
len = strlen(ticket_sum);
if (len < conf->md5length) {
ap_table_setn(r->subprocess_env, "TICKET_ERROR",
"Supplied md5sum was not long enough");
return DECLINED;
}
if (len > 32) {
len = 32;
}
}
else if (conf->md5length) {
ap_table_setn(r->subprocess_env, "TICKET_ERROR",
"Supplied ticket did not have an md5sum");
return DECLINED;
}
while (keynode->next) {
keynode = keynode->next;
if (keynode->name && (strcmp(ticket_name, keynode->name) == 0)) {
if (len > 0) {
md5string = ap_pstrcat(r->pool, keynode->phrase, ticket_value,
(conf->cryptip ? r->connection->
remote_ip : NULL), NULL);
ap_table_setn(r->subprocess_env, "TICKET_SUM", md5string);
sum = ap_md5(r->pool, md5string);
sum = sum + (32 - len);
if (ticket_sum && strcmp(sum, ticket_sum) != 0) {
ap_table_setn(r->subprocess_env, "TICKET_ERROR",
"Ticket failed md5sum check");
return DECLINED;
}
}
ap_table_setn(r->subprocess_env, "TICKET_NAME", ticket_name);
ap_table_setn(r->subprocess_env, "TICKET", ticket_value);
return DECLINED;
}
}
ap_table_setn(r->subprocess_env, "TICKET_ERROR",
"Ticket NAME did not match any of the available keys");
return DECLINED;
}
/*--------------------------------------------------------------------------*/
/* */
/* MODULE DEFINITION -- these tables define the content of the module */
/* */
/*--------------------------------------------------------------------------*/
static const command_rec ticket_cmds[] = {
{
"TicketKey", /* directive name */
ticket_handle_key, /* config action routine */
NULL, /* argument to include in call */
RSRC_CONF, /* in .htaccess if AllowOverride Options */
TAKE2, /* arguments */
"Define a key for decrypting URL tickets. Two arguments: name password"
/* directive description */
},
{
"TicketDelim", /* directive name */
ticket_set_delim, /* config action routine */
NULL, /* argument to include in call */
RSRC_CONF, /* in .htaccess if AllowOverride Options */
TAKE1, /* arguments */
"Define the delimiter character (eg: $) used to delimit tickets\n"
/* directive description */
},
{
"TicketSumLength", /* directive name */
ticket_set_sumlength, /* config action routine */
NULL, /* argument to include in call */
RSRC_CONF, /* in .htaccess if AllowOverride Options */
TAKE1, /* arguments */
"The length of the md5-based checksum used in a ticket (up to 32)\n"
/* directive description */
},
{
"TicketCryptIP", /* directive name */
ticket_set_cryptip, /* config action routine */
NULL, /* argument to include in call */
RSRC_CONF, /* in .htaccess if AllowOverride Options */
FLAG, /* arguments */
"Does the md5sum also hash the IP number of the request?\n"
/* directive description */
},
{NULL}
};
module ticket_module = {
STANDARD_MODULE_STUFF,
NULL, /* module initializer */
NULL, /* per-directory config creator */
NULL, /* dir config merger */
ticket_create_sconfig, /* server config creator */
ticket_merge_sconfig, /* server config merger */
ticket_cmds, /* command table */
NULL, /* [7] list of handlers */
ticket_translate_handler, /* [2] filename-to-URI translation */
NULL, /* [5] check/validate user_id */
NULL, /* [6] check user_id is valid *here* */
NULL, /* [4] check access by host address */
NULL, /* [7] MIME type checker/setter */
NULL, /* [10] logger */
NULL, /* [3] header parser */
NULL, /* process initializer */
NULL, /* process exit/cleanup */
NULL, /* [1] post read_request handling */
};