1 /* $OpenBSD: util.c,v 1.7 2004/08/06 20:08:49 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 base portion and the filename portion. 250 * The path is copied in <base> and the last delimiter is replaced by a NUL 251 * byte. The <file> pointer is set to point to the first character after 252 * that delimiter. 253 * Returns 0 on success, or -1 on failure. 254 */ 255 256 int 257 cvs_splitpath(const char *path, char *base, size_t blen, char **file) 258 { 259 size_t rlen; 260 char *sp; 261 262 if ((rlen = strlcpy(base, path, blen)) >= blen) 263 return (-1); 264 265 while ((rlen > 0) && (base[rlen - 1] == '/')) 266 base[--rlen] = '\0'; 267 268 sp = strrchr(base, '/'); 269 if (sp == NULL) { 270 strlcpy(base, "./", blen); 271 strlcat(base, path, blen); 272 sp = base + 1; 273 } 274 275 *sp = '\0'; 276 if (file != NULL) 277 *file = sp + 1; 278 279 return (0); 280 } 281 282 283 /* 284 * cvs_getargv() 285 * 286 * Parse a line contained in <line> and generate an argument vector by 287 * splitting the line on spaces and tabs. The resulting vector is stored in 288 * <argv>, which can accept up to <argvlen> entries. 289 * Returns the number of arguments in the vector, or -1 if an error occured. 290 */ 291 292 int 293 cvs_getargv(const char *line, char **argv, int argvlen) 294 { 295 u_int i; 296 int argc, err; 297 char linebuf[256], qbuf[128], *lp, *cp, *arg; 298 299 strlcpy(linebuf, line, sizeof(linebuf)); 300 memset(argv, 0, sizeof(argv)); 301 argc = 0; 302 303 /* build the argument vector */ 304 err = 0; 305 for (lp = linebuf; lp != NULL;) { 306 if (*lp == '"') { 307 /* double-quoted string */ 308 lp++; 309 i = 0; 310 memset(qbuf, 0, sizeof(qbuf)); 311 while (*lp != '"') { 312 if (*lp == '\0') { 313 cvs_log(LP_ERR, "no terminating quote"); 314 err++; 315 break; 316 } 317 else if (*lp == '\\') 318 lp++; 319 320 qbuf[i++] = *lp++; 321 if (i == sizeof(qbuf)) { 322 err++; 323 break; 324 } 325 } 326 327 arg = qbuf; 328 } 329 else { 330 cp = strsep(&lp, " \t"); 331 if (cp == NULL) 332 break; 333 else if (*cp == '\0') 334 continue; 335 336 arg = cp; 337 } 338 339 argv[argc] = strdup(arg); 340 if (argv[argc] == NULL) { 341 cvs_log(LP_ERRNO, "failed to copy argument"); 342 err++; 343 break; 344 } 345 argc++; 346 } 347 348 if (err) { 349 /* ditch the argument vector */ 350 for (i = 0; i < (u_int)argc; i++) 351 free(argv[i]); 352 argc = -1; 353 } 354 355 return (argc); 356 } 357 358 359 /* 360 * cvs_freeargv() 361 * 362 * Free an argument vector previously generated by cvs_getargv(). 363 */ 364 365 void 366 cvs_freeargv(char **argv, int argc) 367 { 368 int i; 369 370 for (i = 0; i < argc; i++) 371 free(argv[i]); 372 } 373 374 375 /* 376 * cvs_mkadmin() 377 * 378 * Create the CVS administrative files within the directory <cdir>. If the 379 * files already exist, they are kept as is. 380 * Returns 0 on success, or -1 on failure. 381 */ 382 383 int 384 cvs_mkadmin(struct cvs_file *cdir, mode_t mode) 385 { 386 char path[MAXPATHLEN]; 387 FILE *fp; 388 CVSENTRIES *ef; 389 struct stat st; 390 struct cvsroot *root; 391 392 snprintf(path, sizeof(path), "%s/" CVS_PATH_CVSDIR, cdir->cf_path); 393 if ((mkdir(path, mode) == -1) && (errno != EEXIST)) { 394 cvs_log(LP_ERRNO, "failed to create directory %s", path); 395 return (-1); 396 } 397 398 /* just create an empty Entries file */ 399 ef = cvs_ent_open(cdir->cf_path, O_WRONLY); 400 (void)cvs_ent_close(ef); 401 402 root = cdir->cf_ddat->cd_root; 403 snprintf(path, sizeof(path), "%s/" CVS_PATH_ROOTSPEC, cdir->cf_path); 404 if ((stat(path, &st) != 0) && (errno == ENOENT) && (root != NULL)) { 405 fp = fopen(path, "w"); 406 if (fp == NULL) { 407 cvs_log(LP_ERRNO, "failed to open %s", path); 408 return (-1); 409 } 410 if (root->cr_user != NULL) { 411 fprintf(fp, "%s", root->cr_user); 412 if (root->cr_pass != NULL) 413 fprintf(fp, ":%s", root->cr_pass); 414 if (root->cr_host != NULL) 415 putc('@', fp); 416 } 417 418 if (root->cr_host != NULL) { 419 fprintf(fp, "%s", root->cr_host); 420 if (root->cr_dir != NULL) 421 putc(':', fp); 422 } 423 if (root->cr_dir) 424 fprintf(fp, "%s", root->cr_dir); 425 putc('\n', fp); 426 (void)fclose(fp); 427 } 428 429 snprintf(path, sizeof(path), "%s/" CVS_PATH_REPOSITORY, cdir->cf_path); 430 if ((stat(path, &st) != 0) && (errno == ENOENT) && 431 (cdir->cf_ddat->cd_repo != NULL)) { 432 fp = fopen(path, "w"); 433 if (fp == NULL) { 434 cvs_log(LP_ERRNO, "failed to open %s", path); 435 return (-1); 436 } 437 fprintf(fp, "%s\n", cdir->cf_ddat->cd_repo); 438 (void)fclose(fp); 439 } 440 441 return (0); 442 } 443