1/*	$OpenBSD: cvs.c,v 1.160 2021/01/27 07:18:16 deraadt Exp $	*/
2/*
3 * Copyright (c) 2006, 2007 Joris Vink <joris@openbsd.org>
4 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
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 *
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. The name of the author may not be used to endorse or promote products
14 *    derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
17 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
18 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#include <sys/stat.h>
29
30#include <ctype.h>
31#include <errno.h>
32#include <pwd.h>
33#include <stdlib.h>
34#include <string.h>
35#include <time.h>
36#include <unistd.h>
37#include <err.h>
38
39#include "cvs.h"
40#include "remote.h"
41#include "hash.h"
42
43extern char *__progname;
44
45/* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
46int verbosity = 2;
47
48/* compression level used with zlib, 0 meaning no compression taking place */
49int	cvs_compress = 0;
50int	cvs_readrc = 1;		/* read .cvsrc on startup */
51int	cvs_trace = 0;
52int	cvs_nolog = 0;
53int	cvs_readonly = 0;
54int	cvs_readonlyfs = 0;
55int	cvs_nocase = 0;	/* set to 1 to disable filename case sensitivity */
56int	cvs_noexec = 0;	/* set to 1 to disable disk operations (-n option) */
57int	cvs_cmdop;
58int	cvs_umask = CVS_UMASK_DEFAULT;
59int	cvs_server_active = 0;
60
61char	*cvs_tagname = NULL;
62char	*cvs_defargs;		/* default global arguments from .cvsrc */
63char	*cvs_rootstr;
64char	*cvs_rsh = CVS_RSH_DEFAULT;
65char	*cvs_editor = CVS_EDITOR_DEFAULT;
66char	*cvs_homedir = NULL;
67char	*cvs_tmpdir = CVS_TMPDIR_DEFAULT;
68
69struct cvsroot *current_cvsroot = NULL;
70struct cvs_cmd *cmdp;			/* struct of command we are running */
71
72int		cvs_getopt(int, char **);
73__dead void	usage(void);
74static void	cvs_read_rcfile(void);
75
76struct cvs_varhead cvs_variables;
77
78struct wklhead temp_files;
79
80void sighandler(int);
81volatile sig_atomic_t cvs_quit = 0;
82volatile sig_atomic_t sig_received = 0;
83
84extern CVSENTRIES *current_list;
85
86struct hash_table created_directories;
87struct hash_table created_cvs_directories;
88
89void
90sighandler(int sig)
91{
92	sig_received = sig;
93
94	switch (sig) {
95	case SIGINT:
96	case SIGTERM:
97	case SIGPIPE:
98		cvs_quit = 1;
99		break;
100	default:
101		break;
102	}
103}
104
105void
106cvs_cleanup(void)
107{
108	cvs_log(LP_TRACE, "cvs_cleanup: removing locks");
109	worklist_run(&repo_locks, worklist_unlink);
110
111	cvs_log(LP_TRACE, "cvs_cleanup: removing temp files");
112	worklist_run(&temp_files, worklist_unlink);
113
114	if (cvs_server_path != NULL) {
115		if (cvs_rmdir(cvs_server_path) == -1)
116			cvs_log(LP_ERR,
117			    "warning: failed to remove server directory: %s",
118			    cvs_server_path);
119		free(cvs_server_path);
120		cvs_server_path = NULL;
121	}
122
123	if (current_list != NULL)
124		cvs_ent_close(current_list, ENT_SYNC);
125}
126
127__dead void
128usage(void)
129{
130	(void)fprintf(stderr,
131	    "usage: %s [-flnQqRrtvw] [-d root] [-e editor] [-s var=val]\n"
132	    "           [-T tmpdir] [-z level] command ...\n", __progname);
133	exit(1);
134}
135
136int
137cvs_build_cmd(char ***cmd_argv, char **argv, int argc)
138{
139	int cmd_argc, i, cur;
140	char *cp, *linebuf, *lp;
141
142	if (cmdp->cmd_defargs == NULL) {
143		*cmd_argv = argv;
144		return argc;
145	}
146
147	cur = argc + 2;
148	cmd_argc = 0;
149	*cmd_argv = xcalloc(cur, sizeof(char *));
150	(*cmd_argv)[cmd_argc++] = argv[0];
151
152	linebuf = xstrdup(cmdp->cmd_defargs);
153	for (lp = linebuf; lp != NULL;) {
154		cp = strsep(&lp, " \t\b\f\n\r\t\v");
155		if (cp == NULL)
156			break;
157		if (*cp == '\0')
158			continue;
159
160		if (cmd_argc == cur) {
161			cur += 8;
162			*cmd_argv = xreallocarray(*cmd_argv, cur,
163			    sizeof(char *));
164		}
165
166		(*cmd_argv)[cmd_argc++] = cp;
167	}
168
169	if (cmd_argc + argc > cur) {
170		cur = cmd_argc + argc + 1;
171		*cmd_argv = xreallocarray(*cmd_argv, cur,
172		    sizeof(char *));
173        }
174
175	for (i = 1; i < argc; i++)
176		(*cmd_argv)[cmd_argc++] = argv[i];
177
178	(*cmd_argv)[cmd_argc] = NULL;
179
180	return cmd_argc;
181}
182
183int
184main(int argc, char **argv)
185{
186	char *envstr, **cmd_argv, **targv;
187	int i, ret, cmd_argc;
188	struct passwd *pw;
189	struct stat st;
190	char fpath[PATH_MAX];
191
192	if (pledge("stdio rpath wpath cpath fattr getpw proc exec", NULL) == -1)
193		err(1, "pledge");
194
195	tzset();
196
197	TAILQ_INIT(&cvs_variables);
198	SLIST_INIT(&repo_locks);
199	SLIST_INIT(&temp_files);
200
201	hash_table_init(&created_directories, 100);
202	hash_table_init(&created_cvs_directories, 100);
203
204	/* check environment so command-line options override it */
205	if ((envstr = getenv("CVS_RSH")) != NULL)
206		cvs_rsh = envstr;
207
208	if (((envstr = getenv("CVSEDITOR")) != NULL) ||
209	    ((envstr = getenv("VISUAL")) != NULL) ||
210	    ((envstr = getenv("EDITOR")) != NULL))
211		cvs_editor = envstr;
212
213	if ((envstr = getenv("CVSREAD")) != NULL)
214		cvs_readonly = 1;
215
216	if ((envstr = getenv("CVSREADONLYFS")) != NULL) {
217		cvs_readonlyfs = 1;
218		cvs_nolog = 1;
219	}
220
221	if ((cvs_homedir = getenv("HOME")) == NULL) {
222		if ((pw = getpwuid(getuid())) != NULL)
223			cvs_homedir = pw->pw_dir;
224	}
225
226	if ((envstr = getenv("TMPDIR")) != NULL)
227		cvs_tmpdir = envstr;
228
229	ret = cvs_getopt(argc, argv);
230
231	argc -= ret;
232	argv += ret;
233	if (argc == 0)
234		usage();
235
236	cmdp = cvs_findcmd(argv[0]);
237	if (cmdp == NULL) {
238		fprintf(stderr, "Unknown command: `%s'\n\n", argv[0]);
239		fprintf(stderr, "CVS commands are:\n");
240		for (i = 0; cvs_cdt[i] != NULL; i++)
241			fprintf(stderr, "\t%-16s%s\n",
242			    cvs_cdt[i]->cmd_name, cvs_cdt[i]->cmd_descr);
243		exit(1);
244	}
245
246	/*
247	 * check the tmp dir, either specified through
248	 * the environment variable TMPDIR, or via
249	 * the global option -T <dir>
250	 */
251	if (stat(cvs_tmpdir, &st) == -1)
252		fatal("stat failed on `%s': %s", cvs_tmpdir, strerror(errno));
253	else if (!S_ISDIR(st.st_mode))
254		fatal("`%s' is not valid temporary directory", cvs_tmpdir);
255
256	if (cvs_readrc == 1 && cvs_homedir != NULL) {
257		cvs_read_rcfile();
258
259		if (cvs_defargs != NULL) {
260			if ((targv = cvs_makeargv(cvs_defargs, &i)) == NULL)
261				fatal("failed to load default arguments to %s",
262				    __progname);
263
264			cvs_getopt(i, targv);
265			cvs_freeargv(targv, i);
266			free(targv);
267		}
268	}
269
270	/* setup signal handlers */
271	signal(SIGTERM, sighandler);
272	signal(SIGINT, sighandler);
273	signal(SIGHUP, sighandler);
274	signal(SIGABRT, sighandler);
275	signal(SIGALRM, sighandler);
276	signal(SIGPIPE, sighandler);
277
278	cvs_cmdop = cmdp->cmd_op;
279
280	cmd_argc = cvs_build_cmd(&cmd_argv, argv, argc);
281
282	cvs_file_init();
283
284	if (cvs_cmdop == CVS_OP_SERVER) {
285		cmdp->cmd(cmd_argc, cmd_argv);
286		cvs_cleanup();
287		return (0);
288	}
289
290	cvs_umask = umask(0);
291	umask(cvs_umask);
292
293	if ((current_cvsroot = cvsroot_get(".")) == NULL) {
294		cvs_log(LP_ERR,
295		    "No CVSROOT specified! Please use the '-d' option");
296		fatal("or set the CVSROOT environment variable.");
297	}
298
299	if (cvsroot_is_remote()) {
300		cmdp->cmd(cmd_argc, cmd_argv);
301		cvs_cleanup();
302		return (0);
303	}
304
305	(void)xsnprintf(fpath, sizeof(fpath), "%s/%s",
306	    current_cvsroot->cr_dir, CVS_PATH_ROOT);
307
308	if (stat(fpath, &st) == -1 && cvs_cmdop != CVS_OP_INIT) {
309		if (errno == ENOENT)
310			fatal("repository '%s' does not exist",
311			    current_cvsroot->cr_dir);
312		else
313			fatal("%s: %s", current_cvsroot->cr_dir,
314			    strerror(errno));
315	} else {
316		if (!S_ISDIR(st.st_mode))
317			fatal("'%s' is not a directory",
318			    current_cvsroot->cr_dir);
319	}
320
321	if (cvs_cmdop != CVS_OP_INIT) {
322		cvs_parse_configfile();
323		cvs_parse_modules();
324	}
325
326	cmdp->cmd(cmd_argc, cmd_argv);
327	cvs_cleanup();
328
329	return (0);
330}
331
332int
333cvs_getopt(int argc, char **argv)
334{
335	int ret;
336	char *ep;
337	const char *errstr;
338
339	while ((ret = getopt(argc, argv, "b:d:e:flnQqRrs:T:tvwxz:")) != -1) {
340		switch (ret) {
341		case 'b':
342			/*
343			 * We do not care about the bin directory for RCS files
344			 * as this program has no dependencies on RCS programs,
345			 * so it is only here for backwards compatibility.
346			 */
347			cvs_log(LP_NOTICE, "the -b argument is obsolete");
348			break;
349		case 'd':
350			cvs_rootstr = optarg;
351			break;
352		case 'e':
353			cvs_editor = optarg;
354			break;
355		case 'f':
356			cvs_readrc = 0;
357			break;
358		case 'l':
359			cvs_nolog = 1;
360			break;
361		case 'n':
362			cvs_noexec = 1;
363			cvs_nolog = 1;
364			break;
365		case 'Q':
366			verbosity = 0;
367			break;
368		case 'q':
369			if (verbosity > 1)
370				verbosity = 1;
371			break;
372		case 'R':
373			cvs_readonlyfs = 1;
374			cvs_nolog = 1;
375			break;
376		case 'r':
377			cvs_readonly = 1;
378			break;
379		case 's':
380			ep = strchr(optarg, '=');
381			if (ep == NULL) {
382				cvs_log(LP_ERR, "no = in variable assignment");
383				exit(1);
384			}
385			*(ep++) = '\0';
386			if (cvs_var_set(optarg, ep) < 0)
387				exit(1);
388			break;
389		case 'T':
390			cvs_tmpdir = optarg;
391			break;
392		case 't':
393			cvs_trace = 1;
394			break;
395		case 'v':
396			printf("%s\n", CVS_VERSION);
397			exit(0);
398			/* NOTREACHED */
399		case 'w':
400			cvs_readonly = 0;
401			break;
402		case 'x':
403			/*
404			 * Kerberos encryption support, kept for compatibility
405			 */
406			break;
407		case 'z':
408			cvs_compress = strtonum(optarg, 0, 9, &errstr);
409			if (errstr != NULL)
410				fatal("cvs_compress: %s", errstr);
411			break;
412		default:
413			usage();
414			/* NOTREACHED */
415		}
416	}
417
418	ret = optind;
419	optind = 1;
420	optreset = 1;	/* for next call */
421
422	return (ret);
423}
424
425/*
426 * cvs_read_rcfile()
427 *
428 * Read the CVS `.cvsrc' file in the user's home directory.  If the file
429 * exists, it should contain a list of arguments that should always be given
430 * implicitly to the specified commands.
431 */
432static void
433cvs_read_rcfile(void)
434{
435	char rcpath[PATH_MAX], *buf, *lbuf, *lp, *p;
436	int cmd_parsed, cvs_parsed, i, linenum;
437	size_t len, pos;
438	struct cvs_cmd *tcmdp;
439	FILE *fp;
440
441	linenum = 0;
442
443	i = snprintf(rcpath, PATH_MAX, "%s/%s", cvs_homedir, CVS_PATH_RC);
444	if (i < 0 || i >= PATH_MAX) {
445		cvs_log(LP_ERRNO, "%s", rcpath);
446		return;
447	}
448
449	fp = fopen(rcpath, "r");
450	if (fp == NULL) {
451		if (errno != ENOENT)
452			cvs_log(LP_NOTICE, "failed to open `%s': %s", rcpath,
453			    strerror(errno));
454		return;
455	}
456
457	cmd_parsed = cvs_parsed = 0;
458	lbuf = NULL;
459	while ((buf = fgetln(fp, &len)) != NULL) {
460		if (buf[len - 1] == '\n') {
461			buf[len - 1] = '\0';
462		} else {
463			lbuf = xmalloc(len + 1);
464			memcpy(lbuf, buf, len);
465			lbuf[len] = '\0';
466			buf = lbuf;
467		}
468
469		linenum++;
470
471		/* skip any whitespaces */
472		p = buf;
473		while (*p == ' ')
474			p++;
475
476		/*
477		 * Allow comments.
478		 * GNU cvs stops parsing a line if it encounters a \t
479		 * in front of a command, stick at this behaviour for
480		 * compatibility.
481		 */
482		if (*p == '#' || *p == '\t')
483			continue;
484
485		pos = strcspn(p, " \t");
486		if (pos == strlen(p)) {
487			lp = NULL;
488		} else {
489			lp = p + pos;
490			*lp = '\0';
491		}
492
493		if (strcmp(p, "cvs") == 0 && !cvs_parsed) {
494			/*
495			 * Global default options.  In the case of cvs only,
496			 * we keep the 'cvs' string as first argument because
497			 * getopt() does not like starting at index 0 for
498			 * argument processing.
499			 */
500			if (lp != NULL) {
501				*lp = ' ';
502				cvs_defargs = xstrdup(p);
503			}
504			cvs_parsed = 1;
505		} else {
506			tcmdp = cvs_findcmd(p);
507			if (tcmdp == NULL && verbosity == 2)
508				cvs_log(LP_NOTICE,
509				    "unknown command `%s' in `%s:%d'",
510				    p, rcpath, linenum);
511
512			if (tcmdp != cmdp || cmd_parsed)
513				continue;
514
515			if (lp != NULL) {
516				lp++;
517				cmdp->cmd_defargs = xstrdup(lp);
518			}
519			cmd_parsed = 1;
520		}
521	}
522	free(lbuf);
523
524	if (ferror(fp)) {
525		cvs_log(LP_NOTICE, "failed to read line from `%s'", rcpath);
526	}
527
528	(void)fclose(fp);
529}
530
531/*
532 * cvs_var_set()
533 *
534 * Set the value of the variable <var> to <val>.  If there is no such variable,
535 * a new entry is created, otherwise the old value is overwritten.
536 * Returns 0 on success, or -1 on failure.
537 */
538int
539cvs_var_set(const char *var, const char *val)
540{
541	const char *cp;
542	struct cvs_var *vp;
543
544	if (var == NULL || *var == '\0') {
545		cvs_log(LP_ERR, "no variable name");
546		return (-1);
547	}
548
549	/* sanity check on the name */
550	for (cp = var; *cp != '\0'; cp++)
551		if (!isalnum((unsigned char)*cp) && (*cp != '_')) {
552			cvs_log(LP_ERR,
553			    "variable name `%s' contains invalid characters",
554			    var);
555			return (-1);
556		}
557
558	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
559		if (strcmp(vp->cv_name, var) == 0)
560			break;
561
562	if (vp == NULL) {
563		vp = xcalloc(1, sizeof(*vp));
564
565		vp->cv_name = xstrdup(var);
566		TAILQ_INSERT_TAIL(&cvs_variables, vp, cv_link);
567
568	} else	/* free the previous value */
569		free(vp->cv_val);
570
571	vp->cv_val = xstrdup(val);
572
573	return (0);
574}
575
576/*
577 * cvs_var_unset()
578 *
579 * Remove any entry for the variable <var>.
580 * Returns 0 on success, or -1 on failure.
581 */
582int
583cvs_var_unset(const char *var)
584{
585	struct cvs_var *vp;
586
587	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
588		if (strcmp(vp->cv_name, var) == 0) {
589			TAILQ_REMOVE(&cvs_variables, vp, cv_link);
590			free(vp->cv_name);
591			free(vp->cv_val);
592			free(vp);
593			return (0);
594		}
595
596	return (-1);
597}
598
599/*
600 * cvs_var_get()
601 *
602 * Get the value associated with the variable <var>.  Returns a pointer to the
603 * value string on success, or NULL if the variable does not exist.
604 */
605
606const char *
607cvs_var_get(const char *var)
608{
609	struct cvs_var *vp;
610
611	TAILQ_FOREACH(vp, &cvs_variables, cv_link)
612		if (strcmp(vp->cv_name, var) == 0)
613			return (vp->cv_val);
614
615	return (NULL);
616}
617