xref: /openbsd-src/usr.bin/cvs/server.c (revision daf88648c0e349d5c02e1504293082072c981640)
1 /*	$OpenBSD: server.c,v 1.52 2007/01/26 11:19:44 joris Exp $	*/
2 /*
3  * Copyright (c) 2006 Joris Vink <joris@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include "includes.h"
19 
20 #include "cvs.h"
21 #include "log.h"
22 #include "diff.h"
23 #include "remote.h"
24 
25 struct cvs_resp cvs_responses[] = {
26 	/* this is what our server uses, the client should support it */
27 	{ "Valid-requests",	1,	cvs_client_validreq, RESP_NEEDED },
28 	{ "ok",			0,	cvs_client_ok, RESP_NEEDED},
29 	{ "error",		0,	cvs_client_error, RESP_NEEDED },
30 	{ "E",			0,	cvs_client_e, RESP_NEEDED },
31 	{ "M",			0,	cvs_client_m, RESP_NEEDED },
32 	{ "Checked-in",		0,	cvs_client_checkedin, RESP_NEEDED },
33 	{ "Updated",		0,	cvs_client_updated, RESP_NEEDED },
34 	{ "Merged",		0,	cvs_client_merged, RESP_NEEDED },
35 	{ "Removed",		0,	cvs_client_removed, RESP_NEEDED },
36 	{ "Remove-entry",	0,	cvs_client_remove_entry, RESP_NEEDED },
37 	{ "Set-static-directory",	0,	cvs_client_set_static_directory, RESP_NEEDED },
38 	{ "Clear-static-directory",	0,	cvs_client_clear_static_directory, RESP_NEEDED },
39 	{ "Set-sticky",		0,	cvs_client_set_sticky, RESP_NEEDED },
40 	{ "Clear-sticky",	0,	cvs_client_clear_sticky, RESP_NEEDED },
41 
42 	/* unsupported responses until told otherwise */
43 	{ "New-entry",			0,	NULL, 0 },
44 	{ "Created",			0,	NULL, 0 },
45 	{ "Update-existing",		0,	NULL, 0 },
46 	{ "Rcs-diff",			0,	NULL, 0 },
47 	{ "Patched",			0,	NULL, 0 },
48 	{ "Mode",			0,	NULL, 0 },
49 	{ "Mod-time",			0,	NULL, 0 },
50 	{ "Checksum",			0,	NULL, 0 },
51 	{ "Copy-file",			0,	NULL, 0 },
52 	{ "Template",			0,	NULL, 0 },
53 	{ "Set-checkin-prog",		0,	NULL, 0 },
54 	{ "Set-update-prog",		0,	NULL, 0 },
55 	{ "Notified",			0,	NULL, 0 },
56 	{ "Module-expansion",		0,	NULL, 0 },
57 	{ "Wrapper-rcsOption",		0,	NULL, 0 },
58 	{ "Mbinary",			0,	NULL, 0 },
59 	{ "F",				0,	NULL, 0 },
60 	{ "MT",				0,	NULL, 0 },
61 	{ "",				-1,	NULL, 0 }
62 };
63 
64 int	cvs_server(int, char **);
65 char	*cvs_server_path = NULL;
66 
67 static char *server_currentdir = NULL;
68 static char *server_argv[CVS_CMD_MAXARG];
69 static int server_argc = 1;
70 
71 struct cvs_cmd cvs_cmd_server = {
72 	CVS_OP_SERVER, 0, "server", { "", "" },
73 	"server mode",
74 	NULL,
75 	NULL,
76 	NULL,
77 	cvs_server
78 };
79 
80 
81 int
82 cvs_server(int argc, char **argv)
83 {
84 	int l;
85 	char *cmd, *data;
86 	struct cvs_req *req;
87 
88 	server_argv[0] = xstrdup("server");
89 
90 	cvs_server_path = xmalloc(MAXPATHLEN);
91 	l = snprintf(cvs_server_path, MAXPATHLEN, "%s/cvs-serv%d",
92 	    cvs_tmpdir, getpid());
93 	if (l == -1 || l >= MAXPATHLEN)
94 		fatal("cvs_server: overflow in server path");
95 
96 	if (mkdir(cvs_server_path, 0700) == -1)
97 		fatal("failed to create temporary server directory: %s, %s",
98 		    cvs_server_path, strerror(errno));
99 
100 	if (chdir(cvs_server_path) == -1)
101 		fatal("failed to change directory to '%s'", cvs_server_path);
102 
103 	for (;;) {
104 		cmd = cvs_remote_input();
105 
106 		if ((data = strchr(cmd, ' ')) != NULL)
107 			(*data++) = '\0';
108 
109 		req = cvs_remote_get_request_info(cmd);
110 		if (req == NULL)
111 			fatal("request '%s' is not supported by our server",
112 			    cmd);
113 
114 		if (req->hdlr == NULL)
115 			fatal("opencvs server does not support '%s'", cmd);
116 
117 		(*req->hdlr)(data);
118 		xfree(cmd);
119 	}
120 
121 	return (0);
122 }
123 
124 void
125 cvs_server_send_response(char *fmt, ...)
126 {
127 	va_list ap;
128 	char *data, *s;
129 	struct cvs_resp *resp;
130 
131 	va_start(ap, fmt);
132 	vasprintf(&data, fmt, ap);
133 	va_end(ap);
134 
135 	if ((s = strchr(data, ' ')) != NULL)
136 		*s = '\0';
137 
138 	resp = cvs_remote_get_response_info(data);
139 	if (resp == NULL)
140 		fatal("'%s' is an unknown response", data);
141 
142 	if (resp->supported != 1)
143 		fatal("remote cvs client does not support '%s'", data);
144 
145 	if (s != NULL)
146 		*s = ' ';
147 
148 	cvs_log(LP_TRACE, "%s", data);
149 	cvs_remote_output(data);
150 	xfree(data);
151 }
152 
153 void
154 cvs_server_root(char *data)
155 {
156 	fatal("duplicate Root request from client, violates the protocol");
157 }
158 
159 void
160 cvs_server_validresp(char *data)
161 {
162 	int i;
163 	char *sp, *ep;
164 	struct cvs_resp *resp;
165 
166 	sp = data;
167 	do {
168 		if ((ep = strchr(sp, ' ')) != NULL)
169 			*ep = '\0';
170 
171 		resp = cvs_remote_get_response_info(sp);
172 		if (resp != NULL)
173 			resp->supported = 1;
174 
175 		if (ep != NULL)
176 			sp = ep + 1;
177 	} while (ep != NULL);
178 
179 	for (i = 0; cvs_responses[i].supported != -1; i++) {
180 		resp = &cvs_responses[i];
181 		if ((resp->flags & RESP_NEEDED) &&
182 		    resp->supported != 1) {
183 			fatal("client does not support required '%s'",
184 			    resp->name);
185 		}
186 	}
187 }
188 
189 void
190 cvs_server_validreq(char *data)
191 {
192 	BUF *bp;
193 	char *d;
194 	int i, first;
195 
196 	first = 0;
197 	bp = cvs_buf_alloc(512, BUF_AUTOEXT);
198 	for (i = 0; cvs_requests[i].supported != -1; i++) {
199 		if (cvs_requests[i].hdlr == NULL)
200 			continue;
201 
202 		if (first != 0)
203 			cvs_buf_append(bp, " ", 1);
204 		else
205 			first++;
206 
207 		cvs_buf_append(bp, cvs_requests[i].name,
208 		    strlen(cvs_requests[i].name));
209 	}
210 
211 	cvs_buf_putc(bp, '\0');
212 	d = cvs_buf_release(bp);
213 
214 	cvs_server_send_response("Valid-requests %s", d);
215 	cvs_server_send_response("ok");
216 	xfree(d);
217 }
218 
219 void
220 cvs_server_static_directory(char *data)
221 {
222 	FILE *fp;
223 	char fpath[MAXPATHLEN];
224 
225 	if (cvs_path_cat(server_currentdir, CVS_PATH_STATICENTRIES, fpath,
226 	    MAXPATHLEN) >= MAXPATHLEN)
227 		fatal("cvs_server_static_directory: truncation");
228 
229 	if ((fp = fopen(fpath, "w+")) == NULL) {
230 		cvs_log(LP_ERRNO, "%s", fpath);
231 		return;
232 	}
233 	(void)fclose(fp);
234 }
235 
236 void
237 cvs_server_sticky(char *data)
238 {
239 	FILE *fp;
240 	char tagpath[MAXPATHLEN];
241 
242 	if (cvs_path_cat(server_currentdir, CVS_PATH_TAG, tagpath,
243 	    MAXPATHLEN) >= MAXPATHLEN)
244 		fatal("cvs_server_sticky: truncation");
245 
246 	if ((fp = fopen(tagpath, "w+")) == NULL) {
247 		cvs_log(LP_ERRNO, "%s", tagpath);
248 		return;
249 	}
250 
251 	(void)fprintf(fp, "%s\n", data);
252 	(void)fclose(fp);
253 }
254 
255 void
256 cvs_server_globalopt(char *data)
257 {
258 	if (!strcmp(data, "-l"))
259 		cvs_nolog = 1;
260 
261 	if (!strcmp(data, "-n"))
262 		cvs_noexec = 1;
263 
264 	if (!strcmp(data, "-Q"))
265 		verbosity = 0;
266 
267 	if (!strcmp(data, "-r"))
268 		cvs_readonly = 1;
269 
270 	if (!strcmp(data, "-t"))
271 		cvs_trace = 1;
272 
273 	if (!strcmp(data, "-V"))
274 		verbosity = 2;
275 }
276 
277 void
278 cvs_server_set(char *data)
279 {
280 	char *ep;
281 
282 	ep = strchr(data, '=');
283 	if (ep == NULL)
284 		fatal("no = in variable assignment");
285 
286 	*(ep++) = '\0';
287 	if (cvs_var_set(data, ep) < 0)
288 		fatal("cvs_server_set: cvs_var_set failed");
289 }
290 
291 void
292 cvs_server_directory(char *data)
293 {
294 	int l;
295 	CVSENTRIES *entlist;
296 	char *dir, *repo, *parent, entry[CVS_ENT_MAXLINELEN], *dirn, *p;
297 
298 	dir = cvs_remote_input();
299 	STRIP_SLASH(dir);
300 
301 	if (strlen(dir) < strlen(current_cvsroot->cr_dir))
302 		fatal("cvs_server_directory: bad Directory request");
303 
304 	repo = dir + strlen(current_cvsroot->cr_dir);
305 
306 	/*
307 	 * This is somewhat required for checkout, as the
308 	 * directory request will be:
309 	 *
310 	 * Directory .
311 	 * /path/to/cvs/root
312 	 */
313 	if (repo[0] == '\0')
314 		p = xstrdup(".");
315 	else
316 		p = xstrdup(repo + 1);
317 
318 	cvs_mkpath(p);
319 
320 	if ((dirn = basename(p)) == NULL)
321 		fatal("cvs_server_directory: %s", strerror(errno));
322 
323 	if ((parent = dirname(p)) == NULL)
324 		fatal("cvs_server_directory: %s", strerror(errno));
325 
326 	if (strcmp(parent, ".")) {
327 		entlist = cvs_ent_open(parent);
328 		l = snprintf(entry, CVS_ENT_MAXLINELEN, "D/%s////", dirn);
329 		if (l == -1 || l >= CVS_ENT_MAXLINELEN)
330 			fatal("cvs_server_directory: overflow");
331 
332 		cvs_ent_add(entlist, entry);
333 		cvs_ent_close(entlist, ENT_SYNC);
334 	}
335 
336 	if (server_currentdir != NULL)
337 		xfree(server_currentdir);
338 	server_currentdir = xstrdup(p);
339 
340 	xfree(p);
341 	xfree(dir);
342 }
343 
344 void
345 cvs_server_entry(char *data)
346 {
347 	CVSENTRIES *entlist;
348 
349 	entlist = cvs_ent_open(server_currentdir);
350 	cvs_ent_add(entlist, data);
351 	cvs_ent_close(entlist, ENT_SYNC);
352 }
353 
354 void
355 cvs_server_modified(char *data)
356 {
357 	int fd;
358 	size_t flen;
359 	mode_t fmode;
360 	const char *errstr;
361 	char *mode, *len, fpath[MAXPATHLEN];
362 
363 	mode = cvs_remote_input();
364 	len = cvs_remote_input();
365 
366 	cvs_strtomode(mode, &fmode);
367 	xfree(mode);
368 
369 	flen = strtonum(len, 0, INT_MAX, &errstr);
370 	if (errstr != NULL)
371 		fatal("cvs_server_modified: %s", errstr);
372 	xfree(len);
373 
374 	if (cvs_path_cat(server_currentdir, data, fpath, MAXPATHLEN) >=
375 	    MAXPATHLEN)
376 		fatal("cvs_server_modified: truncation");
377 
378 	if ((fd = open(fpath, O_WRONLY | O_CREAT | O_TRUNC)) == -1)
379 		fatal("cvs_server_modified: %s: %s", fpath, strerror(errno));
380 
381 	cvs_remote_receive_file(fd, flen);
382 
383 	if (fchmod(fd, 0600) == -1)
384 		fatal("cvs_server_modified: failed to set file mode");
385 
386 	(void)close(fd);
387 }
388 
389 void
390 cvs_server_useunchanged(char *data)
391 {
392 }
393 
394 void
395 cvs_server_unchanged(char *data)
396 {
397 	int fd;
398 	char fpath[MAXPATHLEN];
399 	CVSENTRIES *entlist;
400 	struct cvs_ent *ent;
401 	struct timeval tv[2];
402 
403 	if (cvs_path_cat(server_currentdir, data, fpath, MAXPATHLEN) >=
404 	    MAXPATHLEN)
405 		fatal("cvs_server_unchanged: truncation");
406 
407 	if ((fd = open(fpath, O_RDWR | O_CREAT | O_TRUNC)) == -1)
408 		fatal("cvs_server_unchanged: %s: %s", fpath, strerror(errno));
409 
410 	entlist = cvs_ent_open(server_currentdir);
411 	ent = cvs_ent_get(entlist, data);
412 	if (ent == NULL)
413 		fatal("received Unchanged request for non-existing file");
414 	cvs_ent_close(entlist, ENT_NOSYNC);
415 
416 	tv[0].tv_sec = ent->ce_mtime;
417 	tv[0].tv_usec = 0;
418 	tv[1] = tv[0];
419 	if (futimes(fd, tv) == -1)
420 		fatal("cvs_server_unchanged: failed to set modified time");
421 
422 	if (fchmod(fd, 0600) == -1)
423 		fatal("cvs_server_unchanged: failed to set mode");
424 
425 	cvs_ent_free(ent);
426 	(void)close(fd);
427 }
428 
429 void
430 cvs_server_questionable(char *data)
431 {
432 }
433 
434 void
435 cvs_server_argument(char *data)
436 {
437 
438 	if (server_argc > CVS_CMD_MAXARG)
439 		fatal("cvs_server_argument: too many arguments sent");
440 
441 	server_argv[server_argc++] = xstrdup(data);
442 }
443 
444 void
445 cvs_server_argumentx(char *data)
446 {
447 }
448 
449 void
450 cvs_server_update_patches(char *data)
451 {
452 	/*
453 	 * This does not actually do anything.
454 	 * It is used to tell that the server is able to
455 	 * generate patches when given an `update' request.
456 	 * The client must issue the -u argument to `update'
457 	 * to receive patches.
458 	 */
459 }
460 
461 void
462 cvs_server_add(char *data)
463 {
464 	if (chdir(server_currentdir) == -1)
465 		fatal("cvs_server_add: %s", strerror(errno));
466 
467 	cvs_cmdop = CVS_OP_ADD;
468 	cvs_add(server_argc, server_argv);
469 	cvs_server_send_response("ok");
470 }
471 
472 void
473 cvs_server_import(char *data)
474 {
475 	if (chdir(server_currentdir) == -1)
476 		fatal("cvs_server_import: %s", strerror(errno));
477 
478 	cvs_cmdop = CVS_OP_IMPORT;
479 	cvs_import(server_argc, server_argv);
480 	cvs_server_send_response("ok");
481 }
482 
483 void
484 cvs_server_admin(char *data)
485 {
486 	if (chdir(server_currentdir) == -1)
487 		fatal("cvs_server_admin: %s", strerror(errno));
488 
489 	cvs_cmdop = CVS_OP_ADMIN;
490 	cvs_admin(server_argc, server_argv);
491 	cvs_server_send_response("ok");
492 }
493 
494 void
495 cvs_server_annotate(char *data)
496 {
497 	if (chdir(server_currentdir) == -1)
498 		fatal("cvs_server_annotate: %s", strerror(errno));
499 
500 	cvs_cmdop = CVS_OP_ANNOTATE;
501 	cvs_annotate(server_argc, server_argv);
502 	cvs_server_send_response("ok");
503 }
504 
505 void
506 cvs_server_commit(char *data)
507 {
508 	if (chdir(server_currentdir) == -1)
509 		fatal("cvs_server_commit: %s", strerror(errno));
510 
511 	cvs_cmdop = CVS_OP_COMMIT;
512 	cvs_commit(server_argc, server_argv);
513 	cvs_server_send_response("ok");
514 }
515 
516 void
517 cvs_server_checkout(char *data)
518 {	if (chdir(server_currentdir) == -1)
519 		fatal("cvs_server_checkout: %s", strerror(errno));
520 
521 	cvs_cmdop = CVS_OP_CHECKOUT;
522 	cvs_checkout(server_argc, server_argv);
523 	cvs_server_send_response("ok");
524 }
525 
526 void
527 cvs_server_diff(char *data)
528 {
529 	if (chdir(server_currentdir) == -1)
530 		fatal("cvs_server_diff: %s", strerror(errno));
531 
532 	cvs_cmdop = CVS_OP_DIFF;
533 	cvs_diff(server_argc, server_argv);
534 	cvs_server_send_response("ok");
535 }
536 
537 void
538 cvs_server_init(char *data)
539 {
540 	if (chdir(server_currentdir) == -1)
541 		fatal("cvs_server_init: %s", strerror(errno));
542 
543 	cvs_cmdop = CVS_OP_INIT;
544 	cvs_init(server_argc, server_argv);
545 	cvs_server_send_response("ok");
546 }
547 
548 void
549 cvs_server_remove(char *data)
550 {
551 	if (chdir(server_currentdir) == -1)
552 		fatal("cvs_server_remove: %s", strerror(errno));
553 
554 	cvs_cmdop = CVS_OP_REMOVE;
555 	cvs_remove(server_argc, server_argv);
556 	cvs_server_send_response("ok");
557 }
558 
559 void
560 cvs_server_status(char *data)
561 {
562 	if (chdir(server_currentdir) == -1)
563 		fatal("cvs_server_status: %s", strerror(errno));
564 
565 	cvs_cmdop = CVS_OP_STATUS;
566 	cvs_status(server_argc, server_argv);
567 	cvs_server_send_response("ok");
568 }
569 
570 void
571 cvs_server_log(char *data)
572 {
573 	if (chdir(server_currentdir) == -1)
574 		fatal("cvs_server_log: %s", strerror(errno));
575 
576 	cvs_cmdop = CVS_OP_LOG;
577 	cvs_getlog(server_argc, server_argv);
578 	cvs_server_send_response("ok");
579 }
580 
581 void
582 cvs_server_tag(char *data)
583 {
584 	if (chdir(server_currentdir) == -1)
585 		fatal("cvs_server_tag: %s", strerror(errno));
586 
587 	cvs_cmdop = CVS_OP_TAG;
588 	cvs_tag(server_argc, server_argv);
589 	cvs_server_send_response("ok");
590 }
591 
592 void
593 cvs_server_update(char *data)
594 {
595 	if (chdir(server_currentdir) == -1)
596 		fatal("cvs_server_update: %s", strerror(errno));
597 
598 	cvs_cmdop = CVS_OP_UPDATE;
599 	cvs_update(server_argc, server_argv);
600 	cvs_server_send_response("ok");
601 }
602 
603 void
604 cvs_server_version(char *data)
605 {
606 	cvs_cmdop = CVS_OP_VERSION;
607 	cvs_version(server_argc, server_argv);
608 	cvs_server_send_response("ok");
609 }
610 
611 void
612 cvs_server_update_entry(const char *resp, struct cvs_file *cf)
613 {
614 	int l;
615 	char *p, response[MAXPATHLEN];
616 
617 	if ((p = strrchr(cf->file_rpath, ',')) != NULL)
618 		*p = '\0';
619 
620 	l = snprintf(response, MAXPATHLEN, "%s %s/", resp, cf->file_wd);
621 	if (l == -1 || l >= MAXPATHLEN)
622 		fatal("cvs_server_update_entry: overflow");
623 
624 	cvs_server_send_response("%s", response);
625 	cvs_remote_output(cf->file_rpath);
626 
627 	if (p != NULL)
628 		*p = ',';
629 }
630