xref: /openbsd/usr.bin/cvs/cvs.c (revision 2f43a3f5)
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 
43 extern char *__progname;
44 
45 /* verbosity level: 0 = really quiet, 1 = quiet, 2 = verbose */
46 int verbosity = 2;
47 
48 /* compression level used with zlib, 0 meaning no compression taking place */
49 int	cvs_compress = 0;
50 int	cvs_readrc = 1;		/* read .cvsrc on startup */
51 int	cvs_trace = 0;
52 int	cvs_nolog = 0;
53 int	cvs_readonly = 0;
54 int	cvs_readonlyfs = 0;
55 int	cvs_nocase = 0;	/* set to 1 to disable filename case sensitivity */
56 int	cvs_noexec = 0;	/* set to 1 to disable disk operations (-n option) */
57 int	cvs_cmdop;
58 int	cvs_umask = CVS_UMASK_DEFAULT;
59 int	cvs_server_active = 0;
60 
61 char	*cvs_tagname = NULL;
62 char	*cvs_defargs;		/* default global arguments from .cvsrc */
63 char	*cvs_rootstr;
64 char	*cvs_rsh = CVS_RSH_DEFAULT;
65 char	*cvs_editor = CVS_EDITOR_DEFAULT;
66 char	*cvs_homedir = NULL;
67 char	*cvs_tmpdir = CVS_TMPDIR_DEFAULT;
68 
69 struct cvsroot *current_cvsroot = NULL;
70 struct cvs_cmd *cmdp;			/* struct of command we are running */
71 
72 int		cvs_getopt(int, char **);
73 __dead void	usage(void);
74 static void	cvs_read_rcfile(void);
75 
76 struct cvs_varhead cvs_variables;
77 
78 struct wklhead temp_files;
79 
80 void sighandler(int);
81 volatile sig_atomic_t cvs_quit = 0;
82 volatile sig_atomic_t sig_received = 0;
83 
84 extern CVSENTRIES *current_list;
85 
86 struct hash_table created_directories;
87 struct hash_table created_cvs_directories;
88 
89 void
sighandler(int sig)90 sighandler(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 
105 void
cvs_cleanup(void)106 cvs_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
usage(void)128 usage(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 
136 int
cvs_build_cmd(char *** cmd_argv,char ** argv,int argc)137 cvs_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 
183 int
main(int argc,char ** argv)184 main(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 
332 int
cvs_getopt(int argc,char ** argv)333 cvs_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  */
432 static void
cvs_read_rcfile(void)433 cvs_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  */
538 int
cvs_var_set(const char * var,const char * val)539 cvs_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  */
582 int
cvs_var_unset(const char * var)583 cvs_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 
606 const char *
cvs_var_get(const char * var)607 cvs_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