diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62c8935 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ \ No newline at end of file diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..0f4f293 --- /dev/null +++ b/CHANGES @@ -0,0 +1,57 @@ +Changes with Tequila Apache module 1.0 + + 1) Merge Apache1 and Apache2 sources. Now use make apache1 or make apache2 to + compile modules. It makes the source a little bit dirtier, but much easier + to maintain. + + 2) Full SSL support. 2 new directives added : + + TequilaCAFile + + cafile contains the certificate of the certification authority that + signed the tequila server certificate. + + TequilaCheckServerName + + Tells the module to check that the server name matches the name in the + certificate it presents. All possible means of extracting the server + name from the certificate are tried. + + 3) Now the module sets environment variables which contains the values of the + user attributes. These variables are : + + HTTP_TEQUILA_KEY + HTTP_TEQUILA_ORG + HTTP_TEQUILA_USER + HTTP_TEQUILA_HOST + HTTP_TEQUILA_PATH + HTTP_TEQUILA_FILTER + + That means that you can use the module to protect CGI scripts and retrieve + the identity of the user. In very simple cases, this avoid to call the + languages API's. + + 4) Implements the new resource interface. That resources declared to the local + server can be accessed. This adds 3 keywords : + + TequilaResource + TequilaKeyFile + TequilaCertFile + + 5) It is now possible to set the service name of the login interface, + use : + + TequilaService service_name + + 6) It is possible to ask the server for user attributes that will be set + in addition to default attributes (org, user, etc...). The new command + is TequilaRequest and the argiment is a list of Tequila attribute names + separated with white spaces or commas. + + TequilaRequest name firstname unit + + That's all for now. + + + Claude Lecommandeur (claude.lecommandeur@epfl.ch). + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5ffe476 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +apache1: + apxs -c -i -DUSESSL -lssl mod_tequila.c + +apache2: + apxs -c -i -DAPACHE2 -DUSESSL -lssl mod_tequila.c + +apache1nossl: + apxs -c -i mod_tequila.c + +apache2nossl: + apxs -c -i -DAPACHE2 mod_tequila.c + +compile1: + apxs -c -DUSESSL -lssl mod_tequila.c + +compile2: + apxs -c -DAPACHE2 -DUSESSL -lssl mod_tequila.c + diff --git a/mod_tequila.c b/mod_tequila.c new file mode 100644 index 0000000..cc5c656 --- /dev/null +++ b/mod_tequila.c @@ -0,0 +1,1827 @@ +// +// +// Apache module for Tequila authentication tool. +// Version 1.1.1. +// +// +#include +#include +#include +#include +#include + +#ifdef APACHE2 +# include +# include +# include +#endif + +#ifdef USESSL +# include +# include "openssl/bio.h" +# include "openssl/ssl.h" +# include "openssl/err.h" +# include "openssl/x509v3.h" +#endif + +#include "mod_tequila.h" + +static int createserverrequest (request_rec *r); +static Session *fetchattributes (request_rec *r, const char *requestkey); +static void substsepar (char *string, char oldsepar, char newsepar); +static char *unescape_url (AP_POOL *p, const char *url); +static int setenvvars (request_rec *r); +static void error (request_rec *r, char *msg); +static void writeline (AP_FILE_T fp, char *string); + +#ifdef APACHE2 + +#include + +static apr_global_mutex_t *tequila_log_lock = NULL; +static const command_rec command_table [] = { + AP_INIT_TAKE1( "TequilaRewrite", cmd_rewrite, NULL, OR_FILEINFO, "Rewrite"), + AP_INIT_RAW_ARGS( "TequilaAllowIf", cmd_allowif, NULL, OR_AUTHCFG, "Access condition"), + AP_INIT_RAW_ARGS( "TequilaRequest", cmd_request, NULL, OR_AUTHCFG, "Request"), + AP_INIT_RAW_ARGS( "TequilaAllowNet", cmd_allownet, NULL, OR_AUTHCFG, "Allowed Net"), + AP_INIT_RAW_ARGS( "TequilaAllows", cmd_allows, NULL, OR_AUTHCFG, "Allows attribute"), + AP_INIT_TAKE1( "TequilaService", cmd_service , NULL, OR_AUTHCFG, "Service name"), + AP_INIT_TAKE1( "TequilaResource", cmd_resource, NULL, OR_AUTHCFG, "Tequila resource"), + AP_INIT_TAKE1( "TequilaKeyFile", cmd_sslkeyfile, NULL, OR_AUTHCFG, "SSL Key file"), + AP_INIT_TAKE1( "TequilaCertFile", cmd_sslcertfile, NULL, OR_AUTHCFG, "SSL Cert file"), + + AP_INIT_TAKE1( "TequilaServer", cmd_server, NULL, RSRC_CONF, "Tequila server"), + AP_INIT_TAKE1( "TequilaSessionDir", cmd_sessdir, NULL, RSRC_CONF, "Session directory"), + AP_INIT_TAKE1( "TequilaSessionMax", cmd_sessmax, NULL, RSRC_CONF, "Session duration"), + AP_INIT_TAKE1( "TequilaLog", cmd_log, NULL, RSRC_CONF, "Logfile"), + AP_INIT_TAKE1( "TequilaLogLevel", cmd_loglevel, NULL, RSRC_CONF, "Log level"), + AP_INIT_NO_ARGS( "TequilaNoSSL", cmd_nossl, NULL, RSRC_CONF, "No SSL support"), + AP_INIT_NO_ARGS( "TequilaHasSSL", cmd_usessl, NULL, RSRC_CONF, "SSL support"), + AP_INIT_NO_ARGS( "TequilaUseSSL", cmd_usessl, NULL, RSRC_CONF, "SSL support"), + AP_INIT_TAKE1( "TequilaCAFile", cmd_cafile, NULL, RSRC_CONF, "CA file"), + AP_INIT_NO_ARGS( "TequilaCheckServerName", + cmd_checkservername, + NULL, RSRC_CONF, + "Check Server Name"), + { NULL } +}; + +module AP_MODULE_DECLARE_DATA tequila_module = { + STANDARD20_MODULE_STUFF, + create_perdir_config, /* create per-dir config structures */ + merge_perdir_config, /* merge per-dir config structures */ + create_global_config, /* create per-server config structures */ + merge_global_config, /* merge per-server config structures */ + command_table, /* table of config file commands */ + register_hooks /* register hooks */ +}; + +static void register_hooks (AP_POOL *p) { + ap_hook_post_config (post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_header_parser (check_access, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_translate_name (translate, NULL, NULL, APR_HOOK_FIRST); + ap_hook_fixups (setenvvars, NULL, NULL, APR_HOOK_MIDDLE); +} + +APR_DECLARE_OPTIONAL_FN(int, ssl_is_https, (conn_rec *)); +static APR_OPTIONAL_FN_TYPE(ssl_is_https) *url_is_https = NULL; + +#else /* APACHE2 */ + +static const command_rec tequila_cmds[] = { + {"TequilaRewrite", cmd_rewrite, NULL, OR_FILEINFO, TAKE1, "Rewrite" }, + {"TequilaAllowIf", cmd_allowif, NULL, OR_AUTHCFG, RAW_ARGS, "Access condition" }, + {"TequilaRequest", cmd_request, NULL, OR_AUTHCFG, RAW_ARGS, "Request" }, + {"TequilaAllowNet", cmd_allownet, NULL, OR_AUTHCFG, RAW_ARGS, "Allowed Net." }, + {"TequilaAllows", cmd_allows, NULL, OR_AUTHCFG, RAW_ARGS, "Allows attribute" }, + {"TequilaService", cmd_service, NULL, OR_AUTHCFG, TAKE1, "Service name" }, + {"TequilaResource", cmd_resource, NULL, OR_AUTHCFG, TAKE1, "Tequila resource" }, + {"TequilaKeyFile", cmd_sslkeyfile, NULL, OR_AUTHCFG, TAKE1, "SSL Key file" }, + {"TequilaCertFile", cmd_sslcertfile, NULL, OR_AUTHCFG, TAKE1, "SSL Cert file" }, + + {"TequilaServer", cmd_server, NULL, RSRC_CONF, TAKE1, "Local Tequila server" }, + {"TequilaSessionDir", cmd_sessdir, NULL, RSRC_CONF, TAKE1, "Session directory." }, + {"TequilaSessionMax", cmd_sessmax, NULL, RSRC_CONF, TAKE1, "Session duration." }, + {"TequilaLog", cmd_log, NULL, RSRC_CONF, TAKE1, "Filename of the logfile" }, + {"TequilaLogLevel", cmd_loglevel, NULL, RSRC_CONF, TAKE1, "Log level" }, + {"TequilaNoSSL", cmd_nossl, NULL, RSRC_CONF, NO_ARGS, "No SSL support" }, + {"TequilaHasSSL", cmd_usessl, NULL, RSRC_CONF, NO_ARGS, "SSL support" }, + {"TequilaUseSSL", cmd_usessl, NULL, RSRC_CONF, NO_ARGS, "SSL support" }, + + {"TequilaCAFile", cmd_cafile, NULL, RSRC_CONF, TAKE1, "CA file" }, + {"TequilaCheckServerName", + cmd_checkservername, NULL, RSRC_CONF, NO_ARGS, "Check Server Name" + }, + {NULL} +}; + +module MODULE_VAR_EXPORT tequila_module = { + STANDARD_MODULE_STUFF, + init_module, /* initializer */ + create_perdir_config, /* create per-directory config structure */ + merge_perdir_config, /* merge per-directory config structures */ + create_global_config, /* create per-server config structure */ + merge_global_config, /* server config merger */ + tequila_cmds, /* table of config file commands */ + NULL, /* [#8] MIME-typed-dispatched handlers */ + translate, /* [#1] URI to filename translation */ + NULL, /* [#4] validate user id from request */ + NULL, /* [#5] check if the user is ok _here_ */ + NULL, /* [#3] check access by host address */ + NULL, /* [#6] determine MIME type */ + setenvvars, /* [#7] pre-run fixups */ + NULL, /* [#9] logger */ + check_access, /* [#2] header parser */ + NULL, /* child_init */ + NULL, /* child_exit */ + NULL /* [#0] post read-request */ +}; + +#endif /* APACHE2 */ + +static int keyfromcookie = 0; +static char *defaultsessdir = "/var/www/Tequila/Sessions"; +static char *defaultteqserver = "tequila"; +static int defaultsessmax = 12 * 3600; + +#ifdef USESSL +typedef struct { + SSL_CTX *ctx; + BIO *bio; +} SSLenv; + +static SSLenv *opensslsocket (request_rec *r, char *host, int port); +static void closesslsocket (SSLenv *sslenv); +static int checkcertificate (request_rec *r, X509 *cert, char *host); +#endif + +static int translate (request_rec *r) { + tequila_perdir_conf *dconf; + char *left; + + teqlog (r, 99, "translate: uri = %s, filename = %s", r->uri, r->filename); + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + if (!dconf || !dconf->path || !dconf->rewrite) return DECLINED; + + left = skip_match (r->uri, dconf->path); + if (!strncmp (dconf->rewrite, "http:", 5) || + !strncmp (dconf->rewrite, "https:", 6)) { + r->proxyreq = PROXYREQ; + r->handler = "proxy-server"; + r->filename = AP_STRCAT (r->pool, "proxy:", dconf->rewrite, left, NULL); + if (r->path_info != NULL && r->uri == r->unparsed_uri) { + r->filename = AP_STRCAT (r->pool, r->filename, r->path_info, NULL); + } + if (r->args != NULL) { + r->filename = AP_STRCAT (r->pool, r->filename, "?", r->args, NULL); + } + } else { + r->filename = AP_STRCAT (r->pool, dconf->rewrite, left, NULL); + } + /* + if (r->path_info != NULL && r->uri == r->unparsed_uri) { + r->filename = AP_STRCAT (r->pool, r->filename, r->path_info, NULL); + } + if (r->args != NULL) { + r->filename = AP_STRCAT (r->pool, r->filename, "?", r->args, NULL); + } + */ + if (r->main && r->main->args) { + r->args = AP_STRDUP (r->pool, r->main->args); + } + teqlog (r, 2, "translate: url %s rewritten to %s", r->uri, r->filename); + return OK; +} + +static int check_access (request_rec *r) { + tequila_perdir_conf *dconf; + char *key; + int status; + + teqlog (r, 99, "check_access: uri = %s, filename = %s", r->uri, r->filename); + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + if (dconf == NULL || (dconf->allowif == NULL && + dconf->allownet == NULL)) return DECLINED; + if (dconf->allownet && check_net (r)) return OK; + key = getkey (r); + if (key) { + Session *session; + teqlog (r, 5, "check_access: key = %s", key); + session = readsession (r, key); + if (session) { + dconf->session = session; + if (!r->main && !keyfromcookie) set_cookie (r, key); + teqlog (r, 2, "check_access: session OK"); + return OK; + } + teqlog (r, 3, "check_access: readsession failed"); + session = fetchattributes (r, key); + if (session) { + dconf->session = session; + if (!r->main && !keyfromcookie) set_cookie (r, key); + teqlog (r, 2, "check_access: fetchattributes OK"); + return OK; + } + teqlog (r, 3, "check_access: fetchattributes failed"); + return HTTP_NOT_MODIFIED; + } + status = createserverrequest (r); + teqlog (r, 2, "check_access: redirect to %s\n", r->filename); + return status; +} + +static int check_net (request_rec *r) { + conn_rec *connection; + tequila_perdir_conf *dconf = (tequila_perdir_conf*) + ap_get_module_config (r->per_dir_config, &tequila_module); + if (!dconf || !dconf->allownet || !dconf->allownet->nelts) return 0; + connection = r->connection; + if (connection && connection->remote_ip) { + int i; + char *ip = connection->remote_ip; + char **netp = (char**) dconf->allownet->elts; + for (i = 0; i < dconf->allownet->nelts; i++) { + char *net = netp [i]; + teqlog (r, 4, "check_net: comparing %s to %s", ip, net); + if (strstr (ip, net) == ip) { + char *left = skip_match (r->uri, dconf->path); + teqlog (r, 4, "check_net: %s matches %s, access allowed.\n", ip, net); + teqlog (r, 2, "check_net: Acces authorised for ip address %s (%s)", ip, net); + return 1; + } + teqlog (r, 2, "check_net: %s doesn't match %s", ip, net); + } + } + return 0; +} + +static char *getRequest (request_rec *r) { + int i; + char **reqp, *request; + tequila_perdir_conf *dconf = (tequila_perdir_conf*) + ap_get_module_config (r->per_dir_config, &tequila_module); + if (!dconf || !dconf->request || !dconf->request->nelts) return NULL; + reqp = (char**) dconf->request->elts; + request = AP_STRDUP (r->pool, reqp [0]); + for (i = 1; i < dconf->request->nelts; i++) { + request = AP_STRCAT (r->pool, request, "+", reqp [i], NULL); + } + teqlog (r, 1, "getRequest: request = %s", request); + return request; +} + +static char *getFilter (request_rec *r) { + tequila_perdir_conf *dconf; + int i; + char *strfilt = NULL; + Filter *filter; + + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + if (dconf == NULL || dconf->allowif == NULL) return NULL; + filter = (Filter*) dconf->allowif->elts; + for (i = 0; i < dconf->allowif->nelts; i++) { + int first; + Filter *filt = (Filter*) (filter + i); + if (strfilt != NULL) strfilt = AP_STRCAT (r->pool, strfilt, "|", NULL); + first = 1; + while (filt) { + if (!first) strfilt = AP_STRCAT (r->pool, strfilt, "&", NULL); else first = 0; + if (strfilt != NULL) { + strfilt = AP_STRCAT (r->pool, strfilt, filt->nam, "=", filt->val, NULL); + } else { + strfilt = AP_STRCAT (r->pool, filt->nam, "=", filt->val, NULL); + } + filt = filt->next; + } + } + return strfilt; +} + +static char *getAllows (request_rec *r) { + tequila_perdir_conf *dconf; + int i; + char *strallows = NULL; + Filter *allows; + + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + if (dconf == NULL || dconf->allows == NULL) return NULL; + allows = (Filter*) dconf->allows->elts; + for (i = 0; i < dconf->allows->nelts; i++) { + int first; + Filter *allo = (Filter*) (allows + i); + if (strallows != NULL) strallows = AP_STRCAT (r->pool, strallows, "%7c", NULL); + first = 1; + while (allo) { + if (!first) strallows = AP_STRCAT (r->pool, strallows, "%26", NULL); else first = 0; + if (strallows != NULL) { + strallows = AP_STRCAT (r->pool, strallows, allo->nam, "%3d", allo->val, NULL); + } else { + strallows = AP_STRCAT (r->pool, allo->nam, "%3d", allo->val, NULL); + } + allo = allo->next; + } + } + return strallows; +} + +static Session *readsession (request_rec *r, char *key) { + tequila_global_conf *gconf; + tequila_perdir_conf *dconf; + void *sconf = r->server->module_config; + + AP_PERM_T flags = (AP_READ); + AP_MODE_T mode = (AP_UREAD); + AP_FILE_T fp; + AP_STATUS rc; + unsigned int nbytes, i; + + struct stat buf; + time_t now, mtime; + char buffer [1024]; + char *sessionstring = NULL, *s; + char *requiredfilter; + char *sessdir; + time_t sessmax; + char *fname; + Session *session; + + teqlog (r, 3, "readsession: key = %s", key); + gconf = (tequila_global_conf*) ap_get_module_config (sconf, &tequila_module); + sessdir = gconf->sessdir; + if (sessdir == NULL) sessdir = defaultsessdir; + sessmax = gconf->sessmax; + if (sessmax == 0) sessmax = defaultsessmax; + fname = AP_STRCAT (r->pool, sessdir, "/", key, NULL); + + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + if ((dconf == NULL) || (dconf->allowif == NULL)) { + teqlog (r, 2, "readsession: internal error."); + return NULL; + } + if (stat (fname, &buf) == -1) { /* does not exist */ + teqlog (r, 2, "readsession: could not open session file %s", fname); + return NULL; + } + now = time (NULL); + mtime = buf.st_mtime; + if (now > mtime + sessmax) { + unlink (fname); + teqlog (r, 2, "readsession: session %s expired.", key); + return NULL; + } + if (utime (fname, NULL) == -1) { + teqlog (r, 2, "readsession: cannot reset usage time on session %s.", key); + return NULL; + } + sessionstring = ""; +#ifdef APACHE2 + if ((rc = AP_FILEOPEN (&fp, fname, flags, mode, r->pool)) != AP_SUCCESS) { + teqlog (r, 2, "readsession: cannot open session file : %s", fname); + return NULL; + } + nbytes = 1023; + while (AP_FILEREAD (fp, buffer, &nbytes) == AP_SUCCESS) { + buffer [nbytes] = '\0'; + sessionstring = AP_STRCAT (r->pool, sessionstring, buffer, NULL); + nbytes = 1023; + } + AP_FILECLOSE (fp); +#else + if ((fp = AP_FILEOPEN (r->pool, fname, flags, mode)) < 0) { + teqlog (r, 2, "readsession: cannot open session file : %s", fname); + return NULL; + } + while (nbytes = AP_FILEREAD (fp, buffer, 1023)) { + if (nbytes < 0) break; + buffer [nbytes] = '\0'; + sessionstring = AP_STRCAT (r->pool, sessionstring, buffer, NULL); + } + ap_pclosef (r->pool, fp); +#endif + + session = (Session*) AP_ALLOC (r->pool, sizeof (Session)); + session->attrs = AP_TABLE_MAKE (r->pool, 16); + + s = sessionstring; + while (*s) { + char *attr, *value, u; + char *t = s; + while (*t && (*t != '=')) t++; + if (!*t) { + teqlog (r, 1, "readsession: malformed session file : %s", s); + *s = '\0'; break; + } + *t = '\0'; + attr = AP_STRDUP (r->pool, s); + s = ++t; + while (*t && (*t != '\n') && (*t != '\r')) t++; + u = *t; *t = '\0'; + value = AP_STRDUP (r->pool, s); + *t = u; + while ((*t == '\n') || (*t == '\r')) t++; + s = t; + if (!strcmp (attr, "org")) { session->org = value; continue; } + if (!strcmp (attr, "user")) { session->user = value; continue; } + if (!strcmp (attr, "host")) { session->host = value; continue; } + if (!strcmp (attr, "hash")) { session->hash = value; continue; } + if (!strcmp (attr, "path")) { session->path = value; continue; } + if (!strcmp (attr, "require")) { session->filter = value; continue; } + AP_TABLESET (session->attrs, attr, value); + } + if (!session->org) { teqlog (r, 2, "readsession: no org found"); return NULL; } + if (!session->user) { teqlog (r, 2, "readsession: no user found"); return NULL; } + if (!session->host) { teqlog (r, 2, "readsession: no host found"); return NULL; } + if (!session->hash) { teqlog (r, 2, "readsession: no hash found"); return NULL; } + if (!session->path) { teqlog (r, 2, "readsession: no path found"); return NULL; } + + requiredfilter = getFilter (r); + if (requiredfilter) { + if (session->filter) { + teqlog (r, 3, "readsession: requiredfilter = %s, session->filter = %s.", + requiredfilter, session->filter); + } else { + teqlog (r, 3, "readsession: requiredfilter = %s, session->filter = (null).", + requiredfilter); + } + if (!session->filter || strcmp (requiredfilter, session->filter)) { + teqlog (r, 2, "readsession: requiredfilter doesn't match session->filter."); + return NULL; + } + } + return (session); + } + +static int writesession (request_rec *r, char *sessdir, Session *session) { + char *pathhash, *fname; + int i; + AP_FILE_T fp; + AP_STATUS rc; + AP_SIZE_T nbytes; + AP_MODE_T flags = (AP_WRITE | AP_CREATE); + AP_PERM_T mode = (AP_UREAD | AP_UWRITE); + const AP_ARRAY_HEADER_T *hdrs_attrs; + const AP_TABLE_ENTRY_T *hdrs; + + if (sessdir == NULL) sessdir = defaultsessdir; + pathhash = AP_SPRINTF (r->pool, "%u", hash (session->path)); + fname = AP_STRCAT (r->pool, sessdir, "/", session->key, NULL); + +#ifdef APACHE2 + if ((rc = AP_FILEOPEN (&fp, fname, flags, mode, r->pool)) != AP_SUCCESS) { +#else + if ((fp = AP_FILEOPEN (r->pool, fname, flags, mode)) < 0) { +#endif + teqlog (r, 2, "writesession: could not create session file %s, status = %d", fname, rc); + r->status = 450; + r->status_line = AP_SPRINTF (r->pool, "450 Unable to create session : file = %s", fname); + return 0; + } + writeline (fp, AP_STRCAT (r->pool, "org=", session->org, NULL)); + writeline (fp, AP_STRCAT (r->pool, "user=", session->user, NULL)); + writeline (fp, AP_STRCAT (r->pool, "host=", session->host, NULL)); + writeline (fp, AP_STRCAT (r->pool, "hash=", pathhash, NULL)); + writeline (fp, AP_STRCAT (r->pool, "path=", session->path, NULL)); + writeline (fp, AP_STRCAT (r->pool, "require=", session->filter, NULL)); + + hdrs_attrs = AP_TABLE_ELTS (session->attrs); + hdrs = (const AP_TABLE_ENTRY_T*) hdrs_attrs->elts; + for (i = 0; i < hdrs_attrs->nelts; i++) { + if (!hdrs [i].key) continue; + writeline (fp, AP_STRCAT (r->pool, hdrs [i].key, "=", hdrs[i].val, NULL)); + } +#ifdef APACHE2 + AP_FILECLOSE (fp); +#else + ap_pclosef (r->pool, fp); +#endif + teqlog (r, 2, "writesession: session %s created", session->key); +} + +static void writeline (AP_FILE_T fp, char *string) { + unsigned int nbytes = strlen (string); +#ifdef APACHE2 + AP_FILEWRITE (fp, string, &nbytes); + nbytes = 1; + AP_FILEWRITE (fp, "\n", &nbytes); +#else + write (fp, string, nbytes); + write (fp, "\n", 1); +#endif +} + +static void substsepar (char *string, char oldsepar, char newsepar) { + char *s = string; + while (*s) { + if (*s == oldsepar) *s = newsepar; + s++; + } +} + +static const char *cmd_rewrite (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *path) { + tequila_perdir_conf *dconf = (tequila_perdir_conf*) vconf; + dconf->rewrite = AP_STRDUP (cmd->pool, path); + return NULL; +} + +static const char *cmd_allowif (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *cond) { + tequila_perdir_conf *dconf = (tequila_perdir_conf*) vconf; + Filter *filter, *new; + filter = parsefilter (cmd, cond); + if (filter == NULL) return NULL; + new = AP_ARRAYPUSH (dconf->allowif); + new->nam = filter->nam; + new->val = filter->val; + new->next = filter->next; + return NULL; +} + +static const char *cmd_allownet (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *net) { + tequila_perdir_conf *dconf = (tequila_perdir_conf*) vconf; + const char **netp; + if (!net) return NULL; + netp = (const char **) AP_ARRAYPUSH (dconf->allownet); + *netp = AP_STRDUP (cmd->pool, net); + return NULL; +} + +static const char *cmd_request (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *request) { + char *r; + tequila_perdir_conf *dconf = (tequila_perdir_conf*) vconf; + const char **reqp; + if (!request) return NULL; + r = (char*) request; + while ((*r == ' ') || (*r == ',') || (*r == '\r') || (*r == '\n')) r++; + while (*r) { + char *s = r; + char t; + while (*s && (*s != ' ') && (*s != ',') && (*s != '\r') && (*s != '\n')) s++; + t = *s; *s = '\0'; + reqp = (const char **) AP_ARRAYPUSH (dconf->request); + *reqp = AP_STRDUP (cmd->pool, r); + *s = t; + while ((*s == ' ') || (*s == ',') || (*s == '\r') || (*s == '\n')) s++; + r = s; + } + return NULL; +} + +static const char *cmd_service (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *servname) { + tequila_perdir_conf *dconf = (tequila_perdir_conf*) vconf; + dconf->servname = AP_STRDUP (cmd->pool, servname); + return NULL; +} + +static const char *cmd_allows (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *cond) { + tequila_perdir_conf *dconf = (tequila_perdir_conf*) vconf; + Filter *filter, *new; + filter = parsefilter (cmd, cond); + if (filter == NULL) return NULL; + new = AP_ARRAYPUSH (dconf->allows); + new->nam = filter->nam; + new->val = filter->val; + new->next = filter->next; + return NULL; +} + +static const char *cmd_server (cmd_parms *cmd, void *dummy, cmd_arg *server) { + server_rec *s = cmd->server; + tequila_global_conf *gconf; + gconf = (tequila_global_conf*) ap_get_module_config (s->module_config, &tequila_module); + gconf->teqserver = AP_STRDUP (cmd->pool, server); + return NULL; +} + +static const char *cmd_resource (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *resname) { + tequila_perdir_conf *dconf = (tequila_perdir_conf*) vconf; + dconf->resource = AP_STRDUP (cmd->pool, resname); + return NULL; +} + +static const char *cmd_sslkeyfile (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *keyfile) { + tequila_perdir_conf *dconf = (tequila_perdir_conf*) vconf; + dconf->keyfile = AP_STRDUP (cmd->pool, keyfile); + return NULL; +} + +static const char *cmd_sslcertfile (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *certfile) { + tequila_perdir_conf *dconf = (tequila_perdir_conf*) vconf; + dconf->certfile = AP_STRDUP (cmd->pool, certfile); + return NULL; +} + +static const char *cmd_sessdir (cmd_parms *cmd, void *dummy, cmd_arg *sessdir) { + server_rec *s = cmd->server; + tequila_global_conf *gconf; + gconf = (tequila_global_conf*) ap_get_module_config (s->module_config, &tequila_module); + gconf->sessdir = AP_STRDUP (cmd->pool, sessdir); + return NULL; +} + +static const char *cmd_sessmax (cmd_parms *cmd, void *dummy, cmd_arg *sessmax) { + server_rec *s = cmd->server; + tequila_global_conf *gconf; + gconf = (tequila_global_conf*) ap_get_module_config (s->module_config, &tequila_module); + gconf->sessmax = atoi (sessmax); + return NULL; +} + +static const char *cmd_log (cmd_parms *cmd, void *dummy, cmd_arg *logfile) { + server_rec *s = cmd->server; + tequila_global_conf *gconf; + gconf = (tequila_global_conf*) ap_get_module_config (s->module_config, &tequila_module); + gconf->logfile = AP_STRDUP (cmd->pool, logfile); + return NULL; +} + +static const char *cmd_loglevel (cmd_parms *cmd, void *dummy, cmd_arg *loglevel) { + server_rec *s = cmd->server; + tequila_global_conf *gconf; + gconf = (tequila_global_conf*) ap_get_module_config (s->module_config, &tequila_module); + gconf->loglevel = atoi (loglevel); + return NULL; +} + +static const char *cmd_nossl (cmd_parms *cmd, void *dummy) { + server_rec *s = cmd->server; + tequila_global_conf *gconf; + gconf = (tequila_global_conf*) ap_get_module_config (s->module_config, &tequila_module); + gconf->usessl = 0; + return NULL; +} + +static const char *cmd_usessl (cmd_parms *cmd, void *dummy) { + server_rec *s = cmd->server; + tequila_global_conf *gconf; + gconf = (tequila_global_conf*) ap_get_module_config (s->module_config, &tequila_module); + gconf->usessl = 1; + return NULL; +} + +static const char *cmd_cafile (cmd_parms *cmd, void *dummy, cmd_arg *cafile) { + server_rec *s = cmd->server; + tequila_global_conf *gconf; + gconf = (tequila_global_conf*) ap_get_module_config (s->module_config, &tequila_module); + gconf->cafile = AP_STRDUP (cmd->pool, cafile); + return NULL; +} + +static const char *cmd_checkservername (cmd_parms *cmd, void *dummy) { + server_rec *s = cmd->server; + tequila_global_conf *gconf; + gconf = (tequila_global_conf*) ap_get_module_config (s->module_config, &tequila_module); + gconf->checkservername = 1; + return NULL; +} + +static Filter *parsefilter (cmd_parms *cmd, const char *str) { + char *cstr = AP_STRDUP (cmd->pool, str); + char *s = cstr; + Filter *filter = NULL, *filt; + + while (*s) { + char *n, *v; + n = s; + while (*s && *s != '=') s++; + if (!*s) break; + *s = '\0'; s++; v = s; + while (*s && *s != '&') s++; + if (*s) { *s = '\0'; s++; } + filt = (Filter*) AP_ALLOC (cmd->pool, sizeof (Filter)); + filt->nam = AP_STRDUP (cmd->pool, n); + filt->val = AP_STRDUP (cmd->pool, v); + filt->next = filter; + filter = filt; + } + return filt; +} + +static void printfilter (request_rec *r, Filter *filter) { + if (r == NULL) { + fprintf (stderr, "printfilter: filter = "); + } else { + teqlog (r, 3, "printfilter: filter = "); + } + while (filter) { + if (r == NULL) { + fprintf (stderr, " (%s=%s) ", filter->nam, filter->val); + } else { + teqlog (r, 3, " (%s=%s) ", filter->nam, filter->val); + } + filter = filter->next; + } + if (r == NULL) fprintf (stderr, "\n"); +} + +static int uri_match (const char *uri1, const char *uri2) { + const char *end_uri2 = uri2 + strlen (uri2); + const char *u1 = uri1, *u2 = uri2; + while (u2 < end_uri2) { + if (*u2 == '/') { + if (*u1 != '/') return 0; + while (*u2 == '/') ++u2; + while (*u1 == '/') ++u1; + } else { + if (*u1++ != *u2++) return 0; + } + } + if (u2 [-1] != '/' && *u1 != '\0' && *u1 != '/') return 0; + return u1 - uri1; +} + +static char *skip_match (char *uri, char *prefix) { + char *x = prefix + strlen (prefix); + char *p = prefix, *urip = uri; + while (p < x) { + if (*p == '/') { + while (*p == '/') ++p; + while (*urip == '/') ++urip; + } else { + *urip++; *p++; + } + } + return urip; +} + +static void *create_perdir_config (AP_POOL *p, char *path) { + tequila_perdir_conf *dconf; + dconf = (tequila_perdir_conf*) AP_ALLOC (p, sizeof (tequila_perdir_conf)); + + if (path == NULL) return NULL; + dconf->path = AP_STRDUP (p, path); + dconf->rewrite = NULL; + dconf->resource = NULL; + dconf->keyfile = NULL; + dconf->certfile = NULL; + dconf->servname = NULL; + dconf->session = NULL; + dconf->allowif = AP_ARRAYMAKE (p, 2, sizeof (Filter)); + dconf->allownet = AP_ARRAYMAKE (p, 2, sizeof (char*)); + dconf->allows = AP_ARRAYMAKE (p, 2, sizeof (Filter)); + dconf->request = AP_ARRAYMAKE (p, 2, sizeof (char*)); + return (void*) dconf; +} + +static void *merge_perdir_config (AP_POOL *p, void *base_conf, void *over_conf) { + tequila_perdir_conf *mergeconf = AP_ALLOC (p, sizeof (tequila_perdir_conf)); + tequila_perdir_conf *baseconf = (tequila_perdir_conf *) base_conf; + tequila_perdir_conf *overconf = (tequila_perdir_conf *) over_conf; + mergeconf->path = overconf->path; + mergeconf->rewrite = overconf->rewrite; + mergeconf->resource = overconf->resource; + mergeconf->keyfile = overconf->keyfile; + mergeconf->certfile = overconf->certfile; + mergeconf->servname = overconf->servname; + mergeconf->session = overconf->session; + mergeconf->allowif = overconf->allowif; + mergeconf->allownet = overconf->allownet; + mergeconf->allows = overconf->allows; + mergeconf->request = overconf->request; + return (void *) mergeconf; +} + +static void *create_global_config (AP_POOL *p, server_rec *s) { + tequila_global_conf *gconf = AP_ALLOC (p, sizeof (tequila_global_conf)); + gconf->logfile = NULL; + gconf->teqserver = NULL; + gconf->sessdir = NULL; + gconf->sessmax = 0; + gconf->logfp = 0; + gconf->loglevel = -1; + gconf->usessl = 1; + gconf->cafile = NULL; + gconf->checkservername = 0; + return (void*) gconf; +} + +#define max(a,b) ((a > b) ? a : b) +static void *merge_global_config (AP_POOL *p, void *base_conf, void *over_conf) { + tequila_global_conf *mergeconf = AP_ALLOC (p, sizeof (tequila_global_conf)); + tequila_global_conf *baseconf = (tequila_global_conf *) base_conf; + tequila_global_conf *overconf = (tequila_global_conf *) over_conf; + + mergeconf->logfile = overconf->logfile ? AP_STRDUP (p, overconf->logfile) + : AP_STRDUP (p, baseconf->logfile); + mergeconf->teqserver = overconf->teqserver ? AP_STRDUP (p, overconf->teqserver) + : AP_STRDUP (p, baseconf->teqserver); + mergeconf->sessdir = overconf->sessdir ? AP_STRDUP (p, overconf->sessdir) + : AP_STRDUP (p, baseconf->sessdir); + mergeconf->cafile = overconf->cafile ? AP_STRDUP (p, overconf->cafile) + : AP_STRDUP (p, baseconf->cafile); + mergeconf->sessmax = overconf->sessmax ? overconf->sessmax : baseconf->sessmax; + mergeconf->logfp = 0; + if (overconf->loglevel == -1) { + mergeconf->loglevel = baseconf->loglevel; + } else { + mergeconf->loglevel = overconf->loglevel; + } + mergeconf->usessl = baseconf->usessl && overconf->usessl; + mergeconf->checkservername = baseconf->checkservername && overconf->checkservername; + return (void *) mergeconf; +} + +static void openteqlog (server_rec *s, AP_POOL *p) { + tequila_global_conf *gconf; + char *fname; + piped_log *pl; + AP_STATUS rc; + AP_MODE_T flags = (AP_WRITE | AP_APPEND | AP_CREATE); + AP_PERM_T mode = (AP_UREAD | AP_UWRITE | AP_GREAD | AP_WREAD); + + gconf = ap_get_module_config (s->module_config, &tequila_module); + + if (gconf->logfile == NULL) return; + if (*(gconf->logfile) == '\0') return; + if (gconf->logfp) return; /* virtual log shared w/ main server */ + + if (*gconf->logfile == '|') { + if ((pl = ap_open_piped_log (p, gconf->logfile + 1)) == NULL) { +#ifdef APACHE2 + ap_log_error (APLOG_MARK, APLOG_ERR, 0, s, +#else + ap_log_error (APLOG_MARK, APLOG_ERR, s, +#endif + "mod_tequila: could not open reliable pipe " + "to TequilaLog filter %s", gconf->logfile + 1); + exit (1); + } + gconf->logfp = ap_piped_log_write_fd (pl); + } + else if (*gconf->logfile != '\0') { + fname = ap_server_root_relative (p, gconf->logfile); +#ifdef APACHE2 + if ((rc = AP_FILEOPEN (&gconf->logfp, fname, flags, mode, p)) != AP_SUCCESS) { + gconf->logfp = NULL; + ap_log_error (APLOG_MARK, APLOG_ERR, 0, s, +#else + if ((gconf->logfp = AP_FILEOPEN (p, fname, flags, mode)) < 0) { + gconf->logfp = 0; + ap_log_error (APLOG_MARK, APLOG_ERR, s, +#endif + "mod_tequila: could not open Tequila log file : %s, %s", + fname, gconf->logfile); + exit (1); + } + } + return; +} + +static void teqlog (request_rec *r, int level, const char *text, ...) { + tequila_global_conf *gconf; + conn_rec *conn = r->connection; + char *str1; + char str2 [2048]; + char str3 [2100]; + va_list ap; + int i; + ssize_t wsize; + char *now; + const char *rhost; + + va_start (ap, text); + if (r == NULL) { + if (level > default_loglevel) return; + AP_VSNPRINTF (str2, sizeof (str2), text, ap); + wsize = write (2, str2, strlen (str2)); + return; + } + gconf = ap_get_module_config (r->server->module_config, &tequila_module); + if (!gconf->logfp) return; + if (gconf->logfile == NULL) return; + if (*(gconf->logfile) == '\0') return; + if (gconf->loglevel == -1) gconf->loglevel = default_loglevel; + if (level > gconf->loglevel) return; + +#ifdef APACHE2 + rhost = ap_get_remote_host (conn, r->server->module_config, REMOTE_NOLOOKUP, NULL); +#else + rhost = ap_get_remote_host (conn, r->server->module_config, REMOTE_NOLOOKUP); +#endif + if (rhost == NULL) rhost = "UNKNOWN-HOST"; + str1 = AP_STRCAT (r->pool, rhost, NULL); + AP_VSNPRINTF (str2, sizeof(str2), text, ap); + va_end (ap); + + now = current_logtime (r); + AP_SNPRINTF (str3, sizeof(str3), "%s %s (%d) %s\n", str1, now, level, str2); + +#ifdef APACHE2 + AP_STATUS rv = apr_global_mutex_lock (tequila_log_lock); + if (rv != AP_SUCCESS) { + ap_log_rerror (APLOG_MARK, APLOG_ERR, rv, r, + "apr_global_mutex_lock (tequila_log_lock) failed"); + } + AP_SIZE_T nbytes = strlen (str3); + apr_file_write (gconf->logfp, str3, &nbytes); + rv = apr_global_mutex_unlock (tequila_log_lock); + if (rv != AP_SUCCESS) { + ap_log_rerror (APLOG_MARK, APLOG_ERR, rv, r, + "apr_global_mutex_unlock (tequila_log_lock) failed"); + } +#else + fd_lock (r, gconf->logfp); + write (gconf->logfp, str3, strlen (str3)); + fd_unlock (r, gconf->logfp); +#endif + return; +} + +static char *current_logtime (request_rec *r) { + int timz; + char tstr [80]; + char sign; +#ifdef APACHE2 + apr_time_exp_t xt; + AP_SIZE_T rv; + + ap_explode_recent_localtime (&xt, r->request_time); + timz = xt.tm_gmtoff; + sign = (timz < 0 ? '-' : '+'); + if (timz < 0) timz = -timz; + timz = timz / 60; + apr_strftime (tstr, &rv, 80, "[%d/%b/%Y:%H:%M:%S ", &xt); +#else + struct tm *t; + + t = ap_get_gmtoff (&timz); + sign = (timz < 0 ? '-' : '+'); + if (timz < 0) timz = -timz; + timz = timz / 60; + strftime(tstr, 80, "[%d/%b/%Y:%H:%M:%S ", t); +#endif + AP_SNPRINTF (tstr + strlen (tstr), 80 - strlen (tstr), "%c%.2d%.2d]", + sign, timz / 60, timz % 60); + return AP_STRDUP (r->pool, tstr); +} + +#ifdef APACHE2 + +static int post_config (AP_POOL *p, AP_POOL *plog, AP_POOL *ptemp, server_rec *s) { + AP_STATUS rv; + void *data; + + proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL); + if ((rv = apr_global_mutex_create (&tequila_log_lock, NULL, + APR_LOCK_DEFAULT, p)) != AP_SUCCESS) { + ap_log_error (APLOG_MARK, APLOG_CRIT, rv, s, + "mod_tequila : could not create tequila_log_lock"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + url_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); + +#if APR_USE_SYSVSEM_SERIALIZE + rv = unixd_set_global_mutex_perms (tequila_log_lock); + if (rv != AP_SUCCESS) { + ap_log_error (APLOG_MARK, APLOG_CRIT, rv, s, + "mod_tequila : Could not set permissions on " + "tequila_log_lock; check User and Group directives"); + return HTTP_INTERNAL_SERVER_ERROR; + } +#endif + for (; s; s = s->next) { + openteqlog (s, p); + } + return OK; +} + +#else + +static void fd_lock (request_rec *r, int fd) { + int rc = -1; + +#ifdef USE_FCNTL + lock_it.l_whence = SEEK_SET; /* from current point */ + lock_it.l_start = 0; /* -"- */ + lock_it.l_len = 0; /* until end of file */ + lock_it.l_type = F_WRLCK; /* set exclusive/write lock */ + lock_it.l_pid = 0; /* pid not actually interesting */ + + while (((rc = fcntl(fd, F_SETLKW, &lock_it)) < 0) && (errno == EINTR)) continue; +#endif +#ifdef USE_FLOCK + while (((rc = flock(fd, LOCK_EX)) < 0) && (errno == EINTR)) continue; +#endif +#ifdef USE_LOCKING + /* Lock the first byte, always, assume we want to append + and seek to the end afterwards */ + lseek (fd, 0, SEEK_SET); + rc = _locking (fd, _LK_LOCK, 1); + lseek (fd, 0, SEEK_END); +#endif +#ifdef NETWARE + if ((locking_sem != 0) && (TimedWaitOnLocalSemaphore (locking_sem, 10000) != 0)) + rc = -1; + else + rc = 1; +#endif + if (rc < 0) { + fprintf (stderr, "mod_tequila: failed to lock file descriptor\n"); + exit (1); + } + return; +} + +static void fd_unlock (request_rec *r, int fd) { + int rc; + +#ifdef USE_FCNTL + unlock_it.l_whence = SEEK_SET; /* from current point */ + unlock_it.l_start = 0; /* -"- */ + unlock_it.l_len = 0; /* until end of file */ + unlock_it.l_type = F_UNLCK; /* unlock */ + unlock_it.l_pid = 0; /* pid not actually interesting */ + + rc = fcntl (fd, F_SETLKW, &unlock_it); +#endif +#ifdef USE_FLOCK + rc = flock (fd, LOCK_UN); +#endif +#ifdef USE_LOCKING + lseek (fd, 0, SEEK_SET); + rc = _locking (fd, _LK_UNLCK, 1); + lseek (fd, 0, SEEK_END); +#endif +#ifdef NETWARE + if (locking_sem) SignalLocalSemaphore (locking_sem); + rc = 1; +#endif + if (rc < 0) return; +} + +static void init_module (server_rec *s, AP_POOL *p) { + proxy_available = (ap_find_linked_module ("mod_proxy.c") != NULL); + for (; s; s = s->next) { + openteqlog (s, p); + } +} + +#endif + +static void set_cookie (request_rec *r, char *key) { + tequila_perdir_conf *dconf; + char *cookie; + unsigned pathhash; + char *uripath; + + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + if ((dconf == NULL) || (dconf->path == NULL)) return; + teqlog (r, 4, "set_cookie: uri = %s, dconf->path = %s", r->uri, dconf->path); + pathhash = hash (dconf->path); + cookie = AP_SPRINTF (r->pool, "teqkey:%u=%s; path=%s", pathhash, key, "/"); + teqlog (r, 2, "set_cookie: cookie = %s", cookie); + AP_TABLESET (r->err_headers_out, "Set-Cookie", cookie); + return; +} + +static char *getkey (request_rec *r) { + char *key = (char*)0; + int nbytes; + char *keydeb; + char *args = NULL; + const char *cookie; + + keyfromcookie = 0; + if (r->main) { /* subrequest */ + const char *cookie = AP_TABLEGET (r->main->err_headers_out, "Set-Cookie"); + if (cookie) key = getkeyfromcookie (r, cookie); + if (key) { keyfromcookie = 1; return key; } + } + cookie = AP_TABLEGET (r->headers_in, "Cookie"); + if (cookie) key = getkeyfromcookie (r, cookie); + if (key) { keyfromcookie = 1; return key; } + + if (r->method_number == M_GET) { + args = r->args; + teqlog (r, 4, "getkey: method = GET, args : %s", args); + } else { + char buf [1024]; + if (ap_setup_client_block (r, REQUEST_CHUNKED_ERROR)) goto giveup; + if (!ap_should_client_block (r)) goto giveup; + if (r->remaining > 1024*1024) goto giveup; + ap_hard_timeout ("[Tequila] getPostData", r); + memset (buf, 0, sizeof(buf)); + while (ap_get_client_block (r, buf, sizeof (buf) - 1) > 0) { + ap_reset_timeout (r); + args = AP_STRCAT (r->pool, args, buf); + memset (buf, 0, sizeof (buf)); + } + ap_kill_timeout (r); + } + giveup: + if (args && (keydeb = strstr (args, "key="))) { + char *s = keydeb + 4; + while (*s && (*s != '&')) s++; + if (*s == '&') { + *s = '\0'; + key = AP_STRDUP (r->pool, keydeb + 4); + *s = '&'; + } else { + key = AP_STRDUP (r->pool, keydeb + 4); + } + } + if (key) return key; + teqlog (r, 5, "getkey: No key found"); + return NULL; +} + +static char *getkeyfromcookie (request_rec *r, const char *cookie) { + tequila_perdir_conf *dconf; + char *s, *c, *h, *value, *cook, *pathhash; + + if (!cookie) { + teqlog (r, 99, "getkeyfromcookie:no cookie found"); + return (char*)0; + } + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + if ((dconf == NULL) || (dconf->path == NULL)) return (char*)0; + pathhash = AP_SPRINTF (r->pool, "%u", hash (dconf->path)); + + while (cookie && (value = strstr (cookie, "teqkey:"))) { + char *hashbuf, *hashend, *cookbuf, *cookend; + value += strlen ("teqkey:"); + hashbuf = AP_STRDUP (r->pool, value); + hashend = strchr (hashbuf, '='); + if (hashend) *hashend = '\0'; + cookbuf = hashend + 1; + cookend = strchr (cookbuf, ';'); + if (cookend) *cookend = '\0'; + if (!strcmp (hashbuf, pathhash)) return cookbuf; + cookie = cookend ? cookend + 2 : NULL; + } + return (char*)0; +} + +static unsigned rantable [] = { + 747323161, 163537951, 559183603, 535991927, 55228437, + 902893215, 702443576, 383259134, 470551003, 409377227, + 762435312, 12954216, 630240577, 429738414, 747966939, + 524041125, 52534467, 937350059, 721602759, 771156665, + 488380559, 4064707, 292023389, 958556516, 108153117, + 757587078, 10845887, 667567374, 162364313, 635823247, + 605561322, 700796899, 133054934, 174699191, 527643367, + 878021462, 336077656, 744595963, 110892239, 578143469, + 938117957, 189405922, 310366059, 662568395, 302540930, + 415351040, 152178176, 439074741, 546037787, 729518521, + 565999420, 94247440, 488856080, 620697805, 704311001, + 427860482, 958415115, 443025647, 602304977, 275428698, + 305330063, 989115693, 12720429, 762708049, 53701004, + 123361206, 755590333, 111032844, 241454650, 222111569, + 571301928, 740799682, 978018134, 587842825, 666902019, + 771909829, 392389580, 705378773, 802997113, 757532942, + 18403645, 535199863, 443011788, 770089618, 354719086, + 166649883, 562152615, 207773813, 798374317, 548274697, + 191292350, 243399978, 585649237, 746210183, 620179656, + 517625624, 139966790, 911997570, 132500521, 449348102, + 517478498, 125893297, 767028767, 826990710, 576848567, + 935575809, 987216614, 640287812, 350307482, 164030500, + 343584660, 778009689, 385756146, 96474262, 828151765, + 528953582, 479452185, 387811215, 391990207, 277922478, + 812303128, 763943999, 257744410, 582803839, 275887479, + 25822136, 915890024, 434924082, +}; + +static unsigned hash (char *string) { + unsigned h = 0; + + while (*string) { + unsigned char i = *string; + unsigned char l = ((h >> 24) & 0x000000ff); + h = ((h << 8) | l) ^ rantable [i]; + string++; + } + return (h); +} + +static const char c2x_table [] = "0123456789ABCDEF"; + +static char *escape_url (AP_POOL *p, const char *url) { + char *copy; + const char *u; + char *c; + + if (url == NULL) return NULL; + copy = AP_ALLOC (p, 3 * strlen (url) + 3); + u = (const char *) url; + c = (char *)copy; + + while (*u) { + if ((*u == ' ') || (*u == '?') || (*u == '&') || (*u == '/')) { + *c++ = '%'; + *c++ = c2x_table [*u >> 4]; + *c++ = c2x_table [*u & 0xf]; + } + else { + *c++ = *u; + } + ++u; + } + *c = '\0'; + return copy; +} + +static char *unescape_url (AP_POOL *p, const char *url) { + char *copy; + const char *u; + char *c; + + if (url == NULL) return NULL; + copy = AP_ALLOC (p, strlen (url) + 1); + u = (const char *) url; + c = (char *)copy; + + while (*u) { + if (*u == '%') { + int hexcode; + u++; + if (*u && sscanf (u, "%2x", &hexcode)) { + *c++ = hexcode; u++; u++; + } else { + *c++ = '%'; *c++ = *u++; *c++ = *u++; + } + } + else { + *c++ = *u++; + } + } + *c = '\0'; + return copy; +} + +static int createserverrequest (request_rec *r) { + tequila_perdir_conf *dconf; + tequila_global_conf *gconf; + char *teqserver, *proto, *tequrl, *args, *postline, *hostline, *contlen, *res, *key; + int status; + int port = 80; + int https; + int argslen; + int maxresult = 1024; + char result [1025]; + int len; + + + if (!r || !r->server) return DECLINED; + gconf = (tequila_global_conf*) ap_get_module_config (r->server->module_config, &tequila_module); +#ifdef APACHE2 + https = url_is_https && url_is_https (r->connection); + teqlog (r, 2, "createserverrequest: url_is_https = %d", https); + proto = https ? "https" : "http"; +#else + proto = "http"; +#endif + teqserver = gconf->teqserver; + if (teqserver == NULL) teqserver = defaultteqserver; + tequrl = AP_STRCAT (r->pool, "https://", teqserver, "/cgi-bin/tequila/auth", NULL); + + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + if ((dconf == NULL) || (dconf->allowif == NULL)) return HTTP_FORBIDDEN; + + if (dconf->resource) { + args = AP_STRCAT (r->pool, "resource=", dconf->resource, "\n", NULL); + } else { + char *urlaccess, *filter, *allows, *servname, *request; + int port = ap_get_server_port (r); + + if (port != 80) { + char sport [8]; + sprintf (sport, "%d", port); + urlaccess = AP_STRCAT (r->pool, proto, "://", r->hostname, ":", sport, r->uri, NULL); + } else { + urlaccess = AP_STRCAT (r->pool, proto, "://", r->hostname, r->uri, NULL); + } + if (r->args) urlaccess = AP_STRCAT (r->pool, urlaccess, "?", r->args, NULL); + + filter = getFilter (r); + if (filter == NULL) return DECLINED; + + args = AP_STRCAT (r->pool, + "urlaccess=", urlaccess, "\n", + "path=", dconf->path, "\n", + "require=", filter, "\n", + NULL); + + allows = getAllows (r); + if (allows) { + teqlog (r, 9, "createserverrequest: allows = %s.", allows); + args = AP_STRCAT (r->pool, args, "allows=", allows, "\n", NULL); + } + servname = dconf->servname; + if (!servname) { + servname = AP_STRCAT (r->pool, "Document ", r->server->server_hostname, + ":", dconf->path, NULL); + } + args = AP_STRCAT (r->pool, args, "service=", servname, "\n", NULL); + + request = getRequest (r); + if (request) { + teqlog (r, 9, "createserverrequest: request = %s.", request); + args = AP_STRCAT (r->pool, args, "request=", request, "\n", NULL); + } + } + + teqlog (r, 9, "createserverrequest: args = %s", args); + argslen = strlen (args); + postline = "POST /cgi-bin/tequila/createrequest HTTP/1.0\r\n"; + hostline = AP_STRCAT (r->pool, "Host: ", teqserver, "\r\n", NULL); + contlen = AP_SPRINTF (r->pool, "Content-length: %d\r\n", argslen); + +#ifdef USESSL + if (!gconf->usessl) +#endif + { + ssize_t wsize; + int sock, tot; + struct sockaddr_in sockaddr; + struct hostent *hostentry; + + hostentry = gethostbyname (teqserver); + if (!hostentry) { + teqlog (r, 2, "createserverrequest: cannot get address of %s.", teqserver); + close (sock); + return HTTP_FORBIDDEN; + } + bzero ((char*) &sockaddr, sizeof (sockaddr)); + bcopy (hostentry->h_addr, (char*) &sockaddr.sin_addr, hostentry->h_length); + sockaddr.sin_family = hostentry->h_addrtype; + sockaddr.sin_port = htons (port); + sock = socket (AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + teqlog (r, 2, "createserverrequest: cannot create socket."); + return HTTP_FORBIDDEN; + } + status = connect (sock, (struct sockaddr*) &sockaddr, sizeof (sockaddr)); + if (status < 0) { + teqlog (r, 2, "createserverrequest: cannot connect to socket."); + return HTTP_FORBIDDEN; + } + wsize = write (sock, postline, strlen (postline)); + wsize = write (sock, hostline, strlen (hostline)); + wsize = write (sock, contlen, strlen (contlen)); + wsize = write (sock, "\r\n", 2); + wsize = write (sock, args, argslen); + wsize = write (sock, "\r\n", 2); + tot = 0; + while (len = read (sock, result + tot, maxresult - tot)) { + if (len < 0) break; + tot += len; + if (tot > maxresult) { + tot = maxresult; + break; + } + result [tot] = '\0'; + } + close (sock); + } +#ifdef USESSL + else { + SSLenv *sslenv = opensslsocket (r, teqserver, 443); + if (!sslenv) { + teqlog (r, 2, "createserverrequest: cannot open SSL connection to %s.", teqserver); + return HTTP_FORBIDDEN; + } + BIO_write (sslenv->bio, postline, strlen (postline)); + BIO_write (sslenv->bio, hostline, strlen (hostline)); + BIO_write (sslenv->bio, contlen, strlen (contlen)); + BIO_write (sslenv->bio, "\r\n", 2); + BIO_write (sslenv->bio, args, argslen); + BIO_write (sslenv->bio, "\r\n", 2); + int tot = 0; + while (len = BIO_read (sslenv->bio, result + tot, maxresult - tot)) { + if (len < 0) break; + tot += len; + if (tot > maxresult) { + tot = maxresult; + break; + } + } + result [tot] = '\0'; + closesslsocket (sslenv); + } +#endif + teqlog (r, 99, "createserverrequest: result = %s", result); + res = result; + while (*res && (*res != ' ')) res++; + if (!sscanf (res, "%d", &status)) { + teqlog (r, 2, "createserverrequest: invalid status line"); + return HTTP_FORBIDDEN; + } + if (status != 200) { + teqlog (r, 2, "createserverrequest: invalid status : %d", status); + return HTTP_FORBIDDEN; + } + key = NULL; + while (*res) { + while (*res && *res != '\n' && *res != '\r') res++; + if (!*res) break; + while (*res == '\n' || *res == '\r') res++; + if (strncmp (res, "key=", 4) == 0) { + char *end; + res += 4; + end = res; + while (*end && (*end != '\n')) end++; + *end = '\0'; + key = AP_STRDUP (r->pool, res); + break; + } + } + if (!key) { + teqlog (r, 2, "createserverrequest: Bad response from local Tequila server"); + return HTTP_FORBIDDEN; + } + r->args = AP_STRCAT (r->pool, "requestkey=", key, NULL); + r->filename = AP_STRCAT (r->pool, tequrl, "?", r->args, NULL); + teqlog (r, 3, "createserverrequest: r->filename = %s", r->filename); + AP_TABLESET (r->headers_out, "Location", r->filename); + AP_TABLESET (r->headers_out, "Pragma", "no-cache"); + return HTTP_MOVED_PERMANENTLY; +} + +static Session *fetchattributes (request_rec *r, const char *requestkey) { + tequila_perdir_conf *dconf; + tequila_global_conf *gconf; + char *hostline, *teqserver, *res; + int status; + int port = 80; + Session *session; + int maxresult = 8192; + char result [8193]; + int len; + char *org, *user, *host, *path, *filter = ""; + + teqlog (r, 2, "fetchattributes: requestkey = %s", requestkey); + if (!r || !r->server) { + error (r, "Unable to contact Tequila server"); + return NULL; + } + gconf = (tequila_global_conf*) ap_get_module_config (r->server->module_config, &tequila_module); + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + + teqserver = gconf->teqserver; + if (teqserver == NULL) teqserver = defaultteqserver; + + hostline = AP_STRCAT (r->pool, "Host: ", teqserver, "\r\n", NULL); + +#ifdef USESSL + if (!gconf->usessl && (!dconf || !dconf->resource)) +#endif + { + ssize_t wsize; + int sock, tot; + struct sockaddr_in sockaddr; + struct hostent *hostentry; + + char *getline = AP_STRCAT (r->pool, + "GET /cgi-bin/tequila/fetchattributes?key=", + requestkey, + " HTTP/1.0\r\n", NULL); + hostentry = gethostbyname (teqserver); + if (hostentry == NULL) { + close (sock); + teqlog (r, 2, "fetchattributes: cannot get address of %s.", teqserver); + error (r, "Unable to get address of Tequila server"); + return NULL; + } + bzero ((char*) &sockaddr, sizeof (sockaddr)); + bcopy (hostentry->h_addr, (char*) &sockaddr.sin_addr, hostentry->h_length); + sockaddr.sin_family = hostentry->h_addrtype; + sockaddr.sin_port = htons (port); + sock = socket (AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + teqlog (r, 2, "fetchattributes: cannot create socket."); + error (r, "Unable to contact Tequila server"); + return NULL; + } + status = connect (sock, (struct sockaddr*) &sockaddr, sizeof (sockaddr)); + if (status < 0) { + teqlog (r, 2, "fetchattributes: cannot connect to socket."); + error (r, "Unable to connect to Tequila server"); + return NULL; + } + wsize = write (sock, getline, strlen (getline)); + wsize = write (sock, hostline, strlen (hostline)); + wsize = write (sock, "\r\n", 2); + tot = 0; + while (len = read (sock, result + tot, maxresult - tot)) { + if (len < 0) break; + tot += len; + if (tot > maxresult) { + tot = maxresult; + break; + } + result [tot] = '\0'; + } + close (sock); + } +#ifdef USESSL + else { + char *scriptname = (dconf && dconf->resource) + ? "/cgi-bin/tequilac" + : "/cgi-bin/tequila"; + char *getline = AP_STRCAT (r->pool, + "GET ", scriptname, "/fetchattributes?key=", + requestkey, + " HTTP/1.0\r\n", NULL); + SSLenv *sslenv = opensslsocket (r, teqserver, 443); + if (!sslenv) { + teqlog (r, 2, "fetchattributes: cannot open SSL connection to %s.", teqserver); + error (r, "Unable to open SSL connection to Tequila server"); + return NULL; + } + BIO_write (sslenv->bio, getline, strlen (getline)); + BIO_write (sslenv->bio, hostline, strlen (hostline)); + BIO_write (sslenv->bio, "\r\n", 2); + int tot = 0; + while (len = BIO_read (sslenv->bio, result + tot, maxresult - tot)) { + if (len < 0) break; + tot += len; + if (tot > maxresult) { + tot = maxresult; + break; + } + } + result [tot] = '\0'; + closesslsocket (sslenv); + } +#endif + res = result; + while (*res && (*res != ' ')) res++; + if (!sscanf (res, "%d", &status)) { + char *err; + teqlog (r, 2, "fetchattributes: invalid status line"); + err = AP_STRCAT (r->pool, "Tequila server returns invalid line : ", result, NULL); + error (r, err); + return NULL; + } + if (status != 200) { + teqlog (r, 2, "fetchattributes: invalid status : %d", status); + while (*res && (*res != '\n')) res++; + *res = '\0'; + error (r, result); + return NULL; + } + + while (*res) { + int sep = 0; + while (*res && *res != '\n' && *res != '\r') res++; + while (*res == '\n' || *res == '\r') { + if (*res == '\n') sep++; + res++; + } + if (sep > 1) break; + } + session = (Session*) AP_ALLOC (r->pool, sizeof (Session)); + session->attrs = AP_TABLE_MAKE (r->pool, 16); + while (*res) { + char *name = res; + while (*res && *res != '=' && *res != '\n' && *res != '\r') res++; + if (*res == '=') { + char *value; + *res = '\0'; res++; + value = res; + while (*res && *res != '\n' && *res != '\r') res++; + if (*res) *res++ = '\0'; + if (strcmp (name, "org") == 0) { org = value; } + else if (strcmp (name , "user") == 0) { user = value; } + else if (strcmp (name, "host") == 0) { host = value; } + else if (strcmp (name, "path") == 0) { path = value; } + else if (strcmp (name, "require") == 0) { filter = value; } + else { AP_TABLESET (session->attrs, name, value); } + } + while (*res == '\n' || *res == '\r') res++; + } + if (!org || !user || !host || !path) { + teqlog (r, 2, "fetchattributes: invalid response from Tequila server."); + error (r, "Invalid response from Tequila server."); + return NULL; + } + substsepar (filter, ':', '&'); + session->key = AP_STRDUP (r->pool, requestkey); + session->org = AP_STRDUP (r->pool, org); + session->user = AP_STRDUP (r->pool, user); + session->host = AP_STRDUP (r->pool, host); + session->path = AP_STRDUP (r->pool, path); + session->filter = filter ? AP_STRDUP (r->pool, filter) : NULL; + writesession (r, gconf->sessdir, session); + return session; +} + +static int setenvvars (request_rec *r) { + AP_TABLE_T *env = r->subprocess_env; + const AP_ARRAY_HEADER_T *hdrs_attrs; + const AP_TABLE_ENTRY_T *hdrs; + tequila_perdir_conf *dconf; + Session *session; + int i; + + dconf = (tequila_perdir_conf*) ap_get_module_config (r->per_dir_config, &tequila_module); + if (!dconf || !dconf->session) return OK; + + session = dconf->session; + AP_TABLESET (env, "REMOTE_USER", session->user); + AP_TABLESET (env, "HTTP_TEQUILA_KEY", session->key); + AP_TABLESET (env, "HTTP_TEQUILA_ORG", session->org); + AP_TABLESET (env, "HTTP_TEQUILA_USER", session->user); + AP_TABLESET (env, "HTTP_TEQUILA_HOST", session->host); + AP_TABLESET (env, "HTTP_TEQUILA_PATH", session->path); + AP_TABLESET (env, "HTTP_TEQUILA_FILTER", session->filter); + + hdrs_attrs = AP_TABLE_ELTS (session->attrs); + hdrs = (const AP_TABLE_ENTRY_T*) hdrs_attrs->elts; + + for (i = 0; i < hdrs_attrs->nelts; i++) { + char *key, *k, *attrname; + if (!hdrs [i].key) continue; + key = AP_STRDUP (r->pool, hdrs [i].key); + k = key; + while (*k) { *k = AP_TOUPPER (*k); k++; } + attrname = AP_STRCAT (r->pool, "HTTP_TEQUILA_", key, NULL); + AP_TABLESET (env, attrname, hdrs[i].val); + } + return OK; +} + +static void error (request_rec *r, char *msg) { +#ifdef APACHE2 + ap_set_content_type (r, "text/html; charset=iso-8859-1"); +#else + r->content_type = "text/html; charset=iso-8859-1"; +#endif + ap_rvputs (r, + DOCTYPE_HTML_2_0 + "\n", "Tequila error", + "\n\n

