[BACK]Return to common.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / rdist

File: [local] / src / usr.bin / rdist / common.c (download)

Revision 1.39, Fri Sep 21 19:00:45 2018 UTC (5 years, 8 months ago) by millert
Branch: MAIN
CVS Tags: OPENBSD_6_5_BASE, OPENBSD_6_5, OPENBSD_6_4_BASE, OPENBSD_6_4
Changes since 1.38: +24 -15 lines

Use password/group cache functions and avoid stashing a pointer to
the return value of getgrgid(3) or getgrnam(3) which relies on
undefined behavior.  The rdist server will now use getgroups(2) to
determine group membership of the invoking user.  In addition, there
is now one implementation of tilde expansion instead of two.
OK tb@ tim@

/*	$OpenBSD: common.c,v 1.39 2018/09/21 19:00:45 millert 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) < 0) {
		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);
}