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