+userv (0.57); urgency=high
+
+ * Services provided by root work !
+ * uservd can now go into background itself (-daemon option).
+
+ * spec now has default syslog facility for rcfile messages as `user'.
+ * Better prioritisation of syslog messages.
+ * Startup error messages now go to stderr instead.
+ * SIGTERM and SIGINT now produce a syslog message.
+
+ * Version number has VEREXT component, settable via make args &c.
+ * New sections in INSTALL about exit statuses and -daemon.
+
+ -- Ian Jackson <ian@davenant.greenend.org.uk> Tue, 14 Oct 1997 02:04:18 +0100
+
userv (0.56); urgency=medium
* Server now checks itself every hour to see if its socket has been
open(O_WRONLY), close(), dup2, EPIPE, SIGPIPE, &c.
(ie, opening pipes with O_RDWR never blocks; EPIPE happens
if you write with no readers; EOF happens if you read with
- no buffered data and writers)
+ no buffered data and writers);
* POSIX signal handling - sigaction(2), sigprocmask(2), sigsuspend(2);
+* POSIX sessions - setsid(2) (for -daemon flag).
To format the documentation:
* initgroups(3) must use setgroups(2) and dynamic
linking must allow overriding setgroups(2) for initgroups(3);
+DAEMON INVOCATION:
+
+The daemon can be invoked with no arguments, in which case it will not
+fork or detach itself. This is suitable for running from init and
+similar arrangements.
+
+With -daemon it will attempt to detach itself from the controlling
+terminal and fork/exit so that control returns at startup.
+
+In both cases diagnostics which prevent correct startup will appear on
+stderr.
+
+SYSLOG MESSAGES:
+
+The daemon issues diagnostics of various kinds to syslog, usually with
+facility LOG_DAEMON (though this can be changed in daemon.h if you want).
+The syslog levels used are:
+ debug - verbose messages about the activity of the userv daemon.
+ info - two log message about the nature and outcome of each request.
+ notice - messages about the status of the daemon, including the
+ startup message and the hourly socket check messages.
+ warning - if the uservd exits because it believes that it no longer
+ controls the rendezvous socket (ie, its socket has become
+ orphaned), this level will receive messages indicating why
+ the daemon believes this and notifying of its shutdown.
+ err - a believed-recoverable error condition was detected by the
+ userv server in itself, the client or the operating system
+ (this includes resource shortages). The uservd will try to
+ continue.
+ crit - the uservd detected a non-recoverable error condition
+ after startup and will exit.
+ alert - not used.
+ emerg - not used.
+
+The service configuration language has the facility to direct error
+and warning messages to syslog. The default facility and level is
+user.err, but the author of the configuration file(s) can override
+this.
+
+DAEMON EXIT STATUS:
+
+The daemon's exit code will reflect how well things went:
+
+ 0 - The daemon was asked to detach itself from the controlling
+ terminal and this appears to have been done successfully.
+ 1* - The daemon got a SIGTERM or SIGINT and shut itself down.
+ 2* - The daemon believes that it was no longer the uservd and so has
+ exited to clean itself up.
+ 3 - uservd was started with incorrect arguments.
+ 4 - A system call failure or other environmental problem occurred
+ during startup.
+ 5* - There was a non-recoverable error after startup; the uservd had
+ to exit.
+ 6 - The daemon was asked to detach itself, but its detaching child
+ died for some unexpected reason.
+
+ SIGABRT/SIGIOT* - an unexpected internal error, usually caused by a
+ bug in uservd. This can also occur if an attempt to block signals
+ using sigprocmask fails.
+
+Outcomes marked * are not possible if the daemon is asked to detach
+itself - these exit statuses will be reaped by init instead.
+
+The daemon's per-request children will note the success level of its
+request in its exit status. This will not usually be logged unless it
+is higher than those listed below; they are presented here for
+completeness and as programming documentation.
+
+ 2 - The connection was just an internal version check.
+
+ 4 - The client requested that the service be disconnected. This
+ includes normal termination, which is achieved by having the
+ server tell the client that the service has completed and waiting
+ for the client to tell it to disconnect.
+
+ 8 - The client closed its end of the socket when this would not
+ usually have been expected, causing an EPIPE or unexpected EOF in
+ the server. This is not an error condition - it can happen, for
+ example, if the client receives a fatal signal of some kind from
+ its execution environment (eg its controlling terminal).
+
+ 12 - The service failed onm the service side in an expected and
+ controlled manner, for example because it was rejected in the
+ configuration files.
+
+ 16 - A fatal system call failure or other general error occurred,
+ which ought not to have happened at all, barring system resource
+ shortages.
+
+ 20 - The client sent invalid data to the server, after the client
+ dropped all its system privilege. On some systems this can be
+ caused by a malicious calling user.
+
+ SIGABRT/SIGIOT - The client sent invalid data to the server before it
+ dropped all its system privileges, or some other unexpected
+ internal error occurred. This can also occur if an attempt to
+ block signals using sigprocmask fails.
+
+ 0-3,5-7,9-11,13-15,17-19 are not currently used.
+
REENTRANCY IN THE LIBC:
We assume, both in the client and server, that it is safe to use one
# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
VERSION=@VERSION@
+VEREXT=std
CC=@CC@
-CFLAGS=@CFLAGS@ $(XCFLAGS) -DVERSION='"$(VERSION)"'
+CFLAGS=@CFLAGS@ $(XCFLAGS) -DVERSION='"$(VERSION)"' -DVEREXT='"$(VEREXT)"'
OPTIMISE=@OPTIMISE@
CPPFLAGS=@DEBUGDEFS@ $(XCPPFLAGS)
LDLIBS=@DEBUGLIBS@ $(XLDLIBS)
" --spoof-user <username> } or same user\n"
"fdmodifiers: read write overwrite trunc[ate]\n"
"(separate with commas) append sync excl[usive] creat[e] fd\n\n"
- "userv and uservd version " VERSION "; copyright (C)1996-1997 Ian Jackson.\n"
+ "userv and uservd version " VERSION VEREXT "; copyright (C)1996-1997 Ian Jackson.\n"
"there is NO WARRANTY; type `userv --copyright' for details.\n",
stderr) < 0)
syscallerror("write usage to stderr");
request_mbuf.serviceuserlen= strlen(serviceuser);
request_mbuf.servicelen= strlen(argv[0]);
request_mbuf.lognamelen= strlen(logname);
+ request_mbuf.spoofed= spoofuser ? 1 : 0;
request_mbuf.cwdlen= cwdbufsize;
request_mbuf.callinguid= spoofuid;
request_mbuf.ngids= ngids+1;
struct request_msg {
unsigned long magic;
- pid_t clientpid;
+ pid_t clientpid; /* or -1 if no service is required and this was a version check */
int serviceuserlen;
int servicelen;
- int lognamelen;
+ int lognamelen, spoofed; /* spoofed is 0 or 1 */
int cwdlen, overridelen;
uid_t callinguid;
int ngids, nreadfds, nwritefds, nargs, nvars;
#define USERVD_LOGIDENT "uservd"
#define USERVDCHECK_LOGIDENT "uservd/check"
#define USERVD_LOGFACILITY LOG_DAEMON
-#define DEFUSERLOGFACILITY LOG_DAEMON
+#define DEFUSERLOGFACILITY LOG_USER
#define DEFUSERLOGLEVEL LOG_ERR
#define TOPLEVEL_CONFIGURATION " \n\
#define USERVD_MYSELF_CHECK 3600
#define USERVD_MYSELF_TIMEOUT 60
+#define USERVD_CHECKFORK_RETRY 60
#define MAX_INCLUDE_NEST 40
#define MAX_ERRMSG_LEN (MAX_ERRMSG_STRING-1024)
#define ERRMSG_RESERVE_ERRNO 128
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/socket.h>
+#include <sys/fcntl.h>
#include <dirent.h>
#include <sys/un.h>
pid_t overlordpid;
-static pid_t checkpid= -1;
+static pid_t checkpid= -1, detachpid= -1;
static sig_atomic_t needcheck= 1;
static void checkstalepipes(void) {
r= waitpid((pid_t)-1,&status,WNOHANG);
if (!r || (r==-1 && errno==ECHILD)) break;
if (r==-1) { syslog(LOG_ERR,"wait in sigchild handler gave error: %m"); break; }
+ if (r==detachpid) {
+ if (WIFEXITED(status) && WEXITSTATUS(status)==4) _exit(4);
+ fprintf(stderr,"uservd: detaching child failed with unexpected code %d\n",status);
+ exit(6);
+ }
if (r==checkpid) {
if (WIFEXITED(status)) {
if (!WEXITSTATUS(status)) {
- syslog(LOG_NOTICE,"no longer the uservd - exiting");
- _exit(0);
+ syslog(LOG_WARNING,"no longer the uservd - exiting");
+ _exit(2);
} else if (WEXITSTATUS(status)!=1) {
syslog(LOG_ERR,"check pid %ld exited with status %d",
(long)checkpid,WEXITSTATUS(status));
}
} else if (WIFSIGNALED(status)) {
if (WTERMSIG(status) == SIGALRM && !WCOREDUMP(status)) {
- syslog(LOG_NOTICE,"check timed out; no longer the uservd - exiting");
- _exit(0);
+ syslog(LOG_WARNING,"check timed out; no longer the uservd - exiting");
+ _exit(2);
} else {
syslog(LOG_ERR,"check pid %ld %s due to signal %s",
(long)checkpid,
return;
}
+static void sighandler_usr1(int x) {
+ _exit(0);
+}
+
static void sighandler_alrm(int x) {
needcheck= 1;
}
+static void sighandler_termint(int sig) {
+ syslog(LOG_NOTICE,"terminating due to signal %s",strsignal(sig));
+ _exit(1);
+}
+
static void blocksignals(int how) {
int r;
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGCHLD);
sigaddset(&set,SIGALRM);
+ sigaddset(&set,SIGTERM);
+ sigaddset(&set,SIGINT);
r= sigprocmask(how,&set,0); assert(!r);
}
int sfd, r, remain;
unsigned char *p;
struct opening_msg opening_mbuf;
+ struct request_msg request_mbuf;
+ unsigned long endmagic;
struct sigaction sig;
struct sockaddr_un ssockname;
r= connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname));
if (r) {
if (errno == ECONNREFUSED || errno == ENOENT)
- { syslog(LOG_NOTICE,"uservd daemon is not running: %m"); exit(0); }
+ { syslog(LOG_WARNING,"real uservd daemon is not running: %m"); exit(0); }
syslog(LOG_ERR,"unable to connect to uservd daemon: %m"); exit(1);
}
remain-= r; p+= r;
}
if (opening_mbuf.magic != OPENING_MAGIC) {
- syslog(LOG_NOTICE,"magic number mismatch");
+ syslog(LOG_WARNING,"magic number mismatch");
exit(0);
}
if (memcmp(opening_mbuf.protocolchecksumversion,protocolchecksumversion,PCSUMSIZE)) {
- syslog(LOG_NOTICE,"protocol checksum mismatch");
+ syslog(LOG_WARNING,"protocol checksum mismatch");
exit(0);
}
if (opening_mbuf.overlordpid != overlordpid) {
- syslog(LOG_NOTICE,"overlord pid mismatch");
+ syslog(LOG_WARNING,"overlord pid mismatch");
exit(0);
}
- syslog(LOG_NOTICE,"check - same daemon still running");
+ memset(&request_mbuf,0,sizeof(request_mbuf));
+ request_mbuf.magic= REQUEST_MAGIC;
+ request_mbuf.clientpid= -1;
+ request_mbuf.serviceuserlen= 0;
+ request_mbuf.servicelen= 0;
+ request_mbuf.lognamelen= 0;
+ request_mbuf.spoofed= 0;
+ request_mbuf.cwdlen= 0;
+ request_mbuf.overridelen= -1;
+ request_mbuf.callinguid= -1;
+ request_mbuf.ngids= 0;
+ request_mbuf.nreadfds= 0;
+ request_mbuf.nwritefds= 0;
+ request_mbuf.nargs= 0;
+ request_mbuf.nvars= 0;
+ r= write(sfd,&request_mbuf,sizeof(request_mbuf));
+ if (r==sizeof(request_mbuf)) {
+ endmagic= REQUEST_END_MAGIC;
+ write(sfd,&endmagic,sizeof(endmagic));
+ }
+ syslog(LOG_NOTICE,"uservd[%ld] is running",(long)overlordpid);
#endif
exit(1);
}
+static void NONRETURNING startupsyscallerr(const char *what) {
+ fprintf(stderr,
+ "uservd: system call failed during startup:\n"
+ "uservd: %s: %s\n",
+ what,strerror(errno));
+ exit(4);
+}
+
int main(int argc, char *const *argv) {
- int mfd, sfd, csocklen, e;
+ int mfd, sfd, nfd, csocklen, e, r, becomedaemon;
struct sigaction sigact;
struct sockaddr_un ssockname, csockname;
- pid_t child;
+ pid_t child, parentpid, sid;
#ifdef NDEBUG
abort(); /* Do not disable assertions in this security-critical code ! */
#endif
- if (argc>1) { fputs("usage: uservd\n",stderr); exit(3); }
+ becomedaemon= 0;
+
+ if (argv[1] && !strcmp(argv[1],"-daemon")) {
+ becomedaemon= 1;
+ argv++; argc--;
+ }
+ if (argc>1) { fputs("usage: uservd [-daemon]\n",stderr); exit(3); }
openlog(USERVD_LOGIDENT,LOG_NDELAY|LOG_PID,USERVD_LOGFACILITY);
- if (chdir(VARDIR)) { syslog(LOG_CRIT,"cannot change to " VARDIR ": %m"); exit(4); }
+ if (chdir(VARDIR)) startupsyscallerr("cannot change to " VARDIR);
checkstalepipes();
- overlordpid= getpid();
- if (overlordpid==-1) { syslog(LOG_CRIT,"cannot getpid: %m"); exit(4); }
+ overlordpid= parentpid= getpid();
+ if (parentpid==-1) startupsyscallerr("cannot getpid");
mfd= socket(AF_UNIX,SOCK_STREAM,0);
- if (mfd<0) { syslog(LOG_CRIT,"cannot create master socket: %m"); exit(4); }
+ if (mfd<0) startupsyscallerr("cannot create master socket");
assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUS));
ssockname.sun_family= AF_UNIX;
strcpy(ssockname.sun_path,RENDEZVOUS);
unlink(RENDEZVOUS);
- 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); }
+ r= bind(mfd,(struct sockaddr*)&ssockname,sizeof(ssockname));
+ if (r) startupsyscallerr("cannot bind master socket");
+ if (listen(mfd,5)) startupsyscallerr("cannot listen on master socket");
sigemptyset(&sigact.sa_mask);
sigaddset(&sigact.sa_mask,SIGCHLD);
sigact.sa_flags= SA_NOCLDSTOP;
sigact.sa_handler= sighandler_chld;
- if (sigaction(SIGCHLD,&sigact,0))
- { syslog(LOG_CRIT,"cannot setup sigchld handler: %m"); exit(4); }
+ if (sigaction(SIGCHLD,&sigact,0)) startupsyscallerr("cannot setup sigchld handler");
sigact.sa_handler= sighandler_alrm;
- if (sigaction(SIGALRM,&sigact,0))
- { syslog(LOG_CRIT,"cannot setup sigalrm handler: %m"); exit(4); }
+ if (sigaction(SIGALRM,&sigact,0)) startupsyscallerr("cannot setup sigalrm handler");
+
+ if (becomedaemon) {
+ sigact.sa_handler= sighandler_usr1;
+ if (sigaction(SIGUSR1,&sigact,0)) startupsyscallerr("cannot setup sigusr1 handler");
+
+ detachpid= fork(); if (detachpid==-1) startupsyscallerr("cannot fork to detach");
+ if (detachpid) {
+ pause();
+ fputs("uservd: pause unexpectedly returned during detach\n",stderr);
+ exit(4);
+ }
+ sigact.sa_handler= SIG_DFL;
+ if (sigaction(SIGUSR1,&sigact,0)) startupsyscallerr("cannot restore sigusr1");
+ }
+
+ sigact.sa_handler= sighandler_termint;
+ if (sigaction(SIGTERM,&sigact,0)) startupsyscallerr("cannot setup sigterm handler");
+ if (sigaction(SIGINT,&sigact,0)) startupsyscallerr("cannot setup sigint handler");
+
+ if (becomedaemon) {
+ nfd= open("/dev/null",O_RDWR);
+ if (nfd<0) startupsyscallerr("cannot open /dev/null");
+ sid= setsid(); if (sid == -1) startupsyscallerr("cannot create new session");
+ overlordpid= getpid();
+ if (overlordpid == -1) startupsyscallerr("getpid after detach");
+ if (dup2(nfd,0)<0 || dup2(nfd,1)<0)
+ startupsyscallerr("cannot dup /dev/null for stdin/out");
+ r= kill(parentpid,SIGUSR1); if (r) startupsyscallerr("send SIGUSR1 to detach");
+ r= dup2(nfd,2);
+ if (r<0) { syslog(LOG_CRIT,"cannot dup /dev/null for stderr: %m"); exit(5); }
+ close(nfd);
+ }
syslog(LOG_NOTICE,"started");
+
for (;;) {
if (needcheck) {
assert(checkpid==-1);
- checkpid= fork();
- if (checkpid==-1) { syslog(LOG_CRIT,"fork for check: %m"); exit(5); }
- if (!checkpid) docheck();
+ for (;;) {
+ checkpid= fork();
+ if (checkpid!=-1) {
+ if (!checkpid) docheck();
+ break;
+ } else if (errno==EAGAIN) {
+ syslog(LOG_ERR,"fork for check - will wait and retry: %m");
+ r= alarm(USERVD_CHECKFORK_RETRY);
+ if (r<0) { syslog(LOG_CRIT,"set alarm for retry check: %m"); exit(5); }
+ break;
+ } else if (errno!=EINTR) {
+ syslog(LOG_CRIT,"fork for check: %m"); exit(5);
+ }
+ }
needcheck= 0;
}
csocklen= sizeof(csockname);
if (errno != EPIPE) syscallerror("writing to client");
blocksignals();
ensurelogopen(USERVD_LOGFACILITY);
- syslog(LOG_DEBUG,"client went away (broken pipe)");
+ syslog(LOG_INFO,"client went away (broken pipe)");
disconnect(8);
}
if (ferror(srfile)) syscallerror("reading from client");
blocksignals();
assert(feof(srfile));
- syslog(LOG_DEBUG,"client went away (unexpected EOF)");
+ syslog(LOG_INFO,"client went away (unexpected EOF)");
swfile= 0;
disconnect(8);
}
blocksignals();
syslog(LOG_ERR,"client sent bad file descriptor %d to close (max %d)",
fd,fdarrayused-1);
- disconnect(12);
+ disconnect(20);
}
if (fdarray[fd].holdfd!=-1) {
if (close(fdarray[fd].holdfd)) syscallerror("cannot close holding fd");
break;
case et_disconnect:
blocksignals();
- syslog(LOG_DEBUG,"client disconnected");
+ syslog(LOG_INFO,"client disconnected");
disconnect(4);
default:
return;
e= errno;
blocksignals();
syslog(LOG_ERR,"system call failure: %s: %s",what,strerror(e));
- disconnect(18);
+ disconnect(16);
}
/* Functions which may be called from signal handlers. These
xfwrite(&progress_mbuf,sizeof(progress_mbuf),swfile);
xfflush(swfile);
- syslog(LOG_DEBUG,"service completed (status %d %d)",(status>>8)&0x0ff,status&0x0ff);
+ syslog(LOG_INFO,"service completed (status %d %d)",(status>>8)&0x0ff,status&0x0ff);
_exit(0);
}
strnytcat(errmsg,strerror(errnoval),sizeof(errmsg));
}
senderrmsgstderr(errmsg);
- syslog(LOG_DEBUG,"service failed (%s)",errmsg);
+ syslog(LOG_INFO,"service failed (%s)",errmsg);
disconnect(12);
}
xfread(&request_mbuf,sizeof(request_mbuf));
serviceuser= xfreadsetstring(request_mbuf.serviceuserlen);
service= xfreadsetstring(request_mbuf.servicelen);
+ assert(request_mbuf.spoofed==0 || request_mbuf.spoofed==1);
logname= xfreadsetstring(request_mbuf.lognamelen);
cwd= xfreadsetstring(request_mbuf.cwdlen);
if (request_mbuf.overridelen >= 0) {
if (initgroups(pw->pw_name,pw->pw_gid)) syscallerror("initgroups");
if (setreuid(pw->pw_uid,pw->pw_uid)) syscallerror("setreuid 1");
if (setreuid(pw->pw_uid,pw->pw_uid)) syscallerror("setreuid 2");
- if (pw->pw_uid)
+ if (pw->pw_uid) {
if (!setreuid(pw->pw_uid,0)) miscerror("setreuid 3 unexpectedly succeeded");
- if (errno != EPERM) syscallerror("setreuid 3 failed in unexpected way");
+ if (errno != EPERM) syscallerror("setreuid 3 failed in unexpected way");
+ }
service_ngids= getgroups(0,0); if (service_ngids == -1) syscallerror("getgroups(0,0)");
if (service_ngids > MAX_GIDS) miscerror("service user is in far too many groups");
setup_comms(sfd);
send_opening();
receive_request();
+ if (request_mbuf.clientpid == (pid_t)-1) _exit(2);
establish_pipes();
lookup_uidsgids();
debug_dumprequest(mypid);
+ syslog(LOG_INFO,"%s %s -> %s %c %s",
+ request_mbuf.spoofed ? "spoof" : "user",
+ logname, serviceuser, overridedata?'!':':', service);
if (overridedata)
r= parse_string(TOPLEVEL_OVERRIDDEN_CONFIGURATION,
const unsigned char *p;
int i;
- printf("uservd version " VERSION "; copyright (C)1996-1997 Ian Jackson.\n"
+ printf("uservd version " VERSION VEREXT "; copyright (C)1996-1997 Ian Jackson.\n"
#ifdef DEBUG
"DEBUGGING VERSION"
#else
<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/.
+<var/facility/ is <tt/user/; the default <var/level/ is <tt/error/.
</taglist>
<sect1 id="dirs-control">Control structure directives