/*
* Copyright (c) 1983, 1993
* The 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.
*/
#define ack() do { if (write(rem, "\0\n", 2) < 0) error("ack failed: %s\n", strerror(errno)); } while (0)
#define err() do { if (write(rem, "\1\n", 2) < 0) error("err failed: %s\n", strerror(errno)); } while (0)
struct linkbuf *ihead; /* list of files with more than one link */
char buf[BUFSIZ]; /* general purpose buffer */
char target[BUFSIZ]; /* target/source directory name */
char *tp; /* pointer to end of target name */
char *Tdest; /* pointer to last T dest*/
char *Destcopy; /* pointer to current dest */
int Destcopylen; /* length of destination directory name */
int Sourcelen; /* length of source directory name */
int catname; /* cat name to target name */
char *stp[32]; /* stack of saved tp's for directories */
int oumask; /* old umask for creating files */
extern FILE *lfp; /* log file for mailing changes */
/*
* Server routine to read requests and process them.
* Commands are:
* Tname - Transmit file if out of date
* Vname - Verify if file out of date or not
* Qname - Query if file exists. Return mtime & size if it does.
*/
void
server(void)
{
char cmdbuf[BUFSIZ];
char *cp;
rem = 0;
oumask = umask(0);
(void) snprintf(buf, sizeof(buf), "V%d\n", VERSION);
if (write(rem, buf, strlen(buf)) < 0)
error("server: could not write remote end: %s\n",
strerror(errno));
for (;;) {
cp = cmdbuf;
if (read(rem, cp, 1) <= 0)
return;
if (*cp++ == '\n') {
error("server: expected control record\n");
continue;
}
do {
if (read(rem, cp, 1) != 1)
cleanup(0);
} while (*cp++ != '\n' && cp < &cmdbuf[BUFSIZ]);
*--cp = '\0';
cp = cmdbuf;
switch (*cp++) {
case 'T': /* init target file/directory name */
catname = 1; /* target should be directory */
goto dotarget;
case 't': /* init target file/directory name */
catname = 0;
dotarget:
if (exptilde(target, cp) == NULL)
continue;
tp = target;
while (*tp)
tp++;
ack();
continue;
case 'R': /* Transfer a regular file. */
recvf(cp, S_IFREG);
continue;
case 'D': /* Transfer a directory. */
recvf(cp, S_IFDIR);
continue;
case 'K': /* Transfer symbolic link. */
recvf(cp, S_IFLNK);
continue;
case 'k': /* Transfer hard link. */
hardlink(cp);
continue;
case 'E': /* End. (of directory) */
*tp = '\0';
if (catname <= 0) {
error("server: too many 'E's\n");
continue;
}
tp = stp[--catname];
*tp = '\0';
ack();
continue;
case 'C': /* Clean. Cleanup a directory */
clean(cp);
continue;
case 'Q': /* Query. Does the file/directory exist? */
query(cp);
continue;
case 'S': /* Special. Execute commands */
dospecial(cp);
continue;
#ifdef notdef
/*
* These entries are reserved but not currently used.
* The intent is to allow remote hosts to have master copies.
* Currently, only the host rdist runs on can have masters.
*/
case 'X': /* start a new list of files to exclude */
except = bp = NULL;
case 'x': /* add name to list of files to exclude */
if (*cp == '\0') {
ack();
continue;
}
if (*cp == '~') {
if (exptilde(buf, cp) == NULL)
continue;
cp = buf;
}
if (bp == NULL)
except = bp = expand(makeblock(NAME, cp), E_VARS);
else
bp->b_next = expand(makeblock(NAME, cp), E_VARS);
while (bp->b_next != NULL)
bp = bp->b_next;
ack();
continue;
case 'I': /* Install. Transfer file if out of date. */
opts = 0;
while (*cp >= '0' && *cp <= '7')
opts = (opts << 3) | (*cp++ - '0');
if (*cp++ != ' ') {
error("server: options not delimited\n");
return;
}
install(cp, opts);
continue;
case 'L': /* Log. save message in log file */
log(lfp, cp);
continue;
#endif
/*
* Update the file(s) if they are different.
* destdir = 1 if destination should be a directory
* (i.e., more than one source is being copied to the same destination).
*/
void
install(char *src, char *dest, int destdir, int opts)
{
char *rname;
char destcopy[BUFSIZ];
if (dest == NULL) {
opts &= ~WHOLE; /* WHOLE mode only useful if renaming */
dest = src;
} else if (!(opts & WHOLE)) {
/* prepare for proper renaming of directory trees */
Destcopy = destcopy;
Destcopylen = strlen(dest);
while (Destcopylen > 0 && dest[Destcopylen] == '/')
Destcopylen--;
}
strlcpy(destcopy, dest, sizeof(destcopy));
rname = exptilde(target, src);
if (rname == NULL)
return;
tp = target;
while (*tp)
tp++;
if (Destcopy) {
/* We can only do this after expansion of src */
Sourcelen = strlen(target);
while (Sourcelen > 0 && target[Sourcelen] == '/')
Sourcelen--;
}
/*
* If we are renaming a directory and we want to preserve
* the directory hierarchy (-w), we must strip off the leading
* directory name and preserve the rest.
*/
if (opts & WHOLE) {
while (*rname == '/')
rname++;
destdir = 1;
} else {
rname = strrchr(target, '/');
if (rname == NULL)
rname = target;
else
rname++;
}
if (debug)
printf("target = %s, rname = %s\n", target, rname);
/*
* Pass the destination file/directory name to remote.
*/
(void) snprintf(buf, sizeof(buf), "%c%s\n", destdir ? 'T' : 't', dest);
if (debug)
printf("buf = %s", buf);
if (write(rem, buf, strlen(buf)) < 0)
error("could not pass filename to remote: %s\n",
strerror(errno));
if (response() < 0)
return;
#define protoname() (pw ? pw->pw_name : user)
#define protogroup() (gr ? gr->gr_name : group)
/*
* Transfer the file or directory in target[].
* rname is the name of the file on the remote host.
*/
static void
sendf(char *rname, int opts)
{
struct subcmd *sc;
struct stat stb;
int sizerr, f, u, len;
off_t i;
DIR *d;
struct dirent *dp;
char *otp, *cp;
extern struct subcmd *subcmds;
static char user[15], group[15];
if (debug)
printf("sendf(%s, %x)\n", rname, opts);
for (lp = ihead; lp != NULL; lp = lp->nextp)
if (lp->inum == st->st_ino && lp->devnum == st->st_dev) {
lp->count--;
return(lp);
}
lp = (struct linkbuf *) malloc(sizeof(*lp));
if (lp == NULL)
dolog(lfp, "out of memory, link information lost\n");
else {
lp->nextp = ihead;
ihead = lp;
lp->inum = st->st_ino;
lp->devnum = st->st_dev;
lp->count = st->st_nlink - 1;
if (Destcopy) {
/*
* Change the starting directory of target
* into the destination directory
*/
strncpy(lp->pathname, Destcopy, Destcopylen);
strlcpy(lp->pathname + Destcopylen, target + Sourcelen, sizeof(lp->pathname) - Destcopylen);
} else
strlcpy(lp->pathname, target, sizeof(lp->pathname));
if (Tdest)
strlcpy(lp->target, Tdest, sizeof(lp->target));
else
*lp->target = 0;
}
return(NULL);
}
/*
* Check to see if file needs to be updated on the remote machine.
* Returns 0 if no update, 1 if remote doesn't exist, 2 if out of date
* and 3 if comparing binaries to determine if out of date.
*/
static int
update(char *rname, int opts, struct stat *st)
{
char *cp, *s;
off_t size;
time_t mtime;
if (debug)
printf("update(%s, %lx, %lx)\n", rname, (long)opts, (long)st);
/*
* Check to see if the file exists on the remote machine.
*/
(void) snprintf(buf, sizeof(buf), "Q%s\n", rname);
if (debug)
printf("buf = %s", buf);
if (write(rem, buf, strlen(buf)) < 0)
error("write to remote failed: %s\n", strerror(errno));
again:
cp = s = buf;
do {
if (read(rem, cp, 1) != 1)
lostconn(0);
} while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
switch (*s++) {
case 'Y':
break;
case 'N': /* file doesn't exist so install it */
return(1);
case '\1':
nerrs++;
if (*s != '\n') {
if (!iamremote) {
fflush(stdout);
(void) write(2, s, cp - s);
}
if (lfp != NULL)
(void) fwrite(s, 1, cp - s, lfp);
}
return(0);
case '\3':
*--cp = '\0';
if (lfp != NULL)
dolog(lfp, "update: note: %s\n", s);
goto again;
size = 0;
while (isdigit((unsigned char)*s))
size = size * 10 + (*s++ - '0');
if (*s++ != ' ') {
error("update: size not delimited\n");
return(0);
}
mtime = 0;
while (isdigit((unsigned char)*s))
mtime = mtime * 10 + (*s++ - '0');
if (*s != '\n') {
error("update: mtime not delimited\n");
return(0);
}
/*
* File needs to be updated?
*/
if (opts & YOUNGER) {
if (st->st_mtime == mtime)
return(0);
if (st->st_mtime < mtime) {
dolog(lfp, "Warning: %s: remote copy is newer\n",
target);
return(0);
}
} else if (st->st_mtime == mtime && st->st_size == size)
return(0);
return(2);
}
/*
* Query. Check to see if file exists. Return one of the following:
* N\n - doesn't exist
* Ysize mtime\n - exists and its a regular file (size & mtime of file)
* Y\n - exists and its a directory or symbolic link
* ^Aerror message\n
*/
static void
query(char *name)
{
struct stat stb;
/*
* Check for files on the machine being updated that are not on the master
* machine and remove them.
*/
static void
rmchk(int opts)
{
char *cp, *s;
struct stat stb;
if (debug)
printf("rmchk()\n");
/*
* Tell the remote to clean the files from the last directory sent.
*/
(void) snprintf(buf, sizeof(buf), "C%o\n", opts & VERIFY);
if (debug)
printf("buf = %s", buf);
(void) write(rem, buf, strlen(buf));
if (response() < 0)
return;
for (;;) {
cp = s = buf;
do {
if (read(rem, cp, 1) != 1)
lostconn(0);
} while (*cp++ != '\n' && cp < &buf[BUFSIZ]);
switch (*s++) {
case 'Q': /* Query if file should be removed */
/*
* Return the following codes to remove query.
* N\n -- file exists - DON'T remove.
* Y\n -- file doesn't exist - REMOVE.
*/
*--cp = '\0';
(void) snprintf(tp, sizeof(target) - (tp - target),
"/%s", s);
if (debug)
printf("check %s\n", target);
if (except(target))
(void) write(rem, "N\n", 2);
else if (lstat(target, &stb) < 0)
(void) write(rem, "Y\n", 2);
else
(void) write(rem, "N\n", 2);
break;
case '\0':
*--cp = '\0';
if (*s != '\0')
dolog(lfp, "%s\n", s);
break;
case 'E':
*tp = '\0';
ack();
return;
case '\1':
case '\2':
nerrs++;
if (*s != '\n') {
if (!iamremote) {
fflush(stdout);
(void) write(2, s, cp - s);
}
if (lfp != NULL)
(void) fwrite(s, 1, cp - s, lfp);
}
if (buf[0] == '\2')
lostconn(0);
break;
/*
* Check the current directory (initialized by the 'T' command to server())
* for extraneous files and remove them.
*/
static void
clean(char *cp)
{
DIR *d;
struct dirent *dp;
struct stat stb;
char *otp;
int len, opts;
/*
* Remove a file or directory (recursively) and send back an acknowledge
* or an error message.
*/
static void
removeit(struct stat *st)
{
DIR *d;
struct dirent *dp;
char *cp;
struct stat stb;
char *otp;
int len;
switch (st->st_mode & S_IFMT) {
case S_IFREG:
case S_IFLNK:
if (unlink(target) < 0)
goto bad;
goto removed;
case S_IFDIR:
break;
default:
error("%s:%s: not a plain file\n", host, target);
return;
}