xref: /openbsd/usr.bin/rdist/docmd.c (revision 4bdff4be)
1 /*	$OpenBSD: docmd.c,v 1.35 2022/01/28 06:18:41 guenther 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. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #include <ctype.h>
33 #include <dirent.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <paths.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 
41 #include "client.h"
42 #include "gram.h"
43 
44 /*
45  * Functions for rdist that do command (cmd) related activities.
46  */
47 
48 struct subcmd	       *subcmds;		/* list of sub-commands for
49 						   current cmd */
50 struct namelist	       *filelist;		/* list of source files */
51 time_t			lastmod;		/* Last modify time */
52 
53 static void closeconn(void);
54 static void notify(char *, struct namelist *, time_t);
55 static void checkcmd(struct cmd *);
56 static void markfailed(struct cmd *, struct cmd *);
57 static int remotecmd(char *, char *, char *, char *);
58 static int makeconn(char *);
59 static void doarrow(struct cmd *, char **);
60 static void rcmptime(struct stat *, struct subcmd *, char **);
61 static void cmptime(char *, struct subcmd *, char **);
62 static void dodcolon(struct cmd *, char **);
63 static void docmdhost(struct cmd *, char **);
64 static void docmd(struct cmd *, int, char **);
65 
66 /*
67  * Signal end of connection.
68  */
69 static void
70 closeconn(void)
71 {
72 	debugmsg(DM_CALL, "closeconn() called\n");
73 
74 	if (rem_w >= 0) {
75 		/* We don't care if the connection is still good or not */
76 		signal(SIGPIPE, SIG_IGN);
77 
78 		(void) sendcmd(C_FERRMSG, NULL);
79 		(void) close(rem_w);
80 		(void) close(rem_r); /* This can't hurt */
81 		rem_w = -1;
82 		rem_r = -1;
83 	}
84 }
85 
86 /*
87  * Notify the list of people the changes that were made.
88  * rhost == NULL if we are mailing a list of changes compared to at time
89  * stamp file.
90  */
91 static void
92 notify(char *rhost, struct namelist *to, time_t lmod)
93 {
94 	int fd;
95 	ssize_t len;
96 	FILE *pf;
97 	struct stat stb;
98 	static char buf[BUFSIZ];
99 	char *file, *user;
100 
101 	if (IS_ON(options, DO_VERIFY) || to == NULL)
102 		return;
103 
104 	if ((file = getnotifyfile()) == NULL)
105 		return;
106 
107 	if (!IS_ON(options, DO_QUIET)) {
108 		message(MT_INFO, "notify %s%s %s",
109 			(rhost) ? "@" : "",
110 			(rhost) ? rhost : "", getnlstr(to));
111 	}
112 
113 	if (nflag)
114 		return;
115 
116 	debugmsg(DM_MISC, "notify() temp file = '%s'", file);
117 
118 	if ((fd = open(file, O_RDONLY)) == -1) {
119 		error("%s: open for reading failed: %s", file, SYSERR);
120 		return;
121 	}
122 	if (fstat(fd, &stb) == -1) {
123 		error("%s: fstat failed: %s", file, SYSERR);
124 		(void) close(fd);
125 		return;
126 	}
127 	if (stb.st_size == 0) {
128 		(void) close(fd);
129 		return;
130 	}
131 	/*
132 	 * Create a pipe to mailing program.
133 	 * Set IFS to avoid possible security problem with users
134 	 * setting "IFS=/".
135 	 */
136 	(void) snprintf(buf, sizeof(buf), "IFS=\" \t\"; export IFS; %s -oi -t",
137 		       _PATH_SENDMAIL);
138 	pf = popen(buf, "w");
139 	if (pf == NULL) {
140 		error("notify: \"%s\" failed\n", _PATH_SENDMAIL);
141 		(void) unlink(file);
142 		(void) close(fd);
143 		return;
144 	}
145 	/*
146 	 * Output the proper header information.
147 	 */
148 	(void) fprintf(pf, "Auto-Submitted: auto-generated\n");
149 	(void) fprintf(pf, "From: rdist (Remote distribution program)\n");
150 	(void) fprintf(pf, "To:");
151 	if (!any('@', to->n_name) && rhost != NULL)
152 		(void) fprintf(pf, " %s@%s", to->n_name, rhost);
153 	else
154 		(void) fprintf(pf, " %s", to->n_name);
155 	to = to->n_next;
156 	while (to != NULL) {
157 		if (!any('@', to->n_name) && rhost != NULL)
158 			(void) fprintf(pf, ", %s@%s", to->n_name, rhost);
159 		else
160 			(void) fprintf(pf, ", %s", to->n_name);
161 		to = to->n_next;
162 	}
163 	(void) putc('\n', pf);
164 
165 	if ((user = getlogin()) == NULL)
166 		user = locuser;
167 
168 	if (rhost != NULL)
169 		(void) fprintf(pf,
170 			 "Subject: files updated by %s from %s to %s\n",
171 			 locuser, host, rhost);
172 	else
173 		(void) fprintf(pf, "Subject: files updated after %s\n",
174 			       ctime(&lmod));
175 	(void) putc('\n', pf);
176 	(void) putc('\n', pf);
177 	(void) fprintf(pf, "Options: %s\n\n", getondistoptlist(options));
178 
179 	while ((len = read(fd, buf, sizeof(buf))) > 0)
180 		(void) fwrite(buf, 1, len, pf);
181 
182 	(void) pclose(pf);
183 	(void) close(fd);
184 	(void) unlink(file);
185 }
186 
187 /*
188  * XXX Hack for NFS.  If a hostname from the distfile
189  * ends with a '+', then the normal restriction of
190  * skipping files that are on an NFS filesystem is
191  * bypassed.  We always strip '+' to be consistent.
192  */
193 static void
194 checkcmd(struct cmd *cmd)
195 {
196 	int l;
197 
198 	if (!cmd || !(cmd->c_name)) {
199 		debugmsg(DM_MISC, "checkcmd() NULL cmd parameter");
200 		return;
201 	}
202 
203 	l = strlen(cmd->c_name);
204 	if (l <= 0)
205 		return;
206 	if (cmd->c_name[l-1] == '+') {
207 		cmd->c_flags |= CMD_NOCHKNFS;
208 		cmd->c_name[l-1] = CNULL;
209 	}
210 }
211 
212 /*
213  * Mark all other entries for this command (cmd)
214  * as assigned.
215  */
216 void
217 markassigned(struct cmd *cmd, struct cmd *cmdlist)
218 {
219 	struct cmd *pcmd;
220 
221 	for (pcmd = cmdlist; pcmd; pcmd = pcmd->c_next) {
222 		checkcmd(pcmd);
223 		if (pcmd->c_type == cmd->c_type &&
224 		    strcmp(pcmd->c_name, cmd->c_name)==0)
225 			pcmd->c_flags |= CMD_ASSIGNED;
226 	}
227 }
228 
229 /*
230  * Mark the command "cmd" as failed for all commands in list cmdlist.
231  */
232 static void
233 markfailed(struct cmd *cmd, struct cmd *cmdlist)
234 {
235 	struct cmd *pc;
236 
237 	if (!cmd) {
238 		debugmsg(DM_MISC, "markfailed() NULL cmd parameter");
239 		return;
240 	}
241 
242 	checkcmd(cmd);
243 	cmd->c_flags |= CMD_CONNFAILED;
244 	for (pc = cmdlist; pc; pc = pc->c_next) {
245 		checkcmd(pc);
246 		if (pc->c_type == cmd->c_type &&
247 		    strcmp(pc->c_name, cmd->c_name)==0)
248 			pc->c_flags |= CMD_CONNFAILED;
249 	}
250 }
251 
252 static int
253 remotecmd(char *rhost, char *luser, char *ruser, char *cmd)
254 {
255 	int desc;
256 
257 	debugmsg(DM_MISC, "local user = %s remote user = %s\n", luser, ruser);
258 	debugmsg(DM_MISC, "Remote command = '%s'\n", cmd);
259 
260 	(void) fflush(stdout);
261 	(void) fflush(stderr);
262 	(void) signal(SIGALRM, sighandler);
263 	(void) alarm(RTIMEOUT);
264 
265 	debugmsg(DM_MISC, "Remote shell command = '%s'\n",
266 	    path_remsh ? path_remsh : "default");
267 	(void) signal(SIGPIPE, SIG_IGN);
268 	desc = rcmdsh(&rhost, -1, luser, ruser, cmd, path_remsh);
269 	if (desc > 0)
270 		(void) signal(SIGPIPE, sighandler);
271 
272 	(void) alarm(0);
273 
274 	return(desc);
275 }
276 
277 /*
278  * Create a connection to the rdist server on the machine rhost.
279  * Return 0 if the connection fails or 1 if it succeeds.
280  */
281 static int
282 makeconn(char *rhost)
283 {
284 	char *ruser, *cp;
285 	static char *cur_host = NULL;
286 	char tuser[BUFSIZ], buf[BUFSIZ];
287 	u_char respbuff[BUFSIZ];
288 	int n;
289 
290 	debugmsg(DM_CALL, "makeconn(%s)", rhost);
291 
292 	/*
293 	 * See if we're already connected to this host
294 	 */
295 	if (cur_host != NULL && rem_w >= 0) {
296 		if (strcmp(cur_host, rhost) == 0)
297 			return(1);
298 		closeconn();
299 	}
300 
301 	/*
302 	 * Determine remote user and current host names
303 	 */
304 	cur_host = rhost;
305 	cp = strchr(rhost, '@');
306 
307 	if (cp != NULL) {
308 		char c = *cp;
309 
310 		*cp = CNULL;
311 		(void) strlcpy((char *)tuser, rhost, sizeof(tuser));
312 		*cp = c;
313 		rhost = cp + 1;
314 		ruser = tuser;
315 		if (*ruser == CNULL)
316 			ruser = locuser;
317 		else if (!okname(ruser))
318 			return(0);
319 	} else
320 		ruser = locuser;
321 
322 	if (!IS_ON(options, DO_QUIET))
323 		message(MT_VERBOSE, "updating host %s", rhost);
324 
325 	(void) snprintf(buf, sizeof(buf), "%.*s -S",
326 			(int)(sizeof(buf)-5), path_rdistd);
327 
328 	if ((rem_r = rem_w = remotecmd(rhost, locuser, ruser, buf)) < 0)
329 		return(0);
330 
331 	/*
332 	 * First thing received should be S_VERSION
333 	 */
334 	respbuff[0] = '\0';
335 	n = remline(respbuff, sizeof(respbuff), TRUE);
336 	if (n <= 0 || respbuff[0] != S_VERSION) {
337 		if (n > 0)
338 		    error("Unexpected input from server: \"%s\".", respbuff);
339 		else
340 		    error("No input from server.");
341 		closeconn();
342 		return(0);
343 	}
344 
345 	/*
346 	 * For future compatibility we check to see if the server
347 	 * sent its version number to us.  If it did, we use it,
348 	 * otherwise, we send our version number to the server and let
349 	 * it decide if it can handle our protocol version.
350 	 */
351 	if (respbuff[1] == CNULL) {
352 		/*
353 		 * The server wants us to send it our version number
354 		 */
355 		(void) sendcmd(S_VERSION, "%d", VERSION);
356 		if (response() < 0)
357 			return(0);
358 	} else {
359 		/*
360 		 * The server sent its version number to us
361 		 */
362 		int proto_version = atoi(&respbuff[1]);
363 		if (proto_version != VERSION) {
364 			fatalerr(
365 		  "Server version (%d) is not the same as local version (%d).",
366 			      proto_version, VERSION);
367 			return(0);
368 		}
369 	}
370 
371 	/*
372 	 * Send config commands
373 	 */
374 	if (host[0]) {
375 		(void) sendcmd(C_SETCONFIG, "%c%s", SC_HOSTNAME, host);
376 		if (response() < 0)
377 			return(0);
378 	}
379 	if (min_freespace) {
380 		(void) sendcmd(C_SETCONFIG, "%c%lld", SC_FREESPACE,
381 			       min_freespace);
382 		if (response() < 0)
383 			return(0);
384 	}
385 	if (min_freefiles) {
386 		(void) sendcmd(C_SETCONFIG, "%c%lld", SC_FREEFILES,
387 			       min_freefiles);
388 		if (response() < 0)
389 			return(0);
390 	}
391 	if (remotemsglist) {
392 		(void) sendcmd(C_SETCONFIG, "%c%s", SC_LOGGING, remotemsglist);
393 		if (response() < 0)
394 			return(0);
395 	}
396 	if (strcmp(defowner, "bin") != 0) {
397 		(void) sendcmd(C_SETCONFIG, "%c%s", SC_DEFOWNER, defowner);
398 		if (response() < 0)
399 			return(0);
400 	}
401 	if (strcmp(defgroup, "bin") != 0) {
402 		(void) sendcmd(C_SETCONFIG, "%c%s", SC_DEFGROUP, defgroup);
403 		if (response() < 0)
404 			return(0);
405 	}
406 
407 	return(1);
408 }
409 
410 /*
411  * Process commands for sending files to other machines.
412  */
413 static void
414 doarrow(struct cmd *cmd, char **filev)
415 {
416 	struct namelist *f;
417 	struct subcmd *sc;
418 	char **cpp;
419 	int n, ddir, destdir;
420 	volatile opt_t opts = options;
421 	struct namelist *files;
422 	struct subcmd *sbcmds;
423 	char *rhost;
424 	volatile int didupdate = 0;
425 
426         if (setjmp_ok) {
427 		error("reentrant call to doarrow");
428 		abort();
429 	}
430 
431 	if (!cmd) {
432 		debugmsg(DM_MISC, "doarrow() NULL cmd parameter");
433 		return;
434 	}
435 
436 	files = cmd->c_files;
437 	sbcmds = cmd->c_cmds;
438 	rhost = cmd->c_name;
439 
440 	if (files == NULL) {
441 		error("No files to be updated on %s for target \"%s\"",
442 		      rhost, cmd->c_label);
443 		return;
444 	}
445 
446 	debugmsg(DM_CALL, "doarrow(%p, %s, %p) start",
447 		 files, A(rhost), sbcmds);
448 
449 	if (nflag)
450 		(void) printf("updating host %s\n", rhost);
451 	else {
452 		if (cmd->c_flags & CMD_CONNFAILED) {
453 			debugmsg(DM_MISC,
454 				 "makeconn %s failed before; skipping\n",
455 				 rhost);
456 			return;
457 		}
458 
459 		if (setjmp(finish_jmpbuf)) {
460 			setjmp_ok = FALSE;
461 			debugmsg(DM_MISC, "setjmp to finish_jmpbuf");
462 			markfailed(cmd, cmds);
463 			return;
464 		}
465 		setjmp_ok = TRUE;
466 
467 		if (!makeconn(rhost)) {
468 			setjmp_ok = FALSE;
469 			markfailed(cmd, cmds);
470 			return;
471 		}
472 	}
473 
474 	subcmds = sbcmds;
475 	filelist = files;
476 
477 	n = 0;
478 	for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
479 		if (sc->sc_type != INSTALL)
480 			continue;
481 		n++;
482 	/*
483 	 * destination is a directory if one of the following is true:
484 	 * a) more than one name specified on left side of -> directive
485 	 * b) basename of destination in "install" directive is "."
486 	 *    (e.g. install /tmp/.;)
487 	 * c) name on left side of -> directive is a directory on local system.
488  	 *
489  	 * We need 2 destdir flags (destdir and ddir) because single directory
490  	 * source is handled differently.  In this case, ddir is 0 (which
491  	 * tells install() not to send DIRTARGET directive to remote rdistd)
492  	 * and destdir is 1 (which tells remfilename() how to build the FILE
493  	 * variables correctly).  In every other case, destdir and ddir will
494  	 * have the same value.
495 	 */
496   	ddir = files->n_next != NULL;	/* destination is a directory */
497 	if (!ddir) {
498 		struct stat s;
499  		int isadir = 0;
500 
501 		if (lstat(files->n_name, &s) == 0)
502  			isadir = S_ISDIR(s.st_mode);
503  		if (!isadir && sc->sc_name && *sc->sc_name)
504  			ddir = !strcmp(xbasename(sc->sc_name),".");
505  		destdir = isadir | ddir;
506  	} else
507  		destdir = ddir;
508 
509 	debugmsg(DM_MISC,
510 		 "Debug files->n_next= %p, destdir=%d, ddir=%d",
511 		 files->n_next, destdir, ddir);
512 
513 	if (!sc->sc_name || !*sc->sc_name) {
514 		destdir = 0;
515 		ddir = 0;
516 	}
517 
518 	debugmsg(DM_MISC,
519 		 "Debug sc->sc_name=%p, destdir=%d, ddir=%d",
520 		 sc->sc_name, destdir, ddir);
521 
522 	for (f = files; f != NULL; f = f->n_next) {
523 		if (filev) {
524 			for (cpp = filev; *cpp; cpp++)
525 				if (strcmp(f->n_name, *cpp) == 0)
526 					goto found;
527 			continue;
528 		}
529 	found:
530 		if (install(f->n_name, sc->sc_name, ddir, destdir,
531 				sc->sc_options) > 0)
532 			++didupdate;
533 		opts = sc->sc_options;
534 	}
535 
536 	} /* end loop for each INSTALL command */
537 
538 	/* if no INSTALL commands present, do default install */
539 	if (!n) {
540 		for (f = files; f != NULL; f = f->n_next) {
541 			if (filev) {
542 				for (cpp = filev; *cpp; cpp++)
543 					if (strcmp(f->n_name, *cpp) == 0)
544 						goto found2;
545 				continue;
546 			}
547 		found2:
548 			/* ddir & destdir set to zero for default install */
549 			if (install(f->n_name, NULL, 0, 0, options) > 0)
550 				++didupdate;
551 		}
552 	}
553 
554 	/*
555 	 * Run any commands for the entire cmd
556 	 */
557 	if (didupdate > 0) {
558 		runcmdspecial(cmd, opts);
559 		didupdate = 0;
560 	}
561 
562 	if (!nflag)
563 		(void) signal(SIGPIPE, cleanup);
564 
565 	for (sc = sbcmds; sc != NULL; sc = sc->sc_next)
566 		if (sc->sc_type == NOTIFY)
567 			notify(rhost, sc->sc_args, (time_t) 0);
568 
569 	if (!nflag) {
570 		struct linkbuf *nextl, *l;
571 
572 		for (l = ihead; l != NULL; freelinkinfo(l), l = nextl) {
573 			nextl = l->nextp;
574 			if (contimedout || IS_ON(opts, DO_IGNLNKS) ||
575 			    l->count == 0)
576 				continue;
577 			message(MT_WARNING, "%s: Warning: %d %s link%s",
578 				l->pathname, abs(l->count),
579 				(l->count > 0) ? "missing" : "extra",
580 				(l->count == 1) ? "" : "s");
581 		}
582 		ihead = NULL;
583 	}
584 	setjmp_ok = FALSE;
585 }
586 
587 int
588 okname(char *name)
589 {
590 	char *cp = name;
591 	int c, isbad;
592 
593 	for (isbad = FALSE; *cp && !isbad; ++cp) {
594 		c = *cp;
595 		if (c & 0200)
596 			isbad = TRUE;
597 		if (!isalpha(c) && !isdigit(c) && c != '_' && c != '-')
598 			isbad = TRUE;
599 	}
600 
601 	if (isbad) {
602 		error("Invalid user name \"%s\"\n", name);
603 		return(0);
604 	}
605 	return(1);
606 }
607 
608 static void
609 rcmptime(struct stat *st, struct subcmd *sbcmds, char **env)
610 {
611 	DIR *d;
612 	struct dirent *dp;
613 	char *cp;
614 	char *optarget;
615 	int len;
616 
617 	debugmsg(DM_CALL, "rcmptime(%p) start", st);
618 
619 	if ((d = opendir((char *) target)) == NULL) {
620 		error("%s: open directory failed: %s", target, SYSERR);
621 		return;
622 	}
623 	optarget = ptarget;
624 	len = ptarget - target;
625 	while ((dp = readdir(d)) != NULL) {
626 		if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
627 			continue;
628 		if (len + 1 + (int)strlen(dp->d_name) >= BUFSIZ - 1) {
629 			error("%s/%s: Name too long\n", target, dp->d_name);
630 			continue;
631 		}
632 		ptarget = optarget;
633 		*ptarget++ = '/';
634 		cp = dp->d_name;
635 		while ((*ptarget++ = *cp++) != '\0')
636 			;
637 		ptarget--;
638 		cmptime(target, sbcmds, env);
639 	}
640 	(void) closedir((DIR *) d);
641 	ptarget = optarget;
642 	*ptarget = '\0';
643 }
644 
645 /*
646  * Compare the mtime of file to the list of time stamps.
647  */
648 static void
649 cmptime(char *name, struct subcmd *sbcmds, char **env)
650 {
651 	struct subcmd *sc;
652 	struct stat stb;
653 
654 	debugmsg(DM_CALL, "cmptime(%s)", name);
655 
656 	if (except(name))
657 		return;
658 
659 	if (nflag) {
660 		(void) printf("comparing dates: %s\n", name);
661 		return;
662 	}
663 
664 	/*
665 	 * first time cmptime() is called?
666 	 */
667 	if (ptarget == NULL) {
668 		if (exptilde(target, name, sizeof(target)) == NULL)
669 			return;
670 		ptarget = name = target;
671 		while (*ptarget)
672 			ptarget++;
673 	}
674 	if (access(name, R_OK) == -1 || stat(name, &stb) == -1) {
675 		error("%s: cannot access file: %s", name, SYSERR);
676 		return;
677 	}
678 
679 	if (S_ISDIR(stb.st_mode)) {
680 		rcmptime(&stb, sbcmds, env);
681 		return;
682 	} else if (!S_ISREG(stb.st_mode)) {
683 		error("%s: not a plain file", name);
684 		return;
685 	}
686 
687 	if (stb.st_mtime > lastmod) {
688 		message(MT_INFO, "%s: file is newer", name);
689 		for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
690 			char buf[BUFSIZ];
691 			if (sc->sc_type != SPECIAL)
692 				continue;
693 			if (sc->sc_args != NULL && !inlist(sc->sc_args, name))
694 				continue;
695 			(void) snprintf(buf, sizeof(buf), "%s=%s;%s",
696 				        E_LOCFILE, name, sc->sc_name);
697 			message(MT_CHANGE, "special \"%s\"", buf);
698 			if (*env) {
699 				size_t len = strlen(*env) + strlen(name) + 2;
700 				*env = xrealloc(*env, len);
701 				(void) strlcat(*env, name, len);
702 				(void) strlcat(*env, ":", len);
703 			}
704 			if (IS_ON(options, DO_VERIFY))
705 				continue;
706 
707 			runcommand(buf);
708 		}
709 	}
710 }
711 
712 /*
713  * Process commands for comparing files to time stamp files.
714  */
715 static void
716 dodcolon(struct cmd *cmd, char **filev)
717 {
718 	struct subcmd *sc;
719 	struct namelist *f;
720 	char *cp, **cpp;
721 	struct stat stb;
722 	struct namelist *files = cmd->c_files;
723 	struct subcmd *sbcmds = cmd->c_cmds;
724 	char *env, *stamp = cmd->c_name;
725 
726 	debugmsg(DM_CALL, "dodcolon()");
727 
728 	if (files == NULL) {
729 		error("No files to be updated for target \"%s\"",
730 		      cmd->c_label);
731 		return;
732 	}
733 	if (stat(stamp, &stb) == -1) {
734 		error("%s: stat failed: %s", stamp, SYSERR);
735 		return;
736 	}
737 
738 	debugmsg(DM_MISC, "%s: mtime %lld\n", stamp, (long long)stb.st_mtime);
739 
740 	env = NULL;
741 	for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
742 		if (sc->sc_type == CMDSPECIAL) {
743 			env = xmalloc(sizeof(E_FILES) + 3);
744 			(void) snprintf(env, sizeof(E_FILES) + 3,
745 					"%s='", E_FILES);
746 			break;
747 		}
748 	}
749 
750 	subcmds = sbcmds;
751 	filelist = files;
752 
753 	lastmod = stb.st_mtime;
754 	if (!nflag && !IS_ON(options, DO_VERIFY))
755 		/*
756 		 * Set atime and mtime to current time
757 		 */
758 		(void) setfiletime(stamp, (time_t) 0, (time_t) 0);
759 
760 	for (f = files; f != NULL; f = f->n_next) {
761 		if (filev) {
762 			for (cpp = filev; *cpp; cpp++)
763 				if (strcmp(f->n_name, *cpp) == 0)
764 					goto found;
765 			continue;
766 		}
767 	found:
768 		ptarget = NULL;
769 		cmptime(f->n_name, sbcmds, &env);
770 	}
771 
772 	for (sc = sbcmds; sc != NULL; sc = sc->sc_next) {
773 		if (sc->sc_type == NOTIFY)
774 			notify(NULL, sc->sc_args, (time_t)lastmod);
775 		else if (sc->sc_type == CMDSPECIAL && env) {
776 			size_t len = strlen(env);
777 			if (env[len - 1] == ':')
778 				env[--len] = CNULL;
779 			len += 2 + strlen(sc->sc_name) + 1;
780 			env = xrealloc(env, len);
781 			(void) strlcat(env, "';", len);
782 			(void) strlcat(env, sc->sc_name, len);
783 			message(MT_CHANGE, "cmdspecial \"%s\"", env);
784 			if (!nflag && IS_OFF(options, DO_VERIFY))
785 				runcommand(env);
786 			(void) free(env);
787 			env = NULL;	/* so cmdspecial is only called once */
788 		}
789 	}
790 	if (!nflag && !IS_ON(options, DO_VERIFY) && (cp = getnotifyfile()))
791 		(void) unlink(cp);
792 }
793 
794 /*
795  * Return TRUE if file is in the exception list.
796  */
797 int
798 except(char *file)
799 {
800 	struct	subcmd *sc;
801 	struct	namelist *nl;
802 
803 	debugmsg(DM_CALL, "except(%s)", file);
804 
805 	for (sc = subcmds; sc != NULL; sc = sc->sc_next) {
806 		if (sc->sc_type == EXCEPT) {
807 			for (nl = sc->sc_args; nl != NULL; nl = nl->n_next)
808 				if (strcmp(file, nl->n_name) == 0)
809 					return(1);
810   			continue;
811 		}
812 		if (sc->sc_type == PATTERN) {
813 			for (nl = sc->sc_args; nl != NULL; nl = nl->n_next) {
814 				char ebuf[BUFSIZ];
815 				int ecode = 0;
816 
817 				/* allocate and compile n_regex as needed */
818 				if (nl->n_regex == NULL) {
819 					nl->n_regex = xmalloc(sizeof(regex_t));
820 					ecode = regcomp(nl->n_regex, nl->n_name,
821 							REG_NOSUB);
822 				}
823 				if (ecode == 0) {
824 					ecode = regexec(nl->n_regex, file, 0,
825 					    NULL, 0);
826 				}
827 				switch (ecode) {
828 				case REG_NOMATCH:
829 					break;
830 				case 0:
831 					return(1);	/* match! */
832 				default:
833 					regerror(ecode, nl->n_regex, ebuf,
834 						 sizeof(ebuf));
835 					error("Regex error \"%s\" for \"%s\".",
836 					      ebuf, nl->n_name);
837 					return(0);
838 				}
839 			}
840 		}
841 	}
842 	return(0);
843 }
844 
845 /*
846  * Do a specific command for a specific host
847  */
848 static void
849 docmdhost(struct cmd *cmd, char **filev)
850 {
851 	checkcmd(cmd);
852 
853 	/*
854 	 * If we're multi-threaded and we're the parent, spawn a
855 	 * new child process.
856 	 */
857 	if (do_fork && !amchild) {
858 		pid_t pid;
859 
860 		/*
861 		 * If we're at maxchildren, wait for number of active
862 		 * children to fall below max number of children.
863 		 */
864 		while (activechildren >= maxchildren)
865 			waitup();
866 
867 		pid = spawn(cmd, cmds);
868 		if (pid == 0)
869 			/* Child */
870 			amchild = 1;
871 		else
872 			/* Parent */
873 			return;
874 	}
875 
876 	/*
877 	 * Disable NFS checks
878 	 */
879 	if (cmd->c_flags & CMD_NOCHKNFS)
880 		FLAG_OFF(options, DO_CHKNFS);
881 
882 	if (!nflag) {
883 		currenthost = (cmd->c_name) ? cmd->c_name : "<unknown>";
884 		setproctitle("update %s", currenthost);
885 	}
886 
887 	switch (cmd->c_type) {
888 	case ARROW:
889 		doarrow(cmd, filev);
890 		break;
891 	case DCOLON:
892 		dodcolon(cmd, filev);
893 		break;
894 	default:
895 		fatalerr("illegal command type %d", cmd->c_type);
896 	}
897 }
898 
899 /*
900  * Do a specific command (cmd)
901  */
902 static void
903 docmd(struct cmd *cmd, int argc, char **argv)
904 {
905 	struct namelist *f;
906 	int i;
907 
908 	if (argc) {
909 		for (i = 0; i < argc; i++) {
910 			if (cmd->c_label != NULL &&
911 			    strcmp(cmd->c_label, argv[i]) == 0) {
912 				docmdhost(cmd, NULL);
913 				return;
914 			}
915 			for (f = cmd->c_files; f != NULL; f = f->n_next)
916 				if (strcmp(f->n_name, argv[i]) == 0) {
917 					docmdhost(cmd, &argv[i]);
918 					return;
919 				}
920 		}
921 	} else
922 		docmdhost(cmd, NULL);
923 }
924 
925 /*
926  *
927  * Multiple hosts are updated at once via a "ring" of at most
928  * maxchildren rdist processes.  The parent rdist fork()'s a child
929  * for a given host.  That child will update the given target files
930  * and then continue scanning through the remaining targets looking
931  * for more work for a given host.  Meanwhile, the parent gets the
932  * next target command and makes sure that it hasn't encountered
933  * that host yet since the children are responsible for everything
934  * for that host.  If no children have done this host, then check
935  * to see if the number of active proc's is less than maxchildren.
936  * If so, then spawn a new child for that host.  Otherwise, wait
937  * for a child to finish.
938  *
939  */
940 
941 /*
942  * Do the commands in cmds (initialized by yyparse).
943  */
944 void
945 docmds(struct namelist *hostlist, int argc, char **argv)
946 {
947 	struct cmd *c;
948 	char *cp;
949 	int i;
950 
951 	(void) signal(SIGHUP, sighandler);
952 	(void) signal(SIGINT, sighandler);
953 	(void) signal(SIGQUIT, sighandler);
954 	(void) signal(SIGTERM, sighandler);
955 
956 	if (!nflag)
957 		setvbuf(stdout, NULL, _IOLBF, 0);
958 
959 	/*
960 	 * Print errors for any command line targets we didn't find.
961 	 * If any errors are found, return to main() which will then exit.
962 	 */
963 	for (i = 0; i < argc; i++) {
964 		int found;
965 
966 		for (found = FALSE, c = cmds; c != NULL; c = c->c_next) {
967 			if (c->c_label && argv[i] &&
968 			    strcmp(c->c_label, argv[i]) == 0) {
969 				found = TRUE;
970 				break;
971 			}
972 		}
973 		if (!found)
974 			error("Label \"%s\" is not defined in the distfile.",
975 			      argv[i]);
976 	}
977 	if (nerrs)
978 		return;
979 
980 	/*
981 	 * Main command loop.  Loop through all the commands.
982 	 */
983 	for (c = cmds; c != NULL; c = c->c_next) {
984 		checkcmd(c);
985 		if (do_fork) {
986 			/*
987 			 * Let the children take care of their assigned host
988 			 */
989 			if (amchild) {
990 				if (strcmp(c->c_name, currenthost) != 0)
991 					continue;
992 			} else if (c->c_flags & CMD_ASSIGNED) {
993 				/* This cmd has been previously assigned */
994 				debugmsg(DM_MISC, "prev assigned: %s\n",
995 					 c->c_name);
996 				continue;
997 			}
998 		}
999 
1000 		if (hostlist) {
1001 			/* Do specific hosts as specified on command line */
1002 			struct namelist *nlptr;
1003 
1004 			for (nlptr = hostlist; nlptr; nlptr = nlptr->n_next)
1005 				/*
1006 				 * Try an exact match and then a match
1007 				 * without '@' (if present).
1008 				 */
1009 				if ((strcmp(c->c_name, nlptr->n_name) == 0) ||
1010 				    ((cp = strchr(c->c_name, '@')) &&
1011 				     strcmp(++cp, nlptr->n_name) == 0))
1012 					docmd(c, argc, argv);
1013 			continue;
1014 		} else
1015 			/* Do all of the command */
1016 			docmd(c, argc, argv);
1017 	}
1018 
1019 	if (do_fork) {
1020 		/*
1021 		 * We're multi-threaded, so do appropriate shutdown
1022 		 * actions based on whether we're the parent or a child.
1023 		 */
1024 		if (amchild) {
1025 			if (!IS_ON(options, DO_QUIET))
1026 				message(MT_VERBOSE, "updating of %s finished",
1027 					currenthost);
1028 			closeconn();
1029 			cleanup(0);
1030 			exit(nerrs);
1031 		}
1032 
1033 		/*
1034 		 * Wait for all remaining active children to finish
1035 		 */
1036 		while (activechildren > 0) {
1037 			debugmsg(DM_MISC,
1038 				 "Waiting for %d children to finish.\n",
1039 				 activechildren);
1040 			waitup();
1041 		}
1042 	} else if (!nflag) {
1043 		/*
1044 		 * We're single-threaded so close down current connection
1045 		 */
1046 		closeconn();
1047 		cleanup(0);
1048 	}
1049 }
1050