summaryrefslogtreecommitdiff
path: root/usr.bin/rdistd/server.c
diff options
context:
space:
mode:
authordm <dm@cvs.openbsd.org>1996-02-03 12:13:04 +0000
committerdm <dm@cvs.openbsd.org>1996-02-03 12:13:04 +0000
commitf34705a30c83ef27acf58b47c75c736e6858247a (patch)
tree2121eb2901c910af7604845f5538c2fc26d01ac3 /usr.bin/rdistd/server.c
parentfdb8a9c5e300099e2c65b848550471fb1d9f0cd4 (diff)
rdist 6.1.1
Diffstat (limited to 'usr.bin/rdistd/server.c')
-rw-r--r--usr.bin/rdistd/server.c1642
1 files changed, 1642 insertions, 0 deletions
diff --git a/usr.bin/rdistd/server.c b/usr.bin/rdistd/server.c
new file mode 100644
index 00000000000..7331068468a
--- /dev/null
+++ b/usr.bin/rdistd/server.c
@@ -0,0 +1,1642 @@
+/*
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+#ifndef lint
+static char RCSid[] =
+"$Id: server.c,v 1.1 1996/02/03 12:13:03 dm Exp $";
+
+static char sccsid[] = "@(#)server.c 5.3 (Berkeley) 6/7/86";
+
+static char copyright[] =
+"@(#) Copyright (c) 1983 Regents of the University of California.\n\
+ All rights reserved.\n";
+#endif /* not lint */
+
+/*
+ * Server routines
+ */
+
+#include "defs.h"
+
+char tempname[sizeof _RDIST_TMP + 1]; /* Tmp file name */
+char buf[BUFSIZ]; /* general purpose buffer */
+char target[MAXPATHLEN]; /* target/source directory name */
+char *ptarget; /* pointer to end of target name */
+int catname = 0; /* cat name to target name */
+char *sptarget[32]; /* stack of saved ptarget's for directories */
+char *fromhost = NULL; /* Client hostname */
+static long min_freespace = 0; /* Minimium free space on a filesystem */
+static long min_freefiles = 0; /* Minimium free # files on a filesystem */
+int oumask; /* Old umask */
+
+/*
+ * Cat "string" onto the target buffer with error checking.
+ */
+static int cattarget(string)
+ char *string;
+{
+ if (strlen(string) + strlen(target) + 2 > sizeof(target)) {
+ message(MT_INFO, "target buffer is not large enough.");
+ return(-1);
+ }
+ if (!ptarget) {
+ message(MT_INFO, "NULL target pointer set.");
+ return(-10);
+ }
+
+ (void) sprintf(ptarget, "/%s", string);
+
+ return(0);
+}
+
+/*
+ * Set uid and gid ownership of a file.
+ */
+static int setownership(file, fd, uid, gid)
+ char *file;
+ int fd;
+ UID_T uid;
+ GID_T gid;
+{
+ int status = -1;
+
+ /*
+ * We assume only the Superuser can change uid ownership.
+ */
+ if (getuid() == 0) {
+#if defined(HAVE_FCHOWN)
+ if (fd != -1)
+ status = fchown(fd, (CHOWN_UID_T) uid,
+ (CHOWN_GID_T) gid);
+#endif
+ if (status < 0)
+ status = chown(file, (CHOWN_UID_T) uid,
+ (CHOWN_GID_T) gid);
+
+ if (status < 0) {
+ message(MT_NOTICE, "%s: chown %d.%d failed: %s",
+ target, (UID_T) uid, (GID_T) gid, SYSERR);
+ return(-1);
+ }
+ } else {
+#if defined(HAVE_FCHOWN)
+ if (fd != -1)
+ status = fchown(fd, (CHOWN_UID_T) -1,
+ (CHOWN_GID_T) gid);
+#endif
+ if (status < 0)
+ status = chown(file, (CHOWN_UID_T) -1,
+ (CHOWN_GID_T) gid);
+
+ if (status < 0) {
+ message(MT_NOTICE, "%s: chgrp %d failed: %s",
+ target, (GID_T) gid, SYSERR);
+ return(-1);
+ }
+ }
+
+ return(0);
+}
+
+/*
+ * Set mode of a file
+ */
+static int setfilemode(file, fd, mode)
+ char *file;
+ int fd;
+ int mode;
+{
+ int status = -1;
+
+ if (mode == -1)
+ return(0);
+
+#if defined(HAVE_FCHMOD)
+ if (fd != -1)
+ status = fchmod(fd, mode);
+#endif
+
+ if (status < 0)
+ status = chmod(file, mode);
+
+ if (status < 0) {
+ message(MT_NOTICE, "%s: chmod failed: %s", target, SYSERR);
+ return(-1);
+ }
+
+ return(0);
+}
+
+/*
+ * Get group entry. This routine takes a string argument (name).
+ * If name is of form ":N" a lookup for gid N is done.
+ * Otherwise a lookup by name is done.
+ */
+static struct group *mygetgroup(name)
+ char *name;
+{
+ struct group *gr;
+
+ if (*name == ':')
+ gr = getgrgid(atoi(name + 1));
+ else
+ gr = getgrnam(name);
+
+ return(gr);
+}
+
+/*
+ * Change owner, group and mode of file.
+ */
+static int fchog(fd, file, owner, group, mode)
+ int fd;
+ char *file, *owner, *group;
+ int mode;
+{
+ struct group *gr = NULL;
+ static char last_group[128];
+ static char last_owner[128];
+ static GID_T last_gid = (GID_T)-2;
+ extern char *locuser;
+ register int i;
+ UID_T uid;
+ GID_T gid;
+ GID_T primegid = (GID_T)-2;
+
+ uid = userid;
+ if (userid == 0) { /* running as root; take anything */
+ if (*owner == ':') {
+ uid = (UID_T) atoi(owner + 1);
+ } else if (pw == NULL || strcmp(owner, last_owner) != 0) {
+ if ((pw = getpwnam(owner)) == NULL) {
+ if (mode != -1 && IS_ON(mode, S_ISUID)) {
+ message(MT_NOTICE,
+ "%s: unknown login name \"%s\", clearing setuid",
+ target, owner);
+ mode &= ~S_ISUID;
+ uid = 0;
+ } else
+ message(MT_NOTICE,
+ "%s: unknown login name \"%s\"",
+ target, owner);
+ } else {
+ uid = pw->pw_uid;
+ strcpy(last_owner, owner);
+ }
+ } else {
+ uid = pw->pw_uid;
+ primegid = pw->pw_gid;
+ }
+ if (*group == ':') {
+ gid = (GID_T) atoi(group + 1);
+ goto ok;
+ }
+ } else { /* not root, setuid only if user==owner */
+ struct passwd *lupw;
+
+ if (mode != -1) {
+ if (IS_ON(mode, S_ISUID) &&
+ strcmp(locuser, owner) != 0)
+ mode &= ~S_ISUID;
+ if (mode)
+ mode &= ~S_ISVTX; /* and strip sticky too */
+ }
+
+ if ((lupw = getpwnam(locuser)) != NULL)
+ primegid = lupw->pw_gid;
+ }
+
+ gid = (GID_T) -1;
+ if (last_gid < (GID_T)0 || strcmp(group, last_group) != 0) {
+ /*
+ * Invalid cached values so we need to do a new lookup.
+ */
+ if (gr = mygetgroup(group)) {
+ last_gid = gid = gr->gr_gid;
+ strcpy(last_group, gr->gr_name);
+ } else {
+ if (mode != -1 && IS_ON(mode, S_ISGID)) {
+ message(MT_NOTICE,
+ "%s: unknown group \"%s\", clearing setgid",
+ target, group);
+ mode &= ~S_ISGID;
+ } else
+ message(MT_NOTICE,
+ "%s: unknown group \"%s\"",
+ target, group);
+ }
+ } else {
+ /*
+ * Use the cached values.
+ */
+ gid = last_gid;
+ }
+
+ /*
+ * We need to check non-root users to make sure they're a member
+ * of the group. If they are not, we don't set that gid ownership.
+ */
+ if (userid && gid >= 0 && gid != primegid) {
+ if (!gr)
+ gr = mygetgroup(group);
+ if (gr)
+ for (i = 0; gr->gr_mem[i] != NULL; i++)
+ if (strcmp(locuser, gr->gr_mem[i]) == 0)
+ goto ok;
+ if (mode != -1 && IS_ON(mode, S_ISGID)) {
+ message(MT_NOTICE,
+ "%s: user %s not in group %s, clearing setgid",
+ target, locuser, group);
+ mode &= ~S_ISGID;
+ }
+ gid = (GID_T) -1;
+ }
+ok:
+ /*
+ * Set uid and gid ownership. If that fails, strip setuid and
+ * setgid bits from mode. Once ownership is set, successful
+ * or otherwise, set the new file mode.
+ */
+ if (setownership(file, fd, uid, gid) < 0) {
+ if (mode != -1 && IS_ON(mode, S_ISUID)) {
+ message(MT_NOTICE,
+ "%s: chown failed, clearing setuid", target);
+ mode &= ~S_ISUID;
+ }
+ if (mode != -1 && IS_ON(mode, S_ISGID)) {
+ message(MT_NOTICE,
+ "%s: chown failed, clearing setgid", target);
+ mode &= ~S_ISGID;
+ }
+ }
+ (void) setfilemode(file, fd, mode);
+
+
+ return(0);
+}
+
+/*
+ * Remove a file or directory (recursively) and send back an acknowledge
+ * or an error message.
+ */
+static int removefile(statb)
+ struct stat *statb;
+{
+ DIR *d;
+ static DIRENTRY *dp;
+ register char *cp;
+ struct stat stb;
+ char *optarget;
+ int len, failures = 0;
+
+ switch (statb->st_mode & S_IFMT) {
+ case S_IFREG:
+ case S_IFLNK:
+ if (unlink(target) < 0) {
+ if (errno == ETXTBSY) {
+ message(MT_REMOTE|MT_NOTICE,
+ "%s: unlink failed: %s",
+ target, SYSERR);
+ return(0);
+ } else {
+ error("%s: unlink failed: %s", target, SYSERR);
+ return(-1);
+ }
+ }
+ goto removed;
+
+ case S_IFDIR:
+ break;
+
+ default:
+ error("%s: not a plain file", target);
+ return(-1);
+ }
+
+ errno = 0;
+ if ((d = opendir(target)) == NULL) {
+ error("%s: opendir failed: %s", target, SYSERR);
+ return(-1);
+ }
+
+ optarget = ptarget;
+ len = ptarget - target;
+ while (dp = readdir(d)) {
+ if ((D_NAMLEN(dp) == 1 && dp->d_name[0] == '.') ||
+ (D_NAMLEN(dp) == 2 && dp->d_name[0] == '.' &&
+ dp->d_name[1] == '.'))
+ continue;
+
+ if (len + 1 + (int)strlen(dp->d_name) >= MAXPATHLEN - 1) {
+ message(MT_REMOTE|MT_WARNING, "%s/%s: Name too long",
+ target, dp->d_name);
+ continue;
+ }
+ ptarget = optarget;
+ *ptarget++ = '/';
+ cp = dp->d_name;;
+ while (*ptarget++ = *cp++)
+ ;
+ ptarget--;
+ if (lstat(target, &stb) < 0) {
+ message(MT_REMOTE|MT_WARNING, "%s: lstat failed: %s",
+ target, SYSERR);
+ continue;
+ }
+ if (removefile(&stb) < 0)
+ ++failures;
+ }
+ (void) closedir(d);
+ ptarget = optarget;
+ *ptarget = CNULL;
+
+ if (failures)
+ return(-1);
+
+ if (rmdir(target) < 0) {
+ error("%s: rmdir failed: %s", target, SYSERR);
+ return(-1);
+ }
+removed:
+ message(MT_CHANGE|MT_REMOTE, "%s: removed", target);
+ return(0);
+}
+
+/*
+ * Check the current directory (initialized by the 'T' command to server())
+ * for extraneous files and remove them.
+ */
+static void doclean(cp)
+ register char *cp;
+{
+ DIR *d;
+ register DIRENTRY *dp;
+ struct stat stb;
+ char *optarget, *ep;
+ int len;
+ opt_t opts;
+
+ opts = strtol(cp, &ep, 8);
+ if (*ep != CNULL) {
+ error("clean: options not delimited");
+ return;
+ }
+ if ((d = opendir(target)) == NULL) {
+ error("%s: opendir failed: %s", target, SYSERR);
+ return;
+ }
+ ack();
+
+ optarget = ptarget;
+ len = ptarget - target;
+ while (dp = readdir(d)) {
+ if ((D_NAMLEN(dp) == 1 && dp->d_name[0] == '.') ||
+ (D_NAMLEN(dp) == 2 && dp->d_name[0] == '.' &&
+ dp->d_name[1] == '.'))
+ continue;
+
+ if (len + 1 + (int)strlen(dp->d_name) >= MAXPATHLEN - 1) {
+ message(MT_REMOTE|MT_WARNING, "%s/%s: Name too long",
+ target, dp->d_name);
+ continue;
+ }
+ ptarget = optarget;
+ *ptarget++ = '/';
+ cp = dp->d_name;;
+ while (*ptarget++ = *cp++)
+ ;
+ ptarget--;
+ if (lstat(target, &stb) < 0) {
+ message(MT_REMOTE|MT_WARNING, "%s: lstat failed: %s",
+ target, SYSERR);
+ continue;
+ }
+
+ (void) sendcmd(CC_QUERY, "%s", dp->d_name);
+ (void) remline(cp = buf, sizeof(buf), TRUE);
+
+ if (*cp != CC_YES)
+ continue;
+
+ if (IS_ON(opts, DO_VERIFY))
+ message(MT_REMOTE|MT_INFO, "%s: need to remove",
+ target);
+ else
+ (void) removefile(&stb);
+ }
+ (void) closedir(d);
+
+ ptarget = optarget;
+ *ptarget = CNULL;
+}
+
+/*
+ * Frontend to doclean().
+ */
+static void clean(cp)
+ register char *cp;
+{
+ doclean(cp);
+ (void) sendcmd(CC_END, NULL);
+ (void) response();
+}
+
+/*
+ * Execute a shell command to handle special cases.
+ * We can't really set an alarm timeout here since we
+ * have no idea how long the command should take.
+ */
+static void dospecial(cmd)
+ char *cmd;
+{
+ runcommand(cmd);
+}
+
+/*
+ * Do a special cmd command. This differs from normal special
+ * commands in that it's done after an entire command has been updated.
+ * The list of updated target files is sent one at a time with RC_FILE
+ * commands. Each one is added to an environment variable defined by
+ * E_FILES. When an RC_COMMAND is finally received, the E_FILES variable
+ * is stuffed into our environment and a normal dospecial() command is run.
+ */
+static void docmdspecial()
+{
+ register char *cp;
+ char *cmd, *env = NULL;
+ int n;
+ int len;
+
+ /* We're ready */
+ ack();
+
+ for ( ; ; ) {
+ n = remline(cp = buf, sizeof(buf), FALSE);
+ if (n <= 0) {
+ error("cmdspecial: premature end of input.");
+ return;
+ }
+
+ switch (*cp++) {
+ case RC_FILE:
+ if (env == NULL) {
+ len = (2 * sizeof(E_FILES)) + strlen(cp) + 10;
+ env = (char *) xmalloc(len);
+ (void) sprintf(env, "export %s;%s=%s",
+ E_FILES, E_FILES, cp);
+ } else {
+ len = strlen(env);
+ env = (char *) xrealloc(env,
+ len + strlen(cp) + 2);
+ env[len] = CNULL;
+ (void) strcat(env, ":");
+ (void) strcat(env, cp);
+ }
+ ack();
+ break;
+
+ case RC_COMMAND:
+ if (env) {
+ len = strlen(env);
+ env = (char *) xrealloc(env,
+ len + strlen(cp) + 2);
+ env[len] = CNULL;
+ (void) strcat(env, ";");
+ (void) strcat(env, cp);
+ cmd = env;
+ } else
+ cmd = cp;
+
+ dospecial(cmd);
+ if (env)
+ (void) free(env);
+ return;
+
+ default:
+ error("Unknown cmdspecial command '%s'.", cp);
+ return;
+ }
+ }
+}
+
+/*
+ * Query. Check to see if file exists. Return one of the following:
+ *
+#ifdef NFS_CHECK
+ * QC_ONNFS - resides on a NFS
+#endif NFS_CHECK
+#ifdef RO_CHECK
+ * QC_ONRO - resides on a Read-Only filesystem
+#endif RO_CHECK
+ * QC_NO - doesn't exist
+ * QC_YESsize mtime - exists and its a regular file (size & mtime of file)
+ * QC_YES - exists and its a directory or symbolic link
+ * QC_ERRMSGmessage - error message
+ */
+static void query(name)
+ char *name;
+{
+ static struct stat stb;
+ int s = -1, stbvalid = 0;
+
+ if (catname && cattarget(name) < 0)
+ return;
+
+#if defined(NFS_CHECK)
+ if (IS_ON(options, DO_CHKNFS)) {
+ s = is_nfs_mounted(target, &stb, &stbvalid);
+ if (s > 0)
+ (void) sendcmd(QC_ONNFS, NULL);
+
+ /* Either the above check was true or an error occured */
+ /* and is_nfs_mounted sent the error message */
+ if (s != 0) {
+ *ptarget = CNULL;
+ return;
+ }
+ }
+#endif /* NFS_CHECK */
+
+#if defined(RO_CHECK)
+ if (IS_ON(options, DO_CHKREADONLY)) {
+ s = is_ro_mounted(target, &stb, &stbvalid);
+ if (s > 0)
+ (void) sendcmd(QC_ONRO, NULL);
+
+ /* Either the above check was true or an error occured */
+ /* and is_ro_mounted sent the error message */
+ if (s != 0) {
+ *ptarget = CNULL;
+ return;
+ }
+ }
+#endif /* RO_CHECK */
+
+ if (IS_ON(options, DO_CHKSYM)) {
+ if (is_symlinked(target, &stb, &stbvalid) > 0) {
+ (void) sendcmd(QC_SYM, NULL);
+ return;
+ }
+ }
+
+ /*
+ * If stbvalid is false, "stb" is not valid because:
+ * a) RO_CHECK and NFS_CHECK were not defined
+ * b) The stat by is_*_mounted() either failed or
+ * does not match "target".
+ */
+ if (!stbvalid && lstat(target, &stb) < 0) {
+ if (errno == ENOENT)
+ (void) sendcmd(QC_NO, NULL);
+ else
+ error("%s: lstat failed: %s", target, SYSERR);
+ *ptarget = CNULL;
+ return;
+ }
+
+ switch (stb.st_mode & S_IFMT) {
+ case S_IFLNK:
+ case S_IFDIR:
+ case S_IFREG:
+ (void) sendcmd(QC_YES, "%ld %ld %o %s %s",
+ (long) stb.st_size,
+ stb.st_mtime,
+ stb.st_mode & 07777,
+ getusername(stb.st_uid, target, options),
+ getgroupname(stb.st_gid, target, options));
+ break;
+
+ default:
+ error("%s: not a file or directory", target);
+ break;
+ }
+ *ptarget = CNULL;
+}
+
+/*
+ * Check to see if parent directory exists and create one if not.
+ */
+static int chkparent(name, opts)
+ char *name;
+ opt_t opts;
+{
+ register char *cp;
+ struct stat stb;
+ int r = -1;
+
+ debugmsg(DM_CALL, "chkparent(%s, %o) start\n", name, opts);
+
+ cp = strrchr(name, '/');
+ if (cp == NULL || cp == name)
+ return(0);
+
+ *cp = CNULL;
+
+ if (lstat(name, &stb) < 0) {
+ if (errno == ENOENT && chkparent(name, opts) >= 0) {
+ if (mkdir(name, 0777 & ~oumask) == 0) {
+ message(MT_NOTICE, "%s: mkdir", name);
+ r = 0;
+ } else
+ debugmsg(DM_MISC,
+ "chkparent(%s, %o) mkdir fail: %s\n",
+ name, opts, SYSERR);
+ }
+ } else /* It exists */
+ r = 0;
+
+ /* Put back what we took away */
+ *cp = '/';
+
+ return(r);
+}
+
+/*
+ * Save a copy of 'file' by renaming it.
+ */
+static char *savetarget(file)
+ char *file;
+{
+ static char savefile[MAXPATHLEN];
+
+ if (strlen(file) + sizeof(SAVE_SUFFIX) + 1 > MAXPATHLEN) {
+ error("%s: Cannot save: Save name too long", file);
+ return((char *) NULL);
+ }
+
+ (void) sprintf(savefile, "%s%s", file, SAVE_SUFFIX);
+
+ if (unlink(savefile) != 0 && errno != ENOENT) {
+ message(MT_NOTICE, "%s: remove failed: %s", savefile, SYSERR);
+ return((char *) NULL);
+ }
+
+ if (rename(file, savefile) != 0 && errno != ENOENT) {
+ error("%s -> %s: rename failed: %s",
+ file, savefile, SYSERR);
+ return((char *) NULL);
+ }
+
+ return(savefile);
+}
+
+/*
+ * Receive a file
+ */
+static void recvfile(new, opts, mode, owner, group, mtime, atime, size)
+ /*ARGSUSED*/
+ char *new;
+ opt_t opts;
+ int mode;
+ char *owner, *group;
+ time_t mtime;
+ time_t atime;
+ off_t size;
+{
+ int f, wrerr, olderrno;
+ off_t i;
+ register char *cp;
+ char *savefile = NULL;
+
+ /*
+ * Create temporary file
+ */
+ if ((f = creat(new, mode)) < 0) {
+ if (errno != ENOENT || chkparent(new, opts) < 0 ||
+ (f = creat(new, mode)) < 0) {
+ error("%s: create failed: %s", new, SYSERR);
+ (void) unlink(new);
+ return;
+ }
+ }
+
+ /*
+ * Receive the file itself
+ */
+ ack();
+ wrerr = 0;
+ olderrno = 0;
+ for (i = 0; i < size; i += BUFSIZ) {
+ int amt = BUFSIZ;
+
+ cp = buf;
+ if (i + amt > size)
+ amt = size - i;
+ do {
+ int j;
+
+ j = readrem(cp, amt);
+ if (j <= 0) {
+ (void) close(f);
+ (void) unlink(new);
+ fatalerr(
+ "Read error occured while receiving file.");
+ finish();
+ }
+ amt -= j;
+ cp += j;
+ } while (amt > 0);
+ amt = BUFSIZ;
+ if (i + amt > size)
+ amt = size - i;
+ if (wrerr == 0 && xwrite(f, buf, amt) != amt) {
+ olderrno = errno;
+ wrerr++;
+ }
+ }
+
+ if (response() < 0) {
+ (void) close(f);
+ (void) unlink(new);
+ return;
+ }
+ if (wrerr) {
+ error("%s: Write error: %s", new, strerror(olderrno));
+ (void) close(f);
+ (void) unlink(new);
+ return;
+ }
+
+ /*
+ * Do file comparison if enabled
+ */
+ if (IS_ON(opts, DO_COMPARE)) {
+ FILE *f1, *f2;
+ int c;
+
+ errno = 0; /* fopen is not a syscall */
+ if ((f1 = fopen(target, "r")) == NULL) {
+ error("%s: open for read failed: %s", target, SYSERR);
+ (void) close(f);
+ (void) unlink(new);
+ return;
+ }
+ errno = 0;
+ if ((f2 = fopen(new, "r")) == NULL) {
+ error("%s: open for read failed: %s", new, SYSERR);
+ (void) close(f);
+ (void) unlink(new);
+ return;
+ }
+ while ((c = getc(f1)) == getc(f2))
+ if (c == EOF) {
+ debugmsg(DM_MISC,
+ "Files are the same '%s' '%s'.",
+ target, new);
+ (void) fclose(f1);
+ (void) fclose(f2);
+ (void) close(f);
+ (void) unlink(new);
+ /*
+ * This isn't an error per-se, but we
+ * need to indicate to the master that
+ * the file was not updated.
+ */
+ error("");
+ return;
+ }
+ debugmsg(DM_MISC, "Files are different '%s' '%s'.",
+ target, new);
+ (void) fclose(f1);
+ (void) fclose(f2);
+ if (IS_ON(opts, DO_VERIFY)) {
+ message(MT_REMOTE|MT_INFO, "%s: need to update",
+ target);
+ (void) close(f);
+ (void) unlink(new);
+ return;
+ }
+ }
+
+ /*
+ * Set owner, group, and file mode
+ */
+ if (fchog(f, new, owner, group, mode) < 0) {
+ (void) close(f);
+ (void) unlink(new);
+ return;
+ }
+ (void) close(f);
+
+ /*
+ * Perform utimes() after file is closed to make
+ * certain OS's, such as NeXT 2.1, happy.
+ */
+ if (setfiletime(new, time((time_t *) 0), mtime) < 0)
+ message(MT_NOTICE, "%s: utimes failed: %s", new, SYSERR);
+
+ /*
+ * Try to save target file from being over-written
+ */
+ if (IS_ON(opts, DO_SAVETARGETS))
+ if ((savefile = savetarget(target)) == NULL) {
+ (void) unlink(new);
+ return;
+ }
+
+ /*
+ * Install new (temporary) file as the actual target
+ */
+ if (rename(new, target) < 0) {
+ /*
+ * If the rename failed due to "Text file busy", then
+ * try to rename the target file and retry the rename.
+ */
+ if (errno == ETXTBSY) {
+ /* Save the target */
+ if ((savefile = savetarget(target)) != NULL) {
+ /* Retry installing new file as target */
+ if (rename(new, target) < 0) {
+ error("%s -> %s: rename failed: %s",
+ new, target, SYSERR);
+ /* Try to put back save file */
+ if (rename(savefile, target) < 0)
+ error(
+ "%s -> %s: rename failed: %s",
+ savefile, target,
+ SYSERR);
+ } else
+ message(MT_NOTICE, "%s: renamed to %s",
+ target, savefile);
+ }
+ } else {
+ error("%s -> %s: rename failed: %s",
+ new, target, SYSERR);
+ (void) unlink(new);
+ }
+ }
+
+ if (IS_ON(opts, DO_COMPARE))
+ message(MT_REMOTE|MT_CHANGE, "%s: updated", target);
+ else
+ ack();
+}
+
+/*
+ * Receive a directory
+ */
+static void recvdir(opts, mode, owner, group)
+ opt_t opts;
+ int mode;
+ char *owner, *group;
+{
+ static char lowner[100], lgroup[100];
+ register char *cp;
+ struct stat stb;
+ int s;
+
+ s = lstat(target, &stb);
+ if (s == 0) {
+ /*
+ * If target is not a directory, remove it
+ */
+ if (!S_ISDIR(stb.st_mode)) {
+ if (IS_ON(opts, DO_VERIFY))
+ message(MT_NOTICE, "%s: need to remove",
+ target);
+ else {
+ if (unlink(target) < 0) {
+ error("%s: remove failed: %s",
+ target, SYSERR);
+ return;
+ }
+ }
+ s = -1;
+ errno = ENOENT;
+ } else {
+ if (!IS_ON(opts, DO_NOCHKMODE) &&
+ (stb.st_mode & 07777) != mode) {
+ if (IS_ON(opts, DO_VERIFY))
+ message(MT_NOTICE,
+ "%s: need to chmod to %o",
+ target, mode);
+ else {
+ if (chmod(target, mode) != 0)
+ message(MT_NOTICE,
+ "%s: chmod from %o to %o failed: %s",
+ target,
+ stb.st_mode & 07777,
+ mode,
+ SYSERR);
+ else
+ message(MT_NOTICE,
+ "%s: chmod from %o to %o",
+ target,
+ stb.st_mode & 07777,
+ mode);
+ }
+ }
+
+ /*
+ * Check ownership and set if necessary
+ */
+ lowner[0] = CNULL;
+ lgroup[0] = CNULL;
+
+ if (!IS_ON(opts, DO_NOCHKOWNER) && owner) {
+ int o;
+
+ o = (owner[0] == ':') ? opts & DO_NUMCHKOWNER :
+ opts;
+ if (cp = getusername(stb.st_uid, target, o))
+ if (strcmp(owner, cp))
+ (void) strcpy(lowner, cp);
+ }
+ if (!IS_ON(opts, DO_NOCHKGROUP) && group) {
+ int o;
+
+ o = (group[0] == ':') ? opts & DO_NUMCHKGROUP :
+ opts;
+ if (cp = getgroupname(stb.st_gid, target, o))
+ if (strcmp(group, cp))
+ (void) strcpy(lgroup, cp);
+ }
+
+ /*
+ * Need to set owner and/or group
+ */
+#define PRN(n) ((n[0] == ':') ? n+1 : n)
+ if (lowner[0] != CNULL || lgroup[0] != CNULL) {
+ if (lowner[0] == CNULL &&
+ (cp = getusername(stb.st_uid,
+ target, opts)))
+ (void) strcpy(lowner, cp);
+ if (lgroup[0] == CNULL &&
+ (cp = getgroupname(stb.st_gid,
+ target, opts)))
+ (void) strcpy(lgroup, cp);
+
+ if (IS_ON(opts, DO_VERIFY))
+ message(MT_NOTICE,
+ "%s: need to chown from %s.%s to %s.%s",
+ target,
+ PRN(lowner), PRN(lgroup),
+ PRN(owner), PRN(group));
+ else {
+ if (fchog(-1, target, owner,
+ group, -1) == 0)
+ message(MT_NOTICE,
+ "%s: chown from %s.%s to %s.%s",
+ target,
+ PRN(lowner),
+ PRN(lgroup),
+ PRN(owner),
+ PRN(group));
+ }
+ }
+#undef PRN
+ ack();
+ return;
+ }
+ }
+
+ if (IS_ON(opts, DO_VERIFY)) {
+ ack();
+ return;
+ }
+
+ /*
+ * Create the directory
+ */
+ if (s < 0) {
+ if (errno == ENOENT) {
+ if (mkdir(target, mode) == 0 ||
+ chkparent(target, opts) == 0 &&
+ mkdir(target, mode) == 0) {
+ message(MT_NOTICE, "%s: mkdir", target);
+ (void) fchog(-1, target, owner, group, mode);
+ ack();
+ } else {
+ error("%s: mkdir failed: %s", target, SYSERR);
+ ptarget = sptarget[--catname];
+ *ptarget = CNULL;
+ }
+ return;
+ }
+ }
+ error("%s: lstat failed: %s", target, SYSERR);
+ ptarget = sptarget[--catname];
+ *ptarget = CNULL;
+}
+
+/*
+ * Receive a link
+ */
+static void recvlink(new, opts, mode, size)
+ char *new;
+ opt_t opts;
+ int mode;
+ off_t size;
+{
+ struct stat stb;
+ char *optarget;
+ off_t i;
+
+ /*
+ * Read basic link info
+ */
+ ack();
+ (void) remline(buf, sizeof(buf), TRUE);
+
+ if (response() < 0) {
+ err();
+ return;
+ }
+
+ /*
+ * Make new symlink using a temporary name
+ */
+ if (symlink(buf, new) < 0) {
+ if (errno != ENOENT || chkparent(new, opts) < 0 ||
+ symlink(buf, new) < 0) {
+ error("%s -> %s: symlink failed: %s", new, buf,SYSERR);
+ (void) unlink(new);
+ return;
+ }
+ }
+
+ /*
+ * Do comparison of what link is pointing to if enabled
+ */
+ mode &= 0777;
+ if (IS_ON(opts, DO_COMPARE)) {
+ char tbuf[MAXPATHLEN];
+
+ if ((i = readlink(target, tbuf, sizeof(tbuf))) >= 0 &&
+ i == size && strncmp(buf, tbuf, (int) size) == 0) {
+ (void) unlink(new);
+ ack();
+ return;
+ }
+ if (IS_ON(opts, DO_VERIFY)) {
+ (void) unlink(new);
+ message(MT_REMOTE|MT_INFO, "%s: need to update",
+ target);
+ (void) sendcmd(C_END, NULL);
+ (void) response();
+ return;
+ }
+ }
+
+ /*
+ * See if target is a directory and remove it if it is
+ */
+ if (lstat(target, &stb) == 0) {
+ if (S_ISDIR(stb.st_mode)) {
+ optarget = ptarget;
+ for (ptarget = target; *ptarget; ptarget++);
+ if (removefile(&stb) < 0) {
+ ptarget = optarget;
+ (void) unlink(new);
+ (void) sendcmd(C_END, NULL);
+ (void) response();
+ return;
+ }
+ ptarget = optarget;
+ }
+ }
+
+ /*
+ * Install link as the target
+ */
+ if (rename(new, target) < 0) {
+ error("%s -> %s: symlink rename failed: %s",
+ new, target, SYSERR);
+ (void) unlink(new);
+ (void) sendcmd(C_END, NULL);
+ (void) response();
+ return;
+ }
+
+ if (IS_ON(opts, DO_COMPARE))
+ message(MT_REMOTE|MT_CHANGE, "%s: updated", target);
+ else
+ ack();
+
+ /*
+ * Indicate end of receive operation
+ */
+ (void) sendcmd(C_END, NULL);
+ (void) response();
+}
+
+/*
+ * Creat a hard link to existing file.
+ */
+static void hardlink(cmd)
+ char *cmd;
+{
+ struct stat stb;
+ int exists = 0;
+ char *oldname, *newname;
+ char *cp = cmd;
+ static char expbuf[BUFSIZ];
+
+ /* Skip over opts */
+ (void) strtol(cp, &cp, 8);
+ if (*cp++ != ' ') {
+ error("hardlink: options not delimited");
+ return;
+ }
+
+ oldname = strtok(cp, " ");
+ if (oldname == NULL) {
+ error("hardlink: oldname name not delimited");
+ return;
+ }
+
+ newname = strtok((char *)NULL, " ");
+ if (newname == NULL) {
+ error("hardlink: new name not specified");
+ return;
+ }
+
+ if (exptilde(expbuf, oldname) == NULL) {
+ error("hardlink: tilde expansion failed");
+ return;
+ }
+ oldname = expbuf;
+
+ if (catname && cattarget(newname) < 0) {
+ error("Cannot set newname target.");
+ return;
+ }
+
+ if (lstat(target, &stb) == 0) {
+ int mode = stb.st_mode & S_IFMT;
+
+ if (mode != S_IFREG && mode != S_IFLNK) {
+ error("%s: not a regular file", target);
+ return;
+ }
+ exists = 1;
+ }
+
+ if (chkparent(target, options) < 0 ) {
+ error("%s: no parent: %s ", target, SYSERR);
+ return;
+ }
+ if (exists && (unlink(target) < 0)) {
+ error("%s: unlink failed: %s", target, SYSERR);
+ return;
+ }
+ if (link(oldname, target) < 0) {
+ error("%s: cannot link to %s: %s", target, oldname, SYSERR);
+ return;
+ }
+ ack();
+}
+
+/*
+ * Set configuration information.
+ *
+ * A key letter is followed immediately by the value
+ * to set. The keys are:
+ * SC_FREESPACE - Set minimium free space of filesystem
+ * SC_FREEFILES - Set minimium free number of files of filesystem
+ */
+static void setconfig(cmd)
+ char *cmd;
+{
+ register char *cp = cmd;
+ char *estr;
+
+ switch (*cp++) {
+ case SC_HOSTNAME: /* Set hostname */
+ /*
+ * Only use info if we don't know who this is.
+ */
+ if (!fromhost) {
+ fromhost = strdup(cp);
+ message(MT_SYSLOG, "startup for %s", fromhost);
+#if defined(SETARGS)
+ setproctitle("serving %s", cp);
+#endif /* SETARGS */
+ }
+ break;
+
+ case SC_FREESPACE: /* Minimium free space */
+ if (!isdigit(*cp)) {
+ fatalerr("Expected digit, got '%s'.", cp);
+ return;
+ }
+ min_freespace = (unsigned long) atoi(cp);
+ break;
+
+ case SC_FREEFILES: /* Minimium free files */
+ if (!isdigit(*cp)) {
+ fatalerr("Expected digit, got '%s'.", cp);
+ return;
+ }
+ min_freefiles = (unsigned long) atoi(cp);
+ break;
+
+ case SC_LOGGING: /* Logging options */
+ if (estr = msgparseopts(cp, TRUE)) {
+ fatalerr("Bad message option string (%s): %s",
+ cp, estr);
+ return;
+ }
+ break;
+
+ default:
+ message(MT_NOTICE, "Unknown config command \"%s\".", cp-1);
+ return;
+ }
+}
+
+/*
+ * Receive something
+ */
+static void recvit(cmd, type)
+ char *cmd;
+ int type;
+{
+ int mode;
+ opt_t opts;
+ off_t size;
+ time_t mtime, atime;
+ char *owner, *group, *file;
+ char new[MAXPATHLEN];
+ int freespace = -1, freefiles = -1;
+ char *cp = cmd;
+
+ /*
+ * Get rdist option flags
+ */
+ opts = strtol(cp, &cp, 8);
+ if (*cp++ != ' ') {
+ error("recvit: options not delimited");
+ return;
+ }
+
+ /*
+ * Get file mode
+ */
+ mode = strtol(cp, &cp, 8);
+ if (*cp++ != ' ') {
+ error("recvit: mode not delimited");
+ return;
+ }
+
+ /*
+ * Get file size
+ */
+ size = strtol(cp, &cp, 10);
+ if (*cp++ != ' ') {
+ error("recvit: size not delimited");
+ return;
+ }
+
+ /*
+ * Get modification time
+ */
+ mtime = strtol(cp, &cp, 10);
+ if (*cp++ != ' ') {
+ error("recvit: mtime not delimited");
+ return;
+ }
+
+ /*
+ * Get access time
+ */
+ atime = strtol(cp, &cp, 10);
+ if (*cp++ != ' ') {
+ error("recvit: atime not delimited");
+ return;
+ }
+
+ /*
+ * Get file owner name
+ */
+ owner = strtok(cp, " ");
+ if (owner == NULL) {
+ error("recvit: owner name not delimited");
+ return;
+ }
+
+ /*
+ * Get file group name
+ */
+ group = strtok((char *)NULL, " ");
+ if (group == NULL) {
+ error("recvit: group name not delimited");
+ return;
+ }
+
+ /*
+ * Get file name. Can't use strtok() since there could
+ * be white space in the file name.
+ */
+ file = group + strlen(group) + 1;
+ if (file == NULL) {
+ error("recvit: no file name");
+ return;
+ }
+
+ debugmsg(DM_MISC,
+ "recvit: opts = %04o mode = %04o size = %d mtime = %d",
+ opts, mode, size, mtime);
+ debugmsg(DM_MISC,
+ "recvit: owner = '%s' group = '%s' file = '%s' catname = %d isdir = %d",
+ owner, group, file, catname, (type == S_IFDIR) ? 1 : 0);
+
+ if (type == S_IFDIR) {
+ if (catname >= sizeof(sptarget)) {
+ error("%s: too many directory levels", target);
+ return;
+ }
+ sptarget[catname] = ptarget;
+ if (catname++) {
+ *ptarget++ = '/';
+ while (*ptarget++ = *file++)
+ ;
+ ptarget--;
+ }
+ } else {
+ /*
+ * Create name of temporary file
+ */
+ if (catname && cattarget(file) < 0) {
+ error("Cannot set file name.");
+ return;
+ }
+ file = strrchr(target, '/');
+ if (file == NULL)
+ (void) strcpy(new, tempname);
+ else if (file == target)
+ (void) sprintf(new, "/%s", tempname);
+ else {
+ *file = CNULL;
+ (void) sprintf(new, "%s/%s", target, tempname);
+ *file = '/';
+ }
+ (void) mktemp(new);
+ }
+
+ /*
+ * Check to see if there is enough free space and inodes
+ * to install this file.
+ */
+ if (min_freespace || min_freefiles) {
+ /* Convert file size to kilobytes */
+ int fsize = size / 1024;
+
+ if (getfilesysinfo(target, &freespace, &freefiles) != 0)
+ return;
+
+ /*
+ * filesystem values < 0 indicate unsupported or unavailable
+ * information.
+ */
+ if (min_freespace && (freespace >= 0) &&
+ (freespace - fsize < min_freespace)) {
+ error(
+ "%s: Not enough free space on filesystem: min %d free %d",
+ target, min_freespace, freespace);
+ return;
+ }
+ if (min_freefiles && (freefiles >= 0) &&
+ (freefiles - 1 < min_freefiles)) {
+ error(
+ "%s: Not enough free files on filesystem: min %d free %d",
+ target, min_freefiles, freefiles);
+ return;
+ }
+ }
+
+ /*
+ * Call appropriate receive function to receive file
+ */
+ switch (type) {
+ case S_IFDIR:
+ recvdir(opts, mode, owner, group);
+ break;
+
+ case S_IFLNK:
+ recvlink(new, opts, mode, size);
+ break;
+
+ case S_IFREG:
+ recvfile(new, opts, mode, owner, group, mtime, atime, size);
+ break;
+
+ default:
+ error("%d: unknown file type", type);
+ break;
+ }
+}
+
+/*
+ * Set target information
+ */
+static void settarget(cmd, isdir)
+ char *cmd;
+ int isdir;
+{
+ char *cp = cmd;
+ opt_t opts;
+
+ catname = isdir;
+
+ /*
+ * Parse options for this target
+ */
+ opts = strtol(cp, &cp, 8);
+ if (*cp++ != ' ') {
+ error("settarget: options not delimited");
+ return;
+ }
+ options = opts;
+
+ /*
+ * Handle target
+ */
+ if (exptilde(target, cp) == NULL)
+ return;
+ ptarget = target;
+ while (*ptarget)
+ ptarget++;
+
+ ack();
+}
+
+/*
+ * Cleanup in preparation for exiting.
+ */
+extern void cleanup()
+{
+ /* We don't need to do anything */
+}
+
+/*
+ * Server routine to read requests and process them.
+ */
+extern void server()
+{
+ static char cmdbuf[BUFSIZ];
+ register char *cp;
+ register int n;
+ extern jmp_buf finish_jmpbuf;
+
+ if (setjmp(finish_jmpbuf))
+ return;
+ (void) signal(SIGHUP, sighandler);
+ (void) signal(SIGINT, sighandler);
+ (void) signal(SIGQUIT, sighandler);
+ (void) signal(SIGTERM, sighandler);
+ (void) signal(SIGPIPE, sighandler);
+ (void) umask(oumask = umask(0));
+ (void) strcpy(tempname, _RDIST_TMP);
+ if (fromhost) {
+ message(MT_SYSLOG, "Startup for %s", fromhost);
+#if defined(SETARGS)
+ setproctitle("Serving %s", fromhost);
+#endif /* SETARGS */
+ }
+
+ /*
+ * Let client know we want it to send it's version number
+ */
+ (void) sendcmd(S_VERSION, NULL);
+
+ if (remline(cmdbuf, sizeof(cmdbuf), TRUE) < 0) {
+ error("server: expected control record");
+ return;
+ }
+
+ if (cmdbuf[0] != S_VERSION || !isdigit(cmdbuf[1])) {
+ error("Expected version command, received: \"%s\".", cmdbuf);
+ return;
+ }
+
+ proto_version = atoi(&cmdbuf[1]);
+ if (proto_version != VERSION) {
+ error("Protocol version %d is not supported.", proto_version);
+ return;
+ }
+
+ /* Version number is okay */
+ ack();
+
+ /*
+ * Main command loop
+ */
+ for ( ; ; ) {
+ n = remline(cp = cmdbuf, sizeof(cmdbuf), TRUE);
+ if (n == -1) /* EOF */
+ return;
+ if (n == 0) {
+ error("server: expected control record");
+ continue;
+ }
+
+ switch (*cp++) {
+ case C_SETCONFIG: /* Configuration info */
+ setconfig(cp);
+ ack();
+ continue;
+
+ case C_DIRTARGET: /* init target file/directory name */
+ settarget(cp, TRUE);
+ continue;
+
+ case C_TARGET: /* init target file/directory name */
+ settarget(cp, FALSE);
+ continue;
+
+ case C_RECVREG: /* Transfer a regular file. */
+ recvit(cp, S_IFREG);
+ continue;
+
+ case C_RECVDIR: /* Transfer a directory. */
+ recvit(cp, S_IFDIR);
+ continue;
+
+ case C_RECVSYMLINK: /* Transfer symbolic link. */
+ recvit(cp, S_IFLNK);
+ continue;
+
+ case C_RECVHARDLINK: /* Transfer hard link. */
+ hardlink(cp);
+ continue;
+
+ case C_END: /* End of transfer */
+ *ptarget = CNULL;
+ if (catname <= 0) {
+ error("server: too many '%c's", C_END);
+ continue;
+ }
+ ptarget = sptarget[--catname];
+ *ptarget = CNULL;
+ ack();
+ continue;
+
+ case C_CLEAN: /* Clean. Cleanup a directory */
+ clean(cp);
+ continue;
+
+ case C_QUERY: /* Query file/directory */
+ query(cp);
+ continue;
+
+ case C_SPECIAL: /* Special. Execute commands */
+ dospecial(cp);
+ continue;
+
+ case C_CMDSPECIAL: /* Cmd Special. Execute commands */
+ docmdspecial();
+ continue;
+
+#ifdef DOCHMOD
+ case C_CHMOD: /* Set mode */
+ dochmod(cp);
+ continue;
+#endif /* DOCHMOD */
+
+ case C_ERRMSG: /* Normal error message */
+ if (cp && *cp)
+ message(MT_NERROR|MT_NOREMOTE, "%s", cp);
+ continue;
+
+ case C_FERRMSG: /* Fatal error message */
+ if (cp && *cp)
+ message(MT_FERROR|MT_NOREMOTE, "%s", cp);
+ return;
+
+ default:
+ error("server: unknown command '%s'", cp - 1);
+ case CNULL:
+ continue;
+ }
+ }
+}