diff options
author | eadler <eadler@FreeBSD.org> | 2013-06-15 20:29:07 +0000 |
---|---|---|
committer | eadler <eadler@FreeBSD.org> | 2013-06-15 20:29:07 +0000 |
commit | bf7c0f2705c32e44d3c3b62d60453a30dbbffe3f (patch) | |
tree | dca088b474d4fedf5e6d4ef16e823d7756d587bc /contrib/cvs/src/server.c | |
parent | b95c459e182fd072e6dac884c7eed86a220534e7 (diff) | |
download | FreeBSD-src-bf7c0f2705c32e44d3c3b62d60453a30dbbffe3f.zip FreeBSD-src-bf7c0f2705c32e44d3c3b62d60453a30dbbffe3f.tar.gz |
Remove CVS from the base system.
Discussed with: many
Reviewed by: peter, zi
Approved by: core
Diffstat (limited to 'contrib/cvs/src/server.c')
-rw-r--r-- | contrib/cvs/src/server.c | 6760 |
1 files changed, 0 insertions, 6760 deletions
diff --git a/contrib/cvs/src/server.c b/contrib/cvs/src/server.c deleted file mode 100644 index cace89d..0000000 --- a/contrib/cvs/src/server.c +++ /dev/null @@ -1,6760 +0,0 @@ -/* This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2, or (at your option) - any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. */ - -/* - * $FreeBSD$ - */ - -#include <assert.h> -#include "cvs.h" -#include "watch.h" -#include "edit.h" -#include "fileattr.h" -#include "getline.h" -#include "buffer.h" - -int server_active = 0; - -#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) -# ifdef HAVE_GSSAPI -/* This stuff isn't included solely with SERVER_SUPPORT since some of these - * functions (encryption & the like) get compiled with or without server - * support. - * - * FIXME - They should be in a different file. - */ -# include <netdb.h> -# include "xgssapi.h" -/* We use Kerberos 5 routines to map the GSSAPI credential to a user - name. */ -# include <krb5.h> - -/* We need this to wrap data. */ -static gss_ctx_id_t gcontext; - -static void gserver_authenticate_connection PROTO((void)); - -/* Whether we are already wrapping GSSAPI communication. */ -static int cvs_gssapi_wrapping; - -# ifdef ENCRYPTION -/* Whether to encrypt GSSAPI communication. We use a global variable - like this because we use the same buffer type (gssapi_wrap) to - handle both authentication and encryption, and we don't want - multiple instances of that buffer in the communication stream. */ -int cvs_gssapi_encrypt; -# endif -# endif /* HAVE_GSSAPI */ -#endif /* defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) */ - -#ifdef SERVER_SUPPORT - -#ifdef HAVE_WINSOCK_H -#include <winsock.h> -#endif - -#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_KERBEROS) || defined (HAVE_GSSAPI) -#include <sys/socket.h> -#endif - -#ifdef HAVE_SYSLOG_H -# include <syslog.h> -# ifndef LOG_DAEMON /* for ancient syslogs */ -# define LOG_DAEMON 0 -# endif -#endif - -#ifdef HAVE_KERBEROS -# include <netinet/in.h> -# include <krb.h> -# ifndef HAVE_KRB_GET_ERR_TEXT -# define krb_get_err_text(status) krb_err_txt[status] -# endif - -/* Information we need if we are going to use Kerberos encryption. */ -static C_Block kblock; -static Key_schedule sched; - -#endif - -/* for select */ -#include "xselect.h" - -#ifndef O_NONBLOCK -#define O_NONBLOCK O_NDELAY -#endif - -/* EWOULDBLOCK is not defined by POSIX, but some BSD systems will - return it, rather than EAGAIN, for nonblocking writes. */ -#ifdef EWOULDBLOCK -#define blocking_error(err) ((err) == EWOULDBLOCK || (err) == EAGAIN) -#else -#define blocking_error(err) ((err) == EAGAIN) -#endif - -/* For initgroups(). */ -#if HAVE_INITGROUPS -#include <grp.h> -#endif /* HAVE_INITGROUPS */ - -# ifdef AUTH_SERVER_SUPPORT - -# ifdef HAVE_GETSPNAM -# include <shadow.h> -# endif - -/* The cvs username sent by the client, which might or might not be - the same as the system username the server eventually switches to - run as. CVS_Username gets set iff password authentication is - successful. */ -char *CVS_Username = NULL; - -/* Used to check that same repos is transmitted in pserver auth and in - later CVS protocol. Exported because root.c also uses. */ -static char *Pserver_Repos = NULL; - -/* Should we check for system usernames/passwords? Can be changed by - CVSROOT/config. */ -int system_auth = 1; - -# endif /* AUTH_SERVER_SUPPORT */ - - -/* While processing requests, this buffer accumulates data to be sent to - the client, and then once we are in do_cvs_command, we use it - for all the data to be sent. */ -static struct buffer *buf_to_net; - -/* This buffer is used to read input from the client. */ -static struct buffer *buf_from_net; - -/* - * This is where we stash stuff we are going to use. Format string - * which expects a single directory within it, starting with a slash. - */ -static char *server_temp_dir; - -/* This is the original value of server_temp_dir, before any possible - changes inserted by serve_max_dotdot. */ -static char *orig_server_temp_dir; - -/* Nonzero if we should keep the temp directory around after we exit. */ -static int dont_delete_temp; - -static void server_write_entries PROTO((void)); - -/* All server communication goes through buffer structures. Most of - the buffers are built on top of a file descriptor. This structure - is used as the closure field in a buffer. */ - -struct fd_buffer -{ - /* The file descriptor. */ - int fd; - /* Nonzero if the file descriptor is in blocking mode. */ - int blocking; -}; - -static struct buffer *fd_buffer_initialize - PROTO ((int, int, void (*) (struct buffer *))); -static int fd_buffer_input PROTO((void *, char *, int, int, int *)); -static int fd_buffer_output PROTO((void *, const char *, int, int *)); -static int fd_buffer_flush PROTO((void *)); -static int fd_buffer_block PROTO((void *, int)); -static int fd_buffer_shutdown PROTO((struct buffer *)); - -/* Initialize a buffer built on a file descriptor. FD is the file - descriptor. INPUT is nonzero if this is for input, zero if this is - for output. MEMORY is the function to call when a memory error - occurs. */ - -static struct buffer * -fd_buffer_initialize (fd, input, memory) - int fd; - int input; - void (*memory) PROTO((struct buffer *)); -{ - struct fd_buffer *n; - - n = (struct fd_buffer *) xmalloc (sizeof *n); - n->fd = fd; - n->blocking = 1; - return buf_initialize (input ? fd_buffer_input : NULL, - input ? NULL : fd_buffer_output, - input ? NULL : fd_buffer_flush, - fd_buffer_block, - fd_buffer_shutdown, - memory, - n); -} - -/* The buffer input function for a buffer built on a file descriptor. */ - -static int -fd_buffer_input (closure, data, need, size, got) - void *closure; - char *data; - int need; - int size; - int *got; -{ - struct fd_buffer *fd = (struct fd_buffer *) closure; - int nbytes; - - if (! fd->blocking) - nbytes = read (fd->fd, data, size); - else - { - /* This case is not efficient. Fortunately, I don't think it - ever actually happens. */ - nbytes = read (fd->fd, data, need == 0 ? 1 : need); - } - - if (nbytes > 0) - { - *got = nbytes; - return 0; - } - - *got = 0; - - if (nbytes == 0) - { - /* End of file. This assumes that we are using POSIX or BSD - style nonblocking I/O. On System V we will get a zero - return if there is no data, even when not at EOF. */ - return -1; - } - - /* Some error occurred. */ - - if (blocking_error (errno)) - { - /* Everything's fine, we just didn't get any data. */ - return 0; - } - - return errno; -} - -/* The buffer output function for a buffer built on a file descriptor. */ - -static int -fd_buffer_output (closure, data, have, wrote) - void *closure; - const char *data; - int have; - int *wrote; -{ - struct fd_buffer *fd = (struct fd_buffer *) closure; - - *wrote = 0; - - while (have > 0) - { - int nbytes; - - nbytes = write (fd->fd, data, have); - - if (nbytes <= 0) - { - if (! fd->blocking - && (nbytes == 0 || blocking_error (errno))) - { - /* A nonblocking write failed to write any data. Just - return. */ - return 0; - } - - /* Some sort of error occurred. */ - - if (nbytes == 0) - return EIO; - - return errno; - } - - *wrote += nbytes; - data += nbytes; - have -= nbytes; - } - - return 0; -} - -/* The buffer flush function for a buffer built on a file descriptor. */ - -/*ARGSUSED*/ -static int -fd_buffer_flush (closure) - void *closure; -{ - /* Nothing to do. File descriptors are always flushed. */ - return 0; -} - -/* The buffer block function for a buffer built on a file descriptor. */ - -static int -fd_buffer_block (closure, block) - void *closure; - int block; -{ - struct fd_buffer *fd = (struct fd_buffer *) closure; - int flags; - - flags = fcntl (fd->fd, F_GETFL, 0); - if (flags < 0) - return errno; - - if (block) - flags &= ~O_NONBLOCK; - else - flags |= O_NONBLOCK; - - if (fcntl (fd->fd, F_SETFL, flags) < 0) - return errno; - - fd->blocking = block; - - return 0; -} - -/* The buffer shutdown function for a buffer built on a file descriptor. */ - -static int -fd_buffer_shutdown (buf) - struct buffer *buf; -{ - free (buf->closure); - buf->closure = NULL; - return 0; -} - -/* Populate all of the directories between BASE_DIR and its relative - subdirectory DIR with CVSADM directories. Return 0 for success or - errno value. */ -static int create_adm_p PROTO((char *, char *)); - -static int -create_adm_p (base_dir, dir) - char *base_dir; - char *dir; -{ - char *dir_where_cvsadm_lives, *dir_to_register, *p, *tmp; - int retval, done; - FILE *f; - - if (strcmp (dir, ".") == 0) - return 0; /* nothing to do */ - - /* Allocate some space for our directory-munging string. */ - p = xmalloc (strlen (dir) + 1); - if (p == NULL) - return ENOMEM; - - dir_where_cvsadm_lives = xmalloc (strlen (base_dir) + strlen (dir) + 100); - if (dir_where_cvsadm_lives == NULL) - { - free (p); - return ENOMEM; - } - - /* Allocate some space for the temporary string in which we will - construct filenames. */ - tmp = xmalloc (strlen (base_dir) + strlen (dir) + 100); - if (tmp == NULL) - { - free (p); - free (dir_where_cvsadm_lives); - return ENOMEM; - } - - - /* We make several passes through this loop. On the first pass, - we simply create the CVSADM directory in the deepest directory. - For each subsequent pass, we try to remove the last path - element from DIR, create the CVSADM directory in the remaining - pathname, and register the subdirectory in the newly created - CVSADM directory. */ - - retval = done = 0; - - strcpy (p, dir); - strcpy (dir_where_cvsadm_lives, base_dir); - strcat (dir_where_cvsadm_lives, "/"); - strcat (dir_where_cvsadm_lives, p); - dir_to_register = NULL; - - while (1) - { - /* Create CVSADM. */ - (void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM); - if ((CVS_MKDIR (tmp, 0777) < 0) && (errno != EEXIST)) - { - retval = errno; - goto finish; - } - - /* Create CVSADM_REP. */ - (void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_REP); - if (! isfile (tmp)) - { - /* Use Emptydir as the placeholder until the client sends - us the real value. This code is similar to checkout.c - (emptydir_name), but the code below returns errors - differently. */ - - char *empty; - empty = xmalloc (strlen (current_parsed_root->directory) - + sizeof (CVSROOTADM) - + sizeof (CVSNULLREPOS) - + 3); - if (! empty) - { - retval = ENOMEM; - goto finish; - } - - /* Create the directory name. */ - (void) sprintf (empty, "%s/%s/%s", current_parsed_root->directory, - CVSROOTADM, CVSNULLREPOS); - - /* Create the directory if it doesn't exist. */ - if (! isfile (empty)) - { - mode_t omask; - omask = umask (cvsumask); - if (CVS_MKDIR (empty, 0777) < 0) - { - retval = errno; - free (empty); - goto finish; - } - (void) umask (omask); - } - - f = CVS_FOPEN (tmp, "w"); - if (f == NULL) - { - retval = errno; - free (empty); - goto finish; - } - /* Write the directory name to CVSADM_REP. */ - if (fprintf (f, "%s\n", empty) < 0) - { - retval = errno; - fclose (f); - free (empty); - goto finish; - } - if (fclose (f) == EOF) - { - retval = errno; - free (empty); - goto finish; - } - - /* Clean up after ourselves. */ - free (empty); - } - - /* Create CVSADM_ENT. We open in append mode because we - don't want to clobber an existing Entries file. */ - (void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_ENT); - f = CVS_FOPEN (tmp, "a"); - if (f == NULL) - { - retval = errno; - goto finish; - } - if (fclose (f) == EOF) - { - retval = errno; - goto finish; - } - - if (dir_to_register != NULL) - { - /* FIXME: Yes, this results in duplicate entries in the - Entries.Log file, but it doesn't currently matter. We - might need to change this later on to make sure that we - only write one entry. */ - - Subdir_Register ((List *) NULL, dir_where_cvsadm_lives, - dir_to_register); - } - - if (done) - break; - - dir_to_register = strrchr (p, '/'); - if (dir_to_register == NULL) - { - dir_to_register = p; - strcpy (dir_where_cvsadm_lives, base_dir); - done = 1; - } - else - { - *dir_to_register = '\0'; - dir_to_register++; - strcpy (dir_where_cvsadm_lives, base_dir); - strcat (dir_where_cvsadm_lives, "/"); - strcat (dir_where_cvsadm_lives, p); - } - } - - finish: - free (tmp); - free (dir_where_cvsadm_lives); - free (p); - return retval; -} - -/* - * Make directory DIR, including all intermediate directories if necessary. - * Returns 0 for success or errno code. - */ -static int mkdir_p PROTO((char *)); - -static int -mkdir_p (dir) - char *dir; -{ - char *p; - char *q = xmalloc (strlen (dir) + 1); - int retval; - - if (q == NULL) - return ENOMEM; - - retval = 0; - - /* - * Skip over leading slash if present. We won't bother to try to - * make '/'. - */ - p = dir + 1; - while (1) - { - while (*p != '/' && *p != '\0') - ++p; - if (*p == '/') - { - strncpy (q, dir, p - dir); - q[p - dir] = '\0'; - if (q[p - dir - 1] != '/' && CVS_MKDIR (q, 0777) < 0) - { - int saved_errno = errno; - - if (saved_errno != EEXIST - && ((saved_errno != EACCES && saved_errno != EROFS) - || !isdir (q))) - { - retval = saved_errno; - goto done; - } - } - ++p; - } - else - { - if (CVS_MKDIR (dir, 0777) < 0) - retval = errno; - goto done; - } - } - done: - free (q); - return retval; -} - -/* - * Print the error response for error code STATUS. The caller is - * reponsible for making sure we get back to the command loop without - * any further output occuring. - * Must be called only in contexts where it is OK to send output. - */ -static void -print_error (status) - int status; -{ - char *msg; - char tmpstr[80]; - - buf_output0 (buf_to_net, "error "); - msg = strerror (status); - if (msg == NULL) - { - sprintf (tmpstr, "unknown error %d", status); - msg = tmpstr; - } - buf_output0 (buf_to_net, msg); - buf_append_char (buf_to_net, '\n'); - - buf_flush (buf_to_net, 0); -} - -static int pending_error; -/* - * Malloc'd text for pending error. Each line must start with "E ". The - * last line should not end with a newline. - */ -static char *pending_error_text; - -/* If an error is pending, print it and return 1. If not, return 0. - Must be called only in contexts where it is OK to send output. */ -static int -print_pending_error () -{ - if (pending_error_text) - { - buf_output0 (buf_to_net, pending_error_text); - buf_append_char (buf_to_net, '\n'); - if (pending_error) - print_error (pending_error); - else - buf_output0 (buf_to_net, "error \n"); - - buf_flush (buf_to_net, 0); - - pending_error = 0; - free (pending_error_text); - pending_error_text = NULL; - return 1; - } - else if (pending_error) - { - print_error (pending_error); - pending_error = 0; - return 1; - } - else - return 0; -} - -/* Is an error pending? */ -#define error_pending() (pending_error || pending_error_text) - -static int alloc_pending PROTO ((size_t size)); - -/* Allocate SIZE bytes for pending_error_text and return nonzero - if we could do it. */ -static int -alloc_pending (size) - size_t size; -{ - if (error_pending ()) - /* Probably alloc_pending callers will have already checked for - this case. But we might as well handle it if they don't, I - guess. */ - return 0; - pending_error_text = xmalloc (size); - if (pending_error_text == NULL) - { - pending_error = ENOMEM; - return 0; - } - return 1; -} - -static void serve_is_modified PROTO ((char *)); - -static int supported_response PROTO ((char *)); - -static int -supported_response (name) - char *name; -{ - struct response *rs; - - for (rs = responses; rs->name != NULL; ++rs) - if (strcmp (rs->name, name) == 0) - return rs->status == rs_supported; - error (1, 0, "internal error: testing support for unknown response?"); - /* NOTREACHED */ - return 0; -} - -static void -serve_valid_responses (arg) - char *arg; -{ - char *p = arg; - char *q; - struct response *rs; - do - { - q = strchr (p, ' '); - if (q != NULL) - *q++ = '\0'; - for (rs = responses; rs->name != NULL; ++rs) - { - if (strcmp (rs->name, p) == 0) - break; - } - if (rs->name == NULL) - /* - * It is a response we have never heard of (and thus never - * will want to use). So don't worry about it. - */ - ; - else - rs->status = rs_supported; - p = q; - } while (q != NULL); - for (rs = responses; rs->name != NULL; ++rs) - { - if (rs->status == rs_essential) - { - buf_output0 (buf_to_net, "E response `"); - buf_output0 (buf_to_net, rs->name); - buf_output0 (buf_to_net, "' not supported by client\nerror \n"); - - /* FIXME: This call to buf_flush could conceivably - cause deadlock, as noted in server_cleanup. */ - buf_flush (buf_to_net, 1); - - error_exit (); - } - else if (rs->status == rs_optional) - rs->status = rs_not_supported; - } -} - -static void -serve_root (arg) - char *arg; -{ - char *env; - char *path; - - if (error_pending()) return; - - if (!isabsolute (arg)) - { - if (alloc_pending (80 + strlen (arg))) - sprintf (pending_error_text, - "E Root %s must be an absolute pathname", arg); - return; - } - - /* Sending "Root" twice is illegal. - - The other way to handle a duplicate Root requests would be as a - request to clear out all state and start over as if it was a - new connection. Doing this would cause interoperability - headaches, so it should be a different request, if there is - any reason why such a feature is needed. */ - if (current_parsed_root != NULL) - { - if (alloc_pending (80 + strlen (arg))) - sprintf (pending_error_text, - "E Protocol error: Duplicate Root request, for %s", arg); - return; - } - - /* We need to check :ext: server here, :pserver: checks happen below. */ - if (root_allow_used() && !root_allow_ok (arg) -# ifdef AUTH_SERVER_SUPPORT - && Pserver_Repos == NULL -# endif - ) - { - if (alloc_pending (80 + strlen (arg))) - sprintf (pending_error_text, - "E Bad root %s", arg); - return; - } - -#ifdef AUTH_SERVER_SUPPORT - if (Pserver_Repos != NULL) - { - if (strcmp (Pserver_Repos, arg) != 0) - { - if (alloc_pending (80 + strlen (Pserver_Repos) + strlen (arg))) - /* The explicitness is to aid people who are writing clients. - I don't see how this information could help an - attacker. */ - sprintf (pending_error_text, "\ -E Protocol error: Root says \"%s\" but pserver says \"%s\"", - arg, Pserver_Repos); - return; - } - } -#endif - - current_parsed_root = local_cvsroot (arg); - - /* For pserver, this will already have happened, and the call will do - nothing. But for rsh, we need to do it now. */ - parse_config (current_parsed_root->directory); - - /* Now is a good time to read CVSROOT/options too. */ - parseopts(current_parsed_root->directory); - - path = xmalloc (strlen (current_parsed_root->directory) - + sizeof (CVSROOTADM) - + 2); - if (path == NULL) - { - pending_error = ENOMEM; - return; - } - (void) sprintf (path, "%s/%s", current_parsed_root->directory, CVSROOTADM); - if (!isaccessible (path, R_OK | X_OK)) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (path))) - sprintf (pending_error_text, "E Cannot access %s", path); - pending_error = save_errno; - } - free (path); - -#ifdef HAVE_PUTENV - env = xmalloc (strlen (CVSROOT_ENV) + strlen (current_parsed_root->directory) + 2); - if (env == NULL) - { - pending_error = ENOMEM; - return; - } - (void) sprintf (env, "%s=%s", CVSROOT_ENV, current_parsed_root->directory); - (void) putenv (env); - /* do not free env, as putenv has control of it */ -#endif -} - -static int max_dotdot_limit = 0; - -/* Is this pathname OK to recurse into when we are running as the server? - If not, call error() with a fatal error. */ -void -server_pathname_check (path) - char *path; -{ - /* An absolute pathname is almost surely a path on the *client* machine, - and is unlikely to do us any good here. It also is probably capable - of being a security hole in the anonymous readonly case. */ - if (isabsolute (path)) - /* Giving an error is actually kind of a cop-out, in the sense - that it would be nice for "cvs co -d /foo/bar/baz" to work. - A quick fix in the server would be requiring Max-dotdot of - at least one if pathnames are absolute, and then putting - /abs/foo/bar/baz in the temp dir beside the /d/d/d stuff. - A cleaner fix in the server might be to decouple the - pathnames we pass back to the client from pathnames in our - temp directory (this would also probably remove the need - for Max-dotdot). A fix in the client would have the client - turn it into "cd /foo/bar; cvs co -d baz" (more or less). - This probably has some problems with pathnames which appear - in messages. */ - error (1, 0, "absolute pathname `%s' illegal for server", path); - if (pathname_levels (path) > max_dotdot_limit) - { - /* Similar to the isabsolute case in security implications. */ - error (0, 0, "protocol error: `%s' contains more leading ..", path); - error (1, 0, "than the %d which Max-dotdot specified", - max_dotdot_limit); - } -} - -static int outside_root PROTO ((char *)); - -/* Is file or directory REPOS an absolute pathname within the - current_parsed_root->directory? If yes, return 0. If no, set pending_error - and return 1. */ -static int -outside_root (repos) - char *repos; -{ - size_t repos_len = strlen (repos); - size_t root_len = strlen (current_parsed_root->directory); - - /* isabsolute (repos) should always be true, but - this is a good security precaution regardless. -DRP - */ - if (!isabsolute (repos)) - { - if (alloc_pending (repos_len + 80)) - sprintf (pending_error_text, "\ -E protocol error: %s is not absolute", repos); - return 1; - } - - if (repos_len < root_len - || strncmp (current_parsed_root->directory, repos, root_len) != 0) - { - not_within: - if (alloc_pending (strlen (current_parsed_root->directory) - + strlen (repos) - + 80)) - sprintf (pending_error_text, "\ -E protocol error: directory '%s' not within root '%s'", - repos, current_parsed_root->directory); - return 1; - } - if (repos_len > root_len) - { - if (repos[root_len] != '/') - goto not_within; - if (pathname_levels (repos + root_len + 1) > 0) - goto not_within; - } - return 0; -} - -static int outside_dir PROTO ((char *)); - -/* Is file or directory FILE outside the current directory (that is, does - it contain '/')? If no, return 0. If yes, set pending_error - and return 1. */ -static int -outside_dir (file) - char *file; -{ - if (strchr (file, '/') != NULL) - { - if (alloc_pending (strlen (file) - + 80)) - sprintf (pending_error_text, "\ -E protocol error: directory '%s' not within current directory", - file); - return 1; - } - return 0; -} - -/* - * Add as many directories to the temp directory as the client tells us it - * will use "..", so we never try to access something outside the temp - * directory via "..". - */ -static void -serve_max_dotdot (arg) - char *arg; -{ - int lim = atoi (arg); - int i; - char *p; - - if (lim < 0 || lim > 10000) - return; - p = xmalloc (strlen (server_temp_dir) + 2 * lim + 10); - if (p == NULL) - { - pending_error = ENOMEM; - return; - } - strcpy (p, server_temp_dir); - for (i = 0; i < lim; ++i) - strcat (p, "/d"); - if (server_temp_dir != orig_server_temp_dir) - free (server_temp_dir); - server_temp_dir = p; - max_dotdot_limit = lim; -} - -static char *dir_name; - -static void -dirswitch (dir, repos) - char *dir; - char *repos; -{ - int status; - FILE *f; - size_t dir_len; - - server_write_entries (); - - if (error_pending()) return; - - /* Check for bad directory name. - - FIXME: could/should unify these checks with server_pathname_check - except they need to report errors differently. */ - if (isabsolute (dir)) - { - if (alloc_pending (80 + strlen (dir))) - sprintf (pending_error_text, - "E absolute pathname `%s' illegal for server", dir); - return; - } - if (pathname_levels (dir) > max_dotdot_limit) - { - if (alloc_pending (80 + strlen (dir))) - sprintf (pending_error_text, - "E protocol error: `%s' has too many ..", dir); - return; - } - - dir_len = strlen (dir); - - /* Check for a trailing '/'. This is not ISDIRSEP because \ in the - protocol is an ordinary character, not a directory separator (of - course, it is perhaps unwise to use it in directory names, but that - is another issue). */ - if (dir_len > 0 - && dir[dir_len - 1] == '/') - { - if (alloc_pending (80 + dir_len)) - sprintf (pending_error_text, - "E protocol error: invalid directory syntax in %s", dir); - return; - } - - if (dir_name != NULL) - free (dir_name); - - dir_name = xmalloc (strlen (server_temp_dir) + dir_len + 40); - if (dir_name == NULL) - { - pending_error = ENOMEM; - return; - } - - strcpy (dir_name, server_temp_dir); - strcat (dir_name, "/"); - strcat (dir_name, dir); - - status = mkdir_p (dir_name); - if (status != 0 - && status != EEXIST) - { - if (alloc_pending (80 + strlen (dir_name))) - sprintf (pending_error_text, "E cannot mkdir %s", dir_name); - pending_error = status; - return; - } - - /* We need to create adm directories in all path elements because - we want the server to descend them, even if the client hasn't - sent the appropriate "Argument xxx" command to match the - already-sent "Directory xxx" command. See recurse.c - (start_recursion) for a big discussion of this. */ - - status = create_adm_p (server_temp_dir, dir); - if (status != 0) - { - if (alloc_pending (80 + strlen (dir_name))) - sprintf (pending_error_text, "E cannot create_adm_p %s", dir_name); - pending_error = status; - return; - } - - if ( CVS_CHDIR (dir_name) < 0) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (dir_name))) - sprintf (pending_error_text, "E cannot change to %s", dir_name); - pending_error = save_errno; - return; - } - /* - * This is pretty much like calling Create_Admin, but Create_Admin doesn't - * report errors in the right way for us. - */ - if ((CVS_MKDIR (CVSADM, 0777) < 0) && (errno != EEXIST)) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM))) - sprintf (pending_error_text, - "E cannot mkdir %s/%s", dir_name, CVSADM); - pending_error = save_errno; - return; - } - - /* The following will overwrite the contents of CVSADM_REP. This - is the correct behavior -- mkdir_p may have written a - placeholder value to this file and we need to insert the - correct value. */ - - f = CVS_FOPEN (CVSADM_REP, "w"); - if (f == NULL) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP))) - sprintf (pending_error_text, - "E cannot open %s/%s", dir_name, CVSADM_REP); - pending_error = save_errno; - return; - } - if (fprintf (f, "%s", repos) < 0) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP))) - sprintf (pending_error_text, - "E error writing %s/%s", dir_name, CVSADM_REP); - pending_error = save_errno; - fclose (f); - return; - } - /* Non-remote CVS handles a module representing the entire tree - (e.g., an entry like ``world -a .'') by putting /. at the end - of the Repository file, so we do the same. */ - if (strcmp (dir, ".") == 0 - && current_parsed_root != NULL - && current_parsed_root->directory != NULL - && strcmp (current_parsed_root->directory, repos) == 0) - { - if (fprintf (f, "/.") < 0) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP))) - sprintf (pending_error_text, - "E error writing %s/%s", dir_name, CVSADM_REP); - pending_error = save_errno; - fclose (f); - return; - } - } - if (fprintf (f, "\n") < 0) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP))) - sprintf (pending_error_text, - "E error writing %s/%s", dir_name, CVSADM_REP); - pending_error = save_errno; - fclose (f); - return; - } - if (fclose (f) == EOF) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP))) - sprintf (pending_error_text, - "E error closing %s/%s", dir_name, CVSADM_REP); - pending_error = save_errno; - return; - } - /* We open in append mode because we don't want to clobber an - existing Entries file. */ - f = CVS_FOPEN (CVSADM_ENT, "a"); - if (f == NULL) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (CVSADM_ENT))) - sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT); - pending_error = save_errno; - return; - } - if (fclose (f) == EOF) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (CVSADM_ENT))) - sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT); - pending_error = save_errno; - return; - } -} - -static void -serve_repository (arg) - char *arg; -{ - if (alloc_pending (80)) - strcpy (pending_error_text, - "E Repository request is obsolete; aborted"); - return; -} - -static void -serve_directory (arg) - char *arg; -{ - int status; - char *repos; - - status = buf_read_line (buf_from_net, &repos, (int *) NULL); - if (status == 0) - { - if (!outside_root (repos)) - dirswitch (arg, repos); - free (repos); - } - else if (status == -2) - { - pending_error = ENOMEM; - } - else - { - pending_error_text = xmalloc (80 + strlen (arg)); - if (pending_error_text == NULL) - { - pending_error = ENOMEM; - } - else if (status == -1) - { - sprintf (pending_error_text, - "E end of file reading mode for %s", arg); - } - else - { - sprintf (pending_error_text, - "E error reading mode for %s", arg); - pending_error = status; - } - } -} - -static void -serve_static_directory (arg) - char *arg; -{ - FILE *f; - - if (error_pending ()) return; - - f = CVS_FOPEN (CVSADM_ENTSTAT, "w+"); - if (f == NULL) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (CVSADM_ENTSTAT))) - sprintf (pending_error_text, "E cannot open %s", CVSADM_ENTSTAT); - pending_error = save_errno; - return; - } - if (fclose (f) == EOF) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (CVSADM_ENTSTAT))) - sprintf (pending_error_text, "E cannot close %s", CVSADM_ENTSTAT); - pending_error = save_errno; - return; - } -} - -static void -serve_sticky (arg) - char *arg; -{ - FILE *f; - - if (error_pending ()) return; - - f = CVS_FOPEN (CVSADM_TAG, "w+"); - if (f == NULL) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (CVSADM_TAG))) - sprintf (pending_error_text, "E cannot open %s", CVSADM_TAG); - pending_error = save_errno; - return; - } - if (fprintf (f, "%s\n", arg) < 0) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (CVSADM_TAG))) - sprintf (pending_error_text, "E cannot write to %s", CVSADM_TAG); - pending_error = save_errno; - (void) fclose (f); - return; - } - if (fclose (f) == EOF) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (CVSADM_TAG))) - sprintf (pending_error_text, "E cannot close %s", CVSADM_TAG); - pending_error = save_errno; - return; - } -} - -/* - * Read SIZE bytes from buf_from_net, write them to FILE. - * - * Currently this isn't really used for receiving parts of a file -- - * the file is still sent over in one chunk. But if/when we get - * spiffy in-process gzip support working, perhaps the compressed - * pieces could be sent over as they're ready, if the network is fast - * enough. Or something. - */ -static void -receive_partial_file (size, file) - int size; - int file; -{ - while (size > 0) - { - int status, nread; - char *data; - - status = buf_read_data (buf_from_net, size, &data, &nread); - if (status != 0) - { - if (status == -2) - pending_error = ENOMEM; - else - { - pending_error_text = xmalloc (80); - if (pending_error_text == NULL) - pending_error = ENOMEM; - else if (status == -1) - { - sprintf (pending_error_text, - "E premature end of file from client"); - pending_error = 0; - } - else - { - sprintf (pending_error_text, - "E error reading from client"); - pending_error = status; - } - } - return; - } - - size -= nread; - - while (nread > 0) - { - int nwrote; - - nwrote = write (file, data, nread); - if (nwrote < 0) - { - int save_errno = errno; - if (alloc_pending (40)) - strcpy (pending_error_text, "E unable to write"); - pending_error = save_errno; - - /* Read and discard the file data. */ - while (size > 0) - { - int status, nread; - char *data; - - status = buf_read_data (buf_from_net, size, &data, &nread); - if (status != 0) - return; - size -= nread; - } - - return; - } - nread -= nwrote; - data += nwrote; - } - } -} - -/* Receive SIZE bytes, write to filename FILE. */ -static void -receive_file (size, file, gzipped) - int size; - char *file; - int gzipped; -{ - int fd; - char *arg = file; - - /* Write the file. */ - fd = CVS_OPEN (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (fd < 0) - { - int save_errno = errno; - if (alloc_pending (40 + strlen (arg))) - sprintf (pending_error_text, "E cannot open %s", arg); - pending_error = save_errno; - return; - } - - if (gzipped) - { - /* Using gunzip_and_write isn't really a high-performance - approach, because it keeps the whole thing in memory - (contiguous memory, worse yet). But it seems easier to - code than the alternative (and less vulnerable to subtle - bugs). Given that this feature is mainly for - compatibility, that is the better tradeoff. */ - - int toread = size; - char *filebuf; - char *p; - - filebuf = xmalloc (size); - p = filebuf; - /* If NULL, we still want to read the data and discard it. */ - - while (toread > 0) - { - int status, nread; - char *data; - - status = buf_read_data (buf_from_net, toread, &data, &nread); - if (status != 0) - { - if (status == -2) - pending_error = ENOMEM; - else - { - pending_error_text = xmalloc (80); - if (pending_error_text == NULL) - pending_error = ENOMEM; - else if (status == -1) - { - sprintf (pending_error_text, - "E premature end of file from client"); - pending_error = 0; - } - else - { - sprintf (pending_error_text, - "E error reading from client"); - pending_error = status; - } - } - return; - } - - toread -= nread; - - if (filebuf != NULL) - { - memcpy (p, data, nread); - p += nread; - } - } - if (filebuf == NULL) - { - pending_error = ENOMEM; - goto out; - } - - if (gunzip_and_write (fd, file, (unsigned char *) filebuf, size)) - { - if (alloc_pending (80)) - sprintf (pending_error_text, - "E aborting due to compression error"); - } - free (filebuf); - } - else - receive_partial_file (size, fd); - - if (pending_error_text) - { - char *p = xrealloc (pending_error_text, - strlen (pending_error_text) + strlen (arg) + 30); - if (p) - { - pending_error_text = p; - sprintf (p + strlen (p), ", file %s", arg); - } - /* else original string is supposed to be unchanged */ - } - - out: - if (close (fd) < 0 && !error_pending ()) - { - int save_errno = errno; - if (alloc_pending (40 + strlen (arg))) - sprintf (pending_error_text, "E cannot close %s", arg); - pending_error = save_errno; - return; - } -} - -/* Kopt for the next file sent in Modified or Is-modified. */ -static char *kopt; - -/* Timestamp (Checkin-time) for next file sent in Modified or - Is-modified. */ -static int checkin_time_valid; -static time_t checkin_time; - -static void serve_modified PROTO ((char *)); - -static void -serve_modified (arg) - char *arg; -{ - int size, status; - char *size_text; - char *mode_text; - - int gzipped = 0; - - /* - * This used to return immediately if error_pending () was true. - * However, that fails, because it causes each line of the file to - * be echoed back to the client as an unrecognized command. The - * client isn't reading from the socket, so eventually both - * processes block trying to write to the other. Now, we try to - * read the file if we can. - */ - - status = buf_read_line (buf_from_net, &mode_text, (int *) NULL); - if (status != 0) - { - if (status == -2) - pending_error = ENOMEM; - else - { - pending_error_text = xmalloc (80 + strlen (arg)); - if (pending_error_text == NULL) - pending_error = ENOMEM; - else - { - if (status == -1) - sprintf (pending_error_text, - "E end of file reading mode for %s", arg); - else - { - sprintf (pending_error_text, - "E error reading mode for %s", arg); - pending_error = status; - } - } - } - return; - } - - status = buf_read_line (buf_from_net, &size_text, (int *) NULL); - if (status != 0) - { - if (status == -2) - pending_error = ENOMEM; - else - { - pending_error_text = xmalloc (80 + strlen (arg)); - if (pending_error_text == NULL) - pending_error = ENOMEM; - else - { - if (status == -1) - sprintf (pending_error_text, - "E end of file reading size for %s", arg); - else - { - sprintf (pending_error_text, - "E error reading size for %s", arg); - pending_error = status; - } - } - } - free (mode_text); - return; - } - if (size_text[0] == 'z') - { - gzipped = 1; - size = atoi (size_text + 1); - } - else - size = atoi (size_text); - free (size_text); - - if (error_pending ()) - { - /* Now that we know the size, read and discard the file data. */ - while (size > 0) - { - int status, nread; - char *data; - - status = buf_read_data (buf_from_net, size, &data, &nread); - if (status != 0) - return; - size -= nread; - } - free (mode_text); - return; - } - - if (outside_dir (arg)) - { - free (mode_text); - return; - } - - if (size >= 0) - { - receive_file (size, arg, gzipped); - if (error_pending ()) - { - free (mode_text); - return; - } - } - - if (checkin_time_valid) - { - struct utimbuf t; - - memset (&t, 0, sizeof (t)); - t.modtime = t.actime = checkin_time; - if (utime (arg, &t) < 0) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (arg))) - sprintf (pending_error_text, "E cannot utime %s", arg); - pending_error = save_errno; - free (mode_text); - return; - } - checkin_time_valid = 0; - } - - { - int status = change_mode (arg, mode_text, 0); - free (mode_text); - if (status) - { - if (alloc_pending (40 + strlen (arg))) - sprintf (pending_error_text, - "E cannot change mode for %s", arg); - pending_error = status; - return; - } - } - - /* Make sure that the Entries indicate the right kopt. We probably - could do this even in the non-kopt case and, I think, save a stat() - call in time_stamp_server. But for conservatism I'm leaving the - non-kopt case alone. */ - if (kopt != NULL) - serve_is_modified (arg); -} - - -static void -serve_enable_unchanged (arg) - char *arg; -{ -} - -struct an_entry { - struct an_entry *next; - char *entry; -}; - -static struct an_entry *entries; - -static void serve_unchanged PROTO ((char *)); - -static void -serve_unchanged (arg) - char *arg; -{ - struct an_entry *p; - char *name; - char *cp; - char *timefield; - - if (error_pending ()) return; - - if (outside_dir (arg)) - return; - - /* Rewrite entries file to have `=' in timestamp field. */ - for (p = entries; p != NULL; p = p->next) - { - name = p->entry + 1; - cp = strchr (name, '/'); - if (cp != NULL - && strlen (arg) == cp - name - && strncmp (arg, name, cp - name) == 0) - { - if (!(timefield = strchr (cp + 1, '/')) || *++timefield == '\0') - { - /* We didn't find the record separator or it is followed by - * the end of the string, so just exit. - */ - if (alloc_pending (80)) - sprintf (pending_error_text, - "E Malformed Entry encountered."); - return; - } - /* If the time field is not currently empty, then one of - * serve_modified, serve_is_modified, & serve_unchanged were - * already called for this file. We would like to ignore the - * reinvocation silently or, better yet, exit with an error - * message, but we just avoid the copy-forward and overwrite the - * value from the last invocation instead. See the comment below - * for more. - */ - if (*timefield == '/') - { - /* Copy forward one character. Space was allocated for this - * already in serve_entry(). */ - cp = timefield + strlen (timefield); - cp[1] = '\0'; - while (cp > timefield) - { - *cp = cp[-1]; - --cp; - } - } - /* If *TIMEFIELD wasn't "/", we assume that it was because of - * multiple calls to Is-Modified & Unchanged by the client and - * just overwrite the value from the last call. Technically, we - * should probably either ignore calls after the first or send the - * client an error, since the client/server protocol specification - * specifies that only one call to either Is-Modified or Unchanged - * is allowed, but broken versions of WinCVS & TortoiseCVS rely on - * this behavior. - */ - if (*timefield != '+') - /* Skip this for entries with conflict markers. */ - *timefield = '='; - break; - } - } -} - -static void -serve_is_modified (arg) - char *arg; -{ - struct an_entry *p; - char *name; - char *cp; - char *timefield; - /* Have we found this file in "entries" yet. */ - int found; - - if (error_pending ()) return; - - if (outside_dir (arg)) - return; - - /* Rewrite entries file to have `M' in timestamp field. */ - found = 0; - for (p = entries; p != NULL; p = p->next) - { - name = p->entry + 1; - cp = strchr (name, '/'); - if (cp != NULL - && strlen (arg) == cp - name - && strncmp (arg, name, cp - name) == 0) - { - if (!(timefield = strchr (cp + 1, '/')) || *++timefield == '\0') - { - /* We didn't find the record separator or it is followed by - * the end of the string, so just exit. - */ - if (alloc_pending (80)) - sprintf (pending_error_text, - "E Malformed Entry encountered."); - return; - } - /* If the time field is not currently empty, then one of - * serve_modified, serve_is_modified, & serve_unchanged were - * already called for this file. We would like to ignore the - * reinvocation silently or, better yet, exit with an error - * message, but we just avoid the copy-forward and overwrite the - * value from the last invocation instead. See the comment below - * for more. - */ - if (*timefield == '/') - { - /* Copy forward one character. Space was allocated for this - * already in serve_entry(). */ - cp = timefield + strlen (timefield); - cp[1] = '\0'; - while (cp > timefield) - { - *cp = cp[-1]; - --cp; - } - } - /* If *TIMEFIELD wasn't "/", we assume that it was because of - * multiple calls to Is-Modified & Unchanged by the client and - * just overwrite the value from the last call. Technically, we - * should probably either ignore calls after the first or send the - * client an error, since the client/server protocol specification - * specifies that only one call to either Is-Modified or Unchanged - * is allowed, but broken versions of WinCVS & TortoiseCVS rely on - * this behavior. - */ - if (*timefield != '+') - /* Skip this for entries with conflict markers. */ - *timefield = 'M'; - - if (kopt != NULL) - { - if (alloc_pending (strlen (name) + 80)) - sprintf (pending_error_text, - "E protocol error: both Kopt and Entry for %s", - arg); - free (kopt); - kopt = NULL; - return; - } - found = 1; - break; - } - } - if (!found) - { - /* We got Is-modified but no Entry. Add a dummy entry. - The "D" timestamp is what makes it a dummy. */ - p = (struct an_entry *) xmalloc (sizeof (struct an_entry)); - if (p == NULL) - { - pending_error = ENOMEM; - return; - } - p->entry = xmalloc (strlen (arg) + 80); - if (p->entry == NULL) - { - pending_error = ENOMEM; - free (p); - return; - } - strcpy (p->entry, "/"); - strcat (p->entry, arg); - strcat (p->entry, "//D/"); - if (kopt != NULL) - { - strcat (p->entry, kopt); - free (kopt); - kopt = NULL; - } - strcat (p->entry, "/"); - p->next = entries; - entries = p; - } -} - -static void serve_entry PROTO ((char *)); - -static void -serve_entry (arg) - char *arg; -{ - struct an_entry *p; - char *cp; - int i = 0; - if (error_pending()) return; - - /* Verify that the entry is well-formed. This can avoid problems later. - * At the moment we only check that the Entry contains five slashes in - * approximately the correct locations since some of the code makes - * assumptions about this. - */ - cp = arg; - if (*cp == 'D') cp++; - while (i++ < 5) - { - if (!cp || *cp != '/') - { - if (alloc_pending (80)) - sprintf (pending_error_text, - "E protocol error: Malformed Entry"); - return; - } - cp = strchr (cp + 1, '/'); - } - - p = xmalloc (sizeof (struct an_entry)); - if (p == NULL) - { - pending_error = ENOMEM; - return; - } - /* Leave space for serve_unchanged to write '=' if it wants. */ - cp = xmalloc (strlen (arg) + 2); - if (cp == NULL) - { - free (p); - pending_error = ENOMEM; - return; - } - strcpy (cp, arg); - p->next = entries; - p->entry = cp; - entries = p; -} - -static void serve_kopt PROTO ((char *)); - -static void -serve_kopt (arg) - char *arg; -{ - if (error_pending ()) - return; - - if (kopt != NULL) - { - if (alloc_pending (80 + strlen (arg))) - sprintf (pending_error_text, - "E protocol error: duplicate Kopt request: %s", arg); - return; - } - - /* Do some sanity checks. In particular, that it is not too long. - This lets the rest of the code not worry so much about buffer - overrun attacks. Probably should call RCS_check_kflag here, - but that would mean changing RCS_check_kflag to handle errors - other than via exit(), fprintf(), and such. */ - if (strlen (arg) > 10) - { - if (alloc_pending (80 + strlen (arg))) - sprintf (pending_error_text, - "E protocol error: invalid Kopt request: %s", arg); - return; - } - - kopt = xmalloc (strlen (arg) + 1); - if (kopt == NULL) - { - pending_error = ENOMEM; - return; - } - strcpy (kopt, arg); -} - -static void serve_checkin_time PROTO ((char *)); - -static void -serve_checkin_time (arg) - char *arg; -{ - if (error_pending ()) - return; - - if (checkin_time_valid) - { - if (alloc_pending (80 + strlen (arg))) - sprintf (pending_error_text, - "E protocol error: duplicate Checkin-time request: %s", - arg); - return; - } - - checkin_time = get_date (arg, NULL); - if (checkin_time == (time_t)-1) - { - if (alloc_pending (80 + strlen (arg))) - sprintf (pending_error_text, "E cannot parse date %s", arg); - return; - } - checkin_time_valid = 1; -} - -static void -server_write_entries () -{ - FILE *f; - struct an_entry *p; - struct an_entry *q; - - if (entries == NULL) - return; - - f = NULL; - /* Note that we free all the entries regardless of errors. */ - if (!error_pending ()) - { - /* We open in append mode because we don't want to clobber an - existing Entries file. If we are checking out a module - which explicitly lists more than one file in a particular - directory, then we will wind up calling - server_write_entries for each such file. */ - f = CVS_FOPEN (CVSADM_ENT, "a"); - if (f == NULL) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (CVSADM_ENT))) - sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT); - pending_error = save_errno; - } - } - for (p = entries; p != NULL;) - { - if (!error_pending ()) - { - if (fprintf (f, "%s\n", p->entry) < 0) - { - int save_errno = errno; - if (alloc_pending (80 + strlen(CVSADM_ENT))) - sprintf (pending_error_text, - "E cannot write to %s", CVSADM_ENT); - pending_error = save_errno; - } - } - free (p->entry); - q = p->next; - free (p); - p = q; - } - entries = NULL; - if (f != NULL && fclose (f) == EOF && !error_pending ()) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (CVSADM_ENT))) - sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT); - pending_error = save_errno; - } -} - -struct notify_note { - /* Directory in which this notification happens. xmalloc'd*/ - char *dir; - - /* xmalloc'd. */ - char *filename; - - /* The following three all in one xmalloc'd block, pointed to by TYPE. - Each '\0' terminated. */ - /* "E" or "U". */ - char *type; - /* time+host+dir */ - char *val; - char *watches; - - struct notify_note *next; -}; - -static struct notify_note *notify_list; -/* Used while building list, to point to the last node that already exists. */ -static struct notify_note *last_node; - -static void serve_notify PROTO ((char *)); - -static void -serve_notify (arg) - char *arg; -{ - struct notify_note *new = NULL; - char *data = NULL; - int status; - - if (error_pending ()) return; - - if (outside_dir (arg)) - return; - - if (dir_name == NULL) - goto error; - - new = (struct notify_note *) xmalloc (sizeof (struct notify_note)); - if (new == NULL) - { - pending_error = ENOMEM; - return; - } - new->dir = xmalloc (strlen (dir_name) + 1); - new->filename = xmalloc (strlen (arg) + 1); - if (new->dir == NULL || new->filename == NULL) - { - pending_error = ENOMEM; - if (new->dir != NULL) - free (new->dir); - free (new); - return; - } - strcpy (new->dir, dir_name); - strcpy (new->filename, arg); - - status = buf_read_line (buf_from_net, &data, (int *) NULL); - if (status != 0) - { - if (status == -2) - pending_error = ENOMEM; - else - { - pending_error_text = xmalloc (80 + strlen (arg)); - if (pending_error_text == NULL) - pending_error = ENOMEM; - else - { - if (status == -1) - sprintf (pending_error_text, - "E end of file reading notification for %s", arg); - else - { - sprintf (pending_error_text, - "E error reading notification for %s", arg); - pending_error = status; - } - } - } - free (new->filename); - free (new->dir); - free (new); - } - else - { - char *cp; - - if (!data[0]) - goto error; - - if (strchr (data, '+')) - goto error; - - new->type = data; - if (data[1] != '\t') - goto error; - data[1] = '\0'; - cp = data + 2; - new->val = cp; - cp = strchr (cp, '\t'); - if (cp == NULL) - goto error; - *cp++ = '+'; - cp = strchr (cp, '\t'); - if (cp == NULL) - goto error; - *cp++ = '+'; - cp = strchr (cp, '\t'); - if (cp == NULL) - goto error; - *cp++ = '\0'; - new->watches = cp; - /* If there is another tab, ignore everything after it, - for future expansion. */ - cp = strchr (cp, '\t'); - if (cp != NULL) - { - *cp = '\0'; - } - - new->next = NULL; - - if (last_node == NULL) - { - notify_list = new; - } - else - last_node->next = new; - last_node = new; - } - return; - error: - pending_error = 0; - if (alloc_pending (80)) - strcpy (pending_error_text, - "E Protocol error; misformed Notify request"); - if (data != NULL) - free (data); - if (new != NULL) - { - free (new->filename); - free (new->dir); - free (new); - } - return; -} - -/* Process all the Notify requests that we have stored up. Returns 0 - if successful, if not prints error message (via error()) and - returns negative value. */ -static int -server_notify () -{ - struct notify_note *p; - char *repos; - - while (notify_list != NULL) - { - if ( CVS_CHDIR (notify_list->dir) < 0) - { - error (0, errno, "cannot change to %s", notify_list->dir); - return -1; - } - repos = Name_Repository (NULL, NULL); - - lock_dir_for_write (repos); - - fileattr_startdir (repos); - - notify_do (*notify_list->type, notify_list->filename, getcaller(), - notify_list->val, notify_list->watches, repos); - - buf_output0 (buf_to_net, "Notified "); - { - char *dir = notify_list->dir + strlen (server_temp_dir) + 1; - if (dir[0] == '\0') - buf_append_char (buf_to_net, '.'); - else - buf_output0 (buf_to_net, dir); - buf_append_char (buf_to_net, '/'); - buf_append_char (buf_to_net, '\n'); - } - buf_output0 (buf_to_net, repos); - buf_append_char (buf_to_net, '/'); - buf_output0 (buf_to_net, notify_list->filename); - buf_append_char (buf_to_net, '\n'); - free (repos); - - p = notify_list->next; - free (notify_list->filename); - free (notify_list->dir); - free (notify_list->type); - free (notify_list); - notify_list = p; - - fileattr_write (); - fileattr_free (); - - Lock_Cleanup (); - } - - last_node = NULL; - - /* The code used to call fflush (stdout) here, but that is no - longer necessary. The data is now buffered in buf_to_net, - which will be flushed by the caller, do_cvs_command. */ - - return 0; -} - -static int argument_count; -static char **argument_vector; -static int argument_vector_size; - -static void -serve_argument (arg) - char *arg; -{ - char *p; - - if (error_pending()) return; - - if (argument_count >= 10000) - { - if (alloc_pending (80)) - sprintf (pending_error_text, - "E Protocol error: too many arguments"); - return; - } - - if (argument_vector_size <= argument_count) - { - argument_vector_size *= 2; - argument_vector = - (char **) xrealloc ((char *)argument_vector, - argument_vector_size * sizeof (char *)); - if (argument_vector == NULL) - { - pending_error = ENOMEM; - return; - } - } - p = xmalloc (strlen (arg) + 1); - if (p == NULL) - { - pending_error = ENOMEM; - return; - } - strcpy (p, arg); - argument_vector[argument_count++] = p; -} - -static void -serve_argumentx (arg) - char *arg; -{ - char *p; - - if (error_pending()) return; - - if (argument_count <= 1) - { - if (alloc_pending (80)) - sprintf (pending_error_text, - "E Protocol error: called argumentx without prior call to argument"); - return; - } - - p = argument_vector[argument_count - 1]; - p = xrealloc (p, strlen (p) + 1 + strlen (arg) + 1); - if (p == NULL) - { - pending_error = ENOMEM; - return; - } - strcat (p, "\n"); - strcat (p, arg); - argument_vector[argument_count - 1] = p; -} - -static void -serve_global_option (arg) - char *arg; -{ - if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0') - { - error_return: - if (alloc_pending (strlen (arg) + 80)) - sprintf (pending_error_text, - "E Protocol error: bad global option %s", - arg); - return; - } - switch (arg[1]) - { - case 'l': - error(0, 0, "WARNING: global `-l' option ignored."); - break; - case 'n': - noexec = 1; - logoff = 1; - break; - case 'q': - quiet = 1; - break; - case 'r': - cvswrite = 0; - break; - case 'Q': - really_quiet = 1; - break; - case 't': - trace = 1; - break; - default: - goto error_return; - } -} - -static void -serve_set (arg) - char *arg; -{ - /* FIXME: This sends errors immediately (I think); they should be - put into pending_error. */ - variable_set (arg); -} - -#ifdef ENCRYPTION - -#ifdef HAVE_KERBEROS - -static void -serve_kerberos_encrypt (arg) - char *arg; -{ - /* All future communication with the client will be encrypted. */ - - buf_to_net = krb_encrypt_buffer_initialize (buf_to_net, 0, sched, - kblock, - buf_to_net->memory_error); - buf_from_net = krb_encrypt_buffer_initialize (buf_from_net, 1, sched, - kblock, - buf_from_net->memory_error); -} - -#endif /* HAVE_KERBEROS */ - -#ifdef HAVE_GSSAPI - -static void -serve_gssapi_encrypt (arg) - char *arg; -{ - if (cvs_gssapi_wrapping) - { - /* We're already using a gssapi_wrap buffer for stream - authentication. Flush everything we've output so far, and - turn on encryption for future data. On the input side, we - should only have unwrapped as far as the Gssapi-encrypt - command, so future unwrapping will become encrypted. */ - buf_flush (buf_to_net, 1); - cvs_gssapi_encrypt = 1; - return; - } - - /* All future communication with the client will be encrypted. */ - - cvs_gssapi_encrypt = 1; - - buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0, - gcontext, - buf_to_net->memory_error); - buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1, - gcontext, - buf_from_net->memory_error); - - cvs_gssapi_wrapping = 1; -} - -#endif /* HAVE_GSSAPI */ - -#endif /* ENCRYPTION */ - -#ifdef HAVE_GSSAPI - -static void -serve_gssapi_authenticate (arg) - char *arg; -{ - if (cvs_gssapi_wrapping) - { - /* We're already using a gssapi_wrap buffer for encryption. - That includes authentication, so we don't have to do - anything further. */ - return; - } - - buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0, - gcontext, - buf_to_net->memory_error); - buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1, - gcontext, - buf_from_net->memory_error); - - cvs_gssapi_wrapping = 1; -} - -#endif /* HAVE_GSSAPI */ - - - -#ifdef SERVER_FLOWCONTROL -/* The maximum we'll queue to the remote client before blocking. */ -# ifndef SERVER_HI_WATER -# define SERVER_HI_WATER (2 * 1024 * 1024) -# endif /* SERVER_HI_WATER */ -/* When the buffer drops to this, we restart the child */ -# ifndef SERVER_LO_WATER -# define SERVER_LO_WATER (1 * 1024 * 1024) -# endif /* SERVER_LO_WATER */ -#endif /* SERVER_FLOWCONTROL */ - - - -static void serve_questionable PROTO((char *)); - -static void -serve_questionable (arg) - char *arg; -{ - static int initted; - - if (!initted) - { - /* Pick up ignores from CVSROOTADM_IGNORE, $HOME/.cvsignore on server, - and CVSIGNORE on server. */ - ign_setup (); - initted = 1; - } - - if (dir_name == NULL) - { - buf_output0 (buf_to_net, "E Protocol error: 'Directory' missing"); - return; - } - - if (outside_dir (arg)) - return; - - if (!ign_name (arg)) - { - char *update_dir; - - buf_output (buf_to_net, "M ? ", 4); - update_dir = dir_name + strlen (server_temp_dir) + 1; - if (!(update_dir[0] == '.' && update_dir[1] == '\0')) - { - buf_output0 (buf_to_net, update_dir); - buf_output (buf_to_net, "/", 1); - } - buf_output0 (buf_to_net, arg); - buf_output (buf_to_net, "\n", 1); - } -} - - - -static struct buffer *protocol; - -/* This is the output which we are saving up to send to the server, in the - child process. We will push it through, via the `protocol' buffer, when - we have a complete line. */ -static struct buffer *saved_output; -/* Likewise, but stuff which will go to stderr. */ -static struct buffer *saved_outerr; - -static void -protocol_memory_error (buf) - struct buffer *buf; -{ - error (1, ENOMEM, "Virtual memory exhausted"); -} - -/* - * Process IDs of the subprocess, or negative if that subprocess - * does not exist. - */ -static pid_t command_pid; - -static void -outbuf_memory_error (buf) - struct buffer *buf; -{ - static const char msg[] = "E Fatal server error\n\ -error ENOMEM Virtual memory exhausted.\n"; - if (command_pid > 0) - kill (command_pid, SIGTERM); - - /* - * We have arranged things so that printing this now either will - * be legal, or the "E fatal error" line will get glommed onto the - * end of an existing "E" or "M" response. - */ - - /* If this gives an error, not much we could do. syslog() it? */ - write (STDOUT_FILENO, msg, sizeof (msg) - 1); -#ifdef HAVE_SYSLOG_H - syslog (LOG_DAEMON | LOG_ERR, "virtual memory exhausted"); -#endif - error_exit (); -} - -static void -input_memory_error (buf) - struct buffer *buf; -{ - outbuf_memory_error (buf); -} - - - -/* If command is legal, return 1. - * Else if command is illegal and croak_on_illegal is set, then die. - * Else just return 0 to indicate that command is illegal. - */ -static int -check_command_legal_p (cmd_name) - char *cmd_name; -{ - /* Right now, only pserver notices illegal commands -- namely, - * write attempts by a read-only user. Therefore, if CVS_Username - * is not set, this just returns 1, because CVS_Username unset - * means pserver is not active. - */ -#ifdef AUTH_SERVER_SUPPORT - if (CVS_Username == NULL) - return 1; - - if (lookup_command_attribute (cmd_name) & CVS_CMD_MODIFIES_REPOSITORY) - { - /* This command has the potential to modify the repository, so - * we check if the user have permission to do that. - * - * (Only relevant for remote users -- local users can do - * whatever normal Unix file permissions allow them to do.) - * - * The decision method: - * - * If $CVSROOT/CVSADMROOT_READERS exists and user is listed - * in it, then read-only access for user. - * - * Or if $CVSROOT/CVSADMROOT_WRITERS exists and user NOT - * listed in it, then also read-only access for user. - * - * Else read-write access for user. - */ - - char *linebuf = NULL; - int num_red = 0; - size_t linebuf_len = 0; - char *fname; - size_t flen; - FILE *fp; - int found_it = 0; - - /* else */ - flen = strlen (current_parsed_root->directory) - + strlen (CVSROOTADM) - + strlen (CVSROOTADM_READERS) - + 3; - - fname = xmalloc (flen); - (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory, - CVSROOTADM, CVSROOTADM_READERS); - - fp = fopen (fname, "r"); - - if (fp == NULL) - { - if (!existence_error (errno)) - { - /* Need to deny access, so that attackers can't fool - us with some sort of denial of service attack. */ - error (0, errno, "cannot open %s", fname); - free (fname); - return 0; - } - } - else /* successfully opened readers file */ - { - while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0) - { - /* Hmmm, is it worth importing my own readline - library into CVS? It takes care of chopping - leading and trailing whitespace, "#" comments, and - newlines automatically when so requested. Would - save some code here... -kff */ - - /* Chop newline by hand, for strcmp()'s sake. */ - if (num_red > 0 && linebuf[num_red - 1] == '\n') - linebuf[num_red - 1] = '\0'; - - if (strcmp (linebuf, CVS_Username) == 0) - goto handle_illegal; - } - if (num_red < 0 && !feof (fp)) - error (0, errno, "cannot read %s", fname); - - /* If not listed specifically as a reader, then this user - has write access by default unless writers are also - specified in a file . */ - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", fname); - } - free (fname); - - /* Now check the writers file. */ - - flen = strlen (current_parsed_root->directory) - + strlen (CVSROOTADM) - + strlen (CVSROOTADM_WRITERS) - + 3; - - fname = xmalloc (flen); - (void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory, - CVSROOTADM, CVSROOTADM_WRITERS); - - fp = fopen (fname, "r"); - - if (fp == NULL) - { - if (linebuf) - free (linebuf); - if (existence_error (errno)) - { - /* Writers file does not exist, so everyone is a writer, - by default. */ - free (fname); - return 1; - } - else - { - /* Need to deny access, so that attackers can't fool - us with some sort of denial of service attack. */ - error (0, errno, "cannot read %s", fname); - free (fname); - return 0; - } - } - - found_it = 0; - while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0) - { - /* Chop newline by hand, for strcmp()'s sake. */ - if (num_red > 0 && linebuf[num_red - 1] == '\n') - linebuf[num_red - 1] = '\0'; - - if (strcmp (linebuf, CVS_Username) == 0) - { - found_it = 1; - break; - } - } - if (num_red < 0 && !feof (fp)) - error (0, errno, "cannot read %s", fname); - - if (found_it) - { - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", fname); - if (linebuf) - free (linebuf); - free (fname); - return 1; - } - else /* writers file exists, but this user not listed in it */ - { - handle_illegal: - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", fname); - if (linebuf) - free (linebuf); - free (fname); - return 0; - } - } -#endif /* AUTH_SERVER_SUPPORT */ - - /* If ever reach end of this function, command must be legal. */ - return 1; -} - - - -/* Execute COMMAND in a subprocess with the approriate funky things done. */ - -static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain; -#ifdef SUNOS_KLUDGE -static int max_command_fd; -#endif - -#ifdef SERVER_FLOWCONTROL -static int flowcontrol_pipe[2]; -#endif /* SERVER_FLOWCONTROL */ - - - -/* - * Set buffer FD to non-blocking I/O. Returns 0 for success or errno - * code. - */ -int -set_nonblock_fd (fd) - int fd; -{ - int flags; - - flags = fcntl (fd, F_GETFL, 0); - if (flags < 0) - return errno; - if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0) - return errno; - return 0; -} - - - -/* - * Set buffer FD to blocking I/O. Returns 0 for success or errno code. - */ -int -set_block_fd (fd) - int fd; -{ - int flags; - - flags = fcntl (fd, F_GETFL, 0); - if (flags < 0) - return errno; - if (fcntl (fd, F_SETFL, flags & ~O_NONBLOCK) < 0) - return errno; - return 0; -} - - - -static void -do_cvs_command (cmd_name, command) - char *cmd_name; - int (*command) PROTO((int argc, char **argv)); -{ - /* - * The following file descriptors are set to -1 if that file is not - * currently open. - */ - - /* Data on these pipes is a series of '\n'-terminated lines. */ - int stdout_pipe[2]; - int stderr_pipe[2]; - - /* - * Data on this pipe is a series of counted (see buf_send_counted) - * packets. Each packet must be processed atomically (i.e. not - * interleaved with data from stdout_pipe or stderr_pipe). - */ - int protocol_pipe[2]; - - int dev_null_fd = -1; - - int errs = 0; - - command_pid = -1; - stdout_pipe[0] = -1; - stdout_pipe[1] = -1; - stderr_pipe[0] = -1; - stderr_pipe[1] = -1; - protocol_pipe[0] = -1; - protocol_pipe[1] = -1; - - server_write_entries (); - - if (print_pending_error ()) - goto free_args_and_return; - - /* Global `cvs_cmd_name' is probably "server" right now -- only - serve_export() sets it to anything else. So we will use local - parameter `cmd_name' to determine if this command is legal for - this user. */ - if (!check_command_legal_p (cmd_name)) - { - buf_output0 (buf_to_net, "E "); - buf_output0 (buf_to_net, program_name); - buf_output0 (buf_to_net, " [server aborted]: \""); - buf_output0 (buf_to_net, cmd_name); - buf_output0 (buf_to_net, "\" requires write access to the repository\n\ -error \n"); - goto free_args_and_return; - } - cvs_cmd_name = cmd_name; - - (void) server_notify (); - - /* - * We use a child process which actually does the operation. This - * is so we can intercept its standard output. Even if all of CVS - * were written to go to some special routine instead of writing - * to stdout or stderr, we would still need to do the same thing - * for the RCS commands. - */ - - if (pipe (stdout_pipe) < 0) - { - buf_output0 (buf_to_net, "E pipe failed\n"); - print_error (errno); - goto error_exit; - } - if (pipe (stderr_pipe) < 0) - { - buf_output0 (buf_to_net, "E pipe failed\n"); - print_error (errno); - goto error_exit; - } - if (pipe (protocol_pipe) < 0) - { - buf_output0 (buf_to_net, "E pipe failed\n"); - print_error (errno); - goto error_exit; - } -#ifdef SERVER_FLOWCONTROL - if (pipe (flowcontrol_pipe) < 0) - { - buf_output0 (buf_to_net, "E pipe failed\n"); - print_error (errno); - goto error_exit; - } - set_nonblock_fd (flowcontrol_pipe[0]); - set_nonblock_fd (flowcontrol_pipe[1]); -#endif /* SERVER_FLOWCONTROL */ - - dev_null_fd = CVS_OPEN (DEVNULL, O_RDONLY); - if (dev_null_fd < 0) - { - buf_output0 (buf_to_net, "E open /dev/null failed\n"); - print_error (errno); - goto error_exit; - } - - /* We shouldn't have any partial lines from cvs_output and - cvs_outerr, but we handle them here in case there is a bug. */ - /* FIXME: appending a newline, rather than using "MT" as we - do in the child process, is probably not really a very good - way to "handle" them. */ - if (! buf_empty_p (saved_output)) - { - buf_append_char (saved_output, '\n'); - buf_copy_lines (buf_to_net, saved_output, 'M'); - } - if (! buf_empty_p (saved_outerr)) - { - buf_append_char (saved_outerr, '\n'); - buf_copy_lines (buf_to_net, saved_outerr, 'E'); - } - - /* Flush out any pending data. */ - buf_flush (buf_to_net, 1); - - /* Don't use vfork; we're not going to exec(). */ - command_pid = fork (); - if (command_pid < 0) - { - buf_output0 (buf_to_net, "E fork failed\n"); - print_error (errno); - goto error_exit; - } - if (command_pid == 0) - { - int exitstatus; - - /* Since we're in the child, and the parent is going to take - care of packaging up our error messages, we can clear this - flag. */ - error_use_protocol = 0; - - protocol = fd_buffer_initialize (protocol_pipe[1], 0, - protocol_memory_error); - - /* At this point we should no longer be using buf_to_net and - buf_from_net. Instead, everything should go through - protocol. */ - if (buf_to_net != NULL) - { - buf_free (buf_to_net); - buf_to_net = NULL; - } - if (buf_from_net != NULL) - { - buf_free (buf_from_net); - buf_from_net = NULL; - } - - /* These were originally set up to use outbuf_memory_error. - Since we're now in the child, we should use the simpler - protocol_memory_error function. */ - saved_output->memory_error = protocol_memory_error; - saved_outerr->memory_error = protocol_memory_error; - - if (dup2 (dev_null_fd, STDIN_FILENO) < 0) - error (1, errno, "can't set up pipes"); - if (dup2 (stdout_pipe[1], STDOUT_FILENO) < 0) - error (1, errno, "can't set up pipes"); - if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0) - error (1, errno, "can't set up pipes"); - close (dev_null_fd); - close (stdout_pipe[0]); - close (stdout_pipe[1]); - close (stderr_pipe[0]); - close (stderr_pipe[1]); - close (protocol_pipe[0]); - close_on_exec (protocol_pipe[1]); -#ifdef SERVER_FLOWCONTROL - close_on_exec (flowcontrol_pipe[0]); - close (flowcontrol_pipe[1]); -#endif /* SERVER_FLOWCONTROL */ - - /* - * Set this in .bashrc if you want to give yourself time to attach - * to the subprocess with a debugger. - */ - if (getenv ("CVS_SERVER_SLEEP")) - { - int secs = atoi (getenv ("CVS_SERVER_SLEEP")); - sleep (secs); - } - - exitstatus = (*command) (argument_count, argument_vector); - - /* Output any partial lines. If the client doesn't support - "MT", we go ahead and just tack on a newline since the - protocol doesn't support anything better. */ - if (! buf_empty_p (saved_output)) - { - buf_output0 (protocol, supported_response ("MT") ? "MT text " : "M "); - buf_append_buffer (protocol, saved_output); - buf_output (protocol, "\n", 1); - buf_send_counted (protocol); - } - /* For now we just discard partial lines on stderr. I suspect - that CVS can't write such lines unless there is a bug. */ - - buf_free (protocol); - - /* Close the pipes explicitly in order to send an EOF to the parent, - * then wait for the parent to close the flow control pipe. This - * avoids a race condition where a child which dumped more than the - * high water mark into the pipes could complete its job and exit, - * leaving the parent process to attempt to write a stop byte to the - * closed flow control pipe, which earned the parent a SIGPIPE, which - * it normally only expects on the network pipe and that causes it to - * exit with an error message, rather than the SIGCHILD that it knows - * how to handle correctly. - */ - /* Let exit() close STDIN - it's from /dev/null anyhow. */ - fclose (stderr); - fclose (stdout); - close (protocol_pipe[1]); -#ifdef SERVER_FLOWCONTROL - { - char junk; - set_block_fd (flowcontrol_pipe[0]); - while (read (flowcontrol_pipe[0], &junk, 1) > 0); - } - /* FIXME: No point in printing an error message with error(), - * as STDERR is already closed, but perhaps this could be syslogged? - */ -#endif - - rcs_cleanup (); - Lock_Cleanup (); - /* Don't call server_cleanup - the parent will handle that. */ -#ifdef SYSTEM_CLEANUP - /* Hook for OS-specific behavior, for example socket subsystems on - NT and OS2 or dealing with windows and arguments on Mac. */ - SYSTEM_CLEANUP (); -#endif - exit (exitstatus); - } - - /* OK, sit around getting all the input from the child. */ - { - struct buffer *stdoutbuf = NULL; - struct buffer *stderrbuf = NULL; - struct buffer *protocol_inbuf = NULL; - int err_exit = 0; - /* Number of file descriptors to check in select (). */ - int num_to_check; - int count_needed = 1; -#ifdef SERVER_FLOWCONTROL - int have_flowcontrolled = 0; -#endif /* SERVER_FLOWCONTROL */ - - FD_ZERO (&command_fds_to_drain.fds); - num_to_check = stdout_pipe[0]; - FD_SET (stdout_pipe[0], &command_fds_to_drain.fds); - if (stderr_pipe[0] > num_to_check) - num_to_check = stderr_pipe[0]; - FD_SET (stderr_pipe[0], &command_fds_to_drain.fds); - if (protocol_pipe[0] > num_to_check) - num_to_check = protocol_pipe[0]; - FD_SET (protocol_pipe[0], &command_fds_to_drain.fds); - if (STDOUT_FILENO > num_to_check) - num_to_check = STDOUT_FILENO; -#ifdef SUNOS_KLUDGE - max_command_fd = num_to_check; -#endif - /* - * File descriptors are numbered from 0, so num_to_check needs to - * be one larger than the largest descriptor. - */ - ++num_to_check; - if (num_to_check > FD_SETSIZE) - { - buf_output0 (buf_to_net, - "E internal error: FD_SETSIZE not big enough.\n\ -error \n"); - goto error_exit; - } - - stdoutbuf = fd_buffer_initialize (stdout_pipe[0], 1, - input_memory_error); - - stderrbuf = fd_buffer_initialize (stderr_pipe[0], 1, - input_memory_error); - - protocol_inbuf = fd_buffer_initialize (protocol_pipe[0], 1, - input_memory_error); - - set_nonblock (buf_to_net); - set_nonblock (stdoutbuf); - set_nonblock (stderrbuf); - set_nonblock (protocol_inbuf); - - if (close (stdout_pipe[1]) < 0) - { - buf_output0 (buf_to_net, "E close failed\n"); - print_error (errno); - err_exit = 1; - goto child_finish; - } - stdout_pipe[1] = -1; - - if (close (stderr_pipe[1]) < 0) - { - buf_output0 (buf_to_net, "E close failed\n"); - print_error (errno); - err_exit = 1; - goto child_finish; - } - stderr_pipe[1] = -1; - - if (close (protocol_pipe[1]) < 0) - { - buf_output0 (buf_to_net, "E close failed\n"); - print_error (errno); - err_exit = 1; - goto child_finish; - } - protocol_pipe[1] = -1; - -#ifdef SERVER_FLOWCONTROL - if (close (flowcontrol_pipe[0]) < 0) - { - buf_output0 (buf_to_net, "E close failed\n"); - print_error (errno); - err_exit = 1; - goto child_finish; - } - flowcontrol_pipe[0] = -1; -#endif /* SERVER_FLOWCONTROL */ - - if (close (dev_null_fd) < 0) - { - buf_output0 (buf_to_net, "E close failed\n"); - print_error (errno); - dev_null_fd = -1; /* Do not try to close it again. */ - err_exit = 1; - goto child_finish; - } - dev_null_fd = -1; - - while (stdout_pipe[0] >= 0 - || stderr_pipe[0] >= 0 - || protocol_pipe[0] >= 0 - || count_needed <= 0) - { - fd_set readfds; - fd_set writefds; - int numfds; - struct timeval *timeout_ptr; - struct timeval timeout; -#ifdef SERVER_FLOWCONTROL - int bufmemsize; - - /* - * See if we are swamping the remote client and filling our VM. - * Tell child to hold off if we do. - */ - bufmemsize = buf_count_mem (buf_to_net); - if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER)) - { - if (write(flowcontrol_pipe[1], "S", 1) == 1) - have_flowcontrolled = 1; - } - else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER)) - { - if (write(flowcontrol_pipe[1], "G", 1) == 1) - have_flowcontrolled = 0; - } -#endif /* SERVER_FLOWCONTROL */ - - FD_ZERO (&readfds); - FD_ZERO (&writefds); - - if (count_needed <= 0) - { - /* there is data pending which was read from the protocol pipe - * so don't block if we don't find any data - */ - timeout.tv_sec = 0; - timeout.tv_usec = 0; - timeout_ptr = &timeout; - } - else - { - /* block indefinately */ - timeout_ptr = NULL; - } - - if (! buf_empty_p (buf_to_net)) - FD_SET (STDOUT_FILENO, &writefds); - - if (stdout_pipe[0] >= 0) - { - FD_SET (stdout_pipe[0], &readfds); - } - if (stderr_pipe[0] >= 0) - { - FD_SET (stderr_pipe[0], &readfds); - } - if (protocol_pipe[0] >= 0) - { - FD_SET (protocol_pipe[0], &readfds); - } - - /* This process of selecting on the three pipes means that - we might not get output in the same order in which it - was written, thus producing the well-known - "out-of-order" bug. If the child process uses - cvs_output and cvs_outerr, it will send everything on - the protocol_pipe and avoid this problem, so the - solution is to use cvs_output and cvs_outerr in the - child process. */ - do { - /* This used to select on exceptions too, but as far - as I know there was never any reason to do that and - SCO doesn't let you select on exceptions on pipes. */ - numfds = select (num_to_check, &readfds, &writefds, - (fd_set *)0, timeout_ptr); - if (numfds < 0 - && errno != EINTR) - { - buf_output0 (buf_to_net, "E select failed\n"); - print_error (errno); - err_exit = 1; - goto child_finish; - } - } while (numfds < 0); - - if (numfds == 0) - { - FD_ZERO (&readfds); - FD_ZERO (&writefds); - } - - if (FD_ISSET (STDOUT_FILENO, &writefds)) - { - /* What should we do with errors? syslog() them? */ - buf_send_output (buf_to_net); - } - - if (protocol_pipe[0] >= 0 - && (FD_ISSET (protocol_pipe[0], &readfds))) - { - int status; - int count_read; - - status = buf_input_data (protocol_inbuf, &count_read); - - if (status == -1) - { - close (protocol_pipe[0]); - protocol_pipe[0] = -1; - } - else if (status > 0) - { - buf_output0 (buf_to_net, "E buf_input_data failed\n"); - print_error (status); - err_exit = 1; - goto child_finish; - } - - /* - * We only call buf_copy_counted if we have read - * enough bytes to make it worthwhile. This saves us - * from continually recounting the amount of data we - * have. - */ - count_needed -= count_read; - } - /* this is still part of the protocol pipe procedure, but it is - * outside the above conditional so that unprocessed data can be - * left in the buffer and stderr/stdout can be read when a flush - * signal is received and control can return here without passing - * through the select code and maybe blocking - */ - while (count_needed <= 0) - { - int special = 0; - - count_needed = buf_copy_counted (buf_to_net, - protocol_inbuf, - &special); - - /* What should we do with errors? syslog() them? */ - buf_send_output (buf_to_net); - - /* If SPECIAL got set to <0, it means that the child - * wants us to flush the pipe & maybe stderr or stdout. - * - * After that we break to read stderr & stdout again before - * going back to the protocol pipe - * - * Upon breaking, count_needed = 0, so the next pass will only - * perform a non-blocking select before returning here to finish - * processing data we already read from the protocol buffer - */ - if (special == -1) - { - cvs_flushout(); - break; - } - if (special == -2) - { - /* If the client supports the 'F' command, we send it. */ - if (supported_response ("F")) - { - buf_append_char (buf_to_net, 'F'); - buf_append_char (buf_to_net, '\n'); - } - cvs_flusherr (); - break; - } - } - - if (stdout_pipe[0] >= 0 - && (FD_ISSET (stdout_pipe[0], &readfds))) - { - int status; - - status = buf_input_data (stdoutbuf, (int *) NULL); - - buf_copy_lines (buf_to_net, stdoutbuf, 'M'); - - if (status == -1) - { - close (stdout_pipe[0]); - stdout_pipe[0] = -1; - } - else if (status > 0) - { - buf_output0 (buf_to_net, "E buf_input_data failed\n"); - print_error (status); - err_exit = 1; - goto child_finish; - } - - /* What should we do with errors? syslog() them? */ - buf_send_output (buf_to_net); - } - - if (stderr_pipe[0] >= 0 - && (FD_ISSET (stderr_pipe[0], &readfds))) - { - int status; - - status = buf_input_data (stderrbuf, (int *) NULL); - - buf_copy_lines (buf_to_net, stderrbuf, 'E'); - - if (status == -1) - { - close (stderr_pipe[0]); - stderr_pipe[0] = -1; - } - else if (status > 0) - { - buf_output0 (buf_to_net, "E buf_input_data failed\n"); - print_error (status); - err_exit = 1; - goto child_finish; - } - - /* What should we do with errors? syslog() them? */ - buf_send_output (buf_to_net); - } - } - - /* - * OK, we've gotten EOF on all the pipes. If there is - * anything left on stdoutbuf or stderrbuf (this could only - * happen if there was no trailing newline), send it over. - */ - if (! buf_empty_p (stdoutbuf)) - { - buf_append_char (stdoutbuf, '\n'); - buf_copy_lines (buf_to_net, stdoutbuf, 'M'); - } - if (! buf_empty_p (stderrbuf)) - { - buf_append_char (stderrbuf, '\n'); - buf_copy_lines (buf_to_net, stderrbuf, 'E'); - } - if (! buf_empty_p (protocol_inbuf)) - buf_output0 (buf_to_net, - "E Protocol error: uncounted data discarded\n"); - -#ifdef SERVER_FLOWCONTROL - close (flowcontrol_pipe[1]); - flowcontrol_pipe[1] = -1; -#endif /* SERVER_FLOWCONTROL */ - - errs = 0; - - while (command_pid > 0) - { - int status; - pid_t waited_pid; - waited_pid = waitpid (command_pid, &status, 0); - if (waited_pid < 0) - { - /* - * Intentionally ignoring EINTR. Other errors - * "can't happen". - */ - continue; - } - - if (WIFEXITED (status)) - errs += WEXITSTATUS (status); - else - { - int sig = WTERMSIG (status); - char buf[50]; - /* - * This is really evil, because signals might be numbered - * differently on the two systems. We should be using - * signal names (either of the "Terminated" or the "SIGTERM" - * variety). But cvs doesn't currently use libiberty...we - * could roll our own.... FIXME. - */ - buf_output0 (buf_to_net, "E Terminated with fatal signal "); - sprintf (buf, "%d\n", sig); - buf_output0 (buf_to_net, buf); - - /* Test for a core dump. */ - if (WCOREDUMP (status)) - { - buf_output0 (buf_to_net, "E Core dumped; preserving "); - buf_output0 (buf_to_net, orig_server_temp_dir); - buf_output0 (buf_to_net, " on server.\n\ -E CVS locks may need cleaning up.\n"); - dont_delete_temp = 1; - } - ++errs; - } - if (waited_pid == command_pid) - command_pid = -1; - } - - child_finish: - /* - * OK, we've waited for the child. By now all CVS locks are free - * and it's OK to block on the network. - */ - set_block (buf_to_net); - buf_flush (buf_to_net, 1); - if (protocol_inbuf) - { - buf_shutdown (protocol_inbuf); - buf_free (protocol_inbuf); - protocol_inbuf = NULL; - } - if (stderrbuf) - { - buf_shutdown (stderrbuf); - buf_free (stderrbuf); - stderrbuf = NULL; - } - if (stdoutbuf) - { - buf_shutdown (stdoutbuf); - buf_free (stdoutbuf); - stdoutbuf = NULL; - } - if (err_exit) - goto error_exit; - } - - if (errs) - /* We will have printed an error message already. */ - buf_output0 (buf_to_net, "error \n"); - else - buf_output0 (buf_to_net, "ok\n"); - goto free_args_and_return; - - error_exit: - if (command_pid > 0) - kill (command_pid, SIGTERM); - - while (command_pid > 0) - { - pid_t waited_pid; - waited_pid = waitpid (command_pid, (int *) 0, 0); - if (waited_pid < 0 && errno == EINTR) - continue; - if (waited_pid == command_pid) - command_pid = -1; - } - - if (dev_null_fd >= 0) - close (dev_null_fd); - close (protocol_pipe[0]); - close (protocol_pipe[1]); - close (stderr_pipe[0]); - close (stderr_pipe[1]); - close (stdout_pipe[0]); - close (stdout_pipe[1]); -#ifdef SERVER_FLOWCONTROL - close (flowcontrol_pipe[0]); - close (flowcontrol_pipe[1]); -#endif /* SERVER_FLOWCONTROL */ - - free_args_and_return: - /* Now free the arguments. */ - { - /* argument_vector[0] is a dummy argument, we don't mess with it. */ - char **cp; - for (cp = argument_vector + 1; - cp < argument_vector + argument_count; - ++cp) - free (*cp); - - argument_count = 1; - } - - /* Flush out any data not yet sent. */ - set_block (buf_to_net); - buf_flush (buf_to_net, 1); - - return; -} - -#ifdef SERVER_FLOWCONTROL -/* - * Called by the child at convenient points in the server's execution for - * the server child to block.. ie: when it has no locks active. - */ -void -server_pause_check() -{ - int paused = 0; - char buf[1]; - - while (read (flowcontrol_pipe[0], buf, 1) == 1) - { - if (*buf == 'S') /* Stop */ - paused = 1; - else if (*buf == 'G') /* Go */ - paused = 0; - else - return; /* ??? */ - } - while (paused) { - int numfds, numtocheck; - fd_set fds; - - FD_ZERO (&fds); - FD_SET (flowcontrol_pipe[0], &fds); - numtocheck = flowcontrol_pipe[0] + 1; - - do { - numfds = select (numtocheck, &fds, (fd_set *)0, - (fd_set *)0, (struct timeval *)NULL); - if (numfds < 0 - && errno != EINTR) - { - buf_output0 (buf_to_net, "E select failed\n"); - print_error (errno); - return; - } - } while (numfds < 0); - - if (FD_ISSET (flowcontrol_pipe[0], &fds)) - { - int got; - - while ((got = read (flowcontrol_pipe[0], buf, 1)) == 1) - { - if (*buf == 'S') /* Stop */ - paused = 1; - else if (*buf == 'G') /* Go */ - paused = 0; - else - return; /* ??? */ - } - - /* This assumes that we are using BSD or POSIX nonblocking - I/O. System V nonblocking I/O returns zero if there is - nothing to read. */ - if (got == 0) - error (1, 0, "flow control EOF"); - if (got < 0 && ! blocking_error (errno)) - { - error (1, errno, "flow control read failed"); - } - } - } -} -#endif /* SERVER_FLOWCONTROL */ - -/* This variable commented in server.h. */ -char *server_dir = NULL; - - - -static void output_dir PROTO((const char *, const char *)); - -static void -output_dir (update_dir, repository) - const char *update_dir; - const char *repository; -{ - if (server_dir != NULL) - { - buf_output0 (protocol, server_dir); - buf_output0 (protocol, "/"); - } - if (update_dir[0] == '\0') - buf_output0 (protocol, "."); - else - buf_output0 (protocol, update_dir); - buf_output0 (protocol, "/\n"); - buf_output0 (protocol, repository); - buf_output0 (protocol, "/"); -} - - - -/* - * Entries line that we are squirreling away to send to the client when - * we are ready. - */ -static char *entries_line; - -/* - * File which has been Scratch_File'd, we are squirreling away that fact - * to inform the client when we are ready. - */ -static char *scratched_file; - -/* - * The scratched_file will need to be removed as well as having its entry - * removed. - */ -static int kill_scratched_file; - - - -void -server_register (name, version, timestamp, options, tag, date, conflict) - const char *name; - const char *version; - const char *timestamp; - const char *options; - const char *tag; - const char *date; - const char *conflict; -{ - int len; - - if (options == NULL) - options = ""; - - if (trace) - { - (void) fprintf (stderr, - "%s-> server_register(%s, %s, %s, %s, %s, %s, %s)\n", - CLIENT_SERVER_STR, - name, version, timestamp ? timestamp : "", options, - tag ? tag : "", date ? date : "", - conflict ? conflict : ""); - } - - if (entries_line != NULL) - { - /* - * If CVS decides to Register it more than once (which happens - * on "cvs update foo/foo.c" where foo and foo.c are already - * checked out), use the last of the entries lines Register'd. - */ - free (entries_line); - } - - /* - * I have reports of Scratch_Entry and Register both happening, in - * two different cases. Using the last one which happens is almost - * surely correct; I haven't tracked down why they both happen (or - * even verified that they are for the same file). - */ - if (scratched_file != NULL) - { - free (scratched_file); - scratched_file = NULL; - } - - len = (strlen (name) + strlen (version) + strlen (options) + 80); - if (tag) - len += strlen (tag); - if (date) - len += strlen (date); - - entries_line = xmalloc (len); - sprintf (entries_line, "/%s/%s/", name, version); - if (conflict != NULL) - { - strcat (entries_line, "+="); - } - strcat (entries_line, "/"); - strcat (entries_line, options); - strcat (entries_line, "/"); - if (tag != NULL) - { - strcat (entries_line, "T"); - strcat (entries_line, tag); - } - else if (date != NULL) - { - strcat (entries_line, "D"); - strcat (entries_line, date); - } -} - - - -void -server_scratch (fname) - const char *fname; -{ - /* - * I have reports of Scratch_Entry and Register both happening, in - * two different cases. Using the last one which happens is almost - * surely correct; I haven't tracked down why they both happen (or - * even verified that they are for the same file). - * - * Don't know if this is what whoever wrote the above comment was - * talking about, but this can happen in the case where a join - * removes a file - the call to Register puts the '-vers' into the - * Entries file after the file is removed - */ - if (entries_line != NULL) - { - free (entries_line); - entries_line = NULL; - } - - if (scratched_file != NULL) - { - buf_output0 (protocol, - "E CVS server internal error: duplicate Scratch_Entry\n"); - buf_send_counted (protocol); - return; - } - scratched_file = xstrdup (fname); - kill_scratched_file = 1; -} - -void -server_scratch_entry_only () -{ - kill_scratched_file = 0; -} - -/* Print a new entries line, from a previous server_register. */ -static void -new_entries_line () -{ - if (entries_line) - { - buf_output0 (protocol, entries_line); - buf_output (protocol, "\n", 1); - } - else - /* Return the error message as the Entries line. */ - buf_output0 (protocol, - "CVS server internal error: Register missing\n"); - free (entries_line); - entries_line = NULL; -} - - -static void -serve_ci (arg) - char *arg; -{ - do_cvs_command ("commit", commit); -} - -static void -checked_in_response (file, update_dir, repository) - char *file; - char *update_dir; - char *repository; -{ - if (supported_response ("Mode")) - { - struct stat sb; - char *mode_string; - - if ( CVS_STAT (file, &sb) < 0) - { - /* Not clear to me why the file would fail to exist, but it - was happening somewhere in the testsuite. */ - if (!existence_error (errno)) - error (0, errno, "cannot stat %s", file); - } - else - { - buf_output0 (protocol, "Mode "); - mode_string = mode_to_string (sb.st_mode); - buf_output0 (protocol, mode_string); - buf_output0 (protocol, "\n"); - free (mode_string); - } - } - - buf_output0 (protocol, "Checked-in "); - output_dir (update_dir, repository); - buf_output0 (protocol, file); - buf_output (protocol, "\n", 1); - new_entries_line (); -} - -void -server_checked_in (file, update_dir, repository) - const char *file; - const char *update_dir; - const char *repository; -{ - assert (file); - assert (update_dir); - assert (repository); - - if (noexec) - return; - if (scratched_file != NULL && entries_line == NULL) - { - /* - * This happens if we are now doing a "cvs remove" after a previous - * "cvs add" (without a "cvs ci" in between). - */ - buf_output0 (protocol, "Remove-entry "); - output_dir (update_dir, repository); - buf_output0 (protocol, file); - buf_output (protocol, "\n", 1); - free (scratched_file); - scratched_file = NULL; - } - else - { - checked_in_response (file, update_dir, repository); - } - buf_send_counted (protocol); -} - -void -server_update_entries (file, update_dir, repository, updated) - const char *file; - const char *update_dir; - const char *repository; - enum server_updated_arg4 updated; -{ - if (noexec) - return; - if (updated == SERVER_UPDATED) - checked_in_response (file, update_dir, repository); - else - { - if (!supported_response ("New-entry")) - return; - buf_output0 (protocol, "New-entry "); - output_dir (update_dir, repository); - buf_output0 (protocol, file); - buf_output (protocol, "\n", 1); - new_entries_line (); - } - - buf_send_counted (protocol); -} - -static void -serve_update (arg) - char *arg; -{ - do_cvs_command ("update", update); -} - -static void -serve_diff (arg) - char *arg; -{ - do_cvs_command ("diff", diff); -} - -static void -serve_log (arg) - char *arg; -{ - do_cvs_command ("log", cvslog); -} - -static void -serve_rlog (arg) - char *arg; -{ - do_cvs_command ("rlog", cvslog); -} - -static void -serve_add (arg) - char *arg; -{ - do_cvs_command ("add", add); -} - -static void -serve_remove (arg) - char *arg; -{ - do_cvs_command ("remove", cvsremove); -} - -static void -serve_status (arg) - char *arg; -{ - do_cvs_command ("status", cvsstatus); -} - -static void -serve_rdiff (arg) - char *arg; -{ - do_cvs_command ("rdiff", patch); -} - -static void -serve_tag (arg) - char *arg; -{ - do_cvs_command ("tag", cvstag); -} - -static void -serve_rtag (arg) - char *arg; -{ - do_cvs_command ("rtag", cvstag); -} - -static void -serve_import (arg) - char *arg; -{ - do_cvs_command ("import", import); -} - -static void -serve_admin (arg) - char *arg; -{ - do_cvs_command ("admin", admin); -} - -static void -serve_history (arg) - char *arg; -{ - do_cvs_command ("history", history); -} - -static void -serve_release (arg) - char *arg; -{ - do_cvs_command ("release", release); -} - -static void serve_watch_on PROTO ((char *)); - -static void -serve_watch_on (arg) - char *arg; -{ - do_cvs_command ("watch", watch_on); -} - -static void serve_watch_off PROTO ((char *)); - -static void -serve_watch_off (arg) - char *arg; -{ - do_cvs_command ("watch", watch_off); -} - -static void serve_watch_add PROTO ((char *)); - -static void -serve_watch_add (arg) - char *arg; -{ - do_cvs_command ("watch", watch_add); -} - -static void serve_watch_remove PROTO ((char *)); - -static void -serve_watch_remove (arg) - char *arg; -{ - do_cvs_command ("watch", watch_remove); -} - -static void serve_watchers PROTO ((char *)); - -static void -serve_watchers (arg) - char *arg; -{ - do_cvs_command ("watchers", watchers); -} - -static void serve_editors PROTO ((char *)); - -static void -serve_editors (arg) - char *arg; -{ - do_cvs_command ("editors", editors); -} - -static void serve_noop PROTO ((char *)); - -static void -serve_noop (arg) - char *arg; -{ - - server_write_entries (); - if (!print_pending_error ()) - { - (void) server_notify (); - buf_output0 (buf_to_net, "ok\n"); - } - buf_flush (buf_to_net, 1); -} - -static void serve_version PROTO ((char *)); - -static void -serve_version (arg) - char *arg; -{ - do_cvs_command ("version", version); -} - -static void serve_init PROTO ((char *)); - -static void -serve_init (arg) - char *arg; -{ - if (alloc_pending (80 + strlen (arg))) - sprintf (pending_error_text, "E init may not be run remotely"); - - if (print_pending_error ()) - return; -} - -static void serve_annotate PROTO ((char *)); - -static void -serve_annotate (arg) - char *arg; -{ - do_cvs_command ("annotate", annotate); -} - -static void serve_rannotate PROTO ((char *)); - -static void -serve_rannotate (arg) - char *arg; -{ - do_cvs_command ("rannotate", annotate); -} - -static void -serve_co (arg) - char *arg; -{ - char *tempdir; - int status; - - if (print_pending_error ()) - return; - - if (!isdir (CVSADM)) - { - /* - * The client has not sent a "Repository" line. Check out - * into a pristine directory. - */ - tempdir = xmalloc (strlen (server_temp_dir) + 80); - if (tempdir == NULL) - { - buf_output0 (buf_to_net, "E Out of memory\n"); - return; - } - strcpy (tempdir, server_temp_dir); - strcat (tempdir, "/checkout-dir"); - status = mkdir_p (tempdir); - if (status != 0 && status != EEXIST) - { - buf_output0 (buf_to_net, "E Cannot create "); - buf_output0 (buf_to_net, tempdir); - buf_append_char (buf_to_net, '\n'); - print_error (errno); - free (tempdir); - return; - } - - if ( CVS_CHDIR (tempdir) < 0) - { - buf_output0 (buf_to_net, "E Cannot change to directory "); - buf_output0 (buf_to_net, tempdir); - buf_append_char (buf_to_net, '\n'); - print_error (errno); - free (tempdir); - return; - } - free (tempdir); - } - - /* Compensate for server_export()'s setting of cvs_cmd_name. - * - * [It probably doesn't matter if do_cvs_command() gets "export" - * or "checkout", but we ought to be accurate where possible.] - */ - do_cvs_command ((strcmp (cvs_cmd_name, "export") == 0) ? - "export" : "checkout", - checkout); -} - -static void -serve_export (arg) - char *arg; -{ - /* Tell checkout() to behave like export not checkout. */ - cvs_cmd_name = "export"; - serve_co (arg); -} - - - -void -server_copy_file (file, update_dir, repository, newfile) - const char *file; - const char *update_dir; - const char *repository; - const char *newfile; -{ - /* At least for now, our practice is to have the server enforce - noexec for the repository and the client enforce it for the - working directory. This might want more thought, and/or - documentation in cvsclient.texi (other responses do it - differently). */ - - if (!supported_response ("Copy-file")) - return; - buf_output0 (protocol, "Copy-file "); - output_dir (update_dir, repository); - buf_output0 (protocol, file); - buf_output0 (protocol, "\n"); - buf_output0 (protocol, newfile); - buf_output0 (protocol, "\n"); -} - -/* See server.h for description. */ - -void -server_modtime (finfo, vers_ts) - struct file_info *finfo; - Vers_TS *vers_ts; -{ - char date[MAXDATELEN]; - char outdate[MAXDATELEN]; - - assert (vers_ts->vn_rcs != NULL); - - if (!supported_response ("Mod-time")) - return; - - if (RCS_getrevtime (finfo->rcs, vers_ts->vn_rcs, date, 0) == (time_t) -1) - /* FIXME? should we be printing some kind of warning? For one - thing I'm not 100% sure whether this happens in non-error - circumstances. */ - return; - date_to_internet (outdate, date); - buf_output0 (protocol, "Mod-time "); - buf_output0 (protocol, outdate); - buf_output0 (protocol, "\n"); -} - -/* See server.h for description. */ - -#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__) -/* Need to prototype because mode_t might be smaller than int. */ -void -server_updated ( - struct file_info *finfo, - Vers_TS *vers, - enum server_updated_arg4 updated, - mode_t mode, - unsigned char *checksum, - struct buffer *filebuf) -#else -void -server_updated (finfo, vers, updated, mode, checksum, filebuf) - struct file_info *finfo; - Vers_TS *vers; - enum server_updated_arg4 updated; - mode_t mode; - unsigned char *checksum; - struct buffer *filebuf; -#endif -{ - if (noexec) - { - /* Hmm, maybe if we did the same thing for entries_file, we - could get rid of the kludges in server_register and - server_scratch which refrain from warning if both - Scratch_Entry and Register get called. Maybe. */ - if (scratched_file) - { - free (scratched_file); - scratched_file = NULL; - } - buf_send_counted (protocol); - return; - } - - if (entries_line != NULL && scratched_file == NULL) - { - FILE *f; - struct buffer_data *list, *last; - unsigned long size; - char size_text[80]; - - /* The contents of the file will be in one of filebuf, - list/last, or here. */ - unsigned char *file; - size_t file_allocated; - size_t file_used; - - if (filebuf != NULL) - { - size = buf_length (filebuf); - if (mode == (mode_t) -1) - error (1, 0, "\ -CVS server internal error: no mode in server_updated"); - } - else - { - struct stat sb; - - if ( CVS_STAT (finfo->file, &sb) < 0) - { - if (existence_error (errno)) - { - /* If we have a sticky tag for a branch on which - the file is dead, and cvs update the directory, - it gets a T_CHECKOUT but no file. So in this - case just forget the whole thing. */ - free (entries_line); - entries_line = NULL; - goto done; - } - error (1, errno, "reading %s", finfo->fullname); - } - size = sb.st_size; - if (mode == (mode_t) -1) - { - /* FIXME: When we check out files the umask of the - server (set in .bashrc if rsh is in use) affects - what mode we send, and it shouldn't. */ - mode = sb.st_mode; - } - } - - if (checksum != NULL) - { - static int checksum_supported = -1; - - if (checksum_supported == -1) - { - checksum_supported = supported_response ("Checksum"); - } - - if (checksum_supported) - { - int i; - char buf[3]; - - buf_output0 (protocol, "Checksum "); - for (i = 0; i < 16; i++) - { - sprintf (buf, "%02x", (unsigned int) checksum[i]); - buf_output0 (protocol, buf); - } - buf_append_char (protocol, '\n'); - } - } - - if (updated == SERVER_UPDATED) - { - Node *node; - - if (!(supported_response ("Created") - && supported_response ("Update-existing"))) - buf_output0 (protocol, "Updated "); - else - { - assert (vers != NULL); - if (vers->ts_user == NULL) - buf_output0 (protocol, "Created "); - else - buf_output0 (protocol, "Update-existing "); - } - - /* Now munge the entries to say that the file is unmodified, - in case we end up processing it again (e.g. modules3-6 - in the testsuite). */ - node = findnode_fn (finfo->entries, finfo->file); - assert (node != NULL); - if (node != NULL) - { - Entnode *entnode = node->data; - free (entnode->timestamp); - entnode->timestamp = xstrdup ("="); - } - } - else if (updated == SERVER_MERGED) - buf_output0 (protocol, "Merged "); - else if (updated == SERVER_PATCHED) - buf_output0 (protocol, "Patched "); - else if (updated == SERVER_RCS_DIFF) - buf_output0 (protocol, "Rcs-diff "); - else - abort (); - output_dir (finfo->update_dir, finfo->repository); - buf_output0 (protocol, finfo->file); - buf_output (protocol, "\n", 1); - - new_entries_line (); - - { - char *mode_string; - - mode_string = mode_to_string (mode); - buf_output0 (protocol, mode_string); - buf_output0 (protocol, "\n"); - free (mode_string); - } - - list = last = NULL; - - file = NULL; - file_allocated = 0; - file_used = 0; - - if (size > 0) - { - /* Throughout this section we use binary mode to read the - file we are sending. The client handles any line ending - translation if necessary. */ - - if (file_gzip_level - /* - * For really tiny files, the gzip process startup - * time will outweigh the compression savings. This - * might be computable somehow; using 100 here is just - * a first approximation. - */ - && size > 100) - { - /* Basing this routine on read_and_gzip is not a - high-performance approach. But it seems easier - to code than the alternative (and less - vulnerable to subtle bugs). Given that this feature - is mainly for compatibility, that is the better - tradeoff. */ - - int fd; - - /* Callers must avoid passing us a buffer if - file_gzip_level is set. We could handle this case, - but it's not worth it since this case never arises - with a current client and server. */ - if (filebuf != NULL) - error (1, 0, "\ -CVS server internal error: unhandled case in server_updated"); - - fd = CVS_OPEN (finfo->file, O_RDONLY | OPEN_BINARY, 0); - if (fd < 0) - error (1, errno, "reading %s", finfo->fullname); - if (read_and_gzip (fd, finfo->fullname, &file, - &file_allocated, &file_used, - file_gzip_level)) - error (1, 0, "aborting due to compression error"); - size = file_used; - if (close (fd) < 0) - error (1, errno, "reading %s", finfo->fullname); - /* Prepending length with "z" is flag for using gzip here. */ - buf_output0 (protocol, "z"); - } - else if (filebuf == NULL) - { - long status; - - f = CVS_FOPEN (finfo->file, "rb"); - if (f == NULL) - error (1, errno, "reading %s", finfo->fullname); - status = buf_read_file (f, size, &list, &last); - if (status == -2) - (*protocol->memory_error) (protocol); - else if (status != 0) - error (1, ferror (f) ? errno : 0, "reading %s", - finfo->fullname); - if (fclose (f) == EOF) - error (1, errno, "reading %s", finfo->fullname); - } - } - - sprintf (size_text, "%lu\n", size); - buf_output0 (protocol, size_text); - - if (file != NULL) - { - buf_output (protocol, (char *) file, file_used); - free (file); - file = NULL; - } - else if (filebuf == NULL) - buf_append_data (protocol, list, last); - else - { - buf_append_buffer (protocol, filebuf); - } - /* Note we only send a newline here if the file ended with one. */ - - /* - * Avoid using up too much disk space for temporary files. - * A file which does not exist indicates that the file is up-to-date, - * which is now the case. If this is SERVER_MERGED, the file is - * not up-to-date, and we indicate that by leaving the file there. - * I'm thinking of cases like "cvs update foo/foo.c foo". - */ - if ((updated == SERVER_UPDATED - || updated == SERVER_PATCHED - || updated == SERVER_RCS_DIFF) - && filebuf == NULL - /* But if we are joining, we'll need the file when we call - join_file. */ - && !joining ()) - { - if (CVS_UNLINK (finfo->file) < 0) - error (0, errno, "cannot remove temp file for %s", - finfo->fullname); - } - } - else if (scratched_file != NULL && entries_line == NULL) - { - if (strcmp (scratched_file, finfo->file) != 0) - error (1, 0, - "CVS server internal error: `%s' vs. `%s' scratched", - scratched_file, - finfo->file); - free (scratched_file); - scratched_file = NULL; - - if (kill_scratched_file) - buf_output0 (protocol, "Removed "); - else - buf_output0 (protocol, "Remove-entry "); - output_dir (finfo->update_dir, finfo->repository); - buf_output0 (protocol, finfo->file); - buf_output (protocol, "\n", 1); - /* keep the vers structure up to date in case we do a join - * - if there isn't a file, it can't very well have a version number, can it? - * - * we do it here on the assumption that since we just told the client - * to remove the file/entry, it will, and we want to remember that. - * If it fails, that's the client's problem, not ours - */ - if (vers && vers->vn_user != NULL) - { - free (vers->vn_user); - vers->vn_user = NULL; - } - if (vers && vers->ts_user != NULL) - { - free (vers->ts_user); - vers->ts_user = NULL; - } - } - else if (scratched_file == NULL && entries_line == NULL) - { - /* - * This can happen with death support if we were processing - * a dead file in a checkout. - */ - } - else - error (1, 0, - "CVS server internal error: Register *and* Scratch_Entry.\n"); - buf_send_counted (protocol); - done:; -} - -/* Return whether we should send patches in RCS format. */ - -int -server_use_rcs_diff () -{ - return supported_response ("Rcs-diff"); -} - - - -void -server_set_entstat (update_dir, repository) - const char *update_dir; - const char *repository; -{ - static int set_static_supported = -1; - if (set_static_supported == -1) - set_static_supported = supported_response ("Set-static-directory"); - if (!set_static_supported) return; - - buf_output0 (protocol, "Set-static-directory "); - output_dir (update_dir, repository); - buf_output0 (protocol, "\n"); - buf_send_counted (protocol); -} - - - -void -server_clear_entstat (update_dir, repository) - const char *update_dir; - const char *repository; -{ - static int clear_static_supported = -1; - if (clear_static_supported == -1) - clear_static_supported = supported_response ("Clear-static-directory"); - if (!clear_static_supported) return; - - if (noexec) - return; - - buf_output0 (protocol, "Clear-static-directory "); - output_dir (update_dir, repository); - buf_output0 (protocol, "\n"); - buf_send_counted (protocol); -} - - - -void -server_set_sticky (update_dir, repository, tag, date, nonbranch) - const char *update_dir; - const char *repository; - const char *tag; - const char *date; - int nonbranch; -{ - static int set_sticky_supported = -1; - - assert (update_dir != NULL); - - if (set_sticky_supported == -1) - set_sticky_supported = supported_response ("Set-sticky"); - if (!set_sticky_supported) return; - - if (noexec) - return; - - if (tag == NULL && date == NULL) - { - buf_output0 (protocol, "Clear-sticky "); - output_dir (update_dir, repository); - buf_output0 (protocol, "\n"); - } - else - { - buf_output0 (protocol, "Set-sticky "); - output_dir (update_dir, repository); - buf_output0 (protocol, "\n"); - if (tag != NULL) - { - if (nonbranch) - buf_output0 (protocol, "N"); - else - buf_output0 (protocol, "T"); - buf_output0 (protocol, tag); - } - else - { - buf_output0 (protocol, "D"); - buf_output0 (protocol, date); - } - buf_output0 (protocol, "\n"); - } - buf_send_counted (protocol); -} - -struct template_proc_data -{ - const char *update_dir; - const char *repository; -}; - -/* Here as a static until we get around to fixing Parse_Info to pass along - a void * for it. */ -static struct template_proc_data *tpd; - -static int -template_proc PROTO((const char *repository, const char *template)); - -static int -template_proc (repository, template) - const char *repository; - const char *template; -{ - FILE *fp; - char buf[1024]; - size_t n; - struct stat sb; - struct template_proc_data *data = tpd; - - if (!supported_response ("Template")) - /* Might want to warn the user that the rcsinfo feature won't work. */ - return 0; - buf_output0 (protocol, "Template "); - output_dir (data->update_dir, data->repository); - buf_output0 (protocol, "\n"); - - fp = CVS_FOPEN (template, "rb"); - if (fp == NULL) - { - error (0, errno, "Couldn't open rcsinfo template file %s", template); - return 1; - } - if (fstat (fileno (fp), &sb) < 0) - { - error (0, errno, "cannot stat rcsinfo template file %s", template); - return 1; - } - sprintf (buf, "%ld\n", (long) sb.st_size); - buf_output0 (protocol, buf); - while (!feof (fp)) - { - n = fread (buf, 1, sizeof buf, fp); - buf_output (protocol, buf, n); - if (ferror (fp)) - { - error (0, errno, "cannot read rcsinfo template file %s", template); - (void) fclose (fp); - return 1; - } - } - buf_send_counted (protocol); - if (fclose (fp) < 0) - error (0, errno, "cannot close rcsinfo template file %s", template); - return 0; -} - - - -void -server_template (update_dir, repository) - const char *update_dir; - const char *repository; -{ - struct template_proc_data data; - data.update_dir = update_dir; - data.repository = repository; - tpd = &data; - (void) Parse_Info (CVSROOTADM_RCSINFO, repository, template_proc, 1); -} - - - -static void -serve_gzip_contents (arg) - char *arg; -{ - int level; - level = atoi (arg); - if (level == 0) - level = 6; - file_gzip_level = level; -} - -static void -serve_gzip_stream (arg) - char *arg; -{ - int level; - level = atoi (arg); - if (level == 0) - level = 6; - - /* All further communication with the client will be compressed. */ - - buf_to_net = compress_buffer_initialize (buf_to_net, 0, level, - buf_to_net->memory_error); - buf_from_net = compress_buffer_initialize (buf_from_net, 1, level, - buf_from_net->memory_error); -} - -/* Tell the client about RCS options set in CVSROOT/cvswrappers. */ -static void -serve_wrapper_sendme_rcs_options (arg) - char *arg; -{ - /* Actually, this is kind of sdrawkcab-ssa: the client wants - * verbatim lines from a cvswrappers file, but the server has - * already parsed the cvswrappers file into the wrap_list struct. - * Therefore, the server loops over wrap_list, unparsing each - * entry before sending it. - */ - char *wrapper_line = NULL; - - wrap_setup (); - - for (wrap_unparse_rcs_options (&wrapper_line, 1); - wrapper_line; - wrap_unparse_rcs_options (&wrapper_line, 0)) - { - buf_output0 (buf_to_net, "Wrapper-rcsOption "); - buf_output0 (buf_to_net, wrapper_line); - buf_output0 (buf_to_net, "\012");; - free (wrapper_line); - } - - buf_output0 (buf_to_net, "ok\012"); - - /* The client is waiting for us, so we better send the data now. */ - buf_flush (buf_to_net, 1); -} - - -static void -serve_ignore (arg) - char *arg; -{ - /* - * Just ignore this command. This is used to support the - * update-patches command, which is not a real command, but a signal - * to the client that update will accept the -u argument. - */ -} - -static int -expand_proc (argc, argv, where, mwhere, mfile, shorten, - local_specified, omodule, msg) - int argc; - char **argv; - char *where; - char *mwhere; - char *mfile; - int shorten; - int local_specified; - char *omodule; - char *msg; -{ - int i; - char *dir = argv[0]; - - /* If mwhere has been specified, the thing we're expanding is a - module -- just return its name so the client will ask for the - right thing later. If it is an alias or a real directory, - mwhere will not be set, so send out the appropriate - expansion. */ - - if (mwhere != NULL) - { - buf_output0 (buf_to_net, "Module-expansion "); - if (server_dir != NULL) - { - buf_output0 (buf_to_net, server_dir); - buf_output0 (buf_to_net, "/"); - } - buf_output0 (buf_to_net, mwhere); - if (mfile != NULL) - { - buf_append_char (buf_to_net, '/'); - buf_output0 (buf_to_net, mfile); - } - buf_append_char (buf_to_net, '\n'); - } - else - { - /* We may not need to do this anymore -- check the definition - of aliases before removing */ - if (argc == 1) - { - buf_output0 (buf_to_net, "Module-expansion "); - if (server_dir != NULL) - { - buf_output0 (buf_to_net, server_dir); - buf_output0 (buf_to_net, "/"); - } - buf_output0 (buf_to_net, dir); - buf_append_char (buf_to_net, '\n'); - } - else - { - for (i = 1; i < argc; ++i) - { - buf_output0 (buf_to_net, "Module-expansion "); - if (server_dir != NULL) - { - buf_output0 (buf_to_net, server_dir); - buf_output0 (buf_to_net, "/"); - } - buf_output0 (buf_to_net, dir); - buf_append_char (buf_to_net, '/'); - buf_output0 (buf_to_net, argv[i]); - buf_append_char (buf_to_net, '\n'); - } - } - } - return 0; -} - -static void -serve_expand_modules (arg) - char *arg; -{ - int i; - int err; - DBM *db; - err = 0; - - db = open_module (); - for (i = 1; i < argument_count; i++) - err += do_module (db, argument_vector[i], - CHECKOUT, "Updating", expand_proc, - NULL, 0, 0, 0, 0, - (char *) NULL); - close_module (db); - { - /* argument_vector[0] is a dummy argument, we don't mess with it. */ - char **cp; - for (cp = argument_vector + 1; - cp < argument_vector + argument_count; - ++cp) - free (*cp); - - argument_count = 1; - } - if (err) - /* We will have printed an error message already. */ - buf_output0 (buf_to_net, "error \n"); - else - buf_output0 (buf_to_net, "ok\n"); - - /* The client is waiting for the module expansions, so we must - send the output now. */ - buf_flush (buf_to_net, 1); -} - - - -static void serve_valid_requests PROTO((char *arg)); - -#endif /* SERVER_SUPPORT */ -#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) - -/* - * Parts of this table are shared with the client code, - * but the client doesn't need to know about the handler - * functions. - */ - -struct request requests[] = -{ -#ifdef SERVER_SUPPORT -#define REQ_LINE(n, f, s) {n, f, s} -#else -#define REQ_LINE(n, f, s) {n, s} -#endif - - REQ_LINE("Root", serve_root, RQ_ESSENTIAL | RQ_ROOTLESS), - REQ_LINE("Valid-responses", serve_valid_responses, - RQ_ESSENTIAL | RQ_ROOTLESS), - REQ_LINE("valid-requests", serve_valid_requests, - RQ_ESSENTIAL | RQ_ROOTLESS), - REQ_LINE("Repository", serve_repository, 0), - REQ_LINE("Directory", serve_directory, RQ_ESSENTIAL), - REQ_LINE("Max-dotdot", serve_max_dotdot, 0), - REQ_LINE("Static-directory", serve_static_directory, 0), - REQ_LINE("Sticky", serve_sticky, 0), - REQ_LINE("Entry", serve_entry, RQ_ESSENTIAL), - REQ_LINE("Kopt", serve_kopt, 0), - REQ_LINE("Checkin-time", serve_checkin_time, 0), - REQ_LINE("Modified", serve_modified, RQ_ESSENTIAL), - REQ_LINE("Is-modified", serve_is_modified, 0), - REQ_LINE("Empty-conflicts", serve_noop, 0), - - /* The client must send this request to interoperate with CVS 1.5 - through 1.9 servers. The server must support it (although it can - be and is a noop) to interoperate with CVS 1.5 to 1.9 clients. */ - REQ_LINE("UseUnchanged", serve_enable_unchanged, RQ_ENABLEME | RQ_ROOTLESS), - - REQ_LINE("Unchanged", serve_unchanged, RQ_ESSENTIAL), - REQ_LINE("Notify", serve_notify, 0), - REQ_LINE("Questionable", serve_questionable, 0), - REQ_LINE("Argument", serve_argument, RQ_ESSENTIAL), - REQ_LINE("Argumentx", serve_argumentx, RQ_ESSENTIAL), - REQ_LINE("Global_option", serve_global_option, RQ_ROOTLESS), - REQ_LINE("Gzip-stream", serve_gzip_stream, 0), - REQ_LINE("wrapper-sendme-rcsOptions", - serve_wrapper_sendme_rcs_options, - 0), - REQ_LINE("Set", serve_set, RQ_ROOTLESS), -#ifdef ENCRYPTION -# ifdef HAVE_KERBEROS - REQ_LINE("Kerberos-encrypt", serve_kerberos_encrypt, 0), -# endif -# ifdef HAVE_GSSAPI - REQ_LINE("Gssapi-encrypt", serve_gssapi_encrypt, 0), -# endif -#endif -#ifdef HAVE_GSSAPI - REQ_LINE("Gssapi-authenticate", serve_gssapi_authenticate, 0), -#endif - REQ_LINE("expand-modules", serve_expand_modules, 0), - REQ_LINE("ci", serve_ci, RQ_ESSENTIAL), - REQ_LINE("co", serve_co, RQ_ESSENTIAL), - REQ_LINE("update", serve_update, RQ_ESSENTIAL), - REQ_LINE("diff", serve_diff, 0), - REQ_LINE("log", serve_log, 0), - REQ_LINE("rlog", serve_rlog, 0), - REQ_LINE("add", serve_add, 0), - REQ_LINE("remove", serve_remove, 0), - REQ_LINE("update-patches", serve_ignore, 0), - REQ_LINE("gzip-file-contents", serve_gzip_contents, 0), - REQ_LINE("status", serve_status, 0), - REQ_LINE("rdiff", serve_rdiff, 0), - REQ_LINE("tag", serve_tag, 0), - REQ_LINE("rtag", serve_rtag, 0), - REQ_LINE("import", serve_import, 0), - REQ_LINE("admin", serve_admin, 0), - REQ_LINE("export", serve_export, 0), - REQ_LINE("history", serve_history, 0), - REQ_LINE("release", serve_release, 0), - REQ_LINE("watch-on", serve_watch_on, 0), - REQ_LINE("watch-off", serve_watch_off, 0), - REQ_LINE("watch-add", serve_watch_add, 0), - REQ_LINE("watch-remove", serve_watch_remove, 0), - REQ_LINE("watchers", serve_watchers, 0), - REQ_LINE("editors", serve_editors, 0), - REQ_LINE("init", serve_init, RQ_ROOTLESS), - REQ_LINE("annotate", serve_annotate, 0), - REQ_LINE("rannotate", serve_rannotate, 0), - REQ_LINE("noop", serve_noop, RQ_ROOTLESS), - REQ_LINE("version", serve_version, RQ_ROOTLESS), - REQ_LINE(NULL, NULL, 0) - -#undef REQ_LINE -}; - -#endif /* SERVER_SUPPORT or CLIENT_SUPPORT */ -#ifdef SERVER_SUPPORT - -static void -serve_valid_requests (arg) - char *arg; -{ - struct request *rq; - if (print_pending_error ()) - return; - buf_output0 (buf_to_net, "Valid-requests"); - for (rq = requests; rq->name != NULL; rq++) - { - if (rq->func != NULL) - { - buf_append_char (buf_to_net, ' '); - buf_output0 (buf_to_net, rq->name); - } - } - buf_output0 (buf_to_net, "\nok\n"); - - /* The client is waiting for the list of valid requests, so we - must send the output now. */ - buf_flush (buf_to_net, 1); -} - -#ifdef SUNOS_KLUDGE -/* - * Delete temporary files. SIG is the signal making this happen, or - * 0 if not called as a result of a signal. - */ -static int command_pid_is_dead; -static void wait_sig (sig) - int sig; -{ - int status; - pid_t r = wait (&status); - if (r == command_pid) - command_pid_is_dead++; -} -#endif /* SUNOS_KLUDGE */ - -void -server_cleanup (sig) - int sig; -{ - /* Do "rm -rf" on the temp directory. */ - int status; - int save_noexec; - - if (buf_to_net != NULL) - { - /* Since we're done, go ahead and put BUF_TO_NET back into blocking - * mode and send any pending output. In the usual case there won't - * won't be any, but there might be if an error occured. - */ - - set_block (buf_to_net); - buf_flush (buf_to_net, 1); - - /* Next we shut down BUF_FROM_NET. That will pick up the checksum - * generated when the client shuts down its buffer. Then, after we - * have generated any final output, we shut down BUF_TO_NET. - */ - - if (buf_from_net != NULL) - { - status = buf_shutdown (buf_from_net); - if (status != 0) - error (0, status, "shutting down buffer from client"); - buf_free (buf_from_net); - buf_from_net = NULL; - } - - if (dont_delete_temp) - { - (void) buf_flush (buf_to_net, 1); - (void) buf_shutdown (buf_to_net); - buf_free (buf_to_net); - buf_to_net = NULL; - error_use_protocol = 0; - return; - } - } - else if (dont_delete_temp) - return; - - /* What a bogus kludge. This disgusting code makes all kinds of - assumptions about SunOS, and is only for a bug in that system. - So only enable it on Suns. */ -#ifdef SUNOS_KLUDGE - if (command_pid > 0) - { - /* To avoid crashes on SunOS due to bugs in SunOS tmpfs - triggered by the use of rename() in RCS, wait for the - subprocess to die. Unfortunately, this means draining output - while waiting for it to unblock the signal we sent it. Yuck! */ - int status; - pid_t r; - - signal (SIGCHLD, wait_sig); - if (sig) - /* Perhaps SIGTERM would be more correct. But the child - process will delay the SIGINT delivery until its own - children have exited. */ - kill (command_pid, SIGINT); - /* The caller may also have sent a signal to command_pid, so - always try waiting. First, though, check and see if it's still - there.... */ - do_waitpid: - r = waitpid (command_pid, &status, WNOHANG); - if (r == 0) - ; - else if (r == command_pid) - command_pid_is_dead++; - else if (r == -1) - switch (errno) - { - case ECHILD: - command_pid_is_dead++; - break; - case EINTR: - goto do_waitpid; - } - else - /* waitpid should always return one of the above values */ - abort (); - while (!command_pid_is_dead) - { - struct timeval timeout; - struct fd_set_wrapper readfds; - char buf[100]; - int i; - - /* Use a non-zero timeout to avoid eating up CPU cycles. */ - timeout.tv_sec = 2; - timeout.tv_usec = 0; - readfds = command_fds_to_drain; - switch (select (max_command_fd + 1, &readfds.fds, - (fd_set *)0, (fd_set *)0, - &timeout)) - { - case -1: - if (errno != EINTR) - abort (); - case 0: - /* timeout */ - break; - case 1: - for (i = 0; i <= max_command_fd; i++) - { - if (!FD_ISSET (i, &readfds.fds)) - continue; - /* this fd is non-blocking */ - while (read (i, buf, sizeof (buf)) >= 1) - ; - } - break; - default: - abort (); - } - } - } -#endif /* SUNOS_KLUDGE */ - - CVS_CHDIR (Tmpdir); - /* Temporarily clear noexec, so that we clean up our temp directory - regardless of it (this could more cleanly be handled by moving - the noexec check to all the unlink_file_dir callers from - unlink_file_dir itself). */ - save_noexec = noexec; - noexec = 0; - /* FIXME? Would be nice to not ignore errors. But what should we do? - We could try to do this before we shut down the network connection, - and try to notify the client (but the client might not be waiting - for responses). We could try something like syslog() or our own - log file. */ - unlink_file_dir (orig_server_temp_dir); - noexec = save_noexec; - - if (buf_to_net != NULL) - { - (void) buf_flush (buf_to_net, 1); - (void) buf_shutdown (buf_to_net); - buf_free (buf_to_net); - buf_to_net = NULL; - error_use_protocol = 0; - } -} - -int -server (argc, argv) - int argc; - char **argv; -{ - char *error_prog_name; /* Used in error messages */ - - if (argc == -1) - { - static const char *const msg[] = - { - "Usage: %s %s\n", - " Normally invoked by a cvs client on a remote machine.\n", - NULL - }; - usage (msg); - } - /* Ignore argc and argv. They might be from .cvsrc. */ - - buf_to_net = fd_buffer_initialize (STDOUT_FILENO, 0, - outbuf_memory_error); - buf_from_net = stdio_buffer_initialize (stdin, 0, 1, outbuf_memory_error); - - saved_output = buf_nonio_initialize (outbuf_memory_error); - saved_outerr = buf_nonio_initialize (outbuf_memory_error); - - /* Since we're in the server parent process, error should use the - protocol to report error messages. */ - error_use_protocol = 1; - - /* OK, now figure out where we stash our temporary files. */ - { - char *p; - - /* The code which wants to chdir into server_temp_dir is not set - up to deal with it being a relative path. So give an error - for that case. */ - if (!isabsolute (Tmpdir)) - { - if (alloc_pending (80 + strlen (Tmpdir))) - sprintf (pending_error_text, - "E Value of %s for TMPDIR is not absolute", Tmpdir); - - /* FIXME: we would like this error to be persistent, that - is, not cleared by print_pending_error. The current client - will exit as soon as it gets an error, but the protocol spec - does not require a client to do so. */ - } - else - { - int status; - int i = 0; - - server_temp_dir = xmalloc (strlen (Tmpdir) + 80); - if (server_temp_dir == NULL) - { - /* - * Strictly speaking, we're not supposed to output anything - * now. But we're about to exit(), give it a try. - */ - printf ("E Fatal server error, aborting.\n\ -error ENOMEM Virtual memory exhausted.\n"); - - error_exit (); - } - strcpy (server_temp_dir, Tmpdir); - - /* Remove a trailing slash from TMPDIR if present. */ - p = server_temp_dir + strlen (server_temp_dir) - 1; - if (*p == '/') - *p = '\0'; - - /* - * I wanted to use cvs-serv/PID, but then you have to worry about - * the permissions on the cvs-serv directory being right. So - * use cvs-servPID. - */ - strcat (server_temp_dir, "/cvs-serv"); - - p = server_temp_dir + strlen (server_temp_dir); - sprintf (p, "%ld", (long) getpid ()); - - orig_server_temp_dir = server_temp_dir; - - /* Create the temporary directory, and set the mode to - 700, to discourage random people from tampering with - it. */ - while ((status = mkdir_p (server_temp_dir)) == EEXIST) - { - static const char suffix[] = "abcdefghijklmnopqrstuvwxyz"; - - if (i >= sizeof suffix - 1) break; - if (i == 0) p = server_temp_dir + strlen (server_temp_dir); - p[0] = suffix[i++]; - p[1] = '\0'; - } - if (status != 0) - { - if (alloc_pending (80 + strlen (server_temp_dir))) - sprintf (pending_error_text, - "E can't create temporary directory %s", - server_temp_dir); - pending_error = status; - } -#ifndef CHMOD_BROKEN - else if (chmod (server_temp_dir, S_IRWXU) < 0) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (server_temp_dir))) - sprintf (pending_error_text, -"E cannot change permissions on temporary directory %s", - server_temp_dir); - pending_error = save_errno; - } -#endif - else if (CVS_CHDIR (server_temp_dir) < 0) - { - int save_errno = errno; - if (alloc_pending (80 + strlen (server_temp_dir))) - sprintf (pending_error_text, -"E cannot change to temporary directory %s", - server_temp_dir); - pending_error = save_errno; - } - } - } - - /* Now initialize our argument vector (for arguments from the client). */ - - /* Small for testing. */ - argument_vector_size = 1; - argument_vector = xmalloc (argument_vector_size * sizeof (char *)); - argument_count = 1; - /* This gets printed if the client supports an option which the - server doesn't, causing the server to print a usage message. - FIXME: just a nit, I suppose, but the usage message the server - prints isn't literally true--it suggests "cvs server" followed - by options which are for a particular command. Might be nice to - say something like "client apparently supports an option not supported - by this server" or something like that instead of usage message. */ - error_prog_name = xmalloc (strlen (program_name) + 8); - sprintf(error_prog_name, "%s server", program_name); - argument_vector[0] = error_prog_name; - - while (1) - { - char *cmd, *orig_cmd; - struct request *rq; - int status; - - status = buf_read_line (buf_from_net, &cmd, NULL); - if (status == -2) - { - buf_output0 (buf_to_net, "E Fatal server error, aborting.\n\ -error ENOMEM Virtual memory exhausted.\n"); - break; - } - if (status != 0) - break; - - orig_cmd = cmd; - for (rq = requests; rq->name != NULL; ++rq) - if (strncmp (cmd, rq->name, strlen (rq->name)) == 0) - { - int len = strlen (rq->name); - if (cmd[len] == '\0') - cmd += len; - else if (cmd[len] == ' ') - cmd += len + 1; - else - /* - * The first len characters match, but it's a different - * command. e.g. the command is "cooperate" but we matched - * "co". - */ - continue; - - if (!(rq->flags & RQ_ROOTLESS) - && current_parsed_root == NULL) - { - /* For commands which change the way in which data - is sent and received, for example Gzip-stream, - this does the wrong thing. Since the client - assumes that everything is being compressed, - unconditionally, there is no way to give this - error to the client without turning on - compression. The obvious fix would be to make - Gzip-stream RQ_ROOTLESS (with the corresponding - change to the spec), and that might be a good - idea but then again I can see some settings in - CVSROOT about what compression level to allow. - I suppose a more baroque answer would be to - turn on compression (say, at level 1), just - enough to give the "Root request missing" - error. For now we just lose. */ - if (alloc_pending (80)) - sprintf (pending_error_text, - "E Protocol error: Root request missing"); - } - else - (*rq->func) (cmd); - break; - } - if (rq->name == NULL) - { - if (!print_pending_error ()) - { - buf_output0 (buf_to_net, "error unrecognized request `"); - buf_output0 (buf_to_net, cmd); - buf_append_char (buf_to_net, '\''); - buf_append_char (buf_to_net, '\n'); - } - } - free (orig_cmd); - } - free (error_prog_name); - - /* We expect the client is done talking to us at this point. If there is - * any data in the buffer or on the network pipe, then something we didn't - * prepare for is happening. - */ - if (!buf_empty (buf_from_net)) - { - /* Try to send the error message to the client, but also syslog it, in - * case the client isn't listening anymore. - */ -#ifdef HAVE_SYSLOG_H - /* FIXME: Can the IP address of the connecting client be retrieved - * and printed here? - */ - syslog (LOG_DAEMON | LOG_ERR, "Dying gasps received from client."); -#endif - error (0, 0, "Dying gasps received from client."); - } - - /* This command will actually close the network buffers. */ - server_cleanup (0); - return 0; -} - - - -#if defined (HAVE_KERBEROS) || defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI) -static void switch_to_user PROTO((const char *, const char *)); - -static void -switch_to_user (cvs_username, username) - const char *cvs_username; /* Only used for error messages. */ - const char *username; -{ - struct passwd *pw; - - pw = getpwnam (username); - if (pw == NULL) - { - /* check_password contains a similar check, so this usually won't be - reached unless the CVS user is mapped to an invalid system user. */ - - printf ("E Fatal error, aborting.\n\ -error 0 %s: no such system user\n", username); - /* Don't worry about server_cleanup; server_active isn't set yet. */ - error_exit (); - } - - if (pw->pw_uid == 0) - { -#ifdef HAVE_SYSLOG_H - /* FIXME: Can the IP address of the connecting client be retrieved - * and printed here? - */ - syslog (LOG_DAEMON | LOG_ALERT, - "attempt to root from account: %s", cvs_username - ); -#endif - printf("error 0: root not allowed\n"); - error_exit (); - } - -#if HAVE_INITGROUPS - if (initgroups (pw->pw_name, pw->pw_gid) < 0 -# ifdef EPERM - /* At least on the system I tried, initgroups() only works as root. - But we do still want to report ENOMEM and whatever other - errors initgroups() might dish up. */ - && errno != EPERM -# endif - ) - { - /* This could be a warning, but I'm not sure I see the point - in doing that instead of an error given that it would happen - on every connection. We could log it somewhere and not tell - the user. But at least for now make it an error. */ - printf ("error 0 initgroups failed: %s\n", strerror (errno)); - /* Don't worry about server_cleanup; server_active isn't set yet. */ - error_exit (); - } -#endif /* HAVE_INITGROUPS */ - -#ifdef SETXID_SUPPORT - /* honor the setgid bit iff set*/ - if (getgid() != getegid()) - { - if (setgid (getegid ()) < 0) - { - /* See comments at setuid call below for more discussion. */ - printf ("error 0 setgid failed: %s\n", strerror (errno)); - /* Don't worry about server_cleanup; - server_active isn't set yet. */ - error_exit (); - } - } - else -#endif - { - if (setgid (pw->pw_gid) < 0) - { - /* See comments at setuid call below for more discussion. */ - printf ("error 0 setgid failed: %s\n", strerror (errno)); -#ifdef HAVE_SYSLOG_H - syslog (LOG_DAEMON | LOG_ERR, - "setgid to %d failed (%m): real %d/%d, effective %d/%d ", - pw->pw_gid, getuid(), getgid(), geteuid(), getegid()); -#endif - /* Don't worry about server_cleanup; - server_active isn't set yet. */ - error_exit (); - } - } - - if (setuid (pw->pw_uid) < 0) - { - /* Note that this means that if run as a non-root user, - CVSROOT/passwd must contain the user we are running as - (e.g. "joe:FsEfVcu:cvs" if run as "cvs" user). This seems - cleaner than ignoring the error like CVS 1.10 and older but - it does mean that some people might need to update their - CVSROOT/passwd file. */ - printf ("error 0 setuid failed: %s\n", strerror (errno)); -#ifdef HAVE_SYSLOG_H - syslog (LOG_DAEMON | LOG_ERR, - "setuid to %d failed (%m): real %d/%d, effective %d/%d ", - pw->pw_uid, getuid(), getgid(), geteuid(), getegid()); -#endif - /* Don't worry about server_cleanup; server_active isn't set yet. */ - error_exit (); - } - - /* We don't want our umask to change file modes. The modes should - be set by the modes used in the repository, and by the umask of - the client. */ - umask (0); - -#ifdef AUTH_SERVER_SUPPORT - /* Make sure our CVS_Username has been set. */ - if (CVS_Username == NULL) - CVS_Username = xstrdup (username); -#endif - -#if HAVE_PUTENV - /* Set LOGNAME, USER and CVS_USER in the environment, in case they - are already set to something else. */ - { - char *env; - - env = xmalloc (sizeof "LOGNAME=" + strlen (username)); - (void) sprintf (env, "LOGNAME=%s", username); - (void) putenv (env); - - env = xmalloc (sizeof "USER=" + strlen (username)); - (void) sprintf (env, "USER=%s", username); - (void) putenv (env); - -#ifdef AUTH_SERVER_SUPPORT - env = xmalloc (sizeof "CVS_USER=" + strlen (CVS_Username)); - (void) sprintf (env, "CVS_USER=%s", CVS_Username); - (void) putenv (env); -#endif - } -#endif /* HAVE_PUTENV */ -} -#endif - -#ifdef AUTH_SERVER_SUPPORT - -extern char *crypt PROTO((const char *, const char *)); - - -/* - * 0 means no entry found for this user. - * 1 means entry found and password matches (or found password is empty) - * 2 means entry found, but password does not match. - * - * If 1, host_user_ptr will be set to point at the system - * username (i.e., the "real" identity, which may or may not be the - * CVS username) of this user; caller may free this. Global - * CVS_Username will point at an allocated copy of cvs username (i.e., - * the username argument below). - * kff todo: FIXME: last sentence is not true, it applies to caller. - */ -static int -check_repository_password (username, password, repository, host_user_ptr) - char *username, *password, *repository, **host_user_ptr; -{ - int retval = 0; - FILE *fp; - char *filename; - char *linebuf = NULL; - size_t linebuf_len; - int found_it = 0; - int namelen; - - /* We don't use current_parsed_root->directory because it hasn't been - * set yet -- our `repository' argument came from the authentication - * protocol, not the regular CVS protocol. - */ - - filename = xmalloc (strlen (repository) - + 1 - + strlen (CVSROOTADM) - + 1 - + strlen (CVSROOTADM_PASSWD) - + 1); - - (void) sprintf (filename, "%s/%s/%s", repository, - CVSROOTADM, CVSROOTADM_PASSWD); - - fp = CVS_FOPEN (filename, "r"); - if (fp == NULL) - { - if (!existence_error (errno)) - error (0, errno, "cannot open %s", filename); - free (filename); - return 0; - } - - /* Look for a relevant line -- one with this user's name. */ - namelen = strlen (username); - while (getline (&linebuf, &linebuf_len, fp) >= 0) - { - if ((strncmp (linebuf, username, namelen) == 0) - && (linebuf[namelen] == ':')) - { - found_it = 1; - break; - } - } - if (ferror (fp)) - error (0, errno, "cannot read %s", filename); - if (fclose (fp) < 0) - error (0, errno, "cannot close %s", filename); - - /* If found_it, then linebuf contains the information we need. */ - if (found_it) - { - char *found_password, *host_user_tmp; - char *non_cvsuser_portion; - - /* We need to make sure lines such as - * - * "username::sysuser\n" - * "username:\n" - * "username: \n" - * - * all result in a found_password of NULL, but we also need to - * make sure that - * - * "username: :sysuser\n" - * "username: <whatever>:sysuser\n" - * - * continues to result in an impossible password. That way, - * an admin would be on safe ground by going in and tacking a - * space onto the front of a password to disable the account - * (a technique some people use to close accounts - * temporarily). - */ - - /* Make `non_cvsuser_portion' contain everything after the CVS - username, but null out any final newline. */ - non_cvsuser_portion = linebuf + namelen; - strtok (non_cvsuser_portion, "\n"); - - /* If there's a colon now, we just want to inch past it. */ - if (strchr (non_cvsuser_portion, ':') == non_cvsuser_portion) - non_cvsuser_portion++; - - /* Okay, after this conditional chain, found_password and - host_user_tmp will have useful values: */ - - if ((non_cvsuser_portion == NULL) - || (strlen (non_cvsuser_portion) == 0) - || ((strspn (non_cvsuser_portion, " \t")) - == strlen (non_cvsuser_portion))) - { - found_password = NULL; - host_user_tmp = NULL; - } - else if (strncmp (non_cvsuser_portion, ":", 1) == 0) - { - found_password = NULL; - host_user_tmp = non_cvsuser_portion + 1; - if (strlen (host_user_tmp) == 0) - host_user_tmp = NULL; - } - else - { - found_password = strtok (non_cvsuser_portion, ":"); - host_user_tmp = strtok (NULL, ":"); - } - - /* Of course, maybe there was no system user portion... */ - if (host_user_tmp == NULL) - host_user_tmp = username; - - /* Verify blank passwords directly, otherwise use crypt(). */ - if ((found_password == NULL) - || ((strcmp (found_password, crypt (password, found_password)) - == 0))) - { - /* Give host_user_ptr permanent storage. */ - *host_user_ptr = xstrdup (host_user_tmp); - retval = 1; - } - else - { -#ifdef LOG_AUTHPRIV - syslog (LOG_AUTHPRIV | LOG_NOTICE, - "password mismatch for %s in %s: %s vs. %s", username, - repository, crypt(password, found_password), found_password); -#endif - *host_user_ptr = NULL; - retval = 2; - } - } - else /* Didn't find this user, so deny access. */ - { - *host_user_ptr = NULL; - retval = 0; - } - - free (filename); - if (linebuf) - free (linebuf); - - return retval; -} - - - -/* Return a hosting username if password matches, else NULL. */ -static char * -check_password (username, password, repository) - char *username, *password, *repository; -{ - int rc; - char *host_user = NULL; - char *found_passwd = NULL; - struct passwd *pw; - - /* First we see if this user has a password in the CVS-specific - password file. If so, that's enough to authenticate with. If - not, we'll check /etc/passwd. */ - - if (require_real_user) - rc = 0; /* "not found" */ - else - rc = check_repository_password (username, password, repository, - &host_user); - - if (rc == 2) - return NULL; - - if (rc == 1) - { - /* host_user already set by reference, so just return. */ - goto handle_return; - } - - assert (rc == 0); - - if (!system_auth) - { - /* Note that the message _does_ distinguish between the case in - which we check for a system password and the case in which - we do not. It is a real pain to track down why it isn't - letting you in if it won't say why, and I am not convinced - that the potential information disclosure to an attacker - outweighs this. */ - printf ("error 0 no such user %s in CVSROOT/passwd\n", username); - - error_exit (); - } - - /* No cvs password found, so try /etc/passwd. */ - -#ifdef HAVE_GETSPNAM - { - struct spwd *spw; - - spw = getspnam (username); - if (spw != NULL) - { - found_passwd = spw->sp_pwdp; - } - } -#endif - - if (found_passwd == NULL && (pw = getpwnam (username)) != NULL) - { - found_passwd = pw->pw_passwd; - } - - if (found_passwd == NULL) - { - printf ("E Fatal error, aborting.\n\ -error 0 %s: no such user\n", username); - - error_exit (); - } - - /* Allow for dain bramaged HPUX passwd aging - * - Basically, HPUX adds a comma and some data - * about whether the passwd has expired or not - * on the end of the passwd field. - * - This code replaces the ',' with '\0'. - * - * FIXME - our workaround is brain damaged too. I'm - * guessing that HPUX WANTED other systems to think the - * password was wrong so logins would fail if the - * system didn't handle expired passwds and the passwd - * might be expired. I think the way to go here - * is with PAM. - */ - strtok (found_passwd, ","); - - if (*found_passwd) - { - /* user exists and has a password */ - if (strcmp (found_passwd, crypt (password, found_passwd)) == 0) - { - host_user = xstrdup (username); - } - else - { - host_user = NULL; -#ifdef LOG_AUTHPRIV - syslog (LOG_AUTHPRIV | LOG_NOTICE, - "password mismatch for %s: %s vs. %s", username, - crypt(password, found_passwd), found_passwd); -#endif - } - goto handle_return; - } - - if (password && *password) - { - /* user exists and has no system password, but we got - one as parameter */ - host_user = xstrdup (username); - goto handle_return; - } - - /* user exists but has no password at all */ - host_user = NULL; -#ifdef LOG_AUTHPRIV - syslog (LOG_AUTHPRIV | LOG_NOTICE, - "login refused for %s: user has no password", username); -#endif - -handle_return: - if (host_user) - { - /* Set CVS_Username here, in allocated space. - It might or might not be the same as host_user. */ - CVS_Username = xmalloc (strlen (username) + 1); - strcpy (CVS_Username, username); - } - - return host_user; -} - -#endif /* AUTH_SERVER_SUPPORT */ - -#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI) - -/* Read username and password from client (i.e., stdin). - If correct, then switch to run as that user and send an ACK to the - client via stdout, else send NACK and die. */ -void -pserver_authenticate_connection () -{ - char *tmp = NULL; - size_t tmp_allocated = 0; -#ifdef AUTH_SERVER_SUPPORT - char *repository = NULL; - size_t repository_allocated = 0; - char *username = NULL; - size_t username_allocated = 0; - char *password = NULL; - size_t password_allocated = 0; - - char *host_user; - char *descrambled_password; -#endif /* AUTH_SERVER_SUPPORT */ - int verify_and_exit = 0; - - /* The Authentication Protocol. Client sends: - * - * BEGIN AUTH REQUEST\n - * <REPOSITORY>\n - * <USERNAME>\n - * <PASSWORD>\n - * END AUTH REQUEST\n - * - * Server uses above information to authenticate, then sends - * - * I LOVE YOU\n - * - * if it grants access, else - * - * I HATE YOU\n - * - * if it denies access (and it exits if denying). - * - * When the client is "cvs login", the user does not desire actual - * repository access, but would like to confirm the password with - * the server. In this case, the start and stop strings are - * - * BEGIN VERIFICATION REQUEST\n - * - * and - * - * END VERIFICATION REQUEST\n - * - * On a verification request, the server's responses are the same - * (with the obvious semantics), but it exits immediately after - * sending the response in both cases. - * - * Why is the repository sent? Well, note that the actual - * client/server protocol can't start up until authentication is - * successful. But in order to perform authentication, the server - * needs to look up the password in the special CVS passwd file, - * before trying /etc/passwd. So the client transmits the - * repository as part of the "authentication protocol". The - * repository will be redundantly retransmitted later, but that's no - * big deal. - */ - -#ifdef SO_KEEPALIVE - /* Set SO_KEEPALIVE on the socket, so that we don't hang forever - if the client dies while we are waiting for input. */ - { - int on = 1; - - if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, - (char *) &on, sizeof on) < 0) - { -#ifdef HAVE_SYSLOG_H - syslog (LOG_DAEMON | LOG_ERR, "error setting KEEPALIVE: %m"); -#endif - } - } -#endif - - /* Make sure the protocol starts off on the right foot... */ - if (getline_safe (&tmp, &tmp_allocated, stdin, PATH_MAX) < 0) - { -#ifdef HAVE_SYSLOG_H - syslog (LOG_DAEMON | LOG_NOTICE, "bad auth protocol start: EOF"); -#endif - error (1, 0, "bad auth protocol start: EOF"); - } - - if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0) - verify_and_exit = 1; - else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") == 0) - ; - else if (strcmp (tmp, "BEGIN GSSAPI REQUEST\n") == 0) - { -#ifdef HAVE_GSSAPI - free (tmp); - gserver_authenticate_connection (); - return; -#else - error (1, 0, "GSSAPI authentication not supported by this server"); -#endif - } - else - error (1, 0, "bad auth protocol start: %s", tmp); - -#ifndef AUTH_SERVER_SUPPORT - - error (1, 0, "Password authentication not supported by this server"); - -#else /* AUTH_SERVER_SUPPORT */ - - /* Get the three important pieces of information in order. */ - /* See above comment about error handling. */ - getline_safe (&repository, &repository_allocated, stdin, PATH_MAX); - getline_safe (&username, &username_allocated, stdin, PATH_MAX); - getline_safe (&password, &password_allocated, stdin, PATH_MAX); - - /* Make them pure. - * - * We check that none of the lines were truncated by getnline in order - * to be sure that we don't accidentally allow a blind DOS attack to - * authenticate, however slim the odds of that might be. - */ - if (!strip_trailing_newlines (repository) - || !strip_trailing_newlines (username) - || !strip_trailing_newlines (password)) - error (1, 0, "Maximum line length exceeded during authentication."); - - /* ... and make sure the protocol ends on the right foot. */ - /* See above comment about error handling. */ - getline_safe (&tmp, &tmp_allocated, stdin, PATH_MAX); - if (strcmp (tmp, - verify_and_exit ? - "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n") - != 0) - { - error (1, 0, "bad auth protocol end: %s", tmp); - } - if (!root_allow_ok (repository)) - { - printf ("error 0 %s: no such repository\n", repository); -#ifdef HAVE_SYSLOG_H - syslog (LOG_DAEMON | LOG_NOTICE, "login refused for %s", repository); -#endif - goto i_hate_you; - } - - /* OK, now parse the config file, so we can use it to control how - to check passwords. If there was an error parsing the config - file, parse_config already printed an error. We keep going. - Why? Because if we didn't, then there would be no way to check - in a new CVSROOT/config file to fix the broken one! */ - parse_config (repository); - - /* We need the real cleartext before we hash it. */ - descrambled_password = descramble (password); - host_user = check_password (username, descrambled_password, repository); - if (host_user == NULL) - { -#ifdef HAVE_SYSLOG_H - syslog (LOG_DAEMON | LOG_NOTICE, "login failure (for %s)", repository); -#endif - memset (descrambled_password, 0, strlen (descrambled_password)); - free (descrambled_password); - i_hate_you: - printf ("I HATE YOU\n"); - fflush (stdout); - - /* Don't worry about server_cleanup, server_active isn't set - yet. */ - error_exit (); - } - memset (descrambled_password, 0, strlen (descrambled_password)); - free (descrambled_password); - - /* Don't go any farther if we're just responding to "cvs login". */ - if (verify_and_exit) - { - printf ("I LOVE YOU\n"); - fflush (stdout); - - /* It's okay to skip rcs_cleanup() and Lock_Cleanup() here. */ - -#ifdef SYSTEM_CLEANUP - /* Hook for OS-specific behavior, for example socket subsystems on - NT and OS2 or dealing with windows and arguments on Mac. */ - SYSTEM_CLEANUP (); -#endif - - exit (0); - } - - /* Set Pserver_Repos so that we can check later that the same - repository is sent in later client/server protocol. */ - Pserver_Repos = xmalloc (strlen (repository) + 1); - strcpy (Pserver_Repos, repository); - - /* Switch to run as this user. */ - switch_to_user (username, host_user); - free (host_user); - free (tmp); - free (repository); - free (username); - free (password); - - printf ("I LOVE YOU\n"); - fflush (stdout); -#endif /* AUTH_SERVER_SUPPORT */ -} - -#endif /* AUTH_SERVER_SUPPORT || HAVE_GSSAPI */ - - -#ifdef HAVE_KERBEROS -void -kserver_authenticate_connection () -{ - int status; - char instance[INST_SZ]; - struct sockaddr_in peer; - struct sockaddr_in laddr; - int len; - KTEXT_ST ticket; - AUTH_DAT auth; - char version[KRB_SENDAUTH_VLEN]; - char user[ANAME_SZ]; - - strcpy (instance, "*"); - len = sizeof peer; - if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0 - || getsockname (STDIN_FILENO, (struct sockaddr *) &laddr, - &len) < 0) - { - printf ("E Fatal error, aborting.\n\ -error %s getpeername or getsockname failed\n", strerror (errno)); - - error_exit (); - } - -#ifdef SO_KEEPALIVE - /* Set SO_KEEPALIVE on the socket, so that we don't hang forever - if the client dies while we are waiting for input. */ - { - int on = 1; - - if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE, - (char *) &on, sizeof on) < 0) - { -#ifdef HAVE_SYSLOG_H - syslog (LOG_DAEMON | LOG_ERR, "error setting KEEPALIVE: %m"); -#endif - } - } -#endif - - status = krb_recvauth (KOPT_DO_MUTUAL, STDIN_FILENO, &ticket, "rcmd", - instance, &peer, &laddr, &auth, "", sched, - version); - if (status != KSUCCESS) - { - printf ("E Fatal error, aborting.\n\ -error 0 kerberos: %s\n", krb_get_err_text(status)); - - error_exit (); - } - - memcpy (kblock, auth.session, sizeof (C_Block)); - - /* Get the local name. */ - status = krb_kntoln (&auth, user); - if (status != KSUCCESS) - { - printf ("E Fatal error, aborting.\n\ -error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status)); - - error_exit (); - } - - /* Switch to run as this user. */ - switch_to_user ("Kerberos 4", user); -} -#endif /* HAVE_KERBEROS */ - -#ifdef HAVE_GSSAPI - -#ifndef MAXHOSTNAMELEN -#define MAXHOSTNAMELEN (256) -#endif - -/* Authenticate a GSSAPI connection. This is called from - pserver_authenticate_connection, and it handles success and failure - the same way. */ - -static void -gserver_authenticate_connection () -{ - char hostname[MAXHOSTNAMELEN]; - struct hostent *hp; - gss_buffer_desc tok_in, tok_out; - char buf[1024]; - char *credbuf; - size_t credbuflen; - OM_uint32 stat_min, ret; - gss_name_t server_name, client_name; - gss_cred_id_t server_creds; - int nbytes; - gss_OID mechid; - - gethostname (hostname, sizeof hostname); - hp = gethostbyname (hostname); - if (hp == NULL) - error (1, 0, "can't get canonical hostname"); - - sprintf (buf, "cvs@%s", hp->h_name); - tok_in.value = buf; - tok_in.length = strlen (buf); - - if (gss_import_name (&stat_min, &tok_in, GSS_C_NT_HOSTBASED_SERVICE, - &server_name) != GSS_S_COMPLETE) - error (1, 0, "could not import GSSAPI service name %s", buf); - - /* Acquire the server credential to verify the client's - authentication. */ - if (gss_acquire_cred (&stat_min, server_name, 0, GSS_C_NULL_OID_SET, - GSS_C_ACCEPT, &server_creds, - NULL, NULL) != GSS_S_COMPLETE) - error (1, 0, "could not acquire GSSAPI server credentials"); - - gss_release_name (&stat_min, &server_name); - - /* The client will send us a two byte length followed by that many - bytes. */ - if (fread (buf, 1, 2, stdin) != 2) - error (1, errno, "read of length failed"); - - nbytes = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); - if (nbytes <= sizeof buf) - { - credbuf = buf; - credbuflen = sizeof buf; - } - else - { - credbuflen = nbytes; - credbuf = xmalloc (credbuflen); - } - - if (fread (credbuf, 1, nbytes, stdin) != nbytes) - error (1, errno, "read of data failed"); - - gcontext = GSS_C_NO_CONTEXT; - tok_in.length = nbytes; - tok_in.value = credbuf; - - if (gss_accept_sec_context (&stat_min, - &gcontext, /* context_handle */ - server_creds, /* verifier_cred_handle */ - &tok_in, /* input_token */ - NULL, /* channel bindings */ - &client_name, /* src_name */ - &mechid, /* mech_type */ - &tok_out, /* output_token */ - &ret, - NULL, /* ignore time_rec */ - NULL) /* ignore del_cred_handle */ - != GSS_S_COMPLETE) - { - error (1, 0, "could not verify credentials"); - } - - /* FIXME: Use Kerberos v5 specific code to authenticate to a user. - We could instead use an authentication to access mapping. */ - { - krb5_context kc; - krb5_principal p; - gss_buffer_desc desc; - - krb5_init_context (&kc); - if (gss_display_name (&stat_min, client_name, &desc, - &mechid) != GSS_S_COMPLETE - || krb5_parse_name (kc, ((gss_buffer_t) &desc)->value, &p) != 0 - || krb5_aname_to_localname (kc, p, sizeof buf, buf) != 0 - || krb5_kuserok (kc, p, buf) != TRUE) - { - error (1, 0, "access denied"); - } - krb5_free_principal (kc, p); - krb5_free_context (kc); - } - - if (tok_out.length != 0) - { - char cbuf[2]; - - cbuf[0] = (tok_out.length >> 8) & 0xff; - cbuf[1] = tok_out.length & 0xff; - if (fwrite (cbuf, 1, 2, stdout) != 2 - || (fwrite (tok_out.value, 1, tok_out.length, stdout) - != tok_out.length)) - error (1, errno, "fwrite failed"); - } - - switch_to_user ("GSSAPI", buf); - - if (credbuf != buf) - free (credbuf); - - printf ("I LOVE YOU\n"); - fflush (stdout); -} - -#endif /* HAVE_GSSAPI */ - -#endif /* SERVER_SUPPORT */ - -#if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT) - -/* This global variable is non-zero if the user requests encryption on - the command line. */ -int cvsencrypt; - -/* This global variable is non-zero if the users requests stream - authentication on the command line. */ -int cvsauthenticate; - -#ifdef HAVE_GSSAPI - -/* An buffer interface using GSSAPI. This is built on top of a - packetizing buffer. */ - -/* This structure is the closure field of the GSSAPI translation - routines. */ - -struct cvs_gssapi_wrap_data -{ - /* The GSSAPI context. */ - gss_ctx_id_t gcontext; -}; - -static int cvs_gssapi_wrap_input PROTO((void *, const char *, char *, int)); -static int cvs_gssapi_wrap_output PROTO((void *, const char *, char *, int, - int *)); - -/* Create a GSSAPI wrapping buffer. We use a packetizing buffer with - GSSAPI wrapping routines. */ - -struct buffer * -cvs_gssapi_wrap_buffer_initialize (buf, input, gcontext, memory) - struct buffer *buf; - int input; - gss_ctx_id_t gcontext; - void (*memory) PROTO((struct buffer *)); -{ - struct cvs_gssapi_wrap_data *gd; - - gd = (struct cvs_gssapi_wrap_data *) xmalloc (sizeof *gd); - gd->gcontext = gcontext; - - return (packetizing_buffer_initialize - (buf, - input ? cvs_gssapi_wrap_input : NULL, - input ? NULL : cvs_gssapi_wrap_output, - gd, - memory)); -} - -/* Unwrap data using GSSAPI. */ - -static int -cvs_gssapi_wrap_input (fnclosure, input, output, size) - void *fnclosure; - const char *input; - char *output; - int size; -{ - struct cvs_gssapi_wrap_data *gd = - (struct cvs_gssapi_wrap_data *) fnclosure; - gss_buffer_desc inbuf, outbuf; - OM_uint32 stat_min; - int conf; - - inbuf.value = (void *) input; - inbuf.length = size; - - if (gss_unwrap (&stat_min, gd->gcontext, &inbuf, &outbuf, &conf, NULL) - != GSS_S_COMPLETE) - { - error (1, 0, "gss_unwrap failed"); - } - - if (outbuf.length > size) - abort (); - - memcpy (output, outbuf.value, outbuf.length); - - /* The real packet size is stored in the data, so we don't need to - remember outbuf.length. */ - - gss_release_buffer (&stat_min, &outbuf); - - return 0; -} - -/* Wrap data using GSSAPI. */ - -static int -cvs_gssapi_wrap_output (fnclosure, input, output, size, translated) - void *fnclosure; - const char *input; - char *output; - int size; - int *translated; -{ - struct cvs_gssapi_wrap_data *gd = - (struct cvs_gssapi_wrap_data *) fnclosure; - gss_buffer_desc inbuf, outbuf; - OM_uint32 stat_min; - int conf_req, conf; - - inbuf.value = (void *) input; - inbuf.length = size; - -#ifdef ENCRYPTION - conf_req = cvs_gssapi_encrypt; -#else - conf_req = 0; -#endif - - if (gss_wrap (&stat_min, gd->gcontext, conf_req, GSS_C_QOP_DEFAULT, - &inbuf, &conf, &outbuf) != GSS_S_COMPLETE) - error (1, 0, "gss_wrap failed"); - - /* The packetizing buffer only permits us to add 100 bytes. - FIXME: I don't know what, if anything, is guaranteed by GSSAPI. - This may need to be increased for a different GSSAPI - implementation, or we may need a different algorithm. */ - if (outbuf.length > size + 100) - abort (); - - memcpy (output, outbuf.value, outbuf.length); - - *translated = outbuf.length; - - gss_release_buffer (&stat_min, &outbuf); - - return 0; -} - -#endif /* HAVE_GSSAPI */ - -#ifdef ENCRYPTION - -#ifdef HAVE_KERBEROS - -/* An encryption interface using Kerberos. This is built on top of a - packetizing buffer. */ - -/* This structure is the closure field of the Kerberos translation - routines. */ - -struct krb_encrypt_data -{ - /* The Kerberos key schedule. */ - Key_schedule sched; - /* The Kerberos DES block. */ - C_Block block; -}; - -static int krb_encrypt_input PROTO((void *, const char *, char *, int)); -static int krb_encrypt_output PROTO((void *, const char *, char *, int, - int *)); - -/* Create a Kerberos encryption buffer. We use a packetizing buffer - with Kerberos encryption translation routines. */ - -struct buffer * -krb_encrypt_buffer_initialize (buf, input, sched, block, memory) - struct buffer *buf; - int input; - Key_schedule sched; - C_Block block; - void (*memory) PROTO((struct buffer *)); -{ - struct krb_encrypt_data *kd; - - kd = (struct krb_encrypt_data *) xmalloc (sizeof *kd); - memcpy (kd->sched, sched, sizeof (Key_schedule)); - memcpy (kd->block, block, sizeof (C_Block)); - - return packetizing_buffer_initialize (buf, - input ? krb_encrypt_input : NULL, - input ? NULL : krb_encrypt_output, - kd, - memory); -} - -/* Decrypt Kerberos data. */ - -static int -krb_encrypt_input (fnclosure, input, output, size) - void *fnclosure; - const char *input; - char *output; - int size; -{ - struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure; - int tcount; - - des_cbc_encrypt ((C_Block *) input, (C_Block *) output, - size, kd->sched, &kd->block, 0); - - /* SIZE is the size of the buffer, which is set by the encryption - routine. The packetizing buffer will arrange for the first two - bytes in the decrypted buffer to be the real (unaligned) - length. As a safety check, make sure that the length in the - buffer corresponds to SIZE. Note that the length in the buffer - is just the length of the data. We must add 2 to account for - the buffer count itself. */ - tcount = ((output[0] & 0xff) << 8) + (output[1] & 0xff); - if (((tcount + 2 + 7) & ~7) != size) - error (1, 0, "Decryption failure"); - - return 0; -} - -/* Encrypt Kerberos data. */ - -static int -krb_encrypt_output (fnclosure, input, output, size, translated) - void *fnclosure; - const char *input; - char *output; - int size; - int *translated; -{ - struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure; - int aligned; - - /* For security against a known plaintext attack, we should - initialize any padding bytes to random values. Instead, we - just pick up whatever is on the stack, which is at least better - than using zero. */ - - /* Align SIZE to an 8 byte boundary. Note that SIZE includes the - two byte buffer count at the start of INPUT which was added by - the packetizing buffer. */ - aligned = (size + 7) & ~7; - - /* We use des_cbc_encrypt rather than krb_mk_priv because the - latter sticks a timestamp in the block, and krb_rd_priv expects - that timestamp to be within five minutes of the current time. - Given the way the CVS server buffers up data, that can easily - fail over a long network connection. We trust krb_recvauth to - guard against a replay attack. */ - - des_cbc_encrypt ((C_Block *) input, (C_Block *) output, aligned, - kd->sched, &kd->block, 1); - - *translated = aligned; - - return 0; -} - -#endif /* HAVE_KERBEROS */ -#endif /* ENCRYPTION */ -#endif /* defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */ - -/* Output LEN bytes at STR. If LEN is zero, then output up to (not including) - the first '\0' byte. */ - -void -cvs_output (str, len) - const char *str; - size_t len; -{ - if (len == 0) - len = strlen (str); -#ifdef SERVER_SUPPORT - if (error_use_protocol && buf_to_net != NULL) - { - buf_output (saved_output, str, len); - buf_copy_lines (buf_to_net, saved_output, 'M'); - } - else if (server_active && protocol != NULL) - { - buf_output (saved_output, str, len); - buf_copy_lines (protocol, saved_output, 'M'); - buf_send_counted (protocol); - } - else -#endif - { - size_t written; - size_t to_write = len; - const char *p = str; - - /* Local users that do 'cvs status 2>&1' on a local repository - may see the informational messages out-of-order with the - status messages unless we use the fflush (stderr) here. */ - fflush (stderr); - - while (to_write > 0) - { - written = fwrite (p, 1, to_write, stdout); - if (written == 0) - break; - p += written; - to_write -= written; - } - } -} - -/* Output LEN bytes at STR in binary mode. If LEN is zero, then - output zero bytes. */ - -void -cvs_output_binary (str, len) - char *str; - size_t len; -{ -#ifdef SERVER_SUPPORT - if (error_use_protocol || server_active) - { - struct buffer *buf; - char size_text[40]; - - if (error_use_protocol) - buf = buf_to_net; - else - buf = protocol; - - if (!supported_response ("Mbinary")) - { - error (0, 0, "\ -this client does not support writing binary files to stdout"); - return; - } - - buf_output0 (buf, "Mbinary\012"); - sprintf (size_text, "%lu\012", (unsigned long) len); - buf_output0 (buf, size_text); - - /* Not sure what would be involved in using buf_append_data here - without stepping on the toes of our caller (which is responsible - for the memory allocation of STR). */ - buf_output (buf, str, len); - - if (!error_use_protocol) - buf_send_counted (protocol); - } - else -#endif - { - size_t written; - size_t to_write = len; - const char *p = str; -#ifdef USE_SETMODE_STDOUT - int oldmode; -#endif - - /* Local users that do 'cvs status 2>&1' on a local repository - may see the informational messages out-of-order with the - status messages unless we use the fflush (stderr) here. */ - fflush (stderr); - -#ifdef USE_SETMODE_STDOUT - /* It is possible that this should be the same ifdef as - USE_SETMODE_BINARY but at least for the moment we keep them - separate. Mostly this is just laziness and/or a question - of what has been tested where. Also there might be an - issue of setmode vs. _setmode. */ - /* The Windows doc says to call setmode only right after startup. - I assume that what they are talking about can also be helped - by flushing the stream before changing the mode. */ - fflush (stdout); - oldmode = _setmode (_fileno (stdout), OPEN_BINARY); - if (oldmode < 0) - error (0, errno, "failed to setmode on stdout"); -#endif - - while (to_write > 0) - { - written = fwrite (p, 1, to_write, stdout); - if (written == 0) - break; - p += written; - to_write -= written; - } -#ifdef USE_SETMODE_STDOUT - fflush (stdout); - if (_setmode (_fileno (stdout), oldmode) != OPEN_BINARY) - error (0, errno, "failed to setmode on stdout"); -#endif - } -} - - - -/* Like CVS_OUTPUT but output is for stderr not stdout. */ -void -cvs_outerr (str, len) - const char *str; - size_t len; -{ - if (len == 0) - len = strlen (str); -#ifdef SERVER_SUPPORT - if (error_use_protocol) - { - buf_output (saved_outerr, str, len); - buf_copy_lines (buf_to_net, saved_outerr, 'E'); - } - else if (server_active) - { - buf_output (saved_outerr, str, len); - buf_copy_lines (protocol, saved_outerr, 'E'); - buf_send_counted (protocol); - } - else -#endif - { - size_t written; - size_t to_write = len; - const char *p = str; - - /* Make sure that output appears in order if stdout and stderr - point to the same place. For the server case this is taken - care of by the fact that saved_outerr always holds less - than a line. */ - fflush (stdout); - - while (to_write > 0) - { - written = fwrite (p, 1, to_write, stderr); - if (written == 0) - break; - p += written; - to_write -= written; - } - } -} - - - -/* Flush stderr. stderr is normally flushed automatically, of course, - but this function is used to flush information from the server back - to the client. */ -void -cvs_flusherr () -{ -#ifdef SERVER_SUPPORT - if (error_use_protocol) - { - /* skip the actual stderr flush in this case since the parent process - * on the server should only be writing to stdout anyhow - */ - /* Flush what we can to the network, but don't block. */ - buf_flush (buf_to_net, 0); - } - else if (server_active) - { - /* make sure stderr is flushed before we send the flush count on the - * protocol pipe - */ - fflush (stderr); - /* Send a special count to tell the parent to flush. */ - buf_send_special_count (protocol, -2); - } - else -#endif - fflush (stderr); -} - - - -/* Make it possible for the user to see what has been written to - stdout (it is up to the implementation to decide exactly how far it - should go to ensure this). */ -void -cvs_flushout () -{ -#ifdef SERVER_SUPPORT - if (error_use_protocol) - { - /* Flush what we can to the network, but don't block. */ - buf_flush (buf_to_net, 0); - } - else if (server_active) - { - /* Just do nothing. This is because the code which - cvs_flushout replaces, setting stdout to line buffering in - main.c, didn't get called in the server child process. But - in the future it is quite plausible that we'll want to make - this case work analogously to cvs_flusherr. - - FIXME - DRP - I tried to implement this and triggered the following - error: "Protocol error: uncounted data discarded". I don't need - this feature right now, so I'm not going to bother with it yet. - */ - buf_send_special_count (protocol, -1); - } - else -#endif - fflush (stdout); -} - -/* Output TEXT, tagging it according to TAG. There are lots more - details about what TAG means in cvsclient.texi but for the simple - case (e.g. non-client/server), TAG is just "newline" to output a - newline (in which case TEXT must be NULL), and any other tag to - output normal text. - - Note that there is no way to output either \0 or \n as part of TEXT. */ - -void -cvs_output_tagged (tag, text) - const char *tag; - const char *text; -{ - if (text != NULL && strchr (text, '\n') != NULL) - /* Uh oh. The protocol has no way to cope with this. For now - we dump core, although that really isn't such a nice - response given that this probably can be caused by newlines - in filenames and other causes other than bugs in CVS. Note - that we don't want to turn this into "MT newline" because - this case is a newline within a tagged item, not a newline - as extraneous sugar for the user. */ - assert (0); - - /* Start and end tags don't take any text, per cvsclient.texi. */ - if (tag[0] == '+' || tag[0] == '-') - assert (text == NULL); - -#ifdef SERVER_SUPPORT - if (server_active && supported_response ("MT")) - { - struct buffer *buf; - - if (error_use_protocol) - buf = buf_to_net; - else - buf = protocol; - - buf_output0 (buf, "MT "); - buf_output0 (buf, tag); - if (text != NULL) - { - buf_output (buf, " ", 1); - buf_output0 (buf, text); - } - buf_output (buf, "\n", 1); - - if (!error_use_protocol) - buf_send_counted (protocol); - } - else -#endif - { - if (strcmp (tag, "newline") == 0) - cvs_output ("\n", 1); - else if (text != NULL) - cvs_output (text, 0); - } -} |