You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Marc Slemko <ma...@znep.com> on 1998/01/04 21:31:47 UTC

[PATCH] lets do 1.2.5

NOTE: please do not redistribute the below information.  I fully
support the concept of full disclosure, but let us get a release out
without all sorts of rumors starting to fly before it.  Thanks.

The below patch addresses several security problems in the Apache
code that Dean and I have found.  The below changes has been discussed
privately with most of the primary developers.

Once we get some votes on this (the patch and the suggested process), it
will be committed soon and a tarball rolled.  After a day or so to test,
it will be released as early in the week as we can reasonably manage.  The
idea is move very quickly getting this voted on, committed and the tarball
rolled, then give a bit of time to verify things before it goes out.

I am writing a security announcement to go with it, guess I may as well
do the 1.2.5 announcement as well.  Brian, can you please add marcs@znep.com
to those who can post to apache-announce.  

I need a volunteer to roll the tarball when we are ready.


Index: src/CHANGES
===================================================================
RCS file: /export/home/cvs/apache/src/CHANGES,v
retrieving revision 1.286.2.60
diff -u -r1.286.2.60 CHANGES
--- CHANGES	1997/12/30 18:59:30	1.286.2.60
+++ CHANGES	1998/01/04 20:26:56
@@ -1,7 +1,50 @@
 Changes with Apache 1.2.5
 
-  *) no2slash() was O(n^2) in the length of the input.  Make it O(n).
-     [Dean Gaudet]
+  *) SECURITY: Fix a possible buffer overflow in logresolve.  This is
+     only an issue on systems without a MAXDNAME define or where 
+     the resolver returns domain names longer than MAXDNAME.  [Marc Slemko]
+
+  *) Fix an improper length in an ap_snprintf call in proxy_date_canon().
+     [Marc Slemko]
+
+  *) Fix core dump in the ftp proxy when reading incorrectly formatted
+     directory listings.  [Marc Slemko]
+
+  *) SECURITY: Fix possible minor buffer overflow in the proxy cache.
+     [Marc Slemko]
+
+  *) SECURITY: Eliminate possible buffer overflow in cfg_getword, which
+     is used to read various types of files such as htaccess and 
+     htpasswd files.  [Marc Slemko]
+
+  *) SECURITY: Ensure that the buffer returned by ht_time is always
+     properly null terminated.  [Marc Slemko]
+
+  *) SECURITY: General mod_include cleanup, including fixing several
+     possible buffer overflows and a possible infinite loop.  This cleanup
+     was done against 1.3 code and then backported to 1.2, the result
+     is a large difference (due to indentation cleanup in 1.3 code).
+     Users interested in seeing a smaller set of relevant differences
+     should consider comparing against src/modules/standard/mod_include.c
+     from the 1.3b3 release.  Non-indentation changes to mod_include
+     between 1.2 and 1.3 were minimal.  [Dean Gaudet, Marc Slemko]
+
+  *) SECURITY: Numerous changes to mod_imap in a general cleanup
+     including fixing a possible buffer overflow.  This cleanup also
+     was done with 1.3 code as a basis, see the the previous note
+     about mod_include.  [Dean Gaudet]
+
+  *) SECURITY: If a htaccess file can not be read due to bad 
+     permissions, deny access to the directory with a HTTP_FORBIDDEN.  
+     The previous behavior was to ignore the htaccess file if it could not
+     be read.  This change may make some setups with unreadable
+     htaccess files stop working.  PR#817  [Marc Slemko]
+
+  *) SECURITY: no2slash() was O(n^2) in the length of the input.  
+     Make it O(n).  This inefficiency could be used to mount a denial 
+     of service attack against the Apache server.  Thanks to 
+     Michal Zalewski <lc...@boss.staszic.waw.pl> for reporting
+     this.  [Dean Gaudet]
 
   *) mod_include used uninitialized data for some uses of && and ||.
      [Brian Slesinsky <bs...@wired.com>] PR#1139
Index: src/http_config.c
===================================================================
RCS file: /export/home/cvs/apache/src/http_config.c,v
retrieving revision 1.49.2.2
diff -u -r1.49.2.2 http_config.c
--- http_config.c	1997/06/29 18:08:36	1.49.2.2
+++ http_config.c	1998/01/04 20:26:59
@@ -821,8 +821,16 @@
 	}
 	
 	*result = dc;
-    } else
-	dc = NULL;
+    } else {
+	if (errno == ENOENT || errno == ENOTDIR)
+	    dc = NULL;
+	else {
+	    log_unixerr("pfopen", filename,
+		"unable to check htaccess file, ensure it is readable",
+		r->server);
+	    return HTTP_FORBIDDEN;
+	}
+    }
 
 /* cache it */
     new = palloc(r->pool, sizeof(struct htaccess_result));
Index: src/mod_imap.c
===================================================================
RCS file: /export/home/cvs/apache/src/mod_imap.c,v
retrieving revision 1.21.2.2
diff -u -r1.21.2.2 mod_imap.c
--- mod_imap.c	1997/11/05 11:43:14	1.21.2.2
+++ mod_imap.c	1998/01/04 20:27:11
@@ -20,7 +20,8 @@
  *
  * 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.
+ *    prior written permission. For written permission, please contact
+ *    apache@apache.org.
  *
  * 5. Redistributions of any form whatsoever must retain the following
  *    acknowledgment:
@@ -96,8 +97,6 @@
 #include "util_script.h"
 
 #define IMAP_MAGIC_TYPE "application/x-httpd-imap"
-#define LARGEBUF 500
-#define SMALLBUF 256
 #define MAXVERTS 100
 #define X 0
 #define Y 1
@@ -107,62 +106,65 @@
 #define IMAP_BASE_DEFAULT "map"
 
 #ifdef SUNOS4
-double strtod();   /* SunOS needed this */
+double strtod();                /* SunOS needed this */
 #endif
 
 module imap_module;
 
