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