/* $OpenBSD: server.c,v 1.99 2009/04/18 16:26:01 joris Exp $ */ /* * Copyright (c) 2006 Joris Vink * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "cvs.h" #include "remote.h" struct cvs_resp cvs_responses[] = { /* this is what our server uses, the client should support it */ { "Valid-requests", 1, cvs_client_validreq, RESP_NEEDED }, { "ok", 0, cvs_client_ok, RESP_NEEDED}, { "error", 0, cvs_client_error, RESP_NEEDED }, { "E", 0, cvs_client_e, RESP_NEEDED }, { "M", 0, cvs_client_m, RESP_NEEDED }, { "Checked-in", 0, cvs_client_checkedin, RESP_NEEDED }, { "Updated", 0, cvs_client_updated, RESP_NEEDED }, { "Merged", 0, cvs_client_merged, RESP_NEEDED }, { "Removed", 0, cvs_client_removed, RESP_NEEDED }, { "Remove-entry", 0, cvs_client_remove_entry, 0 }, { "Set-static-directory", 0, cvs_client_set_static_directory, 0 }, { "Clear-static-directory", 0, cvs_client_clear_static_directory, 0 }, { "Set-sticky", 0, cvs_client_set_sticky, 0 }, { "Clear-sticky", 0, cvs_client_clear_sticky, 0 }, /* unsupported responses until told otherwise */ { "New-entry", 0, NULL, 0 }, { "Created", 0, NULL, 0 }, { "Update-existing", 0, NULL, 0 }, { "Rcs-diff", 0, NULL, 0 }, { "Patched", 0, NULL, 0 }, { "Mode", 0, NULL, 0 }, { "Mod-time", 0, NULL, 0 }, { "Checksum", 0, NULL, 0 }, { "Copy-file", 0, NULL, 0 }, { "Template", 0, NULL, 0 }, { "Set-checkin-prog", 0, NULL, 0 }, { "Set-update-prog", 0, NULL, 0 }, { "Notified", 0, NULL, 0 }, { "Module-expansion", 0, NULL, 0 }, { "Wrapper-rcsOption", 0, NULL, 0 }, { "Mbinary", 0, NULL, 0 }, { "F", 0, NULL, 0 }, { "MT", 0, NULL, 0 }, { "", -1, NULL, 0 } }; int cvs_server(int, char **); char *cvs_server_path = NULL; static char *server_currentdir = NULL; static char **server_argv; static int server_argc = 1; extern int disable_fast_checkout; struct cvs_cmd cvs_cmd_server = { CVS_OP_SERVER, CVS_USE_WDIR, "server", { "", "" }, "server mode", NULL, NULL, NULL, cvs_server }; int cvs_server(int argc, char **argv) { char *cmd, *data; struct cvs_req *req; if (argc > 1) fatal("server does not take any extra arguments"); /* Be on server-side very verbose per default. */ verbosity = 2; setvbuf(stdin, NULL, _IOLBF, 0); setvbuf(stdout, NULL, _IOLBF, 0); cvs_server_active = 1; server_argv = xcalloc(server_argc + 1, sizeof(*server_argv)); server_argv[0] = xstrdup("server"); (void)xasprintf(&cvs_server_path, "%s/cvs-serv%d", cvs_tmpdir, getpid()); if (mkdir(cvs_server_path, 0700) == -1) fatal("failed to create temporary server directory: %s, %s", cvs_server_path, strerror(errno)); if (chdir(cvs_server_path) == -1) fatal("failed to change directory to '%s'", cvs_server_path); for (;;) { cmd = cvs_remote_input(); if ((data = strchr(cmd, ' ')) != NULL) (*data++) = '\0'; req = cvs_remote_get_request_info(cmd); if (req == NULL) fatal("request '%s' is not supported by our server", cmd); if (req->hdlr == NULL) fatal("opencvs server does not support '%s'", cmd); if ((req->flags & REQ_NEEDDIR) && (server_currentdir == NULL)) fatal("`%s' needs a directory to be sent with " "the `Directory` request first", cmd); (*req->hdlr)(data); xfree(cmd); } return (0); } void cvs_server_send_response(char *fmt, ...) { int i; va_list ap; char *data; va_start(ap, fmt); i = vasprintf(&data, fmt, ap); va_end(ap); if (i == -1) fatal("cvs_server_send_response: could not allocate memory"); cvs_log(LP_TRACE, "%s", data); cvs_remote_output(data); xfree(data); } void cvs_server_root(char *data) { if (data == NULL) fatal("Missing argument for Root"); if (current_cvsroot != NULL) return; if (data[0] != '/' || (current_cvsroot = cvsroot_get(data)) == NULL) fatal("Invalid Root specified!"); cvs_parse_configfile(); cvs_parse_modules(); umask(cvs_umask); } void cvs_server_validresp(char *data) { int i; char *sp, *ep; struct cvs_resp *resp; if ((sp = data) == NULL) fatal("Missing argument for Valid-responses"); do { if ((ep = strchr(sp, ' ')) != NULL) *ep = '\0'; resp = cvs_remote_get_response_info(sp); if (resp != NULL) resp->supported = 1; if (ep != NULL) sp = ep + 1; } while (ep != NULL); for (i = 0; cvs_responses[i].supported != -1; i++) { resp = &cvs_responses[i]; if ((resp->flags & RESP_NEEDED) && resp->supported != 1) { fatal("client does not support required '%s'", resp->name); } } } void cvs_server_validreq(char *data) { BUF *bp; char *d; int i, first; first = 0; bp = cvs_buf_alloc(512); for (i = 0; cvs_requests[i].supported != -1; i++) { if (cvs_requests[i].hdlr == NULL) continue; if (first != 0) cvs_buf_putc(bp, ' '); else first++; cvs_buf_puts(bp, cvs_requests[i].name); } cvs_buf_putc(bp, '\0'); d = cvs_buf_release(bp); cvs_server_send_response("Valid-requests %s", d); cvs_server_send_response("ok"); xfree(d); } void cvs_server_static_directory(char *data) { FILE *fp; char fpath[MAXPATHLEN]; (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", server_currentdir, CVS_PATH_STATICENTRIES); if ((fp = fopen(fpath, "w+")) == NULL) { cvs_log(LP_ERRNO, "%s", fpath); return; } (void)fclose(fp); } void cvs_server_sticky(char *data) { FILE *fp; char tagpath[MAXPATHLEN]; if (data == NULL) fatal("Missing argument for Sticky"); (void)xsnprintf(tagpath, MAXPATHLEN, "%s/%s", server_currentdir, CVS_PATH_TAG); if ((fp = fopen(tagpath, "w+")) == NULL) { cvs_log(LP_ERRNO, "%s", tagpath); return; } (void)fprintf(fp, "%s\n", data); (void)fclose(fp); } void cvs_server_globalopt(char *data) { if (data == NULL) fatal("Missing argument for Global_option"); if (!strcmp(data, "-l")) cvs_nolog = 1; if (!strcmp(data, "-n")) cvs_noexec = 1; if (!strcmp(data, "-Q")) verbosity = 0; if (!strcmp(data, "-q")) verbosity = 1; if (!strcmp(data, "-r")) cvs_readonly = 1; if (!strcmp(data, "-t")) cvs_trace = 1; } void cvs_server_set(char *data) { char *ep; if (data == NULL) fatal("Missing argument for Set"); ep = strchr(data, '='); if (ep == NULL) fatal("no = in variable assignment"); *(ep++) = '\0'; if (cvs_var_set(data, ep) < 0) fatal("cvs_server_set: cvs_var_set failed"); } void cvs_server_directory(char *data) { CVSENTRIES *entlist; char *dir, *repo, *parent, *entry, *dirn, *p; if (current_cvsroot == NULL) fatal("No Root specified for Directory"); dir = cvs_remote_input(); STRIP_SLASH(dir); if (strlen(dir) < strlen(current_cvsroot->cr_dir)) fatal("cvs_server_directory: bad Directory request"); repo = dir + strlen(current_cvsroot->cr_dir); /* * This is somewhat required for checkout, as the * directory request will be: * * Directory . * /path/to/cvs/root */ if (repo[0] == '\0') p = xstrdup("."); else p = xstrdup(repo + 1); cvs_mkpath(p, NULL); if ((dirn = basename(p)) == NULL) fatal("cvs_server_directory: %s", strerror(errno)); if ((parent = dirname(p)) == NULL) fatal("cvs_server_directory: %s", strerror(errno)); if (strcmp(parent, ".")) { entry = xmalloc(CVS_ENT_MAXLINELEN); cvs_ent_line_str(dirn, NULL, NULL, NULL, NULL, 1, 0, entry, CVS_ENT_MAXLINELEN); entlist = cvs_ent_open(parent); cvs_ent_add(entlist, entry); xfree(entry); } if (server_currentdir != NULL) xfree(server_currentdir); server_currentdir = p; xfree(dir); } void cvs_server_entry(char *data) { CVSENTRIES *entlist; if (data == NULL) fatal("Missing argument for Entry"); entlist = cvs_ent_open(server_currentdir); cvs_ent_add(entlist, data); } void cvs_server_modified(char *data) { int fd; size_t flen; mode_t fmode; const char *errstr; char *mode, *len, fpath[MAXPATHLEN]; if (data == NULL) fatal("Missing argument for Modified"); /* sorry, we have to use TMP_DIR */ disable_fast_checkout = 1; mode = cvs_remote_input(); len = cvs_remote_input(); cvs_strtomode(mode, &fmode); xfree(mode); flen = strtonum(len, 0, INT_MAX, &errstr); if (errstr != NULL) fatal("cvs_server_modified: %s", errstr); xfree(len); (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", server_currentdir, data); if ((fd = open(fpath, O_WRONLY | O_CREAT | O_TRUNC)) == -1) fatal("cvs_server_modified: %s: %s", fpath, strerror(errno)); cvs_remote_receive_file(fd, flen); if (fchmod(fd, 0600) == -1) fatal("cvs_server_modified: failed to set file mode"); (void)close(fd); } void cvs_server_useunchanged(char *data) { } void cvs_server_unchanged(char *data) { char fpath[MAXPATHLEN]; CVSENTRIES *entlist; struct cvs_ent *ent; char sticky[CVS_ENT_MAXLINELEN]; char rev[CVS_REV_BUFSZ], entry[CVS_ENT_MAXLINELEN]; if (data == NULL) fatal("Missing argument for Unchanged"); /* sorry, we have to use TMP_DIR */ disable_fast_checkout = 1; (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", server_currentdir, data); entlist = cvs_ent_open(server_currentdir); ent = cvs_ent_get(entlist, data); if (ent == NULL) fatal("received Unchanged request for non-existing file"); sticky[0] = '\0'; if (ent->ce_tag != NULL) (void)xsnprintf(sticky, sizeof(sticky), "T%s", ent->ce_tag); rcsnum_tostr(ent->ce_rev, rev, sizeof(rev)); (void)xsnprintf(entry, sizeof(entry), "/%s/%s/%s/%s/%s", ent->ce_name, rev, CVS_SERVER_UNCHANGED, ent->ce_opts ? ent->ce_opts : "", sticky); cvs_ent_free(ent); cvs_ent_add(entlist, entry); } void cvs_server_questionable(char *data) { CVSENTRIES *entlist; char entry[CVS_ENT_MAXLINELEN]; if (data == NULL) fatal("Questionable request with no data attached"); (void)xsnprintf(entry, sizeof(entry), "/%s/%c///", data, CVS_SERVER_QUESTIONABLE); entlist = cvs_ent_open(server_currentdir); cvs_ent_add(entlist, entry); /* sorry, we have to use TMP_DIR */ disable_fast_checkout = 1; } void cvs_server_argument(char *data) { if (data == NULL) fatal("Missing argument for Argument"); server_argv = xrealloc(server_argv, server_argc + 2, sizeof(*server_argv)); server_argv[server_argc] = xstrdup(data); server_argv[++server_argc] = NULL; } void cvs_server_argumentx(char *data) { int idx; size_t len; if (server_argc == 1) fatal("Protocol Error: ArgumentX without previous argument"); idx = server_argc - 1; len = strlen(server_argv[idx]) + strlen(data) + 2; server_argv[idx] = xrealloc(server_argv[idx], len, sizeof(char)); strlcat(server_argv[idx], "\n", len); strlcat(server_argv[idx], data, len); } void cvs_server_update_patches(char *data) { /* * This does not actually do anything. * It is used to tell that the server is able to * generate patches when given an `update' request. * The client must issue the -u argument to `update' * to receive patches. */ } void cvs_server_add(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_add: %s", strerror(errno)); cvs_cmdop = CVS_OP_ADD; cmdp->cmd_flags = cvs_cmd_add.cmd_flags; cvs_add(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_import(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_import: %s", strerror(errno)); cvs_cmdop = CVS_OP_IMPORT; cmdp->cmd_flags = cvs_cmd_import.cmd_flags; cvs_import(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_admin(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_admin: %s", strerror(errno)); cvs_cmdop = CVS_OP_ADMIN; cmdp->cmd_flags = cvs_cmd_admin.cmd_flags; cvs_admin(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_annotate(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_annotate: %s", strerror(errno)); cvs_cmdop = CVS_OP_ANNOTATE; cmdp->cmd_flags = cvs_cmd_annotate.cmd_flags; cvs_annotate(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_rannotate(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_rannotate: %s", strerror(errno)); cvs_cmdop = CVS_OP_RANNOTATE; cmdp->cmd_flags = cvs_cmd_rannotate.cmd_flags; cvs_annotate(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_commit(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_commit: %s", strerror(errno)); cvs_cmdop = CVS_OP_COMMIT; cmdp->cmd_flags = cvs_cmd_commit.cmd_flags; cvs_commit(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_checkout(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_checkout: %s", strerror(errno)); cvs_cmdop = CVS_OP_CHECKOUT; cmdp->cmd_flags = cvs_cmd_checkout.cmd_flags; cvs_checkout(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_diff(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_diff: %s", strerror(errno)); cvs_cmdop = CVS_OP_DIFF; cmdp->cmd_flags = cvs_cmd_diff.cmd_flags; cvs_diff(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_rdiff(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_rdiff: %s", strerror(errno)); cvs_cmdop = CVS_OP_RDIFF; cmdp->cmd_flags = cvs_cmd_rdiff.cmd_flags; cvs_diff(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_export(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_export: %s", strerror(errno)); cvs_cmdop = CVS_OP_EXPORT; cmdp->cmd_flags = cvs_cmd_export.cmd_flags; cvs_export(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_init(char *data) { if (data == NULL) fatal("Missing argument for init"); if (current_cvsroot != NULL) fatal("Root in combination with init is not supported"); if ((current_cvsroot = cvsroot_get(data)) == NULL) fatal("Invalid argument for init"); cvs_cmdop = CVS_OP_INIT; cmdp->cmd_flags = cvs_cmd_init.cmd_flags; cvs_init(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_release(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_release: %s", strerror(errno)); cvs_cmdop = CVS_OP_RELEASE; cmdp->cmd_flags = cvs_cmd_release.cmd_flags; cvs_release(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_remove(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_remove: %s", strerror(errno)); cvs_cmdop = CVS_OP_REMOVE; cmdp->cmd_flags = cvs_cmd_remove.cmd_flags; cvs_remove(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_status(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_status: %s", strerror(errno)); cvs_cmdop = CVS_OP_STATUS; cmdp->cmd_flags = cvs_cmd_status.cmd_flags; cvs_status(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_log(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_log: %s", strerror(errno)); cvs_cmdop = CVS_OP_LOG; cmdp->cmd_flags = cvs_cmd_log.cmd_flags; cvs_getlog(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_rlog(char *data) { if (chdir(current_cvsroot->cr_dir) == -1) fatal("cvs_server_rlog: %s", strerror(errno)); cvs_cmdop = CVS_OP_RLOG; cmdp->cmd_flags = cvs_cmd_rlog.cmd_flags; cvs_getlog(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_tag(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_tag: %s", strerror(errno)); cvs_cmdop = CVS_OP_TAG; cmdp->cmd_flags = cvs_cmd_tag.cmd_flags; cvs_tag(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_rtag(char *data) { if (chdir(current_cvsroot->cr_dir) == -1) fatal("cvs_server_rtag: %s", strerror(errno)); cvs_cmdop = CVS_OP_RTAG; cmdp->cmd_flags = cvs_cmd_rtag.cmd_flags; cvs_tag(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_update(char *data) { if (chdir(server_currentdir) == -1) fatal("cvs_server_update: %s", strerror(errno)); cvs_cmdop = CVS_OP_UPDATE; cmdp->cmd_flags = cvs_cmd_update.cmd_flags; cvs_update(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_version(char *data) { cvs_cmdop = CVS_OP_VERSION; cmdp->cmd_flags = cvs_cmd_version.cmd_flags; cvs_version(server_argc, server_argv); cvs_server_send_response("ok"); } void cvs_server_update_entry(const char *resp, struct cvs_file *cf) { char *p; char repo[MAXPATHLEN], fpath[MAXPATHLEN]; if ((p = strrchr(cf->file_rpath, ',')) != NULL) *p = '\0'; cvs_get_repository_path(cf->file_wd, repo, MAXPATHLEN); (void)xsnprintf(fpath, MAXPATHLEN, "%s/%s", repo, cf->file_name); cvs_server_send_response("%s %s/", resp, cf->file_wd); cvs_remote_output(fpath); if (p != NULL) *p = ','; } void cvs_server_set_sticky(const char *dir, const char *tag) { char fpath[MAXPATHLEN]; char repo[MAXPATHLEN]; cvs_get_repository_path(dir, repo, MAXPATHLEN); (void)xsnprintf(fpath, MAXPATHLEN, "%s/", repo); cvs_server_send_response("Set-sticky %s/", dir); cvs_remote_output(fpath); cvs_remote_output(tag); } void cvs_server_clear_sticky(char *dir) { char fpath[MAXPATHLEN]; char repo[MAXPATHLEN]; cvs_get_repository_path(dir, repo, MAXPATHLEN); (void)xsnprintf(fpath, MAXPATHLEN, "%s/", repo); cvs_server_send_response("Clear-sticky %s//", dir); cvs_remote_output(fpath); } void cvs_server_exp_modules(char *module) { struct module_checkout *mo; struct cvs_filelist *fl; if (server_argc != 2) fatal("expand-modules with no arguments"); mo = cvs_module_lookup(server_argv[1]); RB_FOREACH(fl, cvs_flisthead, &(mo->mc_modules)) cvs_server_send_response("Module-expansion %s", fl->file_path); cvs_server_send_response("ok"); server_argc--; xfree(server_argv[1]); server_argv[1] = NULL; }