xref: /openbsd-src/usr.bin/cvs/util.c (revision 11efff7f3ac2b3cfeff0c0cddc14294d9b3aca4f)
1 /*	$OpenBSD: util.c,v 1.20 2004/12/22 00:38:26 david Exp $	*/
2 /*
3  * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. The name of the author may not be used to endorse or promote products
13  *    derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
16  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
17  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
18  * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30 
31 #include <md5.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <string.h>
38 
39 #include "cvs.h"
40 #include "log.h"
41 #include "file.h"
42 
43 static const char *cvs_months[] = {
44 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
45 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
46 };
47 
48 
49 /* letter -> mode type map */
50 static const int cvs_modetypes[26] = {
51 	-1, -1, -1, -1, -1, -1,  1, -1, -1, -1, -1, -1, -1,
52 	-1,  2, -1, -1, -1, -1, -1,  0, -1, -1, -1, -1, -1,
53 };
54 
55 /* letter -> mode map */
56 static const mode_t cvs_modes[3][26] = {
57 	{
58 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
59 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
60 		0,  0,       0,       S_IRUSR, 0,  0,  0,    /* n - u */
61 		0,  S_IWUSR, S_IXUSR, 0,       0             /* v - z */
62 	},
63 	{
64 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
65 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
66 		0,  0,       0,       S_IRGRP, 0,  0,  0,    /* n - u */
67 		0,  S_IWGRP, S_IXGRP, 0,       0             /* v - z */
68 	},
69 	{
70 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
71 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
72 		0,  0,       0,       S_IROTH, 0,  0,  0,    /* n - u */
73 		0,  S_IWOTH, S_IXOTH, 0,       0             /* v - z */
74 	}
75 };
76 
77 
78 /* octal -> string */
79 static const char *cvs_modestr[8] = {
80 	"", "x", "w", "wx", "r", "rx", "rw", "rwx"
81 };
82 
83 
84 pid_t cvs_exec_pid;
85 
86 
87 /*
88  * cvs_readrepo()
89  *
90  * Read the path stored in the `Repository' CVS file for a given directory
91  * <dir>, and store that path into the buffer pointed to by <dst>, whose size
92  * is <len>.
93  */
94 int
95 cvs_readrepo(const char *dir, char *dst, size_t len)
96 {
97 	size_t dlen;
98 	FILE *fp;
99 	char repo_path[MAXPATHLEN];
100 
101 	snprintf(repo_path, sizeof(repo_path), "%s/CVS/Repository", dir);
102 	fp = fopen(repo_path, "r");
103 	if (fp == NULL) {
104 		return (-1);
105 	}
106 
107 	if (fgets(dst, (int)len, fp) == NULL) {
108 		if (ferror(fp)) {
109 			cvs_log(LP_ERRNO, "failed to read from `%s'",
110 			    repo_path);
111 		}
112 		(void)fclose(fp);
113 		return (-1);
114 	}
115 	dlen = strlen(dst);
116 	if ((dlen > 0) && (dst[dlen - 1] == '\n'))
117 		dst[--dlen] = '\0';
118 
119 	(void)fclose(fp);
120 	return (0);
121 }
122 
123 
124 /*
125  * cvs_datesec()
126  *
127  * Take a date string and transform it into the number of seconds since the
128  * Epoch.  The <type> argument specifies whether the timestamp is in ctime(3)
129  * format or RFC 822 format (as CVS uses in its protocol).  If the <adj>
130  * parameter is not 0, the returned time will be adjusted according to the
131  * machine's local timezone.
132  */
133 time_t
134 cvs_datesec(const char *date, int type, int adj)
135 {
136 	int i;
137 	long off;
138 	char sign, mon[8], gmt[8], hr[4], min[4], *ep;
139 	struct tm cvs_tm;
140 
141 	memset(&cvs_tm, 0, sizeof(cvs_tm));
142 	cvs_tm.tm_isdst = -1;
143 
144 	if (type == CVS_DATE_RFC822) {
145 		if (sscanf(date, "%d %3s %d %2d:%2d:%2d %5s", &cvs_tm.tm_mday,
146 		    mon, &cvs_tm.tm_year, &cvs_tm.tm_hour, &cvs_tm.tm_min,
147 		    &cvs_tm.tm_sec, gmt) < 7)
148 			return (-1);
149 		cvs_tm.tm_year -= 1900;
150 
151 		if (*gmt == '-') {
152 			sscanf(gmt, "%c%2s%2s", &sign, hr, min);
153 			cvs_tm.tm_gmtoff = strtol(hr, &ep, 10);
154 			if ((cvs_tm.tm_gmtoff == LONG_MIN) ||
155 			    (cvs_tm.tm_gmtoff == LONG_MAX) ||
156 			    (*ep != '\0')) {
157 				cvs_log(LP_ERR,
158 				    "parse error in GMT hours specification `%s'", hr);
159 				cvs_tm.tm_gmtoff = 0;
160 			} else {
161 				/* get seconds */
162 				cvs_tm.tm_gmtoff *= 3600;
163 
164 				/* add the minutes */
165 				off = strtol(min, &ep, 10);
166 				if ((cvs_tm.tm_gmtoff == LONG_MIN) ||
167 				    (cvs_tm.tm_gmtoff == LONG_MAX) ||
168 				    (*ep != '\0')) {
169 					cvs_log(LP_ERR,
170 					    "parse error in GMT minutes "
171 					    "specification `%s'", min);
172 				} else
173 					cvs_tm.tm_gmtoff += off * 60;
174 			}
175 		}
176 		if (sign == '-')
177 			cvs_tm.tm_gmtoff = -cvs_tm.tm_gmtoff;
178 	} else if (type == CVS_DATE_CTIME) {
179 		/* gmt is used for the weekday */
180 		sscanf(date, "%3s %3s %d %2d:%2d:%2d %d", gmt, mon,
181 		    &cvs_tm.tm_mday, &cvs_tm.tm_hour, &cvs_tm.tm_min,
182 		    &cvs_tm.tm_sec, &cvs_tm.tm_year);
183 		cvs_tm.tm_year -= 1900;
184 		cvs_tm.tm_gmtoff = 0;
185 	}
186 
187 	for (i = 0; i < (int)(sizeof(cvs_months)/sizeof(cvs_months[0])); i++) {
188 		if (strcmp(cvs_months[i], mon) == 0) {
189 			cvs_tm.tm_mon = i;
190 			break;
191 		}
192 	}
193 
194 	return mktime(&cvs_tm);
195 }
196 
197 
198 /*
199  * cvs_strtomode()
200  *
201  * Read the contents of the string <str> and generate a permission mode from
202  * the contents of <str>, which is assumed to have the mode format of CVS.
203  * The CVS protocol specification states that any modes or mode types that are
204  * not recognized should be silently ignored.  This function does not return
205  * an error in such cases, but will issue warnings.
206  * Returns 0 on success, or -1 on failure.
207  */
208 int
209 cvs_strtomode(const char *str, mode_t *mode)
210 {
211 	char type;
212 	mode_t m;
213 	char buf[32], ms[4], *sp, *ep;
214 
215 	m = 0;
216 	strlcpy(buf, str, sizeof(buf));
217 	sp = buf;
218 	ep = sp;
219 
220 	for (sp = buf; ep != NULL; sp = ep + 1) {
221 		ep = strchr(sp, ',');
222 		if (ep != NULL)
223 			*ep = '\0';
224 
225 		memset(ms, 0, sizeof ms);
226 		if (sscanf(sp, "%c=%3s", &type, ms) != 2 &&
227 			sscanf(sp, "%c=", &type) != 1) {
228 			cvs_log(LP_WARN, "failed to scan mode string `%s'", sp);
229 			continue;
230 		}
231 
232 		if ((type <= 'a') || (type >= 'z') ||
233 		    (cvs_modetypes[type - 'a'] == -1)) {
234 			cvs_log(LP_WARN,
235 			    "invalid mode type `%c'"
236 			    " (`u', `g' or `o' expected), ignoring", type);
237 			continue;
238 		}
239 
240 		/* make type contain the actual mode index */
241 		type = cvs_modetypes[type - 'a'];
242 
243 		for (sp = ms; *sp != '\0'; sp++) {
244 			if ((*sp <= 'a') || (*sp >= 'z') ||
245 			    (cvs_modes[(int)type][*sp - 'a'] == 0)) {
246 				cvs_log(LP_WARN,
247 				    "invalid permission bit `%c'", *sp);
248 			} else
249 				m |= cvs_modes[(int)type][*sp - 'a'];
250 		}
251 	}
252 
253 	*mode = m;
254 
255 	return (0);
256 }
257 
258 
259 /*
260  * cvs_modetostr()
261  *
262  * Returns 0 on success, or -1 on failure.
263  */
264 int
265 cvs_modetostr(mode_t mode, char *buf, size_t len)
266 {
267 	size_t l;
268 	char tmp[16], *bp;
269 	mode_t um, gm, om;
270 
271 	um = (mode & S_IRWXU) >> 6;
272 	gm = (mode & S_IRWXG) >> 3;
273 	om = mode & S_IRWXO;
274 
275 	bp = buf;
276 	*bp = '\0';
277 	l = 0;
278 
279 	if (um) {
280 		snprintf(tmp, sizeof(tmp), "u=%s", cvs_modestr[um]);
281 		l = strlcat(buf, tmp, len);
282 	}
283 	if (gm) {
284 		if (um)
285 			strlcat(buf, ",", len);
286 		snprintf(tmp, sizeof(tmp), "g=%s", cvs_modestr[gm]);
287 		strlcat(buf, tmp, len);
288 	}
289 	if (om) {
290 		if (um || gm)
291 			strlcat(buf, ",", len);
292 		snprintf(tmp, sizeof(tmp), "o=%s", cvs_modestr[gm]);
293 		strlcat(buf, tmp, len);
294 	}
295 
296 	return (0);
297 }
298 
299 
300 /*
301  * cvs_cksum()
302  *
303  * Calculate the MD5 checksum of the file whose path is <file> and generate
304  * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is
305  * given in <len> and must be at least 33.
306  * Returns 0 on success, or -1 on failure.
307  */
308 int
309 cvs_cksum(const char *file, char *dst, size_t len)
310 {
311 	if (len < CVS_CKSUM_LEN) {
312 		cvs_log(LP_WARN, "buffer too small for checksum");
313 		return (-1);
314 	}
315 	if (MD5File(file, dst) == NULL) {
316 		cvs_log(LP_ERRNO, "failed to generate checksum for %s", file);
317 		return (-1);
318 	}
319 
320 	return (0);
321 }
322 
323 
324 /*
325  * cvs_splitpath()
326  *
327  * Split a path <path> into the base portion and the filename portion.
328  * The path is copied in <base> and the last delimiter is replaced by a NUL
329  * byte.  The <file> pointer is set to point to the first character after
330  * that delimiter.
331  * Returns 0 on success, or -1 on failure.
332  */
333 int
334 cvs_splitpath(const char *path, char *base, size_t blen, char **file)
335 {
336 	size_t rlen;
337 	char *sp;
338 
339 	if ((rlen = strlcpy(base, path, blen)) >= blen)
340 		return (-1);
341 
342 	while ((rlen > 0) && (base[rlen - 1] == '/'))
343 		base[--rlen] = '\0';
344 
345 	sp = strrchr(base, '/');
346 	if (sp == NULL) {
347 		strlcpy(base, "./", blen);
348 		strlcat(base, path, blen);
349 		sp = base + 1;
350 	}
351 
352 	*sp = '\0';
353 	if (file != NULL)
354 		*file = sp + 1;
355 
356 	return (0);
357 }
358 
359 
360 /*
361  * cvs_getargv()
362  *
363  * Parse a line contained in <line> and generate an argument vector by
364  * splitting the line on spaces and tabs.  The resulting vector is stored in
365  * <argv>, which can accept up to <argvlen> entries.
366  * Returns the number of arguments in the vector, or -1 if an error occurred.
367  */
368 int
369 cvs_getargv(const char *line, char **argv, int argvlen)
370 {
371 	u_int i;
372 	int argc, err;
373 	char linebuf[256], qbuf[128], *lp, *cp, *arg;
374 
375 	strlcpy(linebuf, line, sizeof(linebuf));
376 	memset(argv, 0, sizeof(argv));
377 	argc = 0;
378 
379 	/* build the argument vector */
380 	err = 0;
381 	for (lp = linebuf; lp != NULL;) {
382 		if (*lp == '"') {
383 			/* double-quoted string */
384 			lp++;
385 			i = 0;
386 			memset(qbuf, 0, sizeof(qbuf));
387 			while (*lp != '"') {
388 				if (*lp == '\\')
389 					lp++;
390 				if (*lp == '\0') {
391 					cvs_log(LP_ERR, "no terminating quote");
392 					err++;
393 					break;
394 				}
395 
396 				qbuf[i++] = *lp++;
397 				if (i == sizeof(qbuf)) {
398 					err++;
399 					break;
400 				}
401 			}
402 
403 			arg = qbuf;
404 		} else {
405 			cp = strsep(&lp, " \t");
406 			if (cp == NULL)
407 				break;
408 			else if (*cp == '\0')
409 				continue;
410 
411 			arg = cp;
412 		}
413 
414 		if (argc == argvlen) {
415 			err++;
416 			break;
417 		}
418 
419 		argv[argc] = strdup(arg);
420 		if (argv[argc] == NULL) {
421 			cvs_log(LP_ERRNO, "failed to copy argument");
422 			err++;
423 			break;
424 		}
425 		argc++;
426 	}
427 
428 	if (err) {
429 		/* ditch the argument vector */
430 		for (i = 0; i < (u_int)argc; i++)
431 			free(argv[i]);
432 		argc = -1;
433 	}
434 
435 	return (argc);
436 }
437 
438 
439 /*
440  * cvs_makeargv()
441  *
442  * Allocate an argument vector large enough to accommodate for all the
443  * arguments found in <line> and return it.
444  */
445 
446 char**
447 cvs_makeargv(const char *line, int *argc)
448 {
449 	int i, ret;
450 	char *argv[1024], **copy;
451 
452 	ret = cvs_getargv(line, argv, 1024);
453 	if (ret == -1)
454 		return (NULL);
455 
456 	copy = (char **)malloc((ret + 1) * sizeof(char *));
457 	if (copy == NULL) {
458 		cvs_log(LP_ERRNO, "failed to allocate argument vector");
459 		return (NULL);
460 	}
461 	memset(copy, 0, sizeof(copy));
462 
463 	for (i = 0; i < ret; i++)
464 		copy[i] = argv[i];
465 	copy[ret] = NULL;
466 
467 	*argc = ret;
468 	return (copy);
469 }
470 
471 
472 /*
473  * cvs_freeargv()
474  *
475  * Free an argument vector previously generated by cvs_getargv().
476  */
477 void
478 cvs_freeargv(char **argv, int argc)
479 {
480 	int i;
481 
482 	for (i = 0; i < argc; i++)
483 		if (argv[i] != NULL)
484 			free(argv[i]);
485 }
486 
487 
488 /*
489  * cvs_mkadmin()
490  *
491  * Create the CVS administrative files within the directory <cdir>.  If the
492  * files already exist, they are kept as is.
493  * Returns 0 on success, or -1 on failure.
494  */
495 int
496 cvs_mkadmin(CVSFILE *cdir, mode_t mode)
497 {
498 	char dpath[MAXPATHLEN], path[MAXPATHLEN];
499 	FILE *fp;
500 	CVSENTRIES *ef;
501 	struct stat st;
502 	struct cvsroot *root;
503 
504 	cvs_file_getpath(cdir, dpath, sizeof(dpath));
505 
506 	snprintf(path, sizeof(path), "%s/" CVS_PATH_CVSDIR, dpath);
507 	if ((mkdir(path, mode) == -1) && (errno != EEXIST)) {
508 		cvs_log(LP_ERRNO, "failed to create directory %s", path);
509 		return (-1);
510 	}
511 
512 	/* just create an empty Entries file */
513 	ef = cvs_ent_open(dpath, O_WRONLY);
514 	(void)cvs_ent_close(ef);
515 
516 	root = cdir->cf_ddat->cd_root;
517 	snprintf(path, sizeof(path), "%s/" CVS_PATH_ROOTSPEC, dpath);
518 	if ((root != NULL) && (stat(path, &st) != 0) && (errno == ENOENT)) {
519 		fp = fopen(path, "w");
520 		if (fp == NULL) {
521 			cvs_log(LP_ERRNO, "failed to open %s", path);
522 			return (-1);
523 		}
524 		if (root->cr_user != NULL) {
525 			fprintf(fp, "%s", root->cr_user);
526 			if (root->cr_pass != NULL)
527 				fprintf(fp, ":%s", root->cr_pass);
528 			if (root->cr_host != NULL)
529 				putc('@', fp);
530 		}
531 
532 		if (root->cr_host != NULL) {
533 			fprintf(fp, "%s", root->cr_host);
534 			if (root->cr_dir != NULL)
535 				putc(':', fp);
536 		}
537 		if (root->cr_dir)
538 			fprintf(fp, "%s", root->cr_dir);
539 		putc('\n', fp);
540 		(void)fclose(fp);
541 	}
542 
543 	snprintf(path, sizeof(path), "%s/" CVS_PATH_REPOSITORY, dpath);
544 	if ((stat(path, &st) != 0) && (errno == ENOENT) &&
545 	    (cdir->cf_ddat->cd_repo != NULL)) {
546 		fp = fopen(path, "w");
547 		if (fp == NULL) {
548 			cvs_log(LP_ERRNO, "failed to open %s", path);
549 			return (-1);
550 		}
551 		fprintf(fp, "%s\n", cdir->cf_ddat->cd_repo);
552 		(void)fclose(fp);
553 	}
554 
555 	return (0);
556 }
557 
558 
559 /*
560  * cvs_exec()
561  */
562 int
563 cvs_exec(int argc, char **argv, int fds[3])
564 {
565 	int ret;
566 	pid_t pid;
567 
568 	if ((pid = fork()) == -1) {
569 		cvs_log(LP_ERRNO, "failed to fork");
570 		return (-1);
571 	} else if (pid == 0) {
572 		execvp(argv[0], argv);
573 		cvs_log(LP_ERRNO, "failed to exec %s", argv[0]);
574 		exit(1);
575 	}
576 
577 	if (waitpid(pid, &ret, 0) == -1)
578 		cvs_log(LP_ERRNO, "failed to waitpid");
579 
580 	return (ret);
581 }
582