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