-typedef struct { 
-  char *imap_menu;
-  char *imap_default;
-  char *imap_base;
+typedef struct {
+    char *imap_menu;
+    char *imap_default;
+    char *imap_base;
 } imap_conf_rec;
 
-void *create_imap_dir_config (pool *p, char *dummy) { 
-  imap_conf_rec *icr = 
-    (imap_conf_rec *)palloc(p, sizeof(imap_conf_rec));
-
-  icr->imap_menu = NULL;
-  icr->imap_default = NULL;
-  icr->imap_base = NULL;
-
-  return icr;
-}
-
-void *merge_imap_dir_configs (pool *p, void *basev, void *addv)
-{
-  imap_conf_rec *new=(imap_conf_rec *)pcalloc (p, sizeof(imap_conf_rec));
-  imap_conf_rec *base = (imap_conf_rec *)basev;
-  imap_conf_rec *add = (imap_conf_rec *)addv;
- 
-  new->imap_menu = add->imap_menu ? add->imap_menu : base->imap_menu;
-  new->imap_default=add->imap_default ? add->imap_default : base->imap_default;
-  new->imap_base =add-> imap_base ? add->imap_base : base->imap_base;
-
-  return new;
+static void *create_imap_dir_config(pool *p, char *dummy)
+{
+    imap_conf_rec *icr =
+    (imap_conf_rec *) palloc(p, sizeof(imap_conf_rec));
+
+    icr->imap_menu = NULL;
+    icr->imap_default = NULL;
+    icr->imap_base = NULL;
+
+    return icr;
+}
+
+static void *merge_imap_dir_configs(pool *p, void *basev, void *addv)
+{
+    imap_conf_rec *new = (imap_conf_rec *) pcalloc(p, sizeof(imap_conf_rec));
+    imap_conf_rec *base = (imap_conf_rec *) basev;
+    imap_conf_rec *add = (imap_conf_rec *) addv;
+
+    new->imap_menu = add->imap_menu ? add->imap_menu : base->imap_menu;
+    new->imap_default = add->imap_default ? add->imap_default : base->imap_default;
+    new->imap_base = add->imap_base ? add->imap_base : base->imap_base;
+
+    return new;
 }
 
 
-command_rec imap_cmds[] = {
-{ "ImapMenu", set_string_slot, 
-    (void*)XtOffsetOf(imap_conf_rec, imap_menu), OR_INDEXES, TAKE1,
-    "the type of menu generated: none, formatted, semiformatted, unformatted"},
-{ "ImapDefault", set_string_slot, 
-    (void*)XtOffsetOf(imap_conf_rec, imap_default), OR_INDEXES, TAKE1,
-    "the action taken if no match: error, nocontent, referer, menu, URL" },
-{ "ImapBase", set_string_slot, 
-    (void*)XtOffsetOf(imap_conf_rec, imap_base), OR_INDEXES, TAKE1,
-    "the base for all URL's: map, referer, URL (or start of)" },
-{ NULL }
+static command_rec imap_cmds[] =
+{
+    {"ImapMenu", set_string_slot,
+     (void *) XtOffsetOf(imap_conf_rec, imap_menu), OR_INDEXES, TAKE1,
+ "the type of menu generated: none, formatted, semiformatted, unformatted"},
+    {"ImapDefault", set_string_slot,
+     (void *) XtOffsetOf(imap_conf_rec, imap_default), OR_INDEXES, TAKE1,
+     "the action taken if no match: error, nocontent, referer, menu, URL"},
+    {"ImapBase", set_string_slot,
+     (void *) XtOffsetOf(imap_conf_rec, imap_base), OR_INDEXES, TAKE1,
+     "the base for all URL's: map, referer, URL (or start of)"},
+    {NULL}
 };
 
-int pointinrect(double point[2], double coords[MAXVERTS][2])
+static int pointinrect(const double point[2], const double coords[MAXVERTS][2])
 {
     double max[2], min[2];
     if (coords[0][X] > coords[1][X]) {
         max[0] = coords[0][X];
         min[0] = coords[1][X];
-    } else {
+    }
+    else {
         max[0] = coords[1][X];
         min[0] = coords[0][X];
     }
@@ -170,33 +172,35 @@
     if (coords[0][Y] > coords[1][Y]) {
         max[1] = coords[0][Y];
         min[1] = coords[1][Y];
-    } else {
+    }
+    else {
         max[1] = coords[1][Y];
         min[1] = coords[0][Y];
     }
 
     return ((point[X] >= min[0] && point[X] <= max[0]) &&
-	    (point[Y] >= min[1] && point[Y] <= max[1]));
+            (point[Y] >= min[1] && point[Y] <= max[1]));
 }
 
-int pointincircle(double point[2], double coords[MAXVERTS][2])
+static int pointincircle(const double point[2], const double coords[MAXVERTS][2])
 {
-    int radius1, radius2;
+    double radius1, radius2;
 
     radius1 = ((coords[0][Y] - coords[1][Y]) * (coords[0][Y] - coords[1][Y]))
-	+ ((coords[0][X] - coords[1][X]) * (coords[0][X] - coords[1][X]));
-    
+        + ((coords[0][X] - coords[1][X]) * (coords[0][X] - coords[1][X]));
+
     radius2 = ((coords[0][Y] - point[Y]) * (coords[0][Y] - point[Y]))
-	+ ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
+        + ((coords[0][X] - point[X]) * (coords[0][X] - point[X]));
 
     return (radius2 <= radius1);
 }
 
-int pointinpoly(double point[2], double pgon[MAXVERTS][2])
+static int pointinpoly(const double point[2], const double pgon[MAXVERTS][2])
 {
     int i, numverts, inside_flag, xflag0;
     int crossings;
-    double *p, *stop;
+    double *p;
+    const double *stop;
     double tx, ty, y;
 
     for (i = 0; pgon[i][X] != -1 && i < MAXVERTS; i++);
@@ -211,54 +215,54 @@
     p = (double *) pgon + 1;
     if ((y >= ty) != (*p >= ty)) {
 
-	if ((xflag0 = (pgon[numverts - 1][X] >= tx)) == (*(double *) pgon >= tx)) {
-	    if (xflag0)
-		crossings++;
-	}
-	else {
-	    crossings += (pgon[numverts - 1][X] - (y - ty) *
-			  (*(double *) pgon - pgon[numverts - 1][X]) /
-			  (*p - y)) >= tx;
-	}
+        if ((xflag0 = (pgon[numverts - 1][X] >= tx)) == (*(double *) pgon >= tx)) {
+            if (xflag0)
+                crossings++;
+        }
+        else {
+            crossings += (pgon[numverts - 1][X] - (y - ty) *
+                          (*(double *) pgon - pgon[numverts - 1][X]) /
+                          (*p - y)) >= tx;
+        }
     }
 
     stop = pgon[numverts];
 
     for (y = *p, p += 2; p < stop; y = *p, p += 2) {
-	
-	if (y >= ty) {
-        
-	    while ((p < stop) && (*p >= ty))
-		p += 2;
-	    
-	    if (p >= stop)
-		break;
-	    if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx)) {
-		
-		if (xflag0)
-		    crossings++;
-	    }
-	    else {
-		crossings += (*(p - 3) - (*(p - 2) - ty) *
-			      (*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
-	    }
-	}
-	else {
-	    while ((p < stop) && (*p < ty))
-		p += 2;
 
-	    if (p >= stop)
-		break;
+        if (y >= ty) {
 
-	    if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx)) {
-		if (xflag0)
-		    crossings++;
-	    }
-	    else {
-		crossings += (*(p - 3) - (*(p - 2) - ty) *
-			      (*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
-	    }
-	}
+            while ((p < stop) && (*p >= ty))
+                p += 2;
+
+            if (p >= stop)
+                break;
+            if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx)) {
+
+                if (xflag0)
+                    crossings++;
+            }
+            else {
+                crossings += (*(p - 3) - (*(p - 2) - ty) *
+                              (*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
+            }
+        }
+        else {
+            while ((p < stop) && (*p < ty))
+                p += 2;
+
+            if (p >= stop)
+                break;
+
+            if ((xflag0 = (*(p - 3) >= tx)) == (*(p - 1) >= tx)) {
+                if (xflag0)
+                    crossings++;
+            }
+            else {
+                crossings += (*(p - 3) - (*(p - 2) - ty) *
+                              (*(p - 1) - *(p - 3)) / (*p - *(p - 2))) >= tx;
+            }
+        }
     }
 
     inside_flag = crossings & 0x01;
@@ -266,580 +270,607 @@
 }
 
 
-int is_closer(double point[2], double coords[MAXVERTS][2], double *closest)
+static int is_closer(const double point[2], const double coords[MAXVERTS][2], double *closest)
 {
-  double dist_squared =((point[X] - coords[0][X]) * (point[X] - coords[0][X]))
-	     + ((point[Y] - coords[0][Y]) * (point[Y] - coords[0][Y]));
+    double dist_squared = ((point[X] - coords[0][X]) * (point[X] - coords[0][X]))
+    + ((point[Y] - coords[0][Y]) * (point[Y] - coords[0][Y]));
+
+    if (point[X] < 0 || point[Y] < 0)
+        return (0);             /* don't mess around with negative coordinates */
 
-  if (point[X] < 0 || point[Y] < 0 ) 
-    return(0);          /* don't mess around with negative coordinates */
+    if (*closest < 0 || dist_squared < *closest) {
+        *closest = dist_squared;
+        return (1);             /* if this is the first point or is the closest yet
+                                   set 'closest' equal to this distance^2 */
+    }
 
-  if ( *closest < 0 || dist_squared < *closest ) {
-    *closest = dist_squared;
-    return(1);         /* if this is the first point or is the closest yet
-			  set 'closest' equal to this distance^2 */
-  }
-  
-  return(0);           /* if it's not the first or closest */
+    return (0);                 /* if it's not the first or closest */
 
 }
 
-double get_x_coord(char *args) 
+static double get_x_coord(const char *args)
 {
-  char *endptr;           /* we want it non-null */
-  double x_coord = -1;    /* -1 is returned if no coordinate is given */
+    char *endptr;               /* we want it non-null */
+    double x_coord = -1;        /* -1 is returned if no coordinate is given */
 
-  if (args == NULL)
-    return(-1);           /* in case we aren't passed anything */
+    if (args == NULL)
+        return (-1);            /* in case we aren't passed anything */
 
-  while( *args && !isdigit(*args) && *args != ',') 
-    args++;   /* jump to the first digit, but not past a comma or end */
+    while (*args && !isdigit(*args) && *args != ',')
+        args++;                 /* jump to the first digit, but not past a comma or end */
 
-  x_coord = strtod(args, &endptr);
+    x_coord = strtod(args, &endptr);
 
-  if (endptr > args)   /* if a conversion was made */
-    return(x_coord); 
+    if (endptr > args)          /* if a conversion was made */
+        return (x_coord);
 
-  return(-1);  /* else if no conversion was made, or if no args was given */
+    return (-1);                /* else if no conversion was made, or if no args was given */
 }
 
-double get_y_coord(char *args) 
+static double get_y_coord(const char *args)
 {
-  char *endptr;        /* we want it non-null */
-  char *start_of_y = NULL;
-  double y_coord = -1;    /* -1 is returned on error */
+    char *endptr;               /* we want it non-null */
+    char *start_of_y = NULL;
+    double y_coord = -1;        /* -1 is returned on error */
 
-  if (args == NULL)
-    return(-1);           /* in case we aren't passed anything */
+    if (args == NULL)
+        return (-1);            /* in case we aren't passed anything */
 
-  start_of_y = strchr(args, ',');  /* the comma */
+    start_of_y = strchr(args, ',');     /* the comma */
 
-  if (start_of_y) {
-    
-    start_of_y++;    /* start looking at the character after the comma */
+    if (start_of_y) {
 
-    while( *start_of_y && !isdigit(*start_of_y))  
-      start_of_y++;  /* jump to the first digit, but not past the end */
+        start_of_y++;           /* start looking at the character after the comma */
 
-    y_coord = strtod(start_of_y, &endptr);
+        while (*start_of_y && !isdigit(*start_of_y))
+            start_of_y++;       /* jump to the first digit, but not past the end */
+
+        y_coord = strtod(start_of_y, &endptr);
+
+        if (endptr > start_of_y)
+            return (y_coord);
+    }
 
-    if (endptr > start_of_y) 
-      return(y_coord); 
-  }
-  
-  return(-1);   /* if no conversion was made, or no comma was found in args */
+    return (-1);                /* if no conversion was made, or no comma was found in args */
 }
-  
 
-int read_quoted(char *string, char *quoted_part)
-{ 
-  char *starting_pos = string;
-  
-  while ( isspace(*string) )
-    string++;    /* go along string until non-whitespace */
 
-  if ( *string == '"' ) { /* if that character is a double quote */
+/* See if string has a "quoted part", and if so set *quoted_part to
+ * the first character of the quoted part, then hammer a \0 onto the
+ * trailing quote, and set *string to point at the first character
+ * past the second quote.
+ *
+ * Otherwise set *quoted_part to NULL, and leave *string alone.
+ */
+static void read_quoted(char **string, char **quoted_part)
+{
+    char *strp = *string;
 
-    string++;  /* step over it */
+    /* assume there's no quoted part */
+    *quoted_part = NULL;
 
-    while ( *string && *string != '"' ) {
-      *quoted_part++ = *string++;  /* copy the quoted portion */
-    }
+    while (isspace(*strp))
+        strp++;               	/* go along string until non-whitespace */
+
+    if (*strp == '"') {       	/* if that character is a double quote */
+        strp++;               	/* step over it */
+	*quoted_part = strp;  	/* note where the quoted part begins */
+
+        while (*strp && *strp != '"') {
+	    ++strp;		/* skip the quoted portion */
+        }
 
-    *quoted_part = '\0';  /* end the string with a SNUL */
-	
-    string++;  /* step over the last double quote */
-  }
+        *strp = '\0';    	/* end the string with a NUL */
 
-  return(string - starting_pos); /* return the total characters read */
+        strp++;               	/* step over the last double quote */
+	*string = strp;
+    }
 }
 
 /*
- * url needs to point to a string with at least SMALLBUF memory allocated
+ * returns the mapped URL or NULL.
  */
-void imap_url(request_rec *r, char *base, char *value, char *url) 
+static char *imap_url(request_rec *r, const char *base, const char *value)
 {
 /* translates a value into a URL. */
-  int slen, clen;
-  char *string_pos = NULL;
-  char *directory = NULL;
-  char *referer = NULL;
-  char my_base[SMALLBUF] = {'\0'};
-
-  if ( ! strcasecmp(value, "map" ) || ! strcasecmp(value, "menu") ) {
-    if (r->server->port == DEFAULT_PORT ) { 
-      ap_snprintf(url, SMALLBUF,
-		"http://%s%s", r->server->server_hostname, r->uri);
+    int slen, clen;
+    char *string_pos = NULL;
+    const char *string_pos_const = NULL;
+    char *directory = NULL;
+    char *referer = NULL;
+    char *my_base;
+
+    if (!strcasecmp(value, "map") || !strcasecmp(value, "menu")) {
+	return construct_url(r->pool, r->uri, r->server);
+    }
+
+    if (!strcasecmp(value, "nocontent") || !strcasecmp(value, "error")) {
+        return pstrdup(r->pool, value);                 /* these are handled elsewhere, so just copy them */
+    }
+
+    if (!strcasecmp(value, "referer")) {
+        referer = table_get(r->headers_in, "Referer");
+        if (referer && *referer) {
+	    return pstrdup(r->pool, referer);
+        }
+        else {
+	    /* XXX:  This used to do *value = '\0'; ... which is totally bogus
+	     * because it hammers the passed in value, which can be a string constant,
+	     * or part of a config, or whatever.  Total garbage.  This works around
+	     * that without changing the rest of this code much
+	     */
+            value = "";      /* if 'referer' but no referring page, null the value */
+        }
+    }
+
+    string_pos_const = value;
+    while (isalpha(*string_pos_const))
+	string_pos_const++;           /* go along the URL from the map until a non-letter */
+    if (*string_pos_const == ':') {
+	/* if letters and then a colon (like http:) */
+	/* it's an absolute URL, so use it! */
+	return pstrdup(r->pool, value);
+    }
+
+    if (!base || !*base) {
+        if (value && *value) {
+	    return pstrdup(r->pool, value); /* no base: use what is given */
+        }
+	/* no base, no value: pick a simple default */
+	return construct_url(r->pool, "/", r->server);
+    }
+
+    /* must be a relative URL to be combined with base */
+    if (strchr(base, '/') == NULL && (!strncmp(value, "../", 3) || !strcmp(value, ".."))) {
+        log_reason("invalid base directive in map file: %s", r->uri, r);
+        return NULL;
+    }
+    my_base = pstrdup(r->pool, base);
+    string_pos = my_base;
+    while (*string_pos) {
+        if (*string_pos == '/' && *(string_pos + 1) == '/') {
+            string_pos += 2;    /* if there are two slashes, jump over them */
+            continue;
+        }
+        if (*string_pos == '/') {       /* the first single slash */
+            if (value[0] == '/') {
+                *string_pos = '\0';
+            }                   /* if the URL from the map starts from root, end the
+                                   base URL string at the first single slash */
+            else {
+                directory = string_pos;         /* save the start of the directory portion */
+
+                string_pos = strrchr(string_pos, '/');  /* now reuse string_pos */
+                string_pos++;   /* step over that last slash */
+                *string_pos = '\0';
+            }                   /* but if the map url is relative, leave the
+                                   slash on the base (if there is one) */
+            break;
+        }
+        string_pos++;           /* until we get to the end of my_base without finding
+                                   a slash by itself */
+    }
+
+    while (!strncmp(value, "../", 3) || !strcmp(value, "..")) {
+
+        if (directory && (slen = strlen(directory))) {
+
+            /* for each '..',  knock a directory off the end 
+               by ending the string right at the last slash.
+               But only consider the directory portion: don't eat
+               into the server name.  And only try if a directory
+               portion was found */
+
+            clen = slen - 1;
+
+            while ((slen - clen) == 1) {
+
+                if ((string_pos = strrchr(directory, '/')))
+                    *string_pos = '\0';
+                clen = strlen(directory);
+                if (clen == 0)
+                    break;
+            }
+
+            value += 2;         /* jump over the '..' that we found in the value */
+        }
+        else if (directory) {
+            log_reason("invalid directory name in map file: %s", r->uri, r);
+            return NULL;
+        }
+
+        if (!strncmp(value, "/../", 4) || !strcmp(value, "/.."))
+            value++;            /* step over the '/' if there are more '..' to do.
+                                   this way, we leave the starting '/' on value after
+                                   the last '..', but get rid of it otherwise */
+
+    }                           /* by this point, value does not start with '..' */
+
+    if (value && *value) {
+	return pstrcat(r->pool, my_base, value, NULL);
+    }
+    return my_base;
+}
+
+static int imap_reply(request_rec *r, char *redirect)
+{
+    if (!strcasecmp(redirect, "error")) {
+        return SERVER_ERROR;    /* they actually requested an error! */
+    }
+    if (!strcasecmp(redirect, "nocontent")) {
+        return HTTP_NO_CONTENT; /* tell the client to keep the page it has */
+    }
+    if (redirect && *redirect) {
+        table_set(r->headers_out, "Location", redirect);
+        return REDIRECT;        /* must be a URL, so redirect to it */
+    }
+    return SERVER_ERROR;
+}
+
+static void menu_header(request_rec *r, char *menu)
+{
+    r->content_type = "text/html";
+    send_http_header(r);
+    hard_timeout("send menu", r);       /* killed in menu_footer */
+
+    rvputs(r, "<html><head>\n<title>Menu for ", r->uri,
+           "</title>\n</head><body>\n", NULL);
+
+    if (!strcasecmp(menu, "formatted")) {
+        rvputs(r, "<h1>Menu for ", r->uri, "</h1>\n<hr>\n\n", NULL);
     }
-    else {
-      ap_snprintf(url, SMALLBUF, "http://%s:%d%s", r->server->server_hostname,
-	      r->server->port, r->uri);      
+
+    return;
+}
+
+static void menu_blank(request_rec *r, char *menu)
+{
+    if (!strcasecmp(menu, "formatted")) {
+        rputs("\n", r);
+    }
+    if (!strcasecmp(menu, "semiformatted")) {
+        rputs("<br>\n", r);
+    }
+    if (!strcasecmp(menu, "unformatted")) {
+        rputs("\n", r);
+    }
+    return;
+}
+
+static void menu_comment(request_rec *r, char *menu, char *comment)
+{
+    if (!strcasecmp(menu, "formatted")) {
+        rputs("\n", r);         /* print just a newline if 'formatted' */
+    }
+    if (!strcasecmp(menu, "semiformatted") && *comment) {
+        rvputs(r, comment, "\n", NULL);
+    }
+    if (!strcasecmp(menu, "unformatted") && *comment) {
+        rvputs(r, comment, "\n", NULL);
     }
-    return;  
-  }
+    return;                     /* comments are ignored in the 'formatted' form */
+}
 
-  if ( ! strcasecmp(value, "nocontent") || ! strcasecmp(value, "error") ) {
-    strncpy(url, value, SMALLBUF-1);
-    url[SMALLBUF-1] = '\0';
-    return;    /* these are handled elsewhere, so just copy them */
-  }
-
-  if ( ! strcasecmp(value, "referer" ) ) {
-    referer = table_get(r->headers_in, "Referer");
-    if ( referer && *referer ) {
-      strncpy(url, referer, SMALLBUF-1);
-      url[SMALLBUF-1] = '\0';
-      return;
+static void menu_default(request_rec *r, char *menu, char *href, char *text)
+{
+    if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
+        return;                 /* don't print such lines, these aren'te really href's */
+    }
+    if (!strcasecmp(menu, "formatted")) {
+        rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text, "</a></pre>\n",
+               NULL);
+    }
+    if (!strcasecmp(menu, "semiformatted")) {
+        rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text, "</a></pre>\n",
+               NULL);
+    }
+    if (!strcasecmp(menu, "unformatted")) {
+        rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
     }
-    else {
-      *value = '\0';  /* if 'referer' but no referring page, null the value */
-    }                 
-  }         
-
-  string_pos = value;
-  while ( isalpha(*string_pos) )
-    string_pos++;    /* go along the URL from the map until a non-letter */
-  if ( *string_pos == ':' ) { 
-    strncpy(url, value, SMALLBUF-1);        /* if letters and then a colon (like http:) */
-    url[SMALLBUF-1] = '\0';
-    return;                    /* it's an absolute URL, so use it! */
-  }
-
-  if ( ! base || ! *base ) {
-    if ( value && *value ) {  
-      strncpy(url, value, SMALLBUF-1);   /* no base: use what is given */
-      url[SMALLBUF-1] = '\0';
-    }         
-    else {                  
-      if (r->server->port == DEFAULT_PORT ) {  
-	ap_snprintf(url, SMALLBUF, "http://%s/", r->server->server_hostname);
-      }            
-      if (r->server->port != DEFAULT_PORT ) {
-	ap_snprintf(url, SMALLBUF, "http://%s:%d/",
-		r->server->server_hostname, r->server->port);
-      }                     /* no base, no value: pick a simple default */
-    }
-    return;  
-  }
-
-  strncpy(my_base, base, sizeof(my_base)-1);  /* must be a relative URL to be combined with base */
-  my_base[sizeof(my_base)-1] = '\0';
-  if (strchr(my_base, '/') == NULL && (!strncmp(value, "../", 3) || !strcmp(value, "..")) ) {
-    url[0] = '\0';
-    log_reason("invalid base directive in map file", r->uri, r);
     return;
-  }
-  string_pos = my_base; 
-  while (*string_pos) {  
-    if (*string_pos == '/' && *(string_pos+1) == '/') {
-      string_pos += 2;  /* if there are two slashes, jump over them */
-      continue;
-    }
-    if (*string_pos == '/') {  /* the first single slash */
-	if ( value[0] == '/' ) {
-	  *string_pos = '\0';  
-	}              /* if the URL from the map starts from root, end the
-			  base URL string at the first single slash */
-	else {
-	  directory = string_pos; /* save the start of the directory portion */
+}
+
+static void menu_directive(request_rec *r, char *menu, char *href, char *text)
+{
+    if (!strcasecmp(href, "error") || !strcasecmp(href, "nocontent")) {
+        return;                 /* don't print such lines, as this isn't really an href */
+    }
+    if (!strcasecmp(menu, "formatted")) {
+        rvputs(r, "<pre>          <a href=\"", href, "\">", text, "</a></pre>\n",
+               NULL);
+    }
+    if (!strcasecmp(menu, "semiformatted")) {
+        rvputs(r, "<pre>          <a href=\"", href, "\">", text, "</a></pre>\n",
+               NULL);
+    }
+    if (!strcasecmp(menu, "unformatted")) {
+        rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
+    }
+    return;
+}
+
+static void menu_footer(request_rec *r)
+{
+    rputs("\n\n</body>\n</html>\n", r);         /* finish the menu */
+    kill_timeout(r);
+}
 
-	  string_pos = strrchr(string_pos, '/');  /* now reuse string_pos */
-	  string_pos++;  /* step over that last slash */
-	  *string_pos = '\0';
-	}              /* but if the map url is relative, leave the
-			slash on the base (if there is one) */
-	break;
-      }
-    string_pos++;   /* until we get to the end of my_base without finding
-		       a slash by itself */
-  }
-
-  while ( ! strncmp(value, "../", 3) || ! strcmp(value, "..") ) { 
-
-      if (directory && (slen = strlen (directory))) {
-
-	  /* for each '..',  knock a directory off the end 
-	     by ending the string right at the last slash.
-	     But only consider the directory portion: don't eat
-	     into the server name.  And only try if a directory
-	     portion was found */    
-	  
-	  clen = slen - 1;
-	
-	  while ((slen - clen) == 1) {
-	
-	      if ((string_pos = strrchr(directory, '/')))
-		  *string_pos = '\0';
-	      clen = strlen (directory);
-	      if (clen == 0) break;
-	  }
-
-	  value += 2;      /* jump over the '..' that we found in the value */
-      } else if (directory) {
-	url[0] = '\0';
-	log_reason("invalid directory name in map file", r->uri, r);
-	return;
-      }
-      
-      if (! strncmp(value, "/../", 4) || ! strcmp(value, "/..") )
-
-	  value++;       /* step over the '/' if there are more '..' to do.
-			   this way, we leave the starting '/' on value after
-			   the last '..', but get rid of it otherwise */ 
-     
-  }                   /* by this point, value does not start with '..' */
-
-  if ( value && *value ) {
-    ap_snprintf(url, SMALLBUF, "%s%s", my_base, value);   
-  }
-  else {
-    ap_snprintf(url, SMALLBUF, "%s", my_base);   
-  }
-  return;
-}
-
-int imap_reply(request_rec *r, char *redirect)
-{ 
-  if ( ! strcasecmp(redirect, "error") ) {
-    return SERVER_ERROR;  /* they actually requested an error! */
-  }
-  if ( ! strcasecmp(redirect, "nocontent") ) {
-    return HTTP_NO_CONTENT; /* tell the client to keep the page it has */
-  }
-  if (redirect && *redirect ) { 
-    table_set(r->headers_out, "Location", redirect);
-    return REDIRECT;      /* must be a URL, so redirect to it */
-  }    
-  return SERVER_ERROR;
-}
-
-void menu_header(request_rec *r, char *menu)
-{
-  r->content_type = "text/html";
-  send_http_header(r);
-  hard_timeout("send menu", r);   /* killed in menu_footer */
-
-  rvputs(r, "<html><head>\n<title>Menu for ", r->uri,
-	    "</title>\n</head><body>\n", NULL);
-
-  if (!strcasecmp(menu, "formatted")) {
-    rvputs(r, "<h1>Menu for ", r->uri, "</h1>\n<hr>\n\n", NULL);
-  } 
-
-  return;
-}
-
-void menu_blank(request_rec *r, char *menu)
-{
-  if (! strcasecmp(menu, "formatted") ) {
-    rputs("\n", r);
-  }
-  if (! strcasecmp(menu, "semiformatted") ) {
-    rputs("<br>\n", r);
-  }
-  if (! strcasecmp(menu, "unformatted") ) {
-    rputs("\n", r);  
-  }
-  return;  
-}
-
-void menu_comment(request_rec *r, char *menu, char *comment)
-{
-  if (! strcasecmp(menu, "formatted") ) {
-    rputs("\n", r);  /* print just a newline if 'formatted' */
-  }
-  if (! strcasecmp(menu, "semiformatted") && *comment ) {
-    rvputs(r, comment, "\n", NULL);
-  }             
-  if (! strcasecmp(menu, "unformatted") && *comment ) {
-    rvputs(r, comment, "\n", NULL);
-  }             
-  return;    /* comments are ignored in the 'formatted' form */
-}
-
-void menu_default(request_rec *r, char *menu, char *href, char *text)
-{
-  if ( ! strcasecmp(href, "error") || ! strcasecmp(href, "nocontent") ) {
-    return;   /* don't print such lines, these aren'te really href's */
-  }
-  if ( ! strcasecmp(menu, "formatted" ) ) {
-    rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text, "</a></pre>\n",
-	   NULL);
-  }
-  if ( ! strcasecmp(menu, "semiformatted" ) ) {
-    rvputs(r, "<pre>(Default) <a href=\"", href, "\">", text, "</a></pre>\n",
-	   NULL);
-  }
-  if ( ! strcasecmp(menu, "unformatted" ) ) {
-    rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
-  }
-  return;
-}
-
-void menu_directive(request_rec *r, char *menu, char *href, char *text)
-{
-  if ( ! strcasecmp(href, "error") || ! strcasecmp(href, "nocontent") ) {
-    return;   /* don't print such lines, as this isn't really an href */
-  }
-  if ( ! strcasecmp(menu, "formatted" ) ) {
-    rvputs(r, "<pre>          <a href=\"", href, "\">", text, "</a></pre>\n",
-	   NULL);
-  }
-  if ( ! strcasecmp(menu, "semiformatted" ) ) {
-    rvputs(r, "<pre>          <a href=\"", href, "\">", text, "</a></pre>\n",
-	   NULL);
-  }
-  if ( ! strcasecmp(menu, "unformatted" ) ) {
-    rvputs(r, "<a href=\"", href, "\">", text, "</a>", NULL);
-  }
-  return;
-}
-
-void menu_footer(request_rec *r)
-{
-  rputs("\n\n</body>\n</html>\n", r);  /* finish the menu */
-  kill_timeout(r);
-}
-
-int imap_handler(request_rec *r)
-{
-  char input[LARGEBUF] = {'\0'};
-	/* size of input can not be lowered without changing hard-coded
-	 * checks
+static int imap_handler(request_rec *r)
+{
+    char input[MAX_STRING_LEN];
+    char *directive;
+    char *value;
+    char *href_text;
+    char *base;
+    char *redirect;
+    char *mapdflt;
+    char *closest = NULL;
+    double closest_yet = -1;
+
+    double testpoint[2];
+    double pointarray[MAXVERTS + 1][2];
+    int vertex;
+
+    char *string_pos;
+    int showmenu = 0;
+
+    imap_conf_rec *icr = get_module_config(r->per_dir_config, &imap_module);
+
+    char *imap_menu = icr->imap_menu ? icr->imap_menu : IMAP_MENU_DEFAULT;
+    char *imap_default = icr->imap_default
+			    ?  icr->imap_default : IMAP_DEFAULT_DEFAULT;
+    char *imap_base = icr->imap_base ? icr->imap_base : IMAP_BASE_DEFAULT;
+
+    FILE *imap; 
+
+    if (r->method_number != M_GET)
+	return DECLINED;
+
+    imap = pfopen(r->pool, r->filename, "r"); 
+
+    if (!imap)
+        return NOT_FOUND;
+
+    base = imap_url(r, NULL, imap_base);         /* set base according to default */
+    if (!base)
+	return HTTP_INTERNAL_SERVER_ERROR;
+    mapdflt = imap_url(r, NULL, imap_default);   /* and default to global default */
+    if (!mapdflt)
+	return HTTP_INTERNAL_SERVER_ERROR;
+
+    testpoint[X] = get_x_coord(r->args);
+    testpoint[Y] = get_y_coord(r->args);
+
+    if ((testpoint[X] == -1 || testpoint[Y] == -1) ||
+        (testpoint[X] == 0 && testpoint[Y] == 0)) {
+        /* if either is -1 or if both are zero (new Lynx) */
+        /* we don't have valid coordinates */
+        testpoint[X] = -1;
+        testpoint[Y] = -1;
+        if (strncasecmp(imap_menu, "none", 2))
+            showmenu = 1;       /* show the menu _unless_ ImapMenu is 'none' or 'no' */
+    }
+
+    if (showmenu) {             /* send start of imagemap menu if we're going to */
+        menu_header(r, imap_menu);
+    }
+
+    while (!cfg_getline(input, sizeof(input), imap)) {
+        if (!input[0]) {
+            if (showmenu) {
+                menu_blank(r, imap_menu);
+            }
+            continue;
+        }
+
+        if (input[0] == '#') {
+            if (showmenu) {
+                menu_comment(r, imap_menu, input + 1);
+            }
+            continue;
+        }                       /* blank lines and comments are ignored if we aren't printing a menu */
+
+	/* find the first two space delimited fields, recall that
+	 * cfg_getline has removed leading/trailing whitespace and
+	 * compressed the other whitespace down to one space a piece
+	 *
+	 * note that we're tokenizing as we go... if we were to use the
+	 * getword() class of functions we would end up allocating extra
+	 * memory for every line of the map file
 	 */
-  char href_text[SMALLBUF] = {'\0'};
-  char base[SMALLBUF] = {'\0'};
-  char redirect[SMALLBUF] = {'\0'};
-  char directive[SMALLBUF] = {'\0'};
-  char value[SMALLBUF] = {'\0'};
-  char mapdflt[SMALLBUF] = {'\0'};
-  char closest[SMALLBUF] = {'\0'};
-  double closest_yet = -1;
-
-  double testpoint[2] = { -1,-1 }; 
-  double pointarray[MAXVERTS + 1][2] = { {-1,-1} };
-  int vertex = 0;
-
-  char *string_pos = NULL;
-  int chars_read = 0;
-  int showmenu = 0;
-
-  imap_conf_rec *icr = get_module_config(r->per_dir_config, &imap_module);
-
-  char *imap_menu = icr->imap_menu ? 
-    icr->imap_menu : IMAP_MENU_DEFAULT;
-  char *imap_default = icr->imap_default ? 
-    icr->imap_default : IMAP_DEFAULT_DEFAULT;
-  char *imap_base = icr->imap_base ?
-    icr->imap_base : IMAP_BASE_DEFAULT;
-
-  FILE *imap; 
-
-  if (r->method_number != M_GET) return DECLINED;
-
-  imap = pfopen(r->pool, r->filename, "r"); 
-
-  if ( ! imap ) 
-    return NOT_FOUND;
-
-  imap_url(r, NULL, imap_base, base);       /* set base according to default */
-  imap_url(r, NULL, imap_default, mapdflt); /* and default to global default */
-
-  testpoint[X] = get_x_coord(r->args);
-  testpoint[Y] = get_y_coord(r->args);
-
-  if ((testpoint[X] == -1 || testpoint[Y] == -1) ||
-      (testpoint[X] == 0  && testpoint[Y] == 0) ) {
-              /* if either is -1 or if both are zero (new Lynx) */
-              /* we don't have valid coordinates */
-    testpoint[X] = -1;
-    testpoint[Y] = -1;
-    if ( strncasecmp(imap_menu, "none", 2) )
-      showmenu = 1;    /* show the menu _unless_ ImapMenu is 'none' or 'no' */
-  }
-
-  if (showmenu) {        /* send start of imagemap menu if we're going to */
-    menu_header(r, imap_menu);
-  }
-
-  while (!cfg_getline(input, LARGEBUF, imap)) {
-    string_pos = input;   /* always start at the beginning of line */
-
-    directive[0] = '\0';
-    value[0] = '\0';  
-    href_text[0] = '\0';
-    redirect[0] = '\0';
-    chars_read = 0; /* clear these before using */
-
-    if ( ! input[0] ) {     
-      if (showmenu) {
-	menu_blank(r, imap_menu);
-      }
-      continue;                           
-    }
-
-    if ( input[0] == '#' ) {
-      if (showmenu) {
-	menu_comment(r, imap_menu, input + 1); 
-      }           
-      continue;
-    } /* blank lines and comments are ignored if we aren't printing a menu */
-
-
-    if (sscanf(input, "%255s %255s", directive, value) != 2) {
-      continue;                           /* make sure we read two fields */
-    }
-    /* Now skip what we just read... we can't use ANSIism %n */
-    while (!(isspace(*string_pos)))	/* past directive */
-	string_pos++;
-    while (isspace(*string_pos))	/* and whitespace */
-	string_pos++;
-    while (!(isspace(*string_pos)))	/* and value... have to watch it */
-	string_pos++;			/* can have punctuation and stuff */
-    
-    if ( ! strncasecmp(directive, "base", 4 ) ) {       /* base, base_uri */
-      imap_url(r, NULL, value, base);
-      continue; /* base is never printed to a menu */
-    }	
-
-    chars_read = read_quoted(string_pos, href_text);
-    string_pos += chars_read;      /* read the quoted href text if present */
-
-    if ( ! strcasecmp(directive, "default" ) ) {        /* default */
-      imap_url(r, NULL, value, mapdflt);
-      if (showmenu) {              /* print the default if there's a menu */
-	if (! *href_text) {           /* if we didn't find a "href text" */
-	  strncpy(href_text, mapdflt, sizeof(href_text)-1); /* use the href itself as text */
-	  href_text[sizeof(href_text)-1] = '\0';
+        string_pos = input;
+	if (!*string_pos)		/* need at least two fields */
+	    goto need_2_fields;
+
+	directive = string_pos;
+	while (*string_pos && *string_pos != ' ')	/* past directive */
+	    ++string_pos;
+	if (!*string_pos)		/* need at least two fields */
+	    goto need_2_fields;
+	*string_pos++ = '\0';
+
+	if (!*string_pos)		/* need at least two fields */
+	    goto need_2_fields;
+	value = string_pos;
+	while (*string_pos && *string_pos != ' ')	/* past value */
+	    ++string_pos;
+	if (*string_pos == ' ') {
+	    *string_pos++ = '\0';
+	}
+	else {
+	    /* end of input, don't advance past it */
+	    *string_pos = '\0';
 	}
-	imap_url(r, base, mapdflt, redirect); 
-	menu_default(r, imap_menu, redirect, href_text);
-      }
-      continue;
-    }
-
-    vertex = 0;
-    while ( vertex < MAXVERTS &&  
-     sscanf(string_pos, "%lf, %lf",
-     &pointarray[vertex][X], &pointarray[vertex][Y])   == 2)
-    {
-	/* Now skip what we just read... we can't use ANSIism %n */
-	while(isspace(*string_pos))	/* past whitespace */
-	    string_pos++;
-	while(isdigit(*string_pos))	/* and the 1st number */
-	    string_pos++;
-	string_pos++;			/* skip the ',' */
-	while(isspace(*string_pos))	/* past any more whitespace */
-	    string_pos++;
-	while(isdigit(*string_pos))	/* 2nd number */
-	    string_pos++;
-	vertex++;
-    }                /* so long as there are more vertices to read, and
-			we have room, read them in.  We start where we left
-			off of the last sscanf, not at the beginning.*/
-                  
-    pointarray[vertex][X] = -1;  /* signals the end of vertices */
 
+        if (!strncasecmp(directive, "base", 4)) {       /* base, base_uri */
+            base = imap_url(r, NULL, value);
+	    if (!base)
+		goto menu_bail;
+            continue;           /* base is never printed to a menu */
+        }
+
+        read_quoted(&string_pos, &href_text);
+
+        if (!strcasecmp(directive, "default")) {        /* default */
+            mapdflt = imap_url(r, NULL, value);
+	    if (!mapdflt)
+		goto menu_bail;
+            if (showmenu) {     /* print the default if there's a menu */
+                redirect = imap_url(r, base, mapdflt);
+		if (!redirect)
+		    goto menu_bail;
+                menu_default(r, imap_menu, redirect, href_text ? href_text : mapdflt);
+            }
+            continue;
+        }
+
+        vertex = 0;
+        while (vertex < MAXVERTS &&
+               sscanf(string_pos, "%lf%*[, ]%lf",
+                      &pointarray[vertex][X], &pointarray[vertex][Y]) == 2) {
+            /* Now skip what we just read... we can't use ANSIism %n */
+            while (isspace(*string_pos))        /* past whitespace */
+                string_pos++;
+            while (isdigit(*string_pos))        /* and the 1st number */
+                string_pos++;
+            string_pos++;       /* skip the ',' */
+            while (isspace(*string_pos))        /* past any more whitespace */
+                string_pos++;
+            while (isdigit(*string_pos))        /* 2nd number */
+                string_pos++;
+            vertex++;
+        }                       /* so long as there are more vertices to read, and
+                                   we have room, read them in.  We start where we left
+                                   off of the last sscanf, not at the beginning. */
+
+        pointarray[vertex][X] = -1;     /* signals the end of vertices */
+
+        if (showmenu) {
+	    if (!href_text) {
+		read_quoted(&string_pos, &href_text);         /* href text could be here instead */
+	    }
+            redirect = imap_url(r, base, value);
+	    if (!redirect)
+		goto menu_bail;
+            menu_directive(r, imap_menu, redirect, href_text ? href_text : value);
+            continue;
+        }
+        /* note that we don't make it past here if we are making a menu */
+
+        if (testpoint[X] == -1 || pointarray[0][X] == -1)
+            continue;           /* don't try the following tests if testpoints
+                                   are invalid, or if there are no coordinates */
+
+        if (!strcasecmp(directive, "poly")) {   /* poly */
+
+            if (pointinpoly(testpoint, pointarray)) {
+		pfclose(r->pool, imap);
+                redirect = imap_url(r, base, value);
+		if (!redirect)
+		    return HTTP_INTERNAL_SERVER_ERROR;
+                return (imap_reply(r, redirect));
+            }
+            continue;
+        }
+
+        if (!strcasecmp(directive, "circle")) {         /* circle */
+
+            if (pointincircle(testpoint, pointarray)) {
+		pfclose(r->pool, imap);
+                redirect = imap_url(r, base, value);
+		if (!redirect)
+		    return HTTP_INTERNAL_SERVER_ERROR;
+                return (imap_reply(r, redirect));
+            }
+            continue;
+        }
+
+        if (!strcasecmp(directive, "rect")) {   /* rect */
+
+            if (pointinrect(testpoint, pointarray)) {
+		pfclose(r->pool, imap);
+                redirect = imap_url(r, base, value);
+		if (!redirect)
+		    return HTTP_INTERNAL_SERVER_ERROR;
+                return (imap_reply(r, redirect));
+            }
+            continue;
+        }
+
+        if (!strcasecmp(directive, "point")) {  /* point */
+
+            if (is_closer(testpoint, pointarray, &closest_yet)) {
+		closest = pstrdup(r->pool, value);
+            }
+
+            continue;
+        }                       /* move on to next line whether it's closest or not */
+
+    }                           /* nothing matched, so we get another line! */
+
+    pfclose(r->pool, imap);     /* we are done with the map file, so close it */
+
+    if (showmenu) {
+        menu_footer(r);         /* finish the menu and we are done */
+        return OK;
+    }
+
+    if (closest) {             /* if a 'point' directive has been seen */
+        redirect = imap_url(r, base, closest);
+	if (!redirect)
+	    return HTTP_INTERNAL_SERVER_ERROR;
+        return (imap_reply(r, redirect));
+    }
+
+    if (mapdflt) {             /* a default should be defined, even if only 'nocontent' */
+        redirect = imap_url(r, base, mapdflt);
+	if (!redirect)
+	    return HTTP_INTERNAL_SERVER_ERROR;
+        return (imap_reply(r, redirect));
+    }
+
+    return SERVER_ERROR;        /* If we make it this far, we failed. They lose! */
+
+need_2_fields:
+    log_reason("all map file lines require at least two fields", r->uri, r);
+    /* fall through */
+menu_bail:
+    pfclose(r->pool, imap);
     if (showmenu) {
-      read_quoted(string_pos, href_text); /* href text could be here instead */
-      if (! *href_text) {           /* if we didn't find a "href text" */
-	strncpy(href_text, value, sizeof(href_text)-1);  /* use the href itself in the menu */
-	href_text[sizeof(href_text)-1] = '\0';
-      }
-      imap_url(r, base, value, redirect); 
-      menu_directive(r, imap_menu, redirect, href_text);
-      continue;
-    }
-    /* note that we don't make it past here if we are making a menu */
-
-    if (testpoint[X] == -1 || pointarray[0][X] == -1 )
-      continue;    /* don't try the following tests if testpoints
-		    are invalid, or if there are no coordinates */
-
-    if ( ! strcasecmp(directive, "poly" ) ) {        /* poly */
-
-      if (pointinpoly (testpoint, pointarray) ) {
-	pfclose(r->pool, imap); 
-	imap_url(r, base, value, redirect);     
-	return (imap_reply(r, redirect));
-      }
-      continue;
-    }
-
-    if ( ! strcasecmp(directive, "circle" ) ) {        /* circle */
-	
-      if (pointincircle (testpoint, pointarray) ) {
-	pfclose(r->pool, imap); 
-	imap_url(r, base, value, redirect);     
-	return (imap_reply(r, redirect));
-      }
-      continue;
-    }
-    
-    if ( ! strcasecmp(directive, "rect" ) ) {        /* rect */
-      
-      if (pointinrect (testpoint, pointarray) ) {
-	pfclose(r->pool, imap); 
-	imap_url(r, base, value, redirect);     
-	return (imap_reply(r, redirect));
-      }
-      continue;
-    }
-    
-    if ( ! strcasecmp(directive, "point" ) ) {         /* point */
-      
-      if (is_closer(testpoint, pointarray, &closest_yet) ) {
-	strncpy(closest, value, sizeof(closest)-1);  /* if the closest point yet save it */
-	closest[sizeof(closest)-1] = '\0';
-      }
-      
-      continue;    
-    }     /* move on to next line whether it's closest or not */
-    
-  }       /* nothing matched, so we get another line! */
-
-  pfclose(r->pool, imap);   /* we are done with the map file, so close it */
-
-  if (showmenu) {
-    menu_footer(r);   /* finish the menu and we are done */
-    return OK;                
-  }
-
-  if (*closest) {    /* if a 'point' directive has been seen */
-    imap_url(r, base, closest, redirect);     
-    return (imap_reply(r, redirect));
-  }    
-
-  if (*mapdflt ) {   /* a default should be defined, even if only 'nocontent'*/
-    imap_url(r, base, mapdflt, redirect);
-    return(imap_reply(r, redirect));
-  }    
-
-  return SERVER_ERROR;   /* If we make it this far, we failed. They lose! */
-}
-
-
-handler_rec imap_handlers[] = {
-{ IMAP_MAGIC_TYPE, imap_handler },
-{ "imap-file", imap_handler },
-{ NULL }
+	/* There's not much else we can do ... we've already sent the headers
+	 * to the client.
+	 */
+	rputs("\n\n[an internal server error occured]\n", r);
+	menu_footer(r);
+	return OK;
+    }
+    return HTTP_INTERNAL_SERVER_ERROR;
+}
+
+
+static handler_rec imap_handlers[] =
+{
+    {IMAP_MAGIC_TYPE, imap_handler},
+    {"imap-file", imap_handler},
+    {NULL}
 };
 
-module imap_module = {
-   STANDARD_MODULE_STUFF,
-   NULL,			/* initializer */
-   create_imap_dir_config,	/* dir config creater */
-   merge_imap_dir_configs,	/* dir merger --- default is to override */
-   NULL,			/* server config */
-   NULL,			/* merge server config */
-   imap_cmds,			/* command table */
-   imap_handlers,		/* handlers */
-   NULL,			/* filename translation */
-   NULL,			/* check_user_id */
-   NULL,			/* check auth */
-   NULL,			/* check access */
-   NULL,			/* type_checker */
-   NULL,			/* fixups */
-   NULL,			/* logger */
-   NULL				/* header parser */
+module imap_module =
+{
+    STANDARD_MODULE_STUFF,
+    NULL,                       /* initializer */
+    create_imap_dir_config,     /* dir config creater */
+    merge_imap_dir_configs,     /* dir merger --- default is to override */
+    NULL,                       /* server config */
+    NULL,                       /* merge server config */
+    imap_cmds,                  /* command table */
+    imap_handlers,              /* handlers */
+    NULL,                       /* filename translation */
+    NULL,                       /* check_user_id */
+    NULL,                       /* check auth */
+    NULL,                       /* check access */
+    NULL,                       /* type_checker */
+    NULL,                       /* fixups */
+    NULL,                       /* logger */
+    NULL                        /* header parser */
 };
Index: src/mod_include.c
===================================================================
RCS file: /export/home/cvs/apache/src/mod_include.c,v
retrieving revision 1.33.2.7
diff -u -r1.33.2.7 mod_include.c
--- mod_include.c	1997/11/05 11:46:21	1.33.2.7
+++ mod_include.c	1998/01/04 20:27:15
@@ -20,7 +20,8 @@
  *
  * 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.
+ *    prior written permission. For written permission, please contact
+ *    apache@apache.org.
  *
  * 5. Redistributions of any form whatsoever must retain the following
  *    acknowledgment:
@@ -91,18 +92,20 @@
 #define STARTING_SEQUENCE "<!--#"
 #define ENDING_SEQUENCE "-->"
 #define DEFAULT_ERROR_MSG "[an error occurred while processing this directive]"
-#define DEFAULT_TIME_FORMAT "%A, %d-%b-%y %T %Z"
+#define DEFAULT_TIME_FORMAT "%A, %d-%b-%Y %H:%M:%S %Z"
 #define SIZEFMT_BYTES 0
 #define SIZEFMT_KMG 1
 
-static void decodehtml(char *s);
-static char *get_tag(pool *p, FILE *in, char *tag, int tag_len, int dodecode);
-static int get_directive(FILE *in, char *d, pool *p);
 
+static void safe_copy(char *dest, const char *src, size_t max_len)
+{
+    strncpy(dest, src, max_len - 1);
+    dest[max_len - 1] = '\0';
+}
 
 /* ------------------------ Environment function -------------------------- */
 
-void add_include_vars(request_rec *r, char *timefmt)
+static void add_include_vars(request_rec *r, char *timefmt)
 {
     struct passwd *pw;
     table *e = r->subprocess_env;
@@ -111,28 +114,33 @@
 
     table_set(e, "DATE_LOCAL", ht_time(r->pool, date, timefmt, 0));
     table_set(e, "DATE_GMT", ht_time(r->pool, date, timefmt, 1));
-    table_set(e, "LAST_MODIFIED",ht_time(r->pool,r->finfo.st_mtime,timefmt,0));
+    table_set(e, "LAST_MODIFIED",
+              ht_time(r->pool, r->finfo.st_mtime, timefmt, 0));
     table_set(e, "DOCUMENT_URI", r->uri);
     table_set(e, "DOCUMENT_PATH_INFO", r->path_info);
     pw = getpwuid(r->finfo.st_uid);
     if (pw) {
-      table_set(e, "USER_NAME", pw->pw_name);
-    } else {
-      char uid[16];
-      ap_snprintf(uid, sizeof(uid), "user#%lu", (unsigned long)r->finfo.st_uid);
-      table_set(e, "USER_NAME", uid);
+        table_set(e, "USER_NAME", pw->pw_name);
+    }
+    else {
+        char uid[16];
+        ap_snprintf(uid, sizeof(uid), "user#%lu",
+                    (unsigned long) r->finfo.st_uid);
+        table_set(e, "USER_NAME", uid);
     }
 
-    if((t = strrchr(r->filename, '/')))
-        table_set (e, "DOCUMENT_NAME", ++t);
-    else
-        table_set (e, "DOCUMENT_NAME", r->uri);
+    if ((t = strrchr(r->filename, '/'))) {
+        table_set(e, "DOCUMENT_NAME", ++t);
+    }
+    else {
+        table_set(e, "DOCUMENT_NAME", r->uri);
+    }
     if (r->args) {
-	char *arg_copy = pstrdup (r->pool, r->args);
+        char *arg_copy = pstrdup(r->pool, r->args);
 
-        unescape_url (arg_copy);
-	  table_set (e, "QUERY_STRING_UNESCAPED",
-		   escape_shell_cmd (r->pool, arg_copy));
+        unescape_url(arg_copy);
+        table_set(e, "QUERY_STRING_UNESCAPED",
+                  escape_shell_cmd(r->pool, arg_copy));
     }
 }
 
@@ -148,9 +156,11 @@
  */
 #define PUT_CHAR(c,r) \
  { \
-   outbuf[outind++] = c; \
-   if (outind == OUTBUFSIZE) { FLUSH_BUF(r) }; \
- } 
+    outbuf[outind++] = c; \
+    if (outind == OUTBUFSIZE) { \
+        FLUSH_BUF(r) \
+    }; \
+ }
 
 /* there SHOULD be some error checking on the return value of
  * rwrite, however it is unclear what the API for rwrite returning
@@ -176,39 +186,42 @@
 #define GET_CHAR(f,c,ret,r) \
  { \
    int i = getc(f); \
-   if(i == EOF) { /* either EOF or error -- needs error handling if latter */ \
-       if (ferror(f)) \
-	   fprintf(stderr, "encountered error in GET_CHAR macro, mod_include.\n"); \
+   if (i == EOF) { /* either EOF or error -- needs error handling if latter */ \
+       if (ferror(f)) { \
+           fprintf(stderr, "encountered error in GET_CHAR macro, " \
+                   "mod_include.\n"); \
+       } \
        FLUSH_BUF(r); \
-       pfclose(r->pool,f); \
+       pfclose(r->pool, f); \
        return ret; \
    } \
    c = (char)i; \
  }
 
