1/*	$OpenBSD: docmd.c,v 1.36 2024/04/23 13:34:50 jsg 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
48struct subcmd	       *subcmds;		/* list of sub-commands for
49						   current cmd */
50struct namelist	       *filelist;		/* list of source files */
51time_t			lastmod;		/* Last modify time */
52
53static void closeconn(void);
54static void notify(char *, struct namelist *, time_t);
55static void checkcmd(struct cmd *);
56static void markfailed(struct cmd *, struct cmd *);
57static int remotecmd(char *, char *, char *, char *);
58static int makeconn(char *);
59static void doarrow(struct cmd *, char **);
60static void rcmptime(struct stat *, struct subcmd *, char **);
61static void cmptime(char *, struct subcmd *, char **);
62static void dodcolon(struct cmd *, char **);
63static void docmdhost(struct cmd *, char **);
64static void docmd(struct cmd *, int, char **);
65
66/*
67 * Signal end of connection.
68 */
69static void
70closeconn(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 */
91static void
92notify(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 */
193static void
194checkcmd(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 */
216void
217markassigned(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 */
232static void
233markfailed(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
252static int
253remotecmd(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 */
281static int
282makeconn(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 */
413static void
414doarrow(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
587int
588okname(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
608static void
609rcmptime(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 */
648static void
649cmptime(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 */
715static void
716dodcolon(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 */
797int
798except(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 */
848static void
849docmdhost(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 */
902static void
903docmd(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 */
944void
945docmds(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