/*============================================================================= * Copyright (c) 1998-1999 The Apache Group. All rights reserved. * * 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. *============================================================================= * * NAME * EAccess - Extended Access control module * * AUTHOR * Patrick Asty * * VERSION * $Revision: 2.3.2.9 $ * * DOCUMENTATION * See doc/index.html... * * CHANGELOG * $Log: mod_eaccess.c,v $ * Revision 2.3.2.9 2000/02/15 07:36:00 pasty * No more unescape '+' => ' '. * * Revision 2.3.2.8 2000/02/15 06:08:51 pasty * eaccess_unescape: enfin, '\r' suivi de '\n' devient "\n", * cela facilite largement le traitement des body. * * Revision 2.3.2.7 2000/02/07 13:42:34 pasty * on ne re-compile l'ER que si on ne l'a pas conservée... * * Revision 2.3.2.6 2000/02/07 13:21:39 pasty * EAccessOptim => conf->optim = atoi (a1); ... * * Revision 2.3.2.5 2000/02/06 10:51:26 pasty * EAccessOptim 0|1: code, tests, ... * plus de ap_destroy_request_body mais à la place read_length := 0 pour * tromper ap_die (qui appelle ap_destroy_request_body)... * * Revision 2.3.2.4 2000/02/01 14:08:47 pasty * ident EAccess/big-body * * Revision 2.3.2.3 2000/02/01 13:09:36 pasty * destroy body if denied * * Revision 2.3.2.2 2000/01/25 14:35:45 pasty * no more CR-LF problem... (in fact just ignore CR-LF problem...) * EACCESS_BODY_HACK (see doc) * * Revision 2.3.1.10 2000/01/21 10:33:51 pasty * grrrrr: \r & \n on the end of body... * * Revision 2.3.1.9 2000/01/21 07:02:57 pasty * Check on URL buffer overflow * * Revision 2.3.1.8 2000/01/20 07:21:45 pasty * Add deny=nnn facility * (change auth_ttl => action_arg, auth_opt => action_opt) * * Revision 2.3.1.7 2000/01/19 22:26:19 pasty * Error message or regcmp error. * No more use ap_reg* because compiled RE are persistent for the process life * (so no need to free, so no need to use pool...). * * Revision 2.3.1.6 2000/01/18 06:31:04 pasty * Use unescape (method + escape (uri) + "?" + args + "|" + body); * Using unparsed_uri was in fact a bad idea... * * Revision 2.3.1.5 2000/01/09 08:21:09 pasty * Add |log facility * * Revision 2.3.1.4 1999/12/15 09:31:19 pasty * never dbm_open with O_APPEND; use O_WRONLY. * * Revision 2.3.1.3 1999/11/19 10:05:07 pasty * File locking and file locking and file locking and ... * * Revision 2.3.1.2 1999/11/13 06:14:18 pasty * improvements in file locking * * Revision 2.3.1.1 1999/11/08 08:38:18 pasty * eaccess_log lines was truncated * * Revision 2.3 1999/10/28 13:14:30 pasty * 2.2.1.12 -> 2.3 * * Revision 2.2.1.12 1999/10/28 04:58:01 pasty * unused code (Ghislaine Labouret) * * Revision 2.2.1.11 1999/10/28 04:48:30 pasty * file locking on auth cache * checks for returned values for dbm_... (Ghislaine Labouret) * * Revision 2.2.1.10 1999/10/27 13:50:37 pasty * return (int) instead of return (time_t) * * Revision 2.2.1.9 1999/10/27 12:12:08 pasty * unescape (URI?QUERY_STRING|BODY) to use simple RE * * Revision 2.2.1.8 1999/10/26 12:57:17 pasty * difftime for TTL (Ghislaine Labouret) * * Revision 2.2.1.7 1999/10/26 12:37:30 pasty * better checks to get basic auth. (Ghislaine Labouret) * * Revision 2.2.1.6 1999/10/26 11:55:26 pasty * no more warning with -Wall * * Revision 2.2.1.5 1999/10/26 09:13:22 pasty * "EAccessRule warning": loop must continue with "continue" (not break) * * Revision 2.2.1.4 1999/10/26 08:51:03 pasty * check TTL value for auth/... (Ghislaine Labouret) * * Revision 2.2.1.3 1999/10/26 04:20:57 pasty * "EAccessRule warning" correction: loop must continue (Ghislaine Labouret) * * Revision 2.2.1.2 1999/08/30 12:34:19 pasty * "EAccessEnable off" correction (Ghislaine Labouret) * * Revision 2.2.1.1 1999/08/30 12:14:24 pasty * ifndef then define TRUE and FALSE (Ghislaine Labouret) * * Revision 2.2 1999/06/26 04:31:08 pasty * auth/securid OK * * Revision 2.1 1999/06/24 06:20:37 pasty * 2.0.1.4 -> 2.1 * * Revision 2.0.1.4 1999/06/17 09:42:36 pasty * Fixed: no cache creation if EAccessEnable = off * * Revision 2.0.1.3 1999/06/17 09:37:08 pasty * Fixed glibc 2.1 ndbm.h inclusion problems. * * Revision 2.0.1.2 1999/06/15 19:40:53 pasty * + clean * * Revision 2.0.1.1 1999/06/09 21:46:24 pasty * doc * * Revision 2.0.1.0 1999/06/09 14:14:42 pasty * auth/basic done. * * Revision 2.0 1999/06/07 19:31:22 pasty * add permit/deny/warning on EAccessRule, auth not implemented * * Revision 1.6 1999/06/05 06:30:08 pasty * Don't create logfile if loglevel == 0. * Correction: all methods may have arguments and body. * * Revision 1.5 1999/06/02 05:19:07 pasty * Add logging * * Revision 1.4 1999/05/30 14:05:17 pasty * add blah blah * * Revision 1.3 1999/05/29 07:45:44 pasty * POST (and PUT) method done * * Revision 1.2 1999/05/29 05:15:55 pasty * GET method OK. * todo: POST method... * * Revision 1.1 1999/05/28 04:25:43 pasty * Initial revision * *============================================================================= */ static char const rcsid [] = "$Id: mod_eaccess.c,v 2.3.2.9 2000/02/15 07:36:00 pasty Exp $"; #if defined (EACCESS_BODY_HACK) static char const rcsdi [] = "$Id: " "mod_eaccess.c: EAccess/big-body $"; #endif #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "http_log.h" #include "util_script.h" #include "http_main.h" #include "http_request.h" #include "http_core.h" #include "util_md5.h" #if defined (__GLIBC__) && \ defined (__GLIBC_MINOR__) && \ __GLIBC__ >= 2 && \ __GLIBC_MINOR__ >= 1 #include #else #include #endif #ifndef FALSE #define FALSE 0 #define TRUE !FALSE #endif /* * File locking (from mod_rewrite.h) */ #if defined(USE_FCNTL_SERIALIZED_ACCEPT) #define USE_FCNTL 1 #include #endif #if defined(USE_FLOCK_SERIALIZED_ACCEPT) #define USE_FLOCK 1 #include #endif #if !defined(USE_FCNTL) && !defined(USE_FLOCK) #define USE_FLOCK 1 #if !defined(MPE) && !defined(WIN32) && !defined(__TANDEM) #include #endif #ifndef LOCK_UN #undef USE_FLOCK #define USE_FCNTL 1 #include #endif #endif #ifdef AIX #undef USE_FLOCK #define USE_FCNTL 1 #include #endif #ifdef WIN32 #undef USE_FCNTL #define USE_LOCKING #include #endif #if !defined(USE_FCNTL) && !defined(USE_FLOCK) && !defined(USE_LOCKING) #define USE_FCNTL 1 #endif /* * Définitions pour la compilation du module: Configure utilise le texte situé * entre -START et -END. * * MODULE-DEFINITION-START * Name: eaccess_module * ConfigStart . ./helpers/find-dbm-lib * ConfigEnd * MODULE-DEFINITION-END */ /* * Pré-déclaration du module pour les cross-references */ module eaccess_module; /* * La config (globale) du module */ #define EACCESS_DISABLED 1<<0 #define EACCESS_ENABLED 1<<1 /* * Les actions possibles dans une règle */ typedef enum { EACCESS_DENY, /* deny */ EACCESS_PERMIT, /* permit */ EACCESS_WARNING, /* warning */ EACCESS_AUTH_BASIC, /* auth/basic[=n] */ EACCESS_AUTH_SECURID /* auth/securid[=n] */ } eaccess_action; /* * Les actions en chaine de caractères */ #define EACCESS_DENY_STR "deny" #define EACCESS_DENY_LEN 4 /* len ("deny") */ #define EACCESS_PERMIT_STR "permit" #define EACCESS_WARNING_STR "warning" #define EACCESS_AUTH_BASIC_STR "auth/basic" #define EACCESS_AUTH_BASIC_LEN 10 /* len ("auth/basic") */ #define EACCESS_AUTH_SECURID_STR "auth/securid" #define EACCESS_AUTH_SECURID_LEN 12 /* len ("auth/securid") */ /* * Une règle */ typedef struct eaccess_rulentry { char *pattern; /* l'er en clair (sans le '!' */ /* éventuel */ regex_t regexp; /* l'ER compilée */ int revert; /* revert-match ('!') */ eaccess_action action; /* l'action à entreprendre si */ /* l'ER match l'URL */ int action_arg; /* l'argument de l'action, cad: */ /* - le TTL de l'auth (pour les */ /* actions auth/...=) */ /* - le code de retour (pour */ /* les actions deny=) */ char *action_opt; /* les options des règles */ /* auth/... */ } eaccess_rulentry; /* * La config du module (la config dynamique n'est pas implémentée...) */ typedef struct eaccess_cfg { int state; /* eaccess activé ou non */ array_header *rules; /* tableau d'eaccess_rulentry */ char *logfile; /* le fichier de log */ int logfd; /* le file descriptor du log */ int loglevel; /* la verbosité des logs */ char *cachefile; /* le fichier de cache des auth */ char *cachefname; /* le nom complet du cache auth */ int optim; /* la niveau d'optimisation */ } eaccess_cfg; /* * mode_t pour les open() */ #ifdef WIN32 # define EACCESS_OPEN_MODE (_S_IREAD | _S_IWRITE) #else # define EACCESS_OPEN_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) #endif /* * Les niveaux d'optimisation */ #define EACCESS_KEEP_STR_RE optim > -1 #define EACCESS_KEEP_BIN_RE optim > 0 /* ************************************************************************ * File locking and log time (cf mod_rewrite.c) ************************************************************************ */ #ifdef USE_FCNTL static struct flock lock_it; static struct flock unlock_it; #endif static void fd_lock (request_rec *r, int fd) { int rc; #ifdef USE_LOCKING off_t offset; #endif #if defined(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; } #elif defined(USE_FLOCK) while ((rc = flock (fd, LOCK_EX)) < 0 && errno == EINTR) { continue; } #elif defined(USE_LOCKING) /* Store offset, lock the first byte always, and restore offset */ offset = lseek (fd, 0, SEEK_CUR); lseek (fd, 0, SEEK_SET); rc = _locking (fd, _LK_LOCK, 1); lseek (fd, offset, SEEK_SET); #else #error -Dxxx error: use USE_FCNTL, USE_FLOC or USE_LOCKING #endif if (rc < 0) { ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "EAccess: failed to lock file descriptor"); } return; } static void fd_unlock (request_rec *r, int fd) { int rc; #ifdef USE_LOCKING off_t offset; #endif #if defined(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); #elif defined(USE_FLOCK) rc = flock (fd, LOCK_UN); #elif defined(USE_LOCKING) /* Store offset, unlock the first byte always, and restore offset */ offset = lseek (fd, 0, SEEK_CUR); lseek (fd, 0, SEEK_SET); rc = _locking (fd, _LK_UNLOCK, 1); lseek (fd, offset, SEEK_SET); #else #error -Dxxx error: use USE_FCNTL, USE_FLOC or USE_LOCKING #endif if (rc < 0) { ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "EAccess: failed to unlock file descriptor"); } } static char *current_logtime(request_rec *r) { int timz; struct tm *t; char tstr[80]; char sign; t = ap_get_gmtoff(&timz); sign = (timz < 0 ? '-' : '+'); if (timz < 0) { timz = -timz; } strftime(tstr, 80, "[%d/%b/%Y:%H:%M:%S ", t); ap_snprintf(tstr + strlen(tstr), 80-strlen(tstr), "%c%.2d%.2d]", sign, timz/60, timz%60); return ap_pstrdup(r->pool, tstr); } /* ************************************************************************ * Convertir un entier (en base 10) et met à jour endptr: * - NULL si pas d'erreur * - != NULL si erreur ************************************************************************ */ static int eaccess_atoi (const char *nptr, char **endptr) { long int result; /* * strtol n'aime pas traduire une chaine vide (core dump...) * De plus, si la chaine à traduire ne contient que (\0), * on ne va pas plus loin. */ if ((nptr == NULL) || (*nptr == '\0')) { *endptr = "NULL"; return -1; } /* * On traduit, ce qui met à jour endptr à la valeur du dernier caractère * analysé OK: * - s'il n'y a pas d'erreur, c'est le '\0' de la de nptr * (puisqu'on a déjà vérifié que *nptr != '\0'); * - s'il y a une erreur, c'est le pointeur vers le caractère posant problème; * * De plus, s'il y a dépassement de capacité, errno == ERANGE. */ result = strtol (nptr, endptr, 10); /* * Le seul cas OK est donc (**endptr == '\0') */ if (errno == ERANGE) { *endptr = "ERANGE"; return -1; } if (**endptr == '\0') { *endptr = NULL; return (int) result; } return -1; } /* ************************************************************************ * Convertion hexa -> char * * Code extrait de util.c ************************************************************************ */ static char x2c(const char *what) { register char digit; #ifndef CHARSET_EBCDIC digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0')); digit *= 16; digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0')); #else /*CHARSET_EBCDIC*/ char xstr[5]; xstr[0]='0'; xstr[1]='x'; xstr[2]=what[0]; xstr[3]=what[1]; xstr[4]='\0'; digit = os_toebcdic[0xFF & strtol(xstr, NULL, 16)]; #endif /*CHARSET_EBCDIC*/ return (digit); } /* ************************************************************************ * Convertir les %hh ; exceptions: \0, \a, \b, \n, \v, \f, \r: * - %00 ('\0') => "\0" * - %07 ('\a') => "\a" * - %08 ('\b') => "\b" * - %0A ('\n') => "\n" * - %0B ('\v') => "\v" * - %0C ('\f') => "\f" * - %0D ('\r') => "\r" * Note: * - %09 ('\t') n'est pas remplacer par "\t" car facilement "matchable", * - %5C ('\\') n'est pas remplacer par "\\" pour la même raison. * * Puis, convertir les ('\r' suivi de '\n') en "\n". * * Code extrait de util.c ************************************************************************ */ void eaccess_unescape (char *string) { register int x, y; for (x = 0, y = 0; string[y]; ++x, ++y) { if (string[y] != '%') string[x] = string[y]; else { if (!ap_isxdigit(string[y + 1]) || !ap_isxdigit(string[y + 2])) { string[x] = '%'; } else { string[x] = x2c(&string[y + 1]); y += 2; if (string[x] == '\0') { string[x++] = '\\'; string[x] = '0'; } else if (string[x] == '\a') { string[x++] = '\\'; string[x] = 'a'; } else if (string[x] == '\b') { string[x++] = '\\'; string[x] = 'b'; } else if (string[x] == '\n') { string[x++] = '\\'; string[x] = 'n'; } else if (string[x] == '\v') { string[x++] = '\\'; string[x] = 'v'; } else if (string[x] == '\f') { string[x++] = '\\'; string[x] = 'f'; } else if (string[x] == '\r') { string[x++] = '\\'; string[x] = 'r'; } } } if (string[y] == '\r' && string[y+1] == '\n') { string[x++] = '\\'; string[x] = 'n'; y += 1; } } string[x] = '\0'; } /* ************************************************************************ * Logger un message d'un niveau ************************************************************************ */ static void eaccess_log (request_rec *r, int level, const char *text, ...) { /* * La config actuelle */ eaccess_cfg *conf = (eaccess_cfg *) ap_get_module_config (r->server->module_config, &eaccess_module); /* * La liste du text, ... passé */ va_list ap; /* * Les "REMOTE" variables utilisées pour le log */ char *ruser; const char *rhost; /* * Les différentes parties de la ligne à logger: * str1: "rhost rident ruser time", soit approximativement 64 caractères * (arrondi à 128 ...) * str2: l'interprétation de texte, ... * contient souvent (voir les appels à cette fonction): * RE#nnn xxx access to 'GET url?args|data' * 'GET ...' est limité par Apache à DEFAULT_LIMIT_REQUEST_LINE * caractères (cf read_request_line, http_protocol.c, cette limite * inclue le 'HTTP/x.y' présent en fin de ligne); * "RE#nnn xxx access to" fait toujours 24 caractères. */ char str1 [128]; char str2 [24 + DEFAULT_LIMIT_REQUEST_LINE]; /* * On récupère la liste texte, ... passée en arguments */ va_start (ap, text); /* * - si on n'a pas de file descripteur, * - ou si le message à logger est d'un niveau suppérieur à celui demandé par * la directive LogLevel, * alors ce n'est pas la peine d'aller plus loin. */ if ((conf->logfd < 0) || (level > conf->loglevel)) { return; } /* * REMOTE_USER dispo? */ if (r->connection->user == NULL) { ruser = "-"; } else if (strlen (r->connection->user) != 0) { ruser = r->connection->user; } else { ruser = "\"\""; } /* * REMOTE_HOST */ rhost = ap_get_remote_host (r->connection, r->server->module_config, REMOTE_NOLOOKUP); if (rhost == NULL) { rhost = "UNKNOWN-HOST"; } /* * Les 2 parties de la ligne à logger */ ap_snprintf (str1, sizeof (str1), "%s %s %s %s ", rhost, ( r->connection->remote_logname != NULL ? r->connection->remote_logname : "-" ), ruser, current_logtime (r)); ap_vsnprintf (str2, sizeof (str2), text, ap); /* * On écrit */ fd_lock (r, conf->logfd); write (conf->logfd, str1, strlen (str1)); write (conf->logfd, str2, strlen (str2)); write (conf->logfd, "\n", strlen ("\n")); fd_unlock (r, conf->logfd); /* * On libère la liste et c'est fini */ va_end (ap); return; } /* ************************************************************************ * Stockage en cache des auth. ************************************************************************ */ DBM *eaccess_auth_open (request_rec *r, char *fname, int flags) { DBM *cachefd; cachefd = dbm_open (fname, flags, EACCESS_OPEN_MODE); if (cachefd != NULL) { fd_lock (r, dbm_dirfno (cachefd)); } else { ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "EAccess: could not open EAccessCache file '%s'", fname); } return cachefd; } void eaccess_auth_close (request_rec *r, DBM *fdesc) { fd_unlock (r, dbm_dirfno (fdesc)); dbm_close (fdesc); } time_t eaccess_auth_get (request_rec *r, char *cachefname, const char *auth) { datum key; datum val; time_t res; DBM *cachefd; AP_MD5_CTX context; if ((cachefd = eaccess_auth_open (r, cachefname, O_RDONLY)) == NULL) { ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "EAccess: could not open EAccessCache file '%s'", cachefname); return (time_t) 0; } ap_MD5Init (&context); ap_MD5Update (&context, auth, strlen (auth)); key.dptr = ap_md5contextTo64 (r->pool, &context); key.dsize = strlen (key.dptr); val = dbm_fetch (cachefd, key); if (val.dptr != NULL) { memcpy (&res, val.dptr, sizeof (time_t)); eaccess_log (r, 2, "DB-GET: '%s' is found: time_t = %ld", auth, res); eaccess_auth_close (r, cachefd); return res; } eaccess_log (r, 2, "DB-GET: '%s' is NOT found", auth); eaccess_auth_close (r, cachefd); return (time_t) 0; } void eaccess_auth_put (request_rec *r, char *cachefname, const char *auth, time_t *t) { datum key; datum val; DBM *cachefd; AP_MD5_CTX context; if ((cachefd = eaccess_auth_open (r, cachefname, O_WRONLY)) == NULL) { ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "EAccess: could not open EAccessCache file '%s'", cachefname); return; } ap_MD5Init (&context); ap_MD5Update (&context, auth, strlen (auth)); key.dptr = ap_md5contextTo64 (r->pool, &context); key.dsize = strlen (key.dptr); val.dptr = (void *) t; val.dsize = sizeof (time_t); if (dbm_store (cachefd, key, val, DBM_REPLACE) != 0) { ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "EAccess: store error for EAccessCache file (dbm err=%i)'", dbm_error (cachefd)); } eaccess_log (r, 2, "DB-PUT: '%s' is stored", auth); eaccess_auth_close (r, cachefd); } void eaccess_auth_del (request_rec *r, char *cachefname, const char *auth) { datum key; DBM *cachefd; AP_MD5_CTX context; if ((cachefd = eaccess_auth_open (r, cachefname, O_WRONLY)) == NULL) { ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "EAccess: could not open EAccessCache file '%s'", cachefname); return; } ap_MD5Init (&context); ap_MD5Update (&context, auth, strlen (auth)); key.dptr = ap_md5contextTo64 (r->pool, &context); key.dsize = strlen (key.dptr); if (dbm_delete (cachefd, key) != 0) { ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "EAccess: delete error for EAccessCache file (dbm err=%i)'", dbm_error (cachefd)); } eaccess_log (r, 2, "AUTH-DB: '%s' is deleted", auth); eaccess_auth_close (r, cachefd); } /* ************************************************************************ * Initialisation du module (ouverture du log et de log des auth). ************************************************************************ */ static void eaccess_init (server_rec *s, pool *p) { /* * flags pour open() */ int flags_log = (O_WRONLY | O_APPEND | O_CREAT); /* * La config actuelle */ eaccess_cfg *conf = (eaccess_cfg *) ap_get_module_config (s->module_config, &eaccess_module); /* * Pour réinitialiser le cache */ DBM *cachefd; /* * Si EAccessEnable n'est à on, on n'a pas grand chose à faire... */ if (conf->state == EACCESS_DISABLED) { return; } /* * Si l'utilisateur n'a pas utilisé de directive EAccessAuthCache, on utilise * la valeur par défaut pour le fichier de cache */ if (conf->cachefile == NULL) { conf->cachefile = "logs/eaccess_auth"; } /* * On demande à compléter par "Server Root" le nom du fichier si celui-ci * ne commence pas par un "/". */ conf->cachefname = ap_server_root_relative (p, conf->cachefile); /* * On réinitialise le cache (on n'utilise pas eaccess_auth_open et * eaccess_auth_close car on n'est pas dans une requête et donc on n'a pas * de (request_rec *r). * De plus, c'est inutile ici de locker le fichier. */ if ((cachefd = dbm_open (conf->cachefname, O_WRONLY | O_CREAT | O_TRUNC, EACCESS_OPEN_MODE)) == NULL) { ap_log_error (APLOG_MARK, APLOG_ERR, s, "EAccess: could not create EAccessCache file '%s'", conf->cachefname); exit (1); } dbm_close (cachefd); /* * Si EAccessLogLevel est à 0, on n'a rien à faire... */ if (conf->loglevel == 0) { return; } /* * Si l'utilisateur n'a pas utilisé de directive EAccessLog, on utilise la * valeur par défaut pour le fichier de log */ if (conf->logfile == NULL) { conf->logfile = "logs/eaccess_log"; } /* * S'agt-il d'un "vrai fichier" ou d'un "pipe"? */ if (*conf->logfile == '|') { /* * C'est un "pipe" */ piped_log *pl; /* * On l'ouvre */ pl = ap_open_piped_log (p, conf->logfile + 1); if (pl == NULL) { ap_log_error (APLOG_MARK, APLOG_ERR, s, "EAccess: could not open EAccessLog command '%s'", conf->logfile + 1); exit (1); } conf->logfd = ap_piped_log_write_fd (pl); if (conf->logfd < 0) { ap_log_error (APLOG_MARK, APLOG_ERR, s, "EAccess: could not write EAccessLog command '%s'", conf->logfile + 1); exit (1); } } else { /* * C'est un "vrai" fichier */ char *logfname; /* * On demande à compléter par "Server Root" le nom du fichier si celui-ci * ne commence pas par un "/". */ logfname = ap_server_root_relative (p, conf->logfile); /* * On ouvre... */ conf->logfd = ap_popenf (p, logfname, flags_log, EACCESS_OPEN_MODE); if (conf->logfd < 0) { ap_log_error (APLOG_MARK, APLOG_ERR, s, "EAccess: could not open EAccessLog file '%s'", logfname); exit (1); } } /* * Et tout va bien */ return; } /* ************************************************************************ * Récupérer l'"auth" dans une header et la supprimer ************************************************************************ */ const char *eaccess_get_auth_basic (request_rec *r) { /* * Rappelons le format d'une auth/basic dans un header HTTP de requête: * Authorization: Basic toto:titi */ const char *auth = NULL; char *value = NULL; auth = ap_table_get (r->headers_in, "Authorization"); if (auth) { value = strstr (auth, "Basic "); if (value) { auth += strlen ("Basic "); } } return (auth); } void eaccess_unset_auth_basic (request_rec *r) { ap_table_unset (r->headers_in, "Authorization"); } const char *eaccess_get_auth_securid (request_rec *r) { /* * Rappelons le format d'un cookie dans un header HTTP de requête: * Cookie: NAME1=OPAQUE_STRING1; NAME2=OPAQUE_STRING2 ... * * Pour SecurID, cela donne: * Cookie: AceHandle=...; webid2=... */ const char *auth = NULL; const char *cookie = NULL; char *value = NULL; cookie = ap_table_get (r->headers_in, "Cookie"); if (cookie) { value = strstr (cookie, "AceHandle="); if (value) { char *end; value += strlen ("AceHandle="); auth = ap_pstrdup (r->pool, value); end = strchr (auth, ';'); if (end) { *end = '\0'; } } } return (auth); } void eaccess_unset_auth_securid (request_rec *r) { /* * Un peu violent... */ ap_table_unset (r->headers_in, "Cookie"); } /* ************************************************************************ * Code du vérificateur d'URL ************************************************************************ */ static int eaccess_check (request_rec *r) { /* * La config actuelle */ eaccess_cfg *conf = (eaccess_cfg *) ap_get_module_config (r->server->module_config, &eaccess_module); /* * == 1 si "Pattern" à matché l'URL (ou si "!Pattern" n'a pas matché l'URL) */ int matched; /* * Une à une, les ER de la liste des règles de la config */ eaccess_rulentry *entries = (eaccess_rulentry *) conf->rules->elts; /* * Pour parcourir les ER */ int i; /* * l'"URL" à vérifier et ses data éventuelles (ou son body pour les POST/PUT). */ char url [HUGE_STRING_LEN]; /* * 2 paramètres inutilisés pour regexec() */ size_t dummy_nmatch; regmatch_t dummy_pmatch [1]; /* * Si EAccessEnable n'est pas ON, on s'arrête là... */ if (conf->state == EACCESS_DISABLED) { return OK; } /* * On "calcule" " " * Note: * - r->uri ne contient que l'uri (décodée et optimisée pour les ..), il * faudrait y ajouter éventuellement r->args (les fragments (#xxx dans les * URL) ne servent qu'au butineur, et apache ne s'en sert jamais: par * mesure de précaution on peut les supprimer). * - r->unparsed_uri contient le 2ème mot de la requête, c'est à dire l'URI * complète (avec ?args#fragment), non décodée, non optimisée (/xx/../yy). * * On opte donc pour: * r->parsed_uri.fragment := NULL; * URL := r->method + r->uri + decode ("?" + r->args [ + "|" + datas ]); */ /* * Suppression éventuelle des fragments: s'il y en a, ceux-ci ont été * alloués (cf util_uri.c, le seul endroit où Apache utilise les fragments) * dans le pool de la requête par ap_pstrndup. */ if (r->parsed_uri.fragment) { r->parsed_uri.fragment [0] = '\0'; } /* * On calcule method + uri. Comme plus tard on décodera "URL", on va ici * encoder uri, pour avoir uri == décode (encode (uri)). * Cette méthode a l'air d'être utilisée dans Apache, alors pourquoi pas? * De plus, elle permet de n'utiliser qu'*1* chaîne str [HUGE_STRING_LEN]. */ { int to_write, written; char *euri; euri = ap_escape_uri (r->pool, r->uri); to_write = strlen (r->method) + 1 + strlen (euri); written = ap_snprintf (url, sizeof (url), "%s %s", r->method, euri); if (written != to_write) { ap_log_rerror (APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, "EAccess: truncated URL (uri): wanted %d, got %d", to_write, written); } } /* * Ajout des éventuels arguments (QUERY_STRING) */ if (r->args) { int to_write, written; to_write = strlen (url) + 1 + strlen (r->args); written = ap_snprintf (url, sizeof (url), "%s?%s", url, r->args); if (written != to_write) { ap_log_rerror (APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, "EAccess: truncated URL (args): wanted %d, got %d", to_write, written); } } /* * Si un body est présent, on en ajoute le contenu "|" * * Pour cela, 2 possibilités: * * 1/ on appelle ap_blookc() pour lire (sans le consommer) le prochain * caractère du body. * ap_blookc() a pour effet de remplir le buffer inptr si ce n'est pas déjà * fait mais on doit vérifier qu'il y a quelque chose à lire sinon ap_blookc() * attend que le butineur fournisse des données... * ap_blookc retourne 1 si tout va bien. * * Le problème de cette méthode est que le buffer n'est rempli que des données * provenant du 1er "read" sur la socket. On peut généralement contrôler la * taille de ce buffer (setsockopt(SO_RCVBUF); sinon la taille par défaut est * contrôler par une variable système: net.core.rmem_default sous Linux, * tcp_recv_hiwat sous Solaris). * Cependant, certain système "optimise" les read sur les sockets: dès que des * données sont disponibles, le read rend la met pour les mettre à * disposition. Ce n'est pas trop le cas de Linux, mais par contre Solaris * "optimise" parfois "trop". On peut donc n'avoir dans ce buffer inptr que * très (trop) peu de caractères. * * 2/ on "consomme" le body en appelant ap_*_client_block(). * Il faut d'abord indiquer de quelle façon on doit lire les datas par * ap_setup_client_block() (généralement, on utilise la méthode * REQUEST_CHUNKED_ERROR qui signifie que si le butineur n'a pas fourni de * "Content-length", on retourne une erreur). * Puis on vérifie qu'on ne va pas risquer de rester bloqué sur la lecture en * appelant ap_should_client_block(). * On peut alors boucler sur ap_get_client_block() pour lire *toutes* les * datas. * * Le problème avec cette méthode est qu'on ne peut consommer le body qu'une * fois. Donc si ce module le fait, les autres n'en disposeront plus: cela * poserait un sérieux problèmes à mod_cgi et mod_proxy... * * On va donc consommer le body et le stoker dans un fichier temporaire. * On notera dans la table "notes" de la requête le nom de ce fichier dans la * variable "EAccess-body-fn" et son file-descriptor dans "EAccess-body-fd". * On doit également "patcher" ap_should_client_block (qui va regarder * d'abord si la table "notes" contient la variable "EAccess-body-fn"). * Enfin, on "patche" également ap_get_client_block qui retourne le contenu * du fichier temporaire puis le supprimer si "EAccess-body-fn" existe. * * La méthode utilisée par defaut est la première. On peut définir lors de la * compilation EACCESS_BODY_HACK pour utiliser la 2ème méthode. On doit * alors appliquer au code d'Apache le patch mod_eaccess.patch. */ # if !defined (EACCESS_BODY_HACK) // { /* * Méthode 1: on lit sans consommer ce qu'on peut du body. */ { /* * Pour voir si un body est présent, on en lira 1 caractère */ char c; if ((r->connection->client->incnt > 0) && (ap_blookc (&c, r->connection->client) == 1)) { int len; /* * Ok, un body est diponible, on en ajoute le début à l'"url" à vérifier, * sous la forme: "...|début du body". * * Pour infos, url fait HUGE_STRING_LEN caractères de long (actuellement * 8192 dans httpd.h) et inptr DEFAULT_BUFSIZE (4096 dans buff.c). */ strncat (url, "|", sizeof (url)); len = strlen (url); { int to_write, written; to_write = len + 1 + r->connection->client->incnt; /* * On n'utilise pas snprintf car le body n'est pas forcément terminé par * un '\0'. */ if (to_write > sizeof (url)) { written = sizeof (url); memcpy ((void*) (url + len), (const void*) r->connection->client->inptr, sizeof (url) - len); len = sizeof (url) - 1; } else { written = to_write; memcpy ((void*) (url + len), (const void*) r->connection->client->inptr, r->connection->client->incnt); len = len + r->connection->client->incnt; } if (written != to_write) { ap_log_rerror (APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, "EAccess: truncated URL (body): wanted %d, got %d", to_write, written); } } /* * Et on met fin à la chaine */ url [len] = '\0'; } } # else // }{ /* * Méthode 2: on consomme le body en le sauvegardant dans un fichier * temporaire. */ { /* * Code de retour des différentes fonctions ap_*. */ int rc; /* * On veut que le client fournisse un champ "Content-length". Sinon, on * sort en erreur HTTP 411. */ if ((rc = ap_setup_client_block (r, REQUEST_CHUNKED_ERROR))) { return rc; } /* * On vérifie qu'on ne va pas rester bloquer sur la lecture du body */ if (ap_should_client_block (r)) { int len; char tmpfilename [] /* le nom du fichier temporaire */ = "/tmp/eaccess_body_XXXXXX"; int tmpfile /* son file descriptor */ = mkstemp (tmpfilename); char buff [1024]; /* le buffer pour les read */ int nread, /* ce qu'on mis dans le buffer */ to_write, /* ce qu'on devrait écrire */ written; /* ce qu'on a réellement écrit */ int truncated = 0; /* si url a été tronquée 1x */ /* * Ok, un body est diponible, on en ajoute le début à l'"url" à vérifier, * sous la forme: "...|début du body". */ strncat (url, "|", sizeof (url)); len = strlen (url); /* * On vérifie qu'on a bien ouvert le fichier temporaire. */ if (tmpfile != -1) { /* * Ok, on va noter le nom et le file-descripto dans la table * "à-tout-faire" inter-modules. * On utilise les champ "EAccess-body-fn" (pour le filename) et * "EAccess-body-fd" (pour le file-descriptor) (que personne d'autre * que nous ne devrait s'amuser à positionner...). * Afin d'indiquer à ap_get_client_block s'il doit se comporter * "normalement" ou lire le contenu du fichier temporaire, on ne * positionne "EAccess-body-fn" qu'après avoir tout lu et on se sert de * la présence de cette variable dans les patchs de ap_*_client_block. */ ap_table_setn (r->notes, "EAccess-body-fd", (char *) tmpfile); /* * On peut consommer le body et le sauvegarder */ while ((nread = ap_get_client_block (r, buff, sizeof (buff))) > 0) { /* * On sauvegarde dans le fichier temporaire */ written = write (tmpfile, buff, nread); /* XXXX vérif... */ /* * On écrit ce qu'on vient de lire dans URL; on doit écrire: * + + 1 (pour terminer url par '\0'). */ to_write = len + nread + 1; /* * Comme pour la 1ère méthode, on n'utilise pas snprint. */ if (to_write > sizeof (url)) { written = sizeof (url); memcpy ((void*) (url + len), (const void*) buff, sizeof (url) - len); len = sizeof (url) - 1; } else { written = to_write; memcpy ((void*) (url + len), (const void*) buff, nread); len = len + nread; } /* * On met fin à la chaîne de caractères */ url [len] = '\0'; /* * Si on n'a pas assez écrit et que c'est la 1ère fois. */ if (!truncated && written != to_write) { truncated = 1; ap_log_rerror (APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, "EAccess: truncated URL (body): wanted %d, got %d", to_write, written); } } /* * On fait croire aux autres qu'on n'a rien lu (et en particulier à * ap_discard_request_body, qui sera appelé par ap_die en cas * d'interdiction par EAccess; donc ce n'est pas à nous de détruire le * body...). */ r->read_length = 0; /* * On peut "rembobiner" le fichier temporaire */ lseek (tmpfile, 0, SEEK_SET); /* * Et noter le nom du fichier temporaire pour qu'ap_get_client_block * modifie son comportement. * tmpfilename étant une variable locale, on la duplique. */ ap_table_setn (r->notes, "EAccess-body-fn", ap_pstrdup (r->pool, tmpfilename)); } else { /* * Impossible d'ouvrir le fichier temporaire, tant pis pour le body... */ ap_log_rerror (APLOG_MARK, APLOG_ERR, r, "EAccess: cannot open tmp file (%s)'", tmpfilename); } } } # endif // } /* * Enfin, pour ne pas compliquer les expressions régulières, on convertit les * caractères "%encodés", ainsi que les '+'. */ eaccess_unescape (url); /* * On parcourt les ER */ for (i = 0; i < conf->rules->nelts; i++) { /* * Si on n'est pas en niveau d'optimisation 1, on doit recompiler l'ER. */ if (!conf->EACCESS_KEEP_BIN_RE) { regcomp (&(entries[i].regexp), entries[i].pattern, REG_EXTENDED | REG_NOSUB); } /* * On regarde si " " vérifie l'ER */ matched = 0; if (regexec (&(entries[i].regexp), url, dummy_nmatch, dummy_pmatch, 0) == 0) { matched = 1; } /* * Si on n'est pas en niveau d'optimisation 1, on ne conserve pas la compil */ if (!conf->EACCESS_KEEP_BIN_RE) { regfree (&(entries[i].regexp)); } /* * Si on est en revert-match, on inverse le résultat */ if (entries[i].revert) { /* * On est en "revert": on inverse le résultat, * on log que !RE a / n'a pas matchée. */ matched = (matched) ? 0 : 1; eaccess_log (r, 2, "RE '!%s' %s URL '%s'", entries[i].pattern, (matched) ? "matches" : "does not match", url); } else { /* * On log que RE a / n'a pas matchée. */ eaccess_log (r, 2, "RE '%s' %s URL '%s'", entries[i].pattern, (matched) ? "matches" : "does not match", url); } /* * Si l'ER matche, on dispatche suivant l'action à entreprendre */ if (matched) { switch (entries[i].action) { case EACCESS_PERMIT: { eaccess_log (r, 1, "RE #%03d grants access to '%s'", i + 1, url); return OK; } case EACCESS_DENY: { eaccess_log (r, 1, "RE #%03d denies access to '%s'", i + 1, url); return entries[i].action_arg; } case EACCESS_WARNING: { eaccess_log (r, 1, "RE #%03d *** WARNING! *** '%s'", i + 1, url); /* * Pas de return, car on doit continuer à analyser les autres règles. * (mais un break pour arrêter les case...) */ continue; } case EACCESS_AUTH_BASIC: case EACCESS_AUTH_SECURID: { /* * L'éventuelle authentification dans le header, et l'éventuelle * première fois qu'elle a été utilisée (en char* car dans une table * on ne peut mettre que des char*; on convertira en time_t...) */ const char *auth; /* user:pass en base64 */ time_t first_time; const char *auth_type; /* le type de l'auth */ /* pour les logs */ const char *auth_opt; /* le type de l'option */ /* pour les logs */ if (entries[i].action == EACCESS_AUTH_BASIC) { auth_type = "basic"; auth_opt = "realm"; } else if (entries[i].action == EACCESS_AUTH_SECURID) { auth_type = "securid"; auth_opt = "redirect"; } else { auth_type = ""; auth_opt = ""; } eaccess_log (r, 2, "RE #%03d auth/%s: %s='%s', TTL=%d", i + 1, auth_type, auth_opt, entries[i].action_opt, entries[i].action_arg); /* * Y a-t-il dans le Header une authentification? */ if (((entries[i].action == EACCESS_AUTH_BASIC) && (auth = eaccess_get_auth_basic (r))) || ((entries[i].action == EACCESS_AUTH_SECURID) && (auth = eaccess_get_auth_securid (r)))) { /* * Il y a une authentification dans le Header HTTP. */ eaccess_log (r, 2, "RE #%03d auth/%s: Authorization='%s'", i + 1, auth_type, auth); /* * Cette authentification est-elle déjà présente dans la table des * auth de cette ER? */ first_time = eaccess_auth_get (r, conf->cachefname, auth); if (first_time) { /* * Cette auth est déjà connue, on va regarder si elle a expiré. * Donc avant tout, on regarde si TTL == 0 (=> pas d'expiration) */ if (entries[i].action_arg) { /* * Ok, on s'intéresse à l'expiration de cette authentification: * le time_t de maintenant et le delta entre les 2 temps */ time_t this_time; int diff_time; /* * La temps maintenant et la première fois => delta */ time (&this_time); diff_time = (int) difftime (this_time, first_time); eaccess_log (r, 2, "RE #%03d auth/%s: Authorization already used: " "first time=%ld => delta = %d seconds", i + 1, auth_type, first_time, diff_time); /* * Expirée ou non? */ if (diff_time > entries[i].action_arg) { /* * L'authentification présente dans le header est donc * expirée. Si la règle avait précisé une option (cad un * realm pour auth/basic, une redirection pour auth/securid), * on dit 401 (auth/basic) ou 302 (auth/securid), sinon, on * supprime l'auth dans le header et on continue la boucle... */ if (entries[i].action_opt) { if (entries[i].action == EACCESS_AUTH_BASIC) { /* * auth/basic: * * Ok, la règle précise un realm: on supprime cette auth * de la table et on retourne 401 au butineur... */ eaccess_log (r, 2, "RE #%03d auth/basic: Realm set to '%s' => " "err 401 returned", i + 1, entries[i].action_opt); eaccess_auth_del (r, conf->cachefname, auth); ap_table_setn (r->err_headers_out, "WWW-Authenticate", ap_pstrcat (r->pool, "Basic realm=\"", ap_escape_quotes (r->pool, entries[i].action_opt), "\"", NULL)); eaccess_log (r, 1, "RE #%03d AUTH too old for '%s'", i + 1, url); r->proxyreq = 0; return AUTH_REQUIRED; } else if (entries[i].action == EACCESS_AUTH_SECURID) { /* * auth/securid: * * Ok, la règle précise un realm: on supprime cette auth * de la table, on écrit "Location: " dans * le header et on retourne 302 au butineur... */ eaccess_log (r, 2, "RE #%03d auth/securid: " "Option set to '%s' => " "redirected", i + 1, entries[i].action_opt); eaccess_auth_del (r, conf->cachefname, auth); ap_table_setn (r->headers_out, "Location", ap_pstrdup (r->pool, entries[i].action_opt)); eaccess_log (r, 1, "RE #%03d AUTH too old for '%s'", i + 1, url); /*r->proxyreq = 0;*/ return REDIRECT; } else { /* * ... erreur dans le code ... */ ap_log_rerror (APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, "EAccess: internal error: auth/???"); return SERVER_ERROR; } } else { /* * La règle ne précise pas d'option: on supprime cette auth * du Header HTTP et de la table et on continue... */ eaccess_log (r, 2, "RE #%03d auth/%s: %s unset => auth cancelled", i + 1, auth_type, auth_opt); eaccess_auth_del (r, conf->cachefname, auth); if (entries[i].action == EACCESS_AUTH_BASIC) { eaccess_unset_auth_basic (r); } else if (entries[i].action == EACCESS_AUTH_SECURID) { eaccess_unset_auth_securid (r); } else { ap_log_rerror (APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, "EAccess: internal error: auth/???"); return SERVER_ERROR; } eaccess_log (r, 1, "RE #%03d AUTH removed for '%s'", i + 1, url); } } else { /* * L'authentification présente dans le header n'est pas * expirée. */ eaccess_log (r, 1, "RE #%03d AUTH not expired '%s'", i + 1, url); } } else { /* * TTL == 0 => pas d'expiration */ eaccess_log (r, 1, "RE #%03d AUTH unTTLed for '%s'", i + 1, url); } } else { /* * L'auth présente dans le Header HTTP n'est pas déjà connue. * On mémorise cette première fois et on continue la boucle * des vérifications d'accès */ time (&first_time); eaccess_log (r, 2, "RE #%03d auth/%s: first time this " "Authorization is used (%s/%ld)", i + 1, auth_type, auth, first_time); eaccess_auth_put (r, conf->cachefname, auth, &first_time); eaccess_log (r, 1, "RE #%03d AUTH starting on '%s'", i + 1, url); } } else { eaccess_log (r, 2, "RE #%03d auth/%s: NO Authorization", i + 1, auth_type); /* * Il n'y a pas d'authentification dans le Header HTTP. * Si l'option dans la règle avait précisé un realm pour auth/basic * (ou une redirection pour auth/securid), on dit 401 (ou 302 pour * securid), sinon on continue la boucle... */ if (entries[i].action_opt) { if (entries[i].action == EACCESS_AUTH_BASIC) { /* * auth/basic: * * Ok, la règle précise un realm: on retourne 401 au butineur... */ eaccess_log (r, 2, "RE #%03d auth/basic: Realm set to '%s' => " "err 401 returned", i + 1, entries[i].action_opt); ap_table_setn (r->err_headers_out, "WWW-Authenticate", ap_pstrcat (r->pool, "Basic realm=\"", ap_escape_quotes (r->pool, entries[i].action_opt), "\"", NULL)); eaccess_log (r, 1, "RE #%03d AUTH err 401 for '%s'", i + 1, url); r->proxyreq = 0; return AUTH_REQUIRED; } else if (entries[i].action == EACCESS_AUTH_SECURID) { /* * auth/securid: * * Ok, la règle précise une redirection: on retourne 302... */ eaccess_log (r, 2, "RE #%03d auth/securid: " "Redirect set to '%s' => " "err 302 returned", i + 1, entries[i].action_opt); ap_table_setn (r->headers_out, "Location", ap_pstrdup (r->pool, entries[i].action_opt)); eaccess_log (r, 1, "RE #%03d AUTH err 302 for '%s'", i + 1, url); /*r->proxyreq = 0;*/ return REDIRECT; } else { /* * ... erreur dans le code ... */ ap_log_rerror (APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r, "EAccess: internal error: auth/???"); return SERVER_ERROR; } } else { /* * La règle ne précise pas de realm et pas d'authentification * présente dans le Header HTTP: on continue... */ eaccess_log (r, 1, "RE #%03d AUTH not needed '%s'", i + 1, url); } } } } } } /* * On est arrivé à la dernière ER sans succès, c'est perdu... */ eaccess_log (r, 1, "default denies access to '%s'", url); return FORBIDDEN; } /* ************************************************************************ * Traitement des commandes de config ************************************************************************ */ /* * EAccessEnable */ static const char *eaccess_cfg_enable (cmd_parms *cmd, void *dummy, int a1) { /* * La config actuelle */ eaccess_cfg *conf = (eaccess_cfg *) ap_get_module_config (cmd->server->module_config, &eaccess_module); /* * qu'on actualise */ conf->state = (a1 ? EACCESS_ENABLED : EACCESS_DISABLED); return NULL; } /* * EAccessRule */ static const char *eaccess_cfg_rule (cmd_parms *cmd, void *dummy, char *a1, char *a2, char *a3) { /* * La config actuelle */ eaccess_cfg *conf = (eaccess_cfg *) ap_get_module_config (cmd->server->module_config, &eaccess_module); /* * La RE à compilée (a2 ou a2+1 en cas d'utilisation du !) */ char *regex; /* * Le code d'erreur lors de la compilation de la RE */ int reret; /* * La nouvelle RE compilée */ eaccess_rulentry *new; /* * On ajoute une entrée à la liste de rules */ new = ap_push_array (conf->rules); /* * Dispatch de l'action */ if (strcasecmp (a1, EACCESS_PERMIT_STR) == 0) { new->action = EACCESS_PERMIT; } else if (strncasecmp (a1, EACCESS_DENY_STR, EACCESS_DENY_LEN) == 0) { new->action = EACCESS_DENY; /* * Le code de retour est FORBIDDEN par défaut */ new->action_arg = FORBIDDEN; /* * Si a1 a 1 longueur > à deny, il y a donc 1 '=nnn" derrière */ if (strlen (a1) > EACCESS_DENY_LEN) { if (a1 [EACCESS_DENY_LEN] == '=') { char *atoierr; new->action_arg = eaccess_atoi (a1 + EACCESS_DENY_LEN + 1, &atoierr); /* * Si l'erreur n'est pas NULL, c'est une erreur... */ if (atoierr != NULL) { return ap_pstrcat (cmd->pool, "EAccess: invalid response code (", atoierr, ") in action '", a1, "'\n", NULL); } /* * De plus, pour ne pas risquer un "internal error", on vérifie la * validité du code (cf http_core.c, fonction set_error_document). */ if (new->action_arg != HTTP_INTERNAL_SERVER_ERROR && ap_index_of_response (new->action_arg) == ap_index_of_response (HTTP_INTERNAL_SERVER_ERROR)) { return ap_pstrcat (cmd->pool, "EAccess: unsupported response code in " "action '", a1, "'\n", NULL); } } else { return ap_pstrcat (cmd->pool, "EAccess: unknown option in action '", a1, "'\n", NULL); } } } else if (strcasecmp (a1, EACCESS_WARNING_STR) == 0) { new->action = EACCESS_WARNING; } else if (strncasecmp (a1, EACCESS_AUTH_BASIC_STR, EACCESS_AUTH_BASIC_LEN) == 0) { new->action = EACCESS_AUTH_BASIC; /* * Tant qu'on n'a pas lu l'éventuelle option "realm", on le laisse à null */ new->action_opt = NULL; /* * Tant qu'on n'a pas lu l'éventuel TTL, on le laisse à 0 */ new->action_arg = 0; /* * Si a1 a 1 longueur > à auth/basic, il y a donc 1 '=nnn" derrière */ if (strlen (a1) > EACCESS_AUTH_BASIC_LEN) { if (a1 [EACCESS_AUTH_BASIC_LEN] == '=') { char *atoierr; new->action_arg = eaccess_atoi (a1 + EACCESS_AUTH_BASIC_LEN + 1, &atoierr); /* * Si l'erreur n'est pas NULL, c'est une erreur... */ if (atoierr != NULL) { return ap_pstrcat (cmd->pool, "EAccess: invalid TTL (", atoierr, ") in action '", a1, "'\n", NULL); } /* * De plus, un TTL négatif ne veut pas dire grand chose... */ if (new->action_arg < 0) { return ap_pstrcat (cmd->pool, "EAccess: negative TTL in action '", a1, "'\n", NULL); } } else { return ap_pstrcat (cmd->pool, "EAccess: unknown option in action '", a1, "'\n", NULL); } } /* * a3, facultatif, positionne le "realm" pour auth/basic */ if (a3) { new->action_opt = ap_pstrdup (cmd->pool, a3); } } else if (strncasecmp (a1, EACCESS_AUTH_SECURID_STR, EACCESS_AUTH_SECURID_LEN) == 0) { /* * code <=> à EACCESS_AUTH_BASIC */ new->action = EACCESS_AUTH_SECURID; new->action_opt = NULL; new->action_arg = 0; if (strlen (a1) > EACCESS_AUTH_SECURID_LEN) { if (a1 [EACCESS_AUTH_SECURID_LEN] == '=') { char *atoierr; new->action_arg = eaccess_atoi (a1 + EACCESS_AUTH_SECURID_LEN + 1, &atoierr); /* * Si l'erreur n'est pas NULL, c'est une erreur... */ if (atoierr != NULL) { return ap_pstrcat (cmd->pool, "EAccess: invalid TTL (", atoierr, ") in action '", a1, "'\n", NULL); } /* * De plus, un TTL négatif ne veut pas dire grand chose... */ if (new->action_arg < 0) { return ap_pstrcat (cmd->pool, "EAccess: negative TTL in action '", a1, "'\n", NULL); } } else { return ap_pstrcat (cmd->pool, "EAccess: unknown option in action '", a1, "'\n", NULL); } } /* * a3, facultatif, positionne le "redirect" pour auth/securid */ if (a3) { new->action_opt = ap_pstrdup (cmd->pool, a3); } } else { return ap_pstrcat (cmd->pool, "EAccess: unknown action '", a1, "'\n", NULL); } /* * Si l'ER commence par un !: * - on note qu'on est en revert-match et on saute le !, * sinon: * - on note qu'on n'est pas en revert-match */ if (a2 [0] == '!') { new->revert = TRUE; regex = a2 + 1; } else { new->revert = FALSE; regex = a2; } /* * On compile l'ER avec les options: * - Use POSIX Extended Regular Expression syntax * - Support for substring addressing of matches is not required */ reret = regcomp (&(new->regexp), regex, REG_EXTENDED | REG_NOSUB); if (reret != 0) { char errline [1024]; reret = regerror (reret, &(new->regexp), errline, sizeof (errline)); return ap_pstrcat (cmd->pool, "EAccess: cannot compile regular expression '", regex, "': ", errline, ".\n", NULL); } /* * On note l'ER en clair (sans le ! éventuel) */ new->pattern = ap_pstrdup (cmd->pool, regex); /* * Si on n'est pas en optimisation niveau 1, on ne garde pas l'ER compilée */ if (!conf->EACCESS_KEEP_BIN_RE) { regfree (&(new->regexp)); } /* * Et c'est tout */ return NULL; } /* * EAccessLog */ static const char *eaccess_cfg_log (cmd_parms *cmd, void *dummy, char *a1) { /* * La config actuelle */ eaccess_cfg *conf = (eaccess_cfg *) ap_get_module_config (cmd->server->module_config, &eaccess_module); /* * On note le nom du fichier */ conf->logfile = a1; /* * Et c'est tout, on ouvrira le fichier dans le init du module */ return NULL; } /* * EAccessLogLevel */ static const char *eaccess_cfg_loglevel (cmd_parms *cmd, void *dummy, char *a1) { /* * La config actuelle */ eaccess_cfg *conf = (eaccess_cfg *) ap_get_module_config (cmd->server->module_config, &eaccess_module); /* * On note le niveau de verbosité */ conf->loglevel = atoi (a1); /* * Et c'est tout */ return NULL; } /* * EAccessCache */ static const char *eaccess_cfg_cache (cmd_parms *cmd, void *dummy, char *a1) { /* * La config actuelle */ eaccess_cfg *conf = (eaccess_cfg *) ap_get_module_config (cmd->server->module_config, &eaccess_module); /* * On note le nom du fichier */ conf->cachefile = a1; /* * Et c'est tout, on complètera le nom du fichier dans le init du module */ return NULL; } /* * EAccessOptim */ static const char *eaccess_cfg_optim (cmd_parms *cmd, void *dummy, char *a1) { /* * La config actuelle */ eaccess_cfg *conf = (eaccess_cfg *) ap_get_module_config (cmd->server->module_config, &eaccess_module); /* * On note le niveau d'optimisation */ conf->optim = atoi (a1); /* * Et c'est tout */ return NULL; } /* * Liste */ command_rec eaccess_cmds [] = { { "EAccessEnable", eaccess_cfg_enable, NULL, RSRC_CONF, FLAG, "On or Off to enable or disable (default) Extended Access control" }, { "EAccessRule", eaccess_cfg_rule, NULL, RSRC_CONF, TAKE23, "a action and a [!]URL-applied regexp-pattern (! for revert-match) " "and optional options (see docs)" }, { "EAccessLog", eaccess_cfg_log, NULL, RSRC_CONF, TAKE1, "the filename of the Extended Access control logfile" }, { "EAccessLogLevel", eaccess_cfg_loglevel, NULL, RSRC_CONF, TAKE1, "the level of the Extended Access control logfile verbosity" }, { "EAccessCache", eaccess_cfg_cache, NULL, RSRC_CONF, TAKE1, "the filename of the Extended Access control cachefile" }, { "EAccessOptim", eaccess_cfg_optim, NULL, RSRC_CONF, TAKE1, "the optimization level of the Extended Access control" }, {NULL} }; /* ************************************************************************ * Création de config ************************************************************************ */ static void *eaccess_create_srv_config (pool *p, server_rec *s) { eaccess_cfg *conf; /* * On crée une nouvelle config */ conf = (eaccess_cfg *) ap_pcalloc (p, sizeof (eaccess_cfg)); /* * On initialise les valeurs par défaut */ conf->state = EACCESS_DISABLED; conf->rules = ap_make_array (p, 2, sizeof (eaccess_rulentry)); conf->logfile = NULL; /* l'init du module affectera 1 valeur */ conf->logfd = -1; conf->loglevel = 0; conf->optim = 1; /* * Et on retourne cette config */ return (void *) conf; } /* ************************************************************************ * Définition du module ************************************************************************ */ module MODULE_VAR_EXPORT eaccess_module = { STANDARD_MODULE_STUFF, eaccess_init, /* initializer */ NULL, /* create per-directory config structure */ NULL, /* merge per-directory config structures */ eaccess_create_srv_config, /* create per-server config structure */ NULL, /* merge per-server config structures */ eaccess_cmds, /* command table */ NULL, /* handlers */ NULL, /* translate_handler */ NULL, /* check_user_id */ NULL, /* check auth */ eaccess_check, /* check access */ NULL, /* type_checker */ NULL, /* pre-run fixups */ NULL, /* logger */ NULL, /* header parser */ NULL, /* child_init */ NULL, /* child_exit */ NULL /* post read-request */ };