dissertation/usr.bin/rdist/common.c

836 lines
17 KiB
C

/* $OpenBSD: common.c,v 1.40 2019/06/28 13:35:03 deraadt Exp $ */
/*
* Copyright (c) 1983 Regents of the University of California.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <paths.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "defs.h"
/*
* Things common to both the client and server.
*/
/*
* Variables common to both client and server
*/
char host[HOST_NAME_MAX+1]; /* Name of this host */
uid_t userid = (uid_t)-1; /* User's UID */
gid_t groupid = (gid_t)-1; /* User's GID */
gid_t gidset[NGROUPS_MAX]; /* User's GID list */
int gidsetlen = 0; /* Number of GIDS in list */
char *homedir = NULL; /* User's $HOME */
char *locuser = NULL; /* Local User's name */
int isserver = FALSE; /* We're the server */
int amchild = 0; /* This PID is a child */
int do_fork = 1; /* Fork child process */
char *currenthost = NULL; /* Current client hostname */
char *progname = NULL; /* Name of this program */
int rem_r = -1; /* Client file descriptor */
int rem_w = -1; /* Client file descriptor */
volatile sig_atomic_t contimedout = FALSE; /* Connection timed out */
int rtimeout = RTIMEOUT; /* Response time out */
jmp_buf finish_jmpbuf; /* Finish() jmp buffer */
int setjmp_ok = FALSE; /* setjmp()/longjmp() status */
char **realargv; /* Real main() argv */
int realargc; /* Real main() argc */
opt_t options = 0; /* Global install options */
char defowner[64] = "bin"; /* Default owner */
char defgroup[64] = "bin"; /* Default group */
static int sendcmdmsg(int, char *, size_t);
static ssize_t remread(int, u_char *, size_t);
static int remmore(void);
/*
* Front end to write() that handles partial write() requests.
*/
ssize_t
xwrite(int fd, void *buf, size_t len)
{
size_t nleft = len;
ssize_t nwritten;
char *ptr = buf;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
return nwritten;
}
nleft -= nwritten;
ptr += nwritten;
}
return len;
}
/*
* Do run-time initialization
*/
int
init(int argc, char **argv, char **envp)
{
struct passwd *pw;
int i;
/*
* Save a copy of our argc and argv before setargs() overwrites them
*/
realargc = argc;
realargv = xmalloc(sizeof(char *) * (argc+1));
for (i = 0; i < argc; i++)
realargv[i] = xstrdup(argv[i]);
pw = getpwuid(userid = getuid());
if (pw == NULL) {
error("Your user id (%u) is not known to this system.",
getuid());
return(-1);
}
debugmsg(DM_MISC, "UserID = %u pwname = '%s' home = '%s'\n",
userid, pw->pw_name, pw->pw_dir);
homedir = xstrdup(pw->pw_dir);
locuser = xstrdup(pw->pw_name);
groupid = pw->pw_gid;
gidsetlen = getgroups(NGROUPS_MAX, gidset);
gethostname(host, sizeof(host));
#if 0
if ((cp = strchr(host, '.')) != NULL)
*cp = CNULL;
#endif
/*
* If we're not root, disable paranoid ownership checks
* since normal users cannot chown() files.
*/
if (!isserver && userid != 0) {
FLAG_ON(options, DO_NOCHKOWNER);
FLAG_ON(options, DO_NOCHKGROUP);
}
return(0);
}
/*
* Finish things up before ending.
*/
void
finish(void)
{
debugmsg(DM_CALL,
"finish() called: do_fork = %d amchild = %d isserver = %d",
do_fork, amchild, isserver);
cleanup(0);
/*
* There's no valid finish_jmpbuf for the rdist master parent.
*/
if (!do_fork || amchild || isserver) {
if (!setjmp_ok) {
#ifdef DEBUG_SETJMP
error("attemping longjmp() without target");
abort();
#else
exit(1);
#endif
}
longjmp(finish_jmpbuf, 1);
/*NOTREACHED*/
error("Unexpected failure of longjmp() in finish()");
exit(2);
} else
exit(1);
}
/*
* Handle lost connections
*/
void
lostconn(void)
{
/* Prevent looping */
(void) signal(SIGPIPE, SIG_IGN);
rem_r = rem_w = -1; /* Ensure we don't try to send to server */
checkhostname();
error("Lost connection to %s",
(currenthost) ? currenthost : "(unknown)");
finish();
}
/*
* General signal handler
*/
void
sighandler(int sig)
{
int save_errno = errno;
/* XXX signal race */
debugmsg(DM_CALL, "sighandler() received signal %d\n", sig);
switch (sig) {
case SIGALRM:
contimedout = TRUE;
/* XXX signal race */
checkhostname();
error("Response time out");
finish();
break;
case SIGPIPE:
/* XXX signal race */
lostconn();
break;
case SIGFPE:
debug = !debug;
break;
case SIGHUP:
case SIGINT:
case SIGQUIT:
case SIGTERM:
/* XXX signal race */
finish();
break;
default:
/* XXX signal race */
fatalerr("No signal handler defined for signal %d.", sig);
}
errno = save_errno;
}
/*
* Function to actually send the command char and message to the
* remote host.
*/
static int
sendcmdmsg(int cmd, char *msg, size_t msgsize)
{
int len;
if (rem_w < 0)
return(-1);
/*
* All commands except C_NONE should have a newline
*/
if (cmd != C_NONE && !strchr(msg + 1, '\n'))
(void) strlcat(msg + 1, "\n", msgsize - 1);
if (cmd == C_NONE)
len = strlen(msg);
else {
len = strlen(msg + 1) + 1;
msg[0] = cmd;
}
debugmsg(DM_PROTO, ">>> Cmd = %c (\\%3.3o) Msg = \"%.*s\"",
cmd, cmd,
(cmd == C_NONE) ? len-1 : len-2,
(cmd == C_NONE) ? msg : msg + 1);
return(!(xwrite(rem_w, msg, len) == len));
}
/*
* Send a command message to the remote host.
* Called as sendcmd(char cmdchar, char *fmt, arg1, arg2, ...)
* The fmt may be NULL, in which case there are no args.
*/
int
sendcmd(char cmd, const char *fmt, ...)
{
static char buf[BUFSIZ];
va_list args;
va_start(args, fmt);
if (fmt)
(void) vsnprintf(buf + (cmd != C_NONE),
sizeof(buf) - (cmd != C_NONE), fmt, args);
else
buf[1] = CNULL;
va_end(args);
return(sendcmdmsg(cmd, buf, sizeof(buf)));
}
/*
* Internal variables and routines for reading lines from the remote.
*/
static u_char rembuf[BUFSIZ];
static u_char *remptr;
static ssize_t remleft;
#define remc() (--remleft < 0 ? remmore() : *remptr++)
/*
* Back end to remote read()
*/
static ssize_t
remread(int fd, u_char *buf, size_t bufsiz)
{
return(read(fd, (char *)buf, bufsiz));
}
static int
remmore(void)
{
(void) signal(SIGALRM, sighandler);
(void) alarm(rtimeout);
remleft = remread(rem_r, rembuf, sizeof(rembuf));
(void) alarm(0);
if (remleft < 0)
return (-2); /* error */
if (remleft == 0)
return (-1); /* EOF */
remptr = rembuf;
remleft--;
return (*remptr++);
}
/*
* Read an input line from the remote. Return the number of bytes
* stored (equivalent to strlen(p)). If `cleanup' is set, EOF at
* the beginning of a line is returned as EOF (-1); other EOFs, or
* errors, call cleanup() or lostconn(). In other words, unless
* the third argument is nonzero, this routine never returns failure.
*/
int
remline(u_char *buffer, int space, int doclean)
{
int c, left = space;
u_char *p = buffer;
if (rem_r < 0) {
error("Cannot read remote input: Remote descriptor not open.");
return(-1);
}
while (left > 0) {
if ((c = remc()) < -1) { /* error */
if (doclean) {
finish();
/*NOTREACHED*/
}
lostconn();
/*NOTREACHED*/
}
if (c == -1) { /* got EOF */
if (doclean) {
if (left == space)
return (-1);/* signal proper EOF */
finish(); /* improper EOF */
/*NOTREACHED*/
}
lostconn();
/*NOTREACHED*/
}
if (c == '\n') {
*p = CNULL;
if (debug) {
static char mbuf[BUFSIZ];
(void) snprintf(mbuf, sizeof(mbuf),
"<<< Cmd = %c (\\%3.3o) Msg = \"%s\"",
buffer[0], buffer[0],
buffer + 1);
debugmsg(DM_PROTO, "%s", mbuf);
}
return (space - left);
}
*p++ = c;
left--;
}
/* this will probably blow the entire session */
error("remote input line too long");
p[-1] = CNULL; /* truncate */
return (space);
}
/*
* Non-line-oriented remote read.
*/
ssize_t
readrem(char *p, ssize_t space)
{
if (remleft <= 0) {
/*
* Set remote time out alarm.
*/
(void) signal(SIGALRM, sighandler);
(void) alarm(rtimeout);
remleft = remread(rem_r, rembuf, sizeof(rembuf));
(void) alarm(0);
remptr = rembuf;
}
if (remleft <= 0)
return (remleft);
if (remleft < space)
space = remleft;
memcpy(p, remptr, space);
remptr += space;
remleft -= space;
return (space);
}
/*
* Get the user name for the uid.
*/
char *
getusername(uid_t uid, char *file, opt_t opts)
{
static char buf[100];
static uid_t lastuid = (uid_t)-1;
const char *name;
/*
* The value of opts may have changed so we always
* do the opts check.
*/
if (IS_ON(opts, DO_NUMCHKOWNER)) {
(void) snprintf(buf, sizeof(buf), ":%u", uid);
return(buf);
}
/*
* Try to avoid passwd lookup.
*/
if (lastuid == uid && buf[0] != '\0' && buf[0] != ':')
return(buf);
lastuid = uid;
if ((name = user_from_uid(uid, 1)) == NULL) {
if (IS_ON(opts, DO_DEFOWNER) && !isserver)
(void) strlcpy(buf, defowner, sizeof(buf));
else {
message(MT_WARNING,
"%s: No password entry for uid %u", file, uid);
(void) snprintf(buf, sizeof(buf), ":%u", uid);
}
} else {
(void) strlcpy(buf, name, sizeof(buf));
}
return(buf);
}
/*
* Get the group name for the gid.
*/
char *
getgroupname(gid_t gid, char *file, opt_t opts)
{
static char buf[100];
static gid_t lastgid = (gid_t)-1;
const char *name;
/*
* The value of opts may have changed so we always
* do the opts check.
*/
if (IS_ON(opts, DO_NUMCHKGROUP)) {
(void) snprintf(buf, sizeof(buf), ":%u", gid);
return(buf);
}
/*
* Try to avoid group lookup.
*/
if (lastgid == gid && buf[0] != '\0' && buf[0] != ':')
return(buf);
lastgid = gid;
if ((name = group_from_gid(gid, 1)) == NULL) {
if (IS_ON(opts, DO_DEFGROUP) && !isserver)
(void) strlcpy(buf, defgroup, sizeof(buf));
else {
message(MT_WARNING, "%s: No name for group %u",
file, gid);
(void) snprintf(buf, sizeof(buf), ":%u", gid);
}
} else
(void) strlcpy(buf, name, sizeof(buf));
return(buf);
}
/*
* Read a response from the remote host.
*/
int
response(void)
{
static u_char resp[BUFSIZ];
u_char *s;
int n;
debugmsg(DM_CALL, "response() start\n");
n = remline(s = resp, sizeof(resp), 0);
n--;
switch (*s++) {
case C_ACK:
debugmsg(DM_PROTO, "received ACK\n");
return(0);
case C_LOGMSG:
if (n > 0) {
message(MT_CHANGE, "%s", s);
return(1);
}
debugmsg(DM_PROTO, "received EMPTY logmsg\n");
return(0);
case C_NOTEMSG:
if (s)
message(MT_NOTICE, "%s", s);
return(response());
default:
s--;
n++;
/* fall into... */
case C_ERRMSG: /* Normal error message */
if (s)
message(MT_NERROR, "%s", s);
return(-1);
case C_FERRMSG: /* Fatal error message */
if (s)
message(MT_FERROR, "%s", s);
finish();
return(-1);
}
/*NOTREACHED*/
}
/*
* This should be in expand.c but the other routines call other modules
* that we don't want to load in.
*
* Expand file names beginning with `~' into the
* user's home directory path name. Return a pointer in buf to the
* part corresponding to `file'.
*/
char *
exptilde(char *ebuf, char *file, size_t ebufsize)
{
struct passwd *pw;
char *pw_dir, *rest;
static char lastuser[_PW_NAME_LEN + 1];
static char lastdir[PATH_MAX];
size_t len;
if (*file != '~') {
notilde:
(void) strlcpy(ebuf, file, ebufsize);
return(ebuf);
}
pw_dir = homedir;
if (*++file == CNULL) {
rest = NULL;
} else if (*file == '/') {
rest = file;
} else {
rest = file;
while (*rest && *rest != '/')
rest++;
if (*rest == '/')
*rest = CNULL;
else
rest = NULL;
if (strcmp(locuser, file) != 0) {
if (strcmp(lastuser, file) != 0) {
if ((pw = getpwnam(file)) == NULL) {
error("%s: unknown user name", file);
if (rest != NULL)
*rest = '/';
return(NULL);
}
strlcpy(lastuser, pw->pw_name, sizeof(lastuser));
strlcpy(lastdir, pw->pw_dir, sizeof(lastdir));
}
pw_dir = lastdir;
}
if (rest != NULL)
*rest = '/';
}
if ((len = strlcpy(ebuf, pw_dir, ebufsize)) >= ebufsize)
goto notilde;
pw_dir = ebuf + len;
if (rest != NULL) {
pw_dir++;
if ((len = strlcat(ebuf, rest, ebufsize)) >= ebufsize)
goto notilde;
}
return(pw_dir);
}
/*
* Set access and modify times of a given file
*/
int
setfiletime(char *file, time_t atime, time_t mtime)
{
struct timeval tv[2];
if (atime != 0 && mtime != 0) {
tv[0].tv_sec = atime;
tv[1].tv_sec = mtime;
tv[0].tv_usec = tv[1].tv_usec = 0;
return (utimes(file, tv));
} else /* Set to current time */
return (utimes(file, NULL));
}
/*
* Get version info
*/
char *
getversion(void)
{
static char buff[BUFSIZ];
(void) snprintf(buff, sizeof(buff),
"Version %s.%d (%s) - Protocol Version %d, Release %s, Patch level %d",
DISTVERSION, PATCHLEVEL, DISTSTATUS,
VERSION, DISTVERSION, PATCHLEVEL);
return(buff);
}
/*
* Execute a shell command to handle special cases.
* This is now common to both server and client
*/
void
runcommand(char *cmd)
{
ssize_t nread;
pid_t pid, wpid;
char *cp, *s;
char sbuf[BUFSIZ], buf[BUFSIZ];
int fd[2], status;
if (pipe(fd) == -1) {
error("pipe of %s failed: %s", cmd, SYSERR);
return;
}
if ((pid = fork()) == 0) {
/*
* Return everything the shell commands print.
*/
(void) close(0);
(void) close(1);
(void) close(2);
(void) open(_PATH_DEVNULL, O_RDONLY);
(void) dup(fd[PIPE_WRITE]);
(void) dup(fd[PIPE_WRITE]);
(void) close(fd[PIPE_READ]);
(void) close(fd[PIPE_WRITE]);
(void) execl(_PATH_BSHELL, "sh", "-c", cmd, (char *)NULL);
_exit(127);
}
(void) close(fd[PIPE_WRITE]);
s = sbuf;
*s++ = C_LOGMSG;
while ((nread = read(fd[PIPE_READ], buf, sizeof(buf))) > 0) {
cp = buf;
do {
*s++ = *cp++;
if (cp[-1] != '\n') {
if (s < (char *) &sbuf[sizeof(sbuf)-1])
continue;
*s++ = '\n';
}
/*
* Throw away blank lines.
*/
if (s == &sbuf[2]) {
s--;
continue;
}
if (isserver)
(void) xwrite(rem_w, sbuf, s - sbuf);
else {
*s = CNULL;
message(MT_INFO, "%s", sbuf+1);
}
s = &sbuf[1];
} while (--nread);
}
if (s > (char *) &sbuf[1]) {
*s++ = '\n';
if (isserver)
(void) xwrite(rem_w, sbuf, s - sbuf);
else {
*s = CNULL;
message(MT_INFO, "%s", sbuf+1);
}
}
while ((wpid = wait(&status)) != pid && wpid != -1)
;
if (wpid == -1)
status = -1;
(void) close(fd[PIPE_READ]);
if (status)
error("shell returned %d", status);
else if (isserver)
ack();
}
/*
* Malloc with error checking
*/
void *
xmalloc(size_t amt)
{
void *ptr;
if ((ptr = malloc(amt)) == NULL)
fatalerr("Cannot malloc %zu bytes of memory.", amt);
return (ptr);
}
/*
* realloc with error checking
*/
void *
xrealloc(void *baseptr, size_t amt)
{
void *new;
if ((new = realloc(baseptr, amt)) == NULL)
fatalerr("Cannot realloc %zu bytes of memory.", amt);
return (new);
}
/*
* calloc with error checking
*/
void *
xcalloc(size_t num, size_t esize)
{
void *ptr;
if ((ptr = calloc(num, esize)) == NULL)
fatalerr("Cannot calloc %zu * %zu = %zu bytes of memory.",
num, esize, num * esize);
return (ptr);
}
/*
* Strdup with error checking
*/
char *
xstrdup(const char *str)
{
size_t len = strlen(str) + 1;
char *nstr = xmalloc(len);
return (memcpy(nstr, str, len));
}
/*
* Private version of basename()
*/
char *
xbasename(char *path)
{
char *cp;
if ((cp = strrchr(path, '/')) != NULL)
return(cp+1);
else
return(path);
}
/*
* Take a colon (':') separated path to a file and
* search until a component of that path is found and
* return the found file name.
*/
char *
searchpath(char *path)
{
char *file;
char *space;
int found;
struct stat statbuf;
for (found = 0; !found && (file = strsep(&path, ":")) != NULL; ) {
if ((space = strchr(file, ' ')) != NULL)
*space = CNULL;
found = stat(file, &statbuf) == 0;
if (space)
*space = ' '; /* Put back what we zapped */
}
return (file);
}