#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "server.h"
char tempname[sizeof _RDIST_TMP + 1];
char buf[BUFSIZ];
char target[PATH_MAX];
char *ptarget;
int catname = 0;
char *sptarget[32];
char *fromhost = NULL;
static int64_t min_freespace = 0;
static int64_t min_freefiles = 0;
int oumask;
static int cattarget(char *);
static int setownership(char *, int, uid_t, gid_t, int);
static int setfilemode(char *, int, int, int);
static int fchog(int, char *, char *, char *, int);
static int removefile(struct stat *, int);
static void doclean(char *);
static void clean(char *);
static void dospecial(char *);
static void docmdspecial(void);
static void query(char *);
static int chkparent(char *, opt_t);
static char *savetarget(char *, opt_t);
static void recvfile(char *, opt_t, int, char *, char *, time_t, time_t, off_t);
static void recvdir(opt_t, int, char *, char *);
static void recvlink(char *, opt_t, int, off_t);
static void hardlink(char *);
static void setconfig(char *);
static void recvit(char *, int);
static void dochmog(char *);
static void settarget(char *, int);
static int
cattarget(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) snprintf(ptarget, sizeof(target) - (ptarget - target),
"/%s", string);
return(0);
}
static int
setownership(char *file, int fd, uid_t uid, gid_t gid, int islink)
{
static int is_root = -1;
int status = -1;
switch (is_root) {
case -1:
is_root = getuid() == 0;
if (is_root)
break;
case 0:
uid = -1;
break;
case 1:
break;
}
if (fd != -1 && !islink)
status = fchown(fd, uid, gid);
else
status = fchownat(AT_FDCWD, file, uid, gid,
AT_SYMLINK_NOFOLLOW);
if (status == -1) {
if (uid == (uid_t)-1)
message(MT_NOTICE, "%s: chgrp %d failed: %s",
target, gid, SYSERR);
else
message(MT_NOTICE, "%s: chown %d:%d failed: %s",
target, uid, gid, SYSERR);
return(-1);
}
return(0);
}
static int
setfilemode(char *file, int fd, int mode, int islink)
{
int status = -1;
if (mode == -1)
return(0);
if (islink)
status = fchmodat(AT_FDCWD, file, mode, AT_SYMLINK_NOFOLLOW);
if (fd != -1 && !islink)
status = fchmod(fd, mode);
if (status == -1 && !islink)
status = chmod(file, mode);
if (status == -1) {
message(MT_NOTICE, "%s: chmod failed: %s", target, SYSERR);
return(-1);
}
return(0);
}
static int
fchog(int fd, char *file, char *owner, char *group, int mode)
{
int i;
struct stat st;
uid_t uid;
gid_t gid;
gid_t primegid = (gid_t)-2;
uid = userid;
if (userid == 0) {
if (*owner == ':') {
uid = (uid_t) atoi(owner + 1);
} else if (strcmp(owner, locuser) != 0) {
if (uid_from_user(owner, &uid) == -1) {
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 = userid;
primegid = groupid;
}
if (*group == ':') {
gid = (gid_t)atoi(group + 1);
goto ok;
}
} else {
if (mode != -1) {
if (IS_ON(mode, S_ISUID) &&
strcmp(locuser, owner) != 0)
mode &= ~S_ISUID;
if (mode)
mode &= ~S_ISVTX;
}
primegid = groupid;
}
gid = (gid_t)-1;
if (*group == ':') {
gid = (gid_t) atoi(group + 1);
} else if (gid_from_group(group, &gid) == -1) {
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);
}
if (userid && gid != (gid_t)-1 && gid != primegid) {
for (i = 0; i < gidsetlen; i++) {
if (gid == gidset[i])
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:
if (stat(file, &st) == -1) {
error("%s: Stat failed %s", file, SYSERR);
return -1;
}
if (setownership(file, fd, uid, gid, S_ISLNK(st.st_mode)) < 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, S_ISLNK(st.st_mode));
return(0);
}
static int
removefile(struct stat *statb, int silent)
{
DIR *d;
static struct dirent *dp;
char *cp;
struct stat stb;
char *optarget;
int len, failures = 0;
switch (statb->st_mode & S_IFMT) {
case S_IFREG:
case S_IFLNK:
case S_IFCHR:
case S_IFBLK:
case S_IFSOCK:
case S_IFIFO:
if (unlink(target) == -1) {
if (errno == ETXTBSY) {
if (!silent)
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)) != NULL) {
if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
(dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
continue;
if (len + 1 + (int)strlen(dp->d_name) >= PATH_MAX - 1) {
if (!silent)
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++) != '\0')
continue;
ptarget--;
if (lstat(target, &stb) == -1) {
if (!silent)
message(MT_REMOTE|MT_WARNING,
"%s: lstat failed: %s",
target, SYSERR);
continue;
}
if (removefile(&stb, 0) < 0)
++failures;
}
(void) closedir(d);
ptarget = optarget;
*ptarget = CNULL;
if (failures)
return(-1);
if (rmdir(target) == -1) {
error("%s: rmdir failed: %s", target, SYSERR);
return(-1);
}
removed:
#if NEWWAY
if (!silent)
message(MT_CHANGE|MT_REMOTE, "%s: removed", target);
#else
message(MT_NOTICE|MT_REMOTE, "%s: removed", target);
#endif
return(0);
}
static void
doclean(char *cp)
{
DIR *d;
struct dirent *dp;
struct stat stb;
char *optarget, *ep;
int len;
opt_t opts;
char targ[PATH_MAX*4];
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)) != NULL) {
if (dp->d_name[0] == '.' && (dp->d_name[1] == '\0' ||
(dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
continue;
if (len + 1 + (int)strlen(dp->d_name) >= PATH_MAX - 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++) != '\0')
continue;
ptarget--;
if (lstat(target, &stb) == -1) {
message(MT_REMOTE|MT_WARNING, "%s: lstat failed: %s",
target, SYSERR);
continue;
}
ENCODE(targ, dp->d_name);
(void) sendcmd(CC_QUERY, "%s", targ);
(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, 0);
}
(void) closedir(d);
ptarget = optarget;
*ptarget = CNULL;
}
static void
clean(char *cp)
{
doclean(cp);
(void) sendcmd(CC_END, NULL);
(void) response();
}
static void
dospecial(char *xcmd)
{
char cmd[BUFSIZ];
if (DECODE(cmd, xcmd) == -1) {
error("dospecial: Cannot decode command.");
return;
}
runcommand(cmd);
}
static void
docmdspecial(void)
{
char *cp;
char *cmd, *env = NULL;
int n;
size_t len;
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 = xmalloc(len);
(void) snprintf(env, len, "export %s;%s=%s",
E_FILES, E_FILES, cp);
} else {
len = strlen(env) + 1 + strlen(cp) + 1;
env = xrealloc(env, len);
(void) strlcat(env, ":", len);
(void) strlcat(env, cp, len);
}
ack();
break;
case RC_COMMAND:
if (env) {
len = strlen(env) + 1 + strlen(cp) + 1;
env = xrealloc(env, len);
(void) strlcat(env, ";", len);
(void) strlcat(env, cp, len);
cmd = env;
} else
cmd = cp;
dospecial(cmd);
if (env)
(void) free(env);
return;
default:
error("Unknown cmdspecial command '%s'.", cp);
return;
}
}
}
static void
query(char *xname)
{
static struct stat stb;
int s = -1, stbvalid = 0;
char name[PATH_MAX];
if (DECODE(name, xname) == -1) {
error("query: Cannot decode filename");
return;
}
if (catname && cattarget(name) < 0)
return;
if (IS_ON(options, DO_CHKNFS)) {
s = is_nfs_mounted(target, &stb, &stbvalid);
if (s > 0)
(void) sendcmd(QC_ONNFS, NULL);
if (s != 0) {
*ptarget = CNULL;
return;
}
}
if (IS_ON(options, DO_CHKREADONLY)) {
s = is_ro_mounted(target, &stb, &stbvalid);
if (s > 0)
(void) sendcmd(QC_ONRO, NULL);
if (s != 0) {
*ptarget = CNULL;
return;
}
}
if (IS_ON(options, DO_CHKSYM)) {
if (is_symlinked(target, &stb, &stbvalid) > 0) {
(void) sendcmd(QC_SYM, NULL);
return;
}
}
if (!stbvalid && lstat(target, &stb) == -1) {
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, "%lld %lld %o %s %s",
(long long) stb.st_size,
(long long) 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;
}
static int
chkparent(char *name, opt_t opts)
{
char *cp;
struct stat stb;
int r = -1;
debugmsg(DM_CALL, "chkparent(%s, %#x) start\n", name, opts);
cp = strrchr(name, '/');
if (cp == NULL || cp == name)
return(0);
*cp = CNULL;
if (lstat(name, &stb) == -1) {
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, %#04o) mkdir fail: %s\n",
name, opts, SYSERR);
}
} else
r = 0;
*cp = '/';
return(r);
}
static char *
savetarget(char *file, opt_t opts)
{
static char savefile[PATH_MAX];
if (strlen(file) + sizeof(SAVE_SUFFIX) + 1 > PATH_MAX) {
error("%s: Cannot save: Save name too long", file);
return(NULL);
}
if (IS_ON(opts, DO_HISTORY)) {
int i;
struct stat st;
for (i = 1; i < 1000; i++) {
(void) snprintf(savefile, sizeof(savefile),
"%s;%.3d", file, i);
if (lstat(savefile, &st) == -1 && errno == ENOENT)
break;
}
if (i == 1000) {
message(MT_NOTICE,
"%s: More than 1000 versions for %s; reusing 1\n",
savefile, SYSERR);
i = 1;
(void) snprintf(savefile, sizeof(savefile),
"%s;%.3d", file, i);
}
}
else {
(void) snprintf(savefile, sizeof(savefile), "%s%s",
file, SAVE_SUFFIX);
if (unlink(savefile) != 0 && errno != ENOENT) {
message(MT_NOTICE, "%s: remove failed: %s",
savefile, SYSERR);
return(NULL);
}
}
if (rename(file, savefile) != 0 && errno != ENOENT) {
error("%s -> %s: rename failed: %s",
file, savefile, SYSERR);
return(NULL);
}
return(savefile);
}
static void
recvfile(char *new, opt_t opts, int mode, char *owner, char *group,
time_t mtime, time_t atime, off_t size)
{
int f, wrerr, olderrno;
off_t i;
char *cp;
char *savefile = NULL;
static struct stat statbuff;
if (chkparent(new, opts) < 0 || (f = mkstemp(new)) == -1) {
error("%s: create failed: %s", new, SYSERR);
return;
}
ack();
wrerr = 0;
olderrno = 0;
for (i = 0; i < size; i += BUFSIZ) {
off_t amt = BUFSIZ;
cp = buf;
if (i + amt > size)
amt = size - i;
do {
ssize_t j;
j = readrem(cp, amt);
if (j <= 0) {
(void) close(f);
(void) unlink(new);
fatalerr(
"Read error occurred 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;
}
if (IS_ON(opts, DO_COMPARE)) {
FILE *f1, *f2;
int c;
errno = 0;
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) fclose(f1);
(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);
error(NULL);
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;
}
}
if (fchog(f, new, owner, group, mode) < 0) {
(void) close(f);
(void) unlink(new);
return;
}
(void) close(f);
if (setfiletime(new, time(NULL), mtime) < 0)
message(MT_NOTICE, "%s: utimes failed: %s", new, SYSERR);
if (IS_ON(opts, DO_SAVETARGETS))
if ((savefile = savetarget(target, opts)) == NULL) {
(void) unlink(new);
return;
}
if ((stat(target, &statbuff) == 0) && S_ISDIR(statbuff.st_mode)) {
char *saveptr = ptarget;
ptarget = &target[strlen(target)];
removefile(&statbuff, 0);
ptarget = saveptr;
}
if (rename(new, target) == -1) {
static const char fmt[] = "%s -> %s: rename failed: %s";
struct stat stb;
switch (errno) {
case ETXTBSY:
if ((savefile = savetarget(target, opts)) != NULL) {
if (rename(new, target) == -1) {
error(fmt, new, target, SYSERR);
if (rename(savefile, target) == -1)
error(fmt,
savefile, target, SYSERR);
(void) unlink(new);
} else
message(MT_NOTICE, "%s: renamed to %s",
target, savefile);
}
break;
case EISDIR:
if (lstat(target, &stb) == 0) {
if (S_ISDIR(stb.st_mode)) {
char *optarget = ptarget;
for (ptarget = target; *ptarget;
ptarget++);
(void) removefile(&stb, 1);
ptarget = optarget;
}
}
if (rename(new, target) >= 0)
break;
default:
error(fmt, new, target, SYSERR);
(void) unlink(new);
break;
}
}
if (IS_ON(opts, DO_COMPARE))
message(MT_REMOTE|MT_CHANGE, "%s: updated", target);
else
ack();
}
static void
recvdir(opt_t opts, int mode, char *owner, char *group)
{
static char lowner[100], lgroup[100];
char *cp;
struct stat stb;
int s;
s = lstat(target, &stb);
if (s == 0) {
if (!S_ISDIR(stb.st_mode)) {
if (IS_ON(opts, DO_VERIFY))
message(MT_NOTICE, "%s: need to remove",
target);
else {
if (unlink(target) == -1) {
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 %#04o",
target, mode);
else if (chmod(target, mode) != 0)
message(MT_NOTICE,
"%s: chmod from %#04o to %#04o failed: %s",
target,
stb.st_mode & 07777,
mode,
SYSERR);
else
message(MT_NOTICE,
"%s: chmod from %#04o to %#04o",
target,
stb.st_mode & 07777,
mode);
}
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))
!= NULL)
if (strcmp(owner, cp))
(void) strlcpy(lowner, cp,
sizeof(lowner));
}
if (!IS_ON(opts, DO_NOCHKGROUP) && group) {
int o;
o = (group[0] == ':') ? opts & DO_NUMCHKGROUP :
opts;
if ((cp = getgroupname(stb.st_gid, target, o))
!= NULL)
if (strcmp(group, cp))
(void) strlcpy(lgroup, cp,
sizeof(lgroup));
}
#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) strlcpy(lowner, cp,
sizeof(lowner));
if (lgroup[0] == CNULL &&
(cp = getgroupname(stb.st_gid,
target, opts)))
(void) strlcpy(lgroup, cp,
sizeof(lgroup));
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;
}
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;
}
static void
recvlink(char *new, opt_t opts, int mode, off_t size)
{
char tbuf[PATH_MAX], dbuf[BUFSIZ];
struct stat stb;
char *optarget;
int uptodate;
off_t i;
ack();
(void) remline(buf, sizeof(buf), TRUE);
if (response() < 0) {
err();
return;
}
if (DECODE(dbuf, buf) == -1) {
error("recvlink: cannot decode symlink target");
return;
}
uptodate = 0;
if ((i = readlink(target, tbuf, sizeof(tbuf)-1)) != -1) {
tbuf[i] = '\0';
if (i == size && strncmp(dbuf, tbuf, (int) size) == 0)
uptodate = 1;
}
mode &= 0777;
if (IS_ON(opts, DO_VERIFY) || uptodate) {
if (uptodate)
message(MT_REMOTE|MT_INFO, NULL);
else
message(MT_REMOTE|MT_INFO, "%s: need to update",
target);
if (IS_ON(opts, DO_COMPARE))
return;
(void) sendcmd(C_END, NULL);
(void) response();
return;
}
if (chkparent(new, opts) < 0 || mktemp(new) == NULL ||
symlink(dbuf, new) == -1) {
error("%s -> %s: symlink failed: %s", new, dbuf, SYSERR);
return;
}
if (lstat(target, &stb) == 0) {
if (S_ISDIR(stb.st_mode)) {
optarget = ptarget;
for (ptarget = target; *ptarget; ptarget++);
if (removefile(&stb, 0) < 0) {
ptarget = optarget;
(void) unlink(new);
(void) sendcmd(C_END, NULL);
(void) response();
return;
}
ptarget = optarget;
}
}
if (rename(new, target) == -1) {
error("%s -> %s: symlink rename failed: %s",
new, target, SYSERR);
(void) unlink(new);
(void) sendcmd(C_END, NULL);
(void) response();
return;
}
message(MT_REMOTE|MT_CHANGE, "%s: updated", target);
(void) sendcmd(C_END, NULL);
(void) response();
}
static void
hardlink(char *cmd)
{
struct stat stb;
int exists = 0;
char *xoldname, *xnewname;
char *cp = cmd;
static char expbuf[BUFSIZ];
char oldname[BUFSIZ], newname[BUFSIZ];
(void) strtol(cp, &cp, 8);
if (*cp++ != ' ') {
error("hardlink: options not delimited");
return;
}
xoldname = strtok(cp, " ");
if (xoldname == NULL) {
error("hardlink: oldname name not delimited");
return;
}
if (DECODE(oldname, xoldname) == -1) {
error("hardlink: Cannot decode oldname");
return;
}
xnewname = strtok(NULL, " ");
if (xnewname == NULL) {
error("hardlink: new name not specified");
return;
}
if (DECODE(newname, xnewname) == -1) {
error("hardlink: Cannot decode newname");
return;
}
if (exptilde(expbuf, oldname, sizeof(expbuf)) == NULL) {
error("hardlink: tilde expansion failed");
return;
}
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) == -1)) {
error("%s: unlink failed: %s", target, SYSERR);
return;
}
if (linkat(AT_FDCWD, expbuf, AT_FDCWD, target, 0) == -1) {
error("%s: cannot link to %s: %s", target, oldname, SYSERR);
return;
}
ack();
}
static void
setconfig(char *cmd)
{
char *cp = cmd;
char *estr;
const char *errstr;
switch (*cp++) {
case SC_HOSTNAME:
if (!fromhost) {
fromhost = xstrdup(cp);
message(MT_SYSLOG, "startup for %s", fromhost);
setproctitle("serving %s", cp);
}
break;
case SC_FREESPACE:
min_freespace = (int64_t)strtonum(cp, 0, LLONG_MAX, &errstr);
if (errstr)
fatalerr("Minimum free space is %s: '%s'", errstr,
optarg);
break;
case SC_FREEFILES:
min_freefiles = (int64_t)strtonum(cp, 0, LLONG_MAX, &errstr);
if (errstr)
fatalerr("Minimum free files is %s: '%s'", errstr,
optarg);
break;
case SC_LOGGING:
if ((estr = msgparseopts(cp, TRUE)) != NULL) {
fatalerr("Bad message option string (%s): %s",
cp, estr);
return;
}
break;
case SC_DEFOWNER:
(void) strlcpy(defowner, cp, sizeof(defowner));
break;
case SC_DEFGROUP:
(void) strlcpy(defgroup, cp, sizeof(defgroup));
break;
default:
message(MT_NOTICE, "Unknown config command \"%s\".", cp-1);
return;
}
}
static void
recvit(char *cmd, int type)
{
int mode;
opt_t opts;
off_t size;
time_t mtime, atime;
char *owner, *group, *file;
char new[PATH_MAX];
char fileb[PATH_MAX];
int64_t freespace = -1, freefiles = -1;
char *cp = cmd;
opts = strtol(cp, &cp, 8);
if (*cp++ != ' ') {
error("recvit: options not delimited");
return;
}
mode = strtol(cp, &cp, 8);
if (*cp++ != ' ') {
error("recvit: mode not delimited");
return;
}
size = (off_t) strtoll(cp, &cp, 10);
if (*cp++ != ' ') {
error("recvit: size not delimited");
return;
}
mtime = (time_t) strtoll(cp, &cp, 10);
if (*cp++ != ' ') {
error("recvit: mtime not delimited");
return;
}
atime = (time_t) strtoll(cp, &cp, 10);
if (*cp++ != ' ') {
error("recvit: atime not delimited");
return;
}
owner = strtok(cp, " ");
if (owner == NULL) {
error("recvit: owner name not delimited");
return;
}
group = strtok(NULL, " ");
if (group == NULL) {
error("recvit: group name not delimited");
return;
}
if (DECODE(fileb, group + strlen(group) + 1) == -1) {
error("recvit: Cannot decode file name");
return;
}
if (fileb[0] == '\0') {
error("recvit: no file name");
return;
}
file = fileb;
debugmsg(DM_MISC,
"recvit: opts = %#x mode = %#04o size = %lld mtime = %lld",
opts, mode, (long long) size, (long long)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 ((size_t) catname >= sizeof(sptarget)) {
error("%s: too many directory levels", target);
return;
}
sptarget[catname] = ptarget;
if (catname++) {
*ptarget++ = '/';
while ((*ptarget++ = *file++) != '\0')
continue;
ptarget--;
}
} else {
if (catname && cattarget(file) < 0) {
error("Cannot set file name.");
return;
}
file = strrchr(target, '/');
if (file == NULL)
(void) strlcpy(new, tempname, sizeof(new));
else if (file == target)
(void) snprintf(new, sizeof(new), "/%s", tempname);
else {
*file = CNULL;
(void) snprintf(new, sizeof(new), "%s/%s", target,
tempname);
*file = '/';
}
}
if (min_freespace || min_freefiles) {
int64_t fsize = (int64_t)size / 1024;
if (getfilesysinfo(target, &freespace, &freefiles) != 0)
return;
if (min_freespace && (freespace >= 0) &&
(freespace - fsize < min_freespace)) {
error(
"%s: Not enough free space on filesystem: min %lld "
"free %lld", target, min_freespace, freespace);
return;
}
if (min_freefiles && (freefiles >= 0) &&
(freefiles - 1 < min_freefiles)) {
error(
"%s: Not enough free files on filesystem: min %lld free "
"%lld", target, min_freefiles, freefiles);
return;
}
}
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;
}
}
static void
dochmog(char *cmd)
{
int mode;
opt_t opts;
char *owner, *group, *file;
char *cp = cmd;
char fileb[PATH_MAX];
opts = strtol(cp, &cp, 8);
if (*cp++ != ' ') {
error("dochmog: options not delimited");
return;
}
mode = strtol(cp, &cp, 8);
if (*cp++ != ' ') {
error("dochmog: mode not delimited");
return;
}
owner = strtok(cp, " ");
if (owner == NULL) {
error("dochmog: owner name not delimited");
return;
}
group = strtok(NULL, " ");
if (group == NULL) {
error("dochmog: group name not delimited");
return;
}
if (DECODE(fileb, group + strlen(group) + 1) == -1) {
error("dochmog: Cannot decode file name");
return;
}
if (fileb[0] == '\0') {
error("dochmog: no file name");
return;
}
file = fileb;
debugmsg(DM_MISC,
"dochmog: opts = %#x mode = %#04o", opts, mode);
debugmsg(DM_MISC,
"dochmog: owner = '%s' group = '%s' file = '%s' catname = %d",
owner, group, file, catname);
if (catname && cattarget(file) < 0) {
error("Cannot set newname target.");
return;
}
(void) fchog(-1, target, owner, group, mode);
ack();
}
static void
settarget(char *cmd, int isdir)
{
char *cp = cmd;
opt_t opts;
char file[BUFSIZ];
catname = isdir;
opts = strtol(cp, &cp, 8);
if (*cp++ != ' ') {
error("settarget: options not delimited");
return;
}
options = opts;
if (DECODE(file, cp) == -1) {
error("settarget: Cannot decode target name");
return;
}
if (exptilde(target, cp, sizeof(target)) == NULL)
return;
ptarget = target;
while (*ptarget)
ptarget++;
ack();
}
void
cleanup(int dummy)
{
}
void
server(void)
{
static char cmdbuf[BUFSIZ];
char *cp;
int n, proto_version;
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) strlcpy(tempname, _RDIST_TMP, sizeof(tempname));
if (fromhost) {
message(MT_SYSLOG, "Startup for %s", fromhost);
#if defined(SETARGS)
setproctitle("Serving %s", fromhost);
#endif
}
(void) sendcmd(S_VERSION, NULL);
if (remline(cmdbuf, sizeof(cmdbuf), TRUE) < 0) {
error("server: expected control record");
return;
}
if (cmdbuf[0] != S_VERSION || !isdigit((unsigned char)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;
}
ack();
for ( ; ; ) {
n = remline(cp = cmdbuf, sizeof(cmdbuf), TRUE);
if (n == -1)
return;
if (n == 0) {
error("server: expected control record");
continue;
}
switch (*cp++) {
case C_SETCONFIG:
setconfig(cp);
ack();
continue;
case C_DIRTARGET:
settarget(cp, TRUE);
continue;
case C_TARGET:
settarget(cp, FALSE);
continue;
case C_RECVREG:
recvit(cp, S_IFREG);
continue;
case C_RECVDIR:
recvit(cp, S_IFDIR);
continue;
case C_RECVSYMLINK:
recvit(cp, S_IFLNK);
continue;
case C_RECVHARDLINK:
hardlink(cp);
continue;
case C_END:
*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(cp);
continue;
case C_QUERY:
query(cp);
continue;
case C_SPECIAL:
dospecial(cp);
continue;
case C_CMDSPECIAL:
docmdspecial();
continue;
case C_CHMOG:
dochmog(cp);
continue;
case C_ERRMSG:
if (cp && *cp)
message(MT_NERROR|MT_NOREMOTE, "%s", cp);
continue;
case C_FERRMSG:
if (cp && *cp)
message(MT_FERROR|MT_NOREMOTE, "%s", cp);
return;
default:
error("server: unknown command '%s'", cp - 1);
case CNULL:
continue;
}
}
}