/*======================================================================= * 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 */ };