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