Compare commits

..

1 Commits

Author SHA1 Message Date
cvs-fast-export e1ec98d90b Synthetic commit for incomplete tag surflet-send-suspend-with-port 2003-01-22 12:53:46 +00:00
190 changed files with 2372 additions and 29876 deletions

31
.gitignore vendored
View File

@ -1,31 +0,0 @@
# CVS default ignores begin
tags
TAGS
.make.state
.nse_depinfo
*~
\#*
.#*
,*
_$*
*$
*.old
*.bak
*.BAK
*.orig
*.rej
.del-*
*.a
*.olb
*.o
*.obj
*.so
*.exe
*.Z
*.elc
*.ln
core
# CVS default ignores end
test-packages.scm
test
SSAX

28
COPYING
View File

@ -1,28 +0,0 @@
Copyright (c) 1994 by Brian D. Carlstrom and Olin Shivers.
Copyright (c) 1996-2004 by Mike Sperber.
Copyright (c) 1999-2004 by Martin Gasbichler.
Copyright (c) 1998-2004 by Eric Marsden.
Copyright (c) 2001-2004 by Andreas Bernauer
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the authors may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.

48
INSTALL
View File

@ -1,48 +0,0 @@
These are generic installation instructions for scsh packages.
Prerequisites
=============
The scsh installation library is required to install this package.
This library *must* be present on your system before the current
package can be installed. It can be obtained from the following Web
page:
http://lamp.epfl.ch/~schinz/scsh_packages/
The installation library comes with its own documentation which
explains in detail how to install and use scsh packages. It is
recommended that you read it before installing your first scsh
package. What follows is a very brief summary of this documentation,
intended to get you started quickly.
In addition, you need to have the Sunterlib library installed. See
http://www.scsh.net/resources/sunterlib.html
for more information about obtaining and installing Sunterlib.
Installation
============
Installation of a scsh package is performed by launching the
"scsh-install-pkg" script, which is part of the scsh installation
library. This script must be launched from within the directory which
resulted from the expansion of the current package's archive, i.e. the
one containing the file you are reading now.
A list of all the arguments accepted by the "scsh-install-pkg" script
can be obtained by launching it with the "--help" option. One of these
arguments, "--prefix", is mandatory and specifies the location where
installation should be performed. Ideally, you should use the same
prefix to install all scsh packages, as this makes them easier to
manage and use.
For example, to install the current package in
"/usr/local/share/scsh-modules", you should type the following:
scsh-install-pkg --prefix=/usr/local/share/scsh-modules
Provided that no errors are encountered during installation, a
message will be printed at the end explaining how to use the newly
installed package.

View File

@ -1,51 +0,0 @@
SHELL = /bin/sh
version_id = 2.1
distname = sunet-$(version_id)
distdir = /tmp
DISTFILES = COPYING README RELEASE INSTALL pkg-def.scm \
scheme/packages.scm \
scheme/httpd/*.scm scheme/httpd/surflets/*.scm \
scheme/ftpd/*.scm scheme/lib/*.scm \
doc/pdf/sunet.pdf doc/html \
doc/latex/*.tex doc/latex/*.sty doc/latex/*.t2p doc/latex/*.hdir \
web-server
sunet-$(version_id).tar.gz:
.PHONY: dist
dist:
cd doc/latex && $(MAKE) html
cd doc/latex && $(MAKE) man.pdf
cd web-server/root/htdocs
mkdir -p doc/pdf
cp doc/latex/man.pdf doc/pdf/sunet.pdf
mv doc/html/man.html doc/html/index.html
distname=$(distname) && \
distfile=$(distdir)/$$distname.tar.gz && \
if [ -d $(distdir) ] && \
[ -w $$distfile -o -w $(distdir) ]; then \
rm -f $$distname && \
ln -s . $$distname && \
files='' && \
for i in $(DISTFILES); do \
if [ "$$i" != "c/sysdep.h" ]; then \
files="$$files $$distname/$$i"; \
fi \
done && \
tar --exclude .cvsignore --exclude CVS -cf - $$files | \
gzip --best >$$distfile && \
rm $$distname; \
else \
echo "Can't write $$distfile" >&2; \
exit 1; \
fi
.PHONY: tags
tags:
find . -name "*.scm" | etags -

118
README
View File

@ -1,118 +0,0 @@
This is the Scheme Untergrund Networking Package.
The Scheme Untergrund Networking Package (SUnet, for short) is a
collection of applications and libraries for Internet hacking in
Scheme. It contains over 15000 lines of high-quality Scheme code that
runs under Scsh, the Scheme shell. SUnet makes extensive use of
Scsh's facilities for multi-threaded systems programming on Unix.
SUnet includes the following components:
* The SUnet Web server
This is a highly configurable HTTP 1.0 server in Scheme.
The server is accompanied some libraries which may also
be used separately:
* URI and URL parsers and unparsers
* a library for writing CGI scripts in Scheme
* server extensions for interfacing to CGI scripts
* server extensions for uploading Scheme code
* simple structured HTML output library
The server also ships with a sophisticated interface for writing
server-side Web applications called "SUrflets".
* The SUnet ftp server
This is a complete anonymous ftp server in Scheme.
* ftp client library
This library allows you to access ftp servers programmatically.
* Netrc library
This library parses authentication information contained in ~/.netrc.
* SMTP client library
This library allows you to forge mail from the comfort of your own
Scheme process.
* POP3 client library
This library allows you to access your POP3 mailbox from inside Scsh.
* RFC822 header library
This library parses email-style headers.
* Daytime and Time protocol client libraries
These libraries lets you find out what time it is without paying for a
Rolex.
* DNS client library
This is a complete, multithreaded DNS library.
* An ls clone
This library displays Unix-style directory listings without running ls.
Download
========
The SUnet code is available from
http://www.scsh.net/sunet/
To run the code, you need version 0.6.4 or later of Scsh, available from
http://www.scsh.net/
Installation
============
Starting with version 2.1 SUnet conforms to the packaging proposal for
scsh by Michel Schinz and needs Michel's installation library to
install properly. For more information, please see:
<http://lamp.epfl.ch/~schinz/scsh_packages/>
In short, this means that you can install SUnet by unpacking the SUnet
tarball and issuing the following command in the created directory:
scsh-install-pkg --prefix /path/to/your/package/root
See the file INSTALL for the generic installation instructions for
scsh packages.
You need to install version 4.9 of the SSAX package to use SUnet. SSAX
is available from <http://lamp.epfl.ch/~schinz/scsh_packages/>.
Sample Web Server
=================
The installation procedure also installs three scripts to run the
SUnet httpd in the directory
/path/to/your/package/root/0.6/sunet/web-server/
The three scripts are:
start-web-server for running a simple web server on port 8080
start-extended-web-server for running a web server with rman and info gateway
start-surflet-server for running a web server with SUrflets enabled
Pass the --help option to learn how to configure the scripts.
These scripts should provide a pretty good starting point to set up
your own server or serve your own SUrfelts.
Support
=======
Please direct questions, comments, answers about SUnet to the regular
scsh mailing list at
scsh-users@scsh.net
Relax, hack, and enjoy!
Dr. S.
Dr. S.
Martin Gasbichler
Eric Marsden
Andreas Bernauer

30
RELEASE
View File

@ -1,30 +0,0 @@
Scheme Untergrund Networking Library Release Notes
==================================================
We are pleased to release SUnet version 2.1.
SUnet runs under scsh 0.6.4. It is possible, but unlikely that it
runs under earlier versions of scsh. We recommend using version 0.6.6
for simple installation using the new packaging proposal. See file
INSTALL for more details.
The major addition of this release are SUrflets by Andreas
Bernauer. SUrflets are server-side scripts for the web-server written
in Scheme. Based on the idea of Christian Queinnec, SUrflets employ
first-class continuations to overcome the lack of state in the HTTP
protocol and thereby ease the writing of web applications
dramatically. SUrflets represent XHTML using Oleg Kiselyov's SSAX
package, which makes it possible to build on the full power of
s-expressions for generating XHTML.
This release also fixes a number of bugs in various parts of the
library.
Relay, hack, and enjoy!
Dr. S.
Dr. S.
Martin Gasbichler
Eric Marsden
Andreas Bernauer

1
doc/.gitignore vendored
View File

@ -1 +0,0 @@
pdf

1
doc/html/.gitignore vendored
View File

@ -1 +0,0 @@
index.html man--h.idx man--h.ilg man--h.ind man.hlog man-Z-A.scm man-Z-H-10.html man-Z-H-11.html man-Z-H-12.html man-Z-H-13.html man-Z-H-14.html man-Z-H-15.html man-Z-H-16.html man-Z-H-17.html man-Z-H-1.html man-Z-H-2.html man-Z-H-3.html man-Z-H-4.html man-Z-H-5.html man-Z-H-6.html man-Z-H-7.html man-Z-H-8.html man-Z-H-9.html man-Z-L.scm man-Z-S.css

View File

@ -1,8 +0,0 @@
*.aux
*.toc
*.dvi
*.ps
*.pdf
*.log
*.png
*.idx

View File

@ -1 +0,0 @@
../../web-server/root/htdocs/sunet-manual

View File

@ -1,37 +0,0 @@
.SUFFIXES: .idx .ind .tex .dvi .ps .pdf $(.SUFFIXES)
TEX= cgi-script.tex ftp.tex pdfcond.tex smtp.tex ftpd.tex pop3.tex \
uri.tex decls.tex httpd.tex netrc.tex rfc822.tex url.tex dns.tex \
intro.tex nettime.tex skeleton.tex
TEX2PAGE=tex2page
man.dvi: $(TEX) man.ind
man.ind: man.idx
man.pdf: $(TEX) man.ind
.dvi.ps:
dvips -j0 -o $@ $<
.tex.dvi:
latex $< && latex $<
rm $*.log
.tex.pdf:
pdflatex $< && thumbpdf $@ && pdflatex $<
rm $*.log
.idx.ind:
makeindex $<
clean:
-rm -f *.log *.png man.out man.dvi man.ps man.pdf thumb*.png
rm -rf ../../web-server/root/htdocs/sunet-manual
INSTALL_DATA= install -c -m 644
tar:
tar cf - *.tex sty | gzip > man.tar.gz
html: $(TEX)
$(TEX2PAGE) man && $(TEX2PAGE) man

View File

@ -1,24 +0,0 @@
\chapter{Writing CGI Scripts in Scheme}\label{cha:cgi-scripts}
%
The \ex{cgi-scripts} structure provides functionality useful for
writing CGI scripts in Scheme.
\defun{cgi-form-query}{}{data-alist}
\begin{desc}
CGI scripts receive their parameters in various ways, depending on
how they were called (e.g.\ by \ex{GET} method).
This procedure translates the delivered form data into an alist of
decoded strings, using the environment variables set by the server
(\ex{REQUEST\_METHOD}, \ex{QUERY\_STRING} (for a \ex{GET} request),
\ex{CONTENT\_LENGTH} (for a \ex{POST} request)). So a query string
like \codex{button=on\&\ob{}reply=Oh,\ob{}\%20yes} becomes an alist
\codex{(("button" . "on") ("reply" . "Oh, yes"))}
\ex{Cgi-form-query} only works for \ex{GET} and \ex{POST} methods.
\end{desc}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,296 +0,0 @@
% code.sty: -*- latex -*-
% Latex macros for a "weak" verbatim mode.
% -- like verbatim, except \, {, and } have their usual meanings.
% Environments: code, tightcode, codeaux, codebox, centercode
% Commands: \dcd, \cddollar, \cdmath, \cd, \codeallowbreaks, \codeskip, \^
% Already defined in LaTeX, but of some relevance: \#, \$, \%, \&, \_, \{, \}
% Changelog at the end of the file.
% These commands give you an environment, code, that is like verbatim
% except that you can still insert commands in the middle of the environment:
% \begin{code}
% for(x=1; x<loop_bound; x++)
% y += x^3; /* {\em Add in {\tt x} cubed} */
% \end{code}
%
% All characters are ordinary except \{}. To get \{} in your text,
% you use the commands \\, \{, and \}.
% These macros mess with the definition of the special chars (e.g., ^_~%).
% The characters \{} are left alone, so you can still have embedded commands:
% \begin{code} f(a,b,\ldots,y,z) \end{code}
% However, if your embedded commands use the formerly-special chars, as in
% \begin{code} x := x+1 /* \mbox{\em This is $y^3$} */ \end{code}
% then you lose. The $ and ^ chars are scanned in as non-specials,
% so they don't work. If the chars are scanned *outside* the code env,
% then you have no problem:
% \def\ycube{$y^3$}
% \begin{code} x := x+1 /* {\em This is \ycube} */ \end{code}
% If you must put special chars inside the code env, you do it by
% prefixing them with the special \dcd ("decode") command, that
% reverts the chars to back to special status:
% \begin{code} x := x+1 /* {\dcd\em This is $y^3$} */ \end{code}
% \dcd's scope is bounded by its enclosing braces. It is only defined within
% the code env. You can also turn on just $ with the \cddollar command;
% you can turn on just $^_ with the \cdmath command. See below.
%
% Alternatively, just use \(...\) for $...$, \sp for ^, and \sb for _.
% WARNING:
% Like \verb, you cannot put a \cd{...} inside an argument to a macro
% or a command. If you try, for example,
% \mbox{\cd{$x^y$}}
% you will lose. That is because the text "\cd{$x^y$}" gets read in
% as \mbox's argument before the \cd executes. But the \cd has to
% have a chance to run before LaTeX ever reads the $x^y$ so it can
% turn off the specialness of $ and ^. So, \cd has to appear at
% top level, not inside an argument. Similarly, you can't have
% a \cd or a \code inside a macro (Although you could use \gdef to
% define a macro *inside* a \cd, which you could then use outside.
% Don't worry about this if you don't understand it.)
% BUG: In the codebox env, the effect of a \dcd, \cddollar, or \cdmath
% command is reset at the end of each line. This can be hacked by
% messing with the \halign's preamble, if you feel up to it.
% Useage note: the initial newline after the \begin{code} or
% \begin{codebox} is eaten, but the last newline is not.
% So,
% \begin{code}
% foo
% bar
% \end{code}
% leaves one more blank line after bar than does
% \begin{code}
% foo
% bar\end{code}
% Moral: get in the habit of terminating code envs without a newline
% (as in the second example).
%
% All this stuff tweaks the meaning of space, tab, and newline.
%===============================================================================
% \cd@obeyspaces
% Turns all spaces into non-breakable spaces.
% Note: this is like \@vobeyspaces except without spurious space in defn.
% @xobeysp is basically a space; it's defined in latex.tex.
%
{\catcode`\ =\active\gdef\cd@obeyspaces{\catcode`\ =\active\let =\@xobeysp}}
% \cd@obeytabs
% Turns all tabs into 8 non-breakable spaces (which is bogus).
%
{\catcode`\^^I=\active %
\gdef\cd@obeytabs{\catcode`\^^I=\active\let^^I=\cd@tab}}
\def\cd@tab{\@xobeysp\@xobeysp\@xobeysp\@xobeysp\@xobeysp\@xobeysp\@xobeysp\@xobeysp}
% \cd@obeylines
% Turns all cr's into linebreaks. Pagebreaks are not permitted between lines.
% This is copied from lplain.tex's \obeylines, with the cr def'n changed.
%
{\catcode`\^^M=\active % these lines must end with %
\gdef\cd@obeylines{\catcode`\^^M=\active\let^^M=\cd@cr}}
% What ^M turns into. This def'n keeps blank lines from being compressed out.
\def\cd@cr{\par\penalty10000\leavevmode} % TeX magicness
%\def\cd@cr{\par\penalty10000\mbox{}} % LaTeX
% \codeallowbreaks
% Same as \cd@obeylines, except pagebreaks are allowed.
% Put this command inside a code env to allow pagebreaks.
{\catcode`\^^M=\active % these lines must end with %
\gdef\codeallowbreaks{\catcode`\^^M\active\let^^M\cd@crbr}}
%\def\cd@crbr{\leavevmode\endgraf} % What ^M turns into.
\def\cd@crbr{\par\leavevmode} % What ^M turns into.
% \cd@obeycrsp
% Turns cr's into non-breakable spaces. Used by \cd.
{\catcode`\^^M=\active % these lines must end with %
\gdef\cd@obeycrsp{\catcode`\^^M=\active\let^^M=\@xobeysp}}
% =============================================================================
% Set up code environment, in which most of the common special characters
% appearing in code are treated verbatim, namely: $&#^_~%
% \ { } are still enabled so that macros can be called in this
% environment. Use \\, \{, and \} to use these characters verbatim
% in this environment.
%
% Inside a group, you can make
% all the hacked chars special with the \dcd command
% $ special with the \cddollar command
% $^_ special with the \cdmath command.
% If you have a bunch of math $..$'s in your code env, then a global \cddollar
% or \cdmath at the beginning of the env can save a lot of trouble.
% When chars are special (e.g., after a \dcd), you can still get #$%&_{} with
% \#, \$, \%, \&, \_, \{, and \} -- this is standard LaTeX.
% Additionally, \\ gives \ inside the code env, and when \cdmath
% makes ^ special, it also defines \^ to give ^.
%The hacked characters can be made special again
% within a group by using the \dcd command.
% Note: this environment allows no breaking of lines whatsoever; not
% at spaces or hypens. To arrange for a break use the standard \- command,
% or a \discretionary{}{}{} which breaks, but inserts nothing. This is useful,
% for example for allowing hypenated identifiers to be broken, e.g.
% \def\={\discretionary{}{}{}} %optional break
% FOO-\=BAR.
\def\setupcode{\parsep=0pt\parindent=0pt%
\normalfont\ttfamily\frenchspacing\catcode``=13\@noligs%
\def\\{\char`\\}%
\let\dcd=\cd@dcd\let\cddollar=\cd@dollarspecial\let\cdmath=\cd@mathspecial%
\@makeother\$\@makeother\&\@makeother\#%
\@makeother\^\@makeother\_\@makeother\~%
\@makeother\%\cd@obeytabs\cd@obeyspaces}
% other: $&#^_~%
% left special: \{}
% unnecessary: @`'"
%% codebox, centercode
%%=============================================================================
%% The codebox env makes a box exactly as wide as it needs to be
%% (i.e., as wide as the longest line of code is). This is useful
%% if you want to center a chunk of code, or flush it right, or
%% something like that. The optional argument to the environment,
%% [t], [c], or [b], specifies how to vertically align the codebox,
%% just as with arrays or other boxes. Default is [c].
%% Must be a newline immediately after "\begin{codebox}[t]"!
{\catcode`\^^M=\active % these lines must end with %
\gdef\cd@obeycr{\catcode`\^^M=\active\let^^M=\cr}}
% If there is a [<letter>] option, then the following newline will
% be read *after* ^M is bound to \cr, so we're cool. If there isn't
% an option given (i.e., default to [c]), then the @\ifnextchar will
% gobble up the newline as it gobbles whitespace. So we insert the
% \cr explicitly. Isn't TeX fun?
\def\codebox{\leavevmode\@ifnextchar[{\@codebox}{\@codebox[c]\cr}} %]
\def\@codebox[#1]%
{\hbox\bgroup$\if #1t\vtop \else \if#1b\vbox \else \vcenter \fi\fi\bgroup%
\tabskip\z@\setupcode\cd@obeycr% just before cd@obey
\halign\bgroup##\hfil\span}
\def\endcodebox{\crcr\egroup\egroup\m@th$\egroup}
% Center the box on the page:
\newenvironment{centercode}%
{\begin{center}\begin{codebox}[c]}%
{\end{codebox}\end{center}}
%% code, codeaux, tightcode
%%=============================================================================
%% Code environment as described above. Lines are kept on one page.
%% This actually works by setting a huge penalty for breaking
%% between lines of code. Code is indented same as other displayed paras.
%% Note: to increase left margin, use \begin{codeaux}{\leftmargin=1in}.
% To allow pagebreaks, say \codeallowbreaks immediately inside the env.
% You can allow breaks at specific lines with a \pagebreak form.
%% N.B.: The \global\@ignoretrue command must be performed just inside
%% the *last* \end{...} before the following text. If not, you will
%% get an extra space on the following line. Blech.
%% This environment takes two arguments.
%% The second, required argument is the \list parameters to override the
%% \@listi... defaults.
%% - Usefully set by clients: \topsep \leftmargin
%% - Possible, but less useful: \partopsep
%% The first, optional argument is the extra \parskip glue that you get around
%% \list environments. It defaults to the value of \parskip.
\def\codeaux{\@ifnextchar[{\@codeaux}{\@codeaux[\parskip]}} %]
\def\@codeaux[#1]#2{%
\bgroup\parskip#1%
\begin{list}{}%
{\parsep\z@\rightskip\z@\listparindent\z@\itemindent\z@#2}%
\item[]\setupcode\cd@obeylines}%
\def\endcodeaux{\end{list}\leavevmode\egroup\ignorespaces\global\@ignoretrue}
%% Code env is codeaux with the default margin and spacing \list params:
\def\code{\codeaux{}} \let\endcode=\endcodeaux
%% Like code, but with no extra vertical space above and below.
\def\tightcode{\codeaux[=0pt]{\topsep\z@}}%
\let\endtightcode\endcodeaux
% {\vspace{-1\parskip}\begin{codeaux}{\partopsep\z@\topsep\z@}}%
% {\end{codeaux}\vspace{-1\parskip}}
% Reasonable separation between lines of code
\newcommand{\codeskip}{\penalty0\vspace{2ex}}
% \cd is used to build a code environment in the middle of text.
% Note: only difference from display code is that cr's are taken
% as unbreakable spaces instead of linebreaks.
\def\cd{\leavevmode\begingroup\ifmmode\let\startcode=\startmcode\else%
\let\startcode\starttcode\fi%
\setupcode\cd@obeycrsp\startcode}
\def\starttcode#1{#1\endgroup}
\def\startmcode#1{\hbox{#1}\endgroup}
% Restore $&#^_~% to their normal catcodes
% Define \^ to give the ^ char.
% \dcd points to this guy inside a code env.
\def\cd@dcd{\catcode`\$=3\catcode`\&=4\catcode`\#=6\catcode`\^=7%
\catcode`\_=8\catcode`\~=13\catcode`\%=14\def\^{\char`\^}}
% Selectively enable $, and $^_ as special.
% \cd@mathspecial also defines \^ give the ^ char.
% \cddollar and \cdmath point to these guys inside a code env.
\def\cd@dollarspecial{\catcode`\$=3}
\def\cd@mathspecial{\catcode`\$=3\catcode`\^=7\catcode`\_=8%
\def\^{\char`\^}}
% Change log:
% Started off as some macros found in C. Rich's library.
% Olin 1/90:
% Removed \makeatletter, \makeatother's -- they shouldn't be there,
% because style option files are read with makeatletter. The terminal
% makeatother screwed things up for the following style options.
% Olin 3/91:
% Rewritten.
% - Changed things so blank lines don't get compressed out (the \leavevmove
% in \cd@cr and \cd@crwb).
% - Changed names to somewhat less horrible choices.
% - Added lots of doc, so casual hackers can more easily mess with all this.
% - Removed `'"@ from the set of hacked chars, since they are already
% non-special.
% - Removed the bigcode env, which effect can be had with the \codeallowbreaks
% command.
% - Removed the \@noligs command, since it's already defined in latex.tex.
% - Win big with the new \dcd, \cddollar, and \cdmath commands.
% - Now, *only* the chars \{} are special inside the code env. If you need
% more, use the \dcd command inside a group.
% - \cd now works inside math mode. (But if you use it in a superscript,
% it still comes out full size. You must explicitly put a \scriptsize\tt
% inside the \cd: $x^{\cd{\scriptsize\tt...}}$. A \leavevmode was added
% so that if you begin a paragraph with a \cd{...}, TeX realises you
% are starting a paragraph.
% - Added the codebox env. Tricky bit involving the first line hacked
% with help from David Long.
% Olin 8/94
% Changed the font commands for LaTeX2e.

View File

@ -1,114 +0,0 @@
% css.t2p
% Dorai Sitaram
% 19 Jan 2001
% A basic style for HTML documents generated
% with tex2page.
\ifx\shipout\UNDEFINED
\cssblock
body {
color: black;
/* background-color: #e5e5e5;*/
background-color: #ffffff;
/*background-color: beige;*/
margin-top: 2em;
margin-left: 8%;
margin-right: 8%;
}
h1,h2,h3,h4,h5,h6 {
margin-top: .5em;
}
.partheading {
font-size: 100%;
}
.chapterheading {
font-size: 100%;
}
pre {
margin-left: 2em;
}
ol {
list-style-type: decimal;
}
ol ol {
list-style-type: lower-alpha;
}
ol ol ol {
list-style-type: lower-roman;
}
ol ol ol ol {
list-style-type: upper-alpha;
}
.scheme {
color: brown;
}
.scheme .keyword {
color: #990000;
font-weight: bold;
}
.scheme .builtin {
color: #990000;
}
.scheme .variable {
color: navy;
}
.scheme .global {
color: purple;
}
.scheme .selfeval {
color: green;
}
.scheme .comment {
color: teal;
}
.schemeresponse {
color: green;
}
.navigation {
color: red;
text-align: right;
font-style: italic;
}
.disable {
/* color: #e5e5e5; */
color: gray;
}
.smallcaps {
font-size: 75%;
}
.smallprint {
color: gray;
font-size: 75%;
text-align: right;
}
.smallprint hr {
text-align: left;
width: 40%;
}
\endcssblock
\fi
% ex:ft=css

View File

@ -1,6 +0,0 @@
% Loads cmtt fonts in on \tt. -*- latex -*-
% I prefer these to the Courier fonts that latex gives you w/postscript styles.
% Courier is too spidery and too wide -- it's hard to get 80 chars on a line.
% -Olin
\renewcommand{\ttdefault}{cmtt}

View File

@ -1,336 +0,0 @@
\makeatletter
\def\ie{\mbox{\emph{i.e.}}} % \mbox keeps the last period from
\def\Ie{\mbox{\emph{I.e.}}} % looking like an end-of-sentence.
\def\eg{\mbox{\emph{e.g.}}}
\def\Eg{\mbox{\emph{E.g.}}}
\def\etc{{\em etc.}}
\def\Lisp{\textsc{Lisp}}
\def\CommonLisp{\textsc{Common Lisp}}
\def\Ascii{\textsc{Ascii}}
\def\Ansi{\textsc{Ansi}}
\def\Unix{{Unix}} % Not smallcaps, according to Bart.
\def\Scheme{{Scheme}}
\def\scm{{Scheme 48}}
\def\RnRS{R5RS}
\def\Posix{\textsc{Posix}}
\def\sharpf{\textnormal{\texttt{\#f}}}
\def\sharpt{\textnormal{\texttt{\#t}}}
\newcommand{\synteq}{\textnormal{::=}}
\def\maketildeother{\catcode`\~=12}
\def\maketildeactive{\catcode`\~=13}
\def\~{\char`\~}
\newcommand{\evalsto}{\ensuremath{\Rightarrow}}
% One-line code examples
%\newcommand{\codex}[1]% One line, centred. Tight spacing.
% {$$\abovedisplayskip=.75ex plus 1ex minus .5ex%
% \belowdisplayskip=\abovedisplayskip%
% \abovedisplayshortskip=0ex plus .5ex%
% \belowdisplayshortskip=\abovedisplayshortskip%
% \hbox{\ttt #1}$$}
%\newcommand{\codex}[1]{\begin{tightinset}\ex{#1}\end{tightinset}\ignorespaces}
\newcommand{\codex}[1]{\begin{leftinset}\ex{#1}\end{leftinset}\ignorespaces}
\def\widecode{\codeaux{\leftmargin=0pt\topsep=0pt}}
\def\endwidecode{\endcodeaux}
% For multiletter vars in math mode:
\newcommand{\var}[1]{\mbox{\frenchspacing\it{#1}}}
\newcommand{\vari}[2]{\ensuremath{\mbox{\it{#1}}_{#2}}}
%% What you frequently want when you say \tt:
\def\ttchars{\catcode``=13\@noligs\frenchspacing}
\def\ttt{\normalfont\ttfamily\ttchars}
% Works in math mode; all special chars remain special; cheaper than \cd.
% Will not be correct size in super and subscripts, though.
\newcommand{\ex}[1]{{\normalfont\texttt{\ttchars #1}}}
\newenvironment{inset}
{\bgroup\parskip=1ex plus 1ex\begin{list}{}%
{\topsep=0pt\rightmargin\leftmargin}%
\item[]}%
{\end{list}\leavevmode\egroup\global\@ignoretrue}
\newenvironment{leftinset}
{\bgroup\parskip=1ex plus 1ex\begin{list}{}%
{\topsep=0pt}%
\item[]}%
{\end{list}\leavevmode\egroup\global\@ignoretrue}
\newenvironment{tightinset}
{\bgroup\parskip=0pt\begin{list}{}%
{\topsep=0pt\rightmargin\leftmargin}%
\item[]}%
{\end{list}\leavevmode\egroup\global\@ignoretrue}
\newenvironment{tightleftinset}
{\bgroup\parskip=0pt\begin{list}{}%
{\topsep=0pt}%
\item[]}%
{\end{list}\leavevmode\egroup\global\@ignoretrue}
\long\def\remark#1{\bgroup\small\begin{quote}\textsl{Remark: } #1\end{quote}\egroup}
\newenvironment{remarkenv}{\bgroup\small\begin{quote}\textsl{Remark: }}%
{\end{quote}\egroup}
\newcommand{\oops}[1]{\bgroup\small\begin{quote}\textsl{Oops: } #1\end{quote}\egroup}
\newcommand{\note}[1]{\{Note #1\}}
\newcommand{\itum}[1]{\item{\bf #1}\\*}
% For use in code. The \llap magicness makes the lambda exactly as wide as
% the other chars in \tt; the \hskip shifts it right a bit so it doesn't
% crowd the left paren -- which is necessary if \tt is cmtt.
% Note that (\l{x y} (+ x y)) uses the same number of columns in TeX form
% as it produces when typeset. This makes it easy to line up the columns
% in your input. \l is bound to some useless command in LaTeX, so we have to
% define it w/renewcommand.
\let\oldl\l %Save the old \l on \oldl
\renewcommand{\l}[1]{\ \llap{$\lambda$\hskip-.05em}\ (#1)}
% This one is for the rare (lambda x ...) case -- it doesn't have the
% column-invariant property. Oh, well.
\newcommand{\lx}[1]{\ \llap{$\lambda$\hskip-.05em}\ {#1}}
% For subcaptions
\newcommand{\subcaption}[1]
{\unskip\vspace{-2mm}\begin{center}\unskip\em#1\end{center}}
%%% T release notes stuff
\newlength{\notewidth}
\setlength{\notewidth}{\textwidth}
\addtolength{\notewidth}{-1.25in}
%\newcommand{\remark} [1]
% {\par\vspace{\parskip}
% \parbox[t]{.75in}{\sc Remark:}
% \parbox[t]{\notewidth}{\em #1}
% \vspace{\parskip}
% }
\newenvironment{optiontable}%
{\begin{tightinset}\renewcommand{\arraystretch}{1.5}%
\begin{tabular}{@{}>{\ttt}ll@{}}}%
{\end{tabular}\end{tightinset}}%
\newenvironment{desctable}[1]%
{\begin{inset}\renewcommand{\arraystretch}{1.5}%
\begin{tabular}{lp{#1}}}%
{\end{tabular}\end{inset}}
\def\*{{\ttt *}}
% Names of things
\newcommand{\keyword} [1]{\index{#1}{\normalfont\textsf{#1}}}
% \ex{#1} and also generates an index entry.
\newcommand{\exi}[1]{\index{#1@\texttt{#1}}\ex{#1}}
\newcommand{\indextt}[1]{\index{#1@\texttt{#1}}}
\newcommand{\evalto}{$\Longrightarrow$\ }
\renewcommand{\star}{$^*$\/}
\newcommand{\+}{$^+$}
% Semantic domains, used to indicate the type of a value
\newcommand{\sem}{\normalfont\itshape} %semantic font
\newcommand{\semvar}[1]{\textit{#1}} %semantic font
\newcommand{\synvar}[1]{\textrm{\textit{$\left<\right.$#1$\left.\right>$}}} %syntactic font
\newcommand{\type}{\sem}
\newcommand{\zeroormore}[1]{{\sem #1$_1$ \ldots #1$_n$}}
\newcommand{\oneormore}[1]{{\sem #1$_1$ #1$_2$ \ldots #1$_n$}}
\newcommand{\proc} {{\sem procedure}}
\newcommand{\boolean} {{\sem boolean}}
\newcommand{\true} {{\sem true}}
\newcommand{\false} {{\sem false}}
\newcommand{\num} {{\sem number}}
\newcommand{\fixnum} {{\sem fixnum}}
\newcommand{\integer} {{\sem integer}}
\newcommand{\real} {{\sem real}}
\newcommand{\character} {{\sem character}}
\newcommand{\str} {{\sem string}}
\newcommand{\sym} {{\sem symbol}}
\newcommand{\location} {{\sem location}}
\newcommand{\object} {{\sem object}}
\newcommand{\error} {{\sem error}}
\newcommand{\syntaxerror} {{\sem syntax error}}
\newcommand{\readerror} {{\sem read error}}
\newcommand{\undefined} {{\sem undefined}}
\newcommand{\noreturn} {{\sem no return value}}
\newcommand{\port} {{\sem port}}
% semantic variables
\newcommand{\identifier} {{\sem identifier}}
\newcommand{\identifiers} {\zeroormore{\<ident>}}
\newcommand{\expr} {{\sem expression}}
\newcommand{\body} {{\sem body}}
\newcommand{\valueofbody} {{\sem value~of~body}}
\newcommand{\emptylist} {{\sem empty~list}}
\newcommand{\car} {\keyword{car}}
\newcommand{\cdr} {\keyword{cdr}}
\newcommand{\TMPDIR}{\texttt{\$TMPDIR}}
% generally useful things
% For line-breaking \tt stuff.
\renewcommand{\=}{\discretionary{-}{}{-}}
\newcommand{\ob}{\discretionary{}{}{}} % Optional break.
\newcommand{\indx}[1]{#1 \index{ #1 }}
%\newcommand{\gloss}[1]{#1 \glossary{ #1 }}
% This lossage produces #2 if #1 is zero length, otw #3.
% We use it to conditionally add a space between the procedure and
% the args in procedure prototypes, but only if there are any args--
% we want to produce "(read)", not "(read )".
\newlength{\voidlen}
\newcommand{\testvoid}[3]{\settowidth\voidlen{#1}\ifdim\voidlen>0in{#3}\else{#2}\fi}
% Typeset a definition prototype line, e.g.:
% (cons <arg1> <arg2>) -> pair procedure
%
% Five args are: proc-name args ret-value(s) type index-entry
\newcommand{\dfnix}[5]
{\hbox to \linewidth{\ttchars%
{\ttt(#1\testvoid{#2}{}{\ }{\sem{#2}}\testvoid{#2}{}{\/})\hskip 1em minus
0.5em$\longrightarrow$\hskip 1em minus 0.5em{\sem{#3}}\hfill\quad\textnormal{#4}}}\index{#5}}
\newcommand{\dfnx}[4] {\dfnix{#1}{#2}{#3}{#4}{#1@\texttt{#1}}}
\newcommand{\dfn} {\par\medskip\dfnx} % Takes 4 args, actually.
\newcommand{\dfni} {\par\medskip\dfnix} % Takes 5 args, actually.
\newcommand{\defvar} {\par\medskip\defvarx} % Takes 4 args, actually.
\newcommand{\defvarx}[2]%
{\index{#1}
\hbox to \linewidth{\ttchars{{\ttt{#1}} \hfill #2}}}%
\newcommand{\defsyn}{\par\medskip\defsynx} % Takes 2 arguments, actually.
\newcommand{\defsynx}[2]%
{\index{#1}
\hbox to \linewidth{\ttchars{{(#1 \ttt{#2})} \hfill syntax}}}%
% Typeset the protocol line, then do the following descriptive text indented.
% If you want to group two procs together, do the first one with a \dfn,
% then the second one, and the documentation, with a \defndescx.
% This one doesn't put whitespace above. Use it immediately after a \dfn
% to group two prototype lines together.
\newenvironment{dfndescx}[4]%
{\dfnx{#1}{#2}{#3}{#4}\begin{desc}}{\end{desc}}
\newenvironment{dfndesc}[4] % This one puts whitespace above.
{\par\medskip\begin{dfndescx}{#1}{#2}{#3}{#4}}
{\end{dfndescx}}
\newenvironment{desc}%
{\nopagebreak[2]%
\smallskip
\bgroup\begin{list}{}{\topsep=0pt\parskip=0pt}\item[]}
{\end{list}\leavevmode\egroup\global\@ignoretrue}
\def\defun#1#2#3{\dfn{#1}{#2}{#3}{procedure}} % preskip
\newcommand{\defunx}[3]{\dfnx{#1}{#2}{#3}{procedure}} % no skip
\newenvironment{defundescx}[3]%
{\begin{dfndescx}{#1}{#2}{#3}{procedure}}
{\end{dfndescx}}
\newenvironment{defundesc}[3]%
{\begin{dfndesc}{#1}{#2}{#3}{procedure}}
{\end{dfndesc}}
\newenvironment{column}{\begin{tabular}[t]{@{}l@{}}}{\end{tabular}}
\newenvironment{exampletable}%
{\begin{leftinset}%
\newcommand{\header}[1]{\multicolumn{2}{@{}l@{}}{##1}\\}%
\newcommand{\splitline}[2]%
{\multicolumn{2}{@{}l@{}}{##1}\\\multicolumn{2}{@{}l@{}}{\qquad\evalto\quad{##2}}}
\begin{tabular}{@{}l@{\quad\evalto\quad}l@{}}}%
{\end{tabular}\end{leftinset}}
% Put on blank lines in a code env to allow a pagebreak.
\newcommand{\cb}{\pagebreak[0]}
\newenvironment{boxedcode}
{\begin{inset}\tabular{|l|}\hline}
{\\ \hline \end{tabular}\end{inset}}
% A ragged-right decl that doesn't redefine \\ -- for use in tables.
\newcommand{\raggedrightparbox}{\let\temp=\\\raggedright\let\\=\temp}
\newenvironment{boxedfigure}[1]%
{\begin{figure}[#1]\begin{boxedminipage}{\linewidth}\vskip 1.5ex}
{\end{boxedminipage}\end{figure}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%for surflet howto
\def\surflet{SUrflet\xspace}
\def\surflets{SUrflets\xspace}
\def\scsh{scsh\xspace}
\def\sunet{SUnet\xspace}
%From surflet/decls.tex
%{\theoremstyle{break}
%\theoremheaderfont{\normalfont\bfseries\em}
% \newtheorem{proglist}{Listing}[section]}
%\setlength{\theorempreskipamount}{1.5ex plus0.2ex minus0.2ex}
%\setlength{\theorempostskipamount}{2ex plus0.5ex minus0.2ex}
% These environments differ from the other definition by the
% positioning of \normalem
\newenvironment{listing}
{\ULforem\begin{alltt}\small\normalem}
{\end{alltt}}
\newenvironment{reflisting}[1]
{\ULforem[\refinlisting{#1}]\begin{alltt}\small\normalem}
{\end{alltt}}
\newcommand{\contatlisting}[1]{%
{\normalfont\textit{$<$continued in listing~\ref{#1}\/$>$}}}
\newcommand{\contfromlisting}[1]{%
{\normalfont\textit{$<$continued from listing~\ref{#1}\/$>$}}}
\newcommand{\refinlisting}[1]{%
{\normalfont\textit{referenced in listing~\ref{#1}}}}
\newcommand{\seelisting}[1]{%
{\normalfont{\textit{$<$see listing~\ref{#1}\/$>$}}}}
% Use url-package to get function names line-breaked at - / +
% by infos in /usr/share/texmf/tex/latex/misc/url.sty
%%\newcommand\breakfuntt{\begingroup \urlstyle{tt}%
%%\@ifundefined{selectfont}{\def\UrlFont{\tt}}{\def\UrlFont{\ttfamily}}%
%%\def\UrlBreaks{\do\-\do\/\do\+}\def\UrlNoBreaks{\do\!}\Url
%%}
\newcommand{\name}[1]{\texttt{#1}}
%\newcommand{\object}[1]{\breakfuntt{#1}}
\newcommand{\file}[1]{\textttt{#1}}
\newcommand{\codemph}[1]{\emph{#1}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\makeatother

View File

@ -1,482 +0,0 @@
\chapter{DNS Client Library}\label{cha:dns}
%
\begin{description}
\item[Used files:] dns.scm
\item[Name of the package:] dns
\end{description}
%
\section{Overview}
The \ex{dns} structure contains a library for querying DNS servers.
The library contains sophisticated replacements for scsh's interface
to the \ex{gethostbyname} and \ex{gethostbyaddr} and many extensions
to these functions.
The main features of the libraray include:
\begin{itemize}
\item Complete implementation of the DNS protocol
\item Concurrent contacting of multiple DNS servers without blocking
the scsh process
\item Internal caching of DNS responses
\item Parsing of \texttt{resolv.conf}, including \texttt{search}
entries to generate FQDNs from unqualified host names
\item Rich condition hierarchie
\end{itemize}
\section{Conditions}
The library defines a set of conditions raised by the procedures of
the library. The supertype of these conditions is \exi{dns-error}.
\defun{dns-error?}{thing}{\boolean}
\begin{desc}
The predicate for \ex{dns-error} conditions.
\end{desc}
\defun{dns-error->string} {dns-error-condition} {\str}
\begin{desc}
Returns a string with the description of the condition.
\end{desc}
\defvar{parse-error}{condition}
\defvarx{unexpected-eof-from-server}{condition}
\defvarx{bad-address}{condition}
\defvarx{no-nameservers}{condition}
\defvarx{bad-nameserver}{condition}
\defvarx{not-a-hostname}{condition}
\defvarx{not-a-ip} {condition}
\begin{desc}
\end{desc}
\defvar {dns-format-error} {condition}
\defvarx {dns-server-failure} {condition}
\defvarx {dns-name-error} {condition}
\defvarx {dns-not-implemented} {condition}
\defvarx {dns-refused} {condition}
\begin{desc}
These conditons correspond to errors returned by the DNS server.
They are all subtypes of the \exi{dns-server-error} condition which
in turn is a subtype of \ex{dns-error}.
\end{desc}
\defun{dns-server-error?}{thing}{\boolean}
\begin{desc}
The predicate for \ex{dns-server-error} conditions.
\end{desc}
\defun{parse-error?}{thing} {\boolean}
\defunx{unexpected-eof-from-server?}{thing} {\boolean}
\defunx{bad-address?}{thing} {\boolean}
\defunx{no-nameservers?}{thing} {\boolean}
\defunx{bad-nameserver?}{thing} {\boolean}
\defunx{not-a-hostname?}{thing} {\boolean}
\defunx{not-a-ip?}{thing} {\boolean}
\defunx{dns-format-error?} {thing} {\boolean}
\defunx{dns-server-failure?} {thing} {\boolean}
\defunx{dns-name-error?} {thing} {\boolean}
\defunx{dns-not-implemented?} {thing} {\boolean}
\defunx{dns-refused?} {thing} {\boolean}
\begin{desc}
The type predicates for the conditions above.
\end{desc}
\section{High-level Interface}
\def\ipaddr{IP-address\xspace}
\def\ipstring{IP-string\xspace}
\def\fqdn{FQDN\xspace}
The library uses an internal store to cache data obtained from DNS
servers. All procedures take a boolean flag \var{use-cache?} that
indicates whether the cache should be used or not. \var{use-cache?}
defaults to true.
\defun{dns-clear-cache!}{}{\undefined}
\begin{desc}
This procedure erases all information stored in the internal cache.
\end{desc}
The library is further capable of parsing the contents of
\texttt{/etc/resolv.conf} (see Section~\ref{sec:dns-rc}). The
nameservers listed there are the default value for the optional
argument \var{nameserver list} which many procedures of the library
accept. \var{Nameserver} is either a \ipaddr or a dotted IP string.
\defun{dns-lookup-name}{\fqdn [nameserver list][use-cache?]}{\ipaddr}
\begin{desc}
Given the FQDN of a host, \ex{dns-lookup-ip} returns the IP address.
The optional argument specifes the name servers to query, it defaults
to the ones found in \texttt{/etc/resolv.conf}.
\end{desc}
\defun{dns-lookup-ip}{\ipstring/\ipaddr [nameserver list][use-cache?]}{\fqdn}
\begin{desc}
Looks up the FQDN for the given IP address. The optional argument
specifes the name servers to query, it defaults to the ones found in
\texttt{/etc/resolv.conf}.
\end{desc}
\defun{dns-lookup-nameserver}{\ipstring/\ipaddr [nameserver list][use-cache?]}{\ipaddr list}
\begin{desc}
Looks up an authoritative name server for a hostname, returns a list
of name servers.
\end{desc}
\defun{dns-lookup-mail-exchanger}{\ipstring/\ipaddr [nameserver list][use-cache?]}{\fqdn list}
\begin{desc}
Looks up mail-exchangers for a hostname und returns them in a list
sorted by preference.
\end{desc}
\defun{socket-address->fqdn}{socket-address [nameserver list][use-cache?]}{\fqdn}
\begin{desc}
Returns the FQDN for of the address bound to argument. The argument
\var{cache?} indicates whether the internal cache may be queried to
obtain the information.
\end{desc}
\defun{maybe-dns-lookup-name}{\fqdn [nameserver list][use-cache?]}{\ipaddr or \sharpf}
\defunx{maybe-dns-lookup-ip}{\ipstring/\ipaddr [nameserver list][use-cache?]}{\fqdn{} or \sharpf}
\begin{desc}
These procedures provide the same functionality as
\ex{dns-lookup-name} and \ex{dns-lookup-ip} but return \sharpf{} in
case of an \ex{dns-error}.
\end{desc}
\defun{host-fqdn} {name/socket-address [nameserver list][use-cache?]}{\fqdn}
\defunx{system-fqdn}{[nameserver list][use-cache?]}{\fqdn}
\begin{desc}
\ex{host-fqdn} returns the fully qualified domain name (FQDN) for
its argument which can be either a unqualified host name or a socket
address. The procedure \ex{system-fqdn} returns the FQDN of the
local host. These procedures use a list of domain names obtained
from \texttt{/etc/resolv.conf} to the generate FQDNs and try to
resolve these FQDNs.
\end{desc}
\defun{dns-check-nameservers} {[nameserver list]} {\undefined}
\begin{desc}
\ex{dns-check-namservers} checks if the given nameservers are reachable.
If no argument is given, the nameservers in \texttt{/etc/resolv.conf}
are checked. Information about the status of the nameservers is printed
to the current output port.
\end{desc}
\section{Low-level Interface}
This section describes a set of data structures and procedures which
directly correspond to the data flow of the DNS protocol. The central
entity is a \var{message}, the abstraction of the packet sent to the
server or received from the server (The DNS protocol uses the same
data format for both directions). A \var{dns-message} encapsulates the
query message sent to the server, the response message received from
the server, and some additional information the library gathered while
generating the \var{dns-message}.
\defunx{dns-get-information}{message protocol answer-okay? [nameserver
list][use-cache?]}{dns-message}
\begin{desc}
Most general way to submit a DNS query. The message is sent to the
name servers via \var{protocol} which can be either
\ex{(network-procotcol tcp)} or {(network-protocol udp)}, both
members of of the enumerated type \ex{network-protocol}. After
receiving the reply, \ex{dns-get-information} applies the predicate
\var{answer-okay?} to the message. If it returns \sharpf{} and the
answer is not authoritative additional name servers sent with the
reply are checked until an authoritative answer is found. If the
predicate returns \sharpf{} but the answer is authoritative a
\var{bad-address} condition is signalled.
\end{desc}
\dfn{network-protocol}{protocol-name}{network-protocol}{syntax}
\defunx{network-protocol?}{thing}{\boolean}
\begin{desc}
Constructor and predicate for the enumerated type
\ex{network-protocol} with the possible protocol names \ex{tcp} and
\ex{udp}.
\end{desc}
\defun{dns-lookup}{\ipstring/\ipaddr type [nameserver list][use-cache?]}{dns-message}
\begin{desc}
Convenient shortcut to submit a DNS query. The return value
is a \ex{dns-message} structure:
\end{desc}
\defun{dns-message?}{thing}{\boolean}
\defunx{dns-message-query}{dns-message}{message}
\defunx{dns-message-reply}{dns-message}{message}
\defunx{dns-message-cache?}{dns-message}{\boolean}
\defunx{dns-message-protocol}{dns-message}{protocol}
\defunx{dns-message-tried-nameservers}{dns-message}{}
\begin{desc}
A \var{dns-message} records the query sent to the server and the
reply from the server. It also contains information whether the
library took the reply from the cache, which protocol was used and
to which nameservers the query was sent.
\end{desc}
\defun{pretty-print-dns-message}{dns-message [output-port]}{\undefined}
\begin{desc}
Pretty prints a DNS message to \var{out-port} which defaults to the
current output port.
\end{desc}
\defun{message?}{thing}{\boolean}{}
\defunx{message-header}{message}{header}
\defunx{message-questions}{message}{question list}
\defunx{message-answers}{message}{resource-record list}
\defunx{message-nameservers}{message}{resource-record list}
\defunx{message-additionals}{message}{resource-record list}
\defunx{message-source}{message}{char list}
\begin{desc}
A \ex{message} represents the data sent to the DNS server or
received from the DNS server. The DNS protocol uses the same message
format for queries and replies. In queries only the header and the
questions is present, a reply may contain answers, name servers and
and additional informations as resource records. \ex{Message-source}
returns the actual data sent over the network.
\end{desc}
\defun{make-query-message header}{header question [questions]}{message}
\begin{desc}
The procedure generates a message the supplied questions,
\var{header}, and the standard message values for queries.
\end{desc}
\defun{make-simple-query-message}{name type class}{message}
\begin{desc}
This simplified constructor generates a message with one question
which is built from the parameters, and the standard header flags
for queries and the standard message values for queries.
\end{desc}
\defun{header?}{thing}{\boolean}
\defunx{header-id}{header}{number}
\defunx{header-flags}{header}{flags}
\defunx{header-question-count}{header}{number}
\defunx{header-answer-count}{header}{number}
\defunx{header-nameserver-count}{header}{number}
\defunx{header-additional-count}{header}{number}
\begin{desc}
Every DNS message contains a header which stores information about
the data present in the message and contains flags for the query.
\end{desc}
\defun{flags?}{thing}{\boolean}
\defunx{flags-query-type}{flags}{'query or 'response}
\defunx{flags-opcode}{flags}{number}
\defunx{flags-authoritative?}{flags}{\boolean}
\defunx{flags-truncated?}{flags}{\boolean}
\defunx{flags-recursion-desired?}{flags}{\boolean}
\defunx{flags-recursion-available?}{flags}{\boolean}
\defunx{flags-zero}{flags}{0}
\defunx{flags-response-code}{flags}{number}
\begin{desc}
Flags occur within the header of a DNS message. The boolean value
returned from \ex{flags-authoritative} indicates whether the message
was sent from a authoritative server, \ex{flags-truncated?} should
always be \sharpf as the library automatically uses the TCP protocol
is the UDP message size is not sufficied.
\end{desc}
\defun{question?}{thing}{\boolean}
\defunx{question-name}{question}{\str}
\defunx{question-type}{question}{message-type}
\defunx{question-class}{question}{message-class}
\begin{desc}
A question sent to the DNS server.
\end{desc}
The type and class of the question and answer are elements of
enumerated types:
\dfn{message-class}{class-name}{message-class}{syntax}
\defunx{message-class?}{thing}{\boolean}
\defunx{message-class-name}{message-class}{symbol}
\defunx{message-class-number}{message-class}{number}
\begin{desc}
\ex{message-class} constructs a member of the enumerated type,
\ex{message-class?} is the type predicate, \ex{message-class-name}
returns the symbol and \ex{message-class-number} the number used for
the class in the DNS protocol.
\end{desc}
The possible names for the classes are:
\begin{description}
\item[\ex{in}] The Internet
\item[\ex{cs}] obsolete
\item[\ex{ch}] the CHAOS class
\item[\ex{hs}] Hesoid
\end{description}
\dfn{message-type}{type-name}{message-type}{syntax}
\defunx{message-type?}{thing}{\boolean}
\defunx{message-type-name}{message-type}{symbol}
\defunx{message-type-number}{message-type}{number}
\begin{desc}
\ex{message-type} constructs a member of the enumeration from name
\synvar{type-name} listed in Table~\ref{tab:message-types}.
\ex{message-type?} is the type predicate, \ex{message-type-name}
returns the name, and \ex{message-type-number} the number used for
the class the DNS protocol.
\end{desc}
\begin{table}[htb]
\centering
\begin{tabular}{|l|l|}
\hline
\ex{a}& a host address\\\hline
\ex{ns}&an authoritative name server\\\hline
\ex{md}&(obsolete)\\\hline
\ex{mf}&(obsolete)\\\hline
\ex{cname}&the canonical name for an alias\\\hline
\ex{soa}& marks the start of a zone of authority\\\hline
\ex{mb}&(experimental)\\\hline
\ex{mg}&(experimental)\\\hline
\ex{mr}&(experimental)\\\hline
\ex{null}& (experimental)\\\hline
\ex{wks}& a well known service description\\\hline
\ex{ptr}& a domain name pointer\\\hline
\ex{hinfo}& host information\\\hline
\ex{minfo}& (experimental)\\\hline
\ex{mx}& mail exchange\\\hline
\ex{txt}& text strings\\\hline
\end{tabular}
\caption{Message types}
\label{tab:message-types}
\end{table}
\defun{resource-record?}{thing}{\boolean}
\defunx{resource-record-name}{resource-record}{\str}
\defunx{resource-record-type}{resource-record}{message-type}
\defunx{resource-record-class}{resource-record}{message-class}
\defunx{resource-record-ttl}{resource-record}{number}
\defunx{resource-record-data}{resource-record}{resource-record-data-\dots}
\begin{desc}
A resource record as returned from the DNS server. The actual data
of the record is stored in the \texttt{resource-record-data} field.
It is one of the record types for resource record data described
below.
\end{desc}
\defun{resource-record-data-a?}{thing}{\boolean}
\defunx{resource-record-data-a-ip}{resource-record-data-a}{\ipaddr}
\begin{desc}
An address resource record which holds an internet address.
\end{desc}
\defun{resource-record-data-ns?}{thing}{\boolean}
\defunx{resource-record-data-ns-name}{resource-record-data-ns}{\fqdn}
\begin{desc}
A name server resource record containing the FQDN of the name server.
\end{desc}
\defun{resource-record-data-cname?}{thing}{\boolean}
\defunx{resource-record-data-cname-name}{resource-record-data-cname}{\fqdn}
\begin{desc}
A canonical name resource record which contains the canonical or
primary name of the owner.
\end{desc}
\defun{resource-record-data-mx?}{thing}{\boolean}
\defunx{resource-record-data-mx-preference}{resource-record-data-mx}{number}
\defunx{resource-record-data-mx-exchanger}{resource-record-data-mx}{\fqdn}
\begin{desc}
A mail exchange resource record with the preference and the FQDN of
a host willing to act as a mail exchange.
\end{desc}
\defun{resource-record-data-ptr?}{thing}{\boolean}
\defunx{resource-record-data-ptr-name}{resource-record-data-ptr}{\str}
\begin{desc}
A pointer resource record which points to some other domain name.
\end{desc}
\defun{resource-record-data-soa?}{thing}{\boolean}
\defunx{resource-record-data-soa-mname}{resource-record-data-soa}{\fqdn}
\defunx{resource-record-data-soa-rname}{resource-record-data-soa}{\fqdn}
\defunx{resource-record-data-soa-serial}{resource-record-data-soa}{number}
\defunx{resource-record-data-soa-refresh}{resource-record-data-soa}{number}
\defunx{resource-record-data-soa-retry}{resource-record-data-soa}{number}
\defunx{resource-record-data-soa-expire}{resource-record-data-soa}{number}
\defunx{resource-record-data-soa-minimum}{resource-record-data-soa}{number}
\begin{desc}
A start of a zone of authority resource record.
\end{desc}
The protocol specifies other possible values for the \texttt{resource-record-data}
field but we where no able to find test cases for them.
\defun{cache?}{thing}{\boolean}
\defunx{cache-answer}{cache}{dns-message}
\defunx{cache-ttl}{cache}{number}
\defunx{cache-time}{cache}{number}
\begin{desc}
A cache data structure corresponds to a saved answer to a previous
query. \ex{cache-answer} returns the saved message, \ex{cache-ttl}
returns the time when the cache entry expires and \ex{cache-time}
returns the time the entry was created.
\end{desc}
\section{Parsing \texttt{/etc/resolv.conf}}
\label{sec:dns-rc}
\defvar{resolv.conf-parse-error} {condition}
\defun{resolv.conf-parse-error?}{thing}{\boolean}
\begin{desc}
The code signals the condition \var{resolv.conf-parse-error} if a
parse error occurs while scanning \texttt{/etc/resolv.conf}. It is a
subtype of the \var{dns-error} condition.
\ex{resolv.conf-parse-error?} is the type predicate for this
condition.
\end{desc}
\defun{resolv.conf}{}{{symbol$\rightarrow$string} alist}
\begin{desc}
Returns the contents of \texttt{/etc/resolv.conv} as an alist with
the possible keys \texttt{nameserver}, \texttt{domain},
\texttt{search}, \texttt{sortlist} and \texttt{options}.
Note that the library caches the contents of
\texttt{/etc/resolv.conv} and \ex{resolv.conf} only really opens the
file if its modification time is more recent than the modification
time of the cache.
\end{desc}
\defun{parse-resolv.conf!}{}{\undefined}
\begin{desc}
Parses the contents of \texttt{/etc/resolv.conv} and updates the
internal cache of the library.
\end{desc}
\defun{dns-find-nameserver-list}{}{\fqdn list}
\begin{desc}
Returns a list of name servers from \texttt{/etc/resolv.conf}
\end{desc}
\defun{dns-find-nameserver}{}{\fqdn}
\begin{desc}
Returns the first name servers found in \texttt{/etc/resolv.conf}.
\ex{dns-find-nameserver} raises \ex{no-nameservers} if
\texttt{/etc/resolv.conf} does not contain a \texttt{nameserver}
entry.
\end{desc}
\defun{domains-for-search}{}{\str{} list}
\begin{desc}
Parses \texttt{/etc/resolv.conf} and extracts the domains specified
by the \texttt{search} keyword.
\end{desc}
\section{IP Addresses as Dotted Strings}
%
\begin{description}
\item[Used files:] ip.scm
\item[Name of the package:] ips
\end{description}
%
The structure \ex{ips} provides a small set of procedures for turning
the human-readable form of IP addresses (``dotted strings'') into 32
bits numbers.
\defun{address32->ip-string}{\ipaddr}{ip-string}
\defun{ip-string->address32}{ip-string}{\ipaddr}
\defun{ip-string?}{string}{\boolean}
\begin{desc}
Tests whether \var{string} is a valid dotted string for an IP
address.
\end{desc}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,163 +0,0 @@
\chapter{FTP Client}\label{cha:ftp}
The \ex{ftp} structure lets you transfer files between networked
machines from the Scheme Shell, using the File Transfer Protocol as
described in RFC~959.
Some of the procedures in this module extract useful information from
the server's reply, such as the size of a file, or the name of the
directory we have moved to. These procedures return the extracted
information, or, if the server's response doesn't match the expected
code from the server, a catchable \ex{ftp-error} is raised.
\defun{ftp-connect}{host login password passive? [log-port]}{connection}
\begin{desc}
Open a command connection with the remote machine \var{host} and
login on that server with \var{login} and \var{password}.
\var{Login} and \var{password} can be \sharpf, in which case the
information is extracted from the user's \ex{.netrc} file if necessary.
If \var{log-port} is specified, it must be an output port: this
starts logging the conversation with the server to that port. Note
that the log contains passwords in clear text.
\end{desc}
\dfn{ftp-type}{\synvar{name}}{ftp-type}{syntax}
\defunx{ftp-set-type!}{connection ftp-type}{undefined}
\begin{desc}
This change the transfer mode for future file transfers. The
transfer mode is specfified by \var{ftp-type} which can be created
with the \ex{ftp-type} macro. \synvar{Name} must be either
\ex{binary} for binary data or \ex{ascii} for text.
\end{desc}
\defun{ftp-rename}{connection old new}{undefined}
\begin{desc}
This changes the name of \var{old} on the remote host to \var{new}
(assuming sufficient permissions). \var{Old} and \var{new} are
strings.
\end{desc}
\defun{ftp-delete}{connection file}{undefined}
\begin{desc}
This deletes \var{file} from the remote host (assuming the user has
appropriate permissions).
\end{desc}
\defun{ftp-cd}{connection dir}{undefined}
\begin{desc}
This changes the current directory on the server.
\end{desc}
\defun{ftp-cdup}{connection}{undefined}
\begin{desc}
This move to the parent directory on the server.
\end{desc}
\defun{ftp-pwd}{connection}{string}
\begin{desc}
Return the current directory on the remote host, as a string.
\end{desc}
\defun{ftp-ls}{connection [dir]}{list}
\begin{desc}
This returns a list of filenames on the remote host, either from the
current directory (if \var{dir} is not specified), or from the
directory specified by \var{dir}.
\end{desc}
\defun{ftp-dir}{connection [dir]}{status}
\begin{desc}
This returns a list of long-form file name entries on the remote
host, either from the current directory (if \var{dir} is not
specified), or from the directory specified by \var{dir}. (Note
that the format for the long-form entries is not specified by the
FTP standard.)
\end{desc}
\defun{ftp-get}{connection remote-file proc}{undefined}
\begin{desc}
This downloads \var{remote-file} from the FTP server.
\ex{Ftp-get} establishes a data conneciton to the server, attaches
an input port to the data connection, and calls \var{proc} on that
port.
\end{desc}
\defun{ftp-put}{connection remote-file proc}{undefined}
\begin{desc}
This uploads \var{remote-file} to the FTP server. \ex{Ftp-put}
establishes a data conneciton to the server, attaches an output port
to the data connection, and calls \var{proc} on that port.
\end{desc}
\defun{ftp-append}{connection remote-file proc}{undefined}
\begin{desc}
This appends data to \var{remote-file} on the FTP server.
\ex{Ftp-append} establishes a data conneciton to the server,
attaches an output port to the data connection, and calls \var{proc}
on that port.
\end{desc}
\defun{ftp-rmdir}{connection dir}{undefined}
\begin{desc}
This removes the directory \var{dir} from the remote host (assuming
sufficient permissions).
\end{desc}
\defun{ftp-mkdir}{connection dir}{undefined}
\begin{desc}
This create a new directory named \var{dir} on the remote host
(assuming sufficient permissions).
\end{desc}
\defun{ftp-modification-time}{connection file}{date}
\begin{desc}
This requests the time of the last modification of \var{file} on the
remote host, and on success return a Scsh date record. (This command
is not part of RFC~959 and is not implemented by all servers, but is
useful for mirroring.)
\end{desc}
\defun{ftp-size}{connection file}{integer}
\begin{desc}
This returns the size of \var{file} in bytes. (This command is not
part of RFC~959 and is not implemented by all servers.)
\end{desc}
\defun{ftp-quit}{connection}{undefined}
\begin{desc}
This closes the connection to the remote host. The \var{connection}
object is useless after a quit command.
\end{desc}
\defun{ftp-quot}{connection command}{status}
\begin{desc}
This sends a \var{command} verbatim to the remote server and wait
for a response. The response text is returned verbatim.
\end{desc}
\defun{ftp-error?}{thing}{boolean}
\begin{desc}
This returns \sharpt{} if \var{thing} is a \ex{ftp-error} object,
otherwise \sharpf.
\end{desc}
\defun{copy-port->port-binary}{input-port oputput-port}{undefined}
\defunx{copy-port->port-ascii}{input-port oputput-port}{undefined}
\defunx{copy-ascii-port->port}{input-port oputput-port}{undefined}
\begin{desc}
These procedures are useful for downloading and uploading data to an
FTP connection via \ex{ftp-get}, \ex{ftp-get}, and \ex{ftp-append}.
They all copy data from one port to another.
\ex{Copy-port->port-binary} copies verbatim, while the other two
perform CR/LF conversion for ASCII data transfers.
\ex{Copy-port->port-ascii} adds CR/LFs at line endings on output,
whereas \ex{Copy-ascii-port->port} removes CR/LFs at line endings
end replaces them by ordinary LFs.
\end{desc}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,115 +0,0 @@
\chapter{FTP Server}\label{cha:ftpd}
The \ex{ftpd} structure contains a complete anonymous ftp server.
\defun{ftpd}{options}{\noreturn}
\defunx{ftp-inetd}{options}{\noreturn}
\begin{desc}
\ex{Ftpd} starts the server, using \var{anonymous-home} as the
root directory of the server.
\ex{ftpd-inetd} is the version to be used from \ex{inetd}.
\ex{Ftpd-inetd} handles the connection through the current standard
output and input ports.
\end{desc}
%
The \var{options} argument can be constructed through a number of
procedures with names of the form \texttt{with-\ldots}. Each of these
procedures either creates a fresh options value or adds a
configuration parameter to an old options argument. The configuration
parameter value is always the first argument, the (old) options value
the optional second one. Here they are:
\defun{with-port}{port [options]}{options}
\begin{desc}
This specifies the port on which the server listens. Defaults to 21.
\end{desc}
\defun{with-anonymous-home}{string [options]}{options}
\begin{desc}
This specifies the home directory for anonymous logins. Defaults to
\verb|"~ftp"|.
\end{desc}
\defun{with-banner}{list [options]}{options}
\begin{desc}
This specifies an alternative greeting banner for those members of
the Untergrund who prefer to remain covert. The banner is
represented as a list of strings, one for each line of output.
\end{desc}
\defun{with-log-port}{output-port [options]}{options}
\begin{desc}
If this is non-\sharpf, ex{ftpd} outputs a log entry for each file
sent or retrieved on \var{output-port}. Defaults to \sharpf.
\end{desc}
\defun{with-dns-lookup?}{boolean [options]}{options}
\begin{desc}
If \var{dns-lookup?} is \sharpt, the log file will contain the host
names instead of their IP addresses. If \var{dns-lookup?} is \sharpf,
the log will only contain IP addresses. Defaults to \sharpf.
\end{desc}
%
The \ex{make-ftpd-options} eases the construction of the options
argument:
%
\defun{make-ftpd-options}{transformer value \ldots}{options}
\begin{desc}
This constructs an options value from an argument list of parameter
transformers and parameter values. The arguments come in pairs,
each an option transformer from the list above, and a value for that
parameter. \ex{Make-ftpd-options} returns the resulting options value.
\end{desc}
The log format of \ex{ftpd} is the same as the one of
\ex{wuftpd}. The entries look like this:
%
\begin{verbatim}
Fri Apr 19 17:08:14 2002 4 134.2.2.171 56881 /files.lst b _ i a nop@ssword ftp 0 *
\end{verbatim}
%
These are the fields:
\begin{enumerate}
\item Current date and time. This field contains
spaces and is 24 characters long.
\item Transfer time in seconds.
\item Remote host IP (wu-ftpd puts the name here).
\item File size in bytes
\item Name of file (spaces are converted to underscores)
\item Transfer type: \underline{a}scii or \underline{b}inary (image type).
\item Special action flags. As \ex{ftpd} does not support any special
action, it always has \ex{\_} here.
\item File was sent to user (\underline{o}utgoing) or received from user
(\underline{i}ncoming)
\item \underline{A}nonymous access
\item Anonymous ftp password.
\item Service name---always \ex{ftp}.
\item Authentication mode (always ``none'' = `\ex{0}').
\item Authenticated user ID (always ``not available'' = `\ex{*}')
\end{enumerate}
The server also writes log information to the syslog facility.
The following syslog levels occur in the output:
\begin{description}
\item[\ex{notice}]
\begin{itemize}
\item messages concerning \emph{connections} (establishing connection,
connection refused, closing connection due to timeout, etc.)
\item the execution of the \ex{STOR} command\\
Its success (\ie
somebody is putting something on your server via ftp, also known as
\ex{PUT}) is also logged at \ex{notice}.
\item internal errors
\item Unix errors
\item reaching of actually unreachable case branches
\end{itemize}
\item[\ex{info}] Messages concerning all other commands,
including the \ex{RETR} command.
\item[\ex{debug}] all other messages, including debug messages
\end{description}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,16 +0,0 @@
% headings.tex -*- latex -*-
% Quieter headings that the ones used in article.sty.
% This is not a style option. Don't say [headings].
% Instead, say \input{headings} after the \documentstyle.
% -Olin 7/91
\makeatletter
\def\section{\@startsection {section}{1}{\z@}{-3.5ex plus -1ex minus
-.2ex}{2.3ex plus .2ex}{\large\normalfont\bfseries}}
\def\subsection{\@startsection{subsection}{2}{\z@}{-3.25ex plus -1ex minus
-.2ex}{1.5ex plus .2ex}{\normalsize\normalfont\bfseries}}
\def\subsubsection{\@startsection{subsubsection}{3}{\z@}{-3.25ex plus
-1ex minus -.2ex}{1.5ex plus .2ex}{\normalsize\normalfont\bfseries}}
\makeatother

View File

@ -1,742 +0,0 @@
\chapter{HTTP server}\label{cha:httpd}
%
The SUnet HTTP Server is a complete industrial-strength implementation
of the HTTP 1.0 protocol. It is highly configurable and allows the writing
of dynamic web pages that run inside the server without going through
complicated and slow protocols like CGI or Fast/CGI.
\section{Starting and configuring the server}
All procedures described in this section are exported by the
\texttt{httpd} structure.
The Web server is started by calling the \ex{httpd} procedure, which takes
one argument, an options value:
\defun{httpd}{options}{\noreturn}
\begin{desc}
This procedure starts the server. The \var{options} argument
specifies various configuration parameters, explained below.
The server's basic loop is to wait on the port for a connection from
an HTTP client. When it receives a connection, it reads in and
parses the request into a special request data structure. Then the
server forks a thread which binds the current I/O ports to the
connection socket, and then hands off to the top-level
request handler (which must be specified in the options). The
request handler is responsible for actually serving
the request---it can be any arbitrary computation. Its output goes
directly back to the HTTP client that sent the request.
Before calling the request handler to service the request, the HTTP
server installs an error handler that fields any uncaught error,
sends an error reply to the client, and aborts the request
transaction. Hence any error caused by a request handler will be
handled in a reasonable and robust fashion.
\end{desc}
%
The \var{options} argument can be constructed through a number of procedures
with names of the form \texttt{with-\ldots}. Each of these procedures
either creates a fresh options value or adds a configuration parameter
to an old options argument. The configuration parameter value is
always the first argument, the (old) options value the optional second
one. Here they are:
\defun{with-port}{port [options]}{options}
\begin{desc}
This specifies the port on which the server listens. Defaults to 80.
\end{desc}
\defun{with-root-directory}{root-directory [options]}{options}
\begin{desc}
This specifies the current directory of the server. Note that this
is \emph{not} the document root directory. Defaults to \texttt{/}.
\end{desc}
\defun{with-fqdn}{fqdn [options]}{options}
\begin{desc}
This specifies the fully-qualified domain name the server uses in
automatically generated replies, or \ex{\#f} if the server should
query DNS for the fully-qualified domain name.. Defaults to \ex{\#f}.
\end{desc}
\defun{with-reported-port}{reported-port [options]}{options}
\begin{desc}
This specifies the port number the server uses in automatically
generated replies or \ex{\#f} if the reported port is the same as
the port the server is listening on. (This is useful if you're
running the server through an accelerating proxy.) Defaults to
\ex{\#f}.
\end{desc}
\defun{with-server-admin}{mail-address [options]}{options}
\begin{desc}
This specifies the email address of the server administrator the
server uses in automatically generated replies. Defaults to \ex{\#f}.
\end{desc}
\defun{with-request-handler}{request-handler [options]}{options}
\begin{desc}
This specifies the request handler of the server to which the server
delegates the actual work. More on that subject below in
Section~\ref{httpd:request-handlers}. This parameter must be specified.
\end{desc}
\defun{with-simultaneous-requests}{requests [options]}{options}
\begin{desc}
This specifies a limit on the number of simultaneous requests the
server servers. If that limit is exceeded during operation, the
server will hold off on new requests until the number of
simultaneous requests has sunk below the limit again. If this
parameter is \ex{\#f}, no limit is imposed. Defaults to \ex{\#f}.
\end{desc}
\defun{with-log-file}{log-file [options]}{options}
\begin{desc}
This specifies the name of a log file for the server where it writes
Common Log Format logging information. It can also be a port in
which case the information is logged to that port, or \ex{\#f} for
no logging. Defaults to \ex{\#f}.
To allow rotation of log files, the server re-opens the log file
whenever it receives a \texttt{USR1} signal.
\end{desc}
\defun{with-syslog?}{syslog? [options]}{options}
\begin{desc}
This specifies whether the server will log information about
incoming to the Unix syslog facility. Defaults to \ex{\#t}.
\end{desc}
\defun{with-resolve-ips?}{resolve-ip? [options]}{options}
\begin{desc}
This specifies whether the server writes the domain names rather
than numerical IPs to the output log it produces. Defaults to
\ex{\#t}.
\end{desc}
To avoid paranthitis, the \ex{make-httpd-options} procedure eases the
construction of the options argument:
\defun{make-httpd-options}{transformer value \ldots}{options}
\begin{desc}
This constructs an options value from an argument list of parameter
transformers and parameter values. The arguments come in pairs,
each an option transformer from the list above, and a value for that
parameter. \ex{Make-httpd-options} returns the resulting options value.
\end{desc}
For example,
\begin{alltt}
(httpd (make-httpd-options
with-request-handler (rooted-file-handler "/usr/local/etc/httpd")
with-root-directory "/usr/local/etc/httpd"))
\end{alltt}
%
starts the server on port 80 with
\ex{/usr/local/etc/httpd} as its root directory and
lets it serve any file out from this directory.
% #### note about rooted-file-handler
\section{Requests}
\label{httpd:requests}
Request handlers operate on \textit{requests} which contain the
information needed to generate a page. The relevant procedures to
dissect requests are defined in the \texttt{httpd-requests} structure:
\defun{request?}{value}{boolean}
\defunx{request-method}{request}{string}
\defunx{request-uri}{request}{string}
\defunx{request-url}{request}{url}
\defunx{request-version}{request}{pair}
\defunx{request-headers}{request}{list}
\defunx{request-socket}{request}{socket}
\begin{desc}
The procedure inspect request values. \ex{Request?} is a predicate
for requests. \ex{Request-method} extracts the method of the HTTP
request; it's a string such as \verb|"GET"|, \verb|"PUT"|.
\ex{Request-uri} returns the escaped URI string as read from request
line. \ex{Request-url} returns an HTTP URL value (see the
description of the \ex{url} structure in \ref{cha:url}).
\ex{Request-version} returns \verb|(major . minor)| integer pair
representing the version specified in the HTTP request.
\ex{Request-headers} returns an association lists of header field
names and their values, each represented by a list of strings, one
for each line. \ex{Request-socket} returns the socket connected
to the client.\footnote{Request handlers should not perform I/O on the
request record's socket. Request handlers are frequently called
recursively, and doing I/O directly to the socket might bypass a
filtering or other processing step interposed on the current I/O ports
by some superior request handler.}
\end{desc}
\section{Responses}
\label{sec:http-responses}
A path handler must return a \textit{response} value representing the
content to be sent to the client. The machinery presented here for
constructing responses lives in the \ex{httpd-responses} structure.
\defun{make-response}{status-code maybe-message seconds mime extras
body}{response}
\begin{desc}
This procedure constructs a response value. \var{Status-code} is an
HTTP status code (more on that below). \var{Maybe-message} is a a
message elaborating on the circumstances of the status code; it can
also be \sharpf{} meaning that the server should send a default
message associated with the status code. \var{Seconds} natural
number indicating the time the content was created, typically the
value of \verb|(time)|. \var{Mime} is a string indicating the MIME
type of the response (such as \verb|"text/html"| or
\verb|"application/octet-stream"|). \var{Extras} is an association
list with extra headers to be added to the response; its elements
are pairs, each of which consists of a symbol representing the field
name and a string representing the field value. \var{Body}
represents the body of the response; more on that below.
\end{desc}
\defun{make-redirect-response}{location}{response}
\begin{desc}
This is a helper procedure for constructing HTTP redirections. The
server will serve the new file indicated by \var{location}.
\var{Location} must be URI-encoded and begin with a slash.
\end{desc}
\defun{make-error-response}{status-code request [message] extras \ldots}{response}
\begin{desc}
This is a helper procedure for constructing error responses.
\var{code} is status code of the response (see below). \var{Request}
is the request that led to the error. \var{Message} is an optional
string containing an error message written in HTML, and \var{extras}
are further optional arguments containing further message lines to
be added to the web page that's generated.
\ex{Make-error-response} constructs a response value which generates
a web page containg a short explanatory message for the error at hand.
\end{desc}
\begin{table}[htb]
\centering
\begin{tabular}{|l|l|l|}
\hline
ok & 200 & OK\\\hline
created & 201 & Created\\\hline
accepted & 202 & Accepted\\\hline
prov-info & 203 & Provisional Information\\\hline
no-content & 204 & No Content\\\hline
mult-choice & 300 & Multiple Choices\\\hline
moved-perm & 301 & Moved Permanently\\\hline
moved-temp & 302 & Moved Temporarily\\\hline
method & 303 & Method (obsolete)\\\hline
not-mod & 304 & Not Modified\\\hline
bad-request & 400 & Bad Request\\\hline
unauthorized & 401 & Unauthorized\\\hline
payment-req & 402 & Payment Required\\\hline
forbidden & 403 & Forbidden\\\hline
not-found & 404 & Not Found\\\hline
method-not-allowed & 405 & Method Not Allowed\\\hline
none-acceptable & 406 & None Acceptable\\\hline
proxy-auth-required & 407 & Proxy Authentication Required\\\hline
timeout & 408 & Request Timeout\\\hline
conflict & 409 & Conflict\\\hline
gone & 410 & Gone\\\hline
internal-error & 500 & Internal Server Error\\\hline
not-implemented & 501 & Not Implemented\\\hline
bad-gateway & 502 & Bad Gateway\\\hline
service-unavailable & 503 & Service Unavailable\\\hline
gateway-timeout & 504 & Gateway Timeout\\\hline
\end{tabular}
\caption{HTTP status codes}
\label{tab:status-code-names}
\end{table}
\dfn{status-code}{\synvar{name}}{status-code}{syntax}
\defunx{name->status-code}{symbol}{status-code}
\defunx{status-code-number}{status-code}{integer}
\defunx{status-code-message}{status-code}{string}
\begin{desc}
The \ex{status-code} syntax returns a status code where
\synvar{name} is the name from Table~\ref{tab:status-code-names}.
\ex{Name->status-code} also returns a status code for a name
represented as a symbol. For a given status code,
\ex{status-code-number} extracts its number, and
\ex{status-code-message} extracts its associated default message.
\end{desc}
\section{Response Bodies}
\label{httpd:response-bodies}
A \textit{response body} represents the body of an HTTP response.
There are several types of response bodies, depending on the
requirements on content generation.
\defun{make-writer-body}{proc}{body}
\begin{desc}
This constructs a response body from a \textit{writer}---a procedure
that prints the page contents to a port. The \var{proc} argument
must be a procedure accepting an output port (to which \var{proc}
prints the body) and the options value passed to the \ex{httpd}
invocation.
\end{desc}
\defun{make-reader-writer-body}{proc}{body}
\begin{desc}
This constructs a response body from a \textit{reader/writer}---a
procedure that prints the page contents to a port, possibly after
reading input from the socket of the HTTP connection. The
\var{proc} argument must be a procedure accepting three arguments:
an input port (associated with the HTTP connection socket), an
output port (to which \var{proc} prints the body), and the options
value passed to the \ex{httpd} invocation.
\end{desc}
\section{Request Handlers}
\label{httpd:request-handlers}
A request handler generates the actual content for a request; request
handlers form a simple algebra and may be combined and composed in
various ways.
A request handler is a procedure of two arguments like this:
\defun{request-handler}{path req}{response}
\begin{desc}
\var{Req} is a request. The \semvar{path} argument is the URL's
path, parsed and split at slashes into a string list. For example,
if the Web client dereferences URL
%
\begin{verbatim}
http://clark.lcs.mit.edu:8001/h/shivers/code/web.tar.gz
\end{verbatim}
then the server would pass the following path to the top-level
handler:
%
\begin{verbatim}
("h" "shivers" "code" "web.tar.gz")
\end{verbatim}
%
The \var{path} argument's pre-parsed representation as a string
list makes it easy for the request handler to implement recursive
operations dispatch on URL paths.
The request handler must return an HTTP response.
\end{desc}
\subsection{Basic Request Handlers}
The web server comes with a useful toolbox of basic request handlers
that can be used and built upon. The following procedures are
exported by the \ex{httpd\=basic\=handlers} structure:
\defvar{null-request-handler}{request-handler}
\begin{desc}
This request handler always generated a \ex{not-found} error
response, no patter what the request is.
\end{desc}
\defun{make-predicate-handler}{predicate handler
default-handler}{request-handler}
\begin{desc}
The request handler returned by this procedure first calls
\var{predicate} on its path and request; it then acts like
\var{handler} if the predicate returned a true vale, and like
\var{default-handler} if the predicate returned \sharpf.
\end{desc}
\defun{make-host-name-handler}{hostname handler default-handler}{request-handler}
\begin{desc}
The request handler returned by this procedure compares the host
name specified in the request with \var{hostname}: if they match, it
acts like \var{handler}, otherwise, it acts like
\var{default-handler}.
\end{desc}
\defun{make-path-predicate-handler}{predicate handler
default-handler}{request-handler}
\begin{desc}
The request handler returned by this procedure first calls
\var{predicate} on its path; it then acts like \var{handler} if the
predicate returned a true vale, and like \var{default-handler} if
the predicate returned \sharpf.
\end{desc}
\defun{make-path-prefix-handler}{path-prefix handler default-handler}{request-handler}
\begin{desc}
This constructs a request handler that calls \var{handler} on its
argument if \var{path-prefix} (a string) is the first element of the
requested path; it calls \var{handler} on the rest of the path and
the original request. Otherwise, the handler acts like
\var{default-handler}.
\end{desc}
\defun{alist-path-dispatcher}{handler-alist default-handler}{request-handler}
\begin{desc}
This procedure takes as arguments an alist mapping strings to path
handlers, and a default request handler, and returns a handler that
dispatches on its path argument. When the new request handler is
applied to a path
\begin{verbatim}
("foo" "bar" "baz")
\end{verbatim}
it uses the
first element of the path---\ex{foo}---to index into the
alist. If it finds an associated request handler in the alist, it
hands the request off to that handler, passing it the tail of the
path, in this case
\begin{verbatim}
("bar" "baz")
\end{verbatim}
%
On the other hand, if the path is
empty, or the alist search does not yield a hit, we hand off to the
default path handler, passing it the entire original path,
\begin{verbatim}
("foo" "bar" "baz")
\end{verbatim}
%
This procedure is how you say: ``If the first element of the URL's
path is `foo', do X; if it's `bar', do Y; otherwise, do Z.''
The slash-delimited URI path structure implies an associated tree of
names. The request-handler system and the alist dispatcher allow you to
procedurally define the server's response to any arbitrary subtree
of the path space.
Example: A typical top-level request handler is
\begin{alltt}
(define ph
(alist-path-dispatcher
`(("h" . ,(home-dir-handler "public\_html"))
("cgi-bin" . ,(cgi-handler "/usr/local/etc/httpd/cgi-bin"))
("seval" . ,seval-handler))
(rooted-file-handler "/usr/local/etc/httpd/htdocs")))
\end{alltt}
This means:
\begin{itemize}
\item If the path looks like \ex{("h"\ob{} "shivers"\ob{}
"code"\ob{} "web.\ob{}tar.\ob{}gz")}, pass the path
\ex{("shivers"\ob{} "code"\ob{} "web.\ob{}tar.\ob{}gz")} to a
home-directory request handler.
\item If the path looks like \ex{("cgi-\ob{}bin"\ob{} "calendar")},
pass ("calendar") off to the CGI request handler.
\item If the path looks like \ex{("seval"\ob{} \ldots)}, the tail
of the path is passed off to the code-uploading \ex{seval} path
handler.
\item Otherwise, the whole path is passed to a rooted file handler,
who will convert it into a filename, rooted at
\ex{/usr/\ob{}lo\ob{}cal/\ob{}etc/\ob{}httpd/\ob{}htdocs},
and serve that file.
\end{itemize}
\end{desc}
\subsection{Static Content Request Handlers}
The request handlers described in this section are for serving static
content off directory trees in the file system. They live in the
\ex{httpd-file-directory-handlers} structure.
The request handlers in this section eventually call an internal
procedure named \ex{file\=serve} for serving files which implements a
simple directory-generation service using the following rules:
\begin{itemize}
\item If the filename has the form of a directory (i.e., it ends with
a slash), then \ex{file\=serve} actually looks for a file named
\ex{index.html} in that directory.
\item If the filename names a directory, but is not in directory form
(i.e., it doesn't end in a slash, as in
``\ex{/usr\ob{}in\ob{}clu\ob{}de}'' or ``\ex{/usr\ob{}raj}''),
then \ex{file\=serve} sends back a ``301 moved permanently''
message, redirecting the client to a slash-terminated version of the
original URL. For example, the URL
\ex{http://\ob{}clark.\ob{}lcs.\ob{}mit.\ob{}edu/\ob{}~shi\ob{}vers}
would be redirected to
\ex{http://\ob{}clark.\ob{}lcs.\ob{}mit.\ob{}edu/\ob{}~shi\ob{}vers/}
\item If the filename names a regular file, it is served to the
client.
\end{itemize}
%
The \ex{httpd-file-directory-handlers} all take an options value as an
argument, similar to the options for \ex{httpd} itself.
The \var{options} argument can be constructed through a number of procedures
with names of the form \texttt{with-\ldots}. Each of these procedures
either creates a fresh options value or adds a configuration parameter
to an old options argument. The configuration parameter value is
always the first argument, the (old) options value the optional second
one. Here they are:
\defun{with-file-name->content-type}{proc [options]}{options}
\begin{desc}
This specifies a procedure for determining the MIME content type
(``\ex{text/html},'' ``\ex{application/octet-stream}'' etc.)
from a file name. \var{Proc} takes a file name as an argument and
must return a string. (This is relevant in directory listings.) The default is a procedure able to handle the
more common file extensions.
\end{desc}
\defun{with-file-name->content-encoding}{proc [options]}{options}
\begin{desc}
This specifies a procedure for determining the MIME content encoding
(if the file is compressed, gzipped, etc.) from a file name.
(This is relevant in directory listings.)
\var{Proc} takes a file name as an argument and must return two
values: the equivalent, unencoded file name (i.e., without the
trailing \ex{.Z} or \ex{.gz}) and a string representing the content
encoding.
\end{desc}
\defun{with-file-name->icon-url}{proc [options]}{options}
\begin{desc}
This specifies a procedure for determining the icon to be displayed
next to a file name in a directory listing.
\var{Proc} takes a file name as an argument and must return a URL
for the corresponding icon or \sharpf.
\end{desc}
\defun{with-blank-icon-url}{file-name-or-\sharpf{} [options]}{options}
\begin{desc}
This specifies a file name (or its absence) for the special icon
that must be as wide as the icons returned by the previous procedure
but that is blank.
\end{desc}
\defun{with-back-icon-url}{file-name-or-\sharpf{} [options]}{options}
\begin{desc}
This specifies a file name (or its absence) for the special icon
that is displayed next to the ``parent directory'' link in directory
listings.
\end{desc}
\defun{with-unknown-icon-url}{file-name-or-\sharpf{}
[options]}{options}
\begin{desc}
This specifies a file name (or its absence) for the special icon
that is displayed next to the unknown entries in directory listings.
\end{desc}
The \ex{make-file-directory-options} procedure eases the construction
of the options argument:
\defun{make-file-directory-options}{transformer value \ldots}{options}
\begin{desc}
This constructs an options value from an argument list of parameter
transformers and parameter values. The arguments come in pairs,
each an option transformer from the list above, and a value for that
parameter. \ex{Make-file-directory-options} returns the resulting
options value.
\end{desc}
%
Here are procedure for constructing static content request handlers:
%
\defun{rooted-file-handler}{root [options]}{request-handler}
\begin{desc}
This returns a request handler that serves files from a particular
root in the file system. Only the \ex{GET} operation is provided.
The path argument passed to the handler is converted into a
filename, and appended to \var{root}. The file name is checked for
\ex{..} components, and the transaction is aborted if it does.
Otherwise, the file is served to the client.
\end{desc}
\defun{rooted-file-or-directory-handler}{root [options]}{request-handler}
\begin{desc}
Dito, but also serve directory indices for directories without
\ex{index.html}.
\end{desc}
\defun{home-dir-handler}{subdir [options]}{request-handler}
\begin{desc}
This procedure builds a request handler that does basic file serving
out of home directories. If the resulting \var{request-handler} is
passed a path of the form \ex{(\var{user} . \var{file-path})}, then it serves the file
\ex{\var{subdir}/\var{file-path}} inside the user's home directory.
The request handler only handles GET requests; the filename is not
allowed to contain \ex{..} elements.
\end{desc}
\defun{tilde-home-dir-handler}{subdir default-request-handler [options]}{request-handler}
\begin{desc}
This returns request handler that examines the car of the path. If
it is a string beginning with a tilde, e.g., \ex{"~ziggy"}, then the
string is taken to mean a home directory, and the request is served
similarly to a home-dir-handler request handler. Otherwise, the
request is passed off in its entirety to the
\var{default-request-handler}.
\end{desc}
\section{CGI Server}
The procedure(s) described here live in the \ex{httpd-cgi-handlers}
structure.
\defun{cgi-handler}{bin-dir [cgi-bin-path]}{request-handler}
\begin{desc}
Returns a request handler for CGI scripts located in
\var{bin-dir}. \var{Cgi-bin-dir} specifies the value of the
\ex{PATH} variable of the environment the CGI scripts run in. It defaults
to
\begin{verbatim}
/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin
\end{verbatim}
The CGI scripts are called as specified by CGI/1.1\footnote{see
\url{http://hoohoo.ncsa.uiuc.edu/cgi/interface.html} for a sort of
specification.}.
Note that the CGI handler looks at the name of the CGI script to
determine how it should be handled:
\begin{itemize}
\item If the name of the script starts with `\ex{nph-}', its reply
is read, the RFC~822-fields like \ex{Content-Type} and \ex{Status}
are parsed and the client is sent back a real HTTP reply,
containing the rest of the script's output.
\item If the name of the script doesn't start with `\ex{nph-}',
its output is sent back to the client directly. If its return code
is not zero, an error message is generated.
\end{itemize}
\end{desc}
\section{Scheme-Evaluating Request Handlers}
The \ex{httpd-seval-handlers} structure contains a handler which
demonstrates how to safely evaluate Scheme code uploaded from the
client to the server.
\defvar{seval-handler}{request-handler}
\begin{desc}
This request handler is suitable for receiving code entered into an
HTML text form. The Scheme code being uploaded is being \ex{POST}ed
to the server (from a form). The code should be URI-encoded in the
URL as \texttt{program=}$\left<\mathrm{stuff}\right>$.
$\mathrm{stuff}$ must be an (URI-encoded) Scheme expression which
the handler evaluates in a separate subprocess. (It waits for 10
seconds for a result, then kills the subprocess.) The handler then
prints the return values of the Scheme code.
\end{desc}
The following structures define environments that are \RnRS without
features that could examine or effect the file system. You can also
use them as models of how to execute code in other protected
environments in \scm.
\subsection{The \protect{\texttt{loser}} structure}
The \ex{loser} package exports only one procedure:
\begin{defundesc}{loser}{name}{nothing}
Raises an error like ``Illegal call \var{name}''.
\end{defundesc}
\subsection{The \protect{\texttt{toothless}} structure}
The \ex{toothless} structure contains everything of \RnRS except
that following procedure cause an error if called:
\begin{itemize}
\item \ex{call-with-input-file}
\item \ex{call-with-output-file}
\item \ex{load}
\item \ex{open-input-file}
\item \ex{open-output-file}
\item \ex{transcript-on}
\item \ex{with-input-from-file}
\item \ex{with-input-to-file}
\item \ex{eval}
\item \ex{interaction-environment}
\item \ex{scheme-report-environment}
\end{itemize}
\subsection{The \protect{\texttt{toothless-eval}} structure}
\begin{defundesc}{eval-safely} {expression} {any result}
Creates a brand-new structure, imports the \ex{toothless} structure,
and evaluates \semvar{expression} in it. When the evaluation is
done, the environment is thrown away, so \semvar{expression}'s
side-effects don't persist from one \ex{eval\=safely} call to the
next. If \semvar{expression} raises an error exception,
\ex{eval-safely} returns \sharpf.
\end{defundesc}
\section{Writing Request Handlers}
\subsection{Parsing HTML Forms}
In HTML forms, field data are turned into a single string, of the form
\texttt{\synvar{name}=\synvar{val}\&\synvar{name}=\synvar{val}\ldots}.
The \ex{parse-html-forms} structure provides simple functionality to
parse these strings.
\defun{parse-html-form-query}{string}{alist}
\begin{desc}
This parses \verb|"foo=x&bar=y"| into \verb|(("foo" . "x") ("bar" .
"y"))|. Substrings are plus-decoded (i.-e.\ plus characters are
turned into spaces) and then URI-decoded.
This implementation is
slightly sleazy as it will successfully parse a string like
\verb|"a&b=c&d=f"| into \verb|(("a&b" . "c") ("d" . "f"))| without
a complaint.
\end{desc}
\section{SSL encryption with Apache}
Network traffic with a HTTP server is usually encrypted and protected
from manipulation using the cryptographic algorithm provided by an
implementation of the \textit{secure socket layer}, SSL for short.
SUnet does not have support for SSL yet. However, an Apache
web-server with SSL support can be configured as a proxy. In this
setup the Apache web-server accepts encrypted requests and forwards
them to a SUnet web-server running locally. This section describes
how to set up Apache as an encrypting proxy, assuming the reader has
basic knowledge about Apache and its configuration directives.
The following excerpt shows a minimalist SSL virtual host that
forwards requests to a SUnet server.
\begin{alltt}
<VirtualHost 134.2.12.82:443>
DocumentRoot "/www/some-domain/htdocs"
ServerName www.some-domain.de
ServerAdmin admin@some-domain.de
ErrorLog /www/some-domain/logs/error_log
ProxyRequests off
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
SSLEngine on
SSLRequireSSL
SSLCertificateFile /www/some-domain/cert/some-domain.cert
SSLCertificateKeyFile /www/some-domain/cert/some-domain.key
</VirtualHost>
\end{alltt}
First, a virtual host is added to Apache's configuration file. This
virtual host listens for incoming connections on port 443, which is
the standard port for encrypted HTTP traffic. \texttt{SSLRequireSSL}
ensures that server accepts encrypted connections only.
In terms of the Apache documentation, the web-server acts as a so
called \textit{reverse proxy}. The option \texttt{ProxyRequests} has
a misleading name. Setting this option to off does only turns off
Apache's facility to act as a \textit{forward proxy} and has no effect
on the configuration directives for reverse proxies. Actually,
turning on \texttt{ProxyRequests} is dangerous, because this turns
Apache into a proxy server that can be used from anywhere to access
any site that is accessible to the Apache server.
In this setting, all requests get forwarded to a SUnet web-server
which listens for incoming connections on localhost port 8080 only,
thus, it is not reachable from a remote machine. Apache forwards all
requests to the host and port specified by the \texttt{ProxyPass}
directive. \texttt{ProxyPassReverse} specifies how
\texttt{Location}-Header fields of HTTP redirect messages send by the
SUNet server are translated.
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,135 +0,0 @@
\chapter*{}
\thispagestyle{empty}
Of course, there is no Underground---or Untergrund, as those German
new-age kids like to call the movement whose orders they
have sworn to follow. The age we all remember---the cliff-green
turbocharged convertibles, cigarettes hanging loose in the corners of
our mouths, and those trigger-happy fingers always ready for the quick
hack---is long gone.
In retrospect, it all seems like a candy-colored dream, and it may
very well be---after all, there was never any proof that the
Untergrund ever existed, and even if it did, we can be sure the
obedient followers of the shadowy movement leaders have long burned
the papers, subjected the hard drives and diskettes to interminable
sessions of the junkyard magnet, and eradicated all shreds of
memory from the brains of those who might have talked through long
sessions of Tcl hacking to the sounds of Celine Dion records.
Yet there are those who still covet membership in that secret
cult---to gain access to its powerful lore, to usurp invidious and
powerful superiors, or simply to impress their girlfriends. For those
lost souls of the modern age, I have a few words of advice:
It's not a question of ``membership''---silly merchandise and
ridiculous certificates. If you are truly meant to be part of the
Untergrund, you will know. \emph{The Untergrund will find you.}
Alas, probably not.
\medskip
\hfill April, 2003
\chapter{Overview}\label{sec:intro}
The Scheme Untergrund Networking Package (\textit{SUnet}, for short)
is a collection of applications and libraries for Internet hacking in
Scheme. It runs under Scsh, the Scheme shell. SUnet includes the
following components:
%
\begin{description}
\item[The SUnet Web server]
This is a highly configurable HTTP 1.0 server in Scheme.
The server is accompanied by some libraries which may also
be used separately:
\begin{itemize}
\item URI and URL parsers and unparsers
\item a library for writing CGI scripts in Scheme
\item server extensions for interfacing to CGI scripts
\item server extensions for uploading Scheme code
\item simple structured HTML output library
\end{itemize}
The server also ships with a sophisticated interface for writing
server-side Web applications called \textit{SUrflets}.
\item[The SUnet ftp server]
This is a complete anonymous ftp server in Scheme.
\item[ftp client library] This library allows you to access ftp
servers programmatically.
\item[netrc library] This library parses authentication information
contained in \verb|~/.netrc|.
\item[SMTP client library] This library allows you to forge mail from
the comfort of your own Scheme process.
\item[POP3 client library]
This library allows you to access your POP3 mailbox from inside scsh.
\item[RFC822 header library] This library parses email-style headers.
\item[Daytime and Time protocol client libraries]
These libraries lets you find out what time it is without paying for a
Rolex.
\item[DNS client library] This is a complete, multithreaded DNS
library.
\item[An \texttt{ls} clone] This library displays Unix-style directory
listings without running \texttt{ls}.
\end{description}
\section{Obtaining the system}
The SUnet code is available
\urlhd{http://www.scsh.net/resources/sunet.html}{here}{from
\url{http://www.scsh.net/resources/sunet.html}}. To run the code, you need
version 0.6.6 or later of \urlhd{http://www.scsh.net/}{scsh}{scsh from
\url{http://www.scsh.net/}}.
\section{How to install SUnet}
Starting with version 2.1 SUnet conforms to the packaging proposal for
scsh by Michel Schinz and needs Michel's installation library to
install properly. For more information, please see
\url{http://lamp.epfl.ch/~schinz/scsh_packages/}.
In short, this means that you can install SUnet by unpacking the SUnet
tarball and issuing the following command in the created directory:
\begin{verbatim}
scsh-install-pkg --prefix /path/to/your/package/root
\end{verbatim}
See the file INSTALL for the generic installation instructions for
scsh packages.
You need to install version 4.9 of the SSAX package to use SUnet. SSAX
is available from \url{http://lamp.epfl.ch/~schinz/scsh_packages/}.
\section{How to use the packages}
%
After installation, you can use the \verb+-lel+ command-line option to
load the package definitions. If you installed SUnet including
SUrflets (the default), you need to load SSAX as well:
%
\begin{alltt}
atari-2600[72] scsh -lel SSAX-4.9/load.scm -lel sunet-2.1/load.scm
Welcome to scsh 0.6.6 (King Conan)
Type ,? for help.
\end{alltt}
%
Now, all structures defined by SUnet and SSAX are available:
%
\begin{alltt}
> ,open ftp
Load structure ftp (y/n)? y
[netrc netrc.scm]
[ftp ftp.scm]
> \textit{call library code}
> ,exit
atari-2600[73]
\end{alltt}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1 +0,0 @@
../html

View File

@ -1,133 +0,0 @@
% man.t2p
% Dorai Sitaram
% Feb 6, 2000
% This file contains the tex2page macros needed to process
% the scsh LaTeX document scsh-n.n.n/doc/scsh-manual/man.tex.
% Copy (or link) this file alongside man.tex and run
%
% tex2page man
\input css.t2p
\htmlmathstyle{no-image}
\let\pagebreak\relax
\let\small\relax
%\let\PRIMtableofcontents\tableofcontents
%\def\tableofcontents{\chapter*{Contents}\PRIMtableofcontents}
\def\subtitle#1{\def\savesubtitle{#1}}
\def\maketitle{
\subject{\TIIPtitle}
{\bf \hr}
\rightline{\savesubtitle}
\bigskip\bigskip
\bigskip\bigskip
{\bf\TIIPauthor}
{\bf\hr}
}
\let\PRIMdocument\document
\def\document{\PRIMdocument
\let\ttchars\relax
\let\ttt\tt
%\def\~{\rawhtml~\endrawhtml}
\def\~{\char`\~}
\def\cd#1{{\tt\def\\{\char`\\}\defcsactive\${\char`\$}\defcsactive\~{\char`\~}\defcsactive\&{\char`\&}#1}}
\def\cddollar{\undefcsactive\$}
\def\cdmath{\undefcsactive\$}
\def\codeallowbreaks{\relax}
\def\defvarx#1#2{\index{#1}\leftline{{\tt #1} \qquad #2}}
\let\PRIMflushright\flushright
\def\flushright{\PRIMflushright\TIIPtabularborder=0 }
\let\PRIMfigure\figure
\let\PRIMendfigure\endfigure
\def\figure{\par\hrule\PRIMfigure}
\def\endfigure{\PRIMendfigure\hrule\par}
\let\PRIMtable\table
\let\PRIMendtable\endtable
\def\table{\par\hrule\PRIMtable}
\def\endtable{\PRIMendtable\hrule\par}
\imgdef\vdots{\bf.\par.\par.}
%\evalh{
%
%(define all-blanks?
% (lambda (s)
% (andmap
% char-whitespace?
% (string->list s))))
%
%}
%
%
%\def\spaceifnotempty{\evalh{
%
%(let ((x (ungroup (get-token))))
% (unless (all-blanks? x)
% (emit #\space)))
%
%}}
\def\spaceifnotempty#1{%
\def\TEMP{#1}%
\ifx\TEMP\empty\else\ \fi}
\def\dfnix#1#2#3#4{\leftline{{\tt(#1\spaceifnotempty{#2}{\it#2})} \quad $\longrightarrow$ \quad {\it #3} \qquad (#4)} \index}
%\def\ex#1{{\tt #1}}
%\let\ex\texttt
\def\l#1{lambda (#1)}
\def\lx#1{lambda {#1}}
%\def\notenum#1{}
%\def\project#1{}
%\def\var#1{{\it #1\/}}
%\let\var\textit
%\def\vari#1#2{\mbox{{\it #1\/}\undefcsactive\$$_{#2}$}}
%\def\vari#1#2{\textit{#1}$_{#2}$}
\renewenvironment{boxedfigure}{\def\srecomment#1{\\#1\\}%
\begin{figure}\pagestyle}{\end{figure}}
\newenvironment{centercode}{\begin{code}}{\end{code}}
\def\setupcode{\tt%
\def\\{\char`\\}%
\defcsactive\${\$}%
\def\evalto{==> }%
\defcsactive\%{\%}\obeywhitespace}
\newenvironment{code}{\begin{quote}\setupcode\GOBBLEOPTARG}
{\end{quote}}
\newenvironment{codebox}{\begin{tableplain}\bgroup\setupcode\GOBBLEOPTARG}
{\egroup\end{tableplain}}
\renewenvironment{desc}{\begin{quote}}{\end{quote}}
\renewenvironment{exampletable}{%
\def\header#1{\\\leftline{#1}\\}%
\def\splitline#1#2{\\\leftline{#1}\\\leftline{#2}}%
\begin{tabular}{}}{\end{tabular}}
\newenvironment{tightcode}{\begin{code}}{\end{code}}
\renewenvironment{widecode}{\begin{code}}{\end{code}}
\renewenvironment{inset}{\begin{quote}}{\end{quote}}
\renewenvironment{leftinset}{\begin{quote}}{\end{quote}}
\renewenvironment{tightinset}{\begin{quote}}{\end{quote}}
\renewenvironment{tightleftinset}{\begin{quote}}{\end{quote}}
}

View File

@ -1,79 +0,0 @@
% -*- latex -*-
% This is the reference manual for the Scheme Untergrund Networking Package.
\documentclass[twoside]{report}
\usepackage{code,boxedminipage,makeidx,palatino,ct,
headings,mantitle,array,matter,mysize10,tex2page}
\usepackage[latin1]{inputenc}
\usepackage{alltt}
\usepackage{xspace}
\usepackage{tabularx,theorem,ulem,float,afterpage} % need url
\normalem % usually, don't use ulem
\texonly
% tex2page defines \url and hyperref loads the package url
% but setting \url to \relax satisfies \newcommand
\let\url\relax
\input{pdfcond}
\ifpdf
\usepackage[pdftex,hyperindex,
pdftitle={sunet manual, release 2.1},
pdfauthor={Olin Shivers, Mike Sperber, Martin Gasbichler, Eric Marsden
and Andreas Bernauer}
colorlinks=true,linkcolor=blue,pagecolor=blue,urlcolor=blue,
pdfstartview=FitH,pdfview=FitH]{hyperref}
\usepackage{thumbpdf}
\usepackage{tocbibind}
\else
\usepackage[dvipdfm,hyperindex,hypertex,
pdftitle={sunet manual, release 2.1},
pdfauthor={Olin Shivers, Mike Sperber, Martin Gasbichler, Eric Marsden
and Andreas Bernauer}
colorlinks=true,linkcolor=blue,pagecolor=blue,urlcolor=blue,
pdfstartview=FitH,pdfview=FitH]{hyperref}
\fi
\endtexonly
% Style issues
\parskip = 3pt plus 3pt
\sloppy
\input{decls}
\makeindex
%%% End preamble
\begin{document}
\frontmatter
\title{SUnet Reference Manual}
\subtitle{For SUnet release 2.1}
\author{Dr. S\raisebox{1ex}{2}, Martin Gasbichler, Eric Marsden, Andreas Bernauer}
\date{October 2004}
\mainmatter
\maketitle
\tableofcontents
\include{intro}
\include{httpd}
\include{uri}
\include{url}
\include{cgi-script}
\include{surflets}
\include{ftpd}
\include{ftp}
\include{netrc}
\include{rfc822}
\include{nettime}
\include{smtp}
\include{pop3}
\include{dns}
\backmatter
\printindex
\end{document}

View File

@ -1,76 +0,0 @@
% This is the title page style stolen from the Texinfo design,
% and expressed as a LaTeX style option. It is useful for manuals.
%
% Note that I play some *really* revolting games here to override
% the vertical and horizontal margins temporarily for the title page.
% The layout assumes you have 8.5" x 11" paper. You'd have to redo this
% for A4 or another size.
% -Olin 7/94
% Fonts for title page:
\DeclareFixedFont{\titlefont}%
{\encodingdefault}{\familydefault}{bx}{\shapedefault}{20.5pt}
\DeclareFixedFont{\authorfnt}%
{\encodingdefault}{\familydefault}{bx}{\shapedefault}{14.4pt}
\DeclareFixedFont{\subtitlefnt}%
{\encodingdefault}{\familydefault}{m}{\shapedefault}{11}
%\def\authorrm{\normalfont\selectfont\fontseries{bx}\fontsize{14.4}{14.4}}
%\def\subtitlefnt{\normalfont\selectfont\fontsize{11}{11}}
\newskip\titlepagetopglue \titlepagetopglue = 2.5in
\newlength{\widewidth}
\setlength{\widewidth}{6.5in}
\newlength{\negwidemargin}
\setlength{\negwidemargin}{-\oddsidemargin} % Reset the margin
\addtolength{\negwidemargin}{-1in} % to edge of page
\addtolength{\negwidemargin}{1in} % Then move right one inch.
%\def\wideline#1{\hbox to 0pt{\hspace\negwidemargin\hbox to\widewidth{#1}}}
\def\wideline#1{\hbox{\makebox[0pt][l]{\hspace\negwidemargin\hbox to\widewidth{#1}}}}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\maketitle{\begin{titlepage}
\thispagestyle{empty}
\let\footnotesize\small \let\footnoterule\relax
\null
\parindent=0pt
\def\subtitlefont{\normalbaselineskip = 13pt \normalbaselines \subtitlefnt}%
\def\authorfont{\normalbaselineskip = 16pt \normalbaselines \authorfnt}%
%
% Leave some space at the very top of the page.
\vspace*{-1in}\vspace*{-\topmargin}\vspace*{-\headheight}\vspace*{-\headsep}
\vglue\titlepagetopglue
%
\wideline{\titlefont \@title \hfill} % title
% \vskip4pt
\vskip -0.3\baselineskip
\wideline{\leaders\hrule height 4pt\hfill}
\wideline{\hfill\subtitlefont\begin{tabular}[t]{@{}r@{}}\@subtitle%
\\\@date%
\end{tabular}} % subtitle
%
% author
\vskip 0pt plus 1filll
\wideline{\authorfont \begin{tabular}[t]{@{}c@{}}\@author
\end{tabular}\hfill}
%
% \vskip4pt
\vskip -0.3\baselineskip
\wideline{\leaders\hrule height 2pt\hfill}
% This weirdness puts the bottom line 2.75 in from the bottom of
% an 11in page.
\vskip \textheight \vskip \headsep \vskip \headheight
\vskip \topmargin \vskip 1in \vskip -11in \vskip 2.75in
\gdef\@author{}\gdef\@title{}\gdef\@subtitle{}\let\maketitle\relax
\end{titlepage}
\setcounter{page}{2}
}
\def\subtitle#1{\gdef\@subtitle{#1}}
\def\@subtitle{}

View File

@ -1,16 +0,0 @@
%&latex -*- latex -*-
% Implement the \frontmatter, \mainmatter, and \backmatter macros,
% so I can use them in reports, not just books.
\newif\if@mainmatter \@mainmattertrue
\newcommand\frontmatter{%
\cleardoublepage\@mainmatterfalse\pagenumbering{roman}}
\newcommand\mainmatter{%
\cleardoublepage\@mainmattertrue%
\pagenumbering{arabic}\setcounter{page}{1}}
\newcommand\backmatter{%
\if@openright\cleardoublepage\else\clearpage\fi%
\@mainmatterfalse}

View File

@ -1,22 +0,0 @@
%&latex -*- latex -*-
\if@twoside
\oddsidemargin 44pt
\evensidemargin 82pt
\marginparwidth 107pt
\else
\oddsidemargin 63pt
\evensidemargin 63pt
\marginparwidth 90pt
\fi
\marginparsep 11pt
\topmargin 27pt
\headheight 12pt
\headsep 25pt
\topskip = 10pt
\footskip 30pt
\textheight = 43\baselineskip
\advance\textheight by \topskip
\textwidth 345pt
\endinput

View File

@ -1,61 +0,0 @@
\chapter{Parsing Netrc Files}\label{cha:netrc}
%
The \ex{netrc} structures provides procedures to parse authentication
information contained in \ex{~/.netrc}.
On Unix systems the netrc file may contain information allowing
automatic login to remote hosts. The format of the file is defined in
the \ex{ftp(1)} manual page. Example lines are
%
\begin{verbatim}
machine ondine.cict.fr login marsden password secret
default login anonymous password user@site
\end{verbatim}
%
The netrc file should be protected by appropriate permissions, and
(like \ex{/usr/bin/ftp}) this library will refuse to read the file if it is
badly protected. (unlike \ex{ftp} this library will always refuse
to read the file----\ex{ftp} refuses it only if the password is
given for a non-default account). Appropriate permissions are set if
only the user has permissions on the file.
\defun{netrc-machine-entry}{host accept-default? [file-name]}{netrc-entry-or-\sharpf}
\begin{desc}
This procedure looks for the entry related to given host in the
user's netrc file. The host is specified in \var{host}.
\var{Accept-default?} specifies whether \ex{netrc-machine-entry}
should fall back to the default entry if there is no macht for
\var{host} in the netrc file. If specified, \var{file-name}
specifies an alternate file name for the netrc data. It defaults to
\ex{.netrc} in the current user's home directory.
\ex{Netrc-machine-entry} returns a netrc entry (see below) if it was
able to find the requested information; if not, it returns \sharpf.
If the netrc file had inappropriate permissions, \ex{netrc-machine-entry}
raises an error.
\end{desc}
\defun{netrc-entry?}{thing}{boolean}
\defunx{netrc-entry-machine}{netrc-entry}{string}
\defunx{netrc-entry-login}{netrc-entry}{string-or-\sharpf}
\defunx{netrc-entry-password}{netrc-entry}{string-or-\sharpf}
\defunx{netrc-entry-account}{netrc-entry}{string-or-\sharpf}
\begin{desc}
\ex{Netrc-entry?} is the predicate for netrc entries. The other
procedures are selectors for netrc entries as returned by
\ex{netrc-machine-entry}. They return \sharpf{} if the netrc file
didn't contain a binding for the corresponding field.
\end{desc}
\defun{netrc-macro-definitions}{[file-name]}{alist}
\begin{desc}
This returns the macro definitions from the netrc files, represented
as an alist mapping macro names---represented as strings---to
definitions---represented as lists of strings.
\end{desc}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,57 +0,0 @@
\chapter{Time and Daytime}\label{cha:ntp}
Many Unix hosts provide a RFC~867 Daytime service which sends the
current date and time as a human-readable character string. The
daytime service is typically served on port 13 as both TCP and UDP.
The RFC~868 Time protocol provides a site-independent, machine
readable date and time. The Time service is typically served
on port 37 as TCP and UDP. The idea is that you can confirm your
system's idea of the time by polling several independent sites on the
network.
\section{Daytime}
The \ex{rfc867} structure contains an interface to Daytime protocol.
\defun{rfc867-daytime/tcp}{host}{string}
\defunx{rfc867-daytime/udp}{host [timeout-or-\sharpf]}{string-or-\sharpf}
\begin{desc}
These procedures asks \var{host} about the current daytime and
return the host's answer (e.g., ``Thursday, April 4,
2'').
\ex{Rfc867-daytime/tcp} uses the TCP variant of the protocol.
\ex{Rfc867-daytime/udp} uses UDP and sends a single request to the
server. It allows the specification of an optional timeout; if not
specified or \sharpf{}, \ex{Rfc867-daytime/udp} will wait
indefinitely for an answer. If the answer from the server doesn't
arrive within the specified time, \ex{rfc867-daytime/udp} returns
\sharpf.
\end{desc}
\section{Time}
The \ex{rfc868} structure contains an interface to the Time protocol.
\defun{rfc868-time/tcp}{host}{string}
\defunx{rfc868-time/udp}{host [timeout-or-\sharpf]}{string-or-\sharpf}
\begin{desc}
These procedures asks \var{host} about the current time and return
the host's answer. This is the number of second since 1970, just as
with scsh's \texttt{time} procedure.
\ex{rfc868-time/tcp} uses the TCP variant of the protocol.
\ex{rfc868-time/udp} uses UDP and sends a single request to the
server. It allows the specification of an optional timeout; if not
specified or \sharpf{}, \ex{rfc868-time/udp} will wait
indefinitely for an answer. If the answer from the server doesn't
arrive within the specified time, \ex{rfc868-time/udp} returns
\sharpf.
\end{desc}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,14 +0,0 @@
\newif\ifpdf
\ifx\pdfoutput\undefined
\pdffalse % we are not running PDFLaTeX
\else
\pdfoutput=1 % we are running PDFLaTeX
\pdftrue
\fi
% Then use your new variable \ifpdf
% \ifpdf
% \usepackage[pdftex]{graphicx}
% \pdfcompresslevel=9
% \else
% \usepackage{graphicx}
% \fi

View File

@ -1,99 +0,0 @@
\chapter{POP3 Client}\label{cha:pop3}
%
The \ex{pop3} structure provides a client for the POP3 protocol that
allows access to email on a maildrop server. It is often used in
configurations where users connect from a client machine which doesn't
have a permanent network connection or isn't always turned on,
situations which make local SMTP delivery impossible. It is the most
common form of email access provided by ISPs.
Two types of authentication are commonly used. The first, most basic
type involves sending a user's password in clear over the network, and
should be avoided. (Unfortunately, many POP3 clients only implement this
basic authentication.) The digest authentication system involves the
server sending the client a ``challenge'' token; the client encodes
this token with the pass phrase and sends the coded information to the
server. This method avoids sending sensitive information over the
network. Both methods are implemented by \ex{pop3}.
Once connected, a client may request information about the number and
size of the messages waiting on the server, download selected messages
(either their headers or the entire content), and delete selected
messages.
The procedures defined here raise an error detectable via
\ex{pop3-error?} upon protocol errors with the POP3 server.
\defun{pop3-connect}{[host-or-\sharpf] [login-or-\sharpf]
[password-or-\sharpf] [log-port]}{connection}
\begin{desc}
This procedure connects to the maildrop server named \var{host},
and logs in using the provided login name and password. Any of
these can be omitted or \sharpf, in which case the procedure uses
defaults: \ex{MAILHOST} for the host, and \ex{~/.netrc}-provided
values for login and password. If \var{log-port} is provided, the
conversation to the server is logged to the specified output port.
\ex{Pop3-connect} returns a value representing the connection to the
POP3 server, to be used in the procedures below.
\end{desc}
\defun{pop3-stat}{connection}{number bytes}
\begin{desc}
This returns the number of messages and the number of bytes waiting in the
maildrop.
\end{desc}
Most of the following procedures accept a \var{msgid} argument which
specifies a message number, which ranges from 1 for the first message
to the number returned by \ex{pop3-stat}.
\defun{pop3-retrieve-message}{connection msgid}{headers message}
\begin{desc}
This downloads message number \var{msgid} from the mailhost.
It returns the headers as an alist of field names and bodies; the
names are symbols, the bodies are strings. (These are obtained
using the \ex{rfc822} structure, see Section~\ref{cha:rfc822}.)
The message is returned as a list of strings, each string
representing a line of the message.
\end{desc}
\defun{pop3-retrieve-headers}{connection msgid}{headers}
\begin{desc}
This downloads the headers of message number \var{msgid}. It
returns the headers in the same format as \ex{pop3-retrieve-message}.
\end{desc}
\defun{pop3-last}{connection}{msgid}
\begin{desc}
This returns the highest accessed message-id number for the current
session. (This isn't in the RFC, but seems to be supported by several
servers.)
\end{desc}
\defun{pop3-delete}{connection msgid}{undefined}
\begin{desc}
This mark message number \var{msgid} for deletion. The message will
not be deleted until the client logs out.
\end{desc}
\defun{pop3-reset}{connection}{undefined}
\begin{desc}
This marks any messages which have been marked for deletion.
\end{desc}
\defun{pop3-quit}{connection}{undefined}
\begin{desc}
This closes the connection with the mailhost.
\end{desc}
\defun{pop3-error?}{thing}{boolean}
\begin{desc}
This returns \sharpt{} if \var{thing} is a \ex{pop3-error} object,
otherwise \sharpf.
\end{desc}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,107 +0,0 @@
\chapter{RFC~822 Library}\label{cha:rfc822}
%
The \ex{rfc822} structure provides rudimentary support for parsing
headers according to RFC~822 \textit{Standard for the format of ARPA
Internet text messages}. These headers show up in SMTP messages,
HTTP headers, etc.
An RFC~822 header field consists of a \textit{field name} and a
\textit{field body}, like so:
%
\begin{verbatim}
Subject: RFC 822 can format itself in the ARPA
\end{verbatim}
%
Here, the field name is `\ex{Subject}', and the field name is `\ex{
RFC 822 can format itself in the ARPA}' (note the leading space).
The field body can be spread over several lines:
%
\begin{verbatim}
Subject: RFC 822 can format itself
in the ARPA
\end{verbatim}
%
In this case, RFC~822 specifies that the meaning of the field body is
actually all the lines of the body concatenated, without the
intervening line breaks.
The \ex{rfc822} structure provides two sets of parsing
procedures---one represents field bodies in the RFC-822-specified
meaning, as a single string, the other (with \ex{-with-line-breaks}
appended to the names) reflects the line breaks and represents the
bodies as a list of string, one for each line. The latter set only
marginally useful---mainly for code that needs to output headers in
the same form as they were originally provided.
\defun{read-rfc822-field}{[port] [read-line]}{name body}
\defun{read-rfc822-field-with-line-breaks}{[port] [read-line]}{name body-lines}
\begin{desc}
Read one field from the port, and return two values:
%
\begin{description}
\item[\var{name}] This is a symbol describing the field
name, such as \ex{subject} or \ex{to}. The symbol consists of all
lower-case letters.\footnote{In fact, it \ex{read-rfc822-field}
uses the preferred case for symbols of the underlying Scheme
implementation which, in the case of scsh, happens to be lower-case.}
\item[\var{body} or \var{body-lines}] This is the field body.
\var{Body} is a single string, \var{body-lines} is a list of
strings, one for each line of the body. In each case,
the terminating \ex{cr}/\ex{lf}'s (but nothing else) are
trimmed from each string.
\end{description}
%
When there are no more fields---EOF or a blank line has terminated
the header section---then both procedures returns [\sharpf\
\sharpf].
\var{Port} is an optional input port to read from---it defaults to
the value of \ex{(current-input-port)}.
\var{Read-line} is an optional parameter specifying a procedure of
one argument (the input port) used to read the raw header lines.
The default used by these procedures terminates lines with
either \ex{cr}/\ex{lf} or just \ex{lf}, and it trims the terminator
from the line. This procedure should trim the terminator of the
line, so an empty line is returned as an empty string.
The procedure raises an error if the syntax of the read field (the
line returned by the read-line-function) is illegal according to
RFC~822.
\end{desc}
\defun{read-rfc822-headers} {[port] [read-line]} {alist}
\defunx{read-rfc822-headers-with-line-breaks} {[port] [read-line]} {alist}
\begin{desc}
This procedure reads in and parses a section of text that looks like
the header portion of an RFC~822 message. It returns an association
list mapping field names (a symbol such as \ex{date} or \ex{subject}) to
field bodies. The representation of the field bodies is as with
\ex{read-rfc822-field} and \ex{read-rfc822-field-with-line-breaks}.
These procedures preserve the order of the header fields. Note that
several header fields might share the same field name---in that
case, the returned alist will contain several entries with the same
\ex{car}.
\var{Port} and \var{read-line} are as with \ex{read-rfc822-field}
and \ex{read-rfc822-field-with-line-breaks}.
\end{desc}
\defun{rfc822-time->string}{time}{string}
\begin{desc}
This formats a time value (as returned by scsh's \ex{time})
according to the requirements of the RFC~822 \ex{Date} header
field. The format looks like this:
%
\begin{verbatim}
Sun, 06 Nov 1994 08:49:37 GMT
\end{verbatim}
\end{desc}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,8 +0,0 @@
\section{Section-Title}
%
\begin{description}
\item[Used files:]
\item[Name of the package:]
\end{description}
%
Not implemented yet.

View File

@ -1,123 +0,0 @@
\chapter{SMTP Client}\label{cha:smtp}
%
The \ex{smtp} structure provides an client library for the Simple Mail
Transfer Protocol, commonly used for sending email on the Internet.
This library provides a simple wrapper for sending complete emails as
well as procedures for composing custom SMTP transactions.
Some of the procedures described here return an SMTP reply code. For
details, see RFC~821.
\defun{smtp-send-mail}{from to-list headers body [host]}{undefined}
\defunx{smtp-error?}{thing}{boolean}
\defunx{smtp-recipients-rejected-error?}{thing}{boolean}
\begin{desc}
This emails message \var{body} with headers \var{headers} to
recipients in list \var{to-list}, using a sender address \var{from}.
The email is handed off to the SMTP server running on \var{host};
default is the local host. \var{Body} is either a list of strings
representing the lines of the message body or an input port which is
exhausted to determine the message body. \var{Headers} is an
association lists, mapping symbols representing RFC~822 field names
to strings representing field bodies.
If some transaction-related error happens, \ex{smtp-send-mail}
signals an \ex{smtp-error} condition with predicate
\ex{smtp-error?}. More specifically, it raises an
\ex{smtp-recipients-rejected-error} (a subtype of \ex{smtp-error})
if some recipients were rejected. For \ex{smtp-error}, the
arguments to the \ex{signal} call are the error code and the error
message, represented as a list of lines. For
\ex{smtp-recipients-rejected-error}, the arguments are reply code
700 and an association list whose elements are of the form
\ex{(\var{loser-recipient} \var{code} . \var{text})}---that is, for
each recipient refused by the server, you get the error data sent
back for that guy. The success check is \ex{(< code 400)}.
\end{desc}
\defun{smtp-expand}{name host}{code text}
\defunx{smtp-verify}{name host}{code text}
\defunx{smtp-get-help}{host [details]}{code text-list}
\begin{desc}
These three are simple queries of the server as stated in the
RFC~821: \ex{smtp-expann} asks the server to confirm that the
argument identifies a mailing list, and if so, to return the
membership of that list. The full name of the users (if known) and
the fully specified mailboxes are returned in a multiline reply.
\ex{Smtp-verify} asks the receiver to confirm that the argument
identifies a user. If it is a user name, the full name of the user
(if known) and the fully specified mailbox are returned.
\ex{Smtp-get-help} causes the server to send helpful information.
The command may take an argument (\var{details}) (e.g., any command
name) and return more specific information as a response.
\end{desc}
\defun{smtp-connect}{host [port]}{smtp-connection}
\begin{desc}
\ex{Smtp-connect} returns an SMTP connection value that represents
a connection to the SMTP server.
\end{desc}
\defun{smtp-transactions}{smtp-connection transaction1 ...}{code text-list}
\defunx{smtp-transactions/no-close}{smtp-connection transaction1 ...}{code text-list}
\begin{desc}
These procedures make it easy to do simple sequences of SMTP
commands. \var{Smtp-connection} must be an SMTP connection as
returned by \ex{smtp-connect}. The \var{transaction} arguments must
be transactions as returned by the procedures below.
\ex{Smtp-transactions} and \ex{smtp-transactions/no-close} execute
the transactions specified by the arguments.
For each transaction,
\begin{itemize}
\item If the transaction's reply code is 221 or 421 (meaning the socket has
been closed), then the transaction sequence is aborted, and
\ex{smtp-transactions}/\ex{smtp-transactions/no-close} return the
reply code and text from that transaction.
\item If the reply code is an error code (in the four- or five-hundred range),
the transaction sequence is aborted, and the fatal transaction's code
and text values are returned. \ex{Smtp-transactions} will additionally
close the socket for you; \ex{smtp-transactions/no-close} will not.
\item If the transaction is the last in the transaction sequence,
its reply code and text are returned.
\item Otherwise, we throw away the current reply code and text, and
proceed to the next transaction.
\end{itemize}
%
\ex{Smtp-transactions} closes the socket after the transaction. (The
\ex{smtp-quit} transaction, when executed, also closes the transaction.)
If the socket should be kept open in the case of an abort, use
\ex{Smtp-transactions/no-close}.
\end{desc}
\defunx{smtp-helo}{local-host-name}{smtp-transaction}
\defunx{smtp-mail}{sender-address}{smtp-transaction}
\defunx{smtp-rcpt}{destination-address}{smtp-transaction}
\defunx{smtp-data}{socket message}{smtp-transaction}
\defunx{smtp-send}{sender-address}{smtp-transaction}
\defunx{smtp-soml}{sender-address}{smtp-transaction}
\defunx{smtp-saml}{sender-address}{smtp-transaction}
\defvarx{smtp-rset}{smtp-transaction}
\defunx{smtp-vrfy}{user}{smtp-transaction}
\defunx{smtp-expn}{user}{smtp-transaction}
\defunx{smtp-help}{details}{smtp-transaction}
\defvarx{smtp-noop}{smtp-transaction}
\defvarx{smtp-quit}{smtp-transaction}
\defvarx{smtp-turn}{smtp-transaction}
\begin{desc}
These transactions represent the commands of the SMTP protocol for
use in \ex{smtp-transactions} and \ex{smtp-transactions/no-close},
i.e.\ they send the corresponding command along with the argument(s),
if any. For details, consult RFC~821.
The \ex{smtp-quit} transaction, in addition to sending a \ex{QUIT}
command to the SMTP server, also closes the socket of its SMTP
connection.
\end{desc}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
\chapter{SUrflet server}
\newcommand{\typew}[1]{\texttt{#1}}
The \surflet server enables you to write server side scripted web
programs in Scheme.
%Currently, there is only the howto available.
%The complete API is supposed to come soon.
There are lots of example files in
\typew{scheme/httpd/surflet/webserver/root/surflets} from which you
can copy freely.
\input{surflethowto}
\input{surfletapi}

View File

@ -1,166 +0,0 @@
\chapter{Parsing and Processing URIs}\label{cha:uri}
The \ex{uri} structure contains a library for dealing with URIs.
\section{Notes on URI Syntax}
A URI (Uniform Resource Identifier) is of following syntax:
%
\begin{inset}
[\var{scheme}] \verb|:| \var{path} [\verb|?| \var{search}] [\verb|#| \var{fragid}]
\end{inset}
%
Parts in brackets may be omitted.
The URI contains characters like \verb|:| to indicate its different
parts. Some special characters are \emph{escaped} if they are a
regular part of a name and not indicators for the structure of a URI.
Escape sequences are of following scheme: \verb|%|\var{h}\var{h} where \var{h}
is a hexadecimal digit. The hexadecimal number refers to the
ASCII of the escaped character, e.g.\ \verb|%20| is space (ASCII
32) and \verb|%61| is `a' (ASCII 97). This module
provides procedures to escape and unescape strings that are meant to
be used in a URI.
\section{Procedures}
\defun{parse-uri} {uri-string } {scheme path search
frag-id} \label{proc:parse-uri}
\begin{desc}
Parses an \var{uri\=string} into its four fields.
The fields are \emph{not} unescaped, as the rules for
parsing the \var{path} component in particular need unescaped
text, and are dependent on \var{scheme}. The URL parser is
responsible for doing this. If the \var{scheme}, \var{search}
or \var{fragid} portions are not specified, they are \sharpf.
Otherwise, \var{scheme}, \var{search}, and \var{fragid} are
strings. \var{path} is a non-empty string list---the path split
at slashes.
\end{desc}
Here is a description of the parsing technique. It is inwards from
both ends:
\begin{itemize}
\item First, the code searches forwards for the first reserved
character (\verb|=|, \verb|;|, \verb|/|, \verb|#|, \verb|?|,
\verb|:| or \verb|space|). If it's a colon, then that's the
\var{scheme} part, otherwise there is no \var{scheme} part. At
all events, it is removed.
\item Then the code searches backwards from the end for the last reserved
char. If it's a sharp, then that's the \var{fragid} part---remove it.
\item Then the code searches backwards from the end for the last reserved
char. If it's a question-mark, then that's the \var{search}
part----remove it.
\item What's left is the path. The code split it at slashes. The
empty string becomes a list containing the empty string.
\end{itemize}
%
This scheme is tolerant of the various ways people build broken
URI's out there on the Net\footnote{So it does not absolutely conform
to RFC~1630.}, e.g.\ \verb|=| is a reserved character, but used
unescaped in the search-part. It was given to me\footnote{That's
Olin Shivers.} by Dan Connolly of the W3C and slightly modified.
\defun{unescape-uri}{string [start] [end]}{string}
\begin{desc}
\ex{Unescape-uri} unescapes a string. If \var{start} and/or \var{end} are
specified, they specify start and end positions within \var{string}
should be unescaped.
\end{desc}
%
This procedure should only be used \emph{after} the URI was parsed,
since unescaping may introduce characters that blow up the
parse---that's why escape sequences are used in URIs.
\defvar{uri-escaped-chars}{char-set}
\begin{desc}
This is a set of characters (in the sense of SRFI~14) which are
escaped in URIs. RFC 2396 defines this set as all characters which
are neither letters, nor digits, nor one of the following characters:
\verb|-|, \verb|_|, \verb|.|, \verb|!|, %$
\verb|~|, \verb|*|, \verb|'|, \verb|(|, \verb|)|.
\end{desc}
\defun{escape-uri} {string [escaped-chars]} {string}
\begin{desc}
This procedure escapes characters of \var{string} that are in
\var{escaped\=chars}. \var{Escaped\=chars} defaults to
\ex{uri\=escaped\=chars}.
\end{desc}
%
Be careful with using this procedure to chunks of text with
syntactically meaningful reserved characters (e.g., paths with URI
slashes or colons)---they'll be escaped, and lose their special
meaning. E.g.\ it would be a mistake to apply \ex{escape-uri} to
\begin{verbatim}
//lcs.mit.edu:8001/foo/bar.html
\end{verbatim}
%
because the sla\-shes and co\-lons would be escaped.
\defun{split-uri}{uri start end} {list}
\begin{desc}
This procedure splits \var{uri} at slashes. Only the substring given
with \var{start} (inclusive) and \var{end} (exclusive) as indices is
considered. \var{start} and $\var{end} - 1$ have to be within the
range of \var{uri}. Otherwise an \ex{index-out-of-range} exception
will be raised.
Example: \codex{(split-uri "foo/bar/colon" 4 11)} returns
\codex{("bar" "col")}
\end{desc}
\defun{uri-path->uri}{path}{string}
\begin{desc}
This procedure generates a path out of a URI path list by inserting
slashes between the elements of \var{plist}.
\end{desc}
%
If you want to use the resulting string for further operation, you
should escape the elements of \var{plist} in case they contain
slashes, like so:
%
\begin{verbatim}
(uri-path->uri (map escape-uri pathlist))
\end{verbatim}
\defun{simplify-uri-path}{path}{list}
\begin{desc}
This procedure simplifies a URI path. It removes \verb|"."| and
\verb|"/.."| entries from path, and removes parts before a root.
The result is a list, or \sharpf{} if the path tries to back up past
root.
\end{desc}
%
According to RFC~2396, relative paths are considered not to start with
\verb|/|. They are appended to a base URL path and then simplified.
So before you start to simplify a URL try to find out if it is a
relative path (i.e. it does not start with a \verb|/|).
Examples:
%
\begin{alltt}
(simplify-uri-path (split-uri "/foo/bar/baz/.." 0 15))
\(\Rightarrow\) ("" "foo" "bar")
(simplify-uri-path (split-uri "foo/bar/baz/../../.." 0 20))
\(\Rightarrow\) ()
(simplify-uri-path (split-uri "/foo/../.." 0 10))
\(\Rightarrow\) #f
(simplify-uri-path (split-uri "foo/bar//" 0 9))
\(\Rightarrow\) ("")
(simplify-uri-path (split-uri "foo/bar/" 0 8))
\(\Rightarrow\) ("")
(simplify-uri-path (split-uri "/foo/bar//baz/../.." 0 19))
\(\Rightarrow\) #f
\end{alltt}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,113 +0,0 @@
\chapter{Parsing and Processing URLs}\label{cha:url}
%
The \ex{url} structure contains procedures to parse and unparse URLs.
Until now, only the parsing of HTTP URLs is implemented.
\section{Server Records}
A \textit{server} value describes path prefixes of the form
\var{user}:\var{password}@\var{host}:\var{port}. These are
frequently used as the initial prefix of URLs describing Internet
resources.
\defun{make-server}{user password host port}{server}
\defunx{server?}{thing}{boolean}
\defunx{server-user}{server}{string-or-\sharpf}
\defunx{server-password}{server}{string-or-\sharpf}
\defunx{server-host}{server}{string-or-\sharpf}
\defunx{server-port}{server}{string-or-\sharpf}
\begin{desc}
\ex{Make-server} creates a new server record. Each slot is a
decoded string or \sharpf. (\var{Port} is also a string.)
\ex{server?} is the corresponding predicate, \ex{server-user},
\ex{server-password}, \ex{server-host} and \ex{server-port}
are the correspondig selectors.
\end{desc}
\defun{parse-server}{path default}{server}
\defunx{server->string}{server}{string}
\begin{desc}
\ex{Parse-server} parses a URI path \var{path} (a list representing
a path, not a string) into a server value. Default values are taken
from the server \var{default} except for the host. The values
are unescaped and stored into a server record that is returned.
\ex{Fatal-syntax-error} is called, if the specified path has no
initial to slashes (i.e., it starts with `//\ldots').
\ex{server->string} just does the inverse job: it unparses
\var{server} into a string. The elements of the record
are escaped before they are put together.
Example:
\begin{alltt}
> (define default (make-server "andreas" "se ret" "www.sf.net" "80"))
> (server->string default)
"andreas:se\%20ret@www.sf.net:80"
> (parse-server '("" "" "foo\%20bar@www.scsh.net" "docu" "index.html")
default)
'#{server}
> (server->string ##)
"foo\%20bar:se\%20ret@www.scsh.net:80"
\end{alltt}
%
For details about escaping and unescaping see Chapter~\ref{cha:uri}.
\end{desc}
\section{HTTP URLs}
\defun{make-http-url}{server path search frag-id}{http-url}
\defunx{http-url?}{thing}{boolean}
\defunx{http-url-server}{http-url}{server}
\defunx{http-url-path}{http-url}{list}
\defunx{http-url-search}{http-url}{string-or-\sharpf}
\defunx{http-url-fragment-identifier}{http-url}{string-or-\sharpf}
%
\begin{desc}
\ex{Make-http-url} creates a new \ex{httpd-url} record.
\var{Server} is a record, containing the initial part of the address
(like \ex{anonymous@clark.lcs.mit.edu:80}). \var{Path} contains the
URL's URI path ( a list). These elements are in raw, unescaped
format. To convert them back to a string, use
\ex{(uri-path->uri (map escape-uri pathlist))}. \var{Search}
and \var{frag-id} are the last two parts of the URL. (See
Chapter~\ref{cha:uri} about parts of an URI.)
\ex{Http-url?} is the predicate for HTTP URL values, and
\ex{http-url-server}, \ex{http-url-path}, \ex{http-url-search} and
\ex{http-url-fragment-identifier} are the corresponding selectors.
\end{desc}
\defun{parse-http-url}{path search frag-id}{http-url}
\begin{defundescx}{http-url->string}{http-url}{string}
This constructs an HTTP URL record from a URI path (a list of path
components), a search, and a frag-id component.
\ex{Http-url->string} just does the inverse job. It converts an
HTTP URL record into a string.
\end{defundescx}
%
Note: The URI parser \ex{parse-uri} maps a string to four parts:
\var{scheme}, \var{path}, \var{search} and \var{frag-id} (see
Section~\ref{proc:parse-uri} for details). If \var{scheme} is
\ex{http}, then the other three parts can be passed to
\ex{parse-http-url}, which parses them into a \ex{http-url} record.
All strings come back from the URI parser encoded. \var{Search} and
\var{frag-id} are left that way; this parser decodes the path
elements. The first two list elements of the path indicating the
leading double-slash are omitted.
The following procedure combines the jobs of \ex{parse-uri} and
\ex{parse-http-url}:
\defun{parse-http-url-string}{string}{http-url}
\begin{desc}
This parses an HTTP URL and returns the corresponding URL value; it
calls \ex{fatal-syntax-error} if the URL string doesn't have an
\ex{http} scheme.
\end{desc}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "man"
%%% End:

View File

@ -1,26 +0,0 @@
(define-package "sunet" (2 1)
((install-lib-version (1 0))
(options (with-surflets "Install with SUrflets (requires SSAX)" "<yes/no>" #t #t #t
,parse-boolean ,show-boolean)))
(let ((surflets? (get-option-value 'with-surflets)))
(install-directory-contents "scheme" 'scheme)
(install-directory "web-server" 'misc-shared)
(install-directory-contents "doc" 'doc)
(let ((doc-dir (get-directory 'doc #t))
(misc-shared-dir (get-directory 'misc-shared #t)))
(create-symlink (string-append doc-dir "/html")
(string-append misc-shared-dir
"/web-server/root/htdocs/sunet-manual")))
(let ((scheme-dir (get-directory 'scheme #t)))
(write-to-load-script
`((user)
(config)
(load ,(string-append scheme-dir "/packages.scm"))
,@(if surflets?
`((load ,(string-append scheme-dir "/httpd/surflets/packages.scm")))
'())
(user)))))
)

View File

@ -1,82 +0,0 @@
***********************
*** README for DNSD ***
***********************
Copyright (c) 2005/2006 by Norbert Freudemann
<nofreude@informatik.uni-tuebingen.de>
For copyright information, see the file COPYING which comes with
the distribution.
RUNNING THE NAMESERVER:
-----------------------
1) Install SCSH, SUnet and SUnterlib
---------------------------------
For instructions see www.scsh.net
2) The configuration
-----------------
There is a folder etc/ containing the files
dnsd-options.scm
dnsd-zones.scm
dnsd-pre.scm
dnsd-post.scm
and some additional masterfile-examples.
You can copy this files to a directory of your liking
or simply use the given path (from the SUnet-installation).
Either way, the path will be called <path-to-options>.
You can customize the files:
2.1) dnsd-options.scm
Options for DNSD. Open the file for documentation.
2.2) dnsd-zones.scm
Add/remove zones to DNSD. Documentation is included in the file.
2.3) dnsd-pre.scm / dnsd-post.scm
You can customize the behaviour of query-processing within these
two files.
3) Run SCSH:
---------
Load the CML-API from SUnterlib and SUnet.
> scsh -lel cml/load.scm -lel sunet/load.scm
4) SCSH-REPL:
----------
>,in dnsd
Start DNSD with
dnsd> (dnsd-start)
if the current working-directory is <path-to-options> or else use
dnsd> (dnsd-start <path-to-options>)
5) While running DNSD:
-------------------
* Reload the file dnsd-options.scm with the POSIX-signal USR1.
* Reload the file dnsd-zones.scm with the POSIX-signal USR2.

View File

@ -1,170 +0,0 @@
; ----------------------------
; --- Query/Response-Cache ---
; ----------------------------
; Cache for dnsd.scm
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; Revised version of the cache implementation seen in dns.scm.
; The cache stores data that was received during a recursive lookup.
; The access-key of the cache consists of a question-name/class/type, the
; data is a list of answers/additionals/authority.
; It uses r/w-lock to avoid multiple simultaneous writes.
; Cache-Interface:
; -----------------
; (dnsd-cache-clear!) - Removes the whole data.
; (dnsd-cache-clean!) - Removes expired data.
; (dnsd-cache-lookup? msg) - Searches for a cached reply.
; (dnsd-cache-update! msg) - Updates the data to include the given msg.
; (dnsd-cache-pretty-print) - Prints the cache.
;; Cache:
;; ------
(define-record-type dnsd-cache :dnsd-cache
(make-dnsd-cache data lock)
dnsd-cache?
(data get-dnsd-cache-data) ; cache-data-record-type
(lock get-dnsd-cache-lock)) ; r/w-lock
(define-record-type cache-data :cache-data
(make-cache-data answer expires)
cache?
(answer cache-data-answer) ; an answer as needed by lookup-query
(expires cache-data-expires)) ; expiration time of the data (+ ttl (time))
;; Create the cache:
(define *dnsd-cache* (make-dnsd-cache (make-string-table) (make-r/w-lock)))
;; Search for the shortest TTL in the message:
;; TYPE: message -> number or #f
(define (find-shortest-ttl msg)
(let loop ((msg msg))
(cond
((dns-message? msg) (loop (dns-message-reply msg)))
((message? msg) (fold-right
(lambda (e m)
(let ((ttl (resource-record-ttl e)))
(if m
(if (<= m ttl) m ttl)
ttl)))
#f
(append (message-answers msg)
(message-nameservers msg)
(message-additionals msg)))))))
;; Make a cache-key from the message:
;; TYPE: message -> key-string
(define (make-cache-key msg)
(let ((question (car (message-questions msg))))
(format #f "~a;~a;~a" (question-name question)
(message-type-name (question-type question))
(message-class-name (question-class question)))))
;; Reset the cache:
(define (dnsd-cache-clear!)
(with-r/W-lock
(get-dnsd-cache-lock *dnsd-cache*)
(lambda ()
(set! *dnsd-cache*
(make-dnsd-cache (make-string-table)
(get-dnsd-cache-lock *dnsd-cache*))))))
;; Remove expired data from the cache:
(define (dnsd-cache-clean!)
(with-r/W-lock
(get-dnsd-cache-lock *dnsd-cache*)
(lambda ()
(let ((time (time))
(table (get-dnsd-cache-data *dnsd-cache*)))
(table-walk (lambda (k e)
(if (< time (cache-data-expires e))
#t
(table-set! table k #f)))
table)))))
; Look for data in the cache. If the found answer is expired return
; #f and remove the answer from the cache.
; TYPE: message -> '(l-of-answ l-of-auth l-of-addi boolean) or #f
(define (dnsd-cache-lookup? msg)
(let ((lock (get-dnsd-cache-lock *dnsd-cache*)))
(obtain-R/w-lock lock)
(let* ((data (get-dnsd-cache-data *dnsd-cache*))
(key (make-cache-key msg))
(cdata (table-ref data key)))
(if cdata
(if (< (time) (cache-data-expires cdata))
(let ((res (cache-data-answer cdata)))
(release-R/w-lock lock)
res)
(begin
(release-R/w-lock lock)
(obtain-r/W-lock lock)
(table-set! data key #f)
(release-r/W-lock lock)
#f))
(begin
(release-R/w-lock lock)
#f)))))
;; Add the answer-sections (ansers/authority/additionals) and the authoritative
;; flag of a message to the cache:
;; TYPE: message -> unspecific
(define (dnsd-cache-update! msg)
(with-r/W-lock
(get-dnsd-cache-lock *dnsd-cache*)
(lambda ()
(let ((shortest-ttl (find-shortest-ttl msg)))
(if (> shortest-ttl 0)
(table-set!
(get-dnsd-cache-data *dnsd-cache*)
(make-cache-key msg)
(make-cache-data
(list (message-answers msg)
(message-nameservers msg)
(message-additionals msg)
(header-flags (message-header msg))) ; authoritative?
(+ (time) shortest-ttl)))
#f)))))
;; Display the cache:
(define (dnsd-cache-pretty-print)
(with-R/w-lock
(get-dnsd-cache-lock *dnsd-cache*)
(lambda ()
(let ((data (get-dnsd-cache-data *dnsd-cache*)))
(display "DNSD-CACHE:\n")
(display "-----------\n")
(table-walk
(lambda (k e)
(let ((cache-data (cache-data-answer e)))
(display "\n*Question: ")
(display k)(newline)
(display " ---------\n")
(display " Expires in: ")
(display (- (cache-data-expires e) (time)))
(display " seconds.\n")
(display " \n Answer-Section:\n\n")
(map (lambda (x) (pretty-print-dns-message x)) (car cache-data))
(display " \n Authority-Section:\n\n")
(map (lambda (y) (pretty-print-dns-message y)) (cadr cache-data))
(display " \n Additionals-Section:\n\n")
(map (lambda (z) (pretty-print-dns-message z)) (caddr cache-data))))
data)))))

View File

@ -1,674 +0,0 @@
;; ---------------------
;; --- dnsd-database ---
;; ---------------------
; A simple database for dnsd.scm
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; Naming-Scheme:
; --------------
; dbi- == No locks (should not be exported)
; db- == With locks
; Lock-Safe Database-Interface:
; -----------------------------
; (db-clear-database)
; (db-clear-zone name class)
; (db-update-zone zone-list)
; (db-get-zone name class)
; (db-get-zone-for-axfr name class)
; (db-get-zone-soa-rr name class)
; (db-pretty-print)
; Query/Database-Interface
; ------------------------
; (db-lookup-rec qname class type)
; Database Structure:
; -------------------
; db-class-table: hash-table to db-zones
; |
; |-->db-zones-table: hash-table to db-zone
; |
; |-->db-zone: hash-table to db-rr
; |
; |-->db-rr-table: hash-table to lists of resource-records
; of a given message-type
;; Some stuff:
;; -----------
;; Should be a dnsd-option?
(define *debug-info* #t)
;; Make a key for the database:
;; TYPE: string -> string
(define (make-key-name name)
(let ((last-char (string-ref name (- (string-length name) 1))))
(string-downcase (if (not (char=? #\. last-char))
(string-append name ".")
name))))
;; Compare the first string with the rear of the second string.
;; TYPE: string x string -> boolean
(define (string-ci-zone-name=? zone name)
(let ((l1 (string-length zone))
(l2 (string-length name)))
(if (<= l1 l2) (string-ci=? zone (substring name (- l2 l1) l2)) #f)))
;; Search a list of resource-records for the soa-rr:
;; TYPE: list-of-rrs -> soa-rr or #f
(define (maybe-get-soa-rr l)
(let loop ((l l))
(if (null? l)
#f
(let ((e (car l)))
(if (resource-record-data-soa?
(resource-record-data e))
e
(loop (cdr l)))))))
;; Get the name of a zone from a soa-rr within a zone-list:
;; TYPE: list-of-rrs -> zone-name or #f
(define (maybe-get-soa-rr-name l)
(and-let* ((soa-rr (maybe-get-soa-rr l)))
(resource-record-name soa-rr)))
;; TYPE : list-or-rrs -> list-of-rrs
(define (get-zone-list-w/o-soa l)
(fold-right
(lambda (e l)
(if (resource-record-data-soa? (resource-record-data e)) l (cons e l)))
'() l))
;; TODO: Do this different...
(define display-debug
(lambda args
(if *debug-info*
(begin
(display "dnsd: ")
(map (lambda (e) (display e) (display " ")) args)
(newline))
#f)))
;; Duplicate a resource-record: (Needed for wildcard-replies)
(define (duplicate-rr name rr)
(make-resource-record name
(resource-record-type rr)
(resource-record-class rr)
(resource-record-ttl rr)
(resource-record-data rr)))
; ---------------------------
; --- Database definition ---
; ---------------------------
; Record-types:
; -------------
; db-rr-table stores the resource-records of ONE domain-name.
; hash-table is a symbol-table with 'message-type' as keys
; and a list of resource-record of the key-message-type as data.
; glue-data stores the information (as boolean) if the given domain-name
; is for glue-data or official.
(define-record-type db-rr-table :db-rr-table
(really-make-db-rr-table hash-table glue-data)
db-rr-table?
(hash-table db-rr-table-hash-table)
(glue-data db-rr-table-glue-data? set-db-rr-table-glue-data?!))
(define (make-db-rr-table) (really-make-db-rr-table (make-symbol-table) #f))
; db-zone stores data (in form of db-rr-tables) for an entire zone
; as given by e.g. a masterfile
; hash-table a string-table. Keys are the domain-names of the zone
; to link to db-rr-tables.
; name the name of the zone.
; soa-rr for easy-access :-)
(define-record-type db-zone :db-zone
(really-make-db-zone hash-table name soa-rr)
db-zone?
(hash-table db-zone-table)
(name db-zone-name)
(soa-rr get-db-zone-soa-rr))
(define (make-db-zone name soa-rr)
(let ((primary-name (resource-record-data-soa-mname
(resource-record-data soa-rr))))
(really-make-db-zone (make-string-table) name soa-rr)))
; db-zones-table stores all zones of a given message-class
; hash-table key is the zone-name.
(define-record-type db-zones-table :db-zones-table
(really-make-db-zones-table hash-table)
db-zones-table?
(hash-table db-zones-table-hash-table))
(define (make-db-zones-table) (really-make-db-zones-table (make-string-table)))
; db-class-table entry-point for the db.
; hash-table key is the message-class (e.g. in) data are db-zones-tables
; r/w-lock lock for exclusive-write-access.
(define-record-type db-class-table :db-class-table
(really-make-db-class-table hash-table r/w-lock)
db-class-table?
(hash-table db-class-table-hash-table set-db-class-table-hash-table!)
(r/w-lock db-class-table-r/w-lock))
(define *database* (really-make-db-class-table (make-symbol-table)
(make-r/w-lock)))
; Predicates:
; -----------
; Check if there is data for a given message-class:
; TYPE: message-class -> boolean
(define (dbi-class? class)
(if (table-ref (db-class-table-hash-table *database*)
(message-class-name class))
#t #f))
;; Modifiers:
;; ----------
;; Delete the whole data in the database:
(define (db-clear-database)
(with-r/W-lock
(db-class-table-r/w-lock *database*)
(lambda ()
(set-db-class-table-hash-table! *database* (make-symbol-table)))))
;; Delete a zone (if present) with name 'name' from the database:
;; TYPE: string x message-class -> boolean
(define (db-clear-zone name class)
(with-r/W-lock
(db-class-table-r/w-lock *database*)
(lambda ()
(and-let* ((whatever (dbi-class? class))
(class-table (db-class-table-hash-table *database*))
(zones-type (table-ref class-table (message-class-name class)))
(zones-table (db-zones-table-hash-table zones-type))
(key-name (make-key-name name))
(whatever (table-ref zones-table key-name)))
(table-set! zones-table key-name #f)))))
;; Stuff for db-add-zone:
;; Add a new class (if not already present) to the database:
;; TYPE: message-class -> unspecific
(define (dbi-maybe-add-class class)
(if (not (dbi-class? class))
(table-set! (db-class-table-hash-table *database*)
(message-class-name class)
(make-db-zones-table))))
;; --- Detection of Zone-Rules ---
;; Detect and mark glue data (domains with NS and all of their subdomains)
;; Give a warning, if the zone-tree is broken
;; TYPE: db-def-table x string -> unspecific
(define (dbi-mark-glue-in-zone def-table zone-name)
(let ((tree (db-zone-table def-table)))
(table-walk
(lambda (key element)
(if (table-ref (db-rr-table-hash-table element)
(message-type-name (message-type a)))
(let loop ((name key))
(if (string-ci=? name zone-name)
#t
(let ((zone-entry (table-ref tree name)))
(if zone-entry
(if (table-ref (db-rr-table-hash-table zone-entry)
(message-type-name (message-type ns)))
(set-db-rr-table-glue-data?! element #t)
(loop (cut-name name)))
;; Be tolerant if the domain tree is broken...
(begin
(dnsd-log (syslog-level info)
"Warning (re)loading zone ~S. Broken tree: Domain ~S is missing!"
zone-name name)
(loop (cut-name name)))))))
#t))
tree)))
;; Ensures the min. TTL from the soa-rr of the zone. Has to be called
;; after dbi-mark-glue-in-zone!
;; TYPE: db-def-table x soa-rr -> unspecific
(define (dbi-ensure-min-ttl def-table soa-rr)
(let ((min-ttl (resource-record-data-soa-minimum
(resource-record-data soa-rr))))
(table-walk
(lambda (key element)
(if (not (db-rr-table-glue-data? element))
(table-walk
(lambda (tkey telement)
(table-set! (db-rr-table-hash-table element)
tkey
(map (lambda (e)
(let ((rr-ttl (resource-record-ttl e)))
(make-resource-record
(resource-record-name e)
(resource-record-type e)
(resource-record-class e)
(if (< rr-ttl min-ttl)
min-ttl rr-ttl)
(resource-record-data e))))
telement)))
(db-rr-table-hash-table element))))
(db-zone-table def-table))))
;; Give a warning, if a Zone with a CNAME-RR contains other stuff...
;; TYPE: db-def-table -> unspecific
(define (dbi-cname-warning def-table zone-name)
(table-walk
(lambda (key element)
(let ((rr-table (db-rr-table-hash-table element))
(cname (message-type-name (message-type cname))))
(if (table-ref rr-table cname)
(table-walk
(lambda (k e)
(if (not (eq? k cname))
(dnsd-log (syslog-level info)
"Warning (re)loading zone ~S. Domain ~S contains a CNAME-RR and other RRs at the same time."
zone-name key)
(if (not (= 1 (length e)))
(dnsd-log (syslog-level info)
"Warning (re)loading zone ~S. Domain ~S contains 2 or more CNAME-RRs!"
zone-name key))))
rr-table))))
(db-zone-table def-table)))
;; This functions have to be called in the given order:
;; TYPE: db-def-table x string x soa-rr -> unspecific
(define (dbi-set-zone-requirements def-table zone-name soa-rr)
(dbi-mark-glue-in-zone def-table zone-name)
(dbi-ensure-min-ttl def-table soa-rr)
(dbi-cname-warning def-table zone-name))
;; Adds a list of resource-records to a zone-definition-table:
(define (dbi-add-zone-list def-table rr-list)
(let ((tree (db-zone-table def-table)))
(for-each
(lambda (e)
(let* ((domain-key (make-key-name (resource-record-name e)))
(type-key (message-type-name (resource-record-type e)))
(rr-type (table-ref tree domain-key)))
;; Create & link a new rr-table for the first entry of the rr-type:
(if (not (db-rr-table? rr-type))
(begin (set! rr-type (make-db-rr-table))
(table-set! tree domain-key rr-type)))
(let* ((rr-table (db-rr-table-hash-table rr-type))
(entry (table-ref rr-table type-key)))
(if entry
(table-set! rr-table type-key (cons e entry))
(table-set! rr-table type-key (cons e '()))))))
rr-list)))
;; Adds a zone to the database which is given as a list of resource-records.
;; Notes: * db-add-zone doesn't overwrite existing zones.
;; * Just for internal use.
;; TYPE: list-of-rrs -> boolean
(define (db-add-zone zone-list)
(with-r/W-lock
(db-class-table-r/w-lock *database*)
(lambda ()
(and-let* ((soa-rr (maybe-get-soa-rr zone-list))
(zone-name (resource-record-name soa-rr))
(zone-key (make-key-name zone-name))
(zone-class (resource-record-class soa-rr)))
;; Add another class to the database?
(dbi-maybe-add-class zone-class)
;; Get the zone-stuff to insert the zone into together:
(let* ((zone-table (db-zones-table-hash-table
(table-ref (db-class-table-hash-table *database*)
(message-class-name zone-class)))))
;; Don't overwrite an existing zone
(if (table-ref zone-table zone-key) #f
;; Add the zone to the db & ensure data integrity:
(let* ((zone-dtable (make-db-zone zone-key soa-rr)))
(table-set! zone-table zone-key zone-dtable)
(dbi-add-zone-list zone-dtable zone-list)
(dbi-set-zone-requirements zone-dtable zone-name soa-rr))))))))
;; Update a zone if the serial of the new soa isn't the same or less.
;; TYPE: list-of-rrs -> boolean
(define (db-update-zone zone-list)
(and-let* ((new-soa-rr (maybe-get-soa-rr zone-list))
(new-serial (resource-record-data-soa-serial
(resource-record-data new-soa-rr)))
(zone-name (make-key-name (resource-record-name new-soa-rr)))
(zone-class (resource-record-class new-soa-rr)))
(let ((old-soa-rr (db-get-zone-soa-rr zone-name zone-class)))
(cond
((or (not old-soa-rr)
(and old-soa-rr
(> new-serial (resource-record-data-soa-serial
(resource-record-data old-soa-rr)))))
(db-clear-zone zone-name zone-class)
(db-add-zone zone-list))
((= new-serial (resource-record-data-soa-serial
(resource-record-data old-soa-rr)))
#t) ;; !!! If the serial hasn't changed it's considered successfull.
(else #f)))))
; Get all resource records for a zone.
; TYPE: string x message-class -> list-of-rrs or #f
(define (db-get-zone name class)
(with-R/w-lock
(db-class-table-r/w-lock *database*)
(lambda ()
(and-let* ((zone-type (table-ref (db-class-table-hash-table *database*)
(message-class-name class)))
(the-zone-type (table-ref (db-zones-table-hash-table zone-type)
(make-key-name name)))
(zone-tree-tree (db-zone-table the-zone-type))
(res-list '()))
(table-walk
(lambda (k e)
(if e
(table-walk (lambda (k1 e1)
(set! res-list (append e1 res-list)))
(db-rr-table-hash-table e))))
zone-tree-tree)
res-list))))
; ; Get the timestamp for a zone.
; ; TYPE: string x message-class -> number or #f
; (define (db-get-zone-timestamp name class)
; (with-R/w-lock
; (db-class-table-r/w-lock *database*)
; (lambda ()
; (and-let* ((zone-type (table-ref (db-class-table-hash-table *database*)
; (message-class-name class)))
; (the-zone-type (table-ref (db-zones-table-hash-table zone-type)
; (make-key-name name))))
; (get-db-zone-timestamp the-zone-type)))))
;; Get the soa-rr of a zone.
;; TYPE: string x message-class -> soa-rr or #f
(define (db-get-zone-soa-rr name class)
(with-R/w-lock
(db-class-table-r/w-lock *database*)
(lambda ()
(and-let* ((zone-type (table-ref (db-class-table-hash-table *database*)
(message-class-name class)))
(the-zone-type (table-ref (db-zones-table-hash-table zone-type)
(make-key-name name))))
(get-db-zone-soa-rr the-zone-type)))))
; Get all rrs of a zone in an AXFR-ready list: '(soa-rr rr rr ... rr soa-rr)
; TYPE: string x message-class -> list-of-rrs or #f
(define (db-get-zone-for-axfr name class)
(and-let* ((zone-list (db-get-zone name class))
(soa-l (list (maybe-get-soa-rr zone-list)))
(rest-l (get-zone-list-w/o-soa zone-list)))
(append soa-l rest-l soa-l)))
;; Look for the zone in which 'name' is a subdomain or the domain of the
;; given zones. Returns the zone which is the nearest ancestor to 'name'.
;; TYPE: name x message-class -> db-zone-record-type or #f
(define (dbi-lookup-zone-for-name name class)
(and-let* ((zone-record (table-ref (db-class-table-hash-table *database*)
(message-class-name class)))
(zone-table (db-zones-table-hash-table zone-record))
(ancestors '())
(zone-key ""))
;; Look for zones who are ancestors to key:
(table-walk (lambda (k e)
(if (string-ci-zone-name=? k (make-key-name name))
(set! ancestors (cons k ancestors))))
zone-table)
(cond
((null? ancestors) #f)
((= 1 (length ancestors)) (set! zone-key (car ancestors)))
;; If more ancestors are found get the closest one:
(else (set! zone-key (fold-right (lambda (a b) (if (< (string-length a)
(string-length b))
b a))
"" ancestors))))
(table-ref zone-table zone-key)))
; Look for the entries of type 'type' in a given db-rr-table
; TYPE: db-rr-table-rec-type x message-type -> list-of-rrs
(define (dbi-lookup-rrs rr-record-type type)
(let ((rr-table (db-rr-table-hash-table rr-record-type)))
(cond
((eq? (message-type *) type) ; ... return all records.
(let ((res '())) (table-walk (lambda (k e) (set! res (cons e res)))
rr-table)
res))
(else (let ((res (table-ref rr-table (message-type-name type))))
(if res res '()))))))
;; Look for the entries of type 'type' in a given db-rr-table
;; TYPE: db-rr-table-rec-type x messag-type -> list-of-rrs or #f
(define (dbi-lookup-rrs? rr-record-type type)
(let ((res (dbi-lookup-rrs rr-record-type type)))
(if (null? res) #f res)))
;; --------------------------------
;; --- Query/Database Interface ---
;; --------------------------------
;; Requests for mailbox-related resource-records will be handled as mx requests:
;; TYPE: string x type x class ->
;; '(list-of-answers-rrs list-of-nameservers-rrs list-of-additional-rrs boolean)
(define (db-lookup-rec qname class type)
(obtain-R/w-lock (db-class-table-r/w-lock *database*))
(receive
(anli auli adli aufl)
(dbi-lookup-rec-int qname class (if (eq? type (message-type mailb))
(message-type mx)
type) ; Mailb == mx query
'())
(release-R/w-lock (db-class-table-r/w-lock *database*))
(values anli auli adli aufl)))
;; Main part of the algorithm as described in RFC 1034. Returns found rrs and
;; a flag, indicating if the answer is authoritative.
;; The flag ist needed, because of glue-data, that could be part of the
;; response. The operand 'c-list' is used to detect and avoid cname-loops.
;; TYPE: string x type x class x c-list ->
;; '(list-of-answers-rrs list-of-nameservers-rrs list-of-additional-rrs boolean)
(define (dbi-lookup-rec-int qname class type c-list)
(let ((zone (dbi-lookup-zone-for-name qname class)))
(if (not zone)
(values '() '() '() #f) ; no zone in db
(let ((zone-name (db-zone-name zone)))
;; loop over the labels of the name. eg. my.example. / example. / .
;; keep track of the iterations (mostly for wildcard-match support)
(let loop ((name qname) (loop-count 0))
(let ((rr-table (table-ref (db-zone-table zone)
(make-key-name name))))
(if rr-table
(cond
;; A wildcard match
((= 1 loop-count)
;; Set the name of the rrs from * to qname.
(values (map (lambda (e) (duplicate-rr qname e))
(dbi-lookup-rrs rr-table type)) '() '() #t))
;; Direct match (0) or glue-data match (>1)
((or (= 0 loop-count) (< 1 loop-count))
(cond ;c2
;; Found glue data.
((and (dbi-lookup-rrs? rr-table (message-type ns))
(not (string-ci=? name zone-name))
(not (eq? (message-type ns) type)))
(let* ((ns-rr-list (dbi-lookup-rrs?
rr-table (message-type ns)))
(res-l
(fold-right
(lambda (e l)
(receive
(anli auli adli aufl)
(dbi-lookup-rec-int
(resource-record-data-ns-name
(resource-record-data e))
class (message-type a) c-list)
(list (car l) (cadr l)
(append anli (caddr l)) #f)))
'(() () () #t) ns-rr-list)))
(values (car res-l) (append ns-rr-list (cadr res-l))
(caddr res-l) #f)))
;; Looking for correct information (direct match)
((= 0 loop-count)
(cond ;c3
;; CNAME: Causes an additional lookup
((dbi-lookup-rrs? rr-table (message-type cname))
=> (lambda (cname-rr-list)
(let ((cname-rr (car cname-rr-list)))
(if (eq? (message-type cname) type)
(values (list cname-rr) '() '() #t)
(begin
(if (fold-right
(lambda (e b)
(or (string-ci=? e name) b))
#f c-list)
(begin
;; Problem?: The loop will be send
;; as a response... .
(display-debug " Found cname-loop")
(values '() '() '() #t))
(receive
(anli auli adli aufl)
(dbi-lookup-rec-int
(resource-record-data-cname-name
(resource-record-data cname-rr))
class type (cons name c-list))
(values (append (list cname-rr) anli)
auli adli
(and aufl #t)))))))))
;; MX: Causes an additional lookup
((eq? (message-type mx) type)
(let* ((mx-rrs (dbi-lookup-rrs rr-table type))
(res-l
(fold-right
(lambda (e l)
(receive
(anli auli adli aufl)
(dbi-lookup-rec-int
(resource-record-data-mx-exchanger
(resource-record-data e))
class (message-type a) c-list)
(list (car l) (cadr l)
(append anli (caddr l))
(and #t (cadddr l)))))
'(() () () #t) mx-rrs)))
(values (append mx-rrs (car res-l)) (cadr res-l)
(caddr res-l) (and #t (cadddr res-l)))))
;; Glue-Data entries aren't authoritative:
((db-rr-table-glue-data? rr-table)
(values (dbi-lookup-rrs rr-table type) '() '() #f))
;; Found a match with no additional lookups.
(else
(values (dbi-lookup-rrs rr-table type) '() '() #t))))
;; Got a dns-name-error (RCODE=3)
(else (values '() '() '() #t)))))
;; Found no match for the current name.
(cond
((> (string-length zone-name) (string-length name))
(error "Woh, found a bug... ")) ; Just for safety...
;; Search for wildcards in the first iteration:
((= 0 loop-count)
(loop (string-append "*." (cut-name name)) 1))
(else (loop (cut-name name) (+ 1 loop-count)))))))))))
;; ------------------------------
;; --- Database pretty-print: ---
;; ------------------------------
(define (pretty-print-record-type rt)
(cond
((db-class-table? rt)
(table-walk
(lambda (k e)
(newline)
(display "DB-Class: ")
(display k)(newline)
(pretty-print-record-type e))
(db-class-table-hash-table rt)))
((db-zones-table? rt)
(table-walk
(lambda (k e)
(display " DB-Zone: ")
(display k)
(newline)
(pretty-print-record-type e))
(db-zones-table-hash-table rt)))
((db-zone? rt)
(table-walk
(lambda (k e)
(display " DB-Zone-Entries: ")
(display k)
(newline)
(pretty-print-record-type e))
(db-zone-table rt)))
((db-rr-table? rt)
(table-walk
(lambda (k e)
(display " DB-RR-Table: ")
(display k)
(newline)
(display " Glue-data: ")
(display (db-rr-table-glue-data? rt))
(newline)
(newline)
(pretty-print-record-type e))
(db-rr-table-hash-table rt)))
((list? rt)
(for-each
(lambda (e)
(pretty-print-dns-message e)
(newline))
rt))
(else (newline))))
(define (db-pretty-print)
(with-R/w-lock
(db-class-table-r/w-lock *database*)
(lambda ()
(newline)
(display "DNS-Server-Database:")(newline)
(display "--------------------")(newline)
(pretty-print-record-type *database*))))

View File

@ -1,134 +0,0 @@
;; ------------------------
;; --- Database-Options ---
;; ------------------------
; Database-Options for DNS-Server based on the RFCs: 1034 / 1035
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; The format and style of the option procedures is the same as seen
; in the SUNet HTTPD & FTPD - Files
(define-record-type dnsddb-options :dnsddb-options
(really-make-dnsddb-options name class type primary? file filetype master-name master-ip)
dnsddb-options?
(name dnsddb-options-name set-dnsddb-options-name!)
(class dnsddb-options-class set-dnsddb-options-class!)
(type dnsddb-options-type set-dnsddb-options-type!)
(primary? dnsddb-options-primary? set-dnsddb-options-primary?!) ;;depreaced
(file dnsddb-options-file set-dnsddb-options-file!)
(filetype dnsddb-options-filetype set-dnsddb-options-filetype!)
(master-name dnsddb-options-master-name set-dnsddb-options-master-name!)
(master-ip dnsddb-options-master-ip set-dnsddb-options-master-ip!))
(define (make-default-dnsddb-options)
(really-make-dnsddb-options
"" ;; the name of the zone
(message-class in)
"primary" ;;
#t ;; is primary?
"" ;; a filename
"dnsd" ;; "dnsd" or "rfc"
#f ;; Has to be set by dnsd-zones.scm, e.g. "dns01.my.example."
#f)) ;; e.g. "192.168.2.1" or #f
(define (copy-dnsddb-options options)
(really-make-dnsddb-options
(dnsddb-options-name options)
(dnsddb-options-class options)
(dnsddb-options-type options)
(dnsddb-options-primary? options)
(dnsddb-options-file options)
(dnsddb-options-filetype options)
(dnsddb-options-master-name options)
(dnsddb-options-master-ip options)))
(define (make-dnsddb-options-transformer set-option!)
(lambda (new-value . stuff)
(let ((new-options (if (not (null? stuff))
(copy-dnsddb-options (car stuff))
(make-default-dnsddb-options))))
(set-option! new-options new-value)
new-options)))
(define with-name
(make-dnsddb-options-transformer set-dnsddb-options-name!))
(define with-class
(make-dnsddb-options-transformer set-dnsddb-options-class!))
(define with-type
(make-dnsddb-options-transformer set-dnsddb-options-type!))
(define with-primary?
(make-dnsddb-options-transformer set-dnsddb-options-primary?!))
(define with-file
(make-dnsddb-options-transformer set-dnsddb-options-file!))
(define with-filetype
(make-dnsddb-options-transformer set-dnsddb-options-filetype!))
(define with-master-name
(make-dnsddb-options-transformer set-dnsddb-options-master-name!))
(define with-master-ip
(make-dnsddb-options-transformer set-dnsddb-options-master-ip!))
(define (make-dnsddb-options . stuff)
(let loop ((options (make-default-dnsddb-options))
(stuff stuff))
(if (null? stuff)
options
(let* ((transformer (car stuff))
(value (cadr stuff)))
(loop (transformer value options)
(cddr stuff))))))
(define (make-db-options-from-list o-list)
(let ((options (make-default-dnsddb-options)))
(if (eq? (car o-list) 'zone)
(begin
(for-each
(lambda (e)
(let ((id (car e))
(value (cadr e)))
(case id
((name)
(if (string? value)
(set-dnsddb-options-name!
options (make-fqdn-name value))
(error "Bad option argument.")))
((type)
(if (or (string-ci=? "primary" value)
(string-ci=? "secondary" value)
(string-ci=? "master" value)
(string-ci=? "slave" value))
(set-dnsddb-options-type! options value)
(error "Bad option argument.")))
((file)
(if (and (string? value) (file-name-non-directory? value))
(set-dnsddb-options-file! options value)
(error "Bad option argument.")))
((filetype)
(if (or (string-ci=? "dnsd" value)
(string-ci=? "rfc" value))
(set-dnsddb-options-filetype! options value)
(error "Bad option argument.")))
((master-name)
(if (string? value)
(set-dnsddb-options-master-name! options value)
(error "Bad option argument.")))
((master-ip)
(if (string? value)
(set-dnsddb-options-master-ip! options value)
(error "Bad option argument.")))
(else (error "Bad option.")))))
(cdr o-list))
options)
(error "Not an option list."))))

View File

@ -1,836 +0,0 @@
; ------------------
; --- DNS-Server ---
; ------------------
; A DNS-Server based on the RFCs: 1034 / 1035
; This file is (maybe) part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; TODO:
; -----
; Testing, testing, testing...
; Nice stuff to have:
; * IXFR
; * IPv6-Support
; * Support more types (& other classes)
; * Masterfile-parser: $GENERATE ...
; * Some accurate way to limit the cache to a certain mem-size?
; * Better syslog interaction.
; Doc-TODO:
; - Master-File-Parser
; - Cache
; - Database
; - dnsd messages
; - dnsd-options
; Message Example (Query):
; ------------------------
; (define *query-example*
; (make-message (make-header 0815 (make-flags 1 0 #f #f #f #f 0 0) 1 0 0 0)
; (list (make-question "uni-tuebingen.de."
; (message-type a)
; (message-class in)))
; '() '() '() '()))
;; Assignment procedures for messages (basically dns.scm extension)
;; ----------------------------------------------------------------
;; Set the truncation bit of an octet-message (for UDP):
;; TYPE: message x boolean -> message
(define (octet-msg-change-truncation msg bool)
(let* ((id (take msg 2))
(rest (drop msg 3))
(flag (char->ascii (caddr msg)))
(flag-RD (if (even? flag) 0 1))
(flag-shift (arithmetic-shift flag -2)))
(append id (list (ascii->char
(+ flag-RD (arithmetic-shift
(+ (if bool 1 0)
(arithmetic-shift flag-shift 1)) 1))))
rest)))
;; Interpreting the results of db-lookup-rec. Is there a zone in the db:
;; TYPE: '(list-of-ans list-of-aut list-of-add boolean) -> boolean
(define (no-zone? res-l)
(and (null? (car res-l)) (null? (cadr res-l))
(null? (caddr res-l)) (not (cadddr res-l))))
;; A reply is chacheworthy if it contains no errors and is authoritative.
;; TYPE: message -> boolean
(define (msg-cachable? msg)
(and (eq? 'dns-no-error (flags-response-code
(header-flags (message-header msg))))
(flags-authoritative? (header-flags (message-header msg)))))
;; ------------
;; --- AXFR ---
;; ------------
;; AXFR is triggered by the zone-management-thread below:
;; TYPE: rr x string x message-class x dnsd-options -> boolean
(define (axfr-update soa-rr zone-name class dnsd-options dnsddb-options)
;; Search for the primary nameserver (msg) & get the soa-rr (msg2)
;; TYPE: string x string x message-class x dnsd-options -> soa-rr x ns-ip
(define (receive-soa-message ns-name name class dnsd-options dnsddb-options)
(let* ((ip? (dnsddb-options-master-ip dnsddb-options))
;; Lookup the IP or use dnsddb-options-master-ip
(nameserver
(if (and ip? (ip-string? ip?))
(ip-string->address32 ip?)
(let* ((msg (dnsd-ask-resolver-rec
(make-simple-query-message ns-name
(message-type a) class)
(network-protocol udp) dnsd-options))
(error-cond (flags-response-code
(header-flags
(message-header msg)))))
(if (eq? 'dns-no-error error-cond)
(resource-record-data-a-ip
(resource-record-data
(car (message-answers msg))))
(begin
(dnsd-log (syslog-level debug)
"AXFR: Error (~S) during rec.-lookup for the address of the primary NS for zone ~S."
error-cond
name)
#f))))))
(if nameserver
(let* ((msg2 (dnsd-ask-resolver-direct
(make-simple-query-message name (message-type soa)
class)
(list nameserver) (network-protocol udp) dnsd-options))
(error-cond (flags-response-code
(header-flags (message-header msg2)))))
(if (eq? 'dns-no-error error-cond)
(values (car (message-answers msg2)) nameserver)
(begin
(dnsd-log (syslog-level debug)
"AXFR: Error (~S) during rec.-lookup for the SOA-record of the primary NS for zone ~S."
error-cond
name)
(values #f #f))))
(values #f #f))))
;; Try to receive an zone with an AXFR-request:
(define (receive-axfr-message name class nameserver dnsd-options)
(let* ((msg (dnsd-ask-resolver-direct
(make-simple-query-message name (message-type axfr) class)
nameserver (network-protocol tcp) dnsd-options))
(error-cond (flags-response-code (header-flags
(message-header msg)))))
(if (eq? error-cond 'dns-no-error)
(message-answers msg)
(begin
(dnsd-log (syslog-level debug)
"AXFR: Error (~S) during AXFR-request for zone ~S"
error-cond
name)
#f))))
(let* ((soa-data (resource-record-data soa-rr))
(zone-mname (resource-record-data-soa-mname soa-data))
(zone-serial (resource-record-data-soa-serial soa-data)))
(dnsd-log (syslog-level info)
"AXFR: Starting AXFR-Update for zone ~S"
(resource-record-name soa-rr))
(receive
(new-soa nameserver)
(receive-soa-message zone-mname zone-name class dnsd-options dnsddb-options)
(if (not new-soa)
#f
;; Compare the serials of the local and remote soa-rrs to decide
;; if an update is neccessary.
(if (< zone-serial (resource-record-data-soa-serial
(resource-record-data new-soa)))
;; Try an (AXFR)-Update...
(let ((axfr-zone (receive-axfr-message zone-name class
(list nameserver)
dnsd-options)))
(if axfr-zone
(begin
(let ((first (resource-record-data (car axfr-zone)))
(last (resource-record-data
(list-ref axfr-zone
(- (length axfr-zone) 1)))))
(if (and (resource-record-data-soa? first)
(resource-record-data-soa? last))
(begin
(dnsd-log (syslog-level info)
"AXFR: Received AXFR-Reply for zone ~S. Starting database-update."
zone-name)
(db-update-zone (cdr axfr-zone)))
#f)))
#f))
#t)))))
;; ---------------------------------------------
;; --- Query-lookup in database and/or cache ---
;; ---------------------------------------------
;; Currently supported types:
;; TYPE: message-type -> boolean
(define (dnsd-supported-type? type)
(not (null? (filter (lambda (e) (eq? type e))
(list (message-type a)
(message-type ns)
(message-type cname)
(message-type soa)
(message-type ptr)
(message-type hinfo)
(message-type mx)
(message-type txt)
(message-type axfr)
(message-type mailb); Mailbox-related rrs. Here: mx
(message-type *))))))
;; TODO: Find out how to handle a standard query with multiple questions?
;; Should that be allowed at all?
;; Main algorithm for incoming queries. Responsibilities:
;; - decides if the query-type is implemented
;; - decides if and when to use cache/db-lookup/recursive lookup
;; TYPE: message x dnsd-options -> message
(define (lookup-query query dnsd-options)
(let ((query-flags (header-flags (message-header query))))
;; What OPCODE do we have here?
(cond
;; * [1] standard query (the only supported so far)
((= 0 (flags-opcode query-flags))
(let* ((question (car (message-questions query)))
(qname (question-name question))
(qclass (question-class question))
(qtype (question-type question)))
;; What kind of QTYPE do we have?
(cond
;; AXFR (252): A zone transfer... .
((and (eq? (message-type axfr) qtype)
(dnsd-options-use-axfr? dnsd-options))
(let ((zone (db-get-zone-for-axfr qname qclass)))
;; TODO: Is it okay to send the whole zone?
;; Maybe there should be checked who is asking?
(make-response query (list zone '() '() #t) dnsd-options)))
;; Supported QTYPES:
((dnsd-supported-type? qtype)
;; Try to get a database reply
(let ((res-l (if (dnsd-options-use-db? dnsd-options)
(receive
(anli auli adli aufl)
(db-lookup-rec qname qclass qtype)
(list anli auli adli aufl))
(list '() '() '() #f))))
;; Use recursion for local-result: '(() () () #f)
(if (and (dnsd-options-use-recursion? dnsd-options)
(no-zone? res-l)
(flags-recursion-desired? query-flags))
(dnsd-ask-resolver-rec query (network-protocol udp) dnsd-options)
(make-response query res-l dnsd-options))))
;; Unsupported QTYPEs:
(else (msg-set-rcode! query 4) query))))
;; This kind of queries are not implemented:
;; * [2] inverse query (not really used anymore (see RFC 3425))
;; * [3] server status request (marked experimental in RFC 1035)
;; * [4-15] reserved for future use (RFC 1035)
(else (msg-set-rcode! query 4) query))))
;; --------------
;; --- Server ---
;; --------------
;; Management of a zone:
;; ---------------------
;; Management consists of periodically checking the local files for
;; new information for primary-zones and to trigger AXFR-Updates for secondary
;; zones.
;; TYPE channel x channel x dnsd-options x dnsddb-options -> new-thread
(define (dnsd-zone-mgt-thread ch-usr1 ch-usr2 dnsd-options dnsddb-options)
(define (wait-thread zone-refresh ch-wakeup dnsd-options)
(fork-thread
(lambda ()
(let ((refresh (* zone-refresh 1000)))
(if (< refresh (dnsd-options-retry-interval dnsd-options))
(sleep (dnsd-options-retry-interval dnsd-options))
(sleep refresh))
(sync (send-rv ch-wakeup #t))))))
(let* ((dnsd-options dnsd-options)
(ch-wakeup (make-channel))
(zone-name (dnsddb-options-name dnsddb-options))
(type (dnsddb-options-type dnsddb-options))
(primary? (or (string-ci=? type "master")
(string-ci=? type "primary")))
(class (dnsddb-options-class dnsddb-options)))
(fork-thread
(lambda ()
(let refresh-loop ()
(let* ((soa-data (resource-record-data
(db-get-zone-soa-rr zone-name class)))
(zone-refresh (resource-record-data-soa-refresh soa-data))
(retry-val (resource-record-data-soa-retry soa-data))
(expire-val (resource-record-data-soa-expire soa-data)))
;; Start thread for wakeup-channel:
(wait-thread zone-refresh ch-wakeup dnsd-options)
(let inner-loop ()
(sync
(choose
;; Set new dnsd-options:
(wrap (receive-rv ch-usr1)
(lambda (new-dnsd-options)
(set! dnsd-options new-dnsd-options)
(inner-loop)))
;; Terminate the thread if a reload is signaled:
(wrap (receive-rv ch-usr2)
(lambda (ignore) #t))
;; Try a refresh:
(wrap (receive-rv ch-wakeup)
(lambda (ignore)
(dnsd-log (syslog-level info)
"Reloading zone ~S"
zone-name)
;; Primary or secondary zone?
(if (if primary?
(not
(dnsd-reload-zone dnsd-options dnsddb-options))
(axfr-update (db-get-zone-soa-rr zone-name class)
zone-name class dnsd-options
dnsddb-options))
;; Case the refresh didn't work:
(if (< expire-val 0)
(begin
(dnsd-log (syslog-level info)
"Zone ~S expired. Deleting from db!"
zone-name)
(db-clear-zone zone-name class)
(inner-loop)) ;; Wait for termination...
(begin
(set! expire-val (- expire-val retry-val))
(wait-thread retry-val ch-wakeup dnsd-options)
(set! retry-val (* 2 retry-val))
(inner-loop)))
(refresh-loop)))))))))))))
;; Reload options from dnsd-options.scm:
;; -------------------------------------
;; If an error occures (malformed file etc.) the old options are used as the
;; return value.
;; TYPE: dnsd-options -> dnsd-options
(define (dnsd-reload-options dnsd-options)
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level info)
"Error while reloading dnsd-options.scm")
;(dnsd-log (syslog-level debug)"Above condition is: ~A" condition)
dnsd-options)
(lambda ()
(let ((path (dnsd-options-dir dnsd-options)))
(dnsd-log (syslog-level info)
"Reloading dnsd-options.scm with path: ~S"
path)
(let* ((port (if (file-name-directory? path)
(open-input-file (string-append path "dnsd-options.scm"))
(begin
(dnsd-log (syslog-level info)
"Bad path (~S) in dnsd-options. Trying ./dnsd-options.scm"
path)
(open-input-file "./dnsd-options.scm"))))
(options? (read port)))
(close-input-port port)
(make-options-from-list options? dnsd-options))))))
;; (Re)load zones from dnsd-zones.scm:
;; -----------------------------------
;; Make a fake secondary zone for the management thread:
;; TYPE: dnsddb-options -> list-of-rrs
(define (make-sec-zone dnsddb-options)
(list
(dns-rr-soa (dnsddb-options-name dnsddb-options)
(message-class in)
0
(list
(dnsddb-options-master-name dnsddb-options)
"unknown.mail-adress."
0 ;; smallest serial possible
5 ;; fast first fetch
(* 60 10) ;; fast retry
(* 60 60 24 7) ;; expires
0)))) ;; min TTL
;; Reload a zone...
;; TYPE: zone x string x dnsd-options -> boolean
(define (dnsd-reload-zone dnsd-options dnsddb-options)
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level info)
"Error while reloading a zone.")
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
#f)
(lambda ()
(let* ((path (dnsd-options-dir dnsd-options))
(file (dnsddb-options-file dnsddb-options))
(zone-name (dnsddb-options-name dnsddb-options)))
;; Handle secondary zones...
(if (dnsddb-options-master-name dnsddb-options)
(db-update-zone (make-sec-zone dnsddb-options))
;; handle primary zones
(and-let* ((zone-list (if (string-ci=?
(dnsddb-options-filetype dnsddb-options)
"rfc")
(parse-mf file dnsd-options)
(load (string-append path file))))
(soa-zone-name (maybe-get-soa-rr-name zone-list)))
(if (string-ci=? zone-name soa-zone-name)
(db-update-zone zone-list)
(begin
(dnsd-log (syslog-level info)
"Zone names doesn't fit between file (%S) and dnsd-zones (%S)"
soa-zone-name zone-name)
(error " ")))))))))
;; Initialize // reload the zones which are defined in dnsd-zones.scm
;; TYPE: channel x channel x dnsd-options -> unspecific
(define (dnsd-reload-dnsd-zones ch-usr1 ch-usr2 dnsd-options)
(let ((usr1-channel-list '())
(usr2-channel-list '())
(dnsd-options dnsd-options))
(fork-thread
(lambda ()
(let loop ()
(sync
(choose
(wrap (receive-rv ch-usr1)
(lambda (new-dnsd-options)
(set! dnsd-options new-dnsd-options)
(for-each (lambda (e) (sync (send-rv e new-dnsd-options)))
usr1-channel-list)
(loop)))
(wrap
(receive-rv ch-usr2)
(lambda (ignore)
;; Terminate all old management-threads:
(for-each (lambda (e) (sync (send-rv e 'terminate)))
usr2-channel-list)
(set! usr1-channel-list '())
(set! usr2-channel-list '())
;; Clear database:
(db-clear-database)
(if (dnsd-options-use-db? dnsd-options)
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level info)
"Error while reloading dnsd-zones.scm")
#f)
(lambda ()
(let* ((path (dnsd-options-dir dnsd-options))
(port (if (file-name-directory? path)
(open-input-file
(string-append path "dnsd-zones.scm"))
(begin
(dnsd-log (syslog-level info)
"Bad path (~S) in dnsd-zones. Trying ./dnsd-zones.scm"
path)
(open-input-file "./dnsd-zones.scm"))))
(zone-l (read port)))
(close-input-port port)
(if (list? zone-l)
(for-each
(lambda (e)
(let ((dnsddb-options (make-db-options-from-list e))
(ch-usr1-thread (make-channel))
(ch-usr2-thread (make-channel)))
(if (dnsd-reload-zone dnsd-options dnsddb-options)
(begin
(dnsd-zone-mgt-thread ch-usr1-thread
ch-usr2-thread
dnsd-options
dnsddb-options)
(set! usr1-channel-list
(cons ch-usr1-thread
usr1-channel-list))
(set! usr2-channel-list
(cons ch-usr2-thread
usr2-channel-list))))))
zone-l)
(begin
(dnsd-log (syslog-level info)
"Bad sytax in dnsd-zones.scm.")
#f)))))
#f)
(loop))))))))))
;; Management of the datastructures (Cache / SLIST / Blacklist)
;; ------------------------------------------------------------
;; Clean dnsd-cache/slist every now and then.
;; TYPE: channel x dnsd-options -> unspecific
(define (dnsd-management-thread ch-usr1 dnsd-options)
(fork-thread
(lambda ()
(let ((ch-wait (make-channel))
(dnsd-options dnsd-options))
(let loop ()
(let ((time-in-sec (dnsd-options-cleanup-interval dnsd-options)))
;; Starting this thread to wait on ch-wait:
(fork-thread
(lambda ()
(sleep (* time-in-sec 1000))
(sync (send-rv ch-wait 'whatever))))
(sync
(choose
(wrap (receive-rv ch-wait)
(lambda (ignore)
(if (dnsd-options-use-cache? dnsd-options)
(dnsd-cache-clean!))
(dnsd-slist-clean!)
;; deprecated (dnsd-blacklist-clean! dnsd-options)
(dnsd-log (syslog-level info)
"Cleaned CACHE and SLIST. Current interval is ~D seconds."
time-in-sec)
#t))
(wrap (receive-rv ch-usr1)
(lambda (value) (set! dnsd-options value)))))
(loop)))))))
;; Pre- and post-processing of messages:
;; -------------------------------------
(define (dnsd-pre message socket-addr dnsd-options)
(dnsd-pre/post message socket-addr dnsd-options "dnsd-pre.scm"))
(define (dnsd-post message socket-addr dnsd-options)
(dnsd-pre/post message socket-addr dnsd-options "dnsd-post.scm"))
;; Load the pre- and post-processing files...
;; TYPE: msg x socket-addr x dnsd-options x string -> msg x dnsd-options
(define (dnsd-pre/post message socket-addr dnsd-options file)
(if (dnsd-options-use-pre/post dnsd-options)
(with-fatal-error-handler*
(lambda (condition decline)
(values message dnsd-options))
(lambda ()
(let* ((dir (dnsd-options-dir dnsd-options))
(path (if (file-name-directory? dir)
(string-append dir file)
(begin
(dnsd-log (syslog-level info)
"Bad dir (~S) in options. Trying ./~S"
dir file)
(string-append "./" file)))))
((load path) message socket-addr dnsd-options))))
(values message dnsd-options)))
;; UDP thread:
;; -----------
;; Starts the main UDP-loop:
;; TYPE: socket x channel x dnsd-options -> unspecific
(define (dnsd-server-loop-udp socket ch-usr1 dnsd-options)
(let ((ch-receive (make-channel))
(max-con (make-semaphore (dnsd-options-max-connections dnsd-options)))
(dnsd-options dnsd-options))
;; Thread for incoming UDP-messages:
(fork-thread
(lambda ()
(let loop ()
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level info)
"Error while processing a UDP-query.")
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
;(loop))
decline)
(lambda ()
(semaphore-wait max-con)
(receive
(msg addr)
(receive-message/partial socket 512)
(sync (send-rv ch-receive (cons msg addr)))
(loop)))))))
;; Choose between user-interrupt or query-processing
(fork-thread
(lambda ()
(let loop ()
(sync
(choose
(wrap (receive-rv ch-receive)
(lambda (value)
(udp-processing-thread (car value) (cdr value)
socket max-con dnsd-options)))
(wrap (receive-rv ch-usr1)
(lambda (value)
(set! dnsd-options value)
(set-semaphore! max-con (dnsd-options-max-connections
dnsd-options))))))
(loop))))))
;; Start the thread for processing a UDP-query.
;; TYPE: message x address x socket x dnsd-options -> unspecific
(define (udp-processing-thread msg addr socket max-con dnsd-options)
(fork-thread
(lambda ()
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level info)
"Error while processing a UDP-query.")
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
(semaphore-post max-con)
;#f)
decline)
(lambda ()
(let ((msg (parse (string->list msg))))
(if (not msg)(error "Couldn't parse the message."))
;; Preprocess the message...
(receive
(msg dnsd-options)
(dnsd-pre msg addr dnsd-options)
(if (not msg) (semaphore-post max-con)
(let* ((msg-header (message-header msg))
(msg-flags (header-flags msg-header))
(msg-trunc? (flags-truncated? msg-flags)))
(if msg-trunc? (error "Couldn't process truncated query."))
(let ((reply (lookup-query msg dnsd-options)))
(if (not reply) (error "Lookup produced no reply."))
;; Postprocessing the message:
(receive
(reply dnsd-options)
(dnsd-post reply addr dnsd-options)
(if (not reply) (semaphore-post max-con)
(let* ((octet-list (mc-message->octets reply))
(l (length octet-list)))
(if (> l 512) ; Use message-truncation?
(let* ((msg (octet-msg-change-truncation
octet-list #t))
(to-send (list->string (take msg 512))))
(receive
(host-addr port)
(socket-address->internet-address addr)
(dnsd-log (syslog-level info)
"Sending truncated UDP-response to: ~A"
(address32->ip-string host-addr))
(send-message socket to-send 0 511 0 addr)))
(begin
(send-message socket (list->string octet-list)
0 l 0
addr)))
(semaphore-post max-con))))))))))))))
;; TCP thread:
;; -----------
;; Main TCP-loop:
;; TYPE: socket x channel x dnsd-options -> unspecific
(define (dnsd-server-loop-tcp socket ch-usr1 dnsd-options)
(let ((ch-receive (make-channel))
(max-con (make-semaphore (dnsd-options-max-connections dnsd-options)))
(dnsd-options dnsd-options))
;; Thread for incoming TCP-messages:
(fork-thread
(lambda ()
(let loop ()
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level info)
"Error while processing a TCP-query.")
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
(loop))
;decline)
(lambda ()
(semaphore-wait max-con)
(receive
(private-socket addr)
(accept-connection socket)
(sync (send-rv ch-receive (cons private-socket addr)))
(loop)))))))
;; Choose between user-interrupt or query-processing
(fork-thread
(lambda ()
(let loop ()
(sync
(choose
(wrap (receive-rv ch-receive)
(lambda (value)
(tcp-processing-thread (car value) (cdr value)
max-con dnsd-options)))
(wrap (receive-rv ch-usr1)
(lambda (value)
(set! dnsd-options value)
(set-semaphore! max-con (dnsd-options-max-connections
dnsd-options))))))
(loop))))))
;; Start the thread for processing a TCP-query:
;; TYPE: address x socket x dnsd-options -> unspecific
(define (tcp-processing-thread socket addr max-con dnsd-options)
(fork-thread
(lambda ()
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level info)
"Error while processing a TCP-query.")
;(dnsd-log (syslog-level debug) "Above condition is: ~A" condition)
(semaphore-post max-con)
(close-socket socket) #f)
(lambda ()
(let* ((inport (socket:inport socket))
(outport (socket:outport socket))
;; A tcp-message has a 2-octet-length size tag:
(front (read-char inport))
(rear (read-char inport))
(size-tag (octet-pair->number front rear))
(octet-msg (read-string size-tag inport))
(msg (parse (string->list octet-msg))))
(if (not msg)(error "Couldn't parse the message"))
;; Preprocessing:
(receive
(msg dnsd-options)
(dnsd-pre msg addr dnsd-options)
(if (not msg)
(begin
(semaphore-post max-con)
(close-socket socket))
(let* ((msg-header (message-header msg))
(msg-flags (header-flags msg-header))
(msg-trunc? (flags-truncated? msg-flags)))
(if msg-trunc? (error "Couldn't process truncated query."))
(let ((reply (lookup-query msg dnsd-options)))
(if (not reply) (error "Lookup produced no reply."))
;; Postprocessing:
(receive
(reply dnsd-options)
(dnsd-post reply addr dnsd-options)
(if (not reply)
(begin
(semaphore-post max-con)
(close-socket socket))
(let* ((reply (mc-message->octets reply))
(l (number->octet-pair (length reply))))
(write-string (list->string (append l reply)) outport)
(semaphore-post max-con)
(close-socket socket))))))))))))))
;; Initialize and start UDP and TCP threads:
;; TYPE: dnsd-options -> unspecific
(define (init-dnsd dnsd-options)
(let ((ch-usr1-udp (make-channel))
(ch-usr1-tcp (make-channel))
(ch-usr1-mgt (make-channel))
(ch-usr1-zones (make-channel))
(ch-usr2-zones (make-channel))
(dnsd-options dnsd-options))
(call-with-current-continuation
(lambda (escape)
;; Maybe load the options from file:
(set! dnsd-options (dnsd-reload-options dnsd-options))
;; Initializing signal-handler(s)
;; * USR1 (reload dnsd-options.scm)
;; Log debug-level in syslog?
(with-syslog-destination
(string-append "dnsd (" (number->string (pid)) ")")
#f
#f
(if (dnsd-options-debug-mode dnsd-options)
(syslog-mask-upto (syslog-level info))
#f)
(lambda ()
(set-interrupt-handler
interrupt/usr1
(lambda (ignore)
(dnsd-log (syslog-level info)
"Interrupt/USR1: Reloading options.")
(set! dnsd-options (dnsd-reload-options dnsd-options))
(fork-thread
(lambda () (sync (send-rv ch-usr1-udp dnsd-options))))
(fork-thread
(lambda () (sync (send-rv ch-usr1-tcp dnsd-options))))
(fork-thread
(lambda () (sync (send-rv ch-usr1-mgt dnsd-options))))
(fork-thread
(lambda () (sync (send-rv ch-usr1-zones dnsd-options))))))
;; * USR2 (reload dnsd-zones.scm)
(set-interrupt-handler
interrupt/usr2
(lambda (ignore)
(dnsd-log (syslog-level info)
"Interrupt/USR2: Reloading zones.")
(sync (send-rv ch-usr2-zones 'ignore))))
;; Initializing cleanup thread:
(dnsd-management-thread ch-usr1-mgt dnsd-options)
;; Initialize & load the database:
(dnsd-reload-dnsd-zones ch-usr1-zones ch-usr2-zones dnsd-options)
(sync (send-rv ch-usr2-zones 'ignore))
;; Initializing tcp/upd sockets & start thread:
(let* ((the-port (dnsd-options-port dnsd-options))
(udp-socket (create-socket protocol-family/internet
socket-type/datagram))
(tcp-socket (create-socket protocol-family/internet
socket-type/stream))
(socket-addr (internet-address->socket-address
internet-address/any the-port)))
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level info)
"Coudn't start dnsd. Port ~D is already in use."
the-port)
(close-socket udp-socket)
(close-socket tcp-socket)
(escape 'douh!))
(lambda ()
(dnsd-log (syslog-level info)
"Starting the service on port: ~D"
the-port)
(bind-socket udp-socket socket-addr)
(bind-socket tcp-socket socket-addr)
(listen-socket tcp-socket 10))) ; TODO: How big should the queue be?
;; Start the UDP-Loop:
(fork-thread (lambda () (dnsd-server-loop-udp udp-socket ch-usr1-udp
dnsd-options)))
;; Start the TCP-Loop:
(fork-thread (lambda () (dnsd-server-loop-tcp tcp-socket ch-usr1-tcp
dnsd-options))))))))))
;; Entry-Point for run-dnsd
;; ------------------------
(define (dnsd-start . dir)
(with-syslog-destination
(string-append "dnsd (" (number->string (pid)) ")") #f #f #f
(lambda ()
(if (null? dir)
(init-dnsd (make-default-dnsd-options))
(init-dnsd (with-dir
(file-name-as-directory (car dir))
(make-default-dnsd-options)))))))

View File

@ -1,103 +0,0 @@
;; Option-File for DNSD:
;; ---------------------
;; Options can be reloaded using the POSIX-Signal USR1.
;; External option representation <datum>:
;; ---------------------------------------
;; (options
;; [dir string]
;; [nameservers list-of-ip-strings]
;; [use-axfr boolean]
;; [use-cache boolean]
;; [cleanup-interval time-in-sec]
;; [retry-interval time-in-sec]
;; [use-db boolean]
;; [use-recursion boolean]
;; [rec-timeout time-in-s]
;; [socket-timeout time-in-s]
;; [socket-max-tries integer]
;; [max-connections integer]
;; [blacklist-time time-in-s]
;; [blacklist-value integer]
;; [use-pre/post boolean])
;; [...] indicates an optional list.
;; Semantic:
;; ---------
;; (dir string)
;; Path to the directory with this configuration files.
;; Standard value is "." - the dir where dnsd was started or the
;; directory which was passed to (dnsd-start <optional-dir>)
;; (nameservers list-of-ip-strings)
;; A list of nameserver-IPs used for recursive lookups.
;; Standard value is a list of root-nameservers.
;; (use-axfr boolean)
;; Toggles to answer to axfr-requests. Default value is #t.
;; (use-cache boolean)
;; Toggles caching of responses. Default value is #t.
;; (cleanup-interval time-in-sec)
;; Clean the cache and slist after X seconds. Default value is 1h.
;; (retry-interval time-in-sec)
;; Minimum value in seconds to trigger zone-reloads. This can override
;; the value of some masterfiles. Default value is 1h.
;; (use-db boolean boolean)
;; Toggle the usage of the local database. Default value is on - #t.
;; (use-recursion boolean)
;; Switch the recursive-lookup on/off. Default value is on - #t.
;; (rec-timeout time-in-sec)
;; Global timeout for a recursive lookup. Default is 10 seconds.
;; (socket-timeout time-in-sec)
;; Timeout for one lookup during a recursive lookup. Default is 2 seconds.
;; (socket-max-tries integer)
;; Maximum nuber of tries to establish a connection for recursive lookups.
;; Default value is 3.
;; (max-connection integer)
;; Maximum concurrent connections for each UDP and TCP. Default is 25.
;; (blacklist-time time-in-sec)
;; How long will a bad NS be blacklisted/not used? Default is 30 min.
;; (blacklist-value integer)
;; How often, before a bad NS will be ignored? Default is 5 times.
;; (use-pre/post boolean)
;; Toggles load of pre- and post-processing files. Default is off - #f.
;; all args are optional. If not given, the def. value will be used.
;; Some examples:
;; --------------
;;
;; (options (nameservers ("192.168.2.1" "192.168.2.2"))
;; (use-axfr #t)
;; (use-cache #t)
;; (cleanup-interval 666)
;; (use-recursion #t)
;; (use-db #f)
;; (use-pre/post #f))
;;
;; (options) == use the default values.
;;
;; OPTION-DEFINITIONS:
(options)

View File

@ -1,3 +0,0 @@
(lambda (msg socket-addr dnsd-options)
(display "Postprocessing works.")
(values msg dnsd-options))

View File

@ -1,3 +0,0 @@
(lambda (msg socket-addr dnsd-options)
(display "Preprocessing works.")
(values msg dnsd-options))

View File

@ -1,80 +0,0 @@
;; Zones-File for DNSD:
;; --------------------
;; The local zones of the NS can be reloaded using the
;; POSIX signal USR2.
;; External zones representation <datum>:
;; --------------------------------------
;; zone-file ::= list-of-zone-lists
;; list-of-zone ::= primary-zone | secondary-zone
;; primary-zone ::= (zone (name string)
;; (type "master" or "primary")
;; (file string)
;; [filetype string])
;; secondary-zone ::= (zone (name string)
;; (type "slave" or "secondary")
;; (master-name string)
;; [master-ip ip-string])
;; [...] is an optional list.
;; Semantic:
;; ---------
;; list-of-zone-lists
;; A list containing all zones of the NS.
;; list-of-zone
;; A list containing the options for one zone of the NS.
;; (name string)
;; The fully-qualified-domain-name of the zone.
;; (type "master" or "slave")
;; The type of the zone. One of the two strings: "master" or "slave".
;; Alternatively, it can be "primary" or "secondary".
;; (file string)
;; The filename of the masterfile.
;; (filetype string)
;; One of the two strings "dnsd" or "rfc". Default is "dnsd".
;; (master-name string)
;; The domain-name of the master-nameserver.
;; (master-ip ip-string)
;; The IP of the master-nameserver. If non given, DNSD will try to
;; lookup the IP.
;; Examples:
;; --------
;; () == No zones given. Use dnsd as a resolver only.
;;
;; Try the examples and be a secondary NS for the domain "porsche.de"
;;
;;((zone (name "my.example.")
;; (type "master")
;; (file "zone-example-scheme"))
;; (zone (name "example.com.")
;; (type "master")
;; (file "zone-example-rfc")
;; (filetype "rfc")))
;; (zone (name "porsche.de.")
;; (type "slave")
;; (master-name "dns01.fw.porsche.de."))
;; DEFINE HERE:
()

View File

@ -1,30 +0,0 @@
$ORIGIN example.com.
$TTL 2D
example.com. IN SOA gateway root.example.com. (
2003072441 ; serial
1D ; refresh
2H ; retry
1W ; expiry
2D ) ; minimum
IN NS gateway
IN MX 10 sun
gateway IN A 192.168.0.1
IN A 192.168.1.1
sun IN A 192.168.0.2
moon IN A 192.168.0.3
earth IN A 192.168.1.2
mars IN A 192.168.1.3
www IN CNAME venus
; A cname-loop...
venus IN CNAME saturn
saturn IN CNAME venus
; Glue Data
nofreude IN NS ns1.nofreude
ns1.nofreude IN A 192.168.2.66

View File

@ -1,19 +0,0 @@
; Zone-example using the functions from dnsd/rr-def.scm and lib/dns.scm
; ---------------------------------------------------------------------
(let ((mc (message-class in))
(ttl (* 60 60 24)))
(list
(dns-rr-soa "my.example." mc ttl
(list "nameserver.my.example." "webmaster.my.example"
20051203 7200 600 300000 1111))
(dns-rr-a "my.example." mc ttl "192.168.2.1")
(dns-rr-ns "my.example." mc ttl "nameserver.my.example.")
(dns-rr-a "on.my.example." mc ttl "192.168.2.2")
(dns-rr-a "*.my.example." mc ttl "192.168.2.3")
(dns-rr-mx "my.example" mc ttl (list 11 "mx.my.example"))
(dns-rr-cname "cname.my.example" mc ttl "my.example")
(dns-rr-a "mx.my.example" mc ttl "192.168.2.4")
(dns-rr-ns "ns.my.example" mc ttl "ns.test.")
(dns-rr-ns "more.my.example" mc ttl "ns2.my.example")
(dns-rr-a "ns2.my.example" mc ttl "192.168.2.11")))

View File

@ -1,34 +0,0 @@
; ------------------------
; --- Syslog-Interface ---
; ------------------------
; Syslog/Debug-Stuff for dnsd.
; This file is (maybe) part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
(define *debug-info* #f) ; switch debug-information on/off
;; TODO: log-file instead of display-information:
;; Show some debug-information
(define display-debug
(lambda args
(if *debug-info*
(begin
(display "dnsd: ")
(map (lambda (e) (display e) (display " ")) args)
(newline))
#f)))
(define (apply-w/debug proc . args)
(if *debug-info* (apply proc args)))
(define (dnsd-log log-level msg . args)
(syslog log-level (apply format #f msg args)))

View File

@ -1,369 +0,0 @@
; -------------------------
; --- Masterfile-Parser ---
; -------------------------
; Parser for Masterfiles based on the RFCs: 1034 / 1035 / 2308 and
; the BIND-Time-Value-Format convention.
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; Interface:
; ----------
; (parse-mf fileaname dnsd-options) -> list-of-resource-records
;; Lexer:
;; ------
;; The lexer was generated using SILex v1.0 by Danny Dubé with
;; specification file "masterfile.l"
;; For more information about SILex visit: http://www.iro.umontreal.ca/~dube/
;; TYPE: filename x dnsd-options -> list-of-lexems or #f
(define (lex-masterfile file dnsd-options)
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level info)
"Error while parsing the file ~S"
file)
(dnsd-log (syslog-level debug)
"Above condition is: ~A"
condition)
#f)
(lambda ()
(and-let* ((the-path (string-append (dnsd-options-dir dnsd-options) file))
(whatever (file-name-non-directory? the-path))
(the-port (open-input-file the-path)))
(lexer-init 'port the-port)
(let loop ((l '()))
(let ((lexem (lexer)))
(if (eq? lexem 'eof)
(begin
(close-input-port the-port)
(reverse (cons lexem l)))
(loop (cons lexem l)))))))))
;; Parser:
;; -------
;; Maybe append a domain-origin to a string:
;; TYPE: dn-label-string x fqdn-string -> fqdn-string
(define (parse-mf-maybe-append-origin name origin)
(let ((l (string-length name)))
(if (and (not (= 0 l)) (not (char=? #\. (string-ref name (- l 1)))))
(if (string=? origin ".")
(string-append name origin)
(string-append name "." origin))
name)))
;; Parse (or restore) the name of the current line:
;; TYPE: dn-label-string or symbol x fqdn-string x dn-label-string ->
;; fqdn x dn-label-string
(define (parse-mf-node-name? elem origin last-name)
(cond
((eq? elem 'origin-ref) (values origin origin)) ; @ in the masterfile
((eq? elem 'blank) ; no name given - use last one
(values (parse-mf-maybe-append-origin last-name origin) last-name))
(else (values (parse-mf-maybe-append-origin elem origin) elem))))
;; Parse the type of a rr-line:
;; TYPE: string -> message-type
(define (parse-mf-type? elem)
(message-type-symbol->type (string->symbol (string-downcase elem))))
;; Parse the class of a rr-line:
;; TYPE: string -> message-class
(define (parse-mf-class? elem)
(message-class-symbol->type (string->symbol (string-downcase elem))))
;; Parse a RFC-time value or a BIND-Masterfiles value: #w#d#h#m#s
;; eg. 1 Week = 1w or 1d20s = 1 day and 20 seconds
;; This algorithm is very liberal - a possible value would be 12s1d1w1s
;; TYPE: string -> number
(define (parse-mf-time-value? elem)
(let loop ((str elem)
(counter 0)
(val 0))
(let ((l (string-length str)))
(if (= l 0)
val
(let ((sub (substring str counter (+ counter 1))))
(if (string->number sub)
(if (= counter (- l 1))
(string->number str) ; original RFC format
(loop str (+ counter 1) val))
(let ((val2 (string->number (substring str 0 counter)))
(rest-string (substring str (+ counter 1) l)))
(cond
((string-ci=? sub "w")
(loop rest-string 0 (+ val (* 7 24 60 60 val2))))
((string-ci=? sub "d")
(loop rest-string 0 (+ val (* 24 60 60 val2))))
((string-ci=? sub "h")
(loop rest-string 0 (+ val (* 60 60 val2))))
((string-ci=? sub "m")
(loop rest-string 0 (+ val (* 60 val2))))
((string-ci=? sub "s")
(loop rest-string 0 (+ val val2)))
(else
(display elem)
(error "Wrong time-value format"))))))))))
;; Parse a rr-line:
;; Syntax: {<domain>|@|<blank>} [<ttl>] [<class>] <type> <rdata>
;; The algorithm has to guess serveral times which value actually
;; is been parsed.
;; TYPE: rr-line-of-lexems x fqdn x dn-string x ttl-number
;; -> '(name ttl class type rdata origin) x fqdn x dn-string x ttl-number
(define (parse-mf-rr line origin current-rr-name the-ttl)
(receive
(rr-name current-rr-name)
(parse-mf-node-name? (car line) origin current-rr-name)
(let* ((sec (cadr line))
(type (parse-mf-type? sec)))
(if type ; Parsing the type?
(values (list rr-name the-ttl #f type (cddr line) origin)
origin current-rr-name the-ttl)
(let ((class (parse-mf-class? sec)))
(if class ; Parsing a class?
(let ((type (parse-mf-type? (caddr line))))
(values (list rr-name the-ttl class type (cdddr line) origin)
origin current-rr-name the-ttl))
(let ((ttl (parse-mf-time-value? sec)))
(if ttl ; Now it should be a TTL.
(let* ((third (caddr line))
(type (parse-mf-type? third)))
(if type
(values
(list rr-name ttl #f type (cdddr line) origin)
origin current-rr-name the-ttl)
(let ((type (parse-mf-type? (cadddr line))))
(values
(list
rr-name ttl (parse-mf-class? third) type
(cdr (cdddr line)) origin)
origin current-rr-name the-ttl))))
(begin
(display line)
(error "Parsed a bad line!"))))))))))
;; Parse a masterfile-line:
;;<line> ::= $ORIGIN <domain-name>
;; | $INCLUDE ...
;; | $TTL <number> (defined in RFC 2308)
;; | <resource-record>
;; TODO: | $GENERATE ... BIND-Version 9
;;
;; TYPE: mf-line x fqdn x dn-string x ttl-number x dnsd-options
;; -> symbol or list-of-a-rr x fqdn x dn-string x ttl-number
(define (parse-mf-line line origin current-rr-name ttl dnsd-options)
(let ((first (car line)))
(cond
;; $INCLUDE
((eq? first 'include)
(let* ((file-name (cadr line))
(maybe-origin (if (= (length line) 3) (caddr line) #f))
(lexed-file (lex-masterfile file-name dnsd-options))
(line-list (parse-mf-lex->lines lexed-file))
(res (parse-mf-lexem-list
line-list (if maybe-origin maybe-origin origin)
current-rr-name #f dnsd-options)))
(values res origin current-rr-name ttl)))
;; $ORIGIN
((eq? first 'origin)
(let ((new-origin (cadr line)))
(values 'ORIGIN
(parse-mf-maybe-append-origin new-origin origin)
current-rr-name ttl)))
;; $TTL <number>
((eq? first 'ttl)
(let ((new-ttl (cadr line)))
(values 'TTL origin current-rr-name (parse-mf-time-value? new-ttl))))
;; $GENERATE ...
((eq? first 'generate)
(error "parse-masterfile: GENERATE is not supported."))
; <resource-record>
(else (parse-mf-rr line origin current-rr-name ttl)))))
;; Transforms the lexer-output into a list of lines:
;; TYPE: list-of-lexems -> list-of-lexem-lists
(define (parse-mf-lex->lines lex-list)
(let loop ((l lex-list)
(line '())
(ignore-line #f) ; Toggle comments.
(res '()))
(let ((first (car l)))
(cond
((eq? first 'eof)
(if (null? line)
(reverse res)
(reverse (cons line res))))
((eq? first 'left-par) ; Ignore line-breaks.
(loop (cdr l) line #t res))
((eq? first 'right-par) ; Consider line-breaks.
(loop (cdr l) line #f res))
((eq? first 'newline)
(if (not ignore-line)
(if (null? line)
(loop (cdr l) '() ignore-line res)
(loop (cdr l) '() ignore-line (cons line res)))
(loop (cdr l) line ignore-line res)))
((eq? first 'blank-newline)
(if (not ignore-line)
(if (null? line)
(loop (cdr l) (list 'blank) ignore-line res)
(loop (cdr l) (list 'blank) ignore-line (cons line res)))
(loop (cdr l) line ignore-line res)))
(else
(loop (cdr l) (append line (list first)) ignore-line res))))))
;; Actually create a resourc-record from the parsed rr-line:
;; TYPE: '(name ttl class type rdata origin) -> resource-record-data
(define (parse-mf-create-rr line)
(let ((class (caddr line))
(type (cadddr line)))
(if (not (eq? (message-class in) class))
(begin
(display "Message-class not supported: ")
(display class)
(newline))
(let ((name (car line))
(ttl (cadr line))
(data (list-ref line 4))
(origin (list-ref line 5)))
(cond
((eq? type (message-type a))
(dns-rr-a name class ttl (car data)))
((eq? type (message-type ns))
(dns-rr-ns name class ttl
(parse-mf-maybe-append-origin (car data) origin)))
((eq? type (message-type cname))
(dns-rr-cname name class ttl
(parse-mf-maybe-append-origin (car data) origin)))
((eq? type (message-type soa))
(and-let* ((mname (parse-mf-maybe-append-origin (car data) origin))
(rname (parse-mf-maybe-append-origin (cadr data) origin))
(serial (string->number (caddr data)))
(refresh (parse-mf-time-value? (cadddr data)))
(retry (parse-mf-time-value? (list-ref data 4)))
(expire (parse-mf-time-value? (list-ref data 5)))
(minimum (parse-mf-time-value? (list-ref data 6))))
(dns-rr-soa name class ttl
(list mname rname serial
refresh retry expire minimum))))
((eq? type (message-type ptr))
(dns-rr-ptr name class ttl
(parse-mf-maybe-append-origin (car data) origin)))
((eq? type (message-type hinfo))
(dns-rr-hinfo name class ttl data))
((eq? type (message-type mx))
(let ((pref (string->number (car data)))
(exchange (parse-mf-maybe-append-origin (cadr data) origin)))
(dns-rr-mx name class ttl (list pref exchange))))
((eq? type (message-type txt))
(dns-rr-txt name class ttl data))
((eq? type (message-type aaaa))
(dns-rr-aaaa name class ttl (car data)))
(else #f))))))
;; Parse the list-of-lexems and return a list of resource-records:
;; TYPE: list-of-lexems x fqdn x dn-string x ttl-number x dnsd-options
;; -> list-of-resource-records
(define (parse-mf-lexem-list l origin current-rr-name ttl dnsd-options)
(let loop ((l l)
(res '())
(origin origin)
(current-rr-name current-rr-name)
(ttl ttl))
(if (null? l)
res
(receive (next-res origin current-rr-name ttl)
(parse-mf-line (car l) origin current-rr-name ttl
dnsd-options)
(cond
((or (eq? next-res 'ORIGIN)
(eq? next-res 'TTL))
(loop (cdr l) res origin current-rr-name ttl))
((and (list? next-res) ; result from INCLUDE...
(list? (car next-res)))
(loop (cdr l) (append next-res res) origin
current-rr-name ttl))
(else
(loop (cdr l) (cons next-res res) origin
current-rr-name ttl)))))))
;; Stuff for the main parser algorithm:
;; ------------------------------------
;; Searches the results of parse-mf-line for a message-class
(define (get-message-class rrlist)
(let loop ((res rrlist))
(if (null? res)
#f
(let ((class (caddr (car res))))
(if class class
(loop (cdr res)))))))
;; Set the results of parse-mf-line to a message-class...
(define (set-message-class rrlist class)
(map (lambda (e)
(cons (car e) (cons (cadr e) (cons class (cdddr e)))))
rrlist))
;; Searches the results of parse-mf-line for the shortest ttl
(define (get-soa-ttl rrlist)
(let loop ((l rrlist))
(if (null? l)
#f
(let* ((rrs (car l))
(rr-type (cadddr rrs)))
(if (eq? (message-type soa) rr-type)
(let* ((rdata (cadddr (cdr rrs))))
(parse-mf-time-value? (list-ref rdata 6)))
(loop (cdr l)))))))
;; Set the ttl of lines without one...
(define (set-ttl rrlist soa-ttl)
(map (lambda (e)
(let ((ttl (cadr e)))
(if (and ttl
(< soa-ttl ttl))
e
(cons (car e) (cons soa-ttl (cddr e))))))
rrlist))
;; The main parser algorithm:
;; --------------------------
;; Create a list of lexems and parse the lexems into resource-record-data:
;; TYPE: string x dnsd-options -> list-of-resourec-records
(define (parse-mf file dnsd-options)
(and-let* ((lex-list (lex-masterfile file dnsd-options))
(lines (parse-mf-lex->lines lex-list))
(res (parse-mf-lexem-list lines "." "" #f dnsd-options))
(class (get-message-class res))
(res (set-message-class res class))
(soa-ttl (get-soa-ttl res))
(res (set-ttl res soa-ttl))
(res (map (lambda (e) (parse-mf-create-rr e)) res)))
;; Check if there is a line with an error:
(fold-right (lambda (e l) (if (and e l) (cons e l) #f)) '() res)))

View File

@ -1,41 +0,0 @@
; --------------------
; --- masterfile.l ---
; --------------------
; A SIlex configuration file for masterfiles.
; For more information about SILex visit: http://www.iro.umontreal.ca/~dube/
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
dchars [^\n();@ ] ;; last two chars are space and tabulator
space [ ] ;; space and tabulator
%%
{space} (yycontinue)
\n 'newline
\n{space} 'blank-newline
\; (let loop ((c (yygetc)))
(cond
((eq? 'eof c) 'eof)
((char=? #\newline c)
(begin
(yyungetc)
(yycontinue)))
(else (loop (yygetc)))))
\( 'left-par
\) 'right-par
(\$)ORIGIN 'origin
(\$)INCLUDE 'include
(\$)GENERATE 'generate
(\$)TTL 'ttl
\@ 'origin-ref
{dchars}* yytext
<<EOF>> 'eof
<<ERROR>> (error (yygetc))

File diff suppressed because it is too large Load Diff

View File

@ -1,214 +0,0 @@
; ---------------------
; --- DNSD-Options ---
; ---------------------
; Options for DNS-Server based on the RFCs: 1034 / 1035
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; The format and style of the option procedures is the same as seen
; in the SUNet HTTPD & FTPD - Files
(define-record-type dnsd-options :dnsd-options
(really-make-dnsd-options
port dir nameservers use-axfr use-cache cleanup-interval retry-interval
use-db use-recursion rec-timeout socket-timeout socket-max-tries
max-connections blacklist-time blacklist-value use-pre/post debug-mode)
dnsd-options?
(port dnsd-options-port set-dnsd-options-port!)
(dir dnsd-options-dir set-dnsd-options-dir!)
(nameservers dnsd-options-nameservers set-dnsd-options-nameservers!)
(use-axfr dnsd-options-use-axfr? set-dnsd-options-use-axfr?!)
(use-cache dnsd-options-use-cache? set-dnsd-options-use-cache?!)
(cleanup-interval dnsd-options-cleanup-interval set-dnsd-options-cleanup-interval!)
(retry-interval dnsd-options-retry-interval set-dnsd-options-retry-interval!)
(use-db dnsd-options-use-db? set-dnsd-options-use-db?!)
(use-recursion dnsd-options-use-recursion? set-dnsd-options-use-recursion?!)
(rec-timeout dnsd-options-rec-timeout set-dnsd-options-rec-timeout!)
(socket-timeout dnsd-options-socket-timeout set-dnsd-options-socket-timeout!)
(socket-max-tries dnsd-options-socket-max-tries set-dnsd-options-socket-max-tries!)
(max-connections dnsd-options-max-connections set-dnsd-options-max-connections!)
(blacklist-time dnsd-options-blacklist-time set-dnsd-options-blacklist-time!)
(blacklist-value dnsd-options-blacklist-value set-dnsd-options-blacklist-value!)
(use-pre/post dnsd-options-use-pre/post set-dnsd-options-use-pre/post!)
(debug-mode dnsd-options-debug-mode set-dnsd-options-debug-mode!))
(define (make-default-dnsd-options)
(really-make-dnsd-options
53 ; Port to listen
"./" ; Path to the zone & option files.
'() ; Use the default SBELT-Servers
; Example-list: (list "192.168.2.1" "193.159.170.187" "192.36.148.17")
; or (dns-find-nameserver-list) ; SBELT-Nameserver(s) for recursion.
#t ; Toggles sending AXFR-responses
#t ; Toggles the use of the cache
(* 60 60) ; Cache garbage-collect interval in seconds
(* 60 60) ; Min. time-val (sec) to reload a zone
#t ; If #f don't use the db.
#t ; If #f don't use recursion.
10 ; Timeout (sec) for recursion.
2 ; Timeout (sec) for a query (resolver interface).
3 ; Max. tries on a socket (resolver interface).
25 ; Max. concurrent connections for UDP and TCP.
(* 60 30) ; How long will a blacklist entry be valid?
5 ; How often must a NS be bad to be ignored.
#f ; Don't use pre- and post-processing by default.
#f)) ; Print debug-options to syslog.
(define (copy-dnsd-options options)
(really-make-dnsd-options (dnsd-options-port options)
(dnsd-options-dir options)
(dnsd-options-nameservers options)
(dnsd-options-use-axfr? options)
(dnsd-options-use-cache? options)
(dnsd-options-cleanup-interval options)
(dnsd-options-retry-interval options)
(dnsd-options-use-db? options)
(dnsd-options-use-recursion? options)
(dnsd-options-rec-timeout options)
(dnsd-options-socket-timeout options)
(dnsd-options-socket-max-tries options)
(dnsd-options-max-connections options)
(dnsd-options-blacklist-time options)
(dnsd-options-blacklist-value options)
(dnsd-options-use-pre/post options)
(dnsd-options-debug-mode options)))
(define (make-dnsd-options-transformer set-option!)
(lambda (new-value . stuff)
(let ((new-options (if (not (null? stuff))
(copy-dnsd-options (car stuff))
(make-default-dnsd-options))))
(set-option! new-options new-value)
new-options)))
(define with-port
(make-dnsd-options-transformer set-dnsd-options-port!))
(define with-dir
(make-dnsd-options-transformer set-dnsd-options-dir!))
(define with-nameservers
(make-dnsd-options-transformer set-dnsd-options-nameservers!))
(define with-axfr
(make-dnsd-options-transformer set-dnsd-options-use-axfr?!))
(define with-cache
(make-dnsd-options-transformer set-dnsd-options-use-cache?!))
(define with-cleanup-interval
(make-dnsd-options-transformer set-dnsd-options-cleanup-interval!))
(define with-retry-interval
(make-dnsd-options-transformer set-dnsd-options-retry-interval!))
(define with-db
(make-dnsd-options-transformer set-dnsd-options-use-db?!))
(define with-recursion
(make-dnsd-options-transformer set-dnsd-options-use-recursion?!))
(define with-rec-timeout
(make-dnsd-options-transformer set-dnsd-options-rec-timeout!))
(define with-socket-timeout
(make-dnsd-options-transformer set-dnsd-options-socket-timeout!))
(define with-socket-max-tries
(make-dnsd-options-transformer set-dnsd-options-socket-max-tries!))
(define with-max-connections
(make-dnsd-options-transformer set-dnsd-options-max-connections!))
(define with-blacklist-time
(make-dnsd-options-transformer set-dnsd-options-blacklist-time!))
(define with-blacklist-value
(make-dnsd-options-transformer set-dnsd-options-blacklist-value!))
(define with-use-pre/post
(make-dnsd-options-transformer set-dnsd-options-use-pre/post!))
(define with-debug-mode
(make-dnsd-options-transformer set-dnsd-options-debug-mode!))
(define (make-dnsd-options . stuff)
(let loop ((options (make-default-dnsd-options))
(stuff stuff))
(if (null? stuff)
options
(let* ((transformer (car stuff))
(value (cadr stuff)))
(loop (transformer value options)
(cddr stuff))))))
(define (make-options-from-list o-list options)
(if (eq? (car o-list) 'options)
(begin
(for-each
(lambda (e)
(let ((id (car e))
(value (cadr e)))
(case id
((dir)
(if (string? value)
(set-dnsd-options-dir! options value)
(error "Bad option argument.")))
((nameservers)
(if (list? value)
(set-dnsd-options-nameservers! options value)
(error "Bad option argument.")))
((use-axfr)
(if (boolean? value)
(set-dnsd-options-use-axfr?! options value)
(error "Bad option argument.")))
((use-cache)
(if (boolean? value)
(set-dnsd-options-use-cache?! options value)
(error "Bad option argument.")))
((cleanup-interval)
(if (and (number? value) (<= 10 value))
(set-dnsd-options-cleanup-interval! options value)
(error "Bad option argument.")))
((retry-interval)
(if (and (number? value) (<= 10 value))
(set-dnsd-options-retry-interval! options value)
(error "Bad option argument.")))
((use-db)
(if (boolean? value)
(set-dnsd-options-use-db?! options value)
(error "Bad option argument.")))
((use-recursion)
(if (boolean? value)
(set-dnsd-options-use-recursion?! options value)
(error "Bad option argument.")))
((rec-timeout)
(if (and (number? value) (<= 1 value))
(set-dnsd-options-rec-timeout! options value)
(error "Bad options argument.")))
((socket-timeout)
(if (and (number? value) (<= 1 value) (> 13 value))
(set-dnsd-options-socket-timeout! options value)
(error "Bad options argument.")))
((socket-max-tries)
(if (and (number? value) (<= 1 value) (> 13 value))
(set-dnsd-options-socket-max-tries! options value)
(error "Bad options argument.")))
((max-connections)
(if (and (number? value) (<= 1 value))
(set-dnsd-options-max-connections! options value)
(error "Bad options argument.")))
((blacklist-time)
(if (and (number? value) (<= 60 value))
(set-dnsd-options-blacklist-time! options value)
(error "Bad options argument.")))
((blacklist-value)
(if (and (number? value) (<= 1 value))
(set-dnsd-options-blacklist-value! options value)
(error "Bad options argument.")))
((use-pre/post)
(if (boolean? value)
(set-dnsd-options-use-pre/post! options value)
(error "Bad options argument.")))
((debug-mode)
(if (boolean? value)
(set-dnsd-options-debug-mode! options value)
(error "Bad options argument.")))
(else (error "Bad option.")))))
(cdr o-list))
options)
(error "Not an option list.")))

View File

@ -1,753 +0,0 @@
; ----------------
; --- Resolver ---
; ----------------
; A DNS-Server based on the RFCs: 1034 / 1035
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; Interface:
; ----------
;(dnsd-ask-resolver-rec message protocol dnsd-options)
;(dnsd-ask-resolver-direct message list-of-nameservers protocol dnsd-options)
;; The modified send-receive-message socket-interface from dns.scm:
;; ----------------------------------------------------------------
;; Delete the given element(s) from the list:
;; TYPE: list x list -> list
(define (delete-list elems list)
(cond
((null? elems) list)
((null? list) '())
(else (delete-list (cdr elems) (delete (car elems) list)))))
;; dnsd wants the message, not the dns-error codes.
(define (dnsd-acceptable? reply query)
(if (not (= (header-id (message-header reply))
(header-id (message-header query))))
(error "send-receive-message: Bad reply-ID from server.")))
(define (dnsd-send-receive-message-tcp nameserver query dnsd-options)
(send-receive-message-tcp-int nameserver query dnsd-acceptable? dnsd-options))
(define (send-receive-message-tcp-int nameservers query accept? dnsd-options)
(receive
(reply hit-ns other-nss)
(let* ((sockets (map (lambda (nameserver)
(let ((sock (create-socket protocol-family/internet
socket-type/stream))
(addr (internet-address->socket-address
nameserver 53)))
;; Ignore return value and select unconditionally later
(with-fatal-error-handler*
(lambda (condition decline) #f)
(lambda ()
(connect-socket-no-wait sock addr) sock))))
nameservers))
(nameservers (let loop ((sockets sockets)
(nss nameservers))
(cond
((or (null? sockets) (null? nss)) '())
((socket? (car sockets))
(cons (car nss) (loop (cdr sockets) (cdr nss))))
(else (loop (cdr sockets) (cdr nss))))))
(sockets (filter socket? sockets))
(ws (map socket:outport sockets))
(wport-nameserver-alist (map cons ws nameservers))
(wport-socket-alist (map cons ws sockets)))
(with-fatal-error-handler*
(lambda (condition decline)
(for-each close-socket sockets)
decline)
(lambda ()
(dynamic-wind
(lambda () 'nothing-to-be-done-before)
(lambda ()
(let loop-port-channels ((tried-channels '())
(number-tries 1))
;; No channels left to try?
(if (or (null? (delete-list tried-channels ws))
(= (length tried-channels) (length ws))
(>= number-tries
(dnsd-options-socket-max-tries dnsd-options)))
(values query #f nameservers)
(let ((ready
(apply select-ports
(dnsd-options-socket-timeout dnsd-options)
ws)))
(let loop-ready-channels ((ready-channels ready))
(if (null? ready-channels)
(loop-port-channels (append tried-channels ready)
(+ number-tries 1))
(let* ((w (car ready-channels))
(hit-ns
(cdr (assoc w wport-nameserver-alist)))
(sock (cdr (assoc w wport-socket-alist))))
(if (not (connect-socket-successful? sock))
(loop-ready-channels (cdr ready-channels))
(let ((query-string (list->string
(add-size-tag
(message-source query))))
(r (socket:inport sock)))
(with-fatal-error-handler*
(lambda (condition decline)
(loop-ready-channels (cdr ready-channels)))
(lambda ()
(display query-string w)
(force-output w)
(let ((a (read-char r))
(b (read-char r)))
(let ((len (octet-pair->number a b)))
(let ((s (read-string len r)))
(if (and (not (= 0 (string-length s)))
(not (= len (string-length s))))
(error 'unexpected-eof-from-server))
(values (parse (string->list s)) hit-ns
(delete hit-ns nameservers))))))))))))))))
(lambda () (for-each close-socket sockets))))))
(accept? reply query)
(values reply hit-ns other-nss)))
(define (dnsd-send-receive-message-udp nameserver query dnsd-options)
(send-receive-message-udp-int nameserver query dnsd-acceptable? dnsd-options))
(define (send-receive-message-udp-int nameservers query accept? dnsd-options)
(receive
(reply hit-ns other-nss)
(let* ((sockets (map (lambda (nameserver)
(let ((sock (create-socket protocol-family/internet
socket-type/datagram))
(addr (internet-address->socket-address
nameserver 53)))
(connect-socket sock addr)
sock))
nameservers))
(rs (map socket:inport sockets))
(ws (map socket:outport sockets)))
(with-fatal-error-handler*
(lambda (condition decline)
(for-each close-socket sockets)
decline)
(lambda ()
(dynamic-wind
(lambda () 'nothing-to-be-done-before)
(lambda ()
(let ((query-string (list->string (message-source query)))
(rsv (list->vector rs))
(rport-nameserver-alist (map cons rs nameservers))
(rport-socket-alist (map cons rs sockets)))
(for-each (lambda (w) (display query-string w)) ws)
(for-each force-output ws)
(let loop-port-channels ((tried-channels '())
(number-tries 1))
(let ((rs-new (delete-list tried-channels rs)))
(if (or (null? rs-new)
(>= number-tries (dnsd-options-socket-max-tries dnsd-options))
(= (length tried-channels) (length rs)))
(values query #f nameservers)
(let ((ready (apply select-ports
(dnsd-options-socket-timeout dnsd-options)
rs-new)))
(let loop-ready-channels ((ready-channels ready))
(if (null? ready-channels)
(loop-port-channels (append tried-channels ready)
(+ number-tries 1))
(let* ((r (car ready-channels))
(hit-ns (cdr (assoc r rport-nameserver-alist))))
(if (not (connect-socket-successful?
(cdr (assoc r rport-socket-alist))))
(loop-ready-channels (cdr ready-channels))
;; 512 is the maximum udp-message size:
(let ((answer (string->list (read-string/partial 512 r))))
(if (null? answer)
(loop-ready-channels (cdr ready-channels))
(values (parse answer) hit-ns
(delete hit-ns nameservers))))))))))))))
(lambda () (for-each close-socket sockets))))))
(accept? reply query)
(if (flags-truncated? (header-flags (message-header reply)))
(send-receive-message-tcp-int nameservers query accept?)
(values reply hit-ns other-nss))))
(define (dnsd-send-receive-message nameservers query protocol dnsd-options)
((cond
((eq? protocol (network-protocol tcp)) dnsd-send-receive-message-tcp)
((eq? protocol (network-protocol udp)) dnsd-send-receive-message-udp))
nameservers query dnsd-options))
;; Stuff:
;; ------
; Filter a list of rrs of the given type:
; TYPE: list-of-rrs -> list-of-rrs
(define (filter-rr-type type list)
(filter (lambda (e) (eq? (resource-record-type e) type)) list))
;; Randomize a list (needs srfi-1 & srfi-27):
;; TYPE: list -> list
(define (shake-list l)
(define (shake-list-int l res)
(if (null? l)
res
(let ((random-value (random-integer (length l))))
(shake-list-int
(append (take l random-value) (drop l (+ 1 random-value)))
(cons (list-ref l random-value) res)))))
(shake-list-int l '()))
;; Check a message for its response-code:
;; --------------------------------------
;; RCODE-0-Message? (Error-Free)
;; TYPE: message -> boolean
(define (rcode-0-reply? msg)
(eq? 'dns-no-error (flags-response-code (header-flags (message-header msg)))))
;; RCODE-3-Message? (Name-Error (does not exist))
;; TYPE: message -> boolean
(define (rcode-3-reply? msg)
(eq? 'dns-name-error (flags-response-code
(header-flags (message-header msg)))))
;; RCODE-2-Message? Server-Failure
;; TYPE: message -> boolean
(define (rcode-2-reply? msg)
(eq? 'dns-server-failure (flags-response-code
(header-flags (message-header msg)))))
;; RCODE-4-Message? Not Implemented
;; TYPE: message -> boolean
(define (rcode-4-reply? msg)
(eq? 'dns-not-implemented (flags-response-code
(header-flags (message-header msg)))))
;; RCODE-5-Message? (Refused to answer query.)
;; TYPE: message -> boolean
(define (rcode-5-reply? msg)
(eq? 'dns-refused (flags-response-code (header-flags (message-header msg)))))
;; Are there just CNAMEs in the answer-section of a reply?
;; TYPE message -> boolean
(define (cname-answer? msg)
(let ((cnames (fold-right
(lambda (e b)
(or (eq? (message-type cname) (resource-record-type e)) b))
#f (message-answers msg)))
(other (fold-right
(lambda (e b)
(or (not (eq? (message-type cname)
(resource-record-type e))) b))
#f (message-answers msg))))
(if other #f cnames)))
;; Interpreting the results of dbi-lookup-rec - Zone found, but not the name.
;; TYPE res-list-of-db-lookup-rec -> boolean
(define (no-entry? res-l)
(and (null? (car res-l)) (null? (cadr res-l))
(null? (caddr res-l)) (cadddr res-l)))
;; Is the query a cname-question?
;; TYPE: message -> boolean
(define (cname-question? msg)
(eq? (message-type cname) (question-type (car (message-questions msg)))))
;; Create a reply from the internally found (db or cache) information.
;; NOTE: This function is part of the exported functions.
;; TYPE: message x res-list-of-db-lookup-rec x dnsd-options -> message
(define (make-response message r-list dnsd-options)
(let* ((use-recursion? (dnsd-options-use-recursion? dnsd-options))
(error-code (if (no-entry? r-list) 'dns-name-error 'dns-no-error))
(msg-header (message-header message))
(msg-flags (header-flags msg-header))
(anli (car r-list))
(auli (cadr r-list))
(adli (caddr r-list))
(aufl (cadddr r-list)))
(make-message
(make-header (header-id msg-header)
(make-flags
'response
(flags-opcode msg-flags)
aufl
(flags-truncated? msg-flags)
(flags-recursion-desired? msg-flags)
use-recursion?
(flags-zero msg-flags)
error-code)
(header-question-count msg-header)
(length anli)
(length auli)
(length adli))
(message-questions message)
anli auli adli '())))
;; Increment the answer-section (for adding a cname)
;; TYPE: message -> message
(define (msg-inc-answers msg-header)
(let ((msg-flags (header-flags msg-header)))
(make-header (header-id msg-header)
msg-flags
(header-question-count msg-header)
(+ 1 (header-answer-count msg-header))
(header-nameserver-count msg-header)
(header-additional-count msg-header))))
;; Change the type of a question to (message-type cname)
;; TYPE: messag -> message
(define (msg->cname-msg msg)
(let ((q (car (message-questions msg))))
(make-message (message-header msg)
(list (make-question (question-name q)
(message-type cname)
(question-class q)))
(message-answers msg)
(message-nameservers msg)
(message-additionals msg) '())))
;; Assignment procs:
;; -----------------
;; Set the recursion-aviable flag:
;; TYPE: message x boolean -> message
(define (msg-set-recursion-aviable! msg bool)
(set-flags-recursion-available! (header-flags (message-header msg)) bool))
;; Set the response-code of a message:
;; NOTE: This function is part of the exported functions.
;; TYPE: message x rcode -> message
(define (msg-set-rcode! msg code)
(let ((rcode (case code
((0) 'dns-no-error)
((1) 'dns-format-error)
((2) 'dns-server-failure)
((3) 'dns-name-error)
((4) 'dns-not-implemented)
((5) 'dns-refused)
(else code))))
(set-flags-response-code! (header-flags (message-header msg)) rcode)))
;; Direct lookup:
;; --------------
;; Direct lookup of a query asking the given Nameserves:
;; TYPE: message x list-of-address32 tcp/udp x dnsd-options -> message
(define (dnsd-lookup-direct msg ns-list proto dnsd-options)
(receive (msg hit-ip other-ips)
(dnsd-send-receive-message
ns-list
(make-message (message-header msg) (message-questions msg)
(message-answers msg) (message-nameservers msg)
(message-additionals msg) (mc-message->octets msg))
proto dnsd-options)
(if hit-ip
msg
(begin
(dnsd-log (syslog-level info)
"dnsd-direct-lookup. Nameservers ~S not reachable."
ns-list)
(error "dnsd-direct-lookup. No NS reachable.")))))
;; Stuff for recursive lookup:
;; ---------------------------
;; SBELT:
;; ------
;; Fallback nameserver for recursive lookup. This is the default value which
;; can be changed by the dnsd-options:
(define *sbelt*
(list ;(ip-string->address32 "192.5.5.241")
(ip-string->address32 "192.36.148.17")
(ip-string->address32 "192.5.5.241")))
;; Some nameserver IPs:
;; --------------------
;; 192.36.148.17 i.root-servers.net. (for .)
;; 192.5.5.241 f.root-server.net. (for .)
;; 192.5.6.30 A.GTLD-SERVERS.NET. (for .com.
;; 193.159.170.187 deNIC-NS (for .de.)
;; Record-Type for additional information needed by the lookup:
;; cnames is a list of all seen CNAMES to avoid CNAME-loops.
;; ips is a list of used NS-IPs for the query.
;; timestamp is the creation-time of the context and used for timeouts.
(define-record-type context :context
(really-make-context cnames ips timestamp)
context?
(cnames get-context-cnames set-context-cnames!)
(ips get-context-ips set-context-ips!)
(timestamp get-context-timestamp))
;; Makes the lookup-context for a given query.
;; TYPE: message -> context
(define (make-context message)
(really-make-context
(list (question-name (car (message-questions message))))
'()
(time)))
;; Add a name to the context.
;; TYPE: context x string -> context
(define (update-context-cnames! context value)
(set-context-cnames! context (cons value (get-context-cnames context)))
context)
;; Add a IP to the context.
;; TYPE: context x address32 -> context
(define (update-context-ips! context value)
(set-context-ips! context (cons value (get-context-ips context)))
context)
;; Search the SLIST for the best 'nearest' nameserver to query for a message.
;; The nearest server is the server for the domain with the most matching labels
;; seen from the root: 1) www.example.com. 2) example.com. 3) com. 4) . 5) SBELT
;; TYPE: message x dnsd-options -> list-of-nameserver-ips x zone-name-of-ns
(define (search-for-ns-ips msg dnsd-options)
(let* ((q (car (message-questions msg)))
(name (question-name q))
(class (question-class q)))
(let loop ((name name))
(let ((ip-list (dnsd-slist-lookup
(make-simple-query-message name (message-type ns) class)
dnsd-options)))
(if ip-list
(values ip-list name #f)
(if (string=? "." name)
(let* ((sbelt-string (dnsd-options-nameservers dnsd-options))
(sbelt (map ip-string->address32 sbelt-string)))
(if (null? sbelt)
(values *sbelt* name #t)
(values sbelt name #t)))
(loop (cut-name name))))))))
;; Ask the message to some NS from the SLIST. Keep track which NSs were already
;; contacted for the given query in 'context'.
;; TYPE: message x udp/tcp x dnsd-options x context
;; -> message-answer x context x nearest-NS-string x address32
(define (ask-nameservers msg protocol dnsd-options context)
(receive
(ip-list name sbelt?)
(search-for-ns-ips msg dnsd-options)
;; Use only IPs which haven't been tried jet
(let ((good-ips (filter (lambda (e)
(not (fold-right
(lambda (e1 b)
(or b (= e1 e)))
#f (get-context-ips context))))
ip-list)))
;; randomize the list for some simple load-balancing...
(let loop ((good-ips (shake-list good-ips)))
(if (null? good-ips)
(error "ask-nameservers: Tried all known Nameservers.")
(receive
(msg hit-ip other-ips)
(dnsd-send-receive-message
(list (car good-ips))
(make-message (message-header msg) (message-questions msg)
(message-answers msg) (message-nameservers msg)
(message-additionals msg) (mc-message->octets msg))
protocol dnsd-options)
(if hit-ip
(values msg (update-context-ips! context hit-ip)
name hit-ip)
(begin
(if (not sbelt?) (dnsd-blacklist! (car good-ips)))
(loop (cdr good-ips))))))))))
;; Some responses contain nameserver-names but sadly not their IPs.
;; This function searches for those IPs, add the results to the
;; cache and restarts the recursive lookup.
;; TYPE: message x udp/tcp x list-of-rrs x dnsd-options -> unspecific
(define (lookup-nameserver-ips msg protocol ns-rrs dnsd-options)
(let* ((ns-names (map (lambda (e) (resource-record-data-ns-name
(resource-record-data e))) ns-rrs))
(ns-queries (map (lambda (e)
;;(display-debug "Looking for this names: " e)
(make-simple-query-message
e (message-type a)
(question-class
(car (message-questions msg))))) ns-names))
; ;; This step might take a while :-(
; (answers (map (lambda (e)
; (dnsd-ask-resolver-rec e protocol dnsd-options))
; ns-queries))
;; Concurrent lookup of the IPs:
(ch-list (map
(lambda (msg)
(let ((ch-res (make-channel)))
(fork-thread
(lambda ()
(sync (send-rv
ch-res
;; Use dnsd-ask-r... because of the 'good'
;; return value.
(dnsd-ask-resolver-rec msg protocol
dnsd-options)))))
ch-res))
ns-queries))
;; Wait for all results:
(answers (map (lambda (ch) (sync (receive-rv ch))) ch-list))
(good-answers (filter (lambda (e) (rcode-0-reply? e)) answers))
(ip-rrs (map (lambda (msg) (filter-rr-type (message-type a)
(message-answers msg)))
good-answers))
(flat-ns-list (fold-right (lambda (e l) (append e l)) '() ip-rrs)))
(if (null? flat-ns-list)
#f ;TODO: Do we need a strategy to avoid loops if we don't find NS?
(dnsd-slist-update!
(make-message (message-header msg) (message-questions msg)
'() ns-rrs flat-ns-list '())))))
;; Restart dnsd-get-info-int with question-name changed to the cname.
;; TYPE: query-message x response-message x udp/tcp x dnsd-options x context
;; -> respones-message
(define (cname-lookup msg res protocol dnsd-options context)
(let* ((q (car (message-questions msg)))
(msg-name (question-name q))
(cname-rr (fold-right
(lambda (e a)
(if a a
(if (and (eq? (message-type cname)
(resource-record-type e))
(string-ci=? (resource-record-name e)
msg-name))
e a)))
#f (message-answers res)))
(cname (resource-record-data-cname-name
(resource-record-data cname-rr)))
(found-loop? (fold-right (lambda (e b)
(or (string-ci=? cname e) b))
#f (get-context-cnames context))))
(if found-loop? ; Check for CNAME-Loop
(begin ;;(display-debug "Found a CNAME-loop. Aborting!")
(error "Found a CNAME-loop. Aborting recursive lookup."))
(let* ((new-msg (make-message (message-header msg)
(list (make-question cname
(question-type q)
(question-class q)))
'() '() '() '()))
(res (dnsd-get-info-int new-msg protocol dnsd-options
;; Keep timout, allow all IPs again...
(really-make-context
(cons cname (get-context-cnames context))
'()
(get-context-timestamp context))))
(new-res (make-message (msg-inc-answers (message-header res))
(message-questions msg)
(cons cname-rr (message-answers res))
(message-nameservers res)
(message-additionals res) '())))
new-res))))
;; Recursive Lookup as seen in RFC 1034:
;; -------------------------------------
;; 1) Check local information and (if present) return it to the client.
;; 2) Search for server(s) to ask. Wait for a response.
;; 3) Analyze the response:
;; 3.1 cache answers or name error.
;; 3.2 cache delegation info to other servers. Retry.
;; 3.3 if the response shows a CNAME and that is not the
;; answer itself, cache the CNAME, change the SNAME to the
;; canonical name in the CNAME RR and go to step 1.
;; 3.4 servers failure etc.: delete server from cache. Retry.
;; Start the recursive lookup and initialize the first context-list
;; with the name of the question (to avoid CNAME-Loops).
;; TYPE: message x udp/tcp x dnsd-options -> message
(define (dnsd-get-information message protocol dnsd-options)
(dnsd-get-info-int message protocol dnsd-options (make-context message)))
;; TYPE: message x udp/tcp x dnsd-options x context -> message
(define (dnsd-get-info-int message protocol dnsd-options context)
; 1) Search local information:
(let* ((use-cache? (dnsd-options-use-cache? dnsd-options))
(local-res (if use-cache? (dnsd-cache-lookup? message) #f)))
;; Timeout?
(if (> (- (time) (get-context-timestamp context))
(dnsd-options-rec-timeout dnsd-options))
(error "dnsd-get-info-int: Global timeout.")
(if local-res (make-response message local-res dnsd-options)
;; 2) Could be: Search for the best nameserver to ask.
;; Now it's: Ask all servers concurrent and take
;; the first result.
(receive
(rec-res context followed-name hit-ip)
(ask-nameservers message protocol dnsd-options context)
;; 3) Analyze the response:
(let* ((ns-rrs (filter-rr-type (message-type ns)
(message-nameservers rec-res)))
(a-rrs (filter-rr-type (message-type a)
(message-additionals rec-res))))
(cond
;; 3.4) Bad answer: Some NS are to 'lazy' to return cnames
;; and return RCODE 5 instead. The NS of sourceforge.net.
;; are a good bad example.
((rcode-5-reply? rec-res)
(if (not (cname-question? rec-res))
(let ((cname-query
(dnsd-get-information (msg->cname-msg message)
protocol dnsd-options)))
(if (cname-answer? cname-query)
(cname-lookup message cname-query protocol
dnsd-options context)
(begin (dnsd-blacklist! hit-ip)
rec-res)))
(begin (dnsd-blacklist! hit-ip) rec-res)))
;; 3.4) Try again with other servers.
((rcode-2-reply? rec-res)
(dnsd-blacklist! hit-ip)
(dnsd-get-info-int message protocol dnsd-options context))
((rcode-4-reply? rec-res)
(dnsd-blacklist! hit-ip
(dnsd-options-blacklist-value dnsd-options))
(dnsd-get-info-int message protocol dnsd-options context))
(else
;; A "good" reply.
(dnsd-blacklist-unlist! hit-ip dnsd-options)
(cond
;; 3.1) Found a name-error.
((rcode-3-reply? rec-res)
(dnsd-cache-update! rec-res) rec-res)
;; 3.4) Whatever error is left... .
((not (rcode-0-reply? rec-res)) rec-res)
;; 3.1) Found an answer.
((not (null? (message-answers rec-res)))
;; 3.3) CNAME?
(if (and (not (cname-question? rec-res))
(cname-answer? rec-res))
(begin
(dnsd-cache-update! (msg->cname-msg rec-res))
;;(display-debug "Starting CNAME Lookup!")
(cname-lookup message rec-res protocol
dnsd-options context))
;; Returning of not-authoritative data
;; may be a bad habbit...
(if (flags-authoritative?
(header-flags (message-header rec-res)))
rec-res
rec-res)))
(else
;; 3.2) Redirection to other Nameservers?
(cond
((null? ns-rrs) rec-res)
((null? a-rrs)
;; Only nameserver resource-records, search for IPs
(lookup-nameserver-ips rec-res protocol
ns-rrs dnsd-options)
(dnsd-get-info-int message protocol dnsd-options context))
(else
(dnsd-slist-update! rec-res)
(dnsd-get-info-int message protocol
dnsd-options context)))))))))))))
;; ---------------------------------
;; --- Server/Resolver-Interface ---
;; ---------------------------------
;; (dnsd-ask-resolver-direct msg nameserver-list protocol dnsd-options)
;; - Ask a specific nameserver (& don't use the SLIST-Interface.)
;; (E.g. for the AXFR-Update algorihms.)
;;
;; (dnsd-ask-resolver-rec msg protocol dnsd-options)
;; - Ask indirect (and recursive) via the SLIST-Cache.
;; TYPE: message x upd/tcp x dnsd-options -> message
(define (dnsd-ask-resolver-rec msg proto dnsd-options)
(set-message-source! msg (mc-message->octets msg))
(let ((ch-timeout (make-channel))
(ch-res (make-channel)))
(fork-thread
(lambda ()
(sleep (* 1000 (dnsd-options-rec-timeout dnsd-options)))
(sync (send-rv ch-timeout #t))))
(fork-thread
(lambda ()
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level debug)
"Error during recursive lookup.")
(msg-set-rcode! msg 2)
msg)
(lambda ()
(sync (send-rv ch-res (dnsd-get-information msg
proto dnsd-options)))))))
(sync
(choose
(wrap (receive-rv ch-timeout)
(lambda (ignore)
(dnsd-log (syslog-level info)
"Timeout during recursive lookup. Current value is ~Ds"
(dnsd-options-rec-timeout dnsd-options))
(msg-set-rcode! msg 2) msg))
(wrap (receive-rv ch-res)
(lambda (value)
value))))))
;; TYPE: message x list-of-address32 x upd/tcp x dnsd-options -> message
(define (dnsd-ask-resolver-direct msg nameservers proto dnsd-options)
(set-message-source! msg (mc-message->octets msg))
(with-fatal-error-handler*
(lambda (condition decline)
(dnsd-log (syslog-level debug)
"Error during direct lookup.")
(msg-set-rcode! msg 2)
msg)
(lambda ()
(dnsd-lookup-direct msg nameservers proto dnsd-options))))

View File

@ -1,177 +0,0 @@
; ----------------------------------
; --- Resource-Record-Definition ---
; ----------------------------------
; Wrapper for (make-resource-record ___) from dns.scm:
; * Abstraction of (make-resource-record ___ (make-resource-record-data-* ___))
; * Now for all supported types: (dns-rr-<type> ...)
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; Interface:
; (dns-rr-a ...)
; (dns-rr-txt ...)
; etc..
; Abstraction of (make-resource-record ... (make-resource-record-data-* ...))
; Now: (dns-rr-* ...), trying to include data-integrity.
; *** Some stuff ***
(define (make-message-class class)
(cond
((number? class)
(message-class-number->type class))
((symbol? class)
(message-class-symbol->type class))
((message-class? class)
class)
(else #f)))
(define (make-message-type type)
(cond
((number? type)
(message-type-number->type type))
((symbol? type)
(message-type-symbol->type type))
((message-type? type)
type)
(else #f)))
(define (make-address32 ip)
(cond
((address32? ip) ip)
((ip-string? ip)
(ip-string->address32 ip))
(else #f)))
; Nice to know: valid ttls: 0-2147483647
; *02* - (dns-rr-* ...) functions:
; Warning: This functions won't work with any other class than 'IN'!
; TYPES: name x class x ttl x data -> resource-record-record-type or #f
(define (dns-rr-a name class ttl data)
(and-let* ((name (make-fqdn-name name))
(whatever (fqdn? name))
(class (make-message-class class))
(whatever (eq? class (message-class in)))
(a32 (make-address32 data)))
(make-resource-record
name (message-type a)
class ttl
(make-resource-record-data-a a32))))
(define (dns-rr-ns name class ttl data)
(and-let* ((name (make-fqdn-name name))
(whatever (fqdn? name))
(class (make-message-class class))
(whatever (eq? class (message-class in)))
(ns-name (make-fqdn-name data))
(whatever (fqdn? ns-name)))
(make-resource-record
name (message-type ns)
class ttl
(make-resource-record-data-ns ns-name))))
(define (dns-rr-cname name class ttl data)
(and-let* ((name (make-fqdn-name name))
(whatever (fqdn? name))
(class (make-message-class class))
(whatever (eq? class (message-class in)))
(cname-name (make-fqdn-name data))
(whatever (fqdn? cname-name)))
(make-resource-record
name (message-type cname)
class ttl
(make-resource-record-data-cname cname-name))))
(define (dns-rr-soa name class ttl data)
(and-let* ((name (make-fqdn-name name))
(whatever (fqdn? name))
(class (make-message-class class))
(whatever (eq? class (message-class in)))
(mname (make-fqdn-name (car data)))
(whatever (fqdn? mname))
(rname (make-fqdn-name (cadr data)))) ;! what's with fqdn...
(make-resource-record
name (message-type soa)
class ttl
(make-resource-record-data-soa
mname rname
(caddr data)
(cadddr data)
(cadr (cdddr data))
(caddr (cdddr data))
(cadddr (cdddr data))))))
(define (dns-rr-ptr name class ttl data)
(and-let* ((name (make-fqdn-name name))
(whatever (fqdn? name))
(class (make-message-class class))
(whatever (eq? class (message-class in)))
(ptr-name (make-fqdn-name data))
(whatever (fqdn? ptr-name)))
(make-resource-record
name (message-type ptr)
class ttl
(make-resource-record-data-ptr ptr-name))))
(define (dns-rr-hinfo name class ttl data)
(and-let* ((name (make-fqdn-name name))
(whatever (fqdn? name))
(class (make-message-class class))
(whatever (eq? class (message-class in))))
(make-resource-record
name (message-type hinfo)
class ttl
(make-resource-record-data-hinfo data))))
(define (dns-rr-mx name class ttl data)
(and-let* ((name (make-fqdn-name name))
(whatever (fqdn? name))
(class (make-message-class class))
(whatever (eq? class (message-class in)))
(pref (car data))
(whatever (number? pref))
(mx-name (make-fqdn-name (cadr data)))
(whatever (fqdn? mx-name)))
(make-resource-record
name (message-type mx)
class ttl
(make-resource-record-data-mx
pref mx-name))))
(define (dns-rr-txt name class ttl data)
(and-let* ((name (make-fqdn-name name))
(whatever (fqdn? name))
(class (make-message-class class))
(whatever (eq? class (message-class in))))
(make-resource-record
name (message-type txt)
class ttl
(make-resource-record-data-txt data))))
(define (dns-rr-aaaa name class ttl data)
(and-let* ((name (make-fqdn-name name))
(whatever (fqdn? name))
(class (make-message-class class))
(whatever (eq? class (message-class in))))
(make-resource-record
name (message-type aaaa)
class ttl
(make-resource-record-data-aaaa data))))

View File

@ -1,105 +0,0 @@
; -----------------------
; --- Read/Write-Lock ---
; -----------------------
; Locks for a DNS-Server based on the RFCs: 1034 / 1035
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; Simple locks for the dns-server database. The idea behind this sort of
; lock is to permit multiple threads to read the data secured by the lock.
; If a thread tries to write, it'll block all other access to the data
; and do it's work isolated. (One write to block them all... ;-)
; Interface:
; (make-r/w-lock) : creates an r/w-lock
; (obtain-R/w-lock r/w-lock)
; (obtain-r/W-lock r/w-lock)
; (release-R/w-lock r/w-lock)
; (release-r/W-lock r/w-lock)
; (with-R/w-lock rwlock thunk)
; (with-r/W-lock rwlock thunk)
(define-record-type r/w-lock :r/w-lock
(really-make-r/w-lock write-flag read-count write-lock mutex-lock)
r/w-lock?
(write-flag get-r/w-lock-write-flag set-r/w-lock-write-flag!)
(read-count get-r/w-lock-read-count set-r/w-lock-read-count!)
(write-lock get-r/w-lock-write-lock)
(mutex-lock get-r/w-lock-mutex-lock))
(define (make-r/w-lock)
(really-make-r/w-lock #f 0 (make-lock) (make-lock)))
(define (obtain-R/w-lock r/w-lock)
(let ((mutex-lock (get-r/w-lock-mutex-lock r/w-lock)))
(let loop ()
(obtain-lock mutex-lock)
; Is there is a thread writing?
(if (get-r/w-lock-write-flag r/w-lock)
(begin
(release-lock mutex-lock)
; Just wait for some time and try again...
; TODO?: Do that with locks
(relinquish-timeslice)
(loop))
(begin
(set-r/w-lock-read-count!
r/w-lock
(+ 1 (get-r/w-lock-read-count r/w-lock)))
(release-lock mutex-lock))))))
(define (release-R/w-lock r/w-lock)
(let ((mutex-lock (get-r/w-lock-mutex-lock r/w-lock)))
(obtain-lock mutex-lock)
(set-r/w-lock-read-count!
r/w-lock (- (get-r/w-lock-read-count r/w-lock) 1))
(release-lock mutex-lock)))
(define (obtain-r/W-lock r/w-lock)
(let ((mutex-lock (get-r/w-lock-mutex-lock r/w-lock))
(write-lock (get-r/w-lock-write-lock r/w-lock)))
; Maybe wait here for another write-thread:
(obtain-lock write-lock)
(let loop ()
(obtain-lock mutex-lock)
(set-r/w-lock-write-flag! r/w-lock #t)
(if (= 0 (get-r/w-lock-read-count r/w-lock))
(release-lock mutex-lock)
(begin
(release-lock mutex-lock)
; Wait until the reads finish...
; TODO?: Do that with locks
(relinquish-timeslice)
(loop))))))
(define (release-r/W-lock r/w-lock)
(let ((mutex-lock (get-r/w-lock-mutex-lock r/w-lock))
(write-lock (get-r/w-lock-write-lock r/w-lock)))
(obtain-lock mutex-lock)
(set-r/w-lock-write-flag! r/w-lock #f)
(release-lock mutex-lock)
(release-lock write-lock)))
(define (with-R/w-lock rwlock thunk)
(obtain-R/w-lock rwlock)
(let ((value (thunk)))
(release-R/w-lock rwlock)
value))
(define (with-r/W-lock rwlock thunk)
(obtain-r/W-lock rwlock)
(let ((value (thunk)))
(release-r/W-lock rwlock)
value))

View File

@ -1,83 +0,0 @@
; ----------------------
; --- Semaphore-Lock ---
; ----------------------
; Semaphore-locks for a DNS-Server based on the RFCs: 1034 / 1035
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; Wait on the semaphore-lock if the semaphore-counter reaches 0
; Interface:
; (make-semaphore initial-value)
; (set-semaphore! new-value)
; (semaphore-post semaphore)
; (semaphore-wait semaphore)
(define-record-type semaphore :semaphore
(really-make-semaphore value i waiting-list mutex-lock)
semaphore?
(value get-semaphore-value set-semaphore-value!)
(i get-semaphore-counter set-semaphore-counter!)
(waiting-list get-semaphore-waiting set-semaphore-waiting!)
(mutex-lock get-semaphore-lock))
(define (make-semaphore i)
(really-make-semaphore i i '() (make-lock)))
;; Reset the internal semaphore-counter.
(define (set-semaphore! sem new-value)
(if (semaphore? sem)
(begin
(obtain-lock (get-semaphore-lock sem))
(let* ((old-value (get-semaphore-value sem))
(diff (- new-value old-value)))
(set-semaphore-value! sem new-value)
(set-semaphore-counter! sem (+ (get-semaphore-counter sem) diff))
(release-lock (get-semaphore-lock sem))))
(error "Not a semaphore.")))
;; Release a lock, if one is held or add 1 to the counter.
(define (semaphore-post sem)
(if (semaphore? sem)
(begin
(obtain-lock (get-semaphore-lock sem))
(let ((waiting-list (get-semaphore-waiting sem)))
(if (null? waiting-list)
(begin
(set-semaphore-counter! sem (+ 1 (get-semaphore-counter sem)))
(release-lock (get-semaphore-lock sem)))
(let ((locked-thread (car waiting-list)))
(set-semaphore-waiting! sem (cdr waiting-list))
(release-lock locked-thread)
(release-lock (get-semaphore-lock sem))))))
(error "Not a semaphore.")))
;; Wait on the semaphore if the counter is 0
(define (semaphore-wait sem)
(if (semaphore? sem)
(begin
(obtain-lock (get-semaphore-lock sem))
(if (> (get-semaphore-counter sem) 0)
(begin
(set-semaphore-counter! sem (- (get-semaphore-counter sem) 1))
(release-lock (get-semaphore-lock sem)))
(let ((lock (make-lock)))
(set-semaphore-waiting! sem
(cons lock (get-semaphore-waiting sem)))
(obtain-lock lock)
(release-lock (get-semaphore-lock sem))
(obtain-lock lock))))
(error "Not a semaphore.")))

View File

@ -1,364 +0,0 @@
; -----------------------
; --- SLIST/Blacklist ---
; -----------------------
; SLIT-Structure for the recursiv lookup algorithm (resolver.scm).
; The Blacklist is used to store 'bad' Nameserver-IPs.
; This file is part of the Scheme Untergrund Networking package
; Copyright (c) 2005/2006 by Norbert Freudemann
; <nofreude@informatik.uni-tuebingen.de>
; For copyright information, see the file COPYING which comes with
; the distribution.
; Naming-Scheme:
; --------------
; dnsd-slist-...
; dnsd-blacklist-...
;; SLIST-Cache
; The SLIST-Structure as described in RFC1034/1035.
; Lock-Safe Cache-Interface:
; ---------------------------
; (dnsd-slist-clear!) - Removes the whole data.
; (dnsd-slist-clean!) - Removes expired data.
; (dnsd-slist-lookup msg dnsd-options) - Returns nameserver IPs.
; (dnsd-slist-update! msg) - Stores Nameservers & their IPs.
; (dnsd-slist-pretty-print) - Prints the slist.
;; Blacklist:
; An IP-Adress can be blacklisted by bad resolver-results in resolver.scm
; This will cause the increment a blacklist-value. After the value reaches
; a threshold the IP will be ignored for some time (dnsd-options).
;
; After that, the next question for this IP can result in the following:
; - ignore the IP another round for bad answer
; - whitelist the IP for a good answer...
; (A good result will remove any IP from the blacklist.)
; Lock-Safe Cache-Interface:
; ---------------------------
; (dnsd-blacklist! ip . value) - Blacklist a IP.
; (dnsd-blacklist-clean! dnsd-options)
; (dnsd-blacklist-unlist! ip dnsd-options)
; (dnsd-blacklist-clear!)
; (dnsd-blacklist-print)
; Stuff:
; ------
; Filter rrs of the given type:
; TYPE: message-type x list-of-rrs -> list-of-rrs
(define (filter-rr-type type list)
(filter (lambda (e) (eq? (resource-record-type e) type)) list))
(define *debug-info* #f)
; TODO: Do this different:
; Shows the debug-information
(define display-debug
(lambda args
(if *debug-info*
(begin
(display "dnsd: ")
(map (lambda (e) (display e) (display " ")) args)
(newline))
#f)))
; SLIST:
; ------
(define-record-type dnsd-slist :dnsd-slist
(make-dnsd-slist data lock)
dnsd-slist?
(data get-dnsd-slist-data) ; slist-data-record-type
(lock get-dnsd-slist-lock)) ; r/w-lock
(define-record-type slist-data :slist-data
(make-slist-data answer expires)
cache?
(answer slist-data-answer set-slist-data-answer!) ; list-of-rrs
(expires slist-data-expires)) ; expiration time of the data (+ ttl (time))
; Create the slist:
(define *dnsd-slist* (make-dnsd-slist (make-string-table) (make-r/w-lock)))
;; Search for the shortest TTL in the message:
;; TYPE: message -> number or #f
(define (dnsd-slist-find-shortest-ttl msg)
(let loop ((msg msg))
(cond
((dns-message? msg) (loop (dns-message-reply msg)))
((message? msg) (fold-right
(lambda (e m)
(let ((ttl (resource-record-ttl e)))
(if m
(if (<= m ttl) m ttl)
ttl)))
#f (message-additionals msg))))))
;; Make a SLIST-Key from a message:
;; TYPE: message -> key-string
(define (make-slist-key msg)
(let ((question (car (message-questions msg))))
(format #f "~a;~a" (string-downcase (question-name question))
(message-class-name (question-class question)))))
;; Resets the SLIST:
(define (dnsd-slist-clear!)
(with-r/W-lock
(get-dnsd-slist-lock *dnsd-slist*)
(lambda ()
(set! *dnsd-slist* (make-dnsd-slist (make-string-table)
(get-dnsd-slist-lock *dnsd-slist*))))))
;; Removes expired data from the SLIST:
(define (dnsd-slist-clean!)
(with-r/W-lock
(get-dnsd-slist-lock *dnsd-slist*)
(lambda ()
(let ((time (time))
(table (get-dnsd-slist-data *dnsd-slist*)))
(table-walk (lambda (k e)
(if (< time (slist-data-expires e))
#t
(table-set! table k #f)))
table)))))
;; Add the results of the given response to the cache-data
;; a min ttl is given to the NS so that the rec-lookup-algorithm
;; will be able to do it's work properly... .
;; TYPE: message -> unspecific
(define (dnsd-slist-update-ns! msg)
(with-r/W-lock
(get-dnsd-slist-lock *dnsd-slist*)
(lambda ()
(and-let* ((key (make-slist-key msg)))
(let* ((ttl (dnsd-slist-find-shortest-ttl msg))
(min-ttl (if (< ttl 120) 120 ttl))
(expires (+ (time) min-ttl)))
(table-set!
(get-dnsd-slist-data *dnsd-slist*)
key
(make-slist-data (message-additionals msg) expires)))))))
; Take the nameservers & the corresponding IPs from a message and
; (if no entry is present) adds the nameservers to the cache to be looked up
; via the nameserver-zone (found as resource-record name of the servers)
; Some server return nameserver resource records in the answer-section
; others in the additional section.
;; TYPE: message -> unspecific
(define (dnsd-slist-update! msg)
(display-debug "Updating SLIST! Adding a Nameserver.")
(and-let* ((ns-rrs (append (message-answers msg) (message-nameservers msg)))
(additionals (message-additionals msg))
(good-ns-rrs (filter-rr-type (message-type ns) ns-rrs))
(whatever (not (null? good-ns-rrs)))
(good-additionals (filter-rr-type (message-type a) additionals))
(whatever (not (null? good-additionals)))
(class (question-class (car (message-questions msg))))
(nameserver-zone (resource-record-name (car good-ns-rrs)))
(good-ns-rrs (filter (lambda (e)
(string-ci=? nameserver-zone
(resource-record-name e)))
good-ns-rrs))
(nameserver-names (map (lambda (e)
(resource-record-data-ns-name
(resource-record-data e))) good-ns-rrs))
(good-additionals (filter
(lambda (e)
(fold-right
(lambda (name b)
(if b b (string-ci=?
name (resource-record-name e))))
#f nameserver-names))
good-additionals))
(new-msg
(make-message (message-header msg)
(list (make-question nameserver-zone
(message-type ns) class))
good-ns-rrs '() good-additionals '())))
(dnsd-slist-update-ns! new-msg)))
;; Look for the IPs of the nameservers in the cache.
;; TYPE: message -> list-of-address32
(define (dnsd-slist-lookup msg dnsd-options)
;; Look for data in the slist:
(define (dnsd-slist-lookup-int msg)
(let ((lock (get-dnsd-slist-lock *dnsd-slist*)))
(obtain-R/w-lock lock)
(let* ((data (get-dnsd-slist-data *dnsd-slist*))
(key (make-slist-key msg))
(cdata (table-ref data key)))
(if cdata
(if (< (time) (slist-data-expires cdata))
(begin
(let ((res (slist-data-answer cdata)))
(release-R/w-lock lock)
res))
(begin
(release-R/w-lock lock)
(obtain-r/W-lock lock)
(table-set! data key #f)
(release-r/W-lock lock)
#f))
(begin (release-R/w-lock lock) #f)))))
;; ---
(and-let* ((additionals (dnsd-slist-lookup-int msg))
(ns-a-rrs (filter-rr-type (message-type a) additionals))
(ip-list (map (lambda (e) (resource-record-data-a-ip
(resource-record-data e))) ns-a-rrs)))
;; Filter good from blacklisted IPs:
(with-R/w-lock
(get-dnsd-blacklist-lock *blacklist*)
(lambda ()
(filter (lambda (ip)
(let ((element (table-ref (get-dnsd-blacklist-data *blacklist*)
ip)))
(cond
;; IP isn't in the blacklist-table
((not element) #t)
;; The IP hasn't been blacklisted blacklist-value-times
((>= (dnsd-options-blacklist-value dnsd-options)
(cdr element)) #t)
;; Blacklisted longer than blacklist-time-value. Try again.
((<= (+ (dnsd-options-blacklist-time dnsd-options)
(car element))
(time)) #t)
;; Don't use the IP
(else #f))))
ip-list)))))
;; Blacklist:
;; ----------
(define-record-type dnsd-blacklist :dnsd-blacklist
(make-dnsd-blacklist data lock)
dnsd-blacklist?
(data get-dnsd-blacklist-data) ; a integer-table
(lock get-dnsd-blacklist-lock)) ; r/w-lock
(define *blacklist* (make-dnsd-blacklist (make-integer-table) (make-r/w-lock)))
;; Add a IP to the blacklist:
;; TYPE: address32 -> unspecific
(define (dnsd-blacklist! ip . value)
(with-r/W-lock
(get-dnsd-blacklist-lock *blacklist*)
(lambda ()
(let* ((table (get-dnsd-blacklist-data *blacklist*))
(element (table-ref table ip))
(value (if (null? value)
1
(car value))))
(if element
(table-set! table ip (cons (time) (+ value (cdr element))))
(table-set! table ip (cons (time) value)))))))
;; Removes the given ip from the list:
;; TYPE address32 -> unspecific
(define (dnsd-blacklist-unlist! ip dnsd-options)
(with-r/W-lock
(get-dnsd-blacklist-lock *blacklist*)
(lambda ()
(let ((blacklist (get-dnsd-blacklist-data *blacklist*)))
(if (and (table-ref blacklist ip)
(< (cdr (table-ref blacklist ip))
(dnsd-options-blacklist-value dnsd-options)))
(table-set! blacklist ip #f)
#f)))))
;; Remove all blacklisted IPs:
(define (dnsd-blacklist-clear!)
(with-r/W-lock
(get-dnsd-blacklist-lock *blacklist*)
(lambda ()
(set! *blacklist* (make-dnsd-blacklist
(make-integer-table)
(get-dnsd-blacklist-lock *blacklist*))))))
;; Deprecated:
;; Remove old entries:
; (define (dnsd-blacklist-clean! dnsd-options)
; (with-r/W-lock
; (get-dnsd-blacklist-lock *blacklist*)
; (lambda ()
; (table-walk
; (lambda (key element)
; (if (< (dnsd-options-blacklist-time dnsd-options)
; (- (time) (car element)))
; (table-set! (get-dnsd-blacklist-data *blacklist*) key #f)))
; (get-dnsd-blacklist-data *blacklist*)))))
;; Display SLIST / Blacklist:
;; --------------------------
;; Display the blacklisted IPs:
(define (dnsd-blacklist-print)
(with-R/w-lock
(get-dnsd-blacklist-lock *blacklist*)
(lambda ()
(let ((data (get-dnsd-blacklist-data *blacklist*))
(current-time (time)))
(display "DNSD-Blacklist:\n")
(display "---------------\n")
(table-walk
(lambda (key element)
(display "\nIP: ")
(display (address32->ip-string key))
(display " with blacklist-value: ")
(display (cdr element))
(display " [with age ")
(display (- current-time (car element)))
(display "s.]")
(newline))
data)))))
;; Display the SLIST:
(define (dnsd-slist-pretty-print)
(with-R/w-lock
(get-dnsd-slist-lock *dnsd-slist*)
(lambda ()
(let ((data (get-dnsd-slist-data *dnsd-slist*)))
(display "DNSD-SLIST:\n")
(display "-----------\n")
(table-walk
(lambda (k e)
(let ((slist-data (slist-data-answer e)))
(display "\n*Zone: ")
(display k)(newline)
(display " ---------\n")
(display " Expires in: ")
(display (- (slist-data-expires e) (time)))
(display " seconds.\n")
(display " \n Nameservers-Section:\n\n")
(map (lambda (y) (pretty-print-dns-message y))
slist-data)))
data)))))

File diff suppressed because it is too large Load Diff

View File

@ -1,76 +0,0 @@
;;; http server in the Scheme Shell -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1996 by Mike Sperber. <sperber@informatik.uni-tuebingen.de>
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;; This code is very rudimentary at the moment and up for some expansion.
;;; Right now, it is primarily useful for running the server through a
;;; web accelerator
(define (access-denier . hosts)
(lambda (info)
(and (any (lambda (host)
(host-matches? info host))
hosts)
'deny)))
(define (access-allower . hosts)
(lambda (info)
(and (any (lambda (host)
(host-matches? info host))
hosts)
'allow)))
(define (access-controller . controls)
(lambda (info)
(let loop ((controls controls))
(and (pair? controls)
(or ((car controls) info)
(loop (cdr controls)))))))
(define (access-controlled-handler control ph)
(lambda (path req)
(if (eq?
(control (host-info (socket-remote-address (request-socket req))))
'deny)
(http-error (status-code forbidden) req)
(ph path req))))
(define (address->list address)
(list (arithmetic-shift (bitwise-and address #xff000000) -24)
(arithmetic-shift (bitwise-and address #xff0000) -16)
(arithmetic-shift (bitwise-and address #xff00) -8)
(bitwise-and address #xff)))
(define (host-matches? info host)
(cond
((list? host)
(let ((len (length host)))
(any (lambda (address)
(equal? (take len (address->list address)) host))
(host-info:addresses info))))
(else ; (string? host)
(any (lambda (name)
(string-match host (string-map char-downcase name)))
(cons (host-info:name info)
(host-info:aliases info))))))
(define normalize-host
(let ((split (infix-splitter (make-regexp "\\.")))
(number (make-regexp "[0-9]+")))
(lambda (host)
(let ((components (split host)))
(if (every (lambda (component)
(regexp-exec number component))
components)
(map string->number components)
host)))))
(define (take n l)
(let loop ((n n) (l l) (r '()))
(if (zero? n)
(reverse r)
(loop (- n 1) (cdr l) (cons (car l) r)))))

View File

@ -1,300 +0,0 @@
;;; Server support for NCSA's WWW Common Gateway Interface -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1995 by Olin Shivers.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;; See http://hoohoo.ncsa.uiuc.edu/cgi/interface.html for a sort of "spec".
;;; PROBLEMS:
;;; - The handlers could be made -- closed over their parameters
;;; (e.g., root vars, etc.)
;;; This code provides a request handler for the HTTP server that implements
;;; a CGI interface to external programs for doing HTTP transactions.
;;; About HTML forms
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This info is in fact independent of CGI, but important to know about,
;;; as many CGI scripts are written for responding to forms-entry in
;;; HTML browsers.
;;;
;;; The form's field data are turned into a single string, of the form
;;; name=val&name=val
;;; where the <name> and <val> parts are URI encoded to hide their
;;; &, =, and + chars, among other things. After URI encoding, the
;;; space chars are converted to + chars, just for fun. It is important
;;; to encode the spaces this way, because the perfectly general %xx escape
;;; mechanism might be insufficiently confusing. This variant encoding is
;;; called "form-url encoding."
;;;
;;; If the form's method is POST,
;;; Browser sends the form's field data in the entity block, e.g.,
;;; "button=on&ans=yes". The request's Content-type: is application/
;;; x-www-form-urlencoded, and the request's Content-length: is the
;;; number of bytes in the form data.
;;;
;;; If the form's method is GET,
;;; Browser sends the form's field data in the URL's <search> part.
;;; (So the server will pass to the CGI script as $QUERY_STRING,
;;; and perhaps also on in argv[]).
;;;
;;; In either case, the data is "form-url encoded" (as described above).
;;; ISINDEX queries:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; (Likewise for ISINDEX URL queries from browsers.)
;;; Browser url-form encodes the query (see above), which then becomes the
;;; ?<search> part of the URI. (Hence the CGI script will split the individual
;;; fields into argv[].)
;;; CGI interface:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; - The URL's <search> part is assigned to env var $QUERY_STRING, undecoded.
;;; - If it contains no raw "=" chars, it is split at "+" chars. The
;;; substrings are URI decoded, and become the elts of argv[].
;;; - The CGI script is run with stdin hooked up to the socket. If it's going
;;; to read the entity, it should read $CONTENT_LENGTH bytes worth.
;;; - A bunch of env vars are set; see below.
;;; - If the script begins with "nph-" its output is the entire response.
;;; Otherwise, it replies to the server, we peel off a little header
;;; that is used to construct the real header for the response.
;;; See the "spec" for further details. (URL above).
;;;
;;; The "spec" also talks about PUT, but when I tried this on a dummy script,
;;; the NSCA httpd server generated buggy output. So I am only implementing
;;; the POST and GET ops; any other op generates a "405 Method not allowed"
;;; response.
;;; Parameters
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; path for scripts
(define cgi-default-bin-path "/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin")
;;; The request handler for CGI scripts. (car path) is the script to run.
;;; cgi-bin-path is used, if PATH-variable isn't defined
(define (cgi-handler bin-dir . maybe-cgi-bin-path)
(let-optionals
maybe-cgi-bin-path
((cgi-bin-path cgi-default-bin-path))
(let ((request-invariant-cgi-env ; environment variables that never change
`(("PATH" . ,cgi-bin-path)
("SERVER_SOFTWARE" . ,sunet-version-identifier)
("SERVER_NAME" . ,(host-info:name (host-info (system-name))))
("GATEWAY_INTERFACE" . "CGI/1.1"))))
(lambda (path req)
(if (pair? path) ; Got to have at least one elt.
(compute-cgi path req bin-dir request-invariant-cgi-env)
(make-error-response (status-code bad-request) req "Empty CGI script"))))))
(define (compute-cgi path req bin-dir request-invariant-cgi-env)
(let* ((prog (car path))
(filename (or (dotdot-check bin-dir (list prog))
(http-error (status-code bad-request) req
"CGI scripts may not contain \"..\" elements.")))
(nph? (string-prefix? "nph-" prog)) ; PROG starts with "nph-" ?
; why did we had (string-suffix? "-nph" prog) here?
(search (http-url-search (request-url req))) ; Compute the
(argv (if (and search (not (string-index search #\=))) ; argv list.
(split-and-decode-search-spec search)
'()))
(env (cgi-env req bin-dir (cdr path) request-invariant-cgi-env))
(doit (lambda ()
(dup->inport (socket:inport (request-socket req)) 0)
(dup->outport (current-output-port) 1)
(dup 1 2)
(apply exec/env filename env argv))))
(http-syslog (syslog-level debug) "[cgi-server] search: ~s, argv: ~s~%" search argv)
(let ((request-method (request-method req)))
(cond
((or (string=? request-method "GET")
(string=? request-method "POST")) ; Could do others also.
(case (file-not-executable? filename)
((search-denied permission)
(make-error-response (status-code forbidden) req
"Permission denied."))
((no-directory nonexistent)
(make-error-response (status-code not-found) req
"File or directory doesn't exist."))
(else
(if nph?
(cgi-make-nph-response (run/port* doit))
(cgi-make-response (run/port* doit) path req)))))
(else
(make-error-response (status-code method-not-allowed) req request-method))))))
(define (split-and-decode-search-spec s)
(let recur ((i 0))
(cond
((string-index s #\+ i) => (lambda (j) (cons (unescape-uri s i j)
(recur (+ j 1)))))
(else (list (unescape-uri s i (string-length s)))))))
;;; Compute the CGI scripts' process environment by adding the standard CGI
;;; environment var bindings to the current process env -- return result
;;; as an alist.
;;;
;;; You are also supposed to add the headers as env vars in a particular
;;; format, but are allowed to bag it if the environment var storage
;;; requirements might overload the OS. I don't know what you can rely upon
;;; in Unix, so I am just bagging it, period.
;;;
;;; Suppose the URL is
;;; //machine/cgi-bin/test-script/foo/bar?quux%20a+b=c
;;; then:
;; PATH_INFO -- extra info after the script-name path prefix. "/foo/bar"
;;; PATH_TRANSLATED -- non-virtual version of above. "/u/Web/foo/bar/"
;;; SCRIPT_NAME virtual path to script "/cgi-bin/test-script"
;;; QUERY_STRING -- not decoded "quux%20a+b=c"
;;; The first three of these vars are *not* encoded, so information is lost
;;; if the URL's path elements contain encoded /'s (%2F). CGI loses.
(define (cgi-env req bin-dir path-suffix request-invariant-cgi-env)
(let* ((sock (request-socket req))
(raddr (socket-remote-address sock))
(headers (request-headers req))
;; Compute the $PATH_INFO and $PATH_TRANSLATED strings.
(path-info (uri-path->uri path-suffix)) ; No encode or .. check.
(path-translated (path-list->file-name path-info bin-dir))
;; Compute the $SCRIPT_PATH string.
(url-path (http-url-path (request-url req)))
(script-path (take (- (length url-path) (length path-suffix))
url-path))
(script-name (uri-path->uri script-path)))
(receive (rhost rport)
(socket-address->internet-address raddr)
(receive (lhost lport)
(socket-address->internet-address (socket-local-address sock))
`(("SERVER_PROTOCOL" . ,(version->string (request-version req)))
("SERVER_PORT" . ,(number->string lport))
("REQUEST_METHOD" . ,(request-method req))
("PATH_INFO" . ,path-info)
("PATH_TRANSLATED" . ,path-translated)
("SCRIPT_NAME" . ,script-name)
("REMOTE_ADDR" . ,(format-internet-host-address rhost))
;; ("AUTH_TYPE" . xx) ; Random authentication
;; ("REMOTE_USER" . xx) ; features I don't understand.
;; ("REMOTE_IDENT" . xx)
,@request-invariant-cgi-env ; Stuff that never changes (see cgi-handler).
,@(cond ((http-url-search (request-url req)) =>
(lambda (srch) `(("QUERY_STRING" . ,srch))))
(else '()))
,@(cond ((get-header headers 'content-type) =>
(lambda (ct) `(("CONTENT_TYPE" . ,ct))))
(else '()))
,@(cond ((get-header headers 'content-length) =>
(lambda (cl) ; Skip initial whitespace (& other non-digits).
(let ((first-digit (string-index cl char-set:digit))
(cl-len (string-length cl)))
(if first-digit
`(("CONTENT_LENGTH" . ,(substring cl first-digit cl-len)))
(http-error (status-code bad-request) req
"Illegal `Content-length:' header.")))))
(else '()))
. ,(env->alist))))))
(define (take n lis)
(if (zero? n) '()
(cons (car lis) (take (- n 1) (cdr lis)))))
(define (drop n lis)
(if (zero? n) lis
(drop (- n 1) (cdr lis))))
;;; Script's output for request REQ is available on SCRIPT-PORT.
;;; The script isn't an "nph-" script, so we read the response, and mutate
;;; it into a real HTTP response, which we then send back to the HTTP client.
(define (cgi-make-response script-port path req)
(set-port-buffering script-port bufpol/block 4096)
(let* ((headers (read-rfc822-headers script-port))
(ctype (get-header headers 'content-type))
(loc (get-header headers 'location))
(stat (cond ((get-header headers 'status)
=> (lambda (code.text)
(extract-status-code-and-text code.text
req)))
(else
(http-syslog (syslog-level notice)
"CGI script didn't generate status header.")
(cons 200 "OK"))))
(extra-headers (delete-headers (delete-headers (delete-headers headers
'content-type)
'location)
'status)))
(http-syslog (syslog-level debug) "[cgi-server] headers: ~s~%" headers)
(http-syslog (syslog-level debug) "[cgi-server] request-method=~a~%"
(request-method req))
(if loc
(if (uri-has-protocol? (string-trim loc))
(make-error-response (status-code moved-perm) req
loc loc)
(make-redirect-response (string-trim loc)))
;; Send the response header back to the client
(make-response ;code message seconds mime extras body
(number->status-code (car stat))
(cdr stat) ; text
(time)
(or ctype "text/html")
extra-headers
(make-writer-body
(lambda (out options)
(copy-inport->outport script-port out 4096)
(close-input-port script-port)))))))
(define (delete-headers headers tag)
(alist-delete tag headers))
(define (cgi-make-nph-response script-port)
(make-nph-response
(make-writer-body (lambda (out options)
(copy-inport->outport script-port out)))))
(define (uri-has-protocol? loc)
(receive (proto path search frag)
(parse-uri loc)
(if proto #t #f)))
(define (extract-status-code-and-text status req)
(with-fatal-error-handler*
(lambda (c d)
(http-error (status-code bad-gateway) req
"CGI script generated an invalid status header."
status c))
(lambda ()
(let ((status (string-trim status)))
(cons (string->number (substring status 0 3)) ; number
(substring/shared status 4)))))) ; text

View File

@ -1,376 +0,0 @@
;;; http server in the Scheme Shell -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1994 by Brian D. Carlstrom and Olin Shivers.
;;; Copyright (c) 1996-2002 by Mike Sperber.
;;; Copyright (c) 2000-2002 by Martin Gasbichler.
;;; Copyright (c) 2002 by Andreas Bernauer.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;; This file implements the core of an HTTP server: code to establish
;;; net connections, read and parse requests, and handler errors.
;;; It does not have the code to actually handle requests. That's up
;;; to other modules, and could vary from server to server. To build
;;; a complete server, you need to define request handlers (see below) --
;;; they determine how requests are to be handled.
;;;
;;; The RFC detailing the HTTP 1.0 protocol, RFC 1945, can be found at
;;; http://www.w3.org/Protocols/rfc1945/rfc1945
(define server/protocol "HTTP/1.0")
(define (httpd options)
(let ((port (httpd-options-port options))
(root-dir (httpd-options-root-directory options))
(rate-limiter
(cond
((httpd-options-simultaneous-requests options)
=> make-rate-limiter)
(else #f))))
(let-thread-fluid
logging
(make-logging)
(lambda ()
(init-http-log! options)
(with-syslog-destination
"httpd" #f #f #f
(lambda ()
(with-cwd
root-dir
(bind-prepare-listen-accept-loop
protocol-family/internet
(lambda ()
(cond ((httpd-options-post-bind-thunk options)
=> (lambda (thunk)
(thunk)))))
(lambda (sock addr)
(if rate-limiter
(begin
(rate-limit-block rate-limiter)
(rate-limit-open rate-limiter)))
(with-fatal-error-handler
(lambda (c decline)
(http-syslog (syslog-level notice) "error during connection negotiation~%")
(if rate-limiter
(rate-limit-close rate-limiter)))
(call-with-values
(lambda ()
(socket-address->internet-address (socket-remote-address sock)))
(lambda (host-address service-port)
(if (and rate-limiter (http-syslog?))
(http-syslog (syslog-level info) "<~a>~a: concurrent request #~a~%"
(pid)
(format-internet-host-address host-address)
(rate-limiter-current-requests rate-limiter)))
(set-port-buffering (socket:outport sock) bufpol/block 4096)
(fork-thread
(lambda ()
;; If there is buffering for the input,
;; CGI scripts don't get the full request
(set-port-buffering (socket:inport sock) bufpol/none)
(process-toplevel-request sock host-address options)
(if (http-syslog?)
(http-syslog (syslog-level debug) "<~a>~a [closing]~%"
(pid)
(format-internet-host-address host-address)))
(with-fatal-error-handler
(lambda (c decline)
(if (http-syslog?)
(http-syslog (syslog-level notice) "<~a>~a [error closing (~a)]~%"
(pid)
(format-internet-host-address host-address)
c)))
(close-socket sock))
(if rate-limiter
(rate-limit-close rate-limiter))
(if (http-syslog?)
(http-syslog (syslog-level info) "<~a>~a [closed]~%"
(pid)
(format-internet-host-address host-address)))))))))
port))))))))
;;; Top-level http request processor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Read, parse, and handle a single http request. The only thing that makes
;;; this complicated is handling errors -- as a server, we can't just let the
;;; standard error handlers toss us into a breakpoint. We have to catch the
;;; error, send an error response back to the client if we can, and then keep
;;; on trucking. This means using the S48's condition system to catch and
;;; handle the various errors, which introduces a major point of R5RS
;;; incompatibiliy -- R5RS has no exception system. So if you were to port
;;; this code to some other Scheme, you'd really have to sit down and think
;;; about this issue for a minute.
(define (process-toplevel-request sock host-address options)
;; This top-level error-handler catches *all* uncaught errors and warnings.
;; If the error condition is a reportable HTTP error, we send a response back
;; to the client. In any event, we abort the transaction, and return from
;; PROCESS-TOPLEVEL-REQUEST.
;;
;; We *oughta* map non-http-errors into replies anyway.
(with-fatal-error-handler*
(lambda (c decline)
(http-syslog (syslog-level notice) "<~a>~a: error: ~s~%"
(pid)
(format-internet-host-address host-address)
c)
(with-fatal-error-handler*
(lambda (c decline)
(http-syslog (syslog-level notice) "<~a>~a [error shutting down: ~s]~%"
(pid)
(format-internet-host-address host-address)
c))
(lambda ()
(shutdown-socket sock shutdown/sends+receives)
(http-syslog (syslog-level info) "<~a>~a [shut down]~%"
(pid)
(format-internet-host-address host-address)))))
(lambda ()
(call-with-values
(lambda ()
(with-fatal-error-handler*
(lambda (c decline)
(http-syslog (syslog-level notice) "<~a>~a: error: ~s~%"
(pid)
(format-internet-host-address host-address)
c)
(cond
((http-error? c)
(apply (lambda (status-code req . args)
(values req
(apply make-error-response
status-code req
args)))
(condition-stuff c)))
((fatal-syntax-error? c)
(values #f
(apply make-error-response (status-code bad-request)
#f ; No request yet.
"Request parsing error -- report to client maintainer."
(condition-stuff c))))
((not (and (exception? c)
(eq? (exception-reason c)
(enum exception os-error))))
;; try to send bug report to client
(values #f
(apply make-error-response (status-code internal-error)
#f ; don't know
"Internal error occurred while processing request"
c)))
(else
(decline))))
(lambda ()
(let ((initial-req (parse-http-request sock options)))
(let redirect-loop ((req initial-req))
(let response-loop ((response ((httpd-options-request-handler options)
(http-url-path (request-url req))
req)))
(cond
((input-response? response)
(response-loop
((input-response-body-maker response)
(socket:inport sock))))
((nph-response? response)
(values req response))
((eq? (response-code response) (status-code redirect))
(redirect-loop (redirect-request req response sock options)))
(else
(values req response)))))))))
(lambda (req response)
(send-http-response req response
(socket:inport sock)
(socket:outport sock)
options)
)))))
(define (redirect-request req response socket options)
(let* ((new-location-uri (redirect-body-location (response-body response)))
(url (with-fatal-error-handler*
(lambda (c decline)
(if (fatal-syntax-error? c)
(http-error (status-code internal-error) req
(format #f "Bad redirection out from CGI program: ~%~a"
(cdr c)))
(decline c)))
(lambda ()
;; (future) NOTE: With this, a redirection may change the
;; protocol in use (currently, the server only supports one of
;; it). This might be inapplicable.
(parse-http-servers-url-fragment new-location-uri socket options)))))
(make-request "GET"
new-location-uri
url
(request-version req) ; did not change
'() ; no rfc822 headers
(request-socket req))))
;;;; HTTP request parsing
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; This code provides procedures to read requests from an input
;;;; port.
;;; Read and parse an http request from INPORT.
;;;
;;; Note: this parser parses the URI into an http URL record. If the URI
;;; isn't an http URL, the parser fails. This may not be right. There's
;;; nothing in the http protocol to prevent you from passing a non-http
;;; URI -- what this would mean, however, is not clear. Like so much of
;;; the Web, the protocols are redundant, underconstrained, and ill-specified.
(define (parse-http-request sock options)
(let ((line (read-crlf-line (socket:inport sock))))
;; Blat out some logging info.
(if (http-syslog?)
(call-with-values
(lambda ()
(socket-address->internet-address (socket-remote-address sock)))
(lambda (host-address service-port)
(http-syslog (syslog-level info) "<~a>~a: ~a~%"
(pid)
(format-internet-host-address host-address)
line))))
(if (eof-object? line)
(fatal-syntax-error "EOF while parsing request.")
(let* ((elts (string->words line)) ; Split at white-space.
(version (case (length elts)
((2) '(0 . 9))
((3) (parse-http-version (caddr elts)))
(else (fatal-syntax-error "Bad Request Line."))))
(meth (car elts))
(uri-string (cadr elts))
(url (parse-http-servers-url-fragment uri-string sock options))
(headers (if (equal? version '(0 . 9))
'()
(read-rfc822-headers (socket:inport sock)))))
(make-request meth uri-string url version headers sock)))))
;;; Parse the URL, but if it begins without the "http://host:port"
;;; prefix, interpolate one from SOCKET. It would be sleazier but
;;; faster if we just computed the default host and port at
;;; server-startup time, instead of on every request.
;;; REDIRECT-REQUEST relys on that nothing is read out from SOCKET.
(define (parse-http-servers-url-fragment uri-string socket options)
(receive (scheme path search frag-id) (parse-uri uri-string)
(if frag-id ; Can't have a #frag part.
(fatal-syntax-error "HTTP URL contains illegal #<fragment> suffix."
uri-string)
(if scheme
(if (string-ci=? scheme "http") ; Better be an http url.
(parse-http-url path search #f)
(fatal-syntax-error "Non-HTTP URL" uri-string))
;; Interpolate the server struct from our net connection.
(if (and (pair? path) (string=? (car path) ""))
(let* ((addr (socket-local-address socket))
(local-name (or (httpd-options-fqdn options)
(socket-address->fqdn addr)))
(portnum (or (httpd-options-reported-port options)
(my-reported-port addr))))
(make-http-url (make-server #f #f
local-name
(number->string portnum))
(map unescape-uri (cdr path)) ; Skip initial /.
search
#f))
(fatal-syntax-error "Path fragment must begin with slash"
uri-string))))))
(define parse-http-version
(let ((re (make-regexp "^HTTP/([0-9]+)\\.([0-9]+)$"))
(lose (lambda (s) (fatal-syntax-error "Bad HTTP version" s))))
(lambda (vstring)
(let ((m (regexp-exec re vstring)))
(if m
(cons (or (string->number (match:substring m 1) 10) (lose vstring))
(or (string->number (match:substring m 2) 10) (lose vstring)))
(lose vstring))))))
;;; Split string into a list of whitespace-separated strings.
;;; This could have been trivially defined in scsh as (field-splitter " \t\n")
;;; but I hand-coded it because it's short, and I didn't want invoke the
;;; regexp machinery for something so simple.
(define non-whitespace (char-set-complement char-set:whitespace))
(define (string->words s)
(let recur ((start 0))
(cond ((string-index s non-whitespace start) =>
(lambda (start)
(cond ((string-index s char-set:whitespace start) =>
(lambda (end)
(cons (substring s start end)
(recur end))))
(else (list (substring s start (string-length s)))))))
(else '()))))
(define (send-http-headers response port)
(display server/protocol port)
(write-char #\space port)
(display (status-code-number (response-code response)) port)
(write-char #\space port)
(display (or (response-message response)
(status-code-message (response-code response)))
port)
(write-crlf port)
(send-http-header-fields
(list (cons 'server (string-append "Scheme Untergrund " sunet-version-identifier))
(cons 'content-type (response-mime response))
(cons 'date (rfc822-time->string (response-seconds response))))
port)
(send-http-header-fields (response-extras response) port)
(write-crlf port))
(define (send-http-response request response input-port output-port options)
(cond
;;if request-record could not be built (i.e. either
;;fatal-syntax-error was called because of an erroneous request
;;line, or an server-internal error (not an os-error) occurred)
;;and therefore HTTP-version of request is not known, answer
;;with HTTP/1.0
((not request)
(send-http-headers response output-port)
(display-http-body (response-body response) input-port output-port options))
;;no CLF-logging)
((nph-response? response)
(display-http-body (nph-response-body response) input-port output-port options)
(http-log request (status-code ok))); guess the status code
(else
(if (not (v0.9-request? request))
(send-http-headers response output-port))
(if (not (string=? (request-method request) "HEAD"))
(display-http-body (response-body response) input-port output-port options))
(http-log request (response-code response)))))
(define (send-http-header-fields headers port)
(for-each (lambda (pair)
(display (car pair) port)
(write-char #\: port)
(display (cdr pair) port)
(write-crlf port))
headers))
(define (my-reported-port addr)
(receive (ip-addr portnum) (socket-address->internet-address addr)
portnum))

View File

@ -1,41 +0,0 @@
;;; Error stuff for the http server. -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1995 by Olin Shivers.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;; An http error condition is a data structure with the following pieces:
;;; (error-code request message . irritants)
;;; You recognise one with HTTP-ERROR?, and retrieve the pieces with
;;; CONDITION-STUFF.
;;;
;;; HTTP error condition
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Define a sub-type of the S48 error condition, the HTTP error condition.
;;; An HTTP error is one that corresponds to one of the HTTP error response
;;; codes, so you can reliably use an HTTP error condition to construct an
;;; error response message to send back to the HTTP client.
(define-condition-type 'http-error '(error))
(define http-error? (condition-predicate 'http-error))
(define (http-error status-code req . args)
(apply signal 'http-error status-code req args))
;;; Syntax error condition
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Scheme 48 has a "syntax error" error condition, but it isn't an error
;;; condition! It's a warning condition. I don't understand this.
;;; We define a *fatal* syntax error here for the parsers to use.
(define-condition-type 'fatal-syntax-error '(error))
(define fatal-syntax-error? (condition-predicate 'fatal-syntax-error))
(define (fatal-syntax-error msg . irritants)
(apply signal 'fatal-syntax-error msg irritants))

View File

@ -1,540 +0,0 @@
;;; http server in the Scheme Shell -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1994 by Brian D. Carlstrom and Olin Shivers.
;;; Copyright (c) 1996-2003 by Mike Sperber.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
(define server/buffer-size 8192) ; WTF
(define-record-type file-directory-options :file-directory-options
(really-make-file-directory-options file-name->content-type
file-name->content-encoding
file-name->icon-url
directory-icon-url
blank-icon-url
back-icon-url
unknown-icon-url)
file-directory-options?
(file-name->content-type file-directory-options-file-name->content-type
set-file-directory-options-file-name->content-type!)
(file-name->content-encoding file-directory-options-file-name->content-encoding
set-file-directory-options-file-name->content-encoding!)
(file-name->icon-url file-directory-options-file-name->icon-url
set-file-directory-options-file-name->icon-url!)
(directory-icon-url file-directory-options-directory-icon-url
set-file-directory-options-directory-icon-url!)
(blank-icon-url file-directory-options-blank-icon-url
set-file-directory-options-blank-icon-url!)
(back-icon-url file-directory-options-back-icon-url
set-file-directory-options-back-icon-url!)
(unknown-icon-url file-directory-options-unknown-icon-url
set-file-directory-options-unknown-icon-url!))
(define (make-default-file-directory-options)
(really-make-file-directory-options default-file-name->content-type
default-file-name->content-encoding
default-file-name->icon-url
#f #f #f #f))
(define (copy-file-directory-options options)
(let ((new-options (make-default-file-directory-options)))
(set-file-directory-options-file-name->content-type!
new-options
(file-directory-options-file-name->content-type options))
(set-file-directory-options-file-name->content-encoding!
new-options
(file-directory-options-file-name->content-encoding options))
(set-file-directory-options-file-name->icon-url!
new-options
(file-directory-options-file-name->icon-url options))
(set-file-directory-options-directory-icon-url!
new-options
(file-directory-options-directory-icon-url options))
(set-file-directory-options-blank-icon-url!
new-options
(file-directory-options-blank-icon-url options))
(set-file-directory-options-back-icon-url!
new-options
(file-directory-options-back-icon-url options))
(set-file-directory-options-unknown-icon-url!
new-options
(file-directory-options-unknown-icon-url options))
new-options))
(define (make-file-directory-options-transformer set-option!)
(lambda (new-value . stuff)
(let ((new-options (if (not (null? stuff))
(copy-file-directory-options (car stuff))
(make-default-file-directory-options))))
(set-option! new-options new-value)
new-options)))
(define with-file-name->content-type
(make-file-directory-options-transformer
set-file-directory-options-file-name->content-type!))
(define with-file-name->content-encoding
(make-file-directory-options-transformer
set-file-directory-options-file-name->content-encoding!))
(define with-file-name->icon-url
(make-file-directory-options-transformer
set-file-directory-options-file-name->icon-url!))
(define with-blank-icon-url
(make-file-directory-options-transformer
set-file-directory-options-blank-icon-url!))
(define with-back-icon-url
(make-file-directory-options-transformer
set-file-directory-options-back-icon-url!))
(define with-unknown-icon-url
(make-file-directory-options-transformer
set-file-directory-options-unknown-icon-url!))
(define (make-file-directory-options . stuff)
(let loop ((options (make-default-file-directory-options))
(stuff stuff))
(if (null? stuff)
options
(let* ((transformer (car stuff))
(value (cadr stuff)))
(loop (transformer value options)
(cddr stuff))))))
;;; (home-dir-handler user-public-dir) -> handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Return a request handler that looks things up in a specific directory
;;; in the user's home directory. If ph = (home-dir-handler "public_html")
;;; then ph is a request handler that serves files out of peoples' public_html
;;; subdirectory. So
;;; (ph '("shivers" "hk.html") req)
;;; will serve the file
;;; ~shivers/public_html/hk.html
;;; The request handler treats the URL path as (<user> . <file-path>),
;;; serving
;;; ~<user>/<user-public-dir>/<file-path>
(define (home-dir-handler user-public-dir . maybe-options)
(let-optionals maybe-options ((options (make-default-file-directory-options)))
(lambda (path req)
(if (null? path)
(make-error-response (status-code bad-request)
req
"Path contains no home directory.")
(make-rooted-file-path-response (string-append (http-homedir (car path) req)
"/"
user-public-dir)
(cdr path)
file-serve-response
req
options)))))
;;; (tilde-home-dir-handler user-public-dir default-request-handler)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; If the car of the path is a tilde-marked home directory (e.g., "~kgk"),
;;; do home-directory service as in HOME-DIR-HANDLER, otherwise punt to the
;;; default handler.
(define (tilde-home-dir? path req)
(and (not (null? path))
(let ((head (car path))) ; home-directory path?
(and (> (string-length head) 0)
(char=? (string-ref head 0) #\~)))))
(define (tilde-home-dir-handler user-public-dir default-handler . maybe-options)
(let-optionals maybe-options ((options (make-default-file-directory-options)))
(make-predicate-handler
tilde-home-dir?
(lambda (path req)
(let* ((tilde-home (car path)) ; Yes.
(slen (string-length tilde-home))
(subdir (string-append
(http-homedir (substring tilde-home 1 slen) req)
"/"
user-public-dir)))
(make-rooted-file-path-response subdir (cdr path) file-serve-response req
options)))
default-handler)))
;;; Make a handler that serves files relative to a particular root
;;; in the file system. You may follow symlinks, but you can't back up
;;; past ROOT with ..'s.
(define (rooted-file-handler root . maybe-options)
(let-optionals maybe-options ((options (make-default-file-directory-options)))
(lambda (path req)
(make-rooted-file-path-response root path file-serve-response req options))))
;;; Dito, but also serve directory indices for directories without
;;; index.html.
(define (rooted-file-or-directory-handler root . maybe-options)
(let-optionals maybe-options ((options (make-default-file-directory-options)))
(lambda (path req)
(make-rooted-file-path-response root path
file-serve-and-dir-response
req
options))))
;;;; Support procs for the path handlers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; (MAKE-ROOTED-FILE-PATH-RESPONSE root file-path req options)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Do a request for a file. The file-name is determined by appending the
;;; the FILE-PATH list the string ROOT. E.g., if
;;; ROOT = "/usr/shivers" FILE-PATH = ("a" "b" "c" "foo.html")
;;; then we serve file
;;; /usr/shivers/a/b/c/foo.html
;;; Elements of FILE-PATH are *not allowed* to contain .. elements.
;;; (N.B.: Although the ..'s can appear in relative URI's, /foo/../ path
;;; sequences are processed away by the browser when the URI is converted
;;; to an absolute URI before it is sent off to the server.)
;;; It is possible to sneak a .. past this kind of front-end resolving by
;;; encoding it (e.g., "foo%2F%2E%2E" for "foo/.."). If the client tries
;;; this, MAKE-ROOTED-FILE-PATH-RESPONSE will catch it, and abort the transaction.
;;; So you cannot make the reference back up past ROOT. E.g., this is
;;; not allowed:
;;; FILE-PATH = ("a" "../.." "c" "foo.html")
;;;
;;; Only GET and HEAD ops are provided.
;;; The URL's <search> component must be #f.
;;; The file is served if the server has read or stat(2) access to it,
;;; respectively. If the server is run as root, this might be a problem.
;;;
;;; FILE-SERVE is a procedure which gets passed the file name, the
;;; path, and the HTTP request to serve the file propert after the
;;; security checks. Look in ROOTED-FILE-HANDLER and
;;; ROOTED-FILE-OR-DIRECTORY-HANDLER for examples on how to feed this.
(define (make-rooted-file-path-response root file-path file-serve-response req options)
(if (http-url-search (request-url req))
(make-error-response (status-code bad-request) req
"Indexed search not provided for this URL.")
(cond ((dotdot-check root file-path) =>
(lambda (fname)
(file-serve-response fname file-path req options)))
(else
(make-error-response (status-code bad-request) req
"URL contains unresolvable ..'s.")))))
;; Just (file-info fname) with error handling.
(define (stat-carefully fname req)
(with-errno-handler
((errno packet)
((errno/noent)
(http-error (status-code not-found) req))
((errno/acces)
(http-error (status-code forbidden) req)))
(file-info fname #t)))
;;; A basic file request handler -- ship the dude the file. No fancy path
;;; checking. That has presumably been taken care of. This handler only
;;; takes care of GET and HEAD methods.
(define (file-serve-or-dir-response fname file-path req directory-serve-response options)
(if (file-name-directory? fname) ; Simple index generation.
(directory-serve-response fname file-path req options)
(let ((request-method (request-method req)))
(cond
((or (string=? request-method "GET")
(string=? request-method "HEAD")) ; Absolutely.
(let ((info (stat-carefully fname req)))
(case (file-info:type info)
((regular fifo socket)
(send-file-response fname info req options))
((directory) ; Send back a redirection "foo" -> "foo/"
(make-error-response
(status-code moved-perm) req
(string-append (request-uri req) "/")
(string-append (http-url->string (request-url req))
"/")))
(else (make-error-response (status-code forbidden) req)))))
(else
(make-error-response (status-code method-not-allowed) req
request-method))))))
(define (directory-index-serve-response fname file-path req options)
(file-serve-response (string-append fname "index.html") file-path req options))
(define (file-serve-response fname file-path req options)
(file-serve-or-dir-response fname file-path req
directory-index-serve-response
options))
(define (default-file-name->icon-url fname)
#f)
(define (time->directory-index-date-string time)
(format-date "~d-~b-~y ~H:~M:~S GMT" (date time 0)))
(define (read-max-lines fname max)
(call-with-input-file
fname
(lambda (port)
(let loop ((r "") (i max))
(if (zero? i)
r
(let ((line (read-line port)))
(if (eof-object? line)
r
(loop (string-append r " " line) (- i 1)))))))))
(define (string-cut s n)
(if (>= (string-length s) n)
(substring s 0 n)
s))
(define html-file-header
(let ((title-tag-regexp (make-regexp "<[Tt][Ii][Tt][Ll][Ee]>"))
(title-close-tag-regexp (make-regexp "</[Tt][Ii][Tt][Ll][Ee]>")))
(lambda (fname n)
(let ((stuff (read-max-lines fname 10)))
(cond
((regexp-exec title-tag-regexp stuff)
=> (lambda (open-match)
(cond
((regexp-exec title-close-tag-regexp stuff
(match:end open-match 0))
=> (lambda (close-match)
(string-cut (substring stuff
(match:end open-match 0)
(match:start close-match 0))
n)))
(else (string-cut (substring stuff
(match:end open-match 0)
(string-length stuff))
n)))))
(else ""))))))
(define (file-documentation fname n options)
(cond
(((file-directory-options-file-name->content-type options) fname)
=> (lambda (content-type)
(if (and (string=? content-type "text/html" )
(file-readable? fname))
(html-file-header fname n)
"")))
(else "")))
(define (directory-index req dir port options)
(define (pad-file-name file)
(write-string (make-string (max (- 21 (string-length file))
1)
#\space)
port))
(define (emit-file-name file)
(let ((l (string-length file)))
(if (<= l 20)
(emit-text file port)
(emit-text (substring file 0 20) port))))
(define (index-entry file)
(let* ((fname (directory-as-file-name (string-append dir file)))
(info (file-info fname #t))
(type (file-info:type info))
(size (file-info:size info))
(icon-name
(case type
((regular fifo socket)
((file-directory-options-file-name->icon-url options)
fname))
((directory)
(file-directory-options-directory-icon-url options))
(else
(file-directory-options-unknown-icon-url options))))
(tag-name
(case type
((regular fifo socket) "[FILE]")
((directory) "[DIR ]")
(else "[????]"))))
(if icon-name
(emit-tag port 'img
(cons 'src icon-name)
(cons 'alt tag-name))
(display tag-name port))
(with-tag port a ((href file))
(emit-file-name file))
(pad-file-name file)
(emit-text (time->directory-index-date-string (file-info:mtime info)) port)
(if size
(let* ((size-string
(string-append (number->string (quotient size 1024))
"K"))
(size-string
(if (<= (string-length size-string) 7)
size-string
(string-append (number->string (quotient size (* 1024 1024)))
"M")))
(size-string
(if (<= (string-length size-string) 8)
(string-append
(make-string (- 8 (string-length size-string)) #\space)
size-string)
size-string)))
(write-string size-string port))
(write-string (make-string 8 #\space) port))
(write-char #\space port)
(emit-text (file-documentation fname 24 options) port)
(write-crlf port)))
(let ((files (directory-files dir)))
(for-each index-entry files)
(length files)))
(define (directory-serve-response fname file-path req options)
(let ((request-method (request-method req)))
(cond
((or (string=? request-method "GET")
(string=? request-method "HEAD"))
(if (not (eq? 'directory
(file-info:type (file-info fname #t))))
(make-error-response (status-code forbidden) req)
(make-response
(status-code ok)
#f
(time)
"text/html"
'()
(make-writer-body
(lambda (port httpd-options)
(let ((back-icon
(file-directory-options-back-icon-url options))
(blank-icon
(file-directory-options-blank-icon-url options)))
(with-tag port html ()
(let ((title (string-append "Index of /"
(string-join file-path "/"))))
(with-tag port head ()
(emit-title port title))
(with-tag port body ()
(emit-header port 1 title)
(with-tag port pre ()
(if blank-icon
(display "[ ]" port)
(emit-tag port 'img
(cons 'src blank-icon)
(cons 'alt " ")))
(write-string "Name " port)
(write-string "Last modified " port)
(write-string "Size " port)
(write-string "Description" port)
(emit-tag port 'hr)
(if back-icon
(emit-tag port 'img
(cons 'src back-icon)
(cons 'alt "[UP ]"))
(display "[UP ]" port))
(if (not (null? file-path))
(begin
(with-tag port a ((href ".."))
(write-string "Parent directory" port))
(write-crlf port)))
(let ((n-files (directory-index req fname port options)))
(emit-tag port 'hr)
(format port "~d files" n-files))))))))))))
(else
(make-error-response (status-code method-not-allowed) req
request-method)))))
(define (index-or-directory-serve-response fname file-path req options)
(let ((index-fname (string-append fname "index.html")))
(if (file-readable? index-fname)
(file-serve-response index-fname file-path req options)
(directory-serve-response fname file-path req options))))
(define (file-serve-and-dir-response fname file-path req options)
(file-serve-or-dir-response fname file-path req
index-or-directory-serve-response
options))
;;; Look up user's home directory, generating an HTTP error response if you lose.
(define (http-homedir username req)
(with-fatal-error-handler (lambda (c decline)
(apply http-error (status-code bad-request) req
"Couldn't find user's home directory."
(condition-stuff c)))
(home-dir username)))
(define (send-file-response filename info req options)
(if (file-not-readable? filename) ; #### double stats are no good
(make-error-response (status-code not-found) req)
(receive (stripped-filename content-encoding)
((file-directory-options-file-name->content-encoding options) filename)
(make-response (status-code ok)
#f
(time)
((file-directory-options-file-name->content-type options)
stripped-filename)
(append (if content-encoding
(list (cons 'content-encoding content-encoding))
'())
(list
(cons 'last-modified
(rfc822-time->string
(file-info:mtime info)))
(cons 'content-length (file-info:size info))))
(make-writer-body
(lambda (port options)
(call-with-input-file filename
(lambda (in)
(copy-inport->outport in port)))))))))
(define (default-file-name->content-type fname)
(let ((ext (file-name-extension fname)))
(cond
((string-ci=? ext ".htm") "text/html")
((string-ci=? ext ".html") "text/html")
((string-ci=? ext ".txt") "text/plain")
((string-ci=? ext ".css") "text/css")
((string-ci=? ext ".doc") "application/msword")
((string-ci=? ext ".gif") "image/gif")
((string-ci=? ext ".png") "image/png")
((string-ci=? ext ".bmp") "image/bmp")
((or (string-ci=? ext ".jpg")
(string-ci=? ext ".jpeg")) "image/jpeg")
((or (string-ci=? ext ".tiff")
(string-ci=? ext ".tif")) "image/tif")
((string-ci=? ext ".rtf") "text/rtf")
((or (string-ci=? ext ".mpeg")
(string-ci=? ext ".mpg")) "video/mpeg")
((or (string-ci=? ext ".au")
(string-ci=? ext ".snd")) "audio/basic")
((string-ci=? ext ".wav") "audio/x-wav")
((string-ci=? ext ".dvi") "application/x-dvi")
((or (string-ci=? ext ".tex")
(string-ci=? ext ".latex")) "application/latex")
((string-ci=? ext ".zip") "application/zip")
((string-ci=? ext ".tar") "application/tar")
((string-ci=? ext ".hqx") "application/mac-binhex40")
((string-ci=? ext ".ps") "application/postscript")
((string-ci=? ext ".pdf") "application/pdf")
(else "application/octet-stream"))))
(define (default-file-name->content-encoding fname)
(cond
((let ((ext (file-name-extension fname)))
(cond
((string-ci=? ext ".Z") "x-compress")
((string-ci=? ext ".gz") "x-gzip")
(else #f)))
=> (lambda (encoding)
(values (file-name-sans-extension fname) encoding)))
(else (values fname #f))))

View File

@ -1,99 +0,0 @@
;;; http server in the Scheme Shell -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1995 by Olin Shivers.
;;; Copyright (c) 1996-2002 by Mike Sperber.
;;; Copyright (c) 2002 by Andreas Bernauer.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;; Path handlers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Path handlers are the guys that actually perform the requested operation
;;; on the URL. The handler interface is
;;; (handler path-list request)
;;; The path-list is a URL path list that is a suffix of REQUEST's url's
;;; path-list. Path handlers can decide how to handle an operation by
;;; recursively keying off of the elements in path-list.
;;;
;;; The object-oriented view:
;;; One way to look at this is to think of the request's METHOD as a
;;; generic operation on the URL. Recursive request handlers do method
;;; lookup to determine how to implement a given operation on a particular
;;; path.
;;;
;;; The REQUEST is a request record, as defined in httpd-core.scm, containing
;;; the details of the client request.
;; general request handler combinator:
;; predicate: path x request --> boolean
;; if #t, handler is called
;; if #f, default-handler is called
(define (make-predicate-handler predicate handler default-handler)
(lambda (path req)
(if (predicate path req)
(handler path req)
(default-handler path req))))
;; same as MAKE-PREDICATE-HANDLER except that the predicate is only
;; called with the path:
;; predicate: path --> boolean
(define (make-path-predicate-handler predicate handler default-handler)
(make-predicate-handler
(lambda (path req) (predicate path)) handler default-handler))
;; selects handler according to host-field of http-request
(define (make-host-name-handler hostname handler default-handler)
(make-predicate-handler
(lambda (path req)
;; we expect only one host-header-field
(let ((body (string-trim (get-header (request-headers req) 'host))))
(or (string-ci=? hostname body)
(string-prefix-ci? (string-append hostname ":") body))))
handler default-handler))
(define (get-header headers tag)
(cond
((assq tag headers) => cdr)
(else
(http-error (status-code bad-request) #f
(string-append "Request did not contain "
(symbol->string tag)
" header")))))
;; selects handler according to path-prefix
;; if path-prefix matches, handler is called without the path-prefix
(define (make-path-prefix-handler path-prefix handler default-handler)
(lambda (path req)
(if (and (pair? path) (string=? path-prefix (car path)))
(handler (cdr path) req)
(default-handler path req))))
;;; (alist-path-dispatcher handler-alist default-handler) -> handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This function creates a table-driven request handler that dispatches off
;;; of the car of the request path. The handler uses the car to index into
;;; a request handler alist. If it finds a hit, it recurses using the table's
;;; request handler. If no hits, it handles the path with a default handler.
;;; An alist handler is passed the tail of the original path; the
;;; default handler gets the entire original path.
;;;
;;; This procedure is how you say: "If the first element of the URL's
;;; path is 'foo', do X; if it's 'bar', do Y; otherwise, do Z."
(define (alist-path-dispatcher handler-alist default-handler)
(fold-right
(lambda (handler-pair default-handler)
(make-path-prefix-handler
(car handler-pair)
(cdr handler-pair)
default-handler))
default-handler
handler-alist))
;;; The null request handler -- handles nothing, sends back an error response.
;;; Can be useful as the default in table-driven request handlers.
(define (null-request-handler path req)
(make-error-response (status-code not-found) req))

View File

@ -1,661 +0,0 @@
;;; GNU info -> HTML gateway for the SU web server. -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1996 by Mike Sperber.
;;; based on code with the same purpose by Gaebe Engelhart
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;; (info-handler parse-info reference find-icon address) -> handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; This function creates a request handler that converts GNU info pages
;;; on-the-fly. It is highly parameterizable to accomodate a wide
;;; range of environments. The parameters specify how to find the
;;; source code for the info pages, and how to generate certain
;;; elements of the generated HTML output.
;;;
;;; PARSE-INFO specifies how to parse the URLs that end up in the
;;; handler.
;;; It can be:
;;;
;;; * a procedure which is called with the URL as its parameters.
;;; It is expected to return with two values, FIND-ENTRY and
;;; NODE-NAME. FIND-ENTRY, in turn, can be either a procedure
;;; which gets passed the file name of an info node and is
;;; supposed to return with an absolute name of same. If it is a
;;; list, that list is taken as a list of directories in which to
;;; search for the info files. NODE-NAME is supposed to be the
;;; name of an info node of the form (<file>)<node>, extracted
;;; from the URL.
;;;
;;; * a list, in which case that is taken as a list of
;;; directories in which to search for the info files. The node
;;; name of a node is extracted from the URL by just taking the
;;; search component of the URL.
;;;
;;; * #f in which case the info path is taken from the environment
;;; variable INFOPATH, and the node name extraction works as
;;; above.
;;;
;;; REFERENCE specifies how to generate cross-references to other info
;;; nodes. It can be:
;;;
;;; * a procedure which gets called with the URL of the info page
;;; which contains the reference, and the node name of the node
;;; to be referenced. The procedure is expected to return the
;;; text for a link.
;;;
;;; * a string, in which case that is to be a prefix to which the
;;; node name is simply appended to yield the new link.
;;;
;;; * #f in which case all references have the form
;;; info?<node-name>.
;;;
;;; FIND-ICON specifies to to find the various icons used to decorate
;;; info pages. It can be:
;;;
;;; * a procedure which gets passed one of the tags in
;;; DEFAULT-ICON-ALIST and is supposed to return a link for the
;;; appropriate icon (or #f if no icon is to be used)
;;;
;;; * a string which is taken as a prefix to which one of the
;;; appropriate icon name from DEFAULT-ICON-ALIST is appended.
;;; (Note that these icon names were stolen from the
;;; cern-httpd-3.0 distribution at
;;; http://www.w3.org/pub/WWW/Daemon/.)
;;;
;;; * a list which is taken as an alist of the same format as
;;; DEFAULT-ICON-ALIST.
;;;
;;; * #f in which case no icons are used.
;;;
;;; ADDRESS a string to be appended at the bottom of all info pages
;;;
;;; To install a vanilla info handler for a prefix "info?" that looks
;;; in the environment variable INFOPATH, just use something like
;;; (info-handler #f #f #f "Generated by info-gateway")
;;; TODO: write a CGI version of this
(define-condition-type 'info-gateway-error '(error))
(define info-gateway-error? (condition-predicate 'info-gateway-error))
(define (info-gateway-error msg . irritants)
(apply signal 'info-gateway-error msg irritants))
(define default-icon-alist
'((info . "infodoc.gif")
(up . "up.gif")
(next . "next.gif")
(previous . "prev.gif")
(menu . "menu.gif")))
(define (info-handler parse-info reference find-icon address)
(let ((icon-name
(cond
((procedure? find-icon) find-icon)
((string? find-icon)
(let ((alist
(map (lambda (entry)
(cons (car entry)
(string-append find-icon (cdr entry))))
default-icon-alist)))
(lambda (tag)
(cond ((assq tag alist) => cdr)
(else #f)))))
((list? find-icon)
(lambda (tag)
(cond ((assq tag find-icon) => cdr)
(else #f))))
(else (lambda (tag) #f))))
(parse-info-url
(cond
((procedure? parse-info) parse-info)
((list? parse-info) ; it's an info path
(lambda (url)
(values parse-info
(unescape-uri (http-url-search url)))))
(else
(let ((info-path
((infix-splitter ":")
(or (getenv "INFOPATH")
(begin
(format (current-error-port)
"~%Warning: environment variable INFOPATH is unset.~%")
"")))))
(lambda (url)
(values info-path
(unescape-uri (http-url-search url))))))))
(make-reference
(cond
((procedure? reference) reference)
((string? reference)
(lambda (url node-name)
(string-append reference node-name)))
(else
(lambda (url node-name)
(string-append "info?" node-name))))))
(lambda (path req)
(let ((request-method (request-method req)))
(cond
((string=? request-method "GET")
(with-fatal-error-handler
(lambda (c decline)
(cond
((info-gateway-error? c)
(apply http-error (status-code bad-gateway) req
(condition-stuff c)))
((http-error? c)
(apply http-error (car (condition-stuff c)) req
(cddr (condition-stuff c))))
(else
(decline))))
(make-response
(status-code ok)
#f
(time)
"text/html"
'()
(make-writer-body
(lambda (out options)
(receive (find-entry node-name) (parse-info-url (request-url req))
(display-node node-name
(file-finder find-entry)
(referencer make-reference (request-url req) out)
icon-name
out))
(with-tag out address ()
(write-string address out)))))))
(else
(make-error-response (status-code method-not-allowed) req
request-method)))))))
(define split-header-line
(let ((split (infix-splitter (make-regexp "(, *)|( +)|( *\t *)")))
(split-field (infix-splitter (make-regexp ": *"))))
(lambda (l)
(let ((fields (map split-field (split l))))
(define (search-field regexp)
(cond
((find (lambda (field)
(string-match regexp (car field)))
fields)
=> cadr)
(else #f)))
(values (search-field "[F|f]ile")
(search-field "[N|n]ode")
(search-field "[U|u]p")
(search-field "[P|p]rev(ious)?")
(search-field "[N|n]ext"))))))
(define (replace-if-empty-string s v)
(if (zero? (string-length s))
v
s))
(define (string-newline->space s)
(string-map (lambda (c)
(if (char=? c #\newline)
#\space
c))
s))
(define (parse-node-name node-name)
(cond
((string-match "^\\((.*)\\)(.*)$" (string-newline->space node-name))
=> (lambda (match)
(values
(replace-if-empty-string (match:substring match 1) #f)
(replace-if-empty-string (match:substring match 2) "Top"))))
(else (values #f (string-newline->space node-name)))))
(define (unparse-node-name file node)
(let* ((ext (file-name-extension file))
(file (if (string=? ext ".info")
(file-name-sans-extension file)
file)))
(receive (file node) (if (and (string=? "dir" file)
(not (string=? "" node))
(not (string=? "Top" node)))
(values node "Top")
(values file node))
(string-append "(" file ")" node))))
(define (display-icon file alt out)
(emit-tag out 'img
(cons 'src file)
(cons 'alt alt)
(cons 'align "bottom")))
(define (referencer make-reference old-entry out)
(lambda (file node-name label . maybe-icon)
(receive (node-file node) (parse-node-name node-name)
(let ((file (or node-file file)))
(with-tag out a ((href (make-reference
old-entry
(escape-uri (unparse-node-name file node)))))
(if (and (not (null? maybe-icon))
(car maybe-icon))
(display-icon (car maybe-icon) (cadr maybe-icon) out))
(emit-text label out))))))
(define node-prologue (ascii->char 31))
(define node-epilogue-regexp
(make-regexp
(string-append (regexp-quote (string node-prologue))
"|"
(regexp-quote (string (ascii->char 12))))))
(define (string-starts-with-char? s c)
(and (not (zero? (string-length s)))
(char=? c (string-ref s 0))))
(define (node-prologue? s)
(string-starts-with-char? s node-prologue))
(define (node-epilogue? s)
(regexp-exec node-epilogue-regexp s))
;; Document title
(define (display-title file node up previous next
display-reference icon-name out)
(define (maybe-display-header header icon alt)
(if header
(begin
(newline out)
(with-tag out b ()
(display-reference file header header icon alt)))))
(emit-title out (string-append "Info Node: "
(unparse-node-name file node)))
(with-tag out h1 ()
(emit-tag out 'img
(cons 'src (icon-name 'info))
(cons 'alt "Info Node")
(cons 'align 'bottom))
(write-string (unparse-node-name file node) out))
(emit-tag out 'hr)
(maybe-display-header next (icon-name 'next) "[Next]")
(maybe-display-header previous (icon-name 'previous) "[Previous]")
(maybe-display-header up (icon-name 'up) "[Up]")
(if (or next previous up)
(emit-tag out 'hr)))
;; Text
;; Dealing with cross references
;; info sucks
(define xref-marker-regexp (make-regexp "\\*[Nn]ote([ \n]|$)"))
(define xref-regexp (make-regexp "\\*[Nn]ote *([^:]*): *([^\t\n,.;:?!]*)"))
(define max-xref-lines 3)
(define complete-line
(let ((split-xref-markers (field-splitter xref-marker-regexp))
(split-xrefs (field-splitter xref-regexp))
(cr (string #\newline)))
(lambda (line port)
(let loop ((line line) (count max-xref-lines))
(let ((xref-markers (split-xref-markers line))
(xrefs (split-xrefs line)))
(if (= (length xref-markers) (length xrefs))
line
(if (zero? count)
(info-gateway-error "invalid cross reference")
(let ((new-line (read-line port)))
(if (eof-object? new-line)
(info-gateway-error
"unexpected end of info file inside cross reference"))
(loop (string-append line cr new-line) (- count 1))))))))))
(define (display-xref xref file display-reference out)
(let* ((match (regexp-exec xref-regexp xref))
(note (match:substring match 1))
(node-name (match:substring match 2))
(node-name (if (string=? "" node-name) note node-name))
(node-name (substring node-name
(string-skip node-name char-set:whitespace)
(string-length node-name))))
(emit-text "See " out)
(display-reference file node-name note)))
(define display-text
(let ((split-xrefs (infix-splitter xref-regexp #f 'split)))
(lambda (line port file display-reference out)
(let* ((line (complete-line line port))
(components (split-xrefs line)))
;; in components, every 2nd element is a cross reference
;; also, it always has odd length or length zero
(if (not (null? components))
(let loop ((components components))
(emit-text (car components) out)
(if (not (null? (cdr components)))
(begin
(display-xref (cadr components) file display-reference out)
(loop (cddr components))))))
(newline out)))))
;; Menus
(define menu-regexp (make-regexp "^\\* +Menu:"))
(define menu-item-regexp (make-regexp "^\\* +"))
(define (char-splitter c)
(lambda (s)
(cond ((string-index s c)
=> (lambda (i)
(values (substring s 0 i)
(substring s (+ 1 i) (string-length s)))))
(else (values s "")))))
(define colon-split (char-splitter #\:))
(define (display-menu-item-header line port file display-reference icon-name out)
(let ((menu-line-split (infix-splitter menu-item-regexp)))
(receive (note rest) (colon-split (cadr (menu-line-split line)))
(receive (node-name text)
(cond
((string-match ": *(.*)" rest)
=> (lambda (match)
(values note (match:substring match 1))))
((string-match "^ *([^.]*)\\.? *(.*)" rest)
=> (lambda (match)
(values (match:substring match 1)
(match:substring match 2))))
(else
(info-gateway-error "invalid menu item")))
(emit-tag out 'dt)
(display-reference file node-name note (icon-name 'menu) "*")
(newline out)
(if (and (not (string=? "" text))
(not (string=? "." text)))
(begin
(emit-tag out 'dd)
(display-text text port file display-reference out)))))))
(define (display-menu line port file display-reference icon-name out)
(emit-close-tag out 'pre)
(with-tag out dl ()
(let loop ((line line))
(if (eof-object? line)
(info-gateway-error "unexpected end of info file"))
(display-menu-item-header line port file display-reference icon-name out)
(let finish-item-loop ()
(if (eof-object? line)
(info-gateway-error "unexpected end of info file"))
(let ((line (read-line port)))
(cond
((or (eof-object? line)
(node-epilogue? line)
(string=? "" line))
(emit-tag out 'pre)
(dispatch-line line port file display-reference icon-name out))
((regexp-exec menu-item-regexp line)
(loop line))
(else
(display-text line port file display-reference out)
(finish-item-loop))))))))
;; Central dispatch
(define (dispatch-line line port file display-reference icon-name out)
(cond
((or (eof-object? line) (node-epilogue? line)) #f)
((string=? "" line) (emit-p out) #t)
((regexp-exec menu-regexp line) #t) ;; this should probably be expanded
((regexp-exec menu-item-regexp line)
(display-menu line port file display-reference icon-name out))
(else
(display-text line port file display-reference out) #t)))
(define (display-body port file display-reference icon-name out)
(let loop ()
(let ((line (read-line port)))
(if (dispatch-line line port file display-reference icon-name out)
(loop)))))
(define (display-node node-name find-file display-reference icon-name out)
(receive (file node) (parse-node-name node-name)
(receive (port file-header node-header up-header prev-header next-header)
(find-node file node find-file)
(with-tag out html ()
(with-tag out head ()
(display-title file node-header up-header
prev-header next-header
display-reference icon-name
out))
(with-tag out body ()
(with-tag out pre ()
(display-body port file display-reference icon-name out))))
(close-input-port port))))
;; Finding nodes
(define (ensure-node-prologue port msg)
(let ((line (read-line port)))
(if (or (eof-object? line)
(not (node-prologue line)))
(info-gateway-error "invalid info file" msg))))
(define (ensure-regexp-line port regexp msg)
(let ((line (read-line port)))
(if (or (eof-object? line)
(not (string-match regexp line)))
(info-gateway-error "invalid info file" msg))))
(define (ensure-tag-table-node port)
(ensure-regexp-line port "^Tag Table:" "no tag table"))
(define (ensure-indirect-tag-table-header port)
(ensure-regexp-line port "^\\(Indirect\\)" "no indirect tag"))
(define split-indirection (infix-splitter (make-regexp " *: *")))
(define (parse-indirection line)
(let ((l (split-indirection line)))
(if (null? (cdr l))
(info-gateway-error "invalid indirection entry in info file")
(let ((file (car l))
(seek-pos (string->number (cadr l))))
(if (not seek-pos)
(info-gateway-error "invalid indirection entry in info file"))
(cons file seek-pos)))))
(define (read-indirection-table port)
(let loop ((table '()))
(let ((line (read-line port)))
(if (eof-object? line)
(info-gateway-error "invalid info file"))
(if (node-epilogue? line)
(reverse table)
(loop (cons (parse-indirection line) table))))))
(define tag-seek-separator (ascii->char 127))
(define parse-tag
(let ((split (infix-splitter (make-regexp ", *")))
(split-field (infix-splitter ": "))
(split-node-info
(infix-splitter (string tag-seek-separator))))
(define (barf)
(info-gateway-error "invalid tag entry in info file"))
(lambda (line)
(let* ((fields (map split-field (split line)))
(file (cond
((assoc "File" fields)
=> (lambda (p)
(if (null? (cdr p)) (barf))
(cadr p)))
(else #f))))
(cond
((assoc "Node" fields)
=> (lambda (p)
(if (null? (cdr p)) (barf))
(let ((s (split-node-info (cadr p))))
(if (null? (cdr p)) (barf))
(let* ((node (car s))
(seek (string->number (cadr s))))
(if (not seek) (barf))
(values node file seek)))))
(else (barf)))))))
(define (find-tag node port)
(let loop ()
(let ((line (read-line port)))
(if (eof-object? line)
(info-gateway-error "invalid info file"))
(if (regexp-exec node-epilogue-regexp line)
(http-error (status-code not-found) #f "node not found"))
(receive (entry-node file seek) (parse-tag line)
(if (string=? node entry-node)
(cons file seek)
(loop))))))
(define (find-indirection-entry seek-pos indirection-table)
(let loop ((table indirection-table))
(if (null? table)
(http-error (status-code not-found) #f "node not found"))
(let* ((entry (car table))
(pos (cdr entry)))
(if (and (>= seek-pos pos)
(or (null? (cdr table))
(let* ((next-entry (cadr table))
(next-pos (cdr next-entry)))
(< seek-pos next-pos))))
entry
(loop (cdr table))))))
(define (file-finder with)
(cond ((procedure? with) with)
((list? with)
(lambda (file)
(find-info-file file with)))))
(define (find-node-port-with-tag-entry node tag-entry ? find-file)
(let* ((port (if (input-port? ?) ? #f))
(indirection-table (if port #f ?))
(seek-pos (cdr tag-entry))
(indirection-entry
(and indirection-table
(find-indirection-entry seek-pos indirection-table)))
(seek-pos (if indirection-entry
(- seek-pos (cdr indirection-entry))
seek-pos))
;; that's what the documentation says ...
(seek-pos (if (>= seek-pos 1000)
(- seek-pos 1000)
0))
(file (or (car tag-entry)
(and indirection-entry
(car indirection-entry))))
(port (if file
(begin
(if port (close-input-port port))
(open-input-file (find-file file)))
port)))
(seek port seek-pos)
port))
(define (find-node file node find-file)
(if (not file)
(http-error (status-code not-found) #f
"no file in info node specification"))
(let* ((fname (find-file file))
(port (open-input-file fname)))
(let loop ((port port))
(let ((line (read-line port)))
(if (eof-object? line)
(http-error (status-code not-found) #f "info node not found"))
(if (node-prologue? line)
(let ((header (read-line port)))
(if (eof-object? header)
(info-gateway-error "invalid info file"))
(cond
((string-match "^Indirect:" header)
(let ((indirection-table
(read-indirection-table port)))
(ensure-tag-table-node port)
(ensure-indirect-tag-table-header port)
(let ((tag-entry (find-tag node port)))
(close-input-port port)
(loop (find-node-port-with-tag-entry
node tag-entry indirection-table find-file)))))
((string-match "^Tag Table:" header)
(let ((tag-entry (find-tag node port)))
(loop (find-node-port-with-tag-entry
node tag-entry port find-file))))
((string-match "^File:" header)
(receive
(file-header node-header up-header prev-header next-header)
(split-header-line header)
(if (string=? node-header node)
(values port
file-header node-header
up-header prev-header next-header)
(loop port))))
(else (loop port))))
(loop port))))))
;; Finding files
(define (info-file-alternative-names file)
(receive (dir base ext) (parse-file-name file)
(let* ((base
(cond ((string-match "(.*)-info$" base)
=> (lambda (match)
(match:substring match 1)))
(else base)))
(base-ci (string-map char-downcase base))
(alts-1 (if (string=? base base-ci)
(list base)
(list base base-ci)))
(alts (append alts-1
(map (lambda (base)
(string-append base ".info"))
alts-1)))
(alts (append alts
(map (lambda (base)
(string-append base "-info"))
alts-1)))
(alts (map (lambda (f) (string-append dir f)) alts))
(alts (cons file alts)))
alts)))
(define (find-info-file file info-path)
(let ((alts (info-file-alternative-names file)))
(let path-loop ((path info-path))
(if (null? path)
(http-error (status-code not-found) #f "info file not found"))
(let alt-loop ((alts alts))
(if (null? alts)
(path-loop (cdr path))
(let ((try (string-append (file-name-as-directory (car path))
(car alts))))
(if (file-exists? try)
try
(alt-loop (cdr alts)))))))))

View File

@ -1,196 +0,0 @@
;;; logging.scm
;;; logging functionality for web server
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 2002 by Martin Gasbichler.
;;; Copyright (c) 2002 by Andreas Bernauer.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
(define do-nothing-proc (lambda a #f))
(define-record-type logging :logging
(really-make-logging log-port log-proc
syslog? syslog-proc
dns-lookup?)
logging?
;; port to perform CLF-logging
(log-port logging-log-port set-logging-log-port!)
;; proc to run for CLF-logging (req status-code)
(log-proc logging-log-proc set-logging-log-proc!)
;; do syslogging?
(syslog? logging-syslog? set-logging-syslog?!)
;; proc to run for syslog (level fmt . args)
(syslog-proc logging-syslog-proc set-logging-syslog-proc!)
;; perform dns lookups?
(dns-lookup? logging-dns-lookup? set-logging-dns-lookup?!))
(define (make-logging)
(really-make-logging #f
do-nothing-proc
#f
do-nothing-proc
#f))
(define logging (make-preserved-thread-fluid #f))
(define (make-fluid-selector selector)
(lambda () (selector (thread-fluid logging))))
(define (make-fluid-setter setter)
(lambda (value)
(setter (thread-fluid logging) value)))
(define logging-http-log-proc (make-fluid-selector logging-log-proc))
(define logging-http-syslog-proc (make-fluid-selector logging-syslog-proc))
(define logging-http-syslog? (make-fluid-selector logging-syslog?))
(define logging-http-log-port (make-fluid-selector logging-log-port))
(define logging-dns-lookup? (make-fluid-selector logging-dns-lookup?))
(define set-logging-http-log-proc (make-fluid-setter set-logging-log-proc!))
(define set-logging-http-syslog-proc (make-fluid-setter set-logging-syslog-proc!))
(define set-logging-http-syslog? (make-fluid-setter set-logging-syslog?!))
(define set-logging-http-log-port (make-fluid-setter set-logging-log-port!))
(define set-logging-dns-lookup? (make-fluid-setter set-logging-dns-lookup?!))
(define http-syslog
(lambda a
(apply (logging-http-syslog-proc) a)))
(define http-log
(lambda a
(apply (logging-http-log-proc) a)))
(define (http-syslog?)
(logging-http-syslog?))
(define (init-http-log! options)
;; syslog has to be initialized before CLF-logging
;; because the latter may generate syslog-messages
(init-http-syslog! (httpd-options-syslog? options))
(init-http-port-log! (httpd-options-log-file options))
(if (httpd-options-resolve-ips? options)
(set-logging-dns-lookup? #t)
(set-logging-dns-lookup? #f)))
(define (init-http-syslog! syslog?)
(if syslog?
(let ((http-syslog-lock (make-lock)))
(set-logging-http-syslog? #t)
(set-logging-http-syslog-proc
(lambda (level fmt . args)
(with-lock http-syslog-lock
(lambda ()
(syslog level
(apply format #f fmt args)))))))
(begin
(set-logging-http-syslog? #f)
(set-logging-http-syslog-proc do-nothing-proc))))
(define (init-http-port-log! log-file)
(let ((logport
(cond
((string? log-file) ; try to open log-file for appending (output)
(open-log-file log-file))
((output-port? log-file) ; we were given an output port, so let's use it
log-file)
((eq? log-file #f) ; no logging demanded
#f)
; unexpected value of log-file;
(else
(http-syslog
(syslog-level warning)
"[httpd] Warning: Log-File was not specified correctly (given: ~S).~% No CLF logging."
log-file)
(make-null-output-port)))))
(if log-file ; if logging was specified, set up the logger
(let ((http-log-lock (make-lock)))
(set-logging-http-log-port logport)
(if (string? log-file)
(spawn (make-log-file-rotator log-file http-log-lock)))
(set-logging-http-log-proc (make-http-log-proc http-log-lock))))))
(define (make-http-log-proc http-log-lock)
(lambda (req status-code)
(if req
(with-lock http-log-lock
(lambda ()
(display (make-CLF
(receive (host-address _)
(socket-address->internet-address
(socket-remote-address (request-socket req)))
(format-internet-host-address host-address))
(request-method req) ; request method
(uri-path->uri
(http-url-path (request-url req))) ; requested file
(version->string (request-version req)) ; protocol version
(status-code-number status-code)
23 ; filesize (unknown)
(get-header (request-headers req) 'referer)
(get-header (request-headers req) 'user-agent))
(logging-http-log-port))
(force-output (logging-http-log-port)))))))
(define (get-header headers tag)
(cond
((assq tag headers) => cdr)
(else #f)))
;; does the log-file rotation on signal USR1
(define (make-log-file-rotator log-file http-log-lock)
(set-interrupt-handler interrupt/usr1 #f)
(lambda ()
(on-interrupt
interrupt/usr1
(lambda ()
(with-lock http-log-lock
(lambda ()
(close-output-port (logging-http-log-port))
(set-logging-http-log-port (open-log-file log-file))))))))
(define (open-log-file log-file)
(with-errno-handler*
(lambda (errno packet)
(http-syslog (syslog-level warning)
"[httpd] Warning: An error occurred while opening ~S for writing (~A).~%Send signal USR1 when the problem is fixed.~%"
log-file
(car packet))
(make-null-output-port))
(lambda ()
(open-output-file log-file
(bitwise-ior open/create open/append)))))
; returns a string for a CLF entry (Common Log Format)
; note: till now, we do not log the user's time zone code
(define (make-CLF remote-ip request-type requested-file protocol http-code filesize referer user-agent)
(format #f "~A - - ~A ~S ~A ~A ~S ~S~%"
(or (maybe-dns-lookup remote-ip) "-")
(format-date "[~d/~b/~Y:~H:~M:~S +0000]" (date)) ; +0000 as we don't know
(string-join (list request-type
(string-append "/" requested-file)
protocol))
; Unfortunately, we first split the request line into
; method/request-type etc. and put it together here.
; Files conform to CLF are expected to print the original line.
(or http-code "-")
(or filesize "-")
(if (string? referer) (string-trim referer) '-)
(if (string? user-agent)
(string-trim user-agent char-set:whitespace)
'-)))
(define (maybe-dns-lookup remote-ip)
(if (logging-dns-lookup?)
(or (with-fatal-error-handler*
(lambda (condition decline)
(http-syslog (syslog-level debug)
"An error occurred while resolving IP ~A: ~A"
remote-ip condition)
remote-ip)
(lambda ()
(dns-lookup-ip remote-ip)))
remote-ip)
remote-ip))

View File

@ -1,131 +0,0 @@
;;; http server in the Scheme Shell -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 2002 by Mike Sperber.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;; This package manages options to the http server as an abstract
;;; data type.
(define-record-type httpd-options :httpd-options
(really-make-httpd-options port
root-directory
fqdn
reported-port
request-handler
server-admin
simultaneous-requests
log-file
syslog?
resolve-ips?
post-bind-thunk)
httpd-options?
(port httpd-options-port
set-httpd-options-port!)
(root-directory httpd-options-root-directory
set-httpd-options-root-directory!)
(fqdn httpd-options-fqdn
set-httpd-options-fqdn!)
(reported-port httpd-options-reported-port
set-httpd-options-reported-port!)
(request-handler httpd-options-request-handler
set-httpd-options-request-handler!)
(server-admin httpd-options-server-admin
set-httpd-options-server-admin!)
(simultaneous-requests httpd-options-simultaneous-requests
set-httpd-options-simultaneous-requests!)
(log-file httpd-options-log-file set-httpd-options-log-file!)
(syslog? httpd-options-syslog? set-httpd-options-syslog?!)
(resolve-ips? httpd-options-resolve-ips? set-httpd-options-resolve-ips?!)
(post-bind-thunk httpd-options-post-bind-thunk set-httpd-options-post-bind-thunk!))
; default httpd-options generation
(define (make-default-httpd-options)
(really-make-httpd-options 80 ; port
"/" ; root-directory
#f ; fqdn
#f ; reported-port
#f ; request-handler
#f ; server-admin
#f ; simultaneous-requests
#f
; string: filename of log-file (directory must exist)
; output-port: log to this port (e.g. (current-error-port))
; #f: no logging
#t ; Do syslogging?
#t ; Write host names instead of IPs in log-files?
#f)) ; post-bind-thunk
; creates a copy of a given httpd-option
(define (copy-httpd-options options)
(let ((new-options (make-default-httpd-options)))
(set-httpd-options-port! new-options
(httpd-options-port options))
(set-httpd-options-root-directory! new-options
(httpd-options-root-directory options))
(set-httpd-options-fqdn! new-options
(httpd-options-fqdn options))
(set-httpd-options-reported-port! new-options
(httpd-options-reported-port options))
(set-httpd-options-request-handler! new-options
(httpd-options-request-handler options))
(set-httpd-options-server-admin! new-options
(httpd-options-server-admin options))
(set-httpd-options-simultaneous-requests!
new-options
(httpd-options-simultaneous-requests options))
(set-httpd-options-log-file! new-options (httpd-options-log-file options))
(set-httpd-options-syslog?! new-options (httpd-options-syslog? options))
(set-httpd-options-resolve-ips?! new-options (httpd-options-resolve-ips? options))
(set-httpd-options-post-bind-thunk! new-options
(httpd-options-post-bind-thunk options))
new-options))
; (make-httpd-options-transformer set-option!) -> lambda (new-value [httpd-option])
; creates a transformer for httpd-options
; the returned procedure is called with the new value for the option
; and optionally with the httpd-option to change
(define (make-httpd-options-transformer set-option!)
(lambda (new-value . stuff)
(let ((new-options (if (not (null? stuff))
(copy-httpd-options (car stuff))
(make-default-httpd-options))))
(set-option! new-options new-value)
new-options)))
; several transformers for port, root-directory, etc.
(define with-port
(make-httpd-options-transformer set-httpd-options-port!))
(define with-root-directory
(make-httpd-options-transformer set-httpd-options-root-directory!))
(define with-fqdn
(make-httpd-options-transformer set-httpd-options-fqdn!))
(define with-reported-port
(make-httpd-options-transformer set-httpd-options-reported-port!))
(define with-request-handler
(make-httpd-options-transformer set-httpd-options-request-handler!))
(define with-server-admin
(make-httpd-options-transformer set-httpd-options-server-admin!))
(define with-simultaneous-requests
(make-httpd-options-transformer set-httpd-options-simultaneous-requests!))
(define with-log-file
(make-httpd-options-transformer set-httpd-options-log-file!))
(define with-syslog?
(make-httpd-options-transformer set-httpd-options-syslog?!))
(define with-resolve-ips?
(make-httpd-options-transformer set-httpd-options-resolve-ips?!))
(define with-post-bind-thunk
(make-httpd-options-transformer set-httpd-options-post-bind-thunk!))
(define (make-httpd-options . stuff)
(let loop ((options (make-default-httpd-options))
(stuff stuff))
(if (null? stuff)
options
(let* ((transformer (car stuff))
(value (cadr stuff)))
(loop (transformer value options)
(cddr stuff))))))

View File

@ -1,47 +0,0 @@
;;;; HTTP request
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1996 by Olin Shivers.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;;; This code defines the http REQUEST data structure
(define-record-type request :request
(make-request method uri url version headers socket)
request?
(method request-method) ; A string such as "GET", "PUT", etc.
(uri request-uri) ; The escaped URI string as read from request line.
(url request-url) ; An http URL record (see url.scm).
(version request-version) ; A (major . minor) integer pair.
(headers request-headers) ; An rfc822 header alist (see rfc822.scm).
(socket request-socket)) ; The socket connected to the client.
(define-record-discloser :request
(lambda (req)
(list 'request
(request-method req)
(request-uri req)
(request-url req)
(request-version req)
(request-headers req)
(request-socket req))))
;;; A http protocol version is an integer pair: (major . minor).
(define (version< v1 v2)
(or (< (car v1) (car v2))
(and (= (car v1) (car v2))
(< (cdr v1) (cdr v2)))))
(define (version<= v1 v2) (not (version< v2 v1)))
(define (v0.9-request? req)
(version<= (request-version req) '(0 . 9)))
(define (version->string v)
(string-append "HTTP/"
(number->string (car v))
"."
(number->string (cdr v))))

View File

@ -1,281 +0,0 @@
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1994 by Brian D. Carlstrom and Olin Shivers.
;;; Copyright (c) 2002 by Mike Sperber.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
(define-record-type http-response :http-response
(make-response code message seconds mime extras body)
response?
(code response-code) ;;HTTP status code
(message response-message);;reason phrase: textual description of
;;status-code, or #f (-> server sends
;;default reason phrase)
(seconds response-seconds);;time the content was created
(mime response-mime);;string indicating the MIME type of the response
(extras response-extras);;assoc list with extra headers to be
;;added to the response; its elements are
;;pairs, each of which consists of a symbol
;;representing the field name and a string
;;representing the field value.
(body response-body));; message-body
;; This is mainly for nph-... CGI scripts.
;; This means that the body will output the entire MIME message, not
;; just the part after the headers.
(define-record-type http-nph-response :http-nph-response
(make-nph-response body)
nph-response?
(body nph-response-body))
(define-record-type http-input-response :http-input-response
(make-input-response body-maker)
input-response?
(body-maker input-response-body-maker))
(define-record-type http-writer-body :http-writer-body
(make-writer-body proc)
writer-body?
(proc writer-body-proc))
(define-record-type http-reader-writer-body :http-reader-writer-body
(make-reader-writer-body proc)
reader-writer-body?
(proc reader-writer-body-proc))
(define-record-type http-redirect-body :http-redirect-body
(make-redirect-body location)
redirect-body?
(location redirect-body-location))
(define (display-http-body body iport oport options)
(cond
((writer-body? body)
((writer-body-proc body) oport options))
((reader-writer-body? body)
((reader-writer-body-proc body) iport oport options))))
(define-finite-type status-code :http-status-code
(number message)
status-code?
status-codes
status-code-name
status-code-index
(number status-code-number)
(message status-code-message)
(
(ok 200 "OK")
(created 201 "Created")
(accepted 202 "Accepted")
(prov-info 203 "Provisional Information")
(no-content 204 "No Content")
(mult-choice 300 "Multiple Choices")
(moved-perm 301 "Moved Permanently")
(moved-temp 302 "Moved Temporarily")
(method 303 "Method (obsolete)")
(not-mod 304 "Not Modified")
(bad-request 400 "Bad Request")
(unauthorized 401 "Unauthorized")
(payment-req 402 "Payment Required")
(forbidden 403 "Forbidden")
(not-found 404 "Not Found")
(method-not-allowed 405 "Method Not Allowed")
(none-acceptable 406 "None Acceptable")
(proxy-auth-required 407 "Proxy Authentication Required")
(timeout 408 "Request Timeout")
(conflict 409 "Conflict")
(gone 410 "Gone")
(internal-error 500 "Internal Server Error")
(not-implemented 501 "Not Implemented")
(bad-gateway 502 "Bad Gateway")
(service-unavailable 503 "Service Unavailable")
(gateway-timeout 504 "Gateway Timeout")
(redirect -301 "Internal redirect")))
(define (name->status-code name)
(if (not (symbol? name))
(call-error name->status-code (list name))
(let loop ((i 0))
(cond ((= i (vector-length status-codes))
#f)
((eq? name
(status-code-name (vector-ref status-codes i)))
(vector-ref status-codes i))
(else
(loop (+ i 1)))))))
(define (number->status-code number)
(if (not (number? number))
(call-error number->status-code (list number))
(let loop ((i 0))
(cond ((= i (vector-length status-codes))
#f)
((= number
(status-code-number (vector-ref status-codes i)))
(vector-ref status-codes i))
(else
(loop (+ i 1)))))))
;;; (make-error-response status-code req [message . extras])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; As a special case, request REQ is allowed to be #f, meaning we haven't
;;; even had a chance to parse and construct the request. This is only used
;;; for 400 BAD-REQUEST error report.
(define (make-error-response code req . args)
(let* ((message (and (pair? args) (car args)))
(extras (if (pair? args) (cdr args) '()))
(generic-title (lambda (port)
(title-html port
(status-code-message code))))
(send-message (lambda (port)
(if message
(format port "<BR>~%Further Information: ~A<BR>~%" message))))
(close-html (lambda (port)
(for-each (lambda (x) (format port "<BR>~s~%" x)) extras)
(write-string "</BODY>\n" port)))
(create-response
(lambda (headers writer-proc)
(make-response code
#f
(time)
"text/html"
headers
(make-writer-body writer-proc)))))
(cond
;; This error response requires two args: message is the new URI: field,
;; and the first EXTRA is the older Location: field.
((or (eq? code (status-code moved-temp))
(eq? code (status-code moved-perm)))
(create-response
(list (cons 'uri message)
(cons 'location (car extras)))
(lambda (port options)
(title-html port "Document moved")
(format port
"This document has ~A moved to a <A HREF=\"~A\">new location</A>.~%"
(if (eq? code (status-code moved-temp))
"temporarily"
"permanently")
message)
(close-html port))))
((eq? code (status-code bad-request))
(create-response
'()
(lambda (port options)
(generic-title port)
(write-string "<P>Client sent a query that this server could not understand.\n"
port)
(send-message port)
(close-html port))))
((eq? code (status-code method-not-allowed))
(create-response
'()
(lambda (port options)
(generic-title port)
(write-string "<P>Method not allowed.\n" port)
(send-message port)
(close-html port))))
((eq? code (status-code unauthorized))
(create-response
(list (cons 'WWW-Authenticate message)) ; Vas is das?
;; Vas das is? See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.47
;; message should be a challenge(?)
(lambda (port options)
(title-html port "Authorization Required")
(write-string "<P>Browser not authentication-capable or\n" port)
(write-string "authentication failed.\n" port)
(send-message port)
(close-html port))))
((eq? code (status-code forbidden))
(create-response
'()
(lambda (port options)
(title-html port "Request not allowed.")
(format port
"Your client does not have permission to perform a ~A~%"
(request-method req))
(format port "operation on url ~a.~%" (request-uri req))
(send-message port)
(close-html port))))
((eq? code (status-code not-found))
(create-response
'()
(lambda (port options)
(title-html port "URL not found")
(write-string
"<P>The requested URL was not found on this server.\n"
port)
(send-message port)
(close-html port))))
((eq? code (status-code internal-error))
(create-response
'()
(lambda (port options)
(generic-title port)
(format port "The server encountered an internal error or
misconfiguration and was unable to complete your request.
<P>
Please inform the server administrator, ~A, of the circumstances leading to
the error, and time it occurred.~%"
(or (httpd-options-server-admin options)
"[no mail address available]"))
(send-message port)
(close-html port))))
((eq? code (status-code not-implemented))
(create-response
'()
(lambda (port options)
(generic-title port)
(format port "This server does not currently implement
the requested method (~A).~%"
(request-method req))
(send-message port)
(close-html port))))
((eq? code (status-code bad-gateway))
(create-response
'()
(lambda (port options)
(generic-title port)
(format port "An error occurred while waiting for the
response of a gateway.~%")
(send-message port)
(close-html port)))))))
(define (title-html out message)
(format out "<HEAD>~%<TITLE>~%~A~%</TITLE>~%</HEAD>~%~%" message)
(format out "<BODY>~%<H1>~A</H1>~%" message))
;; Creates a redirect response. The server will serve the new file
;; indicated by NEW-LOCATION. NEW-LOCATION must be uri-encoded and
;; begin with a slash. This is intended for CGI scripts. Note that
;; the browser won't notice the redirect. Thus, it will keep the
;; original URL. For "real" redirections, use
;; (make-error-response (status-code moved-perm) req
;; "new-location" "new-location").
(define (make-redirect-response new-location)
(make-response
(status-code redirect)
#f
(time)
""
'()
(make-redirect-body new-location)))

View File

@ -1,196 +0,0 @@
;;; man page -> HTML gateway for the SU web server. -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1996-2003 by Mike Sperber.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;; This uses RosettaMan
;;; (based at ftp.cs.berkeley.edu:/ucb/people/phelps/tcltk/rman.tar.Z)
(define (rman-handler man-binary
nroff-binary
rman-binary
gzcat-binary
finder referencer address . maybe-man)
(let ((parse-man-url
(cond
((procedure? finder) finder)
((list? finder)
(lambda (url)
(values finder
(unescape-uri (http-url-search url))
'())))
(else
(let ((man-path
((infix-splitter ":")
(or (getenv "MANPATH")
(begin
(format (current-error-port)
"~%Warning: environment variable MANPATH is unset.~%")
"")))))
(lambda (url)
(values man-path
(unescape-uri (http-url-search url))
'()))))))
(reference-template
(cond
((procedure? referencer) referencer)
((string? referencer) (lambda (entry section) referencer))
(else (lambda (entry section) "man?%s(%s)"))))
(man (:optional maybe-man man)))
(lambda (path req)
(let ((request-method (request-method req)))
(cond
((string=? request-method "GET")
(with-fatal-error-handler
(lambda (c decline)
(cond
((http-error? c)
(apply http-error (car (condition-stuff c)) req
(cddr (condition-stuff c))))
(else
(decline))))
(make-response
(status-code ok)
#f
(time)
"text/html"
'()
(make-writer-body
(lambda (out options)
(receive (man-path entry and-then)
(parse-man-url (request-url req))
(emit-man-page man-binary nroff-binary rman-binary
gzcat-binary
entry man man-path and-then reference-template out))
(with-tag out address ()
(display address out)))))))
(else
(make-error-response (status-code method-not-allowed) req
request-method)))))))
(define (cat-man-page key section out)
(let ((title (if section
(format #f "~a(~a) manual page" key section)
(format #f "~a manual page" key))))
(emit-title out title)
(emit-header out 1 title)
(newline out)
(with-tag out body ()
(with-tag out pre ()
(copy-inport->outport (current-input-port)
out)))))
(define (emit-man-page man-binary nroff-binary rman-binary
gzcat-binary
entry man man-path and-then reference-template out)
(receive (key section) (parse-man-entry entry)
(let ((status
(cond
((procedure? and-then)
(run (| (begin (man man-binary nroff-binary gzcat-binary
section key man-path))
(begin (and-then key section out)))
(= 1 ,out)
(= 2 ,out)))
(else
(run (| (begin (man man-binary nroff-binary gzcat-binary
section key man-path))
(,rman-binary "-fHTML"
,@and-then
"-r" ,(reference-template entry section)))
(= 1 ,out)
(= 2 ,out))))))
(if (not (zero? status))
(error "internal error emitting man page")))))
(define parse-man-entry
(let ((entry-regexp (make-regexp "(.*)\\((.)\\)")))
(lambda (s)
(cond
((regexp-exec entry-regexp s)
=> (lambda (match)
(values (match:substring match 1)
(match:substring match 2))))
(else (values s #f))))))
(define (man man-binary nroff-binary gzcat-binary section key man-path)
(cond
((procedure? man-path) (man-path))
((find-man-file key section "cat" man-path) =>
(lambda (file)
(cat-n-decode gzcat-binary file)))
((find-man-file key section "man" man-path) =>
(lambda (file)
(nroff-n-decode nroff-binary gzcat-binary file)))
(else
(if (not (zero?
(with-env (("MANPATH" . ,(string-join man-path ":")))
(run (,man-binary "-man" ,@(if section `(,section) '()) ,key)
stdports))))
(http-error (status-code not-found) #f "man page not found")))))
(define man-default-sections
'("1" "2" "3" "4" "5" "6" "7" "8" "9" "o" "l" "n" "p"))
(define (find-man-file name section cat-man man-path . maybe-sections)
(define (section-dir section)
(lambda (dir)
(file-name-as-directory
(string-append (file-name-as-directory dir)
cat-man
section))))
(let* ((prefix (if section
(string-append name "." section)
(string-append name ".")))
(pattern (string-append (glob-quote prefix) "*"))
(sections (:optional maybe-sections man-default-sections))
(path (if section
(map (section-dir section) man-path)
(apply append
(map (lambda (dir)
(map (lambda (section)
((section-dir section) dir))
sections))
man-path)))))
(let loop ((path path))
(and (not (null? path))
(let ((matches (glob (string-append (car path) pattern))))
(if (not (null? matches))
(car matches)
(loop (cdr path))))))))
(define (file->man-directory file)
(path-list->file-name
(reverse
(cdr
(reverse
(split-file-name
(file-name-directory file)))))))
(define (cat-n-decode gzcat-binary file)
(let ((ext (file-name-extension file)))
(cond
((string=? ".gz" ext) (run (,gzcat-binary ,file) stdports))
((string=? ".Z" ext) (run (,gzcat-binary ,file) stdports))
(else (call-with-input-file
file
(lambda (port)
(copy-inport->outport port (current-output-port))))))))
(define (nroff-n-decode nroff-binary gzcat-binary file)
(if (not (zero? (run (| (begin (cat-n-decode gzcat-binary file))
(begin
(with-cwd (file->man-directory file)
(exec-epf (,nroff-binary "-man")))))
stdports)))
(http-error (status-code not-found) #f "man page not found")))

View File

@ -1,50 +0,0 @@
#!/bin/sh
IFS=" "
exec scsh -lm ../packages.scm -dm -o http-top -e top -s "$0" "$@"
!#
;;; Scheme Underground Web Server -*- Scheme -*-
;;; Olin Shivers
;;; To compile as a heap-image:
;;; ,open http-top
;;; (dump-scsh-program top "server")
;;; then insert a #! trigger.
(define-structure http-top (export top)
(open httpd-core
httpd-make-options
httpd-cgi-server
httpd-basic-handlers
httpd-seval-handlers
scheme-with-scsh)
(begin
;; Kitchen-sink request handler.
(define rh
(alist-path-dispatcher
`(("h" . ,(home-dir-handler "public_html"))
("seval" . ,seval-handler)
("cgi-bin" . ,(cgi-handler "/usr/local/etc/httpd/cgi-bin")))
(tilde-home-dir-handler "public_html"
(rooted-file-handler "/usr/local/etc/httpd/htdocs"))))
;; crank up a server on port 8001, first resetting our identity to
;; user "nobody". Initialise the request-invariant part of the CGI
;; env before starting.
(define (top args)
(display "We be jammin, now.\n") (force-output)
(cond ((zero? (user-uid))
(set-gid (->gid "nobody"))
(set-uid (->uid "nobody"))))
;; invariant environment is know initilialized by cgi-handler itself
;; (initialise-request-invariant-cgi-env)
(httpd (with-request-handler
rh
(with-port
8001
(with-root-directory "/usr/local/etc/httpd")))))))

View File

@ -1,106 +0,0 @@
;;; Path handler for uploading Scheme code to the SU web server -*- Scheme -*-
;;; This file is part of the Scheme Untergrund Networking package.
;;; Copyright (c) 1995 by Olin Shivers.
;;; For copyright information, see the file COPYING which comes with
;;; the distribution.
;;; This is really just an handler example demonstrating how to upload code
;;; into the server.
;;; (do/timeout secs thunk)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Run THUNK, and gun it down if it hasn't finished in SECS seconds.
;;; Returns nothing useful, and THUNK gets executed in a subprocess,
;;; so its side-effects are invisible, as well. This is a clever kludge --
;;; it uses three subprocesses -- but I don't have interrupts, so I'm hosed.
(define (do/timeout* secs thunk)
(run (begin (let ((timer (fork (lambda () (sleep secs))))
(worker (fork thunk)))
(receive (process status) (wait-any)
(ignore-errors
(lambda ()
(signal-process (proc:pid (if (eq? worker process)
timer
worker))
signal/kill))))))))
(define-syntax do/timeout
(syntax-rules ()
((do/timeout secs body ...) (do/timeout* secs (lambda () body ...)))))
;;; The request handler for seval ops.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (seval-handler path req)
(let ((request-method (request-method req)))
(cond
((string=? request-method "POST") ; Could do others also.
(seval path req))
(else
(make-error-response (status-code method-not-allowed) req request-method)))))
(define (seval path req)
(make-response
(status-code ok)
#f
(time)
"text/html"
'()
(make-reader-writer-body
(lambda (iport oport options)
(let ((sexp (read-request-sexp req iport)))
(http-syslog (syslog-level debug) "read sexp: ~a" sexp)
(with-tag oport HEAD ()
(newline oport)
(emit-title oport "Scheme program output"))
(newline oport)
(with-tag oport BODY ()
(newline oport)
(do/timeout
10
(receive vals
;; Do the computation.
(begin (emit-header oport 2 "Output from execution")
(newline oport)
(with-tag oport PRE ()
(newline oport)
(force-output oport); In case we're gunned down.
(with-current-output-port oport
(eval-safely sexp))))
;; Pretty-print the returned value(s).
(emit-header oport 2 "Return value(s)")
(with-tag oport PRE ()
(for-each (lambda (val) (p val oport))
vals))))))))))
;;; Read an HTTP request entity body from stdin. The Content-length:
;;; element of request REQ's header tells how many bytes to this entity
;;; is. The entity should be a URI-encoded form body. Pull out the
;;; program=<stuff>
;;; string, extract <stuff>, uri-decode it, parse that into an s-expression,
;;; and return it.
(define (read-request-sexp req iport)
(cond
((get-header (request-headers req) 'content-length) =>
(lambda (cl-str) ; Take the first Content-length: header,
(let* ((cl-start (string-skip cl-str char-set:whitespace)) ; skip whitespace,
(cl (if cl-start ; & convert to
(string->number (substring cl-str ; a number.
cl-start
(string-length cl-str)))
0)) ; All whitespace?? -- WTF.
(qs (read-string cl iport)) ; Read in CL chars,
(q (parse-html-form-query qs)) ; and parse them up.
(s (cond ((assoc "program" q) => cdr)
(else (error "No program in entity body.")))))
(http-syslog (syslog-level debug)
"Seval sexp: ~s" s)
(read (make-string-input-port s)))))
(else (error "No `Content-length:' field in POST request."))))

View File

@ -1,8 +0,0 @@
TODO
inspecting-packages.scm
inspecting-surflet-handler.scm
load-inspecting-surflet-server.scm
*.aux
*.log
*.dvi

View File

@ -1,11 +0,0 @@
--- SSAX-old/lib/packages.scm Sun Apr 14 07:35:07 2002
+++ SSAX/lib/packages.scm Mon Oct 6 12:05:58 2003
@@ -55,7 +55,7 @@
(export SXML->HTML
enattr
entag
- string->goodhtml))
+ string->goodHTML))
(define-interface sxml-to-html-ext-interface
(export make-header

View File

@ -1,10 +0,0 @@
* alias for extract-single-binding (extract/single) and extract-bindings
(extract)?
* send error 411 "Length required" in get-content-length (bindings.scm)
if the content-length-header is missing.
* use internal-error, if surflet fails, not bad gateway
* don't let callback-functions return.
* use another name for callback-functor.
* tell in howto how to start in cygwin environment.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Wins\Parameters

View File

@ -1,68 +0,0 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; return address
;; generates an unique return-address
;; may be used like
;; (let ((address (make-address)))
;; (send-html/suspend
;; (lambda (new-url)
;; ...
;; (url (address new-url) "Click here to get more")...)
(define-record-type address :address
(make-address-record name annotated? annotations)
(name address-name)
(annotated? address-annotated?)
(annotations address-annotations set-address-annotations!))
(define (really-make-address name annotated?)
(if annotated?
(make-address-record name annotated? #f)
(make-address-record name annotated? '())))
(define (address-add-annotation! address annotation)
(let ((index (generate-unique-name "val")))
(set-address-annotations! address
(cons (cons index annotation)
(address-annotations address)))
index))
(define (address-annotation address index)
(cond
((assoc index (address-annotations address)) => cdr)
(else #f)))
(define (make-address)
(let ((address (really-make-address
(generate-unique-name "return") #f)))
(lambda (message)
(cond
((string? message)
(string-append message "?" (address-name address) "="))
((eq? message 'address)
address)
(else
(error "address: unknown message/bad argument"
message (address-name address)))))))
(define (make-annotated-address)
(let ((address (really-make-address
(generate-unique-name "return")
#t)))
(lambda (message . annotation)
(cond
((and (string? message)
(<= (length annotation) 1))
(let ((index (if (null? annotation)
(address-add-annotation! address "")
(address-add-annotation! address (car annotation)))))
(string-append message "?" (address-name address)
"=" index)))
((eq? message 'address)
address)
(else
(error "annotated-address: unknown message/bad argument(s)"
message (address-name address)))))))

View File

@ -1,111 +0,0 @@
;; Copyright 2002, 2003 Andreas Bernauer
;; Bindings of POST requests can be read only once, since they are
;; read from an input port. So we have to cache them, for the case of
;; a later GET-BINDINGS call on the same POST request. The requests
;; are referenced by a weak pointer. Thread-safe as all threads use
;; the same lock.
(define *POST-bindings-cache* '())
(define *cache-lock* (make-lock))
(define (get-bindings surflet-request)
(let ((request-method (surflet-request-method surflet-request))
(content-type (assoc 'content-type
(surflet-request-headers surflet-request))))
;; Check if we the content-type is the one we support. If there's
;; no content-type, assume the default (this is the one we
;; support).
(if (and content-type
;; Have to string-trim now, because the (buggy?) rfc822
;; implementation leaves the leading whitespace of the
;; header value.
(not (string=? (string-trim (cdr content-type))
"application/x-www-form-urlencoded")))
(error "get-bindings currently only supports
'application/x-www-form-urlencoded' as content-type"))
(cond
((string=? request-method "GET")
(form-query-list (http-url-search
(surflet-request-url surflet-request))))
((string=? request-method "POST")
(or (cached-bindings surflet-request)
(let* ((content-length (get-content-length
(surflet-request-headers surflet-request)))
(input-port (surflet-request-input-port surflet-request))
(form-data (read-string content-length input-port))
(form-bindings (form-query-list form-data)))
(obtain-lock *cache-lock*)
(set! *POST-bindings-cache*
(cons (cons (make-weak-pointer surflet-request)
form-bindings)
*POST-bindings-cache*))
(release-lock *cache-lock*)
form-bindings)))
(else
(error "unsupported request type")))))
;; Looking up, if we have cached this request. While going through the
;; list, we remove entries to request objects, that are no longer
;; valid. Expecting a call for an uncached request every now and then,
;; it is guaranteed, that the list is cleaned up every now and
;; then. The cache is a list of pairs
;;; (surflet-request . computed-binding)
(define (cached-bindings surflet-request)
(obtain-lock *cache-lock*)
(let ((result
(let loop ((predecessor #f)
(cache *POST-bindings-cache*))
(if (null? cache)
#f ; no such request cached
(let* ((head (car cache))
(s-req (weak-pointer-ref (car head))))
(if s-req
(if (eq? s-req surflet-request)
(cdr head) ; request is cached
(loop (if predecessor
(cdr predecessor)
cache)
(cdr cache))) ; request isn't cached
(begin ;; request object is gone ==> remove
;; it from list
(if predecessor
(set-cdr! predecessor (cdr cache))
(set! *POST-bindings-cache* (cdr cache)))
(loop predecessor
(cdr cache)))))))))
(release-lock *cache-lock*)
result))
;; Will be needed when we handle POST requests.
(define (get-content-length headers)
(cond ((get-header headers 'content-length) =>
;; adopted from httpd/cgi-server.scm
(lambda (content-length) ; Skip initial whitespace (& other non-digits).
(let ((first-digit (string-index content-length char-set:digit))
(content-length-len (string-length content-length)))
(if first-digit
(string->number (substring content-length first-digit
content-length-len))
;; (status-code bad-request) req
(error "Illegal `Content-length:' header.")))))
(else
(error "No Content-length specified for POST data."))))
(define (extract-bindings key bindings)
(let ((key (if (symbol? key) (symbol->string key) key)))
(map cdr
(filter (lambda (binding)
(equal? (car binding) key))
bindings))))
(define (extract-single-binding key bindings)
(let ((key-bindings (extract-bindings key bindings)))
(if (= 1 (length key-bindings))
(car key-bindings)
(error "extract-one-binding: more than one or zero bindings found"
(length key-bindings)
key bindings))))

View File

@ -1,37 +0,0 @@
;; Copyright 2002, 2003 Andreas Bernauer
;; With callbacks you can create special links that are associated
;; with a function. If the user clicks on the special callback link,
;; the send-html/suspend won't return, but the function will be called
;; instead.
;; NOTE: It is not sensible to create callbacks on top level, as they
;; contain continuations. You have to create a new callback every time
;; you want to use it (inside a function).
(define (make-callback function)
(call-with-current-continuation
(lambda (exit)
(let* ((req (send/suspend (lambda (new-url)
(exit new-url)))))
(function req)))))
(define (make-annotated-callback function)
(let* ((annotated-address (make-annotated-address))
(dispatch
(lambda (req)
(let ((bindings (get-bindings req)))
(cond
((returned-via annotated-address bindings) =>
(lambda (args)
(apply function (cons req args))))
(else
(error "annotated-callback:
unexpected return values from website"))))))
(callback (make-callback dispatch)))
(lambda args
(annotated-address callback args))))
(define callback-function
(lambda (req proc . args)
(apply proc (cons req args))))

View File

@ -1,24 +0,0 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Error-Handler
;;
;; Adopted from WITH-FATAL-ERROR-HANDLER, but handles everything that
;; is catchable. We must catch everything because we also want
;; exceptions (and warnings) to be catched (e.g. when the surflet is
;; loaded.)
(define (with-fatal-handler* handler thunk)
(call-with-current-continuation
(lambda (accept)
((call-with-current-continuation
(lambda (k)
(with-handler (lambda (condition more)
(call-with-current-continuation
(lambda (decline)
(k (lambda () (handler condition decline)))))
(more)) ; Keep looking for a handler.
(lambda () (call-with-values thunk accept)))))))))
(define-syntax with-fatal-handler
(syntax-rules ()
((with-fatal-handler handler body ...)
(with-fatal-handler* handler
(lambda () body ...)))))

View File

@ -1,19 +0,0 @@
(define (surflet-file-name req)
(last (http-url-path (surflet-request-url req))))
;; This works for all requests except for the initial one. For the
;; initial one (main's arg) think about using instance-session-id.
(define (my-session-id req)
(resume-url-session-id (surflet-file-name req)))
;; This works for all requests except for the initial one: we don't
;; have a continuation at this time.
(define (my-continuation-id req)
(resume-url-continuation-id (surflet-file-name req)))
;; Returns two values: session-id and continuation-id. The
;; restrictions from my-session-id and my-continuation-id apply here
;; as well.
(define (my-ids req)
(resume-url-ids (surflet-file-name req)))

View File

@ -1,160 +0,0 @@
;;; Copyright 2002, 2003 Andreas Bernauer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; input-fields
;;; defines input-fields for surflets
;;; Globals
(define *input-field-trigger* `*input-field*)
(define generate-input-field-name generate-unique-name)
;;; Data structure for real-input-field
;; MULTI?: Transformer will get all bindings of request, not only the
;; one concerning the input-field.
(define-record-type real-input-field :real-input-field
(make-real-input-field name type transformer
attributes html-tree-maker
html-tree multi?)
real-input-field?
(name real-input-field-name)
(type real-input-field-type)
(transformer real-input-field-transformer)
(attributes real-input-field-attributes set-real-input-field-attributes!)
(html-tree-maker real-input-field-html-tree-maker)
(html-tree real-input-field-html-tree set-real-input-field-html-tree!)
(multi? real-input-field-multi?))
(define-record-discloser :real-input-field
(lambda (input-field)
(list 'real-input-field
(real-input-field-type input-field)
(real-input-field-name input-field))))
;;; Fake input-field record. This is necessary, as the trigger in SXML
;;; may only be symbols, not arbitrary values. Thus, our input-fields
;;; must be preceeded by a trigger symbol to get recognized by the
;;; SXML transforming routines like sxml->html.
;; Constructors: make-input-field, make-multi-input-field
;; Predicates: input-field?
;; Selectors: input-field-name, input-field-type,
;; input-field-transformer, input-field-attributes,
;; input-field-html-tree-maker, input-field-html-tree,
;; input-field-multi?
;; Mutators: set-input-field-attributes!, touch-input-field!
;;; Constructors for input-field / multi-input-field
(define (make-input-field name type transformer attributes
html-tree-maker)
(make-sxml-input-field
(make-real-input-field name type transformer
attributes html-tree-maker #f #f)))
(define (make-multi-input-field name type transformer attributes
html-tree-maker)
(make-sxml-input-field
(make-real-input-field name type transformer
attributes html-tree-maker #f #t)))
(define (make-sxml-input-field real-input-field)
(list *input-field-trigger* real-input-field))
(define input-field-real-input-field cadr)
(define (input-field? input-field)
(and (pair? input-field)
(eq? *input-field-trigger* (car input-field))
(real-input-field? (input-field-real-input-field input-field))))
(define (make-input-field-selector selector)
(lambda (input-field)
(selector (input-field-real-input-field input-field))))
(define (make-input-field-setter setter reset?)
(lambda (input-field value)
(let ((real-input-field (input-field-real-input-field input-field)))
(if reset?
(set-real-input-field-html-tree! real-input-field #f))
(setter real-input-field value))))
(define input-field-name (make-input-field-selector real-input-field-name))
(define input-field-type (make-input-field-selector real-input-field-type))
(define input-field-transformer
(make-input-field-selector real-input-field-transformer))
(define input-field-attributes
(make-input-field-selector real-input-field-attributes))
(define input-field-html-tree-maker
(make-input-field-selector real-input-field-html-tree-maker))
(define (input-field-html-tree input-field)
(let ((real-input-field (input-field-real-input-field input-field)))
(cond
((real-input-field-html-tree real-input-field)
=> identity)
(else
(let ((html-tree ((real-input-field-html-tree-maker real-input-field)
input-field)))
(set-real-input-field-html-tree! real-input-field html-tree)
html-tree)))))
(define input-field-multi?
(make-input-field-selector real-input-field-multi?))
(define set-input-field-attributes!
(make-input-field-setter set-real-input-field-attributes! #t))
;; not exported:
(define set-input-field-html-tree!
(make-input-field-setter set-real-input-field-html-tree! #f))
;; A touched input-field's html-tree will be recalculated if
;; neccessary.
(define (touch-input-field! input-field)
(set-input-field-html-tree! input-field #f))
;; <input-field>: '(input-field <real-input-field>)
;; <real-input-field>: #{Real-input-field "name"}
(define (raw-input-field-value input-field bindings)
(let ((real-input-field (input-field-real-input-field input-field)))
(cond
((real-input-field-multi? real-input-field)
((real-input-field-transformer real-input-field) input-field bindings))
((real-input-field-binding real-input-field bindings) =>
(lambda (binding)
((real-input-field-transformer real-input-field)
input-field (cdr binding))))
(else
(error "no such input-field" input-field bindings)))))
;; Trys to get a value for INPUT-FIELD in BINDINGS. If this fails
;; (i.e. RAW-INPUT-FIELD-VALUE returns an error), the default-value is
;; returned. The default-value defaults to #f. NOTE: If you do this
;; with input-fields whose valid values may be the same as the default
;; value, you cannot determine by the result if there was such a value
;; or not. Keep in mind, that RAW-INPUT-FIELD-VALUE returns also an
;; error, if there was not such an input field. This makes
;; INPUT-FIELD-VALUE working with checkbox input fields because they
;; miss if they are not checked.
(define (input-field-value input-field bindings . maybe-default)
(let ((default (:optional maybe-default #f)))
(with-fatal-error-handler
(lambda (condition more)
; (format #t "hit error condition: ~s~%" condition)
default)
(raw-input-field-value input-field bindings))))
(define (real-input-field-binding input-field bindings)
(assoc (real-input-field-name input-field) bindings))
;; Returns the binding of the input-field in bindings by the
;; input-field's name. If your input-field will have another name in
;; the bindings than it was created with, use a multi-input-field.
(define (input-field-binding input-field bindings)
(real-input-field-binding (input-field-real-input-field input-field)
bindings))
;;EOF

View File

@ -9,21 +9,14 @@
\input{../../../doc/latex/decls}
\newcommand{\attrib}[1]{\textsf{#1}}
\newcommand{\ovar}[1]{\mbox{\textnormal{[}\frenchspacing\it{#1}\textnormal{]}}}
%FIXME-command: types out the desired FIXME
\newcommand{\FIXME}[1]%
{\typeout{}\typeout{****** FIXME ***** #1}\typeout{}%
\textsf{[#1]}}
\begin{document}
\maketitle
\begin{abstract}
\noindent [THIS FILE IS NOT AT ALL UP TO DATE. However, some specs
are still true. See the sources and the example SUrflets for up to
date info.]\\ \noindent The Scheme Untergrund Network Package
(\textit{SUnet} for short) comes along with a modular web
server. The SUrflet handler described here extends it by the
capability of writing programs in Scheme, that yield an HTML page.
\noindent The Scheme Untergrund Network Package (\textit{SUnet} for
short) comes along with a modular web server. The SUrflet handler
described here extends it by the capability of writing programs in
Scheme, that yield an HTML page.
Suspending of SUrflet computation.
Using Oleg's SXML.
@ -271,7 +264,7 @@ See the examples for further informations.
\defun{input-field-binding}{input-field bindings}{binding}
\defunx{raw-input-field-value}{input-field bindings}{value}
\defunx{input-field-value}{input-field bindings \ovar{default}}{value}
\defunx{input-field-value}{input-field bindings \opt{default}}{value}
\begin{desc}
\ex{input-field-binding} returns the binding for
\semvar{input-field} in \semvar{bindings}.

View File

@ -1,16 +1,35 @@
;;; This file is meant for developing. Use the example startup
;;; scripts to start the webserver,
;;; e.g. $SCSH_LIB_DIRS/sunet/web-server/start-surflet-server
;;; Reads package descriptions in the right order. In the end, the
;;; server can be started via SERVER. Assumes scsh has been started with
;;; SSAX loaded: scsh -lel SSAX/load.scm (otherwise surflets won't work)
;;; and it is called with cwd=sunet/scheme/httpd/surflets/
; reads package description in the right order
; in the end, the server can be started via (server)
(batch 'on)
(config `(load "../../packages.scm"))
(config `(load "packages.scm"))
(config `(load "../../../web-server/start-surflet-server"))
(define *ASSUMED-SUNET-HOME*
(in 'scsh '(run (match:substring
(regexp-search (rx (submatch (* any) "sunet")) (cwd))
1))))
(define *SUNET-PACKAGE*
(in 'scsh `(run (string-append
(or (getenv "SUNETHOME")
,*ASSUMED-SUNET-HOME*)
"/packages.scm"))))
(define *SSAX-PACKAGE*
(in 'scsh `(run (string-append
(or (getenv "SSAXPATH")
(string-append ,*ASSUMED-SUNET-HOME* "/SSAX"))
"/lib/packages.scm"))))
(define *SURFLET-PACKAGE*
(in 'scsh `(run (string-append
(or (getenv "SUNETHOME")
,*ASSUMED-SUNET-HOME*)
"/httpd/surflets/packages.scm"))))
(define *SURFLET-SERVER*
(in 'scsh `(run (string-append
(or (getenv "SUNETHOME")
,*ASSUMED-SUNET-HOME*)
"/httpd/surflets/start-surflet-server"))))
(config `(load ,*SUNET-PACKAGE*))
(config `(load ,*SSAX-PACKAGE*))
(config `(load ,*SURFLET-PACKAGE*))
(config `(load ,*SURFLET-SERVER*))
(user)
(open 'surflet-server)
(batch 'off)

View File

@ -1,33 +0,0 @@
;;; Copyright 2002, 2003 Andreas Bernauer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; outdater
(define-record-type outdater :outdater
(real-make-outdater outdated?)
outdater?
(outdated? outdater-outdated? set-outdater-outdated?!))
(define (make-outdater)
(real-make-outdater #f))
(define-syntax if-outdated
(syntax-rules ()
((if-outdated outdater consequence alternative)
(if (outdater-outdated? outdater)
consequence
(begin
(set-outdater-outdated?! outdater #t)
alternative)))))
(define (show-outdated url)
(send-html
`(html (title "Outdated Data")
(body (h1 "Outdated Data")
(p "The page or action you requested relies on outdated data.")
,(if url
`(p "Try to "
(url ,url "reload")
" the page to get current data.")
'())))))

View File

@ -1,213 +1,221 @@
;; Structures and interfaces for surflets.
;; NOTE: SSAX/lib/packages.scm must be loaded before you can use this
;; downloadable from http://sourceforge.net/project/showfiles.php?group_id=30687
;; (take the r5rs compliant version (ssax-sr5rs-plt200-4.9.tar.gz))
;;; Copyright 2002, 2003 Andreas Bernauer
;;; Copyright 2002 Martin Gasbichler
(define-interface rt-module-language-interface
(export ((lambda-interface
with-names-from-rt-structure)
:syntax)
reify-structure
load-structure
load-config-file
rt-structure-binding))
;;; NOTE: SSAX/lib/packages.scm must be loaded before you can use this
;;; downloadable from
;;; http://sourceforge.net/project/showfiles.php?group_id=30687 (take
;;; the r5rs compliant version (ssax-sr5rs-plt200-4.9.tar.gz); and
;;; patch string->goodhtml in packages.scm to string->goodHTML)
(define-interface rt-modules-interface
(export interface-value-names
reify-structure
load-config-file
rt-structure-binding
load-structure))
(define-structure rt-module-language rt-module-language-interface
(open scheme
rt-modules)
(for-syntax (open scheme
rt-modules))
(begin
(define-syntax lambda-interface
(lambda (expr rename compare)
(let ((%lambda (rename 'lambda))
(interface-name (cadr expr))
(body (cddr expr)))
`(,%lambda ,(interface-value-names interface-name) ,@body))))
;(with-names-from-rt-structure surflet surflet-interface (main))
(define-syntax with-names-from-rt-structure
(lambda (expr rename compare)
(let ((%lambda (rename 'lambda))
(%let (rename 'let))
(%rt-structure-value (rename 'rt-structure-value))
(%rt-structure-binding (rename 'rt-structure-binding))
(rt-structure (cadr expr))
(interface-name (caddr expr))
(body (cdddr expr)))
(let ((ivn (interface-value-names interface-name)))
`(,%let ((,%rt-structure-value ,rt-structure))
((,%lambda ,ivn ,@body)
,@(map (lambda (name)
`(,%rt-structure-binding ,%rt-structure-value ',name))
ivn)))))))))
(define-structure rt-modules rt-modules-interface
(open scheme
meta-types ; syntax-type
interfaces ; for-each-declaration
define-record-types
records
signals
bindings
packages
packages-internal
locations
environments
ensures-loaded
package-commands-internal)
(files rt-module))
;;; Interfaces
;; Surflet Handler
(define-interface surflet-handler-interface
(export surflet-handler))
;; Responses from SUrflets
(define-interface surflet-handler/responses-interface
(export make-surflet-response
valid-surflet-response-data?
surflet-response?
surflet-response-status
surflet-response-content-type
surflet-response-headers
surflet-response-data))
(define-interface surflet-handler/surflet-interface
(export send/suspend ;send and suspend
send/finish ;send and finish
send ;just send (no finish, no suspend)
set-surflet-data!
get-surflet-data
adjust-timeout ;adjusts timeout of current session
;Without `!' because PLT
;doesn't have it.
))
;; SUrflet-requests as expected from the surflet handler
(define-interface surflet-handler/requests-interface
(export make-surflet-request ;FIXME? unusable for user
surflet-request?
surflet-request-request
surflet-request-input-port
surflet-request-method
surflet-request-uri
surflet-request-url
surflet-request-version
surflet-request-headers
surflet-request-socket))
(define-interface surflets/error-interface
(export send-error ;send error response
(status-code :syntax))) ;from httpd-responses
;; Use for SUrflets
(define-interface surflet-handler/primitives-interface
(compound-interface
surflet-handler/responses-interface
surflet-handler/requests-interface
surflets/error-interface
(export send/suspend ;send and suspend
send/finish ;send and finish
send ;just send (no finish, no suspend)
)))
;; Send HTML-Strings (for advanced user)
(define-interface surflets/send-html-string-interface
(export send-html-string/suspend
send-html-string/finish
send-html-string))
;; Extensions/Exports to/from Olegs SSAX library
(define-interface surflets/sxml-interface
(export display-low-level-sxml
sxml->low-level-sxml ;direct map to pre-post-order
sxml->string
sxml->string/internal
sxml-attribute?
sxml-attribute-attributes
default-rule
text-rule
attribute-rule))
;; SUrflets' extensions to SXML
(define-interface surflets/surflet-sxml-interface
(export surflet-sxml->low-level-sxml
surflet-sxml-rules
surflet-form-rule
default-rules
plain-html-rule
nbsp-rule
url-rule))
;; Use for advanced users: make your own conversion rules.
(define-interface surflets/my-sxml-interface
(compound-interface
surflets/send-html-string-interface
surflets/sxml-interface
surflets/surflet-sxml-interface))
(define-interface surflets/continuations-interface
(export get-continuations
delete-continuation!
continuation-id))
;; Access to session-id and continuation-id
(define-interface surflets/ids-interface
(export my-session-id
my-continuation-id
my-ids
instance-session-id))
(define-interface surflets/session-data-interface
(export get-session-data
set-session-data!))
;; Use for advanced users: access to your sessions and continuations
;; (currently you get access to all sessions; this should and will be
;; restricted in the future)
(define-interface surflets/my-sessions-interface
(compound-interface
surflets/ids-interface
surflets/continuations-interface
surflets/session-data-interface
(export get-session
;; That would be too much:
;; get-sessions
delete-session!
instance-session-id
session-adjust-timeout!
adjust-timeout!
session-alive?
session-surflet-name
session-session-id
set-session-lifetime!
options-surflet-path
options-session-lifetime
options-cache-surflets?
options-make-session-timeout-text)))
(define-interface surflets/sessions-interface
(compound-interface
surflets/session-data-interface
(export get-session
get-sessions
delete-session!
instance-session-id
set-session-lifetime!
adjust-timeout!
session-adjust-timeout!
session-alive?
session-surflet-name
session-session-id ;faked
;; FIXME: This is too much and should be restricted:
session-continuation-table
session-continuation-table-lock
session-continuation-counter)))
(define-interface surflet-handler/surflets-interface
(define-interface surflet-handler/admin-interface
(export get-loaded-surflets
unload-surflet
reset-surflet-cache!))
(define-interface surflet-handler/options-interface
(export make-surflet-options
with-surflet-path
with-session-lifetime
with-cache-surflets?
with-make-session-timeout-text
options-surflet-path
options-session-lifetime
set-options-session-lifetime
options-session-lifetime
set-options-cache-surflets?
options-cache-surflets?
options-make-session-timeout-text
set-options-surflet-path!
set-options-session-lifetime!
set-options-cache-surflets?!
set-options-make-session-timeout-text))
(define-interface surflet-handler/resume-url-interface
(export resume-url?
options-surflet-path
options-surflet-prefix
get-sessions
session-surflet-name
session-memo
session-continuation-table
session-continuation-table-lock
session-continuation-counter
delete-session!
session-adjust-timeout!
adjust-timeout
get-continuations
delete-continuation!
instance-session-id
resume-url?
resume-url-ids
resume-url-session-id
resume-url-continuation-id))
;; Use for adminstration of the Surflet Handler
(define-interface surflet-handler/admin-interface
(compound-interface
surflet-handler/surflets-interface
surflets/sessions-interface
surflets/continuations-interface
surflet-handler/resume-url-interface
surflet-handler/options-interface
))
(define-structures
((surflet-handler surflet-handler-interface)
(surflet-handler/surflet surflet-handler/surflet-interface)
(surflet-handler/admin surflet-handler/admin-interface))
(open httpd-responses
httpd-requests
httpd-errors
uri ;URI-PATH-LIST->PATH
tables ;HASH-TABLES
define-record-types ;DEFINE-RECORD-TYPE
rt-module-language ;get structures dynamically
; srfi-13 ;string
srfi-14 ;CHAR-SET:DIGIT
handle-fatal-error ;WITH-FATAL-ERROR-HANDLER* et al.
srfi-27 ;random numbers
locks ;MAKE-LOCK et al.
thread-cells ;THREAD-CELL et al.
profiling ;PROFILE-SPACE
httpd-logging ;HTTP-SYSLOG
shift-reset ;SHIFT and RESET
conditions ;exception
threads ;SLEEP
thread-fluids ;FORK-THREAD
sxml-to-html ;SXML->HTML
scsh ;regexp et al.
; httpd-file-directory-handlers ;send-file-response
srfi-6 ;string-ports
handle
scheme
)
(files surflet-handler))
(define-interface surflets-interface
(export send/suspend
send/finish
send
send-html/suspend
send-html/finish
send-html
form-query
get-bindings
extract-bindings
extract-single-binding
adjust-timeout
make-outdater
(if-outdated :syntax)
show-outdated
generate-input-field-name
make-input-field
make-higher-input-field
make-text-input-field
make-hidden-input-field
make-password-input-field
make-number-input-field
make-textarea-input-field
make-select-input-field
make-checkbox-input-field
make-radio-input-fields
make-submit-button
make-reset-button
make-image-button
input-field-value
raw-input-field-value
input-field-binding
make-address
returned-via?
make-callback
set-surflet-data!
get-surflet-data))
(define-structure surflets surflets-interface
(open surflet-handler/surflet
httpd-responses
httpd-requests ; HTTP-URL:SEARCH
url ; REQUEST:URL
parse-html-forms
sxml-to-html ; SXML->HTML
srfi-1 ; FILTER
(subset srfi-13 (string-index))
sxml-tree-trans
url
define-record-types
weak ;MAKE-WEAK-POINTER
locks
let-opt ;:OPTIONAL
handle-fatal-error
scsh
scheme)
(files surflets))
;; THE interface that SUrflets use.
(define-interface surflet-interface
(export main)) ; MAIN gets one parameter, the REQUEST
;; Simple Surflet API as known from PLT
(define-interface simple-surflet-api-interface
(export single-query
queries
form-query
inform
final-page
make-text
make-password
make-number
make-boolean
make-radio
make-yes-no
extract/single
extract))
;; shift-reset
(define-interface shift-reset-interface
(export (reset :syntax)
(shift :syntax)))
;; For memory profiling
(define-structure shift-reset shift-reset-interface
(open scheme
signals
escapes
thread-cells)
(files shift-reset))
(define-interface profiling-interface
(export profile-space
profile-result
@ -243,251 +251,6 @@
total-count total-bytes
))
;; Handling every condition
(define-interface handle-fatal-interface
(export with-fatal-handler*
(with-fatal-handler :syntax)))
;; Thread-safe counter
(define-interface thread-safe-counter-interface
(export make-thread-safe-counter
thread-safe-counter-value
thread-safe-counter-next!
thread-safe-counter?))
(define-interface with-locks-interface
(export with-lock*
(with-lock :syntax)))
;; Input-fields as Scheme-Objects
(define-interface surflets/input-field-value-interface
(export input-field?
raw-input-field-value
input-field-value
input-field-binding))
;; For advanced users: creating your own input-fields
(define-interface surflets/my-input-fields-interface
(compound-interface
surflets/input-field-value-interface
(export generate-input-field-name
make-input-field
make-multi-input-field
input-field-name
input-field-type
input-field-transformer
input-field-attributes
input-field-html-tree-maker
input-field-html-tree
input-field-multi?
set-input-field-attributes!
touch-input-field!)))
;; For internal use: special exports to create
;; SXL-rules for input-fields
(define-interface surflets/internal-input-fields-interface
(export *input-field-trigger*
make-sxml-input-field))
(define-interface surflets/surflet-input-fields-interface
(compound-interface
surflets/input-field-value-interface
(export make-text-field
set-text-field-value!
make-number-field
set-number-field-value!
make-hidden-field
set-hidden-field-value!
make-password-field
set-password-field-value!
make-textarea
set-textarea-value!
make-select
make-annotated-select
make-simple-select-option
make-annotated-select-option
select-option?
select-select-option!
unselect-select-option!
add-select-option!
delete-select-option!
set-select-option-selected?!
make-radio-group
make-annotated-radio-group
make-radios
check-radio!
uncheck-radio!
set-radio-checked?!
make-checkbox
make-annotated-checkbox
check-checkbox!
uncheck-checkbox!
set-checkbox-checked?!
make-submit-button
make-reset-button
make-image-button)))
;; Some utilities
(define-interface surflets/utilities-interface
(export form-query-list
rev-append
generate-unique-number
generate-unique-name
identity))
;; Intelligent Addresses
(define-interface surflets/addresses-interface
(export make-address
make-annotated-address
address-name
address-annotated?
; address-add-annotation!
address-annotation))
(define-interface surflets/callbacks-interface
(export make-callback
make-annotated-callback
callback-function))
;; Returned-via (dispatcher for input-fields and intelligent
;; addresses)
(define-interface surflets/returned-via-interface
(export returned-via
returned-via?
(case-returned-via :syntax)))
;; Outdater denies access to outdated object
(define-interface surflets/outdaters-interface
(export make-outdater
(if-outdated :syntax)
show-outdated))
;; Access to form bindings in URL
(define-interface surflets/bindings-interface
(export get-bindings
get-content-length
extract-bindings
extract-single-binding))
;; HTML-Extensions to send/suspend et al. (for basic user)
(define-interface surflets/send-html-interface
(export send-html/suspend
send-html/finish
send-html))
(define-interface surflets/send-xml-interface
(export send-xml/finish
send-xml/suspend))
;; Helping functions for surflets (for basic user)
(define-interface surflets-interface
(compound-interface
; surflet-handler/surflets-interface;
; surflets/sxml-interface
; surflets/surflet-sxml-interface
surflets/send-html-interface
surflets/surflet-input-fields-interface
surflets/addresses-interface
surflets/returned-via-interface
surflets/bindings-interface
surflets/session-data-interface))
;;; Structures
;; structures from SUrflet Handler
(define-structures
((surflet-handler surflet-handler-interface)
(surflet-handler/surflets surflet-handler/surflets-interface)
(surflet-handler/options surflet-handler/options-interface)
(surflet-handler/resume-url surflet-handler/resume-url-interface)
(surflet-handler/admin surflet-handler/admin-interface)
(surflet-handler/primitives surflet-handler/primitives-interface)
(surflets/sessions surflets/sessions-interface)
(surflets/continuations surflets/continuations-interface)
(surflets/error surflets/error-interface)
(surflets/session-data surflets/session-data-interface))
(open define-record-types ;DEFINE-RECORD-TYPE
handle-fatal ;WITH-FATAL-ERROR-HANDLER* et al.
handle-fatal-error
httpd-errors ;errors for httpd
httpd-logging ;HTTP-SYSLOG
httpd-requests ;requests from httpd
httpd-responses ;replies for httpd
let-opt ;:OPTIONAL
locks ;MAKE-LOCK et al.
profiling ;PROFILE-SPACE
rt-modules ;get structures dynamically
scheme-with-scsh ;regexp et al.
search-trees
shift-reset ;SHIFT and RESET
(subset srfi-1 (alist-cons alist-delete!))
srfi-6 ;string-ports
srfi-14 ;CHAR-SET:DIGIT
srfi-27 ;random numbers
surflet-requests ;requests for surflets
surflet-responses ;responses from surflets
sxml-to-html ;SXML->HTML
tables ;HASH-TABLES
thread-cells ;THREAD-CELL et al.
thread-fluids ;FORK-THREAD
thread-safe-counter
threads ;SLEEP
uri ;URI-PATH-LIST->PATH
with-locks ;WITH-LOCK
)
(files surflet-handler))
;; SUrflets library of helping functions
(define-structure surflets surflets-interface
(open surflets/session-data
surflets/send-html ;send-html/suspend...
surflets/surflet-input-fields
surflets/addresses ;annotated-address...
surflets/returned-via
surflets/bindings))
(define-structure surflets/send-xml surflets/send-xml-interface
(open scheme
surflets/sxml
surflets/my-sxml
surflet-handler/primitives
surflet-handler/responses)
(files send-xml))
;; SUrflets library for advanced users: make and use your own
;; conversion rules.
(define-structure surflets/my-sxml surflets/my-sxml-interface
(open surflets/send-html-string
surflets/sxml
surflets/surflet-sxml))
;; SUrflets librarary for advanced users: access to session and
;; continuations and stuff.
(define-structure surflets/my-sessions surflets/my-sessions-interface
(open surflets/ids
surflets/continuations
surflets/session-data
surflet-handler/surflets
surflets/sessions
surflet-handler/options))
;; Shift-Reset
(define-structure shift-reset shift-reset-interface
(open scheme
signals
escapes
thread-cells)
(files shift-reset))
;; Measuring memory usage.
(define-structure profiling profiling-interface
(open let-opt
define-record-types
@ -495,186 +258,31 @@
srfi-13
srfi-1
locks
scheme-with-scsh)
scsh
scheme)
(files profile))
;; Simple Surflet API as known from PLT.
(define-interface simple-surflet-api-interface
(export single-query
queries
form-query
inform
final-page
make-password
make-number
make-boolean
make-radio
make-yes-no
extract/single
extract))
(define-structure simple-surflet-api simple-surflet-api-interface
(open scheme-with-scsh
(open scsh
scheme
define-record-types
let-opt
surflets
surflets/surflet-input-fields
(subset srfi-1 (zip filter find make-list))
handle-fatal-error
)
(files simple-surflet-api))
;; Handling every condition
(define-structure handle-fatal handle-fatal-interface
(open scheme conditions handle)
(files handle-fatal))
;; Thread-safe counter
(define-structure thread-safe-counter thread-safe-counter-interface
(open scheme
locks
define-record-types)
(files thread-safe-counter))
;; SUrflet-requests as expected from the SUrflet handler
;;; We have two names for the same thing to ease the use of structure
;;; names: requests seem to be part of the surflet-handler, but are
;;; actually seperate files. If you know everything about SUrflets,
;;; you use `surflet-requests'.
(define-structures
((surflet-handler/requests surflet-handler/requests-interface)
(surflet-requests surflet-handler/requests-interface))
(open scheme
define-record-types
httpd-requests)
(files surflet-request))
(define-structures
((surflet-handler/responses surflet-handler/responses-interface)
(surflet-responses surflet-handler/responses-interface))
(open scheme-with-scsh
define-record-types)
(files surflet-response))
;; Extensions to Olegs SSAX library
(define-structure surflets/sxml surflets/sxml-interface
(open scheme-with-scsh ;string-ports
(subset sxml-to-html (string->goodHTML entag))
(subset sxml-tree-trans (pre-post-order)))
(files sxml))
;; Input fields as Scheme objects
(define-structures
((surflets/input-field-value surflets/input-field-value-interface)
(surflets/my-input-fields surflets/my-input-fields-interface)
(surflets/internal-input-fields surflets/internal-input-fields-interface))
(open scheme-with-scsh ;error, format
(subset let-opt (:optional))
handle-fatal-error
define-record-types
surflets/sxml
surflets/utilities
)
(files input-fields))
(define-structure surflets/input-fields surflets/my-input-fields)
(define-structure surflets/surflet-input-fields
surflets/surflet-input-fields-interface
(open scheme-with-scsh ;error, format
;; avoid name collision for member
(modify srfi-1 (rename (member member/srfi-1)))
define-record-types
let-opt
surflets/my-input-fields
surflets/utilities ;generate-unique-number
surflets/sxml
tables ;make-integer-table
)
(files surflet-input-fields))
;; Extensions to SXML for surflets
(define-structure surflets/surflet-sxml surflets/surflet-sxml-interface
(open scheme-with-scsh ;error,receive
(subset surflets/my-input-fields (input-field-html-tree))
(subset surflets/internal-input-fields
(*input-field-trigger*
make-sxml-input-field))
surflets/utilities
(subset srfi-1 (make-list))
surflets/sxml)
(files surflet-sxml))
;; Access to session-id and continuation-id
(define-structure surflets/ids surflets/ids-interface
(open scheme
(subset surflet-requests (surflet-request-url))
(subset srfi-1 (last))
(subset surflet-handler/admin
(instance-session-id
resume-url-session-id
resume-url-continuation-id
resume-url-ids))
(subset url (http-url-path)))
(files ids))
;; Some utilities
(define-structure surflets/utilities surflets/utilities-interface
(open scheme
parse-html-forms)
(files utilities))
;; Intelligent Addresses
(define-structure surflets/addresses surflets/addresses-interface
(open scheme
srfi-23 ;error
(subset uri (escape-uri))
define-record-types
(subset surflets/utilities (generate-unique-name)))
(files addresses))
(define-structure surflets/callbacks surflets/callbacks-interface
(open scheme
srfi-23 ;error
surflets/addresses
(subset surflet-handler/primitives (send/suspend))
surflets/bindings
surflets/returned-via)
(files callbacks))
(define-structure surflets/returned-via surflets/returned-via-interface
(open scheme
surflets/input-field-value
surflets/addresses
(subset uri (unescape-uri)))
(files returned-via))
(define-structure surflets/outdaters surflets/outdaters-interface
(open scheme
define-record-types
surflets/send-html)
(files outdater))
(define-structure surflets/bindings surflets/bindings-interface
(open scheme-with-scsh ;read-string,error
locks
weak ;weak pointers
surflets/utilities ;form-query-list
surflet-requests
(subset url (http-url-search))
(subset srfi-14 (char-set:digit))
(subset srfi-13 (string-index string-trim))
(subset srfi-1 (filter))
(subset sunet-utilities (get-header)))
(files bindings))
(define-structures
((surflets/send-html surflets/send-html-interface)
(surflets/send-html-string surflets/send-html-string-interface))
(open scheme
surflet-handler/primitives
surflets/sxml
surflets/surflet-sxml)
(files send-html))
(define-structure with-locks with-locks-interface
(open scheme
locks)
(files with-locks))
;;; EOF
;;; Local Variables:
;;; buffer-tag-table: "../../../TAGS"
;;; End::

View File

@ -1,47 +0,0 @@
(define (returned-via return-object bindings)
(if (input-field? return-object)
(input-field-value return-object bindings)
;; We assume we have a return-address-object instead.
(let ((address (return-object 'address)))
(cond
((assoc (address-name address) bindings) =>
(lambda (pair)
(if (address-annotated? address)
(address-annotation address (cdr pair))
#t)))
(else #f)))))
;; It depends on the object, if returned-via returns only boolean
;; values or string values as well. So let us have both names.
(define returned-via? returned-via)
;; This is from Martin Gasbichler
(define-syntax case-returned-via
(syntax-rules (else =>)
((case-returned-via (%bindings ...) clauses ...)
(let ((bindings (%bindings ...)))
(case-returned-via bindings clauses ...)))
((case-returned-via bindings (else body ...))
(begin body ...))
((case-returned-via bindings
((%return-object ...) => %proc))
(cond ((or (returned-via %return-object bindings) ...)
=> %proc)))
((case-returned-via bindings
((%return-object ...) %body ...))
(if (or (returned-via? %return-object bindings) ...)
(begin %body ...)))
((case-returned-via bindings
((%return-object ...) => %proc)
%clause %clauses ...)
(cond ((or (returned-via %return-object bindings) ...)
=> %proc)
(else
(case-returned-via bindings %clause %clauses ...))))
((case-returned-via bindings
((%return-object ...) %body ...)
%clause %clauses ...)
(if (or (returned-via? %return-object bindings) ...)
(begin %body ...)
(case-returned-via bindings %clause %clauses ...)))))

View File

@ -0,0 +1,58 @@
;; rt-module.scm
;; Copyright Martin Gasbichler, 2002
;; Receipt:
;;(load-config-file "test.scm") --> nothing
;; load config file containing structure definition
;;
;; (reify-structure 'surflet) --> #{Rt-stucture surflet}
;; gets structure info about a structure
;;
;; (define surflet ##)
;; (load-structure surflet)
;; loads rt-structure
;;
;; (rt-structure-binding surflet 'main) --> value
;; get a binding of a structure
(define (interface-value-names interface-name)
(let ((interface (environment-ref (config-package) interface-name))
(value-names '()))
(for-each-declaration
(lambda (name base-neme type)
(if (not (equal? type syntax-type))
(set! value-names (cons name value-names))))
interface)
value-names))
(define-record-type rt-structure :rt-structure
(make-rt-structure meta-structure)
rt-structure?
(meta-structure rt-structure-meta-structure))
(define (rt-structure-loaded? rt-structure)
(package-loaded?
(structure-package (rt-structure-meta-structure rt-structure))))
(define-record-discloser :rt-structure
(lambda (s)
(list 'rt-stucture (structure-name (rt-structure-meta-structure s)))))
(define (reify-structure name)
(let ((struct (get-structure name)))
(make-rt-structure struct)))
(define (load-structure rts)
(ensure-loaded (rt-structure-meta-structure rts)))
(define (rt-structure-binding structure name)
(if (not (rt-structure-loaded? structure))
(error "Structure not loaded" structure))
(contents
(binding-place
(generic-lookup (rt-structure-meta-structure structure)
name))))
(define (load-config-file file)
(load file (config-package)))

View File

@ -1,50 +0,0 @@
;;; Allows sending of HTML represented in Oleg-like SXML-list instead
;;; of pure string.
;;; Copyright 2002,2003, Andreas Bernauer
;; Send surflet-sxml lists as HTML
(define (send-html/suspend html-tree-maker)
(send/suspend
(lambda (new-url)
(make-usual-html-response
(sxml->string (html-tree-maker new-url)
surflet-sxml-rules)))))
(define (send-html/finish html-tree)
(do-html-sending send/finish html-tree))
(define (send-html html-tree)
(do-html-sending send html-tree))
;; Semd strings as HTML
(define (send-html-string/suspend html-string-maker)
(send/suspend
(lambda (new-url)
(make-usual-html-response
(html-string-maker new-url)))))
(define (send-html-string/finish html-string)
(do-html-string-sending send/finish html-string))
(define (send-html-string html-string)
(do-html-string-sending send html-string))
;; Helping functions
(define (do-html-sending sender html-tree)
(do-html-string-sending
sender
(sxml->string html-tree surflet-sxml-rules)))
(define (do-html-string-sending sender html-string)
(sender (make-usual-html-response html-string)))
;; This is not for public, as we add the no-cache header that is
;; needed for SUrflets.
(define (make-usual-html-response html-string)
(make-surflet-response
(status-code ok)
"text/html"
'(("Cache-Control" . "no-cache"))
html-string))

View File

@ -1,60 +0,0 @@
(define processing-instruction-rule
`(*PI* *preorder*
. ,(lambda (tag . elems)
`(,(string-append "<?" (symbol->string (car elems)) " ")
,@(cdr elems)
"?>"))))
(define doctype-rule
`(*DOCTYPE* *preorder*
. ,(lambda (content . more)
`("<!DOCTYPE " ,@more ">"))))
(define xml-default-rule
`(*default* .
,(lambda (tag . elems)
(apply (entag tag) elems))))
(define (entag tag)
(lambda elems
(if (and (pair? elems) (pair? (car elems)) (eq? '@ (caar elems)))
(list #\< tag (cdar elems)
(if (pair? (cdr elems)) (list #\> (cdr elems) "</" tag #\>)
"/>"))
(list #\< tag
(if (pair? elems) (list #\> elems "</" tag #\>) "/>")))))
(define comment-rule
`(*COMMENT* *preorder*
. ,(lambda (tag . elems)
`("<!-- " ,@elems "-->"))))
(define xml-rules
(list attribute-rule
xml-default-rule
processing-instruction-rule
doctype-rule
text-rule
comment-rule
url-rule
plain-html-rule
nbsp-rule))
(define (make-xml-reponse xml-string)
(make-surflet-response
(status-code ok)
"text/xml"
'(("Cache-Control" . "no-cache"))
xml-string))
(define (send-xml/suspend xml-tree-maker)
(send/suspend
(lambda (k-url)
(make-xml-reponse
(sxml->string (xml-tree-maker k-url)
xml-rules)))))
(define (send-xml/finish xml-tree)
(send
(make-xml-reponse
(sxml->string xml-tree xml-rules))))

View File

@ -66,7 +66,7 @@
(br)
(p ,@text)
(br)
(url ,url "Continue"))))))
(URL ,url "Continue"))))))
;; Post some information on a Web page, shut down the surflet and all
;; its continuations.
@ -116,16 +116,16 @@
(else (no-method message)))))
(define (make-text text)
(standard-query text (make-text-field) "No bad input possible"))
(standard-query text (make-text-input-field) "No bad input possible"))
(define (make-password text)
(standard-query text (make-password-field) "No bad input possible"))
(standard-query text (make-password-input-field) "No bad input possible"))
(define (make-number text)
(standard-query text (make-number-field) "Please respond with a valid number"))
(standard-query text (make-number-input-field) "Please respond with a valid number"))
(define (make-boolean text)
(let* ((input-field (make-checkbox))
(let* ((input-field (make-checkbox-input-field))
(standard (standard-query text input-field "No bad input possible")))
(lambda (message)
(case message
@ -139,7 +139,7 @@
(define (make-radio text choices . maybe-insist)
(let* ((insist (:optional maybe-insist ""))
(radios (make-radios choices))
(radios (make-radio-input-fields choices))
(standard (standard-query text (car radios)
(string-append "Please respond" insist))))
(lambda (message)
@ -150,9 +150,7 @@
`(tr (td ,text)
(td (table (tr
,@(map (lambda (radio choice)
`((td ,radio ,choice
;; Add some distance
(nbsp)(nbsp))))
`((td ,radio) (td ,choice)))
radios
choices)))))))
(else

View File

@ -0,0 +1,210 @@
#!/bin/sh
echo "Loading..."
fullpath=`which $0`
# $sunet is either $SUNETHOME or created out of fullpath
# Kind of a hack, I know.
sunet=${SUNETHOME:-`dirname $fullpath`/../..}
ssax=${SSAXPATH:-$sunet/SSAX} # path to SSAX
exec scsh -lm $sunet/packages.scm -lm $ssax/lib/packages.scm -lm $sunet/httpd/surflets/packages.scm -dm -o surflet-server -e main -s "$0" "$@"
!#
(define-structure surflet-server
(export main ; sh jump entry point
server) ; scsh entry point
(open httpd-core
httpd-make-options
httpd-basic-handlers
httpd-file-directory-handlers
; cgi-server
; seval-handler
; rman-gateway
; info-gateway
surflet-handler
let-opt
scsh
scheme)
(begin
(define (usage)
(format #f
"Usage: start-surflet-server [-h htdocs-dir] [-s surflet-dir] [-i images-dir]
[-p port] [-l log-file-name]
[-r requests] [--help]
with
htdocs-dir directory of html files (default: ./web-server/root/htdocs)
surflet-dir directory of SUrflet files (default: ./web-server/root/surflets)
images-dir directory of images files (default: ./web-server/root/img)
port port server is listening to (default: 8080)
log-file-name directory where to store the logfile in CLF
(default: ./web-server/httpd.log)
requests maximal amount of simultaneous requests (default 5)
--help show this help
NOTE: This is the SUrflet-server. It does not support cgi-bin.
"
))
(define htdocs-dir #f)
(define images-dir #f)
; (define cgi-bin-dir #f)
(define port #f)
(define log-file-name #f)
(define root #f)
(define surflet-dir #f)
(define simultaneous-requests #f)
(define (init)
(set! htdocs-dir "./web-server/root/htdocs")
(set! images-dir "./web-server/root/img")
; (set! cgi-bin-dir "./web-server/root/cgi-bin")
(set! port "8088")
(set! log-file-name "./web-server/httpd.log")
(set! root "./web-server/root")
(set! surflet-dir "./web-server/root/surflets")
(set! simultaneous-requests "5"))
(define (normalize-options)
(set! htdocs-dir (absolute-file-name htdocs-dir))
(set! images-dir (absolute-file-name images-dir))
(set! log-file-name (absolute-file-name log-file-name))
; (set! cgi-bin-dir (absolute-file-name cgi-bin-dir))
(set! port (string->number port))
(set! surflet-dir (absolute-file-name surflet-dir))
(set! simultaneous-requests (string->number simultaneous-requests)))
(define get-options
(let* ((unknown-option-error
(lambda (option)
(format (error-output-port)
"unknown option `~A'~%try `surflet-server --help'~%"
option)
(exit 1)))
(missing-argument-error
(lambda (option)
(format (error-output-port)
"option `~A' requires an argument~%try `surflet-server --help'~%"
option)
(exit 1))))
(lambda (options)
(let loop ((options options))
(if (null? options)
(normalize-options)
(cond
((string=? (car options) "-h")
(if (null? (cdr options))
(missing-argument-error (car options))
(set! htdocs-dir (cadr options)))
(loop (cddr options)))
((string=? (car options) "-i")
(if (null? (cdr options))
(missing-argument-error (car options))
(set! images-dir (cadr options)))
(loop (cddr options)))
((string=? (car options) "-c")
(format (error-output-port)
"This is the SUrflet server. It does not support cgi.~%")
; (if (null? (cdr options))
; (missing-argument-error (car options))
; (set! cgi-bin-dir (cadr options)))
(loop (cddr options)))
((string=? (car options) "-p")
(if (null? (cdr options))
(missing-argument-error (car options))
(set! port (cadr options)))
(loop (cddr options)))
((string=? (car options) "-l")
(if (null? (cdr options))
(missing-argument-error (car options))
(set! log-file-name (cadr options)))
(loop (cddr options)))
((string=? (car options) "-s")
(if (null? (cdr options))
(missing-argument-error (car options))
(set! surflet-dir (cadr options)))
(loop (cddr options)))
((string=? (car options) "-r")
(if (null? (cdr options))
(missing-argument-error (car options))
(set! simultaneous-requests (cadr options)))
(loop (cddr options)))
((string=? (car options) "--help")
(display (usage))
(exit 0))
((string=? (car options) "--dump")
(let ((image-name (if (null? (cdr options))
"surflet-server"
(cadr options))))
(dump-scsh-program main image-name))
(exit 0))
(else
(unknown-option-error (car options)))))))))
(define (server . args)
(if (pair? args)
(main `(main ,@(car args)))
(main '(main))))
(define (main args)
(init)
(format #t "reading options: ~s~%" (cdr args))
(get-options (cdr args))
(cond ((zero? (user-uid))
(set-gid (->gid "nobody"))
(set-uid (->uid "nobody"))))
(format #t "Going to run SUrflet server with:
htdocs-dir: ~a
surflet-dir: ~a
images-dir: ~a
port: ~a
log-file-name: ~a
a maximum of ~a simultaneous requests, syslogging activated,
and home-dir-handler (public_html) activated.
NOTE: This is the SUrflet server. It does not support cgi.
"
htdocs-dir
surflet-dir
images-dir
port
log-file-name
simultaneous-requests)
(httpd (with-port port
(with-root-directory (cwd)
(with-simultaneous-requests simultaneous-requests
(with-syslog? #t
(with-logfile log-file-name
;; The following settings are made to avoid dns lookups.
(with-reported-port port
(with-fqdn "localhost"
(with-resolve-ips? #f
(with-request-handler
(alist-path-dispatcher
(list (cons "h" (home-dir-handler "public_html"))
; (cons "seval" seval-handler)
; (cons "man" (rman-handler #f "man?%s(%s)"
; "Generated by rman-gateway"))
; (cons "info" (info-handler #f #f #f
; "Generated by info-gateway"))
; (cons "cgi-bin" (cgi-handler cgi-bin-dir))
;; Browsers cannot handle .SCM files: Opera shows
;; it as HTML, Netscape asks for a program to
;; view it. ROOTED-FILE-OR-DIRECTORY-HANDLER
;; should serve .SCM files as text/plain (I did
;; not want to write a handler just for this file
;; type.)
(cons "source" (rooted-file-or-directory-handler surflet-dir))
(cons "img" (rooted-file-handler images-dir))
(cons "surflet" (surflet-handler surflet-dir)))
(rooted-file-or-directory-handler htdocs-dir)))))))))))
))
))
;; EOF
;;; Local Variables:
;;; mode:scheme
;;; End:

File diff suppressed because it is too large Load Diff

View File

@ -1,509 +0,0 @@
;;; SUrflets' input fields
;;; Copyright 2002, 2003 Andreas Bernauer
;;; With additions from Eric Knauel (2003)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions to create simple input fields
;; The interface for input-fields does not prescribe what the type of
;; attributes has to be. We choose a record here.
(define-record-type field-attributes :field-attributes
(make-field-attributes default others)
field-attributes?
(default field-attributes-default set-field-attributes-default!)
(others field-attributes-others set-field-attributes-others!))
;; A simple input-field is a prototype for other input-fields.
;; REPORTED-TYPE is the type of the input-field in HTML, TYPE the
;; internal referenced type and TRANSFORMER the function that
;; translates the HTTP-string of the request into a scheme value.
(define (simple-field-maker reported-type type default-pred transformer)
(lambda maybe-further-attributes
(let ((name (generate-input-field-name type)))
(let-optionals maybe-further-attributes
((default "" default-pred)
(attributes '() sxml-attribute?))
(make-input-field name type
transformer
(make-field-attributes
(and default `(value ,default))
(sxml-attribute-attributes attributes))
(simple-html-tree-maker reported-type))))))
(define (simple-html-tree-maker reported-type)
(lambda (input-field)
(let ((attributes (input-field-attributes input-field)))
`(input (@ (type ,reported-type)
(name ,(input-field-name input-field))
,(field-attributes-default attributes)
,(field-attributes-others attributes))))))
(define (make-simple-default-setter default-pred? error-msg-types)
(lambda (input-field value)
(if (default-pred? value)
(set-field-attributes-default!
(input-field-attributes input-field)
`(value ,value))
(error (format #f "Default value must be ~a." error-msg-types)
value))
(touch-input-field! input-field)))
(define (string-or-symbol? thing)
(or (string? thing) (symbol? thing)))
(define simple-default? string-or-symbol?)
(define set-simple-field-default!
(make-simple-default-setter simple-default? "a string or a symbol"))
(define (second-arg first second) second)
;;;;;;;;;;;;;;;;;;;;
;;; Text input field
(define make-text-field
(simple-field-maker "text" "text" simple-default? second-arg))
(define set-text-field-value! set-simple-field-default!)
;;;;;;;;;;;;;;;;;;;;;;
;;; Number input field
(define (number-field-default? value)
(or (number? value)
(simple-default? value)))
(define (number-field-transformer input-field string)
(or (string->number string)
(error "wrong type")))
(define make-number-field
(simple-field-maker "text" "number"
number-field-default? number-field-transformer))
(define set-number-field-value!
(make-simple-default-setter number-field-default?
"a number a string or a symbol"))
;;;;;;;;;;;;;;;;;;;;;;
;;; hidden input-field
;; The programmer should supply a default value for this input-field
;; as it is hidden.
(define make-hidden-field
(simple-field-maker "hidden" "hidden"
simple-default? second-arg))
(define set-hidden-field-value! set-simple-field-default!)
;;;;;;;;;;;;;;;;;;;;;;;;
;;; Password input field
(define make-password-field
(simple-field-maker "password" "password"
simple-default? second-arg))
(define set-password-field-value! set-simple-field-default!)
;;; That's it for simple input fields.
;;;;;;;;;;;;;;;;;;;;;;;;
;;; Textarea input field
(define (make-textarea . maybe-further-attributes)
(let-optionals maybe-further-attributes
((default-text "" simple-default?)
(rows 5 number?)
(cols 20 number?)
(readonly #f boolean?)
(attributes '() sxml-attribute?))
(let ((name (generate-input-field-name "textarea"))
(all-attributes `((cols ,cols)
(rows ,rows)
,@(if readonly '(readonly) '())
,@(sxml-attribute-attributes attributes))))
(make-input-field
name "textarea"
second-arg
(make-field-attributes (and default-text)
all-attributes)
make-textarea-html-tree))))
(define (make-textarea-html-tree textarea)
(let ((attributes (input-field-attributes textarea)))
`(textarea (@ (type "textarea")
(name ,(input-field-name textarea))
,(field-attributes-others attributes))
,(field-attributes-default attributes))))
(define (set-textarea-value! textarea value)
(if (simple-default? value)
(set-field-attributes-default!
(input-field-attributes textarea)
value)
(error "Default value must be a string or a symbol." value))
(touch-input-field! textarea))
;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Select input field
;(make-select '("this" "that" "those") '(@ ((id "sushi"))))
;(make-select '("this" ("that" '(@ (selected))) "those"))
;; dropdown: (size 1)
;;; A select input field shows a list of options that can be
;;; selected. For this purpose, we introduce an select-option record,
;;; that contains all the information for each option. This is
;;; justified by the fact, that the options list is seperated in HTML,
;;; too. The TAG is the string that is displayed in the website, the
;;; VALUE is the value that is returned by input-field-value, if this
;;; option was selected. TAG is assumed to be unique by some functions
;;; (e.g. select and unselect) SELECTED? tells us, if this option is
;;; preselected.
(define-record-type select-option :select-option
(really-make-select-option tag value selected? attributes)
select-option?
(tag select-option-tag)
(value select-option-value)
(selected? select-option-selected? really-set-select-option-selected?!)
(attributes select-option-attributes set-select-option-attributes!))
;; No check of attributes as this is done by calling function. (This
;; function isn't exported.
(define (make-select-option tag value selected? attributes)
(if (string? tag)
(really-make-select-option tag value selected?
(sxml-attribute-attributes attributes))
(error "Select-option's tag must be a string." tag)))
;; Constructor for valued select input-field option.
(define (make-annotated-select-option tag value . maybe-attributes)
(let-optionals maybe-attributes
((selected? #f boolean?)
(attributes '() sxml-attribute?))
(make-select-option tag value selected? attributes)))
;; Constructor for a simple select input-field option (not annotated).
(define (make-simple-select-option tag . maybe-attributes)
(let-optionals maybe-attributes
((selected? #f boolean?)
(attributes '() sxml-attribute?))
(make-select-option tag tag selected? attributes)))
(define-record-discloser :select-option
(lambda (select-option)
(list 'select-option
(select-option-tag select-option)
(select-option-value select-option)
(select-option-selected? select-option)
(select-option-attributes select-option)
)))
;; Selecting / Unselecting of an option in an select input-field,
;; chosen by tag.
(define (select-select-option! tag select)
(set-select-option-selected?! tag select #t))
(define (unselect-select-option! tag select)
(set-select-option-selected?! tag select #f))
(define (set-select-option-selected?! tag select selected?)
(let ((options (field-attributes-default
(input-field-attributes select))))
(if (number? tag) ; is tag an index?
(really-set-select-option-selected?! (list-ref options tag)
selected?)
(let lp ((options options))
(if (null? options)
(error "No such option" tag select)
(if (tag=select-option? tag (car options))
(really-set-select-option-selected?! (car options)
selected?)
(lp (cdr options))))))
(touch-input-field! select)))
;; Find select-option in a list by its tag.
(define (tag=select-option? tag select-option)
(string=? tag (select-option-tag select-option)))
(define (find-select-option tag select-options)
(cond ((member/srfi-1 tag select-options tag=select-option?)
=> car)
;; MEMBER/SRFI-1 returns the sublist that starts with the
;; searched element.
(else #f)))
(define (find-select-option-value tag select-options)
(cond ((find-select-option tag select-options)
=> select-option-value)
(else #f)))
(define (add-select-option! select select-option)
(let ((attributes (input-field-attributes select)))
(set-field-attributes-default!
attributes
(cons select-option
(field-attributes-default attributes)))
(touch-input-field! select)))
(define (delete-select-option! select select-option)
(let* ((attributes (input-field-attributes select))
(select-options (field-attributes-default attributes)))
(if (select-option? select-option)
(set-field-attributes-default!
attributes
(delete select-option select-options))
(let ((tag select-option))
(set-field-attributes-default!
attributes
(delete tag select-options tag=select-option?))))
(touch-input-field! select)))
;; To be compatible with previous versions of MAKE-SELECT-INPUT-FIELD,
;; we accept also a simple list as an option-list. New programs should
;; use select-options-list (easily createable with
;; (map make-simple-select-option option-list))
(define (simple-options select-options)
(if (and (list? select-options)
(every select-option? select-options))
select-options
(map make-simple-select-option select-options)))
(define (make-select select-options . maybe-further-attributes)
(let ((real-select-options (simple-options select-options)))
(let-optionals maybe-further-attributes
((multiple? #f boolean?)
(attributes '() sxml-attribute?))
(let ((name (generate-input-field-name "select")))
(if multiple?
(make-multiple-select name real-select-options attributes)
(make-single-select name real-select-options
attributes))))))
;; deprecated: Does not introduce further functionality.
(define make-annotated-select make-select)
;; internal
(define (make-multiple-select name select-options attributes)
(make-multi-input-field name "mult-select"
multiple-select-transformer
(make-field-attributes
select-options
`((multiple)
,@(sxml-attribute-attributes attributes)))
make-select-html-tree))
;; internal
(define (make-single-select name select-options attributes)
(make-input-field name "select"
(lambda (input-field tag)
(cond ((find-select-option-value tag select-options)
=> identity)
(else (error "no such option." tag))))
(make-field-attributes
select-options
(sxml-attribute-attributes attributes))
make-select-html-tree))
(define (multiple-select-transformer select bindings)
(let ((name (input-field-name select))
(select-options (field-attributes-default
(input-field-attributes select))))
(let* ((my-bindings (filter (lambda (binding)
(equal? (car binding) name))
bindings))
(tags (map cdr my-bindings)))
(filter-map (lambda (tag)
(find-select-option-value tag select-options))
tags))))
(define (make-select-html-tree select)
(let ((attributes (input-field-attributes select)))
`(select (@ (name ,(input-field-name select))
,(field-attributes-others attributes))
#\newline
,@(make-select-options-html-tree
(field-attributes-default attributes)))))
(define (make-select-options-html-tree select-options)
(map (lambda (select-option)
`(option (@ ,(and (select-option-selected? select-option) '(selected))
,(select-option-attributes select-option))
,(select-option-tag select-option)))
select-options))
;;;;;;;;;;;;;;;;;;;;;;
;;; radio input-fields
;; Because grouped radio input-fields must use the same name, we
;; cannot just return one radio input-field object, but we must
;; generate several ones with the same name.
(define (make-radio-group)
(let ((name (generate-input-field-name "radio")))
(lambda (value-string . maybe-further-attributes)
(let-optionals maybe-further-attributes
((checked? #f boolean?)
(attributes '() sxml-attribute?))
(make-input-field name "radio"
second-arg
(make-field-attributes
(and checked? '(checked))
`((value ,value-string)
,@(sxml-attribute-attributes attributes)))
radio-html-tree-maker)))))
(define (make-annotated-radio-group)
(let* ((name (generate-input-field-name "radio"))
(value-table (make-integer-table))
(transformer (make-radio-transformer value-table)))
(lambda (value . maybe-further-attributes)
(let-optionals maybe-further-attributes
((checked? #f boolean?)
(attributes '() sxml-attribute?))
(let ((number (generate-unique-number)))
(table-set! value-table number value)
(make-input-field name "radio"
transformer
(make-field-attributes
(and checked? '(checked))
`((value ,(number->string number))
,@(sxml-attribute-attributes attributes)))
radio-html-tree-maker))))))
(define (make-radios values . maybe-further-attributes)
(let-optionals maybe-further-attributes
((attributes '() sxml-attribute?))
(let ((radio-gen (make-annotated-radio-group)))
(map (lambda (value)
(if attributes
(radio-gen value attributes)
(radio-gen value)))
values))))
(define (make-radio-transformer value-table)
(lambda (input-field tag)
(cond
((string->number tag) =>
(lambda (number)
(let ((value (table-ref value-table number)))
(if value
value
(error "Unknown tag number for radio input-field" tag)))))
(else
(error "Unknown tag number for radio input-field" tag)))))
(define (radio-html-tree-maker radio)
(let* ((attributes (input-field-attributes radio)))
`(input (@ ((type "radio")
(name ,(input-field-name radio))
,(field-attributes-default attributes)
,(field-attributes-others attributes))))))
(define (set-input-field-checked?! input-field checked?)
(let ((attributes (input-field-attributes input-field)))
(set-field-attributes-default!
attributes
(if checked? '(checked) #f))
(touch-input-field! input-field)))
(define set-radio-checked?! set-input-field-checked?!)
(define (check-radio! radio) (set-radio-checked?! radio #t))
(define (uncheck-radio! radio) (set-radio-checked?! radio #f))
;;;;;;;;;;;;;;;;;;;;;;;;
;;; checkbox input-field
(define (make-checkbox . maybe-further-attributes)
(really-make-checkbox 'defined-in-checkbox-transformer
checkbox-transformer
maybe-further-attributes))
(define (make-annotated-checkbox value . maybe-further-attributes)
(really-make-checkbox value
(make-checkbox-transformer value)
maybe-further-attributes))
(define (really-make-checkbox value transformer attributes)
(let ((name (generate-input-field-name "checkbox")))
(let-optionals attributes
((checked? #f boolean?)
(attributes '() sxml-attribute?))
(make-input-field name "checkbox"
transformer
(make-field-attributes
(and checked? '(checked))
(sxml-attribute-attributes attributes))
checkbox-html-tree-maker))))
(define (make-checkbox-transformer value)
(lambda (input-field tag)
(if (string=? tag "on")
value
#f)))
(define checkbox-transformer (make-checkbox-transformer #t))
(define (checkbox-html-tree-maker checkbox)
(let ((attributes (input-field-attributes checkbox)))
`(input (@ ((type "checkbox")
(name ,(input-field-name checkbox))
,(field-attributes-default attributes)
,(field-attributes-others attributes))))))
(define set-checkbox-checked?! set-input-field-checked?!)
(define (check-checkbox! checkbox) (set-checkbox-checked?! checkbox #t))
(define (uncheck-checkbox! checkbox) (set-checkbox-checked?! checkbox #f))
;;;;;;;;;;;;;;;;;;;;;;
;; button input-fields
(define (make-button type name button-caption attributes)
(make-input-field name type
second-arg
(make-field-attributes
(and button-caption `(value ,button-caption))
(sxml-attribute-attributes attributes))
make-button-html-tree))
(define (make-button-html-tree button)
(let ((attributes (input-field-attributes button)))
`(input (@ (type ,(input-field-type button))
(name ,(input-field-name button))
,(field-attributes-default attributes)
,(field-attributes-others attributes)))))
(define (make-submit-button . maybe-further-attributes)
(let-optionals maybe-further-attributes
((button-caption #f string?)
(attributes '() sxml-attribute?))
(make-button "submit" (generate-input-field-name "submit")
button-caption attributes)))
(define (make-reset-button . maybe-further-attributes)
(let-optionals maybe-further-attributes
((button-caption #f string?)
(attributes '() sxml-attribute?))
(make-button "reset" (generate-input-field-name "reset")
button-caption attributes)))
;; Image buttons cannot be simple buttons, as the browser does not
;; send their simple name, but the coordinates where the user clicked
;; into. Thanks to Eric Knauel for reporting this bug.
(define (make-image-button image-source . maybe-further-attributes)
(let-optionals maybe-further-attributes
((attributes '() sxml-attribute?))
(make-multi-input-field (generate-input-field-name "imgbtn")
"image"
image-button-transformer
(make-field-attributes
`(src ,image-source)
(sxml-attribute-attributes attributes))
make-button-html-tree)))
;; The following two functions are from Eric Knauel's fix for the
;; image-button bug:
(define (image-button-transformer image-button bindings)
(let ((x (find-image-button-coordinate image-button bindings ".x"))
(y (find-image-button-coordinate image-button bindings ".y")))
(let ((x-number (string->number x))
(y-number (string->number y)))
(and x y
(if (and x-number y-number)
(cons x-number y-number)
(error "Image button coordinates aren't numbers. " x y))))))
(define (find-image-button-coordinate image-button bindings suffix)
(let* ((name (input-field-name image-button)))
(cond
((assoc (string-append name suffix) bindings)
=> cdr)
(else #f))))
;;EOF

View File

@ -1,24 +0,0 @@
;; Copyright Andras Bernauer (2003)
;; somehow `extend' httpd-request
(define-record-type surflet-request :surflet-request
(make-surflet-request request input-port)
surflet-request?
(request surflet-request-request)
(input-port surflet-request-input-port))
(define-record-discloser :surflet-request
(lambda (r)
(list 'surflet-request
(surflet-request-request r))))
(define (make-fake-selector request-selector)
(lambda (surflet-request)
(request-selector (surflet-request-request surflet-request))))
(define surflet-request-method (make-fake-selector request-method))
(define surflet-request-uri (make-fake-selector request-uri))
(define surflet-request-url (make-fake-selector request-url))
(define surflet-request-version (make-fake-selector request-version))
(define surflet-request-headers (make-fake-selector request-headers))
(define surflet-request-socket (make-fake-selector request-socket))

View File

@ -1,31 +0,0 @@
;;; SURFLET-RESPONSE: Surflets are expected to return this object type.
;;; STATUS is the status code, an exact integer. See httpd/response.scm
;;; e.g. (status-code ok)
;;; CONTENT-TYPE is a string, most probably "text/html".
;;; HEADERS is a (maybe empty) list of pairs of (string or symbol);
;;; Additional headers to send, e.g. '(("Cache-Control" . "no-cache")) or
;;; '((Cache-Control . "no-cache")) etc.
;;; DATA is either
;;; * a string
;;; * a list of strings
;;; This list maybe extended to vectors later.
(define-record-type surflet-response :surflet-response
(make-surflet-response status content-type headers data)
surflet-response?
(status surflet-response-status)
(content-type surflet-response-content-type)
(headers surflet-response-headers)
(data surflet-response-data))
;; Allowed type for the data field.
(define (valid-surflet-response-data? data)
(or (string? data) (list? data)))
;; For debug purposes
(define (surflet-response->string surflet-response)
(format #f "#{SUrflet-response Status: ~a Content-Type: ~s Headers: ~s~%~s~%"
(surflet-response-status surflet-response)
(surflet-response-content-type surflet-response)
(surflet-response-headers surflet-response)
(surflet-response-data surflet-response)))

Some files were not shown because too many files have changed in this diff Show More