1 /* $OpenBSD: util.c,v 1.5 2004/07/30 16:52:13 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 43 /* letter -> mode type map */ 44 static const int cvs_modetypes[26] = { 45 -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, -1, -1, 46 -1, 2, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, 47 }; 48 49 /* letter -> mode map */ 50 static const mode_t cvs_modes[3][26] = { 51 { 52 0, 0, 0, 0, 0, 0, 0, /* a - g */ 53 0, 0, 0, 0, 0, 0, 0, /* h - m */ 54 0, 0, 0, S_IRUSR, 0, 0, 0, /* n - u */ 55 0, S_IWUSR, S_IXUSR, 0, 0 /* v - z */ 56 }, 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_IRGRP, 0, 0, 0, /* n - u */ 61 0, S_IWGRP, S_IXGRP, 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_IROTH, 0, 0, 0, /* n - u */ 67 0, S_IWOTH, S_IXOTH, 0, 0 /* v - z */ 68 } 69 }; 70 71 72 /* octal -> string */ 73 static const char *cvs_modestr[8] = { 74 "", "x", "w", "wx", "r", "rx", "rw", "rwx" 75 }; 76 77 78 79 80 /* 81 * cvs_readrepo() 82 * 83 * Read the path stored in the `Repository' CVS file for a given directory 84 * <dir>, and store that path into the buffer pointed to by <dst>, whose size 85 * is <len>. 86 */ 87 88 int 89 cvs_readrepo(const char *dir, char *dst, size_t len) 90 { 91 size_t dlen; 92 FILE *fp; 93 char repo_path[MAXPATHLEN]; 94 95 snprintf(repo_path, sizeof(repo_path), "%s/CVS/Repository", dir); 96 fp = fopen(repo_path, "r"); 97 if (fp == NULL) { 98 return (-1); 99 } 100 101 if (fgets(dst, (int)len, fp) == NULL) { 102 if (ferror(fp)) { 103 cvs_log(LP_ERRNO, "failed to read from `%s'", 104 repo_path); 105 } 106 (void)fclose(fp); 107 return (-1); 108 } 109 dlen = strlen(dst); 110 if ((dlen > 0) && (dst[dlen - 1] == '\n')) 111 dst[--dlen] = '\0'; 112 113 (void)fclose(fp); 114 return (0); 115 } 116 117 118 /* 119 * cvs_strtomode() 120 * 121 * Read the contents of the string <str> and generate a permission mode from 122 * the contents of <str>, which is assumed to have the mode format of CVS. 123 * The CVS protocol specification states that any modes or mode types that are 124 * not recognized should be silently ignored. This function does not return 125 * an error in such cases, but will issue warnings. 126 * Returns 0 on success, or -1 on failure. 127 */ 128 129 int 130 cvs_strtomode(const char *str, mode_t *mode) 131 { 132 char type; 133 mode_t m; 134 char buf[32], ms[4], *sp, *ep; 135 136 m = 0; 137 strlcpy(buf, str, sizeof(buf)); 138 sp = buf; 139 ep = sp; 140 141 for (sp = buf; ep != NULL; sp = ep + 1) { 142 ep = strchr(sp, ','); 143 if (ep != NULL) 144 *ep = '\0'; 145 146 if (sscanf(sp, "%c=%3s", &type, ms) != 2) { 147 cvs_log(LP_WARN, "failed to scan mode string `%s'", sp); 148 continue; 149 } 150 151 if ((type <= 'a') || (type >= 'z') || 152 (cvs_modetypes[type - 'a'] == -1)) { 153 cvs_log(LP_WARN, 154 "invalid mode type `%c'" 155 " (`u', `g' or `o' expected), ignoring", type); 156 continue; 157 } 158 159 /* make type contain the actual mode index */ 160 type = cvs_modetypes[type - 'a']; 161 162 for (sp = ms; *sp != '\0'; sp++) { 163 if ((*sp <= 'a') || (*sp >= 'z') || 164 (cvs_modes[(int)type][*sp - 'a'] == 0)) { 165 cvs_log(LP_WARN, 166 "invalid permission bit `%c'", *sp); 167 } 168 else 169 m |= cvs_modes[(int)type][*sp - 'a']; 170 } 171 } 172 173 *mode = m; 174 175 return (0); 176 } 177 178 179 /* 180 * cvs_modetostr() 181 * 182 * Returns 0 on success, or -1 on failure. 183 */ 184 185 int 186 cvs_modetostr(mode_t mode, char *buf, size_t len) 187 { 188 size_t l; 189 char tmp[16], *bp; 190 mode_t um, gm, om; 191 192 um = (mode & S_IRWXU) >> 6; 193 gm = (mode & S_IRWXG) >> 3; 194 om = mode & S_IRWXO; 195 196 bp = buf; 197 *bp = '\0'; 198 l = 0; 199 200 if (um) { 201 snprintf(tmp, sizeof(tmp), "u=%s", cvs_modestr[um]); 202 l = strlcat(buf, tmp, len); 203 } 204 if (gm) { 205 if (um) 206 strlcat(buf, ",", len); 207 snprintf(tmp, sizeof(tmp), "g=%s", cvs_modestr[gm]); 208 strlcat(buf, tmp, len); 209 } 210 if (om) { 211 if (um || gm) 212 strlcat(buf, ",", len); 213 snprintf(tmp, sizeof(tmp), "o=%s", cvs_modestr[gm]); 214 strlcat(buf, tmp, len); 215 } 216 217 return (0); 218 } 219 220 221 /* 222 * cvs_cksum() 223 * 224 * Calculate the MD5 checksum of the file whose path is <file> and generate 225 * a CVS-format 32 hex-digit string, which is stored in <dst>, whose size is 226 * given in <len> and must be at least 33. 227 * Returns 0 on success, or -1 on failure. 228 */ 229 230 int 231 cvs_cksum(const char *file, char *dst, size_t len) 232 { 233 if (len < CVS_CKSUM_LEN) { 234 cvs_log(LP_WARN, "buffer too small for checksum"); 235 return (-1); 236 } 237 if (MD5File(file, dst) == NULL) { 238 cvs_log(LP_ERRNO, "failed to generate file checksum"); 239 return (-1); 240 } 241 242 return (0); 243 } 244 245 246 /* 247 * cvs_splitpath() 248 * 249 * Split a path <path> into the directory portion and the filename portion 250 * and copy them in <dir> and <file>, whose lengths are <dlen> and <flen>, 251 * unless they are NULL. 252 * Returns 0 on success, or -1 on failure. 253 */ 254 255 int 256 cvs_splitpath(const char *path, char *dir, size_t dlen, char *file, size_t flen) 257 { 258 size_t rlen; 259 const char *sp; 260 struct stat st; 261 262 sp = strrchr(path, '/'); 263 if (sp == NULL) { 264 if (stat(path, &st) == -1) 265 return (-1); 266 267 if (S_ISDIR(st.st_mode)) { 268 if (dir != NULL) 269 strlcpy(dir, path, dlen); 270 if (file != NULL) 271 file[0] = '\0'; 272 } 273 else { 274 if (file != NULL) 275 strlcpy(file, path, flen); 276 if (dir != NULL) 277 strlcpy(dir, ".", dlen); 278 } 279 } 280 else { 281 rlen = MIN(dlen - 1, (size_t)(sp - path)); 282 if (dir != NULL) { 283 strncpy(dir, path, rlen); 284 dir[rlen] = '\0'; 285 } 286 287 sp++; 288 if (file != NULL) 289 strlcpy(file, sp, flen); 290 } 291 292 return (0); 293 } 294 295 296 /* 297 * cvs_getargv() 298 * 299 * Parse a line contained in <line> and generate an argument vector by 300 * splitting the line on spaces and tabs. The resulting vector is stored in 301 * <argv>, which can accept up to <argvlen> entries. 302 * Returns the number of arguments in the vector, or -1 if an error occured. 303 */ 304 305 int 306 cvs_getargv(const char *line, char **argv, int argvlen) 307 { 308 u_int i; 309 int argc, err; 310 char linebuf[256], qbuf[128], *lp, *cp, *arg; 311 312 strlcpy(linebuf, line, sizeof(linebuf)); 313 memset(argv, 0, sizeof(argv)); 314 argc = 0; 315 316 /* build the argument vector */ 317 err = 0; 318 for (lp = linebuf; lp != NULL;) { 319 if (*lp == '"') { 320 /* double-quoted string */ 321 lp++; 322 i = 0; 323 memset(qbuf, 0, sizeof(qbuf)); 324 while (*lp != '"') { 325 if (*lp == '\0') { 326 cvs_log(LP_ERR, "no terminating quote"); 327 err++; 328 break; 329 } 330 else if (*lp == '\\') 331 lp++; 332 333 qbuf[i++] = *lp++; 334 if (i == sizeof(qbuf)) { 335 err++; 336 break; 337 } 338 } 339 340 arg = qbuf; 341 } 342 else { 343 cp = strsep(&lp, " \t"); 344 if (cp == NULL) 345 break; 346 else if (*cp == '\0') 347 continue; 348 349 arg = cp; 350 } 351 352 argv[argc] = strdup(arg); 353 if (argv[argc] == NULL) { 354 cvs_log(LP_ERRNO, "failed to copy argument"); 355 err++; 356 break; 357 } 358 argc++; 359 } 360 361 if (err) { 362 /* ditch the argument vector */ 363 for (i = 0; i < (u_int)argc; i++) 364 free(argv[i]); 365 argc = -1; 366 } 367 368 return (argc); 369 } 370 371 372 /* 373 * cvs_freeargv() 374 * 375 * Free an argument vector previously generated by cvs_getargv(). 376 */ 377 378 void 379 cvs_freeargv(char **argv, int argc) 380 { 381 int i; 382 383 for (i = 0; i < argc; i++) 384 free(argv[i]); 385 } 386 387 388 /* 389 * cvs_mkadmin() 390 * 391 * Create the CVS administrative files within the directory <cdir>. If the 392 * files already exist, they are kept as is. 393 * Returns 0 on success, or -1 on failure. 394 */ 395 396 int 397 cvs_mkadmin(struct cvs_file *cdir, mode_t mode) 398 { 399 char path[MAXPATHLEN]; 400 FILE *fp; 401 CVSENTRIES *ef; 402 struct stat st; 403 struct cvsroot *root; 404 405 snprintf(path, sizeof(path), "%s/" CVS_PATH_CVSDIR, cdir->cf_path); 406 if ((mkdir(path, mode) == -1) && (errno != EEXIST)) { 407 cvs_log(LP_ERRNO, "failed to create directory %s", path); 408 return (-1); 409 } 410 411 /* just create an empty Entries file */ 412 ef = cvs_ent_open(cdir->cf_path, O_WRONLY); 413 (void)cvs_ent_close(ef); 414 415 root = cdir->cf_ddat->cd_root; 416 snprintf(path, sizeof(path), "%s/" CVS_PATH_ROOTSPEC, cdir->cf_path); 417 if ((stat(path, &st) != 0) && (errno == ENOENT) && (root != NULL)) { 418 fp = fopen(path, "w"); 419 if (fp == NULL) { 420 cvs_log(LP_ERRNO, "failed to open %s", path); 421 return (-1); 422 } 423 if (root->cr_user != NULL) { 424 fprintf(fp, "%s", root->cr_user); 425 if (root->cr_pass != NULL) 426 fprintf(fp, ":%s", root->cr_pass); 427 if (root->cr_host != NULL) 428 putc('@', fp); 429 } 430 431 if (root->cr_host != NULL) { 432 fprintf(fp, "%s", root->cr_host); 433 if (root->cr_dir != NULL) 434 putc(':', fp); 435 } 436 if (root->cr_dir) 437 fprintf(fp, "%s", root->cr_dir); 438 putc('\n', fp); 439 (void)fclose(fp); 440 } 441 442 snprintf(path, sizeof(path), "%s/" CVS_PATH_REPOSITORY, cdir->cf_path); 443 if ((stat(path, &st) != 0) && (errno == ENOENT) && 444 (cdir->cf_ddat->cd_repo != NULL)) { 445 fp = fopen(path, "w"); 446 if (fp == NULL) { 447 cvs_log(LP_ERRNO, "failed to open %s", path); 448 return (-1); 449 } 450 fprintf(fp, "%s\n", cdir->cf_ddat->cd_repo); 451 (void)fclose(fp); 452 } 453 454 return (0); 455 } 456