/* $OpenBSD: proto.c,v 1.1 2004/07/13 22:02:40 jfb Exp $ */ /* * Copyright (c) 2004 Jean-Francois Brousseau * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * CVS client/server protocol * ========================== * * The following code implements the CVS client/server protocol, which is * documented at the following URL: * http://www.loria.fr/~molli/cvs/doc/cvsclient_toc.html * * The protocol is split up into two parts; the first part is the client side * of things and is composed of all the response handlers, which are all named * with a prefix of "cvs_resp_". The second part is the set of request * handlers used by the server. These handlers process the request and * generate the appropriate response to send back. The prefix for request * handlers is "cvs_req_". * */ #include #include #include #include #include #include #include #include #include #include #ifdef CVS_ZLIB #include #endif #include "buf.h" #include "cvs.h" #include "log.h" extern int verbosity; extern int cvs_compress; extern char *cvs_rsh; extern int cvs_trace; extern int cvs_nolog; extern int cvs_readonly; extern struct cvsroot *cvs_root; /* * Local and remote directory used by the `Directory' request. */ char cvs_ldir[MAXPATHLEN]; char cvs_rdir[MAXPATHLEN]; char *cvs_fcksum = NULL; mode_t cvs_lastmode = 0; static int cvs_resp_validreq (int, char *); static int cvs_resp_cksum (int, char *); static int cvs_resp_m (int, char *); static int cvs_resp_ok (int, char *); static int cvs_resp_error (int, char *); static int cvs_resp_statdir (int, char *); static int cvs_resp_newentry (int, char *); static int cvs_resp_updated (int, char *); static int cvs_resp_removed (int, char *); static int cvs_resp_mode (int, char *); struct cvs_req { int req_id; char req_str[32]; u_int req_flags; int (*req_hdlr)(int, char *); } cvs_requests[] = { { CVS_REQ_DIRECTORY, "Directory", 0, NULL }, { CVS_REQ_MAXDOTDOT, "Max-dotdot", 0, NULL }, { CVS_REQ_STATICDIR, "Static-directory", 0, NULL }, { CVS_REQ_STICKY, "Sticky", 0, NULL }, { CVS_REQ_ENTRY, "Entry", 0, NULL }, { CVS_REQ_ENTRYEXTRA, "EntryExtra", 0, NULL }, { CVS_REQ_CHECKINTIME, "Checkin-time", 0, NULL }, { CVS_REQ_MODIFIED, "Modified", 0, NULL }, { CVS_REQ_ISMODIFIED, "Is-modified", 0, NULL }, { CVS_REQ_UNCHANGED, "Unchanged", 0, NULL }, { CVS_REQ_USEUNCHANGED, "UseUnchanged", 0, NULL }, { CVS_REQ_NOTIFY, "Notify", 0, NULL }, { CVS_REQ_NOTIFYUSER, "NotifyUser", 0, NULL }, { CVS_REQ_QUESTIONABLE, "Questionable", 0, NULL }, { CVS_REQ_CASE, "Case", 0, NULL }, { CVS_REQ_UTF8, "Utf8", 0, NULL }, { CVS_REQ_ARGUMENT, "Argument", 0, NULL }, { CVS_REQ_ARGUMENTX, "Argumentx", 0, NULL }, { CVS_REQ_GLOBALOPT, "Global_option", 0, NULL }, { CVS_REQ_GZIPSTREAM, "Gzip-stream", 0, NULL }, { CVS_REQ_READCVSRC2, "read-cvsrc2", 0, NULL }, { CVS_REQ_READWRAP, "read-cvswrappers", 0, NULL }, { CVS_REQ_READIGNORE, "read-cvsignore", 0, NULL }, { CVS_REQ_ERRIFREADER, "Error-If-Reader", 0, NULL }, { CVS_REQ_VALIDRCSOPT, "Valid-RcsOptions", 0, NULL }, { CVS_REQ_SET, "Set", 0, NULL }, { CVS_REQ_XPANDMOD, "expand-modules", 0, NULL }, { CVS_REQ_LOG, "log", 0, NULL }, { CVS_REQ_CO, "co", 0, NULL }, { CVS_REQ_EXPORT, "export", 0, NULL }, { CVS_REQ_RANNOTATE, "rannotate", 0, NULL }, { CVS_REQ_RDIFF, "rdiff", 0, NULL }, { CVS_REQ_RLOG, "rlog", 0, NULL }, { CVS_REQ_RTAG, "rtag", 0, NULL }, { CVS_REQ_INIT, "init", 0, NULL }, { CVS_REQ_UPDATE, "update", 0, NULL }, { CVS_REQ_HISTORY, "history", 0, NULL }, { CVS_REQ_IMPORT, "import", 0, NULL }, { CVS_REQ_ADD, "add", 0, NULL }, { CVS_REQ_REMOVE, "remove", 0, NULL }, { CVS_REQ_RELEASE, "release", 0, NULL }, { CVS_REQ_ROOT, "Root", 0, NULL }, { CVS_REQ_VALIDRESP, "Valid-responses", 0, NULL }, { CVS_REQ_VALIDREQ, "valid-requests", 0, NULL }, { CVS_REQ_VERSION, "version", 0, NULL }, { CVS_REQ_NOOP, "noop", 0, NULL }, { CVS_REQ_DIFF, "diff", 0, NULL }, }; struct cvs_resp { u_int resp_id; char resp_str[32]; int (*resp_hdlr)(int, char *); } cvs_responses[] = { { CVS_RESP_OK, "ok", cvs_resp_ok }, { CVS_RESP_ERROR, "error", cvs_resp_error }, { CVS_RESP_VALIDREQ, "Valid-requests", cvs_resp_validreq }, { CVS_RESP_M, "M", cvs_resp_m }, { CVS_RESP_MBINARY, "Mbinary", cvs_resp_m }, { CVS_RESP_MT, "MT", cvs_resp_m }, { CVS_RESP_E, "E", cvs_resp_m }, { CVS_RESP_F, "F", cvs_resp_m }, { CVS_RESP_CREATED, "Created", cvs_resp_updated }, { CVS_RESP_UPDATED, "Updated", cvs_resp_updated }, { CVS_RESP_UPDEXIST, "Update-existing", cvs_resp_updated }, { CVS_RESP_REMOVED, "Removed", cvs_resp_removed }, { CVS_RESP_MERGED, "Merged", NULL }, { CVS_RESP_CKSUM, "Checksum", cvs_resp_cksum }, { CVS_RESP_CLRSTATDIR, "Clear-static-directory", cvs_resp_statdir }, { CVS_RESP_SETSTATDIR, "Set-static-directory", cvs_resp_statdir }, { CVS_RESP_NEWENTRY, "New-entry", cvs_resp_newentry }, { CVS_RESP_CHECKEDIN, "Checked-in", cvs_resp_newentry }, { CVS_RESP_MODE, "Mode", cvs_resp_mode }, }; #define CVS_NBREQ (sizeof(cvs_requests)/sizeof(cvs_requests[0])) #define CVS_NBRESP (sizeof(cvs_responses)/sizeof(cvs_responses[0])) /* mask of requets supported by server */ static u_char cvs_server_validreq[CVS_REQ_MAX + 1]; /* * cvs_req_getbyid() * */ const char* cvs_req_getbyid(int reqid) { u_int i; for (i = 0; i < CVS_NBREQ; i++) if (cvs_requests[i].req_id == reqid) return (cvs_requests[i].req_str); return (NULL); } /* * cvs_req_getbyname() */ int cvs_req_getbyname(const char *rname) { u_int i; for (i = 0; i < CVS_NBREQ; i++) if (strcmp(cvs_requests[i].req_str, rname) == 0) return (cvs_requests[i].req_id); return (-1); } /* * cvs_req_getvalid() * * Build a space-separated list of all the requests that this protocol * implementation supports. */ char* cvs_req_getvalid(void) { u_int i; size_t len; char *vrstr; BUF *buf; buf = cvs_buf_alloc(512, BUF_AUTOEXT); if (buf == NULL) return (NULL); cvs_buf_set(buf, cvs_requests[0].req_str, strlen(cvs_requests[0].req_str), 0); for (i = 1; i < CVS_NBREQ; i++) { if ((cvs_buf_putc(buf, ' ') < 0) || (cvs_buf_append(buf, cvs_requests[i].req_str, strlen(cvs_requests[i].req_str)) < 0)) { cvs_buf_free(buf); return (NULL); } } /* NUL-terminate */ if (cvs_buf_putc(buf, '\0') < 0) { cvs_buf_free(buf); return (NULL); } len = cvs_buf_size(buf); vrstr = (char *)malloc(len); cvs_buf_copy(buf, 0, vrstr, len); cvs_buf_free(buf); return (vrstr); } /* * cvs_req_handle() * * Generic request handler dispatcher. */ int cvs_req_handle(char *line) { return (0); } /* * cvs_resp_getbyid() * */ const char* cvs_resp_getbyid(int respid) { u_int i; for (i = 0; i < CVS_NBREQ; i++) if (cvs_responses[i].resp_id == respid) return (cvs_responses[i].resp_str); return (NULL); } /* * cvs_resp_getbyname() */ int cvs_resp_getbyname(const char *rname) { u_int i; for (i = 0; i < CVS_NBREQ; i++) if (strcmp(cvs_responses[i].resp_str, rname) == 0) return (cvs_responses[i].resp_id); return (-1); } /* * cvs_resp_getvalid() * * Build a space-separated list of all the responses that this protocol * implementation supports. */ char* cvs_resp_getvalid(void) { u_int i; size_t len; char *vrstr; BUF *buf; buf = cvs_buf_alloc(512, BUF_AUTOEXT); if (buf == NULL) return (NULL); cvs_buf_set(buf, cvs_responses[0].resp_str, strlen(cvs_responses[0].resp_str), 0); for (i = 1; i < CVS_NBRESP; i++) { if ((cvs_buf_putc(buf, ' ') < 0) || (cvs_buf_append(buf, cvs_responses[i].resp_str, strlen(cvs_responses[i].resp_str)) < 0)) { cvs_buf_free(buf); return (NULL); } } /* NUL-terminate */ if (cvs_buf_putc(buf, '\0') < 0) { cvs_buf_free(buf); return (NULL); } len = cvs_buf_size(buf); vrstr = (char *)malloc(len); cvs_buf_copy(buf, 0, vrstr, len); cvs_buf_free(buf); return (vrstr); } /* * cvs_resp_handle() * * Generic response handler dispatcher. The handler expects the first line * of the command as single argument. * Returns the return value of the command on success, or -1 on failure. */ int cvs_resp_handle(char *line) { u_int i; size_t len; char *cp, *cmd; cmd = line; cp = strchr(cmd, ' '); if (cp != NULL) *(cp++) = '\0'; for (i = 0; i < CVS_NBRESP; i++) { if (strcmp(cvs_responses[i].resp_str, cmd) == 0) return (*cvs_responses[i].resp_hdlr) (cvs_responses[i].resp_id, cp); } /* unhandled */ return (-1); } /* * cvs_client_sendinfo() * * Initialize the connection status by first requesting the list of * supported requests from the server. Then, we send the CVSROOT variable * with the `Root' request. * Returns 0 on success, or -1 on failure. */ static int cvs_client_sendinfo(void) { /* first things first, get list of valid requests from server */ if (cvs_client_sendreq(CVS_REQ_VALIDREQ, NULL, 1) < 0) { cvs_log(LP_ERR, "failed to get valid requests from server"); return (-1); } /* now share our global options with the server */ if (verbosity == 1) cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-q", 0); else if (verbosity == 0) cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-Q", 0); if (cvs_nolog) cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-l", 0); if (cvs_readonly) cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-r", 0); if (cvs_trace) cvs_client_sendreq(CVS_REQ_GLOBALOPT, "-t", 0); /* now send the CVSROOT to the server */ if (cvs_client_sendreq(CVS_REQ_ROOT, cvs_root->cr_dir, 0) < 0) return (-1); /* not sure why, but we have to send this */ if (cvs_client_sendreq(CVS_REQ_USEUNCHANGED, NULL, 0) < 0) return (-1); return (0); } /* * cvs_resp_validreq() * * Handler for the `Valid-requests' response. The list of valid requests is * split on spaces and each request's entry in the valid request array is set * to 1 to indicate the validity. * Returns 0 on success, or -1 on failure. */ static int cvs_resp_validreq(int type, char *line) { int i; char *sp, *ep; /* parse the requests */ sp = line; do { ep = strchr(sp, ' '); if (ep != NULL) *ep = '\0'; i = cvs_req_getbyname(sp); if (i != -1) cvs_server_validreq[i] = 1; if (ep != NULL) sp = ep + 1; } while (ep != NULL); return (0); } /* * cvs_resp_m() * * Handler for the `M', `F' and `E' responses. */ static int cvs_resp_m(int type, char *line) { FILE *stream; stream = NULL; switch (type) { case CVS_RESP_F: fflush(stderr); return (0); case CVS_RESP_M: stream = stdout; break; case CVS_RESP_E: stream = stderr; break; case CVS_RESP_MT: case CVS_RESP_MBINARY: cvs_log(LP_WARN, "MT and Mbinary not supported in client yet"); break; } fputs(line, stream); fputc('\n', stream); return (0); } /* * cvs_resp_ok() * * Handler for the `ok' response. This handler's job is to */ static int cvs_resp_ok(int type, char *line) { return (1); } /* * cvs_resp_error() * * Handler for the `error' response. This handler's job is to */ static int cvs_resp_error(int type, char *line) { return (1); } /* * cvs_resp_statdir() * * Handler for the `Clear-static-directory' and `Set-static-directory' * responses. */ static int cvs_resp_statdir(int type, char *line) { int fd; char statpath[MAXPATHLEN]; snprintf(statpath, sizeof(statpath), "%s/%s", line, CVS_PATH_STATICENTRIES); if ((type == CVS_RESP_CLRSTATDIR) && (unlink(statpath) == -1)) { cvs_log(LP_ERRNO, "failed to unlink %s file", CVS_PATH_STATICENTRIES); return (-1); } else if (type == CVS_RESP_SETSTATDIR) { fd = open(statpath, O_CREAT|O_TRUNC|O_WRONLY, 0400); if (fd == -1) { cvs_log(LP_ERRNO, "failed to create %s file", CVS_PATH_STATICENTRIES); return (-1); } (void)close(fd); } return (0); } /* * cvs_resp_newentry() * * Handler for the `New-entry' response and `Checked-in' responses. */ static int cvs_resp_newentry(int type, char *line) { char entbuf[128], path[MAXPATHLEN]; struct cvs_ent *entp; CVSENTRIES *entfile; if (cvs_splitpath(line, entbuf, sizeof(entbuf), NULL, 0) < 0) return (-1); snprintf(path, sizeof(path), "%s/" CVS_PATH_ENTRIES, entbuf); /* get the remote path */ cvs_client_getln(entbuf, sizeof(entbuf)); /* get the new Entries line */ if (cvs_client_getln(entbuf, sizeof(entbuf)) < 0) return (-1); entp = cvs_ent_parse(entbuf); if (entp == NULL) return (-1); entfile = cvs_ent_open(path); if (entfile == NULL) return (-1); cvs_ent_add(entfile, entp); cvs_ent_close(entfile); return (0); } /* * cvs_resp_cksum() * * Handler for the `Checksum' response. We store the checksum received for * the next file in a dynamically-allocated buffer pointed to by . * Upon next file reception, the handler checks to see if there is a stored * checksum. * The file handler must make sure that the checksums match and free the * checksum buffer once it's done to indicate there is no further checksum. */ static int cvs_resp_cksum(int type, char *line) { if (cvs_fcksum != NULL) { cvs_log(LP_WARN, "unused checksum"); free(cvs_fcksum); } cvs_fcksum = strdup(line); if (cvs_fcksum == NULL) { cvs_log(LP_ERRNO, "failed to copy checksum string"); return (-1); } return (0); } /* * cvs_resp_updated() * * Handler for the `Updated' response. */ static int cvs_resp_updated(int type, char *line) { char cksum_buf[CVS_CKSUM_LEN]; if (type == CVS_RESP_CREATED) { } else if (type == CVS_RESP_UPDEXIST) { } else if (type == CVS_RESP_UPDATED) { } if (cvs_recvfile(line) < 0) { return (-1); } /* now see if there is a checksum */ if (cvs_cksum(line, cksum_buf, sizeof(cksum_buf)) < 0) { } if (strcmp(cksum_buf, cvs_fcksum) != 0) { cvs_log(LP_ERR, "checksum error on received file"); (void)unlink(line); } free(cvs_fcksum); cvs_fcksum = NULL; return (0); } /* * cvs_resp_removed() * * Handler for the `Updated' response. */ static int cvs_resp_removed(int type, char *line) { return (0); } /* * cvs_resp_mode() * * Handler for the `Mode' response. */ static int cvs_resp_mode(int type, char *line) { if (cvs_strtomode(line, &cvs_lastmode) < 0) { return (-1); } return (0); } /* * cvs_sendfile() * * Send the mode and size of a file followed by the file's contents. * Returns 0 on success, or -1 on failure. */ int cvs_sendfile(const char *path) { int fd; ssize_t ret; char buf[4096]; struct stat st; if (stat(path, &st) == -1) { cvs_log(LP_ERRNO, "failed to stat `%s'", path); return (-1); } fd = open(path, O_RDONLY, 0); if (fd == -1) { return (-1); } if (cvs_modetostr(st.st_mode, buf, sizeof(buf)) < 0) return (-1); cvs_client_sendln(buf); snprintf(buf, sizeof(buf), "%lld\n", st.st_size); cvs_client_sendln(buf); while ((ret = read(fd, buf, sizeof(buf))) != 0) { if (ret == -1) { cvs_log(LP_ERRNO, "failed to read file `%s'", path); return (-1); } cvs_client_sendraw(buf, (size_t)ret); } (void)close(fd); return (0); } /* * cvs_recvfile() * * Receive the mode and size of a file followed the file's contents and * create or update the file whose path is with the received * information. */ int cvs_recvfile(const char *path) { int fd; mode_t mode; size_t len; ssize_t ret; off_t fsz, cnt; char buf[4096], *ep; if ((cvs_client_getln(buf, sizeof(buf)) < 0) || (cvs_strtomode(buf, &mode) < 0)) { return (-1); } cvs_client_getln(buf, sizeof(buf)); fsz = (off_t)strtol(buf, &ep, 10); if (*ep != '\0') { cvs_log(LP_ERR, "parse error in file size transmission"); return (-1); } fd = open(path, O_RDONLY, mode); if (fd == -1) { cvs_log(LP_ERRNO, "failed to open `%s'", path); return (-1); } cnt = 0; do { len = MIN(sizeof(buf), (size_t)(fsz - cnt)); ret = cvs_client_recvraw(buf, len); if (ret == -1) { (void)close(fd); (void)unlink(path); return (-1); } if (write(fd, buf, (size_t)ret) == -1) { cvs_log(LP_ERRNO, "failed to write contents to file `%s'", path); (void)close(fd); (void)unlink(path); return (-1); } cnt += (off_t)ret; } while (cnt < fsz); (void)close(fd); return (0); }