diff options
author | dm <dm@cvs.openbsd.org> | 1996-02-03 12:13:04 +0000 |
---|---|---|
committer | dm <dm@cvs.openbsd.org> | 1996-02-03 12:13:04 +0000 |
commit | f34705a30c83ef27acf58b47c75c736e6858247a (patch) | |
tree | 2121eb2901c910af7604845f5538c2fc26d01ac3 /usr.bin/rdistd/server.c | |
parent | fdb8a9c5e300099e2c65b848550471fb1d9f0cd4 (diff) |
rdist 6.1.1
Diffstat (limited to 'usr.bin/rdistd/server.c')
-rw-r--r-- | usr.bin/rdistd/server.c | 1642 |
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; + } + } +} |