From 703b99b834625829d6b285e5bca619475ef54511 Mon Sep 17 00:00:00 2001 From: ian Date: Sun, 24 Aug 1997 21:36:21 +0000 Subject: [PATCH 1/1] Initial CVS checkin. --- .cvsignore | 13 + COPYING | 339 ++++++++++++++ Makefile.in | 78 ++++ acconfig.h | 84 ++++ client.c | 1038 +++++++++++++++++++++++++++++++++++++++++ common.h | 136 ++++++ configure.in | 132 ++++++ daemon.c | 805 ++++++++++++++++++++++++++++++++ daemon.h | 143 ++++++ ddebug.c | 201 ++++++++ language.i4 | 243 ++++++++++ lexer.l.m4 | 73 +++ lib.c | 116 +++++ lib.h | 59 +++ overview.fig | 85 ++++ parser.c | 1217 ++++++++++++++++++++++++++++++++++++++++++++++++ spec.sgml | 1267 ++++++++++++++++++++++++++++++++++++++++++++++++++ tokens.h.m4 | 91 ++++ 18 files changed, 6120 insertions(+) create mode 100644 .cvsignore create mode 100644 COPYING create mode 100644 Makefile.in create mode 100644 acconfig.h create mode 100644 client.c create mode 100644 common.h create mode 100644 configure.in create mode 100644 daemon.c create mode 100644 daemon.h create mode 100644 ddebug.c create mode 100644 language.i4 create mode 100644 lexer.l.m4 create mode 100644 lib.c create mode 100644 lib.h create mode 100644 overview.fig create mode 100644 parser.c create mode 100644 spec.sgml create mode 100644 tokens.h.m4 diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..d3dd2ec --- /dev/null +++ b/.cvsignore @@ -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 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. + + 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.) + +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. + + 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. + + 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 + + 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. + + + Copyright (C) 19yy + + 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. + + , 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 index 0000000..d880a3b --- /dev/null +++ b/Makefile.in @@ -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 index 0000000..998b8e3 --- /dev/null +++ b/acconfig.h @@ -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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 [--] [ ...]\n" + "options: -f|--file []=\n" + " -D|--defvar =\n" + " -t|--timeout \n" + " -S|--signals |number|number-nocore|highbit|stdout\n" + " -w|--fdwait =wait|nowait|close\n" + " -P|--sigpipe -H|--hidecwd -h|--help --copyright\n" + " --override } available only to\n" + " --override-file } 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=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 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; idata.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) 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; fdpw_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=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; i2) + 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) 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 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 index 0000000..ce9285a --- /dev/null +++ b/configure.in @@ -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(,,,,,) +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 ], [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(,) +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 +#include +],[ + 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; iname; sei++) + if (setenv(sei->name,sei->fn(),1)) syscallservfail("setenv standard"); + for (i=0; ienvvarbufsize) { 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>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= 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); + } + + argarray= xmalloc(sizeof(char*)*(request_mbuf.nargs)); + for (i=0; ipw_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; igr_name); + } + + if (overridedata) { + r= parse_string(TOPLEVEL_OVERRIDDEN_CONFIGURATION, + ""); + } else { + r= parse_string(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; fd1) { 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 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 + +#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 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 +#include +#include +#include +#include +#include +#include +#include + +#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\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=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 index 0000000..8d4088c --- /dev/null +++ b/language.i4 @@ -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 index 0000000..2f9dee6 --- /dev/null +++ b/lexer.l.m4 @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +<> 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 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 +#include +#include +#include +#include +#include + +#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 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 index 0000000..97ff7b5 --- /dev/null +++ b/overview.fig @@ -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 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= ""; + } 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,¬edreferer,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 \\ 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 \\ 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)+500 && (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) { + *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; ipw_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,""); + 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,""); +} + +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, + ""); + 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 index 0000000..9c3f1e8 --- /dev/null +++ b/spec.sgml @@ -0,0 +1,1267 @@ + + +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/&/ and <tt/|// +<item> +<example> +( <var/condition/ +& <var/condition/ +& <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 index 0000000..21a3dbe --- /dev/null +++ b/tokens.h.m4 @@ -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 -- 2.30.2