xref: /openbsd-src/usr.bin/rdist/client.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: client.c,v 1.12 1999/03/05 01:36:12 millert Exp $	*/
2 
3 /*
4  * Copyright (c) 1983 Regents of the University of California.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifndef lint
37 #if 0
38 static char RCSid[] =
39 "$From: client.c,v 6.80 1996/02/28 20:34:27 mcooper Exp $";
40 #else
41 static char RCSid[] =
42 "$OpenBSD: client.c,v 1.12 1999/03/05 01:36:12 millert Exp $";
43 #endif
44 
45 static char sccsid[] = "@(#)client.c";
46 
47 static char copyright[] =
48 "@(#) Copyright (c) 1983 Regents of the University of California.\n\
49  All rights reserved.\n";
50 #endif /* not lint */
51 
52 /*
53  * Routines used in client mode to communicate with remove server.
54  */
55 
56 #include "defs.h"
57 #include "y.tab.h"
58 
59 /*
60  * Update status
61  */
62 #define US_NOTHING 	0	/* No update needed */
63 #define US_NOENT	1	/* Entry does not exist */
64 #define US_OUTDATE	2	/* Entry is out of date */
65 #define US_DOCOMP	3	/* Do a binary comparison */
66 #define US_MODE		4	/* Modes of file differ */
67 
68 struct	linkbuf *ihead = NULL;	/* list of files with more than one link */
69 char	buf[BUFSIZ];		/* general purpose buffer */
70 u_char	respbuff[BUFSIZ];	/* Response buffer */
71 char	target[BUFSIZ];		/* target/source directory name */
72 char	source[BUFSIZ];		/* source directory name */
73 char	*ptarget;		/* pointer to end of target name */
74 char	*Tdest;			/* pointer to last T dest*/
75 struct namelist	*updfilelist = NULL; /* List of updated files */
76 
77 static int sendit();
78 
79 /*
80  * return remote file pathname (relative from target)
81  */
82 char *remfilename(src, dest, path, rname, destdir)
83 	char *src, *dest, *path, *rname;
84 	int destdir;
85 {
86 	extern struct namelist *filelist;
87 	register char *lname, *cp;
88 	static char buff[BUFSIZ];
89 	int srclen, pathlen;
90 	char *p;
91 
92 
93 	debugmsg(DM_MISC,
94 		 "remfilename: src=%s dest=%s path=%s rname=%s destdir=%d\n",
95 		A(src), A(dest), A(path), A(rname), destdir);
96 
97 	if (!dest) {
98 		debugmsg(DM_MISC, "remfilename: remote filename=%s\n", path);
99 		return(path);
100 	}
101 
102 	if (!destdir) {
103 		debugmsg(DM_MISC, "remfilename: remote filename=%s\n", dest);
104 		return(dest);
105 	}
106 
107 	buff[0] = CNULL;
108 	lname = buff;
109 	if (path && *path) {
110 		cp = strrchr(path, '/');
111  		if (cp == NULL)
112 			(void) sprintf(buff, "%s/%s", dest, path);
113 		else {
114 			srclen = strlen(src);
115 			pathlen = strlen(path);
116 			if (srclen >= pathlen)
117 				cp++; /* xbasename(path) */
118 			else {
119 				if (filelist && filelist->n_next == NULL)
120 					/* path relative to src */
121 					cp = path + srclen;
122 				else {
123 					if ((p = strrchr(src, '/')))
124 						cp = path + srclen - strlen(p);
125 					else
126 						cp = path;
127 				}
128 			}
129 			if ((*cp != '/') && *cp)
130 				(void) sprintf(buff, "%s/%s", dest, cp);
131 			else
132 				(void) sprintf(buff, "%s%s", dest, cp);
133 		}
134 	} else
135 		strcpy(lname, dest);
136 
137 	debugmsg(DM_MISC, "remfilename: remote filename=%s\n", lname);
138 
139 	return(lname);
140 }
141 
142 /*
143  * Return true if name is in the list.
144  */
145 int inlist(list, file)
146 	struct namelist *list;
147 	char *file;
148 {
149 	register struct namelist *nl;
150 
151 	for (nl = list; nl != NULL; nl = nl->n_next)
152 		if (strcmp(file, nl->n_name) == 0)
153 			return(1);
154 	return(0);
155 }
156 
157 /*
158  * Run any special commands for this file
159  */
160 static void runspecial(starget, opts, rname, destdir)
161 	char *starget;
162 	opt_t opts;
163 	char *rname;
164 	int destdir;
165 {
166 	register struct subcmd *sc;
167 	extern struct subcmd *subcmds;
168 	char *rfile;
169 
170  	rfile = remfilename(source, Tdest, target, rname, destdir);
171 
172 	for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
173 		if (sc->sc_type != SPECIAL)
174 			continue;
175 		if (sc->sc_args != NULL && !inlist(sc->sc_args, starget))
176 			continue;
177 		message(MT_CHANGE, "special \"%s\"", sc->sc_name);
178 		if (IS_ON(opts, DO_VERIFY))
179 			continue;
180 		(void) sendcmd(C_SPECIAL,
181 			"%s=%s;%s=%s;%s=%s;export %s %s %s;%s",
182 			E_LOCFILE, starget,
183 			E_REMFILE, rfile,
184 			E_BASEFILE, xbasename(rfile),
185 			E_LOCFILE, E_REMFILE, E_BASEFILE,
186 			sc->sc_name);
187 		while (response() > 0)
188 			;
189 	}
190 }
191 
192 /*
193  * If we're doing a target with a "cmdspecial" in it, then
194  * save the name of the file being updated for use with "cmdspecial".
195  */
196 static void addcmdspecialfile(starget, rname, destdir)
197 	char *starget;
198 	char *rname;
199 	int destdir;
200 {
201 	char *rfile;
202 	struct namelist *new;
203 	register struct subcmd *sc;
204 	extern struct subcmd *subcmds;
205 	int isokay = 0;
206 
207  	rfile = remfilename(source, Tdest, target, rname, destdir);
208 
209 	for (sc = subcmds; sc != NULL && !isokay; sc = sc->sc_next) {
210 		if (sc->sc_type != CMDSPECIAL)
211 			continue;
212 		if (sc->sc_args != NULL && !inlist(sc->sc_args, starget))
213 			continue;
214 		isokay = TRUE;
215 	}
216 
217 	if (isokay) {
218 		new = (struct namelist *) xmalloc(sizeof(struct namelist));
219 		new->n_name = xstrdup(rfile);
220 		new->n_next = updfilelist;
221 		updfilelist = new;
222 	}
223 }
224 
225 /*
226  * Free the file list
227  */
228 static void freecmdspecialfiles()
229 {
230 	register struct namelist *ptr, *save;
231 
232 	for (ptr = updfilelist; ptr; ) {
233 		if (ptr->n_name) (void) free(ptr->n_name);
234 		save = ptr->n_next;
235 		(void) free(ptr);
236 		if (save)
237 			ptr = save->n_next;
238 		else
239 			ptr = NULL;
240 	}
241 	updfilelist = NULL;
242 }
243 
244 /*
245  * Run commands for an entire cmd
246  */
247 extern void runcmdspecial(cmd, filev, opts)
248 	struct cmd *cmd;
249 	char **filev;
250 	opt_t opts;
251 {
252 	register struct subcmd *sc;
253 	register struct namelist *f;
254 	int first = TRUE;
255 
256 	for (sc = cmd->c_cmds; sc != NULL; sc = sc->sc_next) {
257 		if (sc->sc_type != CMDSPECIAL)
258 			continue;
259 		message(MT_CHANGE, "cmdspecial \"%s\"", sc->sc_name);
260 		if (IS_ON(opts, DO_VERIFY))
261 			continue;
262 		/* Send all the file names */
263 		for (f = updfilelist; f != NULL; f = f->n_next) {
264 			if (first) {
265 				(void) sendcmd(C_CMDSPECIAL, NULL);
266 				if (response() < 0)
267 					return;
268 				first = FALSE;
269 			}
270 			(void) sendcmd(RC_FILE, f->n_name);
271 			if (response() < 0)
272 				return;
273 		}
274 		if (first) {
275 			(void) sendcmd(C_CMDSPECIAL, NULL);
276 			if (response() < 0)
277 				return;
278 			first = FALSE;
279 		}
280 		/* Send command to run and wait for it to complete */
281 		(void) sendcmd(RC_COMMAND, sc->sc_name);
282 		while (response() > 0)
283 			;
284 		first = TRUE;	/* Reset in case there are more CMDSPECIAL's */
285 	}
286 	freecmdspecialfiles();
287 }
288 
289 /*
290  * For security, reject filenames that contains a newline
291  */
292 int checkfilename(name)
293 	char *name;
294 {
295 	register char *cp;
296 
297 	if (strchr(name, '\n')) {
298 		for (cp = name; *cp; cp++)
299 			if (*cp == '\n')
300 				*cp = '?';
301 		message(MT_NERROR,
302 			"Refuse to handle filename containing newline: %s",
303 			name);
304 		return(-1);
305 	}
306 
307 	return(0);
308 }
309 
310 void freelinkinfo(lp)
311 	struct linkbuf *lp;
312 {
313 	if (lp->pathname)
314 		free(lp->pathname);
315 	if (lp->src)
316 		free(lp->src);
317 	if (lp->target)
318 		free(lp->target);
319 	free(lp);
320 }
321 
322 /*
323  * Save and retrieve hard link info
324  */
325 static struct linkbuf *linkinfo(statp)
326 	struct stat *statp;
327 {
328 	struct linkbuf *lp;
329 
330 	/* XXX - linear search doesn't scale with many links */
331 	for (lp = ihead; lp != NULL; lp = lp->nextp)
332 		if (lp->inum == statp->st_ino && lp->devnum == statp->st_dev) {
333 			lp->count--;
334 			return(lp);
335 		}
336 
337 	lp = (struct linkbuf *) xmalloc(sizeof(*lp));
338 	lp->nextp = ihead;
339 	ihead = lp;
340 	lp->inum = statp->st_ino;
341 	lp->devnum = statp->st_dev;
342 	lp->count = statp->st_nlink - 1;
343 	lp->pathname = xstrdup(target);
344 	lp->src = xstrdup(source);
345 	if (Tdest)
346 		lp->target = xstrdup(Tdest);
347 	else
348 		lp->target = NULL;
349 
350 	return(NULL);
351 }
352 
353 /*
354  * Send a hardlink
355  */
356 static int sendhardlink(opts, lp, rname, destdir)
357 	opt_t opts;
358 	struct linkbuf *lp;
359 	char *rname;
360 	int destdir;
361 {
362 	static char buff[MAXPATHLEN];
363 	char *lname;	/* name of file to link to */
364 
365 	debugmsg(DM_MISC,
366 	       "sendhardlink: rname='%s' pathname='%s' src='%s' target='%s'\n",
367 		 rname, lp->pathname ? lp->pathname : "(null)", lp->src
368 		 ? lp->src : "(null)", lp->target ? lp->target : "(null)");
369 
370 	if (lp->target == NULL)
371 		(void) sendcmd(C_RECVHARDLINK, "%o %s %s",
372 			       opts, lp->pathname, rname);
373 	else {
374 		lname = buff;
375 		strcpy(lname, remfilename(lp->src, lp->target,
376 					  lp->pathname, rname,
377 					  destdir));
378 		debugmsg(DM_MISC, "sendhardlink: lname=%s\n", lname);
379 		(void) sendcmd(C_RECVHARDLINK, "%o %s %s",
380 			       opts, lname, rname);
381 	}
382 
383 	return(response());
384 }
385 
386 /*
387  * Send a file
388  */
389 static int sendfile(rname, opts, stb, user, group, destdir)
390 	char *rname;
391 	opt_t opts;
392 	struct stat *stb;
393 	char *user, *group;
394 	int destdir;
395 {
396 	int goterr, f;
397 	off_t i;
398 
399 	if (stb->st_nlink > 1) {
400 		struct linkbuf *lp;
401 
402 		if ((lp = linkinfo(stb)) != NULL)
403 			return(sendhardlink(opts, lp, rname, destdir));
404 	}
405 
406 	if ((f = open(target, O_RDONLY)) < 0) {
407 		error("%s: open for read failed: %s", target, SYSERR);
408 		return(-1);
409 	}
410 
411 	/*
412 	 * Send file info
413 	 */
414 	(void) sendcmd(C_RECVREG, "%o %04o %ld %ld %ld %s %s %s",
415 		       opts, stb->st_mode & 07777,
416 		       (long) stb->st_size,
417 		       stb->st_mtime, stb->st_atime,
418 		       user, group, rname);
419 	if (response() < 0) {
420 		(void) close(f);
421 		return(-1);
422 	}
423 
424 	debugmsg(DM_MISC, "Send file '%s' %d bytes\n",
425 		 rname, (long) stb->st_size);
426 
427 	/*
428 	 * Set remote time out alarm handler.
429 	 */
430 	(void) signal(SIGALRM, sighandler);
431 
432 	/*
433 	 * Actually transfer the file
434 	 */
435 	goterr = 0;
436 	for (i = 0; i < stb->st_size; i += BUFSIZ) {
437 		int amt = BUFSIZ;
438 
439 		(void) alarm(rtimeout);
440 		if (i + amt > stb->st_size)
441 			amt = stb->st_size - i;
442 		if (read(f, buf, amt) != amt) {
443 			error("%s: File changed size", target);
444 			err();
445 			++goterr;
446 			/*
447 			 * XXX - We have to keep going because the
448 			 * server expects to receive a fixed number
449 			 * of bytes that we specified as the file size.
450 			 * We need Out Of Band communication to handle
451 			 * this situation gracefully.
452 			 */
453 		}
454 		if (xwrite(rem_w, buf, amt) < 0) {
455 		    	error("%s: Error writing to client: %s",
456 			      target, SYSERR);
457 			err();
458 			++goterr;
459 			break;
460 		}
461 		(void) alarm(0);
462 	}
463 
464 	(void) alarm(0);	/* Insure alarm is off */
465 	(void) close(f);
466 
467 	debugmsg(DM_MISC, "Send file '%s' %s.\n",
468 		 (goterr) ? "failed" : "complete", rname);
469 
470 	/*
471 	 * Check for errors and end send
472 	 */
473 	if (goterr)
474 		return(-1);
475 	else {
476 		ack();
477 		f = response();
478 		if (f < 0)
479 			return(-1);
480 		else if (f == 0 && IS_ON(opts, DO_COMPARE))
481 			return(0);
482 
483 		runspecial(target, opts, rname, destdir);
484 		addcmdspecialfile(target, rname, destdir);
485 
486 		return(0);
487 	}
488 }
489 
490 /*
491  * Check for files on the machine being updated that are not on the master
492  * machine and remove them.
493  *
494  * Return < 0 on error.
495  * Return 0 if nothing happened.
496  * Return > 0 if anything is updated.
497  */
498 static int rmchk(opts)
499 	opt_t opts;
500 {
501 	register u_char *s;
502 	struct stat stb;
503 	int didupdate = 0;
504 	int n;
505 
506 	debugmsg(DM_CALL, "rmchk()\n");
507 
508 	/*
509 	 * Tell the remote to clean the files from the last directory sent.
510 	 */
511 	(void) sendcmd(C_CLEAN, "%o", IS_ON(opts, DO_VERIFY));
512 	if (response() < 0)
513 		return(-1);
514 
515 	for ( ; ; ) {
516 		n = remline(s = respbuff, sizeof(respbuff), TRUE);
517 		if (n <= 0) {
518 			error("rmchk: unexpected control record");
519 			return(didupdate);
520 		}
521 
522 		switch (*s++) {
523 		case CC_QUERY: /* Query if file should be removed */
524 			/*
525 			 * Return the following codes to remove query.
526 			 * CC_NO -- file exists - DON'T remove.
527 			 * CC_YES -- file doesn't exist - REMOVE.
528 			 */
529 			(void) sprintf(ptarget, "%s%s",
530 				       (ptarget[-1] == '/' ? "" : "/"), s);
531 			debugmsg(DM_MISC, "check %s\n", target);
532 			if (except(target))
533 				(void) sendcmd(CC_NO, NULL);
534 			else if (lstat(target, &stb) < 0) {
535 				if (sendcmd(CC_YES, NULL) == 0)
536 					didupdate = 1;
537 			} else
538 				(void) sendcmd(CC_NO, NULL);
539 			break;
540 
541 		case CC_END:
542 			*ptarget = CNULL;
543 			ack();
544 			return(didupdate);
545 
546 		case C_LOGMSG:
547 			if (n > 0)
548 				message(MT_INFO, "%s", s);
549 			break;
550 
551 		case C_NOTEMSG:
552 			if (n > 0)
553 				message(MT_NOTICE, "%s", s);
554 			break;
555 			/* Goto top of loop */
556 
557 		case C_ERRMSG:
558 			message(MT_NERROR, "%s", s);
559 			return(didupdate);
560 
561 		case C_FERRMSG:
562 			message(MT_FERROR, "%s", s);
563 			finish();
564 
565 		default:
566 			error("rmchk: unexpected response '%s'", respbuff);
567 			err();
568 		}
569 	}
570 	/*NOTREACHED*/
571 }
572 
573 /*
574  * Send a directory
575  *
576  * Return < 0 on error.
577  * Return 0 if nothing happened.
578  * Return > 0 if anything is updated.
579  */
580 static int senddir(rname, opts, stb, user, group, destdir)
581 	char *rname;
582 	opt_t opts;
583 	struct stat *stb;
584 	char *user, *group;
585 	int destdir;
586 {
587 	DIRENTRY *dp;
588 	DIR *d;
589 	char *optarget, *cp;
590 	int len;
591 	int didupdate = 0;
592 
593 	/*
594 	 * Don't descend into directory
595 	 */
596 	if (IS_ON(opts, DO_NODESCEND))
597 		return(0);
598 
599 	if ((d = opendir(target)) == NULL) {
600 		error("%s: opendir failed: %s", target, SYSERR);
601 		return(-1);
602 	}
603 
604 	/*
605 	 * Send recvdir command in recvit() format.
606 	 */
607 	(void) sendcmd(C_RECVDIR, "%o %04o 0 0 0 %s %s %s",
608 		       opts, stb->st_mode & 07777, user, group, rname);
609 	if (response() < 0)
610 		return(-1);
611 
612 	if (IS_ON(opts, DO_REMOVE))
613 		if (rmchk(opts) > 0)
614 			++didupdate;
615 
616 	optarget = ptarget;
617 	len = ptarget - target;
618 	while ((dp = readdir(d))) {
619 		if (!strcmp(dp->d_name, ".") ||
620 		    !strcmp(dp->d_name, ".."))
621 			continue;
622 		if (len + 1 + (int) strlen(dp->d_name) >= MAXPATHLEN - 1) {
623 			error("%s/%s: Name too long", target,
624 			      dp->d_name);
625 			continue;
626 		}
627 		ptarget = optarget;
628 		if (ptarget[-1] != '/')
629 			*ptarget++ = '/';
630 		cp = dp->d_name;
631 		while ((*ptarget++ = *cp++))
632 			;
633 		ptarget--;
634 		if (sendit(dp->d_name, opts, destdir) > 0)
635 			didupdate = 1;
636 	}
637 	(void) closedir(d);
638 
639 	(void) sendcmd(C_END, NULL);
640 	(void) response();
641 
642 	ptarget = optarget;
643 	*ptarget = CNULL;
644 
645 	return(didupdate);
646 }
647 
648 /*
649  * Send a link
650  */
651 static int sendlink(rname, opts, stb, user, group, destdir)
652 	char *rname;
653 	opt_t opts;
654 	struct stat *stb;
655 	char *user;
656 	char *group;
657 	int destdir;
658 {
659 	int f, n;
660 	static char tbuf[BUFSIZ];
661 	char lbuf[MAXPATHLEN];
662 	u_char *s;
663 
664 	debugmsg(DM_CALL, "sendlink(%s, %x, stb, %d)\n", rname, opts, destdir);
665 
666 	if (stb->st_nlink > 1) {
667 		struct linkbuf *lp;
668 
669 		if ((lp = linkinfo(stb)) != NULL)
670 			return(sendhardlink(opts, lp, rname, destdir));
671 	}
672 
673 	/*
674 	 * Gather and send basic link info
675 	 */
676 	(void) sendcmd(C_RECVSYMLINK, "%o %04o %ld %ld %ld %s %s %s",
677 		       opts, stb->st_mode & 07777,
678 		       (long) stb->st_size,
679 		       stb->st_mtime, stb->st_atime,
680 		       user, group, rname);
681 	if (response() < 0)
682 		return(-1);
683 
684 	/*
685 	 * Gather and send additional link info
686 	 */
687 	if ((n = readlink(target, lbuf, sizeof(lbuf)-1)) != -1)
688 		lbuf[n] = '\0';
689 	else {
690 		error("%s: readlink failed", target);
691 		err();
692 	}
693 	(void) sprintf(tbuf, "%.*s", (int) stb->st_size, lbuf);
694 	(void) sendcmd(C_NONE, "%s\n", tbuf);
695 
696 	if (n != stb->st_size) {
697 		error("%s: file changed size", target);
698 		err();
699 	} else
700 		ack();
701 
702 	/*
703 	 * Check response
704 	 */
705 	f = response();
706 	if (f < 0)
707 		return(-1);
708 	else if (f == 0 && IS_ON(opts, DO_COMPARE))
709 		return(0);
710 
711 	/*
712 	 * Read and process responses from server.
713 	 * The server may send multiple messages regarding
714 	 * file deletes if the remote target is a directory.
715 	 */
716 	for (;;) {
717 		n = remline(s = respbuff, sizeof(respbuff), TRUE);
718 		if (n == -1)	/* normal EOF */
719 			return(0);
720 		if (n == 0) {
721 			error("expected control record");
722 			continue;
723 		}
724 
725 		switch (*s++) {
726 		case C_END:	/* End of send operation */
727 			*ptarget = CNULL;
728 			ack();
729 			runspecial(target, opts, rname, destdir);
730 			addcmdspecialfile(target, rname, destdir);
731 			return(0);
732 
733 		case C_LOGMSG:
734 			if (n > 0)
735 				message(MT_INFO, "%s", s);
736 			break;
737 
738 		case C_NOTEMSG:
739 			if (n > 0)
740 				message(MT_NOTICE, "%s", s);
741 			break;
742 			/* Goto top of loop */
743 
744 		case C_ERRMSG:
745 			message(MT_NERROR, "%s", s);
746 			return(-1);
747 
748 		case C_FERRMSG:
749 			message(MT_FERROR, "%s", s);
750 			finish();
751 
752 		default:
753 			error("install link: unexpected response '%s'",
754 			      respbuff);
755 			err();
756 		}
757 	}
758 	/*NOTREACHED*/
759 }
760 
761 /*
762  * Check to see if file needs to be updated on the remote machine.
763  * Returns:
764  * 	US_NOTHING	- no update
765  *	US_NOENT	- remote doesn't exist
766  *	US_OUTDATE	- out of date
767  *	US_DOCOMP	- comparing binaries to determine if out of date
768  *	US_MODE		- File modes do not match
769  */
770 static int update(rname, opts, statp)
771 	char *rname;
772 	opt_t opts;
773 	struct stat *statp;
774 {
775 	register off_t size;
776 	register time_t mtime;
777 	unsigned short lmode;
778 	unsigned short rmode;
779 	char *owner = NULL, *group = NULL;
780 	int done, n;
781 	u_char *cp;
782 
783 	debugmsg(DM_CALL, "update(%s, 0x%x, 0x%x)\n", rname, opts, statp);
784 
785 	if (IS_ON(opts, DO_NOEXEC))
786 		if (isexec(target, statp)) {
787 			debugmsg(DM_MISC, "%s is an executable\n", target);
788 			return(US_NOTHING);
789 		}
790 
791 	/*
792 	 * Check to see if the file exists on the remote machine.
793 	 */
794 	(void) sendcmd(C_QUERY, "%s", rname);
795 
796 	for (done = 0; !done;) {
797 		n = remline(cp = respbuff, sizeof(respbuff), TRUE);
798 		if (n <= 0) {
799 			error("update: unexpected control record in response to query");
800 			return(US_NOTHING);
801 		}
802 
803 		switch (*cp++) {
804 		case QC_ONNFS:  /* Resides on a NFS */
805 			debugmsg(DM_MISC,
806 				 "update: %s is on a NFS.  Skipping...\n",
807 				 rname);
808 			return(US_NOTHING);
809 
810 		case QC_SYM:  /* Is a symbolic link */
811 			debugmsg(DM_MISC,
812 				 "update: %s is a symlink.  Skipping...\n",
813 				 rname);
814 			return(US_NOTHING);
815 
816 		case QC_ONRO:  /* Resides on a Read-Only fs */
817 			debugmsg(DM_MISC,
818 				 "update: %s is on a RO fs.  Skipping...\n",
819 				 rname);
820 			return(US_NOTHING);
821 
822 		case QC_YES:
823 			done = 1;
824 			break;
825 
826 		case QC_NO:  /* file doesn't exist so install it */
827 			return(US_NOENT);
828 
829 		case C_ERRMSG:
830 			if (cp)
831 				message(MT_NERROR, "%s", cp);
832 			return(US_NOTHING);
833 
834 		case C_FERRMSG:
835 			if (cp)
836 				message(MT_FERROR, "%s", cp);
837 			finish();
838 
839 		case C_NOTEMSG:
840 			if (cp)
841 				message(MT_NOTICE, "%s", cp);
842 			break;
843 			/* Goto top of loop */
844 
845 		default:
846 			error("update: unexpected response to query '%s'", cp);
847 			return(US_NOTHING);
848 		}
849 	}
850 
851 	/*
852 	 * Target exists, but no other info passed
853 	 */
854 	if (n <= 1 || !S_ISREG(statp->st_mode))
855 		return(US_OUTDATE);
856 
857 	if (IS_ON(opts, DO_COMPARE))
858 		return(US_DOCOMP);
859 
860 	/*
861 	 * Parse size
862 	 */
863 	size = strtol(cp, (char **)&cp, 10);
864 	if (*cp++ != ' ') {
865 		error("update: size not delimited");
866 		return(US_NOTHING);
867 	}
868 
869 	/*
870 	 * Parse mtime
871 	 */
872 	mtime = strtol(cp, (char **)&cp, 10);
873 	if (*cp++ != ' ') {
874 		error("update: mtime not delimited");
875 		return(US_NOTHING);
876 	}
877 
878 	/*
879 	 * Parse remote file mode
880 	 */
881 	rmode = strtol(cp, (char **)&cp, 8);
882 	if (cp && *cp)
883 		++cp;
884 
885 	/*
886 	 * Be backwards compatible
887 	 */
888 	if (cp && *cp != CNULL) {
889 		/*
890 		 * Parse remote file owner
891 		 */
892 		owner = strtok((char *)cp, " ");
893 		if (owner == NULL) {
894 			error("update: owner not delimited");
895 			return(US_NOTHING);
896 		}
897 
898 		/*
899 		 * Parse remote file group
900 		 */
901 		group = strtok(NULL, " ");
902 		if (group == NULL) {
903 			error("update: group not delimited");
904 			return(US_NOTHING);
905 		}
906 	}
907 
908 	/*
909 	 * File needs to be updated?
910 	 */
911 	lmode = statp->st_mode & 07777;
912 
913 	debugmsg(DM_MISC, "update(%s,) local mode %04o remote mode %04o\n",
914 		 rname, lmode, rmode);
915 	debugmsg(DM_MISC, "update(%s,) size %d mtime %d owner '%s' grp '%s'\n",
916 		 rname, (int) size, mtime, owner, group);
917 
918 	if (statp->st_mtime != mtime) {
919 		if (statp->st_mtime < mtime && IS_ON(opts, DO_YOUNGER)) {
920 			message(MT_WARNING,
921 				"%s: Warning: remote copy is newer",
922 				target);
923 			return(US_NOTHING);
924 		}
925 		return(US_OUTDATE);
926 	}
927 
928 	/*
929 	 * If the mode of a file does not match the local mode, the
930 	 * whole file is updated.  This is done both to insure that
931 	 * a bogus version of the file has not been installed and to
932 	 * avoid having to handle weird cases of chmod'ing symlinks
933 	 * and such.
934 	 */
935 	if (!IS_ON(opts, DO_NOCHKMODE) && lmode != rmode) {
936 		debugmsg(DM_MISC, "modes do not match (%04o != %04o).\n",
937 			 lmode, rmode);
938 		return(US_OUTDATE);
939 	}
940 
941 	if (statp->st_size != size) {
942 		debugmsg(DM_MISC, "size does not match (%d != %d).\n",
943 			 (int) statp->st_size, size);
944 		return(US_OUTDATE);
945 	}
946 
947 	/*
948 	 * Check ownership
949 	 */
950 	if (!IS_ON(opts, DO_NOCHKOWNER) && owner) {
951 		if (!IS_ON(opts, DO_NUMCHKOWNER)) {
952 			/* Check by string compare */
953 			if (strcmp(owner, getusername(statp->st_uid,
954 						      target, opts)) != 0) {
955 				debugmsg(DM_MISC,
956 					 "owner does not match (%s != %s).\n",
957 					 getusername(statp->st_uid,
958 						     target, opts), owner);
959 				return(US_OUTDATE);
960 			}
961 		} else {
962 			/*
963 			 * Check numerically.
964 			 * Allow negative numbers.
965 			 */
966 			while (*owner && !isdigit(*owner) && (*owner != '-'))
967 				++owner;
968 			if (owner && atoi(owner) != statp->st_uid) {
969 				debugmsg(DM_MISC,
970 					 "owner does not match (%d != %s).\n",
971 					 statp->st_uid, owner);
972 				return(US_OUTDATE);
973 			}
974 		}
975 	}
976 
977 	if (!IS_ON(opts, DO_NOCHKGROUP) && group) {
978 		if (!IS_ON(opts, DO_NUMCHKGROUP)) {
979 			/* Check by string compare */
980 			if (strcmp(group, getgroupname(statp->st_gid,
981 						       target, opts)) != 0) {
982 				debugmsg(DM_MISC,
983 					 "group does not match (%s != %s).\n",
984 					 getgroupname(statp->st_gid,
985 						      target, opts), group);
986 				return(US_OUTDATE);
987 			}
988 		} else {
989 			/* Check numerically */
990 			/* Allow negative gid */
991 			while (*group && !isdigit(*group) && (*group != '-'))
992 				++group;
993 			if (group && atoi(group) != statp->st_gid) {
994 				debugmsg(DM_MISC,
995 					 "group does not match (%d != %s).\n",
996 					 statp->st_gid, group);
997 				return(US_OUTDATE);
998 			}
999 		}
1000 	}
1001 
1002 	return(US_NOTHING);
1003 }
1004 
1005 /*
1006  * Stat a file
1007  */
1008 static int dostat(file, statbuf, opts)
1009 	char *file;
1010 	struct stat *statbuf;
1011 	opt_t opts;
1012 {
1013 	int s;
1014 
1015 	if (IS_ON(opts, DO_FOLLOW))
1016 		s = stat(file, statbuf);
1017 	else
1018 		s = lstat(file, statbuf);
1019 
1020 	if (s < 0)
1021 		error("%s: %s failed: %s", file,
1022 		      IS_ON(opts, DO_FOLLOW) ? "stat" : "lstat", SYSERR);
1023 	return(s);
1024 }
1025 
1026 /*
1027  * Transfer the file or directory in target[].
1028  * rname is the name of the file on the remote host.
1029  *
1030  * Return < 0 on error.
1031  * Return 0 if nothing happened.
1032  * Return > 0 if anything is updated.
1033  */
1034 static int sendit(rname, opts, destdir)
1035 	char *rname;
1036 	opt_t opts;
1037 	int destdir;
1038 {
1039 	static struct stat stb;
1040 	extern struct subcmd *subcmds;
1041 	char *user, *group;
1042 	int u, len;
1043 	int didupdate = 0;
1044 
1045 	/*
1046 	 * Remove possible accidental newline
1047 	 */
1048 	len = strlen(rname);
1049 	if (len > 0 && rname[len-1] == '\n')
1050 		rname[len-1] = CNULL;
1051 
1052 	if (checkfilename(rname) != 0)
1053 		return(-1);
1054 
1055 	debugmsg(DM_CALL, "sendit(%s, 0x%x) called\n", rname, opts);
1056 
1057 	if (except(target))
1058 		return(0);
1059 
1060 	if (dostat(target, &stb, opts) < 0)
1061 		return(-1);
1062 
1063 	/*
1064 	 * Does rname need updating?
1065 	 */
1066 	u = update(rname, opts, &stb);
1067 	debugmsg(DM_MISC, "sendit(%s, 0x%x): update status of %s is %d\n",
1068 		 rname, opts, target, u);
1069 
1070 	/*
1071 	 * Don't need to update the file, but we may need to save hardlink
1072 	 * info.
1073 	 */
1074 	if (u == US_NOTHING) {
1075 		if (S_ISREG(stb.st_mode) && stb.st_nlink > 1)
1076 			(void) linkinfo(&stb);
1077 		return(0);
1078 	}
1079 
1080 	/*
1081 	 * File mode needs changing
1082 	 */
1083 	if (u == US_MODE) {
1084 		if (IS_ON(opts, DO_VERIFY)) {
1085 			message(MT_INFO, "%s: need to chmod to %04o",
1086 				target, stb.st_mode & 07777);
1087 			runspecial(target, opts, rname, destdir);
1088 			return(1);
1089 		}
1090 		message(MT_CHANGE, "%s: chmod to %04o",
1091 			target, stb.st_mode & 07777);
1092 		(void) sendcmd(C_CHMOD, "%o %04o %s",
1093 			       opts, stb.st_mode & 07777, rname);
1094 		(void) response();
1095 		return(1);
1096 	}
1097 
1098 	user = getusername(stb.st_uid, target, opts);
1099 	group = getgroupname(stb.st_gid, target, opts);
1100 
1101 	/*
1102 	 * No entry - need to install
1103 	 */
1104 	if (u == US_NOENT) {
1105 		if (IS_ON(opts, DO_VERIFY)) {
1106 			message(MT_INFO, "%s: need to install", target);
1107 			runspecial(target, opts, rname, destdir);
1108 			return(1);
1109 		}
1110 		if (!IS_ON(opts, DO_QUIET))
1111 			message(MT_CHANGE, "%s: installing", target);
1112 		FLAG_OFF(opts, (DO_COMPARE|DO_REMOVE));
1113 	}
1114 
1115 	/*
1116 	 * Handle special file types, including directories and symlinks
1117 	 */
1118 	if (S_ISDIR(stb.st_mode)) {
1119 		if (senddir(rname, opts, &stb, user, group, destdir) > 0)
1120 			didupdate = 1;
1121 	} else if (S_ISLNK(stb.st_mode)) {
1122 		if (u != US_NOENT)
1123 			FLAG_ON(opts, DO_COMPARE);
1124 		/*
1125 		 * Since we always send link info to the server
1126 		 * so the server can determine if the remote link
1127 		 * is correct, we never get any acknowledge meant
1128 		 * from the server whether the link was really
1129 		 * updated or not.
1130 		 */
1131 		(void) sendlink(rname, opts, &stb, user, group, destdir);
1132 	} else if (S_ISREG(stb.st_mode)) {
1133 		if (u == US_OUTDATE) {
1134 			if (IS_ON(opts, DO_VERIFY)) {
1135 				message(MT_INFO, "%s: need to update", target);
1136 				runspecial(target, opts, rname, destdir);
1137 				return(1);
1138 			}
1139 			if (!IS_ON(opts, DO_QUIET))
1140 				message(MT_CHANGE, "%s: updating", target);
1141 		}
1142 		if (sendfile(rname, opts, &stb, user, group, destdir) == 0)
1143 			didupdate = 1;
1144 	} else
1145 		error("%s: unknown file type", target);
1146 
1147 	return(didupdate);
1148 }
1149 
1150 /*
1151  * Remove temporary files and do any cleanup operations before exiting.
1152  */
1153 extern void cleanup()
1154 {
1155 	char *file;
1156 #ifdef USE_STATDB
1157 	extern char statfile[];
1158 
1159 	(void) unlink(statfile);
1160 #endif
1161 
1162 	if ((file = getnotifyfile()))
1163 		(void) unlink(file);
1164 }
1165 
1166 /*
1167  * Update the file(s) if they are different.
1168  * destdir = 1 if destination should be a directory
1169  * (i.e., more than one source is being copied to the same destination).
1170  *
1171  * Return < 0 on error.
1172  * Return 0 if nothing updated.
1173  * Return > 0 if something was updated.
1174  */
1175 extern int install(src, dest, ddir, destdir, opts)
1176 	char *src, *dest;
1177  	int ddir, destdir;
1178 	opt_t opts;
1179 {
1180 	static char destcopy[MAXPATHLEN];
1181 	char *rname;
1182 	int didupdate = 0;
1183 
1184 	debugmsg(DM_CALL,
1185 		"install(src=%s,dest=%s,ddir=%d,destdir=%d,opts=%d) start\n",
1186 		(src?src:"NULL"), (dest?dest:"NULL"), ddir, destdir, opts);
1187 	/*
1188 	 * Save source name
1189 	 */
1190 	if (IS_ON(opts, DO_WHOLE))
1191 		source[0] = CNULL;
1192 	else
1193 		(void) strcpy(source, src);
1194 
1195 	if (dest == NULL) {
1196 		FLAG_OFF(opts, DO_WHOLE); /* WHOLE only useful if renaming */
1197 		dest = src;
1198 	}
1199 
1200 	if (checkfilename(dest) != 0)
1201 		return(-1);
1202 
1203 	if (nflag || debug) {
1204 		static char buff[BUFSIZ];
1205 		char *cp;
1206 
1207 		cp = getondistoptlist(opts);
1208 		(void) sprintf(buff, "%s%s%s %s %s",
1209 			       IS_ON(opts, DO_VERIFY) ? "verify" : "install",
1210 			       (cp) ? " -o" : "", (cp) ? cp : "",
1211 			       src, dest);
1212 		if (nflag) {
1213 			printf("%s\n", buff);
1214 			return(0);
1215 		} else
1216 			debugmsg(DM_MISC, "%s\n", buff);
1217 	}
1218 
1219 	rname = exptilde(target, src);
1220 	if (rname == NULL)
1221 		return(-1);
1222 	ptarget = target;
1223 	while (*ptarget)
1224 		ptarget++;
1225 	/*
1226 	 * If we are renaming a directory and we want to preserve
1227 	 * the directory heirarchy (-w), we must strip off the leading
1228 	 * directory name and preserve the rest.
1229 	 */
1230 	if (IS_ON(opts, DO_WHOLE)) {
1231 		while (*rname == '/')
1232 			rname++;
1233 		ddir = 1;
1234 		destdir = 1;
1235 	} else {
1236 		rname = strrchr(target, '/');
1237 		/* Check if no '/' or target ends in '/' */
1238 		if (rname == NULL ||
1239 		    rname+1 == NULL ||
1240 		    *(rname+1) == CNULL)
1241 			rname = target;
1242 		else
1243 			rname++;
1244 	}
1245 
1246 	debugmsg(DM_MISC,
1247  	"install: target=%s src=%s rname=%s dest='%s' destdir=%d, ddir=%d\n",
1248  		 target, source, rname, dest, destdir, ddir);
1249 
1250 	/*
1251 	 * Pass the destination file/directory name to remote.
1252 	 */
1253  	if (ddir)
1254 		(void) sendcmd(C_DIRTARGET, "%o %s", opts, dest);
1255 	else
1256 		(void) sendcmd(C_TARGET, "%o %s", opts, dest);
1257 	if (response() < 0)
1258 		return(-1);
1259 
1260 	/*
1261 	 * Save the name of the remote target destination if we are
1262 	 * in WHOLE mode (destdir > 0) or if the source and destination
1263 	 * are not the same.  This info will be used later for maintaining
1264 	 * hardlink info.
1265 	 */
1266 	if (destdir || (src && dest && strcmp(src, dest))) {
1267 		(void) strcpy(destcopy, dest);
1268 		Tdest = destcopy;
1269 	}
1270 
1271 	didupdate = sendit(rname, opts, destdir);
1272 	Tdest = 0;
1273 
1274 	return(didupdate);
1275 }
1276