chiark / gitweb /
userv (1.1.1) unstable; urgency=low
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 8 Jun 2012 19:16:52 +0000 (20:16 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 8 Jun 2012 19:16:52 +0000 (20:16 +0100)
  * Include INIT INFO stanza in init script.
  * Draft support for `status' in init script, currently commented
    out pending inclusion into policy of firm specification.
  * Remove spec.ps on make clean.
  * Remove spec.ps and dh log on debian/rules clean.  (Use dh_clean.)
    (To repro bug: dpkg-buildpackage, debian/rules clean, git-ls-files -o)
  * Fix up some copyright messages.

[dgit import package userv 1.1.1]

46 files changed:
COPYING [new file with mode: 0644]
INSTALL [new file with mode: 0644]
Makefile.in [new file with mode: 0644]
README [new file with mode: 0644]
acconfig.h [new file with mode: 0644]
both.c [new file with mode: 0644]
both.h [new file with mode: 0644]
client.c [new file with mode: 0644]
common.h [new file with mode: 0644]
config.h.in [new file with mode: 0644]
configure [new file with mode: 0755]
configure.in [new file with mode: 0644]
daemon.h [new file with mode: 0644]
debian/changelog [new file with mode: 0644]
debian/compat [new file with mode: 0644]
debian/conffiles [new file with mode: 0644]
debian/control [new file with mode: 0644]
debian/copyright [new file with mode: 0644]
debian/initd [new file with mode: 0644]
debian/lintian [new file with mode: 0644]
debian/postinst [new file with mode: 0644]
debian/postrm [new file with mode: 0644]
debian/prerm [new file with mode: 0644]
debian/rules [new file with mode: 0755]
debug.c [new file with mode: 0644]
install-sh [new file with mode: 0755]
language.i4 [new file with mode: 0644]
lexer.c [new file with mode: 0644]
lexer.l [new file with mode: 0644]
lexer.l.m4 [new file with mode: 0644]
lib.c [new file with mode: 0644]
lib.h [new file with mode: 0644]
overlord.c [new file with mode: 0644]
overview.fig [new file with mode: 0644]
overview.ps [new file with mode: 0644]
parser.c [new file with mode: 0644]
process.c [new file with mode: 0644]
servexec.c [new file with mode: 0644]
spec.sgml [new file with mode: 0644]
spec.sgml.in [new file with mode: 0644]
system.default [new file with mode: 0644]
system.override [new file with mode: 0644]
tokens.h [new file with mode: 0644]
tokens.h.m4 [new file with mode: 0644]
userv.1 [new file with mode: 0644]
uservd.8 [new file with mode: 0644]

diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..e77696a
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                          675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..46d3bdc
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,192 @@
+INSTALLATION INSTRUCTIONS:
+
+   $ ./configure
+   $ make
+   # make install
+
+This will not install the documentation, which is shipped as
+pre-prepared HTML and PostScript as well as debiandoc-sgml source.
+Put that (spec.html/ and spec.ps) where you will.
+
+SYSTEM REQUIREMENTS:
+
+Programs:
+
+* md5sum (GNU textutils; alternatively, Colin Plumb's, as shipped with
+          Debian in the `dpkg' package) - used during build only
+* GNU make
+* GCC is preferred but other compilers ought to work (though
+  no portability testing has yet been done).  ANSI C only.
+  ints must be at least 32 bits.
+* A sensible `cat' which notices write errors (ie not SunOS4, BSD 4.3,
+  or many others.  GNU textutils has one.  NB that this `cat' is used
+  at runtime and must be first on the system default PATH, so probably
+  in /bin.)
+* GNU m4   } if you don't want to change those bits of source
+* GNU flex }  you can do without these
+
+C Library:
+
+* [v]snprintf - a real version, not just [v]sprintf with a wrapper
+  that throws the argument away.
+* strsignal;
+* fnmatch;
+* BSD syslog(3);
+* strtoul;
+* memcpy, memset, memcpy;
+* realloc(0,size) must work and be equivalent to malloc(size).
+* free(0) must work and do nothing
+* <stdarg.h> (not varargs) and v[sf][n]printf.
+
+System interfaces:
+
+* setreuid(2), getreuid(2), getgroups(2), initgroups(3), with
+  the ability for root to (a) swap euid and ruid and
+  (b) give away all privilege by calling setreuid(ruid,ruid)
+  twice.
+* wait3 and waitpid, <wait.h> with WNOHANG, WIFSIGNALED,
+  WIFEXITEED, WTERMSIG, WEXITSTATUS and WCOREDUMP.
+* gid_t, uid_t, pid_t.
+* Unix-domain (AF_UNIX) stream sockets, for use with:
+  * BSD sockets - socket(), bind(), listen(), accept(), connect();
+  * socketpair(2);
+* lstat(2) (though stat(2) will be safe on systems without symlinks,
+  if you say -Dlstat=stat).
+* Pipes:
+  * creating using pipe(2) and mkfifo(2);
+  * proper interaction between open(O_RDWR), open(O_RDONLY),
+    open(O_WRONLY), close(), dup2, EPIPE, SIGPIPE, &c.
+    (ie, opening pipes with O_RDWR never blocks; EPIPE happens
+    if you write with no readers; EOF happens if you read with
+    no buffered data and writers);
+* POSIX signal handling - sigaction(2), sigprocmask(2), sigsuspend(2);
+* POSIX sessions - setsid(2) (for -daemon flag).
+
+To format the documentation:
+
+* debiandoc-sgml, and hence sp (aka nsgmls) and sgmlspm.
+* For PostScript output, Lout and possibly psutils.
+
+For debugging version (./configure --enable-debug):
+
+* initgroups(3) must use setgroups(2) and dynamic
+  linking must allow overriding setgroups(2) for initgroups(3);
+
+EXIT STATUS CODES
+
+For information about uservd's exit status, see uservd(8).
+
+The daemon's per-request children will note the success level of its
+request in its exit status.  This will not usually be logged unless it
+is higher than those listed below; they are presented here for
+completeness and as programming documentation.
+
+ 2 - The connection was just an internal version check.
+
+ 4 - The client requested that the service be disconnected.  This
+     includes normal termination, which is achieved by having the
+     server tell the client that the service has completed and waiting
+     for the client to tell it to disconnect.
+
+ 8 - The client closed its end of the socket when this would not
+     usually have been expected, causing an EPIPE or unexpected EOF in
+     the server.  This is not an error condition - it can happen, for
+     example, if the client receives a fatal signal of some kind from
+     its execution environment (eg its controlling terminal).
+
+ 10 - The per-request child wishes the server to check whether it is
+      still the uservd.
+
+ 12 - The service failed onm the service side in an expected and
+      controlled manner, for example because it was rejected in the
+      configuration files.
+
+ 16 - A fatal system call failure or other general error occurred,
+      which ought not to have happened at all, barring system resource
+      shortages.
+
+ 20 - The client sent invalid data to the server, after the client
+      dropped all its system privilege.  On some systems this can be
+      caused by a malicious calling user.
+
+ SIGABRT/SIGIOT - The client sent invalid data to the server before it
+   dropped all its system privileges, or some other unexpected
+   internal error occurred.  This can also occur if an attempt to
+   block signals using sigprocmask fails.
+
+ 0-3,5-7,9,11,13-15,17-19 are not currently used by normal children.
+
+REENTRANCY IN THE LIBC:
+
+We assume, both in the client and server, that it is safe to use one
+stdio stream in a signal handler which interrupts use of a _different_
+stdio stream in another.  We make sure using setvbuf that we have
+pre-allocated buffers so that stdio doesn't need to use malloc() when
+we actually read or write.  stdio had better not do anything else
+weird.
+
+Furthermore, we assume that it is safe to use syslog in a signal
+handler which has interrupted a stdio operation (but we don't require
+that it be safe to invoke when the signal has interrupted a call to
+malloc, unless stdio makes gratuitous mallocs).  openlog will already
+have been called (but syslog will not necessarily have been called).
+
+We assume that strerror is completely reentrant.
+
+PROBLEMS
+
+* `function declaration isn't a prototype'
+
+  One some systems (at least some versions of NetBSD, for example),
+  the SIG_IGN and SIG_DFL macros contain function declarations (as part
+  of a typecast, presumably) which are not prototypes.  The warning
+  options that are used by default if the configure script detects that
+  you're using a good GCC then cause the compilation to fail.  You must
+  use
+    make CFLAGS=-O2
+  instead of just `make', thus suppressing warnings.
+
+  The bug is actually in your system header files, for not specifying
+  the number and types of arguments to signal handler functions when
+  they cast in the SIG_IGN and SIG_DFL macros.
+
+DEBUGGING VERSION
+
+If you run configure with --enable-debug, a debugging version will be
+built.  This will look in the current directory (the build directory)
+for the base of various things, including the IPC area (which you must
+therefore construct yourself).  The debugging version will produce
+extra output at various points.  It will not attempt to call
+setgroups(), instead just checking that the groups list is right, so
+it will work non-setuid if the daemon is invoked as the service user.
+The daemon won't fork for each request; instead, it will handle a
+single request and exit.
+
+There may be other changes.  Consult the code for details.  Making the
+debugging version of the client or daemon setuid root is probably a
+bad idea.  They may not work if they are run as different users.
+
+
+COPYRIGHT
+
+This file, INSTALL, contains installation instructions and other
+details for userv.
+
+userv is
+Copyright (C)1996-2000 Ian Jackson <ian@davenant.greenend.org.uk>.
+Copyright (C)2000      Ben Harris <bjh21@cam.ac.uk>
+
+userv is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with userv as the file COPYING; if not, email me at the address
+above or write to the Free Software Foundation, 59 Temple Place -
+Suite 330, Boston, MA 02111-1307, USA.
diff --git a/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..7ef52c2
--- /dev/null
@@ -0,0 +1,202 @@
+#  userv - Makefile.in
+#  
+#  Copyright (C)1996-1997,1999 Ian Jackson
+#  
+#  This is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+#  
+#  This program is distributed in the hope that it will be useful, but
+#  WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  General Public License for more details.
+#  
+#  You should have received a copy of the GNU General Public License
+#  along with userv; if not, write to the Free Software
+#  Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+VERSION=@VERSION@
+VEREXT=std
+
+CC=@CC@
+CFLAGS=@CFLAGS@ $(XCFLAGS) -DVERSION='"$(VERSION)"' -DVEREXT='"$(VEREXT)"' $(WERROR)
+OPTIMISE=@OPTIMISE@
+CPPFLAGS=@DEBUGDEFS@ $(XCPPFLAGS)
+LDLIBS=@DEBUGLIBS@ @LIBS@ $(XLDLIBS)
+
+M4=m4
+M4FLAGS=
+LEX=flex
+MD5SUM=@MD5SUM_SIMPLE@
+CWD=$(shell pwd)
+
+INSTALL_GROUP=0  # root or wheel
+INSTALL_FLAGS=-o root -g $(INSTALL_GROUP)
+
+INSTALL=@INSTALL@
+INSTALL_PROGRAM=@INSTALL_PROGRAM@ $(INSTALL_FLAGS)
+INSTALL_DATA=@INSTALL_DATA@ $(INSTALL_FLAGS)
+prefix=@prefix@
+exec_prefix=$(prefix)
+bindir=$(exec_prefix)/bin
+mandir=$(prefix)/man
+man1dir=$(mandir)/man1
+man8dir=$(mandir)/man8
+sbindir=$(exec_prefix)/sbin
+etcdir=/etc
+etcsubdir=$(etcdir)/userv
+docdir=$(prefix)/userv
+
+TARGETS=       daemon client
+TARGETS_DOC_PS=        spec.ps overview.ps
+TARGETS_DOC_RM=        $(TARGETS_DOC_PS) spec.html
+TARGETS_DOC=   $(TARGETS_DOC_PS) spec.html/index.html
+MAN1PAGES=     userv.1
+MAN8PAGES=     uservd.8
+
+SOURCES=       Makefile.in configure.in acconfig.h                     \
+               client.c common.h                                       \
+               overlord.c process.c servexec.c                         \
+               daemon.h debug.c parser.c lib.c lib.h                   \
+               language.i4 lexer.l.m4 tokens.h.m4
+
+CONFIG_RESULTS=        config.status config.log config.h config.cache \
+               Makefile pcsum.h
+
+GENSHIP_CLEAN= lexer.l lexer.c tokens.h config.h.in spec.sgml $(TARGETS_DOC)
+GENSHIP=       $(GENSHIP_CLEAN) configure
+
+all:           $(TARGETS)
+
+docs:          $(TARGETS_DOC)
+
+install:       all
+               $(INSTALL_PROGRAM) -m 755 daemon $(sbindir)/uservd
+               $(INSTALL_PROGRAM) -m 4755 client $(bindir)/userv
+               $(INSTALL) -d -m 2755 $(etcsubdir) \
+       $(etcsubdir)/default.d $(etcsubdir)/services.d $(etcsubdir)/override.d
+               if test ! -f $(etcsubdir)/system.default; then \
+                       $(INSTALL_DATA) system.default $(etcsubdir); fi
+               if test ! -f $(etcsubdir)/system.override; then \
+                       $(INSTALL_DATA) system.override $(etcsubdir); fi
+
+install-strip:
+               $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install
+
+install-doc:   $(TARGETS_DOC)
+               $(INSTALL) -d -m 2755 $(docdir) $(docdir)/spec.html
+               $(INSTALL) -d -m 2755 $(mandir) $(man1dir) $(man8dir)
+               $(INSTALL_DATA) -m 644 $(TARGETS_DOC_PS) $(docdir)/.
+               $(INSTALL_DATA) -m 644 spec.html/*.html $(docdir)/spec.html/.
+               $(INSTALL_DATA) -m 644 $(MAN1PAGES) $(man1dir)
+               $(INSTALL_DATA) -m 644 $(MAN8PAGES) $(man8dir)
+
+uninstall:
+               rm -f $(bindir)/userv $(sbindir)/uservd
+
+uninstall-doc:
+               cd $docdir && rm -rf $(TARGETS_DOC_RM)
+
+check:
+               @echo There is no validation suite for this package.
+
+daemon:                overlord.o process.o servexec.o parserlexer.o debug.o lib.o both.o
+               $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
+client:                client.o both.o
+               $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
+lexer.l:       language.i4
+
+spec.sgml:     spec.sgml.in Makefile
+               sed -e '/<version><\/version>/ s/>/&$(VERSION)/' \
+                       spec.sgml.in >$@.new && mv -f $@.new $@
+
+client.o:      config.h common.h pcsum.h both.h version.h
+
+process.o:     config.h common.h pcsum.h both.h daemon.h lib.h tokens.h
+
+overlord.o:    config.h common.h pcsum.h daemon.h
+
+servexec.o:    config.h common.h pcsum.h daemon.h lib.h version.h
+
+lib.o:         config.h common.h lib.h
+
+debug.o:       config.h common.h pcsum.h daemon.h lib.h tokens.h
+
+parserlexer.o: lexer.c parser.c config.h common.h pcsum.h daemon.h lib.h tokens.h
+# lexer.c #include's parser.c at the end.  Blame flex.
+               $(CC) -c $(CPPFLAGS) $(CFLAGS) lexer.c -o $@
+
+pcsum.h:       common.h config.h config.status Makefile
+               cat $^ | $(MD5SUM) \
+                       | sed -e 's/  -$$//; s/../0x&,/g; s/,$$//;' \
+                       >pcsum.h.new
+               cmp pcsum.h.new pcsum.h || mv -f pcsum.h.new pcsum.h
+               @rm -f pcsum.h.new
+
+version.h:     Makefile
+               echo '#define VERSION "$(VERSION)"' >$@.new && mv -f $@.new $@
+
+tokens.h:      language.i4
+
+autoconf configure:
+               autoheader
+               autoconf
+
+clean:
+               find -name '*.orig' -o -name '*~' -o -name '.*~' \
+                       -o -name '*#' -o -name '.#*' -o -name '*.bak' \
+                       | xargs -r rm
+               rm -rf $(TARGETS) *.o core version.h
+               rm -f overview.eps
+               rm -f spec.lout* spec.text* spec.ps* spec.sgml.new spec.tex
+               rm -f lout.li *.ld *.lix *.ldx
+               rm -f userv-*.tar.gz vd/*
+
+distclean mostlyclean: clean
+               rm -f $(CONFIG_RESULTS)
+
+maintainer-clean:      distclean
+               rm -rf $(GENSHIP_CLEAN) spec.html/*.html
+
+dist-prep:     $(GENSHIP)
+
+pre-checkin:
+               ./configure
+               $(MAKE) dist-prep
+
+dist_tmp=dist_tmp/userv-$(VERSION)
+dist_prune=\( -name CVS -o -name 'dist_tmp*' -o -name slash-etc -o -name vd \)
+dist:                  dist-prep distclean
+       rm -rf dist_tmp*
+       mkdir dist_tmp $(dist_tmp)
+       find $(dist_prune) -prune -o -type d -print | \
+               sed -e 's#.*#mkdir -p $(dist_tmp)/&#' | sh
+       find $(dist_prune) -prune -o -type f -print | \
+               sed -e 's#.*#ln & $(dist_tmp)/&#' | sh
+       cd dist_tmp && tar cf ../$(dist_tmp).tar `basename $(dist_tmp)`
+       gzip -9 $(dist_tmp).tar
+       mv $(dist_tmp).tar.gz .
+
+linecount:     $(SOURCES)
+               wc -l $^
+
+%.html/index.html:     %.sgml
+                       debiandoc2html $<
+
+%.ps:          %.sgml
+               debiandoc2ps -1 -O $< >$@.new && mv $@.new $@
+
+%.ps:          %.fig
+               fig2dev -L ps -c -l dummy -P -z A4 $< >$@.new && mv $@.new $@
+
+%.l:           %.l.m4
+               $(M4) $(M4FLAGS) -- $< >$@.new && mv $@.new $@
+
+%.h:           %.h.m4
+               $(M4) $(M4FLAGS) -- $< >$@.new && mv $@.new $@
+
+%:             %.m4
+               $(M4) $(M4FLAGS) -- $< >$@.new && mv $@.new $@
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..8e9a041
--- /dev/null
+++ b/README
@@ -0,0 +1,39 @@
+This is userv, a trust boundary management service for Unix.
+
+Using userv, one program can call another without either of them
+having to trust the other completely.  This enables applications such
+as mail delivery, cron, user-provided CGI scripts and so forth to
+operate without needing to be given root privilege.
+
+For installation requirements and instructions please see INSTALL.
+For usage documentation and discussion see the manual in spec.*
+(various formats via Debiandoc-SGML in spec.sgml).
+
+The WWW page is <URL:http://www.chiark.greenend.org.uk/~ian/userv/>.
+There are two mailing lists: userv-discuss and userv-announce; both
+are managed by majordomo@chiark.greenend.org.uk.  Send majordomo the
+word `help' for instructions on how to subscribe and unsubscribe.
+
+Please send bug reports and suggestions to
+userv-maint@chiark.greenend.org.uk, or to the userv-discuss mailing
+list if you are subscribed to it.
+
+
+userv is
+Copyright (C)1996-2003,2006 Ian Jackson <ian@davenant.greenend.org.uk>.
+Copyright (C)2000           Ben Harris <bjh21@cam.ac.uk>
+
+userv is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with userv as the file COPYING; if not, email me at the address
+above or write to the Free Software Foundation, 59 Temple Place -
+Suite 330, Boston, MA 02111-1307, USA.
diff --git a/acconfig.h b/acconfig.h
new file mode 100644 (file)
index 0000000..3ead528
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * userv - acconfig.h
+ * extra stuff for config.h.in (autoconf)
+ *
+ * Copyright (C)1996-1997,1999 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* Define if EPROTO exists.  */
+#undef HAVE_EPROTO
+
+/* Define if LOG_AUTHPRIV exists.  */
+#undef HAVE_LOG_AUTHPRIV
+
+/* Define if WCOREDUMP exists.  */
+#undef HAVE_WCOREDUMP
+
+/* Define if function attributes a la GCC 2.5 and higher are available.  */
+#undef HAVE_GNUC25_ATTRIB
+
+/* Define if unused functions a la GCC 2.5 and higher are available.  */
+#undef HAVE_GNUC25_UNUSED
+
+/* Define if nonreturning functions a la GCC 2.5 and higher are available.  */
+#undef HAVE_GNUC25_NORETURN
+
+/* Define if printf-format argument lists a la GCC are available.  */
+#undef HAVE_GNUC25_PRINTFFORMAT
+
+@BOTTOM@
+
+/* STRSIGNAL */
+#ifndef HAVE_STRSIGNAL
+#define STRSIGNAL(x) "[platform has no strsignal!]"
+#endif
+
+/* VSNPRINTF */
+#ifndef HAVE_VSNPRINTF
+# error "You must have vsnprintf!  Without vsnprintf it is very hard to write secure programs.  If you don't have it then your system libc is probably full of hideous buffer overrun security bugs.  But, if you don't want to fix your system a portable snprintf can be found at http://www.ijs.si/software/snprintf/"
+#endif
+
+/* EPROTO */
+#ifndef HAVE_EPROTO
+#define EPROTO 0
+#endif
+
+/* LOG_AUTHPRIV */
+#ifndef HAVE_LOG_AUTHPRIV
+#define LOG_AUTHPRIV LOG_AUTH
+#endif
+
+/* WCOREDUMP */
+#ifndef HAVE_WCOREDUMP
+#define WCOREDUMP(x) 0
+#endif
+
+/* GNU C attributes. */
+#ifndef FUNCATTR
+#ifdef HAVE_GNUC25_ATTRIB
+#define FUNCATTR(x) __attribute__(x)
+#else
+#define FUNCATTR(x)
+#endif
+#endif
+
+/* GNU C printf formats, or null. */
+#ifndef ATTRPRINTF
+#ifdef HAVE_GNUC25_PRINTFFORMAT
+#define ATTRPRINTF(si,tc) format(printf,si,tc)
+#else
+#define ATTRPRINTF(si,tc)
+#endif
+#endif
+#ifndef PRINTFFORMAT
+#define PRINTFFORMAT(si,tc) FUNCATTR((ATTRPRINTF(si,tc)))
+#endif
+
+/* GNU C nonreturning functions, or null. */
+#ifndef ATTRNORETURN
+#ifdef HAVE_GNUC25_NORETURN
+#define ATTRNORETURN noreturn
+#else
+#define ATTRNORETURN
+#endif
+#endif
+#ifndef NONRETURNING
+#define NONRETURNING FUNCATTR((ATTRNORETURN))
+#endif
+
+/* Combination of both the above. */
+#ifndef NONRETURNPRINTFFORMAT
+#define NONRETURNPRINTFFORMAT(si,tc) FUNCATTR((ATTRPRINTF(si,tc),ATTRNORETURN))
+#endif
+
+/* GNU C unused functions, or null. */
+#ifndef ATTRUNUSED
+#ifdef HAVE_GNUC25_UNUSED
+#define ATTRUNUSED unused
+#else
+#define ATTRUNUSED
+#endif
+#endif
+#ifndef UNUSED
+#define UNUSED FUNCATTR((ATTRUNUSED))
+#endif
diff --git a/both.c b/both.c
new file mode 100644 (file)
index 0000000..9fe88e9
--- /dev/null
+++ b/both.c
@@ -0,0 +1,79 @@
+/*
+ * userv - both.c
+ * Useful very-low-level utility routines, used in both client and daemon.
+ * These do not (and cannot) depend on infrastructure eg syscallerror,
+ * because these are not the same.
+ *
+ * Copyright (C)1999 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* Some nasty people can return 0/EOF + EINTR from stdio !
+ * These functions attempt to work around this braindamage by retrying
+ * the call after clearerr.  If this doesn't work then clearly your
+ * libc is _completely_ fubar rather than just somewhat fubar.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include "config.h"
+#include "both.h"
+
+void *xmalloc(size_t s) {
+  void *p;
+  p= malloc(s?s:1); if (!p) syscallerror("malloc");
+  return p;
+}
+
+void *xrealloc(void *p, size_t s) {
+  p= realloc(p,s);
+  if (!p) syscallerror("realloc");
+  return p;
+}
+
+char *xstrsave(const char *s) {
+  char *r;
+
+  r= xmalloc(strlen(s)+1);
+  strcpy(r,s);
+  return r;
+}
+
+
+int working_getc(FILE *file) {
+  int c;
+  
+  for (;;) {
+    c= getc(file);
+    if (c != EOF || errno != EINTR) return c;
+    clearerr(file);
+  }
+}
+
+size_t working_fread(void *ptr, size_t sz, FILE *file) {
+  size_t done, nr;
+
+  done= 0;
+  for (;;) {
+    nr= fread((char*)ptr + done, 1, sz-done, file);
+    done += nr;
+    if (done == sz || !ferror(file) || errno != EINTR) return done;
+    clearerr(file);
+  }
+}
diff --git a/both.h b/both.h
new file mode 100644 (file)
index 0000000..1b3033a
--- /dev/null
+++ b/both.h
@@ -0,0 +1,41 @@
+/*
+ * userv - lib.c
+ * Useful very-low-level utility routines' declarations,
+ * for both client and daemon.
+ *
+ * Copyright (C)1999,2001 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef BOTH_H
+#define BOTH_H
+
+/* provided by both.c */
+
+void *xmalloc(size_t s);
+void *xrealloc(void *p, size_t s);
+char *xstrsave(const char *s);
+
+int working_getc(FILE *file);
+size_t working_fread(void *ptr, size_t sz, FILE *file);
+
+/* used by both.c, so must be present */
+
+void syscallerror(const char *what) NONRETURNING;
+
+#define ISCHAR(iswotsit,ch) (iswotsit((unsigned char)(ch))) /*Feh!*/
+
+#endif
diff --git a/client.c b/client.c
new file mode 100644 (file)
index 0000000..4760009
--- /dev/null
+++ b/client.c
@@ -0,0 +1,1359 @@
+/*
+ * userv - client.c
+ * client code
+ *
+ * Copyright (C)1996-1997,1999-2001,2003 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Here too, we do some horrible asynchronous stuff with signals.
+ *
+ * The following objects &c. are used in signal handlers and so
+ * must be protected by calls to blocksignals if they are used in
+ * the main program:
+ *  stderr
+ *  swfile
+ *
+ * The following objects are used in the main program unprotected
+ * and so must not be used in signal handlers:
+ *  srfile
+ *  fdsetup[].copyfd
+ *  fdsetup[].pipefd
+ *
+ * The following objects/functions are not modified/called after the
+ * asynchronicity starts:
+ *  malloc
+ *  fdsetupsize
+ *  fdsetup[].mods
+ *  fdsetup[].filename
+ *  fdsetup[].oflags
+ *  results of argument parsing
+ *
+ * systemerror, swfile, fdsetup[].catpid and fdsetup[].killed are used
+ * for communication between the main thread and the signal handlers.
+ *
+ * All the signal handlers save errno so that is OK too.
+ */
+
+#include <fcntl.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <grp.h>
+#include <signal.h>
+#include <limits.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/resource.h>
+#include <sys/wait.h>
+
+#include "config.h"
+#include "common.h"
+#include "both.h"
+#include "version.h"
+
+enum fdmodifiervalues {
+  fdm_read=       00001,
+  fdm_write=      00002,
+  fdm_create=     00004,
+  fdm_exclusive=  00010,
+  fdm_truncate=   00020,
+  fdm_append=     00040,
+  fdm_sync=       00100,
+  fdm_fd=         00200,
+  fdm_wait=       01000,
+  fdm_nowait=     02000,
+  fdm_close=      04000,
+};
+
+struct fdmodifierinfo {
+  const char *string;
+  int implies;
+  int conflicts;
+  int oflags;
+};
+
+struct fdsetupstate {
+  const char *filename; /* non-null iff this fd has been specified */
+  int copyfd; /* fd to copy, -1 unless mods & fdm_fd */
+  int mods, oflags, pipefd, killed; /* 0,0,-1,0 unless otherwise set */
+  pid_t catpid; /* -1 indicates no cat process */
+};
+
+enum signalsexitspecials { se_number=-100, se_numbernocore, se_highbit, se_stdout };
+enum overridetypes { ot_none, ot_string, ot_file, ot_builtin };
+
+struct constkeyvaluepair { const char *key, *value; };
+
+/* Variables from command-line arguments */
+static const char *serviceuser;
+static uid_t serviceuid;
+static struct fdsetupstate *fdsetup;
+static int fdsetupsize;
+static struct constkeyvaluepair *defvararray;
+static int defvaravail, defvarused;
+static unsigned long timeout;
+static int signalsexit=254;
+static int sigpipeok, hidecwd;
+static int overridetype= ot_none;
+static const char *overridevalue, *spoofuser=0;
+
+/* Other state variables */
+static FILE *srfile, *swfile;
+static pid_t mypid;
+static uid_t myuid, spoofuid;
+static gid_t mygid, spoofgid, *gidarray;
+static int ngids;
+static struct opening_msg opening_mbuf;
+static const char *loginname;
+static char *cwdbuf;
+static size_t cwdbufsize;
+static char *ovbuf;
+static int ovused, systemerror, socketfd;
+
+static void blocksignals(int how) {
+  sigset_t set;
+  static const char blockerrmsg[]= "userv: failed to [un]block signals: ";
+  const char *str;
+
+  sigemptyset(&set);
+  sigaddset(&set,SIGCHLD);
+  sigaddset(&set,SIGALRM);
+  if (sigprocmask(how,&set,0)) {
+    str= strerror(errno);
+    write(2,blockerrmsg,sizeof(blockerrmsg)-1);
+    write(2,str,strlen(str));
+    write(2,"\n",1);
+    exit(-1);
+  }
+}
+
+/* Functions which may be called either from signal handlers or from
+ * the main thread.  They block signals in case they are on the main
+ * thread, and may only use signal handler objects.  None of them
+ * return.  If they did they'd have to restore the signal mask.
+ */
+
+static void NONRETURNPRINTFFORMAT(1,2) miscerror(const char *fmt, ...) {
+  va_list al;
+
+  blocksignals(SIG_BLOCK);
+  va_start(al,fmt);
+  fprintf(stderr,"userv: failure: ");
+  vfprintf(stderr,fmt,al);
+  fprintf(stderr,"\n");
+  exit(-1);
+}
+
+static void NONRETURNPRINTFFORMAT(1,2) fsyscallerror(const char *fmt, ...) {
+  va_list al;
+  int e;
+
+  e= errno;
+  blocksignals(SIG_BLOCK);
+  va_start(al,fmt);
+  fprintf(stderr,"userv: system call failure: ");
+  vfprintf(stderr,fmt,al);
+  fprintf(stderr,": %s\n",strerror(e));
+  exit(-1);
+}
+
+void syscallerror(const char *what) {
+  fsyscallerror("%s",what);
+}
+
+static void NONRETURNING protoreaderror(FILE *file, const char *where) {
+  int e;
+
+  e= errno;
+  blocksignals(SIG_BLOCK);
+  if (ferror(file)) {
+    fprintf(stderr,"userv: failure: read error %s: %s\n",where,strerror(e));
+  } else {
+    assert(feof(file));
+    fprintf(stderr,"userv: internal failure: EOF from server %s\n",where);
+  }
+  exit(-1);
+}
+
+static void NONRETURNPRINTFFORMAT(1,2) protoerror(const char *fmt, ...) {
+  va_list al;
+
+  blocksignals(SIG_BLOCK);
+  va_start(al,fmt);
+  fprintf(stderr,"userv: internal failure: protocol error: ");
+  vfprintf(stderr,fmt,al);
+  fprintf(stderr,"\n");
+  exit(-1);
+}
+
+/*
+ * General-purpose functions; these do nothing special about signals,
+ * except that they can call error-handlers which may block them
+ * to print error messages.
+ */
+
+static void xfread(void *p, size_t sz, FILE *file) {
+  size_t nr;
+  nr= working_fread(p,sz,file);
+  if (nr != sz) protoreaderror(file,"in data");
+}
+
+static void xfwrite(const void *p, size_t sz, FILE *file) {
+  size_t nr;
+  nr= fwrite(p,1,sz,file); if (nr == sz) return;
+  syscallerror("writing to server");
+}
+
+static void xfflush(FILE *file) {
+  if (fflush(file)) syscallerror("flush server socket");
+}
+
+/* Functions which may be called only from the main thread.  These may
+ * use main-thread objects and must block signals before using signal
+ * handler objects.
+ */
+
+#ifdef DEBUG
+static void priv_suspend(void) { }
+static void priv_resume(void) { }
+static void priv_permanentlyrevokesuspended(void) { }
+#else
+static void priv_suspend(void) {
+  if (setreuid(0,myuid) != 0) syscallerror("suspend root setreuid(0,myuid)");
+}
+static void priv_resume(void) {
+  if (setreuid(myuid,0) != 0) syscallerror("resume root setreuid(myuid,0)");
+}
+static void priv_permanentlyrevokesuspended(void) {
+  if (setreuid(myuid,myuid) != 0) syscallerror("revoke root setreuid(myuid,myuid)");
+  if (setreuid(myuid,myuid) != 0) syscallerror("rerevoke root setreuid(myuid,myuid)");
+  if (myuid) {
+    if (!setreuid(myuid,0)) miscerror("revoked root but setreuid(0,0) succeeded !");
+    if (errno != EPERM) syscallerror("revoked and setreuid(myuid,0) unexpected error");
+  }
+}
+#endif
+
+static void checkmagic(unsigned long was, unsigned long should, const char *when) {
+  if (was != should)
+    protoerror("magic number %s was %08lx, expected %08lx",when,was,should);
+}
+
+static void getprogress(struct progress_msg *progress_r, FILE *file) {
+  int i, c;
+  unsigned long ul;
+
+  for (;;) {
+    xfread(progress_r,sizeof(struct progress_msg),file);
+    checkmagic(progress_r->magic,PROGRESS_MAGIC,"in progress message");
+    switch (progress_r->type) {
+    case pt_failed:
+      blocksignals(SIG_BLOCK);
+      fputs("userv: uservd reports that service failed\n",stderr);
+      exit(-1);
+    case pt_errmsg:
+      blocksignals(SIG_BLOCK);
+      fputs("uservd: ",stderr);
+      if (progress_r->data.errmsg.messagelen>MAX_ERRMSG_STRING)
+       protoerror("stderr message length %d is far too long",
+                  progress_r->data.errmsg.messagelen);
+      for (i=0; i<progress_r->data.errmsg.messagelen; i++) {
+       c= working_getc(file);
+       if (c==EOF) protoreaderror(file,"in error message");
+       if (ISCHAR(isprint,c)) putc(c,stderr);
+       else fprintf(stderr,"\\x%02x",(unsigned char)c);
+      }
+      putc('\n',stderr);
+      if (ferror(stderr)) syscallerror("printing error message");
+      xfread(&ul,sizeof(ul),file);
+      checkmagic(ul,PROGRESS_ERRMSG_END_MAGIC,"after error message");
+      blocksignals(SIG_UNBLOCK);
+      break;
+    default:
+      return;
+    }
+  }
+}
+
+/*
+ * Functions which are called only during setup, before
+ * the signal asynchronicity starts.  They can do anything they like.
+ */
+
+/* This includes xmalloc and xrealloc from both.c */
+
+static void xfwritestring(const char *s, FILE *file) {
+  int l;
+  l= strlen(s);
+  assert(l<=MAX_GENERAL_STRING);
+  xfwrite(&l,sizeof(l),file);
+  xfwrite(s,sizeof(*s)*l,file);
+}
+
+static void xfwritefds(int modifier, int expected, FILE *file) {
+  int i, fdcount;
+
+  for (i=0, fdcount=0; i<fdsetupsize; i++) {
+    if (!(fdsetup[i].filename && (fdsetup[i].mods & modifier)))
+      continue;
+    xfwrite(&i,sizeof(int),file); fdcount++;
+  }
+  assert(fdcount == expected);
+}
+
+/* Functions which may be called from signal handlers.  These
+ * may use signal-handler objects.  The main program may only
+ * call them with signals blocked, and they may not use any
+ * main-thread objects.
+ */
+
+static void disconnect(void) /* DOES return, unlike in daemon */ {
+  struct event_msg event_mbuf;
+  int r;
+
+  if (swfile) {
+    memset(&event_mbuf,0,sizeof(event_mbuf));
+    event_mbuf.magic= EVENT_MAGIC;
+    event_mbuf.type= et_disconnect;
+    r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
+    if ((r != sizeof(event_mbuf) || fflush(swfile)) && errno != EPIPE)
+      syscallerror("write to server when disconnecting");
+  }
+  systemerror= 1;
+}
+
+static void sighandler_alrm(int ignored) /* DOES return, unlike in daemon */ {
+  int es;
+  es= errno;
+  fputs("userv: timeout\n",stderr);
+  disconnect();
+  errno= es;
+}
+
+static void sighandler_chld(int ignored) /* DOES return, unlike in daemon */ {
+  struct event_msg event_mbuf;
+  pid_t child;
+  int status, fd, r, es;
+
+  es= errno;
+  for (;;) {
+    child= wait3(&status,WNOHANG,0);
+    if (child == 0 || (child == -1 && errno == ECHILD)) break;
+    if (child == -1) syscallerror("wait for child process (in sigchld handler)");
+    for (fd=0; fd<fdsetupsize && fdsetup[fd].catpid != child; fd++);
+    if (fd>=fdsetupsize) continue; /* perhaps the caller gave us children */
+    if ((WIFEXITED(status) && WEXITSTATUS(status)==0) ||
+       (WIFSIGNALED(status) && WTERMSIG(status)==SIGPIPE) ||
+       (fdsetup[fd].killed && WIFSIGNALED(status) && WTERMSIG(status)==SIGKILL)) {
+      if (swfile && fdsetup[fd].mods & fdm_read) {
+       memset(&event_mbuf,0,sizeof(event_mbuf));
+       event_mbuf.magic= EVENT_MAGIC;
+       event_mbuf.type= et_closereadfd;
+        event_mbuf.data.closereadfd.fd= fd;
+       r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
+       if (r != sizeof(event_mbuf) || fflush(swfile))
+         if (errno != EPIPE) syscallerror("inform service of closed read fd");
+      }
+    } else {
+      if (WIFEXITED(status))
+       fprintf(stderr,"userv: cat for fd %d exited with error exit status %d\n",
+               fd,WEXITSTATUS(status));
+      else if (WIFSIGNALED(status))
+       if (WCOREDUMP(status))
+         fprintf(stderr,"userv: cat for fd %d dumped core due to signal %s (%d)\n",
+                 fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
+       else
+         fprintf(stderr,"userv: cat for fd %d terminated by signal %s (%d)\n",
+                 fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
+      else
+       fprintf(stderr,"userv: cat for fd %d gave unknown wait status %d\n",
+               fd,status);
+      disconnect();
+    }
+    fdsetup[fd].catpid= -1;
+  }
+  errno= es;
+}
+
+/*
+ * Argument parsing.  These functions which are called only during
+ * setup, before the signal asynchronicity starts.
+ */
+
+struct optioninfo;
+
+typedef void optionfunction(const struct optioninfo*, const char *value, char *key);
+
+struct optioninfo {
+  int abbrev;
+  const char *full;
+  int values; /* 0: no value; 1: single value; 2: key and value */
+  optionfunction *fn;
+};
+
+static void usage(FILE *stream) {
+  if (fputs(
+    "usage: userv <options> [--] <service-user> <service-name> [<argument> ...]\n"
+    "usage: userv <options> -B|--builtin [--] <builtin-service> [<info-argument> ...]\n"
+    "options: -f|--file <fd>[<fdmodifiers>]=<filename>\n"
+    "         -D|--defvar <name>=<value>\n"
+    "         -t|--timeout <seconds>\n"
+    "         -S|--signals <status>|number|number-nocore|highbit|stdout\n"
+    "         -w|--fdwait <fd>=wait|nowait|close\n"
+    "         -P|--sigpipe  -H|--hidecwd  -h|--help|--version  --copyright\n"
+    "         --override <configuration-data> } available only\n"
+    "         --override-file <filename>      }  to root\n"
+    "         --spoof-user <username>         }  or same user\n"
+    "fdmodifiers:            read    write  overwrite    trunc[ate]\n"
+    "(separate with commas)  append  sync   excl[usive]  creat[e]  fd\n"
+    "userv -B 'X' ... is same as userv --override 'execute-builtin X' - 'X' ...\n"
+    "         for help, type `userv -B help'; remember to quote multi-word X\n"
+    "userv and uservd version " VERSION VEREXT ".\n"
+    COPYRIGHT("","\n"),
+            stream) < 0)
+    syscallerror("write usage message");
+}
+
+static void NONRETURNPRINTFFORMAT(1,2) usageerror(const char *fmt, ...) {
+  va_list al;
+  va_start(al,fmt);
+  fputs("userv: ",stderr);
+  vfprintf(stderr,fmt,al);
+  fputs("\n\n",stderr);
+  usage(stderr);
+  exit(-1);
+}
+
+static const struct fdmodifierinfo fdmodifierinfos[]= {
+  { "read",      fdm_read,
+                 fdm_write,
+                 O_RDONLY                                                         },
+  { "write",     fdm_write,
+                 fdm_read,
+                 O_WRONLY                                                         },
+  { "overwrite", fdm_write|fdm_create|fdm_truncate,
+                 fdm_read|fdm_fd|fdm_exclusive,
+                 O_WRONLY|O_CREAT|O_TRUNC                                         },
+  { "create",    fdm_write|fdm_create,
+                 fdm_read|fdm_fd,
+                 O_WRONLY|O_CREAT                                                 },
+  { "creat",     fdm_write|fdm_create,
+                 fdm_read|fdm_fd,
+                 O_WRONLY|O_CREAT                                                 },
+  { "exclusive", fdm_write|fdm_create|fdm_exclusive,
+                 fdm_read|fdm_fd|fdm_truncate,
+                 O_WRONLY|O_CREAT|O_EXCL                                          },
+  { "excl",      fdm_write|fdm_create|fdm_exclusive,
+                 fdm_read|fdm_fd|fdm_truncate,
+                 O_WRONLY|O_CREAT|O_EXCL                                          },
+  { "truncate",  fdm_write|fdm_truncate,
+                 fdm_read|fdm_fd|fdm_exclusive,
+                 O_WRONLY|O_CREAT|O_EXCL                                          },
+  { "trunc",     fdm_write|fdm_truncate,
+                 fdm_read|fdm_fd|fdm_exclusive,
+                 O_WRONLY|O_CREAT|O_EXCL                                          },
+  { "append",    fdm_write|fdm_append,
+                 fdm_read|fdm_fd,
+                 O_WRONLY|O_CREAT|O_APPEND                                        },
+  { "sync",      fdm_write|fdm_sync,
+                 fdm_read|fdm_fd,
+                 O_WRONLY|O_CREAT|O_SYNC                                          },
+  { "wait",      fdm_wait,
+                 fdm_nowait|fdm_close,
+                 0                                                                },
+  { "nowait",    fdm_nowait,
+                 fdm_wait|fdm_close,
+                 0                                                                },
+  { "close",     fdm_close,
+                 fdm_wait|fdm_nowait,
+                 0                                                                },
+  { "fd",        fdm_fd,
+                 fdm_create|fdm_exclusive|fdm_truncate|fdm_append|fdm_sync,
+                 0                                                                },
+  {  0                                                                            }
+};
+
+static void addfdmodifier(int fd, const char *key, size_t key_len) {
+  const struct fdmodifierinfo *fdmip;
+  
+  if (!*key) return;
+  for (fdmip= fdmodifierinfos;
+       fdmip->string &&
+        !(strlen(fdmip->string) == key_len &&
+          !memcmp(fdmip->string,key,key_len));
+       fdmip++);
+  if (!fdmip->string) usageerror("unknown fdmodifer `%.*s' for fd %d",
+                                (int)key_len,key,fd);
+  if (fdmip->conflicts & fdsetup[fd].mods)
+    usageerror("fdmodifier `%.*s' conflicts with another for fd %d",
+              (int)key_len,key,fd);
+  fdsetup[fd].mods |= fdmip->implies;
+  fdsetup[fd].oflags |= fdmip->oflags;
+}
+
+static void addfdmodifier_fixed(int fd, const char *key) {
+  addfdmodifier(fd, key, strlen(key));
+}
+
+static int fdstdnumber(const char *string, const char **delim_r) {
+#define FN(v,s) do{                            \
+    if (!memcmp(string,s,sizeof(s)-1)) {       \
+      *delim_r= string+sizeof(s)-1;            \
+      return v;                                        \
+    }                                          \
+  }while(0)
+
+  FN(0,"stdin");
+  FN(1,"stdout");
+  FN(2,"stderr");
+
+  return -1;
+}
+
+static int strtofd(const char *string,
+                  const char **mods_r /* 0: no modifiers, must go to end */,
+                  const char *what) {
+  int fd;
+  unsigned long ul;
+  char *delim_v;
+  const char *mods;
+  
+  fd= fdstdnumber(string,&mods);
+  if (fd>=0) {
+    if (*mods && *mods != ',')
+      usageerror("%s, when it is `stdin', `stdout' or `stderr',"
+                " must be delimited with a comma from any following"
+                " modifiers - `%s' is not permitted",
+                what, mods);
+    goto parsed;
+  }
+
+  errno= 0;
+  ul= strtoul(string,&delim_v,10);
+  if (errno || delim_v == string || ul > INT_MAX)
+    usageerror("%s must be must be numeric file descriptor"
+              " or `stdin', `stdout' or `stderr'"
+              " - `%s' is not recognized",
+              what, string);
+  mods= delim_v;
+  fd= ul;
+
+ parsed:
+  if (*mods==',')
+    mods++;
+  
+  if (mods_r)
+    *mods_r= mods;
+  else if (*mods)
+    usageerror("%s must be must be only file descriptor"
+              " - trailing portion or modifiers `%s' not permitted",
+              what, mods);
+
+  if (fd > MAX_ALLOW_FD)
+    usageerror("%s file descriptor specified (%d)"
+              " is larger than maximum allowed (%d)",
+              what, fd, MAX_ALLOW_FD);
+  
+  return fd;
+}
+  
+static void of_file(const struct optioninfo *oip, const char *value, char *key) {
+  unsigned long fd, copyfd;
+  struct stat stab;
+  int oldarraysize, r;
+  size_t mod_len;
+  const char *mods, *delim;
+
+  fd= strtofd(key,&mods,"first part of argument to -f or --file");
+
+  if (fd >= fdsetupsize) {
+    oldarraysize= fdsetupsize;
+    fdsetupsize+=2; fdsetupsize<<=1;
+    fdsetup= xrealloc(fdsetup,sizeof(struct fdsetupstate)*fdsetupsize);
+    while (oldarraysize < fdsetupsize) {
+      fdsetup[oldarraysize].filename= 0;
+      fdsetup[oldarraysize].pipefd= -1;
+      fdsetup[oldarraysize].copyfd= -1;
+      fdsetup[oldarraysize].mods= 0;
+      fdsetup[oldarraysize].oflags= 0;
+      fdsetup[oldarraysize].catpid= -1;
+      fdsetup[oldarraysize].killed= 0;
+      fdsetup[oldarraysize].filename= 0;
+      oldarraysize++;
+    }
+  }
+  fdsetup[fd].filename= value;
+  fdsetup[fd].oflags= 0;
+  fdsetup[fd].mods= 0;
+  fdsetup[fd].copyfd= -1;
+  while (mods && *mods) {
+    delim= strchr(mods,',');
+    mod_len= delim ? delim-mods : strlen(mods);
+    addfdmodifier(fd,mods,mod_len);
+    mods= delim ? delim+1 : 0;
+  }
+  if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) {
+    if (fd == 0) {
+      addfdmodifier_fixed(fd,"read");
+    } else if (fdsetup[fd].mods & fdm_fd) {
+      addfdmodifier_fixed(fd,"write");
+    } else {
+      addfdmodifier_fixed(fd,"overwrite");
+    }
+  }
+  if (fdsetup[fd].mods & fdm_fd) {
+    copyfd= strtofd(value,0,
+                   "value part of argument to --file with fd modifier");
+    r= fstat(copyfd,&stab);
+    if (r) {
+      if (oip) fsyscallerror("check filedescriptor %lu (named as target of file "
+                            "descriptor redirection for %lu)",copyfd,fd);
+      else fsyscallerror("check basic filedescriptor %lu at program start",copyfd);
+    }
+    fdsetup[fd].copyfd= copyfd;
+  }
+}
+
+static void of_fdwait(const struct optioninfo *oip, const char *value, char *key) {
+  const struct fdmodifierinfo *fdmip;
+  int fd;
+  
+  fd= strtofd(key,0,"first part of argument to --fdwait");
+  if (fd >= fdsetupsize || !fdsetup[fd].filename)
+    usageerror("file descriptor %d specified in --fdwait option is not open",fd);
+  for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,value); fdmip++);
+  if (!fdmip->string || !(fdmip->implies & (fdm_wait|fdm_nowait|fdm_close)))
+    usageerror("value for --fdwait must be `wait', `nowait' or `close', not `%s'",value);
+  fdsetup[fd].mods &= ~(fdm_wait|fdm_nowait|fdm_close);
+  fdsetup[fd].mods |= fdmip->implies;
+}
+
+static void of_defvar(const struct optioninfo *oip, const char *value, char *key) {
+  int i;
+
+  if (!key[0])
+    usageerror("empty string not allowed as variable name");
+  if (strlen(key)>MAX_GENERAL_STRING)
+    usageerror("variable name `%s' is far too long",key);
+  if (strlen(value)>MAX_GENERAL_STRING)
+    usageerror("variable `%s' has value `%s' which is far too long",key,value);
+  for (i=0; i<defvarused && strcmp(defvararray[i].key,key); i++);
+  if (defvarused >= MAX_ARGSDEFVAR) usageerror("far too many --defvar or -D options");
+  if (i>=defvaravail) {
+    defvaravail+=10; defvaravail<<=1;
+    defvararray= xrealloc(defvararray,sizeof(struct constkeyvaluepair)*defvaravail);
+  }
+  if (i==defvarused) defvarused++;
+  defvararray[i].key= key;
+  defvararray[i].value= value;
+}
+
+static void of_timeout(const struct optioninfo *oip, const char *value, char *key) {
+  char *endp;
+  unsigned long ul;
+
+  errno= 0;
+  ul= strtoul(value,&endp,10);
+  if (errno || *endp)
+    usageerror("timeout value `%s' must be a plain decimal string",value);
+  if (ul>INT_MAX) usageerror("timeout value %lu too large",ul);
+  timeout= ul;
+}
+
+static void of_signals(const struct optioninfo *oip, const char *value, char *key) {
+  unsigned long numvalue;
+  char *endp;
+
+  errno= 0;
+  numvalue= strtoul(value,&endp,10);
+  if (errno || *endp) {
+    if (!strcmp(value,"number")) signalsexit= se_number;
+    else if (!strcmp(value,"number-nocore")) signalsexit= se_numbernocore;
+    else if (!strcmp(value,"highbit")) signalsexit= se_highbit;
+    else if (!strcmp(value,"stdout")) signalsexit= se_stdout;
+    else usageerror("value `%s' for --signals not understood",value);
+  } else {
+    if (numvalue<0 || numvalue>255)
+      usageerror("value %lu for --signals not 0...255",numvalue);
+    signalsexit= numvalue;
+  }
+}
+
+static void of_sigpipe(const struct optioninfo *oip, const char *value, char *key) {
+  sigpipeok=1;
+}
+
+static void of_hidecwd(const struct optioninfo *oip, const char *value, char *key) {
+  hidecwd=1;
+}
+
+static void of_help(const struct optioninfo *oip, const char *value, char *key) {
+  usage(stdout);
+  if (fclose(stdout)) syscallerror("fclose stdout after writing usage message");
+  exit(0);
+}
+
+static void of_version(const struct optioninfo *oip, const char *value, char *key) {
+  if (puts(VERSION VEREXT) == EOF || fclose(stdout))
+    syscallerror("write version number");
+  exit(0);
+}
+
+static void of_copyright(const struct optioninfo *oip, const char *value, char *key) {
+  if (fputs(
+" userv - user service daemon and client\n\n"
+COPYRIGHT(" ","\n")
+"\n"
+" This is free software; you can redistribute it and/or modify it under the\n"
+" terms of the GNU General Public License as published by the Free Software\n"
+" Foundation; either version 2 of the License, or (at your option) any\n"
+" later version.\n\n"
+" This program is distributed in the hope that it will be useful, but\n"
+" WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General\n"
+" Public License for more details.\n\n"
+" You should have received a copy of the GNU General Public License along\n"
+" with userv; if not, see <http://www.gnu.org/licenses/>.\n",
+           stdout) < 0) syscallerror("write usage to stderr");
+  exit(0);
+}
+
+static void of_builtin(const struct optioninfo *oip, const char *value, char *key) {
+  overridetype= ot_builtin;
+}
+
+static void of_override(const struct optioninfo *oip, const char *value, char *key) {
+  overridetype= ot_string;
+  overridevalue= value;
+}
+
+static void of_overridefile(const struct optioninfo *oip,
+                            const char *value, char *key) {
+  overridetype= ot_file;
+  overridevalue= value;
+}
+
+static void of_spoofuser(const struct optioninfo *oip,
+                        const char *value, char *key) {
+  spoofuser= value;
+}
+
+const struct optioninfo optioninfos[]= {
+  { 'f', "file",          2, of_file         },
+  { 'w', "fdwait",        2, of_fdwait       },
+  { 'D', "defvar",        2, of_defvar       },
+  { 't', "timeout",       1, of_timeout      },
+  { 'S', "signals",       1, of_signals      },
+  { 'P', "sigpipe",       0, of_sigpipe      },
+  { 'H', "hidecwd",       0, of_hidecwd      },
+  { 'B', "builtin",       0, of_builtin      },
+  { 'h', "help",          0, of_help         },
+  {  0,  "version",       0, of_version      },
+  {  0,  "copyright",     0, of_copyright    },
+  {  0,  "override",      1, of_override     },
+  {  0,  "override-file", 1, of_overridefile },
+  {  0,  "spoof-user",    1, of_spoofuser    },
+  {  0,   0                                  }
+};
+
+static void callvalueoption(const struct optioninfo *oip, char *arg) {
+  char *equals;
+  if (oip->values == 2) {
+    equals= strchr(arg,'=');
+    if (!equals) {
+      if (oip->abbrev)
+        usageerror("option --%s (-%c) passed argument `%s' with no `='",
+                   oip->full,oip->abbrev,arg);
+      else
+        usageerror("option --%s passed argument `%s' with no `='",
+                   oip->full,arg);
+    }
+    *equals++= 0;
+    (oip->fn)(oip,equals,arg);
+  } else {
+    (oip->fn)(oip,arg,0);
+  }
+}
+
+/*
+ * Main thread main processing functions - in order of execution.
+ */
+
+static void security_init(void) {
+  /* May not open any file descriptors. */
+  
+  mypid= getpid(); if (mypid == (pid_t)-1) syscallerror("getpid");
+  myuid= getuid(); if (myuid == (uid_t)-1) syscallerror("getuid");
+  mygid= getgid(); if (mygid == (gid_t)-1) syscallerror("getgid");
+  ngids= getgroups(0,0); if (ngids == -1) syscallerror("getgroups(0,0)");
+  gidarray= xmalloc(sizeof(gid_t)*ngids);
+  if (getgroups(ngids,gidarray) != ngids) syscallerror("getgroups(ngids,)");
+  
+  priv_suspend();
+
+  if (ngids > MAX_GIDS) miscerror("caller is in far too many gids");
+}
+
+static void parse_arguments(int *argcp, char *const **argvp) {
+  static char fd0key[]= "stdin,fd,read";
+  static char fd1key[]= "stdout,fd,write";
+  static char fd2key[]= "stderr,fd,write";
+
+  char *const *argpp;
+  char *argp;
+  const struct optioninfo *oip;
+  int fd;
+
+  assert((*argvp)[0]);
+  of_file(0,"stdin",fd0key);
+  of_file(0,"stdout",fd1key);
+  of_file(0,"stderr",fd2key);
+
+  for (argpp= *argvp+1;
+       (argp= *argpp) && *argp == '-' && argp[1];
+       argpp++) {
+    if (!*++argp) usageerror("unknown option/argument `%s'",*argpp);
+    if (*argp == '-') { /* Two hyphens */
+      if (!*++argp) { argpp++; break; /* End of options. */ }
+      for (oip= optioninfos; oip->full && strcmp(oip->full,argp); oip++);
+      if (!oip->full) usageerror("unknown long option `%s'",*argpp);
+      if (oip->values) {
+        if (!argpp[1]) usageerror("long option `%s' needs a value",*argpp);
+        callvalueoption(oip,*++argpp);
+      } else {
+        (oip->fn)(oip,0,0);
+      }
+    } else {
+      for (; *argp; argp++) {
+        for (oip= optioninfos; oip->full && oip->abbrev != *argp; oip++);
+        if (!oip->full) usageerror("unknown short option `-%c' in argument `%s'",
+                                    *argp, *argpp);
+        if (oip->values) {
+          if (argp[1]) {
+            argp++;
+          } else {
+            if (!argpp[1]) usageerror("short option `-%c' in argument `%s' needs"
+                                      " a value",*argp,*argpp);
+            argp= *++argpp;
+          }
+          callvalueoption(oip,argp);
+          break; /* No more options in this argument, go on to the next one. */
+        } else {
+          (oip->fn)(oip,0,0);
+        }
+      }
+    }
+  }
+  if (overridetype == ot_builtin) {
+    serviceuser= "-";
+  } else {
+    if (!*argpp) usageerror("no service user given after options");
+    serviceuser= *argpp++;
+  }
+  if (!*argpp) usageerror(overridetype == ot_builtin ?
+                         "no service name given after options and service user" :
+                         "no builtin service given after options");
+
+  *argcp-= (argpp-*argvp);
+  *argvp= argpp;
+
+  if (*argcp > MAX_ARGSDEFVAR) usageerror("far too many arguments");
+  
+  for (fd=0; fd<fdsetupsize; fd++) {
+    if (!fdsetup[fd].filename) continue;
+    if (fdsetup[fd].mods & (fdm_wait|fdm_nowait|fdm_close)) continue;
+    assert(fdsetup[fd].mods & (fdm_read|fdm_write));
+    fdsetup[fd].mods |= (fdsetup[fd].mods & fdm_read) ? fdm_close : fdm_wait;
+  }
+}
+
+static void determine_users(void) {
+  int ngidssize;
+  char **mem;
+  struct passwd *pw;
+  struct group *gr;
+  
+  spoofuid= myuid;
+  spoofgid= mygid;
+  loginname= getenv("LOGNAME");
+  if (!loginname) loginname= getenv("USER");
+  if (loginname) {
+    pw= getpwnam(loginname);
+    if (!pw || pw->pw_uid != myuid) loginname= 0;
+  }
+  if (!loginname) {
+    pw= getpwuid(myuid); if (!pw) miscerror("cannot determine your login name");
+    loginname= xstrsave(pw->pw_name);
+  }
+
+  if (!strcmp(serviceuser,"-")) serviceuser= loginname;
+  pw= getpwnam(serviceuser);
+  if (!pw) miscerror("requested service user `%s' is not a user",serviceuser);
+  serviceuid= pw->pw_uid;
+
+  if ((overridetype != ot_none || spoofuser) && myuid != 0 && myuid != serviceuid)
+    miscerror("--override and --spoof options only available to root or to"
+              " the user who will be providing the service");
+
+  if (spoofuser) {
+    loginname= spoofuser;
+    pw= getpwnam(loginname);
+    if (!pw) miscerror("spoofed login name `%s' is not valid",loginname);
+    spoofuid= pw->pw_uid;
+    spoofgid= pw->pw_gid;
+    ngidssize= ngids; ngids= 0;
+    if (ngidssize<5) {
+      ngidssize= 5;
+      gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize);
+    }
+    gidarray[ngids++]= spoofgid;
+    while ((gr= getgrent())) { /* ouch! getgrent has no error behaviour! */
+      for (mem= gr->gr_mem; *mem && strcmp(*mem,loginname); mem++);
+      if (!*mem) continue;
+      if (ngids>=ngidssize) {
+      if (ngids>=MAX_GIDS) miscerror("spoofed user is member of too many groups");
+       ngidssize= (ngids+5)<<1;
+       gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize);
+      }
+      gidarray[ngids++]= gr->gr_gid;
+    }
+  }
+}
+
+static void determine_cwd(void) {
+  cwdbufsize= 0; cwdbuf= 0;
+
+  if (hidecwd) return;
+  
+  for (;;) {
+    if (cwdbufsize > MAX_GENERAL_STRING) { cwdbufsize= 0; free(cwdbuf); break; }
+    cwdbufsize <<= 1; cwdbufsize+= 100;
+    cwdbuf= xrealloc(cwdbuf,cwdbufsize);
+    cwdbuf[cwdbufsize-1]= 0;
+    if (getcwd(cwdbuf,cwdbufsize-1)) { cwdbufsize= strlen(cwdbuf); break; }
+    if (errno != ERANGE) { cwdbufsize= 0; free(cwdbuf); break; }
+  }
+}
+
+static void process_override(const char *servicename) {
+  FILE *ovfile;
+  int ovavail, l, c;
+
+  switch (overridetype) {
+  case ot_none:
+    ovused= -1;
+    ovbuf= 0;
+    break;
+  case ot_builtin:
+    l= strlen(servicename);
+    if (l >= MAX_OVERRIDE_LEN-20)
+      miscerror("builtin service string is too long (%d, max is %d)",
+               l,MAX_OVERRIDE_LEN-21);
+    l+= 20;
+    ovbuf= xmalloc(l);
+    snprintf(ovbuf,l,"execute-builtin %s\n",servicename);
+    ovused= strlen(ovbuf);
+    break;
+  case ot_string:
+    l= strlen(overridevalue);
+    if (l >= MAX_OVERRIDE_LEN)
+      miscerror("override string is too long (%d, max is %d)",l,MAX_OVERRIDE_LEN-1);
+    ovbuf= xmalloc(l+2);
+    snprintf(ovbuf,l+2,"%s\n",overridevalue);
+    ovused= l+1;
+    break;
+  case ot_file:
+    ovfile= fopen(overridevalue,"r");
+    if (!ovfile) fsyscallerror("open overriding configuration file `%s'",overridevalue);
+    ovbuf= 0; ovavail= ovused= 0;
+    while ((c= getc(ovfile)) != EOF) {
+      if (!c) miscerror("overriding config file `%s' contains null(s)",overridevalue);
+      if (ovused >= MAX_OVERRIDE_LEN)
+        miscerror("override file is too long (max is %d)",MAX_OVERRIDE_LEN);
+      if (ovused >= ovavail) {
+        ovavail+=80; ovavail<<=2; ovbuf= xrealloc(ovbuf,ovavail);
+      }
+      ovbuf[ovused++]= c;
+    }
+    if (ferror(ovfile) || fclose(ovfile))
+      fsyscallerror("read overriding configuration file `%s'",overridevalue);
+    ovbuf= xrealloc(ovbuf,ovused+1);
+    ovbuf[ovused]= 0;
+    break;
+  default:
+    abort();
+  }
+}
+
+static int server_connect(void) {
+  struct sockaddr_un ssockname;
+  int sfd;
+  struct sigaction sig;
+  sigset_t sset;
+
+  sigemptyset(&sset);
+  sigaddset(&sset,SIGCHLD);
+  sigaddset(&sset,SIGALRM);
+  sigaddset(&sset,SIGPIPE);
+  if (sigprocmask(SIG_UNBLOCK,&sset,0)) syscallerror("preliminarily unblock signals");
+
+  sig.sa_handler= SIG_IGN;
+  sigemptyset(&sig.sa_mask);
+  sig.sa_flags= 0;
+  if (sigaction(SIGPIPE,&sig,0)) syscallerror("ignore sigpipe");
+
+  sfd= socket(AF_UNIX,SOCK_STREAM,0);
+  if (!sfd) syscallerror("create client socket");
+
+  assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUSPATH));
+  ssockname.sun_family= AF_UNIX;
+  strcpy(ssockname.sun_path,RENDEZVOUSPATH);
+  priv_resume();
+  while (connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname))) {
+    if (errno == ECONNREFUSED || errno == ENOENT)
+      syscallerror("uservd daemon is not running - service not available");
+    if (errno != EINTR)
+      fsyscallerror("unable to connect to uservd daemon: %m");
+  }
+
+  return sfd;
+}
+
+static void server_handshake(int sfd) {
+  srfile= fdopen(sfd,"r");
+  if (!srfile) syscallerror("turn socket fd into FILE* for read");
+  if (setvbuf(srfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket reads");
+
+  swfile= fdopen(sfd,"w");
+  if (!swfile) syscallerror("turn socket fd into FILE* for write");
+  if (setvbuf(swfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket writes");
+
+  xfread(&opening_mbuf,sizeof(opening_mbuf),srfile);
+  checkmagic(opening_mbuf.magic,OPENING_MAGIC,"in opening message");
+  if (memcmp(protocolchecksumversion,opening_mbuf.protocolchecksumversion,PCSUMSIZE))
+    protoerror("protocol version checksum mismatch - server not same as client");
+}
+
+static void server_preparepipes(void) {
+  char pipepathbuf[PIPEPATHMAXLEN+2];
+  int fd, tempfd;
+  
+  for (fd=0; fd<fdsetupsize; fd++) {
+    if (!fdsetup[fd].filename) continue;
+    pipepathbuf[PIPEPATHMAXLEN]= 0;
+    sprintf(pipepathbuf, PIPEPATHFORMAT,
+            (unsigned long)mypid, (unsigned long)opening_mbuf.serverpid, fd);
+    assert(!pipepathbuf[PIPEPATHMAXLEN]);
+    priv_resume();
+    if (unlink(pipepathbuf) && errno != ENOENT)
+      fsyscallerror("remove any old pipe `%s'",pipepathbuf);
+    if (mkfifo(pipepathbuf,0600)) /* permissions are irrelevant */
+      fsyscallerror("create pipe `%s'",pipepathbuf);
+    tempfd= open(pipepathbuf,O_RDWR);
+    if (tempfd<0) fsyscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
+    assert(fdsetup[fd].mods & (fdm_read|fdm_write));
+    fdsetup[fd].pipefd=
+      open(pipepathbuf, (fdsetup[fd].mods & fdm_read) ? O_WRONLY : O_RDONLY);
+    if (fdsetup[fd].pipefd<0) fsyscallerror("real open pipe `%s'",pipepathbuf);
+    if (close(tempfd)) fsyscallerror("close prelim fd onto pipe `%s'",pipepathbuf);
+    priv_suspend();
+  }
+}
+
+static void server_sendrequest(int argc, char *const *argv) {
+  struct request_msg request_mbuf;
+  unsigned long ul;
+  int fd, i;
+  
+  memset(&request_mbuf,0,sizeof(request_mbuf));
+  request_mbuf.magic= REQUEST_MAGIC;
+  request_mbuf.clientpid= getpid();
+  request_mbuf.serviceuserlen= strlen(serviceuser);
+  request_mbuf.servicelen= strlen(argv[0]);
+  request_mbuf.loginnamelen= strlen(loginname);
+  request_mbuf.spoofed= spoofuser ? 1 : 0;
+  request_mbuf.cwdlen= cwdbufsize;
+  request_mbuf.callinguid= spoofuid;
+  request_mbuf.ngids= ngids+1;
+  request_mbuf.nreadfds= 0;
+  request_mbuf.nwritefds= 0;
+  for (fd=0; fd<fdsetupsize; fd++) {
+    if (!fdsetup[fd].filename) continue;
+    assert(fdsetup[fd].mods & (fdm_write|fdm_read));
+    if (fdsetup[fd].mods & fdm_write) request_mbuf.nwritefds++;
+    else request_mbuf.nreadfds++;
+  }
+  request_mbuf.nargs= argc-1;
+  request_mbuf.nvars= defvarused;
+  request_mbuf.overridelen= ovused;
+  xfwrite(&request_mbuf,sizeof(request_mbuf),swfile);
+  xfwrite(serviceuser,sizeof(*serviceuser)*request_mbuf.serviceuserlen,swfile);
+  xfwrite(argv[0],sizeof(*argv[0])*request_mbuf.servicelen,swfile);
+  xfwrite(loginname,sizeof(*loginname)*request_mbuf.loginnamelen,swfile);
+  xfwrite(cwdbuf,sizeof(*cwdbuf)*request_mbuf.cwdlen,swfile);
+  if (ovused>=0) xfwrite(ovbuf,sizeof(*ovbuf)*ovused,swfile);
+  xfwrite(&spoofgid,sizeof(gid_t),swfile);
+  xfwrite(gidarray,sizeof(gid_t)*ngids,swfile);
+  xfwritefds(fdm_read,request_mbuf.nreadfds,swfile);
+  xfwritefds(fdm_write,request_mbuf.nwritefds,swfile);
+  for (i=1; i<argc; i++)
+    xfwritestring(argv[i],swfile);
+  for (i=0; i<defvarused; i++) {
+    xfwritestring(defvararray[i].key,swfile);
+    xfwritestring(defvararray[i].value,swfile);
+  }
+  ul= REQUEST_END_MAGIC; xfwrite(&ul,sizeof(ul),swfile);
+  xfflush(swfile);
+}
+
+static void server_awaitconfirm(void) {
+  struct progress_msg progress_mbuf;
+  
+  getprogress(&progress_mbuf,srfile);
+  if (progress_mbuf.type != pt_ok)
+    protoerror("progress message during configuration phase"
+              " unexpected type %d",progress_mbuf.type);
+}
+
+static void prepare_asynchsignals(void) {
+  static char stderrbuf[BUFSIZ], stdoutbuf[BUFSIZ];
+  
+  struct sigaction sig;
+
+  if (setvbuf(stderr,stderrbuf,_IOLBF,sizeof(stderrbuf)))
+    syscallerror("set buffering on stderr");
+  if (setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf)))
+    syscallerror("set buffering on stdout");
+  
+  sig.sa_handler= sighandler_chld;
+  sigemptyset(&sig.sa_mask);
+  sigaddset(&sig.sa_mask,SIGCHLD);
+  sigaddset(&sig.sa_mask,SIGALRM);
+  sig.sa_flags= 0;
+  if (sigaction(SIGCHLD,&sig,0)) syscallerror("set up sigchld handler");
+
+  sig.sa_handler= sighandler_alrm;
+  if (sigaction(SIGALRM,&sig,0)) syscallerror("set up sigalrm handler");
+
+  if (!timeout) return;
+  alarm(timeout);
+}
+
+
+static void close_unwanted_pipes(void) {
+  int fd;
+
+  for (fd=0; fd<fdsetupsize; fd++) {
+    if (!fdsetup[fd].filename) continue;
+    if (close(fdsetup[fd].pipefd)) fsyscallerror("close pipe fd for %d",fd);
+    if (fdsetup[fd].copyfd>2)
+      if (close(fdsetup[fd].copyfd))
+       if (errno != EBADF)
+         /* EBADF can be induced if cmd line specifies same fd twice */
+         fsyscallerror("close real fd for %d",fd);
+  }
+}
+
+static void catdup(const char *which, int from, int to) {
+  if (dup2(from,to)<0) {
+    blocksignals(SIG_BLOCK);
+    fprintf(stderr,"userv: %s: cannot dup for %s: %s\n",which,
+           to?"stdout":"stdin", strerror(errno));
+    exit(-1);
+  }
+}
+
+static void connect_pipes(void) {
+  struct sigaction sig;
+  char catnamebuf[sizeof(int)*3+30];
+  int fd, reading;
+  pid_t child;
+  
+  for (fd=0; fd<fdsetupsize; fd++) {
+    if (!fdsetup[fd].filename) continue;
+    if (!(fdsetup[fd].mods & fdm_fd)) {
+      fdsetup[fd].copyfd=
+       open(fdsetup[fd].filename,fdsetup[fd].oflags|O_NOCTTY,0777);
+      if (fdsetup[fd].copyfd<0)
+       fsyscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
+    }
+    blocksignals(SIG_BLOCK);
+    child= fork();
+    fdsetup[fd].catpid= child;
+    blocksignals(SIG_UNBLOCK);
+    if (child==-1) fsyscallerror("fork for cat for fd %d",fd);
+    if (!child) {
+      snprintf(catnamebuf,sizeof(catnamebuf),"cat fd%d",fd);
+      catnamebuf[sizeof(catnamebuf)-1]= 0;
+      sig.sa_handler= SIG_DFL;
+      sigemptyset(&sig.sa_mask);
+      sig.sa_flags= 0;
+      if (sigaction(SIGPIPE,&sig,0)) {
+       fprintf(stderr,"userv: %s: reset sigpipe handler for cat: %s",
+               catnamebuf,strerror(errno));
+       exit(-1);
+      }
+      reading= fdsetup[fd].mods & fdm_read;
+      catdup(catnamebuf, fdsetup[fd].copyfd, reading ? 0 : 1);
+      catdup(catnamebuf, fdsetup[fd].pipefd, reading ? 1 : 0);
+      if (close(socketfd))
+       fsyscallerror("%s: close client socket for for cat",catnamebuf);
+      close_unwanted_pipes();
+      execl("/bin/cat",catnamebuf,(char*)0);
+      fprintf(stderr,"userv: %s: cannot exec `cat': %s\n",catnamebuf,strerror(errno));
+      exit(-1);
+    }
+  }
+  close_unwanted_pipes();
+}
+
+static void server_sendconfirm(void) {
+  struct event_msg event_mbuf;
+
+  blocksignals(SIG_BLOCK);
+  memset(&event_mbuf,0,sizeof(event_mbuf));
+  event_mbuf.magic= EVENT_MAGIC;
+  event_mbuf.type= et_confirm;
+  xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
+  xfflush(swfile);
+  blocksignals(SIG_UNBLOCK);
+}
+
+static int server_awaitcompletion(void) {
+  struct progress_msg progress_mbuf;
+  
+  getprogress(&progress_mbuf,srfile);
+  if (progress_mbuf.type != pt_terminated)
+    protoerror("progress message during execution phase"
+              " unexpected type %d",progress_mbuf.type);
+
+  swfile= 0;
+  return progress_mbuf.data.terminated.status;
+}
+
+static void dispose_remaining_pipes(void) {
+  sigset_t sset;
+  int fd, r;
+
+  blocksignals(SIG_BLOCK);
+  for (fd=0; fd<fdsetupsize; fd++) {
+    if (!(fdsetup[fd].catpid!=-1 && (fdsetup[fd].mods & fdm_close))) continue;
+    if (kill(fdsetup[fd].catpid,SIGKILL)) fsyscallerror("kill cat for %d",fd);
+    fdsetup[fd].killed= 1;
+  }
+  blocksignals(SIG_UNBLOCK);
+
+  for (;;) {
+    blocksignals(SIG_BLOCK);
+    for (fd=0;
+        fd<fdsetupsize && !(fdsetup[fd].catpid!=-1 && (fdsetup[fd].mods & fdm_wait));
+        fd++);
+    if (fd>=fdsetupsize) break;
+    sigemptyset(&sset);
+    r= sigsuspend(&sset);
+    if (r && errno != EINTR) syscallerror("sigsuspend failed in unexpected way");
+    blocksignals(SIG_UNBLOCK);
+  }
+}
+
+static void NONRETURNING process_exitstatus(int status) {
+  blocksignals(SIG_BLOCK);
+
+  if (systemerror) _exit(255);
+
+  if (sigpipeok && signalsexit != se_stdout && WIFSIGNALED(status) &&
+      WTERMSIG(status)==SIGPIPE && !WCOREDUMP(status)) status= 0;
+  
+  switch (signalsexit) {
+  case se_number:
+  case se_numbernocore:
+    if (WIFEXITED(status))
+      _exit(WEXITSTATUS(status));
+    else if (WIFSIGNALED(status))
+      _exit(WTERMSIG(status) + (signalsexit==se_number && WCOREDUMP(status) ? 128 : 0));
+    break;
+  case se_highbit:
+    if (WIFEXITED(status))
+      _exit(WEXITSTATUS(status)<=127 ? WEXITSTATUS(status) : 127);
+    else if (WIFSIGNALED(status) && WTERMSIG(status)<=126)
+      _exit(WTERMSIG(status)+128);
+    break;
+  case se_stdout:
+    printf("\n%d %d ",(status>>8)&0x0ff,status&0x0ff);
+    if (WIFEXITED(status))
+      printf("exited with code %d",WEXITSTATUS(status));
+    else if (WIFSIGNALED(status))
+      printf("killed by %s (signal %d)%s",
+            strsignal(WTERMSIG(status)),WTERMSIG(status),
+            WCOREDUMP(status) ? ", core dumped " : "");
+    else
+      printf("unknown wait status");
+    putchar('\n');
+    if (ferror(stdout) || fflush(stdout)) syscallerror("write exit status to stdout");
+    _exit(0);
+  default:
+    if (WIFEXITED(status))
+      _exit(WEXITSTATUS(status));
+    else if (WIFSIGNALED(status))
+      _exit(signalsexit);
+    break;
+  }
+      
+  fprintf(stderr,"userv: unknown wait status %d\n",status);
+  _exit(-1);
+}
+
+int main(int argc, char *const *argv) {
+  int status;
+
+#ifdef NDEBUG
+# error Do not disable assertions in this security-critical code !
+#endif
+
+  security_init();
+  parse_arguments(&argc,&argv);
+  determine_users();
+  determine_cwd();
+  process_override(argv[0]);
+
+  socketfd= server_connect();
+  priv_suspend();
+  server_handshake(socketfd);
+  server_preparepipes();
+  server_sendrequest(argc,argv);
+
+  priv_permanentlyrevokesuspended();
+  
+  server_awaitconfirm();
+  prepare_asynchsignals();
+  connect_pipes();
+  server_sendconfirm();
+  status= server_awaitcompletion();
+  
+  dispose_remaining_pipes();
+  process_exitstatus(status);
+}
diff --git a/common.h b/common.h
new file mode 100644 (file)
index 0000000..e8f5b38
--- /dev/null
+++ b/common.h
@@ -0,0 +1,139 @@
+/*
+ * userv - common.h
+ * definitions shared between client and daemon
+ *
+ * Copyright (C)1996-1997,1999,2012 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef COMMON_H
+#define COMMON_H
+
+#define COPYRIGHT(indent,nl)                                           \
+ indent "Copyright (C)1996-2012 Ian Jackson; copyright (C)2000 Ben Harris." nl \
+ indent "there is NO WARRANTY; type `userv --copyright' for details." nl
+
+#define PCSUMSIZE 16
+
+static const unsigned char protocolchecksumversion[PCSUMSIZE]= {
+#include "pcsum.h"
+};
+
+#ifndef VARDIR
+# define VARDIR "/var/run/userv"
+#endif
+
+#ifndef RENDEZVOUS
+# define RENDEZVOUS "socket"
+#endif
+
+#ifndef RENDEZVOUSPATH
+# define RENDEZVOUSPATH VARDIR "/" RENDEZVOUS
+#endif
+
+#ifndef PIPEFORMAT
+# define PIPEFORMAT "%lx.%lx.%x"
+# define PIPEPATTERN "[0-9a-f]*.[0-9a-f]*.*[0-9a-f]"
+# define PIPEFORMATEXTEND ((int)(sizeof(unsigned long)*2*2+(int)sizeof(int)*2+3))
+# define PIPEMAXLEN ((int)(sizeof(PIPEFORMAT)+PIPEFORMATEXTEND))
+#endif
+
+#ifndef PIPEPATHFORMAT
+# define PIPEPATHFORMAT VARDIR "/" PIPEFORMAT
+# define PIPEPATHMAXLEN ((int)(sizeof(PIPEPATHFORMAT)+PIPEFORMATEXTEND))
+#endif
+
+#define MAX_ALLOW_FD 1024
+#define MAX_GENERAL_STRING (1024*1024)
+#define MAX_OVERRIDE_LEN MAX_GENERAL_STRING
+#define MAX_ERRMSG_STRING 4096
+#define MAX_ARGSDEFVAR 4096
+#define MAX_GIDS 1024
+
+#ifdef DEBUG
+# define BASE_MAGIC 0x5deb7567UL /* "\x5d\xebug" */
+#else
+# define BASE_MAGIC 0x755e7276UL /* "u\x5erv" */
+#endif
+
+enum {
+  OPENING_MAGIC= BASE_MAGIC+1,
+  REQUEST_MAGIC,
+  REQUEST_END_MAGIC,
+  PROGRESS_MAGIC,
+  PROGRESS_ERRMSG_END_MAGIC,
+  EVENT_MAGIC
+};
+
+struct opening_msg {
+  unsigned long magic;
+  unsigned char protocolchecksumversion[PCSUMSIZE];
+  pid_t overlordpid, serverpid;
+};
+
+struct request_msg {
+  unsigned long magic;
+  pid_t clientpid; /* or -1 if no service is required and this was a version check */
+  int serviceuserlen;
+  int servicelen;
+  int loginnamelen, spoofed; /* spoofed is 0 or 1 */
+  int cwdlen, overridelen;
+  uid_t callinguid;
+  int ngids, nreadfds, nwritefds, nargs, nvars;
+  /* Followed by:
+   *   serviceuserlen bytes for the service user (unterminated)
+   *   servicelen bytes for the service (unterminated)
+   *   loginnamelen bytes for the login name (unterminated)
+   *   cwdlen bytes for the cwd (unterminated)
+   *   overridelen bytes for the override data (with extra \n but unterminated),
+   *    or nothing if overridelen==-1
+   *   ngids gid_ts for the primary group and supplementary groups
+   *   nreadfds and then nwritefds ints for the file descriptors
+   *   for each of the nargs arguments
+   *    an int for the string length
+   *    that many characters (unterminated)
+   *   for each for the nvars variable keys
+   *    an int for the key length
+   *    that many characters (unterminated)
+   *    an int for the value length
+   *    that many characters (unterminated)
+   *   one unsigned long, endmagic;
+   */
+};
+
+struct progress_msg {
+  unsigned long magic;
+  enum { pt_ok, pt_errmsg, pt_failed, pt_terminated } type;
+  union {
+    struct { int messagelen; } errmsg;
+    struct { int status; } terminated;
+  } data;
+  /* follwed by variable-length part:
+   *  for ok, failed, terminated: nothing
+   *  for errmsg: messagelen bytes for the error message (unterminated, no \n)
+   *              unsigned long PROGRESS_ERRMSG_END_MAGIC
+   */
+};
+
+struct event_msg {
+  unsigned long magic;
+  enum { et_confirm, et_closereadfd, et_disconnect } type;
+  union {
+    struct { int fd; } closereadfd;
+  } data;
+};
+
+#endif
diff --git a/config.h.in b/config.h.in
new file mode 100644 (file)
index 0000000..e3de9bf
--- /dev/null
@@ -0,0 +1,109 @@
+/* config.h.in.  Generated automatically from configure.in by autoheader 2.13.  */
+
+/* Define if EPROTO exists.  */
+#undef HAVE_EPROTO
+
+/* Define if LOG_AUTHPRIV exists.  */
+#undef HAVE_LOG_AUTHPRIV
+
+/* Define if WCOREDUMP exists.  */
+#undef HAVE_WCOREDUMP
+
+/* Define if function attributes a la GCC 2.5 and higher are available.  */
+#undef HAVE_GNUC25_ATTRIB
+
+/* Define if unused functions a la GCC 2.5 and higher are available.  */
+#undef HAVE_GNUC25_UNUSED
+
+/* Define if nonreturning functions a la GCC 2.5 and higher are available.  */
+#undef HAVE_GNUC25_NORETURN
+
+/* Define if printf-format argument lists a la GCC are available.  */
+#undef HAVE_GNUC25_PRINTFFORMAT
+
+/* Define if you have the setenv function.  */
+#undef HAVE_SETENV
+
+/* Define if you have the strsignal function.  */
+#undef HAVE_STRSIGNAL
+
+/* Define if you have the vsnprintf function.  */
+#undef HAVE_VSNPRINTF
+
+/* Define if you have the socket library (-lsocket).  */
+#undef HAVE_LIBSOCKET
+
+/* STRSIGNAL */
+#ifndef HAVE_STRSIGNAL
+#define STRSIGNAL(x) "[platform has no strsignal!]"
+#endif
+
+/* VSNPRINTF */
+#ifndef HAVE_VSNPRINTF
+# error "You must have vsnprintf!  Without vsnprintf it is very hard to write secure programs.  If you don't have it then your system libc is probably full of hideous buffer overrun security bugs.  But, if you don't want to fix your system a portable snprintf can be found at http://www.ijs.si/software/snprintf/"
+#endif
+
+/* EPROTO */
+#ifndef HAVE_EPROTO
+#define EPROTO 0
+#endif
+
+/* LOG_AUTHPRIV */
+#ifndef HAVE_LOG_AUTHPRIV
+#define LOG_AUTHPRIV LOG_AUTH
+#endif
+
+/* WCOREDUMP */
+#ifndef HAVE_WCOREDUMP
+#define WCOREDUMP(x) 0
+#endif
+
+/* GNU C attributes. */
+#ifndef FUNCATTR
+#ifdef HAVE_GNUC25_ATTRIB
+#define FUNCATTR(x) __attribute__(x)
+#else
+#define FUNCATTR(x)
+#endif
+#endif
+
+/* GNU C printf formats, or null. */
+#ifndef ATTRPRINTF
+#ifdef HAVE_GNUC25_PRINTFFORMAT
+#define ATTRPRINTF(si,tc) format(printf,si,tc)
+#else
+#define ATTRPRINTF(si,tc)
+#endif
+#endif
+#ifndef PRINTFFORMAT
+#define PRINTFFORMAT(si,tc) FUNCATTR((ATTRPRINTF(si,tc)))
+#endif
+
+/* GNU C nonreturning functions, or null. */
+#ifndef ATTRNORETURN
+#ifdef HAVE_GNUC25_NORETURN
+#define ATTRNORETURN noreturn
+#else
+#define ATTRNORETURN
+#endif
+#endif
+#ifndef NONRETURNING
+#define NONRETURNING FUNCATTR((ATTRNORETURN))
+#endif
+
+/* Combination of both the above. */
+#ifndef NONRETURNPRINTFFORMAT
+#define NONRETURNPRINTFFORMAT(si,tc) FUNCATTR((ATTRPRINTF(si,tc),ATTRNORETURN))
+#endif
+
+/* GNU C unused functions, or null. */
+#ifndef ATTRUNUSED
+#ifdef HAVE_GNUC25_UNUSED
+#define ATTRUNUSED unused
+#else
+#define ATTRUNUSED
+#endif
+#endif
+#ifndef UNUSED
+#define UNUSED FUNCATTR((ATTRUNUSED))
+#endif
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..6cbbdb2
--- /dev/null
+++ b/configure
@@ -0,0 +1,1962 @@
+#! /bin/sh
+
+# Guess values for system-dependent variables and create Makefiles.
+# Generated automatically using autoconf version 2.13 
+# Copyright (C) 1992, 93, 94, 95, 96 Free Software Foundation, Inc.
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+
+# Defaults:
+ac_help=
+ac_default_prefix=/usr/local
+# Any additions from configure.in:
+ac_default_prefix=/usr/local
+ac_help="$ac_help
+  --enable-debug          build debugging version"
+
+# Initialize some variables set by options.
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+build=NONE
+cache_file=./config.cache
+exec_prefix=NONE
+host=NONE
+no_create=
+nonopt=NONE
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+target=NONE
+verbose=
+x_includes=NONE
+x_libraries=NONE
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datadir='${prefix}/share'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+libdir='${exec_prefix}/lib'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+infodir='${prefix}/info'
+mandir='${prefix}/man'
+
+# Initialize some other variables.
+subdirs=
+MFLAGS= MAKEFLAGS=
+SHELL=${CONFIG_SHELL-/bin/sh}
+# Maximum number of lines to put in a shell here document.
+ac_max_here_lines=12
+
+ac_prev=
+for ac_option
+do
+
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval "$ac_prev=\$ac_option"
+    ac_prev=
+    continue
+  fi
+
+  case "$ac_option" in
+  -*=*) ac_optarg=`echo "$ac_option" | sed 's/[-_a-zA-Z0-9]*=//'` ;;
+  *) ac_optarg= ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case "$ac_option" in
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir="$ac_optarg" ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build="$ac_optarg" ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file="$ac_optarg" ;;
+
+  -datadir | --datadir | --datadi | --datad | --data | --dat | --da)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=* | --data=* | --dat=* \
+  | --da=*)
+    datadir="$ac_optarg" ;;
+
+  -disable-* | --disable-*)
+    ac_feature=`echo $ac_option|sed -e 's/-*disable-//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_feature| sed 's/[-a-zA-Z0-9_]//g'`"; then
+      { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+    fi
+    ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+    eval "enable_${ac_feature}=no" ;;
+
+  -enable-* | --enable-*)
+    ac_feature=`echo $ac_option|sed -e 's/-*enable-//' -e 's/=.*//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_feature| sed 's/[-_a-zA-Z0-9]//g'`"; then
+      { echo "configure: error: $ac_feature: invalid feature name" 1>&2; exit 1; }
+    fi
+    ac_feature=`echo $ac_feature| sed 's/-/_/g'`
+    case "$ac_option" in
+      *=*) ;;
+      *) ac_optarg=yes ;;
+    esac
+    eval "enable_${ac_feature}='$ac_optarg'" ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix="$ac_optarg" ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he)
+    # Omit some internal or obsolete options to make the list less imposing.
+    # This message is too long to be a string in the A/UX 3.1 sh.
+    cat << EOF
+Usage: configure [options] [host]
+Options: [defaults in brackets after descriptions]
+Configuration:
+  --cache-file=FILE       cache test results in FILE
+  --help                  print this message
+  --no-create             do not create output files
+  --quiet, --silent       do not print \`checking...' messages
+  --version               print the version of autoconf that created configure
+Directory and file names:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [same as prefix]
+  --bindir=DIR            user executables in DIR [EPREFIX/bin]
+  --sbindir=DIR           system admin executables in DIR [EPREFIX/sbin]
+  --libexecdir=DIR        program executables in DIR [EPREFIX/libexec]
+  --datadir=DIR           read-only architecture-independent data in DIR
+                          [PREFIX/share]
+  --sysconfdir=DIR        read-only single-machine data in DIR [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data in DIR
+                          [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data in DIR [PREFIX/var]
+  --libdir=DIR            object code libraries in DIR [EPREFIX/lib]
+  --includedir=DIR        C header files in DIR [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc in DIR [/usr/include]
+  --infodir=DIR           info documentation in DIR [PREFIX/info]
+  --mandir=DIR            man documentation in DIR [PREFIX/man]
+  --srcdir=DIR            find the sources in DIR [configure dir or ..]
+  --program-prefix=PREFIX prepend PREFIX to installed program names
+  --program-suffix=SUFFIX append SUFFIX to installed program names
+  --program-transform-name=PROGRAM
+                          run sed PROGRAM on installed program names
+EOF
+    cat << EOF
+Host type:
+  --build=BUILD           configure for building on BUILD [BUILD=HOST]
+  --host=HOST             configure for HOST [guessed]
+  --target=TARGET         configure for TARGET [TARGET=HOST]
+Features and packages:
+  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
+  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
+  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
+  --x-includes=DIR        X include files are in DIR
+  --x-libraries=DIR       X library files are in DIR
+EOF
+    if test -n "$ac_help"; then
+      echo "--enable and --with options recognized:$ac_help"
+    fi
+    exit 0 ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host="$ac_optarg" ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir="$ac_optarg" ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir="$ac_optarg" ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir="$ac_optarg" ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir="$ac_optarg" ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst \
+  | --locals | --local | --loca | --loc | --lo)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* \
+  | --locals=* | --local=* | --loca=* | --loc=* | --lo=*)
+    localstatedir="$ac_optarg" ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir="$ac_optarg" ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir="$ac_optarg" ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix="$ac_optarg" ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix="$ac_optarg" ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix="$ac_optarg" ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name="$ac_optarg" ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir="$ac_optarg" ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir="$ac_optarg" ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site="$ac_optarg" ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir="$ac_optarg" ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir="$ac_optarg" ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target="$ac_optarg" ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers)
+    echo "configure generated by autoconf version 2.13"
+    exit 0 ;;
+
+  -with-* | --with-*)
+    ac_package=`echo $ac_option|sed -e 's/-*with-//' -e 's/=.*//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_package| sed 's/[-_a-zA-Z0-9]//g'`"; then
+      { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+    fi
+    ac_package=`echo $ac_package| sed 's/-/_/g'`
+    case "$ac_option" in
+      *=*) ;;
+      *) ac_optarg=yes ;;
+    esac
+    eval "with_${ac_package}='$ac_optarg'" ;;
+
+  -without-* | --without-*)
+    ac_package=`echo $ac_option|sed -e 's/-*without-//'`
+    # Reject names that are not valid shell variable names.
+    if test -n "`echo $ac_package| sed 's/[-a-zA-Z0-9_]//g'`"; then
+      { echo "configure: error: $ac_package: invalid package name" 1>&2; exit 1; }
+    fi
+    ac_package=`echo $ac_package| sed 's/-/_/g'`
+    eval "with_${ac_package}=no" ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes="$ac_optarg" ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries="$ac_optarg" ;;
+
+  -*) { echo "configure: error: $ac_option: invalid option; use --help to show usage" 1>&2; exit 1; }
+    ;;
+
+  *)
+    if test -n "`echo $ac_option| sed 's/[-a-z0-9.]//g'`"; then
+      echo "configure: warning: $ac_option: invalid host type" 1>&2
+    fi
+    if test "x$nonopt" != xNONE; then
+      { echo "configure: error: can only configure for one host and one target at a time" 1>&2; exit 1; }
+    fi
+    nonopt="$ac_option"
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  { echo "configure: error: missing argument to --`echo $ac_prev | sed 's/_/-/g'`" 1>&2; exit 1; }
+fi
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+# File descriptor usage:
+# 0 standard input
+# 1 file creation
+# 2 errors and warnings
+# 3 some systems may open it to /dev/tty
+# 4 used on the Kubota Titan
+# 6 checking for... messages and results
+# 5 compiler messages saved in config.log
+if test "$silent" = yes; then
+  exec 6>/dev/null
+else
+  exec 6>&1
+fi
+exec 5>./config.log
+
+echo "\
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+" 1>&5
+
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Also quote any args containing shell metacharacters.
+ac_configure_args=
+for ac_arg
+do
+  case "$ac_arg" in
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c) ;;
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) ;;
+  *" "*|*"     "*|*[\[\]\~\#\$\^\&\*\(\)\{\}\\\|\;\<\>\?]*)
+  ac_configure_args="$ac_configure_args '$ac_arg'" ;;
+  *) ac_configure_args="$ac_configure_args $ac_arg" ;;
+  esac
+done
+
+# NLS nuisances.
+# Only set these to C if already set.  These must not be set unconditionally
+# because not all systems understand e.g. LANG=C (notably SCO).
+# Fixing LC_MESSAGES prevents Solaris sh from translating var values in `set'!
+# Non-C LC_CTYPE values break the ctype check.
+if test "${LANG+set}"   = set; then LANG=C;   export LANG;   fi
+if test "${LC_ALL+set}" = set; then LC_ALL=C; export LC_ALL; fi
+if test "${LC_MESSAGES+set}" = set; then LC_MESSAGES=C; export LC_MESSAGES; fi
+if test "${LC_CTYPE+set}"    = set; then LC_CTYPE=C;    export LC_CTYPE;    fi
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -rf conftest* confdefs.h
+# AIX cpp loses on an empty file, so make sure it contains at least a newline.
+echo > confdefs.h
+
+# A filename unique to this package, relative to the directory that
+# configure is in, which we can look for to find out if srcdir is correct.
+ac_unique_file=language.i4
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then its parent.
+  ac_prog=$0
+  ac_confdir=`echo $ac_prog|sed 's%/[^/][^/]*$%%'`
+  test "x$ac_confdir" = "x$ac_prog" && ac_confdir=.
+  srcdir=$ac_confdir
+  if test ! -r $srcdir/$ac_unique_file; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r $srcdir/$ac_unique_file; then
+  if test "$ac_srcdir_defaulted" = yes; then
+    { echo "configure: error: can not find sources in $ac_confdir or .." 1>&2; exit 1; }
+  else
+    { echo "configure: error: can not find sources in $srcdir" 1>&2; exit 1; }
+  fi
+fi
+srcdir=`echo "${srcdir}" | sed 's%\([^/]\)/*$%\1%'`
+
+# Prefer explicitly selected file to automatically selected ones.
+if test -z "$CONFIG_SITE"; then
+  if test "x$prefix" != xNONE; then
+    CONFIG_SITE="$prefix/share/config.site $prefix/etc/config.site"
+  else
+    CONFIG_SITE="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site"
+  fi
+fi
+for ac_site_file in $CONFIG_SITE; do
+  if test -r "$ac_site_file"; then
+    echo "loading site script $ac_site_file"
+    . "$ac_site_file"
+  fi
+done
+
+if test -r "$cache_file"; then
+  echo "loading cache $cache_file"
+  . $cache_file
+else
+  echo "creating cache $cache_file"
+  > $cache_file
+fi
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+ac_exeext=
+ac_objext=o
+if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null; then
+  # Stardent Vistra SVR4 grep lacks -e, says ghazi@caip.rutgers.edu.
+  if (echo -n testing; echo 1,2,3) | sed s/-n/xn/ | grep xn >/dev/null; then
+    ac_n= ac_c='
+' ac_t='       '
+  else
+    ac_n=-n ac_c= ac_t=
+  fi
+else
+  ac_n= ac_c='\c' ac_t=
+fi
+
+
+
+
+
+
+crdir="`pwd`"
+
+
+DEBUGDEFS=''
+DEBUGLIBS=''
+# Check whether --enable-debug or --disable-debug was given.
+if test "${enable_debug+set}" = set; then
+  enableval="$enable_debug"
+  
+ if test "x$enable_debug" = xyes; then
+  DEBUGDEFS="-DDEBUG -DVARDIR='\"$crdir/vd\"' -DSYSTEMCONFIGDIR='\"$crdir/slash-etc\"' -DSERVICEUSERDIR='\"$crdir/tilde\"'"
+  DEBUGLIBS=-lefence
+  echo will build debugging version
+ elif test "x$enable_debug" != xno; then
+  { echo "configure: error: --enable-debug does not allow any arguments except 'yes' and 'no'" 1>&2; exit 1; }
+ fi
+
+fi
+
+
+# Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:555: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  IFS="${IFS=  }"; ac_save_ifs="$IFS"; IFS=":"
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      ac_cv_prog_CC="gcc"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+  echo "$ac_t""$CC" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:585: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  IFS="${IFS=  }"; ac_save_ifs="$IFS"; IFS=":"
+  ac_prog_rejected=no
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      if test "$ac_dir/$ac_word" = "/usr/ucb/cc"; then
+        ac_prog_rejected=yes
+       continue
+      fi
+      ac_cv_prog_CC="cc"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# -gt 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    set dummy "$ac_dir/$ac_word" "$@"
+    shift
+    ac_cv_prog_CC="$@"
+  fi
+fi
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+  echo "$ac_t""$CC" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+  if test -z "$CC"; then
+    case "`uname -s`" in
+    *win32* | *WIN32*)
+      # Extract the first word of "cl", so it can be a program name with args.
+set dummy cl; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:636: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_CC'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  IFS="${IFS=  }"; ac_save_ifs="$IFS"; IFS=":"
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      ac_cv_prog_CC="cl"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+fi
+fi
+CC="$ac_cv_prog_CC"
+if test -n "$CC"; then
+  echo "$ac_t""$CC" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+ ;;
+    esac
+  fi
+  test -z "$CC" && { echo "configure: error: no acceptable cc found in \$PATH" 1>&2; exit 1; }
+fi
+
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works""... $ac_c" 1>&6
+echo "configure:668: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) works" >&5
+
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+cat > conftest.$ac_ext << EOF
+
+#line 679 "configure"
+#include "confdefs.h"
+
+main(){return(0);}
+EOF
+if { (eval echo configure:684: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  ac_cv_prog_cc_works=yes
+  # If we can't run a trivial program, we are probably using a cross compiler.
+  if (./conftest; exit) 2>/dev/null; then
+    ac_cv_prog_cc_cross=no
+  else
+    ac_cv_prog_cc_cross=yes
+  fi
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  ac_cv_prog_cc_works=no
+fi
+rm -fr conftest*
+ac_ext=c
+# CFLAGS is not in ac_cpp because -g, -O, etc. are not valid cpp options.
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='${CC-cc} -c $CFLAGS $CPPFLAGS conftest.$ac_ext 1>&5'
+ac_link='${CC-cc} -o conftest${ac_exeext} $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS 1>&5'
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo "$ac_t""$ac_cv_prog_cc_works" 1>&6
+if test $ac_cv_prog_cc_works = no; then
+  { echo "configure: error: installation or configuration problem: C compiler cannot create executables." 1>&2; exit 1; }
+fi
+echo $ac_n "checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler""... $ac_c" 1>&6
+echo "configure:710: checking whether the C compiler ($CC $CFLAGS $LDFLAGS) is a cross-compiler" >&5
+echo "$ac_t""$ac_cv_prog_cc_cross" 1>&6
+cross_compiling=$ac_cv_prog_cc_cross
+
+echo $ac_n "checking whether we are using GNU C""... $ac_c" 1>&6
+echo "configure:715: checking whether we are using GNU C" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_gcc'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.c <<EOF
+#ifdef __GNUC__
+  yes;
+#endif
+EOF
+if { ac_try='${CC-cc} -E conftest.c'; { (eval echo configure:724: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }; } | egrep yes >/dev/null 2>&1; then
+  ac_cv_prog_gcc=yes
+else
+  ac_cv_prog_gcc=no
+fi
+fi
+
+echo "$ac_t""$ac_cv_prog_gcc" 1>&6
+
+if test $ac_cv_prog_gcc = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+
+ac_test_CFLAGS="${CFLAGS+set}"
+ac_save_CFLAGS="$CFLAGS"
+CFLAGS=
+echo $ac_n "checking whether ${CC-cc} accepts -g""... $ac_c" 1>&6
+echo "configure:743: checking whether ${CC-cc} accepts -g" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_cc_g'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  echo 'void f(){}' > conftest.c
+if test -z "`${CC-cc} -g -c conftest.c 2>&1`"; then
+  ac_cv_prog_cc_g=yes
+else
+  ac_cv_prog_cc_g=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$ac_cv_prog_cc_g" 1>&6
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS="$ac_save_CFLAGS"
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+
+echo $ac_n "checking how to run the C preprocessor""... $ac_c" 1>&6
+echo "configure:775: checking how to run the C preprocessor" >&5
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+if eval "test \"`echo '$''{'ac_cv_prog_CPP'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+    # This must be in double quotes, not single quotes, because CPP may get
+  # substituted into the Makefile and "${CC-cc}" will confuse make.
+  CPP="${CC-cc} -E"
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp.
+  cat > conftest.$ac_ext <<EOF
+#line 790 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:796: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  :
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  CPP="${CC-cc} -E -traditional-cpp"
+  cat > conftest.$ac_ext <<EOF
+#line 807 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:813: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  :
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  CPP="${CC-cc} -nologo -E"
+  cat > conftest.$ac_ext <<EOF
+#line 824 "configure"
+#include "confdefs.h"
+#include <assert.h>
+Syntax Error
+EOF
+ac_try="$ac_cpp conftest.$ac_ext >/dev/null 2>conftest.out"
+{ (eval echo configure:830: \"$ac_try\") 1>&5; (eval $ac_try) 2>&5; }
+ac_err=`grep -v '^ *+' conftest.out | grep -v "^conftest.${ac_ext}\$"`
+if test -z "$ac_err"; then
+  :
+else
+  echo "$ac_err" >&5
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  CPP=/lib/cpp
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+fi
+rm -f conftest*
+  ac_cv_prog_CPP="$CPP"
+fi
+  CPP="$ac_cv_prog_CPP"
+else
+  ac_cv_prog_CPP="$CPP"
+fi
+echo "$ac_t""$CPP" 1>&6
+
+ac_aux_dir=
+for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do
+  if test -f $ac_dir/install-sh; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install-sh -c"
+    break
+  elif test -f $ac_dir/install.sh; then
+    ac_aux_dir=$ac_dir
+    ac_install_sh="$ac_aux_dir/install.sh -c"
+    break
+  fi
+done
+if test -z "$ac_aux_dir"; then
+  { echo "configure: error: can not find install-sh or install.sh in $srcdir $srcdir/.. $srcdir/../.." 1>&2; exit 1; }
+fi
+ac_config_guess=$ac_aux_dir/config.guess
+ac_config_sub=$ac_aux_dir/config.sub
+ac_configure=$ac_aux_dir/configure # This should be Cygnus configure.
+
+# Find a good install program.  We prefer a C program (faster),
+# so one script is as good as another.  But avoid the broken or
+# incompatible versions:
+# SysV /etc/install, /usr/sbin/install
+# SunOS /usr/etc/install
+# IRIX /sbin/install
+# AIX /bin/install
+# AIX 4 /usr/bin/installbsd, which doesn't work without a -g flag
+# AFS /usr/afsws/bin/install, which mishandles nonexistent args
+# SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
+# ./install, which can be erroneously created by make from ./install.sh.
+echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6
+echo "configure:885: checking for a BSD compatible install" >&5
+if test -z "$INSTALL"; then
+if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+    IFS="${IFS=        }"; ac_save_IFS="$IFS"; IFS=":"
+  for ac_dir in $PATH; do
+    # Account for people who put trailing slashes in PATH elements.
+    case "$ac_dir/" in
+    /|./|.//|/etc/*|/usr/sbin/*|/usr/etc/*|/sbin/*|/usr/afsws/bin/*|/usr/ucb/*) ;;
+    *)
+      # OSF1 and SCO ODT 3.0 have their own names for install.
+      # Don't use installbsd from OSF since it installs stuff as root
+      # by default.
+      for ac_prog in ginstall scoinst install; do
+        if test -f $ac_dir/$ac_prog; then
+         if test $ac_prog = install &&
+            grep dspmsg $ac_dir/$ac_prog >/dev/null 2>&1; then
+           # AIX install.  It has an incompatible calling convention.
+           :
+         else
+           ac_cv_path_install="$ac_dir/$ac_prog -c"
+           break 2
+         fi
+       fi
+      done
+      ;;
+    esac
+  done
+  IFS="$ac_save_IFS"
+
+fi
+  if test "${ac_cv_path_install+set}" = set; then
+    INSTALL="$ac_cv_path_install"
+  else
+    # As a last resort, use the slow shell script.  We don't cache a
+    # path for INSTALL within a source directory, because that will
+    # break other packages using the cache if that directory is
+    # removed, or if the path is relative.
+    INSTALL="$ac_install_sh"
+  fi
+fi
+echo "$ac_t""$INSTALL" 1>&6
+
+# Use test -z because SunOS4 sh mishandles braces in ${var-val}.
+# It thinks the first close brace ends the variable substitution.
+test -z "$INSTALL_PROGRAM" && INSTALL_PROGRAM='${INSTALL}'
+
+test -z "$INSTALL_SCRIPT" && INSTALL_SCRIPT='${INSTALL_PROGRAM}'
+
+test -z "$INSTALL_DATA" && INSTALL_DATA='${INSTALL} -m 644'
+
+for ac_prog in md5sum md5 gmd5sum
+do
+# Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+echo $ac_n "checking for $ac_word""... $ac_c" 1>&6
+echo "configure:942: checking for $ac_word" >&5
+if eval "test \"`echo '$''{'ac_cv_prog_MD5SUM_SIMPLE'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  if test -n "$MD5SUM_SIMPLE"; then
+  ac_cv_prog_MD5SUM_SIMPLE="$MD5SUM_SIMPLE" # Let the user override the test.
+else
+  IFS="${IFS=  }"; ac_save_ifs="$IFS"; IFS=":"
+  ac_dummy="$PATH"
+  for ac_dir in $ac_dummy; do
+    test -z "$ac_dir" && ac_dir=.
+    if test -f $ac_dir/$ac_word; then
+      ac_cv_prog_MD5SUM_SIMPLE="$ac_prog"
+      break
+    fi
+  done
+  IFS="$ac_save_ifs"
+fi
+fi
+MD5SUM_SIMPLE="$ac_cv_prog_MD5SUM_SIMPLE"
+if test -n "$MD5SUM_SIMPLE"; then
+  echo "$ac_t""$MD5SUM_SIMPLE" 1>&6
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+test -n "$MD5SUM_SIMPLE" && break
+done
+
+
+CFLAGS="$CFLAGS -D_GNU_SOURCE"
+
+echo $ac_n "checking for socket in -lsocket""... $ac_c" 1>&6
+echo "configure:975: checking for socket in -lsocket" >&5
+ac_lib_var=`echo socket'_'socket | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lsocket  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 983 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char socket();
+
+int main() {
+socket()
+; return 0; }
+EOF
+if { (eval echo configure:994: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo socket | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lsocket $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+for ac_func in setenv strsignal vsnprintf
+do
+echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
+echo "configure:1024: checking for $ac_func" >&5
+if eval "test \"`echo '$''{'ac_cv_func_$ac_func'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1029 "configure"
+#include "confdefs.h"
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $ac_func(); below.  */
+#include <assert.h>
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char $ac_func();
+
+int main() {
+
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+$ac_func();
+#endif
+
+; return 0; }
+EOF
+if { (eval echo configure:1052: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_func_$ac_func=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_func_$ac_func=no"
+fi
+rm -f conftest*
+fi
+
+if eval "test \"`echo '$ac_cv_func_'$ac_func`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_func=HAVE_`echo $ac_func | tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_func 1
+EOF
+else
+  echo "$ac_t""no" 1>&6
+fi
+done
+
+
+echo $ac_n "checking for EPROTO""... $ac_c" 1>&6
+echo "configure:1078: checking for EPROTO" >&5
+if eval "test \"`echo '$''{'userv_cv_hdr_eproto'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1083 "configure"
+#include "confdefs.h"
+
+#include <errno.h>
+#ifdef EPROTO
+ yes
+#endif
+
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  egrep "yes" >/dev/null 2>&1; then
+  rm -rf conftest*
+  userv_cv_hdr_eproto=yes
+else
+  rm -rf conftest*
+  userv_cv_hdr_eproto=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$userv_cv_hdr_eproto" 1>&6
+if test $userv_cv_hdr_eproto = yes
+then
+       cat >> confdefs.h <<\EOF
+#define HAVE_EPROTO 1
+EOF
+
+fi
+
+echo $ac_n "checking for LOG_AUTHPRIV""... $ac_c" 1>&6
+echo "configure:1114: checking for LOG_AUTHPRIV" >&5
+if eval "test \"`echo '$''{'userv_cv_hdr_logauthpriv'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1119 "configure"
+#include "confdefs.h"
+
+#include <syslog.h>
+#ifdef LOG_AUTHPRIV
+ yes
+#endif
+
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  egrep "yes" >/dev/null 2>&1; then
+  rm -rf conftest*
+  userv_cv_hdr_logauthpriv=yes
+else
+  rm -rf conftest*
+  userv_cv_hdr_logauthpriv=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$userv_cv_hdr_logauthpriv" 1>&6
+if test $userv_cv_hdr_logauthpriv = yes
+then
+       cat >> confdefs.h <<\EOF
+#define HAVE_LOG_AUTHPRIV 1
+EOF
+
+fi
+
+echo $ac_n "checking for WCOREDUMP""... $ac_c" 1>&6
+echo "configure:1150: checking for WCOREDUMP" >&5
+if eval "test \"`echo '$''{'userv_cv_hdr_wcoredump'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  cat > conftest.$ac_ext <<EOF
+#line 1155 "configure"
+#include "confdefs.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#ifdef WCOREDUMP
+ yes
+#endif
+
+EOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  egrep "yes" >/dev/null 2>&1; then
+  rm -rf conftest*
+  userv_cv_hdr_wcoredump=yes
+else
+  rm -rf conftest*
+  userv_cv_hdr_wcoredump=no
+fi
+rm -f conftest*
+
+fi
+
+echo "$ac_t""$userv_cv_hdr_wcoredump" 1>&6
+if test $userv_cv_hdr_wcoredump = yes
+then
+       cat >> confdefs.h <<\EOF
+#define HAVE_WCOREDUMP 1
+EOF
+
+fi
+
+
+if test "${GCC-no}" = yes; then
+ OPTIMISE=-O2
+else
+ OPTIMISE=-O
+fi
+
+
+
+
+ echo $ac_n "checking your C compiler""... $ac_c" 1>&6
+echo "configure:1197: checking your C compiler" >&5
+ if eval "test \"`echo '$''{'dpkg_cv_c_works'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  
+  cat > conftest.$ac_ext <<EOF
+#line 1203 "configure"
+#include "confdefs.h"
+#include <string.h>
+int main() {
+strcmp("a","b")
+; return 0; }
+EOF
+if { (eval echo configure:1210: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  dpkg_cv_c_works=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  dpkg_cv_c_works=no
+fi
+rm -f conftest*
+fi
+
+ if test "x$dpkg_cv_c_works" = xyes; then
+  true
+  echo "$ac_t""works" 1>&6
+ else
+  true
+  echo "$ac_t""broken" 1>&6
+ { echo "configure: error: C compiler is broken" 1>&2; exit 1; }
+ fi
+
+
+
+ echo $ac_n "checking __attribute__((,,))""... $ac_c" 1>&6
+echo "configure:1235: checking __attribute__((,,))" >&5
+ if eval "test \"`echo '$''{'dpkg_cv_c_attribute_supported'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  
+  cat > conftest.$ac_ext <<EOF
+#line 1241 "configure"
+#include "confdefs.h"
+
+int main() {
+extern int testfunction(int x) __attribute__((,,))
+; return 0; }
+EOF
+if { (eval echo configure:1248: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  dpkg_cv_c_attribute_supported=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  dpkg_cv_c_attribute_supported=no
+fi
+rm -f conftest*
+fi
+
+ if test "x$dpkg_cv_c_attribute_supported" = xyes; then
+  true
+  echo "$ac_t""yes" 1>&6
+ cat >> confdefs.h <<\EOF
+#define HAVE_GNUC25_ATTRIB 1
+EOF
+
+  
+ echo $ac_n "checking __attribute__((noreturn))""... $ac_c" 1>&6
+echo "configure:1270: checking __attribute__((noreturn))" >&5
+ if eval "test \"`echo '$''{'dpkg_cv_c_attribute_noreturn'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  
+  cat > conftest.$ac_ext <<EOF
+#line 1276 "configure"
+#include "confdefs.h"
+
+int main() {
+extern int testfunction(int x) __attribute__((noreturn))
+; return 0; }
+EOF
+if { (eval echo configure:1283: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  dpkg_cv_c_attribute_noreturn=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  dpkg_cv_c_attribute_noreturn=no
+fi
+rm -f conftest*
+fi
+
+ if test "x$dpkg_cv_c_attribute_noreturn" = xyes; then
+  true
+  echo "$ac_t""yes" 1>&6
+   cat >> confdefs.h <<\EOF
+#define HAVE_GNUC25_NORETURN 1
+EOF
+
+ else
+  true
+  echo "$ac_t""no" 1>&6
+ fi
+
+  
+ echo $ac_n "checking __attribute__((unused))""... $ac_c" 1>&6
+echo "configure:1310: checking __attribute__((unused))" >&5
+ if eval "test \"`echo '$''{'dpkg_cv_c_attribute_unused'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  
+  cat > conftest.$ac_ext <<EOF
+#line 1316 "configure"
+#include "confdefs.h"
+
+int main() {
+extern int testfunction(int x) __attribute__((unused))
+; return 0; }
+EOF
+if { (eval echo configure:1323: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  dpkg_cv_c_attribute_unused=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  dpkg_cv_c_attribute_unused=no
+fi
+rm -f conftest*
+fi
+
+ if test "x$dpkg_cv_c_attribute_unused" = xyes; then
+  true
+  echo "$ac_t""yes" 1>&6
+   cat >> confdefs.h <<\EOF
+#define HAVE_GNUC25_UNUSED 1
+EOF
+
+ else
+  true
+  echo "$ac_t""no" 1>&6
+ fi
+
+  
+ echo $ac_n "checking __attribute__((format...))""... $ac_c" 1>&6
+echo "configure:1350: checking __attribute__((format...))" >&5
+ if eval "test \"`echo '$''{'dpkg_cv_attribute_format'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  
+  cat > conftest.$ac_ext <<EOF
+#line 1356 "configure"
+#include "confdefs.h"
+
+int main() {
+extern int testfunction(char *y, ...) __attribute__((format(printf,1,2)))
+; return 0; }
+EOF
+if { (eval echo configure:1363: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  dpkg_cv_attribute_format=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  dpkg_cv_attribute_format=no
+fi
+rm -f conftest*
+fi
+
+ if test "x$dpkg_cv_attribute_format" = xyes; then
+  true
+  echo "$ac_t""yes" 1>&6
+   cat >> confdefs.h <<\EOF
+#define HAVE_GNUC25_PRINTFFORMAT 1
+EOF
+
+ else
+  true
+  echo "$ac_t""no" 1>&6
+ fi
+
+ else
+  true
+  echo "$ac_t""no" 1>&6
+ fi
+
+
+
+CWARNS=""
+
+
+
+
+ echo $ac_n "checking GCC warning flag(s) -Wall -Wno-implicit""... $ac_c" 1>&6
+echo "configure:1401: checking GCC warning flag(s) -Wall -Wno-implicit" >&5
+ if test "${GCC-no}" = yes
+ then
+  if eval "test \"`echo '$''{'dpkg_cv_c_gcc_warn_all'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  
+   oldcflags="${CFLAGS-}"
+   CFLAGS="${CFLAGS-} ${CWARNS} -Wall -Wno-implicit -Werror"
+   cat > conftest.$ac_ext <<EOF
+#line 1411 "configure"
+#include "confdefs.h"
+
+#include <string.h>
+#include <stdio.h>
+
+int main() {
+
+    strcmp("a","b"); fprintf(stdout,"test ok\n");
+
+; return 0; }
+EOF
+if { (eval echo configure:1423: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  dpkg_cv_c_gcc_warn_all=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  dpkg_cv_c_gcc_warn_all=no
+fi
+rm -f conftest*
+   CFLAGS="${oldcflags}"
+fi
+
+  if test "x$dpkg_cv_c_gcc_warn_all" = xyes; then
+   CWARNS="${CWARNS} -Wall -Wno-implicit"
+   echo "$ac_t""ok" 1>&6
+  else
+   dpkg_cv_c_gcc_warn_all=''
+   echo "$ac_t""no" 1>&6
+  fi
+ else
+  echo "$ac_t""no" 1>&6
+ fi
+
+
+ echo $ac_n "checking GCC warning flag(s) -Wwrite-strings""... $ac_c" 1>&6
+echo "configure:1449: checking GCC warning flag(s) -Wwrite-strings" >&5
+ if test "${GCC-no}" = yes
+ then
+  if eval "test \"`echo '$''{'dpkg_cv_c_gcc_warn_writestrings'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  
+   oldcflags="${CFLAGS-}"
+   CFLAGS="${CFLAGS-} ${CWARNS} -Wwrite-strings -Werror"
+   cat > conftest.$ac_ext <<EOF
+#line 1459 "configure"
+#include "confdefs.h"
+
+#include <string.h>
+#include <stdio.h>
+
+int main() {
+
+    strcmp("a","b"); fprintf(stdout,"test ok\n");
+
+; return 0; }
+EOF
+if { (eval echo configure:1471: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  dpkg_cv_c_gcc_warn_writestrings=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  dpkg_cv_c_gcc_warn_writestrings=no
+fi
+rm -f conftest*
+   CFLAGS="${oldcflags}"
+fi
+
+  if test "x$dpkg_cv_c_gcc_warn_writestrings" = xyes; then
+   CWARNS="${CWARNS} -Wwrite-strings"
+   echo "$ac_t""ok" 1>&6
+  else
+   dpkg_cv_c_gcc_warn_writestrings=''
+   echo "$ac_t""no" 1>&6
+  fi
+ else
+  echo "$ac_t""no" 1>&6
+ fi
+
+
+ echo $ac_n "checking GCC warning flag(s) -Wpointer-arith""... $ac_c" 1>&6
+echo "configure:1497: checking GCC warning flag(s) -Wpointer-arith" >&5
+ if test "${GCC-no}" = yes
+ then
+  if eval "test \"`echo '$''{'dpkg_cv_c_gcc_warn_pointerarith'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  
+   oldcflags="${CFLAGS-}"
+   CFLAGS="${CFLAGS-} ${CWARNS} -Wpointer-arith -Werror"
+   cat > conftest.$ac_ext <<EOF
+#line 1507 "configure"
+#include "confdefs.h"
+
+#include <string.h>
+#include <stdio.h>
+
+int main() {
+
+    strcmp("a","b"); fprintf(stdout,"test ok\n");
+
+; return 0; }
+EOF
+if { (eval echo configure:1519: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  dpkg_cv_c_gcc_warn_pointerarith=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  dpkg_cv_c_gcc_warn_pointerarith=no
+fi
+rm -f conftest*
+   CFLAGS="${oldcflags}"
+fi
+
+  if test "x$dpkg_cv_c_gcc_warn_pointerarith" = xyes; then
+   CWARNS="${CWARNS} -Wpointer-arith"
+   echo "$ac_t""ok" 1>&6
+  else
+   dpkg_cv_c_gcc_warn_pointerarith=''
+   echo "$ac_t""no" 1>&6
+  fi
+ else
+  echo "$ac_t""no" 1>&6
+ fi
+
+
+ echo $ac_n "checking GCC warning flag(s) -Wimplicit -Wnested-externs""... $ac_c" 1>&6
+echo "configure:1545: checking GCC warning flag(s) -Wimplicit -Wnested-externs" >&5
+ if test "${GCC-no}" = yes
+ then
+  if eval "test \"`echo '$''{'dpkg_cv_c_gcc_warn_implicit'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  
+   oldcflags="${CFLAGS-}"
+   CFLAGS="${CFLAGS-} ${CWARNS} -Wimplicit -Wnested-externs -Werror"
+   cat > conftest.$ac_ext <<EOF
+#line 1555 "configure"
+#include "confdefs.h"
+
+#include <string.h>
+#include <stdio.h>
+
+int main() {
+
+    strcmp("a","b"); fprintf(stdout,"test ok\n");
+
+; return 0; }
+EOF
+if { (eval echo configure:1567: \"$ac_compile\") 1>&5; (eval $ac_compile) 2>&5; }; then
+  rm -rf conftest*
+  dpkg_cv_c_gcc_warn_implicit=yes
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  dpkg_cv_c_gcc_warn_implicit=no
+fi
+rm -f conftest*
+   CFLAGS="${oldcflags}"
+fi
+
+  if test "x$dpkg_cv_c_gcc_warn_implicit" = xyes; then
+   CWARNS="${CWARNS} -Wimplicit -Wnested-externs"
+   echo "$ac_t""ok" 1>&6
+  else
+   dpkg_cv_c_gcc_warn_implicit=''
+   echo "$ac_t""no" 1>&6
+  fi
+ else
+  echo "$ac_t""no" 1>&6
+ fi
+
+
+if test "${GCC-no}" = yes; then
+ CWARNS="${CWARNS} -Wmissing-prototypes -Wstrict-prototypes"
+fi
+CFLAGS="`echo $CFLAGS $CWARNS | sed -e 's/-O[0-9]*/$(OPTIMISE)/'`"
+
+
+VERSION="`sed -n '/^userv (.*)/!d; s/^userv (//; s/).*//; p; q' debian/changelog`"
+echo will build version $VERSION
+
+trap '' 1 2 15
+cat > confcache <<\EOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs.  It is not useful on other systems.
+# If it contains results you don't want to keep, you may remove or edit it.
+#
+# By default, configure uses ./config.cache as the cache file,
+# creating it if it does not exist already.  You can give configure
+# the --cache-file=FILE option to use a different cache file; that is
+# what configure does when it calls configure scripts in
+# subdirectories, so they share the cache.
+# Giving --cache-file=/dev/null disables caching, for debugging configure.
+# config.status only pays attention to the cache file if you give it the
+# --recheck option to rerun configure.
+#
+EOF
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, don't put newlines in cache variables' values.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(set) 2>&1 |
+  case `(ac_space=' '; set | grep ac_space) 2>&1` in
+  *ac_space=\ *)
+    # `set' does not quote correctly, so add quotes (double-quote substitution
+    # turns \\\\ into \\, and sed turns \\ into \).
+    sed -n \
+      -e "s/'/'\\\\''/g" \
+      -e "s/^\\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\\)=\\(.*\\)/\\1=\${\\1='\\2'}/p"
+    ;;
+  *)
+    # `set' quotes correctly as required by POSIX, so do not add quotes.
+    sed -n -e 's/^\([a-zA-Z0-9_]*_cv_[a-zA-Z0-9_]*\)=\(.*\)/\1=${\1=\2}/p'
+    ;;
+  esac >> confcache
+if cmp -s $cache_file confcache; then
+  :
+else
+  if test -w $cache_file; then
+    echo "updating cache $cache_file"
+    cat confcache > $cache_file
+  else
+    echo "not updating unwritable cache $cache_file"
+  fi
+fi
+rm -f confcache
+
+trap 'rm -fr conftest* confdefs* core core.* *.core $ac_clean_files; exit 1' 1 2 15
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Any assignment to VPATH causes Sun make to only execute
+# the first set of double-colon rules, so remove it if not needed.
+# If there is a colon in the path, we need to keep it.
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[        ]*VPATH[        ]*=[^:]*$/d'
+fi
+
+trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15
+
+DEFS=-DHAVE_CONFIG_H
+
+# Without the "./", some shells look in PATH for config.status.
+: ${CONFIG_STATUS=./config.status}
+
+echo creating $CONFIG_STATUS
+rm -f $CONFIG_STATUS
+cat > $CONFIG_STATUS <<EOF
+#! /bin/sh
+# Generated automatically by configure.
+# Run this file to recreate the current configuration.
+# This directory was configured as follows,
+# on host `(hostname || uname -n) 2>/dev/null | sed 1q`:
+#
+# $0 $ac_configure_args
+#
+# Compiler output produced by configure, useful for debugging
+# configure, is in ./config.log if it exists.
+
+ac_cs_usage="Usage: $CONFIG_STATUS [--recheck] [--version] [--help]"
+for ac_option
+do
+  case "\$ac_option" in
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    echo "running \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion"
+    exec \${CONFIG_SHELL-/bin/sh} $0 $ac_configure_args --no-create --no-recursion ;;
+  -version | --version | --versio | --versi | --vers | --ver | --ve | --v)
+    echo "$CONFIG_STATUS generated by autoconf version 2.13"
+    exit 0 ;;
+  -help | --help | --hel | --he | --h)
+    echo "\$ac_cs_usage"; exit 0 ;;
+  *) echo "\$ac_cs_usage"; exit 1 ;;
+  esac
+done
+
+ac_given_srcdir=$srcdir
+ac_given_INSTALL="$INSTALL"
+
+trap 'rm -fr `echo "Makefile config.h" | sed "s/:[^ ]*//g"` conftest*; exit 1' 1 2 15
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+# Protect against being on the right side of a sed subst in config.status.
+sed 's/%@/@@/; s/@%/@@/; s/%g\$/@g/; /@g\$/s/[\\\\&%]/\\\\&/g;
+ s/@@/%@/; s/@@/@%/; s/@g\$/%g/' > conftest.subs <<\\CEOF
+$ac_vpsub
+$extrasub
+s%@SHELL@%$SHELL%g
+s%@CFLAGS@%$CFLAGS%g
+s%@CPPFLAGS@%$CPPFLAGS%g
+s%@CXXFLAGS@%$CXXFLAGS%g
+s%@FFLAGS@%$FFLAGS%g
+s%@DEFS@%$DEFS%g
+s%@LDFLAGS@%$LDFLAGS%g
+s%@LIBS@%$LIBS%g
+s%@exec_prefix@%$exec_prefix%g
+s%@prefix@%$prefix%g
+s%@program_transform_name@%$program_transform_name%g
+s%@bindir@%$bindir%g
+s%@sbindir@%$sbindir%g
+s%@libexecdir@%$libexecdir%g
+s%@datadir@%$datadir%g
+s%@sysconfdir@%$sysconfdir%g
+s%@sharedstatedir@%$sharedstatedir%g
+s%@localstatedir@%$localstatedir%g
+s%@libdir@%$libdir%g
+s%@includedir@%$includedir%g
+s%@oldincludedir@%$oldincludedir%g
+s%@infodir@%$infodir%g
+s%@mandir@%$mandir%g
+s%@DEBUGDEFS@%$DEBUGDEFS%g
+s%@DEBUGLIBS@%$DEBUGLIBS%g
+s%@CC@%$CC%g
+s%@CPP@%$CPP%g
+s%@INSTALL_PROGRAM@%$INSTALL_PROGRAM%g
+s%@INSTALL_SCRIPT@%$INSTALL_SCRIPT%g
+s%@INSTALL_DATA@%$INSTALL_DATA%g
+s%@MD5SUM_SIMPLE@%$MD5SUM_SIMPLE%g
+s%@OPTIMISE@%$OPTIMISE%g
+s%@CWARNS@%$CWARNS%g
+s%@VERSION@%$VERSION%g
+
+CEOF
+EOF
+
+cat >> $CONFIG_STATUS <<\EOF
+
+# Split the substitutions into bite-sized pieces for seds with
+# small command number limits, like on Digital OSF/1 and HP-UX.
+ac_max_sed_cmds=90 # Maximum number of lines to put in a sed script.
+ac_file=1 # Number of current file.
+ac_beg=1 # First line for current file.
+ac_end=$ac_max_sed_cmds # Line after last line for current file.
+ac_more_lines=:
+ac_sed_cmds=""
+while $ac_more_lines; do
+  if test $ac_beg -gt 1; then
+    sed "1,${ac_beg}d; ${ac_end}q" conftest.subs > conftest.s$ac_file
+  else
+    sed "${ac_end}q" conftest.subs > conftest.s$ac_file
+  fi
+  if test ! -s conftest.s$ac_file; then
+    ac_more_lines=false
+    rm -f conftest.s$ac_file
+  else
+    if test -z "$ac_sed_cmds"; then
+      ac_sed_cmds="sed -f conftest.s$ac_file"
+    else
+      ac_sed_cmds="$ac_sed_cmds | sed -f conftest.s$ac_file"
+    fi
+    ac_file=`expr $ac_file + 1`
+    ac_beg=$ac_end
+    ac_end=`expr $ac_end + $ac_max_sed_cmds`
+  fi
+done
+if test -z "$ac_sed_cmds"; then
+  ac_sed_cmds=cat
+fi
+EOF
+
+cat >> $CONFIG_STATUS <<EOF
+
+CONFIG_FILES=\${CONFIG_FILES-"Makefile"}
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+for ac_file in .. $CONFIG_FILES; do if test "x$ac_file" != x..; then
+  # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+  case "$ac_file" in
+  *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+       ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+  *) ac_file_in="${ac_file}.in" ;;
+  esac
+
+  # Adjust a relative srcdir, top_srcdir, and INSTALL for subdirectories.
+
+  # Remove last slash and all that follows it.  Not all systems have dirname.
+  ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+  if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+    # The file is in a subdirectory.
+    test ! -d "$ac_dir" && mkdir "$ac_dir"
+    ac_dir_suffix="/`echo $ac_dir|sed 's%^\./%%'`"
+    # A "../" for each directory in $ac_dir_suffix.
+    ac_dots=`echo $ac_dir_suffix|sed 's%/[^/]*%../%g'`
+  else
+    ac_dir_suffix= ac_dots=
+  fi
+
+  case "$ac_given_srcdir" in
+  .)  srcdir=.
+      if test -z "$ac_dots"; then top_srcdir=.
+      else top_srcdir=`echo $ac_dots|sed 's%/$%%'`; fi ;;
+  /*) srcdir="$ac_given_srcdir$ac_dir_suffix"; top_srcdir="$ac_given_srcdir" ;;
+  *) # Relative path.
+    srcdir="$ac_dots$ac_given_srcdir$ac_dir_suffix"
+    top_srcdir="$ac_dots$ac_given_srcdir" ;;
+  esac
+
+  case "$ac_given_INSTALL" in
+  [/$]*) INSTALL="$ac_given_INSTALL" ;;
+  *) INSTALL="$ac_dots$ac_given_INSTALL" ;;
+  esac
+
+  echo creating "$ac_file"
+  rm -f "$ac_file"
+  configure_input="Generated automatically from `echo $ac_file_in|sed 's%.*/%%'` by configure."
+  case "$ac_file" in
+  *Makefile*) ac_comsub="1i\\
+# $configure_input" ;;
+  *) ac_comsub= ;;
+  esac
+
+  ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+  sed -e "$ac_comsub
+s%@configure_input@%$configure_input%g
+s%@srcdir@%$srcdir%g
+s%@top_srcdir@%$top_srcdir%g
+s%@INSTALL@%$INSTALL%g
+" $ac_file_inputs | (eval "$ac_sed_cmds") > $ac_file
+fi; done
+rm -f conftest.s*
+
+# These sed commands are passed to sed as "A NAME B NAME C VALUE D", where
+# NAME is the cpp macro being defined and VALUE is the value it is being given.
+#
+# ac_d sets the value in "#define NAME VALUE" lines.
+ac_dA='s%^\([  ]*\)#\([        ]*define[       ][      ]*\)'
+ac_dB='\([     ][      ]*\)[^  ]*%\1#\2'
+ac_dC='\3'
+ac_dD='%g'
+# ac_u turns "#undef NAME" with trailing blanks into "#define NAME VALUE".
+ac_uA='s%^\([  ]*\)#\([        ]*\)undef\([    ][      ]*\)'
+ac_uB='\([     ]\)%\1#\2define\3'
+ac_uC=' '
+ac_uD='\4%g'
+# ac_e turns "#undef NAME" without trailing blanks into "#define NAME VALUE".
+ac_eA='s%^\([  ]*\)#\([        ]*\)undef\([    ][      ]*\)'
+ac_eB='$%\1#\2define\3'
+ac_eC=' '
+ac_eD='%g'
+
+if test "${CONFIG_HEADERS+set}" != set; then
+EOF
+cat >> $CONFIG_STATUS <<EOF
+  CONFIG_HEADERS="config.h"
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+fi
+for ac_file in .. $CONFIG_HEADERS; do if test "x$ac_file" != x..; then
+  # Support "outfile[:infile[:infile...]]", defaulting infile="outfile.in".
+  case "$ac_file" in
+  *:*) ac_file_in=`echo "$ac_file"|sed 's%[^:]*:%%'`
+       ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+  *) ac_file_in="${ac_file}.in" ;;
+  esac
+
+  echo creating $ac_file
+
+  rm -f conftest.frag conftest.in conftest.out
+  ac_file_inputs=`echo $ac_file_in|sed -e "s%^%$ac_given_srcdir/%" -e "s%:% $ac_given_srcdir/%g"`
+  cat $ac_file_inputs > conftest.in
+
+EOF
+
+# Transform confdefs.h into a sed script conftest.vals that substitutes
+# the proper values into config.h.in to produce config.h.  And first:
+# Protect against being on the right side of a sed subst in config.status.
+# Protect against being in an unquoted here document in config.status.
+rm -f conftest.vals
+cat > conftest.hdr <<\EOF
+s/[\\&%]/\\&/g
+s%[\\$`]%\\&%g
+s%#define \([A-Za-z_][A-Za-z0-9_]*\) *\(.*\)%${ac_dA}\1${ac_dB}\1${ac_dC}\2${ac_dD}%gp
+s%ac_d%ac_u%gp
+s%ac_u%ac_e%gp
+EOF
+sed -n -f conftest.hdr confdefs.h > conftest.vals
+rm -f conftest.hdr
+
+# This sed command replaces #undef with comments.  This is necessary, for
+# example, in the case of _POSIX_SOURCE, which is predefined and required
+# on some systems where configure will not decide to define it.
+cat >> conftest.vals <<\EOF
+s%^[   ]*#[    ]*undef[        ][      ]*[a-zA-Z_][a-zA-Z_0-9]*%/* & */%
+EOF
+
+# Break up conftest.vals because some shells have a limit on
+# the size of here documents, and old seds have small limits too.
+
+rm -f conftest.tail
+while :
+do
+  ac_lines=`grep -c . conftest.vals`
+  # grep -c gives empty output for an empty file on some AIX systems.
+  if test -z "$ac_lines" || test "$ac_lines" -eq 0; then break; fi
+  # Write a limited-size here document to conftest.frag.
+  echo '  cat > conftest.frag <<CEOF' >> $CONFIG_STATUS
+  sed ${ac_max_here_lines}q conftest.vals >> $CONFIG_STATUS
+  echo 'CEOF
+  sed -f conftest.frag conftest.in > conftest.out
+  rm -f conftest.in
+  mv conftest.out conftest.in
+' >> $CONFIG_STATUS
+  sed 1,${ac_max_here_lines}d conftest.vals > conftest.tail
+  rm -f conftest.vals
+  mv conftest.tail conftest.vals
+done
+rm -f conftest.vals
+
+cat >> $CONFIG_STATUS <<\EOF
+  rm -f conftest.frag conftest.h
+  echo "/* $ac_file.  Generated automatically by configure.  */" > conftest.h
+  cat conftest.in >> conftest.h
+  rm -f conftest.in
+  if cmp -s $ac_file conftest.h 2>/dev/null; then
+    echo "$ac_file is unchanged"
+    rm -f conftest.h
+  else
+    # Remove last slash and all that follows it.  Not all systems have dirname.
+      ac_dir=`echo $ac_file|sed 's%/[^/][^/]*$%%'`
+      if test "$ac_dir" != "$ac_file" && test "$ac_dir" != .; then
+      # The file is in a subdirectory.
+      test ! -d "$ac_dir" && mkdir "$ac_dir"
+    fi
+    rm -f $ac_file
+    mv conftest.h $ac_file
+  fi
+fi; done
+
+EOF
+cat >> $CONFIG_STATUS <<EOF
+
+EOF
+cat >> $CONFIG_STATUS <<\EOF
+
+exit 0
+EOF
+chmod +x $CONFIG_STATUS
+rm -fr confdefs* $ac_clean_files
+test "$no_create" = yes || ${CONFIG_SHELL-/bin/sh} $CONFIG_STATUS || exit 1
+
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..f46c56a
--- /dev/null
@@ -0,0 +1,184 @@
+#  userv - configure.in
+#
+#  Copyright (C)1996-1997,1999-2000,2003 Ian Jackson
+#   
+#  This is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License as published by
+#  the Free Software Foundation; either version 2 of the License, or
+#  (at your option) any later version.
+# 
+#  This program is distributed in the hope that it will be useful, but
+#  WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+#  General Public License for more details.
+# 
+#  You should have received a copy of the GNU General Public License
+#  along with userv; if not, write to the Free Software
+#  Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+AC_INIT(language.i4)
+AC_CONFIG_HEADER(config.h)
+
+AC_PREFIX_DEFAULT(/usr/local)
+
+crdir="`pwd`"
+AC_SUBST(DEBUGDEFS)
+AC_SUBST(DEBUGLIBS)
+DEBUGDEFS=''
+DEBUGLIBS=''
+AC_ARG_ENABLE(debug,
+[  --enable-debug          build debugging version],
+[
+ if test "x$enable_debug" = xyes; then
+  DEBUGDEFS="-DDEBUG -DVARDIR='\"$crdir/vd\"' -DSYSTEMCONFIGDIR='\"$crdir/slash-etc\"' -DSERVICEUSERDIR='\"$crdir/tilde\"'"
+  DEBUGLIBS=-lefence
+  echo will build debugging version
+ elif test "x$enable_debug" != xno; then
+  AC_MSG_ERROR(--enable-debug does not allow any arguments except 'yes' and 'no')
+ fi
+])
+
+AC_PROG_CC
+AC_PROG_CPP
+AC_PROG_INSTALL
+AC_CHECK_PROGS(MD5SUM_SIMPLE, md5sum md5 gmd5sum)
+
+CFLAGS="$CFLAGS -D_GNU_SOURCE"
+
+AC_CHECK_LIB(socket,socket)
+AC_CHECK_FUNCS(setenv strsignal vsnprintf)
+
+AC_CACHE_CHECK(for EPROTO,userv_cv_hdr_eproto,
+ AC_EGREP_CPP(yes,
+[
+#include <errno.h>
+#ifdef EPROTO
+ yes
+#endif
+],userv_cv_hdr_eproto=yes,userv_cv_hdr_eproto=no))
+if test $userv_cv_hdr_eproto = yes
+then
+       AC_DEFINE(HAVE_EPROTO)
+fi
+
+AC_CACHE_CHECK(for LOG_AUTHPRIV,userv_cv_hdr_logauthpriv,
+ AC_EGREP_CPP(yes,
+[
+#include <syslog.h>
+#ifdef LOG_AUTHPRIV
+ yes
+#endif
+],userv_cv_hdr_logauthpriv=yes,userv_cv_hdr_logauthpriv=no))
+if test $userv_cv_hdr_logauthpriv = yes
+then
+       AC_DEFINE(HAVE_LOG_AUTHPRIV)
+fi
+
+AC_CACHE_CHECK(for WCOREDUMP,userv_cv_hdr_wcoredump,
+ AC_EGREP_CPP(yes,
+[
+#include <sys/types.h>
+#include <sys/wait.h>
+#ifdef WCOREDUMP
+ yes
+#endif
+],userv_cv_hdr_wcoredump=yes,userv_cv_hdr_wcoredump=no))
+if test $userv_cv_hdr_wcoredump = yes
+then
+       AC_DEFINE(HAVE_WCOREDUMP)
+fi
+
+AC_SUBST(OPTIMISE)
+if test "${GCC-no}" = yes; then
+ OPTIMISE=-O2
+else
+ OPTIMISE=-O
+fi
+
+dnl DPKG_CACHED_TRY_COMPILE(<description>,<cachevar>,<include>,<program>,<ifyes>,<ifno>)
+define(DPKG_CACHED_TRY_COMPILE,[
+ AC_MSG_CHECKING($1)
+ AC_CACHE_VAL($2,[
+  AC_TRY_COMPILE([$3],[$4],[$2=yes],[$2=no])
+ ])
+ if test "x$$2" = xyes; then
+  true
+  $5
+ else
+  true
+  $6
+ fi
+])
+
+DPKG_CACHED_TRY_COMPILE(your C compiler,dpkg_cv_c_works,
+ [#include <string.h>], [strcmp("a","b")],
+ AC_MSG_RESULT(works),
+ AC_MSG_RESULT(broken)
+ AC_MSG_ERROR(C compiler is broken))
+
+DPKG_CACHED_TRY_COMPILE(__attribute__((,,)),dpkg_cv_c_attribute_supported,,
+ [extern int testfunction(int x) __attribute__((,,))],
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_GNUC25_ATTRIB)
+  DPKG_CACHED_TRY_COMPILE(__attribute__((noreturn)),dpkg_cv_c_attribute_noreturn,,
+   [extern int testfunction(int x) __attribute__((noreturn))],
+   AC_MSG_RESULT(yes)
+   AC_DEFINE(HAVE_GNUC25_NORETURN),
+   AC_MSG_RESULT(no))
+  DPKG_CACHED_TRY_COMPILE(__attribute__((unused)),dpkg_cv_c_attribute_unused,,
+   [extern int testfunction(int x) __attribute__((unused))],
+   AC_MSG_RESULT(yes)
+   AC_DEFINE(HAVE_GNUC25_UNUSED),
+   AC_MSG_RESULT(no))
+  DPKG_CACHED_TRY_COMPILE(__attribute__((format...)),dpkg_cv_attribute_format,,
+   [extern int testfunction(char *y, ...) __attribute__((format(printf,1,2)))],
+   AC_MSG_RESULT(yes)
+   AC_DEFINE(HAVE_GNUC25_PRINTFFORMAT),
+   AC_MSG_RESULT(no)),
+ AC_MSG_RESULT(no))
+
+AC_SUBST(CWARNS)
+CWARNS=""
+
+dnl DPKG_C_GCC_TRY_WARNS(<warnings>,<cachevar>)
+define(DPKG_C_GCC_TRY_WARNS,[
+ AC_MSG_CHECKING([GCC warning flag(s) $1])
+ if test "${GCC-no}" = yes
+ then
+  AC_CACHE_VAL($2,[
+   oldcflags="${CFLAGS-}"
+   CFLAGS="${CFLAGS-} ${CWARNS} $1 -Werror"
+   AC_TRY_COMPILE([
+#include <string.h>
+#include <stdio.h>
+],[
+    strcmp("a","b"); fprintf(stdout,"test ok\n");
+], [$2=yes], [$2=no])
+   CFLAGS="${oldcflags}"])
+  if test "x$$2" = xyes; then
+   CWARNS="${CWARNS} $1"
+   AC_MSG_RESULT(ok)
+  else
+   $2=''
+   AC_MSG_RESULT(no)
+  fi
+ else
+  AC_MSG_RESULT(no, not using GCC)
+ fi
+])
+
+DPKG_C_GCC_TRY_WARNS(-Wall -Wno-implicit, dpkg_cv_c_gcc_warn_all)
+DPKG_C_GCC_TRY_WARNS(-Wwrite-strings, dpkg_cv_c_gcc_warn_writestrings)
+DPKG_C_GCC_TRY_WARNS(-Wpointer-arith, dpkg_cv_c_gcc_warn_pointerarith)
+DPKG_C_GCC_TRY_WARNS(-Wimplicit -Wnested-externs, dpkg_cv_c_gcc_warn_implicit)
+
+if test "${GCC-no}" = yes; then
+ CWARNS="${CWARNS} -Wmissing-prototypes -Wstrict-prototypes"
+fi
+[CFLAGS="`echo $CFLAGS $CWARNS | sed -e 's/-O[0-9]*/$(OPTIMISE)/'`"]
+
+AC_SUBST(VERSION)
+VERSION="`sed -n '/^userv (.*)/!d; s/^userv (//; s/).*//; p; q' debian/changelog`"
+echo will build version $VERSION
+
+AC_OUTPUT(Makefile)
diff --git a/daemon.h b/daemon.h
new file mode 100644 (file)
index 0000000..235271e
--- /dev/null
+++ b/daemon.h
@@ -0,0 +1,173 @@
+/*
+ * userv - daemon.h
+ * definitions used in the daemon's source code
+ *
+ * Copyright (C)1996-1997,1999 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef DAEMON_H
+#define DAEMON_H
+
+#include <sys/types.h>
+
+#define RESET_CONFIGURATION " \n\
+  cd ~/                       \n\
+  reject                      \n\
+  no-set-environment          \n\
+  suppress-args               \n\
+  allow-fd 0 read             \n\
+  allow-fd 1-2 write          \n\
+  reject-fd 3-                \n\
+  disconnect-hup              \n\
+"
+
+#ifndef SYSTEMCONFIGDIR
+# ifdef DEBUG
+#  define SYSTEMCONFIGDIR             "slash-etc"
+# else
+#  define SYSTEMCONFIGDIR             "/etc"
+# endif
+#endif
+
+#ifndef DEFAULTPATH_USER
+# define DEFAULTPATH_USER "/usr/local/bin:/bin:/usr/bin"
+#endif
+
+#ifndef DEFAULTPATH_ROOT
+# define DEFAULTPATH_ROOT "/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
+#endif
+  
+#ifndef SETENVIRONMENT
+# define SETENVIRONMENT "environment"
+#endif
+
+#define USERRCFILE                  "rc"
+#define SYSTEMUSERVCONFIGDIR        "userv"
+#define SHELLLIST                   "shells"
+#define SYSTEMRCFILEDEFAULT         "system.default"
+#define SYSTEMRCFILEOVERRIDE        "system.override"
+#define NONEINCLUDELOOKUP           ":none"
+#define DEFAULTINCLUDELOOKUP        ":default"
+#define EMPTYINCLUDELOOKUP          ":empty"
+
+#define USERCONFIGDIRBASE           SYSTEMUSERVCONFIGDIR
+#define USERCONFIGDIR               "." USERCONFIGDIRBASE
+#define USERUSERVCONFIGPATH         "~/" USERCONFIGDIR
+#define USERRCFILEPATH              USERUSERVCONFIGPATH "/" USERRCFILE
+#define SYSTEMUSERVCONFIGPATH       SYSTEMCONFIGDIR "/" SYSTEMUSERVCONFIGDIR
+#define SYSTEMRCFILEDEFAULTPATH     SYSTEMUSERVCONFIGPATH "/" SYSTEMRCFILEDEFAULT
+#define SYSTEMRCFILEOVERRIDEPATH    SYSTEMUSERVCONFIGPATH "/" SYSTEMRCFILEOVERRIDE
+#define SHELLLISTPATH               SYSTEMCONFIGDIR "/" SHELLLIST
+#define SETENVIRONMENTPATH          SYSTEMCONFIGDIR "/" SETENVIRONMENT
+
+#define USERVD_LOGIDENT "uservd"
+#define USERVDCHECK_LOGIDENT "uservd/check"
+#define USERVD_LOGFACILITY LOG_DAEMON
+#define DEFUSERLOGFACILITY LOG_USER
+#define DEFUSERLOGLEVEL LOG_ERR
+
+#define TOPLEVEL_CONFIGURATION "                   \n\
+  reset                                            \n\
+  user-rcfile " USERRCFILEPATH "                   \n\
+  errors-to-stderr                                 \n\
+  _include-sysconfig " SYSTEMRCFILEDEFAULTPATH "   \n\
+  if grep service-user-shell " SHELLLISTPATH "     \n\
+    errors-push                                    \n\
+      catch-quit                                   \n\
+        _include-user-rcfile                       \n\
+      hctac                                        \n\
+    srorre                                         \n\
+  fi                                               \n\
+  _include-sysconfig " SYSTEMRCFILEOVERRIDEPATH "  \n\
+  quit                                             \n\
+"
+
+#define TOPLEVEL_OVERRIDDEN_CONFIGURATION "        \n\
+  reset                                            \n\
+  errors-to-stderr                                 \n\
+  _include-client-config                           \n\
+  quit                                             \n\
+"
+
+#define USERVD_MYSELF_CHECK 3600
+#define USERVD_MYSELF_TIMEOUT 60
+#define USERVD_CHECKFORK_RETRY 60
+#define MAX_INCLUDE_NEST 40
+#define MAX_ERRMSG_LEN (MAX_ERRMSG_STRING-1024)
+#define ERRMSG_RESERVE_ERRNO 128
+
+int parse_string(const char *string, const char *descrip, int isinternal);
+int parseerrprint(const char *fmt, ...) PRINTFFORMAT(1,2);
+void ensurelogopen(int wantfacility);
+void ensurefdarray(int fd);
+const char *printtoken(int token);
+void senderrmsgstderr(const char *errmsg);
+void disconnect(int exitstatus) NONRETURNING;
+
+void always_dumpparameter(const char *parm, char **values);
+void always_dumpexecsettings(void);
+
+void debug_dumprequest(pid_t mypid);
+void debug_dumpexecsettings(void);
+void debug_dumpparameter(const char *parm, char **values);
+pid_t nondebug_fork(void);
+const char *nondebug_serviceuserdir(const char *ifnondebug);
+
+typedef void builtinserviceexec_fnt(const char *const *args);
+builtinserviceexec_fnt NONRETURNING bisexec_environment, bisexec_parameter;
+builtinserviceexec_fnt NONRETURNING bisexec_version, bisexec_help;
+builtinserviceexec_fnt NONRETURNING bisexec_toplevel, bisexec_override, bisexec_reset;
+builtinserviceexec_fnt NONRETURNING bisexec_execute, bisexec_shutdown;
+extern const char *const builtinservicehelpstrings[];
+
+void execservice(const int synchsocket[], int clientfd) NONRETURNING;
+void servicerequest(int sfd) NONRETURNING;
+int synchread(int fd, int ch);
+const char *defaultpath(void);
+
+struct fdstate {
+  int iswrite; /* 0 or 1; -1 if not open */
+  int realfd, holdfd; /* -1 if not open */
+  int wantstate;
+  /* tokv_word_requirefd, tokv_word_allowfd, tokv_nullfd, tokv_word_rejectfd
+   * (all of which have tokt_wantfdstate set) */
+  int wantrw; /* tokv_word_read, tokv_word_write, 0 for either/both */
+};
+
+struct keyvaluepair { char *key, *value; };
+
+extern pid_t overlordpid;
+extern struct request_msg request_mbuf;
+extern struct keyvaluepair *defvararray;
+extern struct fdstate *fdarray; /* indexed by nominal fd */
+extern int fdarraysize, fdarrayused;
+extern int restfdwantstate, restfdwantrw;
+extern int service_ngids;
+extern char **argarray;
+extern char *serviceuser, *service, *loginname, *cwd;
+extern char *overridedata, *userrcfile;
+extern char *serviceuser_dir, *serviceuser_shell, *callinguser_shell;
+extern gid_t *calling_gids, *service_gids;
+extern uid_t serviceuser_uid;
+extern const char **calling_groups, **service_groups;
+extern char *execpath, **execargs;
+extern int execute; /* One of the execution modes tokt_execmode */
+extern int setenvironment, suppressargs, disconnecthup;
+extern builtinserviceexec_fnt *execbuiltin;
+extern int syslogopenfacility;
+
+#endif
diff --git a/debian/changelog b/debian/changelog
new file mode 100644 (file)
index 0000000..8b6caf7
--- /dev/null
@@ -0,0 +1,406 @@
+userv (1.1.1) unstable; urgency=low
+
+  * Include INIT INFO stanza in init script.
+  * Draft support for `status' in init script, currently commented
+    out pending inclusion into policy of firm specification.
+  * Remove spec.ps on make clean.
+  * Remove spec.ps and dh log on debian/rules clean.  (Use dh_clean.)
+    (To repro bug: dpkg-buildpackage, debian/rules clean, git-ls-files -o)
+  * Fix up some copyright messages.
+  
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Fri, 08 Jun 2012 20:16:52 +0100
+
+userv (1.1.0) unstable; urgency=medium
+
+  Bugfix:
+  * Do not reject comments and blank lines inside not-being-executed
+    conditional clauses.  (Closes: #613862.)
+
+  Important Debian packaging fix:
+  * Remove /var/run/userv from the .deb.
+    (Along with the change in 1.0.6 this Closes: #630528.)
+
+  General minor improvements (relevant outside Debian):
+  * Introduce a use of socklen_t to avoid a compiler warning.
+  * Remove spec.html and spec.ps from revision control.
+  * Revision control switched from cvs to git.
+  * Update my email address.
+
+  Debian packaging minor improvements and bugfixes:
+  * The new version will ensure a rebuild and therefore completely deal
+    with the debiandoc-sgml missing documentation bug.  (Closes: #413873.)
+  * Use lintian for dh_fixperms; this ensures that the permissions of
+    /etc/userv in the .deb are 755, not 2755.
+  * Use `command -v' instead of `type' in maintainer scripts.
+  * Do not ignore errors from commands in rules clean target.
+  * Fix FSF address in copyright file.
+  * Remove obsolete local variable section from debian/changelog.
+  * Update Standards-Version.  No changes required.
+
+ -- Ian Jackson <ijackson@chiark.greenend.org.uk>  Sat, 02 Jun 2012 16:41:09 +0100
+
+userv (1.0.6ubuntu1) lucid; urgency=low
+
+  * Change tetex-bin with texlive and tetex-extra with texlive-latex-extra
+    in Build-Depends for tetex transition. Cherry-picked from Debian,
+    Thanks to Jari Aalto.
+
+ -- James Westby <james.westby@ubuntu.com>  Thu, 15 Apr 2010 12:03:52 +0100
+
+userv (1.0.6) unstable; urgency=low
+
+  Packaging fix:
+  * Create /var/run/userv in the init script as well as shipping
+    it in the .deb package.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Wed, 11 Jul 2007 17:54:36 +0100
+
+userv (1.0.5) unstable; urgency=low
+
+  Bugfixes (thanks to report from Nelson Beebe):
+  * Do not call `assert' on expressions whose side-effects we need (!)
+    Thanks to report from Nelson Beebe.
+  * Correct an erroneous assert() argument so that it would actually
+    detect failure of the assertion.
+  * alarm(2) returns unsigned and can never fail.
+
+  Packaging changes:
+  * Reran flex (flex Debian 2.5.31-31).
+  * Use install -g 0 instead of -g root.  This is more portable, I hope.
+  * Detect missing vsnprintf and mention URL from Nelson Beebe in err msg.
+  * Remove obsolete `buildship' script from top level directory.  We use
+    cvs-buildpackage nowadays.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat,  8 Apr 2006 13:17:14 +0100
+
+userv (1.0.4) unstable; urgency=low
+
+  Bugfixes:
+  * Close client socket fd in spawned cats (avoids some service-side hangs
+    when client terminates).  Thanks to report from Simon Tatham.
+
+  Minor portability fixes:
+  * Missing #include <string.h> and <stdlib.h>  } Thanks to report
+  * getgroups returns int, not gid_t (!).       }  from Peter Benie.
+  * Dummy `check' target in Makefile.in.              } Thanks to
+  * Bogus strsignal emulation for broken platforms.   } report from
+  * Bogus WCOREDUMP emulation for broken platforms.   } Nelson Beebe
+  
+  Packaging improvements (including Debian packaging fixes):
+  * Compress uservd(8) manpage.  Closes: #244735.
+  * spec.tex and spec.html are autogenerated: clean and .cvsignore them.
+  * SHELL=/bin/bash in debian/rules; use of install(8).  Closes: #263979.
+  * Reran autoconf/autoheader (autoconf Debian 2.13-54).
+  * Updated copyright notices.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Fri,  7 Apr 2006 20:04:29 +0100
+
+userv (1.0.3-2) unstable; urgency=low
+
+  Debian packaging improvements (only):
+  * type -p invoke-rc.d changed to type, in postinst and prerm.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat,  1 Nov 2003 16:59:38 +0000
+
+userv (1.0.3) unstable; urgency=medium
+
+  Bugfixes:
+  * Make require-fd work with reading fds !
+    (Thanks to Ben Harris for the bug report).
+  * Close unwanted pipes in client-side cat subprocesses, to avoid
+    wedging at termination.  (Thanks to patchlet from Peter Benie.)
+  * gid_t may be >int, so cast to long when putting in USERV_GIDS
+    (Might conceivably make USERV_GIDS be wrong on some platforms.)
+  * Do not pass char to ctype macros; they can't cope with -ve !
+  * Fix fd modifier, signal, and exit status parsing to be rigourous in
+    their use of strtoul.  (Thanks to report from Peter Benie.)
+
+  Portability fixes:
+  * #include <fcntl.h>, not <sys/fcntl.h> (fixes some implicit decls).
+  * Look for gmd5sum.  (Thanks to Anton Altaparmakov for the report.)
+  * install-sh updated to that from autoconf 2.53.
+  * Use fcntl F_{GET,SET}FD with respect for as-yet-uninvented fd flags.
+    (small patch from Ben Harris.)
+
+  Documentation and help improvements:
+  * userv(1) manpage: fixed broken definitions of fd excl and trunc.
+    (Debian bug report: Closes: #79579.)
+  * Specification's usage notes section improved.
+  * --help and --version behaviour made to conform to GNU standards.
+  * We do ship m4 and flex output now, so say so.
+  * Some groff warnings in userv(1), and source version fixed.
+  * New userv(8) manpage.  (Debian: Closes: #33777.)
+  * Update copyright dates everywhere.
+
+  Debian packaging improvements:
+  * Priority changed to optional as per override file.
+  * Build-Depends: debiandoc-sgml, tetex-bin, tetex-extra.  Closes #190615.
+  * init.d reload is noop, restart now called restart.  Closes #70783.
+  * /etc/init.d/userv nicer output: colons, `.' printed after done.
+  * Maintainer scripts use invoke-rc.d if it's available.
+  * Maintainer scripts discard stdout from update-rc.d.
+  * No more messing with /usr/doc, use only /usr/share/doc.  Closes #91578.
+  * Support unstripped binaries in the .deb, with DEB_BUILD_OPTIONS.
+  * Fixed typo in debian/copyright.
+  * /etc/init.d/userv restart doesn't mind if not already running.
+  * debian/rules clean removes whole spec.html subdirectory.
+  * Ship spec.ps (Closes: #210859)
+  * Lintian override for suid /usr/bin/userv (Closes: #211055)
+  * Standards-Version 3.6.1.
+  * Corrected location of common licenses.
+  * Added -isp to dpkg-gencontrol.
+  (Thanks to Martin Pitt and Bas Zoetekouw's NMUs
+   for many inspirations and one-liners.)
+  
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat,  1 Nov 2003 01:11:59 +0000
+
+userv (1.0.1) stable frozen unstable; urgency=high
+
+  IMPORTANT SECURITY FIX:
+  * fd swapping algorithm would sometimes corrupt security-critical data
+    used to generate the service program's USERV_ environment variables.
+    For details see the 1.0.1 announcement in the userv-announce archives.
+
+  Portability improvement:
+  * Look for `md5' as well as `md5sum' - installs easier on BSDs.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Thu, 27 Jul 2000 01:06:30 +0100
+
+userv (1.0.0) unstable; urgency=low
+
+  * Manpage userv(1) from Ben Harris.  (Debian bug #33777.)
+  * Released out of beta (version number change).
+  * Added a couple of things to .cvsignore.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Mon,  6 Mar 2000 18:13:49 +0000
+
+userv (0.95.0) unstable; urgency=low
+
+  * Count \-continued lines properly in error message line numbers.
+  * Fix lexing bugs with "-quoted strings and \-continuation.
+  * Fix interpretation of \n etc. in "-quoted strings.
+  * Fix bug which ignored erroneous read/write after ignore-fd/reject-fd.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Tue,  9 Nov 1999 23:26:54 +0000
+
+userv (0.65.2) unstable; urgency=high
+
+  * In client, copy results from getpw* when necessary.  This fixes what
+    could be a security problem on some platforms.
+  * Avoid accessing backup, auto-save files, etc, with include-lookup.
+    Everything except a-z 0-9 - _ must now be prefixed by a colon.
+  * Allow \ to continue lines (and do sensible things with whitespace in
+    `message' and `error' directives).
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 10 Oct 1999 12:48:47 +0100
+
+userv (0.64.1) unstable; urgency=low
+
+  * New "shutdown" builtin service for terminating uservd.
+  * Spec. document shows subsections in TOC.
+
+  * setenv emulation using putenv works properly (previously you would get
+    wrong environment variable settings).  (Thanks to Ben Harris.)
+  * Makefile bug fixed (tokens.h would sometimes not be rebuilt).
+
+  * Regenerated formatted documentation (spec.ps, spec.html).
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 20 Jun 1999 19:13:42 +0100
+
+userv (0.62) unstable; urgency=low
+
+  * New builtin service `help' lists builtin services.
+  
+  INSTALL improvements:
+  * Document locations of required programs.
+  * Sort-of document debugging version.
+  
+  Portability fixes for:
+  * md5sum with extra `-'.
+  * -lsocket required for socket().
+  * missing `LOG_AUTHPRIV'.
+  * `logname' name clash.    
+  * missing setenv() (synthesize using putenv).
+  * various required #include's were omitted.
+  * install rule in Makefile `if ! test ...' changed to `if test ! ...'
+
+  Build arrangements changed (new GNU coding standards targets etc):
+  * m4 and flex output now shipped.
+  * `dist' target in Makefile.
+  * Manuals in CVS and shipped pre-formatted.
+
+  * Reran autoconf/autoheader, latest version.
+  * Updated email address to ian@davenant.greenend.org.uk throughout.
+
+ -- Ian Jackson <ian@chiark.greenend.org.uk>  Sun, 18 Apr 1999 20:08:12 +0100
+
+userv (0.60.3) frozen unstable; urgency=medium
+
+  * Fixed misdequoting of \<newline> in "-quoted strings.
+  * Removed -Werror by default.  (Bug#32758, Bug#32747)
+
+ -- Ian Jackson <ian@chiark.greenend.org.uk>  Wed,  3 Feb 1999 22:24:33 +0000
+
+userv (0.60.2) frozen unstable; urgency=high
+
+  * Fixed failure to save pathnames in a couple of places in parser.c.
+    Without this, include-directory would often try to open a garbage
+    filename.  This could be a security problem in certain cases where
+    user-owned config files were included from sysadmin-defined files,
+    and the sysadmin wants to control how a user provides services.
+
+  * Fix "-quoted strings, which previously never worked at all.
+
+  * Fixed spurious failure with `Interrupted system call' on systems
+    where fread can fail due to read giving EINTR (blech!)
+
+  * Fixed race when fd closed at startup, which could cause
+    `system call failure: kill cat for <fd>: No such process'.
+
+  * Fixed spurious assertion failure if user's home directory not
+    accessible.
+  * Fixed a couple of memory and fd leaks in error exits from
+    include-directory and include-lookup in parser.c.
+
+  * Debian and `upstream' version integrated; Changelogs merged.
+
+  * Provided `system.default' file checks /etc/userv/services.d and
+    /etc/userv/default.d; system.override runs /etc/userv/override.d.
+
+  * Add <string.h> to servexec.c.
+  * Add -D_GNU_SOURCE to CFLAGS in configure.in.
+  * Braces added in client.c to prevent GCC `ambiguous else' warning.
+
+  * Reran autoconf.
+  * autoconf-generated files included in CVS.
+
+  * Debian package description mentions use by system admin.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat, 30 Jan 1999 23:38:17 +0000
+
+
+*** Main changelog file included here - see far down this file for the
+*** pre-0.60 Debian-specific changes.
+
+
+userv (0.58); urgency=high
+
+  * Fixed failure to set gid when invoking service !!
+  * Fixed failure to set fd value in et_closereadfd messages from client.
+
+  * Save filename string in parse_file for error reporting (in case it is
+    overwritten by parsing code).
+  * Fixed obscure race in process.c:getevent (et_closereadfd and hold fds).
+  * `builtin version' service had error message and errno string reserve
+    values exchanged.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Thu, 29 Jan 1998 00:00:22 +0000
+
+userv (0.57); urgency=high
+
+  * Services provided by root work !
+  * uservd can now go into background itself (-daemon option).
+
+  * spec now has default syslog facility for rcfile messages as `user'.
+  * Better prioritisation of syslog messages.
+  * Startup error messages now go to stderr instead.
+  * SIGTERM and SIGINT now produce a syslog message.
+
+  * Version number has VEREXT component, settable via make args &c.
+  * New sections in INSTALL about exit statuses and -daemon.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Tue, 14 Oct 1997 02:04:18 +0100
+
+userv (0.56); urgency=medium
+
+  * Server now checks itself every hour to see if its socket has been
+    stolen, and exits if it has.
+  * Client only retries connect(2) on EINTR (and does so silently).
+  * All of even master server's syslog messages have pid.
+  * Don't delete spec.sgml.in in clean targets.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat, 11 Oct 1997 14:38:25 +0100
+
+userv (0.55.2); urgency=low
+
+  * Added info about WWW page, mailing lists and bug reporting to README.
+
+  * clean targets except realclean don't remove spec.html, spec.ps,
+    lexer.[lc], tokens.h, overview.ps.  New totalclean target for
+    removing configure and config.h.in.
+
+  * Added <sys/types.h> and <errno.h> to lexer.l.m4.
+  * <sys/wait.h> instead of <wait.h>; %d instead of %ld for WEXITSTATUS
+    in overlord.c; PIPEMAXLEN etc. #defines include cast to int to avoid
+    problems with sizeof and size_t; EPROTO missing workaround.
+  * Note about SIG_IGN and `function declaration isn't a prototype' added
+    to INSTALL file in new PROBLEMS section.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 5 Oct 1997 17:55:32 +0100
+
+userv (0.55.1); urgency=low
+
+  * README file now shipped.
+  * Added this Changelog, and configure gets version number from it.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 21 Sep 1997 23:58:32 +0100
+
+userv (0.55)
+
+  * Initial release
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sun, 21 Sep 1997 23:52:50 +0100
+
+
+*** Old main changelog file ends here.
+*** Now come pre-0.59 Debian-specific changes.
+
+
+userv (0.58-1.2) unstable; urgency=low
+
+  * Non maintainer upload.
+    Fix for this error on the Arm.
+    cc1: warnings being treated as errors
+    client.c: In function callvalueoption':
+    client.c:747: warning: suggest explicit braces to avoid ambiguous else'
+    Braces inserted, to join the if statements together
+
+ -- Turbo Fredriksson <turbo@debian.org>  Thu, 20 Aug 1998 06:17:43 -0400
+
+userv (0.58-1.1) frozen unstable; urgency=low
+
+  * Non maintainer upload
+  * Finally compiled against libc6
+  * To Brian White: Please either confirm this upload to go into frozen or
+    remove userv from frozen.
+  * Added external declaration for strsignal() to overlord.c and client.c
+
+ -- Martin Schulze <joey@finlandia.infodrom.north.de>  Sat, 18 Jul 1998 10:48:11 +0200
+
+userv (0.58-1) unstable; urgency=low
+
+  * Upgraded to new upstream version; major security fix, error handling
+    fixes.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Thu, 29 Jan 1998 00:03:52 +0000
+
+userv (0.57-1) unstable; urgency=high
+
+  * Upgraded to new upstream version; fixed bug with root-provided
+    services.
+  * Uses new -daemon option in init.d script.
+  * Sets VEREXT during build to set version number.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Tue, 14 Oct 1997 02:22:22 +0100
+
+userv (0.56-1) experimental; urgency=low
+
+  * Upgraded to new upstream version.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Sat, 11 Oct 1997 14:13:22 +0100
+
+userv (0.55.1-1) experimental; urgency=low
+
+  * Initial Debian release.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk>  Mon, 22 Sep 1997 01:08:23 +0100
diff --git a/debian/compat b/debian/compat
new file mode 100644 (file)
index 0000000..7ed6ff8
--- /dev/null
@@ -0,0 +1 @@
+5
diff --git a/debian/conffiles b/debian/conffiles
new file mode 100644 (file)
index 0000000..2463c0f
--- /dev/null
@@ -0,0 +1,3 @@
+/etc/init.d/userv
+/etc/userv/system.default
+/etc/userv/system.override
diff --git a/debian/control b/debian/control
new file mode 100644 (file)
index 0000000..6550ea2
--- /dev/null
@@ -0,0 +1,18 @@
+Source: userv
+Section: admin
+Priority: optional
+Maintainer: Ian Jackson <ijackson@chiark.greenend.org.uk>
+Build-Depends: debiandoc-sgml, texlive, texlive-latex-extra, debhelper (>= 5)
+Standards-Version: 3.9.1
+
+Package: userv
+Architecture: any
+Depends: ${shlibs:Depends}, ${misc:Depends}
+Description: `user services' - program call across trust boundaries
+ userv allows one program to invoke another when only limited trust
+ exists between them.  It is a tool which can be used to avoid having
+ to give other system services root privilege, and which allows users
+ to more securely have programs provide services to others.
+ .
+ userv can be useful as `glue' for system administrators; there are
+ not many full-blown userv-using applications yet.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644 (file)
index 0000000..018cef8
--- /dev/null
@@ -0,0 +1,30 @@
+This is Debian GNU/Linux's prepackaged version of Ian Jackson's
+`userv' system utility.
+
+This package was put together from the upstream sources by the
+upstream author.  If you have queries please contact Ian Jackson at
+<ijackson@chiark.greenend.org.uk>; use the Debian bug system for bugs
+if possible.
+
+The changes were to add support for the Debian package maintenance
+scheme, by adding various debian/* files and arranging for the uservd
+daemon to be started automatically.
+
+
+Copyright (C)1996-2000,2006,2012 Ian Jackson <ian@davenant.greenend.org.uk>.
+Copyright (C)2000                Ben Harris <bjh21@cam.ac.uk>.
+
+userv is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your
+option) any later version.
+
+This program is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License with
+your Debian GNU/Linux system, in /usr/share/common-licenses/GPL, or with
+the Debian GNU/Linux userv source package as the file COPYING; if not,
+see <http://www.gnu.org/licenses/> or email me.
diff --git a/debian/initd b/debian/initd
new file mode 100644 (file)
index 0000000..16e95af
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides:            userv
+# Required-Start:      $remote_fs $syslog
+# Required-Stop:       $remote_fs $syslog
+# Default-Start:       2 3 4 5
+# Default-Stop:                0 1 6
+# Short-Description:   userv services daemon uservd
+# Description:          User services (security boundary) daemon
+### END INIT INFO
+
+test -f /usr/sbin/uservd || exit 0
+
+ensure_var_dir () {
+       var_dir=/var/run/userv
+       test -d "$var_dir" || mkdir -m700 "$var_dir"
+}
+
+case "$1" in
+start)
+       echo -n "Starting user services daemon: uservd"
+       ensure_var_dir
+       start-stop-daemon --start --quiet --exec /usr/sbin/uservd -- -daemon
+       echo "."
+       ;;
+
+stop)
+       echo -n "Stopping user services daemon: uservd"
+       start-stop-daemon --stop --quiet --user root --exec /usr/sbin/uservd
+       echo "."
+       ;;
+
+restart)
+       echo -n "Restarting user services daemon: uservd"
+       ensure_var_dir
+       start-stop-daemon --stop --oknodo --quiet --user root \
+               --exec /usr/sbin/uservd
+       sleep 1
+       start-stop-daemon --start --quiet --exec /usr/sbin/uservd -- -daemon
+       echo "."
+       ;;
+
+reload | force-reload)
+       ;;
+
+# draft implementation of status support
+# commented out pending inclusion into policy of firm specification
+#status)
+#      userv -B version >/dev/null 2>/dev/null
+#      case $? in
+#      0)      echo 'uservd running ok'; exit 0;;
+#      *)      echo 'uservd not running'; exit 3;;
+#      esac
+#      ;;
+
+*)
+       echo "Usage: /etc/init.d/userv {start|stop|restart|reload|force-reload}"
+       exit 1
+esac
+
+exit 0
diff --git a/debian/lintian b/debian/lintian
new file mode 100644 (file)
index 0000000..b497efa
--- /dev/null
@@ -0,0 +1 @@
+userv: setuid-binary usr/bin/userv 4755 root/root
diff --git a/debian/postinst b/debian/postinst
new file mode 100644 (file)
index 0000000..a6879b9
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -e
+update-rc.d userv defaults 19 50 > /dev/null
+
+if command -v invoke-rc.d >/dev/null 2>&1; then
+    invoke-rc.d userv start
+else
+    /etc/init.d/userv start
+fi
diff --git a/debian/postrm b/debian/postrm
new file mode 100644 (file)
index 0000000..9072e11
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+set -e
+if test "$1" = purge
+then
+       update-rc.d userv remove >/dev/null
+fi
diff --git a/debian/prerm b/debian/prerm
new file mode 100644 (file)
index 0000000..0be459b
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+set -e
+if command -v invoke-rc.d >/dev/null 2>&1; then
+       invoke-rc.d userv stop
+else
+       /etc/init.d/userv stop
+fi
+
+if [ \( "$1" = "upgrade" -o "$1" = "remove" \) -a -L /usr/doc/userv ]; then
+       rm -f /usr/doc/userv
+fi
diff --git a/debian/rules b/debian/rules
new file mode 100755 (executable)
index 0000000..97bced1
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/make -f
+
+SHELL=/bin/bash
+package=userv
+
+t=debian/tmp
+
+ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS)))
+       INSTOPTS= LDFLAGS=-s INSTALL_PROGRAM='install -c -s'
+else
+       INSTOPTS= INSTALL_PROGRAM='install -c'
+endif
+
+build: build-arch build-indep
+
+build-arch:
+       $(checkdir)
+       ./configure --prefix=/usr
+       $(MAKE) all docs
+       touch build-arch
+
+clean:
+       $(checkdir)
+       rm -f build build-arch
+       $(MAKE) -i distclean || $(MAKE) -f Makefile.in distclean
+       rm -rf *~ $t
+       rm -rf spec.html spec.ps debian/substvars*
+       dh_clean
+
+build-indep:
+binary-indep:
+
+binary-arch:   checkroot build
+       $(checkdir)
+       -rm -rf $t
+       install -d $t/{DEBIAN,etc/init.d} $t/usr/{sbin,bin}
+       install -d $t/etc/userv/{override,default}.d
+       install -d $t/usr/share/doc/$(package)/examples/
+       install -d $t/usr/share/lintian/overrides/
+       install -m 755 debian/{postinst,prerm,postrm} $t/DEBIAN/.
+       install -m 644 debian/conffiles $t/DEBIAN/.
+       install -m 755 debian/initd $t/etc/init.d/userv
+       $(MAKE) $(INSTOPTS) \
+               prefix=$t/usr etcdir=$t/etc \
+               docdir=$t/usr/share/doc/userv \
+               mandir=$t/usr/share/man \
+               install install-doc
+       cp debian/copyright $t/usr/share/doc/$(package)/.
+       cp debian/changelog $t/usr/share/doc/$(package)/changelog
+       cp README $t/usr/share/doc/$(package)/README.upstream
+       cp system.default system.override $t/usr/share/doc/$(package)/examples
+       ln -s changelog.gz $t/usr/share/doc/$(package)/changelog.Debian.gz
+       gzip -9v $t/usr/share/doc/$(package)/changelog \
+               $t/usr/share/man/man1/*.1 $t/usr/share/man/man8/*.8 \
+               $t/usr/share/doc/userv/*.ps
+       cp debian/lintian $t/usr/share/lintian/overrides/userv
+       dpkg-shlibdeps daemon client
+       dpkg-gencontrol -isp
+       dh_fixperms -Pdebian/tmp
+       chown root.root debian/tmp/usr/bin/userv
+       chmod 4755 debian/tmp/usr/bin/userv
+       dpkg --build $t ..
+
+define checkdir
+       test -f overlord.c -a -f lexer.l.m4 -a -f debian/rules
+endef
+
+# Below here is fairly generic really
+
+binary:                binary-indep binary-arch
+
+source diff:
+       @echo >&2 'source and diff are obsolete - use dpkg-source -b'; false
+
+checkroot:
+       $(checkdir)
+       test root = "`whoami`"
+
+.PHONY: binary binary-arch binary-indep clean checkroot
diff --git a/debug.c b/debug.c
new file mode 100644 (file)
index 0000000..5ec6f94
--- /dev/null
+++ b/debug.c
@@ -0,0 +1,245 @@
+/*
+ * userv - ddebug.c
+ * routines which are different for -DDEBUG
+ *
+ * Copyright (C)1996-1997,1999 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdarg.h>
+#include <syslog.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <grp.h>
+#include <sys/types.h>
+
+#include "config.h"
+#include "common.h"
+#include "daemon.h"
+#include "lib.h"
+#include "tokens.h"
+
+static void fdwantdumprwhead(int *donehead, const char *whichstr, const char *rwstr) {
+  if (*donehead) return;
+  printf("fds %s%s%s:",whichstr,rwstr?" ":"",rwstr?rwstr:"");
+  *donehead= 1;
+}
+
+static void fdwantdumprw(const char *whichstr, int whichval,
+                        int rw, const char *rwstr) {
+  int donehead= 0;
+  int fd;
+  
+  for (fd=0; fd<fdarrayused; fd++) {
+    if (!(fdarray[fd].wantstate == whichval && fdarray[fd].wantrw == rw)) continue;
+    fdwantdumprwhead(&donehead,whichstr,rwstr);
+    printf(" %d",fd);
+  }
+  if (restfdwantstate == whichval && restfdwantrw == rw) {
+    fdwantdumprwhead(&donehead,whichstr,rwstr);
+    printf(" %d-",fdarrayused);
+  }
+  if (donehead) printf("\n");
+}
+
+static void fdwantdump(const char *whichstr, int whichval, const char *rwunspecstr) {
+  if (rwunspecstr) {
+    fdwantdumprw(whichstr,whichval,tokv_word_read,"read");
+    fdwantdumprw(whichstr,whichval,tokv_word_write,"write");
+    fdwantdumprw(whichstr,whichval,0,rwunspecstr);
+  } else {
+    fdwantdumprw(whichstr,whichval,0,0);
+  }
+}
+
+static void truefalsedump(const char *whichstr, int val) {
+  printf("%s: %s\n",whichstr,val?"yes":"no");
+}
+
+void always_dumpparameter(const char *parm, char **values) {
+  printf("config parameter `%s':",parm);
+  while (*values) printf(" `%s'",*values++);
+  printf("\n");
+}
+
+void always_dumpexecsettings(void) {
+  char **cpp;
+  
+  if (userrcfile) printf("user-rcfile: `%s'\n",userrcfile);
+  else printf("user-rcfile: <none>\n");
+  fdwantdump("required",tokv_word_requirefd,"ERROR");
+  fdwantdump("allowed",tokv_word_allowfd,"either");
+  fdwantdump("ignored",tokv_word_ignorefd,0);
+  fdwantdump("null",tokv_word_nullfd,"both");
+  fdwantdump("rejected",tokv_word_rejectfd,0);
+  fputs("execute: ",stdout);
+  switch (execute) {
+  case tokv_word_reject: printf("reject"); break;
+  case tokv_word_execute: printf("`%s'",execpath); break;
+  case tokv_word_executefromdirectory: printf("from directory, `%s'",execpath); break;
+  case tokv_word_executefrompath: printf("from path"); break;
+  case tokv_word_executebuiltin: printf("builtin %s",execpath); break;
+  default: abort();
+  }
+  if (execargs) {
+    fputs("\n" "no exec arguments\n",stdout);
+  } else {
+    fputs("\n" "exec arguments:",stdout);
+    for (cpp= execargs; cpp; cpp++) printf(" `%s'",*cpp);
+    putchar('\n');
+  }
+  truefalsedump("set-environment",setenvironment);
+  truefalsedump("suppress-args",suppressargs);
+  truefalsedump("disconnect-hup",disconnecthup);
+  truefalsedump("set-environment",setenvironment);
+}
+
+#ifdef DEBUG
+
+static const char *sl_ident= "UNSET";
+static int sl_option=0, sl_facility=0;
+
+void openlog(const char *ident, int option, int facility) {
+  sl_ident= ident;
+  sl_option= option;
+  sl_facility= facility;
+}
+
+void syslog(int priority, const char *fmt, ...) {
+  va_list al;
+  fprintf(stderr,"syslog: %s<%d.%d>(%d): ",sl_ident,sl_facility,priority,sl_option);
+  va_start(al,fmt);
+  vfprintf(stderr,fmt,al);
+  va_end(al);
+  fputc('\n',stderr);
+}
+
+void closelog(void) {
+  sl_ident= "CLOSED";
+  sl_option= sl_facility= 0;
+}
+
+static void groupsdump(int ngids, const gid_t *gids, const char *const *groups) {
+  int i;
+  
+  for (i=0; i<ngids; i++) printf(" %ld(%s)",(long)gids[i],groups[i]);
+}
+
+void debug_dumprequest(pid_t mypid) {
+  int i, fd;
+  
+  printf("server pid: %ld\n"
+         "client pid: %ld\n"
+         "service: `%s'\n"
+         "service user: `%s'\n"
+        "service uid: %ld\n"
+         "service user shell: `%s'\n"
+         "service user dir: `%s'\n"
+        "service groups:",
+         (long)mypid, (long)request_mbuf.clientpid,
+         service, serviceuser, (long)serviceuser_uid,
+        serviceuser_shell, serviceuser_dir);
+  groupsdump(service_ngids,service_gids,service_groups);
+  printf("\n"
+        "calling user: `%s'\n"
+        "calling uid: %ld\n"
+         "calling user shell: `%s'\n"
+        "calling groups:",
+        loginname, (long)request_mbuf.callinguid,
+        callinguser_shell);
+  groupsdump(request_mbuf.ngids,calling_gids,calling_groups);
+  printf("\n"
+        "calling cwd: `%s'\n"
+        "fds:",
+         cwd);
+  for (fd=0; fd<fdarrayused; fd++)
+    if (fdarray[fd].iswrite != -1)
+      printf(" %d%s",fd,fdarray[fd].iswrite ? "w" : "r");
+  printf("\n" "arguments:");
+  for (i=0; i<request_mbuf.nargs; i++) printf(" `%s'",argarray[i]);
+  printf("\n" "variables:");
+  for (i=0; i<request_mbuf.nvars; i++)
+    printf(" `%s'=`%s'",defvararray[i].key,defvararray[i].value);
+  printf("\n");
+  if (overridedata) printf("override data: `%s'\n",overridedata);
+  else printf("not overridden\n");
+  if (getenv("USERVD_SLEEP")) sleep(atoi(getenv("USERVD_SLEEP")));
+}
+
+void debug_dumpexecsettings(void) {
+  printf("configuration parsed\n");
+  always_dumpexecsettings();
+}
+
+void debug_dumpparameter(const char *parm, char **values) {
+  always_dumpparameter(parm,values);
+}
+
+static int groupsallin(int na, const gid_t *lista,
+                       int nb, const gid_t *listb) {
+  int i,j;
+  for (i=0; i<na; i++) {
+    for (j=0; j<nb && listb[j] != lista[i]; j++);
+    if (j>=nb) return 0;
+  }
+  return 1;
+}
+
+int setgroups(size_t wantsize, const gid_t *wantlist) {
+  /* This is a bit of a hack really.  What we want when we're in debug mode is to
+   * have initgroups() be a no-op iff the groups are already set right (so that
+   * we notice if we're trying to change to the wrong user) but to fail if they're
+   * not.
+   *
+   * We can't just call initgroups() because it unconditionally calls
+   * setgroups, which always fails for non-root even if the two group
+   * lists are the same.  So here we have a faked-up setgroups which
+   * uses getgroups to see what the group list is and `succeeds' if
+   * the actual group list and the desired one have the same set of
+   * groups, and fails with EPERM if the real setgroups would have
+   * added group(s) or otherwise EINVAL if it would have removed some.
+   *
+   * The usual magic with dynamic linking makes the libc initgroups(3) call
+   * pick up our setgroups() rather than the real setgroups(2).
+   */
+  int realsize, e;
+  gid_t *reallist;
+
+  realsize= getgroups(0,0); if (realsize == -1) return -1;
+  reallist= malloc(sizeof(gid_t)*realsize); if (!reallist) return -1;
+  if (getgroups(realsize,reallist) != realsize)
+    { e= errno; free(reallist); errno= e; return -1; }
+  if (!groupsallin(wantsize,wantlist,realsize,reallist))
+    { free(reallist); errno= EPERM; return -1; }
+  if (!groupsallin(realsize,reallist,wantsize,wantlist))
+    { free(reallist); errno= EINVAL; return -1; }
+  free(reallist); return 0;
+}
+
+pid_t nondebug_fork(void) { return 0; }
+const char *nondebug_serviceuserdir(const char *ifnondebug) { return SERVICEUSERDIR; }
+
+#else
+
+void debug_dumprequest(pid_t mypid) { }
+void debug_dumpexecsettings(void) { }
+void debug_dumpparameter(const char *parm, char **values) { }
+pid_t nondebug_fork(void) { return fork(); }
+const char *nondebug_serviceuserdir(const char *ifnondebug) { return ifnondebug; }
+
+#endif
diff --git a/install-sh b/install-sh
new file mode 100755 (executable)
index 0000000..398a88e
--- /dev/null
@@ -0,0 +1,251 @@
+#!/bin/sh
+#
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
+#
+# Copyright 1991 by the Massachusetts Institute of Technology
+#
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission.  M.I.T. makes no representations about the
+# suitability of this software for any purpose.  It is provided "as is"
+# without express or implied warranty.
+#
+# Calling this script install-sh is preferred over install.sh, to prevent
+# `make' implicit rules from creating a file called install from it
+# when there is no Makefile.
+#
+# This script is compatible with the BSD install script, but was written
+# from scratch.  It can only install one file at a time, a restriction
+# shared with many OS's install programs.
+
+
+# set DOITPROG to echo to test this script
+
+# Don't use :- since 4.3BSD and earlier shells don't like it.
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+       -c) instcmd="$cpprog"
+           shift
+           continue;;
+
+       -d) dir_arg=true
+           shift
+           continue;;
+
+       -m) chmodcmd="$chmodprog $2"
+           shift
+           shift
+           continue;;
+
+       -o) chowncmd="$chownprog $2"
+           shift
+           shift
+           continue;;
+
+       -g) chgrpcmd="$chgrpprog $2"
+           shift
+           shift
+           continue;;
+
+       -s) stripcmd="$stripprog"
+           shift
+           continue;;
+
+       -t=*) transformarg=`echo $1 | sed 's/-t=//'`
+           shift
+           continue;;
+
+       -b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+           shift
+           continue;;
+
+       *)  if [ x"$src" = x ]
+           then
+               src=$1
+           else
+               # this colon is to work around a 386BSD /bin/sh bug
+               :
+               dst=$1
+           fi
+           shift
+           continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+       echo "install:  no input file specified"
+       exit 1
+else
+       :
+fi
+
+if [ x"$dir_arg" != x ]; then
+       dst=$src
+       src=""
+       
+       if [ -d $dst ]; then
+               instcmd=:
+               chmodcmd=""
+       else
+               instcmd=$mkdirprog
+       fi
+else
+
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad 
+# if $src (and thus $dsttmp) contains '*'.
+
+       if [ -f $src -o -d $src ]
+       then
+               :
+       else
+               echo "install:  $src does not exist"
+               exit 1
+       fi
+       
+       if [ x"$dst" = x ]
+       then
+               echo "install:  no destination specified"
+               exit 1
+       else
+               :
+       fi
+
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
+
+       if [ -d $dst ]
+       then
+               dst="$dst"/`basename $src`
+       else
+               :
+       fi
+fi
+
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
+
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
+
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='
+       '
+IFS="${IFS-${defaultIFS}}"
+
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
+
+pathcomp=''
+
+while [ $# -ne 0 ] ; do
+       pathcomp="${pathcomp}${1}"
+       shift
+
+       if [ ! -d "${pathcomp}" ] ;
+        then
+               $mkdirprog "${pathcomp}"
+       else
+               :
+       fi
+
+       pathcomp="${pathcomp}/"
+done
+fi
+
+if [ x"$dir_arg" != x ]
+then
+       $doit $instcmd $dst &&
+
+       if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else : ; fi &&
+       if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else : ; fi &&
+       if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else : ; fi &&
+       if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else : ; fi
+else
+
+# If we're going to rename the final executable, determine the name now.
+
+       if [ x"$transformarg" = x ] 
+       then
+               dstfile=`basename $dst`
+       else
+               dstfile=`basename $dst $transformbasename | 
+                       sed $transformarg`$transformbasename
+       fi
+
+# don't allow the sed command to completely eliminate the filename
+
+       if [ x"$dstfile" = x ] 
+       then
+               dstfile=`basename $dst`
+       else
+               :
+       fi
+
+# Make a temp file name in the proper directory.
+
+       dsttmp=$dstdir/#inst.$$#
+
+# Move or copy the file name to the temp name
+
+       $doit $instcmd $src $dsttmp &&
+
+       trap "rm -f ${dsttmp}" 0 &&
+
+# and set any options; do chmod last to preserve setuid bits
+
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
+
+       if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else :;fi &&
+       if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else :;fi &&
+       if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else :;fi &&
+       if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else :;fi &&
+
+# Now rename the file to the real destination.
+
+       $doit $rmcmd -f $dstdir/$dstfile &&
+       $doit $mvcmd $dsttmp $dstdir/$dstfile 
+
+fi &&
+
+
+exit 0
diff --git a/language.i4 b/language.i4
new file mode 100644 (file)
index 0000000..d660b27
--- /dev/null
@@ -0,0 +1,259 @@
+dnl  userv - language.i4
+dnl  definition of the configuration language, used for tokens.h and lexer.l
+dnl
+dnl  Copyright (C)1996-1997,1999 Ian Jackson
+dnl
+dnl  This is free software; you can redistribute it and/or modify it
+dnl  under the terms of the GNU General Public License as published by
+dnl  the Free Software Foundation; either version 2 of the License, or
+dnl  (at your option) any later version.
+dnl
+dnl  This program is distributed in the hope that it will be useful, but
+dnl  WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+dnl  General Public License for more details.
+dnl
+dnl  You should have received a copy of the GNU General Public License
+dnl  along with userv; if not, write to the Free Software
+dnl  Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+dnl  Diversions are
+dnl   1,2,4: sections of token enum list
+dnl   3:     flex rules
+
+divert(-1)
+
+define(`makename',`translit(``$1'',`-_')')
+
+define(`hasvalistype',`pushdef(`odiv',divnum)dnl
+divert(1)dnl
+  format(``%-50s'',`toki_$1=')`$2',
+divert(2)dnl
+  format(``%-30s'',`tokv_$1=')`$3|toki_$1',
+divert(odiv)popdef(`odiv')')
+
+define(`cautotoki',eval(`0x1'))
+define(`cautotokt',eval(`0x1000'))
+
+define(`autovalistype',`hasvalistype(`$1',format(``0x%08x'',cautotoki),`$2')`'define(`cautotoki',incr(cautotoki))')
+
+define(`autovaldeftype',`pushdef(`odiv',divnum)divert(4)dnl
+  format(``%-25s'',`tokt_$1=')format(``0x%08x'',cautotokt),
+divert(odiv)popdef(`odiv')define(`cautotokt',eval(cautotokt`*2'))')
+
+define(`nametypelexpatexec',`
+autovalistype(`$1',`$2')
+pushdef(`odiv',divnum)divert(3)dnl
+`$3 { $4'`return tokv_$1; }'
+divert(odiv)popdef(`odiv')')
+
+define(`wordtypelexexec',
+`nametypelexpatexec(`word_'makename(`$1'),`$2|tokr_word',`$1',`$3')')
+
+dnl types
+autovaldeftype(`directive')
+autovaldeftype(`controlstart')
+autovaldeftype(`controlend')
+autovaldeftype(`exception')
+autovaldeftype(`parmcondition')
+autovaldeftype(`condop')
+autovaldeftype(`parameter')
+autovaldeftype(`number')
+autovaldeftype(`fdrange')
+autovaldeftype(`logfacility')
+autovaldeftype(`loglevel')
+autovaldeftype(`readwrite')
+autovaldeftype(`string')
+autovaldeftype(`execmode')
+autovaldeftype(`ehandlemode')
+autovaldeftype(`builtinservice')
+autovaldeftype(`misc')
+autovaldeftype(`internal')
+
+dnl simple isdirectives
+define(`isdirectivefn',`dnl
+wordtypelexexec(`$1',`tokt_directive$3',`lr_dir= $2; $4')dnl
+pushdef(`odiv',divnum)
+divert(odiv)popdef(`odiv')')
+define(`isdirective',`isdirectivefn(`$1',`df_'makename(`$1'),`$2')')
+define(`isdirectiveinternal',`isdirectivefn(`$1',`dfi_'makename(`$1'),
+                                            `|tokt_internal$2')')
+define(`isexecmode',`isdirective(`$1',`|tokt_execmode')')
+define(`isehandlemode',`isdirective(`$1',`|tokt_ehandlemode')')
+define(`isfdwant',`isdirectivefn(`$1',`dfg_fdwant',`',
+                      `lr_fdwant_readwrite=$2; ')')
+define(`isflagpair',`isdirectivefn(`$1',`dfg_setflag',`',
+                      `lr_flag= &'makename(`$1')`; lr_flagval= 1; ')
+                     isdirectivefn(`no-$1',`dfg_setflag',`',
+                      `lr_flag= &'makename(`$1')`; lr_flagval= 0; ')')
+dnl `reset' is also a builtin service
+isexecmode(`reject')
+dnl `execute' is also a builtin service
+isexecmode(`execute-from-directory')
+isexecmode(`execute-from-path')
+isexecmode(`execute-builtin')
+isehandlemode(`errors-to-stderr')
+isehandlemode(`errors-to-syslog')
+isehandlemode(`errors-to-file')
+isfdwant(`require-fd',`1')
+isfdwant(`allow-fd',`0')
+isfdwant(`null-fd',`0')
+isfdwant(`reject-fd',`-1')
+isfdwant(`ignore-fd',`-1')
+isflagpair(`set-environment')
+isflagpair(`suppress-args')
+isflagpair(`disconnect-hup')
+isdirective(`cd')
+isdirective(`user-rcfile')
+isdirective(`include')
+isdirectivefn(`include-ifexist',`df_include')
+isdirective(`include-lookup')
+isdirectivefn(`include-lookup-all',`df_includelookup')
+isdirective(`include-directory')
+isdirective(`message')
+isdirectivefn(`_include-sysconfig',`df_include',`|tokt_internal')
+isdirectiveinternal(`_include-user-rcfile')
+isdirectiveinternal(`_include-client-config')
+
+dnl quit and eof are each a directive _and_ an exception
+dnl as separate tokens.  A true end of file is returned by yylex
+dnl as the exception.  The directive (word) tokens are
+dnl tokv_word_{eof,quit}; the exceptions are tokv_{eof,quit}.
+isdirective(`quit')
+isdirective(`eof')
+
+dnl control construct starters
+define(`iscontrolstart',
+`isdirective(`$1',`|tokt_controlstart')')
+iscontrolstart(`if')
+iscontrolstart(`catch-quit')
+iscontrolstart(`errors-push')
+
+dnl control construct enders
+define(`iscontrolend',
+`wordtypelexexec(`$1',`tokt_controlend$3',
+                 `lr_controlend= tokv_word_'makename(`$2')`; ')')
+iscontrolend(`elif',   `if', `|tokt_controlstart')
+iscontrolend(`else',   `if', `|tokt_controlstart')
+iscontrolend(`fi',     `if')
+iscontrolend(`hctac',  `catch-quit')
+iscontrolend(`srorre', `errors-push')
+
+dnl conditions
+define(`isparmcondition',`wordtypelexexec(`$1',`tokt_parmcondition',
+                           `lr_parmcond= pcf_'makename(`$1')`; ')')
+isparmcondition(`glob')
+isparmcondition(`range')
+isparmcondition(`grep')
+
+
+define(`builtininlist',`
+pushdef(`odiv',divnum)divert(5)dnl
+  `"$1$2"',
+divert(odiv)popdef(`odiv')')
+
+dnl builtin services
+define(`isbuiltinservice',
+ `wordtypelexexec(`$1',`tokt_builtinservice$3',
+    `lr_bispa= bispa_'makename(`$2')`; lr_bisexec= bisexec_'makename(`$1')`; $5')
+  builtininlist(`$1',`$4')')
+isbuiltinservice(`environment',`none')
+isbuiltinservice(`parameter',`parameter',`',` <parameter>')
+isbuiltinservice(`version',`none')
+isbuiltinservice(`toplevel',`none')
+isbuiltinservice(`override',`none')
+isbuiltinservice(`shutdown',`none')
+
+dnl builtin services that are also directive names
+define(`isdirectivebuiltinservice',
+   `isbuiltinservice(`$1',`$2',`|tokt_directive$3',`$4',
+                     `lr_dir= df_'makename(`$1')`; ')')
+isdirectivebuiltinservice(`reset',`none')
+isdirectivebuiltinservice(`execute',`none',`|tokt_execmode')
+
+isbuiltinservice(`help',`none')
+
+dnl parameters
+define(`isparameter',`wordtypelexexec(`$1',`tokt_parameter',
+                        `lr_parameter= pf_'makename(`$1')`; ')')
+isparameter(`service')
+isparameter(`calling-user')
+isparameter(`calling-group')
+isparameter(`calling-user-shell')
+isparameter(`service-user')
+isparameter(`service-group')
+isparameter(`service-user-shell')
+
+dnl syslog levels
+define(`isloglevellexpat',
+`nametypelexpatexec(`syslog_$1',`tokt_loglevel|tokr_word',`$2',
+                    `lr_loglevel= LOG_'translit(``$1'',`a-z',`A-Z')`; ')')
+define(`isloglevel',`isloglevellexpat(`$1',`$1')')
+isloglevel(`debug')
+isloglevel(`info')
+isloglevel(`notice')
+isloglevellexpat(`warning',`warn(ing)?')
+isloglevel(`err')dnl also the word error, which has dual meaning (below)
+isloglevel(`crit')
+isloglevel(`alert')
+isloglevellexpat(`emerg',`emerg|panic')
+
+dnl syslog facilities
+define(`islogfacilitylexpat',
+`nametypelexpatexec(`syslog_$1',`tokt_logfacility|tokr_word',`$2',
+                    `lr_logfacility= LOG_'translit(``$1'',`a-z',`A-Z')`; ')')
+define(`islogfacility',`islogfacilitylexpat(`$1',`$1')')
+islogfacilitylexpat(`authpriv',`auth(priv)?|security')
+islogfacility(`cron')
+islogfacility(`daemon')
+islogfacilitylexpat(`kern',`kern(el)?')
+islogfacility(`lpr')
+islogfacility(`mail')
+islogfacility(`news')
+islogfacility(`syslog')
+islogfacility(`user')
+islogfacility(`uucp')
+islogfacility(`local0')
+islogfacility(`local1')
+islogfacility(`local2')
+islogfacility(`local3')
+islogfacility(`local4')
+islogfacility(`local5')
+islogfacility(`local6')
+islogfacility(`local7')
+
+dnl misc. word-like things
+wordtypelexexec(`read',`tokt_readwrite',`')
+wordtypelexexec(`write',`tokt_readwrite',`')
+
+dnl small nonnegative integers and fd ranges
+dnl some of these have two tokt_ bits set, because they can be several things.
+autovalistype(`ordinal',       `tokt_number|tokt_fdrange|tokr_word')
+autovalistype(`fdrange',       `tokt_fdrange|tokr_punct')
+autovalistype(`fdstoend',      `tokt_fdrange|tokr_punct')
+nametypelexpatexec(`dollar',`tokt_misc|tokr_punct',`\$',`')
+
+dnl non-word things
+autovalistype(`lwsp',            `tokt_misc|tokr_nonstring')
+autovalistype(`newline',         `tokt_misc|tokr_nonstring')
+autovalistype(`barestring',      `tokt_string|tokr_string')
+autovalistype(`quotedstring',    `tokt_string|tokr_string')
+
+dnl exceptions - NB that there are also tokv_word_{eof,quit}
+dnl - see above, near the directives.
+autovalistype(`eof',             `tokt_exception|tokr_nonstring')
+autovalistype(`quit',            `tokt_exception|tokr_nonstring')
+autovalistype(`error',           `tokt_exception|tokr_nonstring')
+
+define(`iscondop',`nametypelexpatexec(`$2',`tokt_condop|tokr_punct',`$1',`')')
+iscondop(`\(',`openparen')
+iscondop(`\)',`closeparen')
+iscondop(`\!',`not')
+iscondop(`\&',`and')
+iscondop(`\|',`or')
+
+dnl words that could be two things
+wordtypelexexec(`error',`tokt_directive|tokt_loglevel',
+               `lr_dir= df_error; lr_loglevel= LOG_ERR; ')
+
+divert
diff --git a/lexer.c b/lexer.c
new file mode 100644 (file)
index 0000000..99300ad
--- /dev/null
+++ b/lexer.c
@@ -0,0 +1,2739 @@
+
+#line 3 "<stdout>"
+
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 31
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t; 
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN               (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN              (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN              (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX               (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX              (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX              (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX              (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX             (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX             (4294967295U)
+#endif
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else  /* ! __cplusplus */
+
+#if __STDC__
+
+#define YY_USE_CONST
+
+#endif /* __STDC__ */
+#endif /* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index.  If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* Enter a start condition.  This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN (yy_start) = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state.  The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START (((yy_start) - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart(yyin  )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#define YY_BUF_SIZE 16384
+#endif
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+extern int yyleng;
+
+extern FILE *yyin, *yyout;
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+    #define YY_LESS_LINENO(n)
+    
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+       do \
+               { \
+               /* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+               *yy_cp = (yy_hold_char); \
+               YY_RESTORE_YY_MORE_OFFSET \
+               (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+               YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+               } \
+       while ( 0 )
+
+#define unput(c) yyunput( c, (yytext_ptr)  )
+
+/* The following is because we cannot portably get our hands on size_t
+ * (without autoconf's help, which isn't available because we want
+ * flex-generated scanners to compile on their own).
+ */
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef unsigned int yy_size_t;
+#endif
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+       {
+       FILE *yy_input_file;
+
+       char *yy_ch_buf;                /* input buffer */
+       char *yy_buf_pos;               /* current position in input buffer */
+
+       /* Size of input buffer in bytes, not including room for EOB
+        * characters.
+        */
+       yy_size_t yy_buf_size;
+
+       /* Number of characters read into yy_ch_buf, not including EOB
+        * characters.
+        */
+       int yy_n_chars;
+
+       /* Whether we "own" the buffer - i.e., we know we created it,
+        * and can realloc() it to grow it, and should free() it to
+        * delete it.
+        */
+       int yy_is_our_buffer;
+
+       /* Whether this is an "interactive" input source; if so, and
+        * if we're using stdio for input, then we want to use getc()
+        * instead of fread(), to make sure we stop fetching input after
+        * each newline.
+        */
+       int yy_is_interactive;
+
+       /* Whether we're considered to be at the beginning of a line.
+        * If so, '^' rules will be active on the next match, otherwise
+        * not.
+        */
+       int yy_at_bol;
+
+    int yy_bs_lineno; /**< The line count. */
+    int yy_bs_column; /**< The column count. */
+    
+       /* Whether to try to fill the input buffer when we reach the
+        * end of it.
+        */
+       int yy_fill_buffer;
+
+       int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+       /* When an EOF's been seen but there's still some text to process
+        * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+        * shouldn't try reading from the input source any more.  We might
+        * still have a bunch of tokens to match, though, because of
+        * possible backing-up.
+        *
+        * When we actually see the EOF, we change the status to "new"
+        * (via yyrestart()), so that the user can continue scanning by
+        * just pointing yyin at a new input file.
+        */
+#define YY_BUFFER_EOF_PENDING 2
+
+       };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* Stack of input buffers. */
+static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */
+static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */
+static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \
+                          ? (yy_buffer_stack)[(yy_buffer_stack_top)] \
+                          : NULL)
+
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)]
+
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+static int yy_n_chars;         /* number of characters read into yy_ch_buf */
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = (char *) 0;
+static int yy_init = 1;                /* whether we need to initialize */
+static int yy_start = 0;       /* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin.  A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+
+void yyrestart (FILE *input_file  );
+void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer  );
+YY_BUFFER_STATE yy_create_buffer (FILE *file,int size  );
+void yy_delete_buffer (YY_BUFFER_STATE b  );
+void yy_flush_buffer (YY_BUFFER_STATE b  );
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer  );
+void yypop_buffer_state (void );
+
+static void yyensure_buffer_stack (void );
+static void yy_load_buffer_state (void );
+static void yy_init_buffer (YY_BUFFER_STATE b,FILE *file  );
+
+#define YY_FLUSH_BUFFER yy_flush_buffer(YY_CURRENT_BUFFER )
+
+YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size  );
+YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str  );
+YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,int len  );
+
+void *yyalloc (yy_size_t  );
+void *yyrealloc (void *,yy_size_t  );
+void yyfree (void *  );
+
+#define yy_new_buffer yy_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+       { \
+       if ( ! YY_CURRENT_BUFFER ){ \
+        yyensure_buffer_stack (); \
+               YY_CURRENT_BUFFER_LVALUE =    \
+            yy_create_buffer(yyin,YY_BUF_SIZE ); \
+       } \
+       YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+       }
+
+#define yy_set_bol(at_bol) \
+       { \
+       if ( ! YY_CURRENT_BUFFER ){\
+        yyensure_buffer_stack (); \
+               YY_CURRENT_BUFFER_LVALUE =    \
+            yy_create_buffer(yyin,YY_BUF_SIZE ); \
+       } \
+       YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+       }
+
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* Begin user sect3 */
+
+#define yywrap(n) 1
+#define YY_SKIP_YYWRAP
+
+typedef unsigned char YY_CHAR;
+
+FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0;
+
+typedef int yy_state_type;
+
+extern int yylineno;
+
+int yylineno = 1;
+
+extern char *yytext;
+#define yytext_ptr yytext
+
+static yy_state_type yy_get_previous_state (void );
+static yy_state_type yy_try_NUL_trans (yy_state_type current_state  );
+static int yy_get_next_buffer (void );
+static void yy_fatal_error (yyconst char msg[]  );
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+       (yytext_ptr) = yy_bp; \
+       yyleng = (size_t) (yy_cp - yy_bp); \
+       (yy_hold_char) = *yy_cp; \
+       *yy_cp = '\0'; \
+       (yy_c_buf_p) = yy_cp;
+
+#define YY_NUM_RULES 107
+#define YY_END_OF_BUFFER 108
+/* This struct is not used in this scanner,
+   but its presence is necessary. */
+struct yy_trans_info
+       {
+       flex_int32_t yy_verify;
+       flex_int32_t yy_nxt;
+       };
+static yyconst flex_int16_t yy_accept[565] =
+    {   0,
+        0,    0,  108,  103,   98,   99,   90,  104,  101,   87,
+       91,   88,   89,   94,  105,  103,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,   92,  103,   98,
+       99,  101,    0,    0,  102,    0,  101,  101,  100,   96,
+       94,    0,   97,  103,  103,  103,  103,   19,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,   37,  103,
+      103,  103,  103,   32,  103,  103,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+
+        0,    0,    0,   95,   94,   97,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+      103,   31,   63,  103,  103,  103,  103,  103,  103,  103,
+      103,  103,  103,   71,  103,  103,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+      103,  103,    0,    0,   95,   94,  103,  103,  103,   67,
+      103,  103,   64,   68,  103,  103,  103,   35,   36,  103,
+      103,  103,  103,   40,   42,  103,   51,  103,  103,   60,
+       70,  103,   72,  103,   73,  103,  103,  103,  103,  103,
+
+      103,  103,   30,  103,   85,  103,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,   75,   76,  103,   62,
+      103,    0,   95,   94,  103,   65,  103,  103,  103,  103,
+      103,   59,  103,   66,  103,   93,  103,   38,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+       41,  103,  103,   49,  103,  103,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,   86,   95,   94,  103,  103,
+      103,  103,  103,   69,  103,  103,  103,  103,  103,  103,
+       70,   77,   78,   79,   80,   81,   82,   83,   84,  103,
+      103,  103,  103,   61,  103,  103,  103,    1,  103,  103,
+
+      103,  103,  103,   39,  103,   74,  103,  103,  103,  103,
+       95,   94,  103,  103,  103,  103,  103,  103,  103,  103,
+       50,  103,   21,   26,  103,  103,  103,   10,  103,  103,
+      103,  103,  103,   52,  103,  103,  103,  103,  103,   45,
+       62,   95,   94,  103,    9,   67,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,   47,  103,
+      103,  103,  103,  103,   48,  103,   46,  103,   95,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,   12,
+      103,  103,  103,  103,  103,  103,   44,   11,  103,  103,
+      103,  103,  103,  103,   95,  103,  103,  103,  103,  103,
+
+       33,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+      103,  103,  103,    8,  103,  103,  103,  103,  103,  103,
+      103,  103,  103,  103,  103,   43,   34,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+      103,   20,  103,  103,  103,  103,   53,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+       56,  103,  103,  103,  103,  103,   54,  103,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  103,
+       57,  103,  103,   15,  103,  103,  103,  103,   17,    7,
+      103,  103,  103,  103,  103,  103,  103,   23,  103,  103,
+
+      103,  103,  103,  103,  103,  103,  103,  103,  103,    4,
+      103,  103,  103,   22,  103,  103,  103,  103,  103,   13,
+      103,  103,  103,  103,    5,    6,  103,  103,  103,  103,
+      103,  103,   16,  103,  103,  103,  103,  103,  103,    3,
+       25,  103,   18,  103,  103,  103,   27,  103,   55,  103,
+       24,   14,   58,  103,  103,  103,  103,   28,  103,  103,
+      103,   29,    2,    0
+    } ;
+
+static yyconst flex_int32_t yy_ec[256] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    2,    3,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    2,    4,    5,    6,    7,    8,    9,    8,   10,
+       11,    8,    8,    8,   12,    8,    8,   13,   14,   15,
+       16,   17,   18,   19,   20,   21,   21,    8,    8,    8,
+        8,    8,    8,    8,   22,   22,   22,   22,   22,   22,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        8,   23,    8,    8,   24,    8,   25,   26,   27,   28,
+
+       29,   30,   31,   32,   33,   34,   35,   36,   37,   38,
+       39,   40,   41,   42,   43,   44,   45,   46,   47,   48,
+       49,   50,    8,   51,    8,    8,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1
+    } ;
+
+static yyconst flex_int32_t yy_meta[52] =
+    {   0,
+        1,    2,    3,    1,    2,    1,    1,    1,    1,    1,
+        1,    1,    4,    4,    4,    4,    4,    4,    4,    4,
+        4,    1,    2,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1
+    } ;
+
+static yyconst flex_int16_t yy_base[571] =
+    {   0,
+        0,    0,  779,    0,   50,  780,    0,   49,   55,    0,
+        0,    0,    0,   67,   59,  745,   19,   49,   34,   56,
+      744,   29,   39,   59,  747,   30,   71,   70,  729,  749,
+      728,   76,   74,  733,   65,  742,   82,    0,    0,  111,
+      780,  767,   73,   97,  780,  134,  123,  766,  780,  170,
+      179,  109,  116,  730,   93,  723,   94,    0,   88,  737,
+      739,  721,   98,  734,  716,  731,  718,  730,    0,  719,
+      728,  712,  719,    0,  716,  113,  711,  725,  709,  717,
+      706,  701,  120,  711,  717,  119,  712,  706,  133,  118,
+      698,  703,  701,  697,  699,  709,  710,  694,  693,  701,
+
+      163,    0,  196,  214,  223,  140,  706,  690,  692,  698,
+      693,  701,  683,  688,  688,  679,  696,  692,  692,  678,
+      686,    0,  679,  690,  690,  675,  689,  673,  673,  675,
+      671,  671,  683,    0,  671,  663,  662,  116,  671,  667,
+      660,  668,  675,  655,  667,  669,  667,  650,  665,  648,
+      646,  679,  646,  647,  648,  651,  650,  643,  644,  640,
+      644,  637,    0,  232,  250,  259,  644,  635,  631,  637,
+      643,  643,    0,    0,  635,  642,  633,    0,    0,  640,
+      628,  627,  623,    0,    0,  640,    0,  624,  620,    0,
+      635,  627,    0,  637,    0,  628,  175,  633,  647,  616,
+
+      630,  619,    0,  626,    0,  627,  620,  608,  609,  617,
+      620,  620,  605,  604,  606,  615,  631,    0,  609,  608,
+      611,  276,  287,  296,  594,    0,  626,  595,  598,  623,
+      596,    0,  595,    0,  593,  588,  586,    0,  600,  600,
+      591,  305,  595,  582,  580,  583,  593,  591,  587,  590,
+        0,  574,  575,    0,  583,  588,  576,  574,  583,  582,
+      579,  563,  566,  568,  568,    0,  313,  322,  577,  574,
+      570,  571,  560,    0,  562,  561,  586,  568,  584,  566,
+        0,    0,    0,    0,    0,    0,    0,    0,    0,  565,
+      566,  580,  551,    0,  562,  561,  544,  575,  557,  541,
+
+      555,  537,  535,    0,  538,    0,  551,  552,  540,  546,
+      331,  340,  547,  547,  528,  561,  527,  542,  533,  128,
+      557,  538,  555,    0,  527,  536,  522,    0,  534,  533,
+      531,  548,  510,  546,  524,  518,  512,  518,  523,    0,
+        0,  349,  540,  539,    0,    0,  251,  517,  522,  519,
+      502,  507,  143,  517,  142,  506,  505,  513,    0,  499,
+      512,  509,  252,  496,    0,  525,    0,  503,  358,  353,
+      493,  491,  489,  488,  493,  487,  517,  483,  485,    0,
+      493,  495,  485,  485,  476,  478,    0,    0,  492,  477,
+      475,  478,  491,  479,    0,  478,  464,  469,  472,  481,
+
+        0,  497,  464,  475,  255,  473,  466,  462,  474,  463,
+      472,  467,  456,    0,  459,  468,  458,  453,  465,  460,
+      449,  462,  445,  447,  456,    0,    0,  454,  158,  450,
+      448,  455,  435,  447,  454,  438,  467,  433,  435,  439,
+      444,    0,  445,  446,  430,  431,  458,  424,  432,  439,
+      423,  421,  452,  436,  429,  416,  416,  420,  433,  417,
+      444,  426,  411,  415,  413,  439,    0,  407,  409,  419,
+      418,  410,  412,  353,  400,  400,  402,  429,  402,  397,
+        0,  395,  399,    0,  392,  397,  392,  401,    0,    0,
+      390,  392,  392,  396,  403,  388,  382,  413,  392,  386,
+
+      391,  389,  376,  407,  388,  390,  387,  373,  374,    0,
+      359,  356,  355,    0,  370,  349,  363,  348,  361,    0,
+      362,  355,  357,  350,    0,    0,  356,  352,  334,  346,
+      255,  256,    0,  257,  253,  260,  257,  253,  261,    0,
+        0,  251,    0,  242,  248,  218,    0,  219,    0,  164,
+        0,    0,    0,  176,  176,  164,  148,    0,  138,  148,
+      128,    0,    0,  780,  398,  402,  406,  410,  167,  129
+    } ;
+
+static yyconst flex_int16_t yy_def[571] =
+    {   0,
+      564,    1,  564,  565,  564,  564,  565,  566,  567,  565,
+      565,  565,  565,  565,  564,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  564,
+      564,  568,  564,  566,  564,  566,  567,  568,  564,  565,
+       14,  564,  564,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+
+      564,  569,  566,  565,   14,  564,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  570,  103,  565,   14,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  566,  565,   14,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,   14,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,   14,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,  565,  565,  565,  565,  565,  565,  565,
+      565,  565,  565,    0,  564,  564,  564,  564,  564,  564
+    } ;
+
+static yyconst flex_int16_t yy_nxt[832] =
+    {   0,
+        4,    5,    6,    7,    8,    9,   10,    4,   11,   12,
+       13,    4,   14,   14,   14,   14,   14,   14,   14,   14,
+       14,    4,   15,   16,   17,    4,   18,   19,   20,   21,
+       22,   23,   24,    4,   25,   26,   27,   28,   29,   30,
+       31,   32,   33,   34,   35,   36,   37,    4,    4,    4,
+       38,   40,   41,   45,   55,   42,   48,   49,   60,   48,
+       52,   53,   61,   56,   70,   72,   62,   73,   78,   79,
+       71,   46,   43,   57,   52,   53,   58,   48,   50,   51,
+       51,   51,   51,   51,   51,   51,   51,   51,   74,   75,
+       59,   63,   64,   65,   66,   80,   76,   67,   82,   81,
+
+       88,   45,   90,   68,   89,   91,   99,   96,   83,   97,
+       52,   53,   40,   41,   84,   92,   42,  106,   93,   46,
+      113,  108,   94,  100,   48,   49,  114,   48,  109,  111,
+      118,  138,   44,   43,  564,  101,   44,  112,   43,  130,
+      119,  106,  131,  196,  150,   48,  102,  102,  102,  102,
+      102,  102,  102,  102,  102,  564,  142,  146,  197,  151,
+      143,  152,   43,  139,  101,   44,  147,  351,  378,  381,
+      163,  352,  379,  148,  382,  149,  563,  383,  562,  561,
+      560,  103,  104,  104,  104,  104,  104,  104,  104,  104,
+      104,  105,  105,  105,  105,  105,  105,  105,  105,  105,
+
+       45,  450,  559,  245,  558,  557,  451,  556,  164,  164,
+      164,  164,  164,  164,  164,  164,  164,  164,   46,  246,
+      164,  164,  164,  164,  164,  164,  165,  165,  165,  165,
+      165,  165,  165,  165,  165,  166,  166,  166,  166,  166,
+      166,  166,  166,  166,  222,  222,  222,  222,  222,  222,
+      222,  222,  222,  222,  555,  554,  222,  222,  222,  222,
+      222,  222,  223,  223,  223,  223,  223,  223,  223,  223,
+      223,  224,  224,  224,  224,  224,  224,  224,  224,  224,
+       45,  371,  390,  553,  428,  552,  551,  550,  549,  548,
+      547,  546,  545,  544,  543,  372,  391,  429,   46,  267,
+
+      267,  267,  267,  267,  267,  267,  267,  267,  268,  268,
+      268,  268,  268,  268,  268,  268,  268,  282,  283,  284,
+      285,  286,  287,  288,  289,  311,  311,  311,  311,  311,
+      311,  311,  311,  311,  312,  312,  312,  312,  312,  312,
+      312,  312,  312,  342,  342,  342,  342,  342,  342,  342,
+      342,  342,  343,  343,  343,  343,  343,  343,  343,  343,
+      343,  369,  369,  369,  369,  369,  369,  369,  369,  369,
+      395,  395,  395,  395,  395,  395,  395,  395,  395,  396,
+      494,  542,  541,  540,  539,  538,  537,  536,  535,  534,
+      533,  532,  495,  531,  530,  397,  529,  398,   39,  528,
+
+      527,   39,   44,   44,  526,   44,   47,   47,   47,   47,
+       48,   48,   48,   48,  525,  524,  523,  522,  521,  520,
+      519,  518,  517,  516,  515,  514,  513,  512,  511,  510,
+      509,  508,  507,  506,  505,  504,  503,  502,  501,  500,
+      499,  498,  497,  496,  493,  492,  491,  490,  489,  488,
+      487,  486,  485,  484,  483,  482,  481,  480,  479,  478,
+      477,  476,  475,  474,  473,  472,  471,  470,  469,  468,
+      467,  466,  465,  464,  463,  462,  461,  460,  459,  458,
+      457,  456,  455,  454,  453,  452,  449,  448,  447,  446,
+      445,  444,  443,  442,  441,  440,  439,  438,  437,  436,
+
+      435,  434,  433,  432,  431,  430,  427,  426,  425,  424,
+      423,  422,  421,  420,  419,  418,  417,  416,  415,  414,
+      413,  412,  411,  410,  409,  408,  407,  406,  405,  404,
+      403,  402,  401,  400,  399,  394,  393,  392,  389,  388,
+      387,  386,  385,  384,  380,  377,  376,  375,  374,  373,
+      370,   50,  368,  367,  366,  365,  364,  363,  346,  362,
+      361,  360,  359,  358,  357,  356,  355,  354,  353,  350,
+      349,  348,  347,  346,  345,  344,  341,  340,  339,  338,
+      337,  336,  335,  334,  333,  332,  331,  330,  329,  328,
+      327,  326,  325,  324,  323,  322,  321,  320,  319,  318,
+
+      317,  316,  315,  314,  313,  310,  309,  308,  307,  306,
+      305,  304,  303,  302,  301,  300,  299,  298,  297,  296,
+      295,  294,  293,  292,  291,  290,  281,  280,  279,  278,
+      277,  276,  275,  274,  273,  272,  271,  270,  269,  266,
+      265,  264,  263,  262,  261,  260,  259,  258,  257,  256,
+      255,  254,  253,  252,  251,  250,  234,  249,  248,  247,
+      244,  243,  242,  241,  240,  239,  238,  237,  236,  235,
+      234,  233,  232,  231,  230,  229,  228,  227,  226,  225,
+      221,  220,  219,  218,  217,  216,  215,  214,  213,  212,
+      211,  210,  209,  208,  207,  206,  205,  204,  203,  202,
+
+      201,  200,  199,  198,  195,  194,  193,  192,  191,  190,
+      189,  188,  187,  186,  185,  184,  183,  182,  181,  180,
+      179,  178,  177,  176,  175,  174,  173,  172,  171,  170,
+      169,  168,  167,  162,  161,  160,  159,  158,  157,  156,
+      155,  154,  153,  145,  144,  141,  140,  137,  136,  135,
+      134,  133,  132,  129,  128,  127,  126,  125,  124,  123,
+      122,  121,  120,  117,  116,  115,  110,  107,   49,   49,
+       98,   95,   87,   86,   85,   77,   69,   54,  564,    3,
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+      564
+    } ;
+
+static yyconst flex_int16_t yy_chk[832] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    5,    5,    8,   17,    5,    9,    9,   19,    9,
+       15,   15,   19,   17,   22,   23,   19,   23,   26,   26,
+       22,    8,    5,   18,   43,   43,   18,    9,   14,   14,
+       14,   14,   14,   14,   14,   14,   14,   14,   24,   24,
+       18,   20,   20,   20,   20,   27,   24,   20,   28,   27,
+
+       32,   44,   33,   20,   32,   33,   37,   35,   28,   35,
+       52,   52,   40,   40,   28,   33,   40,   53,   33,   44,
+       59,   55,   33,   37,   47,   47,   59,   47,   55,   57,
+       63,   83,  570,   40,   46,   46,   46,   57,   53,   76,
+       63,  106,   76,  138,   90,   47,   46,   46,   46,   46,
+       46,   46,   46,   46,   46,   46,   86,   89,  138,   90,
+       86,   90,  106,   83,  101,  101,   89,  320,  353,  355,
+      569,  320,  353,   89,  355,   89,  561,  355,  560,  559,
+      557,   46,   50,   50,   50,   50,   50,   50,   50,   50,
+       50,   51,   51,   51,   51,   51,   51,   51,   51,   51,
+
+      103,  429,  556,  197,  555,  554,  429,  550,  103,  103,
+      103,  103,  103,  103,  103,  103,  103,  103,  103,  197,
+      103,  103,  103,  103,  103,  103,  104,  104,  104,  104,
+      104,  104,  104,  104,  104,  105,  105,  105,  105,  105,
+      105,  105,  105,  105,  164,  164,  164,  164,  164,  164,
+      164,  164,  164,  164,  548,  546,  164,  164,  164,  164,
+      164,  164,  165,  165,  165,  165,  165,  165,  165,  165,
+      165,  166,  166,  166,  166,  166,  166,  166,  166,  166,
+      222,  347,  363,  545,  405,  544,  542,  539,  538,  537,
+      536,  535,  534,  532,  531,  347,  363,  405,  222,  223,
+
+      223,  223,  223,  223,  223,  223,  223,  223,  224,  224,
+      224,  224,  224,  224,  224,  224,  224,  242,  242,  242,
+      242,  242,  242,  242,  242,  267,  267,  267,  267,  267,
+      267,  267,  267,  267,  268,  268,  268,  268,  268,  268,
+      268,  268,  268,  311,  311,  311,  311,  311,  311,  311,
+      311,  311,  312,  312,  312,  312,  312,  312,  312,  312,
+      312,  342,  342,  342,  342,  342,  342,  342,  342,  342,
+      369,  369,  369,  369,  369,  369,  369,  369,  369,  370,
+      474,  530,  529,  528,  527,  524,  523,  522,  521,  519,
+      518,  517,  474,  516,  515,  370,  513,  370,  565,  512,
+
+      511,  565,  566,  566,  509,  566,  567,  567,  567,  567,
+      568,  568,  568,  568,  508,  507,  506,  505,  504,  503,
+      502,  501,  500,  499,  498,  497,  496,  495,  494,  493,
+      492,  491,  488,  487,  486,  485,  483,  482,  480,  479,
+      478,  477,  476,  475,  473,  472,  471,  470,  469,  468,
+      466,  465,  464,  463,  462,  461,  460,  459,  458,  457,
+      456,  455,  454,  453,  452,  451,  450,  449,  448,  447,
+      446,  445,  444,  443,  441,  440,  439,  438,  437,  436,
+      435,  434,  433,  432,  431,  430,  428,  425,  424,  423,
+      422,  421,  420,  419,  418,  417,  416,  415,  413,  412,
+
+      411,  410,  409,  408,  407,  406,  404,  403,  402,  400,
+      399,  398,  397,  396,  394,  393,  392,  391,  390,  389,
+      386,  385,  384,  383,  382,  381,  379,  378,  377,  376,
+      375,  374,  373,  372,  371,  368,  366,  364,  362,  361,
+      360,  358,  357,  356,  354,  352,  351,  350,  349,  348,
+      344,  343,  339,  338,  337,  336,  335,  334,  333,  332,
+      331,  330,  329,  327,  326,  325,  323,  322,  321,  319,
+      318,  317,  316,  315,  314,  313,  310,  309,  308,  307,
+      305,  303,  302,  301,  300,  299,  298,  297,  296,  295,
+      293,  292,  291,  290,  280,  279,  278,  277,  276,  275,
+
+      273,  272,  271,  270,  269,  265,  264,  263,  262,  261,
+      260,  259,  258,  257,  256,  255,  253,  252,  250,  249,
+      248,  247,  246,  245,  244,  243,  241,  240,  239,  237,
+      236,  235,  233,  231,  230,  229,  228,  227,  225,  221,
+      220,  219,  217,  216,  215,  214,  213,  212,  211,  210,
+      209,  208,  207,  206,  204,  202,  201,  200,  199,  198,
+      196,  194,  192,  191,  189,  188,  186,  183,  182,  181,
+      180,  177,  176,  175,  172,  171,  170,  169,  168,  167,
+      162,  161,  160,  159,  158,  157,  156,  155,  154,  153,
+      152,  151,  150,  149,  148,  147,  146,  145,  144,  143,
+
+      142,  141,  140,  139,  137,  136,  135,  133,  132,  131,
+      130,  129,  128,  127,  126,  125,  124,  123,  121,  120,
+      119,  118,  117,  116,  115,  114,  113,  112,  111,  110,
+      109,  108,  107,  100,   99,   98,   97,   96,   95,   94,
+       93,   92,   91,   88,   87,   85,   84,   82,   81,   80,
+       79,   78,   77,   75,   73,   72,   71,   70,   68,   67,
+       66,   65,   64,   62,   61,   60,   56,   54,   48,   42,
+       36,   34,   31,   30,   29,   25,   21,   16,    3,  564,
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+      564,  564,  564,  564,  564,  564,  564,  564,  564,  564,
+      564
+    } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+extern int yy_flex_debug;
+int yy_flex_debug = 0;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "lexer.l"
+/*
+ *   Copyright (C)1996-1997,1999 Ian Jackson
+ *  
+ *   This is free software; you can redistribute it and/or modify it
+ *   under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *  
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *   General Public License for more details.
+ *  
+ *   You should have received a copy of the GNU General Public License
+ *   along with userv; if not, write to the Free Software
+ *   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#line 20 "lexer.l"
+
+
+
+
+
+#include <syslog.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <errno.h>
+
+#include "config.h"
+#include "common.h"
+#include "daemon.h"
+#include "lib.h"
+#include "both.h"
+#include "tokens.h"
+
+#define HYPHEN '-'
+
+typedef int directive_fnt(int dtoken);
+static directive_fnt df_reject, df_execute, df_executefrompath;
+static directive_fnt df_executefromdirectory, df_executebuiltin;
+static directive_fnt df_errorstostderr, df_errorstosyslog, df_errorstofile;
+static directive_fnt dfg_fdwant, dfg_setflag;
+static directive_fnt df_reset, df_cd, df_userrcfile, df_include;
+static directive_fnt df_includelookup, df_includedirectory;
+static directive_fnt df_message, df_error, df_quit, df_eof;
+static directive_fnt df_if, df_catchquit, df_errorspush;
+static directive_fnt dfi_includeuserrcfile, dfi_includeclientconfig;
+/* directive functions return:
+ *  0 for success having scanned up to and including end of line but not beyond,
+ *  or tokv_error or tokv_quit.
+ * They expect to parse the whitespace before their parameters (if any).
+ */
+
+typedef int parmcondition_fnt(int ctoken, char *const *parmvalues, int *rtrue);
+static parmcondition_fnt pcf_glob, pcf_range, pcf_grep;
+/* all conditional functions return tokv_error for failure or 0 for success
+ *  at parsing and testing, in which case *rtrue is set to 0 or 1.
+ *  On success they have scanned up to and including the condition's
+ *  terminating newline; the pcf_... functions expect to parse the whitespace
+ *  between the parameter name and the condition's arguments.
+ * Otherwise they return tokv_error.
+ * The parameter-based conditionals take a list of parameter values
+ * as obtained from the parameter functions and pa_parameter,
+ * and do _not_ free it.
+ */
+
+typedef int parameter_fnt(int ptoken, char ***rvalues);
+static parameter_fnt pf_service;
+static parameter_fnt pf_callinguser, pf_serviceuser;
+static parameter_fnt pf_callinggroup, pf_servicegroup;
+static parameter_fnt pf_callingusershell, pf_serviceusershell;
+/* Parameter functions return tokv_error or 0 for success at parsing
+ * and determining the value, in which case *rvalues is made to be
+ * a mallocd null-terminated array of pointers to mallocd strings.
+ * freeparm can be used to free such an array.
+ */
+
+typedef int builtinserviceparse_fnt(char ***rnewargs);
+static builtinserviceparse_fnt bispa_none, bispa_parameter;
+/* These parse the arguments to a builtin service, including the
+ * newline at the end of the line.  *rnewargs will initially be
+ * null, indicating that no arguments are to be set; the function
+ * may store a mallocd array of mallocd strings in it,
+ * containing the arguments it wishes to have set (null-pointer
+ * terminated).
+ */
+
+static int yylex(void);
+/* Returns a token (which may be an eof or error exception) */
+
+static directive_fnt *lr_dir;
+static parmcondition_fnt *lr_parmcond;
+static builtinserviceparse_fnt *lr_bispa;
+static builtinserviceexec_fnt *lr_bisexec;
+static parameter_fnt *lr_parameter;
+static int lr_loglevel, lr_logfacility, lr_min, lr_max, *lr_flag;
+static int lr_flagval, lr_controlend;
+static int lr_fdwant_readwrite; /* -1=never, 0=opt, 1=always */
+
+/* Forward declarations of things used in lexer and parser */
+
+struct parser_state {
+  int lineno, reportlineno, notedreferer, isinternal;
+  const char *filename;
+  struct stat filestab;
+  YY_BUFFER_STATE ybuf;
+  struct parser_state *upstate;
+};
+
+static struct parser_state *cstate;
+
+struct error_handling {
+  int handling; /* One of the error handling modes tokt_ehandlemode */
+  int logfacility, loglevel;
+  int filekeep; /* File is in use by higher-level errors-push, leave it open */
+  FILE *file;
+  char *filename;
+};
+
+static struct error_handling eh = { tokv_word_errorstostderr, 0,0,0,0,0 };
+
+static int dequote(char *inplace);
+static void countnewlines(void);
+
+#define YY_NO_UNPUT
+
+#line 952 "<stdout>"
+
+#define INITIAL 0
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap (void );
+#else
+extern int yywrap (void );
+#endif
+#endif
+
+    static void yyunput (int c,char *buf_ptr  );
+    
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int );
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * );
+#endif
+
+#ifndef YY_NO_INPUT
+
+#ifdef __cplusplus
+static int yyinput (void );
+#else
+static int input (void );
+#endif
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO (void) fwrite( yytext, yyleng, 1, yyout )
+#endif
+
+/* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+       if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+               { \
+               int c = '*'; \
+               size_t n; \
+               for ( n = 0; n < max_size && \
+                            (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+                       buf[n] = (char) c; \
+               if ( c == '\n' ) \
+                       buf[n++] = (char) c; \
+               if ( c == EOF && ferror( yyin ) ) \
+                       YY_FATAL_ERROR( "input in flex scanner failed" ); \
+               result = n; \
+               } \
+       else \
+               { \
+               errno=0; \
+               while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \
+                       { \
+                       if( errno != EINTR) \
+                               { \
+                               YY_FATAL_ERROR( "input in flex scanner failed" ); \
+                               break; \
+                               } \
+                       errno=0; \
+                       clearerr(yyin); \
+                       } \
+               }\
+\
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+#endif
+
+/* end tables serialization structures and prototypes */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int yylex (void);
+
+#define YY_DECL int yylex (void)
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+       YY_USER_ACTION
+
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+       register yy_state_type yy_current_state;
+       register char *yy_cp, *yy_bp;
+       register int yy_act;
+    
+#line 144 "lexer.l"
+
+
+#line 1106 "<stdout>"
+
+       if ( (yy_init) )
+               {
+               (yy_init) = 0;
+
+#ifdef YY_USER_INIT
+               YY_USER_INIT;
+#endif
+
+               if ( ! (yy_start) )
+                       (yy_start) = 1; /* first start state */
+
+               if ( ! yyin )
+                       yyin = stdin;
+
+               if ( ! yyout )
+                       yyout = stdout;
+
+               if ( ! YY_CURRENT_BUFFER ) {
+                       yyensure_buffer_stack ();
+                       YY_CURRENT_BUFFER_LVALUE =
+                               yy_create_buffer(yyin,YY_BUF_SIZE );
+               }
+
+               yy_load_buffer_state( );
+               }
+
+       while ( 1 )             /* loops until end-of-file is reached */
+               {
+               yy_cp = (yy_c_buf_p);
+
+               /* Support of yytext. */
+               *yy_cp = (yy_hold_char);
+
+               /* yy_bp points to the position in yy_ch_buf of the start of
+                * the current run.
+                */
+               yy_bp = yy_cp;
+
+               yy_current_state = (yy_start);
+yy_match:
+               do
+                       {
+                       register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+                       if ( yy_accept[yy_current_state] )
+                               {
+                               (yy_last_accepting_state) = yy_current_state;
+                               (yy_last_accepting_cpos) = yy_cp;
+                               }
+                       while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+                               {
+                               yy_current_state = (int) yy_def[yy_current_state];
+                               if ( yy_current_state >= 565 )
+                                       yy_c = yy_meta[(unsigned int) yy_c];
+                               }
+                       yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+                       ++yy_cp;
+                       }
+               while ( yy_base[yy_current_state] != 780 );
+
+yy_find_action:
+               yy_act = yy_accept[yy_current_state];
+               if ( yy_act == 0 )
+                       { /* have to back up */
+                       yy_cp = (yy_last_accepting_cpos);
+                       yy_current_state = (yy_last_accepting_state);
+                       yy_act = yy_accept[yy_current_state];
+                       }
+
+               YY_DO_BEFORE_ACTION;
+
+do_action:     /* This label is used only to access EOF actions. */
+
+               switch ( yy_act )
+       { /* beginning of action switch */
+                       case 0: /* must back up */
+                       /* undo the effects of YY_DO_BEFORE_ACTION */
+                       *yy_cp = (yy_hold_char);
+                       yy_cp = (yy_last_accepting_cpos);
+                       yy_current_state = (yy_last_accepting_state);
+                       goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 146 "lexer.l"
+{ lr_dir= df_reject; return tokv_word_reject; }
+       YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 147 "lexer.l"
+{ lr_dir= df_executefromdirectory; return tokv_word_executefromdirectory; }
+       YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 148 "lexer.l"
+{ lr_dir= df_executefrompath; return tokv_word_executefrompath; }
+       YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 149 "lexer.l"
+{ lr_dir= df_executebuiltin; return tokv_word_executebuiltin; }
+       YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 150 "lexer.l"
+{ lr_dir= df_errorstostderr; return tokv_word_errorstostderr; }
+       YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 151 "lexer.l"
+{ lr_dir= df_errorstosyslog; return tokv_word_errorstosyslog; }
+       YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 152 "lexer.l"
+{ lr_dir= df_errorstofile; return tokv_word_errorstofile; }
+       YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 153 "lexer.l"
+{ lr_dir= dfg_fdwant; lr_fdwant_readwrite=1; return tokv_word_requirefd; }
+       YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 154 "lexer.l"
+{ lr_dir= dfg_fdwant; lr_fdwant_readwrite=0; return tokv_word_allowfd; }
+       YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 155 "lexer.l"
+{ lr_dir= dfg_fdwant; lr_fdwant_readwrite=0; return tokv_word_nullfd; }
+       YY_BREAK
+case 11:
+YY_RULE_SETUP
+#line 156 "lexer.l"
+{ lr_dir= dfg_fdwant; lr_fdwant_readwrite=-1; return tokv_word_rejectfd; }
+       YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 157 "lexer.l"
+{ lr_dir= dfg_fdwant; lr_fdwant_readwrite=-1; return tokv_word_ignorefd; }
+       YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 158 "lexer.l"
+{ lr_dir= dfg_setflag; lr_flag= &setenvironment; lr_flagval= 1; return tokv_word_setenvironment; }
+       YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 159 "lexer.l"
+{ lr_dir= dfg_setflag; lr_flag= &setenvironment; lr_flagval= 0; return tokv_word_nosetenvironment; }
+       YY_BREAK
+case 15:
+YY_RULE_SETUP
+#line 160 "lexer.l"
+{ lr_dir= dfg_setflag; lr_flag= &suppressargs; lr_flagval= 1; return tokv_word_suppressargs; }
+       YY_BREAK
+case 16:
+YY_RULE_SETUP
+#line 161 "lexer.l"
+{ lr_dir= dfg_setflag; lr_flag= &suppressargs; lr_flagval= 0; return tokv_word_nosuppressargs; }
+       YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 162 "lexer.l"
+{ lr_dir= dfg_setflag; lr_flag= &disconnecthup; lr_flagval= 1; return tokv_word_disconnecthup; }
+       YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 163 "lexer.l"
+{ lr_dir= dfg_setflag; lr_flag= &disconnecthup; lr_flagval= 0; return tokv_word_nodisconnecthup; }
+       YY_BREAK
+case 19:
+YY_RULE_SETUP
+#line 164 "lexer.l"
+{ lr_dir= df_cd; return tokv_word_cd; }
+       YY_BREAK
+case 20:
+YY_RULE_SETUP
+#line 165 "lexer.l"
+{ lr_dir= df_userrcfile; return tokv_word_userrcfile; }
+       YY_BREAK
+case 21:
+YY_RULE_SETUP
+#line 166 "lexer.l"
+{ lr_dir= df_include; return tokv_word_include; }
+       YY_BREAK
+case 22:
+YY_RULE_SETUP
+#line 167 "lexer.l"
+{ lr_dir= df_include; return tokv_word_includeifexist; }
+       YY_BREAK
+case 23:
+YY_RULE_SETUP
+#line 168 "lexer.l"
+{ lr_dir= df_includelookup; return tokv_word_includelookup; }
+       YY_BREAK
+case 24:
+YY_RULE_SETUP
+#line 169 "lexer.l"
+{ lr_dir= df_includelookup; return tokv_word_includelookupall; }
+       YY_BREAK
+case 25:
+YY_RULE_SETUP
+#line 170 "lexer.l"
+{ lr_dir= df_includedirectory; return tokv_word_includedirectory; }
+       YY_BREAK
+case 26:
+YY_RULE_SETUP
+#line 171 "lexer.l"
+{ lr_dir= df_message; return tokv_word_message; }
+       YY_BREAK
+case 27:
+YY_RULE_SETUP
+#line 172 "lexer.l"
+{ lr_dir= df_include; return tokv_word_includesysconfig; }
+       YY_BREAK
+case 28:
+YY_RULE_SETUP
+#line 173 "lexer.l"
+{ lr_dir= dfi_includeuserrcfile; return tokv_word_includeuserrcfile; }
+       YY_BREAK
+case 29:
+YY_RULE_SETUP
+#line 174 "lexer.l"
+{ lr_dir= dfi_includeclientconfig; return tokv_word_includeclientconfig; }
+       YY_BREAK
+case 30:
+YY_RULE_SETUP
+#line 175 "lexer.l"
+{ lr_dir= df_quit; return tokv_word_quit; }
+       YY_BREAK
+case 31:
+YY_RULE_SETUP
+#line 176 "lexer.l"
+{ lr_dir= df_eof; return tokv_word_eof; }
+       YY_BREAK
+case 32:
+YY_RULE_SETUP
+#line 177 "lexer.l"
+{ lr_dir= df_if; return tokv_word_if; }
+       YY_BREAK
+case 33:
+YY_RULE_SETUP
+#line 178 "lexer.l"
+{ lr_dir= df_catchquit; return tokv_word_catchquit; }
+       YY_BREAK
+case 34:
+YY_RULE_SETUP
+#line 179 "lexer.l"
+{ lr_dir= df_errorspush; return tokv_word_errorspush; }
+       YY_BREAK
+case 35:
+YY_RULE_SETUP
+#line 180 "lexer.l"
+{ lr_controlend= tokv_word_if; return tokv_word_elif; }
+       YY_BREAK
+case 36:
+YY_RULE_SETUP
+#line 181 "lexer.l"
+{ lr_controlend= tokv_word_if; return tokv_word_else; }
+       YY_BREAK
+case 37:
+YY_RULE_SETUP
+#line 182 "lexer.l"
+{ lr_controlend= tokv_word_if; return tokv_word_fi; }
+       YY_BREAK
+case 38:
+YY_RULE_SETUP
+#line 183 "lexer.l"
+{ lr_controlend= tokv_word_catchquit; return tokv_word_hctac; }
+       YY_BREAK
+case 39:
+YY_RULE_SETUP
+#line 184 "lexer.l"
+{ lr_controlend= tokv_word_errorspush; return tokv_word_srorre; }
+       YY_BREAK
+case 40:
+YY_RULE_SETUP
+#line 185 "lexer.l"
+{ lr_parmcond= pcf_glob; return tokv_word_glob; }
+       YY_BREAK
+case 41:
+YY_RULE_SETUP
+#line 186 "lexer.l"
+{ lr_parmcond= pcf_range; return tokv_word_range; }
+       YY_BREAK
+case 42:
+YY_RULE_SETUP
+#line 187 "lexer.l"
+{ lr_parmcond= pcf_grep; return tokv_word_grep; }
+       YY_BREAK
+case 43:
+YY_RULE_SETUP
+#line 188 "lexer.l"
+{ lr_bispa= bispa_none; lr_bisexec= bisexec_environment; return tokv_word_environment; }
+       YY_BREAK
+case 44:
+YY_RULE_SETUP
+#line 189 "lexer.l"
+{ lr_bispa= bispa_parameter; lr_bisexec= bisexec_parameter; return tokv_word_parameter; }
+       YY_BREAK
+case 45:
+YY_RULE_SETUP
+#line 190 "lexer.l"
+{ lr_bispa= bispa_none; lr_bisexec= bisexec_version; return tokv_word_version; }
+       YY_BREAK
+case 46:
+YY_RULE_SETUP
+#line 191 "lexer.l"
+{ lr_bispa= bispa_none; lr_bisexec= bisexec_toplevel; return tokv_word_toplevel; }
+       YY_BREAK
+case 47:
+YY_RULE_SETUP
+#line 192 "lexer.l"
+{ lr_bispa= bispa_none; lr_bisexec= bisexec_override; return tokv_word_override; }
+       YY_BREAK
+case 48:
+YY_RULE_SETUP
+#line 193 "lexer.l"
+{ lr_bispa= bispa_none; lr_bisexec= bisexec_shutdown; return tokv_word_shutdown; }
+       YY_BREAK
+case 49:
+YY_RULE_SETUP
+#line 194 "lexer.l"
+{ lr_bispa= bispa_none; lr_bisexec= bisexec_reset; lr_dir= df_reset; return tokv_word_reset; }
+       YY_BREAK
+case 50:
+YY_RULE_SETUP
+#line 195 "lexer.l"
+{ lr_bispa= bispa_none; lr_bisexec= bisexec_execute; lr_dir= df_execute; return tokv_word_execute; }
+       YY_BREAK
+case 51:
+YY_RULE_SETUP
+#line 196 "lexer.l"
+{ lr_bispa= bispa_none; lr_bisexec= bisexec_help; return tokv_word_help; }
+       YY_BREAK
+case 52:
+YY_RULE_SETUP
+#line 197 "lexer.l"
+{ lr_parameter= pf_service; return tokv_word_service; }
+       YY_BREAK
+case 53:
+YY_RULE_SETUP
+#line 198 "lexer.l"
+{ lr_parameter= pf_callinguser; return tokv_word_callinguser; }
+       YY_BREAK
+case 54:
+YY_RULE_SETUP
+#line 199 "lexer.l"
+{ lr_parameter= pf_callinggroup; return tokv_word_callinggroup; }
+       YY_BREAK
+case 55:
+YY_RULE_SETUP
+#line 200 "lexer.l"
+{ lr_parameter= pf_callingusershell; return tokv_word_callingusershell; }
+       YY_BREAK
+case 56:
+YY_RULE_SETUP
+#line 201 "lexer.l"
+{ lr_parameter= pf_serviceuser; return tokv_word_serviceuser; }
+       YY_BREAK
+case 57:
+YY_RULE_SETUP
+#line 202 "lexer.l"
+{ lr_parameter= pf_servicegroup; return tokv_word_servicegroup; }
+       YY_BREAK
+case 58:
+YY_RULE_SETUP
+#line 203 "lexer.l"
+{ lr_parameter= pf_serviceusershell; return tokv_word_serviceusershell; }
+       YY_BREAK
+case 59:
+YY_RULE_SETUP
+#line 204 "lexer.l"
+{ lr_loglevel= LOG_DEBUG; return tokv_syslog_debug; }
+       YY_BREAK
+case 60:
+YY_RULE_SETUP
+#line 205 "lexer.l"
+{ lr_loglevel= LOG_INFO; return tokv_syslog_info; }
+       YY_BREAK
+case 61:
+YY_RULE_SETUP
+#line 206 "lexer.l"
+{ lr_loglevel= LOG_NOTICE; return tokv_syslog_notice; }
+       YY_BREAK
+case 62:
+YY_RULE_SETUP
+#line 207 "lexer.l"
+{ lr_loglevel= LOG_WARNING; return tokv_syslog_warning; }
+       YY_BREAK
+case 63:
+YY_RULE_SETUP
+#line 208 "lexer.l"
+{ lr_loglevel= LOG_ERR; return tokv_syslog_err; }
+       YY_BREAK
+case 64:
+YY_RULE_SETUP
+#line 209 "lexer.l"
+{ lr_loglevel= LOG_CRIT; return tokv_syslog_crit; }
+       YY_BREAK
+case 65:
+YY_RULE_SETUP
+#line 210 "lexer.l"
+{ lr_loglevel= LOG_ALERT; return tokv_syslog_alert; }
+       YY_BREAK
+case 66:
+YY_RULE_SETUP
+#line 211 "lexer.l"
+{ lr_loglevel= LOG_EMERG; return tokv_syslog_emerg; }
+       YY_BREAK
+case 67:
+YY_RULE_SETUP
+#line 212 "lexer.l"
+{ lr_logfacility= LOG_AUTHPRIV; return tokv_syslog_authpriv; }
+       YY_BREAK
+case 68:
+YY_RULE_SETUP
+#line 213 "lexer.l"
+{ lr_logfacility= LOG_CRON; return tokv_syslog_cron; }
+       YY_BREAK
+case 69:
+YY_RULE_SETUP
+#line 214 "lexer.l"
+{ lr_logfacility= LOG_DAEMON; return tokv_syslog_daemon; }
+       YY_BREAK
+case 70:
+YY_RULE_SETUP
+#line 215 "lexer.l"
+{ lr_logfacility= LOG_KERN; return tokv_syslog_kern; }
+       YY_BREAK
+case 71:
+YY_RULE_SETUP
+#line 216 "lexer.l"
+{ lr_logfacility= LOG_LPR; return tokv_syslog_lpr; }
+       YY_BREAK
+case 72:
+YY_RULE_SETUP
+#line 217 "lexer.l"
+{ lr_logfacility= LOG_MAIL; return tokv_syslog_mail; }
+       YY_BREAK
+case 73:
+YY_RULE_SETUP
+#line 218 "lexer.l"
+{ lr_logfacility= LOG_NEWS; return tokv_syslog_news; }
+       YY_BREAK
+case 74:
+YY_RULE_SETUP
+#line 219 "lexer.l"
+{ lr_logfacility= LOG_SYSLOG; return tokv_syslog_syslog; }
+       YY_BREAK
+case 75:
+YY_RULE_SETUP
+#line 220 "lexer.l"
+{ lr_logfacility= LOG_USER; return tokv_syslog_user; }
+       YY_BREAK
+case 76:
+YY_RULE_SETUP
+#line 221 "lexer.l"
+{ lr_logfacility= LOG_UUCP; return tokv_syslog_uucp; }
+       YY_BREAK
+case 77:
+YY_RULE_SETUP
+#line 222 "lexer.l"
+{ lr_logfacility= LOG_LOCAL0; return tokv_syslog_local0; }
+       YY_BREAK
+case 78:
+YY_RULE_SETUP
+#line 223 "lexer.l"
+{ lr_logfacility= LOG_LOCAL1; return tokv_syslog_local1; }
+       YY_BREAK
+case 79:
+YY_RULE_SETUP
+#line 224 "lexer.l"
+{ lr_logfacility= LOG_LOCAL2; return tokv_syslog_local2; }
+       YY_BREAK
+case 80:
+YY_RULE_SETUP
+#line 225 "lexer.l"
+{ lr_logfacility= LOG_LOCAL3; return tokv_syslog_local3; }
+       YY_BREAK
+case 81:
+YY_RULE_SETUP
+#line 226 "lexer.l"
+{ lr_logfacility= LOG_LOCAL4; return tokv_syslog_local4; }
+       YY_BREAK
+case 82:
+YY_RULE_SETUP
+#line 227 "lexer.l"
+{ lr_logfacility= LOG_LOCAL5; return tokv_syslog_local5; }
+       YY_BREAK
+case 83:
+YY_RULE_SETUP
+#line 228 "lexer.l"
+{ lr_logfacility= LOG_LOCAL6; return tokv_syslog_local6; }
+       YY_BREAK
+case 84:
+YY_RULE_SETUP
+#line 229 "lexer.l"
+{ lr_logfacility= LOG_LOCAL7; return tokv_syslog_local7; }
+       YY_BREAK
+case 85:
+YY_RULE_SETUP
+#line 230 "lexer.l"
+{ return tokv_word_read; }
+       YY_BREAK
+case 86:
+YY_RULE_SETUP
+#line 231 "lexer.l"
+{ return tokv_word_write; }
+       YY_BREAK
+case 87:
+YY_RULE_SETUP
+#line 232 "lexer.l"
+{ return tokv_dollar; }
+       YY_BREAK
+case 88:
+YY_RULE_SETUP
+#line 233 "lexer.l"
+{ return tokv_openparen; }
+       YY_BREAK
+case 89:
+YY_RULE_SETUP
+#line 234 "lexer.l"
+{ return tokv_closeparen; }
+       YY_BREAK
+case 90:
+YY_RULE_SETUP
+#line 235 "lexer.l"
+{ return tokv_not; }
+       YY_BREAK
+case 91:
+YY_RULE_SETUP
+#line 236 "lexer.l"
+{ return tokv_and; }
+       YY_BREAK
+case 92:
+YY_RULE_SETUP
+#line 237 "lexer.l"
+{ return tokv_or; }
+       YY_BREAK
+case 93:
+YY_RULE_SETUP
+#line 238 "lexer.l"
+{ lr_dir= df_error; lr_loglevel= LOG_ERR; return tokv_word_error; }
+       YY_BREAK
+case 94:
+YY_RULE_SETUP
+#line 242 "lexer.l"
+{
+                         char *ep;
+                         lr_min=lr_max= (int)strtoul(yytext,&ep,10);
+                         assert(!*ep);
+                         return tokv_ordinal;
+                       }
+       YY_BREAK
+case 95:
+YY_RULE_SETUP
+#line 248 "lexer.l"
+{
+                         char *ep;
+                         lr_min= (int)strtoul(yytext,&ep,10);
+                         assert(*ep == HYPHEN);
+                         ep++;  assert(*ep);
+                         lr_max= (int)strtoul(ep,&ep,10);
+                         assert(!*ep);
+                         if (lr_max < lr_min)
+                           return parseerrprint("fd range has min > max");
+                         return tokv_fdrange;
+                       }
+       YY_BREAK
+case 96:
+YY_RULE_SETUP
+#line 259 "lexer.l"
+{
+                         char *ep;
+                         lr_min= (int)strtoul(yytext,&ep,10);
+                         assert(*ep == HYPHEN);
+                         ep++;  assert(!*ep);
+                         lr_max=-1;
+                         return tokv_fdstoend;
+                       }
+       YY_BREAK
+case 97:
+/* rule 97 can match eol */
+YY_RULE_SETUP
+#line 267 "lexer.l"
+countnewlines(); return tokv_lwsp;
+       YY_BREAK
+case 98:
+YY_RULE_SETUP
+#line 268 "lexer.l"
+return tokv_lwsp;
+       YY_BREAK
+case 99:
+/* rule 99 can match eol */
+YY_RULE_SETUP
+#line 269 "lexer.l"
+cstate->lineno++; return tokv_newline;
+       YY_BREAK
+case 100:
+/* rule 100 can match eol */
+YY_RULE_SETUP
+#line 270 "lexer.l"
+cstate->lineno++; return tokv_newline;
+       YY_BREAK
+case 101:
+YY_RULE_SETUP
+#line 271 "lexer.l"
+return parseerrprint("missing newline at eof after comment");
+       YY_BREAK
+case 102:
+/* rule 102 can match eol */
+YY_RULE_SETUP
+#line 272 "lexer.l"
+{
+                         countnewlines();
+                         return dequote(yytext);
+                       }
+       YY_BREAK
+case 103:
+YY_RULE_SETUP
+#line 276 "lexer.l"
+return tokv_barestring;
+       YY_BREAK
+case YY_STATE_EOF(INITIAL):
+#line 277 "lexer.l"
+return tokv_eof;
+       YY_BREAK
+case 104:
+YY_RULE_SETUP
+#line 278 "lexer.l"
+return parseerrprint("misquoted or unterminated string");
+       YY_BREAK
+case 105:
+YY_RULE_SETUP
+#line 279 "lexer.l"
+return parseerrprint("unexpected backslash");
+       YY_BREAK
+case 106:
+YY_RULE_SETUP
+#line 280 "lexer.l"
+abort(); /* expect lex warning "rule cannot be matched" */
+       YY_BREAK
+case 107:
+YY_RULE_SETUP
+#line 283 "lexer.l"
+ECHO;
+       YY_BREAK
+#line 1757 "<stdout>"
+
+       case YY_END_OF_BUFFER:
+               {
+               /* Amount of text matched not including the EOB char. */
+               int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1;
+
+               /* Undo the effects of YY_DO_BEFORE_ACTION. */
+               *yy_cp = (yy_hold_char);
+               YY_RESTORE_YY_MORE_OFFSET
+
+               if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+                       {
+                       /* We're scanning a new file or input source.  It's
+                        * possible that this happened because the user
+                        * just pointed yyin at a new source and called
+                        * yylex().  If so, then we have to assure
+                        * consistency between YY_CURRENT_BUFFER and our
+                        * globals.  Here is the right place to do so, because
+                        * this is the first action (other than possibly a
+                        * back-up) that will match for the new input source.
+                        */
+                       (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+                       YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+                       YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+                       }
+
+               /* Note that here we test for yy_c_buf_p "<=" to the position
+                * of the first EOB in the buffer, since yy_c_buf_p will
+                * already have been incremented past the NUL character
+                * (since all states make transitions on EOB to the
+                * end-of-buffer state).  Contrast this with the test
+                * in input().
+                */
+               if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+                       { /* This was really a NUL. */
+                       yy_state_type yy_next_state;
+
+                       (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text;
+
+                       yy_current_state = yy_get_previous_state(  );
+
+                       /* Okay, we're now positioned to make the NUL
+                        * transition.  We couldn't have
+                        * yy_get_previous_state() go ahead and do it
+                        * for us because it doesn't know how to deal
+                        * with the possibility of jamming (and we don't
+                        * want to build jamming into it because then it
+                        * will run more slowly).
+                        */
+
+                       yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+                       yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+
+                       if ( yy_next_state )
+                               {
+                               /* Consume the NUL. */
+                               yy_cp = ++(yy_c_buf_p);
+                               yy_current_state = yy_next_state;
+                               goto yy_match;
+                               }
+
+                       else
+                               {
+                               yy_cp = (yy_c_buf_p);
+                               goto yy_find_action;
+                               }
+                       }
+
+               else switch ( yy_get_next_buffer(  ) )
+                       {
+                       case EOB_ACT_END_OF_FILE:
+                               {
+                               (yy_did_buffer_switch_on_eof) = 0;
+
+                               if ( yywrap( ) )
+                                       {
+                                       /* Note: because we've taken care in
+                                        * yy_get_next_buffer() to have set up
+                                        * yytext, we can now set up
+                                        * yy_c_buf_p so that if some total
+                                        * hoser (like flex itself) wants to
+                                        * call the scanner after we return the
+                                        * YY_NULL, it'll still work - another
+                                        * YY_NULL will get returned.
+                                        */
+                                       (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ;
+
+                                       yy_act = YY_STATE_EOF(YY_START);
+                                       goto do_action;
+                                       }
+
+                               else
+                                       {
+                                       if ( ! (yy_did_buffer_switch_on_eof) )
+                                               YY_NEW_FILE;
+                                       }
+                               break;
+                               }
+
+                       case EOB_ACT_CONTINUE_SCAN:
+                               (yy_c_buf_p) =
+                                       (yytext_ptr) + yy_amount_of_matched_text;
+
+                               yy_current_state = yy_get_previous_state(  );
+
+                               yy_cp = (yy_c_buf_p);
+                               yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+                               goto yy_match;
+
+                       case EOB_ACT_LAST_MATCH:
+                               (yy_c_buf_p) =
+                               &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)];
+
+                               yy_current_state = yy_get_previous_state(  );
+
+                               yy_cp = (yy_c_buf_p);
+                               yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+                               goto yy_find_action;
+                       }
+               break;
+               }
+
+       default:
+               YY_FATAL_ERROR(
+                       "fatal flex scanner internal error--no action found" );
+       } /* end of action switch */
+               } /* end of scanning one token */
+} /* end of yylex */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ *     EOB_ACT_LAST_MATCH -
+ *     EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ *     EOB_ACT_END_OF_FILE - end of file
+ */
+static int yy_get_next_buffer (void)
+{
+       register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+       register char *source = (yytext_ptr);
+       register int number_to_move, i;
+       int ret_val;
+
+       if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
+               YY_FATAL_ERROR(
+               "fatal flex scanner internal error--end of buffer missed" );
+
+       if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+               { /* Don't try to fill the buffer, so this is an EOF. */
+               if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 )
+                       {
+                       /* We matched a single character, the EOB, so
+                        * treat this as a final EOF.
+                        */
+                       return EOB_ACT_END_OF_FILE;
+                       }
+
+               else
+                       {
+                       /* We matched some text prior to the EOB, first
+                        * process it.
+                        */
+                       return EOB_ACT_LAST_MATCH;
+                       }
+               }
+
+       /* Try to read more data. */
+
+       /* First move last chars to start of buffer. */
+       number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1;
+
+       for ( i = 0; i < number_to_move; ++i )
+               *(dest++) = *(source++);
+
+       if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+               /* don't do the read, it's not guaranteed to return an EOF,
+                * just force an EOF
+                */
+               YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0;
+
+       else
+               {
+                       size_t num_to_read =
+                       YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+               while ( num_to_read <= 0 )
+                       { /* Not enough room in the buffer - grow it. */
+
+                       /* just a shorter name for the current buffer */
+                       YY_BUFFER_STATE b = YY_CURRENT_BUFFER;
+
+                       int yy_c_buf_p_offset =
+                               (int) ((yy_c_buf_p) - b->yy_ch_buf);
+
+                       if ( b->yy_is_our_buffer )
+                               {
+                               int new_size = b->yy_buf_size * 2;
+
+                               if ( new_size <= 0 )
+                                       b->yy_buf_size += b->yy_buf_size / 8;
+                               else
+                                       b->yy_buf_size *= 2;
+
+                               b->yy_ch_buf = (char *)
+                                       /* Include room in for 2 EOB chars. */
+                                       yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2  );
+                               }
+                       else
+                               /* Can't grow it, we don't own it. */
+                               b->yy_ch_buf = 0;
+
+                       if ( ! b->yy_ch_buf )
+                               YY_FATAL_ERROR(
+                               "fatal error - scanner input buffer overflow" );
+
+                       (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+                       num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+                                               number_to_move - 1;
+
+                       }
+
+               if ( num_to_read > YY_READ_BUF_SIZE )
+                       num_to_read = YY_READ_BUF_SIZE;
+
+               /* Read in more data. */
+               YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+                       (yy_n_chars), num_to_read );
+
+               YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+               }
+
+       if ( (yy_n_chars) == 0 )
+               {
+               if ( number_to_move == YY_MORE_ADJ )
+                       {
+                       ret_val = EOB_ACT_END_OF_FILE;
+                       yyrestart(yyin  );
+                       }
+
+               else
+                       {
+                       ret_val = EOB_ACT_LAST_MATCH;
+                       YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+                               YY_BUFFER_EOF_PENDING;
+                       }
+               }
+
+       else
+               ret_val = EOB_ACT_CONTINUE_SCAN;
+
+       (yy_n_chars) += number_to_move;
+       YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR;
+       YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR;
+
+       (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+       return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+    static yy_state_type yy_get_previous_state (void)
+{
+       register yy_state_type yy_current_state;
+       register char *yy_cp;
+    
+       yy_current_state = (yy_start);
+
+       for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp )
+               {
+               register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+               if ( yy_accept[yy_current_state] )
+                       {
+                       (yy_last_accepting_state) = yy_current_state;
+                       (yy_last_accepting_cpos) = yy_cp;
+                       }
+               while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+                       {
+                       yy_current_state = (int) yy_def[yy_current_state];
+                       if ( yy_current_state >= 565 )
+                               yy_c = yy_meta[(unsigned int) yy_c];
+                       }
+               yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+               }
+
+       return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ *     next_state = yy_try_NUL_trans( current_state );
+ */
+    static yy_state_type yy_try_NUL_trans  (yy_state_type yy_current_state )
+{
+       register int yy_is_jam;
+       register char *yy_cp = (yy_c_buf_p);
+
+       register YY_CHAR yy_c = 1;
+       if ( yy_accept[yy_current_state] )
+               {
+               (yy_last_accepting_state) = yy_current_state;
+               (yy_last_accepting_cpos) = yy_cp;
+               }
+       while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+               {
+               yy_current_state = (int) yy_def[yy_current_state];
+               if ( yy_current_state >= 565 )
+                       yy_c = yy_meta[(unsigned int) yy_c];
+               }
+       yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+       yy_is_jam = (yy_current_state == 564);
+
+       return yy_is_jam ? 0 : yy_current_state;
+}
+
+    static void yyunput (int c, register char * yy_bp )
+{
+       register char *yy_cp;
+    
+    yy_cp = (yy_c_buf_p);
+
+       /* undo effects of setting up yytext */
+       *yy_cp = (yy_hold_char);
+
+       if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+               { /* need to shift things up to make room */
+               /* +2 for EOB chars. */
+               register int number_to_move = (yy_n_chars) + 2;
+               register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[
+                                       YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2];
+               register char *source =
+                               &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move];
+
+               while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+                       *--dest = *--source;
+
+               yy_cp += (int) (dest - source);
+               yy_bp += (int) (dest - source);
+               YY_CURRENT_BUFFER_LVALUE->yy_n_chars =
+                       (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_buf_size;
+
+               if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+                       YY_FATAL_ERROR( "flex scanner push-back overflow" );
+               }
+
+       *--yy_cp = (char) c;
+
+       (yytext_ptr) = yy_bp;
+       (yy_hold_char) = *yy_cp;
+       (yy_c_buf_p) = yy_cp;
+}
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+    static int yyinput (void)
+#else
+    static int input  (void)
+#endif
+
+{
+       int c;
+    
+       *(yy_c_buf_p) = (yy_hold_char);
+
+       if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR )
+               {
+               /* yy_c_buf_p now points to the character we want to return.
+                * If this occurs *before* the EOB characters, then it's a
+                * valid NUL; if not, then we've hit the end of the buffer.
+                */
+               if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+                       /* This was really a NUL. */
+                       *(yy_c_buf_p) = '\0';
+
+               else
+                       { /* need more input */
+                       int offset = (yy_c_buf_p) - (yytext_ptr);
+                       ++(yy_c_buf_p);
+
+                       switch ( yy_get_next_buffer(  ) )
+                               {
+                               case EOB_ACT_LAST_MATCH:
+                                       /* This happens because yy_g_n_b()
+                                        * sees that we've accumulated a
+                                        * token and flags that we need to
+                                        * try matching the token before
+                                        * proceeding.  But for input(),
+                                        * there's no matching to consider.
+                                        * So convert the EOB_ACT_LAST_MATCH
+                                        * to EOB_ACT_END_OF_FILE.
+                                        */
+
+                                       /* Reset buffer status. */
+                                       yyrestart(yyin );
+
+                                       /*FALLTHROUGH*/
+
+                               case EOB_ACT_END_OF_FILE:
+                                       {
+                                       if ( yywrap( ) )
+                                               return EOF;
+
+                                       if ( ! (yy_did_buffer_switch_on_eof) )
+                                               YY_NEW_FILE;
+#ifdef __cplusplus
+                                       return yyinput();
+#else
+                                       return input();
+#endif
+                                       }
+
+                               case EOB_ACT_CONTINUE_SCAN:
+                                       (yy_c_buf_p) = (yytext_ptr) + offset;
+                                       break;
+                               }
+                       }
+               }
+
+       c = *(unsigned char *) (yy_c_buf_p);    /* cast for 8-bit char's */
+       *(yy_c_buf_p) = '\0';   /* preserve yytext */
+       (yy_hold_char) = *++(yy_c_buf_p);
+
+       return c;
+}
+#endif /* ifndef YY_NO_INPUT */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ * 
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+    void yyrestart  (FILE * input_file )
+{
+    
+       if ( ! YY_CURRENT_BUFFER ){
+        yyensure_buffer_stack ();
+               YY_CURRENT_BUFFER_LVALUE =
+            yy_create_buffer(yyin,YY_BUF_SIZE );
+       }
+
+       yy_init_buffer(YY_CURRENT_BUFFER,input_file );
+       yy_load_buffer_state( );
+}
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ * 
+ */
+    void yy_switch_to_buffer  (YY_BUFFER_STATE  new_buffer )
+{
+    
+       /* TODO. We should be able to replace this entire function body
+        * with
+        *              yypop_buffer_state();
+        *              yypush_buffer_state(new_buffer);
+     */
+       yyensure_buffer_stack ();
+       if ( YY_CURRENT_BUFFER == new_buffer )
+               return;
+
+       if ( YY_CURRENT_BUFFER )
+               {
+               /* Flush out information for old buffer. */
+               *(yy_c_buf_p) = (yy_hold_char);
+               YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+               YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+               }
+
+       YY_CURRENT_BUFFER_LVALUE = new_buffer;
+       yy_load_buffer_state( );
+
+       /* We don't actually know whether we did this switch during
+        * EOF (yywrap()) processing, but the only time this flag
+        * is looked at is after yywrap() is called, so it's safe
+        * to go ahead and always set it.
+        */
+       (yy_did_buffer_switch_on_eof) = 1;
+}
+
+static void yy_load_buffer_state  (void)
+{
+       (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+       (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+       yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+       (yy_hold_char) = *(yy_c_buf_p);
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ * 
+ * @return the allocated buffer state.
+ */
+    YY_BUFFER_STATE yy_create_buffer  (FILE * file, int  size )
+{
+       YY_BUFFER_STATE b;
+    
+       b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state )  );
+       if ( ! b )
+               YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+       b->yy_buf_size = size;
+
+       /* yy_ch_buf has to be 2 characters longer than the size given because
+        * we need to put in 2 end-of-buffer characters.
+        */
+       b->yy_ch_buf = (char *) yyalloc(b->yy_buf_size + 2  );
+       if ( ! b->yy_ch_buf )
+               YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+       b->yy_is_our_buffer = 1;
+
+       yy_init_buffer(b,file );
+
+       return b;
+}
+
+/** Destroy the buffer.
+ * @param b a buffer created with yy_create_buffer()
+ * 
+ */
+    void yy_delete_buffer (YY_BUFFER_STATE  b )
+{
+    
+       if ( ! b )
+               return;
+
+       if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+               YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+       if ( b->yy_is_our_buffer )
+               yyfree((void *) b->yy_ch_buf  );
+
+       yyfree((void *) b  );
+}
+
+#ifndef __cplusplus
+extern int isatty (int );
+#endif /* __cplusplus */
+    
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a yyrestart() or at EOF.
+ */
+    static void yy_init_buffer  (YY_BUFFER_STATE  b, FILE * file )
+
+{
+       int oerrno = errno;
+    
+       yy_flush_buffer(b );
+
+       b->yy_input_file = file;
+       b->yy_fill_buffer = 1;
+
+    /* If b is the current buffer, then yy_init_buffer was _probably_
+     * called from yyrestart() or through yy_get_next_buffer.
+     * In that case, we don't want to reset the lineno or column.
+     */
+    if (b != YY_CURRENT_BUFFER){
+        b->yy_bs_lineno = 1;
+        b->yy_bs_column = 0;
+    }
+
+        b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+    
+       errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ * 
+ */
+    void yy_flush_buffer (YY_BUFFER_STATE  b )
+{
+       if ( ! b )
+               return;
+
+       b->yy_n_chars = 0;
+
+       /* We always need two end-of-buffer characters.  The first causes
+        * a transition to the end-of-buffer state.  The second causes
+        * a jam in that state.
+        */
+       b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+       b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+       b->yy_buf_pos = &b->yy_ch_buf[0];
+
+       b->yy_at_bol = 1;
+       b->yy_buffer_status = YY_BUFFER_NEW;
+
+       if ( b == YY_CURRENT_BUFFER )
+               yy_load_buffer_state( );
+}
+
+/** Pushes the new state onto the stack. The new state becomes
+ *  the current state. This function will allocate the stack
+ *  if necessary.
+ *  @param new_buffer The new state.
+ *  
+ */
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer )
+{
+       if (new_buffer == NULL)
+               return;
+
+       yyensure_buffer_stack();
+
+       /* This block is copied from yy_switch_to_buffer. */
+       if ( YY_CURRENT_BUFFER )
+               {
+               /* Flush out information for old buffer. */
+               *(yy_c_buf_p) = (yy_hold_char);
+               YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+               YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+               }
+
+       /* Only push if top exists. Otherwise, replace top. */
+       if (YY_CURRENT_BUFFER)
+               (yy_buffer_stack_top)++;
+       YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+       /* copied from yy_switch_to_buffer. */
+       yy_load_buffer_state( );
+       (yy_did_buffer_switch_on_eof) = 1;
+}
+
+/** Removes and deletes the top of the stack, if present.
+ *  The next element becomes the new top.
+ *  
+ */
+void yypop_buffer_state (void)
+{
+       if (!YY_CURRENT_BUFFER)
+               return;
+
+       yy_delete_buffer(YY_CURRENT_BUFFER );
+       YY_CURRENT_BUFFER_LVALUE = NULL;
+       if ((yy_buffer_stack_top) > 0)
+               --(yy_buffer_stack_top);
+
+       if (YY_CURRENT_BUFFER) {
+               yy_load_buffer_state( );
+               (yy_did_buffer_switch_on_eof) = 1;
+       }
+}
+
+/* Allocates the stack if it does not exist.
+ *  Guarantees space for at least one push.
+ */
+static void yyensure_buffer_stack (void)
+{
+       int num_to_alloc;
+    
+       if (!(yy_buffer_stack)) {
+
+               /* First allocation is just for 2 elements, since we don't know if this
+                * scanner will even need a stack. We use 2 instead of 1 to avoid an
+                * immediate realloc on the next call.
+         */
+               num_to_alloc = 1;
+               (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc
+                                                               (num_to_alloc * sizeof(struct yy_buffer_state*)
+                                                               );
+               
+               memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+                               
+               (yy_buffer_stack_max) = num_to_alloc;
+               (yy_buffer_stack_top) = 0;
+               return;
+       }
+
+       if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){
+
+               /* Increase the buffer to prepare for a possible push. */
+               int grow_size = 8 /* arbitrary grow size */;
+
+               num_to_alloc = (yy_buffer_stack_max) + grow_size;
+               (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc
+                                                               ((yy_buffer_stack),
+                                                               num_to_alloc * sizeof(struct yy_buffer_state*)
+                                                               );
+
+               /* zero only the new slots.*/
+               memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*));
+               (yy_buffer_stack_max) = num_to_alloc;
+       }
+}
+
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ * 
+ * @return the newly allocated buffer state object. 
+ */
+YY_BUFFER_STATE yy_scan_buffer  (char * base, yy_size_t  size )
+{
+       YY_BUFFER_STATE b;
+    
+       if ( size < 2 ||
+            base[size-2] != YY_END_OF_BUFFER_CHAR ||
+            base[size-1] != YY_END_OF_BUFFER_CHAR )
+               /* They forgot to leave room for the EOB's. */
+               return 0;
+
+       b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state )  );
+       if ( ! b )
+               YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+       b->yy_buf_size = size - 2;      /* "- 2" to take care of EOB's */
+       b->yy_buf_pos = b->yy_ch_buf = base;
+       b->yy_is_our_buffer = 0;
+       b->yy_input_file = 0;
+       b->yy_n_chars = b->yy_buf_size;
+       b->yy_is_interactive = 0;
+       b->yy_at_bol = 1;
+       b->yy_fill_buffer = 0;
+       b->yy_buffer_status = YY_BUFFER_NEW;
+
+       yy_switch_to_buffer(b  );
+
+       return b;
+}
+
+/** Setup the input buffer state to scan a string. The next call to yylex() will
+ * scan from a @e copy of @a str.
+ * @param str a NUL-terminated string to scan
+ * 
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ *       yy_scan_bytes() instead.
+ */
+YY_BUFFER_STATE yy_scan_string (yyconst char * yy_str )
+{
+    
+       return yy_scan_bytes(yy_str,strlen(yy_str) );
+}
+
+/** Setup the input buffer state to scan the given bytes. The next call to yylex() will
+ * scan from a @e copy of @a bytes.
+ * @param bytes the byte buffer to scan
+ * @param len the number of bytes in the buffer pointed to by @a bytes.
+ * 
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_bytes  (yyconst char * bytes, int  len )
+{
+       YY_BUFFER_STATE b;
+       char *buf;
+       yy_size_t n;
+       int i;
+    
+       /* Get memory for full buffer, including space for trailing EOB's. */
+       n = len + 2;
+       buf = (char *) yyalloc(n  );
+       if ( ! buf )
+               YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+       for ( i = 0; i < len; ++i )
+               buf[i] = bytes[i];
+
+       buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR;
+
+       b = yy_scan_buffer(buf,n );
+       if ( ! b )
+               YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+       /* It's okay to grow etc. this buffer, and we should throw it
+        * away when we're done.
+        */
+       b->yy_is_our_buffer = 1;
+
+       return b;
+}
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+static void yy_fatal_error (yyconst char* msg )
+{
+       (void) fprintf( stderr, "%s\n", msg );
+       exit( YY_EXIT_FAILURE );
+}
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+       do \
+               { \
+               /* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+               yytext[yyleng] = (yy_hold_char); \
+               (yy_c_buf_p) = yytext + yyless_macro_arg; \
+               (yy_hold_char) = *(yy_c_buf_p); \
+               *(yy_c_buf_p) = '\0'; \
+               yyleng = yyless_macro_arg; \
+               } \
+       while ( 0 )
+
+/* Accessor  methods (get/set functions) to struct members. */
+
+/** Get the current line number.
+ * 
+ */
+int yyget_lineno  (void)
+{
+        
+    return yylineno;
+}
+
+/** Get the input stream.
+ * 
+ */
+FILE *yyget_in  (void)
+{
+        return yyin;
+}
+
+/** Get the output stream.
+ * 
+ */
+FILE *yyget_out  (void)
+{
+        return yyout;
+}
+
+/** Get the length of the current token.
+ * 
+ */
+int yyget_leng  (void)
+{
+        return yyleng;
+}
+
+/** Get the current token.
+ * 
+ */
+
+char *yyget_text  (void)
+{
+        return yytext;
+}
+
+/** Set the current line number.
+ * @param line_number
+ * 
+ */
+void yyset_lineno (int  line_number )
+{
+    
+    yylineno = line_number;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param in_str A readable stream.
+ * 
+ * @see yy_switch_to_buffer
+ */
+void yyset_in (FILE *  in_str )
+{
+        yyin = in_str ;
+}
+
+void yyset_out (FILE *  out_str )
+{
+        yyout = out_str ;
+}
+
+int yyget_debug  (void)
+{
+        return yy_flex_debug;
+}
+
+void yyset_debug (int  bdebug )
+{
+        yy_flex_debug = bdebug ;
+}
+
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy  (void)
+{
+    
+    /* Pop the buffer stack, destroying each element. */
+       while(YY_CURRENT_BUFFER){
+               yy_delete_buffer(YY_CURRENT_BUFFER  );
+               YY_CURRENT_BUFFER_LVALUE = NULL;
+               yypop_buffer_state();
+       }
+
+       /* Destroy the stack itself. */
+       yyfree((yy_buffer_stack) );
+       (yy_buffer_stack) = NULL;
+
+    return 0;
+}
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, yyconst char * s2, int n )
+{
+       register int i;
+       for ( i = 0; i < n; ++i )
+               s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * s )
+{
+       register int n;
+       for ( n = 0; s[n]; ++n )
+               ;
+
+       return n;
+}
+#endif
+
+void *yyalloc (yy_size_t  size )
+{
+       return (void *) malloc( size );
+}
+
+void *yyrealloc  (void * ptr, yy_size_t  size )
+{
+       /* The cast to (char *) in the following accommodates both
+        * implementations that use char* generic pointers, and those
+        * that use void* generic pointers.  It works with the latter
+        * because both ANSI C and C++ allow castless assignment from
+        * any pointer type to void*, and deal with argument conversions
+        * as though doing an assignment.
+        */
+       return (void *) realloc( (char *) ptr, size );
+}
+
+void yyfree (void * ptr )
+{
+       free( (char *) ptr );   /* see yyrealloc() for (char *) cast */
+}
+
+#define YYTABLES_NAME "yytables"
+
+#undef YY_NEW_FILE
+#undef YY_FLUSH_BUFFER
+#undef yy_set_bol
+#undef yy_new_buffer
+#undef yy_set_interactive
+#undef yytext_ptr
+#undef YY_DO_BEFORE_ACTION
+
+#ifdef YY_DECL_IS_OURS
+#undef YY_DECL_IS_OURS
+#undef YY_DECL
+#endif
+#line 283 "lexer.l"
+
+
+
+const char *const builtinservicehelpstrings[]= {
+  "environment",
+  "parameter <parameter>",
+  "version",
+  "toplevel",
+  "override",
+  "shutdown",
+  "reset",
+  "execute",
+  "help",
+   0
+};
+
+#include "parser.c"
+
+
diff --git a/lexer.l b/lexer.l
new file mode 100644 (file)
index 0000000..17b757d
--- /dev/null
+++ b/lexer.l
@@ -0,0 +1,299 @@
+/*
+ *   Copyright (C)1996-1997,1999 Ian Jackson
+ *  
+ *   This is free software; you can redistribute it and/or modify it
+ *   under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *  
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *   General Public License for more details.
+ *  
+ *   You should have received a copy of the GNU General Public License
+ *   along with userv; if not, write to the Free Software
+ *   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+%{
+
+
+
+
+
+#include <syslog.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <errno.h>
+
+#include "config.h"
+#include "common.h"
+#include "daemon.h"
+#include "lib.h"
+#include "both.h"
+#include "tokens.h"
+
+#define HYPHEN '-'
+
+typedef int directive_fnt(int dtoken);
+static directive_fnt df_reject, df_execute, df_executefrompath;
+static directive_fnt df_executefromdirectory, df_executebuiltin;
+static directive_fnt df_errorstostderr, df_errorstosyslog, df_errorstofile;
+static directive_fnt dfg_fdwant, dfg_setflag;
+static directive_fnt df_reset, df_cd, df_userrcfile, df_include;
+static directive_fnt df_includelookup, df_includedirectory;
+static directive_fnt df_message, df_error, df_quit, df_eof;
+static directive_fnt df_if, df_catchquit, df_errorspush;
+static directive_fnt dfi_includeuserrcfile, dfi_includeclientconfig;
+/* directive functions return:
+ *  0 for success having scanned up to and including end of line but not beyond,
+ *  or tokv_error or tokv_quit.
+ * They expect to parse the whitespace before their parameters (if any).
+ */
+
+typedef int parmcondition_fnt(int ctoken, char *const *parmvalues, int *rtrue);
+static parmcondition_fnt pcf_glob, pcf_range, pcf_grep;
+/* all conditional functions return tokv_error for failure or 0 for success
+ *  at parsing and testing, in which case *rtrue is set to 0 or 1.
+ *  On success they have scanned up to and including the condition's
+ *  terminating newline; the pcf_... functions expect to parse the whitespace
+ *  between the parameter name and the condition's arguments.
+ * Otherwise they return tokv_error.
+ * The parameter-based conditionals take a list of parameter values
+ * as obtained from the parameter functions and pa_parameter,
+ * and do _not_ free it.
+ */
+
+typedef int parameter_fnt(int ptoken, char ***rvalues);
+static parameter_fnt pf_service;
+static parameter_fnt pf_callinguser, pf_serviceuser;
+static parameter_fnt pf_callinggroup, pf_servicegroup;
+static parameter_fnt pf_callingusershell, pf_serviceusershell;
+/* Parameter functions return tokv_error or 0 for success at parsing
+ * and determining the value, in which case *rvalues is made to be
+ * a mallocd null-terminated array of pointers to mallocd strings.
+ * freeparm can be used to free such an array.
+ */
+
+typedef int builtinserviceparse_fnt(char ***rnewargs);
+static builtinserviceparse_fnt bispa_none, bispa_parameter;
+/* These parse the arguments to a builtin service, including the
+ * newline at the end of the line.  *rnewargs will initially be
+ * null, indicating that no arguments are to be set; the function
+ * may store a mallocd array of mallocd strings in it,
+ * containing the arguments it wishes to have set (null-pointer
+ * terminated).
+ */
+
+static int yylex(void);
+/* Returns a token (which may be an eof or error exception) */
+
+static directive_fnt *lr_dir;
+static parmcondition_fnt *lr_parmcond;
+static builtinserviceparse_fnt *lr_bispa;
+static builtinserviceexec_fnt *lr_bisexec;
+static parameter_fnt *lr_parameter;
+static int lr_loglevel, lr_logfacility, lr_min, lr_max, *lr_flag;
+static int lr_flagval, lr_controlend;
+static int lr_fdwant_readwrite; /* -1=never, 0=opt, 1=always */
+
+/* Forward declarations of things used in lexer and parser */
+
+struct parser_state {
+  int lineno, reportlineno, notedreferer, isinternal;
+  const char *filename;
+  struct stat filestab;
+  YY_BUFFER_STATE ybuf;
+  struct parser_state *upstate;
+};
+
+static struct parser_state *cstate;
+
+struct error_handling {
+  int handling; /* One of the error handling modes tokt_ehandlemode */
+  int logfacility, loglevel;
+  int filekeep; /* File is in use by higher-level errors-push, leave it open */
+  FILE *file;
+  char *filename;
+};
+
+static struct error_handling eh = { tokv_word_errorstostderr, 0,0,0,0,0 };
+
+static int dequote(char *inplace);
+static void countnewlines(void);
+
+#define YY_NO_UNPUT
+
+%}
+
+%option noyywrap
+
+%%
+
+reject { lr_dir= df_reject; return tokv_word_reject; }
+execute-from-directory { lr_dir= df_executefromdirectory; return tokv_word_executefromdirectory; }
+execute-from-path { lr_dir= df_executefrompath; return tokv_word_executefrompath; }
+execute-builtin { lr_dir= df_executebuiltin; return tokv_word_executebuiltin; }
+errors-to-stderr { lr_dir= df_errorstostderr; return tokv_word_errorstostderr; }
+errors-to-syslog { lr_dir= df_errorstosyslog; return tokv_word_errorstosyslog; }
+errors-to-file { lr_dir= df_errorstofile; return tokv_word_errorstofile; }
+require-fd { lr_dir= dfg_fdwant; lr_fdwant_readwrite=1; return tokv_word_requirefd; }
+allow-fd { lr_dir= dfg_fdwant; lr_fdwant_readwrite=0; return tokv_word_allowfd; }
+null-fd { lr_dir= dfg_fdwant; lr_fdwant_readwrite=0; return tokv_word_nullfd; }
+reject-fd { lr_dir= dfg_fdwant; lr_fdwant_readwrite=-1; return tokv_word_rejectfd; }
+ignore-fd { lr_dir= dfg_fdwant; lr_fdwant_readwrite=-1; return tokv_word_ignorefd; }
+set-environment { lr_dir= dfg_setflag; lr_flag= &setenvironment; lr_flagval= 1; return tokv_word_setenvironment; }
+no-set-environment { lr_dir= dfg_setflag; lr_flag= &setenvironment; lr_flagval= 0; return tokv_word_nosetenvironment; }
+suppress-args { lr_dir= dfg_setflag; lr_flag= &suppressargs; lr_flagval= 1; return tokv_word_suppressargs; }
+no-suppress-args { lr_dir= dfg_setflag; lr_flag= &suppressargs; lr_flagval= 0; return tokv_word_nosuppressargs; }
+disconnect-hup { lr_dir= dfg_setflag; lr_flag= &disconnecthup; lr_flagval= 1; return tokv_word_disconnecthup; }
+no-disconnect-hup { lr_dir= dfg_setflag; lr_flag= &disconnecthup; lr_flagval= 0; return tokv_word_nodisconnecthup; }
+cd { lr_dir= df_cd; return tokv_word_cd; }
+user-rcfile { lr_dir= df_userrcfile; return tokv_word_userrcfile; }
+include { lr_dir= df_include; return tokv_word_include; }
+include-ifexist { lr_dir= df_include; return tokv_word_includeifexist; }
+include-lookup { lr_dir= df_includelookup; return tokv_word_includelookup; }
+include-lookup-all { lr_dir= df_includelookup; return tokv_word_includelookupall; }
+include-directory { lr_dir= df_includedirectory; return tokv_word_includedirectory; }
+message { lr_dir= df_message; return tokv_word_message; }
+_include-sysconfig { lr_dir= df_include; return tokv_word_includesysconfig; }
+_include-user-rcfile { lr_dir= dfi_includeuserrcfile; return tokv_word_includeuserrcfile; }
+_include-client-config { lr_dir= dfi_includeclientconfig; return tokv_word_includeclientconfig; }
+quit { lr_dir= df_quit; return tokv_word_quit; }
+eof { lr_dir= df_eof; return tokv_word_eof; }
+if { lr_dir= df_if; return tokv_word_if; }
+catch-quit { lr_dir= df_catchquit; return tokv_word_catchquit; }
+errors-push { lr_dir= df_errorspush; return tokv_word_errorspush; }
+elif { lr_controlend= tokv_word_if; return tokv_word_elif; }
+else { lr_controlend= tokv_word_if; return tokv_word_else; }
+fi { lr_controlend= tokv_word_if; return tokv_word_fi; }
+hctac { lr_controlend= tokv_word_catchquit; return tokv_word_hctac; }
+srorre { lr_controlend= tokv_word_errorspush; return tokv_word_srorre; }
+glob { lr_parmcond= pcf_glob; return tokv_word_glob; }
+range { lr_parmcond= pcf_range; return tokv_word_range; }
+grep { lr_parmcond= pcf_grep; return tokv_word_grep; }
+environment { lr_bispa= bispa_none; lr_bisexec= bisexec_environment; return tokv_word_environment; }
+parameter { lr_bispa= bispa_parameter; lr_bisexec= bisexec_parameter; return tokv_word_parameter; }
+version { lr_bispa= bispa_none; lr_bisexec= bisexec_version; return tokv_word_version; }
+toplevel { lr_bispa= bispa_none; lr_bisexec= bisexec_toplevel; return tokv_word_toplevel; }
+override { lr_bispa= bispa_none; lr_bisexec= bisexec_override; return tokv_word_override; }
+shutdown { lr_bispa= bispa_none; lr_bisexec= bisexec_shutdown; return tokv_word_shutdown; }
+reset { lr_bispa= bispa_none; lr_bisexec= bisexec_reset; lr_dir= df_reset; return tokv_word_reset; }
+execute { lr_bispa= bispa_none; lr_bisexec= bisexec_execute; lr_dir= df_execute; return tokv_word_execute; }
+help { lr_bispa= bispa_none; lr_bisexec= bisexec_help; return tokv_word_help; }
+service { lr_parameter= pf_service; return tokv_word_service; }
+calling-user { lr_parameter= pf_callinguser; return tokv_word_callinguser; }
+calling-group { lr_parameter= pf_callinggroup; return tokv_word_callinggroup; }
+calling-user-shell { lr_parameter= pf_callingusershell; return tokv_word_callingusershell; }
+service-user { lr_parameter= pf_serviceuser; return tokv_word_serviceuser; }
+service-group { lr_parameter= pf_servicegroup; return tokv_word_servicegroup; }
+service-user-shell { lr_parameter= pf_serviceusershell; return tokv_word_serviceusershell; }
+debug { lr_loglevel= LOG_DEBUG; return tokv_syslog_debug; }
+info { lr_loglevel= LOG_INFO; return tokv_syslog_info; }
+notice { lr_loglevel= LOG_NOTICE; return tokv_syslog_notice; }
+warn(ing)? { lr_loglevel= LOG_WARNING; return tokv_syslog_warning; }
+err { lr_loglevel= LOG_ERR; return tokv_syslog_err; }
+crit { lr_loglevel= LOG_CRIT; return tokv_syslog_crit; }
+alert { lr_loglevel= LOG_ALERT; return tokv_syslog_alert; }
+emerg|panic { lr_loglevel= LOG_EMERG; return tokv_syslog_emerg; }
+auth(priv)?|security { lr_logfacility= LOG_AUTHPRIV; return tokv_syslog_authpriv; }
+cron { lr_logfacility= LOG_CRON; return tokv_syslog_cron; }
+daemon { lr_logfacility= LOG_DAEMON; return tokv_syslog_daemon; }
+kern(el)? { lr_logfacility= LOG_KERN; return tokv_syslog_kern; }
+lpr { lr_logfacility= LOG_LPR; return tokv_syslog_lpr; }
+mail { lr_logfacility= LOG_MAIL; return tokv_syslog_mail; }
+news { lr_logfacility= LOG_NEWS; return tokv_syslog_news; }
+syslog { lr_logfacility= LOG_SYSLOG; return tokv_syslog_syslog; }
+user { lr_logfacility= LOG_USER; return tokv_syslog_user; }
+uucp { lr_logfacility= LOG_UUCP; return tokv_syslog_uucp; }
+local0 { lr_logfacility= LOG_LOCAL0; return tokv_syslog_local0; }
+local1 { lr_logfacility= LOG_LOCAL1; return tokv_syslog_local1; }
+local2 { lr_logfacility= LOG_LOCAL2; return tokv_syslog_local2; }
+local3 { lr_logfacility= LOG_LOCAL3; return tokv_syslog_local3; }
+local4 { lr_logfacility= LOG_LOCAL4; return tokv_syslog_local4; }
+local5 { lr_logfacility= LOG_LOCAL5; return tokv_syslog_local5; }
+local6 { lr_logfacility= LOG_LOCAL6; return tokv_syslog_local6; }
+local7 { lr_logfacility= LOG_LOCAL7; return tokv_syslog_local7; }
+read { return tokv_word_read; }
+write { return tokv_word_write; }
+\$ { return tokv_dollar; }
+\( { return tokv_openparen; }
+\) { return tokv_closeparen; }
+\! { return tokv_not; }
+\& { return tokv_and; }
+\| { return tokv_or; }
+error { lr_dir= df_error; lr_loglevel= LOG_ERR; return tokv_word_error; }
+
+
+
+[0-9]{1,8}             {
+                         char *ep;
+                         lr_min=lr_max= (int)strtoul(yytext,&ep,10);
+                         assert(!*ep);
+                         return tokv_ordinal;
+                       }
+[0-9]{1,8}-[0-9]{1,8}  {
+                         char *ep;
+                         lr_min= (int)strtoul(yytext,&ep,10);
+                         assert(*ep == HYPHEN);
+                         ep++;  assert(*ep);
+                         lr_max= (int)strtoul(ep,&ep,10);
+                         assert(!*ep);
+                         if (lr_max < lr_min)
+                           return parseerrprint("fd range has min > max");
+                         return tokv_fdrange;
+                       }
+[0-9]{1,8}-            {
+                         char *ep;
+                         lr_min= (int)strtoul(yytext,&ep,10);
+                         assert(*ep == HYPHEN);
+                         ep++;  assert(!*ep);
+                         lr_max=-1;
+                         return tokv_fdstoend;
+                       }
+([\ \t]*\\[\ \t]*\n[\ \t]*)+   countnewlines(); return tokv_lwsp;
+[\ \t]+                                return tokv_lwsp;
+[\ \t]*\n              cstate->lineno++; return tokv_newline;
+[\ \t]*\#[^\n]*\n      cstate->lineno++; return tokv_newline;
+[\ \t]*\#[^\n]*                return parseerrprint("missing newline at eof after comment");
+\"([^\\\"\n]|\\[a-z]|\\[0-9]{3}|\\x[0-9A-Fa-f]{2}|\\[[:punct:]]|\\[ \t]*\n)*\" {
+                         countnewlines();
+                         return dequote(yytext);
+                       }
+[^\ \t\n\\\"]+         return tokv_barestring;
+<<EOF>>                        return tokv_eof;
+\"                     return parseerrprint("misquoted or unterminated string");
+\\                     return parseerrprint("unexpected backslash");
+.                      abort(); /* expect lex warning "rule cannot be matched" */
+
+
+%%
+
+const char *const builtinservicehelpstrings[]= {
+  "environment",
+  "parameter <parameter>",
+  "version",
+  "toplevel",
+  "override",
+  "shutdown",
+  "reset",
+  "execute",
+  "help",
+   0
+};
+
+#include "parser.c"
+
diff --git a/lexer.l.m4 b/lexer.l.m4
new file mode 100644 (file)
index 0000000..7b42a1d
--- /dev/null
@@ -0,0 +1,200 @@
+dnl  userv - lexer.l.m4
+dnl  lexer, passed through m4 with defs from langauge.i4
+/*
+ *   Copyright (C)1996-1997,1999 Ian Jackson
+ *  
+ *   This is free software; you can redistribute it and/or modify it
+ *   under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *  
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *   General Public License for more details.
+ *  
+ *   You should have received a copy of the GNU General Public License
+ *   along with userv; if not, write to the Free Software
+ *   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+%{
+include(language.i4)
+
+#include <syslog.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fnmatch.h>
+#include <limits.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <errno.h>
+
+#include "config.h"
+#include "common.h"
+#include "daemon.h"
+#include "lib.h"
+#include "both.h"
+#include "tokens.h"
+
+#define HYPHEN '-'
+
+typedef int directive_fnt(int dtoken);
+static directive_fnt df_reject, df_execute, df_executefrompath;
+static directive_fnt df_executefromdirectory, df_executebuiltin;
+static directive_fnt df_errorstostderr, df_errorstosyslog, df_errorstofile;
+static directive_fnt dfg_fdwant, dfg_setflag;
+static directive_fnt df_reset, df_cd, df_userrcfile, df_include;
+static directive_fnt df_includelookup, df_includedirectory;
+static directive_fnt df_message, df_error, df_quit, df_eof;
+static directive_fnt df_if, df_catchquit, df_errorspush;
+static directive_fnt dfi_includeuserrcfile, dfi_includeclientconfig;
+/* directive functions return:
+ *  0 for success having scanned up to and including end of line but not beyond,
+ *  or tokv_error or tokv_quit.
+ * They expect to parse the whitespace before their parameters (if any).
+ */
+
+typedef int parmcondition_fnt(int ctoken, char *const *parmvalues, int *rtrue);
+static parmcondition_fnt pcf_glob, pcf_range, pcf_grep;
+/* all conditional functions return tokv_error for failure or 0 for success
+ *  at parsing and testing, in which case *rtrue is set to 0 or 1.
+ *  On success they have scanned up to and including the condition's
+ *  terminating newline; the pcf_... functions expect to parse the whitespace
+ *  between the parameter name and the condition's arguments.
+ * Otherwise they return tokv_error.
+ * The parameter-based conditionals take a list of parameter values
+ * as obtained from the parameter functions and pa_parameter,
+ * and do _not_ free it.
+ */
+
+typedef int parameter_fnt(int ptoken, char ***rvalues);
+static parameter_fnt pf_service;
+static parameter_fnt pf_callinguser, pf_serviceuser;
+static parameter_fnt pf_callinggroup, pf_servicegroup;
+static parameter_fnt pf_callingusershell, pf_serviceusershell;
+/* Parameter functions return tokv_error or 0 for success at parsing
+ * and determining the value, in which case *rvalues is made to be
+ * a mallocd null-terminated array of pointers to mallocd strings.
+ * freeparm can be used to free such an array.
+ */
+
+typedef int builtinserviceparse_fnt(char ***rnewargs);
+static builtinserviceparse_fnt bispa_none, bispa_parameter;
+/* These parse the arguments to a builtin service, including the
+ * newline at the end of the line.  *rnewargs will initially be
+ * null, indicating that no arguments are to be set; the function
+ * may store a mallocd array of mallocd strings in it,
+ * containing the arguments it wishes to have set (null-pointer
+ * terminated).
+ */
+
+static int yylex(void);
+/* Returns a token (which may be an eof or error exception) */
+
+static directive_fnt *lr_dir;
+static parmcondition_fnt *lr_parmcond;
+static builtinserviceparse_fnt *lr_bispa;
+static builtinserviceexec_fnt *lr_bisexec;
+static parameter_fnt *lr_parameter;
+static int lr_loglevel, lr_logfacility, lr_min, lr_max, *lr_flag;
+static int lr_flagval, lr_controlend;
+static int lr_fdwant_readwrite; /* -1=never, 0=opt, 1=always */
+
+/* Forward declarations of things used in lexer and parser */
+
+struct parser_state {
+  int lineno, reportlineno, notedreferer, isinternal;
+  const char *filename;
+  struct stat filestab;
+  YY_BUFFER_STATE ybuf;
+  struct parser_state *upstate;
+};
+
+static struct parser_state *cstate;
+
+struct error_handling {
+  int handling; /* One of the error handling modes tokt_ehandlemode */
+  int logfacility, loglevel;
+  int filekeep; /* File is in use by higher-level errors-push, leave it open */
+  FILE *file;
+  char *filename;
+};
+
+static struct error_handling eh = { tokv_word_errorstostderr, 0,0,0,0,0 };
+
+static int dequote(char *inplace);
+static void countnewlines(void);
+
+#define YY_NO_UNPUT
+
+%}
+
+%option noyywrap
+
+%%
+
+dnl simple words
+undivert(3)
+changequote({*,*})
+{*
+[0-9]{1,8}             {
+                         char *ep;
+                         lr_min=lr_max= (int)strtoul(yytext,&ep,10);
+                         assert(!*ep);
+                         return tokv_ordinal;
+                       }
+[0-9]{1,8}-[0-9]{1,8}  {
+                         char *ep;
+                         lr_min= (int)strtoul(yytext,&ep,10);
+                         assert(*ep == HYPHEN);
+                         ep++;  assert(*ep);
+                         lr_max= (int)strtoul(ep,&ep,10);
+                         assert(!*ep);
+                         if (lr_max < lr_min)
+                           return parseerrprint("fd range has min > max");
+                         return tokv_fdrange;
+                       }
+[0-9]{1,8}-            {
+                         char *ep;
+                         lr_min= (int)strtoul(yytext,&ep,10);
+                         assert(*ep == HYPHEN);
+                         ep++;  assert(!*ep);
+                         lr_max=-1;
+                         return tokv_fdstoend;
+                       }
+([\ \t]*\\[\ \t]*\n[\ \t]*)+   countnewlines(); return tokv_lwsp;
+[\ \t]+                                return tokv_lwsp;
+[\ \t]*\n              cstate->lineno++; return tokv_newline;
+[\ \t]*\#[^\n]*\n      cstate->lineno++; return tokv_newline;
+[\ \t]*\#[^\n]*                return parseerrprint("missing newline at eof after comment");
+\"([^\\\"\n]|\\[a-z]|\\[0-9]{3}|\\x[0-9A-Fa-f]{2}|\\[[:punct:]]|\\[ \t]*\n)*\" {
+                         countnewlines();
+                         return dequote(yytext);
+                       }
+[^\ \t\n\\\"]+         return tokv_barestring;
+<<EOF>>                        return tokv_eof;
+\"                     return parseerrprint("misquoted or unterminated string");
+\\                     return parseerrprint("unexpected backslash");
+.                      abort(); /* expect lex warning "rule cannot be matched" */
+*}
+changequote(`,')
+%%
+
+const char *const builtinservicehelpstrings[]= {
+undivert(5)dnl
+   0
+};
+`
+#include "parser.c"
+'
+divert(-1)
+undivert
diff --git a/lib.c b/lib.c
new file mode 100644 (file)
index 0000000..1352afa
--- /dev/null
+++ b/lib.c
@@ -0,0 +1,109 @@
+/*
+ * userv - lib.c
+ * useful utility routines, used in daemon, but not very dependent on it
+ *
+ * Copyright (C)1996-1997,1999 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <errno.h>
+#include <syslog.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+
+#include "config.h"
+#include "common.h"
+#include "lib.h"
+#include "both.h"
+
+char *xstrcat3save(const char *a, const char *b, const char *c) {
+  char *r;
+
+  r= xmalloc(strlen(a)+strlen(b)+strlen(c)+1);
+  strcpy(r,a);
+  strcat(r,b);
+  strcat(r,c);
+  return r;
+}
+
+char *xstrsubsave(const char *begin, int len) {
+  char *r;
+  
+  r= xmalloc(len+1);
+  memcpy(r,begin,len);
+  r[len]= 0;
+  return r;
+}
+
+int makeroom(char **buffer, int *size, int needed) {
+  if (needed > MAX_GENERAL_STRING) return -1;
+  if (*size >= needed) return 0;
+  *buffer= xrealloc(*buffer,needed);
+  *size= needed;
+  return 0;
+}
+
+void vsnyprintf(char *buffer, size_t size, const char *fmt, va_list al) {
+  vsnprintf(buffer,size,fmt,al);
+  buffer[size-1]= 0;
+}
+
+void snyprintf(char *buffer, size_t size, const char *fmt, ...) {
+  va_list al;
+  va_start(al,fmt);
+  vsnyprintf(buffer,size,fmt,al);
+  va_end(al);
+}
+
+void strnycpy(char *dest, const char *src, size_t size) {
+  strncpy(dest,src,size-1);
+  dest[size-1]= 0;
+}
+
+void strnytcat(char *dest, const char *src, size_t size) {
+  size_t l;
+  l= strlen(dest);
+  strnycpy(dest+l,src,size-l);
+}
+
+void vsnytprintfcat(char *buffer, size_t size, const char *fmt, va_list al) {
+  size_t l;
+  l= strlen(buffer);
+  vsnyprintf(buffer+l,size-l,fmt,al);
+}
+
+void snytprintfcat(char *buffer, size_t size, const char *fmt, ...) {
+  va_list al;
+  va_start(al,fmt);
+  vsnytprintfcat(buffer,size,fmt,al);
+  va_end(al);
+}
+
+#ifndef HAVE_SETENV
+int setenv(const char *name, const char *value, int overwrite) {
+  char *buffer= 0;
+  
+  assert(overwrite==1);
+  buffer= xmalloc(strlen(name)+strlen(value)+2);
+
+  sprintf(buffer,"%s=%s",name,value);
+  return putenv(buffer);
+}
+#endif /* HAVE_SETENV */
diff --git a/lib.h b/lib.h
new file mode 100644 (file)
index 0000000..7012016
--- /dev/null
+++ b/lib.h
@@ -0,0 +1,59 @@
+/*
+ * userv - lib.c
+ * useful utility routines' imports and exports, used in daemon
+ *
+ * Copyright (C)1996-1997,1999 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef LIB_H
+#define LIB_H
+
+char *xstrcat3save(const char *a, const char *b, const char *c);
+char *xstrsubsave(const char *begin, int len);
+
+void miscerror(const char *what) NONRETURNING;
+int makeroom(char **buffer, int *size, int needed);
+/* makeroom returns -1 if needed was far too large; otherwise returns 0. */
+
+/* It doesn't appear to be documented whether [v]snprintf put a null
+ * in if they overrun.  GNU libc does, but I don't want to rely on
+ * that.
+ * So here are some functions that always do, regardless - including
+ * versions of strcat and strcpy.  The ...nyt...cat functions take the
+ * maximum length of the resulting buffer as size parameter, rather
+ * than the maximum length of the added portion.
+ *
+ * So,
+ *   ...n...       specify copied length (inc. any null), may or may not null-terminate
+ *   ...ny...      specify copied length (inc. null) and always null-terminate
+ *   ...nyt...cat  specify total buffer length and always null-terminate
+ */
+
+/* Function names best pronounced with a Russian accent. */
+void vsnyprintf(char *buffer, size_t size, const char *fmt, va_list al);
+void snyprintf(char *buffer, size_t size, const char *fmt, ...) PRINTFFORMAT(3,4);
+void strnycpy(char *dest, const char *src, size_t size);
+
+void vsnytprintfcat(char *buffer, size_t size, const char *fmt, va_list al);
+void snytprintfcat(char *buffer, size_t size, const char *fmt, ...) PRINTFFORMAT(3,4);
+void strnytcat(char *dest, const char *src, size_t size);
+
+#ifndef HAVE_SETENV
+int setenv(const char *name, const char *value, int overwrite);
+#endif /* HAVE_SETENV */
+
+#endif /* LIB_H */
diff --git a/overlord.c b/overlord.c
new file mode 100644 (file)
index 0000000..8f0dcb8
--- /dev/null
@@ -0,0 +1,394 @@
+/*
+ * userv - overlord.c
+ * daemon main program, collects request and forks handlers
+ *
+ * Copyright (C)1996-1997,1999 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <unistd.h>
+#include <signal.h>
+#include <syslog.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <fnmatch.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <time.h>
+#include <dirent.h>
+#include <sys/un.h>
+
+#include "config.h"
+#include "common.h"
+#include "daemon.h"
+
+pid_t overlordpid;
+
+static pid_t checkpid= -1, detachpid= -1;
+static sig_atomic_t needcheck= 1; /* 2 means we half-expect the server to be down */
+
+static void checkstalepipes(void) {
+  /* There is an unimportant race here.  If there is a stale pipe but
+   * another pair of processes with the same pids is about to create a
+   * new one we can check that the pipe is stale before they recreate
+   * it but then only remove it afterwards; then we remove the pipe
+   * they're actually using causing that invocation to fail with
+   * ENOENT on the pipe.  However, this can only happen if things are
+   * already shafted, because we check for stale pipes at startup
+   * before any children have been started, and then only when a child
+   * dies unhelpfully - and we actually have to have some stale pipes
+   * for the race to exist.
+   */
+  DIR *dir;
+  struct dirent *de;
+  struct stat stab;
+  time_t now;
+  unsigned long timediff;
+  int r;
+  
+  if (time(&now) == -1) { syslog(LOG_ERR,"get current time: %m"); return; }
+  dir= opendir(".");
+  if (!dir) { syslog(LOG_ERR,"open directory " VARDIR ": %m"); return; }
+  while ((de= readdir(dir))) {
+    if (fnmatch(PIPEPATTERN,de->d_name,FNM_PATHNAME|FNM_PERIOD)) continue;
+    r= lstat(de->d_name,&stab); if (r && errno==ENOENT) continue;
+    if (r) { syslog(LOG_ERR,"could not stat `" VARDIR "/%s': %m",de->d_name); continue; }
+    timediff= (unsigned long)now - (unsigned long)stab.st_ctime;
+    if (timediff >= (~0UL>>1) || timediff < 3600) continue;
+    if (unlink(de->d_name) && errno!=ENOENT)
+      syslog(LOG_ERR,"could not remove stale pipe `%s': %m",de->d_name);
+  }
+  if (closedir(dir)) syslog(LOG_ERR,"close directory " VARDIR ": %m");
+}
+
+static void sighandler_chld(int x) {
+  pid_t r;
+  int status, es;
+
+  es= errno;
+  for (;;) {
+    r= waitpid((pid_t)-1,&status,WNOHANG);
+    if (!r || (r==-1 && errno==ECHILD)) break;
+    if (r==-1) { syslog(LOG_ERR,"wait in sigchild handler gave error: %m"); break; }
+    if (r==detachpid) {
+      if (WIFEXITED(status) && WEXITSTATUS(status)==4) _exit(4);
+      fprintf(stderr,"uservd: detaching child failed with unexpected code %d\n",status);
+      exit(6);
+    }
+    if (r==checkpid) {
+      if (WIFEXITED(status)) {
+       if (!WEXITSTATUS(status)) {
+         syslog(LOG_WARNING,"no longer the uservd - exiting");
+         _exit(2);
+       } else if (WEXITSTATUS(status)!=1) {
+         syslog(LOG_ERR,"check pid %ld exited with status %d",
+                (long)checkpid,WEXITSTATUS(status));
+       }
+      } else if (WIFSIGNALED(status)) {
+       if (WTERMSIG(status) == SIGALRM && !WCOREDUMP(status)) {
+         syslog(LOG_WARNING,"check timed out; no longer the uservd - exiting");
+         _exit(2);
+       } else {
+         syslog(LOG_ERR,"check pid %ld %s due to signal %s",
+                (long)checkpid,
+                WCOREDUMP(status) ? "dumped core" : "died",
+                strsignal(WTERMSIG(status)));
+       }
+      } else {
+       syslog(LOG_ERR,"check pid %ld died due to unknown reason, code %d",
+              (long)checkpid,status);
+      }
+      checkpid= -1;
+      alarm(USERVD_MYSELF_CHECK);
+    } else {
+      if (WIFSIGNALED(status)) {
+       syslog(LOG_ERR,"call pid %ld %s due to signal %s",
+              (long)r,
+              WCOREDUMP(status) ? "dumped core" : "died",
+              strsignal(WTERMSIG(status)));
+      } else if (!WIFEXITED(status)) {
+       syslog(LOG_ERR,"call pid %ld died due to unknown reason, code %d",
+              (long)r,status);
+      } else if (WEXITSTATUS(status)==10) {
+       needcheck= 2;
+      } else if (WEXITSTATUS(status)>12) {
+       if (WEXITSTATUS(status)>24)
+         syslog(LOG_ERR,"call pid %ld exited with status %d >24",
+                (long)r,WEXITSTATUS(status));
+       checkstalepipes();
+       needcheck= 1;
+      }
+    }
+  }
+  errno= es;
+  return;
+}
+
+static void sighandler_usr1(int x) {
+  _exit(0);
+}
+
+static void sighandler_alrm(int x) {
+  needcheck= 1;
+}
+
+static void sighandler_termint(int sig) {
+  syslog(LOG_NOTICE,"terminating due to signal %s",strsignal(sig));
+  _exit(1);
+}
+
+static void blocksignals(int how) {
+  int r;
+  sigset_t set;
+
+  sigemptyset(&set);
+  sigaddset(&set,SIGCHLD);
+  sigaddset(&set,SIGALRM);
+  sigaddset(&set,SIGTERM);
+  sigaddset(&set,SIGINT);
+  r= sigprocmask(how,&set,0); assert(!r);
+}
+
+static void NONRETURNING docheck(int needwanted) {
+#ifndef DEBUG
+  /* This subprocess exits with status 0 if the parent should die,
+   * 1 if it should not, and something else if it fails horribly.
+   */
+  int sfd, r, remain;
+  unsigned char *p;
+  struct opening_msg opening_mbuf;
+  struct request_msg request_mbuf;
+  unsigned long endmagic;
+  struct sigaction sig;
+  struct sockaddr_un ssockname;
+
+  openlog(USERVDCHECK_LOGIDENT,LOG_NDELAY|LOG_PID,USERVD_LOGFACILITY);
+
+  sigemptyset(&sig.sa_mask);
+  sig.sa_flags= 0;
+  sig.sa_handler= SIG_IGN;
+  if (sigaction(SIGPIPE,&sig,0)) { syslog(LOG_ERR,"ignore sigpipe"); exit(1); }
+
+  sig.sa_handler= SIG_DFL;
+  if (sigaction(SIGALRM,&sig,0)) { syslog(LOG_ERR,"default sigalarm"); exit(1); }
+
+  sfd= socket(AF_UNIX,SOCK_STREAM,0);
+  if (!sfd) { syslog(LOG_ERR,"ignore sigpipe"); exit(1); }
+
+  assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUS));
+  ssockname.sun_family= AF_UNIX;
+  strcpy(ssockname.sun_path,RENDEZVOUS);
+
+  r= connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname));
+  if (r) {
+    if (errno == ECONNREFUSED || errno == ENOENT) {
+      if (needwanted != 2)
+       syslog(LOG_WARNING,"real uservd daemon is not running: %m");
+      exit(0);
+    }
+    syslog(LOG_ERR,"unable to connect to uservd daemon: %m"); exit(1);
+  }
+
+  alarm(USERVD_MYSELF_TIMEOUT);
+  remain= sizeof(opening_mbuf); p= (unsigned char*)&opening_mbuf;
+  while (remain) {
+    r= read(sfd,p,remain);
+    if (r<0) { syslog(LOG_ERR,"read from server: %m"); exit(1); }
+    if (r==0) { syslog(LOG_ERR,"unexpected EOF from server"); exit(1); }
+    remain-= r; p+= r;
+  }
+  if (opening_mbuf.magic != OPENING_MAGIC) {
+    syslog(LOG_WARNING,"magic number mismatch");
+    exit(0);
+  }
+  if (memcmp(opening_mbuf.protocolchecksumversion,protocolchecksumversion,PCSUMSIZE)) {
+    syslog(LOG_WARNING,"protocol checksum mismatch");
+    exit(0);
+  }
+  if (opening_mbuf.overlordpid != overlordpid) {
+    syslog(LOG_WARNING,"overlord pid mismatch");
+    exit(0);
+  }
+  memset(&request_mbuf,0,sizeof(request_mbuf));
+  request_mbuf.magic= REQUEST_MAGIC;
+  request_mbuf.clientpid= -1;
+  request_mbuf.serviceuserlen= 0;
+  request_mbuf.servicelen= 0;
+  request_mbuf.loginnamelen= 0;
+  request_mbuf.spoofed= 0;
+  request_mbuf.cwdlen= 0;
+  request_mbuf.overridelen= -1;
+  request_mbuf.callinguid= -1;
+  request_mbuf.ngids= 0;
+  request_mbuf.nreadfds= 0;
+  request_mbuf.nwritefds= 0;
+  request_mbuf.nargs= 0;
+  request_mbuf.nvars= 0;
+  r= write(sfd,&request_mbuf,sizeof(request_mbuf));
+  if (r==sizeof(request_mbuf)) {
+    endmagic= REQUEST_END_MAGIC;
+    write(sfd,&endmagic,sizeof(endmagic));
+  }
+  syslog(LOG_NOTICE,"uservd[%ld] is running",(long)overlordpid);
+#endif
+  exit(1);
+}
+
+static void NONRETURNING startupsyscallerr(const char *what) {
+  fprintf(stderr,
+         "uservd: system call failed during startup:\n"
+         "uservd: %s: %s\n",
+         what,strerror(errno));
+  exit(4);
+}
+
+int main(int argc, char *const *argv) {
+  int mfd, sfd, nfd, e, r, becomedaemon;
+  socklen_t csocklen;
+  struct sigaction sigact;
+  struct sockaddr_un ssockname, csockname;
+  pid_t child, parentpid, sid;
+
+#ifdef NDEBUG
+  abort(); /* Do not disable assertions in this security-critical code ! */
+#endif
+
+  becomedaemon= 0;
+  
+  if (argv[1] && !strcmp(argv[1],"-daemon")) {
+    becomedaemon= 1;
+    argv++; argc--;
+  }
+  if (argc>1) { fputs("usage: uservd [-daemon]\n",stderr); exit(3); }
+
+  openlog(USERVD_LOGIDENT,LOG_NDELAY|LOG_PID,USERVD_LOGFACILITY);
+
+  if (chdir(VARDIR)) startupsyscallerr("cannot change to " VARDIR);
+  checkstalepipes();
+
+  overlordpid= parentpid= getpid();
+  if (parentpid==-1) startupsyscallerr("cannot getpid");
+
+  mfd= socket(AF_UNIX,SOCK_STREAM,0);
+  if (mfd<0) startupsyscallerr("cannot create master socket");
+
+  assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUS));
+  ssockname.sun_family= AF_UNIX;
+  strcpy(ssockname.sun_path,RENDEZVOUS);
+  unlink(RENDEZVOUS);
+  r= bind(mfd,(struct sockaddr*)&ssockname,sizeof(ssockname));
+  if (r) startupsyscallerr("cannot bind master socket");
+  if (listen(mfd,5)) startupsyscallerr("cannot listen on master socket");
+
+  sigemptyset(&sigact.sa_mask);
+  sigaddset(&sigact.sa_mask,SIGCHLD);
+  sigaddset(&sigact.sa_mask,SIGALRM);
+  sigact.sa_flags= SA_NOCLDSTOP;
+
+  sigact.sa_handler= sighandler_chld;
+  if (sigaction(SIGCHLD,&sigact,0)) startupsyscallerr("cannot setup sigchld handler");
+
+  sigact.sa_handler= sighandler_alrm;
+  if (sigaction(SIGALRM,&sigact,0)) startupsyscallerr("cannot setup sigalrm handler");
+
+  if (becomedaemon) {
+    sigact.sa_handler= sighandler_usr1;
+    if (sigaction(SIGUSR1,&sigact,0)) startupsyscallerr("cannot setup sigusr1 handler");
+    
+    detachpid= fork(); if (detachpid==-1) startupsyscallerr("cannot fork to detach");
+    if (detachpid) {
+      pause();
+      fputs("uservd: pause unexpectedly returned during detach\n",stderr);
+      exit(4);
+    }
+    sigact.sa_handler= SIG_DFL;
+    if (sigaction(SIGUSR1,&sigact,0)) startupsyscallerr("cannot restore sigusr1");
+  }
+
+  sigact.sa_handler= sighandler_termint;
+  if (sigaction(SIGTERM,&sigact,0)) startupsyscallerr("cannot setup sigterm handler");
+  if (sigaction(SIGINT,&sigact,0)) startupsyscallerr("cannot setup sigint handler");
+
+  if (becomedaemon) {
+    nfd= open("/dev/null",O_RDWR);
+    if (nfd<0) startupsyscallerr("cannot open /dev/null");
+    sid= setsid(); if (sid == -1) startupsyscallerr("cannot create new session");
+    overlordpid= getpid();
+    if (overlordpid == -1) startupsyscallerr("getpid after detach");
+    if (dup2(nfd,0)<0 || dup2(nfd,1)<0)
+      startupsyscallerr("cannot dup /dev/null for stdin/out");
+    r= kill(parentpid,SIGUSR1); if (r) startupsyscallerr("send SIGUSR1 to detach");
+    r= dup2(nfd,2);
+    if (r<0) { syslog(LOG_CRIT,"cannot dup /dev/null for stderr: %m"); exit(5); }
+    close(nfd);
+  }
+
+  syslog(LOG_NOTICE,"started");
+
+  for (;;) {
+    if (needcheck) {
+      while (checkpid==-1) {
+       checkpid= fork();
+       if (checkpid!=-1) {
+         if (!checkpid) docheck(needcheck);
+         break;
+       } else if (errno==EAGAIN) {
+         syslog(LOG_ERR,"fork for check - will wait and retry: %m");
+         alarm(USERVD_CHECKFORK_RETRY);
+         break;
+       } else if (errno!=EINTR) {
+         syslog(LOG_CRIT,"fork for check: %m"); exit(5);
+       }
+      }
+      needcheck= 0;
+    }
+    csocklen= sizeof(csockname);
+    blocksignals(SIG_UNBLOCK);
+    sfd= accept(mfd,(struct sockaddr*)&csockname,&csocklen);
+    e= errno;
+    blocksignals(SIG_BLOCK);
+    if (sfd<0) {
+      errno= e;
+      if (errno == EINTR) continue;
+      if (errno == ENOMEM || errno == EPROTO || errno == EAGAIN) {
+        syslog(LOG_ERR,"unable to accept connection: %m");
+       continue;
+      } else {
+       syslog(LOG_CRIT,"unable to accept new connections: %m");
+       exit(5);
+      }
+    }
+    child= nondebug_fork();
+    if (child == (pid_t)-1) {
+      syslog(LOG_ERR,"unable to fork server: %m");
+      close(sfd);
+      continue;
+    }
+    if (!child) {
+      close(mfd);
+      closelog();
+      blocksignals(SIG_UNBLOCK);
+      servicerequest(sfd);
+    }
+    close(sfd);
+  }
+}
diff --git a/overview.fig b/overview.fig
new file mode 100644 (file)
index 0000000..97ff7b5
--- /dev/null
@@ -0,0 +1,85 @@
+#FIG 3.2
+Landscape
+Center
+Inches
+A4     
+100.00
+Single
+0
+1200 2
+2 1 2 1 -1 7 0 0 -1 3.000 0 0 -1 1 0 2
+       0 0 1.00 60.00 120.00
+        3375 1875 3375 2475
+2 1 2 1 -1 7 0 0 -1 3.000 0 0 -1 1 1 2
+       0 0 1.00 60.00 120.00
+       0 0 1.00 60.00 120.00
+        4159 2999 6075 3000
+2 1 2 1 -1 7 0 0 -1 3.000 0 0 -1 1 0 2
+       0 0 1.00 60.00 120.00
+        3375 3525 3375 4350
+2 2 0 1 0 7 0 0 -1 0.000 0 0 -1 0 0 5
+        2700 4425 4125 4425 4125 5025 2700 5025 2700 4425
+2 2 0 1 0 7 0 0 -1 0.000 0 0 -1 0 0 5
+        2700 4575 4125 4575 4125 5175 2700 5175 2700 4575
+2 1 2 1 -1 7 0 0 -1 3.000 0 0 -1 1 0 2
+       0 0 1.00 60.00 120.00
+        6900 3525 6900 5775
+2 2 0 1 -1 7 0 0 -1 0.000 0 0 -1 0 0 5
+        2700 2533 4125 2533 4125 3465 2700 3465 2700 2533
+2 2 0 1 -1 7 0 0 -1 0.000 0 0 -1 0 0 5
+        2700 900 4125 900 4125 1834 2700 1834 2700 900
+2 2 0 1 -1 7 0 0 -1 0.000 0 0 -1 0 0 5
+        6176 2533 7650 2533 7650 3465 6176 3465 6176 2533
+2 2 0 1 -1 7 0 0 -1 0.000 0 0 -1 0 0 5
+        6176 5815 7650 5815 7650 6749 6176 6749 6176 5815
+3 2 1 1 -1 7 0 0 -1 4.000 0 0 0 6
+        825 5250 1875 5250 2700 5475 4800 5550 7875 5400 8850 5250
+        0.000 -1.000 -1.000 -1.000 -1.000 0.000
+3 2 1 1 -1 7 0 0 -1 4.000 0 0 0 11
+        4650 6750 4575 5025 4275 4200 3000 3900 2400 3525 2400 2475
+        2775 2100 4800 2025 7500 2100 8175 2325 8850 2475
+        0.000 -1.000 -1.000 -1.000 -1.000 -1.000 -1.000 -1.000
+        -1.000 -1.000 0.000
+3 0 2 1 -1 7 0 0 -1 3.000 0 0 1 4
+       0 0 1.00 60.00 120.00
+        2625 4725 2025 4725 1575 4500 750 4500
+        0.000 1.000 1.000 0.000
+3 0 2 1 -1 7 0 0 -1 3.000 0 0 1 6
+       0 0 1.00 60.00 120.00
+        4200 4650 4650 4650 4950 5025 5100 6150 5550 6300 6075 6300
+        0.000 1.000 1.000 1.000 1.000 0.000
+3 0 2 1 -1 7 0 0 -1 3.000 0 1 0 6
+       0 0 1.00 60.00 120.00
+        4200 4800 4650 4800 4950 5175 5100 6300 5550 6450 6075 6450
+        0.000 1.000 1.000 1.000 1.000 0.000
+3 0 2 1 -1 7 0 0 -1 3.000 0 1 0 4
+       0 0 1.00 60.00 120.00
+        2625 4575 2025 4575 1575 4350 750 4350
+        0.000 1.000 1.000 0.000
+4 0 -1 0 0 1 16 0.0000 4 225 885 3460 2300 fork/exec\001
+4 0 -1 0 0 1 16 0.0000 4 225 885 3460 3825 fork/exec\001
+4 0 -1 0 0 12 19 0.0000 4 165 495 3075 4800 cat\001
+4 0 -1 0 0 12 19 0.0000 4 165 495 3075 4950 cat\001
+4 0 -1 0 0 16 15 0.0000 4 165 675 1425 5175 trusted\001
+4 0 -1 0 0 16 19 0.0000 4 270 2520 1275 6525 unrelated processes\001
+4 0 -1 0 0 16 15 0.0000 4 165 915 1050 5475 untrusted\001
+4 0 -1 0 0 16 15 0.0000 4 210 3240 975 5775 invoking user's security boundary\001
+4 0 -1 0 0 16 19 0.0000 4 210 585 7800 4725 TCB\001
+4 0 -1 0 0 16 19 0.0000 4 210 885 7800 6000 service\001
+4 0 -1 0 0 16 19 0.0000 4 150 525 7875 6225 user\001
+4 0 -1 0 0 16 15 0.0000 4 165 915 7950 2175 untrusted\001
+4 0 -1 0 0 16 19 0.0000 4 270 1635 5475 1500 invoking user\001
+4 0 -1 0 0 1 16 0.0000 4 225 1515 4875 4875 pipes (set up by\001
+4 0 -1 0 0 1 16 0.0000 4 210 1575 5025 5100 client+daemon)\001
+4 0 -1 0 0 1 16 0.0000 4 225 885 5925 3758 fork/exec\001
+4 0 -1 0 0 0 20 0.0000 4 195 975 6422 3058 Daemon\001
+4 0 -1 0 0 0 20 0.0000 4 195 975 6422 6516 program\001
+4 0 -1 0 0 0 20 0.0000 4 195 840 6422 6282 Service\001
+4 0 -1 0 0 0 20 0.0000 4 195 705 2993 3058 Client\001
+4 0 -1 0 0 0 20 0.0000 4 195 705 3000 1425 Caller\001
+4 0 -1 0 0 16 15 0.0000 4 165 675 7050 2325 trusted\001
+4 0 -1 0 0 1 16 0.0000 4 165 600 4800 2942 socket\001
+4 0 -1 0 0 1 16 0.0000 4 225 2070 750 4275 or passed from caller\001
+4 0 -1 0 0 1 16 0.0000 4 225 285 825 3825 fds\001
+4 0 -1 0 0 1 16 0.0000 4 225 1590 825 4050 opened by client\001
+4 0 -1 0 0 16 15 0.0000 4 210 3165 5700 1950 service user's security boundary\001
diff --git a/overview.ps b/overview.ps
new file mode 100644 (file)
index 0000000..b2f6296
--- /dev/null
@@ -0,0 +1,447 @@
+%!PS-Adobe-2.0
+%%Title: overview.fig
+%%Creator: fig2dev Version 3.2 Patchlevel 1
+%%CreationDate: Tue Nov  9 23:30:14 1999
+%%For: ian@davenant (Ian Jackson)
+%%Orientation: Landscape
+%%BoundingBox: 121 174 474 667
+%%Pages: 1
+%%BeginSetup
+%%IncludeFeature: *PageSize A4
+%%EndSetup
+%%Magnification: 1.0000
+%%EndComments
+/$F2psDict 200 dict def
+$F2psDict begin
+$F2psDict /mtrx matrix put
+/col-1 {0 setgray} bind def
+/col0 {0.000 0.000 0.000 srgb} bind def
+/col1 {0.000 0.000 1.000 srgb} bind def
+/col2 {0.000 1.000 0.000 srgb} bind def
+/col3 {0.000 1.000 1.000 srgb} bind def
+/col4 {1.000 0.000 0.000 srgb} bind def
+/col5 {1.000 0.000 1.000 srgb} bind def
+/col6 {1.000 1.000 0.000 srgb} bind def
+/col7 {1.000 1.000 1.000 srgb} bind def
+/col8 {0.000 0.000 0.560 srgb} bind def
+/col9 {0.000 0.000 0.690 srgb} bind def
+/col10 {0.000 0.000 0.820 srgb} bind def
+/col11 {0.530 0.810 1.000 srgb} bind def
+/col12 {0.000 0.560 0.000 srgb} bind def
+/col13 {0.000 0.690 0.000 srgb} bind def
+/col14 {0.000 0.820 0.000 srgb} bind def
+/col15 {0.000 0.560 0.560 srgb} bind def
+/col16 {0.000 0.690 0.690 srgb} bind def
+/col17 {0.000 0.820 0.820 srgb} bind def
+/col18 {0.560 0.000 0.000 srgb} bind def
+/col19 {0.690 0.000 0.000 srgb} bind def
+/col20 {0.820 0.000 0.000 srgb} bind def
+/col21 {0.560 0.000 0.560 srgb} bind def
+/col22 {0.690 0.000 0.690 srgb} bind def
+/col23 {0.820 0.000 0.820 srgb} bind def
+/col24 {0.500 0.190 0.000 srgb} bind def
+/col25 {0.630 0.250 0.000 srgb} bind def
+/col26 {0.750 0.380 0.000 srgb} bind def
+/col27 {1.000 0.500 0.500 srgb} bind def
+/col28 {1.000 0.630 0.630 srgb} bind def
+/col29 {1.000 0.750 0.750 srgb} bind def
+/col30 {1.000 0.880 0.880 srgb} bind def
+/col31 {1.000 0.840 0.000 srgb} bind def
+
+end
+save
+68.0 130.5 translate
+ 90 rotate
+1 -1 scale
+
+/cp {closepath} bind def
+/ef {eofill} bind def
+/gr {grestore} bind def
+/gs {gsave} bind def
+/sa {save} bind def
+/rs {restore} bind def
+/l {lineto} bind def
+/m {moveto} bind def
+/rm {rmoveto} bind def
+/n {newpath} bind def
+/s {stroke} bind def
+/sh {show} bind def
+/slc {setlinecap} bind def
+/slj {setlinejoin} bind def
+/slw {setlinewidth} bind def
+/srgb {setrgbcolor} bind def
+/rot {rotate} bind def
+/sc {scale} bind def
+/sd {setdash} bind def
+/ff {findfont} bind def
+/sf {setfont} bind def
+/scf {scalefont} bind def
+/sw {stringwidth} bind def
+/tr {translate} bind def
+/tnt {dup dup currentrgbcolor
+  4 -2 roll dup 1 exch sub 3 -1 roll mul add
+  4 -2 roll dup 1 exch sub 3 -1 roll mul add
+  4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb}
+  bind def
+/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul
+  4 -2 roll mul srgb} bind def
+/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def
+/$F2psEnd {$F2psEnteredState restore end} def
+%%EndProlog
+
+$F2psBegin
+10 setmiterlimit
+n -1000 7762 m -1000 -1000 l 9946 -1000 l 9946 7762 l cp clip
+ 0.06000 0.06000 sc
+%%Page: 1 1
+% Polyline
+7.500 slw
+ [15 45] 45 sd
+gs  clippath
+3405 2355 m 3375 2475 l 3345 2355 l 3345 2490 l 3405 2490 l cp
+clip
+n 3375 1875 m 3375 2475 l gs col-1 s gr gr
+ [] 0 sd
+% arrowhead
+n 3405 2355 m 3375 2475 l 3345 2355 l  col-1 s
+% Polyline
+ [15 45] 45 sd
+gs  clippath
+5955 2970 m 6075 3000 l 5955 3030 l 6090 3030 l 6090 2970 l cp
+4279 3029 m 4159 2999 l 4279 2969 l 4144 2969 l 4144 3029 l cp
+clip
+n 4159 2999 m 6075 3000 l gs col-1 s gr gr
+ [] 0 sd
+% arrowhead
+n 4279 3029 m 4159 2999 l 4279 2969 l  col-1 s
+% arrowhead
+n 5955 2970 m 6075 3000 l 5955 3030 l  col-1 s
+% Polyline
+ [15 45] 45 sd
+gs  clippath
+3405 4230 m 3375 4350 l 3345 4230 l 3345 4365 l 3405 4365 l cp
+clip
+n 3375 3525 m 3375 4350 l gs col-1 s gr gr
+ [] 0 sd
+% arrowhead
+n 3405 4230 m 3375 4350 l 3345 4230 l  col-1 s
+% Polyline
+n 2700 4425 m 4125 4425 l 4125 5025 l 2700 5025 l cp gs col0 s gr 
+% Polyline
+n 2700 4575 m 4125 4575 l 4125 5175 l 2700 5175 l cp gs col0 s gr 
+% Polyline
+ [15 45] 45 sd
+gs  clippath
+6930 5655 m 6900 5775 l 6870 5655 l 6870 5790 l 6930 5790 l cp
+clip
+n 6900 3525 m 6900 5775 l gs col-1 s gr gr
+ [] 0 sd
+% arrowhead
+n 6930 5655 m 6900 5775 l 6870 5655 l  col-1 s
+% Polyline
+n 2700 2533 m 4125 2533 l 4125 3465 l 2700 3465 l cp gs col-1 s gr 
+% Polyline
+n 2700 900 m 4125 900 l 4125 1834 l 2700 1834 l cp gs col-1 s gr 
+% Polyline
+n 6176 2533 m 7650 2533 l 7650 3465 l 6176 3465 l cp gs col-1 s gr 
+% Polyline
+n 6176 5815 m 7650 5815 l 7650 6749 l 6176 6749 l cp gs col-1 s gr 
+% Polyline
+ [60] 0 sd
+n 825 5250 m 826 5250 l 830 5250 l 836 5250 l 846 5249 l 859 5249 l
+ 877 5248 l 900 5248 l 927 5247 l 959 5246 l 995 5245 l
+ 1035 5244 l 1078 5242 l 1124 5241 l 1172 5240 l 1221 5239 l
+ 1271 5238 l 1322 5237 l 1371 5236 l 1420 5235 l 1468 5235 l
+ 1514 5235 l 1559 5235 l 1601 5235 l 1641 5236 l 1680 5236 l
+ 1716 5238 l 1751 5239 l 1784 5241 l 1815 5244 l 1845 5247 l
+ 1875 5250 l 1908 5254 l 1940 5259 l 1972 5265 l 2001 5271 l
+ 2030 5278 l 2057 5285 l 2083 5293 l 2108 5301 l 2131 5310 l
+ 2153 5319 l 2175 5329 l 2196 5339 l 2217 5349 l 2238 5359 l
+ 2260 5369 l 2282 5379 l 2304 5389 l 2329 5399 l 2354 5408 l
+ 2382 5418 l 2412 5426 l 2444 5435 l 2479 5443 l 2517 5450 l
+ 2558 5457 l 2602 5463 l 2649 5469 l 2700 5475 l 2734 5478 l
+ 2769 5482 l 2805 5485 l 2843 5488 l 2881 5490 l 2921 5493 l
+ 2961 5496 l 3002 5498 l 3045 5501 l 3087 5504 l 3131 5506 l
+ 3175 5508 l 3220 5511 l 3265 5513 l 3311 5515 l 3358 5517 l
+ 3405 5520 l 3452 5522 l 3500 5524 l 3548 5526 l 3596 5528 l
+ 3645 5530 l 3693 5532 l 3743 5534 l 3792 5536 l 3842 5538 l
+ 3892 5539 l 3942 5541 l 3993 5542 l 4043 5544 l 4095 5545 l
+ 4146 5546 l 4198 5548 l 4250 5549 l 4302 5550 l 4355 5550 l
+ 4408 5551 l 4462 5551 l 4517 5552 l 4572 5552 l 4628 5552 l
+ 4684 5551 l 4742 5551 l 4800 5550 l 4848 5549 l 4897 5548 l
+ 4947 5547 l 4998 5546 l 5049 5545 l 5102 5543 l 5155 5541 l
+ 5210 5540 l 5265 5538 l 5321 5536 l 5379 5533 l 5437 5531 l
+ 5496 5529 l 5556 5526 l 5617 5523 l 5679 5521 l 5741 5518 l
+ 5804 5515 l 5868 5512 l 5932 5509 l 5997 5506 l 6062 5502 l
+ 6127 5499 l 6193 5496 l 6258 5492 l 6324 5489 l 6390 5486 l
+ 6456 5482 l 6522 5479 l 6587 5475 l 6652 5472 l 6716 5468 l
+ 6780 5465 l 6844 5461 l 6906 5458 l 6968 5455 l 7029 5451 l
+ 7089 5448 l 7148 5445 l 7206 5441 l 7263 5438 l 7318 5435 l
+ 7373 5432 l 7426 5429 l 7477 5426 l 7527 5423 l 7576 5420 l
+ 7623 5417 l 7669 5414 l 7713 5411 l 7756 5408 l 7797 5406 l
+ 7837 5403 l 7875 5400 l 7940 5395 l 8001 5390 l 8059 5385 l
+ 8113 5379 l 8164 5374 l 8214 5368 l 8261 5362 l 8307 5355 l
+ 8352 5349 l 8396 5342 l 8438 5335 l 8480 5327 l 8520 5320 l
+ 8560 5312 l 8598 5305 l 8634 5298 l 8668 5291 l 8699 5284 l
+ 8728 5278 l 8755 5272 l 8777 5267 l 8797 5262 l 8813 5259 l
+ 8826 5256 l 8836 5253 l 8842 5252 l 8847 5251 l 8849 5250 l
+ 8850 5250 l gs col-1 s gr  [] 0 sd
+% Polyline
+ [60] 0 sd
+n 4650 6750 m 4650 6749 l 4650 6746 l 4650 6740 l 4650 6731 l 4649 6718 l
+ 4649 6701 l 4649 6680 l 4648 6653 l 4647 6621 l 4646 6584 l
+ 4646 6542 l 4644 6496 l 4643 6445 l 4642 6390 l 4641 6331 l
+ 4639 6270 l 4637 6206 l 4636 6140 l 4634 6072 l 4632 6005 l
+ 4630 5937 l 4628 5869 l 4626 5802 l 4623 5737 l 4621 5673 l
+ 4619 5611 l 4616 5552 l 4613 5494 l 4611 5440 l 4608 5387 l
+ 4605 5338 l 4602 5291 l 4599 5246 l 4595 5204 l 4592 5165 l
+ 4588 5127 l 4584 5091 l 4580 5057 l 4575 5025 l 4568 4981 l
+ 4561 4938 l 4553 4898 l 4546 4860 l 4539 4823 l 4532 4788 l
+ 4525 4754 l 4518 4722 l 4512 4690 l 4506 4660 l 4500 4630 l
+ 4494 4601 l 4488 4573 l 4481 4546 l 4475 4519 l 4467 4492 l
+ 4459 4465 l 4450 4439 l 4440 4413 l 4429 4388 l 4417 4362 l
+ 4402 4337 l 4386 4313 l 4369 4289 l 4349 4265 l 4326 4243 l
+ 4302 4221 l 4275 4200 l 4252 4185 l 4228 4170 l 4202 4157 l
+ 4175 4144 l 4146 4132 l 4115 4121 l 4083 4110 l 4049 4100 l
+ 4014 4091 l 3977 4083 l 3939 4075 l 3900 4067 l 3860 4060 l
+ 3820 4053 l 3778 4047 l 3736 4041 l 3694 4035 l 3652 4029 l
+ 3609 4023 l 3567 4018 l 3524 4012 l 3482 4006 l 3441 4000 l
+ 3401 3994 l 3361 3988 l 3322 3981 l 3284 3975 l 3248 3967 l
+ 3212 3960 l 3178 3952 l 3145 3944 l 3114 3936 l 3083 3927 l
+ 3054 3918 l 3027 3909 l 3000 3900 l 2965 3887 l 2932 3874 l
+ 2900 3861 l 2870 3848 l 2841 3836 l 2813 3824 l 2786 3812 l
+ 2760 3801 l 2735 3790 l 2710 3780 l 2686 3769 l 2663 3758 l
+ 2640 3748 l 2618 3737 l 2596 3725 l 2574 3713 l 2553 3700 l
+ 2533 3687 l 2513 3672 l 2494 3656 l 2476 3638 l 2458 3619 l
+ 2442 3598 l 2426 3576 l 2412 3551 l 2400 3525 l 2391 3502 l
+ 2384 3478 l 2377 3452 l 2371 3424 l 2366 3396 l 2362 3365 l
+ 2359 3333 l 2357 3300 l 2355 3266 l 2353 3230 l 2353 3193 l
+ 2352 3156 l 2352 3117 l 2353 3079 l 2353 3039 l 2354 3000 l
+ 2356 2961 l 2357 2921 l 2359 2883 l 2360 2844 l 2362 2807 l
+ 2364 2770 l 2367 2734 l 2369 2700 l 2372 2667 l 2375 2635 l
+ 2378 2604 l 2381 2576 l 2385 2548 l 2390 2522 l 2394 2498 l
+ 2400 2475 l 2408 2449 l 2416 2424 l 2424 2401 l 2433 2380 l
+ 2441 2361 l 2449 2342 l 2456 2325 l 2462 2309 l 2468 2295 l
+ 2474 2280 l 2480 2267 l 2486 2254 l 2493 2242 l 2500 2230 l
+ 2508 2218 l 2518 2207 l 2529 2195 l 2543 2184 l 2559 2173 l
+ 2579 2162 l 2601 2150 l 2628 2140 l 2658 2129 l 2692 2119 l
+ 2731 2109 l 2775 2100 l 2803 2095 l 2833 2090 l 2864 2086 l
+ 2897 2082 l 2932 2078 l 2967 2074 l 3005 2071 l 3043 2068 l
+ 3083 2065 l 3124 2062 l 3166 2060 l 3209 2058 l 3252 2055 l
+ 3297 2053 l 3343 2052 l 3389 2050 l 3436 2048 l 3484 2047 l
+ 3532 2045 l 3580 2044 l 3629 2043 l 3679 2041 l 3728 2040 l
+ 3778 2039 l 3828 2038 l 3878 2037 l 3929 2036 l 3979 2035 l
+ 4030 2034 l 4080 2033 l 4131 2032 l 4182 2032 l 4232 2031 l
+ 4283 2030 l 4334 2029 l 4385 2028 l 4436 2028 l 4487 2027 l
+ 4538 2027 l 4590 2026 l 4642 2026 l 4694 2025 l 4747 2025 l
+ 4800 2025 l 4848 2025 l 4896 2025 l 4945 2026 l 4994 2026 l
+ 5045 2026 l 5096 2027 l 5148 2028 l 5201 2028 l 5255 2029 l
+ 5310 2030 l 5366 2031 l 5423 2032 l 5480 2033 l 5538 2034 l
+ 5597 2035 l 5657 2036 l 5717 2037 l 5778 2039 l 5840 2040 l
+ 5901 2041 l 5963 2043 l 6026 2044 l 6088 2045 l 6151 2047 l
+ 6213 2048 l 6276 2050 l 6338 2051 l 6400 2053 l 6461 2055 l
+ 6522 2056 l 6582 2058 l 6641 2060 l 6700 2061 l 6758 2063 l
+ 6815 2065 l 6870 2067 l 6925 2069 l 6978 2071 l 7030 2073 l
+ 7081 2075 l 7130 2077 l 7177 2079 l 7223 2082 l 7268 2084 l
+ 7311 2086 l 7352 2089 l 7391 2092 l 7429 2094 l 7465 2097 l
+ 7500 2100 l 7562 2106 l 7618 2112 l 7668 2119 l 7713 2127 l
+ 7752 2135 l 7787 2143 l 7817 2152 l 7843 2160 l 7866 2170 l
+ 7885 2179 l 7903 2189 l 7918 2199 l 7932 2209 l 7946 2219 l
+ 7959 2229 l 7973 2239 l 7987 2249 l 8002 2258 l 8018 2268 l
+ 8036 2277 l 8056 2285 l 8077 2294 l 8100 2302 l 8124 2310 l
+ 8149 2318 l 8175 2325 l 8201 2332 l 8228 2339 l 8255 2346 l
+ 8283 2353 l 8313 2360 l 8345 2368 l 8378 2375 l 8412 2383 l
+ 8449 2391 l 8486 2399 l 8525 2407 l 8564 2416 l 8603 2424 l
+ 8641 2432 l 8677 2439 l 8711 2446 l 8742 2453 l 8770 2459 l
+ 8793 2463 l 8812 2467 l 8827 2470 l 8838 2472 l 8845 2474 l
+ 8848 2475 l 8850 2475 l gs col-1 s gr  [] 0 sd
+% Polyline
+ [15 45] 45 sd
+gs  clippath
+2506 4692 m 2625 4725 l 2504 4752 l 2639 4755 l 2641 4695 l cp
+clip
+n 2625 4725 m 2590 4724 l 2569 4724 l 2542 4724 l 2511 4723 l 2477 4722 l
+ 2441 4721 l 2404 4720 l 2368 4719 l 2332 4717 l 2298 4715 l
+ 2266 4714 l 2236 4712 l 2208 4710 l 2182 4707 l 2157 4705 l
+ 2134 4702 l 2112 4699 l 2091 4695 l 2070 4692 l 2050 4688 l
+ 2028 4683 l 2006 4677 l 1985 4672 l 1962 4665 l 1940 4659 l
+ 1917 4652 l 1893 4644 l 1869 4637 l 1845 4629 l 1820 4621 l
+ 1795 4612 l 1769 4604 l 1744 4596 l 1718 4588 l 1692 4581 l
+ 1667 4573 l 1641 4566 l 1616 4560 l 1590 4553 l 1565 4548 l
+ 1539 4542 l 1513 4538 l 1490 4534 l 1466 4530 l 1442 4527 l
+ 1416 4524 l 1390 4521 l 1361 4519 l 1330 4517 l 1298 4515 l
+ 1263 4513 l 1225 4511 l 1185 4509 l 1143 4508 l 1099 4506 l
+ 1053 4505 l 1007 4504 l 962 4503 l 919 4502 l 879 4502 l
+ 843 4501 l 812 4501 l 788 4500 l 771 4500 l 759 4500 l
+ 753 4500 l 750 4500 l gs col-1 s gr gr
+ [] 0 sd
+% arrowhead
+n 2506 4692 m 2625 4725 l 2504 4752 l  col-1 s
+% Polyline
+ [15 45] 45 sd
+gs  clippath
+4318 4686 m 4200 4650 l 4321 4626 l 4186 4619 l 4184 4679 l cp
+clip
+n 4200 4650 m 4242 4652 l 4266 4653 l 4295 4654 l 4326 4656 l 4359 4658 l
+ 4392 4661 l 4423 4664 l 4453 4667 l 4481 4671 l 4506 4675 l
+ 4529 4679 l 4551 4685 l 4571 4690 l 4590 4697 l 4608 4704 l
+ 4625 4713 l 4641 4721 l 4656 4730 l 4671 4740 l 4686 4751 l
+ 4702 4763 l 4717 4776 l 4733 4791 l 4748 4807 l 4763 4825 l
+ 4778 4843 l 4793 4863 l 4808 4885 l 4822 4907 l 4835 4931 l
+ 4848 4955 l 4861 4981 l 4873 5007 l 4884 5034 l 4895 5061 l
+ 4906 5090 l 4915 5119 l 4925 5150 l 4932 5175 l 4939 5201 l
+ 4947 5228 l 4954 5256 l 4961 5285 l 4968 5315 l 4975 5346 l
+ 4982 5378 l 4990 5411 l 4997 5444 l 5005 5478 l 5013 5513 l
+ 5021 5548 l 5028 5582 l 5037 5617 l 5045 5651 l 5053 5685 l
+ 5061 5718 l 5070 5750 l 5078 5781 l 5087 5811 l 5095 5840 l
+ 5104 5868 l 5113 5894 l 5122 5920 l 5131 5943 l 5140 5966 l
+ 5150 5987 l 5150 5988 l 5162 6011 l 5174 6033 l 5187 6055 l
+ 5200 6075 l 5215 6094 l 5230 6112 l 5245 6129 l 5262 6145 l
+ 5279 6160 l 5297 6174 l 5315 6187 l 5334 6199 l 5353 6210 l
+ 5372 6220 l 5391 6229 l 5411 6237 l 5430 6244 l 5450 6250 l
+ 5469 6256 l 5488 6261 l 5507 6265 l 5525 6269 l 5544 6272 l
+ 5563 6275 l 5583 6278 l 5604 6281 l 5625 6283 l 5647 6285 l
+ 5671 6287 l 5696 6289 l 5722 6291 l 5751 6292 l 5782 6293 l
+ 5814 6295 l 5848 6296 l 5884 6297 l 5919 6297 l 5953 6298 l
+ 5985 6299 l 6013 6299 l 6036 6300 l 6054 6300 l 6065 6300 l
+ 6072 6300 l 6075 6300 l gs col-1 s gr gr
+ [] 0 sd
+% arrowhead
+n 4318 4686 m 4200 4650 l 4321 4626 l  col-1 s
+% Polyline
+ [15 45] 45 sd
+gs  clippath
+5955 6420 m 6075 6450 l 5955 6480 l 6090 6480 l 6090 6420 l cp
+clip
+n 4200 4800 m 4203 4800 l 4211 4800 l 4223 4801 l 4242 4802 l 4266 4803 l
+ 4295 4804 l 4326 4806 l 4359 4808 l 4392 4811 l 4423 4814 l
+ 4453 4817 l 4481 4821 l 4506 4825 l 4529 4829 l 4551 4835 l
+ 4571 4840 l 4590 4847 l 4608 4854 l 4625 4863 l 4641 4871 l
+ 4656 4880 l 4671 4890 l 4686 4901 l 4702 4913 l 4717 4926 l
+ 4733 4941 l 4748 4957 l 4763 4975 l 4778 4993 l 4793 5013 l
+ 4808 5035 l 4822 5057 l 4835 5081 l 4848 5105 l 4861 5131 l
+ 4873 5157 l 4884 5184 l 4895 5211 l 4906 5240 l 4915 5269 l
+ 4925 5300 l 4932 5325 l 4939 5351 l 4947 5378 l 4954 5406 l
+ 4961 5435 l 4968 5465 l 4975 5496 l 4982 5528 l 4990 5561 l
+ 4997 5594 l 5005 5628 l 5013 5663 l 5021 5698 l 5028 5732 l
+ 5037 5767 l 5045 5801 l 5053 5835 l 5061 5868 l 5070 5900 l
+ 5078 5931 l 5087 5961 l 5095 5990 l 5104 6018 l 5113 6044 l
+ 5122 6070 l 5131 6093 l 5140 6116 l 5150 6137 l 5150 6138 l
+ 5162 6161 l 5174 6183 l 5187 6205 l 5200 6225 l 5215 6244 l
+ 5230 6262 l 5245 6279 l 5262 6295 l 5279 6310 l 5297 6324 l
+ 5315 6337 l 5334 6349 l 5353 6360 l 5372 6370 l 5391 6379 l
+ 5411 6387 l 5430 6394 l 5450 6400 l 5469 6406 l 5488 6411 l
+ 5507 6415 l 5525 6419 l 5544 6422 l 5563 6425 l 5583 6428 l
+ 5604 6431 l 5625 6433 l 5647 6435 l 5671 6437 l 5696 6439 l
+ 5722 6441 l 5751 6442 l 5782 6443 l 5814 6445 l 5848 6446 l
+ 5884 6447 l 5919 6447 l 5953 6448 l 5985 6449 l 6013 6449 l
+ 6036 6450 l 6075 6450 l gs col-1 s gr gr
+ [] 0 sd
+% arrowhead
+n 5955 6420 m 6075 6450 l 5955 6480 l  col-1 s
+% Polyline
+ [15 45] 45 sd
+gs  clippath
+870 4380 m 750 4350 l 870 4320 l 735 4320 l 735 4380 l cp
+clip
+n 2625 4575 m 2622 4575 l 2617 4575 l 2606 4575 l 2590 4574 l 2569 4574 l
+ 2542 4574 l 2511 4573 l 2477 4572 l 2441 4571 l 2404 4570 l
+ 2368 4569 l 2332 4567 l 2298 4565 l 2266 4564 l 2236 4562 l
+ 2208 4560 l 2182 4557 l 2157 4555 l 2134 4552 l 2112 4549 l
+ 2091 4545 l 2070 4542 l 2050 4538 l 2028 4533 l 2006 4527 l
+ 1985 4522 l 1962 4515 l 1940 4509 l 1917 4502 l 1893 4494 l
+ 1869 4487 l 1845 4479 l 1820 4471 l 1795 4462 l 1769 4454 l
+ 1744 4446 l 1718 4438 l 1692 4431 l 1667 4423 l 1641 4416 l
+ 1616 4410 l 1590 4403 l 1565 4398 l 1539 4392 l 1513 4388 l
+ 1490 4384 l 1466 4380 l 1442 4377 l 1416 4374 l 1390 4371 l
+ 1361 4369 l 1330 4367 l 1298 4365 l 1263 4363 l 1225 4361 l
+ 1185 4359 l 1143 4358 l 1099 4356 l 1053 4355 l 1007 4354 l
+ 962 4353 l 919 4352 l 879 4352 l 843 4351 l 812 4351 l
+ 788 4350 l 750 4350 l gs col-1 s gr gr
+ [] 0 sd
+% arrowhead
+n 870 4380 m 750 4350 l 870 4320 l  col-1 s
+/Times-Italic ff 240.00 scf sf
+3460 2300 m
+gs 1 -1 sc (fork/exec) col-1 sh gr
+/Times-Italic ff 240.00 scf sf
+3460 3825 m
+gs 1 -1 sc (fork/exec) col-1 sh gr
+/Courier ff 285.00 scf sf
+3075 4800 m
+gs 1 -1 sc (cat) col-1 sh gr
+/Courier ff 285.00 scf sf
+3075 4950 m
+gs 1 -1 sc (cat) col-1 sh gr
+/Helvetica ff 225.00 scf sf
+1425 5175 m
+gs 1 -1 sc (trusted) col-1 sh gr
+/Helvetica ff 285.00 scf sf
+1275 6525 m
+gs 1 -1 sc (unrelated processes) col-1 sh gr
+/Helvetica ff 225.00 scf sf
+1050 5475 m
+gs 1 -1 sc (untrusted) col-1 sh gr
+/Helvetica ff 225.00 scf sf
+975 5775 m
+gs 1 -1 sc (invoking user's security boundary) col-1 sh gr
+/Helvetica ff 285.00 scf sf
+7800 4725 m
+gs 1 -1 sc (TCB) col-1 sh gr
+/Helvetica ff 285.00 scf sf
+7800 6000 m
+gs 1 -1 sc (service) col-1 sh gr
+/Helvetica ff 285.00 scf sf
+7875 6225 m
+gs 1 -1 sc (user) col-1 sh gr
+/Helvetica ff 225.00 scf sf
+7950 2175 m
+gs 1 -1 sc (untrusted) col-1 sh gr
+/Helvetica ff 285.00 scf sf
+5475 1500 m
+gs 1 -1 sc (invoking user) col-1 sh gr
+/Times-Italic ff 240.00 scf sf
+4875 4875 m
+gs 1 -1 sc (pipes \(set up by) col-1 sh gr
+/Times-Italic ff 240.00 scf sf
+5025 5100 m
+gs 1 -1 sc (client+daemon\)) col-1 sh gr
+/Times-Italic ff 240.00 scf sf
+5925 3758 m
+gs 1 -1 sc (fork/exec) col-1 sh gr
+/Times-Roman ff 300.00 scf sf
+6422 3058 m
+gs 1 -1 sc (Daemon) col-1 sh gr
+/Times-Roman ff 300.00 scf sf
+6422 6516 m
+gs 1 -1 sc (program) col-1 sh gr
+/Times-Roman ff 300.00 scf sf
+6422 6282 m
+gs 1 -1 sc (Service) col-1 sh gr
+/Times-Roman ff 300.00 scf sf
+2993 3058 m
+gs 1 -1 sc (Client) col-1 sh gr
+/Times-Roman ff 300.00 scf sf
+3000 1425 m
+gs 1 -1 sc (Caller) col-1 sh gr
+/Helvetica ff 225.00 scf sf
+7050 2325 m
+gs 1 -1 sc (trusted) col-1 sh gr
+/Times-Italic ff 240.00 scf sf
+4800 2942 m
+gs 1 -1 sc (socket) col-1 sh gr
+/Times-Italic ff 240.00 scf sf
+750 4275 m
+gs 1 -1 sc (or passed from caller) col-1 sh gr
+/Times-Italic ff 240.00 scf sf
+825 3825 m
+gs 1 -1 sc (fds) col-1 sh gr
+/Times-Italic ff 240.00 scf sf
+825 4050 m
+gs 1 -1 sc (opened by client) col-1 sh gr
+/Helvetica ff 225.00 scf sf
+5700 1950 m
+gs 1 -1 sc (service user's security boundary) col-1 sh gr
+$F2psEnd
+rs
+showpage
diff --git a/parser.c b/parser.c
new file mode 100644 (file)
index 0000000..5157f39
--- /dev/null
+++ b/parser.c
@@ -0,0 +1,1367 @@
+/*
+ * userv - parser.c
+ * configuration file parser; this file is actually #included from
+ * lexer.c, which is generated using flex from lexer.l, in turn from
+ * lexer.l.m4.  It's in a separate file so that we don't have to worry
+ * about m4 quoting &c., but we have to #include it so that the C
+ * objects from the lexer are available.
+ *
+ * Copyright (C)1996-1999,2001,2006,2012 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+static int parse_file(const char *string, int *didexist);
+static int parser(int allowce);
+int parse_string(const char *string, const char *descrip, int isinternal);
+
+/*
+ * Error-handling routines
+ */
+
+static void closeerrorfile(void) {
+  if (eh.file && !eh.filekeep) {
+    if (fclose(eh.file)) {
+      eh.handling= tokv_word_errorstostderr;
+      parseerrprint("error writing to error log file `%s': %s",
+                   eh.filename,strerror(errno));
+    }
+    free(eh.filename);
+  }
+  eh.handling= 0;
+  eh.file= 0; eh.filename= 0; eh.filekeep= 0;
+}
+
+static void senderrmsg(const char *errmsg, int useehandling) {
+  char suberrmsg[MAX_ERRMSG_LEN];
+  int e;
+  time_t now;
+  struct tm *lt;
+
+  switch (useehandling) {
+  case tokv_word_errorstostderr:
+    senderrmsgstderr(errmsg);
+    break;
+  case tokv_word_errorstosyslog:
+    ensurelogopen(eh.logfacility);
+    syslog(eh.loglevel,"%s",errmsg);
+    break;
+  case tokv_word_errorstofile:
+    if (time(&now)==-1) syscallerror("get current time");
+    lt= localtime(&now); if (!lt) syscallerror("convert current time");
+    if (fprintf(eh.file,"%d-%02d-%02d %02d:%02d:%02d: uservd: %s\n",
+               lt->tm_year+1900, lt->tm_mon+1, lt->tm_mday,
+               lt->tm_hour, lt->tm_min, lt->tm_sec,
+               errmsg) == EOF) {
+      e= errno;
+      closeerrorfile(); eh.handling= tokv_word_errorstofile;
+      snyprintf(suberrmsg,sizeof(suberrmsg),
+               "error writing to error log file `%.*s': %s;"
+               " reverting to errors-to-stderr",
+               (int)(sizeof(suberrmsg)>>1),eh.filename,strerror(e));
+      senderrmsg(suberrmsg,eh.handling);
+      senderrmsg(errmsg,eh.handling);
+    }
+    break;
+  default:
+    abort();
+  }
+}
+
+static void errwhere(struct parser_state *tstate, char *bufput, int bufputlen) {
+  static const char suffix[]= "references ...";
+  char errmsg[MAX_ERRMSG_LEN];
+  
+  if (!tstate) {
+    strnycpy(bufput,"<initialisation>: ",bufputlen);
+    return;
+  }
+  if (!tstate->notedreferer && tstate->upstate && !tstate->upstate->isinternal) {
+    errwhere(tstate->upstate,errmsg,sizeof(errmsg)-sizeof(suffix));
+    strcat(errmsg,suffix);
+    senderrmsg(errmsg,eh.handling);
+    tstate->notedreferer= 1;
+  }
+  snyprintf(bufput,bufputlen,"%.*s:%d: ",bufputlen-10,
+           tstate->filename,tstate->reportlineno);
+}
+
+int parseerrprint(const char *fmt, ...) {
+  va_list al;
+  char errmsg[MAX_ERRMSG_LEN];
+
+  va_start(al,fmt);
+  errwhere(cstate,errmsg,sizeof(errmsg)>>1);
+  vsnytprintfcat(errmsg,sizeof(errmsg),fmt,al);
+  senderrmsg(errmsg,eh.handling);
+  va_end(al);
+
+  return tokv_error;
+}
+
+static int unexpected(int found, int wanted, const char *wantedstr) {
+  /* pass wanted==-1 if you know it's not what you wanted;
+   * otherwise this function will check it for you.
+   */
+  if (found == wanted) return 0;
+  if (found == tokv_error) return found;
+  return parseerrprint("found %s, expected %s",printtoken(found),wantedstr);
+}
+
+static int stringoverflow(const char *where) {
+  return parseerrprint("string buffer became far too large building %s",where);
+}
+
+/*
+ * General assistance functions
+ */
+
+static void freecharparray(char **array) {
+  char **pp;
+
+  if (!array) return;
+  for (pp=array; *pp; pp++) free(*pp);
+  free(array);
+}
+
+static void countnewlines(void) {
+  char *p;
+  for (p=yytext; *p; p++)
+    if (*p == '\n')
+      cstate->lineno++;
+}
+
+static int dequote(char *inplace) {
+  char *p, *q, buf[4], *bep;
+  int v;
+
+  p=q=inplace;
+  assert(*p=='"');  p++;
+  while (*p && *p != '"') {
+    if (*p != '\\') { *q++= *p++; continue; }
+    switch (*++p) {
+    case 'n': *q++= '\n'; p++; continue;
+    case 'r': *q++= '\r'; p++; continue;
+    case 't': *q++= '\t'; p++; continue;
+    case 'x':
+      p++;
+      if (!((buf[0]= *p++) && (buf[1]= *p++)))
+       return parseerrprint("quoted string ends inside \\x<hex> sequence");
+      buf[2]= 0;
+      v= strtoul(buf,&bep,16);
+      if (bep != buf+2)
+       return parseerrprint("invalid \\<hex> sequence \\x%s in quoted string",buf);
+      assert(!(v & ~0xff));
+      *q++= v;
+      continue;
+    default:
+      if (ISCHAR(isalpha,*p))
+        return parseerrprint("unknown \\<letter> sequence \\%c in quoted string",*p);
+      if (ISCHAR(isdigit,*p)) {
+        if (!((buf[0]= *p++) && (buf[1]= *p++) && (buf[2]= *p++))) abort();
+        buf[3]= 0; v= strtoul(buf,&bep,8);
+        if (bep != buf+3 || (v & ~0xff))
+          return parseerrprint("invalid \\<octal> sequence \\%s in quoted string",buf);
+        *q++= v; continue;
+      } else if (ISCHAR(ispunct,*p)) {
+        *q++= *p++; continue;
+      } else {
+       while (*p==' ' || *p=='\t') p++;
+       v= *p++; assert(v=='\n');
+      }
+    }
+  }
+  assert(*p); p++; assert(!*p);
+  *q++= 0;
+  return tokv_quotedstring;
+}
+
+const char *printtoken(int token) {
+  /* Returns pointer to static buffer, overwritten by next call. */
+  static const char keywordfmt[]= "keyword `%s'";
+  static const char operatorfmt[]= "operator `%s'";
+  
+  static char buf[250];
+  
+  char *q;
+  const char *p;
+  int i, c, l;
+  
+  if ((token & tokm_repres) == tokr_word) {
+    assert(strlen(yytext)+sizeof(keywordfmt)<sizeof(buf));
+    snyprintf(buf,sizeof(buf),keywordfmt,yytext);
+    return buf;
+  } else if (token & tokt_number) {
+    snyprintf(buf,sizeof(buf),"number %d",lr_min); return buf;
+  } else if (token & tokt_fdrange && lr_max==-1) {
+    snyprintf(buf,sizeof(buf),"fdrange %d-",lr_min); return buf;
+  } else if (token & tokt_fdrange) {
+    snyprintf(buf,sizeof(buf),"fdrange %d-%d",lr_min,lr_max); return buf;
+  } else if ((token & tokm_repres) == tokr_punct) {
+    assert(strlen(yytext)+sizeof(operatorfmt)<sizeof(buf));
+    snyprintf(buf,sizeof(buf),operatorfmt,yytext);
+    return buf;
+  } else if (token & tokt_string) {
+    switch (token) {
+    case tokv_barestring: strcpy(buf,"unquoted string (bare word)"); break;
+    case tokv_quotedstring: strcpy(buf,"quoted string"); break;
+    default: abort();
+    }
+    strcat(buf," `");
+    l= strlen(buf); i= sizeof(buf)-l-2; p= yytext; q= buf+l;
+    while ((c= *p++)) {
+      if (i-- <= 0) { q--; strcpy(q-3,"..."); break; }
+      if (ISCHAR(isspace,c)) c= ' ';
+      else if (!ISCHAR(isprint,c) || ISCHAR(iscntrl,c)) c= '?';
+      else *q++= c;
+    }
+    strcpy(q,"'");
+    return buf;
+  } else {
+    switch (token) {
+    case tokv_lwsp:    return "linear whitespace";
+    case tokv_newline: return "newline (or comment followed by newline)";
+    case tokv_eof:     return "end of input file";
+    case tokv_error:   return "syntax error token";
+    default:
+      sprintf(buf,"token#0%o",token); return buf;
+    }
+  }
+}
+
+static const char *string2path(const char *in) {
+  /* Returned pointers become invalid on next call.
+   * May return 0, having printed an error message.
+   */
+  static char *p;
+  static int pl;
+  
+  if (strncmp(in,"~/",2)) return in;
+  if (makeroom(&p,&pl,strlen(serviceuser_dir)+1+strlen(in+2)+1)) {
+    stringoverflow("pathname");
+    return 0;
+  }
+  snyprintf(p,pl,"%s/%s",serviceuser_dir,in+2);
+  return p;
+}
+
+/*
+ * Parser component functions for parsing parameters to directives
+ *
+ * Functions pa_... parse just one parameter,
+ *  and return tokv_error or 0, having scanned exactly what they were expecting
+ * Functions paa_... parse complete parameter lists, including the leading lwsp,
+ *  and return tokv_error or 0, having scanned up to and including the newline
+ *
+ * For any function which takes `const char **rv' the
+ * value returned in *rv is overwritten by repeated calls to
+ * any of those functions (whether the same or another).
+ */
+
+static int pa_mnl(void) {
+  return unexpected(yylex(),tokv_newline,"newline");
+}
+static int pa_mwsp(void) {
+  return unexpected(yylex(),tokv_lwsp,"linear whitespace");
+}
+
+static int pa_string(const char **rv) {
+  static char *p= 0;
+  static int pl= 0;
+
+  int r, l;
+
+  r= pa_mwsp(); if (r) return r;
+  r= yylex(); if (r == tokv_error) return r;
+  if ((r & tokm_repres) == tokr_nonstring) return unexpected(r,-1,"string");
+  l= strlen(yytext)+1;
+  if (makeroom(&p,&pl,l)) return stringoverflow("string argument");
+  strcpy(p,yytext); *rv= p;
+
+  return 0;
+}  
+
+static int pa_numberdollar(int *value_r) {
+  /* Also parses the whitespace beforehand. */
+  int r;
+
+  r= pa_mwsp(); if (r) return r;
+  r= yylex(); if (r == tokv_error || r == tokv_dollar) return r;
+  if (unexpected(r,tokv_ordinal,"expected number or dollar")) return tokv_error;
+  *value_r= lr_min;
+  return r;
+}
+
+static int paa_1string(const char **rv) {
+  int r;
+
+  r= pa_string(rv); if (r) return r;
+  return pa_mnl();
+}  
+
+static int paa_1path(const char **rv) {
+  const char *cp;
+  int r;
+  
+  r= paa_1string(&cp); if (r) return r;
+  *rv= string2path(cp); if (!*rv) return tokv_error;
+  return 0;
+}
+
+static int paa_pathargs(const char **path_r, char ***newargs_r) {
+  /* Repeated calls do _not_ overwrite newargs_r; caller must free.
+   * Repeated calls _do_ overwrite string returned in path_r.
+   * Any call to this function invalidates previous returns in *rv.
+   */
+  char **newargs;
+  const char *string;
+  int used, size, r;
+
+  r= pa_string(&string); if (r == tokv_error) return r;
+  *path_r= string2path(string); if (!*path_r) return tokv_error;
+  
+  used=0; size=0;
+  newargs= xmalloc(sizeof(char*)*(size+1));
+
+  for (;;) {
+    r= yylex(); if (r == tokv_error) goto error;
+    if (r==tokv_newline) break;
+    if (unexpected(r,tokv_lwsp,"newline after or whitespace between arguments")) {
+      r= tokv_error; goto error;
+    }
+    r= yylex(); if (r==tokv_error) goto error;
+    if ((r & tokm_repres) == tokr_nonstring) {
+      r= unexpected(r,-1,"string for command argument");
+      goto error;
+    }
+    if (used>=size) {
+      if (used >= MAX_ARGSDEFVAR) {
+       r= parseerrprint("far too many arguments to service program");
+       goto error;
+      }
+      size= (used+5)<<1;
+      newargs= xrealloc(newargs,sizeof(char*)*(size+1));
+    }
+    newargs[used++]= xstrsave(yytext);
+  }
+  newargs[used]= 0;
+  *newargs_r= newargs;
+  return 0;
+  
+error:
+  newargs[used]=0;
+  freecharparray(newargs);
+  return r;
+}
+
+static int paa_message(const char **message_r) {
+  /* Returned value is invalidated by repeated calls. */
+  static char *buildbuf;
+  static int buildbuflen;
+  const char *usetext;
+
+  int r, tl;
+
+  r= pa_mwsp(); if (r) return r;
+  tl= 1;
+  if (makeroom(&buildbuf,&buildbuflen,50)) return stringoverflow("start of message");
+  buildbuf[0]= 0;
+  for (;;) {
+    r= yylex(); if (r == tokv_error) return r;
+    if (r == tokv_eof)
+      return parseerrprint("unexpected end of file in message text");
+    if (r == tokv_newline) break;
+    usetext= r == tokv_lwsp ? " " : yytext;
+    tl+= strlen(usetext);
+    if (makeroom(&buildbuf,&buildbuflen,tl)) return stringoverflow("message");
+    strcat(buildbuf,usetext);
+  }
+  *message_r= buildbuf;
+  return 0;
+}
+
+/*
+ * Skipping routines (used by e.g. the `if' code).
+ */
+
+static int skiptoeol(void) {
+  /* Returns 0 if OK, having just parsed the newline
+   * or tokv_error if an error occurs, leaving the position at the error
+   * (which may be EOF or a syntax error token).
+   */
+  int token;
+
+  do { token= yylex(); }
+  while (token != tokv_newline && !(token & tokt_exception));
+  if (token == tokv_newline) return 0;
+  if (token == tokv_error) return token;
+  assert(token == tokv_eof);
+  return parseerrprint("unexpected end of file while looking for end of line");
+}
+
+static int skip(int allowce) {
+  /* Scans a piece of config without executing it.  Returns
+   * tokv_error (leaving the parsing state at the error),
+   * or the tokt_controlend token with type allowce if one
+   * was found (in which case rest of line, including any whitespace
+   * after tokt_controlend, has not been parsed), or tokv_eof.
+   */
+  int token, r;
+
+  for (;;) { /* loop over lines */
+    cstate->reportlineno= cstate->lineno;
+    do { token= yylex(); } while (token == tokv_lwsp || token == tokv_newline);
+    if (token & tokt_exception) {
+      return token;
+    } else if (token & tokt_controlend) {
+      if (allowce == lr_controlend) return token;
+      else return unexpected(token,-1,"control structure end of a different kind");
+    } else if (token & tokt_controlstart) {
+      r= token;
+      while (r & tokt_controlstart) {
+        r= skiptoeol(); if (r) return r;
+       cstate->reportlineno= cstate->lineno;
+        r= skip(token); if (r & tokt_exception) return r;
+      }
+    } else if (!(token & tokt_directive) && !(token & tokt_condop)) {
+      return parseerrprint("not a directive (or conditional operator) "
+                          "while looking for control structure end");
+    }
+    r= skiptoeol(); if (r) return r;
+  }
+}
+
+/*
+ * Routines for parsing and getting the values of parameters
+ */
+
+static void parm_1string(char ***rvalues, const char *tocopy) {
+  char **a;
+  a= xmalloc(sizeof(char*)*2);
+  a[0]= xstrsave(tocopy);
+  a[1]= 0;
+  *rvalues= a;
+}
+
+static char *parm_ulong(unsigned long ul) {
+  char *p;
+  int l;
+
+  l= CHAR_BIT*sizeof(unsigned long)/3+4;
+  p= xmalloc(l);
+  snyprintf(p,l,"%lu",ul);
+  return p;
+}
+
+static int parm_usernameuid(char ***rvalues, const char *name, uid_t id) {
+  char **a;
+  
+  a= xmalloc(sizeof(char*)*3);
+  a[0]= xstrsave(name);
+  a[1]= parm_ulong(id);
+  a[2]= 0;
+  *rvalues= a;
+  return 0;
+}
+
+static int parm_groups(char ***rvalues, int size,
+                      gid_t *gids, const char *const *groups) {
+  char **a;
+  int i;
+  
+  if (size >= 2 && gids[0] == gids[1]) { size--; gids++; groups++; }
+  a= xmalloc(sizeof(char*)*(size+1)*2);
+  for (i=0; i<size; i++) {
+    a[i]= xstrsave(groups[i]);
+    a[size+i]= parm_ulong(gids[i]);
+  }
+  a[size*2]= 0;
+  *rvalues= a;
+  return 0;
+}  
+
+static int pf_service(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,service); return 0;
+}
+
+static int pf_callinguser(int ptoken, char ***rvalues) {
+  return parm_usernameuid(rvalues,loginname,request_mbuf.callinguid);
+}
+
+static int pf_serviceuser(int ptoken, char ***rvalues) {
+  return parm_usernameuid(rvalues,serviceuser,serviceuser_uid);
+}
+
+static int pf_callinggroup(int ptoken, char ***rvalues) {
+  return parm_groups(rvalues,request_mbuf.ngids,calling_gids,calling_groups);
+}
+
+static int pf_servicegroup(int ptoken, char ***rvalues) {
+  return parm_groups(rvalues,service_ngids,service_gids,service_groups);
+}
+
+static int pf_callingusershell(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,callinguser_shell); return 0;
+}
+
+static int pf_serviceusershell(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,serviceuser_shell); return 0;
+}
+
+static int pa_parameter(char ***rvalues, char **rname) {
+  /* Scans a single parameter token and calls the appropriate parameter
+   * function, returning tokv_error (leaving the parser state wherever),
+   * or 0 on success (having just parsed the parameter name).
+   * If rname is non-null then the name of the parameter (malloc'd) will
+   * be stored in it.
+   *
+   * Caller must free *rvalues using freecharparray.
+   */
+  int token, r, i;
+  char *name;
+
+  token= yylex(); if (token == tokv_error) return token;
+  name= xstrsave(yytext);
+  if ((token & tokm_repres) != tokr_nonstring &&
+      !memcmp(yytext,"u-",2) && strlen(yytext)>=3) {
+    for (i=0;
+        i<request_mbuf.nvars && strcmp(yytext+2,defvararray[i].key);
+        i++);
+    if (i>=request_mbuf.nvars) {
+      *rvalues= xmalloc(sizeof(char*));
+      **rvalues= 0;
+    } else {
+      parm_1string(rvalues,defvararray[i].value);
+    }
+  } else if (token & tokt_parameter) {
+    r= (lr_parameter)(token,rvalues);
+    if (r) { free(name); return r; }
+  } else {
+    free(name);
+    return unexpected(token,-1,"parameter name");
+  }
+  debug_dumpparameter(name,*rvalues);
+  if (rname) *rname= name;
+  else free(name);
+  return 0;
+}
+
+/*
+ * Routines for parsing conditions, including
+ * parameter-based conditional functions (parmcondition's).
+ */
+
+int pcf_glob(int ctoken, char *const *pv, int *rtrue) {
+  int token, actrue, r;
+  char *const *pp;
+
+  actrue= 0;
+  r= pa_mwsp(); if (r) return r;
+  for (;;) {
+    token= yylex();
+    if ((token & tokm_repres) == tokr_nonstring)
+      return unexpected(token,-1,"glob pattern");
+    for (pp= pv; !actrue && *pp; pp++) if (!fnmatch(yytext,*pp,0)) actrue= 1;
+    token= yylex(); 
+    if (token == tokv_newline) break;
+    if (unexpected(token,tokv_lwsp,"newline after or whitespace between glob patterns"))
+      return tokv_error;
+  }
+  *rtrue= actrue;
+  return 0;
+}
+
+int pcf_range(int ctoken, char *const *pv, int *rtrue) {
+  int mintoken, min, maxtoken, max, r;
+  char *const *pp;
+  char *ep;
+  unsigned long v;
+
+  r= pa_mwsp(); if (r) return r;
+  mintoken= pa_numberdollar(&min); if (mintoken == tokv_error) return mintoken;
+  r= pa_mwsp(); if (r) return r;
+  maxtoken= pa_numberdollar(&max); if (maxtoken == tokv_error) return maxtoken;
+  r= pa_mnl(); if (r) return r;
+  for (pp= pv; *pp; pp++) {
+    v= strtoul(*pp,&ep,10); if (*ep) continue;
+    if (mintoken != tokv_dollar && v < min) continue;
+    if (maxtoken != tokv_dollar && v > max) continue;
+    *rtrue= 1; return 0;
+  }
+  *rtrue= 0; return 0;
+}
+
+int pcf_grep(int ctoken, char *const *pv, int *rtrue) {
+  FILE *file;
+  const char *cp;
+  char *const *pp;
+  char *buf, *p;
+  int r, maxlen, l, c, actrue, posstrue;
+  
+  r= paa_1path(&cp); if (r) return r;
+  file= fopen(cp,"r");
+  if (!file)
+    return parseerrprint("unable to open file `%s' for grep: %s",cp,strerror(errno));
+  maxlen= 0;
+  for (pp= pv; *pp; pp++) { l= strlen(*pp); if (l > maxlen) maxlen= l; }
+  buf= xmalloc(maxlen+2); actrue= 0; c= 0;
+  while (!actrue && c!=EOF) {
+    c= getc(file); if (c==EOF) break;
+    if (ISCHAR(isspace,c)) continue;
+    l= maxlen+1; p= buf;
+    while (l>0 && c!='\n' && c!=EOF) { *p++= c; l--; c= getc(file); } 
+    if (c=='\n' || c==EOF || ISCHAR(isspace,c)) {
+      while (p>buf && ISCHAR(isspace,p[-1])) --p;
+      *p= 0; posstrue= 0;
+      for (pp= pv; !posstrue && *pp; pp++)
+       if (!strcmp(*pp,buf)) posstrue= 1;
+    } else {
+      posstrue= 0;
+    }
+    if (c!='\n' && c!=EOF) {
+      for (;;) {
+        c= getc(file);
+        if (c==EOF || c=='\n') break;
+        if (!ISCHAR(isspace,c)) posstrue= 0;
+      }
+    }
+    if (posstrue) actrue= 1;
+  }
+  if (ferror(file)) {
+    parseerrprint("error while reading `%s' for grep: %s",cp,strerror(errno));
+    fclose(file); free(buf); return tokv_error;
+  }
+  assert(actrue || feof(file));
+  fclose(file); free(buf); *rtrue= actrue;
+  return 0;
+} 
+
+static int pa_condition(int *rtrue) {
+  /* Scans up to and including the newline following the condition;
+   * may scan more than one line if the condition is a multi-line
+   * one.  Returns 0 (setting *rtrue, and parsing up to and including
+   * the last newline) or tokv_error.
+   * Expects to scan the whitespace before its condition.
+   */
+  int r, token, andor, ctrue, actrue;
+  char **parmvalues;
+
+  r= pa_mwsp(); if (r) return r;
+  token= yylex();
+  if (token == tokv_error) {
+    return token;
+  } else if (token == tokv_not) {
+    r= pa_condition(&ctrue); if (r) return r;
+    *rtrue= !ctrue; return 0;
+  } else if (token == tokv_openparen) {
+    andor= 0; actrue= -1;
+    for (;;) {
+      cstate->reportlineno= cstate->lineno;
+      r= pa_condition(&ctrue); if (r) return r;
+      switch (andor) {
+      case 0:         assert(actrue==-1); actrue= ctrue;           break;
+      case tokv_and:  assert(actrue>=0); actrue= actrue && ctrue;  break;
+      case tokv_or:   assert(actrue>=0); actrue= actrue || ctrue;  break;
+      default:        abort();
+      }
+      do { token= yylex(); } while (token == tokv_lwsp);
+      if (token == tokv_error) return token;
+      if (token == tokv_closeparen) break;
+      if (andor) {
+       r= unexpected(token,andor,"same conjunction as before"); if (r) return r;
+      } else {
+        if (token != tokv_and && token != tokv_or)
+          return unexpected(token,-1,"first conjunction inside connective");
+        andor= token;
+      }
+    }
+    r= pa_mnl(); if (r) return r;
+    *rtrue= actrue; return 0;
+  } else if (token & tokt_parmcondition) {
+    r= pa_mwsp(); if (r) return r;
+    r= pa_parameter(&parmvalues,0); if (r) return r;
+    r= (lr_parmcond)(token,parmvalues,rtrue); freecharparray(parmvalues);
+    return r;
+  } else {
+    return unexpected(token,-1,"condition");
+  }
+}
+
+/*
+ * Directive functions and associated `common code' functions
+ */
+
+/* Directives specifying the service program (execute-... and reset) */
+
+static void execreset(void) {
+  execute= 0;
+  execbuiltin= 0;
+  free(execpath); execpath= 0;
+  freecharparray(execargs); execargs= 0;
+}
+
+int df_reject(int dtoken) {
+  int r;
+  
+  r= pa_mnl(); if (r) return r;
+  execreset();
+  execute= tokv_word_reject;
+  return 0;
+}
+
+int df_executefrompath(int dtoken) {
+  int r;
+  
+  r= pa_mnl(); if (r) return r;
+  execreset();
+  execute= tokv_word_executefrompath;
+  return 0;
+}
+
+int df_execute(int dtoken) {
+  const char *rv;
+  char **newargs;
+  int r;
+
+  r= paa_pathargs(&rv,&newargs); if (r) return r;
+  execreset();
+  execute= tokv_word_execute;
+  execargs= newargs;
+  execpath= xstrsave(rv);
+  return 0;
+}
+
+int df_executefromdirectory(int dtoken) {
+  const char *p, *q, *rv;
+  struct stat stab;
+  char *fn, **newargs;
+  int l, r;
+
+  r= paa_pathargs(&rv,&newargs); if (r) return r;
+  p= strrchr(service,'/'); if (p) p++; else p= service;
+  if (!*p || !ISCHAR(isalnum,*p)) {
+    parseerrprint("execute-from-directory requires initial char of service "
+                 "portion to be alphanumeric (service portion was `%s')",
+                 p);
+    freecharparray(newargs);
+    return tokv_error;
+  }
+  for (q=p+1; *q; q++) {
+    if (!ISCHAR(isalnum,*q) && *q != '-') {
+      parseerrprint("execute-from-directory requires service portion to "
+                   "contain only alphanumerics and hyphens (was `%s')",
+                   p);
+      freecharparray(newargs);
+      return tokv_error;
+    }
+  }
+  l= strlen(rv)+1+strlen(p)+1;
+  fn= xmalloc(l);
+  snyprintf(fn,l,"%s/%s",rv,p);
+  if (stat(fn,&stab)) {
+    if (errno == ENOENT) { free(fn); freecharparray(newargs); return 0; }
+    parseerrprint("failed to stat `%s' for execute-from-directory: %s",
+                 fn,strerror(errno));
+    free(fn); freecharparray(newargs); return tokv_error;
+  }
+  if (!S_ISREG(stab.st_mode)) {
+    parseerrprint("file `%s' in execute-from-directory is not an ordinary file"
+                 " or link to one (mode=0%lo)",fn,(unsigned long)stab.st_mode);
+    free(fn); freecharparray(newargs); return tokv_error;
+  }
+  execreset();
+  execute= tokv_word_executefromdirectory;
+  execargs= newargs;
+  execpath= fn;
+  return 0;
+}
+
+/* Parsing builtin service requests (execute-builtin) */
+
+static int bispa_none(char ***rnewargs) {
+  return pa_mnl();
+}
+
+static int bispa_parameter(char ***rnewargs) {
+  int r, i;
+  char **parmvalues, *name, **newargs;
+  
+  r= pa_mwsp(); if (r) return r;
+  r= pa_parameter(&parmvalues,&name); if (r) return r;
+  for (i=0; parmvalues[i]; i++);
+  newargs= xmalloc(sizeof(char*)*(i+2));
+  newargs[0]= name;
+  memcpy(newargs+1,parmvalues,sizeof(char*)*(i+1));
+  free(parmvalues);
+  r= pa_mnl(); if (r) { free(newargs); return r; }
+  *rnewargs= newargs;
+  return 0;
+}
+
+int df_executebuiltin(int dtoken) {
+  int r;
+  builtinserviceexec_fnt *bisexec;
+  char *newpath, **newargs;
+
+  r= pa_mwsp(); if (r) return r;
+  r= yylex(); if (r == tokv_error) return r;
+  if (!(r & tokt_builtinservice)) return unexpected(r,-1,"builtin service name");
+  bisexec= lr_bisexec;
+  newpath= xstrsave(yytext);
+  newargs= 0;
+  r= lr_bispa(&newargs); if (r) { free(newpath); return r; }
+
+  execreset();
+  execute= tokv_word_executebuiltin;
+  execbuiltin= bisexec;
+  execpath= newpath;
+  execargs= newargs;
+  return 0;
+}
+
+/* Directives for changing other execution parameters */
+
+int dfg_setflag(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  *lr_flag= lr_flagval;
+  return 0;
+}
+
+int df_reset(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  r= parse_string(RESET_CONFIGURATION,"<builtin reset configuration>",1);
+  return r;
+}
+
+int dfg_fdwant(int dtoken) {
+  int fdmin, fdmax, r, needreadwrite, havereadwrite, fd;
+
+  needreadwrite= lr_fdwant_readwrite;
+  r= pa_mwsp(); if (r) return r;
+  r= yylex(); if (r == tokv_error) return r;
+  if (!(r & tokt_fdrange)) return unexpected(r,-1,"file descriptor range");
+  fdmin= lr_min; fdmax= lr_max;
+  if (fdmin<0 || fdmin>MAX_ALLOW_FD ||
+      (fdmax != -1 && fdmax<0) || fdmax>MAX_ALLOW_FD)
+    return parseerrprint("file descriptor in range is negative or far too large");
+  r= yylex(); if (r == tokv_error) return r;
+  if (r == tokv_newline) {
+    if (needreadwrite > 0)
+      return parseerrprint("read or write is required");
+    havereadwrite= 0;
+  } else if (r == tokv_lwsp) {
+    if (needreadwrite < 0)
+      return parseerrprint("read or write not allowed");
+    r= yylex(); if (r == tokv_error) return r;
+    if (!(r & tokt_readwrite))
+      return unexpected(r,-1,"read or write (or perhaps newline)");
+    havereadwrite= r;
+    r= pa_mnl(); if (r) return r;
+  } else {
+    return unexpected(r,-1,"whitespace before read or write or newline");
+  }
+  ensurefdarray(fdmin);
+  if (fdmax == -1) {
+    if (!(dtoken == tokv_word_rejectfd || dtoken == tokv_word_ignorefd))
+      return parseerrprint("unspecified maximum only allowed"
+                          " with reject-fd and ignore-fd");
+    fdmax= fdarrayused-1;
+    restfdwantstate= dtoken;
+    restfdwantrw= havereadwrite;
+  }
+  ensurefdarray(fdmax);
+  for (fd=fdmin; fd<=fdmax; fd++) {
+    fdarray[fd].wantstate= dtoken;
+    fdarray[fd].wantrw= havereadwrite;
+  }
+  return 0;
+}
+
+/* Directives for changing error handling */
+
+int df_errorstostderr(int dtoken) {
+  int r;
+  
+  r= pa_mnl(); if (r) return r;
+  closeerrorfile(); eh.handling= dtoken;
+  return 0;
+}
+
+int df_errorstosyslog(int dtoken) {
+  int token, level, facility;
+
+  facility= DEFUSERLOGFACILITY;
+  level= DEFUSERLOGLEVEL;
+  token= yylex();
+  if (token == tokv_lwsp) {
+    token= yylex(); if (token == tokv_error) return token;
+    if (!(token & tokt_logfacility))
+      return unexpected(token,-1,"syslog facility (or end of line)");
+    facility= lr_logfacility;
+    token= yylex();
+  }    
+  if (token == tokv_lwsp) {
+    token= yylex(); if (token == tokv_error) return token;
+    if (!(token & tokt_loglevel))
+      return unexpected(token,-1,"syslog level (or end of line) after facility");
+    level= lr_loglevel;
+    token= yylex();
+  }
+  if (unexpected(token,tokv_newline,"end of line after errors-to-syslog"))
+    return tokv_error;
+  closeerrorfile(); eh.handling= tokv_word_errorstosyslog;
+  eh.logfacility= facility; eh.loglevel= level;
+  return 0;
+}
+
+int df_errorstofile(int dtoken) {
+  const char *cp;
+  FILE *file;
+  int r;
+  
+  r= paa_1path(&cp); if (r) return r;
+  file= fopen(cp,"a");
+  if (!file)
+    return parseerrprint("unable to open error log file `%s': %s",cp,strerror(errno));
+  if (setvbuf(file,0,_IOLBF,MAX_ERRMSG_LEN)) {
+    parseerrprint("unable to set line buffering on errors file: %s",strerror(errno));
+    fclose(file); return tokv_error;
+  }
+  closeerrorfile(); eh.handling= tokv_word_errorstofile;
+  eh.file= file; eh.filename= xstrsave(cp);
+  return 0;
+}
+
+/* Directives for including other files or configuration data */
+
+int dfi_includeuserrcfile(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  assert(userrcfile);
+  return parse_file(userrcfile,0);
+}
+
+int dfi_includeclientconfig(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  assert(overridedata);
+  return parse_string(overridedata,"<configuration override data>",0);
+}
+
+int df_include(int dtoken) {
+  const char *cp;
+  int r, found;
+
+  r= paa_1path(&cp); if (r) return r;
+  r= parse_file(cp,&found); if (r) return r;
+  if (found || dtoken == tokv_word_includeifexist) return 0;
+  return parseerrprint(dtoken == tokv_word_includesysconfig ?
+                      "system configuration file `%s' does not exist" :
+                      "included file `%s' does not exist",
+                      cp);
+}
+
+int df_includedirectory(int dtoken) {
+  static char *buildbuf=0;
+  static int buildbuflen=0;
+  
+  int r, cpl, tel, c, found;
+  DIR *d;
+  struct dirent *de;
+  const char *p, *cpget;
+  char *cp;
+  
+  r= paa_1path(&cpget); if (r) return r;
+  d= opendir(cpget);
+  if (!d)
+    return parseerrprint("unable to open directory `%s': %s",cpget,strerror(errno));
+  cp= xstrsave(cpget);
+  cpl= strlen(cp);
+  while ((de= readdir(d))) {
+    tel= strlen(de->d_name);
+    if (!tel) continue;
+    p= de->d_name;
+    if (!*p || !ISCHAR(isalnum,*p)) continue;
+    while ((c= *++p)) if (!(ISCHAR(isalnum,c) || c=='-')) break;
+    if (c) continue;
+    if (makeroom(&buildbuf,&buildbuflen,cpl+1+tel+1)) {
+      stringoverflow("pathname in directory");
+      r= tokv_error; goto x_err;
+    }
+    snyprintf(buildbuf,buildbuflen,"%s/%s",cp,de->d_name);
+    r= parse_file(buildbuf,&found); if (r) goto x_err;
+    if (!found) {
+      r= parseerrprint("unable to open file `%s' in included directory `%s': %s",
+                      de->d_name,cp,strerror(errno));
+      goto x_err;
+    }
+  }
+  if (closedir(d)) {
+    parseerrprint("error closing directory `%s': %s",cp,strerror(errno));
+    free(cp);
+    return tokv_error;
+  }
+  free(cp);
+  return 0;
+
+x_err:
+  closedir(d);
+  free(cp);
+  return r;
+}
+
+int df_includelookup(int dtoken) {
+  static char *buildbuf=0;
+  int buildbuflen=0;
+  
+  char **parmvalues, **pp, *p, *q, *cp;
+  const char *cpget;
+  struct stat stab;
+  int r, done, thisdone, cpl, c;
+
+  r= pa_mwsp(); if (r) return r;
+  r= pa_parameter(&parmvalues,0); if (r) return r;
+  r= paa_1path(&cpget); if (r) { freecharparray(parmvalues); return r; }
+  if (stat(cpget,&stab)) {
+    parseerrprint("unable to access directory `%s': %s",cpget,strerror(errno));
+    freecharparray(parmvalues); return tokv_error;
+  }
+  if (!S_ISDIR(stab.st_mode)) {
+    parseerrprint("object `%s' is not a directory or link to one",cpget);
+    freecharparray(parmvalues); return tokv_error;
+  }
+  done= 0;
+  cp= xstrsave(cpget);
+  cpl= strlen(cp);
+  if (!parmvalues[0]) {
+    if (makeroom(&buildbuf,&buildbuflen,cpl+1+sizeof(NONEINCLUDELOOKUP))) {
+      stringoverflow("pathname in directory for lookup of undefined parameter");
+      r= tokv_error; goto x_err;
+    }
+    snyprintf(buildbuf,buildbuflen,"%s/" NONEINCLUDELOOKUP,cp);
+    r= parse_file(buildbuf,&thisdone); if (r) goto x_err;
+    if (thisdone) done= 1;
+  } else {
+    for (pp=parmvalues;
+        *pp && (!done || dtoken == tokv_word_includelookupall);
+        pp++) {
+      if (makeroom(&buildbuf,&buildbuflen,
+                  cpl+1+strlen(*pp)*2+3+sizeof(EMPTYINCLUDELOOKUP)+1)) {
+       stringoverflow("pathname in directory for lookup");
+       r= tokv_error; goto x_err;
+      }
+      strcpy(buildbuf,cp);
+      p= *pp; q= buildbuf+cpl;
+      *q++= '/';
+      if (!*p) {
+       strcpy(q,EMPTYINCLUDELOOKUP);
+      } else {
+       if (*p=='.') *q++= ':';
+       while ((c= *p++)) {
+         if (c=='/') {
+           *q++= ':';
+           c= '-';
+         } else if (!((c >= '0' && c <= '9') ||
+                      (c >= 'a' && c <= 'z') ||
+                      c == '-' || c == '_')) {
+           *q++= ':';
+         }
+         *q++= c;
+       }
+       *q++= 0;
+      }
+      r= parse_file(buildbuf,&thisdone);
+      if (r) goto x_err;
+      if (thisdone) done= 1;
+    }
+  }
+  if (!done) {
+    if (makeroom(&buildbuf,&buildbuflen,
+                cpl+1+sizeof(DEFAULTINCLUDELOOKUP))) {
+      stringoverflow("pathname in directory for lookup of default");
+      r= tokv_error; goto x_err;
+    }
+    snyprintf(buildbuf,buildbuflen,"%s/" DEFAULTINCLUDELOOKUP,cp);
+    r= parse_file(buildbuf,0); if (r) goto x_err;
+  }
+  r= 0;
+  
+x_err:
+  freecharparray(parmvalues);
+  free(cp);
+  return r;
+}
+
+/* Control constructs */
+
+int df_catchquit(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  r= parser(tokv_word_catchquit);
+  if (r == tokv_quit || r == tokv_error) {
+    if (r == tokv_error) {
+      r= parse_string(RESET_CONFIGURATION,
+                     "<builtin reset configuration (caught error)>",1);
+      assert(!r);
+    }
+    r= skip(tokv_word_catchquit);
+  }
+  if (r & tokt_controlend) {
+    assert(r == tokv_word_hctac);
+    r= pa_mnl();
+  }
+  return r;
+}
+
+int df_if(int dtoken) {
+  int r, true, done;
+  
+  done= 0;
+  do {
+    r= pa_condition(&true); if (r) return r;
+    if (!done && true) { r= parser(tokv_word_if); done= 1; }
+    else { r= skip(tokv_word_if); }
+    if (!(r & tokt_controlend)) return r;
+  } while (r == tokv_word_elif);
+  if (r == tokv_word_else) {
+    r= pa_mnl(); if (r) return r;
+    cstate->reportlineno= cstate->lineno;
+    if (done) r= skip(tokv_word_if);
+    else r= parser(tokv_word_if);
+    if (!(r & tokt_controlend)) return r;
+  }
+  if (unexpected(r,tokv_word_fi,"`fi' to end `if'")) return tokv_error;
+  return pa_mnl();
+}
+
+int df_errorspush(int dt) {
+  struct error_handling save;
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+
+  save= eh;
+  eh.filekeep= 1;
+
+  r= parser(tokv_word_errorspush);
+
+  closeerrorfile();
+  eh= save;
+
+  if (r & tokt_controlend) {
+    assert(r == tokv_word_srorre);
+    r= pa_mnl();
+  }
+  return r;
+}
+
+/* Miscelleanous directives */
+
+int df_cd(int dtoken) {
+  const char *cp;
+  int r;
+
+  r= paa_1path(&cp); if (r) return r;
+  if (!chdir(cp)) return 0;
+  return parseerrprint("unable to change directory to `%s': %s",cp,strerror(errno));
+}
+
+int df_userrcfile(int dtoken) {
+  const char *cp;
+  int r;
+
+  r= paa_1path(&cp); if (r) return r;
+  free(userrcfile); userrcfile= xstrsave(cp);
+  return 0;
+}
+
+int df_message(int dtoken) {
+  const char *mp;
+  int r;
+
+  r= paa_message(&mp); if (r) return r;
+  parseerrprint("`message' directive: %s",mp);
+  return 0;
+}
+
+int df_error(int dtoken) {
+  const char *mp;
+  int r;
+
+  r= paa_message(&mp); if (r) return r;
+  return parseerrprint("`error' directive: %s",mp);
+}
+
+int df_eof(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  return tokv_eof;
+}
+
+int df_quit(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  return tokv_quit;
+}
+
+/*
+ * Main parser routines
+ */
+      
+static void parser_push(struct parser_state *usestate,
+                       const char *newfile,
+                       const struct stat *newfilestab,
+                       YY_BUFFER_STATE ybuf,
+                       int isinternal) {
+  usestate->lineno= 1;
+  usestate->reportlineno= 1;
+  usestate->filename= newfile;
+  usestate->filestab= *newfilestab;
+  usestate->notedreferer= 0;
+  usestate->isinternal= isinternal;
+  usestate->ybuf= ybuf;
+  usestate->upstate= cstate;
+
+  cstate= usestate;
+  yy_switch_to_buffer(ybuf);
+}
+
+static void parser_pop(void) {
+  struct parser_state *oldstate;
+
+  oldstate= cstate;
+  cstate= cstate->upstate;
+  if (cstate) yy_switch_to_buffer(cstate->ybuf);
+  yy_delete_buffer(oldstate->ybuf);
+}
+
+int parse_string(const char *string, const char *descrip, int isinternal) {
+  /* Returns the same things as parser, except that tokv_eof is turned
+   * into 0.  *string must be statically allocated or copied, so that
+   * it is not overwritten while the parsing takes place (unlike with
+   * parse_file).
+   */
+  static const struct stat blankstab;
+  
+  struct parser_state usestate;
+  YY_BUFFER_STATE ybuf;
+  int r;
+
+  ybuf= yy_scan_string(string);
+  if (!ybuf) syscallerror("unable to create flex buffer for internal string");
+  parser_push(&usestate,descrip,&blankstab,ybuf,isinternal);
+  
+  r= parser(0);
+
+  parser_pop();
+  if (r == tokv_eof) r= 0;
+  return r;
+}
+
+static int parse_file(const char *string, int *didexist) {
+  /* Returns the same things as parser, except that tokv_eof is turned
+   * into 0.  If *didexist is 0 then errno will have been set.
+   * *string will be copied by parse_file so it may be be overwritten
+   * during the parsing (so, for example, yytext need not be copied).
+   */
+  static int fileparselevel= 0;
+  
+  struct parser_state usestate, *checkrecurse;
+  YY_BUFFER_STATE ybuf;
+  int r;
+  FILE *file;
+  char *filename;
+  struct stat newstab;
+
+  if (fileparselevel >= MAX_INCLUDE_NEST)
+    return parseerrprint("too many nested levels of included files");
+  file= fopen(string,"r");
+  if (!file) {
+    if (errno == ENOENT) {
+      if (didexist) *didexist= 0;
+      return 0;
+    }
+    return parseerrprint("unable to open config file `%s': %s",string,strerror(errno));
+  }
+  r= fstat(fileno(file),&newstab); if (r) syscallerror("unable to fstat new file");
+  for (checkrecurse= cstate; checkrecurse; checkrecurse= checkrecurse->upstate) {
+    if (!checkrecurse->filestab.st_mode) continue;
+    if (newstab.st_dev==checkrecurse->filestab.st_dev &&
+       newstab.st_ino==checkrecurse->filestab.st_ino) {
+      fclose(file);
+      return parseerrprint("recursion detected - config file `%s' calls itself",string);
+    }
+  }
+  
+  if (didexist) *didexist= 1;
+
+  ybuf= yy_create_buffer(file,YY_BUF_SIZE);
+  if (!ybuf) syscallerror("unable to create flex buffer for file");
+  filename= xstrsave(string);
+  parser_push(&usestate,filename,&newstab,ybuf,0);
+  fileparselevel++;
+  
+  r= parser(0);
+  if (ferror(file))
+    r= parseerrprint("error reading configuration file `%s'",string);
+
+  fileparselevel--;
+  parser_pop();
+  free(filename);
+  fclose(file);
+  if (r == tokv_eof) r= 0;
+  return r;
+}
+
+static int parser(int allowce) {
+  /* Returns:
+   *  an exception (error, eof or quit)
+   *   then rest of `file' is uninteresting
+   * or
+   *  token if allowce was !0 and equal to token's controlend
+   *   then rest of `file' (including rest of line with the
+   *   controlend - even the whitespace) not scanned yet
+   */
+  int token, r;
+
+  for (;;) { /* loop over lines */
+    cstate->reportlineno= cstate->lineno;
+    do { token= yylex(); } while (token == tokv_lwsp);
+    if (token & tokt_exception) {
+      return token;
+    } else if (token & tokt_controlend) {
+      if (lr_controlend == allowce) return token;
+      else return unexpected(token,-1,"directive (not this kind of"
+                            " control structure end)");
+    } else if (token & tokt_directive) {
+      if ((token & tokt_internal) && !cstate->isinternal)
+       return unexpected(token,-1,"published directive, not internal-use-only one");
+      r= (lr_dir)(token); if (r) { assert(r & tokt_exception); return r; }
+    } else if (token == tokv_newline) {
+      /* ignore blank lines (and comment-only lines) */
+    } else {
+      return unexpected(token,-1,"directive");
+    }
+  }
+}
diff --git a/process.c b/process.c
new file mode 100644 (file)
index 0000000..e624fb8
--- /dev/null
+++ b/process.c
@@ -0,0 +1,782 @@
+/*
+ * userv - process.c
+ * daemon code to process one request (is parent of service process)
+ *
+ * Copyright (C)1996-1999,2001,2003 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * We do some horrible asynchronous stuff with signals.
+ *
+ * The following objects &c. are used in signal handlers and so
+ * must be protected by calls to blocksignals if they are used in
+ * the main program:
+ *  the syslog() family of calls, and the associated
+ *    syslogopenfacility variable
+ *  swfile (stdio stream)
+ *
+ * The following objects are used in the main program unprotected
+ * and so must not be used in signal handlers:
+ *  srfile
+ *
+ * child and childtokill are used for communication between the
+ * main thread and the signal handlers; none of the signal handlers
+ * return so errno is OK too.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <syslog.h>
+#include <pwd.h>
+#include <grp.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "config.h"
+#include "common.h"
+#include "both.h"
+#include "daemon.h"
+#include "lib.h"
+#include "tokens.h"
+
+/* NB: defaults for the execution state are not set here, but in
+ * the RESET_CONFIGURATION #define in daemon.h. */
+struct request_msg request_mbuf;
+struct keyvaluepair *defvararray;
+struct fdstate *fdarray;
+int fdarraysize, fdarrayused;
+int restfdwantstate= tokv_word_rejectfd, restfdwantrw;
+int service_ngids;
+char **argarray;
+char *serviceuser, *service, *loginname, *cwd;
+char *overridedata, *userrcfile;
+char *serviceuser_dir, *serviceuser_shell, *callinguser_shell;
+gid_t *calling_gids, *service_gids;
+uid_t serviceuser_uid=-1;
+const char **calling_groups, **service_groups;
+char *execpath, **execargs;
+int execute;
+int setenvironment, suppressargs, disconnecthup;
+builtinserviceexec_fnt *execbuiltin;
+int syslogopenfacility=-1;
+
+static FILE *swfile, *srfile;
+static pid_t child=-1, childtokill=-1;
+static pid_t mypid;
+
+/* Function shared with servexec.c: */
+
+int synchread(int fd, int ch) {
+  char synchmsg;
+  int r;
+  
+  for (;;) {
+    r= read(fd,&synchmsg,1);
+    if (r==1) break;
+    if (r==0) { errno= ECONNRESET; return -1; }
+    assert(r<0);
+    if (errno!=EINTR) return -1;
+  };
+  assert(synchmsg==ch);
+  return 0;
+}
+
+const char *defaultpath(void) {
+  return serviceuser_uid ? DEFAULTPATH_USER : DEFAULTPATH_ROOT;
+}
+
+/* General-purpose functions; these do nothing special about signals */
+
+static void blocksignals(void) {
+  int r;
+  sigset_t set;
+
+  sigemptyset(&set);
+  sigaddset(&set,SIGCHLD);
+  sigaddset(&set,SIGPIPE);
+  r= sigprocmask(SIG_BLOCK,&set,0); assert(!r);
+}
+
+static void xfwriteerror(void) {
+  if (errno != EPIPE) syscallerror("writing to client");
+  blocksignals();
+  ensurelogopen(USERVD_LOGFACILITY);
+  syslog(LOG_INFO,"client went away (broken pipe)");
+  disconnect(8);
+}
+
+static void xfwrite(const void *p, size_t sz, FILE *file) {
+  size_t nr;
+  nr= fwrite(p,1,sz,file);
+  if (nr != sz) xfwriteerror();
+}
+
+static void xfflush(FILE *file) {
+  if (fflush(file)) xfwriteerror();
+}
+
+/* Functions which may be called only from the main thread.  These may
+ * use main-thread objects and must block signals before using signal
+ * handler objects.
+ */
+
+static void xfread(void *p, size_t sz) {
+  size_t nr;
+  nr= working_fread(p,sz,srfile); if (nr == sz) return;
+  if (ferror(srfile)) syscallerror("reading from client");
+  blocksignals();
+  assert(feof(srfile));
+  syslog(LOG_INFO,"client went away (unexpected EOF)");
+  swfile= 0;
+  disconnect(8);
+}
+
+static char *xfreadsetstring(int l) {
+  char *s;
+  assert(l<=MAX_GENERAL_STRING);
+  s= xmalloc(l+1);
+  xfread(s,sizeof(*s)*l);
+  s[l]= 0;
+  return s;
+}
+
+static char *xfreadstring(void) {
+  int l;
+  xfread(&l,sizeof(l));
+  return xfreadsetstring(l);
+}
+
+static void getevent(struct event_msg *event_r) {
+  int fd;
+  
+  for (;;) {
+    xfread(event_r,sizeof(struct event_msg));
+    switch (event_r->type) {
+    case et_closereadfd:
+      fd= event_r->data.closereadfd.fd;
+      if (fd >= fdarrayused) {
+       blocksignals();
+       syslog(LOG_ERR,"client sent bad file descriptor %d to close (max %d)",
+              fd,fdarrayused-1);
+       disconnect(20);
+      }
+      if (fdarray[fd].holdfd!=-1) {
+       if (close(fdarray[fd].holdfd)) syscallerror("cannot close holding fd");
+       fdarray[fd].holdfd= -1;
+      }
+      break;
+    case et_disconnect:
+      blocksignals();
+      syslog(LOG_INFO,"client disconnected");
+      disconnect(4);
+    default:
+      return;
+    }
+  }
+}
+
+/* Functions which may be called either from signal handlers or from
+ * the main thread.  They block signals in case they are on the main
+ * thread, and may only use signal handler objects.  None of them
+ * return.  If they did they'd have to restore the signal mask.
+ */
+
+void miscerror(const char *what) {
+  blocksignals();
+  syslog(LOG_ERR,"failure: %s",what);
+  disconnect(16);
+}
+
+void syscallerror(const char *what) {
+  int e;
+
+  e= errno;
+  blocksignals();
+  syslog(LOG_ERR,"system call failure: %s: %s",what,strerror(e));
+  disconnect(16);
+}
+
+/* Functions which may be called from signal handlers.  These
+ * may use signal-handler objects.  The main program may only
+ * call them with signals blocked, and they may not use any
+ * main-thread objects.
+ */
+
+void ensurelogopen(int wantfacility) {
+  if (syslogopenfacility==wantfacility) return;
+  if (syslogopenfacility!=-1) closelog();
+  openlog(USERVD_LOGIDENT,LOG_NDELAY|LOG_PID,wantfacility);
+  syslogopenfacility= wantfacility;
+}
+
+void NONRETURNING disconnect(int exitstatus) {
+  /* This function can sometimes indirectly call itself (eg,
+   * xfwrite, syscallerror can cause it to be called).  So, all
+   * the global variables indicating need for action are reset
+   * before the action is taken so that if it fails it isn't
+   * attempted again.
+   */
+  struct progress_msg progress_mbuf;
+  FILE *swfilereal;
+  pid_t orgtokill;
+  int r;
+  
+  if (childtokill!=-1 && disconnecthup) {
+    orgtokill= childtokill;
+    childtokill= -1;
+    if (disconnecthup) {
+      r= kill(-orgtokill,SIGHUP);
+      if (r && errno!=EPERM && errno!=ESRCH)
+       syscallerror("sending SIGHUP to service process group");
+    }
+    child= -1;
+  }
+  if (swfile) {
+    swfilereal= swfile;
+    swfile= 0;
+    memset(&progress_mbuf,0,sizeof(progress_mbuf));
+    progress_mbuf.magic= PROGRESS_MAGIC;
+    progress_mbuf.type= pt_failed;
+    xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfilereal);
+    xfflush(swfilereal);
+  }
+
+  _exit(exitstatus);
+}
+
+static void reporttermination(int status) {
+  struct progress_msg progress_mbuf;
+  
+  memset(&progress_mbuf,0,sizeof(progress_mbuf));
+  progress_mbuf.magic= PROGRESS_MAGIC;
+  progress_mbuf.type= pt_terminated;
+  progress_mbuf.data.terminated.status= status;
+  xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfile);
+  xfflush(swfile);
+}
+
+static void NONRETURNING sighandler_chld(int ignored) {
+  int status;
+  pid_t returned;
+
+  returned= wait3(&status,WNOHANG,0);
+  if (returned==-1) syscallerror("wait for child failed");
+  if (!returned) syscallerror("spurious sigchld");
+  if (returned!=child) syscallerror("spurious child process");
+  child= childtokill= -1;
+
+  reporttermination(status);
+  syslog(LOG_INFO,"service completed (status %d %d)",(status>>8)&0x0ff,status&0x0ff);
+  _exit(0);
+}
+
+/* Functions which are called only during setup, before
+ * the signal asynchronicity starts.  They can do anything they like.
+ */
+
+void ensurefdarray(int fd) {
+  if (fd < fdarrayused) return;
+  if (fd >= fdarraysize) {
+    fdarraysize= ((fd+2)<<1);
+    fdarray= xrealloc(fdarray,sizeof(struct fdstate)*fdarraysize);
+  }
+  while (fd >= fdarrayused) {
+    fdarray[fdarrayused].iswrite= -1;
+    fdarray[fdarrayused].realfd= -1;
+    fdarray[fdarrayused].holdfd= -1;
+    fdarray[fdarrayused].wantstate= restfdwantstate;
+    fdarray[fdarrayused].wantrw= restfdwantrw;
+    fdarrayused++;
+  }
+}
+
+static void NONRETURNING generalfailure(const char *prefix, int reserveerrno,
+                                       int errnoval, const char *fmt, va_list al) {
+  char errmsg[MAX_ERRMSG_LEN];
+
+  if (prefix) {
+    strnycpy(errmsg,prefix,sizeof(errmsg));
+    strnytcat(errmsg,": ",sizeof(errmsg));
+  } else {
+    errmsg[0]= 0;
+  }
+  vsnytprintfcat(errmsg,sizeof(errmsg)-reserveerrno,fmt,al);
+  if (reserveerrno) {
+    strnytcat(errmsg,": ",sizeof(errmsg));
+    strnytcat(errmsg,strerror(errnoval),sizeof(errmsg));
+  }
+  senderrmsgstderr(errmsg);
+  syslog(LOG_INFO,"service failed (%s)",errmsg);
+  disconnect(12);
+}
+
+static void NONRETURNPRINTFFORMAT(1,2) failure(const char *fmt, ...) {
+  va_list al;
+
+  va_start(al,fmt);
+  generalfailure(0,0,0,fmt,al);
+}  
+
+static void NONRETURNPRINTFFORMAT(1,2) syscallfailure(const char *fmt, ...) {
+  va_list al;
+  int e;
+
+  e= errno;
+  va_start(al,fmt);
+  generalfailure("system call failed",ERRMSG_RESERVE_ERRNO,e,fmt,al);
+}
+
+void senderrmsgstderr(const char *errmsg) {
+  struct progress_msg progress_mbuf;
+  unsigned long ul;
+  int l;
+
+  l= strlen(errmsg);
+  memset(&progress_mbuf,0,sizeof(progress_mbuf));
+  progress_mbuf.magic= PROGRESS_MAGIC;
+  progress_mbuf.type= pt_errmsg;
+  progress_mbuf.data.errmsg.messagelen= l;
+  xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfile);
+  xfwrite(errmsg,l,swfile);
+  ul= PROGRESS_ERRMSG_END_MAGIC;
+  xfwrite(&ul,sizeof(ul),swfile);
+  xfflush(swfile);
+}
+
+/* The per-request main program and its subfunctions. */
+
+static void setup_comms(int sfd) {
+  static char swbuf[BUFSIZ];
+  static char srbuf[BUFSIZ];
+
+  struct sigaction sig;
+  
+  ensurelogopen(USERVD_LOGFACILITY);
+  syslog(LOG_DEBUG,"call connected");
+
+  mypid= getpid(); if (mypid == -1) syscallerror("getpid");
+
+  sig.sa_handler= SIG_IGN;
+  sigemptyset(&sig.sa_mask);
+  sig.sa_flags= 0;
+  if (sigaction(SIGPIPE,&sig,0)) syscallerror("cannot ignore sigpipe");
+
+  srfile= fdopen(sfd,"r");
+  if (!srfile) syscallerror("turn socket fd into reading FILE*");
+  if (setvbuf(srfile,srbuf,_IOFBF,sizeof(srbuf)))
+    syscallerror("set buffering on socket reads");
+
+  swfile= fdopen(sfd,"w");
+  if (!swfile) syscallerror("turn socket fd into writing FILE*");
+  if (setvbuf(swfile,swbuf,_IOFBF,sizeof(swbuf)))
+    syscallerror("set buffering on socket writes");
+}
+
+static void send_opening(void) {
+  struct opening_msg opening_mbuf;
+
+  memset(&opening_mbuf,0,sizeof(opening_mbuf));
+  opening_mbuf.magic= OPENING_MAGIC;
+  memcpy(opening_mbuf.protocolchecksumversion,protocolchecksumversion,PCSUMSIZE);
+  opening_mbuf.overlordpid= overlordpid;
+  opening_mbuf.serverpid= mypid;
+  xfwrite(&opening_mbuf,sizeof(opening_mbuf),swfile);
+  xfflush(swfile);
+}
+
+static void receive_request(void) {
+  int i, fd;
+  unsigned long ul;
+
+  xfread(&request_mbuf,sizeof(request_mbuf));
+  serviceuser= xfreadsetstring(request_mbuf.serviceuserlen);
+  service= xfreadsetstring(request_mbuf.servicelen);
+  assert(request_mbuf.spoofed==0 || request_mbuf.spoofed==1);
+  loginname= xfreadsetstring(request_mbuf.loginnamelen);
+  cwd= xfreadsetstring(request_mbuf.cwdlen);
+  if (request_mbuf.overridelen >= 0) {
+    assert(request_mbuf.overridelen <= MAX_OVERRIDE_LEN);
+    overridedata= xfreadsetstring(request_mbuf.overridelen);
+  } else {
+    assert(request_mbuf.overridelen == -1);
+    overridedata= 0;
+  }
+  assert(request_mbuf.ngids <= MAX_GIDS);
+  calling_gids= xmalloc(sizeof(gid_t)*request_mbuf.ngids);
+  xfread(calling_gids,sizeof(gid_t)*request_mbuf.ngids);
+
+  fdarraysize= 4; fdarray= xmalloc(sizeof(struct fdstate)*fdarraysize);
+  fdarrayused= 1; fdarray[0].iswrite= -1;
+  fdarray[0].wantstate= tokv_word_rejectfd;
+  assert(request_mbuf.nreadfds+request_mbuf.nwritefds <= MAX_ALLOW_FD+1);
+  for (i=0; i<request_mbuf.nreadfds+request_mbuf.nwritefds; i++) {
+    xfread(&fd,sizeof(int));
+    assert(fd <= MAX_ALLOW_FD);
+    ensurefdarray(fd);
+    assert(fdarray[fd].iswrite == -1);
+    fdarray[fd].iswrite= (i>=request_mbuf.nreadfds);
+  }
+  /* fdarray[].iswrite now set; rest is still blank
+   * (ie want reject read, no realfd holdfd). */
+
+  assert(request_mbuf.nargs <= MAX_ARGSDEFVAR);
+  argarray= xmalloc(sizeof(char*)*(request_mbuf.nargs));
+  for (i=0; i<request_mbuf.nargs; i++) argarray[i]= xfreadstring();
+  assert(request_mbuf.nvars <= MAX_ARGSDEFVAR);
+  defvararray= xmalloc(sizeof(struct keyvaluepair)*request_mbuf.nvars);
+  for (i=0; i<request_mbuf.nvars; i++) {
+    defvararray[i].key= xfreadstring();
+    assert(defvararray[i].key[0]);
+    defvararray[i].value= xfreadstring();
+  }
+  xfread(&ul,sizeof(ul));
+  assert(ul == REQUEST_END_MAGIC);
+}
+
+static void establish_pipes(void) {
+  int fd, tempfd;
+  char pipepathbuf[PIPEMAXLEN+2];
+  
+  for (fd=0; fd<fdarrayused; fd++) {
+    if (fdarray[fd].iswrite == -1) continue;
+    pipepathbuf[sizeof(pipepathbuf)-2]= 0;
+    snyprintf(pipepathbuf,sizeof(pipepathbuf),PIPEFORMAT,
+             (unsigned long)request_mbuf.clientpid,(unsigned long)mypid,fd);
+    assert(!pipepathbuf[sizeof(pipepathbuf)-2]);
+    tempfd= open(pipepathbuf,O_RDWR);
+    if (tempfd<0) syscallerror("prelim open pipe");
+    if (fdarray[fd].iswrite) {
+      fdarray[fd].holdfd= -1;
+      fdarray[fd].realfd= open(pipepathbuf, O_WRONLY);
+    } else {
+      fdarray[fd].holdfd= open(pipepathbuf, O_WRONLY);
+      if (fdarray[fd].holdfd<0) syscallerror("hold open pipe");
+      fdarray[fd].realfd= open(pipepathbuf, O_RDONLY);
+    }
+    if (fdarray[fd].realfd<0) syscallerror("real open pipe");
+    if (unlink(pipepathbuf)) syscallerror("unlink pipe");
+    if (close(tempfd)) syscallerror("close prelim fd onto pipe");
+  }
+  /* Now fdarray[].realfd is pipe end for service in case service
+   * wants it.  If it's an input pipe, then .holdfd is the other
+   * (writing) end of the pipe - we keep it around so that the service
+   * doesn't get an apparently clean EOF if the caller disappears (eg
+   * due to a file read error) or the like (ie so that on disconnect
+   * we can guarantee to send the service SIGHUP before it gets EOF on
+   * the input fd).  Otherwise, .holdfd=-1.
+   */
+}
+
+static void groupnames(int ngids, gid_t *gids, const char ***names_r) {
+  const char **names;
+  struct group *gr;
+  int i;
+  
+  names= xmalloc(sizeof(char*)*ngids);
+  for (i=0; i<ngids; i++) {
+    gr= getgrgid(gids[i]);
+    if (!gr) miscerror("get group entry");
+    names[i]= xstrsave(gr->gr_name);
+  }
+  *names_r= names;
+}
+  
+static void lookup_uidsgids(void) {
+  struct passwd *pw;
+
+  pw= getpwnam(loginname);
+  if (!pw) miscerror("look up calling user");
+  assert(!strcmp(pw->pw_name,loginname));
+  callinguser_shell= xstrsave(pw->pw_shell);
+
+  pw= getpwnam(serviceuser);
+  if (!pw) miscerror("look up service user");
+  assert(!strcmp(pw->pw_name,serviceuser));
+  serviceuser_dir= xstrsave(nondebug_serviceuserdir(pw->pw_dir));
+  serviceuser_shell= xstrsave(pw->pw_shell);
+  serviceuser_uid= pw->pw_uid;
+  
+  if (setregid(pw->pw_gid,pw->pw_gid)) syscallerror("setregid 1");
+  if (initgroups(pw->pw_name,pw->pw_gid)) syscallerror("initgroups");
+  if (setreuid(pw->pw_uid,pw->pw_uid)) syscallerror("setreuid 1");
+  if (setreuid(pw->pw_uid,pw->pw_uid)) syscallerror("setreuid 2");
+  if (pw->pw_uid) {
+    if (!setreuid(pw->pw_uid,0)) miscerror("setreuid 3 unexpectedly succeeded");
+    if (errno != EPERM) syscallerror("setreuid 3 failed in unexpected way");
+  }
+  if (setregid(pw->pw_gid,pw->pw_gid)) syscallerror("setregid 2");
+
+  service_ngids= getgroups(0,0); if (service_ngids == -1) syscallerror("getgroups(0,0)");
+  if (service_ngids > MAX_GIDS) miscerror("service user is in far too many groups");
+  service_gids= xmalloc(sizeof(gid_t)*(service_ngids+1));
+  service_gids[0]= pw->pw_gid;
+  if (getgroups(service_ngids,service_gids+1) != service_ngids)
+    syscallerror("getgroups(size,list)");
+
+  groupnames(request_mbuf.ngids,calling_gids,&calling_groups);
+  groupnames(service_ngids,service_gids,&service_groups);
+}
+
+static void findinpath(char *program) {
+  char *part, *exectry;
+  const char *string, *delim, *nextstring;
+  struct stat stab;
+  int r, partsize;
+  
+  if (strchr(program,'/')) {
+    r= stat(program,&stab);
+    if (r) syscallfailure("failed check for program (containing slash) `%s'",program);
+    execpath= program;
+  } else {
+    string= getenv("PATH");
+    if (!string) string= defaultpath();
+    while (string) {
+      delim= strchr(string,':');
+      if (delim) {
+       if (delim-string > MAX_GENERAL_STRING)
+         failure("execute-from-path, but PATH component too long");
+       partsize= delim-string;
+       nextstring= delim+1;
+      } else {
+       partsize= strlen(string);
+       nextstring= 0;
+      }
+      part= xstrsubsave(string,partsize);
+      exectry= part[0] ? xstrcat3save(part,"/",program) : xstrsave(program);
+      free(part);
+      r= stat(exectry,&stab);
+      if (!r) { execpath= exectry; break; }
+      free(exectry);
+      string= nextstring;
+    }
+    if (!execpath) failure("program `%s' not found on default PATH",program);
+  }
+}
+  
+static void check_find_executable(void) {
+  struct stat stab;
+  int r;
+
+  switch (execute) {
+  case tokv_word_reject:
+    failure("request rejected");
+  case tokv_word_execute:
+    findinpath(execpath);
+    break;
+  case tokv_word_executefromdirectory:
+    r= stat(execpath,&stab);
+    if (r) syscallfailure("checking for executable in directory, `%s'",execpath);
+    break;
+  case tokv_word_executebuiltin:
+    break;
+  case tokv_word_executefrompath:
+    findinpath(service);
+    break;
+  default:
+    abort();
+  }
+}
+
+static void makenonexistentfd(int fd) {
+  if (fdarray[fd].realfd == -1) {
+    assert(fdarray[fd].holdfd == -1);
+  } else {
+    if (close(fdarray[fd].realfd))
+      syscallfailure("close unwanted file descriptor %d",fd);
+    fdarray[fd].realfd= -1;
+  
+    if (fdarray[fd].holdfd != -1) {
+      if (close(fdarray[fd].holdfd))
+       syscallfailure("close unwanted hold descriptor for %d",fd);
+      fdarray[fd].holdfd= -1;
+    }
+  }
+}
+
+static void makenullfd(int fd) {
+  fdarray[fd].realfd= open("/dev/null",
+                          fdarray[fd].wantrw == tokv_word_read ? O_RDONLY :
+                          fdarray[fd].wantrw == tokv_word_write ? O_WRONLY :
+                          0);
+  if (fdarray[fd].realfd<0)
+    syscallfailure("cannot open /dev/null for null or allowed, unprovided fd");
+}
+
+static void check_fds(void) {
+  int fd;
+  
+  assert(fdarrayused>=2);
+  if (!(fdarray[2].wantstate == tokv_word_requirefd ||
+       fdarray[2].wantstate == tokv_word_allowfd) ||
+      fdarray[2].wantrw != tokv_word_write)
+    failure("must have stderr (fd 2), but file descriptor setup in "
+           "configuration does not have it or not for writing");
+
+  for (fd=0; fd<fdarrayused; fd++) {
+    switch (fdarray[fd].wantstate) {
+    case tokv_word_rejectfd:
+      if (fdarray[fd].realfd != -1)
+       failure("file descriptor %d provided but rejected",fd);
+      break;
+    case tokv_word_ignorefd:
+      makenonexistentfd(fd);
+      break;
+    case tokv_word_nullfd:
+      makenonexistentfd(fd);
+      makenullfd(fd);
+      break;
+    case tokv_word_requirefd:
+      if (fdarray[fd].realfd == -1)
+       failure("file descriptor %d required but not provided",fd);
+      /* fall through */
+    case tokv_word_allowfd:
+      if (fdarray[fd].realfd == -1) {
+       assert(fdarray[fd].holdfd == -1);
+       makenullfd(fd);
+      } else {
+       if (fdarray[fd].iswrite) {
+         if (fdarray[fd].wantrw == tokv_word_read)
+           failure("file descriptor %d provided write, wanted read",fd);
+       } else {
+         if (fdarray[fd].wantrw == tokv_word_write)
+           failure("file descriptor %d provided read, wanted write",fd);
+       }
+      }
+    }
+  }
+  /* Now fdarray[].realfd is exactly what service wants: pipe end or
+   * /dev/null or -1.  If .realfd is not -1 then .holdfd may be the fd
+   * for the writing end of the corresponding pipe.
+   */
+}
+
+static void send_progress_ok(void) {
+  struct progress_msg progress_mbuf;
+
+  memset(&progress_mbuf,0,sizeof(progress_mbuf));
+  progress_mbuf.magic= PROGRESS_MAGIC;
+  progress_mbuf.type= pt_ok;
+  xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfile);
+  xfflush(swfile);
+}
+
+static void fork_service_synch(void) {
+  pid_t newchild;
+  struct sigaction sig;
+  int r, synchsocket[2];
+  char synchmsg;
+
+  r= socketpair(AF_UNIX,SOCK_STREAM,0,synchsocket);
+  if (r) syscallerror("cannot create socket for synch");
+
+  /* Danger here.  Firstly, we start handling signals asynchronously.
+   * Secondly after we fork the service we want it to put
+   * itself in a separate process group so that we can kill it and all
+   * its children - but, we mustn't kill the whole pgrp before it has
+   * done that (or we kill ourselves) and it mustn't fork until it
+   * knows that we are going to kill it the right way ...
+   */
+  sig.sa_handler= sighandler_chld;
+  sigemptyset(&sig.sa_mask);
+  sigaddset(&sig.sa_mask,SIGCHLD);
+  sig.sa_flags= 0;
+  if (sigaction(SIGCHLD,&sig,0)) syscallerror("cannot set sigchld handler");
+
+  newchild= fork();
+  if (newchild == -1) syscallerror("cannot fork to invoke service");
+  if (!newchild) execservice(synchsocket,fileno(swfile));
+  childtokill= child= newchild;
+
+  if (close(synchsocket[1])) syscallerror("cannot close other end of synch socket");
+
+  r= synchread(synchsocket[0],'y');
+  if (r) syscallerror("read synch byte from child");
+
+  childtokill= -child;
+
+  synchmsg= 'g';
+  r= write(synchsocket[0],&synchmsg,1);
+  if (r!=1) syscallerror("write synch byte to child");
+
+  if (close(synchsocket[0])) syscallerror("cannot close my end of synch socket");
+}
+
+void servicerequest(int sfd) {
+  struct event_msg event_mbuf;
+  int r;
+
+  setup_comms(sfd);
+  send_opening();
+  receive_request();
+  if (request_mbuf.clientpid == (pid_t)-1) _exit(2);
+  establish_pipes();
+  lookup_uidsgids();
+  debug_dumprequest(mypid);
+  syslog(LOG_INFO,"%s %s -> %s %c %s",
+        request_mbuf.spoofed ? "spoof" : "user",
+        loginname, serviceuser, overridedata?'!':':', service);
+
+  if (overridedata)
+    r= parse_string(TOPLEVEL_OVERRIDDEN_CONFIGURATION,
+                   "<builtin toplevel override configuration>",1);
+  else
+    r= parse_string(TOPLEVEL_CONFIGURATION,
+                   "<builtin toplevel configuration>",1);
+  
+  ensurelogopen(USERVD_LOGFACILITY);
+  if (r == tokv_error) failure("error encountered while parsing configuration");
+  assert(r == tokv_quit);
+
+  debug_dumpexecsettings();
+
+  check_find_executable();
+  check_fds();
+  send_progress_ok();
+
+  getevent(&event_mbuf);
+  assert(event_mbuf.type == et_confirm);
+
+  if (execbuiltin == bisexec_shutdown && !serviceuser_uid) {
+    /* The check for the uid is just so we can give a nice
+     * error message (in the actual code for bisexec_shutdown).
+     * If this is spoofed somehow then the unlink() will simply fail.
+     */
+    r= unlink(RENDEZVOUSPATH);
+    if (r) syscallfailure("remove rendezvous socket %s",RENDEZVOUSPATH);
+    syslog(LOG_NOTICE,"arranging for termination, due to client request");
+    reporttermination(0);
+    _exit(10);
+  }
+
+  fork_service_synch();
+  
+  getevent(&event_mbuf);
+  abort();
+}
diff --git a/servexec.c b/servexec.c
new file mode 100644 (file)
index 0000000..5597402
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ * userv - execserv.c
+ * daemon code which executes actual service (ie child process)
+ *
+ * Copyright (C)1996-1997,1999-2001,2003 Ian Jackson
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with userv; if not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "config.h"
+#include "common.h"
+#include "daemon.h"
+#include "lib.h"
+#include "both.h"
+#include "version.h"
+
+static void NONRETURNING serv_syscallfail(const char *msg) {
+  fputs("uservd(service): ",stderr);
+  perror(msg);
+  _exit(-1);
+}
+
+static void NONRETURNING serv_checkstdoutexit(void) {
+  if (ferror(stdout) || fclose(stdout)) serv_syscallfail("write stdout");
+  _exit(0);
+}
+
+void bisexec_environment(const char *const *argv) {
+  execlp("env","env",(char*)0);
+  serv_syscallfail("execute `env'");
+}
+
+void bisexec_parameter(const char *const *argv) {
+  always_dumpparameter(execargs[0],execargs+1);
+  serv_checkstdoutexit();
+}
+  
+void bisexec_help(const char *const *argv) {
+  const char *const *pp;
+  
+  fputs("recognised builtin services:\n",stdout);
+  for (pp= builtinservicehelpstrings; *pp; pp++) printf("  %s\n",*pp);
+  serv_checkstdoutexit();
+}
+  
+void bisexec_version(const char *const *argv) {
+  const unsigned char *p;
+  int i;
+  
+  printf("uservd version " VERSION VEREXT "\n"
+#ifdef DEBUG
+        "DEBUGGING VERSION"
+#else
+        "production version"
+#endif
+        " - protocol magic number %08lx\n"
+        "maximums:    fd %-10d                general string %d\n"
+        "             gids %-10d              override length %d\n"
+        "             args or variables %-10d error message %d\n"
+        "             nested inclusion %-10d  errno string reserve %d\n"
+        "protocol checksum: ",
+        BASE_MAGIC,
+        MAX_ALLOW_FD, MAX_GENERAL_STRING,
+        MAX_GIDS, MAX_OVERRIDE_LEN,
+        MAX_ARGSDEFVAR, MAX_ERRMSG_LEN,
+        MAX_INCLUDE_NEST, ERRMSG_RESERVE_ERRNO);
+  for (i=0, p=protocolchecksumversion; i<sizeof(protocolchecksumversion); i++, p++)
+    printf("%02x",*p);
+  printf("\n"
+        "rendezvous socket: `" RENDEZVOUSPATH "'\n"
+        "system config dir: `" SYSTEMCONFIGDIR "'\n"
+        "pipe filename format: `%s' (max length %d)\n"
+        COPYRIGHT("","\n"),
+        PIPEFORMAT, PIPEMAXLEN);
+  serv_checkstdoutexit();
+}
+
+static void NONRETURNING dumpconfig(const char *string) {
+  int nspaces, c, lnl;
+  
+  nspaces= 0;
+  lnl= 1;
+  while ((c= *string++)) {
+    switch (c) {
+    case ' ': nspaces++; break;
+    case '\n':
+      if (!lnl) putchar('\n');
+      nspaces= 0; lnl= 1;
+      break;
+    default:
+      while (nspaces>0) { putchar(' '); nspaces--; }
+      putchar(c);
+      lnl= 0;
+      break;
+    }
+  }
+  assert(lnl);
+  serv_checkstdoutexit();
+}
+
+void bisexec_toplevel(const char *const *argv) {
+  dumpconfig(TOPLEVEL_CONFIGURATION);
+}
+
+void bisexec_override(const char *const *argv) {
+  dumpconfig(TOPLEVEL_OVERRIDDEN_CONFIGURATION);
+}
+
+void bisexec_reset(const char *const *argv) {
+  dumpconfig(RESET_CONFIGURATION);
+}
+
+void bisexec_execute(const char *const *argv) {
+  always_dumpexecsettings();
+  serv_checkstdoutexit();
+}
+
+void bisexec_shutdown(const char *const *argv) {
+  /* This is only reached if the serviceuser_uid test in
+   * process.c:servicerequest() fails (we have to handle the
+   * shutdown request there, unfortunately).
+   */
+  fputs("uservd: builtin service shutdown: permission denied\n",stderr);
+  _exit(-1);
+}
+
+static void serv_resetsignal(int signo) {
+  struct sigaction sig;
+  
+  sig.sa_handler= SIG_DFL;
+  sigemptyset(&sig.sa_mask);
+  sig.sa_flags= 0;
+  if (sigaction(signo,&sig,0)) serv_syscallfail("reset signal handler");
+}
+
+static const char *see_loginname(void) { return serviceuser; }
+static const char *see_home(void) { return serviceuser_dir; }
+static const char *see_shell(void) { return serviceuser_shell; }
+
+static const char *see_service(void) { return service; }
+static const char *see_c_cwd(void) { return cwd; }
+static const char *see_c_loginname(void) { return loginname; }
+static const char *see_c_uid(void) {
+  static char buf[CHAR_BIT*sizeof(uid_t)/3+4];
+  snyprintf(buf,sizeof(buf),"%lu",(unsigned long)request_mbuf.callinguid);
+  return buf;
+}
+
+static const char *see_c_list(int n, const char *(*fn)(int i)) {
+  int l, i;
+  char *r;
+  
+  for (i=0, l=1; i<n; i++) l+= strlen(fn(i))+1;
+  r= xmalloc(l); r[l-1]= '*';
+  for (i=0, *r=0; i<n; i++) snytprintfcat(r,l,"%s ",fn(i));
+  assert(!r[l-1] && r[l-2]==' ');
+  r[l-2]= 0;
+  return r;
+}
+
+static const char *seei_group(int i) {
+  return calling_groups[i];
+}
+static const char *see_c_group(void) {
+  return see_c_list(request_mbuf.ngids,seei_group);
+}
+
+static const char *seei_gid(int i) {
+  static char buf[CHAR_BIT*sizeof(gid_t)/3+4];
+  
+  snyprintf(buf,sizeof(buf),"%ld",(long)calling_gids[i]);
+  return buf;
+}
+static const char *see_c_gid(void) {
+  return see_c_list(request_mbuf.ngids,seei_gid);
+}
+
+static const struct serv_envinfo {
+  const char *name;
+  const char *(*fn)(void);
+} serv_envinfos[]= {
+  { "USER",           see_loginname   },
+  { "LOGNAME",        see_loginname   },
+  { "HOME",           see_home        },
+  { "SHELL",          see_shell       },
+  { "PATH",           defaultpath     },
+  { "USERV_SERVICE",  see_service     },
+  { "USERV_CWD",      see_c_cwd       },
+  { "USERV_USER",     see_c_loginname },
+  { "USERV_UID",      see_c_uid       },
+  { "USERV_GROUP",    see_c_group     },
+  { "USERV_GID",      see_c_gid       },
+  {  0                                }
+};
+
+void execservice(const int synchsocket[], int clientfd) {
+  static const char *const setenvpfargs[]= {
+    "/bin/sh",
+    "-c",
+    ". " SETENVIRONMENTPATH "; exec \"$@\"",
+    "-",
+    0
+  };
+  int fd, realfd, holdfd, newfd, r, envvarbufsize=0, targ, nargs, i, l, fdflags;
+  char *envvarbuf=0;
+  const char **args, *const *cpp;
+  char *const *pp;
+  char synchmsg;
+  const struct serv_envinfo *sei;
+
+  if (dup2(fdarray[2].realfd,2)<0) {
+    static const char duperrmsg[]= "uservd(service): cannot dup2 for stderr\n";
+    write(fdarray[2].realfd,duperrmsg,sizeof(duperrmsg)-1);
+    _exit(-1);
+  }
+  serv_resetsignal(SIGPIPE);
+  serv_resetsignal(SIGCHLD);
+
+  if (close(synchsocket[0])) serv_syscallfail("close parent synch socket");
+
+  if (setpgid(0,0)) serv_syscallfail("set process group");
+  synchmsg= 'y';
+  r= write(synchsocket[1],&synchmsg,1);
+  if (r!=1) serv_syscallfail("write synch byte to parent");
+  r= synchread(synchsocket[1],'g');
+  if (r) serv_syscallfail("reach synch byte from parent");
+
+  if (close(clientfd)) serv_syscallfail("close client socket fd");
+
+  /* First we need to close the holding writing ends of the pipes
+   * inherited from our parent: */
+  for (fd=0; fd<fdarrayused; fd++) {
+    if (fdarray[fd].holdfd == -1) continue;
+    if (close(fdarray[fd].holdfd)) serv_syscallfail("close pipe hold fd");
+    fdarray[fd].holdfd= -1;
+  }
+  /* Now we can reuse the .holdfd member of the fdarray entries. */
+
+  /* We have to make all the fd's work.  It's rather a complicated
+   * algorithm, unfortunately.  We remember in holdfd[fd] whether fd
+   * is being used to hold a file descriptor we actually want for some
+   * other real fd in the service program; holdfd[fd] contains the fd
+   * we eventually want fd to be dup'd into, so that realfd[holdfd[fd]]==fd.
+   * After setting up the holdfds we go through the fds in order of
+   * eventual fd making sure that fd is the one we want it to be.  If the
+   * holdfd tells us we're currently storing some other fd in there we
+   * move it out of the way with dup and record its new location.
+   */
+  for (fd=0; fd<fdarrayused; fd++) {
+    if (fdarray[fd].realfd < fdarrayused && fdarray[fd].realfd >= 0)
+      fdarray[fdarray[fd].realfd].holdfd= fd;
+  }
+  for (fd=0; fd<fdarrayused; fd++) {
+    realfd= fdarray[fd].realfd;
+    if (realfd == -1) continue;
+    holdfd= fdarray[fd].holdfd;
+    if (holdfd == fd) {
+      assert(realfd == fd);
+      fdarray[fd].holdfd= -1;
+      continue;
+    } else if (holdfd != -1) {
+      assert(fdarray[holdfd].realfd == fd);
+      newfd= dup(fd); if (newfd<0) serv_syscallfail("dup out of the way");
+      fdarray[holdfd].realfd= newfd;
+      if (newfd<fdarrayused) fdarray[newfd].holdfd= holdfd;
+      fdarray[fd].holdfd= -1;
+    }
+    if (dup2(fdarray[fd].realfd,fd)<0) serv_syscallfail("dup2 set up fd");
+    if (close(fdarray[fd].realfd)) serv_syscallfail("close old fd");
+    fdflags= fcntl(fd,F_GETFD); if (fdflags<0) serv_syscallfail("get fd flags");
+    if (fcntl(fd,F_SETFD,fdflags&~FD_CLOEXEC)==-1) serv_syscallfail("set no-close-on-exec on fd");
+    fdarray[fd].realfd= fd;
+  }
+
+  for (sei= serv_envinfos; sei->name; sei++)
+    if (setenv(sei->name,sei->fn(),1)) serv_syscallfail("setenv standard");
+  for (i=0; i<request_mbuf.nvars; i++) {
+    l= strlen(defvararray[i].key)+9;
+    if (l>envvarbufsize) { envvarbufsize= l; envvarbuf= xrealloc(envvarbuf,l); }
+    snyprintf(envvarbuf,l,"USERV_U_%s",defvararray[i].key);
+    if (setenv(envvarbuf,defvararray[i].value,1)) serv_syscallfail("setenv defvar");
+  }
+
+  nargs= 0;
+  if (setenvironment) for (cpp= setenvpfargs; *cpp; cpp++) nargs++;
+  nargs++;
+  if (execargs) for (pp= execargs; *pp; pp++) nargs++;
+  if (!suppressargs) nargs+= request_mbuf.nargs;
+  args= xmalloc(sizeof(char*)*(nargs+1));
+  targ= 0;
+  if (setenvironment) for (cpp= setenvpfargs; *cpp; cpp++) args[targ++]= *cpp;
+  args[targ++]= execpath;
+  if (execargs) for (pp= execargs; *pp; pp++) args[targ++]= *pp;
+  if (!suppressargs) for (i=0; i<request_mbuf.nargs; i++) args[targ++]= argarray[i];
+  args[targ]= 0;
+
+  if (execbuiltin)
+    execbuiltin(args);
+  else
+    execv(args[0],(char* const*)args);
+
+  serv_syscallfail("exec service program");
+  _exit(-1);
+}
diff --git a/spec.sgml b/spec.sgml
new file mode 100644 (file)
index 0000000..c5bd395
--- /dev/null
+++ b/spec.sgml
@@ -0,0 +1,1426 @@
+<!doctype debiandoc system>
+
+<book>
+<title>User service daemon and client specification
+<author>Ian Jackson <email>ian@davenant.greenend.org.uk
+<version>1.1.1~~iwj</version>
+
+<abstract>
+This is a specification for a Unix system facility to allow one
+program to invoke another when only limited trust exists
+between them.
+
+<copyright>
+<prgn/userv/ is
+Copyright 1996-2003,2006 Ian Jackson;
+Copyright 2000 Ben Harris.
+<p>
+
+<prgn/userv/ is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at
+your option) any later version.
+<p>
+
+This program is distributed in the hope that it will be useful, but
+<em/without any warranty/; without even the implied warranty of
+<em/merchantability/ or <em/fitness for a particular purpose/.  See
+the GNU General Public License for more details.
+<p>
+
+You should have received a copy of the GNU General Public License
+along with <prgn/userv/; if not, write to the Free Software
+Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+<toc sect>
+
+<chapt id="intro">Introduction
+<p>
+There is a daemon which invokes user service programs (henceforth
+`services') in response to requests by callers of a companion client
+program (henceforth the `client') and according to rules set forth in
+system-wide and user-specific configuration files.  The companion
+client program is setuid root, and negotiates with the daemon through
+an <prgn/AF_UNIX/ socket and associated objects in a system-wide
+private directory set aside for the purpose.  The user who wishes the
+service to be performed and calls the client is called the `calling
+user'; the process which calls the client is called the `calling
+process'.
+
+<p>
+The daemon and the client are responsible for ensuring that
+information is safely carried across the security boundary between the
+two users, and that the processes on either side cannot interact with
+each other in any unexpected ways.
+
+<chapt id="client">Client program usage
+
+<p>
+<example>
+userv <var/options/ [--] <var/service-user/ <var/service-name/ [<var/argument/ ...]
+userv <var/options/ -B|--builtin [--] <var/builtin-service/ [<var/info-argument/ ...]
+</example>
+<p>
+
+<var/service-user/ specifies which user is to provide the service.
+The user may be a login name or a numeric uid, or <tt/-/ to indicate
+that the service user is to be the same as the calling user.
+<p>
+
+The service name is interpreted by the userv<footnote><prgn/userv/ is
+short for `user services', and is pronounced `you-serve'.</footnote>
+daemon on behalf of the service user.  It will often be the name of a
+program.
+
+<sect>Options
+<p>
+
+Single-letter options may be combined as is usual with Unix programs,
+and the value for such an option may appear in the same argument or in
+the next.
+
+<taglist>
+<tag/<tt/-B//
+<tag/<tt/--builtin//
+<item>
+Requests that a builtin service be provided.  This is equivalent to
+using the <prgn/--override/ option to specify a string consisting of
+<prgn/execute-builtin/ followed by the <var/builtin-service/
+requested, and requesting a service user of <tt/-/ (indicating the
+calling user).
+<p>
+
+If the builtin service being requested requires a
+<var/service-argument/ then this must be supplied to the client in the
+same argument as the <var/builtin-service/.  See <ref
+id="dirs-execution"> for details of the builtin services available,
+and <ref id="optoverride"> for details of the <prgn/--override/
+options.
+<p>
+
+The actual service name passed will be the <var/builtin-service/; note
+that this actual service name (as opposed to the override data) and
+the <var/info-argument/s supplied will be ignored by most builtin
+services; the override mechanism and <prgn/execute-builtin/ will be
+used to ensure that the right builtin service is called with the right
+<var/service-argument/s.
+
+<tag/<tt/-f<var/fd/[<var/modifiers/]=<var/filename///
+<tag/<tt/--file <var/fd/[<var/modifiers/]=<var/filename///
+<item>
+Requests that data be copied in and out of the service using pipes.
+For each file or descriptor this will be done by creating a pipe, one
+end of which is passed to the service program and the other end of
+which is passed to a copy of <prgn/cat/ invoked by the client; the
+other file descriptor passed to <prgn/cat/ will be one inherited by
+the client program from the caller or one opened by the client program
+on behalf of the caller.
+<p>
+
+The descriptor in the service program that should be connected must be
+specified as <var/fd/, either as a decimal number or as one of the
+strings <tt/stdin/, <tt/stdout/ or <tt/stderr/.  The next argument is
+a filename which will be opened by the client with the privileges of
+the calling user.
+
+<p>
+<var/modifiers/ is used to specify whether the file or descriptor is
+to be read from or written to.  It consists of a series of words
+separated by commas.  A comma may separate the <var/modifiers/ from
+the <var/fd/ and is required if <var/fd/ is not numeric.
+
+<p>
+The modifier words are:
+<taglist compact>
+<tag/<tt/read//
+<item>
+<tt/O_RDONLY/: Allow reading and not writing.  May not be used with
+<tt/write/ or things that imply it.
+
+<tag/<tt/write//
+<item>
+<tt/O_WRONLY/: Allow writing and not reading.  <em/Doesn't truncate or
+create/ without <tt/truncate/ or <tt/create/.  <tt/write/ or things
+that imply it may not be used with <tt/read/.
+
+<tag/<tt/overwrite//
+<item>
+Equivalent to <tt/write,create,truncate/.
+
+<tag/<tt/create//
+<tag/<tt/creat//
+<item>
+<tt/O_CREAT/: Creates the file if necessary.  Implies <tt/write/.
+
+<tag/<tt/exclusive//
+<tag/<tt/excl//
+<item>
+<tt/O_EXCL/: Fails if the file already exists.  Implies <tt/write/ and
+<tt/create/.  May not be used with <tt/truncate/.
+
+<tag/<tt/truncate//
+<tag/<tt/trunc//
+<item>
+<tt/O_TRUNC/: Truncate any existing file.  Implies <tt/write/.
+May not be used with <tt/exclusive/.
+
+<tag/<tt/append//
+<item>
+<tt/O_APPEND/: All writes will append to the file.  Implies <tt/write/
+(but not <tt/create/).
+
+<tag/<tt/sync//
+<item>
+<tt/O_SYNC/: Do writes synchronously.  Implies <tt/write/.
+
+<tag/<tt/wait//
+<tag/<tt/nowait//
+<tag/<tt/close//
+<item>
+
+These modifiers control the behaviour of the client, with respect to
+the pipes carrying data to and from the service, when the service
+terminates.  See below.
+
+<tag/<tt/fd//
+<item>
+The <var/filename/ is not a filename but a numeric file descriptor.
+One or both of <tt/read/ and <tt/write/ must be specified, and no
+other words are allowed.  The <var/filename/ may also be <tt/stdin/,
+<tt/stdout/ or <tt/stderr/ for file descriptor 0, 1 or 2 respectively.
+
+</taglist>
+<p>
+
+If no <var/modifiers/ which imply <tt/read/ or <tt/write/ are used it
+is as if <tt/write/ had been specified, except that if the
+filedescriptor 0 of the service is being opened (either specified
+numerically or with <tt/stdin/) it is as if <tt/overwrite/ had been
+specified (or <tt/write/ if only <tt/fd/ was specified).
+<p>
+
+The client will also use <tt/O_NOCTTY/ when opening files specified by
+the caller, to avoid changing its controlling terminal.
+<p>
+
+By default stdin, stdout and stderr of the service will be connected
+to the corresponding descriptors on the client.  Diagnostics from
+the client and daemon will also appear on stderr.
+<p>
+
+If <tt/wait/ is specified, the client will wait for the pipe to be
+closed, and only exit after this has happened.  This means that either
+the receiving end of the pipe connection was closed while data was
+still available at the sending end, or that the end of file was
+reached on the reading file descriptor.  Errors encountered reading or
+writing in the client at this stage will be considered a system error
+and cause the client to exit with status 255, but will not cause
+disconnection at the service side since the service has already
+exited.
+<p>
+
+If <tt/close/ is specified the client will immediately close the pipe
+connection by killing the relevant copy of <prgn/cat/.  If the service
+uses the descriptor it will get <prgn/SIGPIPE/ (or <prgn/EPIPE/) for a
+writing descriptor or end of file for a reading one; the descriptor
+opened by or passed to the client will also be closed.
+<p>
+
+If <tt/nowait/ is specified then the client will not wait and the
+connection will remain open after the client terminates.  Data may
+continue to be passed between the inheritors of the relevant
+descriptor on the service side and the corresponding file or
+descriptor on the client side until either side closes their
+descriptor.  This should not usually be specified for stderr (or
+stdout if <tt/--signals stdout/ is used) since diagnostics from
+the service side may arrive after the client has exited and be
+confused with expected output.
+<p>
+
+The default is <tt/wait/ for writing file descriptors and <tt/close/
+for reading ones.
+
+<tag/<tt/-w<var/fd/=<var/action///
+<tag/<tt/--fdwait<var/fd/=<var/action///
+<item>
+Sets the action on termination of the service for the specified file
+descriptor; <var/action/ must be <tt/wait/, <tt/nowait/ or <tt/close/
+as described above.  The file descriptor must be specified as open
+when this option is encountered; this option is overridden by any
+later <prgn/--file/ or <prgn/--fdwait/ option - even by a
+<prgn/--file/ which does not specify an action on termination (in this
+case the default will be used, as described above).
+
+<tag/<tt/-D<var/name/=<var/value///
+<tag/<tt/--defvar <var/name/=<var/value///
+<item>
+Set a user-defined variable <var/name/ to <var/value/.  These
+user-defined variables are made available in the configuration
+language as the parameters <tt/u-<var/name// and are passed to the
+service in environment variables <tt/USERV_U_<var/name//.  <var/name/
+may contain only alphanumerics and underscores, and must start with a
+letter.  If several definitions are given for the same <var/name/ then
+only the last is effective.
+
+<tag/<tt/-t <var/seconds///
+<tag/<tt/--timeout <var/seconds///
+<item>
+Time out the service if it takes longer than <var/seconds/ seconds (a
+positive integer, in decimal).  Timeout will produce a diagnostic on
+stderr and an exit status of 255.  If <var/seconds/ is zero then no
+timeout will be implemented (this is the default).
+
+<tag/<tt/-S/ <var/method//
+<tag/<tt/--signals/ <var/method//
+<item>
+Affects the handling of the exit status when the service terminates
+due to a signal.  (The client will always finish by calling
+<prgn/_exit/, so that only numbers from 0 to 255 can be returned and
+not the full range of numbers and signal indications which can be
+returned by the <prgn/wait/ family of system calls.)
+<p>
+
+The <var/method/ may be one of the following:
+<taglist compact>
+<tag/<var/status/
+<item>
+The client's exit status will be <var/status/.  This will not be
+distinguishable from the service really having exited with code
+<var/status/.  This method is the default, with a <var/status/ of 254.
+
+<tag/<tt/number//
+<tag/<tt/number-nocore//
+<item>
+The client's exit status will be the number of the signal which caused
+the termination of the service.  If <tt/number/ is used rather than
+<tt/number-nocore/ then 128 will be added if the service dumped core.
+<tt/number/ is very like the exit code mangling done by the Bourne
+shell.
+
+<tag/<tt/highbit//
+<item>The client's exit status will be the number of the signal with
+128 added.  If the service exits normally with an exit code of greater
+than 127 then 127 will be returned.
+
+<tag/<tt/stdout//
+<item>
+The service's numeric wait status as two decimal numbers (high byte
+first) and a textual description of its meaning will be printed to the
+client's standard output.  It will be preceded by a newline and
+followed by an extra newline, and the numbers are separated from each
+other and from the textual description by single spaces.  The exit
+status of the client will be zero, unless a system error occurs in
+which case no exit status and description will be printed to stdout,
+and an error message will be printed to stderr as usual.
+</taglist>
+
+<p>
+Problems such as client usage errors, the service not being found or
+permission being denied or failure of a system call are system errors.
+An error message describing the problem will be printed on the
+client's stderr, and the client's exit status will be 255.  If the
+client dies due to a signal this should be treated as a serious system
+error.
+
+<tag/<tt/-H//
+<tag/<tt/--hidecwd//
+<item>
+Prevents the calling process's current directory name from being
+passed to the service; the null string will be passed instead.
+
+<tag/<tt/-P//
+<tag/<tt/--sigpipe//
+<item>
+If the service program is terminated due to a <prgn/SIGPIPE/ the exit
+status of the client will be zero, even if it would have been
+something else according to the exit status method specified.  This
+option has no effect on the code and description printed if the exit
+status method <tt/stdout/ is in use.
+
+<tag/<tt/-h//
+<tag/<tt/--help//
+<tag/<tt/--copyright//
+<item>
+<tt/-h/ or <tt/--help/ prints the client's usage message;
+<tt/--copyright/ prints the copyright and lack of warranty notice.
+
+</taglist>
+
+<sect id="optoverride">Security-overriding options
+<p>
+
+There are also some options which are available for debugging and to
+allow the system administrator to override a user's policy.  These
+options are available only if the client is called by root or if the
+calling user is the same as the service user.
+
+<taglist>
+
+<tag/<tt/--override <var/configuration-data///
+<tag/<tt/--override-file <var/filename///
+<item>
+Do not read the usual configuration files.  Instead, the client sends
+<var/configuration-data/ (followed by a newline) or the contents of
+<var/filename/ (which is opened in the context of the client) to the
+daemon and the daemon uses that data instead.  The
+<var/configuration-data/ must all be in one argument.  It will have a
+single newline appended so that a single directive can easily be
+given, but if more than one directive is required it will have to
+contain one or more real newlines.
+
+<tag/<tt/--spoof-user <var/user///
+<item>
+Pretend to the service that it is being called by <var/user/ (which
+may be a username or a uid).  This will also affect the group and
+supplementary groups supplied to the service; they will be the
+standard group and supplementary groups for <var/user/.  The
+<tt/--spoof-user/ option will <em/not/ affect which user is chosen if
+the service user is specified as just <tt/-/; in this case the service
+user will be the real calling user.
+
+</taglist>
+
+
+<chapt id="envir">Execution environment of the service program
+<p>
+
+The daemon which is handling the service user side of things will read
+configuration files to decide what to do.  If it decides to allow the
+service to be provided it will fork a subprocess to execute the
+service.
+<p>
+
+The service will have no controlling terminal, but it will be a
+process group leader.
+<p>
+
+If the client is killed or times out or a file or descriptor being
+read or written by the client process gets an error then the service
+will be disconnected from the client.  The client will return an exit
+status of 255 and some the service's pipes may be closed at the other
+end.  The service will become a child of <prgn/init/.  The service may
+well not notice the disconnection, though writing to a pipe after this
+may produce a <prgn/SIGPIPE/ and the facility exists to have a
+<prgn/SIGHUP/ sent to the service on disconnection.
+
+<sect>File descriptors
+<p>
+
+The service program's standard filedescriptors, and possibly other
+file descriptors, will be connected to pipes or to
+<prgn>/dev/null</>.  The <prgn/userv/ client/daemon pair will arrange
+that data is copied between the files or file descriptors specified to
+to the client by the caller and these these pipes.
+<p>
+
+Pipes which may be written to will be closed if a write error occurs
+on the corresponding client-side file or descriptor, which may result
+in a <prgn/SIGPIPE/ in the service program; pipes open for reading
+will get <prgn/EOF/ if the client-side file descriptor gets <prgn/EOF/
+or an error.
+<p>
+
+If the service closes one of its reading file descriptors the writing
+end of the corresponding pipe will generate a <prgn/SIGPIPE/ when
+attempts are made by the client/daemon pair to write to it.  This will
+not be considered an error; rather, the relevant pipe will be
+discarded and the corresponding file or file descriptor held by the
+client will be closed.
+<p>
+
+Likewise, if one of the file descriptors held by the client for
+writing by the service is a pipe whose other end is closed by the
+caller then the client/daemon pair will see an error when trying to
+copy data provided by the service.  This too will not be considered an
+error; rather, the pipe correspondong to that descriptor will be
+closed and any further writes will cause the service to get a
+<prgn/SIGPIPE/.
+<p>
+
+Note that not all write errors or broken pipes on file descriptors may
+be visible to the service, since buffered data may be discarded by the
+operating system and there will be a finite interval between the error
+happening and the service being disconnected from the client or the
+next write causing a <prgn/SIGPIPE/.
+<p>
+
+Read errors on file descriptors (and disconnection) will only be
+visible to the service and distinguishable from normal end of file if
+<prgn/disconnect-hup/ is in effect.
+<p>
+
+Read and write errors (other than broken pipes, as described above)
+will always be visible to the caller; they are system errors, and will
+therefore cause the client to print an error message to stderr and
+return with an exit status of 255.
+<p>
+
+If the main service program process exits while it still has running
+children any file descriptors held by those children can remain open,
+depending on the use of <tt/wait/, <tt/nowait/ or <tt/close/ for the
+relevant file descriptor in the client's arguments.  By default
+writing filedescriptors remain open and the client will wait for them
+to be closed at the service end, and reading file descriptors are
+closed immediately.  These leftover child processes will not get a any
+<prgn/SIGHUP/ even if a read or write error occurs or the client
+disconnects before then.
+
+<sect>Environment
+<p>
+
+The service will have some information in environment variables:
+<taglist compact>
+<tag/<tt/USERV_USER//
+<item>
+The login name of the calling user.  If the <prgn/LOGNAME/ variable is
+set (or, if that is unset, if the <prgn/USER/ variable is set) in the
+environment passed to the client by the caller then the password entry
+for that login name will be looked up; if that password entry's uid is
+the same as that of the calling process then that login name will be
+used, otherwise (or if neither <prgn/LOGNAME/ nor <prgn/USER/ is set)
+the calling process's uid will be looked up to determine their login
+name (and if this lookup fails then the service will not be invoked).
+
+<tag/<tt/USERV_UID//
+<item>
+The uid of the calling process.
+
+<tag/<tt/USERV_GID//
+<item>
+The gid and supplementary group list of the calling process: first the
+group in gid and then those in the supplementary group list, in
+decimal, separated by spaces.
+
+<tag/<tt/USERV_GROUP//
+<item>
+The group names of the calling process, listed in the same way as the
+ids are in <prgn/USERV_GID/.  If no name can be found for any of the
+calling process's group(s) then the service will not be invoked.
+
+<tag/<tt/USERV_CWD//
+<item>
+The client's current working directory name (this directory may not be
+accessible to the service).  If it could not be determined or the
+<prgn/--hidecwd/ flag was used then this variable will be set to an
+empty string (this is not considered an error).
+
+<tag/<tt/USERV_SERVICE//
+<item>
+The service name requested by the caller.
+
+<tag/<tt/USERV_U_<var/name///
+<item>
+The value supplied to the client by the caller using -D<var/name/.
+
+</taglist>
+
+<prgn/HOME/, <prgn/PATH/, <prgn/SHELL/, <prgn/LOGNAME/ and <prgn/USER/
+will be set appropriately (according to the details of the service
+user).
+
+
+<chapt id="config">Service-side configuration
+<p>
+
+Which services may be run by whom and under what conditions is
+controlled by configuration files.
+<p>
+
+The daemon will read these files in order.  Certain directives in the
+files modify the daemon's execution settings for invoking the service,
+for example allowing certain file descriptors to be specified by the
+client or specifying which program to execute to provide the service.
+<p>
+
+The <em/last/ instance of each such setting will take effect.  The
+directives which specify which program to execute will not stop the
+configuration file from being read; they will be remembered and will
+only take effect if they are not overridden by a later directive.
+<p>
+
+The daemon will first read <tt>/etc/userv/system.default</>.  Then, by
+default (this behaviour may be modified), it will read a per-user file
+<tt>~/.userv/rc</>, if it exists and the service user's shell is in
+<tt>/etc/shells</>.  Finally it will read
+<tt>/etc/userv/system.override</>.
+<p>
+
+When it has read all of these files it will act according to the
+currently values of of the execution settings.
+
+<sect>Configuration file syntax
+<p>
+
+The configuration file is a series of directives, usually one per
+line.  The portion of a line following a hash character <tt/#/ is
+taken as a comment and ignored.  Each directive consists of a series
+of tokens separated by linear whitespace (spaces and tabs); tokens may
+be words consisting of non-space characters, or, where a string is
+required, a string in double quotes.  Double-quoted strings may
+contain the following backslash escapes:
+
+<taglist compact>
+<tag/<tt/\n//<item>newline
+<tag/<tt/\t//<item>tab
+<tag/<tt/\r//<item>carriage return
+<tag/<tt/\<var/OOO///<item>character whose octal code is <var/OOO/
+<tag/<tt/\x<var/XX///<item>character whose hex code is <var/XX/
+<tag/<tt/\<var/punctuation///<item>literal punctuation character (eg <tt/\\/, <tt/\"/)
+<tag/<tt/\<var/newline// (ie, backslash at end of line)/
+<item>string continues on next line
+</taglist>
+<p>
+
+Relative pathnames in directives are relative to the service program's
+current directory (usually the service user's home directory).
+Pathnames starting with the two characters <tt>~/</> are taken to be
+relative to the service user's home directory.
+
+<sect id="directives">Configuration file directives
+<p>
+
+<sect1 id="dirs-immediate">Immediate directives
+<p>
+
+The following directives take effect immediately:
+
+<taglist>
+<tag/<tt/cd <var/pathname///
+<item>
+Change directory in the service program.  <prgn/cd/ is cumulative.  It
+is an error if the directory cannot be changed to.
+<p>
+
+<prgn/cd/ should not be used between <prgn/execute-from-directory/ and
+the invocation of the service program, as the test for the
+availability of the service program would be done with the old current
+directory and the actual execution with the new (probably causing an
+error).
+
+<tag/<tt/eof//
+<item>
+Stop reading the configuration file in question, as if end of file had
+been reached.  Any control constructs (<prgn/if/, <prgn/catch-quit/ or
+<prgn/errors-push/) which were started in that file will be considered
+finished.  Parsing will continue in the file which caused the file
+containing the <prgn/eof/ to be read.
+
+<tag/<tt/quit//
+<item>
+Stop reading configuration files and act immediately on the current
+settings.  The behaviour of <prgn/quit/ is subject to the
+<prgn/catch-quit/ control construct.
+
+<tag/<tt/include <var/filename///
+<tag/<tt/include-ifexist <var/filename///
+<item>
+Read the configuration file <var/filename/, and then return to this
+file and continue parsing it with the next directive.  It is an error
+if the file cannot be opened and read, unless <prgn/include-ifexist/
+is used and the file does not exist, in which case the directive is
+silently ignored.
+
+<tag/<tt/include-lookup <var/parameter/ <var/directory///
+<tag/<tt/include-lookup-all <var/parameter/ <var/directory///
+<item>
+Read the configuration file in <var/directory/ whose name is the value
+of <var/parameter/ (see the description of <prgn/if/, <ref
+id="dirs-control">).  If <var/parameter/ has several values they will
+be tried in order; with <prgn/include-lookup/ this search will stop
+when one is found, but with <prgn/include-lookup-all/ the search will
+continue and any files appropriate to other values will be read too.
+<p>
+
+If none of the parameter's values had a corresponding file then the
+file <tt/:default/ will be read, if it exists.  If <var/parameter/'s
+list of values was empty then the file <tt/:none/ will be tried first
+and read if it exists, otherwise <tt/:default/ will be tried.
+<p>
+
+It is not an error for any of the files (including <tt/:default/) not
+to exist, but it is an error if a file exists and cannot be read or if
+the directory cannot be accessed.
+
+<p>
+A translation will be applied to values before they are used to
+construct a filename, so that the lookup cannot access dotfiles or
+files in other directories: values starting with full stops will have
+a colon prepended (making <tt/:./), colons will be doubled, and each
+slash will be replaced with a colon followed by a hyphen <tt>:-</>.  A
+parameter value which is the empty string will be replaced with
+<tt/:empty/ (note that this is different from a parameter not having
+any values).
+
+<tag/<tt/include-directory <var/directory///
+<item>
+Read configuration from all files in directory <var/directory/ which
+are plain files whose names consist only of alphanumerics and hyphens
+and start with an alphanumeric.  They will be read in lexical order.
+It is an error for the directory not to exist or for it or any of the
+files found not to be read successfully, or for anything with an
+appropriate name not to be a plain file or a symbolic link to a plain
+file.
+
+<tag/<tt/error <var/text ...///
+<item>
+Causes an error whose message includes the descriptive string
+<var/text/.  <var/text/ may consist of several tokens with intervening
+whitespace.  The whitespace will be included in the message as found
+in the configuration file: all the characters until the end of the
+line will be included verbatim, unless they are part of a
+double-quoted string, in which case the usual meaning of the string
+(i.e., after backslash escape processing) will be used.  Comments and
+linear whitespace at the end of the line (or just before the comment)
+will still be ignored.
+
+<tag/<tt/message <var/text ...///
+<item>
+Causes a message including the descriptive string <var/text/ to be
+delivered as if it were an error message, but does not actually cause
+an error.
+</taglist>
+
+<sect1 id="dirs-delayed">Directives with delayed effect
+<p>
+
+The following directives have no immediate effect, but are remembered
+and have an effect on later processing of the configuration files.
+
+<taglist>
+<tag/<tt/user-rcfile <var/filename///
+<item>
+Specifies that the file <var/filename/ should be read instead of the
+user's <tt>~/.userv/rc</>.  This does <em/not/ happen immediately;
+instead, the setting is remembered and used after the
+<prgn/system.default/ configuration file has been read.  This
+directive has no effect in a user's configuration file or in the
+<prgn/system.override/ file, as the user's configuration file has
+already been found and read by then and will not be re-read.
+
+<tag/<tt/errors-to-stderr//
+<item>
+Causes error messages to be delivered to the client's stderr.
+
+<tag/<tt/errors-to-file/ <var/filename//
+<item>
+Error messages will be written to <var/filename/, which will be opened
+in the context of and with the privileges of the service user.
+
+<tag/<tt/errors-to-syslog/ [<var/facility/ [<var/level/]]/
+<item>
+Error messages will be delivered using <prgn/syslog/.  The default
+<var/facility/ is <tt/user/; the default <var/level/ is <tt/error/.
+</taglist>
+
+<sect1 id="dirs-control">Control structure directives
+<p>
+
+The following directives are used to create control structures.  If
+the end of the file is encountered before the end of any control
+structure which was started inside it then that control structure is
+considered finished.  This is not an error.
+
+<taglist>
+<tag/<tt/if <var/condition///
+<tag/<tt/elif <var/condition///
+<tag/<tt/else//
+<tag/<tt/fi//
+<item>
+Lines following <prgn/if/ are interpreted only if the condition is
+true.  Many conditions are properties of parameter values.  Most
+parameters have a single string as a value; however, some may yield
+zero or several strings, in which case the condition is true if it is
+true of any of the strings individually.  Parameters are described
+below.
+<p>
+
+The conditions are:
+
+<taglist compact>
+<tag/<tt/glob <var/parameter/ <var/glob-pattern/ ...//
+<item>
+The value of the parameter whose name is given matches one of the glob
+patterns (anchored at both ends; backslashes can be used to escape
+metacharacters).
+
+<tag/<tt/range <var/parameter/ <var/min/ <var/max///
+<item>
+The value of the parameter is a nonnegative integer and lies within
+the range specified.  <var/min/ or <var/max/ may be <tt/$/ to indicate
+no lower or upper limit, respectively.
+
+<tag/<tt/grep <var/parameter/ <var/filename///
+<item>
+The <var/filename/ refers to a file one of whose lines is the value of
+the parameter (leading or trailing whitespace on each line and empty
+lines in the file are ignored).  It is an error for the file not to be
+opened and read.
+
+<tag/<tt/! <var/condition///
+<item>
+The <var/condition/ is <em/not/ true.
+
+<tag/Conjunctions: <tt/&amp;/ and <tt/|//
+<item>
+<example>
+( <var/condition/
+&amp; <var/condition/
+&amp; <var/condition/
+...
+)
+</example>
+is true if all the listed conditions are true; where <tt/|/ is used it
+is true if any of them is true.  Newlines must be used to separate one
+condition from the next, as shown, and the parentheses are mandatory.
+These conjunctions do not do lazy evaluation.
+</taglist>
+<p>
+
+The parameters are:
+
+<taglist compact>
+<tag/<tt/service//
+<item>
+The service name specified when the client was called.
+
+<tag/<tt/calling-user//
+<item>
+Two strings: the login name of the calling user (determined as for
+<prgn/USERV_USER/, above) and the calling uid (represented in
+decimal).
+
+<tag/<tt/calling-group//
+<item>
+Several strings: the primary and supplementary group names and gids
+(in decimal) of the calling process.  All the group names come first,
+and then the gids.  If the first supplementary group is the same as
+the primary group then it is elided.
+
+<tag/<tt/calling-user-shell//
+<item>
+The calling user's shell, as listed in the password entry for the
+calling login name (as determined for <prgn/USERV_USER/, above).
+
+<tag/<tt/service-user//
+<item>
+Two strings: the name of the service user (as specified to the client)
+and their uid (represented in decimal).
+
+<tag/<tt/service-group//
+<item>
+Several strings: the primary and supplementary group names and gids
+(in decimal) of the service user.
+
+<tag/<tt/service-user-shell//
+<item>
+The service user's shell, as listed in their password entry.
+
+<tag/<tt/u-<var/name///
+<item>
+The value of the user-defined variable <var/name/ passed by the caller
+using the <prgn/--defvar/ command-line option to the client.  If the
+variable was not defined then this parameter is an empty list of
+strings; in this case any condition which tests it will be false, and
+<tt/include-lookup/ on it will read the <tt/:none/ file, or
+<tt/:default/ if <tt/:none/ is not found.
+
+</taglist>
+
+<tag/<tt/errors-push/ <var/filename//
+<tag/<tt/srorre//
+<item>
+Stacks the error handling behaviour currently in effect.  Any changes
+to error handling will take effect only between <prgn/errors-push/ and
+<prgn/srorre/.
+
+<tag/<tt/catch-quit//
+<tag/<tt/hctac//
+<item>
+Any use of <prgn/quit/ inside <prgn/catch-quit/ will merely cause the
+parsing to continue at <prgn/hctac/ instead.  Any control constructs
+started since the <prgn/catch-quit/ will be considered finished if a
+<prgn/quit/ is found.
+<p>
+
+If an error occurs inside <prgn/catch-quit/ the execution settings
+will be reset (as if by the <prgn/reset/ directive) and parsing will
+likewise continue at <prgn/hctac/.
+<p>
+
+If a lexical or syntax error is detected in the same configuration
+file as the <prgn/catch-quit/, while looking for the <prgn/hctac/
+after an error or <prgn/quit/, that new error will not be caught.
+
+</taglist>
+
+<sect1 id="dirs-execution">Directives for changing execution settings
+<p>
+
+The following directives modify the execution settings; the server
+will remember the fact that the directive was encountered and act on
+it only after all the configuration has been parsed.  The <em/last/
+directive which modifies any particuar setting will take effect.
+
+<taglist>
+<tag/<tt/reject//
+<item>
+Reject the request.  <prgn/execute/, <prgn/execute-from-directory/ and
+<prgn/execute-from-path/ will change this setting.
+
+<tag/<tt/execute <var/program/ [<var/argument/ ...]//
+<item>
+Execute the program <var/program/, with the arguments as specified,
+followed by any arguments given to the client if
+<prgn/no-suppress-args/ is in effect.  It is an error for the
+execution to fail when it is attempted (after all the configuration
+has been parsed).  If <var/program/ does not contain a slash it will
+be searched for on the service user's path.
+
+<tag/<tt/execute-from-directory <var/pathname/ [<var/argument/ ...]//
+<item>
+Take all the characters after the last slash of the service name
+specified when the client was called, and execute that program in the
+directory named by <var/pathname/ as if it had been specified for
+<var/execute/.  The part of the service name used may contain only
+alphanumerics and hyphens and must start with an alphanumeric (and it
+must be non-empty), otherwise it is an error.
+<p>
+
+This directive is ignored if the relevant program does not exist in
+the directory specified; in this case the program to execute is left
+at its previous setting (or unset, if it was not set before).
+<p>
+
+It is an error for the test for the existence of the program to fail
+other than with a `no such file or directory' indication.  It is also
+an error for the execution to fail if and when it is attempted (after
+all the configuration has been parsed).
+
+<tag/<tt/execute-from-path//
+<item>
+<var/service/ is interpreted as a program on the default <prgn/PATH/
+(or as a pathname of an executable, if it contains a <tt>/</>).  This
+directive is <em/very dangerous/, and is only provided to make the
+<prgn/--override/ options effective.  It should not normally be used.
+It is an error for the execution to fail when it is attempted (after
+all the configuration has been parsed).
+
+<tag/<tt/execute-builtin <var/service-name/ <var/service-arguments//
+<item>
+Executes the builtin service <var/service-name/.  These builtin
+services display information about the server and/or the request, and
+ignore any arguments passed from the service side except possibly to
+print them as part of their output.  They write their results to their
+standard output (i.e., wherever file descriptor 1 is directed).  The
+builtin services are:
+
+<taglist compact>
+<tag/<tt/execute//
+<item>
+Displays the execution settings, defined variables,
+arguments, etc. with which the builtin service was invoked.
+
+<tag/<tt/environment//
+<item>
+Displays the environment variable settings with which the builtin
+service was invoked.
+
+<tag/<tt/parameter <var/parameter///
+<item>
+Displays the values of the service configuration language parameter
+specified.
+
+<tag/<tt/version//
+<item>
+Displays the version string and compilation details of the uservd
+server program.
+
+<tag/<tt/reset//
+<item>
+Displays the default reset configuration (evaluated when <prgn/reset/
+is found in a configuration file, or when an error is caught by
+<prgn/catch-quit/).
+
+<tag/<tt/toplevel//
+<item>
+Displays the top-level default configuration (the configuration data,
+evaluated by the server, which calls all the other configuration
+files).
+
+<tag/<tt/override//
+<item>
+Displays the top-level override configuration (the configuration data,
+evaluated by the server, which causes all the other configuration data
+to be parsed).
+
+<tag/<tt/help//
+<item>
+Displays a list of the understood builtin service names and arguments.
+</taglist>
+
+In the future other builtin services may be defined which do more than
+just print information.
+
+<tag/<tt/set-environment//
+<tag/<tt/no-set-environment//
+<item>
+Runs <tt>/etc/environment</> to set the service user's environment.
+This adds the overhead of invoking a shell, but doesn't cause any
+shell (de)mangling of the service's arguments.  This is achieved by
+invoking
+<example>
+.../program arg arg arg ...
+</example>
+as
+<example>
+/bin/sh -c '. /etc/environment; exec "$@"' - .../program arg arg arg ...
+</example>
+<prgn/no-set-environment/ cancels the effect of
+<prgn/set-environment/.
+
+<tag/<tt/no-suppress-args//
+<tag/<tt/suppress-args//
+<item>
+Include any arguments given to the client as arguments to the program
+invoked as a result of an <prgn/execute/,
+<prgn/execute-from-directory/ or <prgn/execute-from-path/ directive.
+<prgn/suppress-args/ undoes the effect of <prgn/no-suppress-args/.
+
+<tag/<tt/require-fd <var/fd-range/ read|write//
+<item>
+Insist that the filedescriptor(s) be opened for reading resp. writing.
+It is an error if any descriptor marked as required when the service
+is about to be invoked (after the configuration has been parsed) was
+not specified when the client was invoked.  Each file descriptor has a
+separate setting, and the last one of <prgn/require-fd/,
+<prgn/allow-fd/, <prgn/ignore-fd/, <prgn/null-fd/ or <prgn/reject-fd/
+which affected a particular file descriptor will take effect.
+<p>
+
+<var/fd-range/ may be a single number, two numbers separated by a
+hyphen, or one number followed by a hyphen (indicating all descriptors
+from that number onwards).  It may also be one of the words
+<tt/stdin/, <tt/stdout/ or <tt/stderr/.  Open-ended file descriptor
+rangers are allowed only with <prgn/reject-fd/ and <prgn/ignore-fd/,
+as otherwise the service program would find itself with a very large
+number of file descriptors open.
+<p>
+
+When the configuration has been parsed, and before the service is
+about to be executed, stderr (fd 2) must be required or allowed
+(<prgn/require-fd/ or <prgn/allow-fd/) for writing; this is so that
+the error message printed by the server's child process if it cannot
+<prgn/exec/ the service program is not lost.
+
+<tag/<tt/allow-fd <var/fd-range/ [read|write]//
+<item>
+Allow the descriptor(s) to be opened for reading resp. writing, or
+either if neither <tt/read/ nor <tt/write/ is specified.  If a
+particular descriptor not specified by the client then it will be open
+onto <tt>/dev/null</> (for reading, writing, or both, depending on
+whether <tt/read/, <tt/write/ or neither was specified).
+
+<tag/<tt/null-fd <var/fd-range/ [read|write]//
+<item>
+Specify that the descriptor(s) be opened onto <prgn>/dev/null</> for
+reading resp. writing, or both if neither <tt/read/ nor <tt/write/
+is specified.  Any specification of these file descriptors by the
+client will be silently ignored; the client will see its ends of the
+descriptors being closed immediately.
+
+<tag/<tt/reject-fd <var/fd-range///
+<item>
+Do not allow the descriptor(s) to be specified by the client.  It is
+an error if any descriptor(s) marked for rejection are specified when
+the service is about to be invoked (after the configuration has been
+parsed).
+
+<tag/<tt/ignore-fd <var/fd-range///
+<item>
+Silently ignore any specification by the client of those
+descriptor(s).  The pipes corresponding to these descriptors will be
+closed just before the service is invoked.
+
+<tag/<tt/disconnect-hup//
+<tag/<tt/no-disconnect-hup//
+<item>
+Causes the service's process group to get a <prgn/SIGHUP/ if the
+client disconnects before the main service process terminates.
+<prgn/no-disconnect-hup/ cancels <prgn/disconnect-hup/.
+<p>
+
+If one of the reading descriptors specified when the client is called
+gets a read error, or if the service is disconnected for some other
+reason, then the <prgn/SIGHUP/ will be delivered <em/before/ the
+writing end(s) of the service's reading pipe(s) are closed, so that
+the client can distinguish disconnection from reading EOF on a pipe.
+
+<tag/<tt/reset//
+<item>
+Resets the execution settings to the default.  This is equivalent to:
+<example>
+cd ~/
+reject
+no-set-environment
+suppress-args
+allow-fd 0 read
+allow-fd 1-2 write
+reject-fd 3-
+disconnect-hup
+</example>
+
+</taglist>
+
+If no <prgn/execute/, <prgn/execute-from-path/,
+<prgn/execute-from-directory/ or <prgn/builtin/ is interpreted before
+all the files are read then the request is rejected.
+
+
+<sect id="configerrors">Errors in the configuration file
+<p>
+
+If a syntax error or other problem occurs when processing a
+configuration file then a diagnostic will be issued, to wherever the
+error messages are currently being sent (see the <prgn/errors-/ family
+of directives, above).
+<p>
+
+The error will cause processing of the configuration files to cease at
+that point, unless the error was inside a <prgn/catch-quit/ construct.
+In this case the settings controlling the program's execution will be
+reset to the defaults as if a <prgn/reset/ directive had been issued,
+and parsing continues after <prgn/hctac/.
+
+
+<sect id="defaults">Defaults
+<p>
+
+The default configuration processing is as if the daemon were parsing
+an overall configuration file whose contents were as follows:
+
+<example>
+reset
+user-rcfile ~/.userv/rc
+errors-to-stderr
+include /etc/userv/system.default
+if grep service-user-shell /etc/shells
+   errors-push
+     catch-quit
+       include-ifexist <var/file specified by most recent user-rcfile directive/
+     hctac
+   srorre
+fi
+include /etc/userv/system.override
+quit
+</example>
+<p>
+
+If one of the <prgn/--override/ options to the client is used then it
+will instead be as if the daemon were parsing an overall configuration
+as follows:
+
+<example>
+reset
+errors-to-stderr
+include <var/file containing configuration data sent by client/
+quit
+</example>
+
+
+<chapt id="ipass">Information passed through the client/daemon combination
+<p>
+
+The information described below is the only information which passes
+between the caller and the service.
+
+<list>
+<item>
+The service name supplied by the caller is available in the
+configuration language for deciding whether and which service program
+to invoke, in the <prgn/service/ parameter, and is used by the
+<prgn/execute-from-directory/ and <prgn/execute-from-path/
+configuration directives.  It is usually used to select which service
+program to invoke.  It is also passed to the service program in the
+<prgn/USERV_SERVICE/ environment variable.
+
+<item>
+File descriptors specified by the client and allowed according to the
+configuration language will be connected.  Each file descriptor is
+opened for reading or writing.  Communication is via pipes, one end of
+each pipe being open on the appropriate file descriptor in the service
+program (when it is invoked) and the other end being held by the
+client process, which will read and write files it opens on behalf of
+its caller or file descriptors it is passed by its caller.
+<p>
+
+Data may be passed into the service through reading pipes and out of
+it through writing pipes.  These pipes can remain open only until the
+service and client have terminated, or can be made to stay open after
+the client has terminated and (if the service program forks) the main
+service process has exited; the behaviour is controlled by options
+passed to the client by its caller.
+<p>
+
+The caller can arrange that a writing pipe be connected to a pipe or
+similar object and cause attempts to write to that descriptor by the
+service to generate a <prgn/SIGPIPE/ (or <prgn/EPIPE/ if
+<prgn/SIGPIPE/ is caught or ignored) in the service.
+<p>
+
+Likewise, the service can close filedescriptors specified for reading,
+which will cause the corresponding filedescriptors passed by the
+caller to be closed, so that if these are pipes processes which write
+to them will receive <prgn/SIGPIPE/ or <prgn/EPIPE/.
+
+<item>
+If <prgn/no-suppress-args/ is set then arguments passed to the client
+by its caller will be passed on, verbatim, to the service.
+
+<item>
+Fatal signals and system call failures experienced by the client will
+result in the disconnection of the service from the client and
+possibly some of the communication file descriptors described above;
+if <prgn/disconnect-hup/ is set then the service will also be sent a
+<prgn/SIGHUP/.
+
+<item>
+The value of the <prgn/LOGNAME/ (or <prgn/USER/) environment variable
+as passed to the client will be used as the login name of the calling
+user if the uid of the calling process matches the uid corresponding
+to that login name.  Otherwise the calling uid's password entry will
+be used to determine the calling user's login name.
+<p>
+
+This login name and the calling uid are available in the configuration
+language in the <prgn/calling-user/ parameter and are passed to the
+service program in environment variables <prgn/USERV_USER/ and
+<prgn/USERV_UID/.
+<p>
+
+The shell corresponding to that login name (according to the password
+entry) is available as in the configuration language's
+<prgn/calling-user-shell/ parameter.
+<p>
+
+If no relevant password entry can be found then no service will be
+invoked.
+
+<item>
+The numeric values and textual names for calling gid and supplementary
+group list are available in the configuration language in the
+<prgn/calling-group/ parameter and are passed to the service in
+environment variables.
+<p>
+
+If no name can be found for a numeric group to which the calling
+process belongs then no service will be invoked.
+
+<item>
+The name of the current working directory in which the client was
+invoked is passed, if available and not hidden using <prgn/--hidecwd/,
+to the service program in the <prgn/USERV_CWD/ variable.  This grants no
+special access to that directory unless it is a subdirectory of a
+directory which is executable (searchable) but not readable by the
+service user.
+
+<item>
+Settings specified by the caller using the <tt/--defvar
+<var/name/=<var/value// option to the client are available in the
+configuration language as the corresponding <tt/u-<var/name//
+parameters and are passed to the service program in environment
+variables <tt/USERV_U_<var/name//.
+
+<item>
+If the calling user is root or the same as the service user then
+options may be given to the client which bypass the usual security
+features; in this case other information may pass between the caller
+and the service.
+
+</list>
+
+<chapt id="notes">Applications and notes on use
+<p>
+
+<sect id="examples">Examples
+<p>
+
+The companion package, <prgn/userv-utils/, contains a selection of
+example services, some of which are useful tools in their own right.
+See the <prgn/README/ in its top-level directory for details.
+
+<sect id="standards">Standard services and directory management
+<p>
+
+In later versions of this specification standard service names and
+interfaces for common services such as mail delivery and WWW CGI
+scripts may be specified.
+<p>
+
+<prgn/userv/-using applications and system services which hide
+<prgn/userv/ behind wrapper scripts may need to store information in
+the user's filespace to preserve the correct placement of the security
+perimiters.  Such applications should usually do so in a directory
+(created by them) <tt>~/.userv/<var/service/</>, where <var/service/
+is the service name or application in question.
+<p>
+
+If desired, a dot-directory inside <tt>~/.userv</> may be used to
+avoid the user becoming confused by finding parts of a semi-privileged
+application's internal state in their filespace, and/or discourage
+them from fiddling with and thus corrupting it.
+<p>
+
+However, <prgn/userv/ applications should of course not rely for their
+global integrity and security on the integrity of the data on the
+user's side of the security boundary.
+
+<sect id="reducepriv">Reducing the number of absolutely privileged subsystems
+<p>
+
+Currently most Unix systems have many components which need to run as
+root, even though most of their activity does not strictly require
+it.  This gives rise to a large and complex body of code which must be
+trusted with the security of the system.
+<p>
+
+If they were to use <prgn/userv/, many of these subsystems would no
+longer need any unusual privilege.  <p>
+
+<prgn/cron/ and <prgn/at/, <prgn/lpr/ and the system's mail transfer
+agent (<prgn/sendmail/, <prgn/smail/, <prgn/exim/ or the like) all
+fall into this category, though <prgn/userv/-based versions of these
+programs are not currently available.
+
+<sect id="noexcess">Do not give away excessive privilege to <prgn/userv/-using facilities
+<p>
+
+There is a danger that people reimplementing the facilities I mention
+above using <prgn/userv/ will discard much of the security benefit by
+using a naive implementation technique.  This will become clearer with
+an example:
+<p>
+
+Consider the <prgn/lpr/ program.  In current systems this needs to
+have an absolutely privileged component in order to support delayed
+printing without copying: when the user queues a file to be printed
+the filename is stored in the print queue, rather than a copy of it,
+and the printer daemon accesses the file directly when it is ready to
+print the job.  In order that the user can print files which are not
+world-readable the daemon is given root privilege so that it can open
+the file in the context of the user, rather than its own.
+<p>
+
+A simple-minded approach to converting this scheme to use <prgn/userv/
+might involve giving the printer daemon (the <prgn/lp/ user) the
+ability to read the file by allowing them to run <prgn/cat/ (or a
+special-purpose file-reading program) as any user.  The <prgn/lpr/
+program would use a <prgn/userv/ service to store the filename in the
+printer daemon's queues, and the daemon would read the file later when
+it felt like it.
+<p>
+
+However, this would allow the printer daemon to read any file on the
+system, whether or not someone had asked for it to be printed.  Since
+many files will contain passwords and other security-critical
+information this is nearly as bad as giving the daemon root access in
+the first place.  Any security holes in the print server which allow a
+user to execute commands as the <prgn/lp/ user will give the user the
+ability to read any file on the system.
+<p>
+
+Instead, it is necessary to keep a record of which files the daemon
+has been asked to print <em/outside/ the control of the print daemon.
+This record could be kept by a new root-privileged component, but this
+is not necessary: the record of which files a user has asked to be
+printed can be kept under the control of the user in question.  The
+submission program <prgn/lpr/ will make a record in an area under the
+user's control before communicating with the print server, and the
+print server would be given the ability to run a special file-reading
+program which would only allow files to be read which were listed in
+the user's file of things they'd asked to print.
+<p>
+
+Now security holes in most of the printing system do not critically
+affect the security of the entire system: they only allow the attacker
+to read and interfere with print jobs.  Bugs in the programs run by the
+print server to read users' files (and to remove entries from the list
+of files when it has done with them) will still be serious, but this
+program can be quite simple.
+<p>
+
+Similar considerations apply to many <prgn/userv/-based versions of
+facilities which currently run as root.
+<p>
+
+It is debatable whether the user-controlled state should be kept in
+the user's filespace (in dotfiles, say) or kept in a separate area set
+aside for the purpose; however, using the user's home directory (and
+possibly creating a separate subdirectory of it as a dotfile to
+contain subsystem state) has fewer implications for the rest of the
+system and makes it entirely clear where the security boundaries lie.
+
+<sect id="notreally"><prgn/userv/ can often replace <prgn/sudo/, but not <prgn/really/
+<p>
+
+<prgn/userv/ is not intended as a general-purpose system
+administration tool with which system administrators can execute
+arbitrary programs like text editors as root (or other system users)
+when they need to.  It is unsuitable for this purpose precisely
+because it enforces a strong separation between the calling and the
+called program, which is undesirable in this context.
+<p>
+
+However, its use when restricted to running particular programs in
+particular ways is very similar to many common uses of
+<prgn/sudo/<footnote><prgn/sudo/ is a program which allows users to
+execute certain programs as root, according to configuration files
+specified by the system administrator.</footnote>.  <prgn/userv/ is
+generally much better than restricted <prgn/sudo/, because it protects
+the called program much more strongly from bad environmental
+conditions set up by the caller.  Most programs that one might want to
+run via restricted <prgn/sudo/, have not been designed to run in a
+partially hostile environment.  <prgn/userv/ allows these programs to
+be run in a safer environment and should be used instead.
+
+<sect id="stdinerr">Error handling and input streams (eg stdin)
+<p>
+
+When the service program is reading from a file descriptor connected
+to the calling side, the fd that the service program refers to a pipe
+set up by <prgn/userv/ and not to the same object as was presented by
+the caller.
+<p>
+
+Therefore if there is some kind of error it is possible for the
+service-side fd to give premature end of file.  If it is important to
+tell whether all of the intended data has been received by the service
+program, the datastream must contain an explicit end-of-file
+indication of some kind.
+<p>
+
+For example, consider a <prgn/userv/ service for submitting a mail
+message, where message is supplied on the service's stdin.  However,
+if the calling process is interrupted before it has written all of the
+message, the service program will get EOF on the message data.  In a
+naive arrangement this would cause a half-complete message to be
+sent.  To prevent this, it is necessary to adopt some kind of explicit
+end indication; for example, the end of the message could be signalled
+by a dot on a line by itself, and dots doubled, as in SMTP.  Then the
+service program would know when the entire message had been received,
+and could avoid queueing incomplete messages.
+
+<sect id="nogeneral">Don't give access to general-purpose utilities
+<p>
+
+Do not specify general purpose programs like <prgn/mv/ or <prgn/cat/
+in <prgn/execute-/ directives without careful thought about their
+arguments, and certainly not if <prgn/no-suppress-args/ is specified.
+If you do so it will give the caller much more privilige than you
+probably intend.
+<p>
+
+It is a shame that I have to say this here, but inexperienced
+administrators have made similar mistakes with programs like
+<prgn/sudo/.
+
+</book>
diff --git a/spec.sgml.in b/spec.sgml.in
new file mode 100644 (file)
index 0000000..8d04194
--- /dev/null
@@ -0,0 +1,1426 @@
+<!doctype debiandoc system>
+
+<book>
+<title>User service daemon and client specification
+<author>Ian Jackson <email>ian@davenant.greenend.org.uk
+<version></version>
+
+<abstract>
+This is a specification for a Unix system facility to allow one
+program to invoke another when only limited trust exists
+between them.
+
+<copyright>
+<prgn/userv/ is
+Copyright 1996-2003,2006 Ian Jackson;
+Copyright 2000 Ben Harris.
+<p>
+
+<prgn/userv/ is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or (at
+your option) any later version.
+<p>
+
+This program is distributed in the hope that it will be useful, but
+<em/without any warranty/; without even the implied warranty of
+<em/merchantability/ or <em/fitness for a particular purpose/.  See
+the GNU General Public License for more details.
+<p>
+
+You should have received a copy of the GNU General Public License
+along with <prgn/userv/; if not, write to the Free Software
+Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+<toc sect>
+
+<chapt id="intro">Introduction
+<p>
+There is a daemon which invokes user service programs (henceforth
+`services') in response to requests by callers of a companion client
+program (henceforth the `client') and according to rules set forth in
+system-wide and user-specific configuration files.  The companion
+client program is setuid root, and negotiates with the daemon through
+an <prgn/AF_UNIX/ socket and associated objects in a system-wide
+private directory set aside for the purpose.  The user who wishes the
+service to be performed and calls the client is called the `calling
+user'; the process which calls the client is called the `calling
+process'.
+
+<p>
+The daemon and the client are responsible for ensuring that
+information is safely carried across the security boundary between the
+two users, and that the processes on either side cannot interact with
+each other in any unexpected ways.
+
+<chapt id="client">Client program usage
+
+<p>
+<example>
+userv <var/options/ [--] <var/service-user/ <var/service-name/ [<var/argument/ ...]
+userv <var/options/ -B|--builtin [--] <var/builtin-service/ [<var/info-argument/ ...]
+</example>
+<p>
+
+<var/service-user/ specifies which user is to provide the service.
+The user may be a login name or a numeric uid, or <tt/-/ to indicate
+that the service user is to be the same as the calling user.
+<p>
+
+The service name is interpreted by the userv<footnote><prgn/userv/ is
+short for `user services', and is pronounced `you-serve'.</footnote>
+daemon on behalf of the service user.  It will often be the name of a
+program.
+
+<sect>Options
+<p>
+
+Single-letter options may be combined as is usual with Unix programs,
+and the value for such an option may appear in the same argument or in
+the next.
+
+<taglist>
+<tag/<tt/-B//
+<tag/<tt/--builtin//
+<item>
+Requests that a builtin service be provided.  This is equivalent to
+using the <prgn/--override/ option to specify a string consisting of
+<prgn/execute-builtin/ followed by the <var/builtin-service/
+requested, and requesting a service user of <tt/-/ (indicating the
+calling user).
+<p>
+
+If the builtin service being requested requires a
+<var/service-argument/ then this must be supplied to the client in the
+same argument as the <var/builtin-service/.  See <ref
+id="dirs-execution"> for details of the builtin services available,
+and <ref id="optoverride"> for details of the <prgn/--override/
+options.
+<p>
+
+The actual service name passed will be the <var/builtin-service/; note
+that this actual service name (as opposed to the override data) and
+the <var/info-argument/s supplied will be ignored by most builtin
+services; the override mechanism and <prgn/execute-builtin/ will be
+used to ensure that the right builtin service is called with the right
+<var/service-argument/s.
+
+<tag/<tt/-f<var/fd/[<var/modifiers/]=<var/filename///
+<tag/<tt/--file <var/fd/[<var/modifiers/]=<var/filename///
+<item>
+Requests that data be copied in and out of the service using pipes.
+For each file or descriptor this will be done by creating a pipe, one
+end of which is passed to the service program and the other end of
+which is passed to a copy of <prgn/cat/ invoked by the client; the
+other file descriptor passed to <prgn/cat/ will be one inherited by
+the client program from the caller or one opened by the client program
+on behalf of the caller.
+<p>
+
+The descriptor in the service program that should be connected must be
+specified as <var/fd/, either as a decimal number or as one of the
+strings <tt/stdin/, <tt/stdout/ or <tt/stderr/.  The next argument is
+a filename which will be opened by the client with the privileges of
+the calling user.
+
+<p>
+<var/modifiers/ is used to specify whether the file or descriptor is
+to be read from or written to.  It consists of a series of words
+separated by commas.  A comma may separate the <var/modifiers/ from
+the <var/fd/ and is required if <var/fd/ is not numeric.
+
+<p>
+The modifier words are:
+<taglist compact>
+<tag/<tt/read//
+<item>
+<tt/O_RDONLY/: Allow reading and not writing.  May not be used with
+<tt/write/ or things that imply it.
+
+<tag/<tt/write//
+<item>
+<tt/O_WRONLY/: Allow writing and not reading.  <em/Doesn't truncate or
+create/ without <tt/truncate/ or <tt/create/.  <tt/write/ or things
+that imply it may not be used with <tt/read/.
+
+<tag/<tt/overwrite//
+<item>
+Equivalent to <tt/write,create,truncate/.
+
+<tag/<tt/create//
+<tag/<tt/creat//
+<item>
+<tt/O_CREAT/: Creates the file if necessary.  Implies <tt/write/.
+
+<tag/<tt/exclusive//
+<tag/<tt/excl//
+<item>
+<tt/O_EXCL/: Fails if the file already exists.  Implies <tt/write/ and
+<tt/create/.  May not be used with <tt/truncate/.
+
+<tag/<tt/truncate//
+<tag/<tt/trunc//
+<item>
+<tt/O_TRUNC/: Truncate any existing file.  Implies <tt/write/.
+May not be used with <tt/exclusive/.
+
+<tag/<tt/append//
+<item>
+<tt/O_APPEND/: All writes will append to the file.  Implies <tt/write/
+(but not <tt/create/).
+
+<tag/<tt/sync//
+<item>
+<tt/O_SYNC/: Do writes synchronously.  Implies <tt/write/.
+
+<tag/<tt/wait//
+<tag/<tt/nowait//
+<tag/<tt/close//
+<item>
+
+These modifiers control the behaviour of the client, with respect to
+the pipes carrying data to and from the service, when the service
+terminates.  See below.
+
+<tag/<tt/fd//
+<item>
+The <var/filename/ is not a filename but a numeric file descriptor.
+One or both of <tt/read/ and <tt/write/ must be specified, and no
+other words are allowed.  The <var/filename/ may also be <tt/stdin/,
+<tt/stdout/ or <tt/stderr/ for file descriptor 0, 1 or 2 respectively.
+
+</taglist>
+<p>
+
+If no <var/modifiers/ which imply <tt/read/ or <tt/write/ are used it
+is as if <tt/write/ had been specified, except that if the
+filedescriptor 0 of the service is being opened (either specified
+numerically or with <tt/stdin/) it is as if <tt/overwrite/ had been
+specified (or <tt/write/ if only <tt/fd/ was specified).
+<p>
+
+The client will also use <tt/O_NOCTTY/ when opening files specified by
+the caller, to avoid changing its controlling terminal.
+<p>
+
+By default stdin, stdout and stderr of the service will be connected
+to the corresponding descriptors on the client.  Diagnostics from
+the client and daemon will also appear on stderr.
+<p>
+
+If <tt/wait/ is specified, the client will wait for the pipe to be
+closed, and only exit after this has happened.  This means that either
+the receiving end of the pipe connection was closed while data was
+still available at the sending end, or that the end of file was
+reached on the reading file descriptor.  Errors encountered reading or
+writing in the client at this stage will be considered a system error
+and cause the client to exit with status 255, but will not cause
+disconnection at the service side since the service has already
+exited.
+<p>
+
+If <tt/close/ is specified the client will immediately close the pipe
+connection by killing the relevant copy of <prgn/cat/.  If the service
+uses the descriptor it will get <prgn/SIGPIPE/ (or <prgn/EPIPE/) for a
+writing descriptor or end of file for a reading one; the descriptor
+opened by or passed to the client will also be closed.
+<p>
+
+If <tt/nowait/ is specified then the client will not wait and the
+connection will remain open after the client terminates.  Data may
+continue to be passed between the inheritors of the relevant
+descriptor on the service side and the corresponding file or
+descriptor on the client side until either side closes their
+descriptor.  This should not usually be specified for stderr (or
+stdout if <tt/--signals stdout/ is used) since diagnostics from
+the service side may arrive after the client has exited and be
+confused with expected output.
+<p>
+
+The default is <tt/wait/ for writing file descriptors and <tt/close/
+for reading ones.
+
+<tag/<tt/-w<var/fd/=<var/action///
+<tag/<tt/--fdwait<var/fd/=<var/action///
+<item>
+Sets the action on termination of the service for the specified file
+descriptor; <var/action/ must be <tt/wait/, <tt/nowait/ or <tt/close/
+as described above.  The file descriptor must be specified as open
+when this option is encountered; this option is overridden by any
+later <prgn/--file/ or <prgn/--fdwait/ option - even by a
+<prgn/--file/ which does not specify an action on termination (in this
+case the default will be used, as described above).
+
+<tag/<tt/-D<var/name/=<var/value///
+<tag/<tt/--defvar <var/name/=<var/value///
+<item>
+Set a user-defined variable <var/name/ to <var/value/.  These
+user-defined variables are made available in the configuration
+language as the parameters <tt/u-<var/name// and are passed to the
+service in environment variables <tt/USERV_U_<var/name//.  <var/name/
+may contain only alphanumerics and underscores, and must start with a
+letter.  If several definitions are given for the same <var/name/ then
+only the last is effective.
+
+<tag/<tt/-t <var/seconds///
+<tag/<tt/--timeout <var/seconds///
+<item>
+Time out the service if it takes longer than <var/seconds/ seconds (a
+positive integer, in decimal).  Timeout will produce a diagnostic on
+stderr and an exit status of 255.  If <var/seconds/ is zero then no
+timeout will be implemented (this is the default).
+
+<tag/<tt/-S/ <var/method//
+<tag/<tt/--signals/ <var/method//
+<item>
+Affects the handling of the exit status when the service terminates
+due to a signal.  (The client will always finish by calling
+<prgn/_exit/, so that only numbers from 0 to 255 can be returned and
+not the full range of numbers and signal indications which can be
+returned by the <prgn/wait/ family of system calls.)
+<p>
+
+The <var/method/ may be one of the following:
+<taglist compact>
+<tag/<var/status/
+<item>
+The client's exit status will be <var/status/.  This will not be
+distinguishable from the service really having exited with code
+<var/status/.  This method is the default, with a <var/status/ of 254.
+
+<tag/<tt/number//
+<tag/<tt/number-nocore//
+<item>
+The client's exit status will be the number of the signal which caused
+the termination of the service.  If <tt/number/ is used rather than
+<tt/number-nocore/ then 128 will be added if the service dumped core.
+<tt/number/ is very like the exit code mangling done by the Bourne
+shell.
+
+<tag/<tt/highbit//
+<item>The client's exit status will be the number of the signal with
+128 added.  If the service exits normally with an exit code of greater
+than 127 then 127 will be returned.
+
+<tag/<tt/stdout//
+<item>
+The service's numeric wait status as two decimal numbers (high byte
+first) and a textual description of its meaning will be printed to the
+client's standard output.  It will be preceded by a newline and
+followed by an extra newline, and the numbers are separated from each
+other and from the textual description by single spaces.  The exit
+status of the client will be zero, unless a system error occurs in
+which case no exit status and description will be printed to stdout,
+and an error message will be printed to stderr as usual.
+</taglist>
+
+<p>
+Problems such as client usage errors, the service not being found or
+permission being denied or failure of a system call are system errors.
+An error message describing the problem will be printed on the
+client's stderr, and the client's exit status will be 255.  If the
+client dies due to a signal this should be treated as a serious system
+error.
+
+<tag/<tt/-H//
+<tag/<tt/--hidecwd//
+<item>
+Prevents the calling process's current directory name from being
+passed to the service; the null string will be passed instead.
+
+<tag/<tt/-P//
+<tag/<tt/--sigpipe//
+<item>
+If the service program is terminated due to a <prgn/SIGPIPE/ the exit
+status of the client will be zero, even if it would have been
+something else according to the exit status method specified.  This
+option has no effect on the code and description printed if the exit
+status method <tt/stdout/ is in use.
+
+<tag/<tt/-h//
+<tag/<tt/--help//
+<tag/<tt/--copyright//
+<item>
+<tt/-h/ or <tt/--help/ prints the client's usage message;
+<tt/--copyright/ prints the copyright and lack of warranty notice.
+
+</taglist>
+
+<sect id="optoverride">Security-overriding options
+<p>
+
+There are also some options which are available for debugging and to
+allow the system administrator to override a user's policy.  These
+options are available only if the client is called by root or if the
+calling user is the same as the service user.
+
+<taglist>
+
+<tag/<tt/--override <var/configuration-data///
+<tag/<tt/--override-file <var/filename///
+<item>
+Do not read the usual configuration files.  Instead, the client sends
+<var/configuration-data/ (followed by a newline) or the contents of
+<var/filename/ (which is opened in the context of the client) to the
+daemon and the daemon uses that data instead.  The
+<var/configuration-data/ must all be in one argument.  It will have a
+single newline appended so that a single directive can easily be
+given, but if more than one directive is required it will have to
+contain one or more real newlines.
+
+<tag/<tt/--spoof-user <var/user///
+<item>
+Pretend to the service that it is being called by <var/user/ (which
+may be a username or a uid).  This will also affect the group and
+supplementary groups supplied to the service; they will be the
+standard group and supplementary groups for <var/user/.  The
+<tt/--spoof-user/ option will <em/not/ affect which user is chosen if
+the service user is specified as just <tt/-/; in this case the service
+user will be the real calling user.
+
+</taglist>
+
+
+<chapt id="envir">Execution environment of the service program
+<p>
+
+The daemon which is handling the service user side of things will read
+configuration files to decide what to do.  If it decides to allow the
+service to be provided it will fork a subprocess to execute the
+service.
+<p>
+
+The service will have no controlling terminal, but it will be a
+process group leader.
+<p>
+
+If the client is killed or times out or a file or descriptor being
+read or written by the client process gets an error then the service
+will be disconnected from the client.  The client will return an exit
+status of 255 and some the service's pipes may be closed at the other
+end.  The service will become a child of <prgn/init/.  The service may
+well not notice the disconnection, though writing to a pipe after this
+may produce a <prgn/SIGPIPE/ and the facility exists to have a
+<prgn/SIGHUP/ sent to the service on disconnection.
+
+<sect>File descriptors
+<p>
+
+The service program's standard filedescriptors, and possibly other
+file descriptors, will be connected to pipes or to
+<prgn>/dev/null</>.  The <prgn/userv/ client/daemon pair will arrange
+that data is copied between the files or file descriptors specified to
+to the client by the caller and these these pipes.
+<p>
+
+Pipes which may be written to will be closed if a write error occurs
+on the corresponding client-side file or descriptor, which may result
+in a <prgn/SIGPIPE/ in the service program; pipes open for reading
+will get <prgn/EOF/ if the client-side file descriptor gets <prgn/EOF/
+or an error.
+<p>
+
+If the service closes one of its reading file descriptors the writing
+end of the corresponding pipe will generate a <prgn/SIGPIPE/ when
+attempts are made by the client/daemon pair to write to it.  This will
+not be considered an error; rather, the relevant pipe will be
+discarded and the corresponding file or file descriptor held by the
+client will be closed.
+<p>
+
+Likewise, if one of the file descriptors held by the client for
+writing by the service is a pipe whose other end is closed by the
+caller then the client/daemon pair will see an error when trying to
+copy data provided by the service.  This too will not be considered an
+error; rather, the pipe correspondong to that descriptor will be
+closed and any further writes will cause the service to get a
+<prgn/SIGPIPE/.
+<p>
+
+Note that not all write errors or broken pipes on file descriptors may
+be visible to the service, since buffered data may be discarded by the
+operating system and there will be a finite interval between the error
+happening and the service being disconnected from the client or the
+next write causing a <prgn/SIGPIPE/.
+<p>
+
+Read errors on file descriptors (and disconnection) will only be
+visible to the service and distinguishable from normal end of file if
+<prgn/disconnect-hup/ is in effect.
+<p>
+
+Read and write errors (other than broken pipes, as described above)
+will always be visible to the caller; they are system errors, and will
+therefore cause the client to print an error message to stderr and
+return with an exit status of 255.
+<p>
+
+If the main service program process exits while it still has running
+children any file descriptors held by those children can remain open,
+depending on the use of <tt/wait/, <tt/nowait/ or <tt/close/ for the
+relevant file descriptor in the client's arguments.  By default
+writing filedescriptors remain open and the client will wait for them
+to be closed at the service end, and reading file descriptors are
+closed immediately.  These leftover child processes will not get a any
+<prgn/SIGHUP/ even if a read or write error occurs or the client
+disconnects before then.
+
+<sect>Environment
+<p>
+
+The service will have some information in environment variables:
+<taglist compact>
+<tag/<tt/USERV_USER//
+<item>
+The login name of the calling user.  If the <prgn/LOGNAME/ variable is
+set (or, if that is unset, if the <prgn/USER/ variable is set) in the
+environment passed to the client by the caller then the password entry
+for that login name will be looked up; if that password entry's uid is
+the same as that of the calling process then that login name will be
+used, otherwise (or if neither <prgn/LOGNAME/ nor <prgn/USER/ is set)
+the calling process's uid will be looked up to determine their login
+name (and if this lookup fails then the service will not be invoked).
+
+<tag/<tt/USERV_UID//
+<item>
+The uid of the calling process.
+
+<tag/<tt/USERV_GID//
+<item>
+The gid and supplementary group list of the calling process: first the
+group in gid and then those in the supplementary group list, in
+decimal, separated by spaces.
+
+<tag/<tt/USERV_GROUP//
+<item>
+The group names of the calling process, listed in the same way as the
+ids are in <prgn/USERV_GID/.  If no name can be found for any of the
+calling process's group(s) then the service will not be invoked.
+
+<tag/<tt/USERV_CWD//
+<item>
+The client's current working directory name (this directory may not be
+accessible to the service).  If it could not be determined or the
+<prgn/--hidecwd/ flag was used then this variable will be set to an
+empty string (this is not considered an error).
+
+<tag/<tt/USERV_SERVICE//
+<item>
+The service name requested by the caller.
+
+<tag/<tt/USERV_U_<var/name///
+<item>
+The value supplied to the client by the caller using -D<var/name/.
+
+</taglist>
+
+<prgn/HOME/, <prgn/PATH/, <prgn/SHELL/, <prgn/LOGNAME/ and <prgn/USER/
+will be set appropriately (according to the details of the service
+user).
+
+
+<chapt id="config">Service-side configuration
+<p>
+
+Which services may be run by whom and under what conditions is
+controlled by configuration files.
+<p>
+
+The daemon will read these files in order.  Certain directives in the
+files modify the daemon's execution settings for invoking the service,
+for example allowing certain file descriptors to be specified by the
+client or specifying which program to execute to provide the service.
+<p>
+
+The <em/last/ instance of each such setting will take effect.  The
+directives which specify which program to execute will not stop the
+configuration file from being read; they will be remembered and will
+only take effect if they are not overridden by a later directive.
+<p>
+
+The daemon will first read <tt>/etc/userv/system.default</>.  Then, by
+default (this behaviour may be modified), it will read a per-user file
+<tt>~/.userv/rc</>, if it exists and the service user's shell is in
+<tt>/etc/shells</>.  Finally it will read
+<tt>/etc/userv/system.override</>.
+<p>
+
+When it has read all of these files it will act according to the
+currently values of of the execution settings.
+
+<sect>Configuration file syntax
+<p>
+
+The configuration file is a series of directives, usually one per
+line.  The portion of a line following a hash character <tt/#/ is
+taken as a comment and ignored.  Each directive consists of a series
+of tokens separated by linear whitespace (spaces and tabs); tokens may
+be words consisting of non-space characters, or, where a string is
+required, a string in double quotes.  Double-quoted strings may
+contain the following backslash escapes:
+
+<taglist compact>
+<tag/<tt/\n//<item>newline
+<tag/<tt/\t//<item>tab
+<tag/<tt/\r//<item>carriage return
+<tag/<tt/\<var/OOO///<item>character whose octal code is <var/OOO/
+<tag/<tt/\x<var/XX///<item>character whose hex code is <var/XX/
+<tag/<tt/\<var/punctuation///<item>literal punctuation character (eg <tt/\\/, <tt/\"/)
+<tag/<tt/\<var/newline// (ie, backslash at end of line)/
+<item>string continues on next line
+</taglist>
+<p>
+
+Relative pathnames in directives are relative to the service program's
+current directory (usually the service user's home directory).
+Pathnames starting with the two characters <tt>~/</> are taken to be
+relative to the service user's home directory.
+
+<sect id="directives">Configuration file directives
+<p>
+
+<sect1 id="dirs-immediate">Immediate directives
+<p>
+
+The following directives take effect immediately:
+
+<taglist>
+<tag/<tt/cd <var/pathname///
+<item>
+Change directory in the service program.  <prgn/cd/ is cumulative.  It
+is an error if the directory cannot be changed to.
+<p>
+
+<prgn/cd/ should not be used between <prgn/execute-from-directory/ and
+the invocation of the service program, as the test for the
+availability of the service program would be done with the old current
+directory and the actual execution with the new (probably causing an
+error).
+
+<tag/<tt/eof//
+<item>
+Stop reading the configuration file in question, as if end of file had
+been reached.  Any control constructs (<prgn/if/, <prgn/catch-quit/ or
+<prgn/errors-push/) which were started in that file will be considered
+finished.  Parsing will continue in the file which caused the file
+containing the <prgn/eof/ to be read.
+
+<tag/<tt/quit//
+<item>
+Stop reading configuration files and act immediately on the current
+settings.  The behaviour of <prgn/quit/ is subject to the
+<prgn/catch-quit/ control construct.
+
+<tag/<tt/include <var/filename///
+<tag/<tt/include-ifexist <var/filename///
+<item>
+Read the configuration file <var/filename/, and then return to this
+file and continue parsing it with the next directive.  It is an error
+if the file cannot be opened and read, unless <prgn/include-ifexist/
+is used and the file does not exist, in which case the directive is
+silently ignored.
+
+<tag/<tt/include-lookup <var/parameter/ <var/directory///
+<tag/<tt/include-lookup-all <var/parameter/ <var/directory///
+<item>
+Read the configuration file in <var/directory/ whose name is the value
+of <var/parameter/ (see the description of <prgn/if/, <ref
+id="dirs-control">).  If <var/parameter/ has several values they will
+be tried in order; with <prgn/include-lookup/ this search will stop
+when one is found, but with <prgn/include-lookup-all/ the search will
+continue and any files appropriate to other values will be read too.
+<p>
+
+If none of the parameter's values had a corresponding file then the
+file <tt/:default/ will be read, if it exists.  If <var/parameter/'s
+list of values was empty then the file <tt/:none/ will be tried first
+and read if it exists, otherwise <tt/:default/ will be tried.
+<p>
+
+It is not an error for any of the files (including <tt/:default/) not
+to exist, but it is an error if a file exists and cannot be read or if
+the directory cannot be accessed.
+
+<p>
+A translation will be applied to values before they are used to
+construct a filename, so that the lookup cannot access dotfiles or
+files in other directories: values starting with full stops will have
+a colon prepended (making <tt/:./), colons will be doubled, and each
+slash will be replaced with a colon followed by a hyphen <tt>:-</>.  A
+parameter value which is the empty string will be replaced with
+<tt/:empty/ (note that this is different from a parameter not having
+any values).
+
+<tag/<tt/include-directory <var/directory///
+<item>
+Read configuration from all files in directory <var/directory/ which
+are plain files whose names consist only of alphanumerics and hyphens
+and start with an alphanumeric.  They will be read in lexical order.
+It is an error for the directory not to exist or for it or any of the
+files found not to be read successfully, or for anything with an
+appropriate name not to be a plain file or a symbolic link to a plain
+file.
+
+<tag/<tt/error <var/text ...///
+<item>
+Causes an error whose message includes the descriptive string
+<var/text/.  <var/text/ may consist of several tokens with intervening
+whitespace.  The whitespace will be included in the message as found
+in the configuration file: all the characters until the end of the
+line will be included verbatim, unless they are part of a
+double-quoted string, in which case the usual meaning of the string
+(i.e., after backslash escape processing) will be used.  Comments and
+linear whitespace at the end of the line (or just before the comment)
+will still be ignored.
+
+<tag/<tt/message <var/text ...///
+<item>
+Causes a message including the descriptive string <var/text/ to be
+delivered as if it were an error message, but does not actually cause
+an error.
+</taglist>
+
+<sect1 id="dirs-delayed">Directives with delayed effect
+<p>
+
+The following directives have no immediate effect, but are remembered
+and have an effect on later processing of the configuration files.
+
+<taglist>
+<tag/<tt/user-rcfile <var/filename///
+<item>
+Specifies that the file <var/filename/ should be read instead of the
+user's <tt>~/.userv/rc</>.  This does <em/not/ happen immediately;
+instead, the setting is remembered and used after the
+<prgn/system.default/ configuration file has been read.  This
+directive has no effect in a user's configuration file or in the
+<prgn/system.override/ file, as the user's configuration file has
+already been found and read by then and will not be re-read.
+
+<tag/<tt/errors-to-stderr//
+<item>
+Causes error messages to be delivered to the client's stderr.
+
+<tag/<tt/errors-to-file/ <var/filename//
+<item>
+Error messages will be written to <var/filename/, which will be opened
+in the context of and with the privileges of the service user.
+
+<tag/<tt/errors-to-syslog/ [<var/facility/ [<var/level/]]/
+<item>
+Error messages will be delivered using <prgn/syslog/.  The default
+<var/facility/ is <tt/user/; the default <var/level/ is <tt/error/.
+</taglist>
+
+<sect1 id="dirs-control">Control structure directives
+<p>
+
+The following directives are used to create control structures.  If
+the end of the file is encountered before the end of any control
+structure which was started inside it then that control structure is
+considered finished.  This is not an error.
+
+<taglist>
+<tag/<tt/if <var/condition///
+<tag/<tt/elif <var/condition///
+<tag/<tt/else//
+<tag/<tt/fi//
+<item>
+Lines following <prgn/if/ are interpreted only if the condition is
+true.  Many conditions are properties of parameter values.  Most
+parameters have a single string as a value; however, some may yield
+zero or several strings, in which case the condition is true if it is
+true of any of the strings individually.  Parameters are described
+below.
+<p>
+
+The conditions are:
+
+<taglist compact>
+<tag/<tt/glob <var/parameter/ <var/glob-pattern/ ...//
+<item>
+The value of the parameter whose name is given matches one of the glob
+patterns (anchored at both ends; backslashes can be used to escape
+metacharacters).
+
+<tag/<tt/range <var/parameter/ <var/min/ <var/max///
+<item>
+The value of the parameter is a nonnegative integer and lies within
+the range specified.  <var/min/ or <var/max/ may be <tt/$/ to indicate
+no lower or upper limit, respectively.
+
+<tag/<tt/grep <var/parameter/ <var/filename///
+<item>
+The <var/filename/ refers to a file one of whose lines is the value of
+the parameter (leading or trailing whitespace on each line and empty
+lines in the file are ignored).  It is an error for the file not to be
+opened and read.
+
+<tag/<tt/! <var/condition///
+<item>
+The <var/condition/ is <em/not/ true.
+
+<tag/Conjunctions: <tt/&amp;/ and <tt/|//
+<item>
+<example>
+( <var/condition/
+&amp; <var/condition/
+&amp; <var/condition/
+...
+)
+</example>
+is true if all the listed conditions are true; where <tt/|/ is used it
+is true if any of them is true.  Newlines must be used to separate one
+condition from the next, as shown, and the parentheses are mandatory.
+These conjunctions do not do lazy evaluation.
+</taglist>
+<p>
+
+The parameters are:
+
+<taglist compact>
+<tag/<tt/service//
+<item>
+The service name specified when the client was called.
+
+<tag/<tt/calling-user//
+<item>
+Two strings: the login name of the calling user (determined as for
+<prgn/USERV_USER/, above) and the calling uid (represented in
+decimal).
+
+<tag/<tt/calling-group//
+<item>
+Several strings: the primary and supplementary group names and gids
+(in decimal) of the calling process.  All the group names come first,
+and then the gids.  If the first supplementary group is the same as
+the primary group then it is elided.
+
+<tag/<tt/calling-user-shell//
+<item>
+The calling user's shell, as listed in the password entry for the
+calling login name (as determined for <prgn/USERV_USER/, above).
+
+<tag/<tt/service-user//
+<item>
+Two strings: the name of the service user (as specified to the client)
+and their uid (represented in decimal).
+
+<tag/<tt/service-group//
+<item>
+Several strings: the primary and supplementary group names and gids
+(in decimal) of the service user.
+
+<tag/<tt/service-user-shell//
+<item>
+The service user's shell, as listed in their password entry.
+
+<tag/<tt/u-<var/name///
+<item>
+The value of the user-defined variable <var/name/ passed by the caller
+using the <prgn/--defvar/ command-line option to the client.  If the
+variable was not defined then this parameter is an empty list of
+strings; in this case any condition which tests it will be false, and
+<tt/include-lookup/ on it will read the <tt/:none/ file, or
+<tt/:default/ if <tt/:none/ is not found.
+
+</taglist>
+
+<tag/<tt/errors-push/ <var/filename//
+<tag/<tt/srorre//
+<item>
+Stacks the error handling behaviour currently in effect.  Any changes
+to error handling will take effect only between <prgn/errors-push/ and
+<prgn/srorre/.
+
+<tag/<tt/catch-quit//
+<tag/<tt/hctac//
+<item>
+Any use of <prgn/quit/ inside <prgn/catch-quit/ will merely cause the
+parsing to continue at <prgn/hctac/ instead.  Any control constructs
+started since the <prgn/catch-quit/ will be considered finished if a
+<prgn/quit/ is found.
+<p>
+
+If an error occurs inside <prgn/catch-quit/ the execution settings
+will be reset (as if by the <prgn/reset/ directive) and parsing will
+likewise continue at <prgn/hctac/.
+<p>
+
+If a lexical or syntax error is detected in the same configuration
+file as the <prgn/catch-quit/, while looking for the <prgn/hctac/
+after an error or <prgn/quit/, that new error will not be caught.
+
+</taglist>
+
+<sect1 id="dirs-execution">Directives for changing execution settings
+<p>
+
+The following directives modify the execution settings; the server
+will remember the fact that the directive was encountered and act on
+it only after all the configuration has been parsed.  The <em/last/
+directive which modifies any particuar setting will take effect.
+
+<taglist>
+<tag/<tt/reject//
+<item>
+Reject the request.  <prgn/execute/, <prgn/execute-from-directory/ and
+<prgn/execute-from-path/ will change this setting.
+
+<tag/<tt/execute <var/program/ [<var/argument/ ...]//
+<item>
+Execute the program <var/program/, with the arguments as specified,
+followed by any arguments given to the client if
+<prgn/no-suppress-args/ is in effect.  It is an error for the
+execution to fail when it is attempted (after all the configuration
+has been parsed).  If <var/program/ does not contain a slash it will
+be searched for on the service user's path.
+
+<tag/<tt/execute-from-directory <var/pathname/ [<var/argument/ ...]//
+<item>
+Take all the characters after the last slash of the service name
+specified when the client was called, and execute that program in the
+directory named by <var/pathname/ as if it had been specified for
+<var/execute/.  The part of the service name used may contain only
+alphanumerics and hyphens and must start with an alphanumeric (and it
+must be non-empty), otherwise it is an error.
+<p>
+
+This directive is ignored if the relevant program does not exist in
+the directory specified; in this case the program to execute is left
+at its previous setting (or unset, if it was not set before).
+<p>
+
+It is an error for the test for the existence of the program to fail
+other than with a `no such file or directory' indication.  It is also
+an error for the execution to fail if and when it is attempted (after
+all the configuration has been parsed).
+
+<tag/<tt/execute-from-path//
+<item>
+<var/service/ is interpreted as a program on the default <prgn/PATH/
+(or as a pathname of an executable, if it contains a <tt>/</>).  This
+directive is <em/very dangerous/, and is only provided to make the
+<prgn/--override/ options effective.  It should not normally be used.
+It is an error for the execution to fail when it is attempted (after
+all the configuration has been parsed).
+
+<tag/<tt/execute-builtin <var/service-name/ <var/service-arguments//
+<item>
+Executes the builtin service <var/service-name/.  These builtin
+services display information about the server and/or the request, and
+ignore any arguments passed from the service side except possibly to
+print them as part of their output.  They write their results to their
+standard output (i.e., wherever file descriptor 1 is directed).  The
+builtin services are:
+
+<taglist compact>
+<tag/<tt/execute//
+<item>
+Displays the execution settings, defined variables,
+arguments, etc. with which the builtin service was invoked.
+
+<tag/<tt/environment//
+<item>
+Displays the environment variable settings with which the builtin
+service was invoked.
+
+<tag/<tt/parameter <var/parameter///
+<item>
+Displays the values of the service configuration language parameter
+specified.
+
+<tag/<tt/version//
+<item>
+Displays the version string and compilation details of the uservd
+server program.
+
+<tag/<tt/reset//
+<item>
+Displays the default reset configuration (evaluated when <prgn/reset/
+is found in a configuration file, or when an error is caught by
+<prgn/catch-quit/).
+
+<tag/<tt/toplevel//
+<item>
+Displays the top-level default configuration (the configuration data,
+evaluated by the server, which calls all the other configuration
+files).
+
+<tag/<tt/override//
+<item>
+Displays the top-level override configuration (the configuration data,
+evaluated by the server, which causes all the other configuration data
+to be parsed).
+
+<tag/<tt/help//
+<item>
+Displays a list of the understood builtin service names and arguments.
+</taglist>
+
+In the future other builtin services may be defined which do more than
+just print information.
+
+<tag/<tt/set-environment//
+<tag/<tt/no-set-environment//
+<item>
+Runs <tt>/etc/environment</> to set the service user's environment.
+This adds the overhead of invoking a shell, but doesn't cause any
+shell (de)mangling of the service's arguments.  This is achieved by
+invoking
+<example>
+.../program arg arg arg ...
+</example>
+as
+<example>
+/bin/sh -c '. /etc/environment; exec "$@"' - .../program arg arg arg ...
+</example>
+<prgn/no-set-environment/ cancels the effect of
+<prgn/set-environment/.
+
+<tag/<tt/no-suppress-args//
+<tag/<tt/suppress-args//
+<item>
+Include any arguments given to the client as arguments to the program
+invoked as a result of an <prgn/execute/,
+<prgn/execute-from-directory/ or <prgn/execute-from-path/ directive.
+<prgn/suppress-args/ undoes the effect of <prgn/no-suppress-args/.
+
+<tag/<tt/require-fd <var/fd-range/ read|write//
+<item>
+Insist that the filedescriptor(s) be opened for reading resp. writing.
+It is an error if any descriptor marked as required when the service
+is about to be invoked (after the configuration has been parsed) was
+not specified when the client was invoked.  Each file descriptor has a
+separate setting, and the last one of <prgn/require-fd/,
+<prgn/allow-fd/, <prgn/ignore-fd/, <prgn/null-fd/ or <prgn/reject-fd/
+which affected a particular file descriptor will take effect.
+<p>
+
+<var/fd-range/ may be a single number, two numbers separated by a
+hyphen, or one number followed by a hyphen (indicating all descriptors
+from that number onwards).  It may also be one of the words
+<tt/stdin/, <tt/stdout/ or <tt/stderr/.  Open-ended file descriptor
+rangers are allowed only with <prgn/reject-fd/ and <prgn/ignore-fd/,
+as otherwise the service program would find itself with a very large
+number of file descriptors open.
+<p>
+
+When the configuration has been parsed, and before the service is
+about to be executed, stderr (fd 2) must be required or allowed
+(<prgn/require-fd/ or <prgn/allow-fd/) for writing; this is so that
+the error message printed by the server's child process if it cannot
+<prgn/exec/ the service program is not lost.
+
+<tag/<tt/allow-fd <var/fd-range/ [read|write]//
+<item>
+Allow the descriptor(s) to be opened for reading resp. writing, or
+either if neither <tt/read/ nor <tt/write/ is specified.  If a
+particular descriptor not specified by the client then it will be open
+onto <tt>/dev/null</> (for reading, writing, or both, depending on
+whether <tt/read/, <tt/write/ or neither was specified).
+
+<tag/<tt/null-fd <var/fd-range/ [read|write]//
+<item>
+Specify that the descriptor(s) be opened onto <prgn>/dev/null</> for
+reading resp. writing, or both if neither <tt/read/ nor <tt/write/
+is specified.  Any specification of these file descriptors by the
+client will be silently ignored; the client will see its ends of the
+descriptors being closed immediately.
+
+<tag/<tt/reject-fd <var/fd-range///
+<item>
+Do not allow the descriptor(s) to be specified by the client.  It is
+an error if any descriptor(s) marked for rejection are specified when
+the service is about to be invoked (after the configuration has been
+parsed).
+
+<tag/<tt/ignore-fd <var/fd-range///
+<item>
+Silently ignore any specification by the client of those
+descriptor(s).  The pipes corresponding to these descriptors will be
+closed just before the service is invoked.
+
+<tag/<tt/disconnect-hup//
+<tag/<tt/no-disconnect-hup//
+<item>
+Causes the service's process group to get a <prgn/SIGHUP/ if the
+client disconnects before the main service process terminates.
+<prgn/no-disconnect-hup/ cancels <prgn/disconnect-hup/.
+<p>
+
+If one of the reading descriptors specified when the client is called
+gets a read error, or if the service is disconnected for some other
+reason, then the <prgn/SIGHUP/ will be delivered <em/before/ the
+writing end(s) of the service's reading pipe(s) are closed, so that
+the client can distinguish disconnection from reading EOF on a pipe.
+
+<tag/<tt/reset//
+<item>
+Resets the execution settings to the default.  This is equivalent to:
+<example>
+cd ~/
+reject
+no-set-environment
+suppress-args
+allow-fd 0 read
+allow-fd 1-2 write
+reject-fd 3-
+disconnect-hup
+</example>
+
+</taglist>
+
+If no <prgn/execute/, <prgn/execute-from-path/,
+<prgn/execute-from-directory/ or <prgn/builtin/ is interpreted before
+all the files are read then the request is rejected.
+
+
+<sect id="configerrors">Errors in the configuration file
+<p>
+
+If a syntax error or other problem occurs when processing a
+configuration file then a diagnostic will be issued, to wherever the
+error messages are currently being sent (see the <prgn/errors-/ family
+of directives, above).
+<p>
+
+The error will cause processing of the configuration files to cease at
+that point, unless the error was inside a <prgn/catch-quit/ construct.
+In this case the settings controlling the program's execution will be
+reset to the defaults as if a <prgn/reset/ directive had been issued,
+and parsing continues after <prgn/hctac/.
+
+
+<sect id="defaults">Defaults
+<p>
+
+The default configuration processing is as if the daemon were parsing
+an overall configuration file whose contents were as follows:
+
+<example>
+reset
+user-rcfile ~/.userv/rc
+errors-to-stderr
+include /etc/userv/system.default
+if grep service-user-shell /etc/shells
+   errors-push
+     catch-quit
+       include-ifexist <var/file specified by most recent user-rcfile directive/
+     hctac
+   srorre
+fi
+include /etc/userv/system.override
+quit
+</example>
+<p>
+
+If one of the <prgn/--override/ options to the client is used then it
+will instead be as if the daemon were parsing an overall configuration
+as follows:
+
+<example>
+reset
+errors-to-stderr
+include <var/file containing configuration data sent by client/
+quit
+</example>
+
+
+<chapt id="ipass">Information passed through the client/daemon combination
+<p>
+
+The information described below is the only information which passes
+between the caller and the service.
+
+<list>
+<item>
+The service name supplied by the caller is available in the
+configuration language for deciding whether and which service program
+to invoke, in the <prgn/service/ parameter, and is used by the
+<prgn/execute-from-directory/ and <prgn/execute-from-path/
+configuration directives.  It is usually used to select which service
+program to invoke.  It is also passed to the service program in the
+<prgn/USERV_SERVICE/ environment variable.
+
+<item>
+File descriptors specified by the client and allowed according to the
+configuration language will be connected.  Each file descriptor is
+opened for reading or writing.  Communication is via pipes, one end of
+each pipe being open on the appropriate file descriptor in the service
+program (when it is invoked) and the other end being held by the
+client process, which will read and write files it opens on behalf of
+its caller or file descriptors it is passed by its caller.
+<p>
+
+Data may be passed into the service through reading pipes and out of
+it through writing pipes.  These pipes can remain open only until the
+service and client have terminated, or can be made to stay open after
+the client has terminated and (if the service program forks) the main
+service process has exited; the behaviour is controlled by options
+passed to the client by its caller.
+<p>
+
+The caller can arrange that a writing pipe be connected to a pipe or
+similar object and cause attempts to write to that descriptor by the
+service to generate a <prgn/SIGPIPE/ (or <prgn/EPIPE/ if
+<prgn/SIGPIPE/ is caught or ignored) in the service.
+<p>
+
+Likewise, the service can close filedescriptors specified for reading,
+which will cause the corresponding filedescriptors passed by the
+caller to be closed, so that if these are pipes processes which write
+to them will receive <prgn/SIGPIPE/ or <prgn/EPIPE/.
+
+<item>
+If <prgn/no-suppress-args/ is set then arguments passed to the client
+by its caller will be passed on, verbatim, to the service.
+
+<item>
+Fatal signals and system call failures experienced by the client will
+result in the disconnection of the service from the client and
+possibly some of the communication file descriptors described above;
+if <prgn/disconnect-hup/ is set then the service will also be sent a
+<prgn/SIGHUP/.
+
+<item>
+The value of the <prgn/LOGNAME/ (or <prgn/USER/) environment variable
+as passed to the client will be used as the login name of the calling
+user if the uid of the calling process matches the uid corresponding
+to that login name.  Otherwise the calling uid's password entry will
+be used to determine the calling user's login name.
+<p>
+
+This login name and the calling uid are available in the configuration
+language in the <prgn/calling-user/ parameter and are passed to the
+service program in environment variables <prgn/USERV_USER/ and
+<prgn/USERV_UID/.
+<p>
+
+The shell corresponding to that login name (according to the password
+entry) is available as in the configuration language's
+<prgn/calling-user-shell/ parameter.
+<p>
+
+If no relevant password entry can be found then no service will be
+invoked.
+
+<item>
+The numeric values and textual names for calling gid and supplementary
+group list are available in the configuration language in the
+<prgn/calling-group/ parameter and are passed to the service in
+environment variables.
+<p>
+
+If no name can be found for a numeric group to which the calling
+process belongs then no service will be invoked.
+
+<item>
+The name of the current working directory in which the client was
+invoked is passed, if available and not hidden using <prgn/--hidecwd/,
+to the service program in the <prgn/USERV_CWD/ variable.  This grants no
+special access to that directory unless it is a subdirectory of a
+directory which is executable (searchable) but not readable by the
+service user.
+
+<item>
+Settings specified by the caller using the <tt/--defvar
+<var/name/=<var/value// option to the client are available in the
+configuration language as the corresponding <tt/u-<var/name//
+parameters and are passed to the service program in environment
+variables <tt/USERV_U_<var/name//.
+
+<item>
+If the calling user is root or the same as the service user then
+options may be given to the client which bypass the usual security
+features; in this case other information may pass between the caller
+and the service.
+
+</list>
+
+<chapt id="notes">Applications and notes on use
+<p>
+
+<sect id="examples">Examples
+<p>
+
+The companion package, <prgn/userv-utils/, contains a selection of
+example services, some of which are useful tools in their own right.
+See the <prgn/README/ in its top-level directory for details.
+
+<sect id="standards">Standard services and directory management
+<p>
+
+In later versions of this specification standard service names and
+interfaces for common services such as mail delivery and WWW CGI
+scripts may be specified.
+<p>
+
+<prgn/userv/-using applications and system services which hide
+<prgn/userv/ behind wrapper scripts may need to store information in
+the user's filespace to preserve the correct placement of the security
+perimiters.  Such applications should usually do so in a directory
+(created by them) <tt>~/.userv/<var/service/</>, where <var/service/
+is the service name or application in question.
+<p>
+
+If desired, a dot-directory inside <tt>~/.userv</> may be used to
+avoid the user becoming confused by finding parts of a semi-privileged
+application's internal state in their filespace, and/or discourage
+them from fiddling with and thus corrupting it.
+<p>
+
+However, <prgn/userv/ applications should of course not rely for their
+global integrity and security on the integrity of the data on the
+user's side of the security boundary.
+
+<sect id="reducepriv">Reducing the number of absolutely privileged subsystems
+<p>
+
+Currently most Unix systems have many components which need to run as
+root, even though most of their activity does not strictly require
+it.  This gives rise to a large and complex body of code which must be
+trusted with the security of the system.
+<p>
+
+If they were to use <prgn/userv/, many of these subsystems would no
+longer need any unusual privilege.  <p>
+
+<prgn/cron/ and <prgn/at/, <prgn/lpr/ and the system's mail transfer
+agent (<prgn/sendmail/, <prgn/smail/, <prgn/exim/ or the like) all
+fall into this category, though <prgn/userv/-based versions of these
+programs are not currently available.
+
+<sect id="noexcess">Do not give away excessive privilege to <prgn/userv/-using facilities
+<p>
+
+There is a danger that people reimplementing the facilities I mention
+above using <prgn/userv/ will discard much of the security benefit by
+using a naive implementation technique.  This will become clearer with
+an example:
+<p>
+
+Consider the <prgn/lpr/ program.  In current systems this needs to
+have an absolutely privileged component in order to support delayed
+printing without copying: when the user queues a file to be printed
+the filename is stored in the print queue, rather than a copy of it,
+and the printer daemon accesses the file directly when it is ready to
+print the job.  In order that the user can print files which are not
+world-readable the daemon is given root privilege so that it can open
+the file in the context of the user, rather than its own.
+<p>
+
+A simple-minded approach to converting this scheme to use <prgn/userv/
+might involve giving the printer daemon (the <prgn/lp/ user) the
+ability to read the file by allowing them to run <prgn/cat/ (or a
+special-purpose file-reading program) as any user.  The <prgn/lpr/
+program would use a <prgn/userv/ service to store the filename in the
+printer daemon's queues, and the daemon would read the file later when
+it felt like it.
+<p>
+
+However, this would allow the printer daemon to read any file on the
+system, whether or not someone had asked for it to be printed.  Since
+many files will contain passwords and other security-critical
+information this is nearly as bad as giving the daemon root access in
+the first place.  Any security holes in the print server which allow a
+user to execute commands as the <prgn/lp/ user will give the user the
+ability to read any file on the system.
+<p>
+
+Instead, it is necessary to keep a record of which files the daemon
+has been asked to print <em/outside/ the control of the print daemon.
+This record could be kept by a new root-privileged component, but this
+is not necessary: the record of which files a user has asked to be
+printed can be kept under the control of the user in question.  The
+submission program <prgn/lpr/ will make a record in an area under the
+user's control before communicating with the print server, and the
+print server would be given the ability to run a special file-reading
+program which would only allow files to be read which were listed in
+the user's file of things they'd asked to print.
+<p>
+
+Now security holes in most of the printing system do not critically
+affect the security of the entire system: they only allow the attacker
+to read and interfere with print jobs.  Bugs in the programs run by the
+print server to read users' files (and to remove entries from the list
+of files when it has done with them) will still be serious, but this
+program can be quite simple.
+<p>
+
+Similar considerations apply to many <prgn/userv/-based versions of
+facilities which currently run as root.
+<p>
+
+It is debatable whether the user-controlled state should be kept in
+the user's filespace (in dotfiles, say) or kept in a separate area set
+aside for the purpose; however, using the user's home directory (and
+possibly creating a separate subdirectory of it as a dotfile to
+contain subsystem state) has fewer implications for the rest of the
+system and makes it entirely clear where the security boundaries lie.
+
+<sect id="notreally"><prgn/userv/ can often replace <prgn/sudo/, but not <prgn/really/
+<p>
+
+<prgn/userv/ is not intended as a general-purpose system
+administration tool with which system administrators can execute
+arbitrary programs like text editors as root (or other system users)
+when they need to.  It is unsuitable for this purpose precisely
+because it enforces a strong separation between the calling and the
+called program, which is undesirable in this context.
+<p>
+
+However, its use when restricted to running particular programs in
+particular ways is very similar to many common uses of
+<prgn/sudo/<footnote><prgn/sudo/ is a program which allows users to
+execute certain programs as root, according to configuration files
+specified by the system administrator.</footnote>.  <prgn/userv/ is
+generally much better than restricted <prgn/sudo/, because it protects
+the called program much more strongly from bad environmental
+conditions set up by the caller.  Most programs that one might want to
+run via restricted <prgn/sudo/, have not been designed to run in a
+partially hostile environment.  <prgn/userv/ allows these programs to
+be run in a safer environment and should be used instead.
+
+<sect id="stdinerr">Error handling and input streams (eg stdin)
+<p>
+
+When the service program is reading from a file descriptor connected
+to the calling side, the fd that the service program refers to a pipe
+set up by <prgn/userv/ and not to the same object as was presented by
+the caller.
+<p>
+
+Therefore if there is some kind of error it is possible for the
+service-side fd to give premature end of file.  If it is important to
+tell whether all of the intended data has been received by the service
+program, the datastream must contain an explicit end-of-file
+indication of some kind.
+<p>
+
+For example, consider a <prgn/userv/ service for submitting a mail
+message, where message is supplied on the service's stdin.  However,
+if the calling process is interrupted before it has written all of the
+message, the service program will get EOF on the message data.  In a
+naive arrangement this would cause a half-complete message to be
+sent.  To prevent this, it is necessary to adopt some kind of explicit
+end indication; for example, the end of the message could be signalled
+by a dot on a line by itself, and dots doubled, as in SMTP.  Then the
+service program would know when the entire message had been received,
+and could avoid queueing incomplete messages.
+
+<sect id="nogeneral">Don't give access to general-purpose utilities
+<p>
+
+Do not specify general purpose programs like <prgn/mv/ or <prgn/cat/
+in <prgn/execute-/ directives without careful thought about their
+arguments, and certainly not if <prgn/no-suppress-args/ is specified.
+If you do so it will give the caller much more privilige than you
+probably intend.
+<p>
+
+It is a shame that I have to say this here, but inexperienced
+administrators have made similar mistakes with programs like
+<prgn/sudo/.
+
+</book>
diff --git a/system.default b/system.default
new file mode 100644 (file)
index 0000000..3774f0e
--- /dev/null
@@ -0,0 +1,46 @@
+# Generally, if you want all your users to provide a service for your
+# benefit but want them to be able to override your default setting,
+# you should put it in this file but not use quit.  Eg:
+#      if ( grep service-user-shell /etc/shells
+#         & glob service mail-delivery
+#         & glob calling-user mail
+#         )
+#              reset
+#              no-suppress-args
+#              execute /usr/local/bin/procmail-wrapper
+#      fi
+# (procmail-wrapper could extract envelope information from the
+# arguments and/or -D options and pass them to procmail.)
+
+include-directory /etc/userv/default.d
+include-lookup service /etc/userv/services.d
+
+# If you want to force users to provide a particular service,
+# then you can put it here and use `quit'.  Eg:
+#      if ( grep service-user-shell /etc/shells
+#         & glob service cleanup-tmp
+#         )
+#              reset
+#              errors-to-syslog local4
+#              execute /usr/local/bin/cleanup-tmp
+#              no-set-environment
+#              no-disconnect-hup
+#              null-fd 0 read
+#              null-fd 1-2 write
+#              quit
+#      fi
+# Alternatively, you could put the same thing in system.override, with
+# or without the quit.  In this case it's usually important to use
+# reset, and also to note that now users can cause error messages
+# which they could not do before (though due to the implied catch-quit
+# around the user's rc file they wouldn't stop the service being
+# executed).
+#
+# If you want to force all your users' services to have a particular
+# property you should do it in system.override.  Eg, there put
+#      set-environment
+# to force them to run /etc/environment to have ulimits set up, even
+# if they try not to.
+#
+# NB that doing this _won't_ affect things in system.default and
+# earlier in system.override that use `quit'.
diff --git a/system.override b/system.override
new file mode 100644 (file)
index 0000000..4c7e2e0
--- /dev/null
@@ -0,0 +1,7 @@
+# This is for subtle overriding things.  Most things should go in
+# system.default (or default.d or services.d), so that the user can
+# override them if they want and so that if you want to force the user
+# to provide a service you don't needlessly parse their configuration
+# file and get the error messages from it.
+
+include-directory /etc/userv/override.d
diff --git a/tokens.h b/tokens.h
new file mode 100644 (file)
index 0000000..4ea2ac9
--- /dev/null
+++ b/tokens.h
@@ -0,0 +1,265 @@
+/*  
+ *   Copyright (C)1996-1997,1999 Ian Jackson
+ *  
+ *   This is free software; you can redistribute it and/or modify it
+ *   under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *  
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *   General Public License for more details.
+ *  
+ *   You should have received a copy of the GNU General Public License
+ *   along with userv; if not, write to the Free Software
+ *   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+
+
+
+
+
+#ifndef TOKENS_H
+#define TOKENS_H
+
+enum tokens {
+  tokm_instance=           0x000000ff,
+  tokm_repres=             0x00000f00,
+  tokm_type=               0xfffff000,
+  tokr_nonstring=          0x00000100,
+  tokr_word=               0x00000200,
+  tokr_punct=              0x00000300,
+  tokr_string=             0x00000400,
+  tokt_directive=          0x00001000,
+  tokt_controlstart=       0x00002000,
+  tokt_controlend=         0x00004000,
+  tokt_exception=          0x00008000,
+  tokt_parmcondition=      0x00010000,
+  tokt_condop=             0x00020000,
+  tokt_parameter=          0x00040000,
+  tokt_number=             0x00080000,
+  tokt_fdrange=            0x00100000,
+  tokt_logfacility=        0x00200000,
+  tokt_loglevel=           0x00400000,
+  tokt_readwrite=          0x00800000,
+  tokt_string=             0x01000000,
+  tokt_execmode=           0x02000000,
+  tokt_ehandlemode=        0x04000000,
+  tokt_builtinservice=     0x08000000,
+  tokt_misc=               0x10000000,
+  tokt_internal=           0x20000000,
+
+  toki_word_reject=                                 0x00000001,
+  toki_word_executefromdirectory=                   0x00000002,
+  toki_word_executefrompath=                        0x00000003,
+  toki_word_executebuiltin=                         0x00000004,
+  toki_word_errorstostderr=                         0x00000005,
+  toki_word_errorstosyslog=                         0x00000006,
+  toki_word_errorstofile=                           0x00000007,
+  toki_word_requirefd=                              0x00000008,
+  toki_word_allowfd=                                0x00000009,
+  toki_word_nullfd=                                 0x0000000a,
+  toki_word_rejectfd=                               0x0000000b,
+  toki_word_ignorefd=                               0x0000000c,
+  toki_word_setenvironment=                         0x0000000d,
+  toki_word_nosetenvironment=                       0x0000000e,
+  toki_word_suppressargs=                           0x0000000f,
+  toki_word_nosuppressargs=                         0x00000010,
+  toki_word_disconnecthup=                          0x00000011,
+  toki_word_nodisconnecthup=                        0x00000012,
+  toki_word_cd=                                     0x00000013,
+  toki_word_userrcfile=                             0x00000014,
+  toki_word_include=                                0x00000015,
+  toki_word_includeifexist=                         0x00000016,
+  toki_word_includelookup=                          0x00000017,
+  toki_word_includelookupall=                       0x00000018,
+  toki_word_includedirectory=                       0x00000019,
+  toki_word_message=                                0x0000001a,
+  toki_word_includesysconfig=                       0x0000001b,
+  toki_word_includeuserrcfile=                      0x0000001c,
+  toki_word_includeclientconfig=                    0x0000001d,
+  toki_word_quit=                                   0x0000001e,
+  toki_word_eof=                                    0x0000001f,
+  toki_word_if=                                     0x00000020,
+  toki_word_catchquit=                              0x00000021,
+  toki_word_errorspush=                             0x00000022,
+  toki_word_elif=                                   0x00000023,
+  toki_word_else=                                   0x00000024,
+  toki_word_fi=                                     0x00000025,
+  toki_word_hctac=                                  0x00000026,
+  toki_word_srorre=                                 0x00000027,
+  toki_word_glob=                                   0x00000028,
+  toki_word_range=                                  0x00000029,
+  toki_word_grep=                                   0x0000002a,
+  toki_word_environment=                            0x0000002b,
+  toki_word_parameter=                              0x0000002c,
+  toki_word_version=                                0x0000002d,
+  toki_word_toplevel=                               0x0000002e,
+  toki_word_override=                               0x0000002f,
+  toki_word_shutdown=                               0x00000030,
+  toki_word_reset=                                  0x00000031,
+  toki_word_execute=                                0x00000032,
+  toki_word_help=                                   0x00000033,
+  toki_word_service=                                0x00000034,
+  toki_word_callinguser=                            0x00000035,
+  toki_word_callinggroup=                           0x00000036,
+  toki_word_callingusershell=                       0x00000037,
+  toki_word_serviceuser=                            0x00000038,
+  toki_word_servicegroup=                           0x00000039,
+  toki_word_serviceusershell=                       0x0000003a,
+  toki_syslog_debug=                                0x0000003b,
+  toki_syslog_info=                                 0x0000003c,
+  toki_syslog_notice=                               0x0000003d,
+  toki_syslog_warning=                              0x0000003e,
+  toki_syslog_err=                                  0x0000003f,
+  toki_syslog_crit=                                 0x00000040,
+  toki_syslog_alert=                                0x00000041,
+  toki_syslog_emerg=                                0x00000042,
+  toki_syslog_authpriv=                             0x00000043,
+  toki_syslog_cron=                                 0x00000044,
+  toki_syslog_daemon=                               0x00000045,
+  toki_syslog_kern=                                 0x00000046,
+  toki_syslog_lpr=                                  0x00000047,
+  toki_syslog_mail=                                 0x00000048,
+  toki_syslog_news=                                 0x00000049,
+  toki_syslog_syslog=                               0x0000004a,
+  toki_syslog_user=                                 0x0000004b,
+  toki_syslog_uucp=                                 0x0000004c,
+  toki_syslog_local0=                               0x0000004d,
+  toki_syslog_local1=                               0x0000004e,
+  toki_syslog_local2=                               0x0000004f,
+  toki_syslog_local3=                               0x00000050,
+  toki_syslog_local4=                               0x00000051,
+  toki_syslog_local5=                               0x00000052,
+  toki_syslog_local6=                               0x00000053,
+  toki_syslog_local7=                               0x00000054,
+  toki_word_read=                                   0x00000055,
+  toki_word_write=                                  0x00000056,
+  toki_ordinal=                                     0x00000057,
+  toki_fdrange=                                     0x00000058,
+  toki_fdstoend=                                    0x00000059,
+  toki_dollar=                                      0x0000005a,
+  toki_lwsp=                                        0x0000005b,
+  toki_newline=                                     0x0000005c,
+  toki_barestring=                                  0x0000005d,
+  toki_quotedstring=                                0x0000005e,
+  toki_eof=                                         0x0000005f,
+  toki_quit=                                        0x00000060,
+  toki_error=                                       0x00000061,
+  toki_openparen=                                   0x00000062,
+  toki_closeparen=                                  0x00000063,
+  toki_not=                                         0x00000064,
+  toki_and=                                         0x00000065,
+  toki_or=                                          0x00000066,
+  toki_word_error=                                  0x00000067,
+
+  tokv_word_reject=             tokt_directive|tokt_execmode|tokr_word|toki_word_reject,
+  tokv_word_executefromdirectory=tokt_directive|tokt_execmode|tokr_word|toki_word_executefromdirectory,
+  tokv_word_executefrompath=    tokt_directive|tokt_execmode|tokr_word|toki_word_executefrompath,
+  tokv_word_executebuiltin=     tokt_directive|tokt_execmode|tokr_word|toki_word_executebuiltin,
+  tokv_word_errorstostderr=     tokt_directive|tokt_ehandlemode|tokr_word|toki_word_errorstostderr,
+  tokv_word_errorstosyslog=     tokt_directive|tokt_ehandlemode|tokr_word|toki_word_errorstosyslog,
+  tokv_word_errorstofile=       tokt_directive|tokt_ehandlemode|tokr_word|toki_word_errorstofile,
+  tokv_word_requirefd=          tokt_directive|tokr_word|toki_word_requirefd,
+  tokv_word_allowfd=            tokt_directive|tokr_word|toki_word_allowfd,
+  tokv_word_nullfd=             tokt_directive|tokr_word|toki_word_nullfd,
+  tokv_word_rejectfd=           tokt_directive|tokr_word|toki_word_rejectfd,
+  tokv_word_ignorefd=           tokt_directive|tokr_word|toki_word_ignorefd,
+  tokv_word_setenvironment=     tokt_directive|tokr_word|toki_word_setenvironment,
+  tokv_word_nosetenvironment=   tokt_directive|tokr_word|toki_word_nosetenvironment,
+  tokv_word_suppressargs=       tokt_directive|tokr_word|toki_word_suppressargs,
+  tokv_word_nosuppressargs=     tokt_directive|tokr_word|toki_word_nosuppressargs,
+  tokv_word_disconnecthup=      tokt_directive|tokr_word|toki_word_disconnecthup,
+  tokv_word_nodisconnecthup=    tokt_directive|tokr_word|toki_word_nodisconnecthup,
+  tokv_word_cd=                 tokt_directive|tokr_word|toki_word_cd,
+  tokv_word_userrcfile=         tokt_directive|tokr_word|toki_word_userrcfile,
+  tokv_word_include=            tokt_directive|tokr_word|toki_word_include,
+  tokv_word_includeifexist=     tokt_directive|tokr_word|toki_word_includeifexist,
+  tokv_word_includelookup=      tokt_directive|tokr_word|toki_word_includelookup,
+  tokv_word_includelookupall=   tokt_directive|tokr_word|toki_word_includelookupall,
+  tokv_word_includedirectory=   tokt_directive|tokr_word|toki_word_includedirectory,
+  tokv_word_message=            tokt_directive|tokr_word|toki_word_message,
+  tokv_word_includesysconfig=   tokt_directive|tokt_internal|tokr_word|toki_word_includesysconfig,
+  tokv_word_includeuserrcfile=  tokt_directive|tokt_internal|tokr_word|toki_word_includeuserrcfile,
+  tokv_word_includeclientconfig=tokt_directive|tokt_internal|tokr_word|toki_word_includeclientconfig,
+  tokv_word_quit=               tokt_directive|tokr_word|toki_word_quit,
+  tokv_word_eof=                tokt_directive|tokr_word|toki_word_eof,
+  tokv_word_if=                 tokt_directive|tokt_controlstart|tokr_word|toki_word_if,
+  tokv_word_catchquit=          tokt_directive|tokt_controlstart|tokr_word|toki_word_catchquit,
+  tokv_word_errorspush=         tokt_directive|tokt_controlstart|tokr_word|toki_word_errorspush,
+  tokv_word_elif=               tokt_controlend|tokt_controlstart|tokr_word|toki_word_elif,
+  tokv_word_else=               tokt_controlend|tokt_controlstart|tokr_word|toki_word_else,
+  tokv_word_fi=                 tokt_controlend|tokr_word|toki_word_fi,
+  tokv_word_hctac=              tokt_controlend|tokr_word|toki_word_hctac,
+  tokv_word_srorre=             tokt_controlend|tokr_word|toki_word_srorre,
+  tokv_word_glob=               tokt_parmcondition|tokr_word|toki_word_glob,
+  tokv_word_range=              tokt_parmcondition|tokr_word|toki_word_range,
+  tokv_word_grep=               tokt_parmcondition|tokr_word|toki_word_grep,
+  tokv_word_environment=        tokt_builtinservice|tokr_word|toki_word_environment,
+  tokv_word_parameter=          tokt_builtinservice|tokr_word|toki_word_parameter,
+  tokv_word_version=            tokt_builtinservice|tokr_word|toki_word_version,
+  tokv_word_toplevel=           tokt_builtinservice|tokr_word|toki_word_toplevel,
+  tokv_word_override=           tokt_builtinservice|tokr_word|toki_word_override,
+  tokv_word_shutdown=           tokt_builtinservice|tokr_word|toki_word_shutdown,
+  tokv_word_reset=              tokt_builtinservice|tokt_directive|tokr_word|toki_word_reset,
+  tokv_word_execute=            tokt_builtinservice|tokt_directive|tokt_execmode|tokr_word|toki_word_execute,
+  tokv_word_help=               tokt_builtinservice|tokr_word|toki_word_help,
+  tokv_word_service=            tokt_parameter|tokr_word|toki_word_service,
+  tokv_word_callinguser=        tokt_parameter|tokr_word|toki_word_callinguser,
+  tokv_word_callinggroup=       tokt_parameter|tokr_word|toki_word_callinggroup,
+  tokv_word_callingusershell=   tokt_parameter|tokr_word|toki_word_callingusershell,
+  tokv_word_serviceuser=        tokt_parameter|tokr_word|toki_word_serviceuser,
+  tokv_word_servicegroup=       tokt_parameter|tokr_word|toki_word_servicegroup,
+  tokv_word_serviceusershell=   tokt_parameter|tokr_word|toki_word_serviceusershell,
+  tokv_syslog_debug=            tokt_loglevel|tokr_word|toki_syslog_debug,
+  tokv_syslog_info=             tokt_loglevel|tokr_word|toki_syslog_info,
+  tokv_syslog_notice=           tokt_loglevel|tokr_word|toki_syslog_notice,
+  tokv_syslog_warning=          tokt_loglevel|tokr_word|toki_syslog_warning,
+  tokv_syslog_err=              tokt_loglevel|tokr_word|toki_syslog_err,
+  tokv_syslog_crit=             tokt_loglevel|tokr_word|toki_syslog_crit,
+  tokv_syslog_alert=            tokt_loglevel|tokr_word|toki_syslog_alert,
+  tokv_syslog_emerg=            tokt_loglevel|tokr_word|toki_syslog_emerg,
+  tokv_syslog_authpriv=         tokt_logfacility|tokr_word|toki_syslog_authpriv,
+  tokv_syslog_cron=             tokt_logfacility|tokr_word|toki_syslog_cron,
+  tokv_syslog_daemon=           tokt_logfacility|tokr_word|toki_syslog_daemon,
+  tokv_syslog_kern=             tokt_logfacility|tokr_word|toki_syslog_kern,
+  tokv_syslog_lpr=              tokt_logfacility|tokr_word|toki_syslog_lpr,
+  tokv_syslog_mail=             tokt_logfacility|tokr_word|toki_syslog_mail,
+  tokv_syslog_news=             tokt_logfacility|tokr_word|toki_syslog_news,
+  tokv_syslog_syslog=           tokt_logfacility|tokr_word|toki_syslog_syslog,
+  tokv_syslog_user=             tokt_logfacility|tokr_word|toki_syslog_user,
+  tokv_syslog_uucp=             tokt_logfacility|tokr_word|toki_syslog_uucp,
+  tokv_syslog_local0=           tokt_logfacility|tokr_word|toki_syslog_local0,
+  tokv_syslog_local1=           tokt_logfacility|tokr_word|toki_syslog_local1,
+  tokv_syslog_local2=           tokt_logfacility|tokr_word|toki_syslog_local2,
+  tokv_syslog_local3=           tokt_logfacility|tokr_word|toki_syslog_local3,
+  tokv_syslog_local4=           tokt_logfacility|tokr_word|toki_syslog_local4,
+  tokv_syslog_local5=           tokt_logfacility|tokr_word|toki_syslog_local5,
+  tokv_syslog_local6=           tokt_logfacility|tokr_word|toki_syslog_local6,
+  tokv_syslog_local7=           tokt_logfacility|tokr_word|toki_syslog_local7,
+  tokv_word_read=               tokt_readwrite|tokr_word|toki_word_read,
+  tokv_word_write=              tokt_readwrite|tokr_word|toki_word_write,
+  tokv_ordinal=                 tokt_number|tokt_fdrange|tokr_word|toki_ordinal,
+  tokv_fdrange=                 tokt_fdrange|tokr_punct|toki_fdrange,
+  tokv_fdstoend=                tokt_fdrange|tokr_punct|toki_fdstoend,
+  tokv_dollar=                  tokt_misc|tokr_punct|toki_dollar,
+  tokv_lwsp=                    tokt_misc|tokr_nonstring|toki_lwsp,
+  tokv_newline=                 tokt_misc|tokr_nonstring|toki_newline,
+  tokv_barestring=              tokt_string|tokr_string|toki_barestring,
+  tokv_quotedstring=            tokt_string|tokr_string|toki_quotedstring,
+  tokv_eof=                     tokt_exception|tokr_nonstring|toki_eof,
+  tokv_quit=                    tokt_exception|tokr_nonstring|toki_quit,
+  tokv_error=                   tokt_exception|tokr_nonstring|toki_error,
+  tokv_openparen=               tokt_condop|tokr_punct|toki_openparen,
+  tokv_closeparen=              tokt_condop|tokr_punct|toki_closeparen,
+  tokv_not=                     tokt_condop|tokr_punct|toki_not,
+  tokv_and=                     tokt_condop|tokr_punct|toki_and,
+  tokv_or=                      tokt_condop|tokr_punct|toki_or,
+  tokv_word_error=              tokt_directive|tokt_loglevel|tokr_word|toki_word_error,
+
+};
+
+#endif
+
diff --git a/tokens.h.m4 b/tokens.h.m4
new file mode 100644 (file)
index 0000000..00b580b
--- /dev/null
@@ -0,0 +1,42 @@
+dnl  userv - tokens.h.m4
+dnl  token values, passed through m4 with defs from langauge.i4
+/*  
+ *   Copyright (C)1996-1997,1999 Ian Jackson
+ *  
+ *   This is free software; you can redistribute it and/or modify it
+ *   under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *  
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *   General Public License for more details.
+ *  
+ *   You should have received a copy of the GNU General Public License
+ *   along with userv; if not, write to the Free Software
+ *   Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+include(language.i4)
+
+#ifndef TOKENS_H
+#define TOKENS_H
+
+enum tokens {
+  tokm_instance=           0x000000ff,
+  tokm_repres=             0x00000f00,
+  tokm_type=               0xfffff000,
+  tokr_nonstring=          0x00000100,
+  tokr_word=               0x00000200,
+  tokr_punct=              0x00000300,
+  tokr_string=             0x00000400,
+undivert(4)
+undivert(1)
+undivert(2)
+};
+
+#endif
+
+divert(-1)
+undivert
diff --git a/userv.1 b/userv.1
new file mode 100644 (file)
index 0000000..9ad59c2
--- /dev/null
+++ b/userv.1
@@ -0,0 +1,470 @@
+.\"
+.\" This manpage is copyright, like the rest of userv, - see the
+.\" copyright section, below.
+.Dd November 3, 1999
+.Dt USERV 1
+.Os "userv"
+.Sh NAME
+.Nm userv
+.Nd request user services
+.Sh SYNOPSIS
+.Nm userv
+.Op Ar option ...
+.Op Fl -
+.Ar service-user
+.Ar service-name
+.Op Ar argument ...
+.Nm userv
+.Op Ar option ...
+.Fl B | -builtin
+.Op Fl -
+.Ar builtin-service
+.Bk -words
+.Op Ar info-argument ...
+.Ek
+.Sh DESCRIPTION
+.Nm userv
+is used to have a task performed under different userid while
+maintaining limited trust between caller and callee.
+.Pp
+.Ar service-user
+specifies which user account is to perform the task.  The user may be
+a login name or a numeric uid, or
+.Ql -
+to indicate that the service user is to be the same as the calling
+user.
+.Pp
+The service name is interpreted by the userv daemon on behalf of the
+service user.  This is controlled by configuration files in the
+service user's filespace; consult the userv specification for details.
+.Sh OPTIONS
+Single-letter options may be combined as is usual with Unix programs,
+and the value for such an option may appear in the same argument or in
+the next.
+.Bl -tag -width Fl
+.It Fl B | -builtin
+Requests that a builtin service be provided.  This is equivalent to
+using the
+.Fl -override
+option to specify a string consisting of
+.Ql execute-builtin
+followed by the
+.Ar builtin-service
+requested, and requesting a service user of 
+.Ql -
+(indicating the calling user).
+.Pp
+If the builtin service being requested requires a
+.Ar service-argument
+then this must be supplied to the client in the
+same argument as the
+.Ar builtin-service .
+See the specification, or the output of
+.Bd -literal -offset indent -compact
+userv -B help
+.Ed
+for details of the builtin services available,
+and below for details of the
+.Fl -override
+options.
+.Pp
+The actual service name passed will be the
+.Ar builtin-service ;
+note
+that this actual service name (as opposed to the override data) and
+the
+.Ar info-argument Ns s
+supplied will be ignored by most builtin
+services; the override mechanism and
+.Ql execute-builtin
+will be
+used to ensure that the right builtin service is called with the right
+.Ar service-argument Ns s .
+.It Xo
+.Fl f | -file
+.Sm off
+.Ar fd Oo Ar fdmodifiers Oc = Ar filename
+.Sm on
+.Xc
+Requests that data be copied in and out of the service using pipes.
+For each file or descriptor this will be done by creating a pipe, one
+end of which is passed to the service program and the other end of
+which is passed to a copy of
+.Nm cat
+invoked by the client; the other file descriptor passed to
+.Nm cat
+will be one inherited by the client program from the caller or one
+opened by the client program on behalf of the caller.
+.Pp
+The descriptor in the service program that should be connected must be
+specified as
+.Ar fd ,
+either as a decimal number or as one of the
+strings
+.Ql stdin ,
+.Ql stdout
+or
+.Ql stderr .
+The next argument is a filename which will be opened by the client
+with the privileges of the calling user.
+.Pp
+.Ar modifiers
+is used to specify whether the file or descriptor is to be read from
+or written to.  It consists of a series of words separated by commas.
+A comma may separate the
+.Ar modifiers
+from
+the
+.Ar fd
+and is required if
+.Ar fd
+is not numeric.
+The modifier words are:
+.Bl -tag -width Li
+.It Ic read
+.Dv O_RDONLY :
+Allow reading and not writing.  May not be used with
+.Ql write
+or things that imply it.
+.It Ic write
+.Dv O_WRONLY :
+Allow writing and not reading.
+.Em Doesn't truncate or create
+without
+.Ql truncate
+or
+.Ql create .
+.Ql write
+or things that imply it may not be used with
+.Ql read .
+.It Ic overwrite
+Equivalent to
+.Ql write,create,truncate .
+.It Ic create ,  creat
+.Dv O_CREAT :
+Creates the file if necessary.  Implies
+.Ql write .
+.It Ic exclusive , excl
+.Dv O_EXCL:
+Fails if the file already exists. Implies write and create. May
+not be used with
+.Ql truncate .
+.It Ic truncate , trunc
+.Dv O_TRUNC:
+Truncate any existing file.  Implies
+.Ql write .
+May not be used with
+.Ql exclusive .
+.It Ic append
+.Dv O_APPEND :
+All writes will append to the file.  Implies
+.Ql write
+(but not
+.Ql create ) .
+.It Ic sync
+.Dv O_SYNC :
+Do writes synchronously.  Implies
+.Ql write .
+.It Ic wait , nowait , close
+These modifiers control the behaviour of the client, with respect to
+the pipes carrying data to and from the service, when the service
+terminates.  See below.
+.It Ic fd
+The
+.Ar filename
+is not a filename but a numeric file descriptor.
+One or both of
+.Ql read
+and
+.Ql write
+must be specified, and no
+other words are allowed.  The
+.Ar filename
+may also be
+.Ql stdin ,
+.Ql stdout
+or
+.Ql stderr
+for file descriptor 0, 1 or 2 respectively.
+.El
+.Pp
+If no
+.Ar modifiers
+which imply
+.Ql read
+or
+.Ql write
+are used it is as if
+.Ql write
+had been specified, except that if the filedescriptor 0 of the service
+is being opened (either specified numerically or with
+.Ql stdin )
+it is as if
+.Ql overwrite
+had been specified (or
+.Ql write
+if only
+.Ql fd
+was specified).
+.Pp
+The client will also use
+.Dv O_NOCTTY
+when opening files specified by the caller, to avoid changing its
+controlling terminal.
+.Pp
+By default
+.Va stdin ,
+.Va stdout
+and
+.Va stderr
+of the service will be connected to the corresponding descriptors on
+the client.  Diagnostics from the client and daemon will also appear
+on
+.Va stderr .
+.Pp
+If
+.Ql wait
+is specified, the client will wait for the pipe to be closed, and only
+exit after this has happened.  This means that either the receiving
+end of the pipe connection was closed while data was still available
+at the sending end, or that the end of file was reached on the reading
+file descriptor.  Errors encountered reading or writing in the client
+at this stage will be considered a system error and cause the client
+to exit with status 255, but will not cause disconnection at the
+service side since the service has already exited.
+.Pp
+If
+.Ql close
+is specified the client will immediately close the pipe connection by
+killing the relevant copy of
+.Nm cat .
+If the service uses the descriptor it will get
+.Dv SIGPIPE
+(or
+.Er EPIPE )
+for a writing descriptor or end of file for a reading one; the
+descriptor opened by or passed to the client will also be closed.
+.Pp
+If
+.Ql nowait
+is specified then the client will not wait and the
+connection will remain open after the client terminates.  Data may
+continue to be passed between the inheritors of the relevant
+descriptor on the service side and the corresponding file or
+descriptor on the client side until either side closes their
+descriptor.  This should not usually be specified for
+.Va stderr
+(or
+.Va stdout
+if
+.Ql "--signals stdout"
+is used) since diagnostics from the service side may arrive after the
+client has exited and be confused with expected output.
+.Pp
+The default is
+.Ql wait
+for writing file descriptors and
+.Ql close
+for reading ones.
+.It Xo
+.Fl w | -fdwait
+.Ar fd Ns = Ns Ar action
+.Xc
+Sets the action on termination of the service for the specified file
+descriptor;
+.Ar action
+must be
+.Ql wait ,
+.Ql nowait
+or
+.Ql close
+as described above.  The file descriptor must be specified as open
+when this option is encountered; this option is overridden by any
+later
+.Fl -file
+or
+.Fl -fdwait
+option - even by a
+.Fl -file
+which does not specify an action on termination (in this case the
+default will be used, as described above).
+.It Xo
+.Fl D | -defvar
+.Ar name Ns = Ns Ar value
+.Xc
+Set a user-defined variable
+.Ar name
+to
+.Ar value .
+These
+user-defined variables are made available in the configuration
+language as the parameters
+.Ql u- Ns Ar name
+and are passed to the
+service in environment variables
+.Ev USERV_U_ Ns Ar name .
+.Ar name
+may contain only alphanumerics and underscores, and must start with a
+letter.  If several definitions are given for the same
+.Ar name
+then only the last is effective.
+.It Fl t | -timeout Ar seconds
+Time out the service if it takes longer than
+.Ar seconds
+seconds (a positive integer, in decimal).  Timeout will produce a
+diagnostic on stderr and an exit status of 255.  If
+.Ar seconds
+is zero then no timeout will be implemented (this is the default).
+.It Fl S | -signals Ar method
+Affects the handling of the exit status when the service terminates
+due to a signal.  (The client will always finish by calling
+.Fn _exit ,
+so that only numbers from 0 to 255 can be returned and not the full
+range of numbers and signal indications which can be returned by the
+.Fn wait
+family of system calls.)
+.Pp
+The
+.Ar method
+may be one of the following:
+.Bl -tag -width Li
+.It Ar status
+The client's exit status will be
+.Ar status .
+This will not be distinguishable from the service really having exited
+with code
+.Ar status .
+This method is the default, with a
+.Ar status
+of 254.
+.It Ic number , number-nocore
+The client's exit status will be the number of the signal which caused
+the termination of the service.  If
+.Ql number
+is used rather than
+.Ql number-nocore
+then 128 will be added if the service dumped core.
+.Ql number
+is very like the exit code mangling done by the Bourne shell.
+.It Ic highbit
+The client's exit status will be the number of the signal with 128
+added.  If the service exits normally with an exit code of greater
+than 127 then 127 will be returned.
+.It Ic stdout
+The service's numeric wait status as two decimal numbers (high byte
+first) and a textual description of its meaning will be printed to the
+client's standard output.  It will be preceded by a newline and
+followed by an extra newline, and the numbers are separated from each
+other and from the textual description by single spaces.  The exit
+status of the client will be zero, unless a system error occurs in
+which case no exit status and description will be printed to
+.Va stdout ,
+and an error message will be printed to
+.Va stderr
+as usual.
+.Pp
+Problems such as client usage errors, the service not being found or
+permission being denied or failure of a system call are system errors.
+An error message describing the problem will be printed on the
+client's
+.Va stderr ,
+and the client's exit status will be 255.  If the client dies due to a
+signal this should be treated as a serious system error.
+.El
+.It Fl H | -hidecwd
+Prevents the calling process's current directory name from being
+passed to the service; the null string will be passed instead.
+.It Fl P | -sigpipe
+If the service program is terminated due to a
+.Dv SIGPIPE
+the exit status of the client will be zero, even if it would have been
+something else according to the exit status method specified.  This
+option has no effect on the code and description printed if the exit
+status method
+.Ql stdout
+is in use.
+.It Fl h | -help
+Prints the client's usage message.
+.It Fl -copyright
+Prints the copyright and lack of warranty notice.
+.El
+.Sh SECURITY-OVERRIDING OPTIONS
+There are also some options which are available for debugging and to
+allow the system administrator to override a user's policy.  These
+options are available only if the client is called by root or if the
+calling user is the same as the service user.
+.Bl -tag -width Fl
+.It Fl -override Ar configuration-data
+.It Fl -override-file Ar file
+Do not read the usual configuration files.  Instead, the client sends
+.Ar configuration-data
+(followed by a newline) or the contents of
+.Ar filename
+(which is opened in the context of the client) to the daemon and the
+daemon uses that data instead.  The
+.Ar configuration-data
+must all be in one argument.  It will have a single newline appended
+so that a single directive can easily be given, but if more than one
+directive is required it will have to contain one or more real
+newlines.
+.It Fl -spoof-user Ar user
+Pretend to the service that it is being called by
+.Ar user
+(which may be a username or a uid).  This will also affect the group
+and supplementary groups supplied to the service; they will be the
+standard group and supplementary groups for
+.Ar user .
+The
+.Fl -spoof-user
+option will
+.Em not
+affect which user is chosen if the service user is specified as just
+.Ql - ;
+in this case the service user will be the real calling user.
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width Ev
+.It Ev LOGNAME , USER
+These are used to determine the name of the calling user, to be passed
+to the service in
+.Ev USERV_USER .
+Their values will only be used if they correspond to the calling UID.
+.El
+.Sh FILES
+.Bl -tag -width Pa
+.It Pa /var/run/userv/socket
+.Ux Ns -domain
+socket used for communication between
+.Nm
+and
+.Nm uservd .
+.It Pa /var/run/userv/%x.%x.%x
+Pipes used for connecting file descriptors in the client and the
+service.
+.El
+.Sh SEE ALSO
+.Xr uservd 8
+.Rs
+.%T "User service daemon and client specification"
+.%A Ian Jackson
+.Re
+.Sh COPYRIGHT
+GNU userv is Copyright (C)1996-2003,2006 Ian Jackson, except that this
+manpage is Copyright (C)2000 Ben Harris and Copyright (C)2003 Ian
+Jackson.
+.Pp
+GNU userv is licensed under the terms of the GNU General Public
+Licence, version 2 or (at your option) any later version, and it comes
+with NO WARRANTY, not even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for details.
+.Pp
+You should have received a copy of the GNU General Public License
+along with userv, if not, write to the Free Software Foundation, 59
+Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.Sh HISTORY
+.Nm
+was initially written in 1996 by Ian Jackson.  It became
+.Tn GNU
+.Nm
+in 1999, and version 1.0 was released in 2000.
diff --git a/uservd.8 b/uservd.8
new file mode 100644 (file)
index 0000000..b54e65a
--- /dev/null
+++ b/uservd.8
@@ -0,0 +1,146 @@
+.\"
+.\" This manpage is copyright, like the rest of userv, - see the
+.\" copyright section, below.
+.Dd November 3, 1999
+.Dt USERVD 8
+.Os "userv"
+.Sh NAME
+.Nm uservd
+.Nd supply user services
+.Sh SYNOPSIS
+.Nm userv
+.Op Fl daemon
+.Sh DESCRIPTION
+.Nm uservd
+is the daemon called by
+.Nm userv
+to have a task performed under different userid while
+maintaining limited trust between caller and callee.
+.Sh OPTIONS
+There is one optional argument:
+.Bl -tag -width Fl
+.It Fl daemon
+Requests that the program daemonise.  If this flag is supplied,
+.Nm uservd
+will fork and completely detach from the controlling terminal.
+If this option is not supplied,
+.Nm uservd
+will remain in its starting process group and continue to use the
+supplied stderr stream for any runtime system messages; this is useful
+for running
+.Nm uservd
+as a child of
+.Nm init Ns .
+Errors
+detected by
+.Nm uservd
+itself will be reported via
+.Nm syslog
+in either case.
+.El
+.Sh SYSLOG MESSAGES:
+.Nm uservd
+issues diagnostics of various kinds to syslog, with facility
+.Nm LOG_DAEMON Ns .
+The syslog levels used are:
+.Bl -tag -width Nm
+.It Nm debug
+Verbose messages about the activity of the userv daemon.
+.It Nm info
+Two log messages about the nature and outcome of each request.
+.It Nm notice
+Messages about the status of the daemon, including the
+startup message and the hourly socket check messages.
+.It Nm warning
+If the uservd exits because it believes that it no longer controls the
+rendezvous socket (ie, its socket has become orphaned), this level
+will receive messages indicating why the daemon believes this and
+notifying of its shutdown.
+.It Nm err
+A believed-recoverable error condition was detected by the userv
+server in itself, the client or the operating system (this includes
+resource shortages). The uservd will try to continue.
+.It Nm crit
+The uservd detected a non-recoverable error condition after startup
+and will exit.
+.It Nm alert
+not used.
+.It Nm emerg
+not used.
+.El
+.Pp
+The service configuration language has the facility to direct error
+and warning messages to syslog.  The default facility and level is
+.Nm user.err Ns ,
+but the author of the configuration file(s) can override this.
+.Sh EXIT STATUS
+The daemon's exit code will reflect how well things went:
+.Bl -tag -width Nm
+.It Nm 0
+The daemon was asked to detach itself from the controlling
+terminal and this appears to have been done successfully.
+.It Nm 1*
+The daemon got a SIGTERM or SIGINT and shut itself down.
+.It Nm 2*
+The daemon believed that it was no longer the uservd and so exited to
+clean up.
+.It Nm 3
+uservd was started with incorrect arguments.
+.It Nm 4
+A system call failure or other environmental problem occurred
+during startup.
+.It Nm 5*
+There was a non-recoverable error after startup; the uservd had
+to exit.
+.It Nm 6
+The daemon was asked to detach itself, but its detaching child
+died for some unexpected reason.
+.It Nm SIGABRT/SIGIOT*
+An unexpected internal error, usually caused by a bug in uservd.  This
+can also occur if an attempt to block signals using sigprocmask fails.
+.El
+.Pp
+Outcomes marked * are not possible if the daemon is asked to detach
+itself - these exit statuses will be reaped by init instead and so
+will not usually be logged anywhere.
+.Pp
+The daemon's per-request children will report the success or otherwise
+of its request in their exit status.  These are not usually be logged
+unless they indicate a serious problem.
+.Sh ENVIRONMENT
+All of the environment variables passed to
+.Nm uservd
+will be inherited by services as part of the default environment.
+(If the
+.Nm set-environment
+configuration directive is used, then other system configuration files
+can modify the environment.  Consult the specification.)
+.Sh SEE ALSO
+.Xr userv 1
+.Xr init 8
+.Rs
+.%T "User service daemon and client specification"
+.%A Ian Jackson
+.Re
+.Sh COPYRIGHT
+GNU userv, including this manpage, is Copyright (C)1996-2003,2006 Ian
+Jackson, except that the
+.Xr userv 1
+manpage is Copyright (C)2000 Ben Harris and Copyright (C)2003 Ian
+Jackson.
+.Pp
+GNU userv is licensed under the terms of the GNU General Public
+Licence, version 2 or (at your option) any later version, and it comes
+with NO WARRANTY, not even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for details.
+.Pp
+You should have received a copy of the GNU General Public License
+along with userv, if not, write to the Free Software Foundation, 59
+Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+.Sh HISTORY
+.Nm
+was initially written in 1996 by Ian Jackson.  It became
+.Tn GNU
+.Nm
+in 1999, and version 1.0 was released in 2000.