", "Tequila error", "

\n", + NULL); + ap_rvputs (r, msg, NULL); + ap_rvputs (r, ap_psignature ("
\n", r), NULL); + ap_rvputs (r, "\n", NULL); + ap_finalize_request_protocol (r); +} + + +#ifdef USESSL + +static SSLenv *opensslsocket (request_rec *r, char *host, int port) { + char hostport [128]; + sprintf (hostport, "%s:%d", host, port); + teqlog (r, 1, "opensslsocket:Connecting to %s.", hostport); + SSL_CTX *ctx = SSL_CTX_new (SSLv23_client_method ()); + if(ctx == NULL) { + teqlog (r, 1, "Unable to allocate SSL context."); + return NULL; + } + + void *sconf = r->server->module_config; + tequila_global_conf *gconf = (tequila_global_conf*) + ap_get_module_config (sconf, &tequila_module); + tequila_perdir_conf *dconf = (tequila_perdir_conf*) + ap_get_module_config (r->per_dir_config, &tequila_module); + + if (gconf->cafile) { + int loadverif = SSL_CTX_load_verify_locations (ctx, gconf->cafile, NULL); + if(!loadverif) { + teqlog (r, 1, "opensslsocket:Error loading trust store."); + ERR_print_errors_fp (stderr); + SSL_CTX_free (ctx); + return NULL; + } + } + SSL *ssl; + BIO *bio = BIO_new_ssl_connect (ctx); + BIO_get_ssl (bio, &ssl); + SSL_set_mode (ssl, SSL_MODE_AUTO_RETRY); + + if (dconf->resource && dconf->keyfile && dconf->certfile) { + teqlog (r, 2, "opensslsocket:resource = %s, keyfile = %s, certfile = %s.", + dconf->resource, dconf->keyfile, dconf->certfile); + SSL_use_certificate_file (ssl, dconf->certfile, SSL_FILETYPE_PEM); + SSL_use_PrivateKey_file (ssl, dconf->keyfile, SSL_FILETYPE_PEM); + } + BIO_set_conn_hostname (bio, hostport); + if (BIO_do_connect (bio) <= 0) { + teqlog (r, 1, "opensslsocket:Error attempting to connect."); + ERR_print_errors_fp (stderr); + BIO_free_all (bio); + SSL_CTX_free (ctx); + return NULL; + } + + if (gconf->checkservername) { + X509 *cert = SSL_get_peer_certificate (ssl); + if (!checkcertificate (r, cert, host)) { + teqlog (r, 1, "opensslsocket:Bad certificate."); + BIO_free_all (bio); + SSL_CTX_free (ctx); + return NULL; + } + } + if (gconf->cafile) { + teqlog (r, 1, "opensslsocket:Checking server certificate chain."); + int verif = SSL_get_verify_result (ssl); + if (verif != X509_V_OK) { + teqlog (r, 1, "opensslsocket:Certificate verification error: %d.", + SSL_get_verify_result(ssl)); + BIO_free_all (bio); + SSL_CTX_free (ctx); + return NULL; + } + } + SSLenv *sslenv = (SSLenv*) malloc (sizeof (SSLenv*)); + sslenv->ctx = ctx; + sslenv->bio = bio; + return sslenv; +} + +static void closesslsocket (SSLenv *sslenv) { + BIO_free_all (sslenv->bio); + SSL_CTX_free (sslenv->ctx); + free (sslenv); +} + +static int checkcertificate (request_rec *r, X509 *cert, char *host) { + STACK_OF(GENERAL_NAME) *gens; + GENERAL_NAME *name; + + X509_NAME *subject = X509_get_subject_name (cert); + char buf [513]; + X509_NAME_oneline (subject, buf, 512); + teqlog (r, 3, "checkcertificate:host = %s, subject = %s.", host, buf); + + char *c = buf + 1; + while (*c) { + if (!strncasecmp (c, "cn=", 3)) { + c += 3; + if (*c == '(') { + c++; + char *subjects [32]; + int is = 0; + char *t = c; + while (*t && (*t != ')')) { + char *u = t; + while ((*u != '|') && (*u != ')')) u++; + char v = *u; + *u = '\0'; + subjects [is++] = t; + t = u + 1; + if (v != '|') break; + } + if (!*t) break; + char *u = t; + while (*u && (*u != '/')) u++; + *u = '\0'; + char *domain = t; + int ns = is; + char tmp [128]; + for (is = 0; is < ns; is++) { + strcpy (tmp, subjects [is]); + strcat (tmp, domain); + if (!strcmp (tmp, host)) return 1; + } + c = u + 1; + } else + if (*c == '*') { + char *s = c + 1; + char *t = s; + while (*t && (*t != '/')) t++; + *t = '\0'; + char *h = host; + int n = strlen (h) - strlen (s); + if (n >= 0) h += n; + if (!strcasecmp (h, s)) return 1; + c = t + 1; + } else { + char *s = c; + while (*s && (*s != '/')) s++; + *s = '\0'; + if (!strcmp (c, host)) return 1; + c = s + 1; + } + } else { + while (*c && (*c != '/')) c++; + if (*c) c++; + } + } + + gens = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL); + if (!gens) return 0; + + teqlog (r, 99, "Checking alternate names.."); + int rg = sk_GENERAL_NAME_num(gens); + int i; + for (i = 0; i < rg; ++i) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value (gens, i); + if (gn->type == GEN_DNS) { + char *c = (char*) gn->d.ia5->data; + char *h = host; + if (*c == '*') { + ++c; + int n = strlen (h) - strlen (c); + if (n >= 0) h += n; + } + if (!strcasecmp (c, h)) return 1; + } + } + sk_GENERAL_NAME_free (gens); + return 0; +} + +#endif /* USESSL */ diff --git a/mod_tequila.h b/mod_tequila.h new file mode 100644 index 0000000..6caaacc --- /dev/null +++ b/mod_tequila.h @@ -0,0 +1,240 @@ +#define USE_FCNTL 1 + +#ifdef APACHE2 +# if APR_HAVE_SYS_SOCKET_H +# include +# include +# include +# endif +# if APR_HAVE_UNISTD_H +# include +# endif +# if APR_HAVE_SYS_TYPES_H +# include +# endif +#endif + +#if defined(NETWARE) +# define READ_FLAG (S_IREAD) +# define RW_FLAG (S_IREAD|S_IWRITE) +#elif defined(WIN32) +# define READ_FLAG (_S_IREAD) +# define RW_FLAG (_S_IREAD|_S_IWRITE) +#else +# define READ_FLAG (S_IRUSR) +# define RW_FLAG (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) +#endif + +#ifdef APACHE2 +# define AP_POOL apr_pool_t +# define AP_STRDUP apr_pstrdup +# define AP_STRCAT apr_pstrcat +# define AP_ALLOC apr_pcalloc +# define AP_SPRINTF apr_psprintf +# define AP_SNPRINTF apr_snprintf +# define AP_VSNPRINTF apr_vsnprintf + +# define AP_TABLE_T apr_table_t +# define AP_TABLEGET apr_table_get +# define AP_TABLESET apr_table_set +# define AP_TABLE_ELTS apr_table_elts +# define AP_TABLE_ENTRY_T apr_table_entry_t +# define AP_TABLE_MAKE apr_table_make + +# define AP_ARRAY_HEADER_T apr_array_header_t +# define AP_TOUPPER apr_toupper + +# define AP_FILEOPEN apr_file_open +# define AP_FILEREAD apr_file_read +# define AP_FILEWRITE apr_file_write +# define AP_FILECLOSE apr_file_close + +# define AP_PERM_T apr_fileperms_t +# define AP_MODE_T int +# define AP_SIZE_T apr_size_t +# define AP_FILE_T apr_file_t* +# define AP_STATUS apr_status_t +# define AP_SUCCESS APR_SUCCESS +# define AP_ARRAYPUSH apr_array_push +# define AP_ARRAYMAKE apr_array_make + +# define AP_READ APR_READ +# define AP_WRITE APR_WRITE +# define AP_APPEND APR_APPEND +# define AP_CREATE APR_CREATE + +# define AP_UREAD APR_UREAD +# define AP_UWRITE APR_UWRITE +# define AP_GREAD APR_GREAD +# define AP_WREAD APR_WREAD + +# define PROXYREQ (PROXYREQ_REVERSE) + +# define cmd_conf void +# define cmd_arg const char + +# define ap_send_http_header(r) +# define ap_hard_timeout(str,r) +# define ap_reset_timeout(r) +# define ap_kill_timeout(r) + +#else /* APACHE2 */ + +# ifdef USE_FCNTL + static struct flock lock_it; + static struct flock unlock_it; +#endif + +# define AP_POOL pool +# define AP_STRDUP ap_pstrdup +# define AP_STRCAT ap_pstrcat +# define AP_ALLOC ap_palloc +# define AP_SPRINTF ap_psprintf +# define AP_SNPRINTF ap_snprintf +# define AP_VSNPRINTF ap_vsnprintf + +# define AP_TABLE_T table +# define AP_TABLEGET ap_table_get +# define AP_TABLESET ap_table_set +# define AP_TABLE_ELTS ap_table_elts +# define AP_TABLE_ENTRY_T table_entry +# define AP_TABLE_MAKE ap_make_table + +# define AP_ARRAY_HEADER_T array_header +# define AP_TOUPPER ap_toupper + +# define AP_FILEOPEN ap_popenf +# define AP_FILEREAD read +# define AP_FILEWRITE write +# define AP_FILECLOSE(fp) ap_pclosef(r->pool,fp) + +# define AP_PERM_T int +# define AP_MODE_T mode_t +# define AP_SIZE_T size_t +# define AP_FILE_T int +# define AP_STATUS int +# define AP_SUCCESS 0 +# define AP_ARRAYPUSH ap_push_array +# define AP_ARRAYMAKE ap_make_array + +# define AP_READ READ_FLAG +# define AP_WRITE O_WRONLY +# define AP_APPEND O_APPEND +# define AP_CREATE O_CREAT + +# define AP_UREAD READ_FLAG +# define AP_UWRITE RW_FLAG +# define AP_GREAD RW_FLAG +# define AP_WREAD RW_FLAG + +# define PROXYREQ PROXY_PASS + +# define cmd_conf tequila_perdir_conf +# define cmd_arg char + + static void init_module (server_rec *s, AP_POOL *p); + +#endif /* APACHE2 */ + +typedef struct { + char *key; + char *org; + char *user; + char *host; + char *path; + char *hash; + char *filter; + AP_TABLE_T *attrs; +} Session; + +typedef struct { + char *logfile; + char *teqserver; + char *sessdir; + time_t sessmax; + int loglevel; + int usessl; + char *cafile; + int checkservername; + AP_FILE_T logfp; +} tequila_global_conf; + +typedef struct { + char *path; + char *rewrite; + char *resource; + char *keyfile; + char *certfile; + char *servname; + Session *session; + AP_ARRAY_HEADER_T *allowif; + AP_ARRAY_HEADER_T *allownet; + AP_ARRAY_HEADER_T *allows; + AP_ARRAY_HEADER_T *request; +} tequila_perdir_conf; + +typedef struct filter { + char *nam; + char *val; + int status; + struct filter *next; +} Filter; + +static time_t default_sessmax = (2 * 3600); +static int default_loglevel = 2; +static int proxy_available; + +static const char *cmd_rewrite (cmd_parms *cmd, cmd_conf *dconf, cmd_arg *path); +static const char *cmd_allowif (cmd_parms *cmd, cmd_conf *dconf, cmd_arg *cond); +static const char *cmd_request (cmd_parms *cmd, cmd_conf *dconf, cmd_arg *req); +static const char *cmd_allownet (cmd_parms *cmd, cmd_conf *dconf, cmd_arg *net); +static const char *cmd_allows (cmd_parms *cmd, cmd_conf *dconf, cmd_arg *cond); +static const char *cmd_service (cmd_parms *cmd, cmd_conf *dconf, cmd_arg *serv); +static const char *cmd_resource (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *resname); +static const char *cmd_sslkeyfile (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *keyfile); +static const char *cmd_sslcertfile (cmd_parms *cmd, cmd_conf *vconf, cmd_arg *certfile); + +static const char *cmd_server (cmd_parms *cmd, void *dummy, cmd_arg *server); +static const char *cmd_sessdir (cmd_parms *cmd, void *dummy, cmd_arg *sessdir); +static const char *cmd_sessmax (cmd_parms *cmd, void *dummy, cmd_arg *sessmax); +static const char *cmd_log (cmd_parms *cmd, void *dummy, cmd_arg *logfile); +static const char *cmd_loglevel (cmd_parms *cmd, void *dummy, cmd_arg *loglevel); +static const char *cmd_nossl (cmd_parms *cmd, void *dummy); +static const char *cmd_usessl (cmd_parms *cmd, void *dummy); +static const char *cmd_cafile (cmd_parms *cmd, void *dummy, cmd_arg *cafile); +static const char *cmd_checkservername (cmd_parms *cmd, void *dummy); + +static int translate (request_rec *r); +static int check_access (request_rec *r); +static int fixup (request_rec *r); +static char *getkey (request_rec *r); +static int hook (request_rec *r); +static int tequila_checkuser (request_rec *r); +static int uri_match (const char *uri1, const char *uri2); +static char *skip_match (char *uri, char *prefix); +static int createsession (request_rec *r); +static Session *readsession (request_rec *r, char *key); +static int writesession (request_rec *r, char *sessdir, Session *session); +static char *getsession (request_rec *r); +static char *read_key (request_rec *r); +static char *getFilter (request_rec *r); +static void printfilter (request_rec *r, Filter *filter); +static Filter *parsefilter (cmd_parms *cmd, const char *str); +static char *getRequest (request_rec *r); +static char *getAllows (request_rec *r); +static void *create_perdir_config (AP_POOL *p, char *path); +static void *merge_perdir_config (AP_POOL *p, void *d1_conf, void *d2_conf); +static void *create_global_config (AP_POOL *p, server_rec *s); +static void *merge_global_config (AP_POOL *p, void *serv1_conf, void *serv2_conf); +static void openteqlog (server_rec *s, AP_POOL *p); +static void teqlog (request_rec *r, int level, const char *text, ...); +static char *current_logtime (request_rec *r); +static void fd_lock (request_rec *r, int fd); +static void fd_unlock(request_rec *r, int fd); +static void set_cookie (request_rec *r, char *key); +static char *getkeyfromcookie (request_rec *r, const char *cookie); +static unsigned hash (char *string); +static int check_net (request_rec *r); +static char *escape_url (AP_POOL *p, const char *path); +static void register_hooks (AP_POOL *p); +static int post_config (AP_POOL *p, AP_POOL *plog, AP_POOL *ptemp, server_rec *s);