-int find_string(FILE *in,char *str, request_rec *r, int printing) {
-    int x,l=strlen(str),p;
+static int find_string(FILE *in, const char *str, request_rec *r, int printing)
+{
+    int x, l = strlen(str), p;
     char outbuf[OUTBUFSIZE];
     int outind = 0;
     char c;
 
-    p=0;
-    while(1) {
-        GET_CHAR(in,c,1,r);
-        if(c == str[p]) {
-            if((++p) == l) {
-		FLUSH_BUF(r);
+    p = 0;
+    while (1) {
+        GET_CHAR(in, c, 1, r);
+        if (c == str[p]) {
+            if ((++p) == l) {
+                FLUSH_BUF(r);
                 return 0;
-	    }
+            }
         }
         else {
             if (printing) {
-                for(x=0;x<p;x++) {
-                    PUT_CHAR(str[x],r);
+                for (x = 0; x < p; x++) {
+                    PUT_CHAR(str[x], r);
                 }
-                PUT_CHAR(c,r);
+                PUT_CHAR(c, r);
             }
-            p=0;
+            p = 0;
         }
     }
 }
@@ -219,10 +232,12 @@
 #define GET_CHAR(f,c,r,p) \
  { \
    int i = getc(f); \
-   if(i == EOF) { /* either EOF or error -- needs error handling if latter */ \
-       if (ferror(f)) \
-	   fprintf(stderr, "encountered error in GET_CHAR macro, mod_include.\n"); \
-       pfclose(p,f); \
+   if (i == EOF) { /* either EOF or error -- needs error handling if latter */ \
+       if (ferror(f)) { \
+           fprintf(stderr, "encountered error in GET_CHAR macro, " \
+                   "mod_include.\n"); \
+       } \
+       pfclose(p, f); \
        return r; \
    } \
    c = (char)i; \
@@ -244,69 +259,79 @@
 
 /* The following is a shrinking transformation, therefore safe. */
 
-static void
-decodehtml(char *s)
+static void decodehtml(char *s)
 {
     int val, i, j;
-    char *p=s;
-    char *ents;
-    static char *entlist[MAXENTLEN+1]={
-	NULL,  /* 0 */
-	NULL,  /* 1 */
-	"lt\074gt\076", /* 2 */
-	"amp\046ETH\320eth\360", /* 3 */
-	"quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\
-iuml\357ouml\366uuml\374yuml\377", /* 4 */
-	"Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\
+    char *p = s;
+    const char *ents;
+    static const char * const entlist[MAXENTLEN + 1] =
+    {
+        NULL,                   /* 0 */
+        NULL,                   /* 1 */
+        "lt\074gt\076",         /* 2 */
+        "amp\046ETH\320eth\360",        /* 3 */
+        "quot\042Auml\304Euml\313Iuml\317Ouml\326Uuml\334auml\344euml\353\
+iuml\357ouml\366uuml\374yuml\377",      /* 4 */
+        "Acirc\302Aring\305AElig\306Ecirc\312Icirc\316Ocirc\324Ucirc\333\
 THORN\336szlig\337acirc\342aring\345aelig\346ecirc\352icirc\356ocirc\364\
-ucirc\373thorn\376", /* 5 */
-	"Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\
+ucirc\373thorn\376",            /* 5 */
+        "Agrave\300Aacute\301Atilde\303Ccedil\307Egrave\310Eacute\311\
 Igrave\314Iacute\315Ntilde\321Ograve\322Oacute\323Otilde\325Oslash\330\
 Ugrave\331Uacute\332Yacute\335agrave\340aacute\341atilde\343ccedil\347\
 egrave\350eacute\351igrave\354iacute\355ntilde\361ograve\362oacute\363\
-otilde\365oslash\370ugrave\371uacute\372yacute\375" /* 6 */
+otilde\365oslash\370ugrave\371uacute\372yacute\375"     /* 6 */
     };
 
     for (; *s != '\0'; s++, p++) {
-	if (*s != '&') {
-	    *p = *s;
-	    continue;
-	}
-	/* find end of entity */
-	for (i=1; s[i] != ';' && s[i] != '\0'; i++)
-	    continue;
-
-	if (s[i] == '\0') {	/* treat as normal data */
-	    *p = *s;
-	    continue;
-	}
+        if (*s != '&') {
+            *p = *s;
+            continue;
+        }
+        /* find end of entity */
+        for (i = 1; s[i] != ';' && s[i] != '\0'; i++) {
+            continue;
+        }
 
-	/* is it numeric ? */
-	if (s[1] == '#') {
-	    for (j=2, val=0; j < i && isdigit(s[j]); j++)
-		val = val * 10 + s[j] - '0';
-	    s += i;
-	    if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
-		(val >= 127 && val <= 160) || val >= 256)
-		p--;  /* no data to output */
-	    else
-		*p = val;
-	} else{
-	    j = i-1;
-	    if (i-1 > MAXENTLEN || entlist[i-1] == NULL) { /* wrong length */
-		*p = '&';
-		continue;  /* skip it */
-	    }
-	    for (ents=entlist[i-1]; *ents != '\0'; ents += i)
-		if (strncmp(s+1, ents, i-1) == 0) break;
+        if (s[i] == '\0') {     /* treat as normal data */
+            *p = *s;
+            continue;
+        }
 
-	    if (*ents == '\0')
-		*p = '&';  /* unknown */
-	    else {
-		*p = ((const unsigned char *)ents)[i-1];
-		s += i;
-	    }
-	}
+        /* is it numeric ? */
+        if (s[1] == '#') {
+            for (j = 2, val = 0; j < i && isdigit(s[j]); j++) {
+                val = val * 10 + s[j] - '0';
+            }
+            s += i;
+            if (j < i || val <= 8 || (val >= 11 && val <= 31) ||
+                (val >= 127 && val <= 160) || val >= 256) {
+                p--;            /* no data to output */
+            }
+            else {
+                *p = val;
+            }
+        }
+        else {
+            j = i - 1;
+            if (i - 1 > MAXENTLEN || entlist[i - 1] == NULL) {
+                /* wrong length */
+                *p = '&';
+                continue;       /* skip it */
+            }
+            for (ents = entlist[i - 1]; *ents != '\0'; ents += i) {
+                if (strncmp(s + 1, ents, i - 1) == 0) {
+                    break;
+                }
+            }
+
+            if (*ents == '\0') {
+                *p = '&';       /* unknown */
+            }
+            else {
+                *p = ((const unsigned char *) ents)[i - 1];
+                s += i;
+            }
+        }
     }
 
     *p = '\0';
@@ -318,97 +343,117 @@
  * the tag value is html decoded if dodecode is non-zero
  */
 
-static char *
-get_tag(pool *p, FILE *in, char *tag, int tagbuf_len, int dodecode) {
+static char *get_tag(pool *p, FILE *in, char *tag, int tagbuf_len, int dodecode)
+{
     char *t = tag, *tag_val, c, term;
-    int n;
 
-    n = 0;
+    /* makes code below a little less cluttered */
+    --tagbuf_len;
 
-    do { /* skip whitespace */
-	GET_CHAR(in,c,NULL,p);
+    do {                        /* skip whitespace */
+        GET_CHAR(in, c, NULL, p);
     } while (isspace(c));
 
     /* tags can't start with - */
-    if(c == '-') {
-        GET_CHAR(in,c,NULL,p);
-        if(c == '-') {
+    if (c == '-') {
+        GET_CHAR(in, c, NULL, p);
+        if (c == '-') {
             do {
-		GET_CHAR(in,c,NULL,p);
-	    } while (isspace(c));
-            if(c == '>') {
-                strncpy(tag,"done", tagbuf_len-1);
-		tag[tagbuf_len-1] = '\0';
+                GET_CHAR(in, c, NULL, p);
+            } while (isspace(c));
+            if (c == '>') {
+                safe_copy(tag, "done", tagbuf_len);
                 return tag;
             }
         }
-	return NULL; /* failed */
+        return NULL;            /* failed */
     }
 
     /* find end of tag name */
-    while(1) {
-        if(++n == tagbuf_len) {
-            t[tagbuf_len - 1] = '\0';
+    while (1) {
+        if (t - tag == tagbuf_len) {
+            *t = '\0';
             return NULL;
         }
-	if(c == '=' || isspace(c)) break;
-	*(t++) = tolower(c);
-        GET_CHAR(in,c,NULL,p);
+        if (c == '=' || isspace(c)) {
+            break;
+        }
+        *(t++) = tolower(c);
+        GET_CHAR(in, c, NULL, p);
     }
 
     *t++ = '\0';
     tag_val = t;
 
-    while (isspace(c)) GET_CHAR(in, c, NULL,p); /* space before = */
+    while (isspace(c)) {
+        GET_CHAR(in, c, NULL, p);       /* space before = */
+    }
     if (c != '=') {
         ungetc(c, in);
         return NULL;
     }
 
     do {
-	GET_CHAR(in,c,NULL,p);  /* space after = */
+        GET_CHAR(in, c, NULL, p);       /* space after = */
     } while (isspace(c));
 
     /* we should allow a 'name' as a value */
-    
-    if (c != '"' && c != '\'') return NULL;
+
+    if (c != '"' && c != '\'') {
+        return NULL;
+    }
     term = c;
-    while(1) {
-	GET_CHAR(in,c,NULL,p);
-	if(++n == tagbuf_len) {
-	    t[tagbuf_len - 1] = '\0';
-	    return NULL;
-	}
+    while (1) {
+        GET_CHAR(in, c, NULL, p);
+        if (t - tag == tagbuf_len) {
+            *t = '\0';
+            return NULL;
+        }
 /* Want to accept \" as a valid character within a string. */
-	if (c == '\\') {
-	    *(t++) = c; /* Add backslash */
-	    GET_CHAR(in,c,NULL,p);
-	    if (c == term) /* Only if */
-		*(--t) = c; /* Replace backslash ONLY for terminator */
-	} else if (c == term) break;
-	*(t++) = c;
+        if (c == '\\') {
+            *(t++) = c;         /* Add backslash */
+            GET_CHAR(in, c, NULL, p);
+            if (c == term) {    /* Only if */
+                *(--t) = c;     /* Replace backslash ONLY for terminator */
+            }
+        }
+        else if (c == term) {
+            break;
+        }
+        *(t++) = c;
     }
     *t = '\0';
-    if (dodecode) decodehtml(tag_val);
-    return pstrdup (p, tag_val);
+    if (dodecode) {
+        decodehtml(tag_val);
+    }
+    return pstrdup(p, tag_val);
 }
 
-static int
-get_directive(FILE *in, char *d, pool *p) {
+static int get_directive(FILE *in, char *dest, size_t len, pool *p)
+{
+    char *d = dest;
     char c;
 
+    /* make room for nul terminator */
+    --len;
+
     /* skip initial whitespace */
-    while(1) {
-        GET_CHAR(in,c,1,p);
-        if(!isspace(c))
+    while (1) {
+        GET_CHAR(in, c, 1, p);
+        if (!isspace(c)) {
             break;
+        }
     }
     /* now get directive */
-    while(1) {
+    while (1) {
+	if (d - dest == len) {
+	    return 1;
+	}
         *d++ = tolower(c);
-        GET_CHAR(in,c,1,p);
-        if(isspace(c))
+        GET_CHAR(in, c, 1, p);
+        if (isspace(c)) {
             break;
+        }
     }
     *d = '\0';
     return 0;
@@ -417,73 +462,95 @@
 /*
  * Do variable substitution on strings
  */
-void parse_string(request_rec *r, char *in, char *out, int length,
-                  int leave_name)
+static void parse_string(request_rec *r, const char *in, char *out,
+			size_t length, int leave_name)
 {
     char ch;
     char *next = out;
-    int numchars = 0;
+    char *end_out;
+
+    /* leave room for nul terminator */
+    end_out = out + length - 1;
 
     while ((ch = *in++) != '\0') {
-        switch(ch) {
-          case '\\':
-	    if(*in == '$')
-		*next++=*in++;
-	    else
-		*next++=ch;
-            break;
-          case '$':
-          {
-            char var[MAX_STRING_LEN];
-            char vtext[MAX_STRING_LEN];
-            char *val;
-            int braces=0;
-            int vlen, vtlen;
-            /* 
-             * Keep the $ and { around because we do no substitution
-             * if the variable isn't found
-             */
-            vlen = vtlen = 0;
-            vtext[vtlen++] = ch;
-            if (*in == '{') { braces = 1; vtext[vtlen++] = *in++; }
-            while (*in != '\0') {
-                if (vlen == (MAX_STRING_LEN - 1)) continue;
-                if (braces == 1) {
-                    if (*in == '}') break;
-                }
-                else if (! (isalpha((int)*in) || (*in == '_') || isdigit((int)*in)) ) break;
-                if (vtlen < (MAX_STRING_LEN - 1)) vtext[vtlen++] = *in;
-                var[vlen++] = *in++;
-            }
-            var[vlen] = vtext[vtlen] = '\0';
-            if (braces == 1) {
-                if (*in != '}') {
-                    log_printf(r->server, "Invalid variable \"%s%s\"", vtext,in);
-                    *next = '\0';
-                    return;
-                } else
-                    in++;
-            } 
-
-            val = (char *)NULL;
-            if (var[0] == '\0') {
-                val = &vtext[0];
-            } else {
-                val = table_get (r->subprocess_env, &var[0]);
-                if (!val && leave_name)
-                    val = &vtext[0];
-            }
-            while ((val != (char *)NULL) && (*val != '\0')) {
-                *next++ = *val++;
-                if (++numchars == (length -1)) break;
+        switch (ch) {
+        case '\\':
+	    if (next == end_out) {
+		/* truncated */
+		*next = '\0';
+		return;
+	    }
+            if (*in == '$') {
+                *next++ = *in++;
+            }
+            else {
+                *next++ = ch;
             }
             break;
-          }
-          default:
+        case '$':
+            {
+		char var[MAX_STRING_LEN];
+		const char *start_of_var_name;
+		const char *end_of_var_name;	/* end of var name + 1 */
+		const char *expansion;
+		const char *val;
+		size_t l;
+
+		/* guess that the expansion won't happen */
+		expansion = in - 1;
+		if (*in == '{') {
+		    ++in;
+		    start_of_var_name = in;
+		    in = strchr(in, '}');
+		    if (in == NULL) {
+			log_printf(r->server,
+				    "Missing '}' on variable \"%s\" in %s",
+				    expansion, r->filename);
+                        *next = '\0';
+                        return;
+                    }
+		    end_of_var_name = in;
+		    ++in;
+		}
+		else {
+		    start_of_var_name = in;
+		    while (isalnum(*in) || *in == '_') {
+			++in;
+		    }
+		    end_of_var_name = in;
+		}
+		/* what a pain, too bad there's no table_getn where you can
+		 * pass a non-nul terminated string */
+		l = end_of_var_name - start_of_var_name;
+		l = (l > sizeof(var) - 1) ? (sizeof(var) - 1) : l;
+		memcpy(var, start_of_var_name, l);
+		var[l] = '\0';
+
+		val = table_get(r->subprocess_env, var);
+		if (val) {
+		    expansion = val;
+		    l = strlen(expansion);
+		}
+		else if (leave_name) {
+		    l = in - expansion;
+		}
+		else {
+		    break;	/* no expansion to be done */
+		}
+		l = (l > end_out - next) ? (end_out - next) : l;
+		memcpy(next, expansion, l);
+		next += l;
+                break;
+            }
+        default:
+	    if (next == end_out) {
+		/* truncated */
+		*next = '\0';
+		return;
+	    }
             *next++ = ch;
             break;
         }
-        if (++numchars == (length -1)) break;
     }
     *next = '\0';
     return;
@@ -491,23 +558,29 @@
 
 /* --------------------------- Action handlers ---------------------------- */
 
-int include_cgi(char *s, request_rec *r)
+static int include_cgi(char *s, request_rec *r)
 {
-    request_rec *rr = sub_req_lookup_uri (s, r);
+    request_rec *rr = sub_req_lookup_uri(s, r);
     int rr_status;
-    
-    if (rr->status != 200) return -1;
-    
+
+    if (rr->status != HTTP_OK) {
+        return -1;
+    }
+
     /* No hardwired path info or query allowed */
-    
-    if ((rr->path_info && rr->path_info[0]) || rr->args) return -1;
-    if (rr->finfo.st_mode == 0) return -1;
+
+    if ((rr->path_info && rr->path_info[0]) || rr->args) {
+        return -1;
+    }
+    if (rr->finfo.st_mode == 0) {
+        return -1;
+    }
 
     /* Script gets parameters of the *document*, for back compatibility */
-    
-    rr->path_info = r->path_info; /* painful to get right; see mod_cgi.c */
+
+    rr->path_info = r->path_info;       /* hard to get right; see mod_cgi.c */
     rr->args = r->args;
-    
+
     /* Force sub_req to be treated as a CGI request, even if ordinary
      * typing rules would have called it something else.
      */
@@ -515,77 +588,117 @@
     rr->content_type = CGI_MAGIC_TYPE;
 
     /* Run it. */
-    
+
     rr_status = run_sub_req(rr);
     if (is_HTTP_REDIRECT(rr_status)) {
-        char *location = table_get (rr->headers_out, "Location");
+        char *location = table_get(rr->headers_out, "Location");
         location = escape_html(rr->pool, location);
-	rvputs(r,"<A HREF=\"", location, "\">", location, "</A>", NULL);
+        rvputs(r, "<A HREF=\"", location, "\">", location, "</A>", NULL);
     }
-    
-    destroy_sub_req (rr);
+
+    destroy_sub_req(rr);
     chdir_file(r->filename);
-    
+
     return 0;
 }
 
-int handle_include(FILE *in, request_rec *r, char *error, int noexec) {
+/* ensure that path is relative, and does not contain ".." elements
+ * ensentially ensure that it does not match the regex:
+ * (^/|(^|/)\.\.(/|$))
+ * XXX: this needs os abstraction... consider c:..\foo in win32
+ */
+static int is_only_below(const char *path)
+{
+    if (path[0] == '/') {
+	return 0;
+    }
+    if (path[0] == '.' && path[1] == '.'
+	&& (path[2] == '\0' || path[2] == '/')) {
+	return 0;
+    }
+    while (*path) {
+	if (*path == '/' && path[1] == '.' && path[2] == '.'
+	    && (path[3] == '\0' || path[3] == '/')) {
+	    return 0;
+	}
+	++path;
+    }
+    return 1;
+}
+
+static int handle_include(FILE *in, request_rec *r, const char *error, int noexec)
+{
     char tag[MAX_STRING_LEN];
     char parsed_string[MAX_STRING_LEN];
     char *tag_val;
 
-    while(1) {
-        if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
+    while (1) {
+        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
             return 1;
-        if(!strcmp(tag,"file") || !strcmp (tag, "virtual")) {
-	    request_rec *rr=NULL;
-	    char *error_fmt = NULL;
-
-            parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
-	    if (tag[0] == 'f')
-	    { /* be safe; only files in this directory or below allowed */
-		char tmp[MAX_STRING_LEN+2];
-		ap_snprintf(tmp, sizeof(tmp), "/%s/", parsed_string);
-		if (parsed_string[0] == '/' || strstr(tmp, "/../") != NULL)
-		    error_fmt = "unable to include file \"%s\" in parsed file %s";
-		else
-		    rr = sub_req_lookup_file (parsed_string, r);
-	    } else
-		rr = sub_req_lookup_uri (parsed_string, r);
-	    
-	    if (!error_fmt && rr->status != 200)
-	        error_fmt = "unable to include \"%s\" in parsed file %s";
-
-	    if (!error_fmt && noexec && rr->content_type
-		&& (strncmp (rr->content_type, "text/", 5)))
-	        error_fmt =
-		  "unable to include potential exec \"%s\" in parsed file %s";
-            if (error_fmt == NULL)
-            {
+        }
+        if (!strcmp(tag, "file") || !strcmp(tag, "virtual")) {
+            request_rec *rr = NULL;
+            char *error_fmt = NULL;
+
+            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+            if (tag[0] == 'f') {
+                /* be safe; only files in this directory or below allowed */
+		if (!is_only_below(parsed_string)) {
+                    error_fmt = "unable to include file \"%s\" "
+                        "in parsed file %s";
+                }
+                else {
+                    rr = sub_req_lookup_file(parsed_string, r);
+                }
+            }
+            else {
+                rr = sub_req_lookup_uri(parsed_string, r);
+            }
+
+            if (!error_fmt && rr->status != HTTP_OK) {
+                error_fmt = "unable to include \"%s\" in parsed file %s";
+            }
+
+            if (!error_fmt && noexec && rr->content_type
+                && (strncmp(rr->content_type, "text/", 5))) {
+                error_fmt = "unable to include potential exec \"%s\" "
+                    "in parsed file %s";
+            }
+            if (error_fmt == NULL) {
                 request_rec *p;
 
-                for (p=r; p != NULL; p=p->main)
-                    if (strcmp(p->filename, rr->filename) == 0) break;
-                if (p != NULL)
-                    error_fmt = "Recursive include of \"%s\" in parsed file %s";
-            }
-	    
-	    if (!error_fmt && run_sub_req (rr))
-	        error_fmt = "unable to include \"%s\" in parsed file %s";
-	    chdir_file(r->filename);
-		    
+                for (p = r; p != NULL; p = p->main) {
+                    if (strcmp(p->filename, rr->filename) == 0) {
+                        break;
+                    }
+                }
+                if (p != NULL) {
+                    error_fmt = "Recursive include of \"%s\" "
+                        "in parsed file %s";
+                }
+            }
+
+            if (!error_fmt && run_sub_req(rr)) {
+                error_fmt = "unable to include \"%s\" in parsed file %s";
+            }
+            chdir_file(r->filename);
+
             if (error_fmt) {
                 log_printf(r->server, error_fmt, tag_val, r->filename);
                 rputs(error, r);
-            }            
+            }
 
-	    if (rr != NULL) destroy_sub_req (rr);
-        } 
-        else if(!strcmp(tag,"done"))
+            if (rr != NULL) {
+                destroy_sub_req(rr);
+            }
+        }
+        else if (!strcmp(tag, "done")) {
             return 0;
+        }
         else {
-            log_printf(r->server, "unknown parameter \"%s\" to tag include in %s",
-                       tag, r->filename);
+            log_printf(r->server,
+                        "unknown parameter \"%s\" to tag include in %s",
+                        tag, r->filename);
             rputs(error, r);
         }
     }
@@ -596,170 +709,194 @@
     char *s;
 } include_cmd_arg;
 
-void include_cmd_child (void *arg)
+static void include_cmd_child(void *arg)
 {
-    request_rec *r =  ((include_cmd_arg *)arg)->r;
-    char *s = ((include_cmd_arg *)arg)->s;
+    request_rec *r = ((include_cmd_arg *) arg)->r;
+    char *s = ((include_cmd_arg *) arg)->s;
     table *env = r->subprocess_env;
-#ifdef DEBUG_INCLUDE_CMD    
-    FILE *dbg = fopen ("/dev/tty", "w");
-#endif    
-    char err_string [MAX_STRING_LEN];
+#ifdef DEBUG_INCLUDE_CMD
+    FILE *dbg = fopen("/dev/tty", "w");
+#endif
+    char err_string[MAX_STRING_LEN];
 
-#ifdef DEBUG_INCLUDE_CMD    
+#ifdef DEBUG_INCLUDE_CMD
 #ifdef __EMX__
     /* under OS/2 /dev/tty is referenced as con */
-    FILE *dbg = fopen ("con", "w");
+    FILE *dbg = fopen("con", "w");
 #else
-    fprintf (dbg, "Attempting to include command '%s'\n", s);
-#endif    
-#endif    
+    fprintf(dbg, "Attempting to include command '%s'\n", s);
+#endif
+#endif
 
-    if (r->path_info && r->path_info[0] != '\0')
-    {
-	request_rec *pa_req;
+    if (r->path_info && r->path_info[0] != '\0') {
+        request_rec *pa_req;
+
+        table_set(env, "PATH_INFO", escape_shell_cmd(r->pool, r->path_info));
 
-	table_set (env, "PATH_INFO", escape_shell_cmd (r->pool, r->path_info));
-	
-	pa_req = sub_req_lookup_uri(escape_uri(r->pool, r->path_info), r);
-	if (pa_req->filename)
-	    table_set(env, "PATH_TRANSLATED",
-		      pstrcat(r->pool, pa_req->filename, pa_req->path_info,
-			      NULL));
+        pa_req = sub_req_lookup_uri(escape_uri(r->pool, r->path_info), r);
+        if (pa_req->filename) {
+            table_set(env, "PATH_TRANSLATED",
+                      pstrcat(r->pool, pa_req->filename, pa_req->path_info,
+                              NULL));
+        }
     }
 
     if (r->args) {
-	char *arg_copy = pstrdup (r->pool, r->args);
+        char *arg_copy = pstrdup(r->pool, r->args);
+
+        table_set(env, "QUERY_STRING", r->args);
+        unescape_url(arg_copy);
+        table_set(env, "QUERY_STRING_UNESCAPED",
+                  escape_shell_cmd(r->pool, arg_copy));
+    }
 
-        table_set (env, "QUERY_STRING", r->args);
-	unescape_url (arg_copy);
-	table_set (env, "QUERY_STRING_UNESCAPED",
-		   escape_shell_cmd (r->pool, arg_copy));
-    }
-    
-    error_log2stderr (r->server);
-    
-#ifdef DEBUG_INCLUDE_CMD    
-    fprintf (dbg, "Attempting to exec '%s'\n", s);
-#endif    
+    error_log2stderr(r->server);
+
+#ifdef DEBUG_INCLUDE_CMD
+    fprintf(dbg, "Attempting to exec '%s'\n", s);
+#endif
     cleanup_for_exec();
     /* set shellcmd flag to pass arg to SHELL_PATH */
-    call_exec(r, s, create_environment (r->pool, env), 1);
-    
+    call_exec(r, s, create_environment(r->pool, env), 1);
     /* Oh, drat.  We're still here.  The log file descriptors are closed,
      * so we have to whimper a complaint onto stderr...
      */
-    
-#ifdef DEBUG_INCLUDE_CMD    
-    fprintf (dbg, "Exec failed\n");
-#endif    
+
+#ifdef DEBUG_INCLUDE_CMD
+    fprintf(dbg, "Exec failed\n");
+#endif
     ap_snprintf(err_string, sizeof(err_string),
-	"httpd: exec of %s failed, reason: %s (errno = %d)\n",
-	SHELL_PATH, strerror(errno), errno);
-    write (2, err_string, strlen(err_string));
+                "httpd: exec of %s failed, reason: %s (errno = %d)\n",
+                SHELL_PATH, strerror(errno), errno);
+    write(STDERR_FILENO, err_string, strlen(err_string));
     exit(0);
 }
 
-int include_cmd(char *s, request_rec *r) {
+static int include_cmd(char *s, request_rec *r)
+{
     include_cmd_arg arg;
     FILE *f;
 
-    arg.r = r; arg.s = s;
+    arg.r = r;
+    arg.s = s;
 
-    if (!spawn_child (r->pool, include_cmd_child, &arg,
-		      kill_after_timeout, NULL, &f))
+    if (!spawn_child(r->pool, include_cmd_child, &arg,
+                     kill_after_timeout, NULL, &f)) {
         return -1;
-    
-    send_fd(f,r);
-    pfclose(r->pool, f);	/* will wait for zombie when
-				 * r->pool is cleared
-				 */
+    }
+
+    send_fd(f, r);
+    pfclose(r->pool, f);        /* will wait for zombie when
+                                 * r->pool is cleared
+                                 */
     return 0;
 }
 
 
-int handle_exec(FILE *in, request_rec *r, char *error)
+static int handle_exec(FILE *in, request_rec *r, const char *error)
 {
     char tag[MAX_STRING_LEN];
     char *tag_val;
     char *file = r->filename;
     char parsed_string[MAX_STRING_LEN];
 
-    while(1) {
-        if(!(tag_val = get_tag (r->pool, in, tag, MAX_STRING_LEN, 1)))
+    while (1) {
+        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
             return 1;
-        if(!strcmp(tag,"cmd")) {
-            parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 1);
-            if(include_cmd(parsed_string, r) == -1) {
-                log_printf(r->server, "execution failure for parameter \"%s\" to tag exec in file %s", 
-                tag, r->filename);
+        }
+        if (!strcmp(tag, "cmd")) {
+            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 1);
+            if (include_cmd(parsed_string, r) == -1) {
+                log_printf(r->server,
+                            "execution failure for parameter \"%s\" "
+                            "to tag exec in file %s",
+                            tag, r->filename);
                 rputs(error, r);
             }
             /* just in case some stooge changed directories */
             chdir_file(r->filename);
-        } 
-        else if(!strcmp(tag,"cgi")) {
-            parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
-            if(include_cgi(parsed_string, r) == -1) {
-                log_printf(r->server, "invalid CGI ref \"%s\" in %s",tag_val,file);
+        }
+        else if (!strcmp(tag, "cgi")) {
+            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+            if (include_cgi(parsed_string, r) == -1) {
+                log_printf(r->server,
+                            "invalid CGI ref \"%s\" in %s", tag_val, file);
                 rputs(error, r);
             }
             /* grumble groan */
             chdir_file(r->filename);
         }
-        else if(!strcmp(tag,"done"))
+        else if (!strcmp(tag, "done")) {
             return 0;
+        }
         else {
-            log_printf(r->server, "unknown parameter \"%s\" to tag exec in %s",
-                       tag, file);
+            log_printf(r->server,
+                        "unknown parameter \"%s\" to tag exec in %s",
+                        tag, file);
             rputs(error, r);
         }
     }
 
 }
 
-int handle_echo (FILE *in, request_rec *r, char *error) {
+static int handle_echo(FILE *in, request_rec *r, const char *error)
+{
     char tag[MAX_STRING_LEN];
     char *tag_val;
 
-    while(1) {
-        if(!(tag_val = get_tag (r->pool, in, tag, MAX_STRING_LEN, 1)))
+    while (1) {
+        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
             return 1;
-        if(!strcmp(tag,"var")) {
-	    char *val = table_get (r->subprocess_env, tag_val);
+        }
+        if (!strcmp(tag, "var")) {
+            char *val = table_get(r->subprocess_env, tag_val);
 
-	    if (val) rputs(val, r);
-	    else rputs("(none)", r);
-        } else if(!strcmp(tag,"done"))
+            if (val) {
+                rputs(val, r);
+            }
+            else {
+                rputs("(none)", r);
+            }
+        }
+        else if (!strcmp(tag, "done")) {
             return 0;
+        }
         else {
-            log_printf(r->server, "unknown parameter \"%s\" to tag echo in %s",
-                tag, r->filename);
+            log_printf(r->server,
+                        "unknown parameter \"%s\" to tag echo in %s",
+                        tag, r->filename);
             rputs(error, r);
         }
     }
 }
+
 #ifdef USE_PERL_SSI
-int handle_perl (FILE *in, request_rec *r, char *error) {
+static int handle_perl(FILE *in, request_rec *r, const char *error)
+{
     char tag[MAX_STRING_LEN];
     char *tag_val;
     SV *sub = Nullsv;
-    AV *av  = newAV();
+    AV *av = newAV();
 
-    if (!(allow_options (r) & OPT_INCLUDES)) {
+    if (!(allow_options(r) & OPT_INCLUDES)) {
         log_printf(r->server,
-            "httpd: #perl SSI disallowed by IncludesNoExec in %s", r->filename);
-	return DECLINED;
+                    "httpd: #perl SSI disallowed by IncludesNoExec in %s",
+                    r->filename);
+        return DECLINED;
     }
-    while(1) {
-	if(!(tag_val = get_tag (r->pool, in, tag, MAX_STRING_LEN, 1))) 
-	    break;
-	if(strnEQ(tag, "sub", 3)) 
-	    sub = newSVpv(tag_val,0);
-	else if(strnEQ(tag, "arg", 3)) 
-	    av_push(av, newSVpv(tag_val,0));	
-	else if(strnEQ(tag,"done", 4))
-	    break;
+    while (1) {
+        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
+            break;
+        }
+        if (strnEQ(tag, "sub", 3)) {
+            sub = newSVpv(tag_val, 0);
+        }
+        else if (strnEQ(tag, "arg", 3)) {
+            av_push(av, newSVpv(tag_val, 0));
+        }
+        else if (strnEQ(tag, "done", 4)) {
+            break;
+        }
     }
     perl_stdout2client(r);
     perl_call_handler(sub, r, av);
@@ -770,124 +907,134 @@
 /* error and tf must point to a string with room for at 
  * least MAX_STRING_LEN characters 
  */
-int handle_config(FILE *in, request_rec *r, char *error, char *tf,
-                  int *sizefmt) {
+static int handle_config(FILE *in, request_rec *r, char *error, char *tf,
+                         int *sizefmt)
+{
     char tag[MAX_STRING_LEN];
     char *tag_val;
     char parsed_string[MAX_STRING_LEN];
     table *env = r->subprocess_env;
 
-    while(1) {
-        if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 0)))
+    while (1) {
+        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0))) {
             return 1;
-        if(!strcmp(tag,"errmsg")) {
-            parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
-            strncpy(error,parsed_string,MAX_STRING_LEN-1);
-	    error[MAX_STRING_LEN-1] = '\0';
-        } else if(!strcmp(tag,"timefmt")) {
-  	    time_t date = r->request_time;
-            parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
-            strncpy(tf,parsed_string,MAX_STRING_LEN-1);
-	    tf[MAX_STRING_LEN-1] = '\0';
-            table_set (env, "DATE_LOCAL", ht_time(r->pool,date,tf,0));
-            table_set (env, "DATE_GMT", ht_time(r->pool,date,tf,1));
-            table_set (env, "LAST_MODIFIED", ht_time(r->pool,r->finfo.st_mtime,tf,0));
-        }
-        else if(!strcmp(tag,"sizefmt")) {
-            parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
-	    decodehtml(parsed_string);
-            if(!strcmp(parsed_string,"bytes"))
+        }
+        if (!strcmp(tag, "errmsg")) {
+            parse_string(r, tag_val, error, MAX_STRING_LEN, 0);
+        }
+        else if (!strcmp(tag, "timefmt")) {
+            time_t date = r->request_time;
+
+            parse_string(r, tag_val, tf, MAX_STRING_LEN, 0);
+            table_set(env, "DATE_LOCAL", ht_time(r->pool, date, tf, 0));
+            table_set(env, "DATE_GMT", ht_time(r->pool, date, tf, 1));
+            table_set(env, "LAST_MODIFIED",
+                      ht_time(r->pool, r->finfo.st_mtime, tf, 0));
+        }
+        else if (!strcmp(tag, "sizefmt")) {
+            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+            decodehtml(parsed_string);
+            if (!strcmp(parsed_string, "bytes")) {
                 *sizefmt = SIZEFMT_BYTES;
-            else if(!strcmp(parsed_string,"abbrev"))
+            }
+            else if (!strcmp(parsed_string, "abbrev")) {
                 *sizefmt = SIZEFMT_KMG;
-        } 
-        else if(!strcmp(tag,"done"))
+            }
+        }
+        else if (!strcmp(tag, "done")) {
             return 0;
+        }
         else {
-            log_printf(r->server,"unknown parameter \"%s\" to tag config in %s",
-                    tag, r->filename);
+            log_printf(r->server,
+                        "unknown parameter \"%s\" to tag config in %s",
+                        tag, r->filename);
             rputs(error, r);
         }
     }
 }
 
 
-
-int find_file(request_rec *r, char *directive, char *tag, 
-              char *tag_val, struct stat *finfo, char *error)
+static int find_file(request_rec *r, const char *directive, const char *tag,
+                     char *tag_val, struct stat *finfo, const char *error)
 {
-    char *dir = "./";
     char *to_send;
 
-    if(!strcmp(tag,"file")) {
-        getparents(tag_val); /* get rid of any nasties */
-        to_send = make_full_path (r->pool, dir, tag_val);
-        if(stat(to_send,finfo) == -1) {
+    if (!strcmp(tag, "file")) {
+        getparents(tag_val);    /* get rid of any nasties */
+        to_send = make_full_path(r->pool, "./", tag_val);
+        if (stat(to_send, finfo) == -1) {
             log_printf(r->server,
-                    "unable to get information about \"%s\" in parsed file %s",
-                    to_send, r->filename);
+                        "unable to get information about \"%s\" "
+                        "in parsed file %s",
+                        to_send, r->filename);
             rputs(error, r);
             return -1;
         }
         return 0;
     }
-    else if(!strcmp(tag,"virtual")) {
-	request_rec *rr = sub_req_lookup_uri (tag_val, r);
-	
-	if (rr->status == 200 && rr->finfo.st_mode != 0) {
-	    memcpy ((char*)finfo, (const char *)&rr->finfo, sizeof (struct stat));
-	    destroy_sub_req (rr);
-	    return 0;
-        } else {
+    else if (!strcmp(tag, "virtual")) {
+        request_rec *rr = sub_req_lookup_uri(tag_val, r);
+
+        if (rr->status == HTTP_OK && rr->finfo.st_mode != 0) {
+            memcpy((char *) finfo, (const char *) &rr->finfo,
+                   sizeof(struct stat));
+            destroy_sub_req(rr);
+            return 0;
+        }
+        else {
             log_printf(r->server,
-                    "unable to get information about \"%s\" in parsed file %s",
-                    tag_val, r->filename);
+                        "unable to get information about \"%s\" "
+                        "in parsed file %s",
+                        tag_val, r->filename);
             rputs(error, r);
-	    destroy_sub_req (rr);
+            destroy_sub_req(rr);
             return -1;
         }
     }
     else {
-        log_printf(r->server,"unknown parameter \"%s\" to tag %s in %s",
-                tag, directive, r->filename);
+        log_printf(r->server,
+                    "unknown parameter \"%s\" to tag %s in %s",
+                    tag, directive, r->filename);
         rputs(error, r);
         return -1;
     }
 }
 
 
-int handle_fsize(FILE *in, request_rec *r, char *error, int sizefmt) 
+static int handle_fsize(FILE *in, request_rec *r, const char *error, int sizefmt)
 {
     char tag[MAX_STRING_LEN];
     char *tag_val;
     struct stat finfo;
     char parsed_string[MAX_STRING_LEN];
 
-    while(1) {
-        if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
+    while (1) {
+        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
             return 1;
-        else if(!strcmp(tag,"done"))
+        }
+        else if (!strcmp(tag, "done")) {
             return 0;
+        }
         else {
-            parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
-            if(!find_file(r,"fsize",tag,parsed_string,&finfo,error)) {
-                if(sizefmt == SIZEFMT_KMG) {
+            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+            if (!find_file(r, "fsize", tag, parsed_string, &finfo, error)) {
+                if (sizefmt == SIZEFMT_KMG) {
                     send_size(finfo.st_size, r);
                 }
                 else {
-                    int l,x;
+                    int l, x;
 #if defined(BSD) && BSD > 199305
-		    /* ap_snprintf can't handle %qd */
-                    sprintf(tag,"%qd", finfo.st_size);
+                    /* ap_snprintf can't handle %qd */
+                    sprintf(tag, "%qd", finfo.st_size);
 #else
-                    ap_snprintf(tag, sizeof(tag), "%ld",finfo.st_size);
+                    ap_snprintf(tag, sizeof(tag), "%ld", finfo.st_size);
 #endif
-                    l = strlen(tag); /* grrr */
-                    for(x=0;x<l;x++) {
-                        if(x && (!((l-x) % 3))) {
+                    l = strlen(tag);    /* grrr */
+                    for (x = 0; x < l; x++) {
+                        if (x && (!((l - x) % 3))) {
                             rputc(',', r);
                         }
-                        rputc (tag[x],r);
+                        rputc(tag[x], r);
                     }
                 }
             }
@@ -895,136 +1042,204 @@
     }
 }
 
-int handle_flastmod(FILE *in, request_rec *r, char *error, char *tf) 
+static int handle_flastmod(FILE *in, request_rec *r, const char *error, const char *tf)
 {
     char tag[MAX_STRING_LEN];
     char *tag_val;
     struct stat finfo;
     char parsed_string[MAX_STRING_LEN];
 
-    while(1) {
-        if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
+    while (1) {
+        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
             return 1;
-        else if(!strcmp(tag,"done"))
+        }
+        else if (!strcmp(tag, "done")) {
             return 0;
+        }
         else {
-            parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
-            if(!find_file(r,"flastmod",tag,parsed_string,&finfo,error))
+            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+            if (!find_file(r, "flastmod", tag, parsed_string, &finfo, error)) {
                 rputs(ht_time(r->pool, finfo.st_mtime, tf, 0), r);
+            }
         }
     }
-}    
+}
 
-int re_check(request_rec *r, char *string, char *rexp) 
+static int re_check(request_rec *r, char *string, char *rexp)
 {
     regex_t *compiled;
     int regex_error;
 
-    compiled = pregcomp (r->pool, rexp, REG_EXTENDED|REG_NOSUB);
+    compiled = pregcomp(r->pool, rexp, REG_EXTENDED | REG_NOSUB);
     if (compiled == NULL) {
         log_printf(r->server, "unable to compile pattern \"%s\"", rexp);
         return -1;
     }
-    regex_error = regexec(compiled, string, 0, (regmatch_t *)NULL, 0);
-    pregfree (r->pool, compiled);
-    return(!regex_error);
+    regex_error = regexec(compiled, string, 0, (regmatch_t *) NULL, 0);
+    pregfree(r->pool, compiled);
+    return (!regex_error);
 }
 
-enum token_type { token_string,
+enum token_type {
+    token_string,
     token_and, token_or, token_not, token_eq, token_ne,
-    token_rbrace, token_lbrace, token_group
+    token_rbrace, token_lbrace, token_group,
+    token_ge, token_le, token_gt, token_lt
 };
 struct token {
     enum token_type type;
     char value[MAX_STRING_LEN];
 };
 
-char *get_ptoken(request_rec *r, char *string, struct token *token) {
+/* there is an implicit assumption here that string is at most MAX_STRING_LEN-1
+ * characters long...
+ */
+static const char *get_ptoken(request_rec *r, const char *string, struct token *token)
+{
     char ch;
-    int next=0;
-    int qs=0;
+    int next = 0;
+    int qs = 0;
 
     /* Skip leading white space */
-    if (string == (char *)NULL) return (char *)NULL;
-    while ((ch = *string++))
-        if (!isspace(ch)) break;
-    if (ch == '\0') return (char *)NULL;
+    if (string == (char *) NULL) {
+        return (char *) NULL;
+    }
+    while ((ch = *string++)) {
+        if (!isspace(ch)) {
+            break;
+        }
+    }
+    if (ch == '\0') {
+        return (char *) NULL;
+    }
 
-    switch(ch) {
-      case '(':
+    token->type = token_string; /* the default type */
+    switch (ch) {
+    case '(':
         token->type = token_lbrace;
-        return(string);
-      case ')':
+        return (string);
+    case ')':
         token->type = token_rbrace;
-        return(string);
-      case '=':
+        return (string);
+    case '=':
         token->type = token_eq;
-        return(string);
-      case '!':
+        return (string);
+    case '!':
         if (*string == '=') {
             token->type = token_ne;
-            return(string+1);
-        } else {
+            return (string + 1);
+        }
+        else {
             token->type = token_not;
-            return(string);
+            return (string);
         }
-      case '\'':
+    case '\'':
         token->type = token_string;
         qs = 1;
         break;
-      case '|':
+    case '|':
         if (*string == '|') {
             token->type = token_or;
-            return(string+1);
+            return (string + 1);
         }
-      case '&':
+        break;
+    case '&':
         if (*string == '&') {
             token->type = token_and;
-            return(string+1);
+            return (string + 1);
+        }
+        break;
+    case '>':
+        if (*string == '=') {
+            token->type = token_ge;
+            return (string + 1);
+        }
+        else {
+            token->type = token_gt;
+            return (string);
+        }
+    case '<':
+        if (*string == '=') {
+            token->type = token_le;
+            return (string + 1);
+        }
+        else {
+            token->type = token_lt;
+            return (string);
         }
-      default:
+    default:
         token->type = token_string;
         break;
     }
     /* We should only be here if we are in a string */
-    if (!qs) token->value[next++] = ch;
+    if (!qs) {
+        token->value[next++] = ch;
+    }
 
     /* 
      * Yes I know that goto's are BAD.  But, c doesn't allow me to
      * exit a loop from a switch statement.  Yes, I could use a flag,
      * but that is (IMHO) even less readable/maintainable than the goto.
-     */ 
+     */
     /* 
      * I used the ++string throughout this section so that string
      * ends up pointing to the next token and I can just return it
-     */ 
+     */
     for (ch = *string; ch != '\0'; ch = *++string) {
         if (ch == '\\') {
-            if ((ch = *++string) == '\0') goto TOKEN_DONE;
+            if ((ch = *++string) == '\0') {
+                goto TOKEN_DONE;
+            }
             token->value[next++] = ch;
             continue;
         }
         if (!qs) {
-            if (isspace(ch)) goto TOKEN_DONE;
-            switch(ch) {
-              case '(': goto TOKEN_DONE;
-              case ')': goto TOKEN_DONE;
-              case '=': goto TOKEN_DONE;
-              case '!': goto TOKEN_DONE;
-              case '|': if (*(string+1) == '|') goto TOKEN_DONE;
-              case '&': if (*(string+1) == '&') goto TOKEN_DONE;
+            if (isspace(ch)) {
+                goto TOKEN_DONE;
+            }
+            switch (ch) {
+            case '(':
+                goto TOKEN_DONE;
+            case ')':
+                goto TOKEN_DONE;
+            case '=':
+                goto TOKEN_DONE;
+            case '!':
+                goto TOKEN_DONE;
+            case '|':
+                if (*(string + 1) == '|') {
+                    goto TOKEN_DONE;
+                }
+                break;
+            case '&':
+                if (*(string + 1) == '&') {
+                    goto TOKEN_DONE;
+                }
+                break;
+            case '<':
+                goto TOKEN_DONE;
+            case '>':
+                goto TOKEN_DONE;
             }
             token->value[next++] = ch;
-        } else {
-            if (ch == '\'') { qs=0; ++string; goto TOKEN_DONE; }
+        }
+        else {
+            if (ch == '\'') {
+                qs = 0;
+                ++string;
+                goto TOKEN_DONE;
+            }
             token->value[next++] = ch;
         }
     }
-TOKEN_DONE:
+  TOKEN_DONE:
     /* If qs is still set, I have an unmatched ' */
-    if (qs) { rputs("\nUnmatched '\n", r); next=0; }
+    if (qs) {
+        rputs("\nUnmatched '\n", r);
+        next = 0;
+    }
     token->value[next] = '\0';
-    return(string);
+    return (string);
 }
 
 
@@ -1035,112 +1250,124 @@
  * cases.  And, without rewriting this completely, the easiest way
  * is to just branch to the return code which cleans it up.
  */
-int parse_expr(request_rec *r, char *expr, char *error)
+/* there is an implicit assumption here that expr is at most MAX_STRING_LEN-1
+ * characters long...
+ */
+static int parse_expr(request_rec *r, const char *expr, const char *error)
 {
     struct parse_node {
         struct parse_node *left, *right, *parent;
         struct token token;
         int value, done;
-    } *root, *current, *new;
-    char *parse;
+    }         *root, *current, *new;
+    const char *parse;
     char buffer[MAX_STRING_LEN];
-    struct pool *expr_pool;
+    pool *expr_pool;
     int retval = 0;
 
-    if ((parse = expr) == (char *)NULL) return(0);
-    root = current = (struct parse_node*)NULL;
-    if ((expr_pool = make_sub_pool(r->pool)) == (struct pool *)NULL) {
-        log_printf(r->server, "out of memory processing file %s", r->filename);
-        rputs(error, r);
-        return(0);
+    if ((parse = expr) == (char *) NULL) {
+        return (0);
     }
+    root = current = (struct parse_node *) NULL;
+    expr_pool = make_sub_pool(r->pool);
 
     /* Create Parse Tree */
     while (1) {
-        new = (struct parse_node*)palloc(expr_pool, sizeof (struct parse_node));
-        if (new == (struct parse_node*)NULL) {
-            log_printf(r->server,"out of memory processing file %s", r->filename);
-            rputs(error, r);
-            goto RETURN;
-        }
-        new->parent = new->left = new->right = (struct parse_node*)NULL;
+        new = (struct parse_node *) palloc(expr_pool,
+                                           sizeof(struct parse_node));
+        new->parent = new->left = new->right = (struct parse_node *) NULL;
         new->done = 0;
-        if ((parse = get_ptoken(r, parse, &new->token)) == (char *)NULL)
+        if ((parse = get_ptoken(r, parse, &new->token)) == (char *) NULL) {
             break;
-        switch(new->token.type) {
+        }
+        switch (new->token.type) {
 
-          case token_string:
+        case token_string:
 #ifdef DEBUG_INCLUDE
-            rvputs(r,"     Token: string (", new->token.value, ")\n", NULL);
+            rvputs(r, "     Token: string (", new->token.value, ")\n", NULL);
 #endif
-            if (current == (struct parse_node*)NULL) {
+            if (current == (struct parse_node *) NULL) {
                 root = current = new;
                 break;
             }
-            switch(current->token.type) {
-              case token_string:
-                if (current->token.value[0] != '\0')
-                    strncat(current->token.value, " ", 
-			MAX_STRING_LEN-strlen(current->token.value)-1);
-                strncat(current->token.value, new->token.value, 
-			MAX_STRING_LEN-strlen(current->token.value)-1);
+            switch (current->token.type) {
+            case token_string:
+                if (current->token.value[0] != '\0') {
+                    strncat(current->token.value, " ",
+                         MAX_STRING_LEN - strlen(current->token.value) - 1);
+                }
+                strncat(current->token.value, new->token.value,
+                        MAX_STRING_LEN - strlen(current->token.value) - 1);
+		current->token.value[sizeof(current->token.value) - 1] = '\0';
                 break;
-              case token_eq:
-              case token_ne:
-              case token_and:
-              case token_or:
-              case token_lbrace:
-              case token_not:
+            case token_eq:
+            case token_ne:
+            case token_and:
+            case token_or:
+            case token_lbrace:
+            case token_not:
+            case token_ge:
+            case token_gt:
+            case token_le:
+            case token_lt:
                 new->parent = current;
                 current = current->right = new;
                 break;
-              default:
+            default:
                 log_printf(r->server,
-                    "Invalid expression \"%s\" in file %s", expr, r->filename);
+                            "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
                 rputs(error, r);
                 goto RETURN;
             }
             break;
 
-          case token_and:
-          case token_or:
+        case token_and:
+        case token_or:
 #ifdef DEBUG_INCLUDE
-rputs ("     Token: and/or\n", r);
+            rputs("     Token: and/or\n", r);
 #endif
-            if (current == (struct parse_node*)NULL) {
+            if (current == (struct parse_node *) NULL) {
                 log_printf(r->server,
-                    "Invalid expression \"%s\" in file %s", expr, r->filename);
+                            "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
                 rputs(error, r);
                 goto RETURN;
             }
             /* Percolate upwards */
-            while (current != (struct parse_node *)NULL) {
-                switch(current->token.type) {
-                  case token_string:
-                  case token_group:
-                  case token_not:
-                  case token_eq:
-                  case token_ne:
-                  case token_and:
-                  case token_or:
+            while (current != (struct parse_node *) NULL) {
+                switch (current->token.type) {
+                case token_string:
+                case token_group:
+                case token_not:
+                case token_eq:
+                case token_ne:
+                case token_and:
+                case token_or:
+                case token_ge:
+                case token_gt:
+                case token_le:
+                case token_lt:
                     current = current->parent;
                     continue;
-                  case token_lbrace:
+                case token_lbrace:
                     break;
-                  default:
+                default:
                     log_printf(r->server,
-                        "Invalid expression \"%s\" in file %s", expr, r->filename);
+                                "Invalid expression \"%s\" in file %s",
+                                expr, r->filename);
                     rputs(error, r);
                     goto RETURN;
                 }
                 break;
             }
-            if (current == (struct parse_node*)NULL) {
+            if (current == (struct parse_node *) NULL) {
                 new->left = root;
                 new->left->parent = new;
-                new->parent = (struct parse_node*)NULL;
+                new->parent = (struct parse_node *) NULL;
                 root = new;
-            } else {
+            }
+            else {
                 new->left = current->right;
                 current->right = new;
                 new->parent = current;
@@ -1148,38 +1375,44 @@
             current = new;
             break;
 
-          case token_not:
+        case token_not:
 #ifdef DEBUG_INCLUDE
-rputs("     Token: not\n", r);
+            rputs("     Token: not\n", r);
 #endif
-            if (current == (struct parse_node*)NULL) {
+            if (current == (struct parse_node *) NULL) {
                 root = current = new;
                 break;
             }
             /* Percolate upwards */
-            while (current != (struct parse_node *)NULL) {
-                switch(current->token.type) {
-                  case token_not:
-                  case token_eq:
-                  case token_ne:
-                  case token_and:
-                  case token_or:
-                  case token_lbrace:
+            while (current != (struct parse_node *) NULL) {
+                switch (current->token.type) {
+                case token_not:
+                case token_eq:
+                case token_ne:
+                case token_and:
+                case token_or:
+                case token_lbrace:
+                case token_ge:
+                case token_gt:
+                case token_le:
+                case token_lt:
                     break;
-                  default:
+                default:
                     log_printf(r->server,
-                        "Invalid expression \"%s\" in file %s", expr, r->filename);
+                                "Invalid expression \"%s\" in file %s",
+                                expr, r->filename);
                     rputs(error, r);
                     goto RETURN;
                 }
                 break;
             }
-            if (current == (struct parse_node*)NULL) {
+            if (current == (struct parse_node *) NULL) {
                 new->left = root;
                 new->left->parent = new;
-                new->parent = (struct parse_node*)NULL;
+                new->parent = (struct parse_node *) NULL;
                 root = new;
-            } else {
+            }
+            else {
                 new->left = current->right;
                 current->right = new;
                 new->parent = current;
@@ -1187,46 +1420,56 @@
             current = new;
             break;
 
-          case token_eq:
-          case token_ne:
+        case token_eq:
+        case token_ne:
+        case token_ge:
+        case token_gt:
+        case token_le:
+        case token_lt:
 #ifdef DEBUG_INCLUDE
-rputs("     Token: eq/ne\n", r);
+            rputs("     Token: eq/ne/ge/gt/le/lt\n", r);
 #endif
-            if (current == (struct parse_node*)NULL) {
+            if (current == (struct parse_node *) NULL) {
                 log_printf(r->server,
-                    "Invalid expression \"%s\" in file %s", expr, r->filename);
+                            "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
                 rputs(error, r);
                 goto RETURN;
             }
             /* Percolate upwards */
-            while (current != (struct parse_node *)NULL) {
-                switch(current->token.type) {
-                  case token_string:
-                  case token_group:
+            while (current != (struct parse_node *) NULL) {
+                switch (current->token.type) {
+                case token_string:
+                case token_group:
                     current = current->parent;
                     continue;
-                  case token_lbrace:
-                  case token_and:
-                  case token_or:
+                case token_lbrace:
+                case token_and:
+                case token_or:
                     break;
-                  case token_not:
-                  case token_eq:
-                  case token_ne:
-                  default:
-                    log_printf(r->server, 
-                        "Invalid expression \"%s\" in file %s", 
-                        expr, r->filename);
+                case token_not:
+                case token_eq:
+                case token_ne:
+                case token_ge:
+                case token_gt:
+                case token_le:
+                case token_lt:
+                default:
+                    log_printf(r->server,
+                                "Invalid expression \"%s\" in file %s",
+                                expr, r->filename);
                     rputs(error, r);
                     goto RETURN;
                 }
                 break;
             }
-            if (current == (struct parse_node*)NULL) {
+            if (current == (struct parse_node *) NULL) {
                 new->left = root;
                 new->left->parent = new;
-                new->parent = (struct parse_node*)NULL;
+                new->parent = (struct parse_node *) NULL;
                 root = new;
-            } else {
+            }
+            else {
                 new->left = current->right;
                 current->right = new;
                 new->parent = current;
@@ -1234,422 +1477,536 @@
             current = new;
             break;
 
-          case token_rbrace:
+        case token_rbrace:
 #ifdef DEBUG_INCLUDE
-rputs("     Token: rbrace\n", r);
+            rputs("     Token: rbrace\n", r);
 #endif
-            while (current != (struct parse_node*)NULL) {
+            while (current != (struct parse_node *) NULL) {
                 if (current->token.type == token_lbrace) {
                     current->token.type = token_group;
                     break;
                 }
                 current = current->parent;
             }
-            if (current == (struct parse_node*)NULL) {
-                log_printf(r->server,"Unmatched ')' in %s", expr, r->filename);
+            if (current == (struct parse_node *) NULL) {
+                log_printf(r->server, "Unmatched ')' in \"%s\" in file %s",
+			    expr, r->filename);
                 rputs(error, r);
                 goto RETURN;
             }
             break;
 
-          case token_lbrace:
+        case token_lbrace:
 #ifdef DEBUG_INCLUDE
-rputs("     Token: lbrace\n", r);
+            rputs("     Token: lbrace\n", r);
 #endif
-            if (current == (struct parse_node*)NULL) {
+            if (current == (struct parse_node *) NULL) {
                 root = current = new;
                 break;
             }
             /* Percolate upwards */
-            while (current != (struct parse_node *)NULL) {
-                switch(current->token.type) {
-                  case token_not:
-                  case token_eq:
-                  case token_ne:
-                  case token_and:
-                  case token_or:
-                  case token_lbrace:
+            while (current != (struct parse_node *) NULL) {
+                switch (current->token.type) {
+                case token_not:
+                case token_eq:
+                case token_ne:
+                case token_and:
+                case token_or:
+                case token_lbrace:
+                case token_ge:
+                case token_gt:
+                case token_le:
+                case token_lt:
                     break;
-                  case token_string:
-                  case token_group:
-                  default:
+                case token_string:
+                case token_group:
+                default:
                     log_printf(r->server,
-                        "Invalid expression \"%s\" in file %s", 
-			expr, r->filename);
+                                "Invalid expression \"%s\" in file %s",
+                                expr, r->filename);
                     rputs(error, r);
                     goto RETURN;
                 }
                 break;
             }
-            if (current == (struct parse_node*)NULL) {
+            if (current == (struct parse_node *) NULL) {
                 new->left = root;
                 new->left->parent = new;
-                new->parent = (struct parse_node*)NULL;
+                new->parent = (struct parse_node *) NULL;
                 root = new;
-            } else {
+            }
+            else {
                 new->left = current->right;
                 current->right = new;
                 new->parent = current;
             }
             current = new;
             break;
-	  default:
-	    break;
+        default:
+            break;
         }
     }
 
     /* Evaluate Parse Tree */
     current = root;
-    while (current != (struct parse_node *)NULL) {
-        switch(current->token.type) {
-          case token_string:
-#ifdef DEBUG_INCLUDE
-rputs("     Evaluate string\n", r);
-#endif
-            parse_string(r, current->token.value, buffer, MAX_STRING_LEN, 0);
-            strncpy(current->token.value, buffer, MAX_STRING_LEN-1);
-	    current->token.value[MAX_STRING_LEN-1] = '\0';
+    while (current != (struct parse_node *) NULL) {
+        switch (current->token.type) {
+        case token_string:
+#ifdef DEBUG_INCLUDE
+            rputs("     Evaluate string\n", r);
+#endif
+            parse_string(r, current->token.value, buffer, sizeof(buffer), 0);
+	    safe_copy(current->token.value, buffer, sizeof(current->token.value));
             current->value = (current->token.value[0] != '\0');
             current->done = 1;
             current = current->parent;
             break;
 
-          case token_and:
-          case token_or:
+        case token_and:
+        case token_or:
 #ifdef DEBUG_INCLUDE
-rputs("     Evaluate and/or\n", r);
+            rputs("     Evaluate and/or\n", r);
 #endif
-            if (current->left == (struct parse_node*)NULL ||
-                        current->right == (struct parse_node*)NULL) {
-                log_printf(r->server,
-                    "Invalid expression \"%s\" in file %s", expr, r->filename);
+            if (current->left == (struct parse_node *) NULL ||
+                current->right == (struct parse_node *) NULL) {
+                log_printf(r->server, "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
                 rputs(error, r);
                 goto RETURN;
             }
             if (!current->left->done) {
-                switch(current->left->token.type) {
-                  case token_string:
+                switch (current->left->token.type) {
+                case token_string:
                     parse_string(r, current->left->token.value,
-                            buffer, MAX_STRING_LEN, 0);
-                    strncpy(current->left->token.value, buffer,
-                            MAX_STRING_LEN-1);
-		    current->left->token.value[MAX_STRING_LEN-1] = '\0';
-                  current->left->value = (current->token.value[0] != '\0');
+                                 buffer, sizeof(buffer), 0);
+                    safe_copy(current->left->token.value, buffer,
+                            sizeof(current->left->token.value));
+		    current->left->value = (current->left->token.value[0] != '\0');
                     current->left->done = 1;
                     break;
-                  default:
+                default:
                     current = current->left;
                     continue;
                 }
             }
             if (!current->right->done) {
-                switch(current->right->token.type) {
-                  case token_string:
+                switch (current->right->token.type) {
+                case token_string:
                     parse_string(r, current->right->token.value,
-                            buffer, MAX_STRING_LEN, 0);
-                    strncpy(current->right->token.value, buffer,
-                            MAX_STRING_LEN-1);
-		    current->right->token.value[MAX_STRING_LEN-1] = '\0';
-                  current->right->value = (current->token.value[0] != '\0');
+                                 buffer, sizeof(buffer), 0);
+                    safe_copy(current->right->token.value, buffer,
+                            sizeof(current->right->token.value));
+		    current->right->value = (current->right->token.value[0] != '\0');
                     current->right->done = 1;
                     break;
-                  default:
+                default:
                     current = current->right;
                     continue;
                 }
             }
 #ifdef DEBUG_INCLUDE
-rvputs(r,"     Left: ", current->left->value ? "1" : "0", "\n", NULL);
-rvputs(r,"     Right: ", current->right->value ? "1" : "0", "\n", NULL);
+            rvputs(r, "     Left: ", current->left->value ? "1" : "0",
+                   "\n", NULL);
+            rvputs(r, "     Right: ", current->right->value ? "1" : "0",
+                   "\n", NULL);
 #endif
-            if (current->token.type == token_and)
-                current->value =
-                    current->left->value && current->right->value;
-            else
-                current->value =
-                    current->left->value || current->right->value;
+            if (current->token.type == token_and) {
+                current->value = current->left->value && current->right->value;
+            }
+            else {
+                current->value = current->left->value || current->right->value;
+            }
 #ifdef DEBUG_INCLUDE
-rvputs(r,"     Returning ", current->value ? "1" : "0", "\n", NULL);
+            rvputs(r, "     Returning ", current->value ? "1" : "0",
+                   "\n", NULL);
 #endif
             current->done = 1;
             current = current->parent;
             break;
 
-          case token_eq:
-          case token_ne:
+        case token_eq:
+        case token_ne:
 #ifdef DEBUG_INCLUDE
-rputs("     Evaluate eq/ne\n", r);
+            rputs("     Evaluate eq/ne\n", r);
 #endif
-            if ((current->left == (struct parse_node*)NULL) ||
-                        (current->right == (struct parse_node*)NULL) ||
-                        (current->left->token.type != token_string) ||
-                        (current->right->token.type != token_string)) {
-                log_printf(r->server,
-                    "Invalid expression \"%s\" in file %s", expr, r->filename);
+            if ((current->left == (struct parse_node *) NULL) ||
+                (current->right == (struct parse_node *) NULL) ||
+                (current->left->token.type != token_string) ||
+                (current->right->token.type != token_string)) {
+                log_printf(r->server, "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
                 rputs(error, r);
                 goto RETURN;
             }
             parse_string(r, current->left->token.value,
-                         buffer, MAX_STRING_LEN, 0);
-            strncpy(current->left->token.value, buffer, MAX_STRING_LEN-1);
-	    current->left->token.value[MAX_STRING_LEN-1] = '\0';
+                         buffer, sizeof(buffer), 0);
+            safe_copy(current->left->token.value, buffer,
+			sizeof(current->left->token.value));
             parse_string(r, current->right->token.value,
-                         buffer, MAX_STRING_LEN, 0);
-            strncpy(current->right->token.value, buffer, MAX_STRING_LEN-1);
-	    current->right->token.value[MAX_STRING_LEN-1] = '\0';
+                         buffer, sizeof(buffer), 0);
+            safe_copy(current->right->token.value, buffer,
+			sizeof(current->right->token.value));
             if (current->right->token.value[0] == '/') {
                 int len;
                 len = strlen(current->right->token.value);
-                if (current->right->token.value[len-1] == '/') {
-                    current->right->token.value[len-1] = '\0';
-                } else {
-                    log_printf(r->server,"Invalid rexp \"%s\" in file %s",
-                            current->right->token.value, r->filename);
+                if (current->right->token.value[len - 1] == '/') {
+                    current->right->token.value[len - 1] = '\0';
+                }
+                else {
+                    log_printf(r->server, "Invalid rexp \"%s\" in file %s",
+                                current->right->token.value, r->filename);
                     rputs(error, r);
                     goto RETURN;
                 }
 #ifdef DEBUG_INCLUDE
-rvputs(r,"     Re Compare (", current->left->token.value,
-         ") with /", &current->right->token.value[1], "/\n", NULL);
+                rvputs(r, "     Re Compare (", current->left->token.value,
+                  ") with /", &current->right->token.value[1], "/\n", NULL);
 #endif
                 current->value =
                     re_check(r, current->left->token.value,
-                            &current->right->token.value[1]);
-            } else {
+                             &current->right->token.value[1]);
+            }
+            else {
 #ifdef DEBUG_INCLUDE
-rvputs(r,"     Compare (", current->left->token.value,
-         ") with (", current->right->token.value, ")\n", NULL);
+                rvputs(r, "     Compare (", current->left->token.value,
+                       ") with (", current->right->token.value, ")\n", NULL);
 #endif
                 current->value =
-                        (strcmp(current->left->token.value,
-                         current->right->token.value) == 0);
+                    (strcmp(current->left->token.value,
+                            current->right->token.value) == 0);
             }
-            if (current->token.type == token_ne)
+            if (current->token.type == token_ne) {
                 current->value = !current->value;
+            }
 #ifdef DEBUG_INCLUDE
-rvputs(r,"     Returning ", current->value ? "1" : "0", "\n", NULL);
+            rvputs(r, "     Returning ", current->value ? "1" : "0",
+                   "\n", NULL);
+#endif
+            current->done = 1;
+            current = current->parent;
+            break;
+        case token_ge:
+        case token_gt:
+        case token_le:
+        case token_lt:
+#ifdef DEBUG_INCLUDE
+            rputs("     Evaluate ge/gt/le/lt\n", r);
+#endif
+            if ((current->left == (struct parse_node *) NULL) ||
+                (current->right == (struct parse_node *) NULL) ||
+                (current->left->token.type != token_string) ||
+                (current->right->token.type != token_string)) {
+                log_printf(r->server, "Invalid expression \"%s\" in file %s",
+                            expr, r->filename);
+                rputs(error, r);
+                goto RETURN;
+            }
+            parse_string(r, current->left->token.value,
+                         buffer, sizeof(buffer), 0);
+            safe_copy(current->left->token.value, buffer,
+			sizeof(current->left->token.value));
+            parse_string(r, current->right->token.value,
+                         buffer, sizeof(buffer), 0);
+            safe_copy(current->right->token.value, buffer,
+			sizeof(current->right->token.value));
+#ifdef DEBUG_INCLUDE
+            rvputs(r, "     Compare (", current->left->token.value,
+                   ") with (", current->right->token.value, ")\n", NULL);
+#endif
+            current->value =
+                strcmp(current->left->token.value,
+                       current->right->token.value);
+            if (current->token.type == token_ge) {
+                current->value = current->value >= 0;
+            }
+            else if (current->token.type == token_gt) {
+                current->value = current->value > 0;
+            }
+            else if (current->token.type == token_le) {
+                current->value = current->value <= 0;
+            }
+            else if (current->token.type == token_lt) {
+                current->value = current->value < 0;
+            }
+            else {
+                current->value = 0;     /* Don't return -1 if unknown token */
+            }
+#ifdef DEBUG_INCLUDE
+            rvputs(r, "     Returning ", current->value ? "1" : "0",
+                   "\n", NULL);
 #endif
             current->done = 1;
             current = current->parent;
             break;
 
-          case token_not:
-            if (current->right != (struct parse_node *)NULL) {
+        case token_not:
+            if (current->right != (struct parse_node *) NULL) {
                 if (!current->right->done) {
                     current = current->right;
                     continue;
                 }
                 current->value = !current->right->value;
-            } else {
+            }
+            else {
                 current->value = 0;
             }
 #ifdef DEBUG_INCLUDE
-rvputs(r,"     Evaluate !: ", current->value ? "1" : "0", "\n", NULL);
+            rvputs(r, "     Evaluate !: ", current->value ? "1" : "0",
+                   "\n", NULL);
 #endif
             current->done = 1;
             current = current->parent;
             break;
 
-          case token_group:
-            if (current->right != (struct parse_node *)NULL) {
+        case token_group:
+            if (current->right != (struct parse_node *) NULL) {
                 if (!current->right->done) {
                     current = current->right;
                     continue;
                 }
                 current->value = current->right->value;
-            } else {
+            }
+            else {
                 current->value = 1;
             }
 #ifdef DEBUG_INCLUDE
-rvputs(r,"     Evaluate (): ", current->value ? "1" : "0", "\n", NULL);
+            rvputs(r, "     Evaluate (): ", current->value ? "1" : "0",
+                   "\n", NULL);
 #endif
             current->done = 1;
             current = current->parent;
             break;
 
-          case token_lbrace:
-            log_printf(r->server,"Unmatched '(' in %s in file %s", 
-		expr, r->filename);
+        case token_lbrace:
+            log_printf(r->server, "Unmatched '(' in \"%s\" in file %s",
+                        expr, r->filename);
             rputs(error, r);
             goto RETURN;
 
-          case token_rbrace:
-            log_printf(r->server,"Unmatched ')' in %s in file %s\n", 
-		expr, r->filename);
+        case token_rbrace:
+            log_printf(r->server, "Unmatched ')' in \"%s\" in file %s\n",
+                        expr, r->filename);
             rputs(error, r);
             goto RETURN;
 
-          default:
-            log_printf(r->server,"bad token type");
+        default:
+            log_printf(r->server, "bad token type");
             rputs(error, r);
             goto RETURN;
         }
     }
 
-    retval =  (root == (struct parse_node *)NULL) ? 0 : root->value;
-RETURN:
+    retval = (root == (struct parse_node *) NULL) ? 0 : root->value;
+  RETURN:
     destroy_pool(expr_pool);
     return (retval);
-}    
+}
 
-int handle_if(FILE *in, request_rec *r, char *error,
-              int *conditional_status, int *printing) 
+static int handle_if(FILE *in, request_rec *r, const char *error,
+                     int *conditional_status, int *printing)
 {
     char tag[MAX_STRING_LEN];
-    char *tag_val = '\0';
-    char *expr = '\0';
+    char *tag_val;
+    char *expr;
 
-    while(1) {
-        tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 0);
-        if(*tag == '\0')
+    expr = NULL;
+    while (1) {
+        tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0);
+        if (*tag == '\0') {
             return 1;
-        else if(!strcmp(tag,"done")) {
+        }
+        else if (!strcmp(tag, "done")) {
+	    if (expr == NULL) {
+		log_printf(r->server, "missing expr in if statement: %s",
+			    r->filename);
+		rputs(error, r);
+		return 1;
+	    }
             *printing = *conditional_status = parse_expr(r, expr, error);
 #ifdef DEBUG_INCLUDE
-rvputs(r,"**** if conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
+            rvputs(r, "**** if conditional_status=\"",
+                   *conditional_status ? "1" : "0", "\"\n", NULL);
 #endif
             return 0;
-        } else if(!strcmp(tag,"expr")) {
-	    expr = tag_val;
+        }
+        else if (!strcmp(tag, "expr")) {
+            expr = tag_val;
 #ifdef DEBUG_INCLUDE
-rvputs(r,"**** if expr=\"", expr, "\"\n", NULL);
+            rvputs(r, "**** if expr=\"", expr, "\"\n", NULL);
 #endif
-        } else {
-            log_printf(r->server,"unknown parameter \"%s\" to tag if in %s",
-                    tag, r->filename);
+        }
+        else {
+            log_printf(r->server, "unknown parameter \"%s\" to tag if in %s",
+                        tag, r->filename);
             rputs(error, r);
         }
     }
-}    
+}
 
-int handle_elif(FILE *in, request_rec *r, char *error,
-              int *conditional_status, int *printing) 
+static int handle_elif(FILE *in, request_rec *r, const char *error,
+                       int *conditional_status, int *printing)
 {
     char tag[MAX_STRING_LEN];
-    char *tag_val = '\0';
-    char *expr = '\0';
+    char *tag_val;
+    char *expr;
 
-    while(1) {
-        tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 0);
-        if(*tag == '\0')
+    expr = NULL;
+    while (1) {
+        tag_val = get_tag(r->pool, in, tag, sizeof(tag), 0);
+        if (*tag == '\0') {
             return 1;
-        else if(!strcmp(tag,"done")) {
+        }
+        else if (!strcmp(tag, "done")) {
 #ifdef DEBUG_INCLUDE
-rvputs(r,"**** elif conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
+            rvputs(r, "**** elif conditional_status=\"",
+                   *conditional_status ? "1" : "0", "\"\n", NULL);
 #endif
             if (*conditional_status) {
                 *printing = 0;
-                return(0);
+                return (0);
             }
+	    if (expr == NULL) {
+		log_printf(r->server, "missing expr in elif statement: %s",
+			    r->filename);
+		rputs(error, r);
+		return 1;
+	    }
             *printing = *conditional_status = parse_expr(r, expr, error);
 #ifdef DEBUG_INCLUDE
-rvputs(r,"**** elif conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
+            rvputs(r, "**** elif conditional_status=\"",
+                   *conditional_status ? "1" : "0", "\"\n", NULL);
 #endif
             return 0;
-        } else if(!strcmp(tag,"expr")) {
-	    expr = tag_val;
+        }
+        else if (!strcmp(tag, "expr")) {
+            expr = tag_val;
 #ifdef DEBUG_INCLUDE
-rvputs(r,"**** if expr=\"", expr, "\"\n", NULL);
+            rvputs(r, "**** if expr=\"", expr, "\"\n", NULL);
 #endif
-        } else {
-            log_printf(r->server,"unknown parameter \"%s\" to tag if in %s",
-                    tag, r->filename);
+        }
+        else {
+            log_printf(r->server, "unknown parameter \"%s\" to tag if in %s",
+                        tag, r->filename);
             rputs(error, r);
         }
     }
 }
 
-int handle_else(FILE *in, request_rec *r, char *error,
-              int *conditional_status, int *printing) 
+static int handle_else(FILE *in, request_rec *r, const char *error,
+                       int *conditional_status, int *printing)
 {
     char tag[MAX_STRING_LEN];
     char *tag_val;
 
-    if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
+    if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
         return 1;
-    else if(!strcmp(tag,"done")) {
+    }
+    else if (!strcmp(tag, "done")) {
 #ifdef DEBUG_INCLUDE
-rvputs(r,"**** else conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
+        rvputs(r, "**** else conditional_status=\"",
+               *conditional_status ? "1" : "0", "\"\n", NULL);
 #endif
         *printing = !(*conditional_status);
         *conditional_status = 1;
         return 0;
-    } else {
-        log_printf(r->server, "else directive does not take tags");
-        if (*printing) rputs(error, r);
+    }
+    else {
+        log_printf(r->server, "else directive does not take tags in %s",
+		    r->filename);
+        if (*printing) {
+            rputs(error, r);
+        }
         return -1;
     }
-}    
+}
 
-int handle_endif(FILE *in, request_rec *r, char *error, 
-              int *conditional_status, int *printing) 
+static int handle_endif(FILE *in, request_rec *r, const char *error,
+                        int *conditional_status, int *printing)
 {
     char tag[MAX_STRING_LEN];
     char *tag_val;
 
-    if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1))) {
+    if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
         return 1;
-    } else if(!strcmp(tag,"done")) {
+    }
+    else if (!strcmp(tag, "done")) {
 #ifdef DEBUG_INCLUDE
-rvputs(r,"**** endif conditional_status=\"", *conditional_status ? "1" : "0", "\"\n", NULL);
+        rvputs(r, "**** endif conditional_status=\"",
+               *conditional_status ? "1" : "0", "\"\n", NULL);
 #endif
         *printing = 1;
         *conditional_status = 1;
         return 0;
-    } else {
-        log_printf(r->server, "endif directive does not take tags");
+    }
+    else {
+        log_printf(r->server, "endif directive does not take tags in %s",
+		    r->filename);
         rputs(error, r);
         return -1;
     }
-}    
+}
 
-int handle_set(FILE *in, request_rec *r, char *error) 
+static int handle_set(FILE *in, request_rec *r, const char *error)
 {
     char tag[MAX_STRING_LEN];
     char parsed_string[MAX_STRING_LEN];
     char *tag_val;
     char *var;
 
-    var = (char *)NULL;
+    var = (char *) NULL;
     while (1) {
-        if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
+        if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
             return 1;
-        else if(!strcmp(tag,"done"))
+        }
+        else if (!strcmp(tag, "done")) {
             return 0;
-        else if (!strcmp(tag,"var")) {
+        }
+        else if (!strcmp(tag, "var")) {
             var = tag_val;
-        } else if (!strcmp(tag,"value")) {
-            if (var == (char *)NULL) {
+        }
+        else if (!strcmp(tag, "value")) {
+            if (var == (char *) NULL) {
                 log_printf(r->server,
-                    "variable must precede value in set directive");
+                            "variable must precede value in set directive in %s",
+			    r->filename);
                 rputs(error, r);
                 return -1;
-            } 
-            parse_string(r, tag_val, parsed_string, MAX_STRING_LEN, 0);
-            table_set (r->subprocess_env, var, parsed_string);
+            }
+            parse_string(r, tag_val, parsed_string, sizeof(parsed_string), 0);
+            table_set(r->subprocess_env, var, parsed_string);
+        }
+        else {
+            log_printf(r->server, "Invalid tag for set directive in %s",
+			r->filename);
+            rputs(error, r);
+            return -1;
         }
     }
-}    
+}
 
-int handle_printenv(FILE *in, request_rec *r, char *error) 
+static int handle_printenv(FILE *in, request_rec *r, const char *error)
 {
     char tag[MAX_STRING_LEN];
     char *tag_val;
     table_entry *elts = (table_entry *) r->subprocess_env->elts;
     int i;
 
-    if(!(tag_val = get_tag(r->pool, in, tag, MAX_STRING_LEN, 1)))
+    if (!(tag_val = get_tag(r->pool, in, tag, sizeof(tag), 1))) {
         return 1;
-    else if(!strcmp(tag,"done")) {
-            for (i = 0; i < r->subprocess_env->nelts; ++i)
-                rvputs(r, elts[i].key, "=", elts[i].val, "\n", NULL);
-            return 0;
-    } else {
-        log_printf(r->server, "printenv directive does not take tags");
+    }
+    else if (!strcmp(tag, "done")) {
+        for (i = 0; i < r->subprocess_env->nelts; ++i) {
+            rvputs(r, elts[i].key, "=", elts[i].val, "\n", NULL);
+        }
+        return 0;
+    }
+    else {
+        log_printf(r->server, "printenv directive does not take tags in %s",
+		    r->filename);
         rputs(error, r);
         return -1;
     }
-}    
+}
 
 
 
@@ -1657,105 +2014,139 @@
 
 /* This is a stub which parses a file descriptor. */
 
-void send_parsed_content(FILE *f, request_rec *r)
+static void send_parsed_content(FILE *f, request_rec *r)
 {
     char directive[MAX_STRING_LEN], error[MAX_STRING_LEN];
     char timefmt[MAX_STRING_LEN];
-    int noexec = allow_options (r) & OPT_INCNOEXEC;
+    int noexec = allow_options(r) & OPT_INCNOEXEC;
     int ret, sizefmt;
     int if_nesting;
     int printing;
     int conditional_status;
 
-    strncpy(error,DEFAULT_ERROR_MSG, sizeof(error)-1);
-    error[sizeof(error)-1] = '\0';
-    strncpy(timefmt,DEFAULT_TIME_FORMAT, sizeof(timefmt)-1);
-    timefmt[sizeof(timefmt)-1] = '\0';
+    safe_copy(error, DEFAULT_ERROR_MSG, sizeof(error));
+    safe_copy(timefmt, DEFAULT_TIME_FORMAT, sizeof(timefmt));
     sizefmt = SIZEFMT_KMG;
 
 /*  Turn printing on */
     printing = conditional_status = 1;
     if_nesting = 0;
 
-    chdir_file (r->filename);
-    if (r->args) { /* add QUERY stuff to env cause it ain't yet */
-	char *arg_copy = pstrdup (r->pool, r->args);
-
-        table_set (r->subprocess_env, "QUERY_STRING", r->args);
-        unescape_url (arg_copy);
-        table_set (r->subprocess_env, "QUERY_STRING_UNESCAPED",
-                escape_shell_cmd (r->pool, arg_copy));
+    chdir_file(r->filename);
+    if (r->args) {              /* add QUERY stuff to env cause it ain't yet */
+        char *arg_copy = pstrdup(r->pool, r->args);
+
+        table_set(r->subprocess_env, "QUERY_STRING", r->args);
+        unescape_url(arg_copy);
+        table_set(r->subprocess_env, "QUERY_STRING_UNESCAPED",
+                  escape_shell_cmd(r->pool, arg_copy));
     }
 
-    while(1) {
-        if(!find_string(f,STARTING_SEQUENCE,r,printing)) {
-            if(get_directive(f,directive,r->pool))
+    while (1) {
+        if (!find_string(f, STARTING_SEQUENCE, r, printing)) {
+            if (get_directive(f, directive, sizeof(directive), r->pool)) {
+		log_printf(r->server,
+			    "mod_include: error reading directive in %s",
+			    r->filename);
+		rputs(error, r);
                 return;
-            if(!strcmp(directive,"if")) {
+            }
+            if (!strcmp(directive, "if")) {
                 if (!printing) {
                     if_nesting++;
-                } else {
-                    ret=handle_if(f, r, error, &conditional_status, &printing);
+                }
+                else {
+                    ret = handle_if(f, r, error, &conditional_status,
+                                    &printing);
                     if_nesting = 0;
                 }
                 continue;
-            } else if(!strcmp(directive,"else")) {
-                if (!if_nesting)
-                    ret=handle_else(f, r, error, &conditional_status, &printing);
+            }
+            else if (!strcmp(directive, "else")) {
+                if (!if_nesting) {
+                    ret = handle_else(f, r, error, &conditional_status,
+                                      &printing);
+                }
                 continue;
-            } else if(!strcmp(directive,"elif")) {
-                if (!if_nesting)
-                    ret = handle_elif(f, r, error, &conditional_status, &printing);
+            }
+            else if (!strcmp(directive, "elif")) {
+                if (!if_nesting) {
+                    ret = handle_elif(f, r, error, &conditional_status,
+                                      &printing);
+                }
                 continue;
-            } else if(!strcmp(directive,"endif")) {
+            }
+            else if (!strcmp(directive, "endif")) {
                 if (!if_nesting) {
-                    ret = handle_endif(f, r, error, &conditional_status, &printing);
-                } else {
+                    ret = handle_endif(f, r, error, &conditional_status,
+                                       &printing);
+                }
+                else {
                     if_nesting--;
                 }
                 continue;
-            } 
-            if (!printing) continue;
-            if(!strcmp(directive,"exec")) {
-                if(noexec) {
-                    log_printf(r->server,"httpd: exec used but not allowed in %s",
-                            r->filename);
-                    if (printing) rputs(error, r);
-                    ret = find_string(f,ENDING_SEQUENCE,r,0);
-                } else 
-                    ret=handle_exec(f, r, error);
-            } else if(!strcmp(directive,"config"))
-                ret=handle_config(f, r, error, timefmt, &sizefmt);
-            else if(!strcmp(directive,"set"))
-                ret=handle_set(f, r, error);
-            else if(!strcmp(directive,"include"))
-                ret=handle_include(f, r, error, noexec);
-            else if(!strcmp(directive,"echo"))
-                ret=handle_echo(f, r, error);
-            else if(!strcmp(directive,"fsize"))
-                ret=handle_fsize(f, r, error, sizefmt);
-            else if(!strcmp(directive,"flastmod"))
-                ret=handle_flastmod(f, r, error, timefmt);
-            else if(!strcmp(directive,"printenv"))
-                ret=handle_printenv(f, r, error);
+            }
+            if (!printing) {
+                continue;
+            }
+            if (!strcmp(directive, "exec")) {
+                if (noexec) {
+                    log_printf(r->server,
+                                "httpd: exec used but not allowed in %s",
+                                r->filename);
+                    if (printing) {
+                        rputs(error, r);
+                    }
+                    ret = find_string(f, ENDING_SEQUENCE, r, 0);
+                }
+                else {
+                    ret = handle_exec(f, r, error);
+                }
+            }
+            else if (!strcmp(directive, "config")) {
+                ret = handle_config(f, r, error, timefmt, &sizefmt);
+            }
+            else if (!strcmp(directive, "set")) {
+                ret = handle_set(f, r, error);
+            }
+            else if (!strcmp(directive, "include")) {
+                ret = handle_include(f, r, error, noexec);
+            }
+            else if (!strcmp(directive, "echo")) {
+                ret = handle_echo(f, r, error);
+            }
+            else if (!strcmp(directive, "fsize")) {
+                ret = handle_fsize(f, r, error, sizefmt);
+            }
+            else if (!strcmp(directive, "flastmod")) {
+                ret = handle_flastmod(f, r, error, timefmt);
+            }
+            else if (!strcmp(directive, "printenv")) {
+                ret = handle_printenv(f, r, error);
+            }
 #ifdef USE_PERL_SSI
-            else if(!strcmp(directive,"perl")) 
-                ret=handle_perl(f, r, error);
+            else if (!strcmp(directive, "perl")) {
+                ret = handle_perl(f, r, error);
+            }
 #endif
             else {
-                log_printf(r->server,
-                        "httpd: unknown directive \"%s\" in parsed doc %s",
-                        directive,r->filename);
-                if (printing) rputs(error, r);
-                ret=find_string(f,ENDING_SEQUENCE,r,0);
+                log_printf(r->server, "httpd: unknown directive \"%s\" "
+                            "in parsed doc %s",
+                            directive, r->filename);
+                if (printing) {
+                    rputs(error, r);
+                }
+                ret = find_string(f, ENDING_SEQUENCE, r, 0);
             }
-            if(ret) {
-                log_printf(r->server,"httpd: premature EOF in parsed file %s",
-			r->filename);
+            if (ret) {
+                log_printf(r->server, "httpd: premature EOF in parsed file %s",
+                            r->filename);
                 return;
             }
-        } else 
+        }
+        else {
             return;
+        }
     }
 }
 
@@ -1766,52 +2157,68 @@
  */
 
 module includes_module;
-enum xbithack { xbithack_off, xbithack_on, xbithack_full };
+enum xbithack {
+    xbithack_off, xbithack_on, xbithack_full
+};
 
-#ifdef XBITHACK	
+#ifdef XBITHACK
 #define DEFAULT_XBITHACK xbithack_full
 #else
 #define DEFAULT_XBITHACK xbithack_off
 #endif
 
-void *create_includes_dir_config (pool *p, char *dummy)
+static void *create_includes_dir_config(pool *p, char *dummy)
 {
-    enum xbithack *result = (enum xbithack*)palloc(p, sizeof (enum xbithack));
+    enum xbithack *result = (enum xbithack *) palloc(p, sizeof(enum xbithack));
     *result = DEFAULT_XBITHACK;
     return result;
 }
 
-const char *set_xbithack (cmd_parms *cmd, void *xbp, char *arg)
+static const char *set_xbithack(cmd_parms *cmd, void *xbp, char *arg)
 {
-   enum xbithack *state = (enum xbithack *)xbp;
+    enum xbithack *state = (enum xbithack *) xbp;
 
-   if (!strcasecmp (arg, "off")) *state = xbithack_off;
-   else if (!strcasecmp (arg, "on")) *state = xbithack_on;
-   else if (!strcasecmp (arg, "full")) *state = xbithack_full;
-   else return "XBitHack must be set to Off, On, or Full";
+    if (!strcasecmp(arg, "off")) {
+        *state = xbithack_off;
+    }
+    else if (!strcasecmp(arg, "on")) {
+        *state = xbithack_on;
+    }
+    else if (!strcasecmp(arg, "full")) {
+        *state = xbithack_full;
+    }
+    else {
+        return "XBitHack must be set to Off, On, or Full";
+    }
 
-   return NULL;
+    return NULL;
 }
 
-int send_parsed_file(request_rec *r)
+static int send_parsed_file(request_rec *r)
 {
     FILE *f;
     enum xbithack *state =
-	(enum xbithack *)get_module_config(r->per_dir_config,&includes_module);
+    (enum xbithack *) get_module_config(r->per_dir_config, &includes_module);
     int errstatus;
 
-    if (!(allow_options (r) & OPT_INCLUDES)) return DECLINED;
-    if (r->method_number != M_GET) return DECLINED;
+    if (!(allow_options(r) & OPT_INCLUDES)) {
+        return DECLINED;
+    }
+    if (r->method_number != M_GET) {
+        return DECLINED;
+    }
     if (r->finfo.st_mode == 0) {
-        log_reason("File does not exist",
-            r->path_info ? pstrcat(r->pool, r->filename, r->path_info, NULL)
-                : r->filename, r);
-	return NOT_FOUND;
+        log_printf(r->server, "File does not exist: %s",
+                    (r->path_info
+                     ? pstrcat(r->pool, r->filename, r->path_info, NULL)
+                     : r->filename));
+        return HTTP_NOT_FOUND;
     }
 
-    if(!(f=pfopen(r->pool, r->filename, "r"))) {
-        log_reason("file permissions deny server access", r->filename, r);
-	return FORBIDDEN;
+    if (!(f = pfopen(r->pool, r->filename, "r"))) {
+        log_printf(r->server,
+                    "file permissions deny server access: %s", r->filename);
+        return HTTP_FORBIDDEN;
     }
 
     if (*state == xbithack_full
@@ -1825,84 +2232,92 @@
     send_http_header(r);
 
     if (r->header_only) {
-	pfclose (r->pool, f);
-	return OK;
+        pfclose(r->pool, f);
+        return OK;
     }
-   
+
     if (r->main) {
-	/* Kludge --- for nested includes, we want to keep the
-	 * subprocess environment of the base document (for compatibility);
-	 * that means torquing our own last_modified date as well so that
-	 * the LAST_MODIFIED variable gets reset to the proper value if
-	 * the nested document resets <!--#config timefmt-->
-	 */
-	r->subprocess_env = r->main->subprocess_env;
-	r->finfo.st_mtime= r->main->finfo.st_mtime;
-    } else { 
-	add_common_vars (r);
-	add_cgi_vars(r);
-	add_include_vars (r, DEFAULT_TIME_FORMAT);
+        /* Kludge --- for nested includes, we want to keep the
+         * subprocess environment of the base document (for compatibility);
+         * that means torquing our own last_modified date as well so that
+         * the LAST_MODIFIED variable gets reset to the proper value if
+         * the nested document resets <!--#config timefmt-->
+         */
+        r->subprocess_env = r->main->subprocess_env;
+        r->finfo.st_mtime = r->main->finfo.st_mtime;
+    }
+    else {
+        add_common_vars(r);
+        add_cgi_vars(r);
+        add_include_vars(r, DEFAULT_TIME_FORMAT);
     }
     hard_timeout("send SSI", r);
 
-    send_parsed_content (f, r);
-    
-    kill_timeout (r);
+    send_parsed_content(f, r);
+
+    kill_timeout(r);
     return OK;
 }
 
-int send_shtml_file (request_rec *r)
+static int send_shtml_file(request_rec *r)
 {
     r->content_type = "text/html";
     return send_parsed_file(r);
 }
 
-int xbithack_handler (request_rec *r)
+static int xbithack_handler(request_rec *r)
 {
-    enum xbithack *state;
-	
 #ifdef __EMX__
     /* OS/2 dosen't currently support the xbithack. This is being worked on. */
     return DECLINED;
 #else
-    if (!(r->finfo.st_mode & S_IXUSR)) return DECLINED;
+    enum xbithack *state;
+
+    if (!(r->finfo.st_mode & S_IXUSR)) {
+        return DECLINED;
+    }
+
+    state = (enum xbithack *) get_module_config(r->per_dir_config,
+                                                &includes_module);
 
-    state = (enum xbithack *)get_module_config(r->per_dir_config,
-					       &includes_module);
-    
-    if (*state == xbithack_off) return DECLINED;
-    return send_parsed_file (r);
+    if (*state == xbithack_off) {
+        return DECLINED;
+    }
+    return send_parsed_file(r);
 #endif
 }
 
-command_rec includes_cmds[] = {
-{ "XBitHack", set_xbithack, NULL, OR_OPTIONS, TAKE1, "Off, On, or Full" },
-{ NULL }    
+static command_rec includes_cmds[] =
+{
+    {"XBitHack", set_xbithack, NULL, OR_OPTIONS, TAKE1, "Off, On, or Full"},
+    {NULL}
 };
 
-handler_rec includes_handlers[] = {
-{ INCLUDES_MAGIC_TYPE, send_shtml_file },
-{ INCLUDES_MAGIC_TYPE3, send_shtml_file },
-{ "server-parsed", send_parsed_file },
-{ "text/html", xbithack_handler },
-{ NULL }
+static handler_rec includes_handlers[] =
+{
+    {INCLUDES_MAGIC_TYPE, send_shtml_file},
+    {INCLUDES_MAGIC_TYPE3, send_shtml_file},
+    {"server-parsed", send_parsed_file},
+    {"text/html", xbithack_handler},
+    {NULL}
 };
 
-module includes_module = {
-   STANDARD_MODULE_STUFF,
-   NULL,			/* initializer */
-   create_includes_dir_config,	/* dir config creater */
-   NULL,			/* dir merger --- default is to override */
-   NULL,			/* server config */
-   NULL,			/* merge server config */
-   includes_cmds,		/* command table */
-   includes_handlers,		/* handlers */
-   NULL,			/* filename translation */
-   NULL,			/* check_user_id */
-   NULL,			/* check auth */
-   NULL,			/* check access */
-   NULL,			/* type_checker */
-   NULL,			/* fixups */
-   NULL,			/* logger */
-   NULL				/* header parser */
+module includes_module =
+{
+    STANDARD_MODULE_STUFF,
+    NULL,                       /* initializer */
+    create_includes_dir_config, /* dir config creater */
+    NULL,                       /* dir merger --- default is to override */
+    NULL,                       /* server config */
+    NULL,                       /* merge server config */
+    includes_cmds,              /* command table */
+    includes_handlers,          /* handlers */
+    NULL,                       /* filename translation */
+    NULL,                       /* check_user_id */
+    NULL,                       /* check auth */
+    NULL,                       /* check access */
+    NULL,                       /* type_checker */
+    NULL,                       /* fixups */
+    NULL,                       /* logger */
+    NULL                        /* header parser */
 };
Index: src/util.c
===================================================================
RCS file: /export/home/cvs/apache/src/util.c,v
retrieving revision 1.52.2.3
diff -u -r1.52.2.3 util.c
--- util.c	1997/12/30 18:59:32	1.52.2.3
+++ util.c	1998/01/04 20:27:19
@@ -83,6 +83,7 @@
 
     /* check return code? */
     strftime(ts,MAX_STRING_LEN,fmt,tms);
+    ts[MAX_STRING_LEN - 1] = '\0';
     return pstrdup (p, ts);
 }
 
@@ -569,6 +570,11 @@
     if(c == EOF)
 	return 1;
 
+    if(n < 2) {
+	/* too small, assume caller is crazy */
+	return 1;
+    }
+
     while(1) {
         if((c == '\t') || (c == ' ')) {
             s[i++] = ' ';
@@ -578,7 +584,7 @@
         if(c == CR) {
             c = getc(f);
         }
-        if(c == EOF || c == 0x4 || c == LF || i == (n-1)) {
+        if(c == EOF || c == 0x4 || c == LF || i >= (n-2)) {
             /* blast trailing whitespace */
             while(i && (s[i-1] == ' ')) --i;
             s[i] = '\0';
Index: src/modules/proxy/proxy_cache.c
===================================================================
RCS file: /export/home/cvs/apache/src/modules/proxy/proxy_cache.c,v
retrieving revision 1.13.2.1
diff -u -r1.13.2.1 proxy_cache.c
--- proxy_cache.c	1997/08/15 17:08:55	1.13.2.1
+++ proxy_cache.c	1998/01/04 20:27:21
@@ -84,6 +84,7 @@
 static unsigned long int curblocks;
 static time_t now, expire;
 static char *filename;
+static int filenamelen;
 
 static int sub_garbage_coll(request_rec *r,array_header *files,
 			    const char *cachedir,const char *cachesubdir);
@@ -111,9 +112,10 @@
 
     block_alarms();	/* avoid SIGALRM on big cache cleanup */
 
-    filename = palloc(r->pool, strlen(cachedir) + HASH_LEN + 2);
-    strcpy(filename, cachedir);
-    strcat(filename, "/.time");
+    filenamelen = strlen(cachedir) + HASH_LEN + 2;
+    filename = palloc(r->pool, filenamelen);
+    ap_snprintf(filename, filenamelen, "%s/.time", cachedir);
+
     if (stat(filename, &buf) == -1) /* does not exist */
     {
 	if (errno != ENOENT)
@@ -161,7 +163,7 @@
     for (i=0; i < files->nelts; i++)
     {
 	fent = elts[i];
-	sprintf(filename, "%s%s", cachedir, fent->file);
+	ap_snprintf(filename, filenamelen, "%s%s", cachedir, fent->file);
 	Explain3("GC Unlinking %s (expiry %ld, now %ld)",filename,fent->expire,now);
 #if TESTING
 	fprintf(stderr,"Would unlink %s\n",filename);
@@ -216,7 +218,7 @@
     while ((ent = readdir(dir)) != NULL)
     {
 	if (ent->d_name[0] == '.') continue;
-	sprintf(filename, "%s%s", cachedir, ent->d_name);
+	ap_snprintf(filename, filenamelen, "%s%s", cachedir, ent->d_name);
 	Explain1("GC Examining file %s",filename);
 /* is it a temporary file? */
 	if (strncmp(ent->d_name, "tmp", 3) == 0)
@@ -315,8 +317,8 @@
 	fent = palloc(r->pool, sizeof(struct gc_ent));
 	fent->len = buf.st_size;
 	fent->expire = expire;
-	strcpy(fent->file,cachesubdir);
-	strcat(fent->file, ent->d_name);
+	ap_snprintf(fent->file, sizeof(fent->file), "%s%s", cachesubdir,
+	    ent->d_name);
 	*(struct gc_ent **)push_array(files) = fent;
 
 /* accumulate in blocks, to cope with directories > 4Gb */
Index: src/modules/proxy/proxy_ftp.c
===================================================================
RCS file: /export/home/cvs/apache/src/modules/proxy/proxy_ftp.c,v
retrieving revision 1.21.2.1
diff -u -r1.21.2.1 proxy_ftp.c
--- proxy_ftp.c	1997/08/15 17:08:55	1.21.2.1
+++ proxy_ftp.c	1998/01/04 20:27:23
@@ -286,8 +286,8 @@
 	    buf[sizeof(buf)-1] = '\0';
             n=strlen(buf);
         }
-        else if(buf[0]=='d' || buf[0]=='-' || buf[0]=='l' || isdigit(buf[0]))
-        {
+        else if (strrchr(buf, ' ') && (buf[0]=='d' || buf[0]=='-' || 
+				       buf[0]=='l' || isdigit(buf[0])) ) {
 	    if(isdigit(buf[0])) {		/* handle DOS dir */
 	        searchptr = strchr(buf, '<');
 	        if(searchptr != NULL)
Index: src/modules/proxy/proxy_util.c
===================================================================
RCS file: /export/home/cvs/apache/src/modules/proxy/proxy_util.c,v
retrieving revision 1.17
diff -u -r1.17 proxy_util.c
--- proxy_util.c	1997/06/04 21:20:27	1.17
+++ proxy_util.c	1998/01/04 20:27:25
@@ -296,8 +296,11 @@
     for (mon=0; mon < 12; mon++) if (strcmp(month, months[mon]) == 0) break;
     if (mon == 12) return x;
 
-    if (strlen(x) < 31) x = palloc(p, 31);
-    ap_snprintf(x, strlen(x)+1, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", wday[wk], mday,
+    if (strlen(x)+1 < 30) {
+        x = palloc(p, 30);
+    }
+    /* format: "Wed, 17 Dec 1997 00:53:40 GMT" (29 chars data) */
+    ap_snprintf(x, 30, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", wday[wk], mday,
 	    months[mon], year, hour, min, sec);
     return x;
 }
Index: support/logresolve.c
===================================================================
RCS file: /export/home/cvs/apache/support/logresolve.c,v
retrieving revision 1.7
diff -u -r1.7 logresolve.c
--- logresolve.c	1997/02/04 23:54:28	1.7
+++ logresolve.c	1998/01/04 20:27:32
@@ -202,7 +202,9 @@
     } else
 	cachehits++;
 
-    strcpy(string, (*current)->hostname);
+    /* size of string == MAXDNAME +1 */
+    strncpy(string, (*current)->hostname, MAXDNAME);
+    string[MAXDNAME] = '\0';
 }
 
 /*


Re: [PATCH] lets do 1.2.5

Posted by Randy Terbush <ra...@covalent.net>.
On Sun, Jan 04, 1998 at 01:31:47PM -0700, Marc Slemko wrote:
> NOTE: please do not redistribute the below information.  I fully
> support the concept of full disclosure, but let us get a release out
> without all sorts of rumors starting to fly before it.  Thanks.
> 
> The below patch addresses several security problems in the Apache
> code that Dean and I have found.  The below changes has been discussed
> privately with most of the primary developers.
> 
> Once we get some votes on this (the patch and the suggested process), it
> will be committed soon and a tarball rolled.  After a day or so to test,
> it will be released as early in the week as we can reasonably manage.  The
> idea is move very quickly getting this voted on, committed and the tarball
> rolled, then give a bit of time to verify things before it goes out.
> 
> I am writing a security announcement to go with it, guess I may as well
> do the 1.2.5 announcement as well.  Brian, can you please add marcs@znep.com
> to those who can post to apache-announce.  
> 
> I need a volunteer to roll the tarball when we are ready.

+1

I'll roll the tarball when we have the votes.



Re: [PATCH] lets do 1.2.5

Posted by Rodent of Unusual Size <Ke...@Golux.Com>.
Martin Kraemer wrote:
> 
> Note also that in 1.2, it is not possible to define
>   CC=cc -kansi
> in the Configuration file: if $(CC) contains more than one word, it
> won't be passed properly to the subordinate directories.

This strikes me as entirely reasonable; that's EXTRA_CFLAGS'
purpose.  CC should be just the name of the compiler binary,
suitable for feeding to 'which'.

#ken	P-)}

Re: [PATCH] lets do 1.2.5

Posted by Martin Kraemer <Ma...@mch.sni.de>.
On Sun, Jan 04, 1998 at 02:34:06PM -0800, Dean Gaudet wrote:
> I've tarballed the source.  You can find it at
> <http://www.arctic.org/~dgaudet/125.tar.gz>. 
Note that this isn't really a tarball (it's based in a directory 125/
instead of apache_1.2.5/, and the CVS/ directories are still present.

Note also that in 1.2, it is not possible to define
  CC=cc -kansi
in the Configuration file: if $(CC) contains more than one word, it
won't be passed properly to the subordinate directories.

Other than that, +1 for the 1.2.5 patches

   Martin
-- 
| S I E M E N S |  <Ma...@mch.sni.de>  |      Siemens Nixdorf
| ------------- |   Voice: +49-89-636-46021     |  Informationssysteme AG
| N I X D O R F |   FAX:   +49-89-636-44994     |   81730 Munich, Germany
~~~~~~~~~~~~~~~~My opinions only, of course; pgp key available on request

Re: [PATCH] lets do 1.2.5

Posted by Mark J Cox <ma...@awe.com>.
On Sun, 4 Jan 1998, Marc Slemko wrote:
> Once we get some votes on this (the patch and the suggested process), it
> will be committed soon and a tarball rolled.  After a day or so to test,

+1 on patch and process.  Patched 1.2.5 has been tested (apart from
mod_include) and runs fine here on IRIX, Linux and i486 NetBSD.  More
platform tests to follow. 

[IRIX's cc compiler seems broken and gives a warning on the new "const"
definitions in mod_imap.c]

Mark


Re: [PATCH] lets do 1.2.5

Posted by Dean Gaudet <dg...@arctic.org>.
+1. 

For folks wishing to test this, since what Marc posted isn't a patch
against 1.2.4 but rather a patch against what we've had for 1.2.5 in the
repository for a while, I've tarballed the source.  You can find it at
<http://www.arctic.org/~dgaudet/125.tar.gz>. 

Dean

On Sun, 4 Jan 1998, Marc Slemko wrote:

> NOTE: please do not redistribute the below information.  I fully
> support the concept of full disclosure, but let us get a release out
> without all sorts of rumors starting to fly before it.  Thanks.
> 
> The below patch addresses several security problems in the Apache
> code that Dean and I have found.  The below changes has been discussed
> privately with most of the primary developers.
> 
> Once we get some votes on this (the patch and the suggested process), it
> will be committed soon and a tarball rolled.  After a day or so to test,
> it will be released as early in the week as we can reasonably manage.  The
> idea is move very quickly getting this voted on, committed and the tarball
> rolled, then give a bit of time to verify things before it goes out.
> 
> I am writing a security announcement to go with it, guess I may as well
> do the 1.2.5 announcement as well.  Brian, can you please add marcs@znep.com
> to those who can post to apache-announce.  
> 
> I need a volunteer to roll the tarball when we are ready.