On Thu, Oct 12, 2006 at 12:57:14PM +0300, Peter Pentchev wrote: > Hi, > > First of all, thanks for writing and maintaing a great, lightweight, > simple webserver! > > My attention was recently drawn to Mathopd when a friend decided to use > it as a lightweight replacement of Apache for one of the Bulgarian > mirrors of Debian Linux. It's been doing great so far, with a single > excepion - as y'all might have already guessed, it's /../ components in > the URL paths. It turns out that some configurations for fetching > Debian distribution files do indeed depend on traversing directories > back up - it happens only very rarely, but still, it happens, and our > new mirror webserver does not quite allow it.
After another discussion with Alexander Velin, the above-mentioned Debian mirror maintainer, it turned out that it may also be desirable to sometimes allow // in the URL paths, ignoring the second and further slashes. So, here's another version of the patch which changes several things: src/request.c - check_path(): - now returns a bitmask of flags instead of a single boolean value. This makes it possible for it to signal both fatal errors and possible error conditions - see below for deferred error processing. - as in the previous patch, rewrites the request URL path, removing /./ components and processing /../ by removing the last stored "real" path component. - raises a flag in the return code if a dotfile is found in the path. - raises a flag in the return code if a double slash is found in the URL path. src/request.c - process_path(): - only invokes check_path() once now, not twice as in the previous version of the patch. - invokes check_path() before faketoreal(), so faketoreal() works on the real location (consider http://www.example.com/cgi-bin/../doc/). - defers all error processing, including the dotfiles and double-slash checks, after the faketoreal() invocation - check_path() is now called much earlier and it has no idea what the control configuration will be, whether it will allow dotfiles or not, and what error document to send to the client. - checks for allow_dotfiles if check_path() found a dotfile (or dotdir) in the URL path. - new: checks for allow_doubleslash if check_path() found // in the URL path. doc/config.txt: doc/syntax.txt: - document the new AllowDoubleSlash keyword. src/config.c: - process the new AllowDoubleSlash keyword. src/mathopd.h: - add the allow_doubleslash flag to the control block structure. If the patches should not make it to the list, they are also available at http://www.ringlet.net/~roam/patches/mathopd/ G'luck, Peter -- Peter Pentchev [EMAIL PROTECTED] [EMAIL PROTECTED] [EMAIL PROTECTED] PGP key: http://people.FreeBSD.org/~roam/roam.key.asc Key fingerprint FDBA FD79 C26F 3C51 C95E DF9E ED18 B68D 1619 4553 This inert sentence is my body, but my soul is alive, dancing in the sparks of your brain.
Index: doc/config.txt =================================================================== --- doc/config.txt (revision 333) +++ doc/config.txt (revision 456) @@ -91,6 +91,15 @@ a bit, although constructions like '/./', '/../', etc. are still disallowed. +Keyword: AllowDoubleSlash +Where: Control +Type: Flag +Default: Off +Desc: Normally, URL's with two or more slashes in the path portion, + like http://www.example.com/mathopd//doc/, will be rejected. + If the AllowDoubleSlash flag is set, this restriction is removed, + and Mathopd will silently ignore the second and further slashes. + Keyword: AnyHost Where: Virtual Desc: If a virtual server has this keyword, it will match on any Host: Index: doc/syntax.txt =================================================================== --- doc/syntax.txt (revision 333) +++ doc/syntax.txt (revision 456) @@ -84,6 +84,7 @@ "ScriptUser" string "RunScriptsAsOwner" string "AllowDotfiles" flag + "AllowDoubleSlash" flag "UserDirectory" flag "PutEnv" string-block "ExtraHeaders" string-block Index: src/mathopd.h =================================================================== --- src/mathopd.h (revision 333) +++ src/mathopd.h (revision 456) @@ -171,6 +171,7 @@ uid_t script_uid; gid_t script_gid; int allow_dotfiles; + int allow_doubleslash; int user_directory; struct simple_list *putenvs; struct simple_list *extra_headers; Index: src/config.c =================================================================== --- src/config.c (revision 333) +++ src/config.c (revision 456) @@ -84,6 +84,7 @@ static const char c_alias[] = "Alias"; static const char c_allow[] = "Allow"; static const char c_allow_dotfiles[] = "AllowDotfiles"; +static const char c_allow_doubleslash[] = "AllowDoubleSlash"; static const char c_any_host[] = "AnyHost"; static const char c_apply[] = "Apply"; static const char c_auto_index_command[] = "AutoIndexCommand"; @@ -665,6 +666,7 @@ a->script_uid = b->script_uid; a->script_gid = b->script_gid; a->allow_dotfiles = b->allow_dotfiles; + a->allow_doubleslash = b->allow_doubleslash; a->putenvs = b->putenvs; a->extra_headers = b->extra_headers; a->path_info_ok = b->path_info_ok; @@ -687,6 +689,7 @@ a->script_uid = 0; a->script_gid = 0; a->allow_dotfiles = 0; + a->allow_doubleslash = 0; a->putenvs = 0; a->extra_headers = 0; a->path_info_ok = 1; @@ -762,6 +765,8 @@ t = config_run_scripts_as_owner(p, a); else if (!strcasecmp(p->tokbuf, c_allow_dotfiles)) t = config_flag(p, &a->allow_dotfiles); + else if (!strcasecmp(p->tokbuf, c_allow_doubleslash)) + t = config_flag(p, &a->allow_doubleslash); else if (!strcasecmp(p->tokbuf, c_user_directory)) t = config_flag(p, &a->user_directory); else if (!strcasecmp(p->tokbuf, c_putenv)) Index: src/request.c =================================================================== --- src/request.c (revision 333) +++ src/request.c (revision 456) @@ -58,6 +58,12 @@ static const char m_head[] = "HEAD"; static const char m_post[] = "POST"; +/* Flags for the check_path() deferred error processing */ +#define CP_ERR_NONE 0x0000 +#define CP_ERR_DOTFILE 0x0001 +#define CP_ERR_DSLASH 0x0002 +#define CP_ERR_FATAL 0x8000 + static time_t timerfc(const char *s) { static const int daytab[2][12] = { @@ -405,7 +411,7 @@ static int check_path(struct request *r) { - char *p; + char *p, *q, *slash; char c; enum { s_normal, @@ -414,44 +420,77 @@ s_slashdotdot, s_forbidden } s; + char newpath[PATHLEN]; + int retval; p = r->path; + q = newpath; s = s_normal; + retval = CP_ERR_NONE; do { c = *p++; switch (s) { case s_normal: if (c == '/') s = s_slash; + *q++ = c; break; case s_slash: - if (c == '/') - s = s_forbidden; - else if (c == '.') - s = r->c->allow_dotfiles ? s_slashdot : s_forbidden; - else + if (c == '/') { + retval |= CP_ERR_DSLASH; + } else if (c == '.') { + s = s_slashdot; + } else { s = s_normal; + *q++ = c; + } break; case s_slashdot: - if (c == 0 || c == '/') + if (c == 0) { s = s_forbidden; - else if (c == '.') + } else if (c == '/') { + s = s_slash; + } else if (c == '.') { s = s_slashdotdot; - else + } else { + *q++ = '.'; + *q++ = c; + retval |= CP_ERR_DOTFILE; s = s_normal; + } break; case s_slashdotdot: - if (c == 0 || c == '/') + if (c == 0) { s = s_forbidden; - else + } else if (c == '/') { + /* Find the last slash and go from there */ + for (slash = q - 2; slash >= newpath; slash--) + if (*slash == '/') + break; + if (slash < newpath || *slash != '/') { + s = s_forbidden; + } else { + q = slash + 1; + s = s_slash; + } + } else { + *q++ = '.'; + *q++ = '.'; + *q++ = c; + retval |= CP_ERR_DOTFILE; s = s_normal; + } break; case s_forbidden: c = 0; break; } } while (c); - return s == s_forbidden ? -1 : 0; + if (s == s_forbidden) + retval |= CP_ERR_FATAL; + else + strcpy(r->path, newpath); + return retval; } static int makedir(struct request *r) @@ -794,21 +833,36 @@ static void process_path(struct request *r) { + int sanitized; + if (find_vs(r) == -1) { if (debug) log_d("find_vs failed (host=%s)", r->host ? r->host : "[not set]"); r->status = 400; return; } + sanitized = check_path(r); + if (sanitized & CP_ERR_FATAL) { + /* A "really" invalid path - do not even try to translate it */ + if (debug) + log_d("check_path failed for %s", r->path); + r->path[0] = '\0'; + } if ((r->c = faketoreal(r->path, r->path_translated, r, 1, sizeof r->path_translated)) == 0) { if (debug) log_d("faketoreal failed"); r->status = 500; return; } - if (check_path(r) == -1) { - if (debug) - log_d("check_path failed for %s", r->path); + /* + * If check_path() signalled any fatal or possible error conditions, + * the actual check should be performed now - we have the per-server + * (or per-location) configuration, and we know what restrictions to + * apply and what error documents to use. + */ + if ((sanitized & CP_ERR_FATAL) || + (sanitized & CP_ERR_DOTFILE && !r->c->allow_dotfiles) || + (sanitized & CP_ERR_DSLASH && !r->c->allow_doubleslash)) { r->error_file = r->c->error_404_file; r->status = 404; return; Property changes on: . ___________________________________________________________________ Name: svnmerge-integrated + /www/mathopd/branches/vendor:1-627
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.5 (FreeBSD) iD8DBQBFOhhE7Ri2jRYZRVMRAiQbAJ42wJPICNSvx9ysvKPIJSmBF+S4swCfbSSx naYBUQnPGromo8VeYUejt6E= =1f7k -----END PGP SIGNATURE-----
Index: doc/config.txt =================================================================== --- doc/config.txt (revision 344) +++ doc/config.txt (revision 458) @@ -65,6 +65,15 @@ a bit, although constructions like '/./', '/../', etc. are still disallowed. +Keyword: AllowDoubleSlash +Where: Control +Type: Flag +Default: Off +Desc: Normally, URL's with two or more slashes in the path portion, + like http://www.example.com/mathopd//doc/, will be rejected. + If the AllowDoubleSlash flag is set, this restriction is removed, + and Mathopd will silently ignore the second and further slashes. + Keyword: AnyHost Where: Virtual Desc: If a virtual server has this keyword, it will match on any Host: Index: doc/syntax.txt =================================================================== --- doc/syntax.txt (revision 344) +++ doc/syntax.txt (revision 458) @@ -84,6 +84,7 @@ "ScriptUser" string "RunScriptsAsOwner" string "AllowDotfiles" flag + "AllowDoubleSlash" flag "UserDirectory" flag "PutEnv" string-block "ExtraHeaders" string-block Index: src/mathopd.h =================================================================== --- src/mathopd.h (revision 344) +++ src/mathopd.h (revision 458) @@ -165,6 +165,7 @@ uid_t script_uid; gid_t script_gid; int allow_dotfiles; + int allow_doubleslash; int user_directory; struct simple_list *putenvs; struct simple_list *extra_headers; Index: src/config.c =================================================================== --- src/config.c (revision 344) +++ src/config.c (revision 458) @@ -79,6 +79,7 @@ static const char c_admin[] = "Admin"; static const char c_alias[] = "Alias"; static const char c_allow_dotfiles[] = "AllowDotfiles"; +static const char c_allow_doubleslash[] = "AllowDoubleSlash"; static const char c_any_host[] = "AnyHost"; static const char c_auto_index_command[] = "AutoIndexCommand"; static const char c_backlog[] = "Backlog"; @@ -531,6 +532,7 @@ a->script_uid = b->script_uid; a->script_gid = b->script_gid; a->allow_dotfiles = b->allow_dotfiles; + a->allow_doubleslash = b->allow_doubleslash; a->putenvs = b->putenvs; a->extra_headers = b->extra_headers; a->path_info_ok = b->path_info_ok; @@ -552,6 +554,7 @@ a->script_uid = 0; a->script_gid = 0; a->allow_dotfiles = 0; + a->allow_doubleslash = 0; a->putenvs = 0; a->extra_headers = 0; a->path_info_ok = 1; @@ -622,6 +625,8 @@ t = config_run_scripts_as_owner(p, a); else if (!strcasecmp(p->tokbuf, c_allow_dotfiles)) t = config_flag(p, &a->allow_dotfiles); + else if (!strcasecmp(p->tokbuf, c_allow_doubleslash)) + t = config_flag(p, &a->allow_doubleslash); else if (!strcasecmp(p->tokbuf, c_user_directory)) t = config_flag(p, &a->user_directory); else if (!strcasecmp(p->tokbuf, c_putenv)) Index: src/request.c =================================================================== --- src/request.c (revision 344) +++ src/request.c (revision 458) @@ -56,6 +56,12 @@ static const char m_head[] = "HEAD"; static const char m_post[] = "POST"; +/* Flags for the check_path() deferred error processing */ +#define CP_ERR_NONE 0x0000 +#define CP_ERR_DOTFILE 0x0001 +#define CP_ERR_DSLASH 0x0002 +#define CP_ERR_FATAL 0x8000 + static time_t timerfc(const char *s) { static const int daytab[2][12] = { @@ -396,7 +402,7 @@ static int check_path(struct request *r) { - char *p; + char *p, *q, *slash; char c; enum { s_normal, @@ -405,44 +411,77 @@ s_slashdotdot, s_forbidden } s; + char newpath[PATHLEN]; + int retval; p = r->path; + q = newpath; s = s_normal; + retval = CP_ERR_NONE; do { c = *p++; switch (s) { case s_normal: if (c == '/') s = s_slash; + *q++ = c; break; case s_slash: - if (c == '/') - s = s_forbidden; - else if (c == '.') - s = r->c->allow_dotfiles ? s_slashdot : s_forbidden; - else + if (c == '/') { + retval |= CP_ERR_DSLASH; + } else if (c == '.') { + s = s_slashdot; + } else { s = s_normal; + *q++ = c; + } break; case s_slashdot: - if (c == 0 || c == '/') + if (c == 0) { s = s_forbidden; - else if (c == '.') + } else if (c == '/') { + s = s_slash; + } else if (c == '.') { s = s_slashdotdot; - else + } else { + *q++ = '.'; + *q++ = c; + retval |= CP_ERR_DOTFILE; s = s_normal; + } break; case s_slashdotdot: - if (c == 0 || c == '/') + if (c == 0) { s = s_forbidden; - else + } else if (c == '/') { + /* Find the last slash and go from there */ + for (slash = q - 2; slash >= newpath; slash--) + if (*slash == '/') + break; + if (slash < newpath || *slash != '/') { + s = s_forbidden; + } else { + q = slash + 1; + s = s_slash; + } + } else { + *q++ = '.'; + *q++ = '.'; + *q++ = c; + retval |= CP_ERR_DOTFILE; s = s_normal; + } break; case s_forbidden: c = 0; break; } } while (c); - return s == s_forbidden ? -1 : 0; + if (s == s_forbidden) + retval |= CP_ERR_FATAL; + else + strcpy(r->path, newpath); + return retval; } static int makedir(struct request *r) @@ -783,21 +822,36 @@ static void process_path(struct request *r) { + int sanitized; + if (find_vs(r) == -1) { if (debug) log_d("find_vs failed (host=%s)", r->host ? r->host : "[not set]"); r->status = 400; return; } + sanitized = check_path(r); + if (sanitized & CP_ERR_FATAL) { + /* A "really" invalid path - do not even try to translate it */ + if (debug) + log_d("check_path failed for %s", r->path); + r->path[0] = '\0'; + } if ((r->c = faketoreal(r->path, r->path_translated, r, 1, sizeof r->path_translated)) == 0) { if (debug) log_d("faketoreal failed"); r->status = 500; return; } - if (check_path(r) == -1) { - if (debug) - log_d("check_path failed for %s", r->path); + /* + * If check_path() signalled any fatal or possible error conditions, + * the actual check should be performed now - we have the per-server + * (or per-location) configuration, and we know what restrictions to + * apply and what error documents to use. + */ + if ((sanitized & CP_ERR_FATAL) || + (sanitized & CP_ERR_DOTFILE && !r->c->allow_dotfiles) || + (sanitized & CP_ERR_DSLASH && !r->c->allow_doubleslash)) { r->error_file = r->c->error_404_file; r->status = 404; return; Property changes on: . ___________________________________________________________________ Name: svnmerge-integrated + /www/mathopd/branches/vendor/mathopd-1.6:1-630
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.5 (FreeBSD) iD8DBQBFOhhH7Ri2jRYZRVMRAgDCAJ0Qa6nSpK4SlhzCsa5XKgaKzm0u0ACeP06R KnhtRoMc2gElgOiF0yG5tPY= =8oBP -----END PGP SIGNATURE-----
pgpHt8j1s4eAh.pgp
Description: PGP signature