xref: /openbsd-src/usr.bin/rdistd/server.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /*	$OpenBSD: server.c,v 1.8 1999/02/04 23:18:57 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 #ifndef lint
36 #if 0
37 static char RCSid[] =
38 "$From: server.c,v 6.85 1996/03/12 22:55:38 mcooper Exp $";
39 #else
40 static char RCSid[] =
41 "$OpenBSD: server.c,v 1.8 1999/02/04 23:18:57 millert Exp $";
42 #endif
43 
44 static char sccsid[] = "@(#)server.c	5.3 (Berkeley) 6/7/86";
45 
46 static char copyright[] =
47 "@(#) Copyright (c) 1983 Regents of the University of California.\n\
48  All rights reserved.\n";
49 #endif /* not lint */
50 
51 /*
52  * Server routines
53  */
54 
55 #include "defs.h"
56 
57 char	tempname[sizeof _RDIST_TMP + 1]; /* Tmp file name */
58 char	buf[BUFSIZ];		/* general purpose buffer */
59 char	target[MAXPATHLEN];	/* target/source directory name */
60 char	*ptarget;		/* pointer to end of target name */
61 int	catname = 0;		/* cat name to target name */
62 char	*sptarget[32];		/* stack of saved ptarget's for directories */
63 char   *fromhost = NULL;	/* Client hostname */
64 static long min_freespace = 0;	/* Minimium free space on a filesystem */
65 static long min_freefiles = 0;	/* Minimium free # files on a filesystem */
66 int	oumask;			/* Old umask */
67 
68 /*
69  * Cat "string" onto the target buffer with error checking.
70  */
71 static int cattarget(string)
72 	char *string;
73 {
74 	if (strlen(string) + strlen(target) + 2 > sizeof(target)) {
75 		message(MT_INFO, "target buffer is not large enough.");
76 		return(-1);
77 	}
78 	if (!ptarget) {
79 		message(MT_INFO, "NULL target pointer set.");
80 		return(-10);
81 	}
82 
83 	(void) sprintf(ptarget, "/%s", string);
84 
85 	return(0);
86 }
87 
88 /*
89  * Set uid and gid ownership of a file.
90  */
91 static int setownership(file, fd, uid, gid)
92 	char *file;
93 	int fd;
94 	UID_T uid;
95 	GID_T gid;
96 {
97 	int status = -1;
98 
99 	/*
100 	 * We assume only the Superuser can change uid ownership.
101 	 */
102 	if (getuid() == 0) {
103 #if	defined(HAVE_FCHOWN)
104 		if (fd != -1)
105 			status = fchown(fd, (CHOWN_UID_T) uid,
106 					(CHOWN_GID_T) gid);
107 #endif
108 		if (status < 0)
109 			status = chown(file, (CHOWN_UID_T) uid,
110 				       (CHOWN_GID_T) gid);
111 
112 		if (status < 0) {
113 			message(MT_NOTICE, "%s: chown %d.%d failed: %s",
114 				target, (UID_T) uid, (GID_T) gid, SYSERR);
115 			return(-1);
116 		}
117 	} else {
118 #if	defined(HAVE_FCHOWN)
119 		if (fd != -1)
120 			status = fchown(fd, (CHOWN_UID_T) -1,
121 					(CHOWN_GID_T) gid);
122 #endif
123 		if (status < 0)
124 			status = chown(file, (CHOWN_UID_T) -1,
125 				       (CHOWN_GID_T) gid);
126 
127 		if (status < 0) {
128 			message(MT_NOTICE, "%s: chgrp %d failed: %s",
129 				target, (GID_T) gid, SYSERR);
130 			return(-1);
131 		}
132 	}
133 
134 	return(0);
135 }
136 
137 /*
138  * Set mode of a file
139  */
140 static int setfilemode(file, fd, mode)
141 	char *file;
142 	int fd;
143 	int mode;
144 {
145 	int status = -1;
146 
147 	if (mode == -1)
148 		return(0);
149 
150 #if	defined(HAVE_FCHMOD)
151 	if (fd != -1)
152 		status = fchmod(fd, mode);
153 #endif
154 
155 	if (status < 0)
156 		status = chmod(file, mode);
157 
158 	if (status < 0) {
159 		message(MT_NOTICE, "%s: chmod failed: %s", target, SYSERR);
160 		return(-1);
161 	}
162 
163 	return(0);
164 }
165 
166 /*
167  * Get group entry.  This routine takes a string argument (name).
168  * If name is of form ":N" a lookup for gid N is done.
169  * Otherwise a lookup by name is done.
170  */
171 static struct group *mygetgroup(name)
172 	char *name;
173 {
174     	struct group *gr;
175 
176 	if (*name == ':')
177 	    	gr = getgrgid(atoi(name + 1));
178 	else
179 	    	gr = getgrnam(name);
180 
181 	return(gr);
182 }
183 
184 /*
185  * Change owner, group and mode of file.
186  */
187 static int fchog(fd, file, owner, group, mode)
188 	int fd;
189 	char *file, *owner, *group;
190 	int mode;
191 {
192 	struct group *gr = NULL;
193 	static char last_group[128];
194 	static char last_owner[128];
195 	static GID_T last_gid = (GID_T)-2;
196 	static UID_T last_uid = (UID_T)-2;
197 	static GID_T last_primegid;
198 	extern char *locuser;
199 	register int i;
200 	UID_T uid;
201 	GID_T gid;
202 	GID_T primegid = (GID_T)-2;
203 
204 	uid = userid;
205 	if (userid == 0) {	/* running as root; take anything */
206 		if (*owner == ':') {
207 			uid = (UID_T) atoi(owner + 1);
208 		} else if (last_uid == (UID_T)-2 ||
209 			   strcmp(owner, last_owner) != 0) {
210 			if ((pw = getpwnam(owner)) == NULL) {
211 				if (mode != -1 && IS_ON(mode, S_ISUID)) {
212 					message(MT_NOTICE,
213 			      "%s: unknown login name \"%s\", clearing setuid",
214 						target, owner);
215 					mode &= ~S_ISUID;
216 					uid = 0;
217 				} else
218 					message(MT_NOTICE,
219 					"%s: unknown login name \"%s\"",
220 						target, owner);
221 			} else {
222 				uid	 = last_uid	 = pw->pw_uid;
223 				primegid = last_primegid = pw->pw_gid;
224 				strcpy(last_owner, owner);
225 			}
226 		} else {
227 			uid = last_uid;
228 			primegid = last_primegid;
229 		}
230 		if (*group == ':') {
231 			gid = (GID_T) atoi(group + 1);
232 			goto ok;
233 		}
234 	} else {	/* not root, setuid only if user==owner */
235 		struct passwd *lupw;
236 
237 		if (mode != -1) {
238 			if (IS_ON(mode, S_ISUID) &&
239 			    strcmp(locuser, owner) != 0)
240 				mode &= ~S_ISUID;
241 			if (mode)
242 				mode &= ~S_ISVTX; /* and strip sticky too */
243 		}
244 
245 		if ((lupw = getpwnam(locuser)) != NULL)
246 			primegid = lupw->pw_gid;
247 	}
248 
249 	gid = (GID_T) -1;
250 	if (last_gid < (GID_T)0 || strcmp(group, last_group) != 0) {
251 	        /*
252 		 * Invalid cached values so we need to do a new lookup.
253 		 */
254 		if ((gr = mygetgroup(group))) {
255 			last_gid = gid = gr->gr_gid;
256 			strcpy(last_group, gr->gr_name);
257 		} else {
258 			if (mode != -1 && IS_ON(mode, S_ISGID)) {
259 				message(MT_NOTICE,
260 				"%s: unknown group \"%s\", clearing setgid",
261 					target, group);
262 				mode &= ~S_ISGID;
263 			} else
264 				message(MT_NOTICE,
265 					"%s: unknown group \"%s\"",
266 					target, group);
267 		}
268 	} else {
269 	    	/*
270 		 * Use the cached values.
271 		 */
272 		gid = last_gid;
273 	}
274 
275 	/*
276 	 * We need to check non-root users to make sure they're a member
277 	 * of the group.  If they are not, we don't set that gid ownership.
278 	 */
279 	if (userid && gid >= 0 && gid != primegid) {
280 		if (!gr)
281 		    	gr = mygetgroup(group);
282 		if (gr)
283 			for (i = 0; gr->gr_mem[i] != NULL; i++)
284 			    	if (strcmp(locuser, gr->gr_mem[i]) == 0)
285 					goto ok;
286 		if (mode != -1 && IS_ON(mode, S_ISGID)) {
287 			message(MT_NOTICE,
288 				"%s: user %s not in group %s, clearing setgid",
289 				target, locuser, group);
290 			mode &= ~S_ISGID;
291 		}
292 		gid = (GID_T) -1;
293 	}
294 ok:
295 	/*
296 	 * Set uid and gid ownership.  If that fails, strip setuid and
297 	 * setgid bits from mode.  Once ownership is set, successful
298 	 * or otherwise, set the new file mode.
299 	 */
300 	if (setownership(file, fd, uid, gid) < 0) {
301 		if (mode != -1 && IS_ON(mode, S_ISUID)) {
302 			message(MT_NOTICE,
303 				"%s: chown failed, clearing setuid", target);
304 			mode &= ~S_ISUID;
305 		}
306 		if (mode != -1 && IS_ON(mode, S_ISGID)) {
307 			message(MT_NOTICE,
308 				"%s: chown failed, clearing setgid", target);
309 			mode &= ~S_ISGID;
310 		}
311 	}
312 	(void) setfilemode(file, fd, mode);
313 
314 
315 	return(0);
316 }
317 
318 /*
319  * Remove a file or directory (recursively) and send back an acknowledge
320  * or an error message.
321  */
322 static int removefile(statb)
323 	struct stat *statb;
324 {
325 	DIR *d;
326 	static DIRENTRY *dp;
327 	register char *cp;
328 	struct stat stb;
329 	char *optarget;
330 	int len, failures = 0;
331 
332 	switch (statb->st_mode & S_IFMT) {
333 	case S_IFREG:
334 	case S_IFLNK:
335 		if (unlink(target) < 0) {
336 			if (errno == ETXTBSY) {
337 				message(MT_REMOTE|MT_NOTICE,
338 					"%s: unlink failed: %s",
339 					target, SYSERR);
340 				return(0);
341 			} else {
342 				error("%s: unlink failed: %s", target, SYSERR);
343 				return(-1);
344 			}
345 		}
346 		goto removed;
347 
348 	case S_IFDIR:
349 		break;
350 
351 	default:
352 		error("%s: not a plain file", target);
353 		return(-1);
354 	}
355 
356 	errno = 0;
357 	if ((d = opendir(target)) == NULL) {
358 		error("%s: opendir failed: %s", target, SYSERR);
359 		return(-1);
360 	}
361 
362 	optarget = ptarget;
363 	len = ptarget - target;
364 	while ((dp = readdir(d))) {
365 		if ((D_NAMLEN(dp) == 1 && dp->d_name[0] == '.') ||
366 		    (D_NAMLEN(dp) == 2 && dp->d_name[0] == '.' &&
367 		     dp->d_name[1] == '.'))
368 			continue;
369 
370 		if (len + 1 + (int)strlen(dp->d_name) >= MAXPATHLEN - 1) {
371 			message(MT_REMOTE|MT_WARNING, "%s/%s: Name too long",
372 				target, dp->d_name);
373 			continue;
374 		}
375 		ptarget = optarget;
376 		*ptarget++ = '/';
377 		cp = dp->d_name;;
378 		while ((*ptarget++ = *cp++))
379 			;
380 		ptarget--;
381 		if (lstat(target, &stb) < 0) {
382 			message(MT_REMOTE|MT_WARNING, "%s: lstat failed: %s",
383 				target, SYSERR);
384 			continue;
385 		}
386 		if (removefile(&stb) < 0)
387 			++failures;
388 	}
389 	(void) closedir(d);
390 	ptarget = optarget;
391 	*ptarget = CNULL;
392 
393 	if (failures)
394 		return(-1);
395 
396 	if (rmdir(target) < 0) {
397 		error("%s: rmdir failed: %s", target, SYSERR);
398 		return(-1);
399 	}
400 removed:
401 	/*
402 	 * We use MT_NOTICE instead of MT_CHANGE because this function is
403 	 * sometimes called by other functions that are suppose to return a
404 	 * single ack() back to the client (rdist).  This is a kludge until
405 	 * the Rdist protocol is re-done.  Sigh.
406 	 */
407 	message(MT_NOTICE|MT_REMOTE, "%s: removed", target);
408 	return(0);
409 }
410 
411 /*
412  * Check the current directory (initialized by the 'T' command to server())
413  * for extraneous files and remove them.
414  */
415 static void doclean(cp)
416 	register char *cp;
417 {
418 	DIR *d;
419 	register DIRENTRY *dp;
420 	struct stat stb;
421 	char *optarget, *ep;
422 	int len;
423 	opt_t opts;
424 
425 	opts = strtol(cp, &ep, 8);
426 	if (*ep != CNULL) {
427 		error("clean: options not delimited");
428 		return;
429 	}
430 	if ((d = opendir(target)) == NULL) {
431 		error("%s: opendir failed: %s", target, SYSERR);
432 		return;
433 	}
434 	ack();
435 
436 	optarget = ptarget;
437 	len = ptarget - target;
438 	while ((dp = readdir(d))) {
439 		if ((D_NAMLEN(dp) == 1 && dp->d_name[0] == '.') ||
440 		    (D_NAMLEN(dp) == 2 && dp->d_name[0] == '.' &&
441 		     dp->d_name[1] == '.'))
442 			continue;
443 
444 		if (len + 1 + (int)strlen(dp->d_name) >= MAXPATHLEN - 1) {
445 			message(MT_REMOTE|MT_WARNING, "%s/%s: Name too long",
446 				target, dp->d_name);
447 			continue;
448 		}
449 		ptarget = optarget;
450 		*ptarget++ = '/';
451 		cp = dp->d_name;;
452 		while ((*ptarget++ = *cp++))
453 			;
454 		ptarget--;
455 		if (lstat(target, &stb) < 0) {
456 			message(MT_REMOTE|MT_WARNING, "%s: lstat failed: %s",
457 				target, SYSERR);
458 			continue;
459 		}
460 
461 		(void) sendcmd(CC_QUERY, "%s", dp->d_name);
462 		(void) remline(cp = buf, sizeof(buf), TRUE);
463 
464 		if (*cp != CC_YES)
465 			continue;
466 
467 		if (IS_ON(opts, DO_VERIFY))
468 			message(MT_REMOTE|MT_INFO, "%s: need to remove",
469 				target);
470 		else
471 			(void) removefile(&stb);
472 	}
473 	(void) closedir(d);
474 
475 	ptarget = optarget;
476 	*ptarget = CNULL;
477 }
478 
479 /*
480  * Frontend to doclean().
481  */
482 static void clean(cp)
483 	register char *cp;
484 {
485 	doclean(cp);
486 	(void) sendcmd(CC_END, NULL);
487 	(void) response();
488 }
489 
490 /*
491  * Execute a shell command to handle special cases.
492  * We can't really set an alarm timeout here since we
493  * have no idea how long the command should take.
494  */
495 static void dospecial(cmd)
496 	char *cmd;
497 {
498 	runcommand(cmd);
499 }
500 
501 /*
502  * Do a special cmd command.  This differs from normal special
503  * commands in that it's done after an entire command has been updated.
504  * The list of updated target files is sent one at a time with RC_FILE
505  * commands.  Each one is added to an environment variable defined by
506  * E_FILES.  When an RC_COMMAND is finally received, the E_FILES variable
507  * is stuffed into our environment and a normal dospecial() command is run.
508  */
509 static void docmdspecial()
510 {
511 	register char *cp;
512 	char *cmd, *env = NULL;
513 	int n;
514 	int len;
515 
516 	/* We're ready */
517 	ack();
518 
519 	for ( ; ; ) {
520 		n = remline(cp = buf, sizeof(buf), FALSE);
521 		if (n <= 0) {
522 			error("cmdspecial: premature end of input.");
523 			return;
524 		}
525 
526 		switch (*cp++) {
527 		case RC_FILE:
528 			if (env == NULL) {
529 				len = (2 * sizeof(E_FILES)) + strlen(cp) + 10;
530 				env = (char *) xmalloc(len);
531 				(void) sprintf(env, "export %s;%s=%s",
532 					       E_FILES, E_FILES, cp);
533 			} else {
534 				len = strlen(env);
535 				env = (char *) xrealloc(env,
536 							len + strlen(cp) + 2);
537 				env[len] = CNULL;
538 				(void) strcat(env, ":");
539 				(void) strcat(env, cp);
540 			}
541 			ack();
542 			break;
543 
544 		case RC_COMMAND:
545 			if (env) {
546 				len = strlen(env);
547 				env = (char *) xrealloc(env,
548 							len + strlen(cp) + 2);
549 				env[len] = CNULL;
550 				(void) strcat(env, ";");
551 				(void) strcat(env, cp);
552 				cmd = env;
553 			} else
554 				cmd = cp;
555 
556 			dospecial(cmd);
557 			if (env)
558 				(void) free(env);
559 			return;
560 
561 		default:
562 			error("Unknown cmdspecial command '%s'.", cp);
563 			return;
564 		}
565 	}
566 }
567 
568 /*
569  * Query. Check to see if file exists. Return one of the following:
570  *
571 #ifdef NFS_CHECK
572  *  QC_ONNFS		- resides on a NFS
573 #endif NFS_CHECK
574 #ifdef RO_CHECK
575  *  QC_ONRO		- resides on a Read-Only filesystem
576 #endif RO_CHECK
577  *  QC_NO		- doesn't exist
578  *  QC_YESsize mtime 	- exists and its a regular file (size & mtime of file)
579  *  QC_YES		- exists and its a directory or symbolic link
580  *  QC_ERRMSGmessage 	- error message
581  */
582 static void query(name)
583 	char *name;
584 {
585 	static struct stat stb;
586 	int s = -1, stbvalid = 0;
587 
588 	if (catname && cattarget(name) < 0)
589 		return;
590 
591 #if	defined(NFS_CHECK)
592 	if (IS_ON(options, DO_CHKNFS)) {
593 		s = is_nfs_mounted(target, &stb, &stbvalid);
594 		if (s > 0)
595 			(void) sendcmd(QC_ONNFS, NULL);
596 
597 		/* Either the above check was true or an error occured */
598 		/* and is_nfs_mounted sent the error message */
599 		if (s != 0) {
600 			*ptarget = CNULL;
601 			return;
602 		}
603 	}
604 #endif 	/* NFS_CHECK */
605 
606 #if	defined(RO_CHECK)
607 	if (IS_ON(options, DO_CHKREADONLY)) {
608 		s = is_ro_mounted(target, &stb, &stbvalid);
609 		if (s > 0)
610 			(void) sendcmd(QC_ONRO, NULL);
611 
612 		/* Either the above check was true or an error occured */
613 		/* and is_ro_mounted sent the error message */
614 		if (s != 0) {
615 			*ptarget = CNULL;
616 			return;
617 		}
618 	}
619 #endif 	/* RO_CHECK */
620 
621 	if (IS_ON(options, DO_CHKSYM)) {
622 		if (is_symlinked(target, &stb, &stbvalid) > 0) {
623 			(void) sendcmd(QC_SYM, NULL);
624 			return;
625 		}
626 	}
627 
628 	/*
629 	 * If stbvalid is false, "stb" is not valid because:
630 	 *	a) RO_CHECK and NFS_CHECK were not defined
631 	 *	b) The stat by is_*_mounted() either failed or
632 	 *	   does not match "target".
633 	 */
634 	if (!stbvalid && lstat(target, &stb) < 0) {
635 		if (errno == ENOENT)
636 			(void) sendcmd(QC_NO, NULL);
637 		else
638 			error("%s: lstat failed: %s", target, SYSERR);
639 		*ptarget = CNULL;
640 		return;
641 	}
642 
643 	switch (stb.st_mode & S_IFMT) {
644 	case S_IFLNK:
645 	case S_IFDIR:
646 	case S_IFREG:
647 		(void) sendcmd(QC_YES, "%ld %ld %o %s %s",
648 			       (long) stb.st_size,
649 			       stb.st_mtime,
650 			       stb.st_mode & 07777,
651 			       getusername(stb.st_uid, target, options),
652 			       getgroupname(stb.st_gid, target, options));
653 		break;
654 
655 	default:
656 		error("%s: not a file or directory", target);
657 		break;
658 	}
659 	*ptarget = CNULL;
660 }
661 
662 /*
663  * Check to see if parent directory exists and create one if not.
664  */
665 static int chkparent(name, opts)
666 	char *name;
667 	opt_t opts;
668 {
669 	register char *cp;
670 	struct stat stb;
671 	int r = -1;
672 
673 	debugmsg(DM_CALL, "chkparent(%s, %o) start\n", name, opts);
674 
675 	cp = strrchr(name, '/');
676 	if (cp == NULL || cp == name)
677 		return(0);
678 
679 	*cp = CNULL;
680 
681 	if (lstat(name, &stb) < 0) {
682 		if (errno == ENOENT && chkparent(name, opts) >= 0) {
683 			if (mkdir(name, 0777 & ~oumask) == 0) {
684 				message(MT_NOTICE, "%s: mkdir", name);
685 				r = 0;
686 			} else
687 				debugmsg(DM_MISC,
688 					 "chkparent(%s, %o) mkdir fail: %s\n",
689 					 name, opts, SYSERR);
690 		}
691 	} else	/* It exists */
692 		r = 0;
693 
694 	/* Put back what we took away */
695 	*cp = '/';
696 
697 	return(r);
698 }
699 
700 /*
701  * Save a copy of 'file' by renaming it.
702  */
703 static char *savetarget(file)
704 	char *file;
705 {
706 	static char savefile[MAXPATHLEN];
707 
708 	if (strlen(file) + sizeof(SAVE_SUFFIX) + 1 > MAXPATHLEN) {
709 		error("%s: Cannot save: Save name too long", file);
710 		return(NULL);
711 	}
712 
713 	(void) sprintf(savefile, "%s%s", file, SAVE_SUFFIX);
714 
715 	if (unlink(savefile) != 0 && errno != ENOENT) {
716 		message(MT_NOTICE, "%s: remove failed: %s", savefile, SYSERR);
717 		return(NULL);
718 	}
719 
720 	if (rename(file, savefile) != 0 && errno != ENOENT) {
721 		error("%s -> %s: rename failed: %s",
722 		      file, savefile, SYSERR);
723 		return(NULL);
724 	}
725 
726 	return(savefile);
727 }
728 
729 /*
730  * See if buf is all zeros (sparse check)
731  */
732 static int iszeros (buf, size)
733 	char *buf;
734 	off_t size;
735 {
736     	while (size > 0) {
737 	    if (*buf != CNULL)
738 		return(0);
739 	    buf++;
740 	    size--;
741 	}
742 
743 	return(1);
744 }
745 
746 
747 /*
748  * Receive a file
749  */
750 static void recvfile(new, opts, mode, owner, group, mtime, atime, size)
751 	/*ARGSUSED*/
752 	char *new;
753 	opt_t opts;
754 	int mode;
755 	char *owner, *group;
756 	time_t mtime;
757 	time_t atime;
758 	off_t size;
759 {
760 	int f, wrerr, olderrno, lastwashole = 0, wassparse = 0;
761 	off_t i;
762 	register char *cp;
763 	char *savefile = NULL;
764 	static struct stat statbuff;
765 
766 	/*
767 	 * Create temporary file
768 	 */
769 	if ((f = open(new, O_CREAT|O_EXCL|O_WRONLY, mode)) < 0) {
770 		if (errno != ENOENT || chkparent(new, opts) < 0 ||
771 		    (f = open(new, O_CREAT|O_EXCL|O_WRONLY, mode)) < 0) {
772 			error("%s: create failed: %s", new, SYSERR);
773 			(void) unlink(new);
774 			return;
775 		}
776 	}
777 
778 	/*
779 	 * Receive the file itself
780 	 */
781 	ack();
782 	wrerr = 0;
783 	olderrno = 0;
784 	for (i = 0; i < size; i += BUFSIZ) {
785 		int amt = BUFSIZ;
786 
787 		cp = buf;
788 		if (i + amt > size)
789 			amt = size - i;
790 		do {
791 			int j;
792 
793 			j = readrem(cp, amt);
794 			if (j <= 0) {
795 				(void) close(f);
796 				(void) unlink(new);
797 				fatalerr(
798 				   "Read error occured while receiving file.");
799 				finish();
800 			}
801 			amt -= j;
802 			cp += j;
803 		} while (amt > 0);
804 		amt = BUFSIZ;
805 		if (i + amt > size)
806 			amt = size - i;
807 		if (IS_ON(opts, DO_SPARSE) && iszeros(buf, amt)) {
808 		    	if (lseek (f, amt, SEEK_CUR) < 0L) {
809 			    	olderrno = errno;
810 				wrerr++;
811 			}
812 			lastwashole = 1;
813 			wassparse++;
814 		} else {
815 		    	if (wrerr == 0 && xwrite(f, buf, amt) != amt) {
816 			    	olderrno = errno;
817 				wrerr++;
818 			}
819 			lastwashole = 0;
820 		}
821 	}
822 
823 	if (lastwashole) {
824 #if	defined(HAVE_FTRUNCATE)
825 	    	if (write (f, "", 1) != 1 || ftruncate (f, size) < 0)
826 #else
827 		/* Seek backwards one character and write a null.  */
828 		if (lseek (f, (off_t) -1, SEEK_CUR) < 0L
829 		    || write (f, "", 1) != 1)
830 #endif
831 		{
832 			olderrno = errno;
833 			wrerr++;
834 		}
835 	}
836 
837 	if (response() < 0) {
838 		(void) close(f);
839 		(void) unlink(new);
840 		return;
841 	}
842 
843 	if (wrerr) {
844 		error("%s: Write error: %s", new, strerror(olderrno));
845 		(void) close(f);
846 		(void) unlink(new);
847 		return;
848 	}
849 
850 	/*
851 	 * Do file comparison if enabled
852 	 */
853 	if (IS_ON(opts, DO_COMPARE)) {
854 		FILE *f1, *f2;
855 		int c;
856 
857 		errno = 0;	/* fopen is not a syscall */
858 		if ((f1 = fopen(target, "r")) == NULL) {
859 			error("%s: open for read failed: %s", target, SYSERR);
860 			(void) close(f);
861 			(void) unlink(new);
862 			return;
863 		}
864 		errno = 0;
865 		if ((f2 = fopen(new, "r")) == NULL) {
866 			error("%s: open for read failed: %s", new, SYSERR);
867 			(void) close(f);
868 			(void) unlink(new);
869 			return;
870 		}
871 		while ((c = getc(f1)) == getc(f2))
872 			if (c == EOF) {
873 				debugmsg(DM_MISC,
874 					 "Files are the same '%s' '%s'.",
875 					 target, new);
876 				(void) fclose(f1);
877 				(void) fclose(f2);
878 				(void) close(f);
879 				(void) unlink(new);
880 				/*
881 				 * This isn't an error per-se, but we
882 				 * need to indicate to the master that
883 				 * the file was not updated.
884 				 */
885 				error("");
886 				return;
887 			}
888 		debugmsg(DM_MISC, "Files are different '%s' '%s'.",
889 			 target, new);
890 		(void) fclose(f1);
891 		(void) fclose(f2);
892 		if (IS_ON(opts, DO_VERIFY)) {
893 			message(MT_REMOTE|MT_INFO, "%s: need to update",
894 				target);
895 			(void) close(f);
896 			(void) unlink(new);
897 			return;
898 		}
899 	}
900 
901 	/*
902 	 * Set owner, group, and file mode
903 	 */
904 	if (fchog(f, new, owner, group, mode) < 0) {
905 		(void) close(f);
906 		(void) unlink(new);
907 		return;
908 	}
909 	(void) close(f);
910 
911 	/*
912 	 * Perform utimes() after file is closed to make
913 	 * certain OS's, such as NeXT 2.1, happy.
914 	 */
915 	if (setfiletime(new, time((time_t *) 0), mtime) < 0)
916 		message(MT_NOTICE, "%s: utimes failed: %s", new, SYSERR);
917 
918 	/*
919 	 * Try to save target file from being over-written
920 	 */
921 	if (IS_ON(opts, DO_SAVETARGETS))
922 		if ((savefile = savetarget(target)) == NULL) {
923 			(void) unlink(new);
924 			return;
925 		}
926 
927 	/*
928 	 * If the target is a directory, we need to remove it first
929 	 * before we can rename the new file.
930 	 */
931 	if ((stat(target, &statbuff) == 0) && S_ISDIR(statbuff.st_mode)) {
932 		char *saveptr = ptarget;
933 
934 		ptarget = &target[strlen(target)];
935 		removefile(&statbuff);
936 		ptarget = saveptr;
937 	}
938 
939 	/*
940 	 * Install new (temporary) file as the actual target
941 	 */
942 	if (rename(new, target) < 0) {
943 		/*
944 		 * If the rename failed due to "Text file busy", then
945 		 * try to rename the target file and retry the rename.
946 		 */
947 		if (errno == ETXTBSY) {
948 			/* Save the target */
949 			if ((savefile = savetarget(target)) != NULL) {
950 				/* Retry installing new file as target */
951 				if (rename(new, target) < 0) {
952 					error("%s -> %s: rename failed: %s",
953 					      new, target, SYSERR);
954 					/* Try to put back save file */
955 					if (rename(savefile, target) < 0)
956 						error(
957 					         "%s -> %s: rename failed: %s",
958 						      savefile, target,
959 						      SYSERR);
960 				} else
961 					message(MT_NOTICE, "%s: renamed to %s",
962 						target, savefile);
963 			}
964 		} else {
965 			error("%s -> %s: rename failed: %s",
966 			      new, target, SYSERR);
967 			(void) unlink(new);
968 		}
969 	}
970 
971 	if (wassparse)
972 	    	message (MT_NOTICE, "%s: was sparse", target);
973 
974 	if (IS_ON(opts, DO_COMPARE))
975 		message(MT_REMOTE|MT_CHANGE, "%s: updated", target);
976 	else
977 		ack();
978 }
979 
980 /*
981  * Receive a directory
982  */
983 static void recvdir(opts, mode, owner, group)
984 	opt_t opts;
985 	int mode;
986 	char *owner, *group;
987 {
988 	static char lowner[100], lgroup[100];
989 	register char *cp;
990 	struct stat stb;
991 	int s;
992 
993 	s = lstat(target, &stb);
994 	if (s == 0) {
995 		/*
996 		 * If target is not a directory, remove it
997 		 */
998 		if (!S_ISDIR(stb.st_mode)) {
999 			if (IS_ON(opts, DO_VERIFY))
1000 				message(MT_NOTICE, "%s: need to remove",
1001 					target);
1002 			else {
1003 				if (unlink(target) < 0) {
1004 					error("%s: remove failed: %s",
1005 					      target, SYSERR);
1006 					return;
1007 				}
1008 			}
1009 			s = -1;
1010 			errno = ENOENT;
1011 		} else {
1012 			if (!IS_ON(opts, DO_NOCHKMODE) &&
1013 			    (stb.st_mode & 07777) != mode) {
1014 				if (IS_ON(opts, DO_VERIFY))
1015 					message(MT_NOTICE,
1016 						"%s: need to chmod to %o",
1017 						target, mode);
1018 				else {
1019 					if (chmod(target, mode) != 0)
1020 						message(MT_NOTICE,
1021 					  "%s: chmod from %o to %o failed: %s",
1022 							target,
1023 							stb.st_mode & 07777,
1024 							mode,
1025 							SYSERR);
1026 					else
1027 						message(MT_NOTICE,
1028 						"%s: chmod from %o to %o",
1029 							target,
1030 							stb.st_mode & 07777,
1031 							mode);
1032 				}
1033 			}
1034 
1035 			/*
1036 			 * Check ownership and set if necessary
1037 			 */
1038 			lowner[0] = CNULL;
1039 			lgroup[0] = CNULL;
1040 
1041 			if (!IS_ON(opts, DO_NOCHKOWNER) && owner) {
1042 				int o;
1043 
1044 				o = (owner[0] == ':') ? opts & DO_NUMCHKOWNER :
1045 					opts;
1046 				if ((cp = getusername(stb.st_uid, target, o)))
1047 					if (strcmp(owner, cp))
1048 						(void) strcpy(lowner, cp);
1049 			}
1050 			if (!IS_ON(opts, DO_NOCHKGROUP) && group) {
1051 				int o;
1052 
1053 				o = (group[0] == ':') ? opts & DO_NUMCHKGROUP :
1054 					opts;
1055 				if ((cp = getgroupname(stb.st_gid, target, o)))
1056 					if (strcmp(group, cp))
1057 						(void) strcpy(lgroup, cp);
1058 			}
1059 
1060 			/*
1061 			 * Need to set owner and/or group
1062 			 */
1063 #define PRN(n) ((n[0] == ':') ? n+1 : n)
1064 			if (lowner[0] != CNULL || lgroup[0] != CNULL) {
1065 				if (lowner[0] == CNULL &&
1066 				    (cp = getusername(stb.st_uid,
1067 						      target, opts)))
1068 					(void) strcpy(lowner, cp);
1069 				if (lgroup[0] == CNULL &&
1070 				    (cp = getgroupname(stb.st_gid,
1071 						       target, opts)))
1072 					(void) strcpy(lgroup, cp);
1073 
1074 				if (IS_ON(opts, DO_VERIFY))
1075 					message(MT_NOTICE,
1076 				"%s: need to chown from %s.%s to %s.%s",
1077 						target,
1078 						PRN(lowner), PRN(lgroup),
1079 						PRN(owner), PRN(group));
1080 				else {
1081 					if (fchog(-1, target, owner,
1082 						  group, -1) == 0)
1083 						message(MT_NOTICE,
1084 					       "%s: chown from %s.%s to %s.%s",
1085 							target,
1086 							PRN(lowner),
1087 							PRN(lgroup),
1088 							PRN(owner),
1089 							PRN(group));
1090 				}
1091 			}
1092 #undef PRN
1093 			ack();
1094 			return;
1095 		}
1096 	}
1097 
1098 	if (IS_ON(opts, DO_VERIFY)) {
1099 		ack();
1100 		return;
1101 	}
1102 
1103 	/*
1104 	 * Create the directory
1105 	 */
1106 	if (s < 0) {
1107 		if (errno == ENOENT) {
1108 			if (mkdir(target, mode) == 0 ||
1109 			    (chkparent(target, opts) == 0 &&
1110 			    mkdir(target, mode) == 0)) {
1111 				message(MT_NOTICE, "%s: mkdir", target);
1112 				(void) fchog(-1, target, owner, group, mode);
1113 				ack();
1114 			} else {
1115 				error("%s: mkdir failed: %s", target, SYSERR);
1116 				ptarget = sptarget[--catname];
1117 				*ptarget = CNULL;
1118 			}
1119 			return;
1120 		}
1121 	}
1122 	error("%s: lstat failed: %s", target, SYSERR);
1123 	ptarget = sptarget[--catname];
1124 	*ptarget = CNULL;
1125 }
1126 
1127 /*
1128  * Receive a link
1129  */
1130 static void recvlink(new, opts, mode, size)
1131 	char *new;
1132 	opt_t opts;
1133 	int mode;
1134 	off_t size;
1135 {
1136 	struct stat stb;
1137 	char *optarget;
1138 	off_t i;
1139 
1140 	/*
1141 	 * Read basic link info
1142 	 */
1143 	ack();
1144 	(void) remline(buf, sizeof(buf), TRUE);
1145 
1146 	if (response() < 0) {
1147 		err();
1148 		return;
1149 	}
1150 
1151 	/*
1152 	 * Make new symlink using a temporary name
1153 	 */
1154 	if (symlink(buf, new) < 0) {
1155 		if (errno != ENOENT || chkparent(new, opts) < 0 ||
1156 		    symlink(buf, new) < 0) {
1157 			error("%s -> %s: symlink failed: %s", new, buf,SYSERR);
1158 			(void) unlink(new);
1159 			return;
1160 		}
1161 	}
1162 
1163 	/*
1164 	 * Do comparison of what link is pointing to if enabled
1165 	 */
1166 	mode &= 0777;
1167 	if (IS_ON(opts, DO_COMPARE)) {
1168 		char tbuf[MAXPATHLEN];
1169 
1170 		if ((i = readlink(target, tbuf, sizeof(tbuf)-1)) != -1)
1171 			tbuf[i] = '\0';
1172 		if (i != -1 && i == size && strncmp(buf, tbuf, (size_t) size) == 0) {
1173 			(void) unlink(new);
1174 			ack();
1175 			return;
1176 		}
1177 		if (IS_ON(opts, DO_VERIFY)) {
1178 			(void) unlink(new);
1179 			message(MT_REMOTE|MT_INFO, "%s: need to update",
1180 				target);
1181 			(void) sendcmd(C_END, NULL);
1182 			(void) response();
1183 			return;
1184 		}
1185 	}
1186 
1187 	/*
1188 	 * See if target is a directory and remove it if it is
1189 	 */
1190 	if (lstat(target, &stb) == 0) {
1191 		if (S_ISDIR(stb.st_mode)) {
1192 			optarget = ptarget;
1193 			for (ptarget = target; *ptarget; ptarget++);
1194 			if (removefile(&stb) < 0) {
1195 				ptarget = optarget;
1196 				(void) unlink(new);
1197 				(void) sendcmd(C_END, NULL);
1198 				(void) response();
1199 				return;
1200 			}
1201 			ptarget = optarget;
1202 		}
1203 	}
1204 
1205 	/*
1206 	 * Install link as the target
1207 	 */
1208 	if (rename(new, target) < 0) {
1209 		error("%s -> %s: symlink rename failed: %s",
1210 		      new, target, SYSERR);
1211 		(void) unlink(new);
1212 		(void) sendcmd(C_END, NULL);
1213 		(void) response();
1214 		return;
1215 	}
1216 
1217 	if (IS_ON(opts, DO_COMPARE))
1218 		message(MT_REMOTE|MT_CHANGE, "%s: updated", target);
1219 	else
1220 	        ack();
1221 
1222 	/*
1223 	 * Indicate end of receive operation
1224 	 */
1225 	(void) sendcmd(C_END, NULL);
1226 	(void) response();
1227 }
1228 
1229 /*
1230  * Creat a hard link to existing file.
1231  */
1232 static void hardlink(cmd)
1233 	char *cmd;
1234 {
1235 	struct stat stb;
1236 	int exists = 0;
1237 	char *oldname, *newname;
1238 	char *cp = cmd;
1239 	static char expbuf[BUFSIZ];
1240 
1241 	/* Skip over opts */
1242 	(void) strtol(cp, &cp, 8);
1243 	if (*cp++ != ' ') {
1244 		error("hardlink: options not delimited");
1245 		return;
1246 	}
1247 
1248 	oldname = strtok(cp, " ");
1249 	if (oldname == NULL) {
1250 		error("hardlink: oldname name not delimited");
1251 		return;
1252 	}
1253 
1254 	newname = strtok(NULL, " ");
1255 	if (newname == NULL) {
1256 		error("hardlink: new name not specified");
1257 		return;
1258 	}
1259 
1260 	if (exptilde(expbuf, oldname) == NULL) {
1261 		error("hardlink: tilde expansion failed");
1262 		return;
1263 	}
1264 	oldname = expbuf;
1265 
1266 	if (catname && cattarget(newname) < 0) {
1267 		error("Cannot set newname target.");
1268 		return;
1269 	}
1270 
1271 	if (lstat(target, &stb) == 0) {
1272 		int mode = stb.st_mode & S_IFMT;
1273 
1274 		if (mode != S_IFREG && mode != S_IFLNK) {
1275 			error("%s: not a regular file", target);
1276 			return;
1277 		}
1278 		exists = 1;
1279 	}
1280 
1281 	if (chkparent(target, options) < 0 ) {
1282 		error("%s: no parent: %s ", target, SYSERR);
1283 		return;
1284 	}
1285 	if (exists && (unlink(target) < 0)) {
1286 		error("%s: unlink failed: %s", target, SYSERR);
1287 		return;
1288 	}
1289 	if (link(oldname, target) < 0) {
1290 		error("%s: cannot link to %s: %s", target, oldname, SYSERR);
1291 		return;
1292 	}
1293 	ack();
1294 }
1295 
1296 /*
1297  * Set configuration information.
1298  *
1299  * A key letter is followed immediately by the value
1300  * to set.  The keys are:
1301  *	SC_FREESPACE	- Set minimium free space of filesystem
1302  *	SC_FREEFILES	- Set minimium free number of files of filesystem
1303  */
1304 static void setconfig(cmd)
1305 	char *cmd;
1306 {
1307 	register char *cp = cmd;
1308 	char *estr;
1309 
1310 	switch (*cp++) {
1311 	case SC_HOSTNAME:	/* Set hostname */
1312 		/*
1313 		 * Only use info if we don't know who this is.
1314 		 */
1315 		if (!fromhost) {
1316 			fromhost = xstrdup(cp);
1317 			message(MT_SYSLOG, "startup for %s",  fromhost);
1318 #if defined(SETARGS) || defined(HAVE_SETPROCTITLE)
1319 			setproctitle("serving %s", cp);
1320 #endif /* SETARGS || HAVE_SETPROCTITLE */
1321 		}
1322 		break;
1323 
1324 	case SC_FREESPACE: 	/* Minimium free space */
1325 		if (!isdigit(*cp)) {
1326 			fatalerr("Expected digit, got '%s'.", cp);
1327 			return;
1328 		}
1329 		min_freespace = (unsigned long) atoi(cp);
1330 		break;
1331 
1332 	case SC_FREEFILES: 	/* Minimium free files */
1333 		if (!isdigit(*cp)) {
1334 			fatalerr("Expected digit, got '%s'.", cp);
1335 			return;
1336 		}
1337 		min_freefiles = (unsigned long) atoi(cp);
1338 		break;
1339 
1340 	case SC_LOGGING:	/* Logging options */
1341 		if ((estr = msgparseopts(cp, TRUE))) {
1342 			fatalerr("Bad message option string (%s): %s",
1343 				 cp, estr);
1344 			return;
1345 		}
1346 		break;
1347 
1348 	default:
1349 		message(MT_NOTICE, "Unknown config command \"%s\".", cp-1);
1350 		return;
1351 	}
1352 }
1353 
1354 /*
1355  * Receive something
1356  */
1357 static void recvit(cmd, type)
1358 	char *cmd;
1359 	int type;
1360 {
1361 	int mode;
1362 	opt_t opts;
1363 	off_t size;
1364 	time_t mtime, atime;
1365 	char *owner, *group, *file;
1366 	char new[MAXPATHLEN];
1367 	long freespace = -1, freefiles = -1;
1368 	char *cp = cmd;
1369 
1370 	/*
1371 	 * Get rdist option flags
1372 	 */
1373 	opts = strtol(cp, &cp, 8);
1374 	if (*cp++ != ' ') {
1375 		error("recvit: options not delimited");
1376 		return;
1377 	}
1378 
1379 	/*
1380 	 * Get file mode
1381 	 */
1382 	mode = strtol(cp, &cp, 8);
1383 	if (*cp++ != ' ') {
1384 		error("recvit: mode not delimited");
1385 		return;
1386 	}
1387 
1388 	/*
1389 	 * Get file size
1390 	 */
1391 	size = strtol(cp, &cp, 10);
1392 	if (*cp++ != ' ') {
1393 		error("recvit: size not delimited");
1394 		return;
1395 	}
1396 
1397 	/*
1398 	 * Get modification time
1399 	 */
1400 	mtime = strtol(cp, &cp, 10);
1401 	if (*cp++ != ' ') {
1402 		error("recvit: mtime not delimited");
1403 		return;
1404 	}
1405 
1406 	/*
1407 	 * Get access time
1408 	 */
1409 	atime = strtol(cp, &cp, 10);
1410 	if (*cp++ != ' ') {
1411 		error("recvit: atime not delimited");
1412 		return;
1413 	}
1414 
1415 	/*
1416 	 * Get file owner name
1417 	 */
1418 	owner = strtok(cp, " ");
1419 	if (owner == NULL) {
1420 		error("recvit: owner name not delimited");
1421 		return;
1422 	}
1423 
1424 	/*
1425 	 * Get file group name
1426 	 */
1427 	group = strtok(NULL, " ");
1428 	if (group == NULL) {
1429 		error("recvit: group name not delimited");
1430 		return;
1431 	}
1432 
1433 	/*
1434 	 * Get file name.  Can't use strtok() since there could
1435 	 * be white space in the file name.
1436 	 */
1437 	file = group + strlen(group) + 1;
1438 	if (file == NULL) {
1439 		error("recvit: no file name");
1440 		return;
1441 	}
1442 
1443 	debugmsg(DM_MISC,
1444 		 "recvit: opts = %04o mode = %04o size = %d mtime = %d",
1445 		 opts, mode, size, mtime);
1446 	debugmsg(DM_MISC,
1447        "recvit: owner = '%s' group = '%s' file = '%s' catname = %d isdir = %d",
1448 		 owner, group, file, catname, (type == S_IFDIR) ? 1 : 0);
1449 
1450 	if (type == S_IFDIR) {
1451 		if (catname >= sizeof(sptarget)) {
1452 			error("%s: too many directory levels", target);
1453 			return;
1454 		}
1455 		sptarget[catname] = ptarget;
1456 		if (catname++) {
1457 			*ptarget++ = '/';
1458 			while ((*ptarget++ = *file++))
1459 			    ;
1460 			ptarget--;
1461 		}
1462 	} else {
1463 		/*
1464 		 * Create name of temporary file
1465 		 */
1466 		if (catname && cattarget(file) < 0) {
1467 			error("Cannot set file name.");
1468 			return;
1469 		}
1470 		file = strrchr(target, '/');
1471 		if (file == NULL)
1472 			(void) strcpy(new, tempname);
1473 		else if (file == target)
1474 			(void) sprintf(new, "/%s", tempname);
1475 		else {
1476 			*file = CNULL;
1477 			(void) sprintf(new, "%s/%s", target, tempname);
1478 			*file = '/';
1479 		}
1480 		(void) mktemp(new);
1481 	}
1482 
1483 	/*
1484 	 * Check to see if there is enough free space and inodes
1485 	 * to install this file.
1486 	 */
1487 	if (min_freespace || min_freefiles) {
1488 		/* Convert file size to kilobytes */
1489 		long fsize = (long) (size / 1024);
1490 
1491 		if (getfilesysinfo(target, &freespace, &freefiles) != 0)
1492 			return;
1493 
1494 		/*
1495 		 * filesystem values < 0 indicate unsupported or unavailable
1496 		 * information.
1497 		 */
1498 		if (min_freespace && (freespace >= 0) &&
1499 		    (freespace - fsize < min_freespace)) {
1500 			error(
1501 		     "%s: Not enough free space on filesystem: min %d free %d",
1502 			      target, min_freespace, freespace);
1503 			return;
1504 		}
1505 		if (min_freefiles && (freefiles >= 0) &&
1506 		    (freefiles - 1 < min_freefiles)) {
1507 			error(
1508 		     "%s: Not enough free files on filesystem: min %d free %d",
1509 			      target, min_freefiles, freefiles);
1510 			return;
1511 		}
1512 	}
1513 
1514 	/*
1515 	 * Call appropriate receive function to receive file
1516 	 */
1517 	switch (type) {
1518 	case S_IFDIR:
1519 		recvdir(opts, mode, owner, group);
1520 		break;
1521 
1522 	case S_IFLNK:
1523 		recvlink(new, opts, mode, size);
1524 		break;
1525 
1526 	case S_IFREG:
1527 		recvfile(new, opts, mode, owner, group, mtime, atime, size);
1528 		break;
1529 
1530 	default:
1531 		error("%d: unknown file type", type);
1532 		break;
1533 	}
1534 }
1535 
1536 /*
1537  * Set target information
1538  */
1539 static void settarget(cmd, isdir)
1540 	char *cmd;
1541 	int isdir;
1542 {
1543 	char *cp = cmd;
1544 	opt_t opts;
1545 
1546 	catname = isdir;
1547 
1548 	/*
1549 	 * Parse options for this target
1550 	 */
1551 	opts = strtol(cp, &cp, 8);
1552 	if (*cp++ != ' ') {
1553 		error("settarget: options not delimited");
1554 		return;
1555 	}
1556 	options = opts;
1557 
1558 	/*
1559 	 * Handle target
1560 	 */
1561 	if (exptilde(target, cp) == NULL)
1562 		return;
1563 	ptarget = target;
1564 	while (*ptarget)
1565 		ptarget++;
1566 
1567 	ack();
1568 }
1569 
1570 /*
1571  * Cleanup in preparation for exiting.
1572  */
1573 extern void cleanup()
1574 {
1575 	/* We don't need to do anything */
1576 }
1577 
1578 /*
1579  * Server routine to read requests and process them.
1580  */
1581 extern void server()
1582 {
1583 	static char cmdbuf[BUFSIZ];
1584 	register char *cp;
1585 	register int n;
1586 	extern jmp_buf finish_jmpbuf;
1587 
1588 	if (setjmp(finish_jmpbuf)) {
1589 		setjmp_ok = FALSE;
1590 		return;
1591 	}
1592         setjmp_ok = TRUE;
1593 	(void) signal(SIGHUP, sighandler);
1594 	(void) signal(SIGINT, sighandler);
1595 	(void) signal(SIGQUIT, sighandler);
1596 	(void) signal(SIGTERM, sighandler);
1597 	(void) signal(SIGPIPE, sighandler);
1598 	(void) umask(oumask = umask(0));
1599 	(void) strcpy(tempname, _RDIST_TMP);
1600 	if (fromhost) {
1601 		message(MT_SYSLOG, "Startup for %s", fromhost);
1602 #if 	defined(SETARGS)
1603 		setproctitle("Serving %s", fromhost);
1604 #endif	/* SETARGS */
1605 	}
1606 
1607 	/*
1608 	 * Let client know we want it to send it's version number
1609 	 */
1610 	(void) sendcmd(S_VERSION, NULL);
1611 
1612 	if (remline(cmdbuf, sizeof(cmdbuf), TRUE) < 0) {
1613 		setjmp_ok = FALSE;
1614 		error("server: expected control record");
1615 		return;
1616 	}
1617 
1618 	if (cmdbuf[0] != S_VERSION || !isdigit(cmdbuf[1])) {
1619 		setjmp_ok = FALSE;
1620 		error("Expected version command, received: \"%s\".", cmdbuf);
1621 		return;
1622 	}
1623 
1624 	proto_version = atoi(&cmdbuf[1]);
1625 	if (proto_version != VERSION) {
1626 		setjmp_ok = FALSE;
1627 		error("Protocol version %d is not supported.", proto_version);
1628 		return;
1629 	}
1630 
1631 	/* Version number is okay */
1632 	ack();
1633 
1634 	/*
1635 	 * Main command loop
1636 	 */
1637 	for ( ; ; ) {
1638 		n = remline(cp = cmdbuf, sizeof(cmdbuf), TRUE);
1639 		if (n == -1) {		/* EOF */
1640 			setjmp_ok = FALSE;
1641 			return;
1642 		}
1643 		if (n == 0) {
1644 			error("server: expected control record");
1645 			continue;
1646 		}
1647 
1648 		switch (*cp++) {
1649 		case C_SETCONFIG:  	/* Configuration info */
1650 		        setconfig(cp);
1651 			ack();
1652 			continue;
1653 
1654 		case C_DIRTARGET:  	/* init target file/directory name */
1655 			settarget(cp, TRUE);
1656 			continue;
1657 
1658 		case C_TARGET:  	/* init target file/directory name */
1659 			settarget(cp, FALSE);
1660 			continue;
1661 
1662 		case C_RECVREG:  	/* Transfer a regular file. */
1663 			recvit(cp, S_IFREG);
1664 			continue;
1665 
1666 		case C_RECVDIR:  	/* Transfer a directory. */
1667 			recvit(cp, S_IFDIR);
1668 			continue;
1669 
1670 		case C_RECVSYMLINK:  	/* Transfer symbolic link. */
1671 			recvit(cp, S_IFLNK);
1672 			continue;
1673 
1674 		case C_RECVHARDLINK:  	/* Transfer hard link. */
1675 			hardlink(cp);
1676 			continue;
1677 
1678 		case C_END:  		/* End of transfer */
1679 			*ptarget = CNULL;
1680 			if (catname <= 0) {
1681 				error("server: too many '%c's", C_END);
1682 				continue;
1683 			}
1684 			ptarget = sptarget[--catname];
1685 			*ptarget = CNULL;
1686 			ack();
1687 			continue;
1688 
1689 		case C_CLEAN:  		/* Clean. Cleanup a directory */
1690 			clean(cp);
1691 			continue;
1692 
1693 		case C_QUERY:  		/* Query file/directory */
1694 			query(cp);
1695 			continue;
1696 
1697 		case C_SPECIAL:  	/* Special. Execute commands */
1698 			dospecial(cp);
1699 			continue;
1700 
1701 		case C_CMDSPECIAL:  	/* Cmd Special. Execute commands */
1702 			docmdspecial();
1703 			continue;
1704 
1705 #ifdef DOCHMOD
1706 	        case C_CHMOD:  		/* Set mode */
1707 			dochmod(cp);
1708 			continue;
1709 #endif /* DOCHMOD */
1710 
1711 		case C_ERRMSG:		/* Normal error message */
1712 			if (cp && *cp)
1713 				message(MT_NERROR|MT_NOREMOTE, "%s", cp);
1714 			continue;
1715 
1716 		case C_FERRMSG:		/* Fatal error message */
1717 			if (cp && *cp)
1718 				message(MT_FERROR|MT_NOREMOTE, "%s", cp);
1719 			setjmp_ok = FALSE;
1720 			return;
1721 
1722 		default:
1723 			error("server: unknown command '%s'", cp - 1);
1724 		case CNULL:
1725 			continue;
1726 		}
1727 	}
1728 }
1729