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