1650 lines
35 KiB
C
1650 lines
35 KiB
C
/*
|
|
* 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.6 1998/12/01 16:43:09 rwu 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"
|
|
#include <limits.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[PATH_MAX/2]; /* stack of saved ptarget's for directories */
|
|
/* assuming the use of a/b/c/d style names */
|
|
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, 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, SYSERR);
|
|
return(-1);
|
|
}
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* Set mode of a file
|
|
*/
|
|
static int setmode(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);
|
|
}
|
|
/*
|
|
* Change owner, group and mode of file.
|
|
*/
|
|
static int fchog(fd, file, owner, group, mode)
|
|
int fd;
|
|
char *file, *owner, *group;
|
|
int mode;
|
|
{
|
|
#ifdef sgi
|
|
/*
|
|
** The following was supposed to cache group structures, but
|
|
** Irix reuses this space. Until someone has the time to
|
|
** fix this right we just turn it off.
|
|
*/
|
|
struct group *gr = NULL;
|
|
#else
|
|
static struct group *gr = NULL;
|
|
#endif
|
|
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, pw->pw_name) != 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;
|
|
} 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 (gr == NULL || strcmp(group, gr->gr_name) != 0) {
|
|
if ((*group == ':' &&
|
|
(getgrgid(gid = atoi(group + 1)) == NULL))
|
|
|| ((gr = (struct group *)getgrnam(group)) == NULL)) {
|
|
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
|
|
gid = gr->gr_gid;
|
|
} else
|
|
gid = gr->gr_gid;
|
|
|
|
if (userid && gid >= 0 && gid != primegid) {
|
|
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 = -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) setmode(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",
|
|
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 && write(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];
|
|
#ifdef sgi
|
|
long long freespace = -1, freefiles = -1;
|
|
#else
|
|
int freespace = -1, freefiles = -1;
|
|
#endif
|
|
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.
|
|
*/
|
|
#ifdef sgi
|
|
|
|
if (min_freespace && (freespace >= 0) &&
|
|
(freespace - (long long)fsize < (long long)min_freespace)) {
|
|
error(
|
|
"%s: Not enough free space on filesystem: min %d free %lld",
|
|
target, min_freespace, freespace);
|
|
return;
|
|
}
|
|
#else
|
|
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;
|
|
}
|
|
#endif
|
|
#ifdef sgi
|
|
if (min_freefiles && (freefiles >= 0) &&
|
|
(freefiles - 1 < (long long)min_freefiles)) {
|
|
error(
|
|
"%s: Not enough free files on filesystem: min %d free %d",
|
|
target, min_freefiles, freefiles);
|
|
return;
|
|
}
|
|
#else
|
|
if (min_freefiles && (freefiles >= 0) &&
|
|
(freefiles - 1 < (long long)min_freefiles)) {
|
|
error(
|
|
"%s: Not enough free files on filesystem: min %d free %d",
|
|
target, min_freefiles, freefiles);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
/*
|
|
* 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)) {
|
|
setjmp_ok = FALSE;
|
|
return;
|
|
}
|
|
setjmp_ok = TRUE;
|
|
(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) {
|
|
setjmp_ok = FALSE;
|
|
error("server: expected control record");
|
|
return;
|
|
}
|
|
|
|
if (cmdbuf[0] != S_VERSION || !isdigit(cmdbuf[1])) {
|
|
setjmp_ok = FALSE;
|
|
error("Expected version command, received: \"%s\".", cmdbuf);
|
|
return;
|
|
}
|
|
|
|
proto_version = atoi(&cmdbuf[1]);
|
|
if (proto_version != VERSION) {
|
|
setjmp_ok = FALSE;
|
|
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 */
|
|
setjmp_ok = FALSE;
|
|
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);
|
|
setjmp_ok = FALSE;
|
|
return;
|
|
|
|
default:
|
|
error("server: unknown command '%s'", cp - 1);
|
|
case CNULL:
|
|
continue;
|
|
}
|
|
}
|
|
}
|