/* $OpenBSD: client.c,v 1.22 2009/10/27 23:59:42 deraadt Exp $ */ /* * 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. 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. */ #include "defs.h" #include "y.tab.h" /* * Routines used in client mode to communicate with remove server. */ /* * Update status */ #define US_NOTHING 0 /* No update needed */ #define US_NOENT 1 /* Entry does not exist */ #define US_OUTDATE 2 /* Entry is out of date */ #define US_DOCOMP 3 /* Do a binary comparison */ #define US_CHMOG 4 /* Modes or ownership of file differ */ struct linkbuf *ihead = NULL; /* list of files with more than one link */ char buf[BUFSIZ]; /* general purpose buffer */ u_char respbuff[BUFSIZ]; /* Response buffer */ char target[BUFSIZ]; /* target/source directory name */ char source[BUFSIZ]; /* source directory name */ char *ptarget; /* pointer to end of target name */ char *Tdest; /* pointer to last T dest*/ struct namelist *updfilelist = NULL; /* List of updated files */ static void runspecial(char *, opt_t, char *, int); static void addcmdspecialfile(char *, char *, int); static void freecmdspecialfiles(void); static struct linkbuf *linkinfo(struct stat *); static int sendhardlink(opt_t, struct linkbuf *, char *, int); static int sendfile(char *, opt_t, struct stat *, char *, char *, int); static int rmchk(opt_t); static int senddir(char *, opt_t, struct stat *, char *, char *, int); static int sendlink(char *, opt_t, struct stat *, char *, char *, int); static int update(char *, opt_t, struct stat *); static int dostat(char *, struct stat *, opt_t); static int statupdate(int, char *, opt_t, char *, int, struct stat *, char *, char *); static int fullupdate(int, char *, opt_t, char *, int, struct stat *, char *, char *); static int sendit(char *, opt_t, int); /* * return remote file pathname (relative from target) */ char * remfilename(char *src, char *dest, char *path, char *rname, int destdir) { extern struct namelist *filelist; char *lname, *cp; static char buff[BUFSIZ]; int srclen, pathlen; char *p; debugmsg(DM_MISC, "remfilename: src=%s dest=%s path=%s rname=%s destdir=%d\n", A(src), A(dest), A(path), A(rname), destdir); if (!dest) { debugmsg(DM_MISC, "remfilename: remote filename=%s\n", path); return(path); } if (!destdir) { debugmsg(DM_MISC, "remfilename: remote filename=%s\n", dest); return(dest); } buff[0] = CNULL; lname = buff; if (path && *path) { cp = strrchr(path, '/'); if (cp == NULL) (void) snprintf(buff, sizeof(buff), "%s/%s", dest, path); else { srclen = strlen(src); pathlen = strlen(path); if (srclen >= pathlen) cp++; /* xbasename(path) */ else { if (filelist && filelist->n_next == NULL) /* path relative to src */ cp = path + srclen; else { if ((p = strrchr(src, '/'))) cp = path + srclen - strlen(p); else cp = path; } } if ((*cp != '/') && *cp) (void) snprintf(buff, sizeof(buff), "%s/%s", dest, cp); else (void) snprintf(buff, sizeof(buff), "%s%s", dest, cp); } } else (void) strlcpy(lname, dest, buf + sizeof buff - lname); debugmsg(DM_MISC, "remfilename: remote filename=%s\n", lname); return(lname); } /* * Return true if name is in the list. */ int inlist(struct namelist *list, char *file) { struct namelist *nl; for (nl = list; nl != NULL; nl = nl->n_next) if (strcmp(file, nl->n_name) == 0) return(1); return(0); } /* * Run any special commands for this file */ static void runspecial(char *starget, opt_t opts, char *rname, int destdir) { struct subcmd *sc; extern struct subcmd *subcmds; char *rfile; rfile = remfilename(source, Tdest, target, rname, destdir); for (sc = subcmds; sc != NULL; sc = sc->sc_next) { if (sc->sc_type != SPECIAL) continue; if (sc->sc_args != NULL && !inlist(sc->sc_args, starget)) continue; message(MT_CHANGE, "special \"%s\"", sc->sc_name); if (IS_ON(opts, DO_VERIFY)) continue; (void) sendcmd(C_SPECIAL, "%s=%s;%s=%s;%s=%s;export %s %s %s;%s", E_LOCFILE, starget, E_REMFILE, rfile, E_BASEFILE, xbasename(rfile), E_LOCFILE, E_REMFILE, E_BASEFILE, sc->sc_name); while (response() > 0) ; } } /* * If we're doing a target with a "cmdspecial" in it, then * save the name of the file being updated for use with "cmdspecial". */ static void addcmdspecialfile(char *starget, char *rname, int destdir) { char *rfile; struct namelist *new; struct subcmd *sc; extern struct subcmd *subcmds; int isokay = 0; rfile = remfilename(source, Tdest, target, rname, destdir); for (sc = subcmds; sc != NULL && !isokay; sc = sc->sc_next) { if (sc->sc_type != CMDSPECIAL) continue; if (sc->sc_args != NULL && !inlist(sc->sc_args, starget)) continue; isokay = TRUE; } if (isokay) { new = (struct namelist *) xmalloc(sizeof(struct namelist)); new->n_name = xstrdup(rfile); new->n_regex = NULL; new->n_next = updfilelist; updfilelist = new; } } /* * Free the file list */ static void freecmdspecialfiles(void) { struct namelist *ptr, *save; for (ptr = updfilelist; ptr; ) { if (ptr->n_name) (void) free(ptr->n_name); save = ptr->n_next; (void) free(ptr); if (save) ptr = save->n_next; else ptr = NULL; } updfilelist = NULL; } /* * Run commands for an entire cmd */ void runcmdspecial(struct cmd *cmd, opt_t opts) { struct subcmd *sc; struct namelist *f; int first = TRUE; for (sc = cmd->c_cmds; sc != NULL; sc = sc->sc_next) { if (sc->sc_type != CMDSPECIAL) continue; message(MT_CHANGE, "cmdspecial \"%s\"", sc->sc_name); if (IS_ON(opts, DO_VERIFY)) continue; /* Send all the file names */ for (f = updfilelist; f != NULL; f = f->n_next) { if (first) { (void) sendcmd(C_CMDSPECIAL, NULL); if (response() < 0) return; first = FALSE; } (void) sendcmd(RC_FILE, f->n_name); if (response() < 0) return; } if (first) { (void) sendcmd(C_CMDSPECIAL, NULL); if (response() < 0) return; first = FALSE; } /* Send command to run and wait for it to complete */ (void) sendcmd(RC_COMMAND, sc->sc_name); while (response() > 0) ; first = TRUE; /* Reset in case there are more CMDSPECIAL's */ } freecmdspecialfiles(); } /* * For security, reject filenames that contains a newline */ int checkfilename(char *name) { char *cp; if (strchr(name, '\n')) { for (cp = name; *cp; cp++) if (*cp == '\n') *cp = '?'; message(MT_NERROR, "Refuse to handle filename containing newline: %s", name); return(-1); } return(0); } void freelinkinfo(struct linkbuf *lp) { if (lp->pathname) free(lp->pathname); if (lp->src) free(lp->src); if (lp->target) free(lp->target); free(lp); } /* * Save and retrieve hard link info */ static struct linkbuf * linkinfo(struct stat *statp) { struct linkbuf *lp; /* XXX - linear search doesn't scale with many links */ for (lp = ihead; lp != NULL; lp = lp->nextp) if (lp->inum == statp->st_ino && lp->devnum == statp->st_dev) { lp->count--; return(lp); } lp = (struct linkbuf *) xmalloc(sizeof(*lp)); lp->nextp = ihead; ihead = lp; lp->inum = statp->st_ino; lp->devnum = statp->st_dev; lp->count = statp->st_nlink - 1; lp->pathname = xstrdup(target); lp->src = xstrdup(source); if (Tdest) lp->target = xstrdup(Tdest); else lp->target = NULL; return(NULL); } /* * Send a hardlink */ static int sendhardlink(opt_t opts, struct linkbuf *lp, char *rname, int destdir) { static char buff[MAXPATHLEN]; char *lname; /* name of file to link to */ char ername[MAXPATHLEN*4], elname[MAXPATHLEN*4]; debugmsg(DM_MISC, "sendhardlink: rname='%s' pathname='%s' src='%s' target='%s'\n", rname, lp->pathname ? lp->pathname : "", lp->src ? lp->src : "", lp->target ? lp->target : ""); if (lp->target == NULL) lname = lp->pathname; else { lname = buff; strlcpy(lname, remfilename(lp->src, lp->target, lp->pathname, rname, destdir), sizeof(buff)); debugmsg(DM_MISC, "sendhardlink: lname=%s\n", lname); } ENCODE(elname, lname); ENCODE(ername, rname); (void) sendcmd(C_RECVHARDLINK, "%o %s %s", opts, elname, ername); return(response()); } /* * Send a file */ static int sendfile(char *rname, opt_t opts, struct stat *stb, char *user, char *group, int destdir) { int goterr, f; off_t i; char ername[MAXPATHLEN*4]; if (stb->st_nlink > 1) { struct linkbuf *lp; if ((lp = linkinfo(stb)) != NULL) return(sendhardlink(opts, lp, rname, destdir)); } if ((f = open(target, O_RDONLY)) < 0) { error("%s: open for read failed: %s", target, SYSERR); return(-1); } /* * Send file info */ ENCODE(ername, rname); (void) sendcmd(C_RECVREG, "%o %04o %ld %ld %ld %s %s %s", opts, stb->st_mode & 07777, (long) stb->st_size, stb->st_mtime, stb->st_atime, user, group, ername); if (response() < 0) { (void) close(f); return(-1); } debugmsg(DM_MISC, "Send file '%s' %ld bytes\n", rname, (long) stb->st_size); /* * Set remote time out alarm handler. */ (void) signal(SIGALRM, sighandler); /* * Actually transfer the file */ goterr = 0; for (i = 0; i < stb->st_size; i += BUFSIZ) { off_t amt = BUFSIZ; (void) alarm(rtimeout); if (i + amt > stb->st_size) amt = stb->st_size - i; if (read(f, buf, (size_t) amt) != (ssize_t) amt) { error("%s: File changed size", target); err(); ++goterr; /* * XXX - We have to keep going because the * server expects to receive a fixed number * of bytes that we specified as the file size. * We need Out Of Band communication to handle * this situation gracefully. */ } if (xwrite(rem_w, buf, (size_t) amt) < 0) { error("%s: Error writing to client: %s", target, SYSERR); err(); ++goterr; break; } (void) alarm(0); } (void) alarm(0); /* Insure alarm is off */ (void) close(f); debugmsg(DM_MISC, "Send file '%s' %s.\n", (goterr) ? "failed" : "complete", rname); /* * Check for errors and end send */ if (goterr) return(-1); else { ack(); f = response(); if (f < 0) return(-1); else if (f == 0 && IS_ON(opts, DO_COMPARE)) return(0); runspecial(target, opts, rname, destdir); addcmdspecialfile(target, rname, destdir); return(0); } } /* * Check for files on the machine being updated that are not on the master * machine and remove them. * * Return < 0 on error. * Return 0 if nothing happened. * Return > 0 if anything is updated. */ static int rmchk(opt_t opts) { u_char *s; struct stat stb; int didupdate = 0; int n; char targ[MAXPATHLEN*4]; debugmsg(DM_CALL, "rmchk()\n"); /* * Tell the remote to clean the files from the last directory sent. */ (void) sendcmd(C_CLEAN, "%o", IS_ON(opts, DO_VERIFY)); if (response() < 0) return(-1); for ( ; ; ) { n = remline(s = respbuff, sizeof(respbuff), TRUE); if (n <= 0) { error("rmchk: unexpected control record"); return(didupdate); } switch (*s++) { case CC_QUERY: /* Query if file should be removed */ /* * Return the following codes to remove query. * CC_NO -- file exists - DON'T remove. * CC_YES -- file doesn't exist - REMOVE. */ if (DECODE(targ, (char *) s) == -1) { error("rmchk: cannot decode file"); return(-1); } (void) snprintf(ptarget, sizeof(target) - (ptarget - target), "%s%s", (ptarget[-1] == '/' ? "" : "/"), targ); debugmsg(DM_MISC, "check %s\n", target); if (except(target)) (void) sendcmd(CC_NO, NULL); else if (lstat(target, &stb) < 0) { if (sendcmd(CC_YES, NULL) == 0) didupdate = 1; } else (void) sendcmd(CC_NO, NULL); break; case CC_END: *ptarget = CNULL; ack(); return(didupdate); case C_LOGMSG: if (n > 0) message(MT_INFO, "%s", s); break; case C_NOTEMSG: if (n > 0) message(MT_NOTICE, "%s", s); break; /* Goto top of loop */ case C_ERRMSG: message(MT_NERROR, "%s", s); return(didupdate); case C_FERRMSG: message(MT_FERROR, "%s", s); finish(); default: error("rmchk: unexpected response '%s'", respbuff); err(); } } /*NOTREACHED*/ } /* * Send a directory * * Return < 0 on error. * Return 0 if nothing happened. * Return > 0 if anything is updated. */ static int senddir(char *rname, opt_t opts, struct stat *stb, char *user, char *group, int destdir) { DIRENTRY *dp; DIR *d; char *optarget, *cp; int len; int didupdate = 0; char ername[MAXPATHLEN*4]; /* * Send recvdir command in recvit() format. */ ENCODE(ername, rname); (void) sendcmd(C_RECVDIR, "%o %04o 0 0 0 %s %s %s", opts, stb->st_mode & 07777, user, group, ername); if (response() < 0) return(-1); /* * Don't descend into directory */ if (IS_ON(opts, DO_NODESCEND)) return(0); if (IS_ON(opts, DO_REMOVE)) if (rmchk(opts) > 0) ++didupdate; 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 (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; if (len + 1 + (int) strlen(dp->d_name) >= MAXPATHLEN - 1) { error("%s/%s: Name too long", target, dp->d_name); continue; } ptarget = optarget; if (ptarget[-1] != '/') *ptarget++ = '/'; cp = dp->d_name; while ((*ptarget++ = *cp++) != '\0') continue; ptarget--; if (sendit(dp->d_name, opts, destdir) > 0) didupdate = 1; } (void) closedir(d); (void) sendcmd(C_END, NULL); (void) response(); ptarget = optarget; *ptarget = CNULL; return(didupdate); } /* * Send a link */ static int sendlink(char *rname, opt_t opts, struct stat *stb, char *user, char *group, int destdir) { int f, n; static char tbuf[BUFSIZ]; char lbuf[MAXPATHLEN]; u_char *s; char ername[MAXPATHLEN*4]; debugmsg(DM_CALL, "sendlink(%s, %x, stb, %d)\n", rname, opts, destdir); if (stb->st_nlink > 1) { struct linkbuf *lp; if ((lp = linkinfo(stb)) != NULL) return(sendhardlink(opts, lp, rname, destdir)); } /* * Gather and send basic link info */ ENCODE(ername, rname); (void) sendcmd(C_RECVSYMLINK, "%o %04o %ld %ld %ld %s %s %s", opts, stb->st_mode & 07777, (long) stb->st_size, stb->st_mtime, stb->st_atime, user, group, ername); if (response() < 0) return(-1); /* * Gather and send additional link info */ if ((n = readlink(target, lbuf, sizeof(lbuf)-1)) != -1) lbuf[n] = '\0'; else { error("%s: readlink failed", target); err(); } (void) snprintf(tbuf, sizeof(tbuf), "%.*s", (int) stb->st_size, lbuf); ENCODE(ername, tbuf); (void) sendcmd(C_NONE, "%s\n", ername); if (n != stb->st_size) { error("%s: file changed size", target); err(); } else ack(); /* * Check response */ f = response(); if (f < 0) return(-1); else if (f == 0 && IS_ON(opts, DO_COMPARE)) return(0); /* * Read and process responses from server. * The server may send multiple messages regarding * file deletes if the remote target is a directory. */ for (;;) { n = remline(s = respbuff, sizeof(respbuff), TRUE); if (n == -1) /* normal EOF */ return(0); if (n == 0) { error("expected control record"); continue; } switch (*s++) { case C_END: /* End of send operation */ *ptarget = CNULL; ack(); runspecial(target, opts, rname, destdir); addcmdspecialfile(target, rname, destdir); return(0); case C_LOGMSG: if (n > 0) message(MT_INFO, "%s", s); break; case C_NOTEMSG: if (n > 0) message(MT_NOTICE, "%s", s); break; /* Goto top of loop */ case C_ERRMSG: message(MT_NERROR, "%s", s); return(-1); case C_FERRMSG: message(MT_FERROR, "%s", s); finish(); default: error("install link: unexpected response '%s'", respbuff); err(); } } /*NOTREACHED*/ } /* * Check to see if file needs to be updated on the remote machine. * Returns: * US_NOTHING - no update * US_NOENT - remote doesn't exist * US_OUTDATE - out of date * US_DOCOMP - comparing binaries to determine if out of date * US_CHMOG - File modes or ownership do not match */ static int update(char *rname, opt_t opts, struct stat *statp) { off_t size; time_t mtime; unsigned short lmode; unsigned short rmode; char *owner = NULL, *group = NULL; int done, n; u_char *cp; char ername[MAXPATHLEN*4]; debugmsg(DM_CALL, "update(%s, 0x%x, 0x%x)\n", rname, opts, statp); switch (statp->st_mode & S_IFMT) { case S_IFBLK: debugmsg(DM_MISC, "%s is a block special; skipping\n", target); return(US_NOTHING); case S_IFCHR: debugmsg(DM_MISC, "%s is a character special; skipping\n", target); return(US_NOTHING); case S_IFIFO: debugmsg(DM_MISC, "%s is a fifo; skipping\n", target); return(US_NOTHING); case S_IFSOCK: debugmsg(DM_MISC, "%s is a socket; skipping\n", target); return(US_NOTHING); } if (IS_ON(opts, DO_NOEXEC)) if (isexec(target, statp)) { debugmsg(DM_MISC, "%s is an executable\n", target); return(US_NOTHING); } /* * Check to see if the file exists on the remote machine. */ ENCODE(ername, rname); (void) sendcmd(C_QUERY, "%s", ername); for (done = 0; !done;) { n = remline(cp = respbuff, sizeof(respbuff), TRUE); if (n <= 0) { error("update: unexpected control record in response to query"); return(US_NOTHING); } switch (*cp++) { case QC_ONNFS: /* Resides on a NFS */ debugmsg(DM_MISC, "update: %s is on a NFS. Skipping...\n", rname); return(US_NOTHING); case QC_SYM: /* Is a symbolic link */ debugmsg(DM_MISC, "update: %s is a symlink. Skipping...\n", rname); return(US_NOTHING); case QC_ONRO: /* Resides on a Read-Only fs */ debugmsg(DM_MISC, "update: %s is on a RO fs. Skipping...\n", rname); return(US_NOTHING); case QC_YES: done = 1; break; case QC_NO: /* file doesn't exist so install it */ return(US_NOENT); case C_ERRMSG: if (cp) message(MT_NERROR, "%s", cp); return(US_NOTHING); case C_FERRMSG: if (cp) message(MT_FERROR, "%s", cp); finish(); case C_NOTEMSG: if (cp) message(MT_NOTICE, "%s", cp); break; /* Goto top of loop */ default: error("update: unexpected response to query '%s'", respbuff); return(US_NOTHING); } } /* * Target exists, but no other info passed */ if (n <= 1 || !S_ISREG(statp->st_mode)) return(US_OUTDATE); if (IS_ON(opts, DO_COMPARE)) return(US_DOCOMP); /* * Parse size */ size = (off_t) strtol(cp, (char **)&cp, 10); if (*cp++ != ' ') { error("update: size not delimited"); return(US_NOTHING); } /* * Parse mtime */ mtime = strtol(cp, (char **)&cp, 10); if (*cp++ != ' ') { error("update: mtime not delimited"); return(US_NOTHING); } /* * Parse remote file mode */ rmode = strtol(cp, (char **)&cp, 8); if (cp && *cp) ++cp; /* * Be backwards compatible */ if (cp && *cp != CNULL) { /* * Parse remote file owner */ owner = strtok((char *)cp, " "); if (owner == NULL) { error("update: owner not delimited"); return(US_NOTHING); } /* * Parse remote file group */ group = strtok(NULL, " "); if (group == NULL) { error("update: group not delimited"); return(US_NOTHING); } } /* * File needs to be updated? */ lmode = statp->st_mode & 07777; debugmsg(DM_MISC, "update(%s,) local mode %04o remote mode %04o\n", rname, lmode, rmode); debugmsg(DM_MISC, "update(%s,) size %ld mtime %d owner '%s' grp '%s'\n", rname, (long) size, mtime, owner, group); if (statp->st_mtime != mtime) { if (statp->st_mtime < mtime && IS_ON(opts, DO_YOUNGER)) { message(MT_WARNING, "%s: Warning: remote copy is newer", target); return(US_NOTHING); } return(US_OUTDATE); } if (statp->st_size != size) { debugmsg(DM_MISC, "size does not match (%ld != %ld).\n", (long) statp->st_size, (long) size); return(US_OUTDATE); } if (!IS_ON(opts, DO_NOCHKMODE) && lmode != rmode) { debugmsg(DM_MISC, "modes do not match (%04o != %04o).\n", lmode, rmode); return(US_CHMOG); } /* * Check ownership */ if (!IS_ON(opts, DO_NOCHKOWNER) && owner) { if (!IS_ON(opts, DO_NUMCHKOWNER)) { /* Check by string compare */ if (strcmp(owner, getusername(statp->st_uid, target, opts)) != 0) { debugmsg(DM_MISC, "owner does not match (%s != %s).\n", getusername(statp->st_uid, target, opts), owner); return(US_CHMOG); } } else { /* * Check numerically. * Allow negative numbers. */ while (*owner && !isdigit((unsigned char)*owner) && (*owner != '-')) ++owner; if (owner && (UID_T) atoi(owner) != statp->st_uid) { debugmsg(DM_MISC, "owner does not match (%d != %s).\n", statp->st_uid, owner); return(US_CHMOG); } } } if (!IS_ON(opts, DO_NOCHKGROUP) && group) { if (!IS_ON(opts, DO_NUMCHKGROUP)) { /* Check by string compare */ if (strcmp(group, getgroupname(statp->st_gid, target, opts)) != 0) { debugmsg(DM_MISC, "group does not match (%s != %s).\n", getgroupname(statp->st_gid, target, opts), group); return(US_CHMOG); } } else { /* Check numerically */ /* Allow negative gid */ while (*group && !isdigit((unsigned char) *group) && (*group != '-')) ++group; if (group && (UID_T) atoi(group) != statp->st_gid) { debugmsg(DM_MISC, "group does not match (%d != %s).\n", statp->st_gid, group); return(US_CHMOG); } } } return(US_NOTHING); } /* * Stat a file */ static int dostat(char *file, struct stat *statbuf, opt_t opts) { int s; if (IS_ON(opts, DO_FOLLOW)) s = stat(file, statbuf); else s = lstat(file, statbuf); if (s < 0) error("%s: %s failed: %s", file, IS_ON(opts, DO_FOLLOW) ? "stat" : "lstat", SYSERR); return(s); } /* * We need to just change file info. */ static int statupdate(int u, char *target, opt_t opts, char *rname, int destdir, struct stat *st, char *user, char *group) { int rv = 0; char ername[MAXPATHLEN*4]; int lmode = st->st_mode & 07777; if (u == US_CHMOG) { if (IS_ON(opts, DO_VERIFY)) { message(MT_INFO, "%s: need to change to perm %04o, owner %s, group %s", target, lmode, user, group); runspecial(target, opts, rname, destdir); } else { message(MT_CHANGE, "%s: change to perm %04o, owner %s, group %s", target, lmode, user, group); ENCODE(ername, rname); (void) sendcmd(C_CHMOG, "%o %04o %s %s %s", opts, lmode, user, group, ername); (void) response(); } rv = 1; } return(rv); } /* * We need to install/update: */ static int fullupdate(int u, char *target, opt_t opts, char *rname, int destdir, struct stat *st, char *user, char *group) { /* * No entry - need to install */ if (u == US_NOENT) { if (IS_ON(opts, DO_VERIFY)) { message(MT_INFO, "%s: need to install", target); runspecial(target, opts, rname, destdir); return(1); } if (!IS_ON(opts, DO_QUIET)) message(MT_CHANGE, "%s: installing", target); FLAG_OFF(opts, (DO_COMPARE|DO_REMOVE)); } /* * Handle special file types, including directories and symlinks */ if (S_ISDIR(st->st_mode)) { if (senddir(rname, opts, st, user, group, destdir) > 0) return(1); return(0); } else if (S_ISLNK(st->st_mode)) { if (u == US_NOENT) FLAG_ON(opts, DO_COMPARE); /* * Since we always send link info to the server * so the server can determine if the remote link * is correct, we never get any acknowledgement * from the server whether the link was really * updated or not. */ (void) sendlink(rname, opts, st, user, group, destdir); return(0); } else if (S_ISREG(st->st_mode)) { if (u == US_OUTDATE) { if (IS_ON(opts, DO_VERIFY)) { message(MT_INFO, "%s: need to update", target); runspecial(target, opts, rname, destdir); return(1); } if (!IS_ON(opts, DO_QUIET)) message(MT_CHANGE, "%s: updating", target); } return (sendfile(rname, opts, st, user, group, destdir) == 0); } else { message(MT_INFO, "%s: unknown file type 0%o", target, st->st_mode); return(0); } } /* * Transfer the file or directory in target[]. * rname is the name of the file on the remote host. * * Return < 0 on error. * Return 0 if nothing happened. * Return > 0 if anything is updated. */ static int sendit(char *rname, opt_t opts, int destdir) { static struct stat stb; char *user, *group; int u, len; /* * Remove possible accidental newline */ len = strlen(rname); if (len > 0 && rname[len-1] == '\n') rname[len-1] = CNULL; if (checkfilename(rname) != 0) return(-1); debugmsg(DM_CALL, "sendit(%s, 0x%x) called\n", rname, opts); if (except(target)) return(0); if (dostat(target, &stb, opts) < 0) return(-1); /* * Does rname need updating? */ u = update(rname, opts, &stb); debugmsg(DM_MISC, "sendit(%s, 0x%x): update status of %s is %d\n", rname, opts, target, u); /* * Don't need to update the file, but we may need to save hardlink * info. */ if (u == US_NOTHING) { if (S_ISREG(stb.st_mode) && stb.st_nlink > 1) (void) linkinfo(&stb); return(0); } user = getusername(stb.st_uid, target, opts); group = getgroupname(stb.st_gid, target, opts); if (u == US_CHMOG && IS_OFF(opts, DO_UPDATEPERM)) u = US_OUTDATE; if (u == US_NOENT || u == US_OUTDATE || u == US_DOCOMP) return(fullupdate(u, target, opts, rname, destdir, &stb, user, group)); if (u == US_CHMOG) return(statupdate(u, target, opts, rname, destdir, &stb, user, group)); return(0); } /* * Remove temporary files and do any cleanup operations before exiting. */ void cleanup(int dummy) { char *file; #ifdef USE_STATDB extern char statfile[]; (void) unlink(statfile); #endif if ((file = getnotifyfile()) != NULL) (void) unlink(file); } /* * 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). * * Return < 0 on error. * Return 0 if nothing updated. * Return > 0 if something was updated. */ int install(char *src, char *dest, int ddir, int destdir, opt_t opts) { static char destcopy[MAXPATHLEN]; char *rname; int didupdate = 0; char ername[MAXPATHLEN*4]; debugmsg(DM_CALL, "install(src=%s,dest=%s,ddir=%d,destdir=%d,opts=%d) start\n", (src?src:"NULL"), (dest?dest:"NULL"), ddir, destdir, opts); /* * Save source name */ if (IS_ON(opts, DO_WHOLE)) source[0] = CNULL; else (void) strlcpy(source, src, sizeof(source)); if (dest == NULL) { FLAG_OFF(opts, DO_WHOLE); /* WHOLE only useful if renaming */ dest = src; } if (checkfilename(dest) != 0) return(-1); if (nflag || debug) { static char buff[BUFSIZ]; char *cp; cp = getondistoptlist(opts); (void) snprintf(buff, sizeof(buff), "%s%s%s %s %s", IS_ON(opts, DO_VERIFY) ? "verify" : "install", (cp) ? " -o" : "", (cp) ? cp : "", src, dest); if (nflag) { printf("%s\n", buff); return(0); } else debugmsg(DM_MISC, "%s\n", buff); } rname = exptilde(target, src, sizeof(target)); if (rname == NULL) return(-1); ptarget = target; while (*ptarget) ptarget++; /* * 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 (IS_ON(opts, DO_WHOLE)) { while (*rname == '/') rname++; ddir = 1; destdir = 1; } else { rname = strrchr(target, '/'); /* Check if no '/' or target ends in '/' */ if (rname == NULL || rname+1 == NULL || *(rname+1) == CNULL) rname = target; else rname++; } debugmsg(DM_MISC, "install: target=%s src=%s rname=%s dest='%s' destdir=%d, ddir=%d\n", target, source, rname, dest, destdir, ddir); /* * Pass the destination file/directory name to remote. */ ENCODE(ername, dest); if (ddir) (void) sendcmd(C_DIRTARGET, "%o %s", opts, ername); else (void) sendcmd(C_TARGET, "%o %s", opts, ername); if (response() < 0) return(-1); /* * Save the name of the remote target destination if we are * in WHOLE mode (destdir > 0) or if the source and destination * are not the same. This info will be used later for maintaining * hardlink info. */ if (destdir || (src && dest && strcmp(src, dest))) { (void) strlcpy(destcopy, dest, sizeof(destcopy)); Tdest = destcopy; } didupdate = sendit(rname, opts, destdir); Tdest = 0; return(didupdate); }