1/*	$OpenBSD: rdist.c,v 1.33 2022/12/04 23:50:49 cheloha 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 <errno.h>
34#include <limits.h>
35#include <paths.h>
36#include <stdlib.h>
37#include <string.h>
38#include <unistd.h>
39
40#include "client.h"
41#include "gram.h"
42
43
44/*
45 * Remote distribution program.
46 */
47
48int     	maxchildren = MAXCHILDREN;	/* Max no of concurrent PIDs */
49int		nflag = 0;			/* Say without doing */
50int64_t		min_freespace = 0;		/* Min filesys free space */
51int64_t		min_freefiles = 0;		/* Min filesys free # files */
52FILE   	       *fin = NULL;			/* Input file pointer */
53char		localmsglist[] = "stdout=all:notify=all:syslog=nerror,ferror";
54char   	       *remotemsglist = NULL;
55char		optchars[] = "A:a:bcd:DFf:hil:L:M:m:NnOo:p:P:qRrst:Vvwxy";
56char	       *path_rdistd = _PATH_RDISTD;
57char	       *path_remsh = NULL;
58
59static void addhostlist(char *, struct namelist **);
60static void usage(void);
61int main(int, char **, char **);
62
63/*
64 * Add a hostname to the host list
65 */
66static void
67addhostlist(char *name, struct namelist **hostlist)
68{
69	struct namelist *ptr, *new;
70
71	if (!name || !hostlist)
72		return;
73
74	new = xmalloc(sizeof *new);
75	new->n_name = xstrdup(name);
76	new->n_regex = NULL;
77	new->n_next = NULL;
78
79	if (*hostlist) {
80		for (ptr = *hostlist; ptr && ptr->n_next; ptr = ptr->n_next)
81			;
82		ptr->n_next = new;
83	} else
84		*hostlist = new;
85}
86
87int
88main(int argc, char **argv, char **envp)
89{
90	extern char *__progname;
91	struct namelist *hostlist = NULL;
92	char *distfile = NULL;
93	char *cp;
94	int cmdargs = 0;
95	int c;
96	const char *errstr;
97
98	progname = __progname;
99
100	if ((cp = msgparseopts(localmsglist, TRUE)) != NULL) {
101		error("Bad builtin log option (%s): %s.",
102		      localmsglist, cp);
103		usage();
104	}
105
106	if ((cp = getenv("RDIST_OPTIONS")) != NULL)
107		if (parsedistopts(cp, &options, TRUE)) {
108			error("Bad dist option environment string \"%s\".",
109			      cp);
110			exit(1);
111		}
112
113	if (init(argc, argv, envp) < 0)
114		exit(1);
115
116	/*
117	 * Perform check to make sure we are not incorrectly installed
118	 * setuid to root or anybody else.
119	 */
120	if (getuid() != geteuid())
121		fatalerr("This version of rdist should not be installed setuid.");
122
123	while ((c = getopt(argc, argv, optchars)) != -1)
124		switch (c) {
125		case 'l':
126			if ((cp = msgparseopts(optarg, TRUE)) != NULL) {
127				error("Bad log option \"%s\": %s.", optarg,cp);
128				usage();
129			}
130			break;
131
132		case 'L':
133			remotemsglist = xstrdup(optarg);
134			break;
135
136		case 'A':
137		case 'a':
138		case 'M':
139		case 't':
140			if (!isdigit((unsigned char)*optarg)) {
141				error("\"%s\" is not a number.", optarg);
142				usage();
143			}
144			if (c == 'a') {
145				min_freespace = (int64_t)strtonum(optarg,
146					0, LLONG_MAX, &errstr);
147				if (errstr)
148					fatalerr("Minimum free space is %s: "
149						 "'%s'", errstr, optarg);
150			}
151			else if (c == 'A') {
152				min_freefiles = (int64_t)strtonum(optarg,
153					0, LLONG_MAX, &errstr);
154				if (errstr)
155					fatalerr("Minimum free files is %s: "
156						 "'%s'", errstr, optarg);
157			}
158			else if (c == 'M')
159				maxchildren = atoi(optarg);
160			else if (c == 't')
161				rtimeout = atoi(optarg);
162			break;
163
164		case 'F':
165			do_fork = FALSE;
166			break;
167
168		case 'f':
169			distfile = xstrdup(optarg);
170			if (distfile[0] == '-' && distfile[1] == CNULL)
171				fin = stdin;
172			break;
173
174		case 'm':
175			addhostlist(optarg, &hostlist);
176			break;
177
178		case 'd':
179			define(optarg);
180			break;
181
182		case 'D':
183			debug = DM_ALL;
184			if ((cp = msgparseopts("stdout=all,debug",
185			    TRUE)) != NULL) {
186				error("Enable debug messages failed: %s.", cp);
187				usage();
188			}
189			break;
190
191		case 'c':
192			cmdargs = 1;
193			break;
194
195		case 'n':
196			nflag = 1;
197			break;
198
199		case 'V':
200			printf("%s\n", getversion());
201			exit(0);
202
203		case 'o':
204			if (parsedistopts(optarg, &options, TRUE)) {
205				error("Bad dist option string \"%s\".",
206				      optarg);
207				usage();
208			}
209			break;
210
211		case 'p':
212			if (!optarg) {
213				error("No path specified to \"-p\".");
214				usage();
215			}
216			path_rdistd = xstrdup(optarg);
217			break;
218
219		case 'P':
220			if (!optarg) {
221				error("No path specified to \"-P\".");
222				usage();
223			}
224			if ((cp = searchpath(optarg)) != NULL)
225				path_remsh = xstrdup(cp);
226			else {
227				error("No component of path \"%s\" exists.",
228				      optarg);
229				usage();
230			}
231			break;
232
233			/*
234			 * These options are obsoleted by -o.  They are
235			 * provided only for backwards compatibility
236			 */
237		case 'v':	FLAG_ON(options, DO_VERIFY);		break;
238		case 'N':	FLAG_ON(options, DO_CHKNFS);		break;
239		case 'O':	FLAG_ON(options, DO_CHKREADONLY);	break;
240		case 'q':	FLAG_ON(options, DO_QUIET);		break;
241		case 'b':	FLAG_ON(options, DO_COMPARE);		break;
242		case 'r':	FLAG_ON(options, DO_NODESCEND);		break;
243		case 'R':	FLAG_ON(options, DO_REMOVE);		break;
244		case 's':	FLAG_ON(options, DO_SAVETARGETS);	break;
245		case 'w':	FLAG_ON(options, DO_WHOLE);		break;
246		case 'y':	FLAG_ON(options, DO_YOUNGER);		break;
247		case 'h':	FLAG_ON(options, DO_FOLLOW);		break;
248		case 'i':	FLAG_ON(options, DO_IGNLNKS);		break;
249		case 'x':	FLAG_ON(options, DO_NOEXEC);		break;
250
251		default:
252			usage();
253		}
254
255	if (debug) {
256		printf("%s\n", getversion());
257		msgprconfig();
258	}
259
260	if (nflag && IS_ON(options, DO_VERIFY))
261		fatalerr(
262		 "The -n flag and \"verify\" mode may not both be used.");
263
264	if (path_remsh == NULL) {
265		if ((cp = getenv("RSH")) != NULL && *cp != '\0')
266			path_remsh = cp;
267		else
268			path_remsh = _PATH_RSH;
269	}
270
271	/*
272	 * Don't fork children for nflag
273	 */
274	if (nflag)
275		do_fork = 0;
276
277	if (cmdargs)
278		docmdargs(realargc - optind, &realargv[optind]);
279	else {
280		if (fin == NULL)
281			fin = opendist(distfile);
282		(void) yyparse();
283		/*
284		 * Need to keep stdin open for child processing later
285		 */
286		if (fin != stdin)
287			(void) fclose(fin);
288		if (nerrs == 0)
289			docmds(hostlist, realargc-optind, &realargv[optind]);
290	}
291
292	exit(nerrs != 0);
293}
294
295/*
296 * Open a distfile
297 */
298FILE *
299opendist(char *distfile)
300{
301	char *file = NULL;
302	FILE *fp;
303
304	if (distfile == NULL) {
305		if (access("distfile", R_OK) == 0)
306			file = "distfile";
307		else if (access("Distfile", R_OK) == 0)
308			file = "Distfile";
309	} else {
310		/*
311		 * Try to test to see if file is readable before running m4.
312		 */
313		if (access(distfile, R_OK) != 0)
314			fatalerr("%s: Cannot access file: %s.",
315				 distfile, SYSERR);
316		file = distfile;
317	}
318
319	if (file == NULL)
320		fatalerr("No distfile found.");
321
322	fp = fopen(file, "r");
323
324	if (fp == NULL)
325		fatalerr("%s: open failed: %s.", file, SYSERR);
326
327	return(fp);
328}
329
330/*
331 * Print usage message and exit.
332 */
333static void
334usage(void)
335{
336	extern char *__progname;
337
338	(void) fprintf(stderr,
339		"usage: %s [-DFnV] [-A num] [-a num] [-c mini_distfile]"
340		" [-d var=value]\n"
341		"\t[-f distfile] [-L remote_logopts] [-l local_logopts]"
342		" [-M maxproc]\n"
343		"\t[-m host] [-o distopts] [-P rsh-path] [-p rdistd-path]"
344		" [-t timeout]\n"
345		"\t[name ...]\n", __progname);
346
347	exit(1);
348}
349
350/*
351 * rcp like interface for distributing files.
352 */
353void
354docmdargs(int nargs, char **args)
355{
356	struct namelist *nl, *prev;
357	char *cp;
358	struct namelist *files, *hosts;
359	struct subcmd *scmds;
360	char *dest;
361	static struct namelist tnl;
362	int i;
363
364	if (nargs < 2)
365		usage();
366
367	prev = NULL;
368	files = NULL;
369	for (i = 0; i < nargs - 1; i++) {
370		nl = makenl(args[i]);
371		if (prev == NULL)
372			files = prev = nl;
373		else {
374			prev->n_next = nl;
375			prev = nl;
376		}
377	}
378
379	cp = args[i];
380	if ((dest = strchr(cp, ':')) != NULL)
381		*dest++ = '\0';
382	tnl.n_name = cp;
383	tnl.n_regex = NULL;
384	tnl.n_next = NULL;
385	hosts = expand(&tnl, E_ALL);
386	if (nerrs)
387		exit(1);
388
389	if (dest == NULL || *dest == '\0')
390		scmds = NULL;
391	else {
392		scmds = makesubcmd(INSTALL);
393		scmds->sc_options = options;
394		scmds->sc_name = dest;
395	}
396
397	debugmsg(DM_MISC, "docmdargs()\nfiles = %s", getnlstr(files));
398	debugmsg(DM_MISC, "host = %s", getnlstr(hosts));
399
400	insert(NULL, files, hosts, scmds);
401	docmds(NULL, 0, NULL);
402}
403
404/*
405 * Get a list of NAME blocks (mostly for debugging).
406 */
407char *
408getnlstr(struct namelist *nl)
409{
410	static char buf[16384];
411	size_t len = 0;
412
413	(void) snprintf(buf, sizeof(buf), "(");
414
415	while (nl != NULL) {
416		if (nl->n_name == NULL)
417			continue;
418		len += strlen(nl->n_name) + 2;
419		if (len >= sizeof(buf)) {
420			(void) strlcpy(buf,
421				       "getnlstr() Buffer not large enough",
422				       sizeof(buf));
423			return(buf);
424		}
425		(void) strlcat(buf, " ", sizeof(buf));
426		(void) strlcat(buf, nl->n_name, sizeof(buf));
427		nl = nl->n_next;
428	}
429
430	(void) strlcat(buf, " )", sizeof(buf));
431
432	return(buf);
433}
434