From: ian Date: Sun, 24 Aug 1997 21:36:21 +0000 (+0000) Subject: Initial CVS checkin. X-Git-Tag: release-0-50~17 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?p=userv.git;a=commitdiff_plain;h=703b99b834625829d6b285e5bca619475ef54511 Initial CVS checkin. --- 703b99b834625829d6b285e5bca619475ef54511 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