/* $OpenBSD: root.c,v 1.11 2004/08/31 11:54:35 jfb Exp $ */ /* * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <sys/types.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <err.h> #include <errno.h> #include <string.h> #include <paths.h> #include "cvs.h" #include "log.h" #include "proto.h" extern char *cvs_rootstr; /* keep these ordered with the defines */ const char *cvs_methods[] = { "", "local", "ssh", "pserver", "kserver", "gserver", "ext", "fork", }; #define CVS_NBMETHODS (sizeof(cvs_methods)/sizeof(cvs_methods[0])) /* * CVSROOT cache * * Whenever cvsroot_parse() gets called for a specific string, it first * checks in the cache to see if there is already a parsed version of the * same string and returns a pointer to it in case one is found (it also * increases the reference count). Otherwise, it does the parsing and adds * the result to the cache for future hits. */ static struct cvsroot **cvs_rcache = NULL; static u_int cvs_rcsz = 0; /* * cvsroot_parse() * * Parse a CVS root string (as found in CVS/Root files or the CVSROOT * environment variable) and store the fields in a dynamically * allocated cvs_root structure. The format of the string is as follows: * :method:[[user[:pass]@host]:path * Returns a pointer to the allocated information on success, or NULL * on failure. */ struct cvsroot* cvsroot_parse(const char *str) { u_int i; char *cp, *sp, *pp; void *tmp; struct cvsroot *root; for (i = 0; i < cvs_rcsz; i++) { if (strcmp(str, cvs_rcache[i]->cr_str) == 0) { cvs_rcache[i]->cr_ref++; return (cvs_rcache[i]); } } root = (struct cvsroot *)malloc(sizeof(*root)); if (root == NULL) { cvs_log(LP_ERRNO, "failed to allocate CVS root data"); return (NULL); } memset(root, 0, sizeof(*root)); root->cr_ref = 2; root->cr_method = CVS_METHOD_NONE; CVS_RSTVR(root); /* enable the most basic commands at least */ CVS_SETVR(root, CVS_REQ_VALIDREQ); CVS_SETVR(root, CVS_REQ_VALIDRESP); root->cr_str = strdup(str); if (root->cr_str == NULL) { free(root); return (NULL); } root->cr_buf = strdup(str); if (root->cr_buf == NULL) { cvs_log(LP_ERRNO, "failed to copy CVS root"); cvsroot_free(root); return (NULL); } sp = root->cr_buf; cp = root->cr_buf; if (*sp == ':') { sp++; cp = strchr(sp, ':'); if (cp == NULL) { cvs_log(LP_ERR, "failed to parse CVSROOT: " "unterminated method"); cvsroot_free(root); return (NULL); } *(cp++) = '\0'; for (i = 0; i < CVS_NBMETHODS; i++) { if (strcmp(sp, cvs_methods[i]) == 0) { root->cr_method = i; break; } } } /* find the start of the actual path */ sp = strchr(cp, '/'); if (sp == NULL) { cvs_log(LP_ERR, "no path specification in CVSROOT"); cvsroot_free(root); return (NULL); } root->cr_dir = sp; if (sp == cp) { if (root->cr_method == CVS_METHOD_NONE) root->cr_method = CVS_METHOD_LOCAL; /* stop here, it's just a path */ return (root); } if (*(sp - 1) != ':') { cvs_log(LP_ERR, "missing host/path delimiter in CVS root"); cvsroot_free(root); return (NULL); } *(sp - 1) = '\0'; /* * looks like we have more than just a directory path, so * attempt to split it into user and host parts */ sp = strchr(cp, '@'); if (sp != NULL) { *(sp++) = '\0'; /* password ? */ pp = strchr(cp, ':'); if (pp != NULL) { *(pp++) = '\0'; root->cr_pass = pp; } root->cr_user = cp; } else sp = cp; pp = strchr(sp, ':'); if (pp != NULL) { *(pp++) = '\0'; root->cr_port = (u_int)strtol(pp, &cp, 10); if (*cp != '\0' || root->cr_port > 65535) { cvs_log(LP_ERR, "invalid port specification in CVSROOT"); cvsroot_free(root); return (NULL); } } root->cr_host = sp; if (root->cr_method == CVS_METHOD_NONE) { /* no method found from start of CVSROOT, guess */ if (root->cr_host != NULL) root->cr_method = CVS_METHOD_SERVER; else root->cr_method = CVS_METHOD_LOCAL; } /* add to the cache */ tmp = realloc(cvs_rcache, (cvs_rcsz + 1) * sizeof(struct cvsroot *)); if (tmp == NULL) { /* just forget about the cache and return anyways */ root->cr_ref--; } else { cvs_rcache = (struct cvsroot **)tmp; cvs_rcache[cvs_rcsz++] = root; } return (root); } /* * cvsroot_free() * * Free a CVSROOT structure previously allocated and returned by * cvsroot_parse(). */ void cvsroot_free(struct cvsroot *root) { root->cr_ref--; if (root->cr_ref == 0) { if (root->cr_str != NULL) free(root->cr_str); if (root->cr_buf != NULL) free(root->cr_buf); if (root->cr_version != NULL) free(root->cr_version); free(root); } } /* * cvsroot_get() * * Get the CVSROOT information for a specific directory <dir>. The * value is taken from one of 3 possible sources (in order of precedence): * * 1) the `-d' command-line option * 2) the CVS/Root file found in checked-out trees * 3) the CVSROOT environment variable */ struct cvsroot* cvsroot_get(const char *dir) { size_t len; char rootpath[MAXPATHLEN], *rootstr, line[128]; FILE *fp; if (cvs_rootstr != NULL) return cvsroot_parse(cvs_rootstr); snprintf(rootpath, sizeof(rootpath), "%s/" CVS_PATH_ROOTSPEC, dir); fp = fopen(rootpath, "r"); if (fp == NULL) { if (errno == ENOENT) { /* try env as a last resort */ if ((rootstr = getenv("CVSROOT")) != NULL) return cvsroot_parse(rootstr); else return (NULL); } else { cvs_log(LP_ERRNO, "failed to open CVS/Root"); return (NULL); } } if (fgets(line, sizeof(line), fp) == NULL) { cvs_log(LP_ERR, "failed to read CVSROOT line from CVS/Root"); (void)fclose(fp); return (NULL); } (void)fclose(fp); len = strlen(line); if (len == 0) { cvs_log(LP_WARN, "empty CVS/Root file"); } else if (line[len - 1] == '\n') line[--len] = '\0'; return cvsroot_parse(line); }