1 /* $OpenBSD: root.c,v 1.11 2004/08/31 11:54:35 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 29 #include <stdlib.h> 30 #include <stdio.h> 31 #include <unistd.h> 32 #include <err.h> 33 #include <errno.h> 34 #include <string.h> 35 #include <paths.h> 36 37 #include "cvs.h" 38 #include "log.h" 39 #include "proto.h" 40 41 42 extern char *cvs_rootstr; 43 44 45 /* keep these ordered with the defines */ 46 const char *cvs_methods[] = { 47 "", 48 "local", 49 "ssh", 50 "pserver", 51 "kserver", 52 "gserver", 53 "ext", 54 "fork", 55 }; 56 57 #define CVS_NBMETHODS (sizeof(cvs_methods)/sizeof(cvs_methods[0])) 58 59 /* 60 * CVSROOT cache 61 * 62 * Whenever cvsroot_parse() gets called for a specific string, it first 63 * checks in the cache to see if there is already a parsed version of the 64 * same string and returns a pointer to it in case one is found (it also 65 * increases the reference count). Otherwise, it does the parsing and adds 66 * the result to the cache for future hits. 67 */ 68 69 static struct cvsroot **cvs_rcache = NULL; 70 static u_int cvs_rcsz = 0; 71 72 73 74 /* 75 * cvsroot_parse() 76 * 77 * Parse a CVS root string (as found in CVS/Root files or the CVSROOT 78 * environment variable) and store the fields in a dynamically 79 * allocated cvs_root structure. The format of the string is as follows: 80 * :method:[[user[:pass]@host]:path 81 * Returns a pointer to the allocated information on success, or NULL 82 * on failure. 83 */ 84 85 struct cvsroot* 86 cvsroot_parse(const char *str) 87 { 88 u_int i; 89 char *cp, *sp, *pp; 90 void *tmp; 91 struct cvsroot *root; 92 93 for (i = 0; i < cvs_rcsz; i++) { 94 if (strcmp(str, cvs_rcache[i]->cr_str) == 0) { 95 cvs_rcache[i]->cr_ref++; 96 return (cvs_rcache[i]); 97 } 98 } 99 100 root = (struct cvsroot *)malloc(sizeof(*root)); 101 if (root == NULL) { 102 cvs_log(LP_ERRNO, "failed to allocate CVS root data"); 103 return (NULL); 104 } 105 memset(root, 0, sizeof(*root)); 106 root->cr_ref = 2; 107 root->cr_method = CVS_METHOD_NONE; 108 CVS_RSTVR(root); 109 110 /* enable the most basic commands at least */ 111 CVS_SETVR(root, CVS_REQ_VALIDREQ); 112 CVS_SETVR(root, CVS_REQ_VALIDRESP); 113 114 root->cr_str = strdup(str); 115 if (root->cr_str == NULL) { 116 free(root); 117 return (NULL); 118 } 119 root->cr_buf = strdup(str); 120 if (root->cr_buf == NULL) { 121 cvs_log(LP_ERRNO, "failed to copy CVS root"); 122 cvsroot_free(root); 123 return (NULL); 124 } 125 126 sp = root->cr_buf; 127 cp = root->cr_buf; 128 if (*sp == ':') { 129 sp++; 130 cp = strchr(sp, ':'); 131 if (cp == NULL) { 132 cvs_log(LP_ERR, "failed to parse CVSROOT: " 133 "unterminated method"); 134 cvsroot_free(root); 135 return (NULL); 136 } 137 *(cp++) = '\0'; 138 139 for (i = 0; i < CVS_NBMETHODS; i++) { 140 if (strcmp(sp, cvs_methods[i]) == 0) { 141 root->cr_method = i; 142 break; 143 } 144 } 145 } 146 147 /* find the start of the actual path */ 148 sp = strchr(cp, '/'); 149 if (sp == NULL) { 150 cvs_log(LP_ERR, "no path specification in CVSROOT"); 151 cvsroot_free(root); 152 return (NULL); 153 } 154 155 root->cr_dir = sp; 156 if (sp == cp) { 157 if (root->cr_method == CVS_METHOD_NONE) 158 root->cr_method = CVS_METHOD_LOCAL; 159 /* stop here, it's just a path */ 160 return (root); 161 } 162 163 if (*(sp - 1) != ':') { 164 cvs_log(LP_ERR, "missing host/path delimiter in CVS root"); 165 cvsroot_free(root); 166 return (NULL); 167 } 168 *(sp - 1) = '\0'; 169 170 /* 171 * looks like we have more than just a directory path, so 172 * attempt to split it into user and host parts 173 */ 174 sp = strchr(cp, '@'); 175 if (sp != NULL) { 176 *(sp++) = '\0'; 177 178 /* password ? */ 179 pp = strchr(cp, ':'); 180 if (pp != NULL) { 181 *(pp++) = '\0'; 182 root->cr_pass = pp; 183 } 184 185 root->cr_user = cp; 186 } 187 else 188 sp = cp; 189 190 pp = strchr(sp, ':'); 191 if (pp != NULL) { 192 *(pp++) = '\0'; 193 root->cr_port = (u_int)strtol(pp, &cp, 10); 194 if (*cp != '\0' || root->cr_port > 65535) { 195 cvs_log(LP_ERR, 196 "invalid port specification in CVSROOT"); 197 cvsroot_free(root); 198 return (NULL); 199 } 200 201 } 202 203 root->cr_host = sp; 204 205 if (root->cr_method == CVS_METHOD_NONE) { 206 /* no method found from start of CVSROOT, guess */ 207 if (root->cr_host != NULL) 208 root->cr_method = CVS_METHOD_SERVER; 209 else 210 root->cr_method = CVS_METHOD_LOCAL; 211 } 212 213 /* add to the cache */ 214 tmp = realloc(cvs_rcache, (cvs_rcsz + 1) * sizeof(struct cvsroot *)); 215 if (tmp == NULL) { 216 /* just forget about the cache and return anyways */ 217 root->cr_ref--; 218 } 219 else { 220 cvs_rcache = (struct cvsroot **)tmp; 221 cvs_rcache[cvs_rcsz++] = root; 222 } 223 224 return (root); 225 } 226 227 228 /* 229 * cvsroot_free() 230 * 231 * Free a CVSROOT structure previously allocated and returned by 232 * cvsroot_parse(). 233 */ 234 235 void 236 cvsroot_free(struct cvsroot *root) 237 { 238 root->cr_ref--; 239 if (root->cr_ref == 0) { 240 if (root->cr_str != NULL) 241 free(root->cr_str); 242 if (root->cr_buf != NULL) 243 free(root->cr_buf); 244 if (root->cr_version != NULL) 245 free(root->cr_version); 246 free(root); 247 } 248 } 249 250 251 /* 252 * cvsroot_get() 253 * 254 * Get the CVSROOT information for a specific directory <dir>. The 255 * value is taken from one of 3 possible sources (in order of precedence): 256 * 257 * 1) the `-d' command-line option 258 * 2) the CVS/Root file found in checked-out trees 259 * 3) the CVSROOT environment variable 260 */ 261 262 struct cvsroot* 263 cvsroot_get(const char *dir) 264 { 265 size_t len; 266 char rootpath[MAXPATHLEN], *rootstr, line[128]; 267 FILE *fp; 268 269 if (cvs_rootstr != NULL) 270 return cvsroot_parse(cvs_rootstr); 271 272 snprintf(rootpath, sizeof(rootpath), "%s/" CVS_PATH_ROOTSPEC, dir); 273 fp = fopen(rootpath, "r"); 274 if (fp == NULL) { 275 if (errno == ENOENT) { 276 /* try env as a last resort */ 277 if ((rootstr = getenv("CVSROOT")) != NULL) 278 return cvsroot_parse(rootstr); 279 else 280 return (NULL); 281 } 282 else { 283 cvs_log(LP_ERRNO, "failed to open CVS/Root"); 284 return (NULL); 285 } 286 } 287 288 if (fgets(line, sizeof(line), fp) == NULL) { 289 cvs_log(LP_ERR, "failed to read CVSROOT line from CVS/Root"); 290 (void)fclose(fp); 291 return (NULL); 292 } 293 (void)fclose(fp); 294 295 len = strlen(line); 296 if (len == 0) { 297 cvs_log(LP_WARN, "empty CVS/Root file"); 298 } 299 else if (line[len - 1] == '\n') 300 line[--len] = '\0'; 301 302 return cvsroot_parse(line); 303 } 304