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