chiark / gitweb /
Initial CVS checkin.
authorian <ian>
Sun, 24 Aug 1997 21:36:21 +0000 (21:36 +0000)
committerian <ian>
Sun, 24 Aug 1997 21:36:21 +0000 (21:36 +0000)
18 files changed:
.cvsignore [new file with mode: 0644]
COPYING [new file with mode: 0644]
Makefile.in [new file with mode: 0644]
acconfig.h [new file with mode: 0644]
client.c [new file with mode: 0644]
common.h [new file with mode: 0644]
configure.in [new file with mode: 0644]
daemon.c [new file with mode: 0644]
daemon.h [new file with mode: 0644]
ddebug.c [new file with mode: 0644]
language.i4 [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]
overview.fig [new file with mode: 0644]
parser.c [new file with mode: 0644]
spec.sgml [new file with mode: 0644]
tokens.h.m4 [new file with mode: 0644]

diff --git a/.cvsignore b/.cvsignore
new file mode 100644 (file)
index 0000000..d3dd2ec
--- /dev/null
@@ -0,0 +1,13 @@
+english.lp
+*.sasp*
+config.cache
+configure
+Makefile
+lexer.l
+lexer.c
+tokens.h
+sources
+id
+vd
+services
+slash-etc
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/Makefile.in b/Makefile.in
new file mode 100644 (file)
index 0000000..d880a3b
--- /dev/null
@@ -0,0 +1,78 @@
+#  userv - Makefile.in
+#  
+#  Copyright (C)1996-1997 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.
+
+CC=@CC@
+CFLAGS=@CFLAGS@ $(XCFLAGS)
+OPTIMISE=@OPTIMISE@
+CPPFLAGS=@DEBUGDEFS@ $(XCPPFLAGS)
+LDLIBS=@DEBUGLIBS@ $(XLDLIBS)
+
+M4=m4
+M4FLAGS=
+LEX=flex
+CWD=$(shell pwd)
+
+all:           daemon client
+
+daemon:                daemon.o parserlexer.o ddebug.o lib.o
+
+lexer.l:       language.i4
+
+client.o:      config.h common.h pcsum.h
+
+daemon.o:      config.h common.h pcsum.h daemon.h lib.h tokens.h
+
+lib.o:         config.h lib.h
+
+ddebug.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 Makefile
+               cat common.h Makefile | md5sum | perl -pe 's/../0x$$&,/g; s/,$$//;' \
+                       >pcsum.h.new && mv pcsum.h.new pcsum.h
+
+tokens.h:      language.i4
+
+autoconf configure:
+               autoheader
+               autoconf
+
+clean:
+               rm -f daemon client lexer.l lexer.c tokens.h pcsum.h
+               rm -f overview.eps overview.ps
+               rm -f spec.lout* spec.ps spec.text* lout.li
+               rm -rf spec.html*
+               rm -f *.o *~ core ./#*#
+
+distclean:     clean
+               rm -f config.status config.log Makefile config.h
+
+realclean:     distclean
+               rm -f configure config.h.in
+
+%.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/acconfig.h b/acconfig.h
new file mode 100644 (file)
index 0000000..998b8e3
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * userv - acconfig.h
+ * extra stuff for config.h.in (autoconf)
+ *
+ * Copyright (C)1996-1997 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 function attributes a la GCC 2.5 and higher are available.  */
+#undef HAVE_GNUC25_ATTRIB
+
+/* Define if constant functions a la GCC 2.5 and higher are available.  */
+#undef HAVE_GNUC25_CONST
+
+/* 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@
+
+/* 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 constant functions, or null. */
+#ifndef ATTRCONST
+#ifdef HAVE_GNUC25_CONST
+#define ATTRCONST const
+#else
+#define ATTRCONST
+#endif
+#endif
+#ifndef CONSTANT
+#define CONSTANT FUNCATTR((ATTRCONST))
+#endif
diff --git a/client.c b/client.c
new file mode 100644 (file)
index 0000000..527da1b
--- /dev/null
+++ b/client.c
@@ -0,0 +1,1038 @@
+/*
+ * userv - client.c
+ * client code
+ *
+ * Copyright (C)1996-1997 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 <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 <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"
+
+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;
+};
+
+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;
+};
+
+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                                                                            }
+};
+
+struct fdsetupstate {
+  const char *filename;
+  int copyfd;
+  int mods, oflags, pipefd, killed;
+  pid_t catpid;
+};
+
+enum signalsexitspecials { se_number=-100, se_numbernocore, se_highbit, se_stdout };
+enum overridetypes { ot_none, ot_string, ot_file };
+
+static const char *serviceuser=0;
+static uid_t serviceuid, myuid;
+static struct fdsetupstate *fdsetup=0;
+static int fdsetupsize=0;
+static const char *(*defvarsarray)[2];
+static int defvarsavail=0, defvarsused=0;
+static unsigned long timeout=0;
+static int signalsexit=254;
+static int sigpipeok=0, hidecwd=0;
+static int overridetype= ot_none;
+static const char *overridevalue;
+
+static FILE *srfile, *swfile;
+
+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);
+  }
+}
+
+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) syscallerror(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);
+}
+
+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);
+}
+
+#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 *xmalloc(size_t s) {
+  void *p;
+  p= malloc(s?s:1);
+  if (!p) syscallerror("malloc (%lu bytes)",(unsigned long)s);
+  return p;
+}
+
+static void *xrealloc(void *p, size_t s) {
+  p= realloc(p,s);
+  if (!p) syscallerror("realloc (%lu bytes)",(unsigned long)s);
+  return p;
+}
+
+static void xfread(void *p, size_t sz, FILE *file) {
+  size_t nr;
+  nr= fread(p,1,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 xfwritestring(const char *s, FILE *file) {
+  int l;
+  l= strlen(s);
+  xfwrite(&l,sizeof(l),file);
+  xfwrite(s,sizeof(*s)*l,file);
+}
+
+static void xfflush(FILE *file) {
+  if (fflush(file)) syscallerror("flush server socket");
+}
+
+static void usage(void) {
+  if (fprintf(stderr,
+              "usage: userv <options> [--] <service-user> <service-name> [<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  --copyright\n"
+              "         --override <configuration-data> } available only to\n"
+              "         --override-file <filename>      } root or same user\n"
+              "fdmodifiers:            read    write  overwrite    trunc[ate]\n"
+              "(separate with commas)  append  sync   excl[usive]  creat[e]  fd\n\n"
+             "userv and uservd are copyright (C)1996-1997 Ian Jackson.\n"
+              "They come with NO WARRANTY; type `userv --copyright' for details.\n")
+      == EOF) syscallerror("write usage to stderr");
+}
+
+static void NONRETURNPRINTFFORMAT(1,2) usageerror(const char *fmt, ...) {
+  va_list al;
+  va_start(al,fmt);
+  fprintf(stderr,"userv: ");
+  vfprintf(stderr,fmt,al);
+  fprintf(stderr,"\n\n");
+  usage();
+  exit(-1);
+}
+
+static void addfdmodifier(struct fdsetupstate *fdsus, int fd, const char *key) {
+  const struct fdmodifierinfo *fdmip;
+  
+  if (!*key) return;
+  for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,key); fdmip++);
+  if (!fdmip->string) usageerror("unknown fdmodifer `%s' for fd %d",key,fd);
+  if (fdmip->conflicts & fdsetup[fd].mods)
+    usageerror("fdmodifier `%s' conflicts with another for fd %d",key,fd);
+  fdsetup[fd].mods |= fdmip->implies;
+  fdsetup[fd].oflags |= fdmip->oflags;
+}
+
+static int fdstdnumber(const char *string) {
+  if (!strcmp(string,"stdin")) return 0;
+  else if (!strcmp(string,"stdout")) return 1;
+  else if (!strcmp(string,"stderr")) return 2;
+  else return -1;
+}
+
+static void of_file(const struct optioninfo *oip, const char *value, char *key) {
+  unsigned long fd, copyfd;
+  struct stat stab;
+  int oldarraysize, r;
+  char *delim;
+
+  fd= strtoul(key,&delim,10);
+  if (delim == key) {
+    delim= strchr(key,',');
+    if (delim) *delim++= 0;
+    fd= fdstdnumber(key);
+    if (fd<0) usageerror("first part of argument to -f or --file must be numeric "
+                        "file descriptor or `stdin', `stdout' or `stderr' - `%s' "
+                        "is not recognized",key);
+  }
+  if (fd > MAX_ALLOW_FD)
+    usageerror("file descriptor specified (%lu) is larger than maximum allowed (%d)",
+              fd,MAX_ALLOW_FD);
+  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].copyfd= -1;
+      fdsetup[oldarraysize].mods= 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 (delim && *delim) {
+    key= delim;
+    delim= strchr(key,',');
+    if (delim) *delim++= 0;
+    addfdmodifier(&fdsetup[fd],fd,key);
+  }
+  if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) {
+    if (fd != 1 && fd != 2) {
+      addfdmodifier(&fdsetup[fd],fd,"read");
+    } else if (fdsetup[fd].mods & fdm_fd) {
+      addfdmodifier(&fdsetup[fd],fd,"write");
+    } else {
+      addfdmodifier(&fdsetup[fd],fd,"overwrite");
+    }
+  }
+  if (fdsetup[fd].mods & fdm_fd) {
+    copyfd= fdstdnumber(value);
+    if (copyfd<0) {
+      copyfd= strtoul(value,&delim,0);
+      if (*delim)
+       usageerror("value part of argument to --file with fd modifier must be "
+                  "numeric or fd name- `%s' is not recognised",value);
+      else if (copyfd > MAX_ALLOW_FD)
+       usageerror("file descriptor %lu named as target of file descriptor redirection"
+                  " (for file descriptor %lu) is larger than maximum allowed (%d)",
+                  copyfd,fd,MAX_ALLOW_FD);
+    }
+    do { r= fstat(copyfd,&stab); } while (r && errno==EINTR);
+    if (r) {
+      if (oip) syscallerror("check filedescriptor %lu (named as target of file "
+                           "descriptor redirection for %lu)",copyfd,fd);
+      else syscallerror("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;
+  unsigned long fd;
+  char *delim;
+  
+  fd= fdstdnumber(key);
+  if (fd<0) {
+    fd= strtoul(value,&delim,0);
+    if (*delim) usageerror("first part of argument to --fdwait must be "
+                          "numeric or fd name - `%s' is not recognised",key);
+  }
+  if (fd >= fdsetupsize || !fdsetup[fd].filename)
+    usageerror("file descriptor %lu 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;
+
+  for (i=0; i<defvarsused && strcmp(defvarsarray[i][0],key); i++);
+  if (i>=defvarsavail) {
+    defvarsavail+=10; defvarsavail<<=1;
+    defvarsarray= xrealloc(defvarsarray,sizeof(const char*)*2*defvarsavail);
+  }
+  if (i==defvarsused) defvarsused++;
+  defvarsarray[i][0]= key;
+  defvarsarray[i][1]= value;
+}
+
+static void of_timeout(const struct optioninfo *oip, const char *value, char *key) {
+  char *endp;
+  timeout= strtoul(value,&endp,0);
+  if (*endp) usageerror("timeout value `%s' must be a plain decimal string",value);
+  if (timeout>INT_MAX) usageerror("timeout value %lu too large",timeout);
+}
+
+static void of_signals(const struct optioninfo *oip, const char *value, char *key) {
+  unsigned long numvalue;
+  char *endp;
+  numvalue= strtoul(value,&endp,0);
+  if (*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();
+  exit(0);
+}
+
+static void of_copyright(const struct optioninfo *oip, const char *value, char *key) {
+  if (fprintf(stdout,
+" userv - user service daemon and client; copyright (C)1996-1997 Ian Jackson\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, write to Ian Jackson <ian@chiark.greenend.org.uk> or\n"
+" to the Free Software Foundation, 59 Temple Place - Suite 330, Boston,\n"
+" MA 02111-1307, USA.\n"
+             ) == EOF) syscallerror("write usage to stderr");
+  exit(0);
+}
+
+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;
+}
+
+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      },
+  { 'h', "help",          0, of_help         },
+  {  0,  "copyright",     0, of_copyright    },
+  {  0,  "override",      1, of_override     },
+  {  0,  "override-file", 1, of_overridefile },
+  {  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);
+  }
+}
+
+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);
+    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>4096)
+       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= getc(file); if (c==EOF) protoreaderror(file,"in error message");
+       if (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;
+    }
+  }
+}
+
+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);
+}
+
+static void disconnect(void) /* DOES return, unlike in daemon */ {
+  struct event_msg event_mbuf;
+
+  if (!swfile) {
+    fputs("userv: failed, after service program terminated\n",stderr);
+    _exit(255);
+  }
+  memset(&event_mbuf,0,sizeof(event_mbuf));
+  event_mbuf.magic= EVENT_MAGIC;
+  event_mbuf.type= et_disconnect;
+  xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
+  xfflush(swfile);
+}
+
+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 invoker 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;
+       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;
+}
+
+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);
+  }
+}
+
+int main(int argc, char *const *argv) {
+  static char fd0key[]= "stdin,fd,read";
+  static char fd1key[]= "stdout,fd,write";
+  static char fd2key[]= "stderr,fd,write";
+  static char stderrbuf[BUFSIZ], stdoutbuf[1024];
+  
+  char *const *argpp;
+  char *argp;
+  const struct optioninfo *oip;
+  struct sockaddr_un ssockname;
+  int sfd, ngids, i, j, tempfd, l, c, reading, fd, r, status;
+  sigset_t sset;
+  unsigned long ul;
+  size_t cwdbufsize;
+  char *cwdbuf;
+  struct opening_msg opening_mbuf;
+  struct request_msg request_mbuf;
+  struct progress_msg progress_mbuf;
+  struct event_msg event_mbuf;
+  struct passwd *pw;
+  gid_t mygid, *gidarray;
+  pid_t mypid;
+  const char *logname;
+  FILE *ovfile;
+  char *ovbuf;
+  int ovavail, ovused;
+  char pipepathbuf[PIPEPATHMAXLEN], catnamebuf[sizeof(int)*3+30];
+  struct sigaction sig;
+
+#ifdef NDEBUG
+# error Do not disable assertions in this security-critical code !
+#endif
+
+  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 == (gid_t)-1) syscallerror("getgroups(0,0)");
+  gidarray= xmalloc(sizeof(gid_t)*ngids);
+  if (getgroups(ngids,gidarray) != ngids) syscallerror("getgroups(ngids,)");
+  priv_suspend();
+
+  assert(argv[0]);
+  of_file(0,"stdin",fd0key);
+  of_file(0,"stdout",fd1key);
+  of_file(0,"stderr",fd2key);
+
+  for (argpp= argv+1;
+       (argp= *argpp) && *argp == '-';
+       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 (!*argpp) usageerror("no service user given after options");
+  serviceuser= *argpp++;
+  if (!*argpp) usageerror("no service name given after options and service user");
+  
+  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;
+  }
+
+  if (setvbuf(stderr,stderrbuf,_IOLBF,sizeof(stderrbuf)))
+    syscallerror("set buffering on stderr");
+  if (setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf)))
+    syscallerror("set buffering on stdout");
+
+  argc-= (argpp-argv);
+  argv= argpp;
+
+  pw= getpwnam(serviceuser);
+  if (!pw) miscerror("requested service user `%s' is not a user",serviceuser);
+  serviceuid= pw->pw_uid;
+
+  if (overridetype != ot_none && myuid != 0 && myuid != serviceuid)
+    miscerror("--override options only available to root or to"
+              " the user who will be providing the service");
+
+  logname= getenv("LOGNAME");
+  if (!logname) logname= getenv("USER");
+  if (logname) {
+    pw= getpwnam(logname);
+    if (!pw || pw->pw_uid != myuid) logname= 0;
+  }
+  if (!logname) {
+    pw= getpwuid(myuid); if (!pw) syscallerror("cannot determine your login name");
+    logname= pw->pw_name;
+  }
+
+  cwdbufsize= 0; cwdbuf= 0;
+  if (!hidecwd) {
+    for (;;) {
+      assert(cwdbufsize < INT_MAX/3);
+      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; break; }
+    }
+  }
+
+  switch (overridetype) {
+  case ot_none:
+    ovused= -1;
+    ovbuf= 0;
+    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);
+    strcpy(ovbuf,overridevalue);
+    strcat(ovbuf,"\n");
+    ovused= l+1;
+    break;
+  case ot_file:
+    ovfile= fopen(overridevalue,"r");
+    if (!ovfile) syscallerror("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))
+      syscallerror("read overriding configuration file `%s'",overridevalue);
+    ovbuf= xrealloc(ovbuf,ovused+1);
+    ovbuf[ovused]= 0;
+    break;
+  default:
+    abort();
+  }
+
+  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");
+    syscallerror("unable to connect to uservd daemon");
+  }
+  priv_suspend();
+
+  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");
+
+  for (fd=0; fd<fdsetupsize; fd++) {
+    if (!fdsetup[fd].filename) continue;
+    sprintf(pipepathbuf, PIPEPATHFORMAT,
+            (unsigned long)mypid, (unsigned long)opening_mbuf.serverpid, fd);
+    priv_resume();
+    if (unlink(pipepathbuf) && errno != ENOENT)
+      syscallerror("remove any old pipe `%s'",pipepathbuf);
+    if (mkfifo(pipepathbuf,0600)) /* permissions are irrelevant */
+      syscallerror("create pipe `%s'",pipepathbuf);
+    tempfd= open(pipepathbuf,O_RDWR);
+    if (tempfd == -1) syscallerror("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 == -1) syscallerror("real open pipe `%s'",pipepathbuf);
+    if (close(tempfd)) syscallerror("close prelim fd onto pipe `%s'",pipepathbuf);
+    priv_suspend();
+  }
+
+  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.lognamelen= strlen(logname);
+  request_mbuf.cwdlen= cwdbufsize;
+  request_mbuf.callinguid= myuid;
+  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= defvarsused;
+  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(logname,sizeof(*logname)*request_mbuf.lognamelen,swfile);
+  xfwrite(cwdbuf,sizeof(*cwdbuf)*request_mbuf.cwdlen,swfile);
+  if (ovused>=0) xfwrite(ovbuf,sizeof(*ovbuf)*ovused,swfile);
+  xfwrite(&mygid,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<defvarsused; i++)
+    for (j=0; j<2; j++)
+      xfwritestring(defvarsarray[i][j],swfile);
+  ul= REQUEST_END_MAGIC; xfwrite(&ul,sizeof(ul),swfile);
+  xfflush(swfile);
+
+  priv_permanentlyrevokesuspended(); /* Must not do this before we give our real id */
+
+  getprogress(&progress_mbuf,srfile);
+  if (progress_mbuf.type != pt_ok)
+    protoerror("progress message during configuration phase"
+              " unexpected type %d",progress_mbuf.type);
+
+  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");
+
+  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,0777);
+      if (fdsetup[fd].copyfd<0)
+       syscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
+    }
+    fdsetup[fd].catpid= fork();
+    if (fdsetup[fd].catpid==-1) syscallerror("fork for cat for fd %d",fd);
+    if (!fdsetup[fd].catpid) {
+      snprintf(catnamebuf,sizeof(catnamebuf),"cat fd%d",fd);
+      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);
+      }
+      catnamebuf[sizeof(catnamebuf)-1]= 0;
+      reading= fdsetup[fd].mods & fdm_read;
+      catdup(catnamebuf, fdsetup[fd].copyfd, reading ? 0 : 1);
+      catdup(catnamebuf, fdsetup[fd].pipefd, reading ? 1 : 0);
+      execlp("cat",catnamebuf,(char*)0);
+      fprintf(stderr,"userv: %s: cannot exec `cat': %s\n",catnamebuf,strerror(errno));
+      exit(-1);
+    }
+    if (fdsetup[fd].copyfd>2)
+      if (close(fdsetup[fd].copyfd)) syscallerror("close real fd for %d",fd);
+    if (close(fdsetup[fd].pipefd)) syscallerror("close pipe fd for %d",fd);
+  }
+
+  if (timeout)
+    if (alarm(timeout)<0) syscallerror("set up timeout alarm");
+
+  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);
+
+  getprogress(&progress_mbuf,srfile);
+  if (progress_mbuf.type != pt_terminated)
+    protoerror("progress message during execution phase"
+              " unexpected type %d",progress_mbuf.type);
+
+  swfile= 0;
+
+  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)) syscallerror("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);
+  }
+
+  blocksignals(SIG_BLOCK);
+
+  status= progress_mbuf.data.terminated.status;
+  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,"unknown wait status %d\n",status);
+  _exit(-1);
+}
diff --git a/common.h b/common.h
new file mode 100644 (file)
index 0000000..539ecf0
--- /dev/null
+++ b/common.h
@@ -0,0 +1,136 @@
+/*
+ * userv - common.h
+ * definitions shared between client and daemon
+ *
+ * Copyright (C)1996-1997 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 PCSUMSIZE 16
+
+static const unsigned char protocolchecksumversion[PCSUMSIZE]= {
+#include "pcsum.h"
+};
+
+#ifndef VARDIR
+# define VARDIR "/var/run/userv"
+#endif
+
+#define DIRSEP "/"
+
+#ifndef RENDEZVOUS
+# define RENDEZVOUS "socket"
+#endif
+
+#ifndef RENDEZVOUSPATH
+# define RENDEZVOUSPATH VARDIR DIRSEP RENDEZVOUS
+#endif
+
+#ifndef PIPEFORMAT
+# ifdef AC_SYS_LONG_FILENAMES
+#  define PIPEFORMAT "pipe.%lu.%lu.%d"
+#  define PIPEFORMATEXTEND (sizeof(long)*3*2+sizeof(int)*3+1)
+# else
+#  define PIPEFORMAT "%lx.%lx.%x"
+#  define PIPEFORMATEXTEND (sizeof(long)*2*2+sizeof(int)*2+1)
+# endif
+#endif
+
+#ifndef PIPEPATHFORMAT
+# define PIPEPATHFORMAT VARDIR DIRSEP PIPEFORMAT
+# define PIPEPATHMAXLEN (sizeof(PIPEPATHFORMAT)+PIPEFORMATEXTEND)
+#endif
+
+#define MAX_ALLOW_FD 255
+#define MAX_INCLUDE_NEST 40
+#define MAX_OVERRIDE_LEN (1024*1024)
+
+#ifdef DEBUG
+# define BASE_MAGIC 0x5deb7567 /* "\x5d\xebug" */
+#else
+# define BASE_MAGIC 0x755e7276 /* "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 serverpid;
+};
+
+struct request_msg {
+  unsigned long magic;
+  pid_t clientpid;
+  int serviceuserlen;
+  int servicelen;
+  int lognamelen;
+  int cwdlen;
+  uid_t callinguid;
+  int ngids, nreadfds, nwritefds, nargs, nvars, overridelen;
+  /* Followed by:
+   *   serviceuserlen bytes for the service user (unterminated)
+   *   servicelen bytes for the service (unterminated)
+   *   lognamelen bytes for the login name (unterminated)
+   *   cwdlen bytes for the cwd (unterminated)
+   *   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: nothing
+   *  for errmsg: messagelen bytes for the error message (unterminated)
+   *              unsigned long PROGRESS_MAGIC
+   *  for terminated: nothing
+   */
+};
+
+struct event_msg {
+  unsigned long magic;
+  enum { et_confirm, et_closereadfd, et_disconnect } type;
+  union {
+    struct { int fd; } closereadfd;
+  } data;
+};
+
+#endif
diff --git a/configure.in b/configure.in
new file mode 100644 (file)
index 0000000..ce9285a
--- /dev/null
@@ -0,0 +1,132 @@
+#  userv - configure.in
+# 
+#  Copyright (C)1996-1997 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
+ 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_SYS_LONG_FILE_NAMES
+
+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__((const)),dpkg_cv_c_attribute_const,,
+   [extern int testfunction(int x) __attribute__((const))],
+   AC_MSG_RESULT(yes)
+   AC_DEFINE(HAVE_GNUC25_CONST),
+   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 -Werror"
+fi
+[CFLAGS="`echo $CFLAGS $CWARNS | sed -e 's/-O[0-9]*/$(OPTIMISE)/'`"]
+
+AC_OUTPUT(Makefile)
diff --git a/daemon.c b/daemon.c
new file mode 100644 (file)
index 0000000..49fff81
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,805 @@
+/*
+ * userv - daemon.c
+ * daemon main program
+ *
+ * Copyright (C)1996-1997 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 <stdarg.h>
+#include <unistd.h>
+#include <wait.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 <sys/types.h>
+#include <sys/fcntl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <limits.h>
+#include <ctype.h>
+
+#include "config.h"
+#include "common.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. */
+gid_t *gidarray=0;
+char **argarray=0;
+char *((*defvararray)[2])=0;
+struct fdstate *fdarray=0;
+int fdarraysize=0, fdarrayused=0;
+int restfdwantstate= tokv_word_rejectfd, restfdwantrw= 0;
+struct request_msg request_mbuf;
+char *serviceuser=0, *service=0, *logname=0, *cwd=0;
+char *overridedata=0, *userrcfile=0;
+char *serviceuser_dir=0, *serviceuser_shell=0;
+uid_t serviceuser_uid=-1;
+gid_t serviceuser_gid=-1;
+char *execpath=0, **execargs=0;
+int execute, setenvironment, suppressargs, disconnecthup, ehandling;
+int ehlogfacility=0, ehloglevel=0, ehfilekeep=0, syslogopenfacility=-1;
+FILE *ehfile=0;
+char *ehfilename=0;
+
+static FILE *swfile= 0, *srfile= 0;
+static pid_t child= -1, childtokill= -1;
+
+static struct passwd *servicepw, *callingpw;
+static const char **grouparray;
+
+static void sigchildhandler(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 (WIFSIGNALED(status))
+      if (WCOREDUMP(status))
+       syslog(LOG_ERR,"call pid %ld dumped core due to signal %s",(long)r,
+              strsignal(WTERMSIG(status)));
+      else
+       syslog(LOG_ERR,"call pid %ld died due to signal %s",
+              (long)r,strsignal(WTERMSIG(status)));
+    else if (!WIFEXITED(status))
+      syslog(LOG_ERR,"call pid %ld died due to unknown reason, code %ld",
+            (long)r,status);
+    else if (WEXITSTATUS(status)>24)
+      syslog(LOG_ERR,"call pid %ld exited with status %ld >24",
+            (long)r,WEXITSTATUS(status));
+  }
+  errno= es;
+  return;
+}
+
+static void xfread(void *p, size_t sz) {
+  size_t nr;
+  nr= fread(p,1,sz,srfile); if (nr == sz) return;
+  if (ferror(srfile)) syscallerror("reading from client");
+  assert(feof(srfile));
+  syslog(LOG_DEBUG,"client went away (unexpected EOF)");
+  swfile= 0;
+  disconnect(12);
+}
+
+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 client");
+}
+
+static char *xfreadsetstring(int l) {
+  char *s;
+  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 xfflush(FILE *file) {
+  if (fflush(file)) syscallerror("flush client socket");
+}
+
+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].wantstate= restfdwantstate;
+    fdarray[fdarrayused].wantrw= restfdwantrw;
+    fdarrayused++;
+  }
+}
+
+void ensurelogopen(int wantfacility) {
+  if (syslogopenfacility==wantfacility) return;
+  if (syslogopenfacility!=-1) closelog();
+  openlog(USERVD_LOGIDENT,LOG_NDELAY|LOG_PID,wantfacility);
+  syslogopenfacility= wantfacility;
+}
+
+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);
+}
+
+void miscerror(const char *what) {
+  syslog(LOG_ERR,"failure: %s",what);
+  disconnect(16);
+}
+
+void syscallerror(const char *what) {
+  int e;
+
+  e= errno;
+  syslog(LOG_ERR,"system call failure: %s: %s",what,strerror(e));
+  disconnect(18);
+}
+
+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_DEBUG,"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 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 NONRETURNING syscallservfail(const char *msg) {
+  fputs("uservd(service): ",stderr);
+  perror(msg);
+  _exit(-1);
+}
+
+static void servresetsig(int signo) {
+  struct sigaction sig;
+  
+  sig.sa_handler= SIG_DFL;
+  sigemptyset(&sig.sa_mask);
+  sig.sa_flags= 0;
+  if (sigaction(signo,&sig,0)) syscallservfail("reset signal handler");
+}
+
+static 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;
+  };
+  if (synchmsg != ch) { errno= EPROTO; return -1; }
+  return 0;
+}
+
+static const char *see_logname(void) { return servicepw->pw_name; }
+static const char *see_home(void) { return servicepw->pw_dir; }
+static const char *see_shell(void) { return servicepw->pw_shell; }
+
+static const char *see_path(void) {
+  return servicepw->pw_uid ?
+    "/usr/local/bin:/bin:/usr/bin" :
+    "/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin";
+}
+
+static const char *see_service(void) { return service; }
+static const char *see_c_cwd(void) { return cwd; }
+static const char *see_c_logname(void) { return logname; }
+static const char *see_c_uid(void) {
+  static char buf[CHAR_BIT*sizeof(uid_t)/3+4];
+  snyprintf(buf,sizeof(buf),"%lu",(unsigned long)callingpw->pw_uid);
+  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 grouparray[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),"%d",gidarray[i]);
+  return buf;
+}
+static const char *see_c_gid(void) {
+  return see_c_list(request_mbuf.ngids,seei_gid);
+}
+
+static const struct servenvinfo {
+  const char *name;
+  const char *(*fn)(void);
+} servenvinfos[]= {
+  { "USER",           see_logname    },
+  { "LOGNAME",        see_logname    },
+  { "HOME",           see_home       },
+  { "SHELL",          see_shell      },
+  { "PATH",           see_path       },
+  { "USERV_SERVICE",  see_service    },
+  { "USERV_CWD",      see_c_cwd      },
+  { "USERV_USER",     see_c_logname  },
+  { "USERV_UID",      see_c_uid      },
+  { "USERV_GROUP",    see_c_group    },
+  { "USERV_GID",      see_c_gid      },
+  {  0                               }
+};
+
+static void NONRETURNING execservice(const int synchsocket[]) {
+  static const char *const setenvpfargs[]= {
+    "/bin/sh",
+    "-c",
+    ". " SYSTEMCONFIGDIR "/environment; exec \"$@\"",
+    "-",
+    0
+  };
+  int fd, realfd, holdfd, newfd, r, envvarbufsize=0, targ, nargs, i, l;
+  char *envvarbuf=0;
+  const char **args, *const *cpp;
+  char *const *pp;
+  char synchmsg;
+  const struct servenvinfo *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);
+  }
+  if (close(synchsocket[0])) syscallservfail("close parent synch socket");
+
+  if (setpgid(0,0)) syscallservfail("set process group");
+  synchmsg= 'y';
+  r= write(synchsocket[1],&synchmsg,1);
+  if (r!=1) syscallservfail("write synch byte to parent");
+  r= synchread(synchsocket[1],'g');
+  if (r) syscallservfail("reach synch byte from parent");
+
+  if (close(fileno(swfile))) syscallservfail("close client socket fd");
+  for (fd=0; fd<fdarrayused; fd++) {
+    if (fdarray[fd].holdfd == -1) continue;
+    if (close(fdarray[fd].holdfd)) syscallservfail("close pipe hold fd");
+    fdarray[fd].holdfd= -1;
+  }
+  for (fd=0; fd<fdarrayused; fd++) {
+    if (fdarray[fd].realfd < fdarrayused) 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) syscallservfail("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) syscallservfail("dup2 set up fd");
+    if (close(fdarray[fd].realfd)) syscallservfail("close old fd");
+    if (fcntl(fd,F_SETFD,0)<0) syscallservfail("set no-close-on-exec on fd");
+    fdarray[fd].realfd= fd;
+  }
+  servresetsig(SIGPIPE);
+  servresetsig(SIGCHLD);
+
+  for (sei= servenvinfos; sei->name; sei++)
+    if (setenv(sei->name,sei->fn(),1)) syscallservfail("setenv standard");
+  for (i=0; i<request_mbuf.nvars; i++) {
+    l= strlen(defvararray[i][0])+9;
+    if (l>envvarbufsize) { envvarbufsize= l; envvarbuf= xrealloc(envvarbuf,l); }
+    snyprintf(envvarbuf,l,"USERV_U_%s",defvararray[i][0]);
+    if (setenv(envvarbuf,defvararray[i][1],1)) syscallservfail("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;
+
+  execv(args[0],(char* const*)args);
+
+  syscallservfail("exec service program");
+  _exit(-1);
+}
+
+static void NONRETURNING sighandler_pipe(int ignored) {
+  swfile= 0;
+  ensurelogopen(USERVD_LOGFACILITY);
+  syslog(LOG_DEBUG,"client went away (server got sigpipe)");
+  disconnect(8);
+}
+
+static void NONRETURNING sighandler_chld(int ignored) {
+  struct progress_msg progress_mbuf;
+  int status;
+  pid_t returned;
+
+  returned= wait3(&status,WNOHANG,0);
+  if (returned==-1) syscallerror("wait for child failed");
+  if (!returned) syscallfailure("spurious sigchld");
+  if (returned!=child) syscallfailure("spurious child process (pid %ld)",(long)returned);
+  child= childtokill= -1;
+
+  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);
+
+  syslog(LOG_DEBUG,"service completed (status %d %d)",(status>>8)&0x0ff,status&0x0ff);
+  _exit(0);
+}
+
+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;
+      assert(fd<fdarrayused);
+      assert(fdarray[fd].holdfd!=-1);
+      if (close(fdarray[fd].holdfd)) syscallfailure("cannot close holding fd %d",fd);
+      break;
+    case et_disconnect:
+      syslog(LOG_DEBUG,"client disconnected");
+      disconnect(4);
+    default:
+      return;
+    }
+  }
+}
+
+static void NONRETURNING servicerequest(int sfd) {
+  struct opening_msg opening_mbuf;
+  struct progress_msg progress_mbuf;
+  struct event_msg event_mbuf;
+  pid_t mypid, newchild;
+  unsigned long ul;
+  int i,j, r, tempfd, fd, partsize, synchsocket[2];
+  char pipepathbuf[PIPEPATHMAXLEN];
+  const char *string, *delim, *nextstring;
+  char *part, *exectry;
+  char synchmsg;
+  struct stat stab;
+  struct sigaction sig;
+  struct group *cgrp;
+
+  ensurelogopen(USERVD_LOGFACILITY);
+  syslog(LOG_DEBUG,"call connected");
+
+  mypid= getpid(); if (mypid == -1) syscallerror("getpid");
+
+  srfile= fdopen(sfd,"r");
+  if (!srfile) syscallerror("turn socket fd into reading FILE*");
+  if (setvbuf(srfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket reads");
+
+  swfile= fdopen(sfd,"w");
+  if (!swfile) syscallerror("turn socket fd into writing FILE*");
+  if (setvbuf(swfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket writes");
+
+  opening_mbuf.magic= OPENING_MAGIC;
+  memcpy(opening_mbuf.protocolchecksumversion,protocolchecksumversion,PCSUMSIZE);
+  opening_mbuf.serverpid= mypid;
+  xfwrite(&opening_mbuf,sizeof(opening_mbuf),swfile);
+  xfflush(swfile);
+
+  xfread(&request_mbuf,sizeof(request_mbuf));
+  serviceuser= xfreadsetstring(request_mbuf.serviceuserlen);
+  service= xfreadsetstring(request_mbuf.servicelen);
+  logname= xfreadsetstring(request_mbuf.lognamelen);
+  cwd= xfreadsetstring(request_mbuf.cwdlen);
+  if (request_mbuf.overridelen >= 0) {
+    assert(request_mbuf.overridelen <= MAX_OVERRIDE_LEN);
+    overridedata= xfreadsetstring(request_mbuf.overridelen);
+  } else {
+    overridedata= 0;
+  }
+  gidarray= xmalloc(sizeof(gid_t)*request_mbuf.ngids);
+  xfread(gidarray,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;
+  for (i=0; i<request_mbuf.nreadfds+request_mbuf.nwritefds; i++) {
+    xfread(&fd,sizeof(int));
+    ensurefdarray(fd);
+    assert(fdarray[fd].iswrite == -1);
+    fdarray[fd].iswrite= (i>=request_mbuf.nreadfds);
+  }
+
+  argarray= xmalloc(sizeof(char*)*(request_mbuf.nargs));
+  for (i=0; i<request_mbuf.nargs; i++) argarray[i]= xfreadstring();
+  defvararray= xmalloc(sizeof(char*)*request_mbuf.nvars*2);
+  for (i=0; i<request_mbuf.nvars; i++)
+    for (j=0; j<2; j++) defvararray[i][j]= xfreadstring();
+  xfread(&ul,sizeof(ul));
+  assert(ul == REQUEST_END_MAGIC);
+
+  for (fd=0; fd<fdarrayused; fd++) {
+    if (fdarray[fd].iswrite == -1) continue;
+    sprintf(pipepathbuf, PIPEPATHFORMAT, (unsigned long)request_mbuf.clientpid,
+            (unsigned long)mypid, fd);
+    tempfd= open(pipepathbuf,O_RDWR);
+    if (tempfd == -1) syscallerror("prelim open pipe");
+    if (!fdarray[fd].iswrite) {
+      fdarray[fd].holdfd= open(pipepathbuf, O_WRONLY);
+      if (fdarray[fd].holdfd == -1) syscallerror("hold open pipe");
+      fdarray[fd].realfd= open(pipepathbuf, O_RDONLY);
+    } else {
+      fdarray[fd].holdfd= -1;
+      fdarray[fd].realfd= open(pipepathbuf, O_WRONLY);
+    }
+    if (fdarray[fd].realfd == -1) syscallerror("real open pipe");
+    if (unlink(pipepathbuf)) syscallerror("unlink pipe");
+    if (close(tempfd)) syscallerror("close prelim fd onto pipe");
+  }
+
+  servicepw= getpwnam(serviceuser);
+  if (!servicepw) syscallerror("look up service user");
+  assert(!strcmp(servicepw->pw_name,serviceuser));
+  serviceuser_dir= xstrdup(nondebug_serviceuserdir(servicepw->pw_dir));
+  serviceuser_shell= xstrdup(servicepw->pw_shell);
+  serviceuser_uid= servicepw->pw_uid;
+  serviceuser_gid= servicepw->pw_gid;
+  if (initgroups(servicepw->pw_name,servicepw->pw_gid)) syscallerror("initgroups");
+  if (setreuid(servicepw->pw_uid,servicepw->pw_uid)) syscallerror("setreuid 1");
+  if (setreuid(servicepw->pw_uid,servicepw->pw_uid)) syscallerror("setreuid 2");
+  if (servicepw->pw_uid)
+    if (!setreuid(servicepw->pw_uid,0)) miscerror("setreuid 3 unexpectedly succeeded");
+  if (errno != EPERM) syscallerror("setreuid 3 failed in unexpected way");
+
+  debug_dumprequest(mypid);
+
+  callingpw= getpwnam(logname);
+  if (!callingpw) syscallerror("get passwd entry for calling user");
+  grouparray= xmalloc(sizeof(char*)*request_mbuf.ngids);
+  for (i=0; i<request_mbuf.ngids; i++) {
+    cgrp= getgrgid(gidarray[i]);
+    if (!cgrp) syscallerror("get group entry for calling group");
+    grouparray[i]= xmstrsave(cgrp->gr_name);
+  }
+
+  if (overridedata) {
+    r= parse_string(TOPLEVEL_OVERRIDDEN_CONFIGURATION,
+                   "<builtin toplevel override configuration>");
+  } else {
+    r= parse_string(TOPLEVEL_CONFIGURATION,
+                   "<builtin toplevel configuration>");
+  }
+
+  ensurelogopen(USERVD_LOGFACILITY);
+
+  if (r == tokv_error) failure("error encountered while parsing configuration files");
+  assert(r == tokv_quit);
+
+  debug_dumpexecsettings();
+  
+  switch (execute) {
+  case tokv_word_reject:
+    failure("request rejected");
+  case tokv_word_execute:
+    r= stat(execpath,&stab);
+    if (r) syscallfailure("checking for executable `%s'",execpath);
+    break;
+  case tokv_word_executefromdirectory:
+    r= stat(execpath,&stab);
+    if (r) syscallfailure("checking for executable in directory, `%s'",execpath);
+    break;
+  case tokv_word_executefrompath:
+    if (strchr(service,'/')) {
+      r= stat(service,&stab);
+      if (r) syscallfailure("execute-from-path (contains slash)"
+                           " cannot check for executable `%s'",service);
+      execpath= service;
+    } else {
+      string= getenv("PATH");
+      if (!string) failure("execute-from-path, but daemon inherited no PATH !");
+      while (string) {
+       delim= strchr(string,':');
+       if (delim) {
+         if (delim-string > INT_MAX)
+           failure("execute-from-path, but PATH component too long");
+         partsize= delim-string;
+         nextstring= delim+1;
+       } else {
+         partsize= strlen(string);
+         nextstring= 0;
+       }
+       part= xmstrsubsave(string,partsize);
+       exectry= part[0] ? xmstrcat3save(part,"/",service) : xmstrsave(service);
+       free(part);
+       r= stat(exectry,&stab);
+       if (!r) { execpath= exectry; break; }
+       free(exectry);
+       string= nextstring;
+      }
+      if (!execpath) failure("execute-from-path, but program `%s' not found",service);
+    }
+    break;
+  default:
+    abort();
+  }
+
+  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:
+      if (fdarray[fd].realfd != -1)
+       if (close(fdarray[fd].realfd))
+         syscallfailure("close unwanted file descriptor %d",fd);
+      fdarray[fd].realfd= -1;
+      break;
+    case tokv_word_nullfd:
+      if (fdarray[fd].realfd != -1) close(fdarray[fd].realfd);
+      fdarray[fd].realfd= open("/dev/null",
+                              fdarray[fd].iswrite == -1 ? O_RDWR :
+                              fdarray[fd].iswrite ? O_WRONLY : O_RDONLY);
+      if (fdarray[fd].realfd == -1)
+       syscallfailure("cannot open /dev/null for null fd");
+      break;
+    case tokv_word_requirefd:
+      if (fdarray[fd].realfd == -1)
+       failure("file descriptor %d not provided but required",fd);
+      /* fall through */
+    case tokv_word_allowfd:
+      if (fdarray[fd].realfd == -1) {
+       fdarray[fd].iswrite= (fdarray[fd].wantrw == tokv_word_write);
+       fdarray[fd].realfd= open("/dev/null",fdarray[fd].iswrite ? O_WRONLY : O_RDONLY);
+       if (fdarray[fd].realfd == -1)
+         syscallfailure("cannot open /dev/null for allowed but not provided fd");
+      } else {
+       if (fdarray[fd].iswrite) {
+         if (fdarray[fd].wantrw != tokv_word_write)
+           failure("file descriptor %d provided write, wanted read",fd);
+       } else {
+         if (fdarray[fd].wantrw != tokv_word_read)
+           failure("file descriptor %d provided read, wanted write",fd);
+       }
+      }
+    }
+  }
+
+  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);
+
+  r= socketpair(AF_UNIX,SOCK_STREAM,0,synchsocket);
+  if (r) syscallfailure("cannot create socket for synch");
+
+  sig.sa_handler= sighandler_pipe;
+  sigemptyset(&sig.sa_mask);
+  sigaddset(&sig.sa_mask,SIGPIPE);
+  sigaddset(&sig.sa_mask,SIGCHLD);
+  sig.sa_flags= 0;
+  if (sigaction(SIGPIPE,&sig,0)) syscallfailure("cannot set sigpipe handler");
+
+  sig.sa_handler= sighandler_chld;
+  if (sigaction(SIGCHLD,&sig,0)) syscallfailure("cannot set sigchld handler");
+
+  getevent(&event_mbuf);
+  assert(event_mbuf.type == et_confirm);
+
+  newchild= fork();
+  if (newchild == -1) syscallfailure("cannot fork to invoke service");
+  if (!newchild) execservice(synchsocket);
+  childtokill= child= newchild;
+
+  if (close(synchsocket[1])) syscallfailure("cannot close other end of synch socket");
+
+  r= synchread(synchsocket[0],'y');
+  if (r) syscallfailure("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])) syscallfailure("cannot close my end of synch socket");
+
+  getevent(&event_mbuf);
+  abort();
+}
+
+int main(int argc, char *const *argv) {
+  int mfd, sfd, csocklen;
+  struct sigaction childact;
+  struct sockaddr_un ssockname, csockname;
+
+#ifdef NDEBUG
+  abort(); /* Do not disable assertions in this security-critical code ! */
+#endif
+
+  if (argc>1) { fputs("usage: uservd\n",stderr); exit(3); }
+
+  openlog(USERVD_LOGIDENT,LOG_NDELAY,USERVD_LOGFACILITY);
+  mfd= socket(AF_UNIX,SOCK_STREAM,0);
+  if (!mfd) { syslog(LOG_CRIT,"cannot create master socket: %m"); exit(4); }
+
+  assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUSPATH));
+  ssockname.sun_family= AF_UNIX;
+  strcpy(ssockname.sun_path,RENDEZVOUSPATH);
+  unlink(RENDEZVOUSPATH);
+  if (bind(mfd,(struct sockaddr*)&ssockname,sizeof(ssockname)))
+    { syslog(LOG_CRIT,"cannot bind master socket: %m"); exit(4); }
+  if (listen(mfd,5))
+    { syslog(LOG_CRIT,"cannot listen on master socket: %m"); exit(4); }
+
+  childact.sa_handler= sigchildhandler;
+  sigemptyset(&childact.sa_mask);
+  childact.sa_flags= SA_NOCLDSTOP;
+  if (sigaction(SIGCHLD,&childact,0))
+    { syslog(LOG_CRIT,"cannot setup sigchld handler: %m"); exit(4); }
+  syslog(LOG_NOTICE,"started");
+  for (;;) {
+    csocklen= sizeof(csockname);
+    sfd= accept(mfd,(struct sockaddr*)&csockname,&csocklen);
+    if (sfd == -1) {
+      if (errno == EINTR) continue;
+      if (errno == ENOMEM) {
+        syslog(LOG_ERR,"unable to accept connection: %m"); continue;
+      }
+      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(); servicerequest(sfd);
+    }
+    close(sfd);
+  }
+}
diff --git a/daemon.h b/daemon.h
new file mode 100644 (file)
index 0000000..45bb102
--- /dev/null
+++ b/daemon.h
@@ -0,0 +1,143 @@
+/*
+ * userv - daemon.h
+ * definitions used in the daemon's source code
+ *
+ * Copyright (C)1996-1997 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 " USERDIRPREFIX "        \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
+
+#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 USERDIRPREFIX               USERDIR DIRSEP
+#define USERCONFIGDIRBASE           SYSTEMUSERVCONFIGDIR
+#define USERCONFIGDIR               HIDDENPREFIX USERCONFIGDIRBASE
+#define USERUSERVCONFIGPATH         USERDIR DIRSEP USERCONFIGDIR
+#define USERRCFILEPATH              USERUSERVCONFIGPATH DIRSEP USERRCFILE
+#define SYSTEMUSERVCONFIGPATH       SYSTEMCONFIGDIR DIRSEP SYSTEMUSERVCONFIGDIR
+#define SYSTEMRCFILEDEFAULTPATH     SYSTEMUSERVCONFIGPATH DIRSEP SYSTEMRCFILEDEFAULT
+#define SYSTEMRCFILEOVERRIDEPATH    SYSTEMUSERVCONFIGPATH DIRSEP SYSTEMRCFILEOVERRIDE
+#define SHELLLISTPATH               SYSTEMCONFIGDIR DIRSEP SHELLLIST
+
+#define USERDIR                     "~"
+#define HIDDENPREFIX                "."
+
+#define USERVD_LOGIDENT "uservd"
+#define USERVD_LOGFACILITY LOG_DAEMON
+#define DEFUSERLOGFACILITY LOG_DAEMON
+#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 MAX_INCLUDE_NEST 40
+#define MAX_ERRMSG_LEN 2048
+#define ERRMSG_RESERVE_ERRNO 128
+
+int parse_string(const char *string, const char *descrip);
+void 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 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);
+
+struct fdstate {
+  int iswrite, realfd, holdfd;
+  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 */
+};
+
+extern gid_t *gidarray;
+extern char **argarray;
+extern char *((*defvararray)[2]);
+extern struct fdstate *fdarray; /* indexed by nominal fd */
+extern int fdarraysize, fdarrayused;
+extern int restfdwantstate, restfdwantrw;
+extern struct request_msg request_mbuf;
+extern char *serviceuser, *service, *logname, *cwd;
+extern char *overridedata, *userrcfile;
+extern char *serviceuser_dir, *serviceuser_shell;
+extern uid_t serviceuser_uid;
+extern gid_t serviceuser_gid;
+extern char *execpath, **execargs;
+extern int execute; /* One of the execution modes tokt_execmode */
+extern int setenvironment, suppressargs, disconnecthup;
+extern int ehandling; /* One of the error handling modes tokt_ehandlemode */
+extern int ehlogfacility, ehloglevel, syslogopenfacility, ehfilekeep;
+extern FILE *ehfile;
+extern char *ehfilename;
+
+#endif
diff --git a/ddebug.c b/ddebug.c
new file mode 100644 (file)
index 0000000..1ea8a06
--- /dev/null
+++ b/ddebug.c
@@ -0,0 +1,201 @@
+/*
+ * userv - ddebug.c
+ * routines which are different for -DDEBUG
+ *
+ * Copyright (C)1996-1997 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"
+
+#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 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 debug_dumprequest(pid_t mypid) {
+  int i, fd;
+  
+  printf("server pid: %ld\n"
+         "client pid: %ld\n"
+         "service: `%s'\n"
+         "service user: `%s'\n"
+         "calling user: `%s'\n"
+         "calling cwd: `%s'\n"
+         "calling uid: %ld\n"
+         "calling gids:",
+         (long)mypid, (long)request_mbuf.clientpid,
+         service, serviceuser, logname, cwd,
+         (long)request_mbuf.callinguid);
+  for (i=0; i<request_mbuf.ngids; i++) printf(" %ld",(long)gidarray[i]);
+  printf("\n" "fds:");
+  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][0],defvararray[i][1]);
+  printf("\n");
+  if (getenv("USERVD_SLEEP")) sleep(atoi(getenv("USERVD_SLEEP")));
+}
+
+void debug_dumpexecsettings(void) {
+  printf("configuration parsed\n");
+  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);
+  printf("execute: ");
+  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;
+  default: abort();
+  }
+  printf("\n");
+  truefalsedump("set-environment",setenvironment);
+  truefalsedump("suppress-args",suppressargs);
+  truefalsedump("disconnect-hup",disconnecthup);
+  truefalsedump("set-environment",setenvironment);
+  printf("errors: ");
+  switch (ehandling) {
+  case tokv_word_errorstostderr: printf("stderr"); break;
+  case tokv_word_errorstofile: printf("file"); break;
+  case tokv_word_errorstosyslog: printf("syslog %d.%d",ehlogfacility,ehloglevel); break;
+  default: abort();
+  }
+  printf("\n");
+}
+
+void debug_dumpparameter(const char *parm, char **values) {
+  printf("config parameter `%s':",parm);
+  while (*values) printf(" `%s'",*values++);
+  printf("\n");
+}
+
+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) {
+  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/language.i4 b/language.i4
new file mode 100644 (file)
index 0000000..8d4088c
--- /dev/null
@@ -0,0 +1,243 @@
+dnl  userv - language.i4
+dnl  definition of the configuration language, used for tokens.h and lexer.l
+dnl
+dnl  Copyright (C)1996-1997 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',`01')
+define(`cautotokt',eval(`010000'))
+
+define(`autovalistype',`hasvalistype(`$1',format(``0%03o'',cautotoki),`$2')`'define(`cautotoki',incr(cautotoki))')
+
+define(`autovaldeftype',`pushdef(`odiv',divnum)divert(4)dnl
+  format(``%-25s'',`tokt_$1=')format(``0%011o'',cautotokt),
+divert(odiv)popdef(`odiv')define(`cautotokt',eval(cautotokt`*2'))')
+
+define(`nametypelexpatexec',`
+autovalistype(`$1',`$2')
+pushdef(`odiv',divnum)divert(3)dnl
+`$3 { $4'`atnewline= 0; 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(`misc')
+
+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'),`$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; ')')
+isexecmode(`reject')
+isexecmode(`execute')
+isexecmode(`execute-from-directory')
+isexecmode(`execute-from-path')
+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(`reset')
+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')
+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')
+
+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')
+isloglevelexpat(`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
+dnl things.
+nametypelexpatexec(`ordinal',`tokt_number|tokt_fdrange|tokr_word',`[0-9]{1,8}',
+`{ char *ep;
+   lr_min=lr_max= (int)strtoul(yytext,&ep,10);
+   assert(!*ep); }; ')
+nametypelexpatexec(`fdrange',`tokt_fdrange|tokr_punct',`[0-9]{1,8}-[0-9]{1,8}',
+`{ char *ep;
+   lr_min=(int)strtoul(yytext,&ep,10);
+   assert(*ep == HYPHEN); assert(*++ep);
+   lr_max=(int)strtoul(ep,&ep,10);
+   if (lr_max < lr_min) {
+     atnewline= 0; parseerrprint("fd range has min > max"); return tokv_error;
+   }
+   assert(!*ep); }; ')
+nametypelexpatexec(`fdstoend',`tokt_fdrange|tokr_punct',`[0-9]{1,8}-',
+`{ char *ep;
+   lr_min= (int)strtoul(yytext,&ep,10); lr_max=-1;
+   assert(*ep == HYPHEN); assert(!*++ep); }; ')
+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.l.m4 b/lexer.l.m4
new file mode 100644 (file)
index 0000000..2f9dee6
--- /dev/null
@@ -0,0 +1,73 @@
+dnl  userv - lexer.l.m4
+dnl  lexer, passed through m4 with defs from langauge.i4
+/*
+ *   Copyright (C)1996-1997 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/stat.h>
+#include <time.h>
+
+#include "config.h"
+#include "common.h"
+#include "daemon.h"
+#include "lib.h"
+#include "tokens.h"
+
+#define HYPHEN '-'
+
+static int lineno, atnewline, notedreferer;
+static int dequote(char *inplace);
+%}
+%option noyywrap
+%%
+
+dnl simple words
+undivert(3)
+changequote({*,*})
+{*
+[\ \t]+                        return tokv_lwsp;
+[\ \t]*\n              atnewline= 1; lineno++; return tokv_newline;
+[\ \t]*\#[^\n]*\n      atnewline= 1; lineno++; return tokv_newline;
+[\ \t]*\#[^\n]*                atnewline= 0; parseerrprint("missing newline at eof after comment"); return tokv_error;
+[^\ \t\n]+             atnewline= 0; return tokv_barestring;
+<<EOF>>                        return tokv_eof;
+\"([^\\\"\n]|\\[a-z]|\\[0-9]{3}|\\x[0-9a-f]{2}|\\[:punct:]|\\[ \t]*\n)*\" return dequote(yytext);
+\".*                   atnewline= 0; parseerrprint("misquoted or unterminated string"); return tokv_error;
+*}
+changequote(`,')
+%%
+`
+#include "parser.c"
+'
+divert(-1)
+undivert
+
diff --git a/lib.c b/lib.c
new file mode 100644 (file)
index 0000000..4af9b80
--- /dev/null
+++ b/lib.c
@@ -0,0 +1,116 @@
+/*
+ * userv - lib.c
+ * useful utility routines, used in daemon, but not very dependent on it
+ *
+ * Copyright (C)1996-1997 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 "config.h"
+#include "lib.h"
+
+char *xmstrcat3save(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 *xmstrsave(const char *s) {
+  char *r;
+
+  r= xmalloc(strlen(s)+1);
+  strcpy(r,s);
+  return r;
+}
+
+char *xmstrsubsave(const char *begin, int len) {
+  char *r;
+  
+  r= xmalloc(len+1);
+  memcpy(r,begin,len);
+  r[len]= 0;
+  return r;
+}
+
+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 *xstrdup(const char *str) {
+  char *r;
+  r= xmalloc(strlen(str)+1);
+  strcpy(r,str); return r;
+}
+
+void makeroom(char **buffer, int *size, int needed) {
+  if (*size >= needed) return;
+  *buffer= xrealloc(*buffer,needed);
+  *size= needed;
+}
+
+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);
+}
diff --git a/lib.h b/lib.h
new file mode 100644 (file)
index 0000000..8a9c348
--- /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 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 *xmstrcat3save(const char *a, const char *b, const char *c);
+char *xmstrsave(const char *s);
+char *xmstrsubsave(const char *begin, int len);
+
+void miscerror(const char *what) NONRETURNING;
+void syscallerror(const char *what) NONRETURNING;
+void *xmalloc(size_t s);
+void *xrealloc(void *p, size_t s);
+char *xstrdup(const char *str);
+void makeroom(char **buffer, int *size, int needed);
+
+/* 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);
+
+#endif /* LIB_H */
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/parser.c b/parser.c
new file mode 100644 (file)
index 0000000..e173e3a
--- /dev/null
+++ b/parser.c
@@ -0,0 +1,1217 @@
+/*
+ * 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.
+ *
+ * Copyright (C)1996-1997 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.
+ */
+
+struct parser_state {
+  int lineno, atnewline, notedreferer;
+  const char *filename;
+  YY_BUFFER_STATE ybuf;
+  struct parser_state *upstate;
+};
+
+static const char *currentfile= 0;
+static struct parser_state *parser_topstate= 0;
+
+static directive_fnt *lr_dir=0;
+static parmcondition_fnt *lr_parmcond=0;
+static parameter_fnt *lr_parameter=0;
+static int lr_loglevel, lr_logfacility, lr_min, lr_max, *lr_flag;
+static int lr_flagval, lr_controlend;
+static int lr_fdwant_readwrite;
+
+static void useless(void) { (void)yyunput; (void)useless; /* to shut up GCC ! */ }
+
+static void closeerrorfile(void) {
+  if (ehfile && !ehfilekeep) { fclose(ehfile); free(ehfilename); }
+  ehfile= 0; ehfilename= 0; ehfilekeep= 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(ehlogfacility);
+    syslog(ehloglevel,"%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(ehfile,"%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(); ehandling= tokv_word_errorstofile;
+      snyprintf(suberrmsg,sizeof(suberrmsg),
+               "error writing to error log file `%.*s': %s;"
+               " reverting to errors-to-stderr",
+               (int)(sizeof(suberrmsg)>>1),ehfilename,strerror(e));
+      senderrmsg(suberrmsg,ehandling);
+      senderrmsg(errmsg,ehandling);
+    }
+    break;
+  default:
+    abort();
+  }
+}
+
+static void errwhere(int iflineno, const char *filename, int *ifnotedreferer,
+                    struct parser_state *upstate, char *bufput, int bufputlen) {
+  static const char suffix[]= "references ...";
+  char errmsg[MAX_ERRMSG_LEN];
+  
+  if (!upstate) {
+    filename= "<initialisation>";
+  } else if (!*ifnotedreferer && upstate->upstate && upstate->upstate->upstate) {
+    errwhere(upstate->lineno-upstate->atnewline,upstate->filename,
+            &upstate->notedreferer,upstate->upstate,
+            errmsg,sizeof(errmsg)-sizeof(suffix));
+    strcat(errmsg,suffix);
+    senderrmsg(errmsg,ehandling);
+    *ifnotedreferer= 1;
+  }
+  snyprintf(bufput,bufputlen,"%.*s:%d: ",bufputlen-10,filename,iflineno);
+}
+
+void parseerrprint(const char *fmt, ...) {
+  va_list al;
+  char errmsg[MAX_ERRMSG_LEN];
+
+  va_start(al,fmt);
+  errwhere(lineno-atnewline,currentfile,&notedreferer,parser_topstate,
+          errmsg,sizeof(errmsg)>>1);
+  vsnytprintfcat(errmsg,sizeof(errmsg),fmt,al);
+  senderrmsg(errmsg,ehandling);
+  va_end(al);
+}
+
+static void freecharparray(char **array) {
+  char **pp;
+
+  if (!array) return;
+  for (pp=array; *pp; pp++) free(*pp);
+  free(array);
+}
+
+static int dequote(char *inplace) {
+  char *p, *q, buf[4], *bep;
+  int v;
+  
+  p=q=inplace;
+  assert(*p++ = '"');
+  while (*p && *p != '"') {
+    if (*p != '\\') { *q++= *p++; continue; }
+    switch (*++p) {
+    case 'n': *q++= '\n'; continue;
+    case 'r': *q++= '\r'; continue;
+    case 't': *q++= '\t'; continue;
+    case 'x':
+      assert(buf[0]= *++p); assert(buf[1]= *++p); buf[2]= 0;
+      v= strtoul(buf,&bep,16); assert(bep == buf+2);
+      assert(!(v & ~0xff)); *q++= v; p++; continue;
+    default:
+      if (isalpha(*p)) {
+        parseerrprint("unknown \\<letter> sequence \\%c in quoted string",*p);
+        return tokv_error;
+      } else if (isdigit(*p)) {
+        assert(buf[0]= *++p); assert(buf[1]= *++p); assert(buf[2]= *++p);
+        buf[3]= 0; v= strtoul(buf,&bep,8);
+        if (bep != buf+3 || (v & ~0xff)); {
+          parseerrprint("invalid \\<octal> sequence \\%s in quoted string",buf);
+          return tokv_error;
+        }
+        *q++= v; p++; continue;
+      } else if (ispunct(*p)) {
+        *q++= *p++; continue;
+      } else {
+       while (*p==' ' || *p=='\t') p++;
+       assert(*p=='\n');
+      }
+    }
+  }
+  assert(*p); assert(!*++p); return tokv_quotedstring;
+}
+
+const char *printtoken(int token) {
+  static char buf[250];
+  
+  char *q;
+  const char *p;
+  int i, c;
+  
+  if ((token & tokm_repres) == tokr_word) {
+    assert(strlen(yytext)+50<sizeof(buf));
+    sprintf(buf,"word `%s'",yytext);
+    return buf;
+  } else if (token & tokt_number) {
+    sprintf(buf,"number %d",lr_min); return buf;
+  } else if (token & tokt_fdrange) {
+    sprintf(buf,"fdrange %d..%d",lr_min,lr_max); return buf;
+  } else if ((token & tokm_repres) == tokr_punct) {
+    assert(strlen(yytext)+50<sizeof(buf));
+    sprintf(buf,"operator `%s'",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," `");
+    i= sizeof(buf)-50; p= yytext; q= buf+strlen(buf);
+    while (i-- >0 && (c= *p++)) {
+      if (isspace(c)) c= ' ';
+      else if (!isprint(c) || iscntrl(c)) c= '?';
+      *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) {
+  static char *p=0;
+  static int pl=0;
+  
+  int l;
+  
+  if (strncmp(in,USERDIRPREFIX,sizeof(USERDIRPREFIX)-1)) return in;
+  l= strlen(serviceuser_dir)+strlen(in)+1-(sizeof(USERDIRPREFIX)-1)+
+     sizeof(DIRSEP)-1;
+  if (l>pl) { p= realloc(p,l); pl=l; }
+  strcpy(p,serviceuser_dir); strcat(p,DIRSEP);
+  strcat(p,in+(sizeof(USERDIRPREFIX)-1));
+  return p;
+}
+
+static void parser_push(struct parser_state *saveinto,
+                       const char *newfile) {
+  saveinto->lineno= lineno;
+  saveinto->atnewline= atnewline;
+  saveinto->filename= currentfile;
+  saveinto->notedreferer= notedreferer;
+  saveinto->ybuf= YY_CURRENT_BUFFER;
+  saveinto->upstate= parser_topstate;
+
+  lineno= 1;
+  notedreferer= 0;
+  currentfile= newfile;
+  parser_topstate= saveinto;
+}
+
+static void parser_pop(void) {
+  YY_BUFFER_STATE ybuf;
+  ybuf= YY_CURRENT_BUFFER;
+  
+  lineno= parser_topstate->lineno;
+  atnewline= parser_topstate->atnewline;
+  currentfile= parser_topstate->filename;
+  notedreferer= parser_topstate->notedreferer;
+  if (parser_topstate->ybuf) yy_switch_to_buffer(parser_topstate->ybuf);
+  parser_topstate= parser_topstate->upstate;
+  
+  yy_delete_buffer(ybuf);
+}
+
+/* parser component functions pa_ return tokv_error or 0,
+ *  having scanned exactly what they were expecting
+ * complete argument list parsers paa_ return tokv_error or 0,
+ *  having scanned up to and including the newline
+ */
+
+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) {
+    parseerrprint("found %s, expected %s",printtoken(found),wantedstr);
+  }
+  return tokv_error;
+}
+
+static int pa_mnl(void) {
+  return unexpected(yylex(),tokv_newline,"newline");
+}
+static int pa_mwsp(void) {
+  return unexpected(yylex(),tokv_lwsp,"linear whitespace");
+}
+
+static void parm_1string(char ***rvalues, const char *tocopy) {
+  char **a;
+  a= xmalloc(sizeof(char*)*2);
+  a[0]= xstrdup(tocopy);
+  a[1]= 0;
+  *rvalues= a;
+}
+
+static int pa_string(const char **rv) {
+  /* Value returned in *rv is overwritten by repeated calls */
+  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;
+  makeroom(&p,&pl,l);
+  strcpy(p,yytext); *rv= p;
+
+  return 0;
+}  
+
+static int paa_1string(const char **rv) {
+  /* Value returned in *rv is overwritten by repeated calls */
+  int r;
+
+  r= pa_string(rv); if (r) return r;
+  return pa_mnl();
+}  
+
+static int paa_1path(const char **rv) {
+  /* Value returned in *rv is overwritten by repeated calls */
+  const char *cp;
+  int r;
+  
+  r= paa_1string(&cp); if (r) return r;
+  *rv= string2path(cp); return 0;
+}
+
+static int pa_parameter(char ***rvalues) {
+  /* Scans a single parameter token and calls the appropriate parameter
+   * function, returning tokv_error or 0 just like the parameter function.
+   */
+  int token, r, i;
+
+  token= yylex(); if (token == tokv_error) return token;
+  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][0]);
+        i++);
+    if (i>=request_mbuf.nvars) {
+      *rvalues= xmalloc(sizeof(char*));
+      **rvalues= 0;
+    } else {
+      parm_1string(rvalues,defvararray[i][1]);
+    }
+  } else {
+    if (!(token & tokt_parameter)) return unexpected(token,-1,"parameter name");
+    r= (lr_parameter)(token,rvalues);
+    if (r) return r;
+  }
+  debug_dumpparameter(yytext,*rvalues);
+  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) 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 (;;) {
+      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); if (r) return r;
+    r= (lr_parmcond)(token,parmvalues,rtrue); freecharparray(parmvalues);
+    return r;
+  }
+  return unexpected(token,-1,"condition");
+}
+
+static int pa_numberdollar(int *rv) {
+  /* 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;
+  *rv= lr_min; return r;
+}
+
+/* Main parser routines */
+
+static int skiptoeol(void) {
+  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);
+  parseerrprint("unexpected end of file while looking for end of line");
+  return tokv_error;
+}
+
+static int skip(int allowce) {
+  /* Scans a piece of config without executing it.
+   * Returns tokv_error, or the tokt_controlend token
+   * with type allowce if one was found.
+   */
+  int token, r;
+
+  for (;;) { /* loop over lines */
+    do { token= yylex(); } while (token == tokv_lwsp);
+    if (token & tokt_exception) {
+      return token;
+    } else if (token & tokt_controlend) {
+      if (allowce == lr_controlend) return token;
+      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;
+        r= skip(token); if (r & tokt_exception) return r;
+      }
+    } else if (!(token & tokt_directive) && !(token & tokt_condop)) {
+      parseerrprint("not a directive (or conditional operator) "
+                   "while looking for control structure end");
+      return tokv_error;
+    }
+    r= skiptoeol(); if (r) 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' not scanned yet
+   */
+  int token, r;
+
+  for (;;) { /* loop over lines */
+    do { token= yylex(); } while (token == tokv_lwsp);
+    if (token & tokt_exception) {
+      return token;
+    } else if (token & tokt_controlend) {
+      if (lr_controlend == allowce) return token;
+      return unexpected(token,-1,"directive (not this kind of control structure end)");
+    } else if (token & tokt_directive) {
+      r= (lr_dir)(token); if (r) { assert(r & tokt_exception); return r; }
+    } else if (token == tokv_newline) {
+      /* ignore blank links (and comment-only lines) */
+    } else {
+      return unexpected(token,-1,"directive");
+    }
+  }
+}
+
+int parse_string(const char *string, const char *descrip) {
+  /* Returns the same things as parser, except that tokv_eof
+   * is turned into 0. */
+  struct parser_state save;
+  YY_BUFFER_STATE ybuf;
+  int r;
+  
+  parser_push(&save,descrip);
+  ybuf= yy_scan_string(string);
+  if (!ybuf) syscallerror("unable to create flex buffer for internal string");
+  yy_switch_to_buffer(ybuf);
+  
+  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. */
+  static int fileparselevel= 0;
+  
+  struct parser_state save;
+  YY_BUFFER_STATE ybuf;
+  int r;
+  FILE *file;
+
+  if (fileparselevel >= MAX_INCLUDE_NEST) {
+    parseerrprint("too many nested levels of included files");
+    return tokv_error;
+  }
+  file= fopen(string,"r"); if (!file) {
+    if (errno == ENOENT) {
+      if (didexist) *didexist= 0;
+      return 0;
+    }
+    parseerrprint("unable to open config file `%s': %s",string,strerror(errno));
+    return tokv_error;
+  }
+  if (didexist) *didexist= 1;
+
+  parser_push(&save,string);
+  ybuf= yy_create_buffer(file,YY_BUF_SIZE);
+  if (!ybuf) syscallerror("unable to create flex buffer for file");
+  yy_switch_to_buffer(ybuf);
+  fileparselevel++;
+  
+  r= parser(0);
+  if (ferror(file)) {
+    parseerrprint("error reading configuration file `%s'",string);
+    r= tokv_error;
+  }
+  
+  fileparselevel--;
+  parser_pop();
+  fclose(file);
+  if (r == tokv_eof) r= 0;
+  return r;
+}
+
+/* Parameter-based conditional functions (parmcondition's) */
+
+int pcf_glob(int ctoken, char **pv, int *rtrue) {
+  int token, actrue, r;
+  char **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 **pv, int *rtrue) {
+  int mintoken, min, maxtoken, max, r;
+  char **pp, *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 **pv, int *rtrue) {
+  FILE *file;
+  const char *cp;
+  char **pp, *buf, *p;
+  int r, maxlen, l, c, actrue, posstrue;
+  
+  r= paa_1path(&cp); if (r) return r;
+  file= fopen(cp,"r"); if (!file) {
+    parseerrprint("unable to open file `%s' for grep: %s",cp,strerror(errno));
+    return tokv_error;
+  }
+  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 (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 || isspace(c)) {
+      while (p>buf && isspace(p[-1])) --p;
+      *p= 0; posstrue= 0;
+      for (pp= pv; *pp; pp++) if (!strcmp(*pp,buf)) { posstrue= 1; break; }
+    } else {
+      posstrue= 0;
+    }
+    if (c!='\n' && c!=EOF) {
+      for (;;) {
+        c= getc(file);
+        if (c==EOF || c=='\n') break;
+        if (!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;
+} 
+
+/* Parameter functions and associated `common code' functions */
+
+int pf_service(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,service); return 0;
+}
+
+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]= xstrdup(name);
+  a[1]= parm_ulong(id);
+  a[2]= 0;
+  *rvalues= a; return 0;
+}
+
+int pf_callinguser(int ptoken, char ***rvalues) {
+  return parm_usernameuid(rvalues,logname,request_mbuf.callinguid);
+}
+
+int pf_serviceuser(int ptoken, char ***rvalues) {
+  return parm_usernameuid(rvalues,serviceuser,serviceuser_uid);
+}
+
+static char *parm_gidname(gid_t id) {
+  static char ebuf[200];
+
+  struct group *ge;
+
+  ge= getgrgid(id);
+  if (!ge) {
+    sprintf(ebuf,"look up group with id %lu",(unsigned long)id);
+    syscallerror(ebuf);
+  }
+  return xstrdup(ge->gr_name);
+}
+
+static int parm_gids(char ***rvalues, int size, gid_t *list) {
+  char **a;
+  int i;
+  
+  if (size >= 2 && list[0] == list[1]) { size--; list++; }
+  a= xmalloc(sizeof(char*)*(size+1)*2);
+  for (i=0; i<size; i++) {
+    a[i]= parm_gidname(list[i]);
+    a[size+i]= parm_ulong(list[i]);
+  }
+  a[size*2]= 0;
+  *rvalues= a; return 0;
+}  
+
+int pf_callinggroup(int ptoken, char ***rvalues) {
+  return parm_gids(rvalues,request_mbuf.ngids,gidarray);
+}
+
+int pf_servicegroup(int ptoken, char ***rvalues) {
+  static int size=-1;
+  static gid_t *list;
+
+  if (size == -1) {
+    size= getgroups(0,0); if (size == -1) syscallerror("getgroups(0,0)");
+    list= xmalloc(sizeof(gid_t)*(size+1));
+    if (getgroups(size,list+1) != size) syscallerror("getgroups(size,list)");
+    list[0]= serviceuser_gid;
+  }
+  return parm_gids(rvalues,size,list);
+}
+
+int pf_callingusershell(int ptoken, char ***rvalues) {
+  struct passwd *pw;
+
+  pw= getpwnam(logname); if (!pw) syscallerror("looking up calling user");
+  parm_1string(rvalues,pw->pw_shell); return 0;
+}
+
+int pf_serviceusershell(int ptoken, char ***rvalues) {
+  parm_1string(rvalues,serviceuser_shell); return 0;
+}
+
+/* Directive functions and associated `common code' functions */
+
+int df_reject(int dtoken) {
+  int r;
+  
+  r= pa_mnl(); if (r) return r;
+  execute= tokv_word_reject;
+  free(execpath); execpath= 0;
+  freecharparray(execargs); execargs= 0;
+  return 0;
+}
+
+int df_executefrompath(int dtoken) {
+  int r;
+  
+  r= pa_mnl(); if (r) return r;
+  execute= tokv_word_executefrompath;
+  free(execpath); execpath= 0;
+  freecharparray(execargs); execargs= 0;
+  return 0;
+}
+
+static int paa_pathargs(const char **rv, char ***newargs_r) {
+  /* Repeated calls do _not_ overwrite newargs_r; caller must free.
+   * Repeated calls _do_ overwrite string returned in rv.
+   */
+  char **newargs;
+  int used, size, r;
+
+  used=0; size=0;
+  newargs= xmalloc(sizeof(char*)*(size+1));
+  r= pa_string(rv); if (r == tokv_error) return r;
+  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) {
+      size= (used+5)<<2;
+      newargs= xrealloc(newargs,sizeof(char*)*(size+1));
+    }
+    newargs[used++]= xmstrsave(yytext);
+  }
+  newargs[used]= 0;
+  *newargs_r= newargs;
+  return 0;
+  
+error:
+  newargs[used]=0;
+  freecharparray(newargs);
+  return r;
+}
+
+int df_execute(int dtoken) {
+  const char *rv;
+  char **newargs;
+  int r;
+
+  r= paa_pathargs(&rv,&newargs); if (r) return r;
+  execute= tokv_word_execute;
+  freecharparray(execargs); execargs= newargs;
+  free(execpath); execpath= xmstrsave(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 || !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 (!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)+sizeof(DIRSEP)+strlen(p);
+  fn= xmalloc(l); strcpy(fn,rv); strcat(fn,DIRSEP); strcat(fn,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%o)",fn,stab.st_mode);
+    free(fn); freecharparray(newargs); return tokv_error;
+  }
+  execute= tokv_word_executefromdirectory;
+  freecharparray(execargs); execargs= newargs;
+  free(execpath); execpath= fn;
+  return 0;
+}
+
+int df_errorstostderr(int dtoken) {
+  int r;
+  
+  r= pa_mnl(); if (r) return r;
+  closeerrorfile(); ehandling= 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 somewhere after errors-to-syslog"))
+    return tokv_error;
+  closeerrorfile(); ehandling= tokv_word_errorstosyslog;
+  ehlogfacility= facility; ehloglevel= 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) {
+    parseerrprint("unable to open error log file `%s': %s",cp,strerror(errno));
+    return tokv_error;
+  }
+  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(); ehandling= tokv_word_errorstofile;
+  ehfile= file; ehfilename= xmstrsave(cp); ehfilekeep= 0; return 0;
+}
+
+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>");
+  assert(!r); return 0;  
+}
+
+int df_cd(int dtoken) {
+  const char *cp;
+  int r;
+
+  r= paa_1path(&cp); if (r) return r;
+  if (!chdir(cp)) return 0;
+  parseerrprint("unable to change directory to `%s': %s",cp,strerror(errno));
+  return tokv_error;
+}
+
+int df_userrcfile(int dtoken) {
+  const char *cp;
+  int r;
+
+  r= paa_1path(&cp); if (r) return r;
+  free(userrcfile); userrcfile= xstrdup(cp);
+  return 0;
+}
+
+int dfi_includeuserrcfile(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  if (userrcfile) return parse_file(userrcfile,0);
+  parseerrprint("_include-user-rcfile (for-internal-use directive) "
+               "found but user-rcfile not set"); return tokv_error;
+}
+
+int dfi_includeclientconfig(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  if (!overridedata) {
+    parseerrprint("_include-client-config (for-internal-use directive) "
+                 "found but configuration not overridden");
+    return tokv_error;
+  }
+  return parse_string(overridedata,"<configuration override data>");
+}
+
+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;
+  parseerrprint(dtoken == tokv_word_includesysconfig ?
+               "system configuration file `%s' does not exist" :
+               "included file `%s' does not exist",
+               cp);
+  return tokv_error;
+}
+
+static int paa_message(const char **message_r) {
+  static char *buildbuf;
+  static int buildbuflen;
+
+  int r, tl;
+
+  r= pa_mwsp(); if (r) return r;
+  tl= 1;
+  makeroom(&buildbuf,&buildbuflen,10);
+  buildbuf[0]= 0;
+  for (;;) {
+    r= yylex(); if (r == tokv_error) return r;
+    if (r == tokv_eof) {
+      parseerrprint("unexpected end of file in message text"); return tokv_error;
+    }
+    if (r == tokv_newline) break;
+    tl+= strlen(yytext);
+    makeroom(&buildbuf,&buildbuflen,tl);
+    strcat(buildbuf,yytext);
+  }
+  *message_r= buildbuf;
+  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;
+  parseerrprint("`error' directive: %s",mp); return tokv_error;
+}
+
+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, *cp;
+  
+  r= paa_1path(&cp); if (r) return r;
+  d= opendir(cp);
+  if (!d) {
+    parseerrprint("unable to open directory `%s': %s",cp,strerror(errno));
+    return tokv_error;
+  }
+  cpl= strlen(cp);
+  while ((de= readdir(d))) {
+    tel= strlen(de->d_name);
+    if (!tel) continue;
+    p= de->d_name;
+    if (!isalnum(*p)) continue;
+    while ((c= *++p)) if (!(isalnum(c) || c=='-')) break;
+    if (c) continue;
+    makeroom(&buildbuf,&buildbuflen,cpl+tel+sizeof(DIRSEP));
+    strcpy(buildbuf,cp);
+    strcat(buildbuf,DIRSEP);
+    strcat(buildbuf,de->d_name);
+    r= parse_file(buildbuf,&found); if (r) { closedir(d); return r; }
+    if (!found) {
+      parseerrprint("unable to open file `%s' in included directory `%s': %s",
+                   de->d_name,cp,strerror(errno));
+      closedir(d); return tokv_error;
+    }
+  }
+  if (closedir(d)) {
+    parseerrprint("error closing directory `%s': %s",cp,strerror(errno));
+    return tokv_error;
+  }
+  return 0;
+}
+
+int df_includelookup(int dtoken) {
+  static char *buildbuf=0;
+  int buildbuflen=0;
+  
+  char **parmvalues, **pp, *p, *q;
+  const char *cp;
+  struct stat stab;
+  int r, done, thisdone, cpl, c;
+
+  r= pa_mwsp(); if (r) return r;
+  r= pa_parameter(&parmvalues); if (r) return r;
+  r= paa_1path(&cp); if (r) { freecharparray(parmvalues); return r; }
+  if (stat(cp,&stab)) {
+    parseerrprint("unable to access directory `%s': %s",cp,strerror(errno));
+    freecharparray(parmvalues); return tokv_error;
+  }
+  if (!S_ISDIR(stab.st_mode)) {
+    parseerrprint("object `%s' is not a directory or link to one",cp);
+    freecharparray(parmvalues); return tokv_error;
+  }
+  done= 0;
+  cpl= strlen(cp);
+  if (!parmvalues[0]) {
+    makeroom(&buildbuf,&buildbuflen,cpl+sizeof(DIRSEP NONEINCLUDELOOKUP));
+    strcpy(buildbuf,cp);
+    strcat(buildbuf,DIRSEP NONEINCLUDELOOKUP);
+    r= parse_file(buildbuf,&thisdone);
+    if (r) { freecharparray(parmvalues); return r; }
+    if (thisdone) done= 1;
+  } else {
+    for (pp=parmvalues;
+        *pp && (!done || dtoken == tokv_word_includelookupall);
+        pp++) {
+      makeroom(&buildbuf,&buildbuflen,
+              cpl+sizeof(DIRSEP)+strlen(*pp)*2+3+sizeof(EMPTYINCLUDELOOKUP));
+      strcpy(buildbuf,cp);
+      strcat(buildbuf,DIRSEP);
+      p= *pp; q= buildbuf+cpl+sizeof(DIRSEP)-1;
+      if (*p=='.') *q++= ':';
+      while ((c= *p++)) {
+       if (c=='/') { *q++= ':'; c='-'; }
+       else if (c==':') { *q++= ':'; }
+       *q++= c;
+      }
+      *q++= 0;
+      r= parse_file(buildbuf,&thisdone);
+      if (r) { freecharparray(parmvalues); return r; }
+      if (thisdone) done= 1;
+    }
+  }
+  freecharparray(parmvalues);
+  if (!done) {
+    makeroom(&buildbuf,&buildbuflen,
+             cpl+sizeof(DIRSEP)+sizeof(DEFAULTINCLUDELOOKUP));
+    strcpy(buildbuf,cp);
+    strcat(buildbuf,DIRSEP DEFAULTINCLUDELOOKUP);
+    r= parse_file(buildbuf,0); if (r) return r;
+  }
+  return 0;
+}
+
+int df_catchquit(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  r= parser(tokv_word_catchquit);
+  if (r & tokt_controlend) {
+    assert(r == tokv_word_hctac);
+    r= pa_mnl();
+  } else if (r == tokv_quit || r == tokv_error) {
+    if (r == tokv_error) {
+      r= parse_string(RESET_CONFIGURATION,
+                     "<builtin reset configuration (caught error)>");
+      assert(!r);
+      while (!atnewline) {
+       r= yylex(); if (r == tokv_error) return 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 & tokv_word_if)) return r;
+  } while (r == tokv_word_elif);
+  if (r == tokv_word_else) {
+    r= pa_mnl(); if (r) return r;
+    if (done) r= skip(tokv_word_if);
+    else r= parser(tokv_word_if);
+    if (!(r & tokv_word_if)) return r;
+  }
+  if (unexpected(r,tokv_word_fi,"`fi' to end `if'")) return tokv_error;
+  return pa_mnl();
+}
+
+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 r;
+  fdmin= lr_min; fdmax= lr_max;
+  r= yylex(); if (r == tokv_error) return r;
+  if (r == tokv_newline) {
+    if (needreadwrite > 0) {
+      parseerrprint("read or write is required"); return tokv_error;
+    }
+    havereadwrite= 0;
+  } else if (r == tokv_lwsp) {
+    if (needreadwrite < 0) {
+      parseerrprint("read or write not allowed"); return tokv_error;
+    }
+    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))
+      parseerrprint("unspecified maximum only allowed with reject-fd and ignore-fd");
+    fdmax= fdarrayused-1;
+    restfdwantstate= dtoken;
+    restfdwantrw= havereadwrite;
+  }    
+  for (fd=fdmin; fd<=fdmax; fd++) {
+    fdarray[fd].wantstate= dtoken;
+    fdarray[fd].wantrw= havereadwrite;
+  }
+  return 0;
+}
+
+int df_quit(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  return tokv_quit;
+}
+
+int df_eof(int dtoken) {
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+  return tokv_eof;
+}
+
+int df_errorspush(int dt) {
+  int saveehandling, saveehlogfacility, saveehloglevel, saveehfilekeep;
+  FILE *saveehfile;
+  char *saveehfilename;
+  int r;
+
+  r= pa_mnl(); if (r) return r;
+
+  saveehandling= ehandling;
+  saveehlogfacility= ehlogfacility;
+  saveehloglevel= ehloglevel;
+  saveehfile= ehfile;
+  saveehfilekeep= ehfilekeep;
+  saveehfilename= ehfilename;
+  if (ehandling == tokv_word_errorstofile) ehfilekeep= 1;
+
+  r= parser(tokv_word_errorspush);
+  
+  ehandling= saveehandling;
+  ehlogfacility= saveehlogfacility;
+  ehloglevel= saveehloglevel;
+  ehfile= saveehfile;
+  ehfilekeep= saveehfilekeep;
+  ehfilename= saveehfilename;
+
+  if (r & tokt_controlend) {
+    assert(r == tokv_word_srorre);
+    r= pa_mnl();
+  }
+  return r;
+}
diff --git a/spec.sgml b/spec.sgml
new file mode 100644 (file)
index 0000000..9c3f1e8
--- /dev/null
+++ b/spec.sgml
@@ -0,0 +1,1267 @@
+<!doctype debiandoc system>
+<book>
+<title>User service daemon and client specification
+<author>Ian Jackson <email>ian@chiark.greenend.org.uk
+<version>draft 0.17
+
+<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>
+Copyright 1996-1997 Ian Jackson.
+<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 <tt/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/ ...]
+</example>
+<p>
+
+<var/service-user/ specifies which user is to provide the service.
+The user may be a login name or a numeric uid.
+<p>
+
+The service name is interpreted by the userv<footnote><tt/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/-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/.
+
+<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/read/ had been specified, except that if the
+filedescriptor 1 or 2 of the service is being opened (either specified
+numerically or with <tt/stdout/ or <tt/stderr/) 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 <tt/--file/ or <tt/--fdwait/ option - even by a <tt/--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 <tt/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
+<tt/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>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/.
+
+</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
+<tt/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
+<tt/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 <tt/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
+<tt/--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>Configuration file directives
+<p>
+
+<sect1>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 (<tt/if/, <tt/catch-quit/ or
+<tt/errors-push/) which were started in that file will be considered
+finished.  Parsing will continue in the file which caused the file
+containing the <tt/eof/ to be read.
+
+<tag/<tt/quit//
+<item>
+Stop reading configuration files and act immediately on the current
+settings.  The behaviour of <tt/quit/ is subject to the
+<tt/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 <tt/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 <tt/if/, above).  If
+<var/parameter/ has several values they will be tried in order; with
+<tt/include-lookup/ this search will stop when one is found, but with
+<tt/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>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
+<tt/system.default/ configuration file has been read.  This directive
+has no effect in a user's configuration file or in the
+<tt/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/daemon/; the default <var/level/ is <tt/error/.
+</taglist>
+
+<sect1>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 <tt/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
+<tt/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 <tt/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 <tt/-D/ 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 <tt/errors-push/ and
+<tt/srorre/.
+
+<tag/<tt/catch-quit//
+<tag/<tt/hctac//
+<item>
+Any use of <tt/quit/ inside <tt/catch-quit/ will merely cause the
+parsing to continue at <tt/hctac/ instead.  Any control constructs
+started since the <tt/catch-quit/ will be considered finished if a
+<tt/quit/ is found.
+<p>
+
+If an error occurs inside <tt/catch-quit/ the execution settings will
+be reset (as if by the <tt/reset/ directive) and parsing will likewise
+continue at <tt/hctac/.
+<p>
+
+If a serious lexical or syntax error is detected in the same
+configuration file as the <tt/catch-quit/, while looking for the
+<tt/hctac/, that error will not be caught.
+
+</taglist>
+
+<sect1>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.  <tt/execute/, <tt/execute-from-directory/ and
+<tt/execute-from-path/ will change this setting.
+
+<tag/<tt/execute <var/pathname/ [<var/argument/ ...]//
+<item>
+Execute the program <var/pathname/, with the arguments as specified,
+followed by any arguments given to the client if <tt/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).
+
+<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
+<tt/--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/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>
+<tt/no-set-environment/ cancels the effect of <tt/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 <tt/execute/ or <tt/execute-from-directory/
+directive.  <tt/suppress-args/ undoes the effect of
+<tt/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 <tt/require-fd/, <tt/allow-fd/,
+<tt/ignore-fd/, <tt/null-fd/ or <tt/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 <tt/reject-fd/ and <tt/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
+(<tt/require-fd/ or <tt/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.
+<tt/no-disconnect-hup/ cancels <tt/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 <tt/execute/, <tt/execute-from-path/ or
+<tt/execute-from-directory/ is interpreted before all the files are
+read then the request is rejected.
+
+
+<sect>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 <tt/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 <tt/catch-quit/ construct.
+In this case the settings controlling the program's execution will be
+reset to the defaults as if a <tt/reset/ directive had been issued,
+and parsing continues after <tt/hctac/.
+
+
+<sect>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 <tt/override/ options to the client is used then it will
+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 <tt/service/ parameter, and is used by the
+<tt/execute-from-directory/ and <tt/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
+<tt/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 <tt/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 <tt/disconnect-hup/ is set they will also cause the service to get
+a <prgn/SIGHUP/.
+
+<item>
+The value of the <tt/LOGNAME/ (or <tt/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 <tt/calling-user/ parameter and are passed to the
+service program in environment variables <tt/USERV_USER/ and
+<tt/USERV_UID/.
+<p>
+
+The shell corresponding to that login name (according to the password
+entry) is available as in the configuration language's
+<tt/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
+<tt/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 <tt/--hidecwd/,
+to the service program in the <tt/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/-D<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>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>
+
+Using <prgn/userv/ many of these subsystems 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.
+
+<sect>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 <tt/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 <tt/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
+probably creating a separate subdirectory of it as a dotfile to
+contain many subsystems' state) has fewer implications for the rest of
+the system and makes it entirely clear where the security boundaries
+lie.
+
+<sect><prgn/userv/ is not a replacement for <prgn/really/ and <prgn/sudo/
+<p>
+
+<prgn/userv/ is not intended as a general-purpose system
+administration tool with which system administrators can execute
+privileged programs 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>
+
+Its facilities for restricting activities to running certain programs
+may at first glance seem to provide similar functionality to
+<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>.  However, the
+separation mentioned above is a problem here too, particular for
+interaction - it can be hard for a <prgn/userv/ service program to
+interact with its real caller or the user in question.
+
+<sect>Don't give access to general-purpose utilities
+<p>
+
+Do not specify general purpose programs like <prgn/mv/ or <prgn/cat/
+in <tt/execute-/ directives without careful thought about their
+arguments, and certainly not if <tt/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/tokens.h.m4 b/tokens.h.m4
new file mode 100644 (file)
index 0000000..21a3dbe
--- /dev/null
@@ -0,0 +1,91 @@
+dnl  userv - tokens.h.m4
+dnl  token values, passed through m4 with defs from langauge.i4
+/*  
+ *   Copyright (C)1996-1997 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=           000000000777,
+  tokm_repres=             000000007000,
+  tokm_type=               017777770000,
+  tokr_nonstring=          000000001000,
+  tokr_word=               000000002000,
+  tokr_punct=              000000003000,
+  tokr_string=             000000004000,
+undivert(4)
+undivert(1)
+undivert(2)
+};
+
+typedef int directive_fnt(int dtoken);
+directive_fnt df_reject, df_execute, df_executefrompath;
+directive_fnt df_executefromdirectory;
+directive_fnt df_errorstostderr, df_errorstosyslog, df_errorstofile;
+directive_fnt dfg_fdwant, dfg_setflag;
+directive_fnt df_reset, df_cd, df_userrcfile, df_include;
+directive_fnt df_includelookup, df_includedirectory;
+directive_fnt df_message, df_error, df_quit, df_eof;
+directive_fnt df_if, df_catchquit, df_errorspush;
+directive_fnt dfi_includeuserrcfile, dfi_includeclientconfig;
+/* directive functions return:
+ *  0 for success
+ *   having scanned up to and including end of line but not beyond
+ *  an exception (eg tokv_error) for failure of some kind
+ */
+
+typedef int parmcondition_fnt(int ctoken, char **parmvalues, int *rtrue);
+parmcondition_fnt pcf_glob, pcf_range, pcf_grep;
+/* all conditional functions return tokv_error 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 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);
+parameter_fnt pf_service;
+parameter_fnt pf_callinguser, pf_serviceuser;
+parameter_fnt pf_callinggroup, pf_servicegroup;
+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.
+ */
+
+int yylex(void);
+/* Returns a token (which may be a eof or error exception) */
+
+extern directive_fnt *lr_dir;
+extern parmcondition_fnt *lr_parmcond;
+extern parameter_fnt *lr_parameter;
+extern int lr_loglevel, lr_logfacility, lr_min, lr_max, *lr_flag;
+extern int lr_flagval, lr_controlend;
+extern int lr_fdwant_readwrite; /* -1=never, 0=opt, 1=always */
+
+undivert(4)
+
+#endif
+
+divert(-1)
+undivert