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