xref: /openbsd-src/usr.bin/cvs/util.c (revision 4deeb87832e5d00dbfea5b08ed9c463296b435ef)
1 /*	$OpenBSD: util.c,v 1.10 2004/08/13 12:48:51 jfb 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 
30 #include <md5.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <unistd.h>
36 #include <string.h>
37 
38 #include "cvs.h"
39 #include "log.h"
40 #include "file.h"
41 
42 static const char *cvs_months[] = {
43 	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
44 	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
45 };
46 
47 
48 /* letter -> mode type map */
49 static const int cvs_modetypes[26] = {
50 	-1, -1, -1, -1, -1, -1,  1, -1, -1, -1, -1, -1, -1,
51 	-1,  2, -1, -1, -1, -1, -1,  0, -1, -1, -1, -1, -1,
52 };
53 
54 /* letter -> mode map */
55 static const mode_t cvs_modes[3][26] = {
56 	{
57 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
58 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
59 		0,  0,       0,       S_IRUSR, 0,  0,  0,    /* n - u */
60 		0,  S_IWUSR, S_IXUSR, 0,       0             /* v - z */
61 	},
62 	{
63 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
64 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
65 		0,  0,       0,       S_IRGRP, 0,  0,  0,    /* n - u */
66 		0,  S_IWGRP, S_IXGRP, 0,       0             /* v - z */
67 	},
68 	{
69 		0,  0,       0,       0,       0,  0,  0,    /* a - g */
70 		0,  0,       0,       0,       0,  0,  0,    /* h - m */
71 		0,  0,       0,       S_IROTH, 0,  0,  0,    /* n - u */
72 		0,  S_IWOTH, S_IXOTH, 0,       0             /* v - z */
73 	}
74 };
75 
76 
77 /* octal -> string */
78 static const char *cvs_modestr[8] = {
79 	"", "x", "w", "wx", "r", "rx", "rw", "rwx"
80 };
81 
82 
83 
84 
85 /*
86  * cvs_readrepo()
87  *
88  * Read the path stored in the `Repository' CVS file for a given directory
89  * <dir>, and store that path into the buffer pointed to by <dst>, whose size
90  * is <len>.
91  */
92 
93 int
94 cvs_readrepo(const char *dir, char *dst, size_t len)
95 {
96 	size_t dlen;
97 	FILE *fp;
98 	char repo_path[MAXPATHLEN];
99 
100 	snprintf(repo_path, sizeof(repo_path), "%s/CVS/Repository", dir);
101 	fp = fopen(repo_path, "r");
102 	if (fp == NULL) {
103 		return (-1);
104 	}
105 
106 	if (fgets(dst, (int)len, fp) == NULL) {
107 		if (ferror(fp)) {
108 			cvs_log(LP_ERRNO, "failed to read from `%s'",
109 			    repo_path);
110 		}
111 		(void)fclose(fp);
112 		return (-1);
113 	}
114 	dlen = strlen(dst);
115 	if ((dlen > 0) && (dst[dlen - 1] == '\n'))
116 		dst[--dlen] = '\0';
117 
118 	(void)fclose(fp);
119 	return (0);
120 }
121 
122 
123 /*
124  * cvs_datesec()
125  *
126  * Take a date string and transform it into the number of seconds since the
127  * Epoch.  The <type> argument specifies whether the timestamp is in ctime(3)
128  * format or RFC 822 format (as CVS uses in its protocol).  If the <adj>
129  * parameter is not 0, the returned time will be adjusted according to the
130  * machine's local timezone.
131  */
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 			}
161 			else {
162 				/* get seconds */
163 				cvs_tm.tm_gmtoff *= 3600;
164 
165 				/* add the minutes */
166 				off = strtol(min, &ep, 10);
167 				if ((cvs_tm.tm_gmtoff == LONG_MIN) ||
168 				    (cvs_tm.tm_gmtoff == LONG_MAX) ||
169 				    (*ep != '\0')) {
170 					cvs_log(LP_ERR,
171 					    "parse error in GMT minutes "
172 					    "specification `%s'", min);
173 				}
174 				else
175 					cvs_tm.tm_gmtoff += off * 60;
176 			}
177 		}
178 		if (sign == '-')
179 			cvs_tm.tm_gmtoff = -cvs_tm.tm_gmtoff;
180 	}
181 	else if (type == CVS_DATE_CTIME) {
182 		/* gmt is used for the weekday */
183 		sscanf(date, "%3s %3s %d %2d:%2d:%2d %d", gmt, mon,
184 		    &cvs_tm.tm_mday, &cvs_tm.tm_hour, &cvs_tm.tm_min,
185 		    &cvs_tm.tm_sec, &cvs_tm.tm_year);
186 		cvs_tm.tm_year -= 1900;
187 		cvs_tm.tm_gmtoff = 0;
188 	}
189 
190 	for (i = 0; i < (int)(sizeof(cvs_months)/sizeof(cvs_months[0])); i++) {
191 		if (strcmp(cvs_months[i], mon) == 0) {
192 			cvs_tm.tm_mon = i;
193 			break;
194 		}
195 	}
196 
197 	return mktime(&cvs_tm);
198 }
199 
200 
201 /*
202  * cvs_strtomode()
203  *
204  * Read the contents of the string <str> and generate a permission mode from
205  * the contents of <str>, which is assumed to have the mode format of CVS.
206  * The CVS protocol specification states that any modes or mode types that are
207  * not recognized should be silently ignored.  This function does not return
208  * an error in such cases, but will issue warnings.
209  * Returns 0 on success, or -1 on failure.
210  */
211 
212 int
213 cvs_strtomode(const char *str, mode_t *mode)
214 {
215 	char type;
216 	mode_t m;
217 	char buf[32], ms[4], *sp, *ep;
218 
219 	m = 0;
220 	strlcpy(buf, str, sizeof(buf));
221 	sp = buf;
222 	ep = sp;
223 
224 	for (sp = buf; ep != NULL; sp = ep + 1) {
225 		ep = strchr(sp, ',');
226 		if (ep != NULL)
227 			*ep = '\0';
228 
229 		if (sscanf(sp, "%c=%3s", &type, ms) != 2) {
230 			cvs_log(LP_WARN, "failed to scan mode string `%s'", sp);
231 			continue;
232 		}
233 
234 		if ((type <= 'a') || (type >= 'z') ||
235 		    (cvs_modetypes[type - 'a'] == -1)) {
236 			cvs_log(LP_WARN,
237 			    "invalid mode type `%c'"
238 			    " (`u', `g' or `o' expected), ignoring", type);
239 			continue;
240 		}
241 
242 		/* make type contain the actual mode index */
243 		type = cvs_modetypes[type - 'a'];
244 
245 		for (sp = ms; *sp != '\0'; sp++) {
246 			if ((*sp <= 'a') || (*sp >= 'z') ||
247 			    (cvs_modes[(int)type][*sp - 'a'] == 0)) {
248 				cvs_log(LP_WARN,
249 				    "invalid permission bit `%c'", *sp);
250 			}
251 			else
252 				m |= cvs_modes[(int)type][*sp - 'a'];
253 		}
254 	}
255 
256 	*mode = m;
257 
258 	return (0);
259 }
260 
261 
262 /*
263  * cvs_modetostr()
264  *
265  * Returns 0 on success, or -1 on failure.
266  */
267 
268 int
269 cvs_modetostr(mode_t mode, char *buf, size_t len)
270 {
271 	size_t l;
272 	char tmp[16], *bp;
273 	mode_t um, gm, om;
274 
275 	um = (mode & S_IRWXU) >> 6;
276 	gm = (mode & S_IRWXG) >> 3;
277 	om = mode & S_IRWXO;
278 
279 	bp = buf;
280 	*bp = '\0';
281 	l = 0;
282 
283 	if (um) {
284 		snprintf(tmp, sizeof(tmp), "u=%s", cvs_modestr[um]);
285 		l = strlcat(buf, tmp, len);
286 	}
287 	if (gm) {
288 		if (um)
289 			strlcat(buf, ",", len);
290 		snprintf(tmp, sizeof(tmp), "g=%s", cvs_modestr[gm]);
291 		strlcat(buf, tmp, len);
292 	}
293 	if (om) {
294 		if (um || gm)
295 			strlcat(buf, ",", len);
296 		snprintf(tmp, sizeof(tmp), "o=%s", cvs_modestr[gm]);
297 		strlcat(buf, tmp, len);
298 	}
299 
300 	return (0);
301 }
302 
303 
304 /*
305  * cvs_cksum()
306  *
307  * Calculate the MD5 checksum of the file whose path is <file> and generate
308  * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is
309  * given in <len> and must be at least 33.
310  * Returns 0 on success, or -1 on failure.
311  */
312 
313 int
314 cvs_cksum(const char *file, char *dst, size_t len)
315 {
316 	if (len < CVS_CKSUM_LEN) {
317 		cvs_log(LP_WARN, "buffer too small for checksum");
318 		return (-1);
319 	}
320 	if (MD5File(file, dst) == NULL) {
321 		cvs_log(LP_ERRNO, "failed to generate file checksum");
322 		return (-1);
323 	}
324 
325 	return (0);
326 }
327 
328 
329 /*
330  * cvs_splitpath()
331  *
332  * Split a path <path> into the base portion and the filename portion.
333  * The path is copied in <base> and the last delimiter is replaced by a NUL
334  * byte.  The <file> pointer is set to point to the first character after
335  * that delimiter.
336  * Returns 0 on success, or -1 on failure.
337  */
338 
339 int
340 cvs_splitpath(const char *path, char *base, size_t blen, char **file)
341 {
342 	size_t rlen;
343 	char *sp;
344 
345 	if ((rlen = strlcpy(base, path, blen)) >= blen)
346 		return (-1);
347 
348 	while ((rlen > 0) && (base[rlen - 1] == '/'))
349 		base[--rlen] = '\0';
350 
351 	sp = strrchr(base, '/');
352 	if (sp == NULL) {
353 		strlcpy(base, "./", blen);
354 		strlcat(base, path, blen);
355 		sp = base + 1;
356 	}
357 
358 	*sp = '\0';
359 	if (file != NULL)
360 		*file = sp + 1;
361 
362 	return (0);
363 }
364 
365 
366 /*
367  * cvs_getargv()
368  *
369  * Parse a line contained in <line> and generate an argument vector by
370  * splitting the line on spaces and tabs.  The resulting vector is stored in
371  * <argv>, which can accept up to <argvlen> entries.
372  * Returns the number of arguments in the vector, or -1 if an error occured.
373  */
374 
375 int
376 cvs_getargv(const char *line, char **argv, int argvlen)
377 {
378 	u_int i;
379 	int argc, err;
380 	char linebuf[256], qbuf[128], *lp, *cp, *arg;
381 
382 	strlcpy(linebuf, line, sizeof(linebuf));
383 	memset(argv, 0, sizeof(argv));
384 	argc = 0;
385 
386 	/* build the argument vector */
387 	err = 0;
388 	for (lp = linebuf; lp != NULL;) {
389 		if (*lp == '"') {
390 			/* double-quoted string */
391 			lp++;
392 			i = 0;
393 			memset(qbuf, 0, sizeof(qbuf));
394 			while (*lp != '"') {
395 				if (*lp == '\0') {
396 					cvs_log(LP_ERR, "no terminating quote");
397 					err++;
398 					break;
399 				}
400 				else if (*lp == '\\')
401 					lp++;
402 
403 				qbuf[i++] = *lp++;
404 				if (i == sizeof(qbuf)) {
405 					err++;
406 					break;
407 				}
408 			}
409 
410 			arg = qbuf;
411 		}
412 		else {
413 			cp = strsep(&lp, " \t");
414 			if (cp == NULL)
415 				break;
416 			else if (*cp == '\0')
417 				continue;
418 
419 			arg = cp;
420 		}
421 
422 		argv[argc] = strdup(arg);
423 		if (argv[argc] == NULL) {
424 			cvs_log(LP_ERRNO, "failed to copy argument");
425 			err++;
426 			break;
427 		}
428 		argc++;
429 	}
430 
431 	if (err) {
432 		/* ditch the argument vector */
433 		for (i = 0; i < (u_int)argc; i++)
434 			free(argv[i]);
435 		argc = -1;
436 	}
437 
438 	return (argc);
439 }
440 
441 
442 /*
443  * cvs_freeargv()
444  *
445  * Free an argument vector previously generated by cvs_getargv().
446  */
447 
448 void
449 cvs_freeargv(char **argv, int argc)
450 {
451 	int i;
452 
453 	for (i = 0; i < argc; i++)
454 		free(argv[i]);
455 }
456 
457 
458 /*
459  * cvs_mkadmin()
460  *
461  * Create the CVS administrative files within the directory <cdir>.  If the
462  * files already exist, they are kept as is.
463  * Returns 0 on success, or -1 on failure.
464  */
465 
466 int
467 cvs_mkadmin(struct cvs_file *cdir, mode_t mode)
468 {
469 	char path[MAXPATHLEN];
470 	FILE *fp;
471 	CVSENTRIES *ef;
472 	struct stat st;
473 	struct cvsroot *root;
474 
475 	snprintf(path, sizeof(path), "%s/" CVS_PATH_CVSDIR, cdir->cf_path);
476 	if ((mkdir(path, mode) == -1) && (errno != EEXIST)) {
477 		cvs_log(LP_ERRNO, "failed to create directory %s", path);
478 		return (-1);
479 	}
480 
481 	/* just create an empty Entries file */
482 	ef = cvs_ent_open(cdir->cf_path, O_WRONLY);
483 	(void)cvs_ent_close(ef);
484 
485 	root = cdir->cf_ddat->cd_root;
486 	snprintf(path, sizeof(path), "%s/" CVS_PATH_ROOTSPEC, cdir->cf_path);
487 	if ((root != NULL) && (stat(path, &st) != 0) && (errno == ENOENT)) {
488 		fp = fopen(path, "w");
489 		if (fp == NULL) {
490 			cvs_log(LP_ERRNO, "failed to open %s", path);
491 			return (-1);
492 		}
493 		if (root->cr_user != NULL) {
494 			fprintf(fp, "%s", root->cr_user);
495 			if (root->cr_pass != NULL)
496 				fprintf(fp, ":%s", root->cr_pass);
497 			if (root->cr_host != NULL)
498 				putc('@', fp);
499 		}
500 
501 		if (root->cr_host != NULL) {
502 			fprintf(fp, "%s", root->cr_host);
503 			if (root->cr_dir != NULL)
504 				putc(':', fp);
505 		}
506 		if (root->cr_dir)
507 			fprintf(fp, "%s", root->cr_dir);
508 		putc('\n', fp);
509 		(void)fclose(fp);
510 	}
511 
512 	snprintf(path, sizeof(path), "%s/" CVS_PATH_REPOSITORY, cdir->cf_path);
513 	if ((stat(path, &st) != 0) && (errno == ENOENT) &&
514 	    (cdir->cf_ddat->cd_repo != NULL)) {
515 		fp = fopen(path, "w");
516 		if (fp == NULL) {
517 			cvs_log(LP_ERRNO, "failed to open %s", path);
518 			return (-1);
519 		}
520 		fprintf(fp, "%s\n", cdir->cf_ddat->cd_repo);
521 		(void)fclose(fp);
522 	}
523 
524 	return (0);
525 }
526