From 41b6a93cef97b6f969ddfffa048e57b1dad76492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Minisini?= Date: Thu, 27 Sep 2012 20:52:08 +0000 Subject: [PATCH] [EXAMPLES] * BUG: Fix Embedded window layout. [INTERPRETER] * NEW: The '-H' option now runs the interpreted program as a CGI script through an embedded HTTP server. [GB.HTTPD] * NEW: New hidden component that embeds an HTTP server that runs the project as a CGI script. It is activated by a new interpreter option. Warning! This is highly experimental. The HTTP server is based on the thttpd source code. [GB.WEB] * BUG: As the _init static class is always executed at startup for exported classes, delay the intialization of Session class the first time one of its properties is used. * BUG: If the SCRIPT_NAME CGI variable equals nothing or "/", set it to "/." so that it can be concatenated to any path and still gives a valid url. * BUG: Support for explicit URL ports. * NEW: Application.Port is a new property that returns the port used by the HTTP request. git-svn-id: svn://localhost/gambas/trunk@5209 867c0c6c-44f3-4631-809d-bfa615b0a4ec --- Makefile.am | 1 + app/other/MakeWebSite/.settings | 2 +- app/other/MakeWebSite/.src/MMain.module | 2 +- .../gambas.sourceforge.net/home.html | 6 +- app/src/gambas3/.project | 1 + comp/src/gb.report/.component | 2 +- comp/src/gb.report/.project | 3 +- comp/src/gb.web/.info | 8 +- comp/src/gb.web/.src/Application.module | 11 +- comp/src/gb.web/.src/CGI.module | 2 +- comp/src/gb.web/.src/Main.module | 14 + comp/src/gb.web/.src/Response.module | 5 +- comp/src/gb.web/.src/Session.module | 35 +- configure.ac | 1 + examples/examples/Control/Embedder/.project | 6 +- examples/examples/Control/Embedder/.settings | 4 +- .../Control/Embedder/.src/FMain.class | 4 +- .../examples/Control/Embedder/.src/FMain.form | 10 +- examples/examples/Drawing/Fractal/.project | 4 +- examples/examples/Drawing/Fractal/.startup | 2 +- gb.httpd/AUTHORS | 0 gb.httpd/COPYING | 1 + gb.httpd/ChangeLog | 0 gb.httpd/INSTALL | 1 + gb.httpd/Makefile.am | 3 + gb.httpd/NEWS | 0 gb.httpd/README | 0 gb.httpd/acinclude.m4 | 1 + gb.httpd/component.am | 1 + gb.httpd/configure.ac | 151 + gb.httpd/gambas.h | 1 + gb.httpd/gb_common.h | 1 + gb.httpd/m4 | 1 + gb.httpd/missing | 1 + gb.httpd/reconf | 1 + gb.httpd/src/Makefile.am | 21 + gb.httpd/src/fdwatch.c | 840 ++++ gb.httpd/src/fdwatch.h | 85 + gb.httpd/src/gb.httpd.component | 3 + gb.httpd/src/libhttpd.c | 4308 +++++++++++++++++ gb.httpd/src/libhttpd.h | 294 ++ gb.httpd/src/main.c | 71 + gb.httpd/src/main.h | 51 + gb.httpd/src/match.c | 87 + gb.httpd/src/match.h | 36 + gb.httpd/src/mime_encodings.h | 8 + gb.httpd/src/mime_types.h | 569 +++ gb.httpd/src/mmc.c | 521 ++ gb.httpd/src/mmc.h | 55 + gb.httpd/src/strerror.c | 37 + gb.httpd/src/tdate_parse.c | 313 ++ gb.httpd/src/tdate_parse.h | 33 + gb.httpd/src/thttpd.c | 2235 +++++++++ gb.httpd/src/thttpd.h | 403 ++ gb.httpd/src/timers.c | 334 ++ gb.httpd/src/timers.h | 110 + gb.httpd/src/version.h | 9 + m4/gbhttpd.m4 | 188 + main/gbx/gbx.c | 45 +- main/gbx/gbx_archive.c | 34 +- main/gbx/gbx_archive.h | 5 +- main/gbx/gbx_component.c | 31 +- main/gbx/gbx_component.h | 7 +- main/gbx/gbx_library.c | 9 + main/gbx/gbx_library.h | 2 + main/gbx/gbx_project.c | 18 +- main/gbx/gbx_project.h | 1 + main/share/gb_component.h | 1 + 68 files changed, 10970 insertions(+), 80 deletions(-) create mode 100644 gb.httpd/AUTHORS create mode 120000 gb.httpd/COPYING create mode 100644 gb.httpd/ChangeLog create mode 120000 gb.httpd/INSTALL create mode 100644 gb.httpd/Makefile.am create mode 100644 gb.httpd/NEWS create mode 100644 gb.httpd/README create mode 120000 gb.httpd/acinclude.m4 create mode 120000 gb.httpd/component.am create mode 100644 gb.httpd/configure.ac create mode 120000 gb.httpd/gambas.h create mode 120000 gb.httpd/gb_common.h create mode 120000 gb.httpd/m4 create mode 120000 gb.httpd/missing create mode 120000 gb.httpd/reconf create mode 100644 gb.httpd/src/Makefile.am create mode 100644 gb.httpd/src/fdwatch.c create mode 100644 gb.httpd/src/fdwatch.h create mode 100644 gb.httpd/src/gb.httpd.component create mode 100644 gb.httpd/src/libhttpd.c create mode 100644 gb.httpd/src/libhttpd.h create mode 100644 gb.httpd/src/main.c create mode 100644 gb.httpd/src/main.h create mode 100644 gb.httpd/src/match.c create mode 100644 gb.httpd/src/match.h create mode 100644 gb.httpd/src/mime_encodings.h create mode 100644 gb.httpd/src/mime_types.h create mode 100644 gb.httpd/src/mmc.c create mode 100644 gb.httpd/src/mmc.h create mode 100644 gb.httpd/src/strerror.c create mode 100644 gb.httpd/src/tdate_parse.c create mode 100644 gb.httpd/src/tdate_parse.h create mode 100644 gb.httpd/src/thttpd.c create mode 100644 gb.httpd/src/thttpd.h create mode 100644 gb.httpd/src/timers.c create mode 100644 gb.httpd/src/timers.h create mode 100644 gb.httpd/src/version.h create mode 100644 m4/gbhttpd.m4 diff --git a/Makefile.am b/Makefile.am index 99dd380a3..9f55243c0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,6 +31,7 @@ SUBDIRS = \ @ncurses_dir@ \ @media_dir@ \ @jit_dir@ \ + @httpd_dir@ \ comp \ app \ examples \ diff --git a/app/other/MakeWebSite/.settings b/app/other/MakeWebSite/.settings index 770e39411..27208e851 100644 --- a/app/other/MakeWebSite/.settings +++ b/app/other/MakeWebSite/.settings @@ -39,7 +39,7 @@ SearchString=True File[1]="gambas.sourceforge.net/menu.html:79.149" File[2]=".src/MChangeLog.module:18.0" Active=3 -File[3]=".src/MMain.module:15.31" +File[3]=".src/MMain.module:16.31" File[4]="gambas.sourceforge.net/style.css:427.0" File[5]="gambas.sourceforge.net/style-rtl.css:431.0" Count=5 diff --git a/app/other/MakeWebSite/.src/MMain.module b/app/other/MakeWebSite/.src/MMain.module index 33f6c995f..c696e6ac9 100644 --- a/app/other/MakeWebSite/.src/MMain.module +++ b/app/other/MakeWebSite/.src/MMain.module @@ -14,7 +14,7 @@ Sub InitVar() 'DIM aDev AS String[] = ["92", "91", "90", "51"] $cVar["OLD_VERSION"] = "2.24.0" - $cVar["DEV_VERSION"] = "3.3.0" + $cVar["DEV_VERSION"] = "3.3.1" InitAuthor diff --git a/app/other/MakeWebSite/gambas.sourceforge.net/home.html b/app/other/MakeWebSite/gambas.sourceforge.net/home.html index 15e054433..231e2da5e 100644 --- a/app/other/MakeWebSite/gambas.sourceforge.net/home.html +++ b/app/other/MakeWebSite/gambas.sourceforge.net/home.html @@ -56,10 +56,10 @@
- - {Download} Gambas 3.3.0 + + {Download} Gambas 3.3.1 - +
diff --git a/app/src/gambas3/.project b/app/src/gambas3/.project index 33905eaf3..1a107b251 100644 --- a/app/src/gambas3/.project +++ b/app/src/gambas3/.project @@ -2,6 +2,7 @@ # Compiled with Gambas 3.3.0 Title=Gambas 3 Startup=Project +Profiling=1 Icon=img/logo/logo-ide.png Version=3.3.0 VersionFile=1 diff --git a/comp/src/gb.report/.component b/comp/src/gb.report/.component index dc7a398e6..33f23775d 100644 --- a/comp/src/gb.report/.component +++ b/comp/src/gb.report/.component @@ -1,6 +1,6 @@ [Component] Key=gb.report -Version=3.2.90 +Version=3.3.0 State=1 Authors=Fabien Bodard Needs=Form,ImageIO diff --git a/comp/src/gb.report/.project b/comp/src/gb.report/.project index cb5992712..a92f50750 100644 --- a/comp/src/gb.report/.project +++ b/comp/src/gb.report/.project @@ -3,12 +3,13 @@ Title=Report designer Startup=Report1 Icon=printer1.png -Version=3.2.90 +Version=3.3.0 VersionFile=1 Component=gb.image Component=gb.gui Component=gb.form Component=gb.db +Component=gb.report Description="Report engine for gambas" Authors="Fabien Bodard" Environment="GB_GUI=gb.gtk" diff --git a/comp/src/gb.web/.info b/comp/src/gb.web/.info index a7bbc53b9..51d483643 100644 --- a/comp/src/gb.web/.info +++ b/comp/src/gb.web/.info @@ -5,6 +5,10 @@ Host R s +Port +R +s + Root R s @@ -196,10 +200,6 @@ CookiePath P s -_init -M - - _exit M diff --git a/comp/src/gb.web/.src/Application.module b/comp/src/gb.web/.src/Application.module index 9b458b85c..97c2023cb 100644 --- a/comp/src/gb.web/.src/Application.module +++ b/comp/src/gb.web/.src/Application.module @@ -3,6 +3,7 @@ Export Property Read Host As String +Property Read Port As String Property Read Root As String Property Read Request As String Property LogFile As String @@ -22,9 +23,9 @@ Private Function Request_Read() As String Dim sReq As String - sReq = $sProtocol & "://" & CGI["HTTP_HOST"] &/ CGI["SCRIPT_NAME"] &/ CGI["PATH_INFO"] + sReq = CGI["SCRIPT_NAME"] &/ CGI["PATH_INFO"] If CGI["QUERY_STRING"] Then sReq &= "?" & CGI["QUERY_STRING"] - Return sReq + Return Main.GetAbsoluteURL(sReq) End @@ -65,3 +66,9 @@ Private Sub Protocol_Write(Value As String) End + +Private Function Port_Read() As String + + Return CGI["SERVER_PORT"] + +End diff --git a/comp/src/gb.web/.src/CGI.module b/comp/src/gb.web/.src/CGI.module index ff28656c6..2ed14a177 100644 --- a/comp/src/gb.web/.src/CGI.module +++ b/comp/src/gb.web/.src/CGI.module @@ -69,7 +69,7 @@ Public Sub _init() sRoot = CGI["SCRIPT_NAME"] If Right(sRoot) = "/" Then sRoot = Left$(sRoot, -1) - If Not sRoot Then sRoot = "/" + If Not sRoot Then sRoot = "/." CGI["SCRIPT_NAME"] = sRoot End diff --git a/comp/src/gb.web/.src/Main.module b/comp/src/gb.web/.src/Main.module index a21e88681..bf0f40a10 100644 --- a/comp/src/gb.web/.src/Main.module +++ b/comp/src/gb.web/.src/Main.module @@ -58,6 +58,20 @@ Public Sub DecodeURL(sUrl As String, aField As String[], cVal As Collection) ' End +Public Sub GetAbsoluteURL(sPath As String) As String + + Dim sReq As String + Dim sPort As String + + sReq = Application.Protocol & "://" & Application.Host + + sPort = Application.Port + If sPort And If sPort <> "80" Then sReq &= ":" & sPort + + Return sReq &/ sPath + +End + Public Sub Main() diff --git a/comp/src/gb.web/.src/Response.module b/comp/src/gb.web/.src/Response.module index bdc369afe..e5c40f547 100644 --- a/comp/src/gb.web/.src/Response.module +++ b/comp/src/gb.web/.src/Response.module @@ -30,7 +30,7 @@ Public Sub Redirect(URL As String) If URL Like "*://*" Then AddHeader("Location", URL) Else - AddHeader("Location", Application.Protocol & "://" & CGI["HTTP_HOST"] &/ URL) + AddHeader("Location", Main.GetAbsoluteURL(URL)) Endif Response.Begin @@ -113,7 +113,7 @@ Public Sub End() If ShouldCompress() Then If Split(CGI["HTTP_ACCEPT_ENCODING"], ",").Exist("gzip*", gb.Like) Then - If Stat(sFile).Size >= 128 Then + If Stat(sFile).Size >= 1024 Then AddHeader("Content-Encoding", "gzip") AddHeader("Vary", "Accept-Encoding") Exec ["gzip", "-9", sFile] Wait @@ -126,6 +126,7 @@ Public Sub End() 'hLog = Open "/tmp/response." & CStr(Application.Id) For Create AddHeader("Content-Length", Lof($hFile)) Print $sHeader + Main.Log("Header = " & $sHeader) $sHeader = "" While Not Eof($hFile) diff --git a/comp/src/gb.web/.src/Session.module b/comp/src/gb.web/.src/Session.module index 8994fd548..82e4e4885 100644 --- a/comp/src/gb.web/.src/Session.module +++ b/comp/src/gb.web/.src/Session.module @@ -13,6 +13,7 @@ Private $hLock As File Private $sPrefix As String Private $bUnique As Boolean Private $sCookiePath As String +Private $bInit As Boolean Property Id As String Property Timeout As Float @@ -261,11 +262,19 @@ Private Sub SelectSession() End -Public Sub _init() +Private Sub Init() + + If $bInit Then Return + + $bInit = True Main.AllowLog = Exist("/tmp/session.debug") + 'Main.Log("Session.Init") + + 'Main.Log("HTTP_COOKIE = " & CGI["HTTP_COOKIE"] & " / " & Env["HTTP_COOKIE"]) $sId = Request.Cookies["SESSION"] + Main.Log("Cookie = " & $sId) '$sId = "9E2496B3AB6DDED93ABE6F0CF6E071B3@" If Not $sId Then Return @@ -278,17 +287,21 @@ End Public Sub _exit() + Main.Log("Session._exit") + SaveSession End Private Sub GetCookiePath() As String - If $sCookiePath Then - Return $sCookiePath - Else - Return CGI["SCRIPT_NAME"] - Endif + Dim sPath As String + + If $sCookiePath Then Return $sCookiePath + + sPath = CGI["SCRIPT_NAME"] + If sPath = "/." Then sPath = "/" + Return sPath End @@ -311,12 +324,14 @@ End Public Sub _get(Key As String) As Variant + Init If $cVal Then Return $cVal[Key] End Public Sub _put(Value As Variant, Key As String) + Init If Not $cVal Then CreateSession $cVal[Key] = Value $bModify = True @@ -326,6 +341,7 @@ End Private Function Id_Read() As String + Init Return $sId End @@ -333,6 +349,7 @@ End Private Sub Id_Write(Value As String) + Init Abandon $sId = Value SelectSession @@ -341,24 +358,28 @@ End Private Function Timeout_Read() As Float + Init Return Int($eTimeout * 86400 + 0.5) End Private Sub Timeout_Write(Value As Float) + Init $eTimeout = Value / 86400 End Public Sub Save() + Init SaveSession End Public Sub Load() + Init LoadSession End @@ -384,6 +405,7 @@ End Private Sub Unique_Write(Value As Boolean) + Init $bUnique = Value CheckUnique @@ -397,6 +419,7 @@ End Private Sub Modified_Write(Value As Boolean) + Init $bModify = Value End diff --git a/configure.ac b/configure.ac index 6ad7d0a5c..3c167294a 100644 --- a/configure.ac +++ b/configure.ac @@ -40,6 +40,7 @@ GB_CONFIG_SUBDIRS(gsl, gb.gsl) GB_CONFIG_SUBDIRS(ncurses, gb.ncurses) GB_CONFIG_SUBDIRS(media, gb.media) GB_CONFIG_SUBDIRS(jit, gb.jit) +GB_CONFIG_SUBDIRS(httpd, gb.httpd) AC_CONFIG_SUBDIRS(comp) AC_CONFIG_SUBDIRS(app) diff --git a/examples/examples/Control/Embedder/.project b/examples/examples/Control/Embedder/.project index fbd4e7be8..b57bb7b29 100644 --- a/examples/examples/Control/Embedder/.project +++ b/examples/examples/Control/Embedder/.project @@ -1,14 +1,15 @@ # Gambas Project File 3.0 -# Compiled with Gambas 3.0.0 +# Compiled with Gambas 3.3.0 Title=Embedder Startup=FMain Icon=embedder.png -Version=3.0.0 +Version=3.3.0 VersionFile=1 Component=gb.image Component=gb.gui Component=gb.form Component=gb.desktop +Environment="GB_GUI=gb.gtk" TabSize=2 Translate=1 Language=fr @@ -16,3 +17,4 @@ Maintainer=benoit Vendor=Princeton Address=benoit@localhost License=General Public Licence +Packager=1 diff --git a/examples/examples/Control/Embedder/.settings b/examples/examples/Control/Embedder/.settings index 06f179f15..320a9bec8 100644 --- a/examples/examples/Control/Embedder/.settings +++ b/examples/examples/Control/Embedder/.settings @@ -12,9 +12,9 @@ SearchComment=False SearchString=True [OpenFile] +Active=1 File[1]=".src/FMain.form" -Active=2 -File[2]=".src/FMain.class:15.30" +File[2]=".src/FMain.class:10.2" Count=2 [Watches] diff --git a/examples/examples/Control/Embedder/.src/FMain.class b/examples/examples/Control/Embedder/.src/FMain.class index 85e857f16..4fc5b9dde 100644 --- a/examples/examples/Control/Embedder/.src/FMain.class +++ b/examples/examples/Control/Embedder/.src/FMain.class @@ -6,7 +6,9 @@ Public Sub btnEmbed_Click() Dim aHandle As Integer[] Dim iHandle As Integer - sTitle = txtTitle.Text + sTitle = Trim(txtTitle.Text) + If Not sTitle Then Return + If Left(sTitle, 2) = "0x" Then iHandle = Val("&" & Mid$(sTitle, 3)) Else If Left(sTitle) = "&" Then diff --git a/examples/examples/Control/Embedder/.src/FMain.form b/examples/examples/Control/Embedder/.src/FMain.form index b47e6b1fd..40465f3de 100644 --- a/examples/examples/Control/Embedder/.src/FMain.form +++ b/examples/examples/Control/Embedder/.src/FMain.form @@ -1,15 +1,15 @@ # Gambas Form File 3.0 { Form Form - MoveScaled(32.6667,25,84,60) + MoveScaled(32.7143,25,84,60) Text = ("Desktop application embedder") Icon = Picture["embedder.png"] Arrangement = Arrange.Vertical { Panel1 Panel - MoveScaled(0,0,82,5) + MoveScaled(0,0,82,6) Arrangement = Arrange.Horizontal - Spacing = 8 - Padding = 4 + Spacing = True + Margin = True { Label1 Label MoveScaled(2,1,16,4) Font = Font["Bold"] @@ -20,7 +20,6 @@ MoveScaled(19,1,17,4) ToolTip = ("Enter there the title of the window you want to embed.") Expand = True - Text = ("") } { btnEmbed Button MoveScaled(37,1,14,4) @@ -37,7 +36,6 @@ { lblID Label MoveScaled(69,1,11,4) Visible = False - Text = ("") Alignment = Align.Center Border = Border.Sunken } diff --git a/examples/examples/Drawing/Fractal/.project b/examples/examples/Drawing/Fractal/.project index d0c9f8c86..714ca00d7 100644 --- a/examples/examples/Drawing/Fractal/.project +++ b/examples/examples/Drawing/Fractal/.project @@ -1,9 +1,9 @@ # Gambas Project File 3.0 -# Compiled with Gambas 3.2.0 +# Compiled with Gambas 3.3.0 Title=Fractal Startup=FFractal Icon=icon.png -Version=3.2.0 +Version=3.3.0 VersionFile=1 Component=gb.image Component=gb.gui diff --git a/examples/examples/Drawing/Fractal/.startup b/examples/examples/Drawing/Fractal/.startup index f45ca569a..2eb9fab6a 100644 --- a/examples/examples/Drawing/Fractal/.startup +++ b/examples/examples/Drawing/Fractal/.startup @@ -2,7 +2,7 @@ FFractal Fractal 0 0 -3.2.0 +3.3.0 gb.image gb.gui diff --git a/gb.httpd/AUTHORS b/gb.httpd/AUTHORS new file mode 100644 index 000000000..e69de29bb diff --git a/gb.httpd/COPYING b/gb.httpd/COPYING new file mode 120000 index 000000000..012065c85 --- /dev/null +++ b/gb.httpd/COPYING @@ -0,0 +1 @@ +../COPYING \ No newline at end of file diff --git a/gb.httpd/ChangeLog b/gb.httpd/ChangeLog new file mode 100644 index 000000000..e69de29bb diff --git a/gb.httpd/INSTALL b/gb.httpd/INSTALL new file mode 120000 index 000000000..99d491b4f --- /dev/null +++ b/gb.httpd/INSTALL @@ -0,0 +1 @@ +../INSTALL \ No newline at end of file diff --git a/gb.httpd/Makefile.am b/gb.httpd/Makefile.am new file mode 100644 index 000000000..4cac8ec72 --- /dev/null +++ b/gb.httpd/Makefile.am @@ -0,0 +1,3 @@ +ACLOCAL_AMFLAGS = -I m4 --install +SUBDIRS = @HTTPD_DIR@ +EXTRA_DIST = reconf gambas.h gb*.h diff --git a/gb.httpd/NEWS b/gb.httpd/NEWS new file mode 100644 index 000000000..e69de29bb diff --git a/gb.httpd/README b/gb.httpd/README new file mode 100644 index 000000000..e69de29bb diff --git a/gb.httpd/acinclude.m4 b/gb.httpd/acinclude.m4 new file mode 120000 index 000000000..d84c32a31 --- /dev/null +++ b/gb.httpd/acinclude.m4 @@ -0,0 +1 @@ +../acinclude.m4 \ No newline at end of file diff --git a/gb.httpd/component.am b/gb.httpd/component.am new file mode 120000 index 000000000..2f0eee34f --- /dev/null +++ b/gb.httpd/component.am @@ -0,0 +1 @@ +../component.am \ No newline at end of file diff --git a/gb.httpd/configure.ac b/gb.httpd/configure.ac new file mode 100644 index 000000000..036c27214 --- /dev/null +++ b/gb.httpd/configure.ac @@ -0,0 +1,151 @@ +dnl ---- configure.ac for gb.httpd + +AC_INIT(configure.ac) +AC_CONFIG_MACRO_DIR([m4]) +dnl AC_CANONICAL_SYSTEM +dnl AC_PROG_CC + +GB_INIT(gb.httpd) +AC_PROG_LIBTOOL + +V_CCOPT="-O" +if test "$GCC" = yes ; then + AC_MSG_CHECKING(gcc version) + AC_CACHE_VAL(ac_cv_lbl_gcc_vers, + ac_cv_lbl_gcc_vers=`$CC -dumpversion 2>&1 | \ + sed -e 's/\..*//'`) + AC_MSG_RESULT($ac_cv_lbl_gcc_vers) + if test "$ac_cv_lbl_gcc_vers" -gt 1 ; then + V_CCOPT="-O2" + fi +fi +if test -f .devel ; then + V_CCOPT="-g $V_CCOPT -Wall -Wmissing-prototypes -Wstrict-prototypes" +fi + +dnl +dnl maybe this should be a loop +dnl +AC_MSG_CHECKING(how to link static binaries) +AC_CACHE_VAL(ac_cv_lbl_static_flag, + ac_cv_lbl_static_flag=unknown + echo 'main() {}' > conftest.c + if test "$GCC" != yes ; then + trial_flag="-Bstatic" + test=`$CC $trial_flag -o conftest conftest.c 2>&1` + if test -z "$test" ; then + ac_cv_lbl_static_flag="$trial_flag" + fi + rm -f conftest + fi + if test "$ac_cv_lbl_static_flag" = unknown ; then + trial_flag="-static" + test=`$CC $trial_flag -o conftest conftest.c 2>&1` + if test -z "$test" ; then + ac_cv_lbl_static_flag="$trial_flag" + fi + rm -f conftest + fi + rm conftest.c) +AC_MSG_RESULT($ac_cv_lbl_static_flag) +if test "$ac_cv_lbl_static_flag" != unknown ; then + V_STATICFLAG="$ac_cv_lbl_static_flag" +fi + +AC_MSG_CHECKING(for __progname) +AC_CACHE_VAL(ac_cv_extern__progname, + AC_TRY_LINK([], + [extern char *__progname; + puts(__progname)], + ac_cv_extern__progname=yes, + ac_cv_extern__progname=no)) +if test $ac_cv_extern__progname = yes ; then + AC_DEFINE([HAVE__PROGNAME], [], [have __progname]) + AC_MSG_RESULT(yes) +else + AC_MSG_RESULT(no) +fi + +AC_CHECK_HEADERS(fcntl.h grp.h memory.h paths.h poll.h sys/poll.h sys/devpoll.h sys/event.h osreldate.h) +AC_HEADER_TIME +AC_HEADER_DIRENT + +d="/usr/local/v6/lib" +AC_MSG_CHECKING(for $d) +if test -d $d; then + AC_MSG_RESULT(yes (Adding -L$d to LDFLAGS)) + LDFLAGS="$LDFLAGS -L$d" +else + AC_MSG_RESULT(no) +fi + +dnl +dnl Most operating systems have gethostbyname() in the default searched +dnl libraries (i.e. libc): +dnl +V_NETLIBS="" +AC_CHECK_FUNC(gethostbyname, , + # Some OSes (eg. Solaris) place it in libnsl: + GB_AC_LBL_CHECK_LIB(nsl, gethostbyname, + V_NETLIBS="-lnsl $V_NETLIBS", + # Some strange OSes (SINIX) have it in libsocket: + GB_AC_LBL_CHECK_LIB(socket, gethostbyname, + V_NETLIBS="-lsocket $V_NETLIBS", + # Unfortunately libsocket sometimes depends on libnsl. + # AC_CHECK_LIB's API is essentially broken so the + # following ugliness is necessary: + GB_AC_LBL_CHECK_LIB(socket, gethostbyname, + V_NETLIBS="-lsocket -lnsl $V_NETLIBS", + AC_CHECK_LIB(resolv, gethostbyname, + V_NETLIBS="-lresolv $V_NETLIBS"), + -lnsl)))) +AC_CHECK_FUNC(socket, , + AC_CHECK_LIB(socket, socket, + V_NETLIBS="-lsocket $V_NETLIBS", + GB_AC_LBL_CHECK_LIB(socket, socket, + V_NETLIBS="-lsocket -lnsl $V_NETLIBS", , -lnsl))) + +AC_CHECK_LIB(inet6, main) + +AC_CHECK_FUNC(crypt, , AC_CHECK_LIB(crypt, crypt)) +AC_CHECK_FUNC(hstrerror, , + AC_CHECK_LIB(resolv, hstrerror, V_NETLIBS="-lresolv $V_NETLIBS")) + +AC_REPLACE_FUNCS(strerror) +AC_CHECK_FUNCS(waitpid vsnprintf daemon setsid setlogin getaddrinfo getnameinfo gai_strerror kqueue atoll) +AC_FUNC_MMAP + +case "$target_os" in +solaris*) + dnl Solaris's select() is a bad wrapper routine. + AC_CHECK_FUNCS(poll) + ;; +*) + AC_CHECK_FUNCS(select poll) + ;; +esac + +GB_AC_ACME_TM_GMTOFF +GB_AC_ACME_INT64T +GB_AC_ACME_SOCKLENT + +AC_PROG_MAKE_SET +AC_PROG_INSTALL + +AC_SUBST(DEFS) +AC_SUBST(V_CCOPT) +AC_SUBST(V_STATICFLAG) +AC_SUBST(V_NETLIBS) + +GB_COMPONENT( + httpd, + HTTPD, + gb.httpd, + [src], + [], + [], + [], + []) + +AC_OUTPUT( Makefile src/Makefile ) +GB_PRINT_MESSAGES diff --git a/gb.httpd/gambas.h b/gb.httpd/gambas.h new file mode 120000 index 000000000..03677ecd0 --- /dev/null +++ b/gb.httpd/gambas.h @@ -0,0 +1 @@ +../main/share/gambas.h \ No newline at end of file diff --git a/gb.httpd/gb_common.h b/gb.httpd/gb_common.h new file mode 120000 index 000000000..707d79da6 --- /dev/null +++ b/gb.httpd/gb_common.h @@ -0,0 +1 @@ +../main/share/gb_common.h \ No newline at end of file diff --git a/gb.httpd/m4 b/gb.httpd/m4 new file mode 120000 index 000000000..7d49a2a4b --- /dev/null +++ b/gb.httpd/m4 @@ -0,0 +1 @@ +../m4 \ No newline at end of file diff --git a/gb.httpd/missing b/gb.httpd/missing new file mode 120000 index 000000000..f3ade9ba1 --- /dev/null +++ b/gb.httpd/missing @@ -0,0 +1 @@ +../missing \ No newline at end of file diff --git a/gb.httpd/reconf b/gb.httpd/reconf new file mode 120000 index 000000000..48a376da6 --- /dev/null +++ b/gb.httpd/reconf @@ -0,0 +1 @@ +../reconf \ No newline at end of file diff --git a/gb.httpd/src/Makefile.am b/gb.httpd/src/Makefile.am new file mode 100644 index 000000000..772180410 --- /dev/null +++ b/gb.httpd/src/Makefile.am @@ -0,0 +1,21 @@ +COMPONENT = gb.httpd +include $(top_srcdir)/component.am + +gblib_LTLIBRARIES = gb.httpd.la + +gb_httpd_la_LIBADD = @HTTPD_LIB@ +gb_httpd_la_LDFLAGS = -module @LD_FLAGS@ @HTTPD_LDFLAGS@ +gb_httpd_la_CPPFLAGS = @HTTPD_INC@ + +gb_httpd_la_SOURCES = main.c main.h \ + fdwatch.h fdwatch.c \ + libhttpd.h libhttpd.c \ + match.h match.c \ + mime_encodings.h \ + mime_types.h \ + mmc.h mmc.c \ + tdate_parse.h tdate_parse.c \ + thttpd.h thttpd.c \ + timers.h timers.c \ + version.h + diff --git a/gb.httpd/src/fdwatch.c b/gb.httpd/src/fdwatch.c new file mode 100644 index 000000000..129d9af3b --- /dev/null +++ b/gb.httpd/src/fdwatch.c @@ -0,0 +1,840 @@ +/* fdwatch.c - fd watcher routines, either select() or poll() +** +** Copyright 1999,2000 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifdef HAVE_POLL_H +#include +#else /* HAVE_POLL_H */ +#ifdef HAVE_SYS_POLL_H +#include +#endif /* HAVE_SYS_POLL_H */ +#endif /* HAVE_POLL_H */ + +#ifdef HAVE_SYS_DEVPOLL_H +#include +#ifndef HAVE_DEVPOLL +#define HAVE_DEVPOLL +#endif /* !HAVE_DEVPOLL */ +#endif /* HAVE_SYS_DEVPOLL_H */ + +#ifdef HAVE_SYS_EVENT_H +#include +#endif /* HAVE_SYS_EVENT_H */ + +#include "fdwatch.h" + +#ifdef HAVE_SELECT +#ifndef FD_SET +#define NFDBITS 32 +#define FD_SETSIZE 32 +#define FD_SET(n, p) ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS))) +#define FD_CLR(n, p) ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS))) +#define FD_ISSET(n, p) ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS))) +#define FD_ZERO(p) bzero((char*)(p), sizeof(*(p))) +#endif /* !FD_SET */ +#endif /* HAVE_SELECT */ + +static int nfiles; +static long nwatches; +static int *fd_rw; +static void **fd_data; +static int nreturned, next_ridx; + +#ifdef HAVE_KQUEUE + +#define WHICH "kevent" +#define INIT( nfiles ) kqueue_init( nfiles ) +#define ADD_FD( fd, rw ) kqueue_add_fd( fd, rw ) +#define DEL_FD( fd ) kqueue_del_fd( fd ) +#define WATCH( timeout_msecs ) kqueue_watch( timeout_msecs ) +#define CHECK_FD( fd ) kqueue_check_fd( fd ) +#define GET_FD( ridx ) kqueue_get_fd( ridx ) + +static int kqueue_init (int nfiles); +static void kqueue_add_fd (int fd, int rw); +static void kqueue_del_fd (int fd); +static int kqueue_watch (long timeout_msecs); +static int kqueue_check_fd (int fd); +static int kqueue_get_fd (int ridx); + +#else /* HAVE_KQUEUE */ +#ifdef HAVE_DEVPOLL + +#define WHICH "devpoll" +#define INIT( nfiles ) devpoll_init( nfiles ) +#define ADD_FD( fd, rw ) devpoll_add_fd( fd, rw ) +#define DEL_FD( fd ) devpoll_del_fd( fd ) +#define WATCH( timeout_msecs ) devpoll_watch( timeout_msecs ) +#define CHECK_FD( fd ) devpoll_check_fd( fd ) +#define GET_FD( ridx ) devpoll_get_fd( ridx ) + +static int devpoll_init (int nfiles); +static void devpoll_add_fd (int fd, int rw); +static void devpoll_del_fd (int fd); +static int devpoll_watch (long timeout_msecs); +static int devpoll_check_fd (int fd); +static int devpoll_get_fd (int ridx); + +#else /* HAVE_DEVPOLL */ +#ifdef HAVE_POLL + +#define WHICH "poll" +#define INIT( nfiles ) poll_init( nfiles ) +#define ADD_FD( fd, rw ) poll_add_fd( fd, rw ) +#define DEL_FD( fd ) poll_del_fd( fd ) +#define WATCH( timeout_msecs ) poll_watch( timeout_msecs ) +#define CHECK_FD( fd ) poll_check_fd( fd ) +#define GET_FD( ridx ) poll_get_fd( ridx ) + +static int poll_init (int nfiles); +static void poll_add_fd (int fd, int rw); +static void poll_del_fd (int fd); +static int poll_watch (long timeout_msecs); +static int poll_check_fd (int fd); +static int poll_get_fd (int ridx); + +#else /* HAVE_POLL */ +#ifdef HAVE_SELECT + +#define WHICH "select" +#define INIT( nfiles ) select_init( nfiles ) +#define ADD_FD( fd, rw ) select_add_fd( fd, rw ) +#define DEL_FD( fd ) select_del_fd( fd ) +#define WATCH( timeout_msecs ) select_watch( timeout_msecs ) +#define CHECK_FD( fd ) select_check_fd( fd ) +#define GET_FD( ridx ) select_get_fd( ridx ) + +static int select_init (int nfiles); +static void select_add_fd (int fd, int rw); +static void select_del_fd (int fd); +static int select_watch (long timeout_msecs); +static int select_check_fd (int fd); +static int select_get_fd (int ridx); + +#endif /* HAVE_SELECT */ +#endif /* HAVE_POLL */ +#endif /* HAVE_DEVPOLL */ +#endif /* HAVE_KQUEUE */ + + +/* Routines. */ + +/* Figure out how many file descriptors the system allows, and +** initialize the fdwatch data structures. Returns -1 on failure. +*/ +int fdwatch_get_nfiles (void) +{ + int i; +#ifdef RLIMIT_NOFILE + struct rlimit rl; +#endif /* RLIMIT_NOFILE */ + + /* Figure out how many fd's we can have. */ + nfiles = getdtablesize (); +#ifdef RLIMIT_NOFILE + /* If we have getrlimit(), use that, and attempt to raise the limit. */ + if (getrlimit (RLIMIT_NOFILE, &rl) == 0) + { + nfiles = rl.rlim_cur; + if (rl.rlim_max == RLIM_INFINITY) + rl.rlim_cur = 8192; /* arbitrary */ + else if (rl.rlim_max > rl.rlim_cur) + rl.rlim_cur = rl.rlim_max; + if (setrlimit (RLIMIT_NOFILE, &rl) == 0) + nfiles = rl.rlim_cur; + } +#endif /* RLIMIT_NOFILE */ + +#if defined(HAVE_SELECT) && ! ( defined(HAVE_POLL) || defined(HAVE_DEVPOLL) || defined(HAVE_KQUEUE) ) + /* If we use select(), then we must limit ourselves to FD_SETSIZE. */ + nfiles = MIN (nfiles, FD_SETSIZE); +#endif /* HAVE_SELECT && ! ( HAVE_POLL || HAVE_DEVPOLL || HAVE_KQUEUE ) */ + + /* Initialize the fdwatch data structures. */ + nwatches = 0; + fd_rw = (int *) malloc (sizeof (int) * nfiles); + fd_data = (void **) malloc (sizeof (void *) * nfiles); + if (fd_rw == (int *) 0 || fd_data == (void **) 0) + return -1; + for (i = 0; i < nfiles; ++i) + fd_rw[i] = -1; + if (INIT (nfiles) == -1) + return -1; + + return nfiles; +} + + +/* Add a descriptor to the watch list. rw is either FDW_READ or FDW_WRITE. */ +void fdwatch_add_fd (int fd, void *client_data, int rw) +{ + if (fd < 0 || fd >= nfiles || fd_rw[fd] != -1) + { + syslog (LOG_ERR, "bad fd (%d) passed to fdwatch_add_fd!", fd); + return; + } + ADD_FD (fd, rw); + fd_rw[fd] = rw; + fd_data[fd] = client_data; +} + + +/* Remove a descriptor from the watch list. */ +void fdwatch_del_fd (int fd) +{ + if (fd < 0 || fd >= nfiles || fd_rw[fd] == -1) + { + syslog (LOG_ERR, "bad fd (%d) passed to fdwatch_del_fd!", fd); + return; + } + DEL_FD (fd); + fd_rw[fd] = -1; + fd_data[fd] = (void *) 0; +} + +/* Do the watch. Return value is the number of descriptors that are ready, +** or 0 if the timeout expired, or -1 on errors. A timeout of INFTIM means +** wait indefinitely. +*/ +int fdwatch (long timeout_msecs) +{ + ++nwatches; + nreturned = WATCH (timeout_msecs); + next_ridx = 0; + return nreturned; +} + + +/* Check if a descriptor was ready. */ +int fdwatch_check_fd (int fd) +{ + if (fd < 0 || fd >= nfiles || fd_rw[fd] == -1) + { + syslog (LOG_ERR, "bad fd (%d) passed to fdwatch_check_fd!", fd); + return 0; + } + return CHECK_FD (fd); +} + + +void *fdwatch_get_next_client_data (void) +{ + int fd; + + if (next_ridx >= nreturned) + return (void *) -1; + fd = GET_FD (next_ridx++); + if (fd < 0 || fd >= nfiles) + return (void *) 0; + return fd_data[fd]; +} + + +/* Generate debugging statistics syslog message. */ +void fdwatch_logstats (long secs) +{ + if (secs > 0) + syslog (LOG_INFO, " fdwatch - %ld %ss (%g/sec)", + nwatches, WHICH, (float) nwatches / secs); + nwatches = 0; +} + + +#ifdef HAVE_KQUEUE + +static int maxkqevents; +static struct kevent *kqevents; +static int nkqevents; +static struct kevent *kqrevents; +static int *kqrfdidx; +static int kq; + + +static int kqueue_init (int nfiles) +{ + kq = kqueue (); + if (kq == -1) + return -1; + maxkqevents = nfiles * 2; + kqevents = (struct kevent *) malloc (sizeof (struct kevent) * maxkqevents); + kqrevents = (struct kevent *) malloc (sizeof (struct kevent) * nfiles); + kqrfdidx = (int *) malloc (sizeof (int) * nfiles); + if (kqevents == (struct kevent *) 0 || kqrevents == (struct kevent *) 0 || + kqrfdidx == (int *) 0) + return -1; + (void) memset (kqevents, 0, sizeof (struct kevent) * maxkqevents); + (void) memset (kqrfdidx, 0, sizeof (int) * nfiles); + return 0; +} + + +static void kqueue_add_fd (int fd, int rw) +{ + if (nkqevents >= maxkqevents) + { + syslog (LOG_ERR, "too many kqevents in kqueue_add_fd!"); + return; + } + kqevents[nkqevents].ident = fd; + kqevents[nkqevents].flags = EV_ADD; + switch (rw) + { + case FDW_READ: + kqevents[nkqevents].filter = EVFILT_READ; + break; + case FDW_WRITE: + kqevents[nkqevents].filter = EVFILT_WRITE; + break; + default: + break; + } + ++nkqevents; +} + + +static void kqueue_del_fd (int fd) +{ + if (nkqevents >= maxkqevents) + { + syslog (LOG_ERR, "too many kqevents in kqueue_del_fd!"); + return; + } + kqevents[nkqevents].ident = fd; + kqevents[nkqevents].flags = EV_DELETE; + switch (fd_rw[fd]) + { + case FDW_READ: + kqevents[nkqevents].filter = EVFILT_READ; + break; + case FDW_WRITE: + kqevents[nkqevents].filter = EVFILT_WRITE; + break; + } + ++nkqevents; +} + + +static int kqueue_watch (long timeout_msecs) +{ + int i, r; + + if (timeout_msecs == INFTIM) + r = + kevent (kq, kqevents, nkqevents, kqrevents, nfiles, + (struct timespec *) 0); + else + { + struct timespec ts; + ts.tv_sec = timeout_msecs / 1000L; + ts.tv_nsec = (timeout_msecs % 1000L) * 1000000L; + r = kevent (kq, kqevents, nkqevents, kqrevents, nfiles, &ts); + } + nkqevents = 0; + if (r == -1) + return -1; + + for (i = 0; i < r; ++i) + kqrfdidx[kqrevents[i].ident] = i; + + return r; +} + + +static int kqueue_check_fd (int fd) +{ + int ridx = kqrfdidx[fd]; + + if (ridx < 0 || ridx >= nfiles) + { + syslog (LOG_ERR, "bad ridx (%d) in kqueue_check_fd!", ridx); + return 0; + } + if (ridx >= nreturned) + return 0; + if (kqrevents[ridx].ident != fd) + return 0; + if (kqrevents[ridx].flags & EV_ERROR) + return 0; + switch (fd_rw[fd]) + { + case FDW_READ: + return kqrevents[ridx].filter == EVFILT_READ; + case FDW_WRITE: + return kqrevents[ridx].filter == EVFILT_WRITE; + default: + return 0; + } +} + + +static int kqueue_get_fd (int ridx) +{ + if (ridx < 0 || ridx >= nfiles) + { + syslog (LOG_ERR, "bad ridx (%d) in kqueue_get_fd!", ridx); + return -1; + } + return kqrevents[ridx].ident; +} + +#else /* HAVE_KQUEUE */ + + +#ifdef HAVE_DEVPOLL + +static int maxdpevents; +static struct pollfd *dpevents; +static int ndpevents; +static struct pollfd *dprevents; +static int *dp_rfdidx; +static int dp; + + +static int devpoll_init (int nfiles) +{ + dp = open ("/dev/poll", O_RDWR); + if (dp == -1) + return -1; + (void) fcntl (dp, F_SETFD, 1); + maxdpevents = nfiles * 2; + dpevents = (struct pollfd *) malloc (sizeof (struct pollfd) * maxdpevents); + dprevents = (struct pollfd *) malloc (sizeof (struct pollfd) * nfiles); + dp_rfdidx = (int *) malloc (sizeof (int) * nfiles); + if (dpevents == (struct pollfd *) 0 || dprevents == (struct pollfd *) 0 || + dp_rfdidx == (int *) 0) + return -1; + (void) memset (dp_rfdidx, 0, sizeof (int) * nfiles); + return 0; +} + + +static void devpoll_add_fd (int fd, int rw) +{ + if (ndpevents >= maxdpevents) + { + syslog (LOG_ERR, "too many fds in devpoll_add_fd!"); + return; + } + dpevents[ndpevents].fd = fd; + switch (rw) + { + case FDW_READ: + dpevents[ndpevents].events = POLLIN; + break; + case FDW_WRITE: + dpevents[ndpevents].events = POLLOUT; + break; + default: + break; + } + ++ndpevents; +} + + +static void devpoll_del_fd (int fd) +{ + if (ndpevents >= maxdpevents) + { + syslog (LOG_ERR, "too many fds in devpoll_del_fd!"); + return; + } + dpevents[ndpevents].fd = fd; + dpevents[ndpevents].events = POLLREMOVE; + ++ndpevents; +} + + +static int devpoll_watch (long timeout_msecs) +{ + int i, r; + struct dvpoll dvp; + + r = sizeof (struct pollfd) * ndpevents; + if (r > 0 && write (dp, dpevents, r) != r) + return -1; + + ndpevents = 0; + dvp.dp_fds = dprevents; + dvp.dp_nfds = nfiles; + dvp.dp_timeout = (int) timeout_msecs; + + r = ioctl (dp, DP_POLL, &dvp); + if (r == -1) + return -1; + + for (i = 0; i < r; ++i) + dp_rfdidx[dprevents[i].fd] = i; + + return r; +} + + +static int devpoll_check_fd (int fd) +{ + int ridx = dp_rfdidx[fd]; + + if (ridx < 0 || ridx >= nfiles) + { + syslog (LOG_ERR, "bad ridx (%d) in devpoll_check_fd!", ridx); + return 0; + } + if (ridx >= nreturned) + return 0; + if (dprevents[ridx].fd != fd) + return 0; + if (dprevents[ridx].revents & POLLERR) + return 0; + switch (fd_rw[fd]) + { + case FDW_READ: + return dprevents[ridx].revents & (POLLIN | POLLHUP | POLLNVAL); + case FDW_WRITE: + return dprevents[ridx].revents & (POLLOUT | POLLHUP | POLLNVAL); + default: + return 0; + } +} + + +static int devpoll_get_fd (int ridx) +{ + if (ridx < 0 || ridx >= nfiles) + { + syslog (LOG_ERR, "bad ridx (%d) in devpoll_get_fd!", ridx); + return -1; + } + return dprevents[ridx].fd; +} + + +#else /* HAVE_DEVPOLL */ + + +#ifdef HAVE_POLL + +static struct pollfd *pollfds; +static int npoll_fds; +static int *poll_fdidx; +static int *poll_rfdidx; + + +static int poll_init (int nfiles) +{ + int i; + + pollfds = (struct pollfd *) malloc (sizeof (struct pollfd) * nfiles); + poll_fdidx = (int *) malloc (sizeof (int) * nfiles); + poll_rfdidx = (int *) malloc (sizeof (int) * nfiles); + if (pollfds == (struct pollfd *) 0 || poll_fdidx == (int *) 0 || + poll_rfdidx == (int *) 0) + return -1; + for (i = 0; i < nfiles; ++i) + pollfds[i].fd = poll_fdidx[i] = -1; + return 0; +} + + +static void poll_add_fd (int fd, int rw) +{ + if (npoll_fds >= nfiles) + { + syslog (LOG_ERR, "too many fds in poll_add_fd!"); + return; + } + pollfds[npoll_fds].fd = fd; + switch (rw) + { + case FDW_READ: + pollfds[npoll_fds].events = POLLIN; + break; + case FDW_WRITE: + pollfds[npoll_fds].events = POLLOUT; + break; + default: + break; + } + poll_fdidx[fd] = npoll_fds; + ++npoll_fds; +} + + +static void poll_del_fd (int fd) +{ + int idx = poll_fdidx[fd]; + + if (idx < 0 || idx >= nfiles) + { + syslog (LOG_ERR, "bad idx (%d) in poll_del_fd!", idx); + return; + } + --npoll_fds; + pollfds[idx] = pollfds[npoll_fds]; + poll_fdidx[pollfds[idx].fd] = idx; + pollfds[npoll_fds].fd = -1; + poll_fdidx[fd] = -1; +} + + +static int poll_watch (long timeout_msecs) +{ + int r, ridx, i; + + r = poll (pollfds, npoll_fds, (int) timeout_msecs); + if (r <= 0) + return r; + + ridx = 0; + for (i = 0; i < npoll_fds; ++i) + if (pollfds[i].revents & + (POLLIN | POLLOUT | POLLERR | POLLHUP | POLLNVAL)) + { + poll_rfdidx[ridx++] = pollfds[i].fd; + if (ridx == r) + break; + } + + return ridx; /* should be equal to r */ +} + + +static int poll_check_fd (int fd) +{ + int fdidx = poll_fdidx[fd]; + + if (fdidx < 0 || fdidx >= nfiles) + { + syslog (LOG_ERR, "bad fdidx (%d) in poll_check_fd!", fdidx); + return 0; + } + if (pollfds[fdidx].revents & POLLERR) + return 0; + switch (fd_rw[fd]) + { + case FDW_READ: + return pollfds[fdidx].revents & (POLLIN | POLLHUP | POLLNVAL); + case FDW_WRITE: + return pollfds[fdidx].revents & (POLLOUT | POLLHUP | POLLNVAL); + default: + return 0; + } +} + + +static int poll_get_fd (int ridx) +{ + if (ridx < 0 || ridx >= nfiles) + { + syslog (LOG_ERR, "bad ridx (%d) in poll_get_fd!", ridx); + return -1; + } + return poll_rfdidx[ridx]; +} + +#else /* HAVE_POLL */ + + +#ifdef HAVE_SELECT + +static fd_set master_rfdset; +static fd_set master_wfdset; +static fd_set working_rfdset; +static fd_set working_wfdset; +static int *select_fds; +static int *select_fdidx; +static int *select_rfdidx; +static int nselect_fds; +static int maxfd; +static int maxfd_changed; + + +static int select_init (int nfiles) +{ + int i; + + FD_ZERO (&master_rfdset); + FD_ZERO (&master_wfdset); + select_fds = (int *) malloc (sizeof (int) * nfiles); + select_fdidx = (int *) malloc (sizeof (int) * nfiles); + select_rfdidx = (int *) malloc (sizeof (int) * nfiles); + if (select_fds == (int *) 0 || select_fdidx == (int *) 0 || + select_rfdidx == (int *) 0) + return -1; + nselect_fds = 0; + maxfd = -1; + maxfd_changed = 0; + for (i = 0; i < nfiles; ++i) + select_fds[i] = select_fdidx[i] = -1; + return 0; +} + + +static void select_add_fd (int fd, int rw) +{ + if (nselect_fds >= nfiles) + { + syslog (LOG_ERR, "too many fds in select_add_fd!"); + return; + } + select_fds[nselect_fds] = fd; + switch (rw) + { + case FDW_READ: + FD_SET (fd, &master_rfdset); + break; + case FDW_WRITE: + FD_SET (fd, &master_wfdset); + break; + default: + break; + } + if (fd > maxfd) + maxfd = fd; + select_fdidx[fd] = nselect_fds; + ++nselect_fds; +} + + +static void select_del_fd (int fd) +{ + int idx = select_fdidx[fd]; + + if (idx < 0 || idx >= nfiles) + { + syslog (LOG_ERR, "bad idx (%d) in select_del_fd!", idx); + return; + } + + --nselect_fds; + select_fds[idx] = select_fds[nselect_fds]; + select_fdidx[select_fds[idx]] = idx; + select_fds[nselect_fds] = -1; + select_fdidx[fd] = -1; + + FD_CLR (fd, &master_rfdset); + FD_CLR (fd, &master_wfdset); + + if (fd >= maxfd) + maxfd_changed = 1; +} + + +static int select_get_maxfd (void) +{ + if (maxfd_changed) + { + int i; + maxfd = -1; + for (i = 0; i < nselect_fds; ++i) + if (select_fds[i] > maxfd) + maxfd = select_fds[i]; + maxfd_changed = 0; + } + return maxfd; +} + + +static int select_watch (long timeout_msecs) +{ + int mfd; + int r, idx, ridx; + + working_rfdset = master_rfdset; + working_wfdset = master_wfdset; + mfd = select_get_maxfd (); + if (timeout_msecs == INFTIM) + r = select (mfd + 1, &working_rfdset, &working_wfdset, (fd_set *) 0, + (struct timeval *) 0); + else + { + struct timeval timeout; + timeout.tv_sec = timeout_msecs / 1000L; + timeout.tv_usec = (timeout_msecs % 1000L) * 1000L; + r = + select (mfd + 1, &working_rfdset, &working_wfdset, (fd_set *) 0, + &timeout); + } + if (r <= 0) + return r; + + ridx = 0; + for (idx = 0; idx < nselect_fds; ++idx) + if (select_check_fd (select_fds[idx])) + { + select_rfdidx[ridx++] = select_fds[idx]; + if (ridx == r) + break; + } + + return ridx; /* should be equal to r */ +} + + +static int select_check_fd (int fd) +{ + switch (fd_rw[fd]) + { + case FDW_READ: + return FD_ISSET (fd, &working_rfdset); + case FDW_WRITE: + return FD_ISSET (fd, &working_wfdset); + default: + return 0; + } +} + + +static int select_get_fd (int ridx) +{ + if (ridx < 0 || ridx >= nfiles) + { + syslog (LOG_ERR, "bad ridx (%d) in select_get_fd!", ridx); + return -1; + } + return select_rfdidx[ridx]; +} + +#endif /* HAVE_SELECT */ + +#endif /* HAVE_POLL */ + +#endif /* HAVE_DEVPOLL */ + +#endif /* HAVE_KQUEUE */ diff --git a/gb.httpd/src/fdwatch.h b/gb.httpd/src/fdwatch.h new file mode 100644 index 000000000..5588b739e --- /dev/null +++ b/gb.httpd/src/fdwatch.h @@ -0,0 +1,85 @@ +/* fdwatch.h - header file for fdwatch package +** +** This package abstracts the use of the select()/poll()/kqueue() +** system calls. The basic function of these calls is to watch a set +** of file descriptors for activity. select() originated in the BSD world, +** while poll() came from SysV land, and their interfaces are somewhat +** different. fdwatch lets you write your code to a single interface, +** with the portability differences hidden inside the package. +** +** Usage is fairly simple. Call fdwatch_get_nfiles() to initialize +** the package and find out how many fine descriptors are available. +** Then each time through your main loop, call fdwatch_clear(), then +** fdwatch_add_fd() for each of the descriptors you want to watch, +** then call fdwatch() to actually perform the watch. After it returns +** you can check which descriptors are ready via fdwatch_check_fd(). +** +** If your descriptor set hasn't changed from the last time through +** the loop, you can skip calling fdwatch_clear() and fdwatch_add_fd() +** to save a little CPU time. +** +** +** Copyright © 1999 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#ifndef _FDWATCH_H_ +#define _FDWATCH_H_ + +#define FDW_READ 0 +#define FDW_WRITE 1 + +#ifndef INFTIM +#define INFTIM -1 +#endif /* INFTIM */ + +/* Figure out how many file descriptors the system allows, and +** initialize the fdwatch data structures. Returns -1 on failure. +*/ +extern int fdwatch_get_nfiles (void); + +/* Add a descriptor to the watch list. rw is either FDW_READ or FDW_WRITE. */ +extern void fdwatch_add_fd (int fd, void *client_data, int rw); + +/* Delete a descriptor from the watch list. */ +extern void fdwatch_del_fd (int fd); + +/* Do the watch. Return value is the number of descriptors that are ready, +** or 0 if the timeout expired, or -1 on errors. A timeout of INFTIM means +** wait indefinitely. +*/ +extern int fdwatch (long timeout_msecs); + +/* Check if a descriptor was ready. */ +extern int fdwatch_check_fd (int fd); + +/* Get the client data for the next returned event. Returns -1 when there +** are no more events. +*/ +extern void *fdwatch_get_next_client_data (void); + +/* Generate debugging statistics syslog message. */ +extern void fdwatch_logstats (long secs); + +#endif /* _FDWATCH_H_ */ diff --git a/gb.httpd/src/gb.httpd.component b/gb.httpd/src/gb.httpd.component new file mode 100644 index 000000000..10e636c3f --- /dev/null +++ b/gb.httpd/src/gb.httpd.component @@ -0,0 +1,3 @@ +[Component] +Author= +Alpha=1 diff --git a/gb.httpd/src/libhttpd.c b/gb.httpd/src/libhttpd.c new file mode 100644 index 000000000..e8be20416 --- /dev/null +++ b/gb.httpd/src/libhttpd.c @@ -0,0 +1,4308 @@ +/* libhttpd.c - HTTP protocol library +** +** Copyright � 1995,1998,1999,2000,2001 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#include "main.h" +#include "thttpd.h" +#include "version.h" + +#ifdef SHOW_SERVER_VERSION +#define EXPOSED_SERVER_SOFTWARE SERVER_SOFTWARE +#else /* SHOW_SERVER_VERSION */ +#define EXPOSED_SERVER_SOFTWARE "thttpd" +#endif /* SHOW_SERVER_VERSION */ + +#include +#include +#include + +#include +#include +#include +#include +#ifdef HAVE_MEMORY_H +#include +#endif /* HAVE_MEMORY_H */ +#include +#include +#include +#include +#include +//#include +#include +#include + +#ifdef HAVE_OSRELDATE_H +#include +#endif /* HAVE_OSRELDATE_H */ + +#ifdef HAVE_DIRENT_H +#include +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#ifdef HAVE_SYS_NDIR_H +#include +#endif +#ifdef HAVE_SYS_DIR_H +#include +#endif +#ifdef HAVE_NDIR_H +#include +#endif +#endif + +extern char *crypt (const char *key, const char *setting); + +#include "libhttpd.h" +#include "mmc.h" +#include "timers.h" +#include "match.h" +#include "tdate_parse.h" + +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif + +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +#ifndef HAVE_INT64T +typedef long long int64_t; +#endif + +#ifndef HAVE_SOCKLENT +typedef int socklen_t; +#endif + +#ifdef __CYGWIN__ +#define timezone _timezone +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +extern char **environ; + +/* Forwards. */ +static void check_options (void); +static void free_httpd_server (httpd_server * hs); +static int initialize_listen_socket (httpd_sockaddr * saP); +static void add_response (httpd_conn * hc, char *str); +static void send_mime (httpd_conn * hc, int status, char *title, + char *encodings, char *extraheads, char *type, + off_t length, time_t mod); +static void send_response (httpd_conn * hc, int status, char *title, + char *extraheads, char *form, char *arg); +static void send_response_tail (httpd_conn * hc); +static void defang (char *str, char *dfstr, int dfsize); +#ifdef ERR_DIR +static int send_err_file (httpd_conn * hc, int status, char *title, + char *extraheads, char *filename); +#endif /* ERR_DIR */ +#ifdef AUTH_FILE +static void send_authenticate (httpd_conn * hc, char *realm); +static int b64_decode (const char *str, unsigned char *space, int size); +//static int auth_check (httpd_conn * hc, char *dirname); +//static int auth_check2 (httpd_conn * hc, char *dirname); +#endif /* AUTH_FILE */ +//static void send_dirredirect (httpd_conn * hc); +static int hexit (char c); +static void strdecode (char *to, char *from); +#ifdef GENERATE_INDEXES +static void strencode (char *to, int tosize, char *from); +#endif /* GENERATE_INDEXES */ +#ifdef TILDE_MAP_1 +static int tilde_map_1 (httpd_conn * hc); +#endif /* TILDE_MAP_1 */ +#ifdef TILDE_MAP_2 +static int tilde_map_2 (httpd_conn * hc); +#endif /* TILDE_MAP_2 */ +static int vhost_map (httpd_conn * hc); +//static char *expand_symlinks (char *path, char **restP, int no_symlink_check, int tildemapped); +static char *bufgets (httpd_conn * hc); +static void de_dotdot (char *file); +static void init_mime (void); +//static void figure_mime (httpd_conn * hc); +#ifdef CGI_TIMELIMIT +static void cgi_kill2 (ClientData client_data, struct timeval *nowP); +static void cgi_kill (ClientData client_data, struct timeval *nowP); +#endif /* CGI_TIMELIMIT */ +#ifdef GENERATE_INDEXES +static int ls (httpd_conn * hc); +#endif /* GENERATE_INDEXES */ +static char *build_env (char *fmt, char *arg); +#ifdef SERVER_NAME_LIST +static char *hostname_map (char *hostname); +#endif /* SERVER_NAME_LIST */ +static char **make_envp (httpd_conn * hc); +static char **make_argp (httpd_conn * hc); +static void cgi_interpose_input (httpd_conn * hc, int wfd); +static void post_post_garbage_hack (httpd_conn * hc); +static void cgi_interpose_output (httpd_conn * hc, int rfd); +static void cgi_child (httpd_conn * hc); +static int cgi (httpd_conn * hc); +static int really_start_request (httpd_conn * hc, struct timeval *nowP); +static void make_log_entry (httpd_conn * hc, struct timeval *nowP); +static int check_referer (httpd_conn * hc); +static int really_check_referer (httpd_conn * hc); +static int sockaddr_check (httpd_sockaddr * saP); +static size_t sockaddr_len (httpd_sockaddr * saP); +static int my_snprintf (char *str, size_t size, const char *format, ...); +#ifndef HAVE_ATOLL +static long long atoll (const char *str); +#endif /* HAVE_ATOLL */ + + +/* This global keeps track of whether we are in the main process or a +** sub-process. The reason is that httpd_write_response() can get called +** in either context; when it is called from the main process it must use +** non-blocking I/O to avoid stalling the server, but when it is called +** from a sub-process it wants to use blocking I/O so that the whole +** response definitely gets written. So, it checks this variable. A bit +** of a hack but it seems to do the right thing. +*/ +static int sub_process = 0; + + +static void check_options (void) +{ +#if defined(TILDE_MAP_1) && defined(TILDE_MAP_2) + syslog (LOG_CRIT, "both TILDE_MAP_1 and TILDE_MAP_2 are defined"); + exit (1); +#endif /* both */ +} + + +static void free_httpd_server (httpd_server * hs) +{ + if (hs->binding_hostname != (char *) 0) + free ((void *) hs->binding_hostname); + if (hs->cwd != (char *) 0) + free ((void *) hs->cwd); + if (hs->cgi_pattern != (char *) 0) + free ((void *) hs->cgi_pattern); + if (hs->charset != (char *) 0) + free ((void *) hs->charset); + if (hs->p3p != (char *) 0) + free ((void *) hs->p3p); + if (hs->url_pattern != (char *) 0) + free ((void *) hs->url_pattern); + if (hs->local_pattern != (char *) 0) + free ((void *) hs->local_pattern); + free ((void *) hs); +} + + +httpd_server *httpd_initialize (char *hostname, httpd_sockaddr * sa4P, + httpd_sockaddr * sa6P, unsigned short port, + char *cgi_pattern, int cgi_limit, + int cgi_timelimit, char *charset, char *p3p, + int max_age, char *cwd, int no_log, + FILE * logfp, int no_symlink_check, int vhost, + int global_passwd, char *url_pattern, + char *local_pattern, int no_empty_referers) +{ + httpd_server *hs; + static char ghnbuf[256]; + char *cp; + + check_options (); + + hs = NEW (httpd_server, 1); + if (hs == (httpd_server *) 0) + { + syslog (LOG_CRIT, "out of memory allocating an httpd_server"); + return (httpd_server *) 0; + } + + if (hostname != (char *) 0) + { + hs->binding_hostname = strdup (hostname); + if (hs->binding_hostname == (char *) 0) + { + syslog (LOG_CRIT, "out of memory copying hostname"); + return (httpd_server *) 0; + } + hs->server_hostname = hs->binding_hostname; + } + else + { + hs->binding_hostname = (char *) 0; + hs->server_hostname = (char *) 0; + if (gethostname (ghnbuf, sizeof (ghnbuf)) < 0) + ghnbuf[0] = '\0'; +#ifdef SERVER_NAME_LIST + if (ghnbuf[0] != '\0') + hs->server_hostname = hostname_map (ghnbuf); +#endif /* SERVER_NAME_LIST */ + if (hs->server_hostname == (char *) 0) + { +#ifdef SERVER_NAME + hs->server_hostname = SERVER_NAME; +#else /* SERVER_NAME */ + if (ghnbuf[0] != '\0') + hs->server_hostname = ghnbuf; +#endif /* SERVER_NAME */ + } + } + + hs->port = port; + if (cgi_pattern == (char *) 0) + hs->cgi_pattern = (char *) 0; + else + { + /* Nuke any leading slashes. */ + if (cgi_pattern[0] == '/') + ++cgi_pattern; + hs->cgi_pattern = strdup (cgi_pattern); + if (hs->cgi_pattern == (char *) 0) + { + syslog (LOG_CRIT, "out of memory copying cgi_pattern"); + return (httpd_server *) 0; + } + /* Nuke any leading slashes in the cgi pattern. */ + while ((cp = strstr (hs->cgi_pattern, "|/")) != (char *) 0) + (void) strcpy (cp + 1, cp + 2); + } + hs->cgi_limit = cgi_limit; + hs->cgi_timelimit = cgi_timelimit; + hs->cgi_count = 0; + hs->charset = strdup (charset); + hs->p3p = strdup (p3p); + hs->max_age = max_age; + hs->cwd = strdup (cwd); + if (hs->cwd == (char *) 0) + { + syslog (LOG_CRIT, "out of memory copying cwd"); + return (httpd_server *) 0; + } + if (url_pattern == (char *) 0) + hs->url_pattern = (char *) 0; + else + { + hs->url_pattern = strdup (url_pattern); + if (hs->url_pattern == (char *) 0) + { + syslog (LOG_CRIT, "out of memory copying url_pattern"); + return (httpd_server *) 0; + } + } + if (local_pattern == (char *) 0) + hs->local_pattern = (char *) 0; + else + { + hs->local_pattern = strdup (local_pattern); + if (hs->local_pattern == (char *) 0) + { + syslog (LOG_CRIT, "out of memory copying local_pattern"); + return (httpd_server *) 0; + } + } + hs->no_log = no_log; + hs->logfp = (FILE *) 0; + httpd_set_logfp (hs, logfp); + hs->no_symlink_check = no_symlink_check; + hs->vhost = vhost; + hs->global_passwd = global_passwd; + hs->no_empty_referers = no_empty_referers; + + /* Initialize listen sockets. Try v6 first because of a Linux peculiarity; + ** like some other systems, it has magical v6 sockets that also listen for + ** v4, but in Linux if you bind a v4 socket first then the v6 bind fails. + */ + if (sa6P == (httpd_sockaddr *) 0) + hs->listen6_fd = -1; + else + hs->listen6_fd = initialize_listen_socket (sa6P); + if (sa4P == (httpd_sockaddr *) 0) + hs->listen4_fd = -1; + else + hs->listen4_fd = initialize_listen_socket (sa4P); + /* If we didn't get any valid sockets, fail. */ + if (hs->listen4_fd == -1 && hs->listen6_fd == -1) + { + free_httpd_server (hs); + return (httpd_server *) 0; + } + + init_mime (); + + /* Done initializing. */ + if (hs->binding_hostname == (char *) 0) + syslog (LOG_NOTICE, "%.80s starting on port %d", SERVER_SOFTWARE, + (int) hs->port); + else + syslog (LOG_NOTICE, "%.80s starting on %.80s, port %d", SERVER_SOFTWARE, + httpd_ntoa (hs->listen4_fd != -1 ? sa4P : sa6P), (int) hs->port); + return hs; +} + + +static int initialize_listen_socket (httpd_sockaddr * saP) +{ + int listen_fd; + int on, flags; + + /* Check sockaddr. */ + if (!sockaddr_check (saP)) + { + syslog (LOG_CRIT, "unknown sockaddr family on listen socket"); + return -1; + } + + /* Create socket. */ + listen_fd = socket (saP->sa.sa_family, SOCK_STREAM, 0); + if (listen_fd < 0) + { + syslog (LOG_CRIT, "socket %.80s - %m", httpd_ntoa (saP)); + return -1; + } + (void) fcntl (listen_fd, F_SETFD, 1); + + /* Allow reuse of local addresses. */ + on = 1; + if (setsockopt (listen_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, + sizeof (on)) < 0) + syslog (LOG_CRIT, "setsockopt SO_REUSEADDR - %m"); + + /* Bind to it. */ + if (bind (listen_fd, &saP->sa, sockaddr_len (saP)) < 0) + { + syslog (LOG_CRIT, "bind %.80s - %m", httpd_ntoa (saP)); + (void) close (listen_fd); + return -1; + } + + /* Set the listen file descriptor to no-delay / non-blocking mode. */ + flags = fcntl (listen_fd, F_GETFL, 0); + if (flags == -1) + { + syslog (LOG_CRIT, "fcntl F_GETFL - %m"); + (void) close (listen_fd); + return -1; + } + if (fcntl (listen_fd, F_SETFL, flags | O_NDELAY) < 0) + { + syslog (LOG_CRIT, "fcntl O_NDELAY - %m"); + (void) close (listen_fd); + return -1; + } + + /* Start a listen going. */ + if (listen (listen_fd, LISTEN_BACKLOG) < 0) + { + syslog (LOG_CRIT, "listen - %m"); + (void) close (listen_fd); + return -1; + } + + /* Use accept filtering, if available. */ +#ifdef SO_ACCEPTFILTER + { +#if ( __FreeBSD_version >= 411000 ) +#define ACCEPT_FILTER_NAME "httpready" +#else +#define ACCEPT_FILTER_NAME "dataready" +#endif + struct accept_filter_arg af; + (void) bzero (&af, sizeof (af)); + (void) strcpy (af.af_name, ACCEPT_FILTER_NAME); + (void) setsockopt (listen_fd, SOL_SOCKET, SO_ACCEPTFILTER, (char *) &af, + sizeof (af)); + } +#endif /* SO_ACCEPTFILTER */ + + return listen_fd; +} + + +void httpd_set_logfp (httpd_server * hs, FILE * logfp) +{ + if (hs->logfp != (FILE *) 0) + (void) fclose (hs->logfp); + hs->logfp = logfp; +} + + +void httpd_terminate (httpd_server * hs) +{ + httpd_unlisten (hs); + if (hs->logfp != (FILE *) 0) + (void) fclose (hs->logfp); + free_httpd_server (hs); +} + + +void httpd_unlisten (httpd_server * hs) +{ + if (hs->listen4_fd != -1) + { + (void) close (hs->listen4_fd); + hs->listen4_fd = -1; + } + if (hs->listen6_fd != -1) + { + (void) close (hs->listen6_fd); + hs->listen6_fd = -1; + } +} + + +/* Conditional macro to allow two alternate forms for use in the built-in +** error pages. If EXPLICIT_ERROR_PAGES is defined, the second and more +** explicit error form is used; otherwise, the first and more generic +** form is used. +*/ +#ifdef EXPLICIT_ERROR_PAGES +#define ERROR_FORM(a,b) b +#else /* EXPLICIT_ERROR_PAGES */ +#define ERROR_FORM(a,b) a +#endif /* EXPLICIT_ERROR_PAGES */ + + +static char *ok200title = "OK"; +static char *ok206title = "Partial Content"; + +static char *err302title = "Found"; +//static char *err302form = "The actual URL is '%.80s'.\n"; + +static char *err304title = "Not Modified"; + +char *httpd_err400title = "Bad Request"; +char *httpd_err400form = + "Your request has bad syntax or is inherently impossible to satisfy.\n"; + +#ifdef AUTH_FILE +static char *err401title = "Unauthorized"; +static char *err401form = "Authorization required for the URL '%.80s'.\n"; +#endif /* AUTH_FILE */ + +static char *err403title = "Forbidden"; +#ifndef EXPLICIT_ERROR_PAGES +static char *err403form = + "You do not have permission to get URL '%.80s' from this server.\n"; +#endif /* !EXPLICIT_ERROR_PAGES */ + +static char *err404title = "Not Found"; +//static char *err404form = "The requested URL '%.80s' was not found on this server.\n"; + +char *httpd_err408title = "Request Timeout"; +char *httpd_err408form = + "No request appeared within a reasonable time period.\n"; + +static char *err500title = "Internal Error"; +static char *err500form = + "There was an unusual problem serving the requested URL '%.80s'.\n"; + +static char *err501title = "Not Implemented"; +static char *err501form = + "The requested method '%.80s' is not implemented by this server.\n"; + +char *httpd_err503title = "Service Temporarily Overloaded"; +char *httpd_err503form = + "The requested URL '%.80s' is temporarily overloaded. Please try again later.\n"; + + +/* Append a string to the buffer waiting to be sent as response. */ +static void add_response (httpd_conn * hc, char *str) +{ + size_t len; + + len = strlen (str); + httpd_realloc_str (&hc->response, &hc->maxresponse, hc->responselen + len); + (void) memmove (&(hc->response[hc->responselen]), str, len); + hc->responselen += len; +} + +/* Send the buffered response. */ +void httpd_write_response (httpd_conn * hc) +{ + /* If we are in a sub-process, turn off no-delay mode. */ + if (sub_process) + httpd_clear_ndelay (hc->conn_fd); + /* Send the response, if necessary. */ + if (hc->responselen > 0) + { + (void) httpd_write_fully (hc->conn_fd, hc->response, hc->responselen); + hc->responselen = 0; + } +} + + +/* Set no-delay / non-blocking mode on a socket. */ +void httpd_set_ndelay (int fd) +{ + int flags, newflags; + + flags = fcntl (fd, F_GETFL, 0); + if (flags != -1) + { + newflags = flags | (int) O_NDELAY; + if (newflags != flags) + (void) fcntl (fd, F_SETFL, newflags); + } +} + + +/* Clear no-delay / non-blocking mode on a socket. */ +void httpd_clear_ndelay (int fd) +{ + int flags, newflags; + + flags = fcntl (fd, F_GETFL, 0); + if (flags != -1) + { + newflags = flags & ~(int) O_NDELAY; + if (newflags != flags) + (void) fcntl (fd, F_SETFL, newflags); + } +} + + +static void +send_mime (httpd_conn * hc, int status, char *title, char *encodings, + char *extraheads, char *type, off_t length, time_t mod) +{ + time_t now, expires; + const char *rfc1123fmt = "%a, %d %b %Y %H:%M:%S GMT"; + char nowbuf[100]; + char modbuf[100]; + char expbuf[100]; + char fixed_type[500]; + char buf[1000]; + int partial_content; + int s100; + + hc->status = status; + hc->bytes_to_send = length; + if (hc->mime_flag) + { + if (status == 200 && hc->got_range && + (hc->last_byte_index >= hc->first_byte_index) && + ((hc->last_byte_index != length - 1) || + (hc->first_byte_index != 0)) && + (hc->range_if == (time_t) - 1 || hc->range_if == hc->sb.st_mtime)) + { + partial_content = 1; + hc->status = status = 206; + title = ok206title; + } + else + { + partial_content = 0; + hc->got_range = 0; + } + + now = time ((time_t *) 0); + if (mod == (time_t) 0) + mod = now; + (void) strftime (nowbuf, sizeof (nowbuf), rfc1123fmt, gmtime (&now)); + (void) strftime (modbuf, sizeof (modbuf), rfc1123fmt, gmtime (&mod)); + (void) my_snprintf (fixed_type, sizeof (fixed_type), type, + hc->hs->charset); + (void) my_snprintf (buf, sizeof (buf), + "%.20s %d %s\015\012Server: %s\015\012Content-Type: %s\015\012Date: %s\015\012Last-Modified: %s\015\012Accept-Ranges: bytes\015\012Connection: close\015\012", + hc->protocol, status, title, EXPOSED_SERVER_SOFTWARE, + fixed_type, nowbuf, modbuf); + add_response (hc, buf); + s100 = status / 100; + if (s100 != 2 && s100 != 3) + { + (void) my_snprintf (buf, sizeof (buf), + "Cache-Control: no-cache,no-store\015\012"); + add_response (hc, buf); + } + if (encodings[0] != '\0') + { + (void) my_snprintf (buf, sizeof (buf), + "Content-Encoding: %s\015\012", encodings); + add_response (hc, buf); + } + if (partial_content) + { + (void) my_snprintf (buf, sizeof (buf), + "Content-Range: bytes %lld-%lld/%lld\015\012Content-Length: %lld\015\012", + (int64_t) hc->first_byte_index, + (int64_t) hc->last_byte_index, (int64_t) length, + (int64_t) (hc->last_byte_index - + hc->first_byte_index + 1)); + add_response (hc, buf); + } + else if (length >= 0) + { + (void) my_snprintf (buf, sizeof (buf), + "Content-Length: %lld\015\012", (int64_t) length); + add_response (hc, buf); + } + if (hc->hs->p3p[0] != '\0') + { + (void) my_snprintf (buf, sizeof (buf), "P3P: %s\015\012", hc->hs->p3p); + add_response (hc, buf); + } + if (hc->hs->max_age >= 0) + { + expires = now + hc->hs->max_age; + (void) strftime (expbuf, sizeof (expbuf), rfc1123fmt, + gmtime (&expires)); + (void) my_snprintf (buf, sizeof (buf), + "Cache-Control: max-age=%d\015\012Expires: %s\015\012", + hc->hs->max_age, expbuf); + add_response (hc, buf); + } + if (extraheads[0] != '\0') + add_response (hc, extraheads); + add_response (hc, "\015\012"); + } +} + + +static int str_alloc_count = 0; +static size_t str_alloc_size = 0; + +void httpd_realloc_str (char **strP, size_t * maxsizeP, size_t size) +{ + if (*maxsizeP == 0) + { + *maxsizeP = MAX (200, size + 100); + *strP = NEW (char, *maxsizeP + 1); + ++str_alloc_count; + str_alloc_size += *maxsizeP; + } + else if (size > *maxsizeP) + { + str_alloc_size -= *maxsizeP; + *maxsizeP = MAX (*maxsizeP * 2, size * 5 / 4); + *strP = RENEW (*strP, char, *maxsizeP + 1); + str_alloc_size += *maxsizeP; + } + else + return; + if (*strP == (char *) 0) + { + syslog (LOG_ERR, "out of memory reallocating a string to %d bytes", + *maxsizeP); + exit (1); + } +} + + +static void +send_response (httpd_conn * hc, int status, char *title, char *extraheads, + char *form, char *arg) +{ + char defanged_arg[1000], buf[2000]; + + send_mime (hc, status, title, "", extraheads, "text/html; charset=%s", + (off_t) - 1, (time_t) 0); + (void) my_snprintf (buf, sizeof (buf), "\ +\n\ +%d %s\n\ +\n\ +

%d %s

\n", status, title, status, title); + add_response (hc, buf); + defang (arg, defanged_arg, sizeof (defanged_arg)); + (void) my_snprintf (buf, sizeof (buf), form, defanged_arg); + add_response (hc, buf); + if (match ("**MSIE**", hc->useragent)) + { + int n; + add_response (hc, "\n"); + } + send_response_tail (hc); +} + + +static void send_response_tail (httpd_conn * hc) +{ + char buf[1000]; + + (void) my_snprintf (buf, sizeof (buf), "\ +
\n\ +
%s
\n\ +\n\ +\n", SERVER_ADDRESS, EXPOSED_SERVER_SOFTWARE); + add_response (hc, buf); +} + + +static void defang (char *str, char *dfstr, int dfsize) +{ + char *cp1; + char *cp2; + + for (cp1 = str, cp2 = dfstr; + *cp1 != '\0' && cp2 - dfstr < dfsize - 5; ++cp1, ++cp2) + { + switch (*cp1) + { + case '<': + *cp2++ = '&'; + *cp2++ = 'l'; + *cp2++ = 't'; + *cp2 = ';'; + break; + case '>': + *cp2++ = '&'; + *cp2++ = 'g'; + *cp2++ = 't'; + *cp2 = ';'; + break; + default: + *cp2 = *cp1; + break; + } + } + *cp2 = '\0'; +} + + +void +httpd_send_err (httpd_conn * hc, int status, char *title, char *extraheads, + char *form, char *arg) +{ +#ifdef ERR_DIR + + char filename[1000]; + + /* Try virtual host error page. */ + if (hc->hs->vhost && hc->hostdir[0] != '\0') + { + (void) my_snprintf (filename, sizeof (filename), + "%s/%s/err%d.html", hc->hostdir, ERR_DIR, status); + if (send_err_file (hc, status, title, extraheads, filename)) + return; + } + + /* Try server-wide error page. */ + (void) my_snprintf (filename, sizeof (filename), + "%s/err%d.html", ERR_DIR, status); + if (send_err_file (hc, status, title, extraheads, filename)) + return; + + /* Fall back on built-in error page. */ + send_response (hc, status, title, extraheads, form, arg); + +#else /* ERR_DIR */ + + send_response (hc, status, title, extraheads, form, arg); + +#endif /* ERR_DIR */ +} + + +#ifdef ERR_DIR +static int +send_err_file (httpd_conn * hc, int status, char *title, char *extraheads, + char *filename) +{ + FILE *fp; + char buf[1000]; + size_t r; + + fp = fopen (filename, "r"); + if (fp == (FILE *) 0) + return 0; + send_mime (hc, status, title, "", extraheads, "text/html; charset=%s", + (off_t) - 1, (time_t) 0); + for (;;) + { + r = fread (buf, 1, sizeof (buf) - 1, fp); + if (r == 0) + break; + buf[r] = '\0'; + add_response (hc, buf); + } + (void) fclose (fp); + +#ifdef ERR_APPEND_SERVER_INFO + send_response_tail (hc); +#endif /* ERR_APPEND_SERVER_INFO */ + + return 1; +} +#endif /* ERR_DIR */ + + +#ifdef AUTH_FILE + +static void send_authenticate (httpd_conn * hc, char *realm) +{ + static char *header; + static size_t maxheader = 0; + static char headstr[] = "WWW-Authenticate: Basic realm=\""; + + httpd_realloc_str (&header, &maxheader, + sizeof (headstr) + strlen (realm) + 3); + (void) my_snprintf (header, maxheader, "%s%s\"\015\012", headstr, realm); + httpd_send_err (hc, 401, err401title, header, err401form, hc->encodedurl); + /* If the request was a POST then there might still be data to be read, + ** so we need to do a lingering close. + */ + if (hc->method == METHOD_POST) + hc->should_linger = 1; +} + + +/* Base-64 decoding. This represents binary data as printable ASCII +** characters. Three 8-bit binary bytes are turned into four 6-bit +** values, like so: +** +** [11111111] [22222222] [33333333] +** +** [111111] [112222] [222233] [333333] +** +** Then the 6-bit values are represented using the characters "A-Za-z0-9+/". +*/ + +static int b64_decode_table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 00-0F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 10-1F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, /* 20-2F */ + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, /* 30-3F */ + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, /* 40-4F */ + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, /* 50-5F */ + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 60-6F */ + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, /* 70-7F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 80-8F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 90-9F */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A0-AF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* B0-BF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* C0-CF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* D0-DF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* E0-EF */ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 /* F0-FF */ +}; + +/* Do base-64 decoding on a string. Ignore any non-base64 bytes. +** Return the actual number of bytes generated. The decoded size will +** be at most 3/4 the size of the encoded, and may be smaller if there +** are padding characters (blanks, newlines). +*/ +static int b64_decode (const char *str, unsigned char *space, int size) +{ + const char *cp; + int space_idx, phase; + int d, prev_d = 0; + unsigned char c; + + space_idx = 0; + phase = 0; + for (cp = str; *cp != '\0'; ++cp) + { + d = b64_decode_table[(int) *cp]; + if (d != -1) + { + switch (phase) + { + case 0: + ++phase; + break; + case 1: + c = ((prev_d << 2) | ((d & 0x30) >> 4)); + if (space_idx < size) + space[space_idx++] = c; + ++phase; + break; + case 2: + c = (((prev_d & 0xf) << 4) | ((d & 0x3c) >> 2)); + if (space_idx < size) + space[space_idx++] = c; + ++phase; + break; + case 3: + c = (((prev_d & 0x03) << 6) | d); + if (space_idx < size) + space[space_idx++] = c; + phase = 0; + break; + } + prev_d = d; + } + } + return space_idx; +} + +/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */ +static int auth_check (httpd_conn * hc, char *dirname) +{ + if (hc->hs->global_passwd) + { + char *topdir; + if (hc->hs->vhost && hc->hostdir[0] != '\0') + topdir = hc->hostdir; + else + topdir = "."; + switch (auth_check2 (hc, topdir)) + { + case -1: + return -1; + case 1: + return 1; + } + } + return auth_check2 (hc, dirname); +} + +/* Returns -1 == unauthorized, 0 == no auth file, 1 = authorized. */ +static int auth_check2 (httpd_conn * hc, char *dirname) +{ + static char *authpath; + static size_t maxauthpath = 0; + struct stat sb; + char authinfo[500]; + char *authpass; + char *colon; + int l; + FILE *fp; + char line[500]; + char *cryp; + static char *prevauthpath; + static size_t maxprevauthpath = 0; + static time_t prevmtime; + static char *prevuser; + static size_t maxprevuser = 0; + static char *prevcryp; + static size_t maxprevcryp = 0; + + /* Construct auth filename. */ + httpd_realloc_str (&authpath, &maxauthpath, + strlen (dirname) + 1 + sizeof (AUTH_FILE)); + (void) my_snprintf (authpath, maxauthpath, "%s/%s", dirname, AUTH_FILE); + + /* Does this directory have an auth file? */ + if (stat (authpath, &sb) < 0) + /* Nope, let the request go through. */ + return 0; + + /* Does this request contain basic authorization info? */ + if (hc->authorization[0] == '\0' || + strncmp (hc->authorization, "Basic ", 6) != 0) + { + /* Nope, return a 401 Unauthorized. */ + send_authenticate (hc, dirname); + return -1; + } + + /* Decode it. */ + l = b64_decode (&(hc->authorization[6]), (unsigned char *) authinfo, + sizeof (authinfo) - 1); + authinfo[l] = '\0'; + /* Split into user and password. */ + authpass = strchr (authinfo, ':'); + if (authpass == (char *) 0) + { + /* No colon? Bogus auth info. */ + send_authenticate (hc, dirname); + return -1; + } + *authpass++ = '\0'; + /* If there are more fields, cut them off. */ + colon = strchr (authpass, ':'); + if (colon != (char *) 0) + *colon = '\0'; + + /* See if we have a cached entry and can use it. */ + if (maxprevauthpath != 0 && + strcmp (authpath, prevauthpath) == 0 && + sb.st_mtime == prevmtime && strcmp (authinfo, prevuser) == 0) + { + /* Yes. Check against the cached encrypted password. */ + if (strcmp (crypt (authpass, prevcryp), prevcryp) == 0) + { + /* Ok! */ + httpd_realloc_str (&hc->remoteuser, &hc->maxremoteuser, + strlen (authinfo)); + (void) strcpy (hc->remoteuser, authinfo); + return 1; + } + else + { + /* No. */ + send_authenticate (hc, dirname); + return -1; + } + } + + /* Open the password file. */ + fp = fopen (authpath, "r"); + if (fp == (FILE *) 0) + { + /* The file exists but we can't open it? Disallow access. */ + syslog (LOG_ERR, "%.80s auth file %.80s could not be opened - %m", + httpd_ntoa (&hc->client_addr), authpath); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' is protected by an authentication file, but the authentication file cannot be opened.\n"), + hc->encodedurl); + return -1; + } + + /* Read it. */ + while (fgets (line, sizeof (line), fp) != (char *) 0) + { + /* Nuke newline. */ + l = strlen (line); + if (line[l - 1] == '\n') + line[l - 1] = '\0'; + /* Split into user and encrypted password. */ + cryp = strchr (line, ':'); + if (cryp == (char *) 0) + continue; + *cryp++ = '\0'; + /* Is this the right user? */ + if (strcmp (line, authinfo) == 0) + { + /* Yes. */ + (void) fclose (fp); + /* So is the password right? */ + if (strcmp (crypt (authpass, cryp), cryp) == 0) + { + /* Ok! */ + httpd_realloc_str (&hc->remoteuser, &hc->maxremoteuser, + strlen (line)); + (void) strcpy (hc->remoteuser, line); + /* And cache this user's info for next time. */ + httpd_realloc_str (&prevauthpath, &maxprevauthpath, + strlen (authpath)); + (void) strcpy (prevauthpath, authpath); + prevmtime = sb.st_mtime; + httpd_realloc_str (&prevuser, &maxprevuser, strlen (authinfo)); + (void) strcpy (prevuser, authinfo); + httpd_realloc_str (&prevcryp, &maxprevcryp, strlen (cryp)); + (void) strcpy (prevcryp, cryp); + return 1; + } + else + { + /* No. */ + send_authenticate (hc, dirname); + return -1; + } + } + } + + /* Didn't find that user. Access denied. */ + (void) fclose (fp); + send_authenticate (hc, dirname); + return -1; +} + +#endif /* AUTH_FILE */ + +#if 0 +static void send_dirredirect (httpd_conn * hc) +{ + static char *location; + static char *header; + static size_t maxlocation = 0, maxheader = 0; + static char headstr[] = "Location: "; + + if (hc->query[0] != '\0') + { + char *cp = strchr (hc->encodedurl, '?'); + if (cp != (char *) 0) /* should always find it */ + *cp = '\0'; + httpd_realloc_str (&location, &maxlocation, + strlen (hc->encodedurl) + 2 + strlen (hc->query)); + (void) my_snprintf (location, maxlocation, + "%s/?%s", hc->encodedurl, hc->query); + } + else + { + httpd_realloc_str (&location, &maxlocation, strlen (hc->encodedurl) + 1); + (void) my_snprintf (location, maxlocation, "%s/", hc->encodedurl); + } + httpd_realloc_str (&header, &maxheader, + sizeof (headstr) + strlen (location)); + (void) my_snprintf (header, maxheader, "%s%s\015\012", headstr, location); + send_response (hc, 302, err302title, header, err302form, location); +} +#endif + +char *httpd_method_str (int method) +{ + switch (method) + { + case METHOD_GET: + return "GET"; + case METHOD_HEAD: + return "HEAD"; + case METHOD_POST: + return "POST"; + default: + return "UNKNOWN"; + } +} + + +static int hexit (char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return 0; /* shouldn't happen, we're guarded by isxdigit() */ +} + + +/* Copies and decodes a string. It's ok for from and to to be the +** same string. +*/ +static void strdecode (char *to, char *from) +{ + for (; *from != '\0'; ++to, ++from) + { + if (from[0] == '%' && isxdigit (from[1]) && isxdigit (from[2])) + { + *to = hexit (from[1]) * 16 + hexit (from[2]); + from += 2; + } + else + *to = *from; + } + *to = '\0'; +} + + +#ifdef GENERATE_INDEXES +/* Copies and encodes a string. */ +static void strencode (char *to, int tosize, char *from) +{ + int tolen; + + for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) + { + if (isalnum (*from) || strchr ("/_.-~", *from) != (char *) 0) + { + *to = *from; + ++to; + ++tolen; + } + else + { + (void) sprintf (to, "%%%02x", (int) *from & 0xff); + to += 3; + tolen += 3; + } + } + *to = '\0'; +} +#endif /* GENERATE_INDEXES */ + + +#ifdef TILDE_MAP_1 +/* Map a ~username/whatever URL into /username. */ +static int tilde_map_1 (httpd_conn * hc) +{ + static char *temp; + static size_t maxtemp = 0; + int len; + static char *prefix = TILDE_MAP_1; + + len = strlen (hc->expnfilename) - 1; + httpd_realloc_str (&temp, &maxtemp, len); + (void) strcpy (temp, &hc->expnfilename[1]); + httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, + strlen (prefix) + 1 + len); + (void) strcpy (hc->expnfilename, prefix); + if (prefix[0] != '\0') + (void) strcat (hc->expnfilename, "/"); + (void) strcat (hc->expnfilename, temp); + return 1; +} +#endif /* TILDE_MAP_1 */ + +#ifdef TILDE_MAP_2 +/* Map a ~username/whatever URL into /. */ +static int tilde_map_2 (httpd_conn * hc) +{ + static char *temp; + static size_t maxtemp = 0; + static char *postfix = TILDE_MAP_2; + char *cp; + struct passwd *pw; + char *alt; + char *rest; + + /* Get the username. */ + httpd_realloc_str (&temp, &maxtemp, strlen (hc->expnfilename) - 1); + (void) strcpy (temp, &hc->expnfilename[1]); + cp = strchr (temp, '/'); + if (cp != (char *) 0) + *cp++ = '\0'; + else + cp = ""; + + /* Get the passwd entry. */ + pw = getpwnam (temp); + if (pw == (struct passwd *) 0) + return 0; + + /* Set up altdir. */ + httpd_realloc_str (&hc->altdir, &hc->maxaltdir, + strlen (pw->pw_dir) + 1 + strlen (postfix)); + (void) strcpy (hc->altdir, pw->pw_dir); + if (postfix[0] != '\0') + { + (void) strcat (hc->altdir, "/"); + (void) strcat (hc->altdir, postfix); + } + alt = expand_symlinks (hc->altdir, &rest, 0, 1); + if (rest[0] != '\0') + return 0; + httpd_realloc_str (&hc->altdir, &hc->maxaltdir, strlen (alt)); + (void) strcpy (hc->altdir, alt); + + /* And the filename becomes altdir plus the post-~ part of the original. */ + httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, + strlen (hc->altdir) + 1 + strlen (cp)); + (void) my_snprintf (hc->expnfilename, hc->maxexpnfilename, + "%s/%s", hc->altdir, cp); + + /* For this type of tilde mapping, we want to defeat vhost mapping. */ + hc->tildemapped = 1; + + return 1; +} +#endif /* TILDE_MAP_2 */ + + +/* Virtual host mapping. */ +static int vhost_map (httpd_conn * hc) +{ + httpd_sockaddr sa; + socklen_t sz; + static char *tempfilename; + static size_t maxtempfilename = 0; + char *cp1; + int len; +#ifdef VHOST_DIRLEVELS + int i; + char *cp2; +#endif /* VHOST_DIRLEVELS */ + + /* Figure out the virtual hostname. */ + if (hc->reqhost[0] != '\0') + hc->hostname = hc->reqhost; + else if (hc->hdrhost[0] != '\0') + hc->hostname = hc->hdrhost; + else + { + sz = sizeof (sa); + if (getsockname (hc->conn_fd, &sa.sa, &sz) < 0) + { + syslog (LOG_ERR, "getsockname - %m"); + return 0; + } + hc->hostname = httpd_ntoa (&sa); + } + /* Pound it to lower case. */ + for (cp1 = hc->hostname; *cp1 != '\0'; ++cp1) + if (isupper (*cp1)) + *cp1 = tolower (*cp1); + + if (hc->tildemapped) + return 1; + + /* Figure out the host directory. */ +#ifdef VHOST_DIRLEVELS + httpd_realloc_str (&hc->hostdir, &hc->maxhostdir, + strlen (hc->hostname) + 2 * VHOST_DIRLEVELS); + if (strncmp (hc->hostname, "www.", 4) == 0) + cp1 = &hc->hostname[4]; + else + cp1 = hc->hostname; + for (cp2 = hc->hostdir, i = 0; i < VHOST_DIRLEVELS; ++i) + { + /* Skip dots in the hostname. If we don't, then we get vhost + ** directories in higher level of filestructure if dot gets + ** involved into path construction. It's `while' used here instead + ** of `if' for it's possible to have a hostname formed with two + ** dots at the end of it. + */ + while (*cp1 == '.') + ++cp1; + /* Copy a character from the hostname, or '_' if we ran out. */ + if (*cp1 != '\0') + *cp2++ = *cp1++; + else + *cp2++ = '_'; + /* Copy a slash. */ + *cp2++ = '/'; + } + (void) strcpy (cp2, hc->hostname); +#else /* VHOST_DIRLEVELS */ + httpd_realloc_str (&hc->hostdir, &hc->maxhostdir, strlen (hc->hostname)); + (void) strcpy (hc->hostdir, hc->hostname); +#endif /* VHOST_DIRLEVELS */ + + /* Prepend hostdir to the filename. */ + len = strlen (hc->expnfilename); + httpd_realloc_str (&tempfilename, &maxtempfilename, len); + (void) strcpy (tempfilename, hc->expnfilename); + httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, + strlen (hc->hostdir) + 1 + len); + (void) strcpy (hc->expnfilename, hc->hostdir); + (void) strcat (hc->expnfilename, "/"); + (void) strcat (hc->expnfilename, tempfilename); + return 1; +} + + +#if 0 +/* Expands all symlinks in the given filename, eliding ..'s and leading /'s. +** Returns the expanded path (pointer to static string), or (char*) 0 on +** errors. Also returns, in the string pointed to by restP, any trailing +** parts of the path that don't exist. +** +** This is a fairly nice little routine. It handles any size filenames +** without excessive mallocs. +*/ +static char *expand_symlinks (char *path, char **restP, int no_symlink_check, + int tildemapped) +{ + static char *checked; + static char *rest; + char link[5000]; + static size_t maxchecked = 0, maxrest = 0; + size_t checkedlen, restlen, linklen, prevcheckedlen, prevrestlen; + int nlinks, i; + char *r; + char *cp1; + char *cp2; + + if (no_symlink_check) + { + /* If we are chrooted, we can actually skip the symlink-expansion, + ** since it's impossible to get out of the tree. However, we still + ** need to do the pathinfo check, and the existing symlink expansion + ** code is a pretty reasonable way to do this. So, what we do is + ** a single stat() of the whole filename - if it exists, then we + ** return it as is with nothing in restP. If it doesn't exist, we + ** fall through to the existing code. + ** + ** One side-effect of this is that users can't symlink to central + ** approved CGIs any more. The workaround is to use the central + ** URL for the CGI instead of a local symlinked one. + */ + struct stat sb; + if (stat (path, &sb) != -1) + { + checkedlen = strlen (path); + httpd_realloc_str (&checked, &maxchecked, checkedlen); + (void) strcpy (checked, path); + /* Trim trailing slashes. */ + while (checked[checkedlen - 1] == '/') + { + checked[checkedlen - 1] = '\0'; + --checkedlen; + } + httpd_realloc_str (&rest, &maxrest, 0); + rest[0] = '\0'; + *restP = rest; + return checked; + } + } + + /* Start out with nothing in checked and the whole filename in rest. */ + httpd_realloc_str (&checked, &maxchecked, 1); + checked[0] = '\0'; + checkedlen = 0; + restlen = strlen (path); + httpd_realloc_str (&rest, &maxrest, restlen); + (void) strcpy (rest, path); + if (rest[restlen - 1] == '/') + rest[--restlen] = '\0'; /* trim trailing slash */ + if (!tildemapped) + /* Remove any leading slashes. */ + while (rest[0] == '/') + { + (void) strcpy (rest, &(rest[1])); + --restlen; + } + r = rest; + nlinks = 0; + + /* While there are still components to check... */ + while (restlen > 0) + { + /* Save current checkedlen in case we get a symlink. Save current + ** restlen in case we get a non-existant component. + */ + prevcheckedlen = checkedlen; + prevrestlen = restlen; + + /* Grab one component from r and transfer it to checked. */ + cp1 = strchr (r, '/'); + if (cp1 != (char *) 0) + { + i = cp1 - r; + if (i == 0) + { + /* Special case for absolute paths. */ + httpd_realloc_str (&checked, &maxchecked, checkedlen + 1); + (void) strncpy (&checked[checkedlen], r, 1); + checkedlen += 1; + } + else if (strncmp (r, "..", MAX (i, 2)) == 0) + { + /* Ignore ..'s that go above the start of the path. */ + if (checkedlen != 0) + { + cp2 = strrchr (checked, '/'); + if (cp2 == (char *) 0) + checkedlen = 0; + else if (cp2 == checked) + checkedlen = 1; + else + checkedlen = cp2 - checked; + } + } + else + { + httpd_realloc_str (&checked, &maxchecked, checkedlen + 1 + i); + if (checkedlen > 0 && checked[checkedlen - 1] != '/') + checked[checkedlen++] = '/'; + (void) strncpy (&checked[checkedlen], r, i); + checkedlen += i; + } + checked[checkedlen] = '\0'; + r += i + 1; + restlen -= i + 1; + } + else + { + /* No slashes remaining, r is all one component. */ + if (strcmp (r, "..") == 0) + { + /* Ignore ..'s that go above the start of the path. */ + if (checkedlen != 0) + { + cp2 = strrchr (checked, '/'); + if (cp2 == (char *) 0) + checkedlen = 0; + else if (cp2 == checked) + checkedlen = 1; + else + checkedlen = cp2 - checked; + checked[checkedlen] = '\0'; + } + } + else + { + httpd_realloc_str (&checked, &maxchecked, checkedlen + 1 + restlen); + if (checkedlen > 0 && checked[checkedlen - 1] != '/') + checked[checkedlen++] = '/'; + (void) strcpy (&checked[checkedlen], r); + checkedlen += restlen; + } + r += restlen; + restlen = 0; + } + + /* Try reading the current filename as a symlink */ + if (checked[0] == '\0') + continue; + linklen = readlink (checked, link, sizeof (link) - 1); + if (linklen == -1) + { + if (errno == EINVAL) + continue; /* not a symlink */ + if (errno == EACCES || errno == ENOENT || errno == ENOTDIR) + { + /* That last component was bogus. Restore and return. */ + *restP = r - (prevrestlen - restlen); + if (prevcheckedlen == 0) + (void) strcpy (checked, "."); + else + checked[prevcheckedlen] = '\0'; + return checked; + } + syslog (LOG_ERR, "readlink %.80s - %m", checked); + return (char *) 0; + } + ++nlinks; + if (nlinks > MAX_LINKS) + { + syslog (LOG_ERR, "too many symlinks in %.80s", path); + return (char *) 0; + } + link[linklen] = '\0'; + if (link[linklen - 1] == '/') + link[--linklen] = '\0'; /* trim trailing slash */ + + /* Insert the link contents in front of the rest of the filename. */ + if (restlen != 0) + { + (void) strcpy (rest, r); + httpd_realloc_str (&rest, &maxrest, restlen + linklen + 1); + for (i = restlen; i >= 0; --i) + rest[i + linklen + 1] = rest[i]; + (void) strcpy (rest, link); + rest[linklen] = '/'; + restlen += linklen + 1; + r = rest; + } + else + { + /* There's nothing left in the filename, so the link contents + ** becomes the rest. + */ + httpd_realloc_str (&rest, &maxrest, linklen); + (void) strcpy (rest, link); + restlen = linklen; + r = rest; + } + + if (rest[0] == '/') + { + /* There must have been an absolute symlink - zero out checked. */ + checked[0] = '\0'; + checkedlen = 0; + } + else + { + /* Re-check this component. */ + checkedlen = prevcheckedlen; + checked[checkedlen] = '\0'; + } + } + + /* Ok. */ + *restP = r; + if (checked[0] == '\0') + (void) strcpy (checked, "."); + return checked; +} +#endif + +int httpd_get_conn (httpd_server * hs, int listen_fd, httpd_conn * hc) +{ + httpd_sockaddr sa; + socklen_t sz; + + if (!hc->initialized) + { + hc->read_size = 0; + httpd_realloc_str (&hc->read_buf, &hc->read_size, 500); + hc->maxdecodedurl = + hc->maxorigfilename = hc->maxexpnfilename = hc->maxencodings = + hc->maxpathinfo = hc->maxquery = hc->maxaccept = + hc->maxaccepte = hc->maxreqhost = hc->maxhostdir = + hc->maxremoteuser = hc->maxresponse = 0; +#ifdef TILDE_MAP_2 + hc->maxaltdir = 0; +#endif /* TILDE_MAP_2 */ + httpd_realloc_str (&hc->decodedurl, &hc->maxdecodedurl, 1); + httpd_realloc_str (&hc->origfilename, &hc->maxorigfilename, 1); + httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, 0); + httpd_realloc_str (&hc->encodings, &hc->maxencodings, 0); + httpd_realloc_str (&hc->pathinfo, &hc->maxpathinfo, 0); + httpd_realloc_str (&hc->query, &hc->maxquery, 0); + httpd_realloc_str (&hc->accept, &hc->maxaccept, 0); + httpd_realloc_str (&hc->accepte, &hc->maxaccepte, 0); + httpd_realloc_str (&hc->reqhost, &hc->maxreqhost, 0); + httpd_realloc_str (&hc->hostdir, &hc->maxhostdir, 0); + httpd_realloc_str (&hc->remoteuser, &hc->maxremoteuser, 0); + httpd_realloc_str (&hc->response, &hc->maxresponse, 0); +#ifdef TILDE_MAP_2 + httpd_realloc_str (&hc->altdir, &hc->maxaltdir, 0); +#endif /* TILDE_MAP_2 */ + hc->initialized = 1; + } + + /* Accept the new connection. */ + sz = sizeof (sa); + hc->conn_fd = accept (listen_fd, &sa.sa, &sz); + if (hc->conn_fd < 0) + { + if (errno == EWOULDBLOCK) + return GC_NO_MORE; + syslog (LOG_ERR, "accept - %m"); + return GC_FAIL; + } + if (!sockaddr_check (&sa)) + { + syslog (LOG_ERR, "unknown sockaddr family"); + close (hc->conn_fd); + hc->conn_fd = -1; + return GC_FAIL; + } + (void) fcntl (hc->conn_fd, F_SETFD, 1); + hc->hs = hs; + (void) memset (&hc->client_addr, 0, sizeof (hc->client_addr)); + (void) memmove (&hc->client_addr, &sa, sockaddr_len (&sa)); + hc->read_idx = 0; + hc->checked_idx = 0; + hc->checked_state = CHST_FIRSTWORD; + hc->method = METHOD_UNKNOWN; + hc->status = 0; + hc->bytes_to_send = 0; + hc->bytes_sent = 0; + hc->encodedurl = ""; + hc->decodedurl[0] = '\0'; + hc->protocol = "UNKNOWN"; + hc->origfilename[0] = '\0'; + hc->expnfilename[0] = '\0'; + hc->encodings[0] = '\0'; + hc->pathinfo[0] = '\0'; + hc->query[0] = '\0'; + hc->referer = ""; + hc->useragent = ""; + hc->accept[0] = '\0'; + hc->accepte[0] = '\0'; + hc->acceptl = ""; + hc->cookie = ""; + hc->contenttype = ""; +#ifdef X_CGI_HEADER + hc->xcgi = ""; +#endif + hc->reqhost[0] = '\0'; + hc->hdrhost = ""; + hc->hostdir[0] = '\0'; + hc->authorization = ""; + hc->remoteuser[0] = '\0'; + hc->response[0] = '\0'; +#ifdef TILDE_MAP_2 + hc->altdir[0] = '\0'; +#endif /* TILDE_MAP_2 */ + hc->responselen = 0; + hc->if_modified_since = (time_t) - 1; + hc->range_if = (time_t) - 1; + hc->contentlength = -1; + hc->type = ""; + hc->hostname = (char *) 0; + hc->mime_flag = 1; + hc->one_one = 0; + hc->got_range = 0; + hc->tildemapped = 0; + hc->first_byte_index = 0; + hc->last_byte_index = -1; + hc->keep_alive = 0; + hc->should_linger = 0; + hc->file_address = (char *) 0; + return GC_OK; +} + + +/* Checks hc->read_buf to see whether a complete request has been read so far; +** either the first line has two words (an HTTP/0.9 request), or the first +** line has three words and there's a blank line present. +** +** hc->read_idx is how much has been read in; hc->checked_idx is how much we +** have checked so far; and hc->checked_state is the current state of the +** finite state machine. +*/ +int httpd_got_request (httpd_conn * hc) +{ + char c; + + for (; hc->checked_idx < hc->read_idx; ++hc->checked_idx) + { + c = hc->read_buf[hc->checked_idx]; + switch (hc->checked_state) + { + case CHST_FIRSTWORD: + switch (c) + { + case ' ': + case '\t': + hc->checked_state = CHST_FIRSTWS; + break; + case '\012': + case '\015': + hc->checked_state = CHST_BOGUS; + return GR_BAD_REQUEST; + } + break; + case CHST_FIRSTWS: + switch (c) + { + case ' ': + case '\t': + break; + case '\012': + case '\015': + hc->checked_state = CHST_BOGUS; + return GR_BAD_REQUEST; + default: + hc->checked_state = CHST_SECONDWORD; + break; + } + break; + case CHST_SECONDWORD: + switch (c) + { + case ' ': + case '\t': + hc->checked_state = CHST_SECONDWS; + break; + case '\012': + case '\015': + /* The first line has only two words - an HTTP/0.9 request. */ + return GR_GOT_REQUEST; + } + break; + case CHST_SECONDWS: + switch (c) + { + case ' ': + case '\t': + break; + case '\012': + case '\015': + hc->checked_state = CHST_BOGUS; + return GR_BAD_REQUEST; + default: + hc->checked_state = CHST_THIRDWORD; + break; + } + break; + case CHST_THIRDWORD: + switch (c) + { + case ' ': + case '\t': + hc->checked_state = CHST_THIRDWS; + break; + case '\012': + hc->checked_state = CHST_LF; + break; + case '\015': + hc->checked_state = CHST_CR; + break; + } + break; + case CHST_THIRDWS: + switch (c) + { + case ' ': + case '\t': + break; + case '\012': + hc->checked_state = CHST_LF; + break; + case '\015': + hc->checked_state = CHST_CR; + break; + default: + hc->checked_state = CHST_BOGUS; + return GR_BAD_REQUEST; + } + break; + case CHST_LINE: + switch (c) + { + case '\012': + hc->checked_state = CHST_LF; + break; + case '\015': + hc->checked_state = CHST_CR; + break; + } + break; + case CHST_LF: + switch (c) + { + case '\012': + /* Two newlines in a row - a blank line - end of request. */ + return GR_GOT_REQUEST; + case '\015': + hc->checked_state = CHST_CR; + break; + default: + hc->checked_state = CHST_LINE; + break; + } + break; + case CHST_CR: + switch (c) + { + case '\012': + hc->checked_state = CHST_CRLF; + break; + case '\015': + /* Two returns in a row - end of request. */ + return GR_GOT_REQUEST; + default: + hc->checked_state = CHST_LINE; + break; + } + break; + case CHST_CRLF: + switch (c) + { + case '\012': + /* Two newlines in a row - end of request. */ + return GR_GOT_REQUEST; + case '\015': + hc->checked_state = CHST_CRLFCR; + break; + default: + hc->checked_state = CHST_LINE; + break; + } + break; + case CHST_CRLFCR: + switch (c) + { + case '\012': + case '\015': + /* Two CRLFs or two CRs in a row - end of request. */ + return GR_GOT_REQUEST; + default: + hc->checked_state = CHST_LINE; + break; + } + break; + case CHST_BOGUS: + return GR_BAD_REQUEST; + } + } + return GR_NO_REQUEST; +} + + +int httpd_parse_request (httpd_conn * hc) +{ + char *buf; + char *method_str; + char *url; + char *protocol; + char *reqhost; + char *eol; + char *cp; + char *pi; + + hc->checked_idx = 0; /* reset */ + method_str = bufgets (hc); + url = strpbrk (method_str, " \t\012\015"); + if (url == (char *) 0) + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + return -1; + } + *url++ = '\0'; + url += strspn (url, " \t\012\015"); + protocol = strpbrk (url, " \t\012\015"); + if (protocol == (char *) 0) + { + protocol = "HTTP/0.9"; + hc->mime_flag = 0; + } + else + { + *protocol++ = '\0'; + protocol += strspn (protocol, " \t\012\015"); + if (*protocol != '\0') + { + eol = strpbrk (protocol, " \t\012\015"); + if (eol != (char *) 0) + *eol = '\0'; + if (strcasecmp (protocol, "HTTP/1.0") != 0) + hc->one_one = 1; + } + } + hc->protocol = protocol; + + /* Check for HTTP/1.1 absolute URL. */ + if (strncasecmp (url, "http://", 7) == 0) + { + if (!hc->one_one) + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + return -1; + } + reqhost = url + 7; + url = strchr (reqhost, '/'); + if (url == (char *) 0) + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + return -1; + } + *url = '\0'; + if (strchr (reqhost, '/') != (char *) 0 || reqhost[0] == '.') + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + return -1; + } + httpd_realloc_str (&hc->reqhost, &hc->maxreqhost, strlen (reqhost)); + (void) strcpy (hc->reqhost, reqhost); + *url = '/'; + } + + if (*url != '/') + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + return -1; + } + + if (strcasecmp (method_str, httpd_method_str (METHOD_GET)) == 0) + hc->method = METHOD_GET; + else if (strcasecmp (method_str, httpd_method_str (METHOD_HEAD)) == 0) + hc->method = METHOD_HEAD; + else if (strcasecmp (method_str, httpd_method_str (METHOD_POST)) == 0) + hc->method = METHOD_POST; + else + { + httpd_send_err (hc, 501, err501title, "", err501form, method_str); + return -1; + } + + hc->encodedurl = url; + httpd_realloc_str (&hc->decodedurl, &hc->maxdecodedurl, + strlen (hc->encodedurl)); + strdecode (hc->decodedurl, hc->encodedurl); + + httpd_realloc_str (&hc->origfilename, &hc->maxorigfilename, + strlen (hc->decodedurl)); + + pi = &hc->decodedurl[1]; + while (*pi == '/') + pi++; + + (void) strcpy (hc->origfilename, pi); + + /* Special case for top-level URL. */ + //if ( hc->origfilename[0] == '\0' ) + // (void) strcpy( hc->origfilename, "." ); + + /* Extract query string from encoded URL. */ + cp = strchr (hc->encodedurl, '?'); + if (cp != (char *) 0) + { + ++cp; + httpd_realloc_str (&hc->query, &hc->maxquery, strlen (cp)); + (void) strcpy (hc->query, cp); + /* Remove query from (decoded) origfilename. */ + cp = strchr (hc->origfilename, '?'); + if (cp != (char *) 0) + *cp = '\0'; + } + + de_dotdot (hc->origfilename); + + if (hc->origfilename[0] == '/' || + (hc->origfilename[0] == '.' && hc->origfilename[1] == '.' && + (hc->origfilename[2] == '\0' || hc->origfilename[2] == '/'))) + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + return -1; + } + + if (hc->mime_flag) + { + /* Read the MIME headers. */ + while ((buf = bufgets (hc)) != (char *) 0) + { + if (buf[0] == '\0') + break; + if (strncasecmp (buf, "Referer:", 8) == 0) + { + cp = &buf[8]; + cp += strspn (cp, " \t"); + hc->referer = cp; + } + else if (strncasecmp (buf, "User-Agent:", 11) == 0) + { + cp = &buf[11]; + cp += strspn (cp, " \t"); + hc->useragent = cp; + } + else if (strncasecmp (buf, "Host:", 5) == 0) + { + cp = &buf[5]; + cp += strspn (cp, " \t"); + hc->hdrhost = cp; + cp = strchr (hc->hdrhost, ':'); + if (cp != (char *) 0) + *cp = '\0'; + if (strchr (hc->hdrhost, '/') != (char *) 0 || hc->hdrhost[0] == '.') + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, + ""); + return -1; + } + } + else if (strncasecmp (buf, "Accept:", 7) == 0) + { + cp = &buf[7]; + cp += strspn (cp, " \t"); + if (hc->accept[0] != '\0') + { + if (strlen (hc->accept) > 5000) + { + syslog (LOG_ERR, "%.80s way too much Accept: data", + httpd_ntoa (&hc->client_addr)); + continue; + } + httpd_realloc_str (&hc->accept, &hc->maxaccept, + strlen (hc->accept) + 2 + strlen (cp)); + (void) strcat (hc->accept, ", "); + } + else + httpd_realloc_str (&hc->accept, &hc->maxaccept, strlen (cp)); + + (void) strcat (hc->accept, cp); + } + else if (strncasecmp (buf, "Accept-Encoding:", 16) == 0) + { + cp = &buf[16]; + cp += strspn (cp, " \t"); + if (hc->accepte[0] != '\0') + { + if (strlen (hc->accepte) > 5000) + { + syslog (LOG_ERR, "%.80s way too much Accept-Encoding: data", + httpd_ntoa (&hc->client_addr)); + continue; + } + httpd_realloc_str (&hc->accepte, &hc->maxaccepte, + strlen (hc->accepte) + 2 + strlen (cp)); + (void) strcat (hc->accepte, ", "); + } + else + httpd_realloc_str (&hc->accepte, &hc->maxaccepte, strlen (cp)); + + (void) strcpy (hc->accepte, cp); + } + else if (strncasecmp (buf, "Accept-Language:", 16) == 0) + { + cp = &buf[16]; + cp += strspn (cp, " \t"); + hc->acceptl = cp; + } + else if (strncasecmp (buf, "If-Modified-Since:", 18) == 0) + { + cp = &buf[18]; + hc->if_modified_since = tdate_parse (cp); + if (hc->if_modified_since == (time_t) - 1) + syslog (LOG_DEBUG, "unparsable time: %.80s", cp); + } + else if (strncasecmp (buf, "Cookie:", 7) == 0) + { + cp = &buf[7]; + cp += strspn (cp, " \t"); + hc->cookie = cp; + } + else if (strncasecmp (buf, "Range:", 6) == 0) + { + /* Only support %d- and %d-%d, not %d-%d,%d-%d or -%d. */ + if (strchr (buf, ',') == (char *) 0) + { + char *cp_dash; + cp = strpbrk (buf, "="); + if (cp != (char *) 0) + { + cp_dash = strchr (cp + 1, '-'); + if (cp_dash != (char *) 0 && cp_dash != cp + 1) + { + *cp_dash = '\0'; + hc->got_range = 1; + hc->first_byte_index = atoll (cp + 1); + if (hc->first_byte_index < 0) + hc->first_byte_index = 0; + if (isdigit ((int) cp_dash[1])) + { + hc->last_byte_index = atoll (cp_dash + 1); + if (hc->last_byte_index < 0) + hc->last_byte_index = -1; + } + } + } + } + } + else if (strncasecmp (buf, "Range-If:", 9) == 0 || + strncasecmp (buf, "If-Range:", 9) == 0) + { + cp = &buf[9]; + hc->range_if = tdate_parse (cp); + if (hc->range_if == (time_t) - 1) + syslog (LOG_DEBUG, "unparsable time: %.80s", cp); + } + else if (strncasecmp (buf, "Content-Type:", 13) == 0) + { + cp = &buf[13]; + cp += strspn (cp, " \t"); + hc->contenttype = cp; + } + else if (strncasecmp (buf, "Content-Length:", 15) == 0) + { + cp = &buf[15]; + hc->contentlength = atol (cp); + } + else if (strncasecmp (buf, "Authorization:", 14) == 0) + { + cp = &buf[14]; + cp += strspn (cp, " \t"); + hc->authorization = cp; + } + else if (strncasecmp (buf, "Connection:", 11) == 0) + { + cp = &buf[11]; + cp += strspn (cp, " \t"); + if (strcasecmp (cp, "keep-alive") == 0) + hc->keep_alive = 1; + } +#ifdef X_CGI_HEADER + else if (strncasecmp (buf, "X-Cgi:", 6) == 0) + { + cp = &buf[6]; + cp += strspn (cp, " \t"); + hc->xcgi = cp; + } +#endif +#ifdef LOG_UNKNOWN_HEADERS + else if (strncasecmp (buf, "Accept-Charset:", 15) == 0 || + strncasecmp (buf, "Accept-Language:", 16) == 0 || + strncasecmp (buf, "Agent:", 6) == 0 || + strncasecmp (buf, "Cache-Control:", 14) == 0 || + strncasecmp (buf, "Cache-Info:", 11) == 0 || + strncasecmp (buf, "Charge-To:", 10) == 0 || + strncasecmp (buf, "Client-IP:", 10) == 0 || + strncasecmp (buf, "Date:", 5) == 0 || + strncasecmp (buf, "Extension:", 10) == 0 || + strncasecmp (buf, "Forwarded:", 10) == 0 || + strncasecmp (buf, "From:", 5) == 0 || + strncasecmp (buf, "HTTP-Version:", 13) == 0 || + strncasecmp (buf, "Max-Forwards:", 13) == 0 || + strncasecmp (buf, "Message-Id:", 11) == 0 || + strncasecmp (buf, "MIME-Version:", 13) == 0 || + strncasecmp (buf, "Negotiate:", 10) == 0 || + strncasecmp (buf, "Pragma:", 7) == 0 || + strncasecmp (buf, "Proxy-Agent:", 12) == 0 || + strncasecmp (buf, "Proxy-Connection:", 17) == 0 || + strncasecmp (buf, "Security-Scheme:", 16) == 0 || + strncasecmp (buf, "Session-Id:", 11) == 0 || + strncasecmp (buf, "UA-Color:", 9) == 0 || + strncasecmp (buf, "UA-CPU:", 7) == 0 || + strncasecmp (buf, "UA-Disp:", 8) == 0 || + strncasecmp (buf, "UA-OS:", 6) == 0 || + strncasecmp (buf, "UA-Pixels:", 10) == 0 || + strncasecmp (buf, "User:", 5) == 0 || + strncasecmp (buf, "Via:", 4) == 0 || + strncasecmp (buf, "X-", 2) == 0) + ; /* ignore */ + else + syslog (LOG_DEBUG, "unknown request header: %.80s", buf); +#endif /* LOG_UNKNOWN_HEADERS */ + } + } + + if (hc->one_one) + { + /* Check that HTTP/1.1 requests specify a host, as required. */ + if (hc->reqhost[0] == '\0' && hc->hdrhost[0] == '\0') + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + return -1; + } + + /* If the client wants to do keep-alives, it might also be doing + ** pipelining. There's no way for us to tell. Since we don't + ** implement keep-alives yet, if we close such a connection there + ** might be unread pipelined requests waiting. So, we have to + ** do a lingering close. + */ + if (hc->keep_alive) + hc->should_linger = 1; + } + + /* Ok, the request has been parsed. Now we resolve stuff that + ** may require the entire request. + */ + + /* Copy original filename to expanded filename. */ + httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, + strlen (hc->origfilename)); + (void) strcpy (hc->expnfilename, hc->origfilename); + + /* Tilde mapping. */ + if (hc->expnfilename[0] == '~') + { +#ifdef TILDE_MAP_1 + if (!tilde_map_1 (hc)) + { + httpd_send_err (hc, 404, err404title, "", err404form, hc->encodedurl); + return -1; + } +#endif /* TILDE_MAP_1 */ +#ifdef TILDE_MAP_2 + if (!tilde_map_2 (hc)) + { + httpd_send_err (hc, 404, err404title, "", err404form, hc->encodedurl); + return -1; + } +#endif /* TILDE_MAP_2 */ + } + + /* Virtual host mapping. */ + if (hc->hs->vhost) + if (!vhost_map (hc)) + { + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + return -1; + } + +#if 0 + /* Expand all symbolic links in the filename. This also gives us + ** any trailing non-existing components, for pathinfo. + */ + cp = + expand_symlinks (hc->expnfilename, &pi, hc->hs->no_symlink_check, + hc->tildemapped); + + //fprintf(stderr, "expnfilename: %s -> cp = %s pi = %s\n", hc->expnfilename, cp, pi); + + if (cp == (char *) 0) + { + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + return -1; + } + httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, strlen (cp)); + (void) strcpy (hc->expnfilename, cp); + httpd_realloc_str (&hc->pathinfo, &hc->maxpathinfo, strlen (pi)); + (void) strcpy (hc->pathinfo, pi); + + /* Remove pathinfo stuff from the original filename too. */ + if (hc->pathinfo[0] != '\0') + { + int i; + i = strlen (hc->origfilename) - strlen (hc->pathinfo); + if (i > 0 && strcmp (&hc->origfilename[i], hc->pathinfo) == 0) + hc->origfilename[i - 1] = '\0'; + } + + /* If the expanded filename is an absolute path, check that it's still + ** within the current directory or the alternate directory. + */ + if (hc->expnfilename[0] == '/') + { + if (strncmp (hc->expnfilename, hc->hs->cwd, strlen (hc->hs->cwd)) == 0) + { + /* Elide the current directory. */ + (void) strcpy (hc->expnfilename, + &hc->expnfilename[strlen (hc->hs->cwd)]); + } +#ifdef TILDE_MAP_2 + else if (hc->altdir[0] != '\0' && + (strncmp (hc->expnfilename, hc->altdir, + strlen (hc->altdir)) == 0 && + (hc->expnfilename[strlen (hc->altdir)] == '\0' || + hc->expnfilename[strlen (hc->altdir)] == '/'))) + { + } +#endif /* TILDE_MAP_2 */ + else + { + syslog (LOG_NOTICE, "%.80s URL \"%.80s\" goes outside the web tree", + httpd_ntoa (&hc->client_addr), hc->encodedurl); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' resolves to a file outside the permitted web server directory tree.\n"), + hc->encodedurl); + return -1; + } + } +#endif + + return 0; +} + + +static char *bufgets (httpd_conn * hc) +{ + int i; + char c; + + for (i = hc->checked_idx; hc->checked_idx < hc->read_idx; ++hc->checked_idx) + { + c = hc->read_buf[hc->checked_idx]; + if (c == '\012' || c == '\015') + { + hc->read_buf[hc->checked_idx] = '\0'; + ++hc->checked_idx; + if (c == '\015' && hc->checked_idx < hc->read_idx && + hc->read_buf[hc->checked_idx] == '\012') + { + hc->read_buf[hc->checked_idx] = '\0'; + ++hc->checked_idx; + } + return &(hc->read_buf[i]); + } + } + return (char *) 0; +} + + +static void de_dotdot (char *file) +{ + char *cp; + char *cp2; + int l; + + /* Collapse any multiple / sequences. */ + while ((cp = strstr (file, "//")) != (char *) 0) + { + for (cp2 = cp + 2; *cp2 == '/'; ++cp2) + continue; + (void) strcpy (cp + 1, cp2); + } + + /* Remove leading ./ and any /./ sequences. */ + while (strncmp (file, "./", 2) == 0) + (void) strcpy (file, file + 2); + while ((cp = strstr (file, "/./")) != (char *) 0) + (void) strcpy (cp, cp + 2); + + /* Alternate between removing leading ../ and removing xxx/../ */ + for (;;) + { + while (strncmp (file, "../", 3) == 0) + (void) strcpy (file, file + 3); + cp = strstr (file, "/../"); + if (cp == (char *) 0) + break; + for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) + continue; + (void) strcpy (cp2 + 1, cp + 4); + } + + /* Also elide any xxx/.. at the end. */ + while ((l = strlen (file)) > 3 && strcmp ((cp = file + l - 3), "/..") == 0) + { + for (cp2 = cp - 1; cp2 >= file && *cp2 != '/'; --cp2) + continue; + if (cp2 < file) + break; + *cp2 = '\0'; + } +} + + +void httpd_close_conn (httpd_conn * hc, struct timeval *nowP) +{ + make_log_entry (hc, nowP); + + if (hc->file_address != (char *) 0) + { + mmc_unmap (hc->file_address, &(hc->sb), nowP); + hc->file_address = (char *) 0; + } + if (hc->conn_fd >= 0) + { + (void) close (hc->conn_fd); + hc->conn_fd = -1; + } +} + +void httpd_destroy_conn (httpd_conn * hc) +{ + if (hc->initialized) + { + free ((void *) hc->read_buf); + free ((void *) hc->decodedurl); + free ((void *) hc->origfilename); + free ((void *) hc->expnfilename); + free ((void *) hc->encodings); + free ((void *) hc->pathinfo); + free ((void *) hc->query); + free ((void *) hc->accept); + free ((void *) hc->accepte); + free ((void *) hc->reqhost); + free ((void *) hc->hostdir); + free ((void *) hc->remoteuser); + free ((void *) hc->response); +#ifdef TILDE_MAP_2 + free ((void *) hc->altdir); +#endif /* TILDE_MAP_2 */ + hc->initialized = 0; + } +} + + +struct mime_entry +{ + char *ext; + size_t ext_len; + char *val; + size_t val_len; +}; +static struct mime_entry enc_tab[] = { +#include "mime_encodings.h" +}; + +static const int n_enc_tab = sizeof (enc_tab) / sizeof (*enc_tab); +static struct mime_entry typ_tab[] = { +#include "mime_types.h" +}; + +static const int n_typ_tab = sizeof (typ_tab) / sizeof (*typ_tab); + + +/* qsort comparison routine - declared old-style on purpose, for portability. */ +static int ext_compare (a, b) + struct mime_entry *a; + struct mime_entry *b; +{ + return strcmp (a->ext, b->ext); +} + + +static void init_mime (void) +{ + int i; + + /* Sort the tables so we can do binary search. */ + qsort (enc_tab, n_enc_tab, sizeof (*enc_tab), ext_compare); + qsort (typ_tab, n_typ_tab, sizeof (*typ_tab), ext_compare); + + /* Fill in the lengths. */ + for (i = 0; i < n_enc_tab; ++i) + { + enc_tab[i].ext_len = strlen (enc_tab[i].ext); + enc_tab[i].val_len = strlen (enc_tab[i].val); + } + for (i = 0; i < n_typ_tab; ++i) + { + typ_tab[i].ext_len = strlen (typ_tab[i].ext); + typ_tab[i].val_len = strlen (typ_tab[i].val); + } + +} + +#if 0 +/* Figure out MIME encodings and type based on the filename. Multiple +** encodings are separated by commas, and are listed in the order in +** which they were applied to the file. +*/ +static void figure_mime (httpd_conn * hc) +{ + char *prev_dot; + char *dot; + char *ext; + int me_indexes[100], n_me_indexes; + size_t ext_len, encodings_len; + int i, top, bot, mid; + int r; + char *default_type = "text/plain; charset=%s"; + + /* Peel off encoding extensions until there aren't any more. */ + n_me_indexes = 0; + for (prev_dot = &hc->expnfilename[strlen (hc->expnfilename)];; + prev_dot = dot) + { + for (dot = prev_dot - 1; dot >= hc->expnfilename && *dot != '.'; --dot) + ; + if (dot < hc->expnfilename) + { + /* No dot found. No more encoding extensions, and no type + ** extension either. + */ + hc->type = default_type; + goto done; + } + ext = dot + 1; + ext_len = prev_dot - ext; + /* Search the encodings table. Linear search is fine here, there + ** are only a few entries. + */ + for (i = 0; i < n_enc_tab; ++i) + { + if (ext_len == enc_tab[i].ext_len + && strncasecmp (ext, enc_tab[i].ext, ext_len) == 0) + { + if (n_me_indexes < sizeof (me_indexes) / sizeof (*me_indexes)) + { + me_indexes[n_me_indexes] = i; + ++n_me_indexes; + } + goto next; + } + } + /* No encoding extension found. Break and look for a type extension. */ + break; + + next:; + } + + /* Binary search for a matching type extension. */ + top = n_typ_tab - 1; + bot = 0; + while (top >= bot) + { + mid = (top + bot) / 2; + r = strncasecmp (ext, typ_tab[mid].ext, ext_len); + if (r < 0) + top = mid - 1; + else if (r > 0) + bot = mid + 1; + else if (ext_len < typ_tab[mid].ext_len) + top = mid - 1; + else if (ext_len > typ_tab[mid].ext_len) + bot = mid + 1; + else + { + hc->type = typ_tab[mid].val; + goto done; + } + } + hc->type = default_type; + +done: + + /* The last thing we do is actually generate the mime-encoding header. */ + hc->encodings[0] = '\0'; + encodings_len = 0; + for (i = n_me_indexes - 1; i >= 0; --i) + { + httpd_realloc_str (&hc->encodings, &hc->maxencodings, + encodings_len + enc_tab[me_indexes[i]].val_len + 1); + if (hc->encodings[0] != '\0') + { + (void) strcpy (&hc->encodings[encodings_len], ","); + ++encodings_len; + } + (void) strcpy (&hc->encodings[encodings_len], enc_tab[me_indexes[i]].val); + encodings_len += enc_tab[me_indexes[i]].val_len; + } + +} +#endif + +#ifdef CGI_TIMELIMIT +static void cgi_kill2 (ClientData client_data, struct timeval *nowP) +{ + pid_t pid; + + pid = (pid_t) client_data.i; + if (kill (pid, SIGKILL) == 0) + syslog (LOG_ERR, "hard-killed CGI process %d", pid); +} + +static void cgi_kill (ClientData client_data, struct timeval *nowP) +{ + pid_t pid; + + pid = (pid_t) client_data.i; + if (kill (pid, SIGINT) == 0) + { + syslog (LOG_ERR, "killed CGI process %d", pid); + /* In case this isn't enough, schedule an uncatchable kill. */ + if (tmr_create (nowP, cgi_kill2, client_data, 5 * 1000L, 0) == + (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(cgi_kill2) failed"); + exit (1); + } + } +} +#endif /* CGI_TIMELIMIT */ + + +#ifdef GENERATE_INDEXES + +/* qsort comparison routine - declared old-style on purpose, for portability. */ +static int name_compare (a, b) + char **a; + char **b; +{ + return strcmp (*a, *b); +} + +#if 0 +static int ls (httpd_conn * hc) +{ + DIR *dirp; + struct dirent *de; + int namlen; + static int maxnames = 0; + int nnames; + static char *names; + static char **nameptrs; + static char *name; + static size_t maxname = 0; + static char *rname; + static size_t maxrname = 0; + static char *encrname; + static size_t maxencrname = 0; + FILE *fp; + int i, r; + struct stat sb; + struct stat lsb; + char modestr[20]; + char *linkprefix; + char link[MAXPATHLEN + 1]; + int linklen; + char *fileclass; + time_t now; + char *timestr; + ClientData client_data; + + dirp = opendir (hc->expnfilename); + if (dirp == (DIR *) 0) + { + syslog (LOG_ERR, "opendir %.80s - %m", hc->expnfilename); + httpd_send_err (hc, 404, err404title, "", err404form, hc->encodedurl); + return -1; + } + + if (hc->method == METHOD_HEAD) + { + closedir (dirp); + send_mime (hc, 200, ok200title, "", "", "text/html; charset=%s", + (off_t) - 1, hc->sb.st_mtime); + } + else if (hc->method == METHOD_GET) + { + if (hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit) + { + closedir (dirp); + httpd_send_err (hc, 503, httpd_err503title, "", httpd_err503form, + hc->encodedurl); + return -1; + } + ++hc->hs->cgi_count; + r = fork (); + if (r < 0) + { + syslog (LOG_ERR, "fork - %m"); + closedir (dirp); + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + return -1; + } + if (r == 0) + { + /* Child process. */ + sub_process = 1; + httpd_unlisten (hc->hs); + send_mime (hc, 200, ok200title, "", "", "text/html; charset=%s", + (off_t) - 1, hc->sb.st_mtime); + httpd_write_response (hc); + +#ifdef CGI_NICE + /* Set priority. */ + (void) nice (CGI_NICE); +#endif /* CGI_NICE */ + + /* Open a stdio stream so that we can use fprintf, which is more + ** efficient than a bunch of separate write()s. We don't have + ** to worry about double closes or file descriptor leaks cause + ** we're in a subprocess. + */ + fp = fdopen (hc->conn_fd, "w"); + if (fp == (FILE *) 0) + { + syslog (LOG_ERR, "fdopen - %m"); + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + httpd_write_response (hc); + closedir (dirp); + exit (1); + } + + (void) fprintf (fp, "\ +\n\ +Index of %.80s\n\ +\n\ +

Index of %.80s

\n\ +
\n\
+mode  links  bytes  last-changed  name\n\
+
", hc->encodedurl, hc->encodedurl); + + /* Read in names. */ + nnames = 0; + while ((de = readdir (dirp)) != 0) /* dirent or direct */ + { + if (nnames >= maxnames) + { + if (maxnames == 0) + { + maxnames = 100; + names = NEW (char, maxnames * (MAXPATHLEN + 1)); + nameptrs = NEW (char *, maxnames); + } + else + { + maxnames *= 2; + names = RENEW (names, char, maxnames * (MAXPATHLEN + 1)); + nameptrs = RENEW (nameptrs, char *, maxnames); + } + if (names == (char *) 0 || nameptrs == (char **) 0) + { + syslog (LOG_ERR, "out of memory reallocating directory names"); + exit (1); + } + for (i = 0; i < maxnames; ++i) + nameptrs[i] = &names[i * (MAXPATHLEN + 1)]; + } + namlen = NAMLEN (de); + (void) strncpy (nameptrs[nnames], de->d_name, namlen); + nameptrs[nnames][namlen] = '\0'; + ++nnames; + } + closedir (dirp); + + /* Sort the names. */ + qsort (nameptrs, nnames, sizeof (*nameptrs), name_compare); + + /* Generate output. */ + for (i = 0; i < nnames; ++i) + { + httpd_realloc_str (&name, &maxname, + strlen (hc->expnfilename) + 1 + + strlen (nameptrs[i])); + httpd_realloc_str (&rname, &maxrname, + strlen (hc->origfilename) + 1 + + strlen (nameptrs[i])); + if (hc->expnfilename[0] == '\0' + || strcmp (hc->expnfilename, ".") == 0) + { + (void) strcpy (name, nameptrs[i]); + (void) strcpy (rname, nameptrs[i]); + } + else + { + (void) my_snprintf (name, maxname, + "%s/%s", hc->expnfilename, nameptrs[i]); + if (strcmp (hc->origfilename, ".") == 0) + (void) my_snprintf (rname, maxrname, "%s", nameptrs[i]); + else + (void) my_snprintf (rname, maxrname, + "%s%s", hc->origfilename, nameptrs[i]); + } + httpd_realloc_str (&encrname, &maxencrname, 3 * strlen (rname) + 1); + strencode (encrname, maxencrname, rname); + + if (stat (name, &sb) < 0 || lstat (name, &lsb) < 0) + continue; + + linkprefix = ""; + link[0] = '\0'; + /* Break down mode word. First the file type. */ + switch (lsb.st_mode & S_IFMT) + { + case S_IFIFO: + modestr[0] = 'p'; + break; + case S_IFCHR: + modestr[0] = 'c'; + break; + case S_IFDIR: + modestr[0] = 'd'; + break; + case S_IFBLK: + modestr[0] = 'b'; + break; + case S_IFREG: + modestr[0] = '-'; + break; + case S_IFSOCK: + modestr[0] = 's'; + break; + case S_IFLNK: + modestr[0] = 'l'; + linklen = readlink (name, link, sizeof (link) - 1); + if (linklen != -1) + { + link[linklen] = '\0'; + linkprefix = " -> "; + } + break; + default: + modestr[0] = '?'; + break; + } + /* Now the world permissions. Owner and group permissions + ** are not of interest to web clients. + */ + modestr[1] = (lsb.st_mode & S_IROTH) ? 'r' : '-'; + modestr[2] = (lsb.st_mode & S_IWOTH) ? 'w' : '-'; + modestr[3] = (lsb.st_mode & S_IXOTH) ? 'x' : '-'; + modestr[4] = '\0'; + + /* We also leave out the owner and group name, they are + ** also not of interest to web clients. Plus if we're + ** running under chroot(), they would require a copy + ** of /etc/passwd and /etc/group, which we want to avoid. + */ + + /* Get time string. */ + now = time ((time_t *) 0); + timestr = ctime (&lsb.st_mtime); + timestr[0] = timestr[4]; + timestr[1] = timestr[5]; + timestr[2] = timestr[6]; + timestr[3] = ' '; + timestr[4] = timestr[8]; + timestr[5] = timestr[9]; + timestr[6] = ' '; + if (now - lsb.st_mtime > 60 * 60 * 24 * 182) /* 1/2 year */ + { + timestr[7] = ' '; + timestr[8] = timestr[20]; + timestr[9] = timestr[21]; + timestr[10] = timestr[22]; + timestr[11] = timestr[23]; + } + else + { + timestr[7] = timestr[11]; + timestr[8] = timestr[12]; + timestr[9] = ':'; + timestr[10] = timestr[14]; + timestr[11] = timestr[15]; + } + timestr[12] = '\0'; + + /* The ls -F file class. */ + switch (sb.st_mode & S_IFMT) + { + case S_IFDIR: + fileclass = "/"; + break; + case S_IFSOCK: + fileclass = "="; + break; + case S_IFLNK: + fileclass = "@"; + break; + default: + fileclass = (sb.st_mode & S_IXOTH) ? "*" : ""; + break; + } + + /* And print. */ + (void) fprintf (fp, + "%s %3ld %10ld %s %.80s%s%s%s\n", + modestr, (long) lsb.st_nlink, (int64_t) lsb.st_size, + timestr, encrname, S_ISDIR (sb.st_mode) ? "/" : "", + nameptrs[i], linkprefix, link, fileclass); + } + + (void) fprintf (fp, "
\n\n"); + (void) fclose (fp); + exit (0); + } + + /* Parent process. */ + closedir (dirp); + syslog (LOG_INFO, "spawned indexing process %d for directory '%.200s'", r, + hc->expnfilename); +#ifdef CGI_TIMELIMIT + /* Schedule a kill for the child process, in case it runs too long */ + if (hc->hs->cgi_timelimit) + { + client_data.i = r; + if (tmr_create + ((struct timeval *) 0, cgi_kill, client_data, + hc->hs->cgi_timelimit * 1000L, 0) == (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(cgi_kill ls) failed"); + exit (1); + } + } +#endif /* CGI_TIMELIMIT */ + hc->status = 200; + hc->bytes_sent = CGI_BYTECOUNT; + hc->should_linger = 0; + } + else + { + closedir (dirp); + httpd_send_err (hc, 501, err501title, "", err501form, + httpd_method_str (hc->method)); + return -1; + } + + return 0; +} +#endif + +#endif /* GENERATE_INDEXES */ + + +static char *build_env (char *fmt, char *arg) +{ + char *cp; + size_t size; + static char *buf; + static size_t maxbuf = 0; + + size = strlen (fmt) + strlen (arg); + if (size > maxbuf) + httpd_realloc_str (&buf, &maxbuf, size); + + (void) my_snprintf (buf, maxbuf, fmt, arg); + cp = strdup (buf); + if (cp == (char *) 0) + { + syslog (LOG_ERR, "out of memory copying environment variable"); + exit (1); + } + //fprintf(stderr, "build_env: %s\n", cp); + return cp; +} + + +#ifdef SERVER_NAME_LIST +static char *hostname_map (char *hostname) +{ + int len, n; + static char *list[] = { SERVER_NAME_LIST }; + + len = strlen (hostname); + for (n = sizeof (list) / sizeof (*list) - 1; n >= 0; --n) + if (strncasecmp (hostname, list[n], len) == 0) + if (list[n][len] == '/') /* check in case of a substring match */ + return &list[n][len + 1]; + return (char *) 0; +} +#endif /* SERVER_NAME_LIST */ + + +/* Set up environment variables. Be real careful here to avoid +** letting malicious clients overrun a buffer. We don't have +** to worry about freeing stuff since we're a sub-process. +*/ +static char **make_envp (httpd_conn * hc) +{ + static char *envp[50]; + int envn; + char *cp; + char buf[256]; + + envn = 0; + envp[envn++] = build_env ("PATH=%s", CGI_PATH); +#ifdef CGI_LD_LIBRARY_PATH + envp[envn++] = build_env ("LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH); +#endif /* CGI_LD_LIBRARY_PATH */ + envp[envn++] = build_env ("SERVER_SOFTWARE=%s", SERVER_SOFTWARE); + /* If vhosting, use that server-name here. */ + if (hc->hs->vhost && hc->hostname != (char *) 0) + cp = hc->hostname; + else + cp = hc->hs->server_hostname; + if (cp != (char *) 0) + envp[envn++] = build_env ("SERVER_NAME=%s", cp); + envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1"; + envp[envn++] = build_env ("SERVER_PROTOCOL=%s", hc->protocol); + (void) my_snprintf (buf, sizeof (buf), "%d", (int) hc->hs->port); + envp[envn++] = build_env ("SERVER_PORT=%s", buf); + envp[envn++] = + build_env ("REQUEST_METHOD=%s", httpd_method_str (hc->method)); + + /*if ( hc->pathinfo[0] != '\0' ) + { + char* cp2; + size_t l; + envp[envn++] = build_env( "PATH_INFO=/%s", hc->pathinfo ); + l = strlen( hc->hs->cwd ) + strlen( hc->pathinfo ) + 1; + cp2 = NEW( char, l ); + if ( cp2 != (char*) 0 ) + { + (void) my_snprintf( cp2, l, "%s%s", hc->hs->cwd, hc->pathinfo ); + envp[envn++] = build_env( "PATH_TRANSLATED=%s", cp2 ); + } + } */ + + //envp[envn++] = build_env("SCRIPT_NAME=/%s", strcmp( hc->origfilename, "." ) == 0 ? "" : hc->origfilename ); + + envp[envn++] = build_env ("PATH_INFO=/%s", hc->expnfilename); + //envp[envn++] = build_env( "SCRIPT_NAME=%s", ""); + + if (hc->query[0] != '\0') + envp[envn++] = build_env ("QUERY_STRING=%s", hc->query); + envp[envn++] = build_env ("REMOTE_ADDR=%s", httpd_ntoa (&hc->client_addr)); + if (hc->referer[0] != '\0') + envp[envn++] = build_env ("HTTP_REFERER=%s", hc->referer); + if (hc->useragent[0] != '\0') + envp[envn++] = build_env ("HTTP_USER_AGENT=%s", hc->useragent); + if (hc->accept[0] != '\0') + envp[envn++] = build_env ("HTTP_ACCEPT=%s", hc->accept); + if (hc->accepte[0] != '\0') + envp[envn++] = build_env ("HTTP_ACCEPT_ENCODING=%s", hc->accepte); + if (hc->acceptl[0] != '\0') + envp[envn++] = build_env ("HTTP_ACCEPT_LANGUAGE=%s", hc->acceptl); + if (hc->cookie[0] != '\0') + envp[envn++] = build_env ("HTTP_COOKIE=%s", hc->cookie); + if (hc->contenttype[0] != '\0') + envp[envn++] = build_env ("CONTENT_TYPE=%s", hc->contenttype); + if (hc->hdrhost[0] != '\0') + envp[envn++] = build_env ("HTTP_HOST=%s", hc->hdrhost); + + if (hc->contentlength != -1) + { + (void) my_snprintf (buf, sizeof (buf), "%lu", + (unsigned long) hc->contentlength); + envp[envn++] = build_env ("CONTENT_LENGTH=%s", buf); + } + if (hc->remoteuser[0] != '\0') + envp[envn++] = build_env ("REMOTE_USER=%s", hc->remoteuser); + if (hc->authorization[0] != '\0') + envp[envn++] = build_env ("AUTH_TYPE=%s", "Basic"); + + /* We only support Basic auth at the moment. */ + if (getenv ("TZ") != (char *) 0) + envp[envn++] = build_env ("TZ=%s", getenv ("TZ")); + + //fprintf(stderr, "make_envp #2.4: %d %p\n", getpid(), hc->hs->cgi_pattern); + //envp[envn++] = build_env( "CGI_PATTERN=%s", hc->hs->cgi_pattern ); + +#ifdef X_CGI_HEADER + //if (hc->xcgi[0]) + envp[envn++] = build_env ("X_CGI=%s", hc->xcgi); + if (hc->if_modified_since != (time_t) - 1) + { + my_snprintf (buf, sizeof (buf), "%lld", + (int64_t) (hc->if_modified_since)); + envp[envn++] = build_env ("HTTP_IF_MODIFIED_SINCE=%s", buf); + } +#endif + + envp[envn] = (char *) 0; + return envp; +} + + +/* Set up argument vector. Again, we don't have to worry about freeing stuff +** since we're a sub-process. This gets done after make_envp() because we +** scribble on hc->query. +*/ +static char **make_argp (httpd_conn * hc) +{ + char **argp; + int argn; + char *cp1; + char *cp2; + + /* By allocating an arg slot for every character in the query, plus + ** one for the filename and one for the NULL, we are guaranteed to + ** have enough. We could actually use strlen/2. + */ + argp = NEW (char *, strlen (hc->query) + 2); + if (argp == (char **) 0) + return (char **) 0; + + argp[0] = strrchr (hc->expnfilename, '/'); + if (argp[0] != (char *) 0) + ++argp[0]; + else + argp[0] = hc->expnfilename; + + argn = 1; + /* According to the CGI spec at http://hoohoo.ncsa.uiuc.edu/cgi/cl.html, + ** "The server should search the query information for a non-encoded = + ** character to determine if the command line is to be used, if it finds + ** one, the command line is not to be used." + */ + if (strchr (hc->query, '=') == (char *) 0) + { + for (cp1 = cp2 = hc->query; *cp2 != '\0'; ++cp2) + { + if (*cp2 == '+') + { + *cp2 = '\0'; + strdecode (cp1, cp1); + argp[argn++] = cp1; + cp1 = cp2 + 1; + } + } + if (cp2 != cp1) + { + strdecode (cp1, cp1); + argp[argn++] = cp1; + } + } + + argp[argn] = (char *) 0; + return argp; +} + + +/* This routine is used only for POST requests. It reads the data +** from the request and sends it to the child process. The only reason +** we need to do it this way instead of just letting the child read +** directly is that we have already read part of the data into our +** buffer. +*/ +static void cgi_interpose_input (httpd_conn * hc, int wfd) +{ + size_t c; + ssize_t r; + char buf[1024]; + + c = hc->read_idx - hc->checked_idx; + if (c > 0) + { + if (httpd_write_fully (wfd, &(hc->read_buf[hc->checked_idx]), c) != c) + return; + } + while (c < hc->contentlength) + { + r = read (hc->conn_fd, buf, MIN (sizeof (buf), hc->contentlength - c)); + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + { + sleep (1); + continue; + } + if (r <= 0) + return; + if (httpd_write_fully (wfd, buf, r) != r) + return; + c += r; + } + post_post_garbage_hack (hc); +} + + +/* Special hack to deal with broken browsers that send a LF or CRLF +** after POST data, causing TCP resets - we just read and discard up +** to 2 bytes. Unfortunately this doesn't fix the problem for CGIs +** which avoid the interposer process due to their POST data being +** short. Creating an interposer process for all POST CGIs is +** unacceptably expensive. The eventual fix will come when interposing +** gets integrated into the main loop as a tasklet instead of a process. +*/ +static void post_post_garbage_hack (httpd_conn * hc) +{ + char buf[2]; + + /* If we are in a sub-process, turn on no-delay mode in case we + ** previously cleared it. + */ + if (sub_process) + httpd_set_ndelay (hc->conn_fd); + /* And read up to 2 bytes. */ + (void) read (hc->conn_fd, buf, sizeof (buf)); +} + + +/* This routine is used for parsed-header CGIs. The idea here is that the +** CGI can return special headers such as "Status:" and "Location:" which +** change the return status of the response. Since the return status has to +** be the very first line written out, we have to accumulate all the headers +** and check for the special ones before writing the status. Then we write +** out the saved headers and proceed to echo the rest of the response. +*/ +static void cgi_interpose_output (httpd_conn * hc, int rfd) +{ + int r; + char buf[1024]; + size_t headers_size, headers_len; + char *headers; + char *br; + int status; + char *title; + char *cp; + + /* Make sure the connection is in blocking mode. It should already + ** be blocking, but we might as well be sure. + */ + httpd_clear_ndelay (hc->conn_fd); + + /* Slurp in all headers. */ + headers_size = 0; + httpd_realloc_str (&headers, &headers_size, 500); + headers_len = 0; + for (;;) + { + r = read (rfd, buf, sizeof (buf)); + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + { + sleep (1); + continue; + } + if (r <= 0) + { + br = &(headers[headers_len]); + break; + } + httpd_realloc_str (&headers, &headers_size, headers_len + r); + (void) memmove (&(headers[headers_len]), buf, r); + headers_len += r; + headers[headers_len] = '\0'; + if ((br = strstr (headers, "\015\012\015\012")) != (char *) 0 || + (br = strstr (headers, "\012\012")) != (char *) 0) + break; + } + + /* If there were no headers, bail. */ + if (headers[0] == '\0') + return; + + /* Figure out the status. Look for a Status: or Location: header; + ** else if there's an HTTP header line, get it from there; else + ** default to 200. + */ + status = 200; + if (strncmp (headers, "HTTP/", 5) == 0) + { + cp = headers; + cp += strcspn (cp, " \t"); + status = atoi (cp); + } + if ((cp = strstr (headers, "Status:")) != (char *) 0 && + cp < br && (cp == headers || *(cp - 1) == '\012')) + { + cp += 7; + cp += strspn (cp, " \t"); + status = atoi (cp); + } + if ((cp = strstr (headers, "Location:")) != (char *) 0 && + cp < br && (cp == headers || *(cp - 1) == '\012')) + status = 302; + + /* Write the status line. */ + switch (status) + { + case 200: + title = ok200title; + break; + case 302: + title = err302title; + break; + case 304: + title = err304title; + break; + case 400: + title = httpd_err400title; + break; +#ifdef AUTH_FILE + case 401: + title = err401title; + break; +#endif /* AUTH_FILE */ + case 403: + title = err403title; + break; + case 404: + title = err404title; + break; + case 408: + title = httpd_err408title; + break; + case 500: + title = err500title; + break; + case 501: + title = err501title; + break; + case 503: + title = httpd_err503title; + break; + default: + title = "Something"; + break; + } + (void) my_snprintf (buf, sizeof (buf), "HTTP/1.0 %d %s\015\012", status, + title); + (void) httpd_write_fully (hc->conn_fd, buf, strlen (buf)); + + /* Write the saved headers. */ + (void) httpd_write_fully (hc->conn_fd, headers, headers_len); + + /* Echo the rest of the output. */ + for (;;) + { + r = read (rfd, buf, sizeof (buf)); + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + { + sleep (1); + continue; + } + if (r <= 0) + break; + if (httpd_write_fully (hc->conn_fd, buf, r) != r) + break; + } + shutdown (hc->conn_fd, SHUT_WR); +} + + +/* CGI child process. */ +static void cgi_child (httpd_conn * hc) +{ + int r; + char **argp; + char **envp; + //char* binary; + //char* directory; + + /* Unset close-on-exec flag for this socket. This actually shouldn't + ** be necessary, according to POSIX a dup()'d file descriptor does + ** *not* inherit the close-on-exec flag, its flag is always clear. + ** However, Linux messes this up and does copy the flag to the + ** dup()'d descriptor, so we have to clear it. This could be + ** ifdeffed for Linux only. + */ + (void) fcntl (hc->conn_fd, F_SETFD, 0); + + /* Close the syslog descriptor so that the CGI program can't + ** mess with it. All other open descriptors should be either + ** the listen socket(s), sockets from accept(), or the file-logging + ** fd, and all of those are set to close-on-exec, so we don't + ** have to close anything else. + */ + closelog (); + + /* If the socket happens to be using one of the stdin/stdout/stderr + ** descriptors, move it to another descriptor so that the dup2 calls + ** below don't screw things up. We arbitrarily pick fd 3 - if there + ** was already something on it, we clobber it, but that doesn't matter + ** since at this point the only fd of interest is the connection. + ** All others will be closed on exec. + */ + if (hc->conn_fd == STDIN_FILENO || hc->conn_fd == STDOUT_FILENO + || hc->conn_fd == STDERR_FILENO) + { + int newfd = dup2 (hc->conn_fd, STDERR_FILENO + 1); + if (newfd >= 0) + hc->conn_fd = newfd; + /* If the dup2 fails, shrug. We'll just take our chances. + ** Shouldn't happen though. + */ + } + + /* Make the environment vector. */ + envp = make_envp (hc); + + /* Make the argument vector. */ + argp = make_argp (hc); + + /* Set up stdin. For POSTs we may have to set up a pipe from an + ** interposer process, depending on if we've read some of the data + ** into our buffer. + */ + if (hc->method == METHOD_POST && hc->read_idx > hc->checked_idx) + { + int p[2]; + + if (pipe (p) < 0) + { + syslog (LOG_ERR, "pipe - %m"); + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + httpd_write_response (hc); + exit (1); + } + + r = fork (); + + if (r < 0) + { + syslog (LOG_ERR, "fork - %m"); + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + httpd_write_response (hc); + exit (1); + } + + if (r == 0) + { + /* Interposer process. */ + sub_process = 1; + (void) close (p[0]); + cgi_interpose_input (hc, p[1]); + exit (0); + } + + /* Need to schedule a kill for process r; but in the main process! */ + (void) close (p[1]); + if (p[0] != STDIN_FILENO) + { + (void) dup2 (p[0], STDIN_FILENO); + (void) close (p[0]); + } + } + else + { + /* Otherwise, the request socket is stdin. */ + if (hc->conn_fd != STDIN_FILENO) + (void) dup2 (hc->conn_fd, STDIN_FILENO); + } + + /* Set up stdout/stderr. If we're doing CGI header parsing, + ** we need an output interposer too. + */ + if (strncmp (argp[0], "nph-", 4) != 0 && hc->mime_flag) + { + int p[2]; + + if (pipe (p) < 0) + { + syslog (LOG_ERR, "pipe - %m"); + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + httpd_write_response (hc); + exit (1); + } + r = fork (); + if (r < 0) + { + syslog (LOG_ERR, "fork - %m"); + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + httpd_write_response (hc); + exit (1); + } + if (r == 0) + { + /* Interposer process. */ + sub_process = 1; + (void) close (p[1]); + cgi_interpose_output (hc, p[0]); + exit (0); + } + /* Need to schedule a kill for process r; but in the main process! */ + (void) close (p[0]); + if (p[1] != STDOUT_FILENO) + (void) dup2 (p[1], STDOUT_FILENO); + if (p[1] != STDERR_FILENO) + (void) dup2 (p[1], STDERR_FILENO); + if (p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO) + (void) close (p[1]); + } + else + { + /* Otherwise, the request socket is stdout/stderr. */ + if (hc->conn_fd != STDOUT_FILENO) + (void) dup2 (hc->conn_fd, STDOUT_FILENO); + if (hc->conn_fd != STDERR_FILENO) + (void) dup2 (hc->conn_fd, STDERR_FILENO); + } + + /* At this point we would like to set close-on-exec again for hc->conn_fd + ** (see previous comments on Linux's broken behavior re: close-on-exec + ** and dup.) Unfortunately there seems to be another Linux problem, or + ** perhaps a different aspect of the same problem - if we do this + ** close-on-exec in Linux, the socket stays open but stderr gets + ** closed - the last fd duped from the socket. What a mess. So we'll + ** just leave the socket as is, which under other OSs means an extra + ** file descriptor gets passed to the child process. Since the child + ** probably already has that file open via stdin stdout and/or stderr, + ** this is not a problem. + */ + /* (void) fcntl( hc->conn_fd, F_SETFD, 1 ); */ + +#ifdef CGI_NICE + /* Set priority. */ + (void) nice (CGI_NICE); +#endif /* CGI_NICE */ + +#if 0 + /* Split the program into directory and binary, so we can chdir() + ** to the program's own directory. This isn't in the CGI 1.1 + ** spec, but it's what other HTTP servers do. + */ + directory = strdup (hc->expnfilename); + if (directory == (char *) 0) + binary = hc->expnfilename; /* ignore errors */ + else + { + binary = strrchr (directory, '/'); + if (binary == (char *) 0) + binary = hc->expnfilename; + else + { + *binary++ = '\0'; + (void) chdir (directory); /* ignore errors */ + } + } +#endif + + // Reset signal handlers + + (void) signal (SIGTERM, SIG_DFL); + (void) signal (SIGINT, SIG_DFL); + (void) signal (SIGCHLD, SIG_DFL); + (void) signal (SIGPIPE, SIG_DFL); + (void) signal (SIGHUP, SIG_DFL); + (void) signal (SIGUSR1, SIG_DFL); + (void) signal (SIGUSR2, SIG_DFL); + (void) signal (SIGALRM, SIG_DFL); + +#if 0 + /* Run the program. */ + (void) execve (binary, argp, envp); + + /* Something went wrong. */ + syslog (LOG_ERR, "execve %.80s - %m", hc->expnfilename); + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + httpd_write_response (hc); + exit (1); +#endif + + environ = envp; + run_cgi (); + +} + + +static int cgi (httpd_conn * hc) +{ + int r; + ClientData client_data; + + if (hc->method == METHOD_GET || hc->method == METHOD_POST) + { + if (hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit) + { + httpd_send_err (hc, 503, httpd_err503title, "", httpd_err503form, + hc->encodedurl); + return -1; + } + + ++hc->hs->cgi_count; + httpd_clear_ndelay (hc->conn_fd); + r = fork (); + if (r < 0) + { + syslog (LOG_ERR, "fork - %m"); + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + return -1; + } + + if (r == 0) + { + /* Child process. */ + sub_process = 1; + httpd_unlisten (hc->hs); + cgi_child (hc); + } + else + { + /* Parent process. */ + syslog (LOG_INFO, "spawned CGI process %d for path '%.200s'", r, + hc->expnfilename); +#ifdef CGI_TIMELIMIT + /* Schedule a kill for the child process, in case it runs too long */ + if (hc->hs->cgi_timelimit) + { + client_data.i = r; + if (tmr_create + ((struct timeval *) 0, cgi_kill, client_data, + hc->hs->cgi_timelimit * 1000L, 0) == (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(cgi_kill child) failed"); + exit (1); + } + } +#endif /* CGI_TIMELIMIT */ + hc->status = 200; + hc->bytes_sent = CGI_BYTECOUNT; + hc->should_linger = 0; + } + } + else + { + httpd_send_err (hc, 501, err501title, "", err501form, + httpd_method_str (hc->method)); + return -1; + } + + return 0; +} + + +static int really_start_request (httpd_conn * hc, struct timeval *nowP) +{ + //static char *indexname; + //static size_t maxindexname = 0; + //static const char *index_names[] = { INDEX_NAMES }; + //int i; +#ifdef AUTH_FILE + //static char *dirname; + //static size_t maxdirname = 0; +#endif /* AUTH_FILE */ + //size_t expnlen, indxlen; + //char *cp; + //char *pi; + + //expnlen = strlen (hc->expnfilename); + + if (hc->method != METHOD_GET && hc->method != METHOD_HEAD && + hc->method != METHOD_POST) + { + httpd_send_err (hc, 501, err501title, "", err501form, + httpd_method_str (hc->method)); + return -1; + } + + //-------------------------------------------------------------- + + /* Referer check. */ + if (!check_referer (hc)) + return -1; + + /* Is it world-executable and in the CGI area? */ + //if ( hc->hs->cgi_pattern != (char*) 0 && ( hc->sb.st_mode & S_IXOTH ) && match( hc->hs->cgi_pattern, hc->expnfilename ) ) + return cgi (hc); + + //-------------------------------------------------------------- + +#if 0 + /* Stat the file. */ + if (stat (hc->expnfilename, &hc->sb) < 0) + { + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + return -1; + } + + /* Is it world-readable or world-executable? We check explicitly instead + ** of just trying to open it, so that no one ever gets surprised by + ** a file that's not set world-readable and yet somehow is + ** readable by the HTTP server and therefore the *whole* world. + */ + if (!(hc->sb.st_mode & (S_IROTH | S_IXOTH))) + { + syslog (LOG_INFO, + "%.80s URL \"%.80s\" resolves to a non world-readable file", + httpd_ntoa (&hc->client_addr), hc->encodedurl); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' resolves to a file that is not world-readable.\n"), + hc->encodedurl); + return -1; + } + + /* Is it a directory? */ + if (S_ISDIR (hc->sb.st_mode)) + { + /* If there's pathinfo, it's just a non-existent file. */ + if (hc->pathinfo[0] != '\0') + { + httpd_send_err (hc, 404, err404title, "", err404form, hc->encodedurl); + return -1; + } + + /* Special handling for directory URLs that don't end in a slash. + ** We send back an explicit redirect with the slash, because + ** otherwise many clients can't build relative URLs properly. + */ + if (strcmp (hc->origfilename, "") != 0 && + strcmp (hc->origfilename, ".") != 0 && + hc->origfilename[strlen (hc->origfilename) - 1] != '/') + { + send_dirredirect (hc); + return -1; + } + + /* Check for an index file. */ + for (i = 0; i < sizeof (index_names) / sizeof (char *); ++i) + { + httpd_realloc_str (&indexname, &maxindexname, + expnlen + 1 + strlen (index_names[i])); + (void) strcpy (indexname, hc->expnfilename); + indxlen = strlen (indexname); + if (indxlen == 0 || indexname[indxlen - 1] != '/') + (void) strcat (indexname, "/"); + if (strcmp (indexname, "./") == 0) + indexname[0] = '\0'; + (void) strcat (indexname, index_names[i]); + if (stat (indexname, &hc->sb) >= 0) + goto got_one; + } + + /* Nope, no index file, so it's an actual directory request. */ +#ifdef GENERATE_INDEXES + /* Directories must be readable for indexing. */ + if (!(hc->sb.st_mode & S_IROTH)) + { + syslog (LOG_INFO, + "%.80s URL \"%.80s\" tried to index a directory with indexing disabled", + httpd_ntoa (&hc->client_addr), hc->encodedurl); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' resolves to a directory that has indexing disabled.\n"), + hc->encodedurl); + return -1; + } +#ifdef AUTH_FILE + /* Check authorization for this directory. */ + if (auth_check (hc, hc->expnfilename) == -1) + return -1; +#endif /* AUTH_FILE */ + /* Referer check. */ + if (!check_referer (hc)) + return -1; + /* Ok, generate an index. */ + return ls (hc); +#else /* GENERATE_INDEXES */ + syslog (LOG_INFO, "%.80s URL \"%.80s\" tried to index a directory", + httpd_ntoa (&hc->client_addr), hc->encodedurl); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' is a directory, and directory indexing is disabled on this server.\n"), + hc->encodedurl); + return -1; +#endif /* GENERATE_INDEXES */ + + got_one:; + /* Got an index file. Expand symlinks again. More pathinfo means + ** something went wrong. + */ + cp = + expand_symlinks (indexname, &pi, hc->hs->no_symlink_check, + hc->tildemapped); + if (cp == (char *) 0 || pi[0] != '\0') + { + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + return -1; + } + expnlen = strlen (cp); + httpd_realloc_str (&hc->expnfilename, &hc->maxexpnfilename, expnlen); + (void) strcpy (hc->expnfilename, cp); + + /* Now, is the index version world-readable or world-executable? */ + if (!(hc->sb.st_mode & (S_IROTH | S_IXOTH))) + { + syslog (LOG_INFO, + "%.80s URL \"%.80s\" resolves to a non-world-readable index file", + httpd_ntoa (&hc->client_addr), hc->encodedurl); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' resolves to an index file that is not world-readable.\n"), + hc->encodedurl); + return -1; + } + } + +#ifdef AUTH_FILE + /* Check authorization for this directory. */ + httpd_realloc_str (&dirname, &maxdirname, expnlen); + (void) strcpy (dirname, hc->expnfilename); + cp = strrchr (dirname, '/'); + if (cp == (char *) 0) + (void) strcpy (dirname, "."); + else + *cp = '\0'; + if (auth_check (hc, dirname) == -1) + return -1; + + /* Check if the filename is the AUTH_FILE itself - that's verboten. */ + if (expnlen == sizeof (AUTH_FILE) - 1) + { + if (strcmp (hc->expnfilename, AUTH_FILE) == 0) + { + syslog (LOG_NOTICE, + "%.80s URL \"%.80s\" tried to retrieve an auth file", + httpd_ntoa (&hc->client_addr), hc->encodedurl); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n"), + hc->encodedurl); + return -1; + } + } + else if (expnlen >= sizeof (AUTH_FILE) && + strcmp (&(hc->expnfilename[expnlen - sizeof (AUTH_FILE) + 1]), + AUTH_FILE) == 0 + && hc->expnfilename[expnlen - sizeof (AUTH_FILE)] == '/') + { + syslog (LOG_NOTICE, + "%.80s URL \"%.80s\" tried to retrieve an auth file", + httpd_ntoa (&hc->client_addr), hc->encodedurl); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' is an authorization file, retrieving it is not permitted.\n"), + hc->encodedurl); + return -1; + } +#endif /* AUTH_FILE */ + + /* Referer check. */ + if (!check_referer (hc)) + return -1; + + /* Is it world-executable and in the CGI area? */ + if (hc->hs->cgi_pattern != (char *) 0 && + (hc->sb.st_mode & S_IXOTH) && + match (hc->hs->cgi_pattern, hc->expnfilename)) + return cgi (hc); + + /* It's not CGI. If it's executable or there's pathinfo, someone's + ** trying to either serve or run a non-CGI file as CGI. Either case + ** is prohibited. + */ + if (hc->sb.st_mode & S_IXOTH) + { + syslog (LOG_NOTICE, "%.80s URL \"%.80s\" is executable but isn't CGI", + httpd_ntoa (&hc->client_addr), hc->encodedurl); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' resolves to a file which is marked executable but is not a CGI file; retrieving it is forbidden.\n"), + hc->encodedurl); + return -1; + } + if (hc->pathinfo[0] != '\0') + { + syslog (LOG_INFO, "%.80s URL \"%.80s\" has pathinfo but isn't CGI", + httpd_ntoa (&hc->client_addr), hc->encodedurl); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "The requested URL '%.80s' resolves to a file plus CGI-style pathinfo, but the file is not a valid CGI file.\n"), + hc->encodedurl); + return -1; + } + + /* Fill in last_byte_index, if necessary. */ + if (hc->got_range && + (hc->last_byte_index == -1 || hc->last_byte_index >= hc->sb.st_size)) + hc->last_byte_index = hc->sb.st_size - 1; + + figure_mime (hc); + + if (hc->method == METHOD_HEAD) + { + send_mime (hc, 200, ok200title, hc->encodings, "", hc->type, + hc->sb.st_size, hc->sb.st_mtime); + } + else if (hc->if_modified_since != (time_t) - 1 && + hc->if_modified_since >= hc->sb.st_mtime) + { + send_mime (hc, 304, err304title, hc->encodings, "", hc->type, (off_t) - 1, + hc->sb.st_mtime); + } + else + { + hc->file_address = mmc_map (hc->expnfilename, &(hc->sb), nowP); + if (hc->file_address == (char *) 0) + { + httpd_send_err (hc, 500, err500title, "", err500form, hc->encodedurl); + return -1; + } + send_mime (hc, 200, ok200title, hc->encodings, "", hc->type, + hc->sb.st_size, hc->sb.st_mtime); + } + + return 0; +#endif +} + + +int httpd_start_request (httpd_conn * hc, struct timeval *nowP) +{ + int r; + + /* Really start the request. */ + r = really_start_request (hc, nowP); + + /* And return the status. */ + return r; +} + + +static void make_log_entry (httpd_conn * hc, struct timeval *nowP) +{ + char *ru; + char url[305]; + char bytes[40]; + + if (hc->hs->no_log) + return; + + /* This is straight CERN Combined Log Format - the only tweak + ** being that if we're using syslog() we leave out the date, because + ** syslogd puts it in. The included syslogtocern script turns the + ** results into true CERN format. + */ + + /* Format remote user. */ + if (hc->remoteuser[0] != '\0') + ru = hc->remoteuser; + else + ru = "-"; + /* If we're vhosting, prepend the hostname to the url. This is + ** a little weird, perhaps writing separate log files for + ** each vhost would make more sense. + */ + if (hc->hs->vhost && !hc->tildemapped) + (void) my_snprintf (url, sizeof (url), + "/%.100s%.200s", + hc->hostname == + (char *) 0 ? hc->hs->server_hostname : hc->hostname, + hc->encodedurl); + else + (void) my_snprintf (url, sizeof (url), "%.200s", hc->encodedurl); + /* Format the bytes. */ + if (hc->bytes_sent >= 0) + (void) my_snprintf (bytes, sizeof (bytes), "%lld", + (int64_t) hc->bytes_sent); + else + (void) strcpy (bytes, "-"); + + /* Logfile or syslog? */ + if (hc->hs->logfp != (FILE *) 0) + { + time_t now; + struct tm *t; + const char *cernfmt_nozone = "%d/%b/%Y:%H:%M:%S"; + char date_nozone[100]; + int zone; + char sign; + char date[100]; + + /* Get the current time, if necessary. */ + if (nowP != (struct timeval *) 0) + now = nowP->tv_sec; + else + now = time ((time_t *) 0); + /* Format the time, forcing a numeric timezone (some log analyzers + ** are stoooopid about this). + */ + t = localtime (&now); + (void) strftime (date_nozone, sizeof (date_nozone), cernfmt_nozone, t); +#ifdef HAVE_TM_GMTOFF + zone = t->tm_gmtoff / 60L; +#else + zone = -timezone / 60L; + /* Probably have to add something about daylight time here. */ +#endif + if (zone >= 0) + sign = '+'; + else + { + sign = '-'; + zone = -zone; + } + zone = (zone / 60) * 100 + zone % 60; + (void) my_snprintf (date, sizeof (date), + "%s %c%04d", date_nozone, sign, zone); + /* And write the log entry. */ + (void) fprintf (hc->hs->logfp, + "%.80s - %.80s [%s] \"%.80s %.300s %.80s\" %d %s \"%.200s\" \"%.200s\"\n", + httpd_ntoa (&hc->client_addr), ru, date, + httpd_method_str (hc->method), url, hc->protocol, + hc->status, bytes, hc->referer, hc->useragent); +#ifdef FLUSH_LOG_EVERY_TIME + (void) fflush (hc->hs->logfp); +#endif + } + else + syslog (LOG_INFO, + "%.80s - %.80s \"%.80s %.200s %.80s\" %d %s \"%.200s\" \"%.200s\"", + httpd_ntoa (&hc->client_addr), ru, + httpd_method_str (hc->method), url, hc->protocol, + hc->status, bytes, hc->referer, hc->useragent); +} + + +/* Returns 1 if ok to serve the url, 0 if not. */ +static int check_referer (httpd_conn * hc) +{ + int r; + char *cp; + + /* Are we doing referer checking at all? */ + if (hc->hs->url_pattern == (char *) 0) + return 1; + + r = really_check_referer (hc); + + if (!r) + { + if (hc->hs->vhost && hc->hostname != (char *) 0) + cp = hc->hostname; + else + cp = hc->hs->server_hostname; + if (cp == (char *) 0) + cp = ""; + syslog (LOG_INFO, "%.80s non-local referer \"%.80s%.80s\" \"%.80s\"", + httpd_ntoa (&hc->client_addr), cp, hc->encodedurl, hc->referer); + httpd_send_err (hc, 403, err403title, "", + ERROR_FORM (err403form, + "You must supply a local referer to get URL '%.80s' from this server.\n"), + hc->encodedurl); + } + return r; +} + + +/* Returns 1 if ok to serve the url, 0 if not. */ +static int really_check_referer (httpd_conn * hc) +{ + httpd_server *hs; + char *cp1; + char *cp2; + char *cp3; + static char *refhost = (char *) 0; + static size_t refhost_size = 0; + char *lp; + + hs = hc->hs; + + /* Check for an empty referer. */ + if (hc->referer == (char *) 0 || hc->referer[0] == '\0' || + (cp1 = strstr (hc->referer, "//")) == (char *) 0) + { + /* Disallow if we require a referer and the url matches. */ + if (hs->no_empty_referers && match (hs->url_pattern, hc->origfilename)) + return 0; + /* Otherwise ok. */ + return 1; + } + + /* Extract referer host. */ + cp1 += 2; + for (cp2 = cp1; *cp2 != '/' && *cp2 != ':' && *cp2 != '\0'; ++cp2) + continue; + httpd_realloc_str (&refhost, &refhost_size, cp2 - cp1); + for (cp3 = refhost; cp1 < cp2; ++cp1, ++cp3) + if (isupper (*cp1)) + *cp3 = tolower (*cp1); + else + *cp3 = *cp1; + *cp3 = '\0'; + + /* Local pattern? */ + if (hs->local_pattern != (char *) 0) + lp = hs->local_pattern; + else + { + /* No local pattern. What's our hostname? */ + if (!hs->vhost) + { + /* Not vhosting, use the server name. */ + lp = hs->server_hostname; + if (lp == (char *) 0) + /* Couldn't figure out local hostname - give up. */ + return 1; + } + else + { + /* We are vhosting, use the hostname on this connection. */ + lp = hc->hostname; + if (lp == (char *) 0) + /* Oops, no hostname. Maybe it's an old browser that + ** doesn't send a Host: header. We could figure out + ** the default hostname for this IP address, but it's + ** not worth it for the few requests like this. + */ + return 1; + } + } + + /* If the referer host doesn't match the local host pattern, and + ** the filename does match the url pattern, it's an illegal reference. + */ + if (!match (lp, refhost) && match (hs->url_pattern, hc->origfilename)) + return 0; + /* Otherwise ok. */ + return 1; +} + + +char *httpd_ntoa (httpd_sockaddr * saP) +{ +#ifdef USE_IPV6 + static char str[200]; + + if (getnameinfo + (&saP->sa, sockaddr_len (saP), str, sizeof (str), 0, 0, + NI_NUMERICHOST) != 0) + { + str[0] = '?'; + str[1] = '\0'; + } + else if (IN6_IS_ADDR_V4MAPPED (&saP->sa_in6.sin6_addr) + && strncmp (str, "::ffff:", 7) == 0) + /* Elide IPv6ish prefix for IPv4 addresses. */ + (void) strcpy (str, &str[7]); + + return str; + +#else /* USE_IPV6 */ + + return inet_ntoa (saP->sa_in.sin_addr); + +#endif /* USE_IPV6 */ +} + + +static int sockaddr_check (httpd_sockaddr * saP) +{ + switch (saP->sa.sa_family) + { + case AF_INET: + return 1; +#ifdef USE_IPV6 + case AF_INET6: + return 1; +#endif /* USE_IPV6 */ + default: + return 0; + } +} + + +static size_t sockaddr_len (httpd_sockaddr * saP) +{ + switch (saP->sa.sa_family) + { + case AF_INET: + return sizeof (struct sockaddr_in); +#ifdef USE_IPV6 + case AF_INET6: + return sizeof (struct sockaddr_in6); +#endif /* USE_IPV6 */ + default: + return 0; /* shouldn't happen */ + } +} + + +/* Some systems don't have snprintf(), so we make our own that uses +** either vsnprintf() or vsprintf(). If your system doesn't have +** vsnprintf(), it is probably vulnerable to buffer overruns. +** Upgrade! +*/ +static int my_snprintf (char *str, size_t size, const char *format, ...) +{ + va_list ap; + int r; + + va_start (ap, format); +#ifdef HAVE_VSNPRINTF + r = vsnprintf (str, size, format, ap); +#else /* HAVE_VSNPRINTF */ + r = vsprintf (str, format, ap); +#endif /* HAVE_VSNPRINTF */ + va_end (ap); + return r; +} + + +#ifndef HAVE_ATOLL +static long long atoll (const char *str) +{ + long long value; + long long sign; + + while (isspace (*str)) + ++str; + switch (*str) + { + case '-': + sign = -1; + ++str; + break; + case '+': + sign = 1; + ++str; + break; + default: + sign = 1; + break; + } + value = 0; + while (isdigit (*str)) + { + value = value * 10 + (*str - '0'); + ++str; + } + return sign * value; +} +#endif /* HAVE_ATOLL */ + + +/* Read the requested buffer completely, accounting for interruptions. */ +int httpd_read_fully (int fd, void *buf, size_t nbytes) +{ + int nread; + + nread = 0; + while (nread < nbytes) + { + int r; + + r = read (fd, (char *) buf + nread, nbytes - nread); + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + { + sleep (1); + continue; + } + if (r < 0) + return r; + if (r == 0) + break; + nread += r; + } + + return nread; +} + + +/* Write the requested buffer completely, accounting for interruptions. */ +int httpd_write_fully (int fd, const void *buf, size_t nbytes) +{ + int nwritten; + + nwritten = 0; + while (nwritten < nbytes) + { + int r; + + r = write (fd, (char *) buf + nwritten, nbytes - nwritten); + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + { + sleep (1); + continue; + } + if (r < 0) + return r; + if (r == 0) + break; + nwritten += r; + } + + return nwritten; +} + + +/* Generate debugging statistics syslog message. */ +void httpd_logstats (long secs) +{ + if (str_alloc_count > 0) + syslog (LOG_INFO, + " libhttpd - %d strings allocated, %lu bytes (%g bytes/str)", + str_alloc_count, (unsigned long) str_alloc_size, + (float) str_alloc_size / str_alloc_count); +} diff --git a/gb.httpd/src/libhttpd.h b/gb.httpd/src/libhttpd.h new file mode 100644 index 000000000..ea722b61b --- /dev/null +++ b/gb.httpd/src/libhttpd.h @@ -0,0 +1,294 @@ +/* libhttpd.h - defines for libhttpd +** +** Copyright � 1995,1998,1999,2000,2001 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#ifndef _LIBHTTPD_H_ +#define _LIBHTTPD_H_ + +#include +#include +#include +#include +#include +#include +#include + +#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED) +#define USE_IPV6 +#endif + + +/* A few convenient defines. */ + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#define NEW(t,n) ((t*) malloc( sizeof(t) * (n) )) +#define RENEW(o,t,n) ((t*) realloc( (void*) o, sizeof(t) * (n) )) + + +/* The httpd structs. */ + +/* A multi-family sockaddr. */ +typedef union +{ + struct sockaddr sa; + struct sockaddr_in sa_in; +#ifdef USE_IPV6 + struct sockaddr_in6 sa_in6; + struct sockaddr_storage sa_stor; +#endif /* USE_IPV6 */ +} httpd_sockaddr; + +/* A server. */ +typedef struct +{ + char *binding_hostname; + char *server_hostname; + unsigned short port; + char *cgi_pattern; + int cgi_limit, cgi_timelimit, cgi_count; + char *charset; + char *p3p; + int max_age; + char *cwd; + int listen4_fd, listen6_fd; + int no_log; + FILE *logfp; + int no_symlink_check; + int vhost; + int global_passwd; + char *url_pattern; + char *local_pattern; + int no_empty_referers; +} httpd_server; + +/* A connection. */ +typedef struct +{ + int initialized; + httpd_server *hs; + httpd_sockaddr client_addr; + char *read_buf; + size_t read_size, read_idx, checked_idx; + int checked_state; + int method; + int status; + off_t bytes_to_send; + off_t bytes_sent; + char *encodedurl; + char *decodedurl; + char *protocol; + char *origfilename; + char *expnfilename; + char *encodings; + char *pathinfo; + char *query; + char *referer; + char *useragent; + char *accept; + char *accepte; + char *acceptl; + char *cookie; + char *contenttype; +#ifdef X_CGI_HEADER + char *xcgi; +#endif + char *reqhost; + char *hdrhost; + char *hostdir; + char *authorization; + char *remoteuser; + char *response; + size_t maxdecodedurl, maxorigfilename, maxexpnfilename, maxencodings, + maxpathinfo, maxquery, maxaccept, maxaccepte, maxreqhost, maxhostdir, + maxremoteuser, maxresponse; +#ifdef TILDE_MAP_2 + char *altdir; + size_t maxaltdir; +#endif /* TILDE_MAP_2 */ + size_t responselen; + time_t if_modified_since, range_if; + size_t contentlength; + char *type; /* not malloc()ed */ + char *hostname; /* not malloc()ed */ + int mime_flag; + int one_one; /* HTTP/1.1 or better */ + int got_range; + int tildemapped; /* this connection got tilde-mapped */ + off_t first_byte_index, last_byte_index; + int keep_alive; + int should_linger; + struct stat sb; + int conn_fd; + char *file_address; +} httpd_conn; + +/* Methods. */ +#define METHOD_UNKNOWN 0 +#define METHOD_GET 1 +#define METHOD_HEAD 2 +#define METHOD_POST 3 + +/* States for checked_state. */ +#define CHST_FIRSTWORD 0 +#define CHST_FIRSTWS 1 +#define CHST_SECONDWORD 2 +#define CHST_SECONDWS 3 +#define CHST_THIRDWORD 4 +#define CHST_THIRDWS 5 +#define CHST_LINE 6 +#define CHST_LF 7 +#define CHST_CR 8 +#define CHST_CRLF 9 +#define CHST_CRLFCR 10 +#define CHST_BOGUS 11 + + +/* Initializes. Does the socket(), bind(), and listen(). Returns an +** httpd_server* which includes a socket fd that you can select() on. +** Return (httpd_server*) 0 on error. +*/ +extern httpd_server *httpd_initialize (char *hostname, httpd_sockaddr * sa4P, + httpd_sockaddr * sa6P, + unsigned short port, char *cgi_pattern, + int cgi_limit, int cgi_timelimit, + char *charset, char *p3p, int max_age, + char *cwd, int no_log, FILE * logfp, + int no_symlink_check, int vhost, + int global_passwd, char *url_pattern, + char *local_pattern, + int no_empty_referers); + +/* Change the log file. */ +extern void httpd_set_logfp (httpd_server * hs, FILE * logfp); + +/* Call to unlisten/close socket(s) listening for new connections. */ +extern void httpd_unlisten (httpd_server * hs); + +/* Call to shut down. */ +extern void httpd_terminate (httpd_server * hs); + + +/* When a listen fd is ready to read, call this. It does the accept() and +** returns an httpd_conn* which includes the fd to read the request from and +** write the response to. Returns an indication of whether the accept() +** failed, succeeded, or if there were no more connections to accept. +** +** In order to minimize malloc()s, the caller passes in the httpd_conn. +** The caller is also responsible for setting initialized to zero before the +** first call using each different httpd_conn. +*/ +extern int httpd_get_conn (httpd_server * hs, int listen_fd, httpd_conn * hc); +#define GC_FAIL 0 +#define GC_OK 1 +#define GC_NO_MORE 2 + +/* Checks whether the data in hc->read_buf constitutes a complete request +** yet. The caller reads data into hc->read_buf[hc->read_idx] and advances +** hc->read_idx. This routine checks what has been read so far, using +** hc->checked_idx and hc->checked_state to keep track, and returns an +** indication of whether there is no complete request yet, there is a +** complete request, or there won't be a valid request due to a syntax error. +*/ +extern int httpd_got_request (httpd_conn * hc); +#define GR_NO_REQUEST 0 +#define GR_GOT_REQUEST 1 +#define GR_BAD_REQUEST 2 + +/* Parses the request in hc->read_buf. Fills in lots of fields in hc, +** like the URL and the various headers. +** +** Returns -1 on error. +*/ +extern int httpd_parse_request (httpd_conn * hc); + +/* Starts sending data back to the client. In some cases (directories, +** CGI programs), finishes sending by itself - in those cases, hc->file_fd +** is <0. If there is more data to be sent, then hc->file_fd is a file +** descriptor for the file to send. If you don't have a current timeval +** handy just pass in 0. +** +** Returns -1 on error. +*/ +extern int httpd_start_request (httpd_conn * hc, struct timeval *nowP); + +/* Actually sends any buffered response text. */ +extern void httpd_write_response (httpd_conn * hc); + +/* Call this to close down a connection and free the data. A fine point, +** if you fork() with a connection open you should still call this in the +** parent process - the connection will stay open in the child. +** If you don't have a current timeval handy just pass in 0. +*/ +extern void httpd_close_conn (httpd_conn * hc, struct timeval *nowP); + +/* Call this to de-initialize a connection struct and *really* free the +** mallocced strings. +*/ +extern void httpd_destroy_conn (httpd_conn * hc); + + +/* Send an error message back to the client. */ +extern void httpd_send_err (httpd_conn * hc, int status, char *title, + char *extraheads, char *form, char *arg); + +/* Some error messages. */ +extern char *httpd_err400title; +extern char *httpd_err400form; +extern char *httpd_err408title; +extern char *httpd_err408form; +extern char *httpd_err503title; +extern char *httpd_err503form; + +/* Generate a string representation of a method number. */ +extern char *httpd_method_str (int method); + +/* Reallocate a string. */ +extern void httpd_realloc_str (char **strP, size_t * maxsizeP, size_t size); + +/* Format a network socket to a string representation. */ +extern char *httpd_ntoa (httpd_sockaddr * saP); + +/* Set NDELAY mode on a socket. */ +extern void httpd_set_ndelay (int fd); + +/* Clear NDELAY mode on a socket. */ +extern void httpd_clear_ndelay (int fd); + +/* Read the requested buffer completely, accounting for interruptions. */ +extern int httpd_read_fully (int fd, void *buf, size_t nbytes); + +/* Write the requested buffer completely, accounting for interruptions. */ +extern int httpd_write_fully (int fd, const void *buf, size_t nbytes); + +/* Generate debugging statistics syslog message. */ +extern void httpd_logstats (long secs); + +#endif /* _LIBHTTPD_H_ */ diff --git a/gb.httpd/src/main.c b/gb.httpd/src/main.c new file mode 100644 index 000000000..72cf98d62 --- /dev/null +++ b/gb.httpd/src/main.c @@ -0,0 +1,71 @@ +/*************************************************************************** + + main.c + + gb.httpd component + + (c) 2012 Benoît Minisini + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + +***************************************************************************/ + +#define __MAIN_C + +#include "gb_common.h" + +#include + +#include "main.h" + +int thttpd_main (int argc, char **argv); + +const GB_INTERFACE *GB_PTR EXPORT; + +static jmp_buf _setjmp_env; + +void syslog (int priority, const char *format, ...) +{ + va_list args; + + va_start (args, format); + + fprintf (stderr, "gb.httpd: "); + vfprintf (stderr, format, args); + putc ('\n', stderr); +} + +void run_cgi (void) +{ + longjmp (_setjmp_env, 1); +} + +void EXPORT GB_MAIN (int argc, char **argv) +{ + if (setjmp (_setjmp_env) == 0) + thttpd_main (argc, argv); + else + GB.System.HasForked (); +} + +int EXPORT GB_INIT () +{ + return 0; +} + +void EXPORT GB_EXIT () +{ +} diff --git a/gb.httpd/src/main.h b/gb.httpd/src/main.h new file mode 100644 index 000000000..defbd9c9b --- /dev/null +++ b/gb.httpd/src/main.h @@ -0,0 +1,51 @@ +/*************************************************************************** + + main.h + + gb.httpd component + + (c) 2012 Benoît Minisini + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 1, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301, USA. + +***************************************************************************/ + +#ifndef __MAIN_H +#define __MAIN_H + +#include "gambas.h" + +#ifndef __MAIN_C +extern const GB_INTERFACE *GB_PTR; +#endif + +#define GB (*GB_PTR) + +#define LOG_EMERG 0 /* system is unusable */ +#define LOG_ALERT 1 /* action must be taken immediately */ +#define LOG_CRIT 2 /* critical conditions */ +#define LOG_ERR 3 /* error conditions */ +#define LOG_WARNING 4 /* warning conditions */ +#define LOG_NOTICE 5 /* normal but significant condition */ +#define LOG_INFO 6 /* informational */ +#define LOG_DEBUG 7 /* debug-level messages */ + +void syslog (int priority, const char *format, ...); +#define closelog() + +void run_cgi (); + +#endif /* __MAIN_H */ diff --git a/gb.httpd/src/match.c b/gb.httpd/src/match.c new file mode 100644 index 000000000..47ee7563d --- /dev/null +++ b/gb.httpd/src/match.c @@ -0,0 +1,87 @@ +/* match.c - simple shell-style filename matcher +** +** Only does ? * and **, and multiple patterns separated by |. Returns 1 or 0. +** +** Copyright © 1995,2000 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + + +#include + +#include "match.h" + +static int match_one (const char *pattern, int patternlen, + const char *string); + +int match (const char *pattern, const char *string) +{ + const char *or; + + for (;;) + { + or = strchr (pattern, '|'); + if (or == (char *) 0) + return match_one (pattern, strlen (pattern), string); + if (match_one (pattern, or - pattern, string)) + return 1; + pattern = or + 1; + } +} + + +static int match_one (const char *pattern, int patternlen, const char *string) +{ + const char *p; + + for (p = pattern; p - pattern < patternlen; ++p, ++string) + { + if (*p == '?' && *string != '\0') + continue; + if (*p == '*') + { + int i, pl; + ++p; + if (*p == '*') + { + /* Double-wildcard matches anything. */ + ++p; + i = strlen (string); + } + else + /* Single-wildcard matches anything but slash. */ + i = strcspn (string, "/"); + pl = patternlen - (p - pattern); + for (; i >= 0; --i) + if (match_one (p, pl, &(string[i]))) + return 1; + return 0; + } + if (*p != *string) + return 0; + } + if (*string == '\0') + return 1; + return 0; +} diff --git a/gb.httpd/src/match.h b/gb.httpd/src/match.h new file mode 100644 index 000000000..a0d7569ef --- /dev/null +++ b/gb.httpd/src/match.h @@ -0,0 +1,36 @@ +/* match.h - simple shell-style filename patcher +** +** Copyright © 1995 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#ifndef _MATCH_H_ +#define _MATCH_H_ + +/* Simple shell-style filename pattern matcher. Only does ? * and **, and +** multiple patterns separated by |. Returns 1 or 0. +*/ +extern int match (const char *pattern, const char *string); + +#endif /* _MATCH_H_ */ diff --git a/gb.httpd/src/mime_encodings.h b/gb.httpd/src/mime_encodings.h new file mode 100644 index 000000000..b1130e7e0 --- /dev/null +++ b/gb.httpd/src/mime_encodings.h @@ -0,0 +1,8 @@ +{ +"Z", 0, "compress", 0}, + +{ +"gz", 0, "gzip", 0}, + +{ +"uu", 0, "x-uuencode", 0}, diff --git a/gb.httpd/src/mime_types.h b/gb.httpd/src/mime_types.h new file mode 100644 index 000000000..e049fc4ca --- /dev/null +++ b/gb.httpd/src/mime_types.h @@ -0,0 +1,569 @@ +{ +"a", 0, "application/octet-stream", 0}, + +{ +"aab", 0, "application/x-authorware-bin", 0}, + +{ +"aam", 0, "application/x-authorware-map", 0}, + +{ +"aas", 0, "application/x-authorware-seg", 0}, + +{ +"ai", 0, "application/postscript", 0}, + +{ +"aif", 0, "audio/x-aiff", 0}, + +{ +"aifc", 0, "audio/x-aiff", 0}, + +{ +"aiff", 0, "audio/x-aiff", 0}, + +{ +"asc", 0, "text/plain", 0}, + +{ +"asf", 0, "video/x-ms-asf", 0}, + +{ +"asx", 0, "video/x-ms-asf", 0}, + +{ +"au", 0, "audio/basic", 0}, + +{ +"avi", 0, "video/x-msvideo", 0}, + +{ +"bcpio", 0, "application/x-bcpio", 0}, + +{ +"bin", 0, "application/octet-stream", 0}, + +{ +"bmp", 0, "image/bmp", 0}, + +{ +"cdf", 0, "application/x-netcdf", 0}, + +{ +"class", 0, "application/x-java-vm", 0}, + +{ +"cpio", 0, "application/x-cpio", 0}, + +{ +"cpt", 0, "application/mac-compactpro", 0}, + +{ +"crl", 0, "application/x-pkcs7-crl", 0}, + +{ +"crt", 0, "application/x-x509-ca-cert", 0}, + +{ +"csh", 0, "application/x-csh", 0}, + +{ +"css", 0, "text/css", 0}, + +{ +"dcr", 0, "application/x-director", 0}, + +{ +"dir", 0, "application/x-director", 0}, + +{ +"djv", 0, "image/vnd.djvu", 0}, + +{ +"djvu", 0, "image/vnd.djvu", 0}, + +{ +"dll", 0, "application/octet-stream", 0}, + +{ +"dms", 0, "application/octet-stream", 0}, + +{ +"doc", 0, "application/msword", 0}, + +{ +"dtd", 0, "text/xml", 0}, + +{ +"dump", 0, "application/octet-stream", 0}, + +{ +"dvi", 0, "application/x-dvi", 0}, + +{ +"dxr", 0, "application/x-director", 0}, + +{ +"eps", 0, "application/postscript", 0}, + +{ +"etx", 0, "text/x-setext", 0}, + +{ +"exe", 0, "application/octet-stream", 0}, + +{ +"ez", 0, "application/andrew-inset", 0}, + +{ +"fgd", 0, "application/x-director", 0}, + +{ +"fh", 0, "image/x-freehand", 0}, + +{ +"fh4", 0, "image/x-freehand", 0}, + +{ +"fh5", 0, "image/x-freehand", 0}, + +{ +"fh7", 0, "image/x-freehand", 0}, + +{ +"fhc", 0, "image/x-freehand", 0}, + +{ +"gif", 0, "image/gif", 0}, + +{ +"gtar", 0, "application/x-gtar", 0}, + +{ +"hdf", 0, "application/x-hdf", 0}, + +{ +"hqx", 0, "application/mac-binhex40", 0}, + +{ +"htm", 0, "text/html; charset=%s", 0}, + +{ +"html", 0, "text/html; charset=%s", 0}, + +{ +"ice", 0, "x-conference/x-cooltalk", 0}, + +{ +"ief", 0, "image/ief", 0}, + +{ +"iges", 0, "model/iges", 0}, + +{ +"igs", 0, "model/iges", 0}, + +{ +"iv", 0, "application/x-inventor", 0}, + +{ +"jar", 0, "application/x-java-archive", 0}, + +{ +"jfif", 0, "image/jpeg", 0}, + +{ +"jpe", 0, "image/jpeg", 0}, + +{ +"jpeg", 0, "image/jpeg", 0}, + +{ +"jpg", 0, "image/jpeg", 0}, + +{ +"js", 0, "application/x-javascript", 0}, + +{ +"kar", 0, "audio/midi", 0}, + +{ +"latex", 0, "application/x-latex", 0}, + +{ +"lha", 0, "application/octet-stream", 0}, + +{ +"lzh", 0, "application/octet-stream", 0}, + +{ +"m3u", 0, "audio/x-mpegurl", 0}, + +{ +"man", 0, "application/x-troff-man", 0}, + +{ +"mathml", 0, "application/mathml+xml", 0}, + +{ +"me", 0, "application/x-troff-me", 0}, + +{ +"mesh", 0, "model/mesh", 0}, + +{ +"mid", 0, "audio/midi", 0}, + +{ +"midi", 0, "audio/midi", 0}, + +{ +"mif", 0, "application/vnd.mif", 0}, + +{ +"mime", 0, "message/rfc822", 0}, + +{ +"mml", 0, "application/mathml+xml", 0}, + +{ +"mov", 0, "video/quicktime", 0}, + +{ +"movie", 0, "video/x-sgi-movie", 0}, + +{ +"mp2", 0, "audio/mpeg", 0}, + +{ +"mp3", 0, "audio/mpeg", 0}, + +{ +"mp4", 0, "video/mp4", 0}, + +{ +"mpe", 0, "video/mpeg", 0}, + +{ +"mpeg", 0, "video/mpeg", 0}, + +{ +"mpg", 0, "video/mpeg", 0}, + +{ +"mpga", 0, "audio/mpeg", 0}, + +{ +"ms", 0, "application/x-troff-ms", 0}, + +{ +"msh", 0, "model/mesh", 0}, + +{ +"mv", 0, "video/x-sgi-movie", 0}, + +{ +"mxu", 0, "video/vnd.mpegurl", 0}, + +{ +"nc", 0, "application/x-netcdf", 0}, + +{ +"o", 0, "application/octet-stream", 0}, + +{ +"oda", 0, "application/oda", 0}, + +{ +"ogg", 0, "application/x-ogg", 0}, + +{ +"pac", 0, "application/x-ns-proxy-autoconfig", 0}, + +{ +"pbm", 0, "image/x-portable-bitmap", 0}, + +{ +"pdb", 0, "chemical/x-pdb", 0}, + +{ +"pdf", 0, "application/pdf", 0}, + +{ +"pgm", 0, "image/x-portable-graymap", 0}, + +{ +"pgn", 0, "application/x-chess-pgn", 0}, + +{ +"png", 0, "image/png", 0}, + +{ +"pnm", 0, "image/x-portable-anymap", 0}, + +{ +"ppm", 0, "image/x-portable-pixmap", 0}, + +{ +"ppt", 0, "application/vnd.ms-powerpoint", 0}, + +{ +"ps", 0, "application/postscript", 0}, + +{ +"qt", 0, "video/quicktime", 0}, + +{ +"ra", 0, "audio/x-realaudio", 0}, + +{ +"ram", 0, "audio/x-pn-realaudio", 0}, + +{ +"ras", 0, "image/x-cmu-raster", 0}, + +{ +"rdf", 0, "application/rdf+xml", 0}, + +{ +"rgb", 0, "image/x-rgb", 0}, + +{ +"rm", 0, "audio/x-pn-realaudio", 0}, + +{ +"roff", 0, "application/x-troff", 0}, + +{ +"rpm", 0, "audio/x-pn-realaudio-plugin", 0}, + +{ +"rss", 0, "application/rss+xml", 0}, + +{ +"rtf", 0, "text/rtf", 0}, + +{ +"rtx", 0, "text/richtext", 0}, + +{ +"sgm", 0, "text/sgml", 0}, + +{ +"sgml", 0, "text/sgml", 0}, + +{ +"sh", 0, "application/x-sh", 0}, + +{ +"shar", 0, "application/x-shar", 0}, + +{ +"silo", 0, "model/mesh", 0}, + +{ +"sit", 0, "application/x-stuffit", 0}, + +{ +"skd", 0, "application/x-koan", 0}, + +{ +"skm", 0, "application/x-koan", 0}, + +{ +"skp", 0, "application/x-koan", 0}, + +{ +"skt", 0, "application/x-koan", 0}, + +{ +"smi", 0, "application/smil", 0}, + +{ +"smil", 0, "application/smil", 0}, + +{ +"snd", 0, "audio/basic", 0}, + +{ +"so", 0, "application/octet-stream", 0}, + +{ +"spl", 0, "application/x-futuresplash", 0}, + +{ +"src", 0, "application/x-wais-source", 0}, + +{ +"stc", 0, "application/vnd.sun.xml.calc.template", 0}, + +{ +"std", 0, "application/vnd.sun.xml.draw.template", 0}, + +{ +"sti", 0, "application/vnd.sun.xml.impress.template", 0}, + +{ +"stw", 0, "application/vnd.sun.xml.writer.template", 0}, + +{ +"sv4cpio", 0, "application/x-sv4cpio", 0}, + +{ +"sv4crc", 0, "application/x-sv4crc", 0}, + +{ +"svg", 0, "image/svg+xml", 0}, + +{ +"svgz", 0, "image/svg+xml", 0}, + +{ +"swf", 0, "application/x-shockwave-flash", 0}, + +{ +"sxc", 0, "application/vnd.sun.xml.calc", 0}, + +{ +"sxd", 0, "application/vnd.sun.xml.draw", 0}, + +{ +"sxg", 0, "application/vnd.sun.xml.writer.global", 0}, + +{ +"sxi", 0, "application/vnd.sun.xml.impress", 0}, + +{ +"sxm", 0, "application/vnd.sun.xml.math", 0}, + +{ +"sxw", 0, "application/vnd.sun.xml.writer", 0}, + +{ +"t", 0, "application/x-troff", 0}, + +{ +"tar", 0, "application/x-tar", 0}, + +{ +"tcl", 0, "application/x-tcl", 0}, + +{ +"tex", 0, "application/x-tex", 0}, + +{ +"texi", 0, "application/x-texinfo", 0}, + +{ +"texinfo", 0, "application/x-texinfo", 0}, + +{ +"tif", 0, "image/tiff", 0}, + +{ +"tiff", 0, "image/tiff", 0}, + +{ +"tr", 0, "application/x-troff", 0}, + +{ +"tsp", 0, "application/dsptype", 0}, + +{ +"tsv", 0, "text/tab-separated-values", 0}, + +{ +"txt", 0, "text/plain; charset=%s", 0}, + +{ +"ustar", 0, "application/x-ustar", 0}, + +{ +"vcd", 0, "application/x-cdlink", 0}, + +{ +"vrml", 0, "model/vrml", 0}, + +{ +"vx", 0, "video/x-rad-screenplay", 0}, + +{ +"wav", 0, "audio/x-wav", 0}, + +{ +"wax", 0, "audio/x-ms-wax", 0}, + +{ +"wbmp", 0, "image/vnd.wap.wbmp", 0}, + +{ +"wbxml", 0, "application/vnd.wap.wbxml", 0}, + +{ +"wm", 0, "video/x-ms-wm", 0}, + +{ +"wma", 0, "audio/x-ms-wma", 0}, + +{ +"wmd", 0, "application/x-ms-wmd", 0}, + +{ +"wml", 0, "text/vnd.wap.wml", 0}, + +{ +"wmlc", 0, "application/vnd.wap.wmlc", 0}, + +{ +"wmls", 0, "text/vnd.wap.wmlscript", 0}, + +{ +"wmlsc", 0, "application/vnd.wap.wmlscriptc", 0}, + +{ +"wmv", 0, "video/x-ms-wmv", 0}, + +{ +"wmx", 0, "video/x-ms-wmx", 0}, + +{ +"wmz", 0, "application/x-ms-wmz", 0}, + +{ +"wrl", 0, "model/vrml", 0}, + +{ +"wsrc", 0, "application/x-wais-source", 0}, + +{ +"wvx", 0, "video/x-ms-wvx", 0}, + +{ +"xbm", 0, "image/x-xbitmap", 0}, + +{ +"xht", 0, "application/xhtml+xml", 0}, + +{ +"xhtml", 0, "application/xhtml+xml", 0}, + +{ +"xls", 0, "application/vnd.ms-excel", 0}, + +{ +"xml", 0, "text/xml", 0}, + +{ +"xpm", 0, "image/x-xpixmap", 0}, + +{ +"xsl", 0, "text/xml", 0}, + +{ +"xwd", 0, "image/x-xwindowdump", 0}, + +{ +"xyz", 0, "chemical/x-xyz", 0}, + +{ +"zip", 0, "application/zip", 0}, diff --git a/gb.httpd/src/mmc.c b/gb.httpd/src/mmc.c new file mode 100644 index 000000000..113108776 --- /dev/null +++ b/gb.httpd/src/mmc.c @@ -0,0 +1,521 @@ +/* mmc.c - mmap cache +** +** Copyright 1998,2001 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#include "thttpd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_MMAP +#include +#endif /* HAVE_MMAP */ + +#include "mmc.h" +#include "libhttpd.h" + +#ifndef HAVE_INT64T +typedef long long int64_t; +#endif + + +/* Defines. */ +#ifndef DEFAULT_EXPIRE_AGE +#define DEFAULT_EXPIRE_AGE 600 +#endif +#ifndef DESIRED_FREE_COUNT +#define DESIRED_FREE_COUNT 100 +#endif +#ifndef DESIRED_MAX_MAPPED_FILES +#define DESIRED_MAX_MAPPED_FILES 2000 +#endif +#ifndef DESIRED_MAX_MAPPED_BYTES +#define DESIRED_MAX_MAPPED_BYTES 1000000000 +#endif +#ifndef INITIAL_HASH_SIZE +#define INITIAL_HASH_SIZE (1 << 10) +#endif + +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif + + +/* The Map struct. */ +typedef struct MapStruct +{ + ino_t ino; + dev_t dev; + off_t size; + time_t ctime; + int refcount; + time_t reftime; + void *addr; + unsigned int hash; + int hash_idx; + struct MapStruct *next; +} Map; + + +/* Globals. */ +static Map *maps = (Map *) 0; +static Map *free_maps = (Map *) 0; +static int alloc_count = 0, map_count = 0, free_count = 0; +static Map **hash_table = (Map **) 0; +static int hash_size; +static unsigned int hash_mask; +static time_t expire_age = DEFAULT_EXPIRE_AGE; +static off_t mapped_bytes = 0; + + + +/* Forwards. */ +static void panic (void); +static void really_unmap (Map ** mm); +static int check_hash_size (void); +static int add_hash (Map * m); +static Map *find_hash (ino_t ino, dev_t dev, off_t size, time_t ctime); +static unsigned int hash (ino_t ino, dev_t dev, off_t size, time_t ctime); + + +void *mmc_map (char *filename, struct stat *sbP, struct timeval *nowP) +{ + time_t now; + struct stat sb; + Map *m; + int fd; + + /* Stat the file, if necessary. */ + if (sbP != (struct stat *) 0) + sb = *sbP; + else + { + if (stat (filename, &sb) != 0) + { + syslog (LOG_ERR, "stat - %m"); + return (void *) 0; + } + } + + /* Get the current time, if necessary. */ + if (nowP != (struct timeval *) 0) + now = nowP->tv_sec; + else + now = time ((time_t *) 0); + + /* See if we have it mapped already, via the hash table. */ + if (check_hash_size () < 0) + { + syslog (LOG_ERR, "check_hash_size() failure"); + return (void *) 0; + } + m = find_hash (sb.st_ino, sb.st_dev, sb.st_size, sb.st_ctime); + if (m != (Map *) 0) + { + /* Yep. Just return the existing map */ + ++m->refcount; + m->reftime = now; + return m->addr; + } + + /* Open the file. */ + fd = open (filename, O_RDONLY); + if (fd < 0) + { + syslog (LOG_ERR, "open - %m"); + return (void *) 0; + } + + /* Find a free Map entry or make a new one. */ + if (free_maps != (Map *) 0) + { + m = free_maps; + free_maps = m->next; + --free_count; + } + else + { + m = (Map *) malloc (sizeof (Map)); + if (m == (Map *) 0) + { + (void) close (fd); + syslog (LOG_ERR, "out of memory allocating a Map"); + return (void *) 0; + } + ++alloc_count; + } + + /* Fill in the Map entry. */ + m->ino = sb.st_ino; + m->dev = sb.st_dev; + m->size = sb.st_size; + m->ctime = sb.st_ctime; + m->refcount = 1; + m->reftime = now; + + /* Avoid doing anything for zero-length files; some systems don't like + ** to mmap them, other systems dislike mallocing zero bytes. + */ + if (m->size == 0) + m->addr = (void *) 1; /* arbitrary non-NULL address */ + else + { + size_t size_size = (size_t) m->size; /* loses on files >2GB */ +#ifdef HAVE_MMAP + /* Map the file into memory. */ + m->addr = mmap (0, size_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (m->addr == (void *) -1 && errno == ENOMEM) + { + /* Ooo, out of address space. Free all unreferenced maps + ** and try again. + */ + panic (); + m->addr = mmap (0, size_size, PROT_READ, MAP_PRIVATE, fd, 0); + } + if (m->addr == (void *) -1) + { + syslog (LOG_ERR, "mmap - %m"); + (void) close (fd); + free ((void *) m); + --alloc_count; + return (void *) 0; + } +#else /* HAVE_MMAP */ + /* Read the file into memory. */ + m->addr = (void *) malloc (size_size); + if (m->addr == (void *) 0) + { + /* Ooo, out of memory. Free all unreferenced maps + ** and try again. + */ + panic (); + m->addr = (void *) malloc (size_size); + } + if (m->addr == (void *) 0) + { + syslog (LOG_ERR, "out of memory storing a file"); + (void) close (fd); + free ((void *) m); + --alloc_count; + return (void *) 0; + } + if (httpd_read_fully (fd, m->addr, size_size) != size_size) + { + syslog (LOG_ERR, "read - %m"); + (void) close (fd); + free ((void *) m); + --alloc_count; + return (void *) 0; + } +#endif /* HAVE_MMAP */ + } + (void) close (fd); + + /* Put the Map into the hash table. */ + if (add_hash (m) < 0) + { + syslog (LOG_ERR, "add_hash() failure"); + free ((void *) m); + --alloc_count; + return (void *) 0; + } + + /* Put the Map on the active list. */ + m->next = maps; + maps = m; + ++map_count; + + /* Update the total byte count. */ + mapped_bytes += m->size; + + /* And return the address. */ + return m->addr; +} + + +void mmc_unmap (void *addr, struct stat *sbP, struct timeval *nowP) +{ + Map *m = (Map *) 0; + + /* Find the Map entry for this address. First try a hash. */ + if (sbP != (struct stat *) 0) + { + m = find_hash (sbP->st_ino, sbP->st_dev, sbP->st_size, sbP->st_ctime); + if (m != (Map *) 0 && m->addr != addr) + m = (Map *) 0; + } + /* If that didn't work, try a full search. */ + if (m == (Map *) 0) + for (m = maps; m != (Map *) 0; m = m->next) + if (m->addr == addr) + break; + if (m == (Map *) 0) + syslog (LOG_ERR, "mmc_unmap failed to find entry!"); + else if (m->refcount <= 0) + syslog (LOG_ERR, "mmc_unmap found zero or negative refcount!"); + else + { + --m->refcount; + if (nowP != (struct timeval *) 0) + m->reftime = nowP->tv_sec; + else + m->reftime = time ((time_t *) 0); + } +} + + +void mmc_cleanup (struct timeval *nowP) +{ + time_t now; + Map **mm; + Map *m; + + /* Get the current time, if necessary. */ + if (nowP != (struct timeval *) 0) + now = nowP->tv_sec; + else + now = time ((time_t *) 0); + + /* Really unmap any unreferenced entries older than the age limit. */ + for (mm = &maps; *mm != (Map *) 0;) + { + m = *mm; + if (m->refcount == 0 && now - m->reftime >= expire_age) + really_unmap (mm); + else + mm = &(*mm)->next; + } + + /* Adjust the age limit if there are too many bytes mapped, or + ** too many or too few files mapped. + */ + if (mapped_bytes > DESIRED_MAX_MAPPED_BYTES) + expire_age = MAX ((expire_age * 2) / 3, DEFAULT_EXPIRE_AGE / 10); + else if (map_count > DESIRED_MAX_MAPPED_FILES) + expire_age = MAX ((expire_age * 2) / 3, DEFAULT_EXPIRE_AGE / 10); + else if (map_count < DESIRED_MAX_MAPPED_FILES / 2) + expire_age = MIN ((expire_age * 5) / 4, DEFAULT_EXPIRE_AGE * 3); + + /* Really free excess blocks on the free list. */ + while (free_count > DESIRED_FREE_COUNT) + { + m = free_maps; + free_maps = m->next; + --free_count; + free ((void *) m); + --alloc_count; + } +} + + +static void panic (void) +{ + Map **mm; + Map *m; + + syslog (LOG_ERR, "mmc panic - freeing all unreferenced maps"); + + /* Really unmap all unreferenced entries. */ + for (mm = &maps; *mm != (Map *) 0;) + { + m = *mm; + if (m->refcount == 0) + really_unmap (mm); + else + mm = &(*mm)->next; + } +} + + +static void really_unmap (Map ** mm) +{ + Map *m; + + m = *mm; + if (m->size != 0) + { +#ifdef HAVE_MMAP + if (munmap (m->addr, m->size) < 0) + syslog (LOG_ERR, "munmap - %m"); +#else /* HAVE_MMAP */ + free ((void *) m->addr); +#endif /* HAVE_MMAP */ + } + /* Update the total byte count. */ + mapped_bytes -= m->size; + /* And move the Map to the free list. */ + *mm = m->next; + --map_count; + m->next = free_maps; + free_maps = m; + ++free_count; + /* This will sometimes break hash chains, but that's harmless; the + ** unmapping code that searches the hash table knows to keep searching. + */ + hash_table[m->hash_idx] = (Map *) 0; +} + + +void mmc_destroy (void) +{ + Map *m; + + while (maps != (Map *) 0) + really_unmap (&maps); + while (free_maps != (Map *) 0) + { + m = free_maps; + free_maps = m->next; + --free_count; + free ((void *) m); + --alloc_count; + } +} + + +/* Make sure the hash table is big enough. */ +static int check_hash_size (void) +{ + int i; + Map *m; + + /* Are we just starting out? */ + if (hash_table == (Map **) 0) + { + hash_size = INITIAL_HASH_SIZE; + hash_mask = hash_size - 1; + } + /* Is it at least three times bigger than the number of entries? */ + else if (hash_size >= map_count * 3) + return 0; + else + { + /* No, got to expand. */ + free ((void *) hash_table); + /* Double the hash size until it's big enough. */ + do + { + hash_size = hash_size << 1; + } + while (hash_size < map_count * 6); + hash_mask = hash_size - 1; + } + /* Make the new table. */ + hash_table = (Map **) malloc (hash_size * sizeof (Map *)); + if (hash_table == (Map **) 0) + return -1; + /* Clear it. */ + for (i = 0; i < hash_size; ++i) + hash_table[i] = (Map *) 0; + /* And rehash all entries. */ + for (m = maps; m != (Map *) 0; m = m->next) + if (add_hash (m) < 0) + return -1; + return 0; +} + + +static int add_hash (Map * m) +{ + unsigned int h, he, i; + + h = hash (m->ino, m->dev, m->size, m->ctime); + he = (h + hash_size - 1) & hash_mask; + for (i = h;; i = (i + 1) & hash_mask) + { + if (hash_table[i] == (Map *) 0) + { + hash_table[i] = m; + m->hash = h; + m->hash_idx = i; + return 0; + } + if (i == he) + break; + } + return -1; +} + + +static Map *find_hash (ino_t ino, dev_t dev, off_t size, time_t ctime) +{ + unsigned int h, he, i; + Map *m; + + h = hash (ino, dev, size, ctime); + he = (h + hash_size - 1) & hash_mask; + for (i = h;; i = (i + 1) & hash_mask) + { + m = hash_table[i]; + if (m == (Map *) 0) + break; + if (m->hash == h && m->ino == ino && m->dev == dev && + m->size == size && m->ctime == ctime) + return m; + if (i == he) + break; + } + return (Map *) 0; +} + + +static unsigned int hash (ino_t ino, dev_t dev, off_t size, time_t ctime) +{ + unsigned int h = 177573; + + h ^= ino; + h += h << 5; + h ^= dev; + h += h << 5; + h ^= size; + h += h << 5; + h ^= ctime; + + return h & hash_mask; +} + + +/* Generate debugging statistics syslog message. */ +void mmc_logstats (long secs) +{ + syslog (LOG_INFO, + " map cache - %d allocated, %d active (%ld bytes), %d free; hash size: %d; expire age: %ld", + alloc_count, map_count, (int64_t) mapped_bytes, free_count, + hash_size, expire_age); + if (map_count + free_count != alloc_count) + syslog (LOG_ERR, "map counts don't add up!"); +} diff --git a/gb.httpd/src/mmc.h b/gb.httpd/src/mmc.h new file mode 100644 index 000000000..6fa7087a1 --- /dev/null +++ b/gb.httpd/src/mmc.h @@ -0,0 +1,55 @@ +/* mmc.h - header file for mmap cache package +** +** Copyright © 1998 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#ifndef _MMC_H_ +#define _MMC_H_ + +/* Returns an mmap()ed area for the given file, or (void*) 0 on errors. +** If you have a stat buffer on the file, pass it in, otherwise pass 0. +** Same for the current time. +*/ +extern void *mmc_map (char *filename, struct stat *sbP, struct timeval *nowP); + +/* Done with an mmap()ed area that was returned by mmc_map(). +** If you have a stat buffer on the file, pass it in, otherwise pass 0. +** Same for the current time. +*/ +extern void mmc_unmap (void *addr, struct stat *sbP, struct timeval *nowP); + +/* Clean up the mmc package, freeing any unused storage. +** This should be called periodically, say every five minutes. +** If you have the current time, pass it in, otherwise pass 0. +*/ +extern void mmc_cleanup (struct timeval *nowP); + +/* Free all storage, usually in preparation for exitting. */ +extern void mmc_destroy (void); + +/* Generate debugging statistics syslog message. */ +extern void mmc_logstats (long secs); + +#endif /* _MMC_H_ */ diff --git a/gb.httpd/src/strerror.c b/gb.httpd/src/strerror.c new file mode 100644 index 000000000..26e0e7936 --- /dev/null +++ b/gb.httpd/src/strerror.c @@ -0,0 +1,37 @@ +/* + * Copyright (c) 1988 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by the University of California, Berkeley. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)strerror.c 5.1 (Berkeley) 4/9/89"; +#endif /* LIBC_SCCS and not lint */ + +#include + +#include + +char *strerror (errnum) + int errnum; +{ + extern int sys_nerr; + extern char *sys_errlist[]; + static char ebuf[20]; + + if ((unsigned int) errnum < sys_nerr) + return (sys_errlist[errnum]); + (void) sprintf (ebuf, "Unknown error: %d", errnum); + return (ebuf); +} diff --git a/gb.httpd/src/tdate_parse.c b/gb.httpd/src/tdate_parse.c new file mode 100644 index 000000000..212e3d6eb --- /dev/null +++ b/gb.httpd/src/tdate_parse.c @@ -0,0 +1,313 @@ +/* tdate_parse - parse string dates into internal form, stripped-down version +** +** Copyright © 1995 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +/* This is a stripped-down version of date_parse.c, available at +** http://www.acme.com/software/date_parse/ +*/ + +#include + +#include +#ifdef HAVE_MEMORY_H +#include +#endif +#include +#include +#include +#include + +#include "tdate_parse.h" + + +struct strlong +{ + char *s; + long l; +}; + + +static void pound_case (char *str) +{ + for (; *str != '\0'; ++str) + { + if (isupper ((int) *str)) + *str = tolower ((int) *str); + } +} + +static int strlong_compare (v1, v2) + char *v1; + char *v2; +{ + return strcmp (((struct strlong *) v1)->s, ((struct strlong *) v2)->s); +} + + +static int strlong_search (char *str, struct strlong *tab, int n, long *lP) +{ + int i, h, l, r; + + l = 0; + h = n - 1; + for (;;) + { + i = (h + l) / 2; + r = strcmp (str, tab[i].s); + if (r < 0) + h = i - 1; + else if (r > 0) + l = i + 1; + else + { + *lP = tab[i].l; + return 1; + } + if (h < l) + return 0; + } +} + + +static int scan_wday (char *str_wday, long *tm_wdayP) +{ + static struct strlong wday_tab[] = { + {"sun", 0}, {"sunday", 0}, + {"mon", 1}, {"monday", 1}, + {"tue", 2}, {"tuesday", 2}, + {"wed", 3}, {"wednesday", 3}, + {"thu", 4}, {"thursday", 4}, + {"fri", 5}, {"friday", 5}, + {"sat", 6}, {"saturday", 6}, + }; + static int sorted = 0; + + if (!sorted) + { + (void) qsort (wday_tab, sizeof (wday_tab) / sizeof (struct strlong), + sizeof (struct strlong), strlong_compare); + sorted = 1; + } + pound_case (str_wday); + return strlong_search (str_wday, wday_tab, + sizeof (wday_tab) / sizeof (struct strlong), + tm_wdayP); +} + + +static int scan_mon (char *str_mon, long *tm_monP) +{ + static struct strlong mon_tab[] = { + {"jan", 0}, {"january", 0}, + {"feb", 1}, {"february", 1}, + {"mar", 2}, {"march", 2}, + {"apr", 3}, {"april", 3}, + {"may", 4}, + {"jun", 5}, {"june", 5}, + {"jul", 6}, {"july", 6}, + {"aug", 7}, {"august", 7}, + {"sep", 8}, {"september", 8}, + {"oct", 9}, {"october", 9}, + {"nov", 10}, {"november", 10}, + {"dec", 11}, {"december", 11}, + }; + static int sorted = 0; + + if (!sorted) + { + (void) qsort (mon_tab, sizeof (mon_tab) / sizeof (struct strlong), + sizeof (struct strlong), strlong_compare); + sorted = 1; + } + pound_case (str_mon); + return strlong_search (str_mon, mon_tab, + sizeof (mon_tab) / sizeof (struct strlong), tm_monP); +} + + +static int is_leap (int year) +{ + return year % 400 ? (year % 100 ? (year % 4 ? 0 : 1) : 0) : 1; +} + + +/* Basically the same as mktime(). */ +static time_t tm_to_time (struct tm *tmP) +{ + time_t t; + static int monthtab[12] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + + /* Years since epoch, converted to days. */ + t = (tmP->tm_year - 70) * 365; + /* Leap days for previous years. */ + t += (tmP->tm_year - 69) / 4; + /* Days for the beginning of this month. */ + t += monthtab[tmP->tm_mon]; + /* Leap day for this year. */ + if (tmP->tm_mon >= 2 && is_leap (tmP->tm_year + 1900)) + ++t; + /* Days since the beginning of this month. */ + t += tmP->tm_mday - 1; /* 1-based field */ + /* Hours, minutes, and seconds. */ + t = t * 24 + tmP->tm_hour; + t = t * 60 + tmP->tm_min; + t = t * 60 + tmP->tm_sec; + + return t; +} + + +time_t tdate_parse (char *str) +{ + struct tm tm; + char *cp; + char str_mon[500], str_wday[500]; + int tm_sec, tm_min, tm_hour, tm_mday, tm_year; + long tm_mon, tm_wday; + time_t t; + + /* Initialize. */ + (void) memset ((char *) &tm, 0, sizeof (struct tm)); + + /* Skip initial whitespace ourselves - sscanf is clumsy at this. */ + for (cp = str; *cp == ' ' || *cp == '\t'; ++cp) + continue; + + /* And do the sscanfs. WARNING: you can add more formats here, + ** but be careful! You can easily screw up the parsing of existing + ** formats when you add new ones. The order is important. + */ + + /* DD-mth-YY HH:MM:SS GMT */ + if (sscanf (cp, "%d-%400[a-zA-Z]-%d %d:%d:%d GMT", + &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, + &tm_sec) == 6 && scan_mon (str_mon, &tm_mon)) + { + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } + + /* DD mth YY HH:MM:SS GMT */ + else if (sscanf (cp, "%d %400[a-zA-Z] %d %d:%d:%d GMT", + &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, + &tm_sec) == 6 && scan_mon (str_mon, &tm_mon)) + { + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } + + /* HH:MM:SS GMT DD-mth-YY */ + else if (sscanf (cp, "%d:%d:%d GMT %d-%400[a-zA-Z]-%d", + &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, + &tm_year) == 6 && scan_mon (str_mon, &tm_mon)) + { + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + } + + /* HH:MM:SS GMT DD mth YY */ + else if (sscanf (cp, "%d:%d:%d GMT %d %400[a-zA-Z] %d", + &tm_hour, &tm_min, &tm_sec, &tm_mday, str_mon, + &tm_year) == 6 && scan_mon (str_mon, &tm_mon)) + { + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + } + + /* wdy, DD-mth-YY HH:MM:SS GMT */ + else if (sscanf (cp, "%400[a-zA-Z], %d-%400[a-zA-Z]-%d %d:%d:%d GMT", + str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, + &tm_sec) == 7 && + scan_wday (str_wday, &tm_wday) && scan_mon (str_mon, &tm_mon)) + { + tm.tm_wday = tm_wday; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } + + /* wdy, DD mth YY HH:MM:SS GMT */ + else if (sscanf (cp, "%400[a-zA-Z], %d %400[a-zA-Z] %d %d:%d:%d GMT", + str_wday, &tm_mday, str_mon, &tm_year, &tm_hour, &tm_min, + &tm_sec) == 7 && + scan_wday (str_wday, &tm_wday) && scan_mon (str_mon, &tm_mon)) + { + tm.tm_wday = tm_wday; + tm.tm_mday = tm_mday; + tm.tm_mon = tm_mon; + tm.tm_year = tm_year; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + } + + /* wdy mth DD HH:MM:SS GMT YY */ + else if (sscanf (cp, "%400[a-zA-Z] %400[a-zA-Z] %d %d:%d:%d GMT %d", + str_wday, str_mon, &tm_mday, &tm_hour, &tm_min, &tm_sec, + &tm_year) == 7 && + scan_wday (str_wday, &tm_wday) && scan_mon (str_mon, &tm_mon)) + { + tm.tm_wday = tm_wday; + tm.tm_mon = tm_mon; + tm.tm_mday = tm_mday; + tm.tm_hour = tm_hour; + tm.tm_min = tm_min; + tm.tm_sec = tm_sec; + tm.tm_year = tm_year; + } + else + return (time_t) - 1; + + if (tm.tm_year > 1900) + tm.tm_year -= 1900; + else if (tm.tm_year < 70) + tm.tm_year += 100; + + t = tm_to_time (&tm); + + return t; +} diff --git a/gb.httpd/src/tdate_parse.h b/gb.httpd/src/tdate_parse.h new file mode 100644 index 000000000..58811ca5b --- /dev/null +++ b/gb.httpd/src/tdate_parse.h @@ -0,0 +1,33 @@ +/* tdate_parse.h - parse string dates into internal form, stripped-down version +** +** Copyright © 1995 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#ifndef _TDATE_PARSE_H_ +#define _TDATE_PARSE_H_ + +extern time_t tdate_parse (char *str); + +#endif /* _TDATE_PARSE_H_ */ diff --git a/gb.httpd/src/thttpd.c b/gb.httpd/src/thttpd.c new file mode 100644 index 000000000..29afd90b3 --- /dev/null +++ b/gb.httpd/src/thttpd.c @@ -0,0 +1,2235 @@ +/* thttpd.c - tiny/turbo/throttling HTTP server +** +** Copyright (c) 1995,1998,1999,2000,2001 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#include "main.h" +#include "thttpd.h" +#include "version.h" + +#include +#include +#include +#include +#include +#include + +#include +#ifdef HAVE_FCNTL_H +#include +#endif +#include +#ifdef HAVE_GRP_H +#include +#endif +#include +#include +#include +#include +//#include +#ifdef TIME_WITH_SYS_TIME +#include +#endif +#include + +#include "fdwatch.h" +#include "libhttpd.h" +#include "mmc.h" +#include "timers.h" +#include "match.h" + +#ifndef SHUT_WR +#define SHUT_WR 1 +#endif + +#ifndef HAVE_INT64T +typedef long long int64_t; +#endif + + +static char *argv0; +static int debug; +static unsigned short port; +static char *dir; +static char *data_dir; +static int do_chroot, no_log, no_symlink_check, do_vhost, do_global_passwd; +static char *cgi_pattern; +static int cgi_limit; +static int cgi_timelimit; +static char *url_pattern; +static int no_empty_referers; +static char *local_pattern; +static char *logfile; +static char *throttlefile; +static char *hostname; +static char *pidfile; +static char *user; +static char *charset; +static char *p3p; +static int max_age; + + +typedef struct +{ + char *pattern; + long max_limit, min_limit; + long rate; + off_t bytes_since_avg; + int num_sending; +} throttletab; +static throttletab *throttles; +static int numthrottles, maxthrottles; + +#define THROTTLE_NOLIMIT -1 + + +typedef struct +{ + int conn_state; + int next_free_connect; + httpd_conn *hc; + int tnums[MAXTHROTTLENUMS]; /* throttle indexes */ + int numtnums; + long max_limit, min_limit; + time_t started_at, active_at; + Timer *wakeup_timer; + Timer *linger_timer; + long wouldblock_delay; + off_t bytes; + off_t end_byte_index; + off_t next_byte_index; +} connecttab; +static connecttab *connects; +static int num_connects, max_connects, first_free_connect; +static int httpd_conn_count; + +/* The connection states. */ +#define CNST_FREE 0 +#define CNST_READING 1 +#define CNST_SENDING 2 +#define CNST_PAUSING 3 +#define CNST_LINGERING 4 + + +static httpd_server *hs = (httpd_server *) 0; +int terminate = 0; +time_t start_time, stats_time; +long stats_connections; +off_t stats_bytes; +int stats_simultaneous; + +static volatile int got_hup, got_usr1, watchdog_flag; + + +/* Forwards. */ +static void parse_args (int argc, char **argv); +/*static void usage (void); +static void read_config (char *filename); +static void value_required (char *name, char *value); +static void no_value_required (char *name, char *value);*/ +static char *e_strdup (char *oldstr); +static void lookup_hostname (httpd_sockaddr * sa4P, size_t sa4_len, + int *gotv4P, httpd_sockaddr * sa6P, + size_t sa6_len, int *gotv6P); +static void read_throttlefile (char *throttlefile); +static void shut_down (void); +static int handle_newconnect (struct timeval *tvP, int listen_fd); +static void handle_read (connecttab * c, struct timeval *tvP); +static void handle_send (connecttab * c, struct timeval *tvP); +static void handle_linger (connecttab * c, struct timeval *tvP); +static int check_throttles (connecttab * c); +static void clear_throttles (connecttab * c, struct timeval *tvP); +static void update_throttles (ClientData client_data, struct timeval *nowP); +static void finish_connection (connecttab * c, struct timeval *tvP); +static void clear_connection (connecttab * c, struct timeval *tvP); +static void really_clear_connection (connecttab * c, struct timeval *tvP); +static void idle (ClientData client_data, struct timeval *nowP); +static void wakeup_connection (ClientData client_data, struct timeval *nowP); +static void linger_clear_connection (ClientData client_data, + struct timeval *nowP); +static void occasional (ClientData client_data, struct timeval *nowP); +#ifdef STATS_TIME +static void show_stats (ClientData client_data, struct timeval *nowP); +#endif /* STATS_TIME */ +static void logstats (struct timeval *nowP); +static void thttpd_logstats (long secs); + + +/* SIGTERM and SIGINT say to exit immediately. */ +static void handle_term (int sig) +{ + /* Don't need to set up the handler again, since it's a one-shot. */ + + shut_down (); + syslog (LOG_NOTICE, "exiting due to signal %d", sig); + closelog (); + exit (1); +} + + +/* SIGCHLD - a chile process exitted, so we need to reap the zombie */ +static void handle_chld (int sig) +{ + const int oerrno = errno; + pid_t pid; + int status; + +#ifndef HAVE_SIGSET + /* Set up handler again. */ + (void) signal (SIGCHLD, handle_chld); +#endif /* ! HAVE_SIGSET */ + + /* Reap defunct children until there aren't any more. */ + for (;;) + { +#ifdef HAVE_WAITPID + pid = waitpid ((pid_t) - 1, &status, WNOHANG); +#else /* HAVE_WAITPID */ + pid = wait3 (&status, WNOHANG, (struct rusage *) 0); +#endif /* HAVE_WAITPID */ + if ((int) pid == 0) /* none left */ + break; + if ((int) pid < 0) + { + if (errno == EINTR || errno == EAGAIN) + continue; + /* ECHILD shouldn't happen with the WNOHANG option, + ** but with some kernels it does anyway. Ignore it. + */ + if (errno != ECHILD) + syslog (LOG_ERR, "child wait - %m"); + break; + } + /* Decrement the CGI count. Note that this is not accurate, since + ** each CGI can involve two or even three child processes. + ** Decrementing for each child means that when there is heavy CGI + ** activity, the count will be lower than it should be, and therefore + ** more CGIs will be allowed than should be. + */ + if (hs != (httpd_server *) 0) + { + --hs->cgi_count; + if (hs->cgi_count < 0) + hs->cgi_count = 0; + } + } + + /* Restore previous errno. */ + errno = oerrno; +} + + +/* SIGHUP says to re-open the log file. */ +static void handle_hup (int sig) +{ + const int oerrno = errno; + +#ifndef HAVE_SIGSET + /* Set up handler again. */ + (void) signal (SIGHUP, handle_hup); +#endif /* ! HAVE_SIGSET */ + + /* Just set a flag that we got the signal. */ + got_hup = 1; + + /* Restore previous errno. */ + errno = oerrno; +} + + +/* SIGUSR1 says to exit as soon as all current connections are done. */ +static void handle_usr1 (int sig) +{ + /* Don't need to set up the handler again, since it's a one-shot. */ + + if (num_connects == 0) + { + /* If there are no active connections we want to exit immediately + ** here. Not only is it faster, but without any connections the + ** main loop won't wake up until the next new connection. + */ + shut_down (); + syslog (LOG_NOTICE, "exiting"); + closelog (); + exit (0); + } + + /* Otherwise, just set a flag that we got the signal. */ + got_usr1 = 1; + + /* Don't need to restore old errno, since we didn't do any syscalls. */ +} + + +/* SIGUSR2 says to generate the stats syslogs immediately. */ +static void handle_usr2 (int sig) +{ + const int oerrno = errno; + +#ifndef HAVE_SIGSET + /* Set up handler again. */ + (void) signal (SIGUSR2, handle_usr2); +#endif /* ! HAVE_SIGSET */ + + logstats ((struct timeval *) 0); + + /* Restore previous errno. */ + errno = oerrno; +} + + +/* SIGALRM is used as a watchdog. */ +static void handle_alrm (int sig) +{ + const int oerrno = errno; + + /* If nothing has been happening */ + if (!watchdog_flag) + { + /* Try changing dirs to someplace we can write. */ + (void) chdir ("/tmp"); + /* Dump core. */ + abort (); + } + watchdog_flag = 0; + +#ifndef HAVE_SIGSET + /* Set up handler again. */ + (void) signal (SIGALRM, handle_alrm); +#endif /* ! HAVE_SIGSET */ + /* Set up alarm again. */ + (void) alarm (OCCASIONAL_TIME * 3); + + /* Restore previous errno. */ + errno = oerrno; +} + + +static void re_open_logfile (void) +{ + FILE *logfp; + + if (no_log || hs == (httpd_server *) 0) + return; + + /* Re-open the log file. */ + if (logfile != (char *) 0 && strcmp (logfile, "-") != 0) + { + syslog (LOG_NOTICE, "re-opening logfile"); + logfp = fopen (logfile, "a"); + if (logfp == (FILE *) 0) + { + syslog (LOG_CRIT, "re-opening %.80s - %m", logfile); + return; + } + (void) fcntl (fileno (logfp), F_SETFD, 1); + httpd_set_logfp (hs, logfp); + } +} + + +int thttpd_main (int argc, char **argv) +{ + char *cp; + struct passwd *pwd; + uid_t uid = 32767; + gid_t gid = 32767; + char cwd[MAXPATHLEN + 1]; + FILE *logfp; + int num_ready; + int cnum; + connecttab *c; + httpd_conn *hc; + httpd_sockaddr sa4; + httpd_sockaddr sa6; + int gotv4, gotv6; + struct timeval tv; + + argv0 = argv[0]; + + cp = strrchr (argv0, '/'); + if (cp != (char *) 0) + ++cp; + else + cp = argv0; + + //openlog( cp, LOG_NDELAY|LOG_PID, LOG_FACILITY ); + + /* Handle command-line arguments. */ + parse_args (argc, argv); + debug = 1; + do_chroot = 0; + + /* Read zone info now, in case we chroot(). */ + tzset (); + + /* Look up hostname now, in case we chroot(). */ + lookup_hostname (&sa4, sizeof (sa4), &gotv4, &sa6, sizeof (sa6), &gotv6); + if (!(gotv4 || gotv6)) + { + syslog (LOG_ERR, "can't find any valid address"); + (void) fprintf (stderr, "%s: can't find any valid address\n", argv0); + exit (1); + } + + /* Throttle file. */ + numthrottles = 0; + maxthrottles = 0; + throttles = (throttletab *) 0; + if (throttlefile != (char *) 0) + read_throttlefile (throttlefile); + + /* If we're root and we're going to become another user, get the uid/gid + ** now. + */ + if (getuid () == 0) + { + pwd = getpwnam (user); + if (pwd == (struct passwd *) 0) + { + syslog (LOG_CRIT, "unknown user - '%.80s'", user); + (void) fprintf (stderr, "%s: unknown user - '%s'\n", argv0, user); + exit (1); + } + uid = pwd->pw_uid; + gid = pwd->pw_gid; + } + + /* Log file. */ +#if 0 + if (logfile != (char *) 0) + { + if (strcmp (logfile, "/dev/null") == 0) + { + no_log = 1; + logfp = (FILE *) 0; + } + else if (strcmp (logfile, "-") == 0) + logfp = stdout; + else + { + logfp = fopen (logfile, "a"); + if (logfp == (FILE *) 0) + { + syslog (LOG_CRIT, "%.80s - %m", logfile); + perror (logfile); + exit (1); + } + if (logfile[0] != '/') + { + syslog (LOG_WARNING, + "logfile is not an absolute path, you may not be able to re-open it"); + (void) fprintf (stderr, + "%s: logfile is not an absolute path, you may not be able to re-open it\n", + argv0); + } + (void) fcntl (fileno (logfp), F_SETFD, 1); + if (getuid () == 0) + { + /* If we are root then we chown the log file to the user we'll + ** be switching to. + */ + if (fchown (fileno (logfp), uid, gid) < 0) + { + syslog (LOG_WARNING, "fchown logfile - %m"); + perror ("fchown logfile"); + } + } + } + } + else +#endif + logfp = (FILE *) 0; + +#if 0 + /* Switch directories if requested. */ + if (dir != (char *) 0) + { + if (chdir (dir) < 0) + { + syslog (LOG_CRIT, "chdir - %m"); + perror ("chdir"); + exit (1); + } + } +#ifdef USE_USER_DIR + else if (getuid () == 0) + { + /* No explicit directory was specified, we're root, and the + ** USE_USER_DIR option is set - switch to the specified user's + ** home dir. + */ + if (chdir (pwd->pw_dir) < 0) + { + syslog (LOG_CRIT, "chdir - %m"); + perror ("chdir"); + exit (1); + } + } +#endif /* USE_USER_DIR */ +#endif + + /* Get current directory. */ + (void) getcwd (cwd, sizeof (cwd) - 1); + if (cwd[strlen (cwd) - 1] != '/') + (void) strcat (cwd, "/"); + + if (0) //!debug) + { + /* We're not going to use stdin stdout or stderr from here on, so close + ** them to save file descriptors. + */ + (void) fclose (stdin); + if (logfp != stdout) + (void) fclose (stdout); + (void) fclose (stderr); + + /* Daemonize - make ourselves a subprocess. */ +#ifdef HAVE_DAEMON + if (daemon (1, 1) < 0) + { + syslog (LOG_CRIT, "daemon - %m"); + exit (1); + } +#else /* HAVE_DAEMON */ + switch (fork ()) + { + case 0: + break; + case -1: + syslog (LOG_CRIT, "fork - %m"); + exit (1); + default: + exit (0); + } +#ifdef HAVE_SETSID + (void) setsid (); +#endif /* HAVE_SETSID */ +#endif /* HAVE_DAEMON */ + } + else + { + /* Even if we don't daemonize, we still want to disown our parent + ** process. + */ +#ifdef HAVE_SETSID + (void) setsid (); +#endif /* HAVE_SETSID */ + } + +#if 0 + if (pidfile != (char *) 0) + { + /* Write the PID file. */ + FILE *pidfp = fopen (pidfile, "w"); + if (pidfp == (FILE *) 0) + { + syslog (LOG_CRIT, "%.80s - %m", pidfile); + exit (1); + } + (void) fprintf (pidfp, "%d\n", (int) getpid ()); + (void) fclose (pidfp); + } +#endif + + /* Initialize the fdwatch package. Have to do this before chroot, + ** if /dev/poll is used. + */ + max_connects = fdwatch_get_nfiles (); + if (max_connects < 0) + { + syslog (LOG_CRIT, "fdwatch initialization failure"); + exit (1); + } + max_connects -= SPARE_FDS; + + /* Chroot if requested. */ + if (0) //do_chroot) + { + if (chroot (cwd) < 0) + { + syslog (LOG_CRIT, "chroot - %m"); + perror ("chroot"); + exit (1); + } + /* If we're logging and the logfile's pathname begins with the + ** chroot tree's pathname, then elide the chroot pathname so + ** that the logfile pathname still works from inside the chroot + ** tree. + */ + if (logfile != (char *) 0 && strcmp (logfile, "-") != 0) + { + if (strncmp (logfile, cwd, strlen (cwd)) == 0) + { + (void) strcpy (logfile, &logfile[strlen (cwd) - 1]); + /* (We already guaranteed that cwd ends with a slash, so leaving + ** that slash in logfile makes it an absolute pathname within + ** the chroot tree.) + */ + } + else + { + syslog (LOG_WARNING, + "logfile is not within the chroot tree, you will not be able to re-open it"); + (void) fprintf (stderr, + "%s: logfile is not within the chroot tree, you will not be able to re-open it\n", + argv0); + } + } + (void) strcpy (cwd, "/"); + /* Always chdir to / after a chroot. */ + if (chdir (cwd) < 0) + { + syslog (LOG_CRIT, "chroot chdir - %m"); + perror ("chroot chdir"); + exit (1); + } + } + + /* Switch directories again if requested. */ +#if 0 + if (data_dir != (char *) 0) + { + if (chdir (data_dir) < 0) + { + syslog (LOG_CRIT, "data_dir chdir - %m"); + perror ("data_dir chdir"); + exit (1); + } + } +#endif + + /* Set up to catch signals. */ +#ifdef HAVE_SIGSET + (void) sigset (SIGTERM, handle_term); + (void) sigset (SIGINT, handle_term); + (void) sigset (SIGCHLD, handle_chld); + (void) sigset (SIGPIPE, SIG_IGN); /* get EPIPE instead */ + (void) sigset (SIGHUP, handle_hup); + (void) sigset (SIGUSR1, handle_usr1); + (void) sigset (SIGUSR2, handle_usr2); + (void) sigset (SIGALRM, handle_alrm); +#else /* HAVE_SIGSET */ + (void) signal (SIGTERM, handle_term); + (void) signal (SIGINT, handle_term); + (void) signal (SIGCHLD, handle_chld); + (void) signal (SIGPIPE, SIG_IGN); /* get EPIPE instead */ + (void) signal (SIGHUP, handle_hup); + (void) signal (SIGUSR1, handle_usr1); + (void) signal (SIGUSR2, handle_usr2); + (void) signal (SIGALRM, handle_alrm); +#endif /* HAVE_SIGSET */ + got_hup = 0; + got_usr1 = 0; + watchdog_flag = 0; + (void) alarm (OCCASIONAL_TIME * 3); + + /* Initialize the timer package. */ + tmr_init (); + + /* Initialize the HTTP layer. Got to do this before giving up root, + ** so that we can bind to a privileged port. + */ + hs = httpd_initialize (hostname, + gotv4 ? &sa4 : (httpd_sockaddr *) 0, + gotv6 ? &sa6 : (httpd_sockaddr *) 0, port, + cgi_pattern, cgi_limit, cgi_timelimit, charset, p3p, + max_age, cwd, no_log, logfp, no_symlink_check, + do_vhost, do_global_passwd, url_pattern, + local_pattern, no_empty_referers); + if (hs == (httpd_server *) 0) + exit (1); + + /* Set up the occasional timer. */ + if (tmr_create + ((struct timeval *) 0, occasional, JunkClientData, + OCCASIONAL_TIME * 1000L, 1) == (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(occasional) failed"); + exit (1); + } + /* Set up the idle timer. */ + if (tmr_create ((struct timeval *) 0, idle, JunkClientData, 5 * 1000L, 1) == + (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(idle) failed"); + exit (1); + } + if (numthrottles > 0) + { + /* Set up the throttles timer. */ + if (tmr_create + ((struct timeval *) 0, update_throttles, JunkClientData, + THROTTLE_TIME * 1000L, 1) == (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(update_throttles) failed"); + exit (1); + } + } +#ifdef STATS_TIME + /* Set up the stats timer. */ + if (tmr_create + ((struct timeval *) 0, show_stats, JunkClientData, STATS_TIME * 1000L, + 1) == (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(show_stats) failed"); + exit (1); + } +#endif /* STATS_TIME */ + start_time = stats_time = time ((time_t *) 0); + stats_connections = 0; + stats_bytes = 0; + stats_simultaneous = 0; + + /* If we're root, try to become someone else. */ + if (getuid () == 0) + { + /* Set aux groups to null. */ + if (setgroups (0, (const gid_t *) 0) < 0) + { + syslog (LOG_CRIT, "setgroups - %m"); + exit (1); + } + /* Set primary group. */ + if (setgid (gid) < 0) + { + syslog (LOG_CRIT, "setgid - %m"); + exit (1); + } + /* Try setting aux groups correctly - not critical if this fails. */ + if (initgroups (user, gid) < 0) + syslog (LOG_WARNING, "initgroups - %m"); +#ifdef HAVE_SETLOGIN + /* Set login name. */ + (void) setlogin (user); +#endif /* HAVE_SETLOGIN */ + /* Set uid. */ + if (setuid (uid) < 0) + { + syslog (LOG_CRIT, "setuid - %m"); + exit (1); + } + /* Check for unnecessary security exposure. */ + if (!do_chroot) + syslog (LOG_WARNING, + "started as root without requesting chroot(), warning only"); + } + + /* Initialize our connections table. */ + connects = NEW (connecttab, max_connects); + if (connects == (connecttab *) 0) + { + syslog (LOG_CRIT, "out of memory allocating a connecttab"); + exit (1); + } + for (cnum = 0; cnum < max_connects; ++cnum) + { + connects[cnum].conn_state = CNST_FREE; + connects[cnum].next_free_connect = cnum + 1; + connects[cnum].hc = (httpd_conn *) 0; + } + connects[max_connects - 1].next_free_connect = -1; /* end of link list */ + first_free_connect = 0; + num_connects = 0; + httpd_conn_count = 0; + + if (hs != (httpd_server *) 0) + { + if (hs->listen4_fd != -1) + fdwatch_add_fd (hs->listen4_fd, (void *) 0, FDW_READ); + if (hs->listen6_fd != -1) + fdwatch_add_fd (hs->listen6_fd, (void *) 0, FDW_READ); + } + + /* Main loop. */ + (void) gettimeofday (&tv, (struct timezone *) 0); + while ((!terminate) || num_connects > 0) + { + /* Do we need to re-open the log file? */ + if (got_hup) + { + re_open_logfile (); + got_hup = 0; + } + + /* Do the fd watch. */ + num_ready = fdwatch (tmr_mstimeout (&tv)); + if (num_ready < 0) + { + if (errno == EINTR || errno == EAGAIN) + continue; /* try again */ + syslog (LOG_ERR, "fdwatch - %m"); + exit (1); + } + (void) gettimeofday (&tv, (struct timezone *) 0); + + if (num_ready == 0) + { + /* No fd's are ready - run the timers. */ + tmr_run (&tv); + continue; + } + + /* Is it a new connection? */ + if (hs != (httpd_server *) 0 && hs->listen6_fd != -1 && + fdwatch_check_fd (hs->listen6_fd)) + { + if (handle_newconnect (&tv, hs->listen6_fd)) + /* Go around the loop and do another fdwatch, rather than + ** dropping through and processing existing connections. + ** New connections always get priority. + */ + continue; + } + if (hs != (httpd_server *) 0 && hs->listen4_fd != -1 && + fdwatch_check_fd (hs->listen4_fd)) + { + if (handle_newconnect (&tv, hs->listen4_fd)) + /* Go around the loop and do another fdwatch, rather than + ** dropping through and processing existing connections. + ** New connections always get priority. + */ + continue; + } + + /* Find the connections that need servicing. */ + while ((c = + (connecttab *) fdwatch_get_next_client_data ()) != + (connecttab *) - 1) + { + if (c == (connecttab *) 0) + continue; + hc = c->hc; + if (!fdwatch_check_fd (hc->conn_fd)) + /* Something went wrong. */ + clear_connection (c, &tv); + else + switch (c->conn_state) + { + case CNST_READING: + handle_read (c, &tv); + break; + case CNST_SENDING: + handle_send (c, &tv); + break; + case CNST_LINGERING: + handle_linger (c, &tv); + break; + } + } + tmr_run (&tv); + + if (got_usr1 && !terminate) + { + terminate = 1; + if (hs != (httpd_server *) 0) + { + if (hs->listen4_fd != -1) + fdwatch_del_fd (hs->listen4_fd); + if (hs->listen6_fd != -1) + fdwatch_del_fd (hs->listen6_fd); + httpd_unlisten (hs); + } + } + } + + /* The main loop terminated. */ + shut_down (); + syslog (LOG_NOTICE, "exiting"); + //closelog(); + exit (0); +} + + +static void parse_args (int argc, char **argv) +{ + char *env; + int val; + + debug = 0; + port = DEFAULT_PORT; + dir = (char *) 0; + data_dir = (char *) 0; +#ifdef ALWAYS_CHROOT + do_chroot = 1; +#else /* ALWAYS_CHROOT */ + do_chroot = 0; +#endif /* ALWAYS_CHROOT */ + no_log = 0; + no_symlink_check = do_chroot; +#ifdef ALWAYS_VHOST + do_vhost = 1; +#else /* ALWAYS_VHOST */ + do_vhost = 0; +#endif /* ALWAYS_VHOST */ +#ifdef ALWAYS_GLOBAL_PASSWD + do_global_passwd = 1; +#else /* ALWAYS_GLOBAL_PASSWD */ + do_global_passwd = 0; +#endif /* ALWAYS_GLOBAL_PASSWD */ +#ifdef CGI_PATTERN + cgi_pattern = CGI_PATTERN; +#else /* CGI_PATTERN */ + cgi_pattern = (char *) 0; +#endif /* CGI_PATTERN */ +#ifdef CGI_LIMIT + cgi_limit = CGI_LIMIT; +#else /* CGI_LIMIT */ + cgi_limit = 0; +#endif /* CGI_LIMIT */ +#ifdef CGI_TIMELIMIT + cgi_timelimit = CGI_TIMELIMIT; +#else /* CGI_LIMIT */ + cgi_timelimit = 0; +#endif /* CGI_LIMIT */ + url_pattern = (char *) 0; + no_empty_referers = 0; + local_pattern = (char *) 0; + throttlefile = (char *) 0; + hostname = (char *) 0; + logfile = (char *) 0; + pidfile = (char *) 0; + user = DEFAULT_USER; + charset = DEFAULT_CHARSET; + p3p = ""; + max_age = -1; + + env = getenv("GB_HTTPD_PORT"); + if (env && *env) + { + port = (unsigned short)atoi(env); + if (port == 0) + port = 80; + } + + env = getenv("GB_HTTPD_TIMEOUT"); + if (env && *env) + { + val = atoi(env); + if (val == 0) + { + if (env[0] == '0' && env[1] == 0) + cgi_timelimit = 0; + } + else + cgi_timelimit = val; + } + +#if 0 + argn = 1; + while (argn < argc && argv[argn][0] == '-') + { + if (strcmp (argv[argn], "-V") == 0) + { + (void) printf ("%s\n", SERVER_SOFTWARE); + exit (0); + } + else if (strcmp (argv[argn], "-C") == 0 && argn + 1 < argc) + { + ++argn; + read_config (argv[argn]); + } + else if (strcmp (argv[argn], "-p") == 0 && argn + 1 < argc) + { + ++argn; + port = (unsigned short) atoi (argv[argn]); + } + else if (strcmp (argv[argn], "-d") == 0 && argn + 1 < argc) + { + ++argn; + dir = argv[argn]; + } + else if (strcmp (argv[argn], "-r") == 0) + { + do_chroot = 1; + no_symlink_check = 1; + } + else if (strcmp (argv[argn], "-nor") == 0) + { + do_chroot = 0; + no_symlink_check = 0; + } + else if (strcmp (argv[argn], "-dd") == 0 && argn + 1 < argc) + { + ++argn; + data_dir = argv[argn]; + } + else if (strcmp (argv[argn], "-s") == 0) + no_symlink_check = 0; + else if (strcmp (argv[argn], "-nos") == 0) + no_symlink_check = 1; + else if (strcmp (argv[argn], "-u") == 0 && argn + 1 < argc) + { + ++argn; + user = argv[argn]; + } + else if (strcmp (argv[argn], "-c") == 0 && argn + 1 < argc) + { + ++argn; + cgi_pattern = argv[argn]; + } + else if (strcmp (argv[argn], "-t") == 0 && argn + 1 < argc) + { + ++argn; + throttlefile = argv[argn]; + } + else if (strcmp (argv[argn], "-h") == 0 && argn + 1 < argc) + { + ++argn; + hostname = argv[argn]; + } + else if (strcmp (argv[argn], "-l") == 0 && argn + 1 < argc) + { + ++argn; + logfile = argv[argn]; + } + else if (strcmp (argv[argn], "-v") == 0) + do_vhost = 1; + else if (strcmp (argv[argn], "-nov") == 0) + do_vhost = 0; + else if (strcmp (argv[argn], "-g") == 0) + do_global_passwd = 1; + else if (strcmp (argv[argn], "-nog") == 0) + do_global_passwd = 0; + else if (strcmp (argv[argn], "-i") == 0 && argn + 1 < argc) + { + ++argn; + pidfile = argv[argn]; + } + else if (strcmp (argv[argn], "-T") == 0 && argn + 1 < argc) + { + ++argn; + charset = argv[argn]; + } + else if (strcmp (argv[argn], "-P") == 0 && argn + 1 < argc) + { + ++argn; + p3p = argv[argn]; + } + else if (strcmp (argv[argn], "-M") == 0 && argn + 1 < argc) + { + ++argn; + max_age = atoi (argv[argn]); + } + else if (strcmp (argv[argn], "-D") == 0) + debug = 1; + else + usage (); + ++argn; + } + if (argn != argc) + usage (); +#endif +} + +#if 0 +static void usage (void) +{ + (void) fprintf (stderr, + "usage: %s [-C configfile] [-p port] [-d dir] [-r|-nor] [-dd data_dir] [-s|-nos] [-v|-nov] [-g|-nog] [-u user] [-c cgipat] [-t throttles] [-h host] [-l logfile] [-i pidfile] [-T charset] [-P P3P] [-M maxage] [-V] [-D]\n", + argv0); + exit (1); +} +#endif + +#if 0 +static void read_config (char *filename) +{ + FILE *fp; + char line[10000]; + char *cp; + char *cp2; + char *name; + char *value; + + fp = fopen (filename, "r"); + if (fp == (FILE *) 0) + { + perror (filename); + exit (1); + } + + while (fgets (line, sizeof (line), fp) != (char *) 0) + { + /* Trim comments. */ + if ((cp = strchr (line, '#')) != (char *) 0) + *cp = '\0'; + + /* Skip leading whitespace. */ + cp = line; + cp += strspn (cp, " \t\n\r"); + + /* Split line into words. */ + while (*cp != '\0') + { + /* Find next whitespace. */ + cp2 = cp + strcspn (cp, " \t\n\r"); + /* Insert EOS and advance next-word pointer. */ + while (*cp2 == ' ' || *cp2 == '\t' || *cp2 == '\n' || *cp2 == '\r') + *cp2++ = '\0'; + /* Split into name and value. */ + name = cp; + value = strchr (name, '='); + if (value != (char *) 0) + *value++ = '\0'; + /* Interpret. */ + if (strcasecmp (name, "debug") == 0) + { + no_value_required (name, value); + debug = 1; + } + else if (strcasecmp (name, "port") == 0) + { + value_required (name, value); + port = (unsigned short) atoi (value); + } + else if (strcasecmp (name, "dir") == 0) + { + value_required (name, value); + dir = e_strdup (value); + } + else if (strcasecmp (name, "chroot") == 0) + { + no_value_required (name, value); + do_chroot = 1; + no_symlink_check = 1; + } + else if (strcasecmp (name, "nochroot") == 0) + { + no_value_required (name, value); + do_chroot = 0; + no_symlink_check = 0; + } + else if (strcasecmp (name, "data_dir") == 0) + { + value_required (name, value); + data_dir = e_strdup (value); + } + else if (strcasecmp (name, "symlink") == 0) + { + no_value_required (name, value); + no_symlink_check = 0; + } + else if (strcasecmp (name, "nosymlink") == 0) + { + no_value_required (name, value); + no_symlink_check = 1; + } + else if (strcasecmp (name, "symlinks") == 0) + { + no_value_required (name, value); + no_symlink_check = 0; + } + else if (strcasecmp (name, "nosymlinks") == 0) + { + no_value_required (name, value); + no_symlink_check = 1; + } + else if (strcasecmp (name, "user") == 0) + { + value_required (name, value); + user = e_strdup (value); + } + else if (strcasecmp (name, "cgipat") == 0) + { + value_required (name, value); + cgi_pattern = e_strdup (value); + } + else if (strcasecmp (name, "cgilimit") == 0) + { + value_required (name, value); + cgi_limit = atoi (value); + } + else if (strcasecmp (name, "cgitimelimit") == 0) + { + value_required (name, value); + cgi_timelimit = atoi (value); + } + else if (strcasecmp (name, "urlpat") == 0) + { + value_required (name, value); + url_pattern = e_strdup (value); + } + else if (strcasecmp (name, "noemptyreferers") == 0) + { + no_value_required (name, value); + no_empty_referers = 1; + } + else if (strcasecmp (name, "localpat") == 0) + { + value_required (name, value); + local_pattern = e_strdup (value); + } + else if (strcasecmp (name, "throttles") == 0) + { + value_required (name, value); + throttlefile = e_strdup (value); + } + else if (strcasecmp (name, "host") == 0) + { + value_required (name, value); + hostname = e_strdup (value); + } + else if (strcasecmp (name, "logfile") == 0) + { + value_required (name, value); + logfile = e_strdup (value); + } + else if (strcasecmp (name, "vhost") == 0) + { + no_value_required (name, value); + do_vhost = 1; + } + else if (strcasecmp (name, "novhost") == 0) + { + no_value_required (name, value); + do_vhost = 0; + } + else if (strcasecmp (name, "globalpasswd") == 0) + { + no_value_required (name, value); + do_global_passwd = 1; + } + else if (strcasecmp (name, "noglobalpasswd") == 0) + { + no_value_required (name, value); + do_global_passwd = 0; + } + else if (strcasecmp (name, "pidfile") == 0) + { + value_required (name, value); + pidfile = e_strdup (value); + } + else if (strcasecmp (name, "charset") == 0) + { + value_required (name, value); + charset = e_strdup (value); + } + else if (strcasecmp (name, "p3p") == 0) + { + value_required (name, value); + p3p = e_strdup (value); + } + else if (strcasecmp (name, "max_age") == 0) + { + value_required (name, value); + max_age = atoi (value); + } + else + { + (void) fprintf (stderr, "%s: unknown config option '%s'\n", + argv0, name); + exit (1); + } + + /* Advance to next word. */ + cp = cp2; + cp += strspn (cp, " \t\n\r"); + } + } + + (void) fclose (fp); +} + +static void value_required (char *name, char *value) +{ + if (value == (char *) 0) + { + (void) fprintf (stderr, "%s: value required for %s option\n", argv0, + name); + exit (1); + } +} + + +static void no_value_required (char *name, char *value) +{ + if (value != (char *) 0) + { + (void) fprintf (stderr, "%s: no value required for %s option\n", + argv0, name); + exit (1); + } +} +#endif + + +static char *e_strdup (char *oldstr) +{ + char *newstr; + + newstr = strdup (oldstr); + if (newstr == (char *) 0) + { + syslog (LOG_CRIT, "out of memory copying a string"); + (void) fprintf (stderr, "%s: out of memory copying a string\n", argv0); + exit (1); + } + return newstr; +} + + +static void +lookup_hostname (httpd_sockaddr * sa4P, size_t sa4_len, int *gotv4P, + httpd_sockaddr * sa6P, size_t sa6_len, int *gotv6P) +{ +#ifdef USE_IPV6 + + struct addrinfo hints; + char portstr[10]; + int gaierr; + struct addrinfo *ai; + struct addrinfo *ai2; + struct addrinfo *aiv6; + struct addrinfo *aiv4; + + (void) memset (&hints, 0, sizeof (hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + (void) snprintf (portstr, sizeof (portstr), "%d", (int) port); + if ((gaierr = getaddrinfo (hostname, portstr, &hints, &ai)) != 0) + { + syslog (LOG_CRIT, "getaddrinfo %.80s - %.80s", + hostname, gai_strerror (gaierr)); + (void) fprintf (stderr, "%s: getaddrinfo %s - %s\n", + argv0, hostname, gai_strerror (gaierr)); + exit (1); + } + + /* Find the first IPv6 and IPv4 entries. */ + aiv6 = (struct addrinfo *) 0; + aiv4 = (struct addrinfo *) 0; + for (ai2 = ai; ai2 != (struct addrinfo *) 0; ai2 = ai2->ai_next) + { + switch (ai2->ai_family) + { + case AF_INET6: + if (aiv6 == (struct addrinfo *) 0) + aiv6 = ai2; + break; + case AF_INET: + if (aiv4 == (struct addrinfo *) 0) + aiv4 = ai2; + break; + } + } + + if (aiv6 == (struct addrinfo *) 0) + *gotv6P = 0; + else + { + if (sa6_len < aiv6->ai_addrlen) + { + syslog (LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", + hostname, (unsigned long) sa6_len, + (unsigned long) aiv6->ai_addrlen); + exit (1); + } + (void) memset (sa6P, 0, sa6_len); + (void) memmove (sa6P, aiv6->ai_addr, aiv6->ai_addrlen); + *gotv6P = 1; + } + + if (aiv4 == (struct addrinfo *) 0) + *gotv4P = 0; + else + { + if (sa4_len < aiv4->ai_addrlen) + { + syslog (LOG_CRIT, "%.80s - sockaddr too small (%lu < %lu)", + hostname, (unsigned long) sa4_len, + (unsigned long) aiv4->ai_addrlen); + exit (1); + } + (void) memset (sa4P, 0, sa4_len); + (void) memmove (sa4P, aiv4->ai_addr, aiv4->ai_addrlen); + *gotv4P = 1; + } + + freeaddrinfo (ai); + +#else /* USE_IPV6 */ + + struct hostent *he; + + *gotv6P = 0; + + (void) memset (sa4P, 0, sa4_len); + sa4P->sa.sa_family = AF_INET; + if (hostname == (char *) 0) + sa4P->sa_in.sin_addr.s_addr = htonl (INADDR_ANY); + else + { + sa4P->sa_in.sin_addr.s_addr = inet_addr (hostname); + if ((int) sa4P->sa_in.sin_addr.s_addr == -1) + { + he = gethostbyname (hostname); + if (he == (struct hostent *) 0) + { +#ifdef HAVE_HSTRERROR + syslog (LOG_CRIT, "gethostbyname %.80s - %.80s", + hostname, hstrerror (h_errno)); + (void) fprintf (stderr, "%s: gethostbyname %s - %s\n", + argv0, hostname, hstrerror (h_errno)); +#else /* HAVE_HSTRERROR */ + syslog (LOG_CRIT, "gethostbyname %.80s failed", hostname); + (void) fprintf (stderr, "%s: gethostbyname %s failed\n", argv0, + hostname); +#endif /* HAVE_HSTRERROR */ + exit (1); + } + if (he->h_addrtype != AF_INET) + { + syslog (LOG_CRIT, "%.80s - non-IP network address", hostname); + (void) fprintf (stderr, "%s: %s - non-IP network address\n", + argv0, hostname); + exit (1); + } + (void) memmove (&sa4P->sa_in.sin_addr.s_addr, he->h_addr, he->h_length); + } + } + sa4P->sa_in.sin_port = htons (port); + *gotv4P = 1; + +#endif /* USE_IPV6 */ +} + + +static void read_throttlefile (char *throttlefile) +{ + FILE *fp; + char buf[5000]; + char *cp; + int len; + char pattern[5000]; + long max_limit, min_limit; + struct timeval tv; + + fp = fopen (throttlefile, "r"); + if (fp == (FILE *) 0) + { + syslog (LOG_CRIT, "%.80s - %m", throttlefile); + perror (throttlefile); + exit (1); + } + + (void) gettimeofday (&tv, (struct timezone *) 0); + + while (fgets (buf, sizeof (buf), fp) != (char *) 0) + { + /* Nuke comments. */ + cp = strchr (buf, '#'); + if (cp != (char *) 0) + *cp = '\0'; + + /* Nuke trailing whitespace. */ + len = strlen (buf); + while (len > 0 && + (buf[len - 1] == ' ' || buf[len - 1] == '\t' || + buf[len - 1] == '\n' || buf[len - 1] == '\r')) + buf[--len] = '\0'; + + /* Ignore empty lines. */ + if (len == 0) + continue; + + /* Parse line. */ + if (sscanf + (buf, " %4900[^ \t] %ld-%ld", pattern, &min_limit, &max_limit) == 3) + { + } + else if (sscanf (buf, " %4900[^ \t] %ld", pattern, &max_limit) == 2) + min_limit = 0; + else + { + syslog (LOG_CRIT, + "unparsable line in %.80s - %.80s", throttlefile, buf); + (void) fprintf (stderr, + "%s: unparsable line in %.80s - %.80s\n", + argv0, throttlefile, buf); + continue; + } + + /* Nuke any leading slashes in pattern. */ + if (pattern[0] == '/') + (void) strcpy (pattern, &pattern[1]); + while ((cp = strstr (pattern, "|/")) != (char *) 0) + (void) strcpy (cp + 1, cp + 2); + + /* Check for room in throttles. */ + if (numthrottles >= maxthrottles) + { + if (maxthrottles == 0) + { + maxthrottles = 100; /* arbitrary */ + throttles = NEW (throttletab, maxthrottles); + } + else + { + maxthrottles *= 2; + throttles = RENEW (throttles, throttletab, maxthrottles); + } + if (throttles == (throttletab *) 0) + { + syslog (LOG_CRIT, "out of memory allocating a throttletab"); + (void) fprintf (stderr, + "%s: out of memory allocating a throttletab\n", + argv0); + exit (1); + } + } + + /* Add to table. */ + throttles[numthrottles].pattern = e_strdup (pattern); + throttles[numthrottles].max_limit = max_limit; + throttles[numthrottles].min_limit = min_limit; + throttles[numthrottles].rate = 0; + throttles[numthrottles].bytes_since_avg = 0; + throttles[numthrottles].num_sending = 0; + + ++numthrottles; + } + (void) fclose (fp); +} + + +static void shut_down (void) +{ + int cnum; + struct timeval tv; + + (void) gettimeofday (&tv, (struct timezone *) 0); + logstats (&tv); + for (cnum = 0; cnum < max_connects; ++cnum) + { + if (connects[cnum].conn_state != CNST_FREE) + httpd_close_conn (connects[cnum].hc, &tv); + if (connects[cnum].hc != (httpd_conn *) 0) + { + httpd_destroy_conn (connects[cnum].hc); + free ((void *) connects[cnum].hc); + --httpd_conn_count; + connects[cnum].hc = (httpd_conn *) 0; + } + } + if (hs != (httpd_server *) 0) + { + httpd_server *ths = hs; + hs = (httpd_server *) 0; + if (ths->listen4_fd != -1) + fdwatch_del_fd (ths->listen4_fd); + if (ths->listen6_fd != -1) + fdwatch_del_fd (ths->listen6_fd); + httpd_terminate (ths); + } + mmc_destroy (); + tmr_destroy (); + free ((void *) connects); + if (throttles != (throttletab *) 0) + free ((void *) throttles); +} + + +static int handle_newconnect (struct timeval *tvP, int listen_fd) +{ + connecttab *c; + //ClientData client_data; + + /* This loops until the accept() fails, trying to start new + ** connections as fast as possible so we don't overrun the + ** listen queue. + */ + for (;;) + { + /* Is there room in the connection table? */ + if (num_connects >= max_connects) + { + /* Out of connection slots. Run the timers, then the + ** existing connections, and maybe we'll free up a slot + ** by the time we get back here. + */ + syslog (LOG_WARNING, "too many connections!"); + tmr_run (tvP); + return 0; + } + /* Get the first free connection entry off the free list. */ + if (first_free_connect == -1 + || connects[first_free_connect].conn_state != CNST_FREE) + { + syslog (LOG_CRIT, "the connects free list is messed up"); + exit (1); + } + c = &connects[first_free_connect]; + /* Make the httpd_conn if necessary. */ + if (c->hc == (httpd_conn *) 0) + { + c->hc = NEW (httpd_conn, 1); + if (c->hc == (httpd_conn *) 0) + { + syslog (LOG_CRIT, "out of memory allocating an httpd_conn"); + exit (1); + } + c->hc->initialized = 0; + ++httpd_conn_count; + } + + /* Get the connection. */ + switch (httpd_get_conn (hs, listen_fd, c->hc)) + { + /* Some error happened. Run the timers, then the + ** existing connections. Maybe the error will clear. + */ + case GC_FAIL: + tmr_run (tvP); + return 0; + + /* No more connections to accept for now. */ + case GC_NO_MORE: + return 1; + } + c->conn_state = CNST_READING; + /* Pop it off the free list. */ + first_free_connect = c->next_free_connect; + c->next_free_connect = -1; + ++num_connects; + //client_data.p = c; + c->active_at = tvP->tv_sec; + c->wakeup_timer = (Timer *) 0; + c->linger_timer = (Timer *) 0; + c->next_byte_index = 0; + c->numtnums = 0; + + /* Set the connection file descriptor to no-delay mode. */ + httpd_set_ndelay (c->hc->conn_fd); + + fdwatch_add_fd (c->hc->conn_fd, c, FDW_READ); + + ++stats_connections; + if (num_connects > stats_simultaneous) + stats_simultaneous = num_connects; + } +} + + +static void handle_read (connecttab * c, struct timeval *tvP) +{ + int sz; + //ClientData client_data; + httpd_conn *hc = c->hc; + + /* Is there room in our buffer to read more bytes? */ + if (hc->read_idx >= hc->read_size) + { + if (hc->read_size > 5000) + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + finish_connection (c, tvP); + return; + } + httpd_realloc_str (&hc->read_buf, &hc->read_size, hc->read_size + 1000); + } + + /* Read some more bytes. */ + sz = read (hc->conn_fd, &(hc->read_buf[hc->read_idx]), + hc->read_size - hc->read_idx); + if (sz == 0) + { + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + finish_connection (c, tvP); + return; + } + if (sz < 0) + { + /* Ignore EINTR and EAGAIN. Also ignore EWOULDBLOCK. At first glance + ** you would think that connections returned by fdwatch as readable + ** should never give an EWOULDBLOCK; however, this apparently can + ** happen if a packet gets garbled. + */ + if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) + return; + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + finish_connection (c, tvP); + return; + } + hc->read_idx += sz; + c->active_at = tvP->tv_sec; + + /* Do we have a complete request yet? */ + switch (httpd_got_request (hc)) + { + case GR_NO_REQUEST: + return; + case GR_BAD_REQUEST: + httpd_send_err (hc, 400, httpd_err400title, "", httpd_err400form, ""); + finish_connection (c, tvP); + return; + } + + /* Yes. Try parsing and resolving it. */ + if (httpd_parse_request (hc) < 0) + { + finish_connection (c, tvP); + return; + } + + /* Check the throttle table */ + if (!check_throttles (c)) + { + httpd_send_err (hc, 503, httpd_err503title, "", httpd_err503form, + hc->encodedurl); + finish_connection (c, tvP); + return; + } + + /* Start the connection going. */ + if (httpd_start_request (hc, tvP) < 0) + { + /* Something went wrong. Close down the connection. */ + finish_connection (c, tvP); + return; + } + + /* Fill in end_byte_index. */ + if (hc->got_range) + { + c->next_byte_index = hc->first_byte_index; + c->end_byte_index = hc->last_byte_index + 1; + } + else if (hc->bytes_to_send < 0) + c->end_byte_index = 0; + else + c->end_byte_index = hc->bytes_to_send; + + /* Check if it's already handled. */ + if (hc->file_address == (char *) 0) + { + /* No file address means someone else is handling it. */ + int tind; + for (tind = 0; tind < c->numtnums; ++tind) + throttles[c->tnums[tind]].bytes_since_avg += hc->bytes_sent; + c->next_byte_index = hc->bytes_sent; + finish_connection (c, tvP); + return; + } + if (c->next_byte_index >= c->end_byte_index) + { + /* There's nothing to send. */ + finish_connection (c, tvP); + return; + } + + /* Cool, we have a valid connection and a file to send to it. */ + c->conn_state = CNST_SENDING; + c->started_at = tvP->tv_sec; + c->wouldblock_delay = 0; + //client_data.p = c; + + fdwatch_del_fd (hc->conn_fd); + fdwatch_add_fd (hc->conn_fd, c, FDW_WRITE); +} + + +static void handle_send (connecttab * c, struct timeval *tvP) +{ + size_t max_bytes; + int sz, coast; + ClientData client_data; + time_t elapsed; + httpd_conn *hc = c->hc; + int tind; + + if (c->max_limit == THROTTLE_NOLIMIT) + max_bytes = 1000000000L; + else + max_bytes = c->max_limit / 4; /* send at most 1/4 seconds worth */ + + /* Do we need to write the headers first? */ + if (hc->responselen == 0) + { + /* No, just write the file. */ + sz = write (hc->conn_fd, &(hc->file_address[c->next_byte_index]), + MIN (c->end_byte_index - c->next_byte_index, max_bytes)); + } + else + { + /* Yes. We'll combine headers and file into a single writev(), + ** hoping that this generates a single packet. + */ + struct iovec iv[2]; + + iv[0].iov_base = hc->response; + iv[0].iov_len = hc->responselen; + iv[1].iov_base = &(hc->file_address[c->next_byte_index]); + iv[1].iov_len = MIN (c->end_byte_index - c->next_byte_index, max_bytes); + sz = writev (hc->conn_fd, iv, 2); + } + + if (sz < 0 && errno == EINTR) + return; + + if (sz == 0 || (sz < 0 && (errno == EWOULDBLOCK || errno == EAGAIN))) + { + /* This shouldn't happen, but some kernels, e.g. + ** SunOS 4.1.x, are broken and select() says that + ** O_NDELAY sockets are always writable even when + ** they're actually not. + ** + ** Current workaround is to block sending on this + ** socket for a brief adaptively-tuned period. + ** Fortunately we already have all the necessary + ** blocking code, for use with throttling. + */ + c->wouldblock_delay += MIN_WOULDBLOCK_DELAY; + c->conn_state = CNST_PAUSING; + fdwatch_del_fd (hc->conn_fd); + client_data.p = c; + if (c->wakeup_timer != (Timer *) 0) + syslog (LOG_ERR, "replacing non-null wakeup_timer!"); + c->wakeup_timer = + tmr_create (tvP, wakeup_connection, client_data, c->wouldblock_delay, + 0); + if (c->wakeup_timer == (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(wakeup_connection) failed"); + exit (1); + } + return; + } + + if (sz < 0) + { + /* Something went wrong, close this connection. + ** + ** If it's just an EPIPE, don't bother logging, that + ** just means the client hung up on us. + ** + ** On some systems, write() occasionally gives an EINVAL. + ** Dunno why, something to do with the socket going + ** bad. Anyway, we don't log those either. + ** + ** And ECONNRESET isn't interesting either. + */ + if (errno != EPIPE && errno != EINVAL && errno != ECONNRESET) + syslog (LOG_ERR, "write - %m sending %.80s", hc->encodedurl); + clear_connection (c, tvP); + return; + } + + /* Ok, we wrote something. */ + c->active_at = tvP->tv_sec; + /* Was this a headers + file writev()? */ + if (hc->responselen > 0) + { + /* Yes; did we write only part of the headers? */ + if (sz < hc->responselen) + { + /* Yes; move the unwritten part to the front of the buffer. */ + int newlen = hc->responselen - sz; + (void) memmove (hc->response, &(hc->response[sz]), newlen); + hc->responselen = newlen; + sz = 0; + } + else + { + /* Nope, we wrote the full headers, so adjust accordingly. */ + sz -= hc->responselen; + hc->responselen = 0; + } + } + /* And update how much of the file we wrote. */ + c->next_byte_index += sz; + c->hc->bytes_sent += sz; + for (tind = 0; tind < c->numtnums; ++tind) + throttles[c->tnums[tind]].bytes_since_avg += sz; + + /* Are we done? */ + if (c->next_byte_index >= c->end_byte_index) + { + /* This connection is finished! */ + finish_connection (c, tvP); + return; + } + + /* Tune the (blockheaded) wouldblock delay. */ + if (c->wouldblock_delay > MIN_WOULDBLOCK_DELAY) + c->wouldblock_delay -= MIN_WOULDBLOCK_DELAY; + + /* If we're throttling, check if we're sending too fast. */ + if (c->max_limit != THROTTLE_NOLIMIT) + { + elapsed = tvP->tv_sec - c->started_at; + if (elapsed == 0) + elapsed = 1; /* count at least one second */ + if (c->hc->bytes_sent / elapsed > c->max_limit) + { + c->conn_state = CNST_PAUSING; + fdwatch_del_fd (hc->conn_fd); + /* How long should we wait to get back on schedule? If less + ** than a second (integer math rounding), use 1/2 second. + */ + coast = c->hc->bytes_sent / c->max_limit - elapsed; + client_data.p = c; + if (c->wakeup_timer != (Timer *) 0) + syslog (LOG_ERR, "replacing non-null wakeup_timer!"); + c->wakeup_timer = tmr_create (tvP, wakeup_connection, client_data, + coast > 0 ? (coast * 1000L) : 500L, 0); + if (c->wakeup_timer == (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(wakeup_connection) failed"); + exit (1); + } + } + } + /* (No check on min_limit here, that only controls connection startups.) */ +} + + +static void handle_linger (connecttab * c, struct timeval *tvP) +{ + char buf[4096]; + int r; + + /* In lingering-close mode we just read and ignore bytes. An error + ** or EOF ends things, otherwise we go until a timeout. + */ + r = read (c->hc->conn_fd, buf, sizeof (buf)); + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return; + if (r <= 0) + really_clear_connection (c, tvP); +} + + +static int check_throttles (connecttab * c) +{ + int tnum; + long l; + + c->numtnums = 0; + c->max_limit = c->min_limit = THROTTLE_NOLIMIT; + for (tnum = 0; tnum < numthrottles && c->numtnums < MAXTHROTTLENUMS; ++tnum) + if (match (throttles[tnum].pattern, c->hc->expnfilename)) + { + /* If we're way over the limit, don't even start. */ + if (throttles[tnum].rate > throttles[tnum].max_limit * 2) + return 0; + /* Also don't start if we're under the minimum. */ + if (throttles[tnum].rate < throttles[tnum].min_limit) + return 0; + if (throttles[tnum].num_sending < 0) + { + syslog (LOG_ERR, + "throttle sending count was negative - shouldn't happen!"); + throttles[tnum].num_sending = 0; + } + c->tnums[c->numtnums++] = tnum; + ++throttles[tnum].num_sending; + l = throttles[tnum].max_limit / throttles[tnum].num_sending; + if (c->max_limit == THROTTLE_NOLIMIT) + c->max_limit = l; + else + c->max_limit = MIN (c->max_limit, l); + l = throttles[tnum].min_limit; + if (c->min_limit == THROTTLE_NOLIMIT) + c->min_limit = l; + else + c->min_limit = MAX (c->min_limit, l); + } + return 1; +} + + +static void clear_throttles (connecttab * c, struct timeval *tvP) +{ + int tind; + + for (tind = 0; tind < c->numtnums; ++tind) + --throttles[c->tnums[tind]].num_sending; +} + + +static void update_throttles (ClientData client_data, struct timeval *nowP) +{ + int tnum, tind; + int cnum; + connecttab *c; + long l; + + /* Update the average sending rate for each throttle. This is only used + ** when new connections start up. + */ + for (tnum = 0; tnum < numthrottles; ++tnum) + { + throttles[tnum].rate = + (2 * throttles[tnum].rate + + throttles[tnum].bytes_since_avg / THROTTLE_TIME) / 3; + throttles[tnum].bytes_since_avg = 0; + /* Log a warning message if necessary. */ + if (throttles[tnum].rate > throttles[tnum].max_limit + && throttles[tnum].num_sending != 0) + { + if (throttles[tnum].rate > throttles[tnum].max_limit * 2) + syslog (LOG_NOTICE, + "throttle #%d '%.80s' rate %ld greatly exceeding limit %ld; %d sending", + tnum, throttles[tnum].pattern, throttles[tnum].rate, + throttles[tnum].max_limit, throttles[tnum].num_sending); + else + syslog (LOG_INFO, + "throttle #%d '%.80s' rate %ld exceeding limit %ld; %d sending", + tnum, throttles[tnum].pattern, throttles[tnum].rate, + throttles[tnum].max_limit, throttles[tnum].num_sending); + } + if (throttles[tnum].rate < throttles[tnum].min_limit + && throttles[tnum].num_sending != 0) + { + syslog (LOG_NOTICE, + "throttle #%d '%.80s' rate %ld lower than minimum %ld; %d sending", + tnum, throttles[tnum].pattern, throttles[tnum].rate, + throttles[tnum].min_limit, throttles[tnum].num_sending); + } + } + + /* Now update the sending rate on all the currently-sending connections, + ** redistributing it evenly. + */ + for (cnum = 0; cnum < max_connects; ++cnum) + { + c = &connects[cnum]; + if (c->conn_state == CNST_SENDING || c->conn_state == CNST_PAUSING) + { + c->max_limit = THROTTLE_NOLIMIT; + for (tind = 0; tind < c->numtnums; ++tind) + { + tnum = c->tnums[tind]; + l = throttles[tnum].max_limit / throttles[tnum].num_sending; + if (c->max_limit == THROTTLE_NOLIMIT) + c->max_limit = l; + else + c->max_limit = MIN (c->max_limit, l); + } + } + } +} + + +static void finish_connection (connecttab * c, struct timeval *tvP) +{ + /* If we haven't actually sent the buffered response yet, do so now. */ + httpd_write_response (c->hc); + + /* And clear. */ + clear_connection (c, tvP); +} + + +static void clear_connection (connecttab * c, struct timeval *tvP) +{ + ClientData client_data; + + if (c->wakeup_timer != (Timer *) 0) + { + tmr_cancel (c->wakeup_timer); + c->wakeup_timer = 0; + } + + /* This is our version of Apache's lingering_close() routine, which is + ** their version of the often-broken SO_LINGER socket option. For why + ** this is necessary, see http://www.apache.org/docs/misc/fin_wait_2.html + ** What we do is delay the actual closing for a few seconds, while reading + ** any bytes that come over the connection. However, we don't want to do + ** this unless it's necessary, because it ties up a connection slot and + ** file descriptor which means our maximum connection-handling rate + ** is lower. So, elsewhere we set a flag when we detect the few + ** circumstances that make a lingering close necessary. If the flag + ** isn't set we do the real close now. + */ + if (c->conn_state == CNST_LINGERING) + { + /* If we were already lingering, shut down for real. */ + tmr_cancel (c->linger_timer); + c->linger_timer = (Timer *) 0; + c->hc->should_linger = 0; + } + if (c->hc->should_linger) + { + if (c->conn_state != CNST_PAUSING) + fdwatch_del_fd (c->hc->conn_fd); + c->conn_state = CNST_LINGERING; + shutdown (c->hc->conn_fd, SHUT_WR); + fdwatch_add_fd (c->hc->conn_fd, c, FDW_READ); + client_data.p = c; + if (c->linger_timer != (Timer *) 0) + syslog (LOG_ERR, "replacing non-null linger_timer!"); + c->linger_timer = + tmr_create (tvP, linger_clear_connection, client_data, LINGER_TIME, 0); + if (c->linger_timer == (Timer *) 0) + { + syslog (LOG_CRIT, "tmr_create(linger_clear_connection) failed"); + exit (1); + } + } + else + really_clear_connection (c, tvP); +} + + +static void really_clear_connection (connecttab * c, struct timeval *tvP) +{ + stats_bytes += c->hc->bytes_sent; + if (c->conn_state != CNST_PAUSING) + fdwatch_del_fd (c->hc->conn_fd); + httpd_close_conn (c->hc, tvP); + clear_throttles (c, tvP); + if (c->linger_timer != (Timer *) 0) + { + tmr_cancel (c->linger_timer); + c->linger_timer = 0; + } + c->conn_state = CNST_FREE; + c->next_free_connect = first_free_connect; + first_free_connect = c - connects; /* division by sizeof is implied */ + --num_connects; +} + + +static void idle (ClientData client_data, struct timeval *nowP) +{ + int cnum; + connecttab *c; + + for (cnum = 0; cnum < max_connects; ++cnum) + { + c = &connects[cnum]; + switch (c->conn_state) + { + case CNST_READING: + if (nowP->tv_sec - c->active_at >= IDLE_READ_TIMELIMIT) + { + syslog (LOG_INFO, + "%.80s connection timed out reading", + httpd_ntoa (&c->hc->client_addr)); + httpd_send_err (c->hc, 408, httpd_err408title, "", + httpd_err408form, ""); + finish_connection (c, nowP); + } + break; + case CNST_SENDING: + case CNST_PAUSING: + if (nowP->tv_sec - c->active_at >= IDLE_SEND_TIMELIMIT) + { + syslog (LOG_INFO, + "%.80s connection timed out sending", + httpd_ntoa (&c->hc->client_addr)); + clear_connection (c, nowP); + } + break; + } + } +} + + +static void wakeup_connection (ClientData client_data, struct timeval *nowP) +{ + connecttab *c; + + c = (connecttab *) client_data.p; + c->wakeup_timer = (Timer *) 0; + if (c->conn_state == CNST_PAUSING) + { + c->conn_state = CNST_SENDING; + fdwatch_add_fd (c->hc->conn_fd, c, FDW_WRITE); + } +} + +static void +linger_clear_connection (ClientData client_data, struct timeval *nowP) +{ + connecttab *c; + + c = (connecttab *) client_data.p; + c->linger_timer = (Timer *) 0; + really_clear_connection (c, nowP); +} + + +static void occasional (ClientData client_data, struct timeval *nowP) +{ + mmc_cleanup (nowP); + tmr_cleanup (); + watchdog_flag = 1; /* let the watchdog know that we are alive */ +} + + +#ifdef STATS_TIME +static void show_stats (ClientData client_data, struct timeval *nowP) +{ + logstats (nowP); +} +#endif /* STATS_TIME */ + + +/* Generate debugging statistics syslog messages for all packages. */ +static void logstats (struct timeval *nowP) +{ + struct timeval tv; + time_t now; + long up_secs, stats_secs; + + if (nowP == (struct timeval *) 0) + { + (void) gettimeofday (&tv, (struct timezone *) 0); + nowP = &tv; + } + now = nowP->tv_sec; + up_secs = now - start_time; + stats_secs = now - stats_time; + if (stats_secs == 0) + stats_secs = 1; /* fudge */ + stats_time = now; + syslog (LOG_INFO, + "up %ld seconds, stats for %ld seconds:", up_secs, stats_secs); + + thttpd_logstats (stats_secs); + httpd_logstats (stats_secs); + mmc_logstats (stats_secs); + fdwatch_logstats (stats_secs); + tmr_logstats (stats_secs); +} + + +/* Generate debugging statistics syslog message. */ +static void thttpd_logstats (long secs) +{ + if (secs > 0) + syslog (LOG_INFO, + " thttpd - %ld connections (%g/sec), %d max simultaneous, %lld bytes (%g/sec), %d httpd_conns allocated", + stats_connections, (float) stats_connections / secs, + stats_simultaneous, (int64_t) stats_bytes, + (float) stats_bytes / secs, httpd_conn_count); + stats_connections = 0; + stats_bytes = 0; + stats_simultaneous = 0; +} diff --git a/gb.httpd/src/thttpd.h b/gb.httpd/src/thttpd.h new file mode 100644 index 000000000..008501c94 --- /dev/null +++ b/gb.httpd/src/thttpd.h @@ -0,0 +1,403 @@ +/* config.h - configuration defines for thttpd and libhttpd +** +** Copyright � 1995,1998,1999,2000,2001 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#ifndef _THTTPD_H_ +#define _THTTPD_H_ + +#include "config.h" + +/* The following configuration settings are sorted in order of decreasing +** likelihood that you'd want to change them - most likely first, least +** likely last. +** +** In case you're not familiar with the convention, "#ifdef notdef" +** is a Berkeleyism used to indicate temporarily disabled code. +** The idea here is that you re-enable it by just moving it outside +** of the ifdef. +*/ + +/* CONFIGURE: CGI programs must match this pattern to get executed. It's +** a simple shell-style wildcard pattern, with * meaning any string not +** containing a slash, ** meaning any string at all, and ? meaning any +** single character; or multiple such patterns separated by |. The +** patterns get checked against the filename part of the incoming URL. +** +** Restricting CGI programs to a single directory lets the site administrator +** review them for security holes, and is strongly recommended. If there +** are individual users that you trust, you can enable their directories too. +** +** You can also specify a CGI pattern on the command line, with the -c flag. +** Such a pattern overrides this compiled-in default. +** +** If no CGI pattern is specified, neither here nor on the command line, +** then CGI programs cannot be run at all. If you want to disable CGI +** as a security measure that's how you do it, just don't define any +** pattern here and don't run with the -c flag. +*/ +#ifdef notdef +/* Some sample patterns. Allow programs only in one central directory: */ +#define CGI_PATTERN "/cgi-bin/*" +/* Allow programs in a central directory, or anywhere in a trusted +** user's tree: */ +#define CGI_PATTERN "/cgi-bin/*|/jef/**" +/* Allow any program ending with a .cgi: */ +#define CGI_PATTERN "**.cgi" +/* When virtual hosting, enable the central directory on every host: */ +#define CGI_PATTERN "/*/cgi-bin/*" +#endif + +/* CONFIGURE: How many seconds to allow CGI programs to run before killing +** them. This is in case someone writes a CGI program that goes into an +** infinite loop, or does a massive database lookup that would take hours, +** or whatever. If you don't want any limit, comment this out, but that's +** probably a really bad idea. +*/ +#define CGI_TIMELIMIT 30 + +/* CONFIGURE: Maximum number of simultaneous CGI programs allowed. +** If this many are already running, then attempts to run more will +** return an HTTP 503 error. If this is not defined then there's +** no limit (and you'd better have a lot of memory). This can also be +** set in the runtime config file. +*/ +#ifdef notdef +#define CGI_LIMIT 50 +#endif + +/* CONFIGURE: How many seconds to allow for reading the initial request +** on a new connection. +*/ +#define IDLE_READ_TIMELIMIT 60 + +/* CONFIGURE: How many seconds before an idle connection gets closed. +*/ +#define IDLE_SEND_TIMELIMIT 300 + +/* CONFIGURE: The syslog facility to use. Using this you can set up your +** syslog.conf so that all thttpd messages go into a separate file. Note +** that even if you use the -l command line flag to send logging to a +** file, errors still get sent via syslog. +*/ +#define LOG_FACILITY LOG_DAEMON + +/* CONFIGURE: Tilde mapping. Many URLs use ~username to indicate a +** user's home directory. thttpd provides two options for mapping +** this construct to an actual filename. +** +** 1) Map ~username to /username. This is the recommended choice. +** Each user gets a subdirectory in the main chrootable web tree, and +** the tilde construct points there. The prefix could be something +** like "users", or it could be empty. See also the makeweb program +** for letting users create their own web subdirectories. +** +** 2) Map ~username to /. The postfix would be +** the name of a subdirectory off of the user's actual home dir, something +** like "public_html". This is what Apache and other servers do. The problem +** is, you can't do this and chroot() at the same time, so it's inherently +** a security hole. This is strongly dis-recommended, but it's here because +** some people really want it. Use at your own risk. +** +** You can also leave both options undefined, and thttpd will not do +** anything special about tildes. Enabling both options is an error. +*/ +#ifdef notdef +#define TILDE_MAP_1 "users" +#define TILDE_MAP_2 "public_html" +#endif + +/* CONFIGURE: The file to use for authentication. If this is defined then +** thttpd checks for this file in the local directory before every fetch. +** If the file exists then authentication is done, otherwise the fetch +** proceeds as usual. +** +** If you undefine this then thttpd will not implement authentication +** at all and will not check for auth files, which saves a bit of CPU time. +*/ +//#define AUTH_FILE ".htpasswd" + +/* CONFIGURE: The default character set name to use with text MIME types. +** This gets substituted into the MIME types where they have a "%s". +** +** You can override this in the config file with the "charset" setting, +** or on the command like with the -T flag. +*/ +#define DEFAULT_CHARSET "iso-8859-1" + + +/* Most people won't want to change anything below here. */ + +/* CONFIGURE: This controls the SERVER_NAME environment variable that gets +** passed to CGI programs. By default thttpd does a gethostname(), which +** gives the host's canonical name. If you want to always use some other name +** you can define it here. +** +** Alternately, if you want to run the same thttpd binary on multiple +** machines, and want to build in alternate names for some or all of +** them, you can define a list of canonical name to altername name +** mappings. thttpd seatches the list and when it finds a match on +** the canonical name, that alternate name gets used. If no match +** is found, the canonical name gets used. +** +** If both SERVER_NAME and SERVER_NAME_LIST are defined here, thttpd searches +** the list as above, and if no match is found then SERVER_NAME gets used. +** +** In any case, if thttpd is started with the -h flag, that name always +** gets used. +*/ +#ifdef notdef +#define SERVER_NAME "your.hostname.here" +#define SERVER_NAME_LIST \ + "canonical.name.here/alternate.name.here", \ + "canonical.name.two/alternate.name.two" +#endif + +/* CONFIGURE: Undefine this if you want thttpd to hide its specific version +** when returning into to browsers. Instead it'll just say "thttpd" with +** no version. +*/ +#define SHOW_SERVER_VERSION + +/* CONFIGURE: Define this if you want to always chroot(), without having +** to give the -r command line flag. Some people like this as a security +** measure, to prevent inadvertant exposure by accidentally running without -r. +** You can still disable it at runtime with the -nor flag. +*/ +#ifdef notdef +#define ALWAYS_CHROOT +#endif + +/* CONFIGURE: Define this if you want to always do virtual hosting, without +** having to give the -v command line flag. You can still disable it at +** runtime with the -nov flag. +*/ +#ifdef notdef +#define ALWAYS_VHOST +#endif + +/* CONFIGURE: If you're using the vhost feature and you have a LOT of +** virtual hostnames (like, hundreds or thousands), you will want to +** enable this feature. It avoids a problem with most Unix filesystems, +** where if there are a whole lot of items in a directory then name lookup +** becomes very slow. This feature makes thttpd use subdirectories +** based on the first characters of each hostname. You can set it to use +** from one to three characters. If the hostname starts with "www.", that +** part is skipped over. Dots are also skipped over, and if the name isn't +** long enough then "_"s are used. Here are some examples of how hostnames +** would get turned into directory paths, for each different setting: +** 1: www.acme.com -> a/www.acme.com +** 1: foobar.acme.com -> f/foobar.acme.com +** 2: www.acme.com -> a/c/www.acme.com +** 2: foobar.acme.com -> f/o/foobar.acme.com +** 3: www.acme.com -> a/c/m/www.acme.com +** 3: foobar.acme.com -> f/o/o/foobar.acme.com +** 3: m.tv -> m/t/v/m.tv +** 4: m.tv -> m/t/v/_/m.tv +** Note that if you compile this setting in but then forget to set up +** the corresponding subdirectories, the only error indication you'll +** get is a "404 Not Found" when you try to visit a site. So be careful. +*/ +#ifdef notdef +#define VHOST_DIRLEVELS 1 +#define VHOST_DIRLEVELS 2 +#define VHOST_DIRLEVELS 3 +#endif + +/* CONFIGURE: Define this if you want to always use a global passwd file, +** without having to give the -P command line flag. You can still disable +** it at runtime with the -noP flag. +*/ +#ifdef notdef +#define ALWAYS_GLOBAL_PASSWD +#endif + +/* CONFIGURE: When started as root, the default username to switch to after +** initializing. If this user (or the one specified by the -u flag) does +** not exist, the program will refuse to run. +*/ +#define DEFAULT_USER "nobody" + +/* CONFIGURE: When started as root, the program can automatically chdir() +** to the home directory of the user specified by -u or DEFAULT_USER. +** An explicit -d still overrides this. +*/ +#ifdef notdef +#define USE_USER_DIR +#endif + +/* CONFIGURE: If this is defined, some of the built-in error pages will +** have more explicit information about exactly what the problem is. +** Some sysadmins don't like this, for security reasons. +*/ +#define EXPLICIT_ERROR_PAGES + +/* CONFIGURE: Subdirectory for custom error pages. The error filenames are +** $WEBDIR/$ERR_DIR/err%d.html - if virtual hosting is enabled then +** $WEBDIR/hostname/$ERR_DIR/err%d.html is searched first. This allows +** different custom error pages for each virtual hosting web server. If +** no custom page for a given error can be found, the built-in error page +** is generated. If ERR_DIR is not defined at all, only the built-in error +** pages will be generated. +*/ +#define ERR_DIR "errors" + +/* CONFIGURE: Define this if you want a standard HTML tail containing +** $SERVER_SOFTWARE and $SERVER_ADDRESS to be appended to the custom error +** pages. (It is always appended to the built-in error pages.) +*/ +#define ERR_APPEND_SERVER_INFO + +/* CONFIGURE: nice(2) value to use for CGI programs. If this is undefined, +** CGI programs run at normal priority. +*/ +#define CGI_NICE 10 + +/* CONFIGURE: $PATH to use for CGI programs. +*/ +#define CGI_PATH "/usr/local/bin:/usr/ucb:/bin:/usr/bin" + +/* CONFIGURE: If defined, $LD_LIBRARY_PATH to use for CGI programs. +*/ +#ifdef notdef +#define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib" +#endif + +/* CONFIGURE: How often to run the occasional cleanup job. +*/ +#define OCCASIONAL_TIME 120 + +/* CONFIGURE: Seconds between stats syslogs. If this is undefined then +** no stats are accumulated and no stats syslogs are done. +*/ +//#define STATS_TIME 3600 + +/* CONFIGURE: The mmap cache tries to keep the total number of mapped +** files below this number, so you don't run out of kernel file descriptors. +** If you have reconfigured your kernel to have more descriptors, you can +** raise this and thttpd will keep more maps cached. However it's not +** a hard limit, thttpd will go over it if you really are accessing +** a whole lot of files. +*/ +#define DESIRED_MAX_MAPPED_FILES 1000 + +/* CONFIGURE: The mmap cache also tries to keep the total mapped bytes +** below this number, so you don't run out of address space. Again +** it's not a hard limit, thttpd will go over it if you really are +** accessing a bunch of large files. +*/ +#define DESIRED_MAX_MAPPED_BYTES 1000000000 + +/* CONFIGURE: Minimum and maximum intervals between child-process reaping, +** in seconds. +*/ +#define MIN_REAP_TIME 30 +#define MAX_REAP_TIME 900 + + +/* You almost certainly don't want to change anything below here. */ + +/* CONFIGURE: When throttling CGI programs, we don't know how many bytes +** they send back to the client because it would be inefficient to +** interpose a counter. CGI programs are much more expensive than +** regular files to serve, so we set an arbitrary and high byte count +** that gets applied to all CGI programs for throttling purposes. +*/ +#define CGI_BYTECOUNT 25000 + +/* CONFIGURE: The default port to listen on. 80 is the standard HTTP port. +*/ +#define DEFAULT_PORT 80 + +/* CONFIGURE: A list of index filenames to check. The files are searched +** for in this order. +*/ +#define INDEX_NAMES "index.html", "index.htm", "index.xhtml", "index.xht", "Default.htm", "index.cgi" + +/* CONFIGURE: If this is defined then thttpd will automatically generate +** index pages for directories that don't have an explicit index file. +** If you want to disable this behavior site-wide, perhaps for security +** reasons, just undefine this. Note that you can disable indexing of +** individual directories by merely doing a "chmod 711" on them - the +** standard Unix file permission to allow file access but disable "ls". +*/ +//#define GENERATE_INDEXES + +/* CONFIGURE: Whether to log unknown request headers. Most sites will not +** want to log them, which will save them a bit of CPU time. +*/ +#ifdef notdef +#define LOG_UNKNOWN_HEADERS +#endif + +/* CONFIGURE: Whether to fflush() the log file after each request. If +** this is turned off there's a slight savings in CPU cycles. +*/ +//#define FLUSH_LOG_EVERY_TIME + +/* CONFIGURE: Time between updates of the throttle table's rolling averages. */ +#define THROTTLE_TIME 2 + +/* CONFIGURE: The listen() backlog queue length. The 1024 doesn't actually +** get used, the kernel uses its maximum allowed value. This is a config +** parameter only in case there's some OS where asking for too high a queue +** length causes an error. Note that on many systems the maximum length is +** way too small - see http://www.acme.com/software/thttpd/notes.html +*/ +#define LISTEN_BACKLOG 1024 + +/* CONFIGURE: Maximum number of throttle patterns that any single URL can +** be included in. This has nothing to do with the number of throttle +** patterns that you can define, which is unlimited. +*/ +#define MAXTHROTTLENUMS 10 + +/* CONFIGURE: Number of file descriptors to reserve for uses other than +** connections. Currently this is 10, representing one for the listen fd, +** one for dup()ing at connection startup time, one for reading the file, +** one for syslog, and possibly one for the regular log file, which is +** five, plus a factor of two for who knows what. +*/ +#define SPARE_FDS 10 + +/* CONFIGURE: How many milliseconds to leave a connection open while doing a +** lingering close. +*/ +#define LINGER_TIME 500 + +/* CONFIGURE: Maximum number of symbolic links to follow before +** assuming there's a loop. +*/ +#define MAX_LINKS 32 + +/* CONFIGURE: You don't even want to know. +*/ +#define MIN_WOULDBLOCK_DELAY 100L + +/* CONFIGURE: Pass the X-Cgi header to the CGI script +*/ +#define X_CGI_HEADER + +#endif /* _THTTPD_H_ */ diff --git a/gb.httpd/src/timers.c b/gb.httpd/src/timers.c new file mode 100644 index 000000000..f6e91671a --- /dev/null +++ b/gb.httpd/src/timers.c @@ -0,0 +1,334 @@ +/* timers.c - simple timer routines +** +** Copyright © 1995,1998,2000 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#include + +#include +#include +//#include + +#include "main.h" +#include "timers.h" + + +#define HASH_SIZE 67 +static Timer *timers[HASH_SIZE]; +static Timer *free_timers; +static int alloc_count, active_count, free_count; + +ClientData JunkClientData; + + + +static unsigned int hash (Timer * t) +{ + /* We can hash on the trigger time, even though it can change over + ** the life of a timer via either the periodic bit or the tmr_reset() + ** call. This is because both of those guys call l_resort(), which + ** recomputes the hash and moves the timer to the appropriate list. + */ + return ((unsigned int) t->time.tv_sec ^ + (unsigned int) t->time.tv_usec) % HASH_SIZE; +} + + +static void l_add (Timer * t) +{ + int h = t->hash; + register Timer *t2; + register Timer *t2prev; + + t2 = timers[h]; + if (t2 == (Timer *) 0) + { + /* The list is empty. */ + timers[h] = t; + t->prev = t->next = (Timer *) 0; + } + else + { + if (t->time.tv_sec < t2->time.tv_sec || + (t->time.tv_sec == t2->time.tv_sec && + t->time.tv_usec <= t2->time.tv_usec)) + { + /* The new timer goes at the head of the list. */ + timers[h] = t; + t->prev = (Timer *) 0; + t->next = t2; + t2->prev = t; + } + else + { + /* Walk the list to find the insertion point. */ + for (t2prev = t2, t2 = t2->next; t2 != (Timer *) 0; + t2prev = t2, t2 = t2->next) + { + if (t->time.tv_sec < t2->time.tv_sec || + (t->time.tv_sec == t2->time.tv_sec && + t->time.tv_usec <= t2->time.tv_usec)) + { + /* Found it. */ + t2prev->next = t; + t->prev = t2prev; + t->next = t2; + t2->prev = t; + return; + } + } + /* Oops, got to the end of the list. Add to tail. */ + t2prev->next = t; + t->prev = t2prev; + t->next = (Timer *) 0; + } + } +} + + +static void l_remove (Timer * t) +{ + int h = t->hash; + + if (t->prev == (Timer *) 0) + timers[h] = t->next; + else + t->prev->next = t->next; + if (t->next != (Timer *) 0) + t->next->prev = t->prev; +} + + +static void l_resort (Timer * t) +{ + /* Remove the timer from its old list. */ + l_remove (t); + /* Recompute the hash. */ + t->hash = hash (t); + /* And add it back in to its new list, sorted correctly. */ + l_add (t); +} + + +void tmr_init (void) +{ + int h; + + for (h = 0; h < HASH_SIZE; ++h) + timers[h] = (Timer *) 0; + free_timers = (Timer *) 0; + alloc_count = active_count = free_count = 0; +} + + +Timer *tmr_create (struct timeval *nowP, TimerProc * timer_proc, + ClientData client_data, long msecs, int periodic) +{ + Timer *t; + + if (free_timers != (Timer *) 0) + { + t = free_timers; + free_timers = t->next; + --free_count; + } + else + { + t = (Timer *) malloc (sizeof (Timer)); + if (t == (Timer *) 0) + return (Timer *) 0; + ++alloc_count; + } + + t->timer_proc = timer_proc; + t->client_data = client_data; + t->msecs = msecs; + t->periodic = periodic; + if (nowP != (struct timeval *) 0) + t->time = *nowP; + else + (void) gettimeofday (&t->time, (struct timezone *) 0); + t->time.tv_sec += msecs / 1000L; + t->time.tv_usec += (msecs % 1000L) * 1000L; + if (t->time.tv_usec >= 1000000L) + { + t->time.tv_sec += t->time.tv_usec / 1000000L; + t->time.tv_usec %= 1000000L; + } + t->hash = hash (t); + /* Add the new timer to the proper active list. */ + l_add (t); + ++active_count; + + return t; +} + + +struct timeval *tmr_timeout (struct timeval *nowP) +{ + long msecs; + static struct timeval timeout; + + msecs = tmr_mstimeout (nowP); + if (msecs == INFTIM) + return (struct timeval *) 0; + timeout.tv_sec = msecs / 1000L; + timeout.tv_usec = (msecs % 1000L) * 1000L; + return &timeout; +} + + +long tmr_mstimeout (struct timeval *nowP) +{ + int h; + int gotone; + long msecs, m; + register Timer *t; + + gotone = 0; + msecs = 0; /* make lint happy */ + /* Since the lists are sorted, we only need to look at the + ** first timer on each one. + */ + for (h = 0; h < HASH_SIZE; ++h) + { + t = timers[h]; + if (t != (Timer *) 0) + { + m = (t->time.tv_sec - nowP->tv_sec) * 1000L + + (t->time.tv_usec - nowP->tv_usec) / 1000L; + if (!gotone) + { + msecs = m; + gotone = 1; + } + else if (m < msecs) + msecs = m; + } + } + if (!gotone) + return INFTIM; + if (msecs <= 0) + msecs = 0; + return msecs; +} + + +void tmr_run (struct timeval *nowP) +{ + int h; + Timer *t; + Timer *next; + + for (h = 0; h < HASH_SIZE; ++h) + for (t = timers[h]; t != (Timer *) 0; t = next) + { + next = t->next; + /* Since the lists are sorted, as soon as we find a timer + ** that isn't ready yet, we can go on to the next list. + */ + if (t->time.tv_sec > nowP->tv_sec || + (t->time.tv_sec == nowP->tv_sec && t->time.tv_usec > nowP->tv_usec)) + break; + (t->timer_proc) (t->client_data, nowP); + if (t->periodic) + { + /* Reschedule. */ + t->time.tv_sec += t->msecs / 1000L; + t->time.tv_usec += (t->msecs % 1000L) * 1000L; + if (t->time.tv_usec >= 1000000L) + { + t->time.tv_sec += t->time.tv_usec / 1000000L; + t->time.tv_usec %= 1000000L; + } + l_resort (t); + } + else + tmr_cancel (t); + } +} + + +void tmr_reset (struct timeval *nowP, Timer * t) +{ + t->time = *nowP; + t->time.tv_sec += t->msecs / 1000L; + t->time.tv_usec += (t->msecs % 1000L) * 1000L; + if (t->time.tv_usec >= 1000000L) + { + t->time.tv_sec += t->time.tv_usec / 1000000L; + t->time.tv_usec %= 1000000L; + } + l_resort (t); +} + + +void tmr_cancel (Timer * t) +{ + /* Remove it from its active list. */ + l_remove (t); + --active_count; + /* And put it on the free list. */ + t->next = free_timers; + free_timers = t; + ++free_count; + t->prev = (Timer *) 0; +} + + +void tmr_cleanup (void) +{ + Timer *t; + + while (free_timers != (Timer *) 0) + { + t = free_timers; + free_timers = t->next; + --free_count; + free ((void *) t); + --alloc_count; + } +} + + +void tmr_destroy (void) +{ + int h; + + for (h = 0; h < HASH_SIZE; ++h) + while (timers[h] != (Timer *) 0) + tmr_cancel (timers[h]); + tmr_cleanup (); +} + + +/* Generate debugging statistics syslog message. */ +void tmr_logstats (long secs) +{ + syslog (LOG_INFO, " timers - %d allocated, %d active, %d free", + alloc_count, active_count, free_count); + if (active_count + free_count != alloc_count) + syslog (LOG_ERR, "timer counts don't add up!"); +} diff --git a/gb.httpd/src/timers.h b/gb.httpd/src/timers.h new file mode 100644 index 000000000..867484b84 --- /dev/null +++ b/gb.httpd/src/timers.h @@ -0,0 +1,110 @@ +/* timers.h - header file for timers package +** +** Copyright © 1995,1998,1999,2000 by Jef Poskanzer . +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +#ifndef _TIMERS_H_ +#define _TIMERS_H_ + +#include + +#ifndef INFTIM +#define INFTIM -1 +#endif /* INFTIM */ + +/* ClientData is a random value that tags along with a timer. The client +** can use it for whatever, and it gets passed to the callback when the +** timer triggers. +*/ +typedef union +{ + void *p; + int i; + long l; +} ClientData; + +extern ClientData JunkClientData; /* for use when you don't care */ + +/* The TimerProc gets called when the timer expires. It gets passed +** the ClientData associated with the timer, and a timeval in case +** it wants to schedule another timer. +*/ +typedef void TimerProc (ClientData client_data, struct timeval *nowP); + +/* The Timer struct. */ +typedef struct TimerStruct +{ + TimerProc *timer_proc; + ClientData client_data; + long msecs; + int periodic; + struct timeval time; + struct TimerStruct *prev; + struct TimerStruct *next; + int hash; +} Timer; + +/* Initialize the timer package. */ +extern void tmr_init (void); + +/* Set up a timer, either periodic or one-shot. Returns (Timer*) 0 on errors. */ +extern Timer *tmr_create (struct timeval *nowP, TimerProc * timer_proc, + ClientData client_data, long msecs, int periodic); + +/* Returns a timeout indicating how long until the next timer triggers. You +** can just put the call to this routine right in your select(). Returns +** (struct timeval*) 0 if no timers are pending. +*/ +extern struct timeval *tmr_timeout (struct timeval *nowP); + +/* Returns a timeout in milliseconds indicating how long until the next timer +** triggers. You can just put the call to this routine right in your poll(). +** Returns INFTIM (-1) if no timers are pending. +*/ +extern long tmr_mstimeout (struct timeval *nowP); + +/* Run the list of timers. Your main program needs to call this every so often, +** or as indicated by tmr_timeout(). +*/ +extern void tmr_run (struct timeval *nowP); + +/* Reset the clock on a timer, to current time plus the original timeout. */ +extern void tmr_reset (struct timeval *nowP, Timer * timer); + +/* Deschedule a timer. Note that non-periodic timers are automatically +** descheduled when they run, so you don't have to call this on them. +*/ +extern void tmr_cancel (Timer * timer); + +/* Clean up the timers package, freeing any unused storage. */ +extern void tmr_cleanup (void); + +/* Cancel all timers and free storage, usually in preparation for exitting. */ +extern void tmr_destroy (void); + +/* Generate debugging statistics syslog message. */ +extern void tmr_logstats (long secs); + +#endif /* _TIMERS_H_ */ diff --git a/gb.httpd/src/version.h b/gb.httpd/src/version.h new file mode 100644 index 000000000..5ad5212b8 --- /dev/null +++ b/gb.httpd/src/version.h @@ -0,0 +1,9 @@ +/* version.h - version defines for thttpd and libhttpd */ + +#ifndef _VERSION_H_ +#define _VERSION_H_ + +#define SERVER_SOFTWARE "thttpd/2.25b.patch2 " __DATE__ +#define SERVER_ADDRESS "http://www.acme.com/software/thttpd/" + +#endif /* _VERSION_H_ */ diff --git a/m4/gbhttpd.m4 b/m4/gbhttpd.m4 new file mode 100644 index 000000000..fea63a5a6 --- /dev/null +++ b/m4/gbhttpd.m4 @@ -0,0 +1,188 @@ +dnl +dnl Improved version of AC_CHECK_LIB +dnl +dnl Thanks to John Hawkinson (jhawk@mit.edu) +dnl +dnl usage: +dnl +dnl GB_AC_LBL_CHECK_LIB(LIBRARY, FUNCTION [, ACTION-IF-FOUND [, +dnl ACTION-IF-NOT-FOUND [, OTHER-LIBRARIES]]]) +dnl +dnl results: +dnl +dnl LIBS +dnl + +define(GB_AC_LBL_CHECK_LIB, +[AC_MSG_CHECKING([for $2 in -l$1]) +dnl Use a cache variable name containing both the library and function name, +dnl because the test really is for library $1 defining function $2, not +dnl just for library $1. Separate tests with the same $1 and different $2's +dnl may have different results. +ac_lib_var=`echo $1['_']$2['_']$5 | sed 'y%./+- %__p__%'` +AC_CACHE_VAL(ac_cv_lbl_lib_$ac_lib_var, +[ac_save_LIBS="$LIBS" +LIBS="-l$1 $5 $LIBS" +AC_TRY_LINK(dnl +ifelse([$2], [main], , dnl Avoid conflicting decl of main. +[/* Override any gcc2 internal prototype to avoid an error. */ +]ifelse(_AC_LANG_CURRENT, CPLUSPLUS, [#ifdef __cplusplus +extern "C" +#endif +])dnl +[/* We use char because int might match the return type of a gcc2 + builtin and then its argument prototype would still apply. */ +char $2(); +]), + [$2()], + eval "ac_cv_lbl_lib_$ac_lib_var=yes", + eval "ac_cv_lbl_lib_$ac_lib_var=no") +LIBS="$ac_save_LIBS" +])dnl +if eval "test \"`echo '$ac_cv_lbl_lib_'$ac_lib_var`\" = yes"; then + AC_MSG_RESULT(yes) + ifelse([$3], , +[changequote(, )dnl + ac_tr_lib=HAVE_LIB`echo $1 | sed -e 's/[^a-zA-Z0-9_]/_/g' \ + -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'` +changequote([, ])dnl + AC_DEFINE_UNQUOTED($ac_tr_lib, [], []) + LIBS="-l$1 $LIBS" +], [$3]) +else + AC_MSG_RESULT(no) +ifelse([$4], , , [$4 +])dnl +fi +]) + +dnl +dnl GB_AC_LBL_LIBRARY_NET +dnl +dnl This test is for network applications that need socket() and +dnl gethostbyname() -ish functions. Under Solaris, those applications +dnl need to link with "-lsocket -lnsl". Under IRIX, they need to link +dnl with "-lnsl" but should *not* link with "-lsocket" because +dnl libsocket.a breaks a number of things (for instance: +dnl gethostbyname() under IRIX 5.2, and snoop sockets under most +dnl versions of IRIX). +dnl +dnl Unfortunately, many application developers are not aware of this, +dnl and mistakenly write tests that cause -lsocket to be used under +dnl IRIX. It is also easy to write tests that cause -lnsl to be used +dnl under operating systems where neither are necessary (or useful), +dnl such as SunOS 4.1.4, which uses -lnsl for TLI. +dnl +dnl This test exists so that every application developer does not test +dnl this in a different, and subtly broken fashion. + +dnl It has been argued that this test should be broken up into two +dnl seperate tests, one for the resolver libraries, and one for the +dnl libraries necessary for using Sockets API. Unfortunately, the two +dnl are carefully intertwined and allowing the autoconf user to use +dnl them independantly potentially results in unfortunate ordering +dnl dependancies -- as such, such component macros would have to +dnl carefully use indirection and be aware if the other components were +dnl executed. Since other autoconf macros do not go to this trouble, +dnl and almost no applications use sockets without the resolver, this +dnl complexity has not been implemented. +dnl +dnl The check for libresolv is in case you are attempting to link +dnl statically and happen to have a libresolv.a lying around (and no +dnl libnsl.a). +dnl +AC_DEFUN([GB_AC_LBL_LIBRARY_NET], [ + # Most operating systems have gethostbyname() in the default searched + # libraries (i.e. libc): + AC_CHECK_FUNC(gethostbyname, , + # Some OSes (eg. Solaris) place it in libnsl: + GB_AC_LBL_CHECK_LIB(nsl, gethostbyname, , + # Some strange OSes (SINIX) have it in libsocket: + GB_AC_LBL_CHECK_LIB(socket, gethostbyname, , + # Unfortunately libsocket sometimes depends on libnsl. + # AC_CHECK_LIB's API is essentially broken so the + # following ugliness is necessary: + GB_AC_LBL_CHECK_LIB(socket, gethostbyname, + LIBS="-lsocket -lnsl $LIBS", + AC_CHECK_LIB(resolv, gethostbyname), + -lnsl)))) + AC_CHECK_FUNC(socket, , AC_CHECK_LIB(socket, socket, , + GB_AC_LBL_CHECK_LIB(socket, socket, LIBS="-lsocket -lnsl $LIBS", , + -lnsl))) + # DLPI needs putmsg under HPUX so test for -lstr while we're at it + AC_CHECK_LIB(str, putmsg) + ]) + +dnl +dnl Checks to see if struct tm has the BSD tm_gmtoff member +dnl +dnl usage: +dnl +dnl GB_AC_ACME_TM_GMTOFF +dnl +dnl results: +dnl +dnl HAVE_TM_GMTOFF (defined) +dnl +AC_DEFUN([GB_AC_ACME_TM_GMTOFF], + [AC_MSG_CHECKING(if struct tm has tm_gmtoff member) + AC_CACHE_VAL(ac_cv_acme_tm_has_tm_gmtoff, + AC_TRY_COMPILE([ +# include +# include ], + [u_int i = sizeof(((struct tm *)0)->tm_gmtoff)], + ac_cv_acme_tm_has_tm_gmtoff=yes, + ac_cv_acme_tm_has_tm_gmtoff=no)) + AC_MSG_RESULT($ac_cv_acme_tm_has_tm_gmtoff) + if test $ac_cv_acme_tm_has_tm_gmtoff = yes ; then + AC_DEFINE([HAVE_TM_GMTOFF], [], [if struct tm has the BSD tm_gmtoff member]) + fi]) + +dnl +dnl Checks to see if int64_t exists +dnl +dnl usage: +dnl +dnl GB_AC_ACME_INT64T +dnl +dnl results: +dnl +dnl HAVE_INT64T (defined) +dnl +AC_DEFUN([GB_AC_ACME_INT64T], + [AC_MSG_CHECKING(if int64_t exists) + AC_CACHE_VAL(ac_cv_acme_int64_t, + AC_TRY_COMPILE([ +# include ], + [int64_t i64], + ac_cv_acme_int64_t=yes, + ac_cv_acme_int64_t=no)) + AC_MSG_RESULT($ac_cv_acme_int64_t) + if test $ac_cv_acme_int64_t = yes ; then + AC_DEFINE([HAVE_INT64T], [], [if int64_t exists]) + fi]) + +dnl +dnl Checks to see if socklen_t exists +dnl +dnl usage: +dnl +dnl GB_AC_ACME_SOCKLENT +dnl +dnl results: +dnl +dnl HAVE_SOCKLENT (defined) +dnl +AC_DEFUN([GB_AC_ACME_SOCKLENT], + [AC_MSG_CHECKING(if socklen_t exists) + AC_CACHE_VAL(ac_cv_acme_socklen_t, + AC_TRY_COMPILE([ +# include +# include ], + [socklen_t slen], + ac_cv_acme_socklen_t=yes, + ac_cv_acme_socklen_t=no)) + AC_MSG_RESULT($ac_cv_acme_socklen_t) + if test $ac_cv_acme_socklen_t = yes ; then + AC_DEFINE([HAVE_SOCKLENT], [], [if socklen_t exists]) + fi]) diff --git a/main/gbx/gbx.c b/main/gbx/gbx.c index eb9980fad..0491f6ba8 100644 --- a/main/gbx/gbx.c +++ b/main/gbx/gbx.c @@ -66,6 +66,7 @@ FILE *log_file; static bool _welcome = FALSE; static bool _quit_after_main = FALSE; +static bool _run_httpd = FALSE; static void NORETURN my_exit(int ret) { @@ -89,10 +90,11 @@ static void NORETURN fatal(const char *msg, ...) my_exit(1); } -static void init(const char *file) +static void init(const char *file, int argc, char **argv) { COMPONENT_init(); FILE_init(); + EXEC_init(); CLASS_init(); CFILE_init(); @@ -112,6 +114,11 @@ static void init(const char *file) else fatal("no project file in '%s'.", file); } + + if (_run_httpd) + COMPONENT_exec("gb.httpd", argc, argv); + + PROJECT_load_finish(); } else STACK_init(); @@ -127,7 +134,7 @@ static void init(const char *file) static void main_exit(bool silent) { - // If the stack has not been initialized because the project could not be started, do it know + // If the stack has not been initialized because the project could not be started, do it now if (!SP) STACK_init(); @@ -207,17 +214,6 @@ int main(int argc, char *argv[]) if (setrlimit(RLIMIT_CORE, &rl)) perror(strerror(errno));*/ - /*VALUE c, s, a, b, r; - double v = (argc - 2) / 2.0; - c._float.value = cos(v); - s._float.value = __builtin_sin(v); - a._float.value = c._float.value * c._float.value; - b._float.value = s._float.value * s._float.value; - r._float.value = a._float.value + b._float.value; - - fprintf(stderr, "%.24g %.24g / %.24g + %.24g = %.24g %d\n", c._float.value, s._float.value, a._float.value, b._float.value, r._float.value, r._float.value == 1.0); - */ - MEMORY_init(); COMMON_init(); //STRING_init(); @@ -245,18 +241,19 @@ int main(int argc, char *argv[]) } printf( "Options:\n" - " -g enter debugging mode\n" - " -p activate profiling and debugging mode\n" - " -k do not unload shared libraries\n" + " -g enter debugging mode\n" + " -p activate profiling and debugging mode\n" + " -k do not unload shared libraries\n" + " -H --httpd run through an embedded http server\n" ); if (!EXEC_arch) { - printf(" -e evaluate an expression\n"); + printf(" -e evaluate an expression\n"); } printf( - " -V --version display version\n" - " -L --license display license\n" - " -h --help display this help\n" + " -V --version display version\n" + " -L --license display license\n" + " -h --help display this help\n" "\n" ); @@ -284,7 +281,7 @@ int main(int argc, char *argv[]) TRY { - init(NULL); + init(NULL, argc, argv); EVAL_string(argv[2]); } CATCH @@ -323,6 +320,10 @@ int main(int argc, char *argv[]) { _quit_after_main = TRUE; } + else if (is_long_option(argv[i], 'H', "httpd")) + { + _run_httpd = TRUE; + } else if (is_option(argv[i], '-')) { i++; @@ -359,7 +360,7 @@ int main(int argc, char *argv[]) TRY { - init(file); + init(file, argc, argv); if (!EXEC_arch) argv[0] = PROJECT_name; diff --git a/main/gbx/gbx_archive.c b/main/gbx/gbx_archive.c index 73833444d..982c150a1 100644 --- a/main/gbx/gbx_archive.c +++ b/main/gbx/gbx_archive.c @@ -75,7 +75,7 @@ ARCHIVE *ARCHIVE_create(const char *name, const char *path) return arch; } -static void load_exported_class(ARCHIVE *arch) +void ARCHIVE_load_exported_class(ARCHIVE *arch) { /*STREAM stream;*/ char *buffer; @@ -84,11 +84,19 @@ static void load_exported_class(ARCHIVE *arch) CLASS *class; CLASS **exported = NULL; int i; + COMPONENT *current; + if (arch->exported_classes_loaded) + return; + + current = COMPONENT_current; + + COMPONENT_current = (COMPONENT *)arch->current_component; + /* COMPONENT_current is set => it will look in the archive */ #if DEBUG_COMP - fprintf(stderr, "load_exported_class: %s (component: %s)\n", arch->name, COMPONENT_current ? COMPONENT_current->name : "?"); + fprintf(stderr, "load_exported_class: %s (component: %s)\n", arch->name, COMPONENT_current ? COMPONENT_current->name : "?"); #endif if (!FILE_exist(".list")) @@ -139,6 +147,10 @@ static void load_exported_class(ARCHIVE *arch) ARRAY_delete(&exported); FREE(&buffer, "load_exported_class"); + + arch->exported_classes_loaded = TRUE; + + COMPONENT_current = current; } #if 0 @@ -161,14 +173,6 @@ static void load_component(char *name) FREE(&buffer, "load_dependencies"); }*/ -static void load_archive(ARCHIVE *arch, const char *path) //, bool dep) -{ - arch->arch = ARCH_open(path); - load_exported_class(arch); - //if (dep) - // load_dependencies(arch); -} - static char *exist_library(const char *dir, const char *name) { char *path; @@ -180,7 +184,7 @@ static char *exist_library(const char *dir, const char *name) return NULL; } -void ARCHIVE_load(ARCHIVE *arch) //, bool dep) +void ARCHIVE_load(ARCHIVE *arch, bool load_exp) { char *path; @@ -204,7 +208,11 @@ void ARCHIVE_load(ARCHIVE *arch) //, bool dep) sprintf(path, ARCH_PATTERN, COMPONENT_path, arch->name); } - load_archive(arch, path); //, dep); + arch->arch = ARCH_open(path); + arch->current_component = COMPONENT_current; + + if (load_exp) + ARCHIVE_load_exported_class(arch); //, dep); } @@ -221,7 +229,7 @@ void ARCHIVE_create_main(const char *path) void ARCHIVE_load_main() { - load_exported_class(ARCHIVE_main); + ARCHIVE_load_exported_class(ARCHIVE_main); } diff --git a/main/gbx/gbx_archive.h b/main/gbx/gbx_archive.h index 026f773de..70f733e7f 100644 --- a/main/gbx/gbx_archive.h +++ b/main/gbx/gbx_archive.h @@ -39,7 +39,9 @@ typedef char *domain; TABLE *classes; const char *path; + void *current_component; unsigned translation_loaded : 1; + unsigned exported_classes_loaded : 1; } ARCHIVE; @@ -65,7 +67,8 @@ void ARCHIVE_load_main(void); ARCHIVE *ARCHIVE_create(const char *name, const char *path); void ARCHIVE_delete(ARCHIVE *arch); -void ARCHIVE_load(ARCHIVE *arch); +void ARCHIVE_load(ARCHIVE *arch, bool load_exp); +void ARCHIVE_load_exported_class(ARCHIVE *arch); bool ARCHIVE_get(ARCHIVE *arch, const char **ppath, ARCHIVE_FIND *find); diff --git a/main/gbx/gbx_component.c b/main/gbx/gbx_component.c index e7936873e..2df6465be 100644 --- a/main/gbx/gbx_component.c +++ b/main/gbx/gbx_component.c @@ -65,6 +65,7 @@ char *COMPONENT_path; static COMPONENT *_component_list = NULL; +static bool _load_all = FALSE; void COMPONENT_init(void) { @@ -123,11 +124,26 @@ void COMPONENT_load_all(void) COMPONENT_create("gb.debug"); } + _load_all = TRUE; + LIST_for_each(comp, _component_list) { comp->preload = TRUE; COMPONENT_load(comp); } + + _load_all = FALSE; +} + +void COMPONENT_load_all_finish(void) +{ + COMPONENT *comp; + + LIST_for_each(comp, _component_list) + { + if (comp->preload && comp->archive) + ARCHIVE_load_exported_class(comp->archive); + } } @@ -265,7 +281,7 @@ void COMPONENT_load(COMPONENT *comp) } if (comp->archive) - ARCHIVE_load(comp->archive); + ARCHIVE_load(comp->archive, !_load_all); comp->loading = FALSE; comp->loaded = TRUE; @@ -341,3 +357,16 @@ bool COMPONENT_get_info(const char *key, void **value) return TRUE; } + +void COMPONENT_exec(const char *name, int argc, char **argv) +{ + COMPONENT *comp; + + comp = COMPONENT_create(name); + + COMPONENT_load(comp); + + if (comp->library) + LIBRARY_exec(comp->library, argc, argv); +} + diff --git a/main/gbx/gbx_component.h b/main/gbx/gbx_component.h index 2b86f2be7..7d0e662ea 100644 --- a/main/gbx/gbx_component.h +++ b/main/gbx/gbx_component.h @@ -67,9 +67,12 @@ COMPONENT *COMPONENT_find(const char *name); bool COMPONENT_exist(const char *name); void COMPONENT_load(COMPONENT *comp); -void COMPONENT_load_all(void); void COMPONENT_unload(COMPONENT *comp); +void COMPONENT_load_all(void); +void COMPONENT_load_all_finish(void); + + COMPONENT *COMPONENT_next(COMPONENT *comp); void COMPONENT_translation_must_be_reloaded(void); @@ -80,4 +83,6 @@ void COMPONENT_signal(int signal, void *param); bool COMPONENT_get_info(const char *key, void **value); +void COMPONENT_exec(const char *name, int argc, char **argv); + #endif diff --git a/main/gbx/gbx_library.c b/main/gbx/gbx_library.c index eb00af46b..fc8df1373 100644 --- a/main/gbx/gbx_library.c +++ b/main/gbx/gbx_library.c @@ -274,6 +274,15 @@ int LIBRARY_load(LIBRARY *lib) return order; } +void LIBRARY_exec(LIBRARY *lib, int argc, char **argv) +{ + void (*func)(); + + func = get_symbol(lib, LIB_MAIN, FALSE); + if (func) + (*func)(argc, argv); +} + void LIBRARY_declare(GB_DESC **desc) { diff --git a/main/gbx/gbx_library.h b/main/gbx/gbx_library.h index aaec49203..f02bbe7e2 100644 --- a/main/gbx/gbx_library.h +++ b/main/gbx/gbx_library.h @@ -71,4 +71,6 @@ void LIBRARY_declare(GB_DESC **desc); bool LIBRARY_get_interface_by_name(const char *name, int version, void *iface); void LIBRARY_get_interface(LIBRARY *lib, int version, void *iface); +void LIBRARY_exec(LIBRARY *lib, int argc, char **argv); + #endif diff --git a/main/gbx/gbx_project.c b/main/gbx/gbx_project.c index 467ccd5ac..2f83f2c7b 100644 --- a/main/gbx/gbx_project.c +++ b/main/gbx/gbx_project.c @@ -461,17 +461,23 @@ bool PROJECT_load() if (len < 0) return TRUE; - /* Loads all component */ + // Loads all component COMPONENT_load_all(); - /* Loads main archive */ - ARCHIVE_load_main(); - - /* Startup class */ - PROJECT_class = CLASS_find(PROJECT_startup); return FALSE; } +void PROJECT_load_finish(void) +{ + // Load exported class of components written in Gambas + COMPONENT_load_all_finish(); + + // Loads main archive + ARCHIVE_load_main(); + + // Startup class + PROJECT_class = CLASS_find(PROJECT_startup); +} void PROJECT_exit(void) { diff --git a/main/gbx/gbx_project.h b/main/gbx/gbx_project.h index d34ad7082..d332d9ca2 100644 --- a/main/gbx/gbx_project.h +++ b/main/gbx/gbx_project.h @@ -52,6 +52,7 @@ EXTERN char *PROJECT_user_home; void PROJECT_init(const char *file); bool PROJECT_load(void); +void PROJECT_load_finish(void); void PROJECT_exit(void); char *PROJECT_get_home(void); void PROJECT_analyze_startup(char *addr, int len, PROJECT_COMPONENT_CALLBACK cb); diff --git a/main/share/gb_component.h b/main/share/gb_component.h index e98af53bf..c7d162524 100644 --- a/main/share/gb_component.h +++ b/main/share/gb_component.h @@ -43,6 +43,7 @@ typedef #define LIB_NEED "GB_NEED" #define LIB_GAMBAS "GB" #define LIB_GAMBAS_PTR "GB_PTR" +#define LIB_MAIN "GB_MAIN" #ifdef DONT_USE_LTDL #if defined(OS_MACOSX)