xref: /openbsd-src/usr.bin/cvs/root.c (revision 5738bc6274bbfbba95d07e5dc8d380a26b687449)
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