summaryrefslogtreecommitdiff
path: root/src/server.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/server.c')
-rw-r--r--src/server.c7987
1 files changed, 7987 insertions, 0 deletions
diff --git a/src/server.c b/src/server.c
new file mode 100644
index 0000000..41ece74
--- /dev/null
+++ b/src/server.c
@@ -0,0 +1,7987 @@
+/* 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. */
+
+#include "cvs.h"
+
+/* CVS */
+#include "edit.h"
+#include "fileattr.h"
+#include "watch.h"
+
+/* GNULIB */
+#include "buffer.h"
+#include "getline.h"
+#include "getnline.h"
+
+int server_active = 0;
+
+#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
+
+# include "log-buffer.h"
+# include "ms-buffer.h"
+#endif /* defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT) */
+
+#if defined (HAVE_GSSAPI) && defined (SERVER_SUPPORT)
+# include "canon-host.h"
+# include "gssapi-client.h"
+
+/* 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.
+ */
+/* We use Kerberos 5 routines to map the GSSAPI credential to a user
+ name. */
+# include <krb5.h>
+
+static void gserver_authenticate_connection (void);
+
+/* Whether we are already wrapping GSSAPI communication. */
+static int cvs_gssapi_wrapping;
+
+#endif /* defined (HAVE_GSSAPI) && defined (SERVER_SUPPORT) */
+
+#ifdef SERVER_SUPPORT
+
+extern char *server_hostname;
+
+# 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 /* HAVE_SYSLOG_H */
+
+# 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 /* HAVE_KERBEROS */
+
+/* for select */
+# include "xselect.h"
+
+# ifndef O_NONBLOCK
+# define O_NONBLOCK O_NDELAY
+# 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;
+
+# endif /* AUTH_SERVER_SUPPORT */
+
+# ifdef HAVE_PAM
+# if defined(HAVE_SECURITY_PAM_APPL_H)
+# include <security/pam_appl.h>
+# elif defined(HAVE_PAM_PAM_APPL_H)
+# include <pam/pam_appl.h>
+# endif
+
+static pam_handle_t *pamh = NULL;
+
+static char *pam_username;
+static char *pam_password;
+# endif /* HAVE_PAM */
+
+
+
+/* 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;
+
+
+
+# ifdef PROXY_SUPPORT
+/* These are the secondary log buffers so that we can disable them after
+ * creation, when it is determined that they are unneeded, regardless of what
+ * other filters have been prepended to the buffer chain.
+ */
+static struct buffer *proxy_log;
+static struct buffer *proxy_log_out;
+
+/* Set while we are reprocessing a log so that we can avoid sending responses
+ * to some requests twice.
+ */
+static bool reprocessing;
+# endif /* PROXY_SUPPORT */
+
+
+
+/* Arguments storage for `Argument' & `Argumentx' requests. */
+static int argument_count;
+static char **argument_vector;
+static int argument_vector_size;
+
+/*
+ * 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 (void);
+
+cvsroot_t *referrer;
+
+
+
+/* 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 (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 (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 (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 (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;
+static char *pending_warning_text;
+
+/* If an error is pending, print it and return 1. If not, return 0.
+ Also prints pending warnings, but this does not affect the return value.
+ Must be called only in contexts where it is OK to send output. */
+static int
+print_pending_error (void)
+{
+ /* Check this case first since it usually means we are out of memory and
+ * the buffer output routines might try and allocate memory.
+ */
+ if (!pending_error_text && pending_error)
+ {
+ print_error (pending_error);
+ pending_error = 0;
+ return 1;
+ }
+
+ if (pending_warning_text)
+ {
+ buf_output0 (buf_to_net, pending_warning_text);
+ buf_append_char (buf_to_net, '\n');
+ buf_flush (buf_to_net, 0);
+
+ free (pending_warning_text);
+ pending_warning_text = NULL;
+ }
+
+ 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;
+ }
+
+ return 0;
+}
+
+
+
+/* Is an error pending? */
+# define error_pending() (pending_error || pending_error_text)
+# define warning_pending() (pending_warning_text)
+
+/* Allocate SIZE bytes for pending_error_text and return nonzero
+ if we could do it. */
+static inline int
+alloc_pending_internal (char **dest, size_t size)
+{
+ *dest = malloc (size);
+ if (!*dest)
+ {
+ pending_error = ENOMEM;
+ return 0;
+ }
+ return 1;
+}
+
+
+
+/* Allocate SIZE bytes for pending_error_text and return nonzero
+ if we could do it. */
+static int
+alloc_pending (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;
+ return alloc_pending_internal (&pending_error_text, size);
+}
+
+
+
+/* Allocate SIZE bytes for pending_error_text and return nonzero
+ if we could do it. */
+static int
+alloc_pending_warning (size_t size)
+{
+ if (warning_pending ())
+ /* Warnings can be lost here. */
+ return 0;
+ return alloc_pending_internal (&pending_warning_text, size);
+}
+
+
+
+static int
+supported_response (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;
+}
+
+
+
+/*
+ * Return true if we need to relay write requests to a primary server
+ * and false otherwise.
+ *
+ * NOTES
+ *
+ * - primarily handles :ext: method as this seems most likely to be used in
+ * practice.
+ *
+ * - :fork: method is handled for testing.
+ *
+ * - Could handle pserver too, but would have to store the password
+ * the client sent us.
+ *
+ *
+ * GLOBALS
+ * config->PrimaryServer
+ * The parsed setting from CVSROOT/config, if any, or
+ * NULL, otherwise.
+ * current_parsed_root The current repository.
+ *
+ * RETURNS
+ * true If this server is configured as a secondary server.
+ * false Otherwise.
+ */
+static inline bool
+isProxyServer (void)
+{
+ assert (current_parsed_root);
+
+ /***
+ *** The following is done as a series of if/return combinations an an
+ *** optimization.
+ ***/
+
+ /* If there is no primary server defined in CVSROOT/config, then we can't
+ * be a secondary.
+ */
+ if (!config || !config->PrimaryServer) return false;
+
+ /* The directory must not match for all methods. */
+ if (!isSamePath (config->PrimaryServer->directory,
+ current_parsed_root->directory))
+ return true;
+
+ /* Only the directory is important for fork. */
+ if (config->PrimaryServer->method == fork_method)
+ return false;
+
+ /* Must be :ext: method, then. This is enforced when CVSROOT/config is
+ * parsed.
+ */
+ assert (config->PrimaryServer->isremote);
+
+ if (isThisHost (config->PrimaryServer->hostname))
+ return false;
+
+ return true;
+}
+
+
+
+static void
+serve_valid_responses (char *arg)
+{
+ char *p = arg;
+ char *q;
+ struct response *rs;
+
+# ifdef PROXY_SUPPORT
+ /* Process this in the first pass since the data it gathers can be used
+ * prior to a `Root' request.
+ */
+ if (reprocessing) return;
+# endif /* PROXY_SUPPORT */
+
+ 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);
+
+ exit (EXIT_FAILURE);
+ }
+ else if (rs->status == rs_optional)
+ rs->status = rs_not_supported;
+ }
+}
+
+
+
+/*
+ * Process IDs of the subprocess, or negative if that subprocess
+ * does not exist.
+ */
+static pid_t command_pid;
+
+static void
+outbuf_memory_error (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 valid, 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 /* HAVE_SYSLOG_H */
+ exit (EXIT_FAILURE);
+}
+
+
+
+static void
+input_memory_error (struct buffer *buf)
+{
+ outbuf_memory_error (buf);
+}
+
+
+
+# ifdef PROXY_SUPPORT
+/* This function rewinds the net connection using the write proxy log file.
+ *
+ * GLOBALS
+ * proxy_log The buffer object containing the write proxy log.
+ *
+ * RETURNS
+ * Nothing.
+ */
+static void
+rewind_buf_from_net (void)
+{
+ struct buffer *log;
+
+ assert (proxy_log);
+
+ /* Free the arguments since we processed some of them in the first pass.
+ */
+ {
+ /* 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;
+ }
+
+ log = log_buffer_rewind (proxy_log);
+ proxy_log = NULL;
+ /* Dispose of any read but unused data in the net buffer since it will
+ * already be in the log.
+ */
+ buf_free_data (buf_from_net);
+ buf_from_net = ms_buffer_initialize (outbuf_memory_error, log,
+ buf_from_net);
+ reprocessing = true;
+}
+# endif /* PROXY_SUPPORT */
+
+
+
+char *gConfigPath;
+
+
+
+/*
+ * This request cannot be ignored by a potential secondary since it is used to
+ * determine if we _are_ a secondary.
+ */
+static void
+serve_root (char *arg)
+{
+ char *path;
+
+ TRACE (TRACE_FUNCTION, "serve_root (%s)", arg ? arg : "(null)");
+
+ /* Don't process this twice or when errors are pending. */
+ if (error_pending()
+# ifdef PROXY_SUPPORT
+ || reprocessing
+# endif /* PROXY_SUPPORT */
+ ) 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 invalid.
+
+ 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;
+ }
+
+ /* Set original_parsed_root here, not because it can be changed in the
+ * client Redirect sense, but so we don't have to switch in code that
+ * runs in both modes to decide which to print.
+ */
+ original_parsed_root = current_parsed_root = local_cvsroot (arg);
+
+# ifdef AUTH_SERVER_SUPPORT
+ if (Pserver_Repos != NULL)
+ {
+ if (strcmp (Pserver_Repos, current_parsed_root->directory) != 0)
+ {
+ if (alloc_pending (80 + strlen (Pserver_Repos)
+ + strlen (current_parsed_root->directory)))
+ /* 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\"",
+ current_parsed_root->directory, Pserver_Repos);
+ return;
+ }
+ }
+# endif
+
+ /* For pserver, this will already have happened, and the call will do
+ nothing. But for rsh, we need to do it now. */
+ config = get_root_allow_config (current_parsed_root->directory,
+ gConfigPath);
+
+# ifdef PROXY_SUPPORT
+ /* At this point we have enough information to determine if we are a
+ * secondary server or not.
+ */
+ if (proxy_log && !isProxyServer ())
+ {
+ /* Else we are not a secondary server. There is no point in
+ * reprocessing since we handle all the requests we can receive
+ * before `Root' as we receive them. But close the logs.
+ */
+ log_buffer_closelog (proxy_log);
+ log_buffer_closelog (proxy_log_out);
+ proxy_log = NULL;
+ /*
+ * Don't need this. We assume it when proxy_log == NULL.
+ *
+ * proxy_log_out = NULL;
+ */
+ }
+# endif /* PROXY_SUPPORT */
+
+ /* Now set the TMPDIR environment variable. If it was set in the config
+ * file, we now know it.
+ */
+ push_env_temp_dir ();
+
+ /* 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 (get_cvs_tmp_dir ()))
+ {
+ if (alloc_pending (80 + strlen (get_cvs_tmp_dir ())))
+ sprintf (pending_error_text,
+ "E Value of %s for TMPDIR is not absolute",
+ get_cvs_tmp_dir ());
+
+ /* 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 (get_cvs_tmp_dir ()) + 80);
+ if (!server_temp_dir)
+ {
+ /* 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");
+
+ exit (EXIT_FAILURE);
+ }
+ strcpy (server_temp_dir, get_cvs_tmp_dir ());
+
+ /* 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)
+ {
+ 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 that we have a config, verify our compression level. Since
+ * most clients do not send Gzip-stream requests until after the root
+ * request, wait until the first request following Root to verify that
+ * compression is being used when level 0 is not allowed.
+ */
+ if (gzip_level)
+ {
+ bool forced = false;
+
+ if (gzip_level < config->MinCompressionLevel)
+ {
+ gzip_level = config->MinCompressionLevel;
+ forced = true;
+ }
+
+ if (gzip_level > config->MaxCompressionLevel)
+ {
+ gzip_level = config->MaxCompressionLevel;
+ forced = true;
+ }
+
+ if (forced && !quiet
+ && alloc_pending_warning (120 + strlen (program_name)))
+ sprintf (pending_warning_text,
+"E %s server: Forcing compression level %d (allowed: %d <= z <= %d).",
+ program_name, gzip_level, config->MinCompressionLevel,
+ config->MaxCompressionLevel);
+ }
+
+ 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);
+
+ setenv (CVSROOT_ENV, current_parsed_root->directory, 1);
+}
+
+
+
+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 (char *path)
+{
+ TRACE (TRACE_FUNCTION, "server_pathname_check (%s)",
+ path ? path : "(null)");
+
+ /* 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 pathnames invalid for server (specified `%s')",
+ 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);
+ }
+}
+
+
+
+/* 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 (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;
+}
+
+
+
+/* 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 (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 (char *arg)
+{
+ int lim = atoi (arg);
+ int i;
+ char *p;
+
+#ifdef PROXY_SUPPORT
+ if (proxy_log) return;
+#endif /* PROXY_SUPPORT */
+
+ 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 *gDirname;
+static char *gupdate_dir;
+
+static void
+dirswitch (char *dir, char *repos)
+{
+ int status;
+ FILE *f;
+ size_t dir_len;
+
+ TRACE (TRACE_FUNCTION, "dirswitch (%s, %s)", dir ? dir : "(null)",
+ repos ? repos : "(null)");
+
+ 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 pathnames invalid for server (specified `%s')",
+ 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 ISSLASH 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 (gDirname != NULL)
+ free (gDirname);
+ if (gupdate_dir != NULL)
+ free (gupdate_dir);
+
+ if (!strcmp (dir, "."))
+ gupdate_dir = xstrdup ("");
+ else
+ gupdate_dir = xstrdup (dir);
+
+ gDirname = xmalloc (strlen (server_temp_dir) + dir_len + 40);
+ if (gDirname == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+
+ strcpy (gDirname, server_temp_dir);
+ strcat (gDirname, "/");
+ strcat (gDirname, dir);
+
+ status = mkdir_p (gDirname);
+ if (status != 0
+ && status != EEXIST)
+ {
+ if (alloc_pending (80 + strlen (gDirname)))
+ sprintf (pending_error_text, "E cannot mkdir %s", gDirname);
+ 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 (gDirname)))
+ sprintf (pending_error_text, "E cannot create_adm_p %s", gDirname);
+ pending_error = status;
+ return;
+ }
+
+ if ( CVS_CHDIR (gDirname) < 0)
+ {
+ int save_errno = errno;
+ if (alloc_pending (80 + strlen (gDirname)))
+ sprintf (pending_error_text, "E cannot change to %s", gDirname);
+ 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 (gDirname) + strlen (CVSADM)))
+ sprintf (pending_error_text,
+ "E cannot mkdir %s/%s", gDirname, 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 (gDirname) + strlen (CVSADM_REP)))
+ sprintf (pending_error_text,
+ "E cannot open %s/%s", gDirname, CVSADM_REP);
+ pending_error = save_errno;
+ return;
+ }
+ if (fprintf (f, "%s", repos) < 0)
+ {
+ int save_errno = errno;
+ if (alloc_pending (80 + strlen (gDirname) + strlen (CVSADM_REP)))
+ sprintf (pending_error_text,
+ "E error writing %s/%s", gDirname, 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 (gDirname) + strlen (CVSADM_REP)))
+ sprintf (pending_error_text,
+ "E error writing %s/%s", gDirname, CVSADM_REP);
+ pending_error = save_errno;
+ fclose (f);
+ return;
+ }
+ }
+ if (fprintf (f, "\n") < 0)
+ {
+ int save_errno = errno;
+ if (alloc_pending (80 + strlen (gDirname) + strlen (CVSADM_REP)))
+ sprintf (pending_error_text,
+ "E error writing %s/%s", gDirname, CVSADM_REP);
+ pending_error = save_errno;
+ fclose (f);
+ return;
+ }
+ if (fclose (f) == EOF)
+ {
+ int save_errno = errno;
+ if (alloc_pending (80 + strlen (gDirname) + strlen (CVSADM_REP)))
+ sprintf (pending_error_text,
+ "E error closing %s/%s", gDirname, 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 (char *arg)
+{
+# ifdef PROXY_SUPPORT
+ assert (!proxy_log);
+# endif /* PROXY_SUPPORT */
+
+ if (alloc_pending (80))
+ strcpy (pending_error_text,
+ "E Repository request is obsolete; aborted");
+ return;
+}
+
+
+
+static void
+serve_directory (char *arg)
+{
+ int status;
+ char *repos;
+
+ TRACE (TRACE_FUNCTION, "serve_directory (%s)", arg ? arg : "(null)");
+
+
+ /* The data needs to be read into the secondary log regardless, but
+ * processing of anything other than errors is skipped until later.
+ */
+ status = buf_read_line (buf_from_net, &repos, NULL);
+ if (status == 0)
+ {
+ if (!ISABSOLUTE (repos))
+ {
+ /* Make absolute.
+ *
+ * FIXME: This is kinda hacky - we should probably only ever store
+ * and pass SHORT_REPOS (perhaps with the occassional exception
+ * for optimizations, but many, many functions end up
+ * deconstructing REPOS to gain SHORT_REPOS anyhow) - the
+ * CVSROOT portion of REPOS is redundant with
+ * current_parsed_root->directory - but since this is the way
+ * things have always been done, changing this will likely involve
+ * a major overhaul.
+ */
+ char *short_repos;
+
+ short_repos = repos;
+ repos = Xasprintf ("%s/%s",
+ current_parsed_root->directory, short_repos);
+ free (short_repos);
+ }
+ else
+ repos = xstrdup (primary_root_translate (repos));
+
+ if (
+# ifdef PROXY_SUPPORT
+ !proxy_log &&
+# endif /* PROXY_SUPPORT */
+ !outside_root (repos))
+ dirswitch (arg, repos);
+ free (repos);
+ }
+ else if (status == -2)
+ {
+ pending_error = ENOMEM;
+ }
+ else if (status != 0)
+ {
+ 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 (char *arg)
+{
+ FILE *f;
+
+ if (error_pending ()
+# ifdef PROXY_SUPPORT
+ || proxy_log
+# endif /* PROXY_SUPPORT */
+ ) 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 (char *arg)
+{
+ FILE *f;
+
+ if (error_pending ()
+# ifdef PROXY_SUPPORT
+ || proxy_log
+# endif /* PROXY_SUPPORT */
+ ) 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;
+ 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_t size, int file)
+{
+ while (size > 0)
+ {
+ int status;
+ size_t 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)
+ {
+ ssize_t 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;
+ size_t 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_t 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. */
+
+ size_t 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;
+ size_t 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;
+
+
+
+/*
+ * Used to keep track of Entry requests.
+ */
+struct an_entry {
+ struct an_entry *next;
+ char *entry;
+};
+
+static struct an_entry *entries;
+
+static void
+serve_is_modified (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 ()
+# ifdef PROXY_SUPPORT
+ || proxy_log
+# endif /* PROXY_SUPPORT */
+ ) 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;
+ }
+
+ /* *timefield == '/'; */
+ }
+ /* If *TIMEFIELD wasn't '/' and 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
+ * CVSNT (at least 2.0.34 - 2.0.41, reported fixed in 2.0.41a) and
+ * the WinCVS & TortoiseCVS clients which depend on those broken
+ * versions of CVSNT (WinCVS 1.3 & at least one TortoiseCVS
+ * release) rely on this behavior.
+ */
+ if (*timefield != '+')
+ *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 = 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_modified (char *arg)
+{
+ size_t size;
+ int read_size;
+ int 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, 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, 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;
+ read_size = atoi (size_text + 1);
+ }
+ else
+ read_size = atoi (size_text);
+ free (size_text);
+
+ if (read_size < 0 && alloc_pending (80))
+ {
+ sprintf (pending_error_text,
+ "E client sent invalid (negative) file size");
+ return;
+ }
+ else
+ size = read_size;
+
+ if (error_pending ())
+ {
+ /* Now that we know the size, read and discard the file data. */
+ while (size > 0)
+ {
+ int status;
+ size_t nread;
+ char *data;
+
+ status = buf_read_data (buf_from_net, size, &data, &nread);
+ if (status != 0)
+ return;
+ size -= nread;
+ }
+ free (mode_text);
+ return;
+ }
+
+ if (
+# ifdef PROXY_SUPPORT
+ !proxy_log &&
+# endif /* PROXY_SUPPORT */
+ outside_dir (arg))
+ {
+ free (mode_text);
+ return;
+ }
+
+ receive_file (size,
+# ifdef PROXY_SUPPORT
+ proxy_log ? DEVNULL :
+# endif /* PROXY_SUPPORT */
+ arg,
+ gzipped);
+ if (error_pending ())
+ {
+ free (mode_text);
+ return;
+ }
+
+# ifdef PROXY_SUPPORT
+ /* We've read all the data that needed to be read if we're still logging
+ * for a secondary. Return.
+ */
+ if (proxy_log) return;
+# endif /* PROXY_SUPPORT */
+
+ 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 (char *arg)
+{
+# ifdef PROXY_SUPPORT
+ /* Might as well skip this since this function does nothing anyhow. If
+ * it did do anything and could generate errors, then the line below would
+ * be necessary since this can be processed before a `Root' request.
+ *
+ * if (reprocessing) return;
+ */
+# endif /* PROXY_SUPPORT */
+}
+
+
+
+static void
+serve_unchanged (char *arg)
+{
+ struct an_entry *p;
+ char *name;
+ char *cp;
+ char *timefield;
+
+ if (error_pending ()
+# ifdef PROXY_SUPPORT
+ || proxy_log
+# endif /* PROXY_SUPPORT */
+ ) 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;
+ }
+
+ /* *timefield == '/'; */
+ }
+ if (*timefield != '+')
+ {
+ /* '+' is a conflict marker and we don't want to mess with it
+ * until Version_TS catches it.
+ */
+ if (timefield[1] != '/')
+ {
+ /* Obliterate anything else in TIMEFIELD. This is again to
+ * support the broken CVSNT clients mentioned below, in
+ * conjunction with strict timestamp string boundry
+ * checking in time_stamp_server() from vers_ts.c &
+ * file_has_conflict() from subr.c, since the broken
+ * clients used to send malformed timestamp fields in the
+ * Entry request that they then depended on the subsequent
+ * Unchanged request to overwrite.
+ */
+ char *d = timefield + 1;
+ if ((cp = strchr (d, '/')))
+ {
+ while (*cp)
+ {
+ *d++ = *cp++;
+ }
+ *d = '\0';
+ }
+ }
+ /* 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
+ * CVSNT (at least 2.0.34 - 2.0.41, reported fixed in 2.0.41a)
+ * and the WinCVS & TortoiseCVS clients which depend on those
+ * broken versions of CVSNT (WinCVS 1.3 & at least one
+ * TortoiseCVS release) rely on this behavior.
+ */
+ *timefield = '=';
+ }
+ break;
+ }
+ }
+}
+
+
+
+static void
+serve_entry (char *arg)
+{
+ struct an_entry *p;
+ char *cp;
+ int i = 0;
+
+ if (error_pending()
+# ifdef PROXY_SUPPORT
+ || proxy_log
+# endif /* PROXY_SUPPORT */
+ ) 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 (char *arg)
+{
+ if (error_pending ()
+# ifdef PROXY_SUPPORT
+ || proxy_log
+# endif /* PROXY_SUPPORT */
+ )
+ 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 (char *arg)
+{
+ struct timespec t;
+
+ if (error_pending ()
+# ifdef PROXY_SUPPORT
+ || proxy_log
+# endif /* PROXY_SUPPORT */
+ )
+ 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;
+ }
+
+ if (!get_date (&t, arg, NULL))
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text, "E cannot parse date %s", arg);
+ return;
+ }
+
+ /* Truncate any nanoseconds returned by get_date(). */
+ checkin_time = t.tv_sec;
+ checkin_time_valid = 1;
+}
+
+
+
+static void
+server_write_entries (void)
+{
+ 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;
+ }
+}
+
+
+
+# ifdef PROXY_SUPPORT
+/*
+ * callback proc to run a script when admin finishes.
+ */
+static int
+prepost_proxy_proc (const char *repository, const char *filter, void *closure)
+{
+ char *cmdline;
+ bool *pre = closure;
+
+ /* %c = cvs_cmd_name
+ * %p = shortrepos
+ * %r = repository
+ */
+ TRACE (TRACE_FUNCTION, "prepost_proxy_proc (%s, %s, %s)", repository,
+ filter, *pre ? "pre" : "post");
+
+ /*
+ * Cast any NULL arguments as appropriate pointers as this is an
+ * stdarg function and we need to be certain the caller gets what
+ * is expected.
+ */
+ cmdline = format_cmdline (
+# ifdef SUPPORT_OLD_INFO_FMT_STRINGS
+ 0, ".",
+# endif /* SUPPORT_OLD_INFO_FMT_STRINGS */
+ filter,
+ "c", "s", cvs_cmd_name,
+ "R", "s", referrer ? referrer->original : "NONE",
+ "p", "s", ".",
+ "r", "s", current_parsed_root->directory,
+ "P", "s", config->PrimaryServer->original,
+ (char *) NULL);
+
+ if (!cmdline || !strlen (cmdline))
+ {
+ if (cmdline) free (cmdline);
+ if (*pre)
+ error (0, 0, "preadmin proc resolved to the empty string!");
+ else
+ error (0, 0, "postadmin proc resolved to the empty string!");
+ return 1;
+ }
+
+ run_setup (cmdline);
+
+ free (cmdline);
+
+ /* FIXME - read the comment in verifymsg_proc() about why we use abs()
+ * below() and shouldn't.
+ */
+ return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
+ RUN_NORMAL | RUN_SIGIGNORE));
+}
+
+
+
+/* Become a secondary write proxy to a master server.
+ *
+ * This function opens the connection to the primary, dumps the secondary log
+ * to the primary, then reads data from any available connection and writes it
+ * to its partner:
+ *
+ * buf_from_net -> buf_to_primary
+ * buf_from_primary -> buf_to_net
+ *
+ * When all "from" connections have sent EOF and all data has been sent to
+ * "to" connections, this function closes the "to" pipes and returns.
+ */
+static void
+become_proxy (void)
+{
+ struct buffer *buf_to_primary;
+ struct buffer *buf_from_primary;
+
+ /* Close the client log and open it for read. */
+ struct buffer *buf_clientlog = log_buffer_rewind (proxy_log_out);
+ int status, to_primary_fd, from_primary_fd, to_net_fd, from_net_fd;
+
+ /* Call presecondary script. */
+ bool pre = true;
+
+ char *data;
+ size_t thispass, got;
+ int s;
+ char *newdata;
+
+ Parse_Info (CVSROOTADM_PREPROXY, current_parsed_root->directory,
+ prepost_proxy_proc, PIOPT_ALL, &pre);
+
+ /* Open connection to primary server. */
+ open_connection_to_server (config->PrimaryServer, &buf_to_primary,
+ &buf_from_primary);
+ setup_logfiles ("CVS_SECONDARY_LOG", &buf_to_primary, &buf_from_primary);
+ if ((status = set_nonblock (buf_from_primary)))
+ error (1, status, "failed to set nonblocking io from primary");
+ if ((status = set_nonblock (buf_from_net)))
+ error (1, status, "failed to set nonblocking io from client");
+ if ((status = set_nonblock (buf_to_primary)))
+ error (1, status, "failed to set nonblocking io to primary");
+ if ((status = set_nonblock (buf_to_net)))
+ error (1, status, "failed to set nonblocking io to client");
+
+ to_primary_fd = buf_get_fd (buf_to_primary);
+ from_primary_fd = buf_get_fd (buf_from_primary);
+ to_net_fd = buf_get_fd (buf_to_net);
+ assert (to_primary_fd >= 0 && from_primary_fd >= 0 && to_net_fd >= 0);
+
+ /* Close the client log and open it for read. */
+ rewind_buf_from_net ();
+
+ while (from_primary_fd >= 0 || to_primary_fd >= 0)
+ {
+ fd_set readfds, writefds;
+ int status, numfds = -1;
+ struct timeval *timeout_ptr;
+ struct timeval timeout;
+ size_t toread;
+
+ FD_ZERO (&readfds);
+ FD_ZERO (&writefds);
+
+ /* The fd for a multi-source buffer can change with any read. */
+ from_net_fd = buf_from_net ? buf_get_fd (buf_from_net) : -1;
+
+ if ((buf_from_net && !buf_empty_p (buf_from_net))
+ || (buf_from_primary && !buf_empty_p (buf_from_primary)))
+ {
+ /* There is data pending so don't block if we don't find any new
+ * data on the fds.
+ */
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 0;
+ timeout_ptr = &timeout;
+ }
+ else
+ /* block indefinately */
+ timeout_ptr = NULL;
+
+ /* Set writefds if data is pending. */
+ if (to_net_fd >= 0 && !buf_empty_p (buf_to_net))
+ {
+ FD_SET (to_net_fd, &writefds);
+ numfds = MAX (numfds, to_net_fd);
+ }
+ if (to_primary_fd >= 0 && !buf_empty_p (buf_to_primary))
+ {
+ FD_SET (to_primary_fd, &writefds);
+ numfds = MAX (numfds, to_primary_fd);
+ }
+
+ /* Set readfds if descriptors are still open. */
+ if (from_net_fd >= 0)
+ {
+ FD_SET (from_net_fd, &readfds);
+ numfds = MAX (numfds, from_net_fd);
+ }
+ if (from_primary_fd >= 0)
+ {
+ FD_SET (from_primary_fd, &readfds);
+ numfds = MAX (numfds, from_primary_fd);
+ }
+
+ /* NUMFDS needs to be the highest descriptor + 1 according to the
+ * select spec.
+ */
+ numfds++;
+
+ 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 (numfds, &readfds, &writefds,
+ NULL, timeout_ptr);
+ if (numfds < 0 && errno != EINTR)
+ {
+ /* Sending an error to the client, possibly in the middle of a
+ * separate protocol message, will likely not mean much to the
+ * client, but it's better than nothing, I guess.
+ */
+ buf_output0 (buf_to_net, "E select failed\n");
+ print_error (errno);
+ exit (EXIT_FAILURE);
+ }
+ } while (numfds < 0);
+
+ if (numfds == 0)
+ {
+ FD_ZERO (&readfds);
+ FD_ZERO (&writefds);
+ }
+
+ if (to_net_fd >= 0 && FD_ISSET (to_net_fd, &writefds))
+ {
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (buf_to_net);
+ buf_flush (buf_to_net, false);
+ }
+
+ status = 0;
+ if (from_net_fd >= 0 && (FD_ISSET (from_net_fd, &readfds)))
+ status = buf_input_data (buf_from_net, NULL);
+
+ if (buf_from_net && !buf_empty_p (buf_from_net))
+ {
+ if (buf_to_primary)
+ buf_append_buffer (buf_to_primary, buf_from_net);
+ else
+ /* (Sys?)log this? */;
+
+ }
+
+ if (status == -1 /* EOF */)
+ {
+ SIG_beginCrSect();
+ /* Need only to shut this down and set to NULL, really, in
+ * crit sec, to ensure no double-dispose and to make sure
+ * network pipes are closed as properly as possible, but I
+ * don't see much optimization potential in saving values and
+ * postponing the free.
+ */
+ buf_shutdown (buf_from_net);
+ buf_free (buf_from_net);
+ buf_from_net = NULL;
+ /* So buf_to_primary will be closed at the end of this loop. */
+ from_net_fd = -1;
+ SIG_endCrSect();
+ }
+ else if (status > 0 /* ERRNO */)
+ {
+ buf_output0 (buf_to_net,
+ "E buf_input_data failed reading from client\n");
+ print_error (status);
+ exit (EXIT_FAILURE);
+ }
+
+ if (to_primary_fd >= 0 && FD_ISSET (to_primary_fd, &writefds))
+ {
+ /* What should we do with errors? syslog() them? */
+ buf_send_output (buf_to_primary);
+ buf_flush (buf_to_primary, false);
+ }
+
+ status = 0;
+ if (from_primary_fd >= 0 && FD_ISSET (from_primary_fd, &readfds))
+ status = buf_input_data (buf_from_primary, &toread);
+
+ /* Avoid resending data from the server which we already sent to the
+ * client. Otherwise clients get really confused.
+ */
+ if (buf_clientlog
+ && buf_from_primary && !buf_empty_p (buf_from_primary))
+ {
+ /* Dispose of data we already sent to the client. */
+ while (buf_clientlog && toread > 0)
+ {
+ s = buf_read_data (buf_clientlog, toread, &data, &got);
+ if (s == -2)
+ error (1, ENOMEM, "Failed to read data.");
+ if (s == -1)
+ {
+ buf_shutdown (buf_clientlog);
+ buf_clientlog = NULL;
+ }
+ else if (s)
+ error (1, s, "Error reading writeproxy log.");
+ else
+ {
+ thispass = got;
+ while (thispass > 0)
+ {
+ /* No need to check for errors here since we know we
+ * won't read more than buf_input read into
+ * BUF_FROM_PRIMARY (see how TOREAD is set above).
+ */
+ buf_read_data (buf_from_primary, thispass, &newdata,
+ &got);
+ /* Verify that we are throwing away what we think we
+ * are.
+ *
+ * It is valid to assume that the secondary and primary
+ * are closely enough in sync that this portion of the
+ * communication will be in sync beacuse if they were
+ * not, then the secondary might provide a
+ * valid-request string to the client which contained a
+ * request that the primary didn't support. If the
+ * client later used the request, the primary server
+ * would exit anyhow.
+ *
+ * FIXME?
+ * An alternative approach might be to make sure that
+ * the secondary provides the same string as the
+ * primary regardless, for purposes like pointing a
+ * secondary at an unwitting primary, in which case it
+ * might be useful to have some way to override the
+ * valid-requests string on a secondary, but it seems
+ * much easier to simply sync the versions, at the
+ * moment.
+ */
+ if (memcmp (data, newdata, got))
+ error (1, 0, "Secondary out of sync with primary!");
+ data += got;
+ thispass -= got;
+ }
+ toread -= got;
+ }
+ }
+ }
+
+ if (buf_from_primary && !buf_empty_p (buf_from_primary))
+ {
+ if (buf_to_net)
+ buf_append_buffer (buf_to_net, buf_from_primary);
+ else
+ /* (Sys?)log this? */;
+
+ }
+
+ if (status == -1 /* EOF */)
+ {
+ buf_shutdown (buf_from_primary);
+ buf_from_primary = NULL;
+ from_primary_fd = -1;
+ }
+ else if (status > 0 /* ERRNO */)
+ {
+ buf_output0 (buf_to_net,
+ "E buf_input_data failed reading from primary\n");
+ print_error (status);
+ exit (EXIT_FAILURE);
+ }
+
+ /* If our "source pipe" is closed and all data has been sent, avoid
+ * selecting it for writability, but don't actually close the buffer in
+ * case other routines want to use it later. The buffer will be closed
+ * in server_cleanup ().
+ */
+ if (from_primary_fd < 0
+ && buf_to_net && buf_empty_p (buf_to_net))
+ to_net_fd = -1;
+
+ if (buf_to_primary
+ && (/* Assume that there is no further reason to keep the buffer to
+ * the primary open if we can no longer read its responses.
+ */
+ (from_primary_fd < 0 && buf_to_primary)
+ /* Also close buf_to_primary when it becomes impossible to find
+ * more data to send to it. We don't close buf_from_primary
+ * yet since there may be data pending or the primary may react
+ * to the EOF on its input pipe.
+ */
+ || (from_net_fd < 0 && buf_empty_p (buf_to_primary))))
+ {
+ buf_shutdown (buf_to_primary);
+ buf_free (buf_to_primary);
+ buf_to_primary = NULL;
+
+ /* Setting the fd < 0 with from_primary_fd already < 0 will cause
+ * an escape from this while loop.
+ */
+ to_primary_fd = -1;
+ }
+ }
+
+ /* Call postsecondary script. */
+ pre = false;
+ Parse_Info (CVSROOTADM_POSTPROXY, current_parsed_root->directory,
+ prepost_proxy_proc, PIOPT_ALL, &pre);
+}
+# endif /* PROXY_SUPPORT */
+
+
+
+struct notify_note {
+ /* Directory in which this notification happens. xmalloc'd*/
+ char *dir;
+
+ /* xmalloc'd. */
+ char *update_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 (char *arg)
+{
+ struct notify_note *new = NULL;
+ char *data = NULL;
+ int status;
+
+ if (error_pending ()) return;
+
+ if (isProxyServer())
+ {
+# ifdef PROXY_SUPPORT
+ if (!proxy_log)
+ {
+# endif /* PROXY_SUPPORT */
+ if (alloc_pending (160) + strlen (program_name))
+ sprintf (pending_error_text,
+"E This CVS server does not support disconnected `%s edit'. For now, remove all `%s' files in your workspace and try your command again.",
+ program_name, CVSADM_NOTIFY);
+ return;
+# ifdef PROXY_SUPPORT
+ }
+ else
+ {
+ /* This is effectively a write command, so run it on the primary. */
+ become_proxy ();
+ exit (EXIT_SUCCESS);
+ }
+# endif /* PROXY_SUPPORT */
+ }
+
+ if (outside_dir (arg))
+ return;
+
+ if (gDirname == NULL)
+ goto error;
+
+ new = xmalloc (sizeof (struct notify_note));
+ if (new == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ new->dir = xmalloc (strlen (gDirname) + 1);
+ new->update_dir = xmalloc (strlen (gupdate_dir) + 1);
+ new->filename = xmalloc (strlen (arg) + 1);
+ if (new->dir == NULL || new->update_dir == NULL || new->filename == NULL)
+ {
+ pending_error = ENOMEM;
+ if (new->dir != NULL)
+ free (new->dir);
+ free (new);
+ return;
+ }
+ strcpy (new->dir, gDirname);
+ strcpy (new->update_dir, gupdate_dir);
+ strcpy (new->filename, arg);
+
+ status = buf_read_line (buf_from_net, &data, 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->update_dir);
+ free (new->dir);
+ free (new);
+ }
+ return;
+}
+
+
+
+static void
+serve_hostname (char *arg)
+{
+ free (hostname);
+ hostname = xstrdup (arg);
+ return;
+}
+
+
+
+static void
+serve_localdir (char *arg)
+{
+ if (CurDir) free (CurDir);
+ CurDir = xstrdup (arg);
+}
+
+
+
+/* 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 (void)
+{
+ struct notify_note *p;
+ char *repos;
+
+ TRACE (TRACE_FUNCTION, "server_notify()");
+
+ 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,
+ notify_list->update_dir, 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;
+}
+
+
+
+/* This request is processed in all passes since requests which must
+ * sometimes be processed before it is known whether we are running as a
+ * secondary or not, for instance the `expand-modules' request, sometimes use
+ * the `Arguments'.
+ */
+static void
+serve_argument (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 = xnrealloc (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;
+}
+
+
+
+/* For secondary servers, this is handled in all passes, as is the `Argument'
+ * request, and for the same reasons.
+ */
+static void
+serve_argumentx (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 (char *arg)
+{
+# ifdef PROXY_SUPPORT
+ /* This can generate error messages and termination before `Root' requests,
+ * so it must be dealt with in the first pass.
+ */
+ if (reprocessing) return;
+# endif /* PROXY_SUPPORT */
+
+ 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++;
+ break;
+ default:
+ goto error_return;
+ }
+}
+
+
+
+/* This needs to be processed before Root requests, so we allow it to be
+ * be processed before knowing whether we are running as a secondary server
+ * to allow `noop' and `Root' requests to generate errors as before.
+ */
+static void
+serve_set (char *arg)
+{
+# ifdef PROXY_SUPPORT
+ if (reprocessing) return;
+# endif /* PROXY_SUPPORT */
+
+ /* 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( char *arg )
+{
+# ifdef PROXY_SUPPORT
+ assert (!proxy_log);
+# endif /* PROXY_SUPPORT */
+
+ /* 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( char *arg )
+{
+# ifdef PROXY_SUPPORT
+ assert (!proxy_log);
+# endif /* PROXY_SUPPORT */
+
+ 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 (char *arg)
+{
+# ifdef PROXY_SUPPORT
+ assert (!proxy_log);
+# endif /* PROXY_SUPPORT */
+
+ 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 (char *arg)
+{
+ static int initted;
+
+# ifdef PROXY_SUPPORT
+ if (proxy_log) return;
+# endif /* PROXY_SUPPORT */
+
+ if (error_pending ()) return;
+
+ if (!initted)
+ {
+ /* Pick up ignores from CVSROOTADM_IGNORE, $HOME/.cvsignore on server,
+ and CVSIGNORE on server. */
+ ign_setup ();
+ initted = 1;
+ }
+
+ if (gDirname == NULL)
+ {
+ if (alloc_pending (80))
+ sprintf (pending_error_text,
+"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 = gDirname + 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 = NULL;
+
+/* 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 (struct buffer *buf)
+{
+ error (1, ENOMEM, "Virtual memory exhausted");
+}
+
+
+
+/* If command is valid, return 1.
+ * Else if command is invalid and croak_on_invalid is set, then die.
+ * Else just return 0 to indicate that command is invalid.
+ */
+static bool
+check_command_valid_p (char *cmd_name)
+{
+ /* Right now, only pserver notices invalid 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 true;
+
+ 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 false;
+ }
+ }
+ 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_invalid;
+ }
+ 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 true;
+ }
+ 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 false;
+ }
+ }
+
+ 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 true;
+ }
+ else /* writers file exists, but this user not listed in it */
+ {
+ handle_invalid:
+ if (fclose (fp) < 0)
+ error (0, errno, "cannot close %s", fname);
+ if (linebuf)
+ free (linebuf);
+ free (fname);
+ return false;
+ }
+ }
+# endif /* AUTH_SERVER_SUPPORT */
+
+ /* If ever reach end of this function, command must be valid. */
+ return true;
+}
+
+
+
+/* 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 (int fd)
+{
+# if defined (F_GETFL) && defined (O_NONBLOCK) && defined (F_SETFL)
+ int flags;
+
+ flags = fcntl (fd, F_GETFL, 0);
+ if (flags < 0)
+ return errno;
+ if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0)
+ return errno;
+# endif /* F_GETFL && O_NONBLOCK && F_SETFL */
+ return 0;
+}
+
+
+
+static void
+do_cvs_command (char *cmd_name, int (*command) (int, char **))
+{
+ /*
+ * 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;
+
+ TRACE (TRACE_FUNCTION, "do_cvs_command (%s)", cmd_name);
+
+ /* Write proxy logging is always terminated when a command is received.
+ * Therefore, we wish to avoid reprocessing the command since that would
+ * cause endless recursion.
+ */
+ if (isProxyServer())
+ {
+# ifdef PROXY_SUPPORT
+ if (reprocessing)
+ /* This must be the second time we've reached this point.
+ * Done reprocessing.
+ */
+ reprocessing = false;
+ else
+ {
+ if (lookup_command_attribute (cmd_name)
+ & CVS_CMD_MODIFIES_REPOSITORY)
+ {
+ become_proxy ();
+ exit (EXIT_SUCCESS);
+ }
+ else if (/* serve_co may have called this already and missing logs
+ * should have generated an error in serve_root().
+ */
+ proxy_log)
+ {
+ /* Set up the log for reprocessing. */
+ rewind_buf_from_net ();
+ /* And return to the main loop in server(), where we will now
+ * find the logged secondary data and reread it.
+ */
+ return;
+ }
+ }
+# else /* !PROXY_SUPPORT */
+ if (lookup_command_attribute (cmd_name)
+ & CVS_CMD_MODIFIES_REPOSITORY
+ && alloc_pending (120))
+ sprintf (pending_error_text,
+"E You need a CVS client that supports the `Redirect' response for write requests to this server.");
+ return;
+# endif /* PROXY_SUPPORT */
+ }
+
+ 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 valid for
+ this user. */
+ if (!check_command_valid_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, NULL, false,
+ 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"));
+ TRACE (TRACE_DATA, "Sleeping CVS_SERVER_SLEEP (%d) seconds", secs);
+ sleep (secs);
+ }
+ else
+ TRACE (TRACE_DATA, "CVS_SERVER_SLEEP not set.");
+
+ 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;
+ ssize_t status;
+ while ((status = read (flowcontrol_pipe[0], &junk, 1)) > 0
+ || (status == -1 && errno == EAGAIN));
+ }
+ /* FIXME: No point in printing an error message with error(),
+ * as STDERR is already closed, but perhaps this could be syslogged?
+ */
+# endif
+
+ exit (exitstatus);
+ }
+
+ /* OK, sit around getting all the input from the child. */
+ {
+ struct buffer *stdoutbuf;
+ struct buffer *stderrbuf;
+ struct buffer *protocol_inbuf;
+ /* 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);
+ num_to_check = MAX (num_to_check, stderr_pipe[0]);
+ FD_SET (stderr_pipe[0], &command_fds_to_drain.fds);
+ num_to_check = MAX (num_to_check, protocol_pipe[0]);
+ FD_SET (protocol_pipe[0], &command_fds_to_drain.fds);
+ num_to_check = MAX (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], 0, NULL, true,
+ input_memory_error);
+
+ stderrbuf = fd_buffer_initialize (stderr_pipe[0], 0, NULL, true,
+ input_memory_error);
+
+ protocol_inbuf = fd_buffer_initialize (protocol_pipe[0], 0, NULL, true,
+ 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);
+ goto error_exit;
+ }
+ stdout_pipe[1] = -1;
+
+ if (close (stderr_pipe[1]) < 0)
+ {
+ buf_output0 (buf_to_net, "E close failed\n");
+ print_error (errno);
+ goto error_exit;
+ }
+ stderr_pipe[1] = -1;
+
+ if (close (protocol_pipe[1]) < 0)
+ {
+ buf_output0 (buf_to_net, "E close failed\n");
+ print_error (errno);
+ goto error_exit;
+ }
+ 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);
+ goto error_exit;
+ }
+ 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);
+ goto error_exit;
+ }
+ 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,
+ NULL, timeout_ptr);
+ if (numfds < 0
+ && errno != EINTR)
+ {
+ buf_output0 (buf_to_net, "E select failed\n");
+ print_error (errno);
+ goto error_exit;
+ }
+ } 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;
+ size_t 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);
+ goto error_exit;
+ }
+
+ /*
+ * 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, 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);
+ goto error_exit;
+ }
+
+ /* 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, 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);
+ goto error_exit;
+ }
+
+ /* 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;
+ }
+
+ /*
+ * 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);
+ buf_shutdown (protocol_inbuf);
+ buf_free (protocol_inbuf);
+ protocol_inbuf = NULL;
+ buf_shutdown (stderrbuf);
+ buf_free (stderrbuf);
+ stderrbuf = NULL;
+ buf_shutdown (stdoutbuf);
+ buf_free (stdoutbuf);
+ stdoutbuf = NULL;
+ }
+
+ 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, NULL, 0);
+ if (waited_pid < 0 && errno == EINTR)
+ continue;
+ if (waited_pid == command_pid)
+ command_pid = -1;
+ }
+
+ 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(void)
+{
+ 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, NULL, NULL, 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 (const char *update_dir, const char *repository)
+{
+ /* Set up SHORT_REPOS. */
+ const char *short_repos = Short_Repository (repository);
+
+ /* Send the update_dir/repos. */
+ 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");
+ if (short_repos[0] == '\0')
+ buf_output0 (protocol, ".");
+ else
+ buf_output0 (protocol, short_repos);
+ 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 (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 = "";
+
+ TRACE (TRACE_FUNCTION, "server_register(%s, %s, %s, %s, %s, %s, %s)",
+ 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 (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 (void)
+{
+ kill_scratched_file = 0;
+}
+
+
+
+/* Print a new entries line, from a previous server_register. */
+static void
+new_entries_line (void)
+{
+ 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 (char *arg)
+{
+ do_cvs_command ("commit", commit);
+}
+
+
+
+static void
+checked_in_response (const char *file, const char *update_dir,
+ const char *repository)
+{
+ if (supported_response ("Mode"))
+ {
+ struct stat sb;
+ char *mode_string;
+
+ if (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 (const char *file, const char *update_dir,
+ const char *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 (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 (char *arg)
+{
+ do_cvs_command ("update", update);
+}
+
+
+
+static void
+serve_diff (char *arg)
+{
+ do_cvs_command ("diff", diff);
+}
+
+
+
+static void
+serve_log (char *arg)
+{
+ do_cvs_command ("log", cvslog);
+}
+
+
+
+static void
+serve_rlog (char *arg)
+{
+ do_cvs_command ("rlog", cvslog);
+}
+
+
+
+static void
+serve_ls (char *arg)
+{
+ do_cvs_command ("ls", ls);
+}
+
+
+
+static void
+serve_rls (char *arg)
+{
+ do_cvs_command ("rls", ls);
+}
+
+
+
+static void
+serve_add (char *arg)
+{
+ do_cvs_command ("add", add);
+}
+
+
+
+static void
+serve_remove (char *arg)
+{
+ do_cvs_command ("remove", cvsremove);
+}
+
+
+
+static void
+serve_status (char *arg)
+{
+ do_cvs_command ("status", cvsstatus);
+}
+
+
+
+static void
+serve_rdiff (char *arg)
+{
+ do_cvs_command ("rdiff", patch);
+}
+
+
+
+static void
+serve_tag (char *arg)
+{
+ do_cvs_command ("tag", cvstag);
+}
+
+
+
+static void
+serve_rtag (char *arg)
+{
+ do_cvs_command ("rtag", cvstag);
+}
+
+
+
+static void
+serve_import (char *arg)
+{
+ do_cvs_command ("import", import);
+}
+
+
+
+static void
+serve_admin (char *arg)
+{
+ do_cvs_command ("admin", admin);
+}
+
+
+
+static void
+serve_history (char *arg)
+{
+ do_cvs_command ("history", history);
+}
+
+
+
+static void
+serve_release (char *arg)
+{
+ do_cvs_command ("release", release);
+}
+
+
+
+static void
+serve_watch_on (char *arg)
+{
+ do_cvs_command ("watch", watch_on);
+}
+
+
+
+static void
+serve_watch_off (char *arg)
+{
+ do_cvs_command ("watch", watch_off);
+}
+
+
+
+static void
+serve_watch_add (char *arg)
+{
+ do_cvs_command ("watch", watch_add);
+}
+
+
+
+static void
+serve_watch_remove (char *arg)
+{
+ do_cvs_command ("watch", watch_remove);
+}
+
+
+
+static void
+serve_watchers (char *arg)
+{
+ do_cvs_command ("watchers", watchers);
+}
+
+
+
+static void
+serve_editors (char *arg)
+{
+ do_cvs_command ("editors", editors);
+}
+
+
+
+static void
+serve_edit (char *arg)
+{
+ do_cvs_command ("edit", edit);
+}
+
+
+
+# ifdef PROXY_SUPPORT
+/* We need to handle some of this before reprocessing since it is defined to
+ * send a response and print errors before a Root request is received.
+ */
+# endif /* PROXY_SUPPORT */
+static void
+serve_noop (char *arg)
+{
+ /* Errors could be encountered in the first or second passes, so always
+ * send them to the client.
+ */
+ bool pe = print_pending_error();
+
+# ifdef PROXY_SUPPORT
+ /* The portions below need not be handled until reprocessing anyhow since
+ * there should be no entries or notifications prior to that. */
+ if (!proxy_log)
+# endif /* PROXY_SUPPORT */
+ {
+ server_write_entries ();
+ if (!pe)
+ (void) server_notify ();
+ }
+
+ if (!pe
+# ifdef PROXY_SUPPORT
+ /* "ok" only goes across in the first pass. */
+ && !reprocessing
+# endif /* PROXY_SUPPORT */
+ )
+ buf_output0 (buf_to_net, "ok\n");
+ buf_flush (buf_to_net, 1);
+}
+
+
+
+static void
+serve_version (char *arg)
+{
+ do_cvs_command ("version", version);
+}
+
+
+
+static void
+serve_init (char *arg)
+{
+ cvsroot_t *saved_parsed_root;
+
+ if (!ISABSOLUTE (arg))
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E init %s must be an absolute pathname", arg);
+ }
+# ifdef AUTH_SERVER_SUPPORT
+ else 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: init says \"%s\" but pserver says \"%s\"",
+ arg, Pserver_Repos);
+ }
+ }
+# endif
+
+ if (print_pending_error ())
+ return;
+
+ saved_parsed_root = current_parsed_root;
+ current_parsed_root = local_cvsroot (arg);
+
+ do_cvs_command ("init", init);
+
+ /* Do not free CURRENT_PARSED_ROOT since it is still in the cache. */
+ current_parsed_root = saved_parsed_root;
+}
+
+
+
+static void
+serve_annotate (char *arg)
+{
+ do_cvs_command ("annotate", annotate);
+}
+
+
+
+static void
+serve_rannotate (char *arg)
+{
+ do_cvs_command ("rannotate", annotate);
+}
+
+
+
+static void
+serve_co (char *arg)
+{
+ if (print_pending_error ())
+ return;
+
+# ifdef PROXY_SUPPORT
+ /* If we are not a secondary server, the write proxy log will already have
+ * been processed.
+ */
+ if (isProxyServer ())
+ {
+ if (reprocessing)
+ reprocessing = false;
+ else if (/* The proxy log may be closed if the client sent a
+ * `Command-prep' request.
+ */
+ proxy_log)
+ {
+ /* Set up the log for reprocessing. */
+ rewind_buf_from_net ();
+ /* And return to the main loop in server(), where we will now find
+ * the logged secondary data and reread it.
+ */
+ return;
+ }
+ }
+# endif /* PROXY_SUPPORT */
+
+ /* 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") ? "export" : "checkout",
+ checkout);
+}
+
+
+
+static void
+serve_export (char *arg)
+{
+ /* Tell checkout() to behave like export not checkout. */
+ cvs_cmd_name = "export";
+ serve_co (arg);
+}
+
+
+
+void
+server_copy_file (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 (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. */
+void
+server_updated (
+ struct file_info *finfo,
+ Vers_TS *vers,
+ enum server_updated_arg4 updated,
+ mode_t mode,
+ unsigned char *checksum,
+ struct buffer *filebuf)
+{
+ 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 (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;
+ Entnode *entnode;
+
+ 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);
+ 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 (void)
+{
+ return supported_response ("Rcs-diff");
+}
+
+
+
+void
+server_set_entstat (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 (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 (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);
+}
+
+
+
+void
+server_edit_file (struct file_info *finfo)
+{
+ buf_output (protocol, "Edit-file ", 10);
+ output_dir (finfo->update_dir, finfo->repository);
+ buf_output0 (protocol, finfo->file);
+ buf_output (protocol, "\n", 1);
+ buf_send_counted (protocol);
+}
+
+
+
+struct template_proc_data
+{
+ const char *update_dir;
+ const char *repository;
+};
+
+static int
+template_proc (const char *repository, const char *template, void *closure)
+{
+ FILE *fp;
+ char buf[1024];
+ size_t n;
+ struct stat sb;
+ struct template_proc_data *data = (struct template_proc_data *)closure;
+
+ 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_clear_template (const char *update_dir, const char *repository)
+{
+ assert (update_dir != NULL);
+
+ if (noexec)
+ return;
+
+ if (!supported_response ("Clear-template") &&
+ !supported_response ("Template"))
+ /* Might want to warn the user that the rcsinfo feature won't work. */
+ return;
+
+ if (supported_response ("Clear-template"))
+ {
+ buf_output0 (protocol, "Clear-template ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, "\n");
+ buf_send_counted (protocol);
+ }
+ else
+ {
+ buf_output0 (protocol, "Template ");
+ output_dir (update_dir, repository);
+ buf_output0 (protocol, "\n");
+ buf_output0 (protocol, "0\n");
+ buf_send_counted (protocol);
+ }
+}
+
+
+
+void
+server_template (const char *update_dir, const char *repository)
+{
+ struct template_proc_data data;
+ data.update_dir = update_dir;
+ data.repository = repository;
+ (void) Parse_Info (CVSROOTADM_RCSINFO, repository, template_proc,
+ PIOPT_ALL, &data);
+}
+
+
+
+static void
+serve_gzip_contents (char *arg)
+{
+ int level;
+ bool forced = false;
+
+# ifdef PROXY_SUPPORT
+ assert (!proxy_log);
+# endif /* PROXY_SUPPORT */
+
+ level = atoi (arg);
+ if (level == 0)
+ level = 6;
+
+ if (config && level < config->MinCompressionLevel)
+ {
+ level = config->MinCompressionLevel;
+ forced = true;
+ }
+ if (config && level > config->MaxCompressionLevel)
+ {
+ level = config->MaxCompressionLevel;
+ forced = true;
+ }
+
+ if (forced && !quiet
+ && alloc_pending_warning (120 + strlen (program_name)))
+ sprintf (pending_warning_text,
+"E %s server: Forcing compression level %d (allowed: %d <= z <= %d).",
+ program_name, level, config->MinCompressionLevel,
+ config->MaxCompressionLevel);
+
+ gzip_level = file_gzip_level = level;
+}
+
+
+
+static void
+serve_gzip_stream (char *arg)
+{
+ int level;
+ bool forced = false;
+
+ level = atoi (arg);
+
+ if (config && level < config->MinCompressionLevel)
+ {
+ level = config->MinCompressionLevel;
+ forced = true;
+ }
+ if (config && level > config->MaxCompressionLevel)
+ {
+ level = config->MaxCompressionLevel;
+ forced = true;
+ }
+
+ if (forced && !quiet
+ && alloc_pending_warning (120 + strlen (program_name)))
+ sprintf (pending_warning_text,
+"E %s server: Forcing compression level %d (allowed: %d <= z <= %d).",
+ program_name, level, config->MinCompressionLevel,
+ config->MaxCompressionLevel);
+
+ gzip_level = level;
+
+ /* All further communication with the client will be compressed.
+ *
+ * The deflate buffers need to be initialized even for compression level
+ * 0, or the client will no longer be able to understand us. At
+ * compression level 0, the correct compression headers will be created and
+ * sent, but data will thereafter simply be copied to the network buffers.
+ */
+
+ /* This needs to be processed in both passes so that we may continue to
+ * understand client requests on both the socket and from the log.
+ */
+ buf_from_net = compress_buffer_initialize (buf_from_net, 1,
+ 0 /* Not used. */,
+ buf_from_net->memory_error);
+
+ /* This needs to be skipped in subsequent passes to avoid compressing data
+ * to the client twice.
+ */
+# ifdef PROXY_SUPPORT
+ if (reprocessing) return;
+# endif /* PROXY_SUPPORT */
+ buf_to_net = compress_buffer_initialize (buf_to_net, 0, level,
+ buf_to_net->memory_error);
+}
+
+
+
+/* Tell the client about RCS options set in CVSROOT/cvswrappers. */
+static void
+serve_wrapper_sendme_rcs_options (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;
+
+# ifdef PROXY_SUPPORT
+ if (reprocessing) return;
+# endif /* PROXY_SUPPORT */
+
+ 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 (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.
+ */
+# ifdef PROXY_SUPPORT
+ assert (!proxy_log);
+# endif /* PROXY_SUPPORT */
+}
+
+
+
+static int
+expand_proc (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 (char *arg)
+{
+ int i;
+ int err = 0;
+ DBM *db;
+
+# ifdef PROXY_SUPPORT
+ /* This needs to be processed in the first pass since the client expects a
+ * response but we may not yet know if we are a secondary.
+ *
+ * On the second pass, we still must make sure to ignore the arguments.
+ */
+ if (!reprocessing)
+# endif /* PROXY_SUPPORT */
+ {
+ 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, 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;
+ }
+
+# ifdef PROXY_SUPPORT
+ if (!reprocessing)
+# endif /* PROXY_SUPPORT */
+ {
+ 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);
+ }
+}
+
+
+
+/* Decide if we should redirect the client to another server.
+ *
+ * GLOBALS
+ * config->PrimaryServer The server to redirect write requests to, if
+ * any.
+ *
+ * ASSUMPTIONS
+ * The `Root' request has already been processed.
+ *
+ * RETURNS
+ * Nothing.
+ */
+static void
+serve_command_prep (char *arg)
+{
+ bool redirect_supported;
+# ifdef PROXY_SUPPORT
+ bool ditch_log;
+# endif /* PROXY_SUPPORT */
+
+ if (print_pending_error ()) return;
+
+ redirect_supported = supported_response ("Redirect");
+ if (redirect_supported
+ && lookup_command_attribute (arg) & CVS_CMD_MODIFIES_REPOSITORY
+ /* I call isProxyServer() last because it can probably be the slowest
+ * call due to the call to gethostbyname().
+ */
+ && isProxyServer ())
+ {
+ /* Before sending a redirect, send a "Referrer" line to the client,
+ * if possible, to give admins more control over canonicalizing roots
+ * sent from the client.
+ */
+ if (supported_response ("Referrer"))
+ {
+ /* assume :ext:, since that is all we currently support for
+ * proxies and redirection.
+ */
+ char *referrer = Xasprintf (":ext:%s@%s%s", getcaller(),
+ server_hostname,
+ current_parsed_root->directory);
+
+ buf_output0 (buf_to_net, "Referrer ");
+ buf_output0 (buf_to_net, referrer);
+ buf_output0 (buf_to_net, "\n");
+
+ free (referrer);
+ }
+
+ /* Send `Redirect' to redirect client requests to the primary. */
+ buf_output0 (buf_to_net, "Redirect ");
+ buf_output0 (buf_to_net, config->PrimaryServer->original);
+ buf_output0 (buf_to_net, "\n");
+ buf_flush (buf_to_net, 1);
+# ifdef PROXY_SUPPORT
+ ditch_log = true;
+# endif /* PROXY_SUPPORT */
+ }
+ else
+ {
+ /* Send `ok' so the client can proceed. */
+ buf_output0 (buf_to_net, "ok\n");
+ buf_flush (buf_to_net, 1);
+# ifdef PROXY_SUPPORT
+ if (lookup_command_attribute (arg) & CVS_CMD_MODIFIES_REPOSITORY
+ && isProxyServer ())
+ /* Don't ditch the log for write commands on a proxy server. We
+ * we got here because the `Redirect' response was not supported.
+ */
+ ditch_log = false;
+ else
+ ditch_log = true;
+# endif /* PROXY_SUPPORT */
+ }
+# ifdef PROXY_SUPPORT
+ if (proxy_log && ditch_log)
+ {
+ /* If the client supported the redirect response, then they will always
+ * be redirected if they are preparing for a write request. It is
+ * therefore safe to close the proxy logs.
+ *
+ * If the client is broken and ignores the redirect, this will be
+ * detected later, in rewind_buf_from_net().
+ *
+ * Since a `Command-prep' response is only acceptable immediately
+ * following the `Root' request according to the specification, there
+ * is no need to rewind the log and reprocess.
+ */
+ log_buffer_closelog (proxy_log);
+ log_buffer_closelog (proxy_log_out);
+ proxy_log = NULL;
+ }
+# endif /* PROXY_SUPPORT */
+}
+
+
+
+/* Save a referrer, potentially for passing to hook scripts later.
+ *
+ * GLOBALS
+ * referrer Where we save the parsed referrer.
+ *
+ * ASSUMPTIONS
+ * The `Root' request has already been processed.
+ * There is no need to dispose of REFERRER if it is set. It's memory is
+ * tracked by parse_root().
+ *
+ * RETURNS
+ * Nothing.
+ */
+static void
+serve_referrer (char *arg)
+{
+ if (error_pending ()) return;
+
+ referrer = parse_cvsroot (arg);
+
+ if (!referrer
+ && alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E Protocol error: Invalid Referrer: `%s'",
+ arg);
+}
+
+
+
+static void serve_valid_requests (char *arg);
+
+#endif /* SERVER_SUPPORT */
+/*
+ * Comment to move position of the following #if line which works
+ * around an apparent bug in Microsoft Visual C++ 6.0 compiler.
+ */
+#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("Command-prep", serve_command_prep, 0),
+ REQ_LINE("Referrer", serve_referrer, 0),
+ REQ_LINE("Repository", serve_repository, 0),
+ REQ_LINE("Directory", serve_directory, RQ_ESSENTIAL),
+ REQ_LINE("Relative-directory", serve_directory, 0),
+ 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),
+
+ /* 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("Hostname", serve_hostname, 0),
+ REQ_LINE("LocalDir", serve_localdir, 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),
+ /* This is rootless, even though the client/server spec does not specify
+ * such, to allow error messages to be understood by the client when they are
+ * sent.
+ */
+ REQ_LINE("Gzip-stream", serve_gzip_stream, RQ_ROOTLESS),
+ REQ_LINE("wrapper-sendme-rcsOptions",
+ serve_wrapper_sendme_rcs_options,
+ 0),
+ REQ_LINE("Set", serve_set, RQ_ROOTLESS),
+#ifdef ENCRYPTION
+ /* These are rootless despite what the client/server spec says for the same
+ * reasons as Gzip-stream.
+ */
+# ifdef HAVE_KERBEROS
+ REQ_LINE("Kerberos-encrypt", serve_kerberos_encrypt, RQ_ROOTLESS),
+# endif
+# ifdef HAVE_GSSAPI
+ REQ_LINE("Gssapi-encrypt", serve_gssapi_encrypt, RQ_ROOTLESS),
+# endif
+#endif
+#ifdef HAVE_GSSAPI
+ REQ_LINE("Gssapi-authenticate", serve_gssapi_authenticate, RQ_ROOTLESS),
+#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("list", serve_ls, 0),
+ REQ_LINE("rlist", serve_rls, 0),
+ /* This allows us to avoid sending `-q' as a command argument to `cvs ls',
+ * or more accurately, allows us to send `-q' to backwards CVSNT servers.
+ */
+ REQ_LINE("global-list-quiet", serve_noop, RQ_ROOTLESS),
+ /* Deprecated synonym for rlist, for compatibility with CVSNT. */
+ REQ_LINE("ls", serve_rls, 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, RQ_ROOTLESS),
+ 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("edit", serve_edit, 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
+/*
+ * This server request is not ignored by the secondary.
+ */
+static void
+serve_valid_requests (char *arg)
+{
+ struct request *rq;
+
+ /* Since this is processed in the first pass, don't reprocess it in the
+ * second.
+ *
+ * We still print errors since new errors could have been generated in the
+ * second pass.
+ */
+ if (print_pending_error ()
+#ifdef PROXY_SUPPORT
+ || reprocessing
+#endif /* PROXY_SUPPORT */
+ )
+ 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);
+ }
+ }
+
+ if (config && config->MinCompressionLevel
+ && supported_response ("Force-gzip"))
+ {
+ buf_output0 (buf_to_net, "\n");
+ buf_output0 (buf_to_net, "Force-gzip");
+ }
+
+ 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 (int sig)
+{
+ int status;
+ pid_t r = wait (&status);
+ if (r == command_pid)
+ command_pid_is_dead++;
+}
+#endif /* SUNOS_KLUDGE */
+
+
+
+/*
+ * This function cleans up after the server. Specifically, it:
+ *
+ * <ol>
+ * <li>Sets BUF_TO_NET to blocking and fluxhes it.</li>
+ * <li>With SUNOS_KLUDGE enabled:
+ * <ol>
+ * <li>Terminates the command process.</li>
+ * <li>Waits on the command process, draining output as necessary.</li>
+ * </ol>
+ * </li>
+ * <li>Removes the temporary directory.</li>
+ * <li>Flush and shutdown the buffers.</li>
+ * <li>Set ERROR_USE_PROTOCOL and SERVER_ACTIVE to false.</li>
+ * </ol>
+ *
+ * NOTES
+ * This function needs to be reentrant since a call to exit() can cause a
+ * call to this function, which can then be interrupted by a signal, which
+ * can cause a second call to this function.
+ *
+ * GLOBALS
+ * buf_from_net The input buffer which brings data from the
+ * CVS client.
+ * buf_to_net The output buffer which moves data to the CVS
+ * client.
+ * error_use_protocol Set when the server parent process is active.
+ * Cleared for the server child processes.
+ * dont_delete_temp Set when a core dump of a child process is
+ * detected so that the core and related data may
+ * be preserved.
+ * noexec Whether we are supposed to change the disk.
+ * orig_server_temp_dir The temporary directory we created within
+ * Tmpdir for our duplicate of the client
+ * workspace.
+ *
+ * INPUTS
+ * None.
+ *
+ * ERRORS
+ * Problems encountered during the cleanup, for instance low memory or
+ * problems deleting the temp files and directories, can cause the error
+ * function to be called, which might call exit. If exit gets called in this
+ * manner. this routine will not complete, but the other exit handlers
+ * registered via atexit() will still run.
+ *
+ * RETURNS
+ * Nothing.
+ */
+void
+server_cleanup (void)
+{
+ TRACE (TRACE_FUNCTION, "server_cleanup()");
+
+ assert (server_active);
+
+ /* FIXME: Do not perform buffered I/O from an interrupt handler like
+ * this (via error). However, I'm leaving the error-calling code there
+ * in the hope that on the rare occasion the error call is actually made
+ * (e.g., a fluky I/O error or permissions problem prevents the deletion
+ * of a just-created file) reentrancy won't be an issue.
+ */
+
+ /* We don't want to be interrupted during calls which set globals to NULL,
+ * but we know that by the time we reach this function, interrupts have
+ * already been blocked.
+ */
+
+ /* Since we install this function in an atexit() handler before forking,
+ * reuse the ERROR_USE_PROTOCOL flag, which we know is only set in the
+ * parent server process, to avoid cleaning up the temp space multiple
+ * times. Skip the buf_to_net checks too as an optimization since we know
+ * they will be set to NULL in the child process anyhow.
+ */
+ if (error_use_protocol)
+ {
+ if (buf_to_net != NULL)
+ {
+ int status;
+
+ /* 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.
+ */
+
+ /* SIG_beginCrSect(); */
+ if (buf_from_net)
+ {
+ 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;
+ }
+ /* SIG_endCrSect(); */
+ }
+
+ if (!dont_delete_temp)
+ {
+ int save_noexec;
+
+ /* 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);
+ /* 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,
+ NULL, NULL &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 */
+
+ /* Make sure our working directory isn't inside the tree we're
+ going to delete. */
+ CVS_CHDIR (get_cvs_tmp_dir ());
+
+ /* 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;
+
+ /* SIG_beginCrSect(); */
+ noexec = 0;
+ unlink_file_dir (orig_server_temp_dir);
+ noexec = save_noexec;
+ /* SIG_endCrSect(); */
+ } /* !dont_delete_temp */
+
+ /* SIG_beginCrSect(); */
+ if (buf_to_net != NULL)
+ {
+ /* Save BUF_TO_NET and set the global pointer to NULL so that any
+ * error messages generated during shutdown go to the syslog rather
+ * than getting lost.
+ */
+ struct buffer *buf_to_net_save = buf_to_net;
+ buf_to_net = NULL;
+
+ (void) buf_flush (buf_to_net_save, 1);
+ (void) buf_shutdown (buf_to_net_save);
+ buf_free (buf_to_net_save);
+ error_use_protocol = 0;
+ }
+ /* SIG_endCrSect(); */
+ }
+
+ server_active = 0;
+}
+
+
+
+#ifdef PROXY_SUPPORT
+size_t MaxProxyBufferSize = (size_t)(8 * 1024 * 1024); /* 8 megabytes,
+ * by default.
+ */
+#endif /* PROXY_SUPPORT */
+
+static const char *const server_usage[] =
+{
+ "Usage: %s %s [-c config-file]\n",
+ "\t-c config-file\tPath to an alternative CVS config file.\n",
+ "Normally invoked by a cvs client on a remote machine.\n",
+ NULL
+};
+
+
+
+void
+parseServerOptions (int argc, char **argv)
+{
+ int c;
+
+ optind = 0;
+ while ((c = getopt (argc, argv, "+c:")) != -1)
+ {
+ switch (c)
+ {
+#ifdef ALLOW_CONFIG_OVERRIDE
+ case 'c':
+ if (gConfigPath) free (gConfigPath);
+ gConfigPath = xstrdup (optarg);
+ break;
+#endif
+ case '?':
+ default:
+ usage (server_usage);
+ break;
+ }
+ }
+}
+
+
+
+int
+server (int argc, char **argv)
+{
+ char *error_prog_name; /* Used in error messages */
+
+ if (argc == -1)
+ usage (server_usage);
+
+ /* Options were pre-parsed in main.c. */
+
+ /*
+ * Set this in .bashrc if you want to give yourself time to attach
+ * to the subprocess with a debugger.
+ */
+ if (getenv ("CVS_PARENT_SERVER_SLEEP"))
+ {
+ int secs = atoi (getenv ("CVS_PARENT_SERVER_SLEEP"));
+ TRACE (TRACE_DATA, "Sleeping CVS_PARENT_SERVER_SLEEP (%d) seconds",
+ secs);
+ sleep (secs);
+ }
+ else
+ TRACE (TRACE_DATA, "CVS_PARENT_SERVER_SLEEP not set.");
+
+ /* pserver_authenticate_connection () (called from main ()) can initialize
+ * these.
+ */
+ if (!buf_to_net)
+ {
+ buf_to_net = fd_buffer_initialize (STDOUT_FILENO, 0, NULL, false,
+ outbuf_memory_error);
+ buf_from_net = fd_buffer_initialize (STDIN_FILENO, 0, NULL, true,
+ outbuf_memory_error);
+ }
+
+ setup_logfiles ("CVS_SERVER_LOG", &buf_to_net, &buf_from_net);
+
+#ifdef PROXY_SUPPORT
+ /* We have to set up the recording for all servers. Until we receive the
+ * `Root' request and load CVSROOT/config, we can't tell if we are a
+ * secondary or primary.
+ */
+ {
+ /* Open the secondary log. */
+ buf_from_net = log_buffer_initialize (buf_from_net, NULL,
+# ifdef PROXY_SUPPORT
+ true,
+ config
+ ? config->MaxProxyBufferSize
+ : MaxProxyBufferSize,
+# endif /* PROXY_SUPPORT */
+ true, outbuf_memory_error);
+ proxy_log = buf_from_net;
+
+ /* And again for the out log. */
+ buf_to_net = log_buffer_initialize (buf_to_net, NULL,
+# ifdef PROXY_SUPPORT
+ true,
+ config
+ ? config->MaxProxyBufferSize
+ : MaxProxyBufferSize,
+# endif /* PROXY_SUPPORT */
+ false, outbuf_memory_error);
+ proxy_log_out = buf_to_net;
+ }
+#endif /* PROXY_SUPPORT */
+
+ 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;
+
+ /* 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)
+ {
+ if (alloc_pending (80))
+ sprintf (pending_error_text,
+ "E Protocol error: Root request missing");
+ }
+ else
+ {
+ if (config && config->MinCompressionLevel && !gzip_level
+ && !(rq->flags & RQ_ROOTLESS))
+ {
+ /* This is a rootless request, a minimum compression
+ * level has been configured, and no compression has
+ * been requested by the client.
+ */
+ if (alloc_pending (80 + strlen (program_name)))
+ sprintf (pending_error_text,
+"E %s [server aborted]: Compression must be used with this server.",
+ program_name);
+ }
+ (*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 /* HAVE_SYSLOG_H */
+ error (0, 0, "Dying gasps received from client.");
+ }
+
+#ifdef HAVE_PAM
+ if (pamh)
+ {
+ int retval;
+
+ retval = pam_close_session (pamh, 0);
+# ifdef HAVE_SYSLOG_H
+ if (retval != PAM_SUCCESS)
+ syslog (LOG_DAEMON | LOG_ERR,
+ "PAM close session error: %s",
+ pam_strerror (pamh, retval));
+# endif /* HAVE_SYSLOG_H */
+
+ retval = pam_end (pamh, retval);
+# ifdef HAVE_SYSLOG_H
+ if (retval != PAM_SUCCESS)
+ syslog (LOG_DAEMON | LOG_ERR,
+ "PAM failed to release authenticator, error: %s",
+ pam_strerror (pamh, retval));
+# endif /* HAVE_SYSLOG_H */
+ }
+#endif /* HAVE_PAM */
+
+ /* server_cleanup() will be called on a normal exit and close the buffers
+ * explicitly.
+ */
+ return 0;
+}
+
+
+
+#if defined (HAVE_KERBEROS) || defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
+static void
+switch_to_user (const char *cvs_username, const char *username)
+{
+ struct passwd *pw;
+#ifdef HAVE_PAM
+ int retval;
+ char *pam_stage = "open session";
+
+ if (pamh)
+ {
+ retval = pam_open_session (pamh, 0);
+ if (retval == PAM_SUCCESS)
+ {
+ pam_stage = "get pam user";
+ retval = pam_get_item (pamh, PAM_USER, (const void **)&username);
+ }
+
+ if (retval != PAM_SUCCESS)
+ {
+ printf("E PAM %s error: %s\n", pam_stage,
+ pam_strerror (pamh, retval));
+ exit (EXIT_FAILURE);
+ }
+ }
+#endif
+
+ 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);
+ exit (EXIT_FAILURE);
+ }
+
+ 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 /* HAVE_SYSLOG_H */
+ printf("error 0: root not allowed\n");
+ exit (EXIT_FAILURE);
+ }
+
+#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));
+ exit (EXIT_FAILURE);
+ }
+#endif /* HAVE_INITGROUPS */
+
+#ifdef HAVE_PAM
+ if (pamh)
+ {
+ retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
+ if (retval != PAM_SUCCESS)
+ {
+ printf("E PAM reestablish credentials error: %s\n",
+ pam_strerror (pamh, retval));
+ exit (EXIT_FAILURE);
+ }
+ }
+#endif
+
+#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));
+ exit (EXIT_FAILURE);
+ }
+ }
+ 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 /* HAVE_SYSLOG_H */
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ 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 /* HAVE_SYSLOG_H */
+ exit (EXIT_FAILURE);
+ }
+
+ /* 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
+
+ /* Set LOGNAME, USER and CVS_USER in the environment, in case they
+ are already set to something else. */
+ setenv ("LOGNAME", username, 1);
+ setenv ("USER", username, 1);
+# ifdef AUTH_SERVER_SUPPORT
+ setenv ("CVS_USER", CVS_Username, 1);
+# endif
+}
+#endif
+
+#ifdef AUTH_SERVER_SUPPORT
+
+extern char *crypt (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 (char *username, char *password, char *repository, char **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;
+}
+
+#ifdef HAVE_PAM
+
+static int
+cvs_pam_conv (int num_msg, const struct pam_message **msg,
+ struct pam_response **resp, void *appdata_ptr)
+{
+ int i;
+ struct pam_response *response;
+
+ assert (msg && resp);
+
+ response = xnmalloc (num_msg, sizeof (struct pam_response));
+ memset (response, 0, num_msg * sizeof (struct pam_response));
+
+ for (i = 0; i < num_msg; i++)
+ {
+ switch (msg[i]->msg_style)
+ {
+ /* PAM wants a username */
+ case PAM_PROMPT_ECHO_ON:
+ assert (pam_username != 0);
+ response[i].resp = xstrdup (pam_username);
+ break;
+ /* PAM wants a password */
+ case PAM_PROMPT_ECHO_OFF:
+ assert (pam_password != 0);
+ response[i].resp = xstrdup (pam_password);
+ break;
+ case PAM_ERROR_MSG:
+ case PAM_TEXT_INFO:
+ printf ("E %s\n", msg[i]->msg);
+ break;
+ /* PAM wants something we don't understand - bail out */
+ default:
+ goto cleanup;
+ }
+ }
+
+ *resp = response;
+ return PAM_SUCCESS;
+
+cleanup:
+ for (i = 0; i < num_msg; i++)
+ {
+ if (response[i].resp)
+ {
+ free (response[i].resp);
+ response[i].resp = 0;
+ }
+ }
+ free (response);
+ return PAM_CONV_ERR;
+}
+
+static int
+check_pam_password (char **username, char *password)
+{
+ int retval, err;
+ struct pam_conv conv = { cvs_pam_conv, 0 };
+ char *pam_stage = "start";
+
+ pam_username = *username;
+ pam_password = password;
+
+ retval = pam_start (PAM_SERVICE_NAME, *username, &conv, &pamh);
+
+ /* sets a dummy tty name which pam modules can check for */
+ if (retval == PAM_SUCCESS)
+ {
+ pam_stage = "set dummy tty";
+ retval = pam_set_item (pamh, PAM_TTY, PAM_SERVICE_NAME);
+ }
+
+ if (retval == PAM_SUCCESS)
+ {
+ pam_stage = "authenticate";
+ retval = pam_authenticate (pamh, 0);
+ }
+
+ if (retval == PAM_SUCCESS)
+ {
+ pam_stage = "account";
+ retval = pam_acct_mgmt (pamh, 0);
+ }
+
+ if (retval == PAM_SUCCESS)
+ {
+ pam_stage = "get pam user";
+ retval = pam_get_item (pamh, PAM_USER, (const void **)username);
+ }
+
+ if (retval != PAM_SUCCESS)
+ printf ("E PAM %s error: %s\n", pam_stage, pam_strerror (pamh, retval));
+
+ /* clear the pointers to make sure we don't use these references again */
+ pam_username = 0;
+ pam_password = 0;
+
+ return retval == PAM_SUCCESS; /* indicate success */
+}
+#endif
+
+static int
+check_system_password (char *username, char *password)
+{
+ char *found_passwd = NULL;
+ struct passwd *pw;
+#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);
+
+ exit (EXIT_FAILURE);
+ }
+
+ /* 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)
+ return 1;
+ else
+ {
+#ifdef LOG_AUTHPRIV
+ syslog (LOG_AUTHPRIV | LOG_NOTICE,
+ "password mismatch for %s: %s vs. %s", username,
+ crypt(password, found_passwd), found_passwd);
+#endif
+ return 0;
+ }
+ }
+
+#ifdef LOG_AUTHPRIV
+ syslog (LOG_AUTHPRIV | LOG_NOTICE,
+ "user %s authenticated because of blank system password",
+ username);
+#endif
+ return 1;
+}
+
+
+
+/* Return a hosting username if password matches, else NULL. */
+static char *
+check_password (char *username, char *password, char *repository)
+{
+ int rc;
+ char *host_user = NULL;
+
+ /* 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 or maybe whatever is configured via PAM. */
+
+ 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 (!config->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);
+
+ exit (EXIT_FAILURE);
+ }
+
+ /* No cvs password found, so try /etc/passwd. */
+#ifdef HAVE_PAM
+ if (check_pam_password (&username, password))
+#else /* !HAVE_PAM */
+ if (check_system_password (username, password))
+#endif /* HAVE_PAM */
+ host_user = xstrdup (username);
+ else
+ host_user = NULL;
+
+#ifdef LOG_AUTHPRIV
+ if (!host_user)
+ 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)
+
+static void
+pserver_read_line (char **tmp, size_t *tmp_len)
+{
+ int status;
+
+ /* Make sure the protocol starts off on the right foot... */
+ status = buf_read_short_line (buf_from_net, tmp, tmp_len, PATH_MAX);
+ if (status == -1)
+ {
+# ifdef HAVE_SYSLOG_H
+ syslog (LOG_DAEMON | LOG_NOTICE,
+ "unexpected EOF encountered during authentication");
+# endif /* HAVE_SYSLOG_H */
+ error (1, 0, "unexpected EOF encountered during authentication");
+ }
+ if (status == -2)
+ status = ENOMEM;
+ if (status != 0)
+ {
+# ifdef HAVE_SYSLOG_H
+ syslog (LOG_DAEMON | LOG_NOTICE,
+ "error reading from net while validating pserver");
+# endif /* HAVE_SYSLOG_H */
+ error (1, status, "error reading from net while validating pserver");
+ }
+}
+
+/* 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 (void)
+{
+ char *tmp;
+#ifdef AUTH_SERVER_SUPPORT
+ char *repository = NULL;
+ char *username = NULL;
+ char *password = NULL;
+
+ 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.
+ */
+
+ /* Initialize buffers. */
+ buf_to_net = fd_buffer_initialize (STDOUT_FILENO, 0, NULL, false,
+ outbuf_memory_error);
+ buf_from_net = fd_buffer_initialize (STDIN_FILENO, 0, NULL, true,
+ outbuf_memory_error);
+
+#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,
+ &on, sizeof on) < 0)
+ {
+# ifdef HAVE_SYSLOG_H
+ syslog (LOG_DAEMON | LOG_ERR, "error setting KEEPALIVE: %m");
+# endif /* HAVE_SYSLOG_H */
+ }
+ }
+#endif
+
+ /* Make sure the protocol starts off on the right foot... */
+ pserver_read_line (&tmp, NULL);
+
+ if (strcmp (tmp, "BEGIN VERIFICATION REQUEST") == 0)
+ verify_and_exit = 1;
+ else if (strcmp (tmp, "BEGIN AUTH REQUEST") == 0)
+ ;
+ else if (strcmp (tmp, "BEGIN GSSAPI REQUEST") == 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 */
+
+ free (tmp);
+
+ /* Get the three important pieces of information in order. */
+ /* See above comment about error handling. */
+ pserver_read_line (&repository, NULL);
+ pserver_read_line (&username, NULL);
+ pserver_read_line (&password, NULL);
+
+ /* ... and make sure the protocol ends on the right foot. */
+ /* See above comment about error handling. */
+ pserver_read_line (&tmp, NULL);
+ if (strcmp (tmp,
+ verify_and_exit ?
+ "END VERIFICATION REQUEST" : "END AUTH REQUEST")
+ != 0)
+ {
+ error (1, 0, "bad auth protocol end: %s", tmp);
+ }
+ free (tmp);
+
+ if (!root_allow_ok (repository))
+ {
+ error (1, 0, "%s: no such repository", repository);
+# ifdef HAVE_SYSLOG_H
+ syslog (LOG_DAEMON | LOG_NOTICE, "login refused for %s", repository);
+# endif /* HAVE_SYSLOG_H */
+ 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! */
+ config = get_root_allow_config (repository, gConfigPath);
+
+ /* 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 /* HAVE_SYSLOG_H */
+ memset (descrambled_password, 0, strlen (descrambled_password));
+ free (descrambled_password);
+ i_hate_you:
+ buf_output0 (buf_to_net, "I HATE YOU\n");
+ buf_flush (buf_to_net, true);
+
+ /* Don't worry about server_cleanup, server_active isn't set
+ yet. */
+ exit (EXIT_FAILURE);
+ }
+ 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)
+ {
+ buf_output0 (buf_to_net, "I LOVE YOU\n");
+ buf_flush (buf_to_net, true);
+ exit (EXIT_SUCCESS);
+ }
+
+ /* 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 (repository);
+ free (username);
+ free (password);
+
+ buf_output0 (buf_to_net, "I LOVE YOU\n");
+ buf_flush (buf_to_net, true);
+#endif /* AUTH_SERVER_SUPPORT */
+}
+
+#endif /* AUTH_SERVER_SUPPORT || HAVE_GSSAPI */
+
+
+#ifdef HAVE_KERBEROS
+void
+kserver_authenticate_connection( void )
+{
+ 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));
+
+ exit (EXIT_FAILURE);
+ }
+
+#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 /* HAVE_SYSLOG_H */
+ }
+ }
+#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));
+
+ exit (EXIT_FAILURE);
+ }
+
+ 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));
+
+ exit (EXIT_FAILURE);
+ }
+
+ /* Switch to run as this user. */
+ switch_to_user ("Kerberos 4", user);
+}
+#endif /* HAVE_KERBEROS */
+
+
+
+# ifdef HAVE_GSSAPI /* && SERVER_SUPPORT */
+/* Authenticate a GSSAPI connection. This is called from
+ * pserver_authenticate_connection, and it handles success and failure
+ * the same way.
+ *
+ * GLOBALS
+ * server_hostname The name of this host, as set via a call to
+ * xgethostname() in main().
+ */
+static void
+gserver_authenticate_connection (void)
+{
+ char *hn;
+ 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;
+
+ hn = canon_host (server_hostname);
+ if (!hn)
+ error (1, 0, "can't get canonical hostname for `%s': %s",
+ server_hostname, ch_strerror ());
+
+ sprintf (buf, "cvs@%s", hn);
+ free (hn);
+ 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 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;
+};
+
+
+
+/* Decrypt Kerberos data. */
+static int
+krb_encrypt_input( 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( 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;
+}
+
+
+
+/* Create a Kerberos encryption buffer. We use a packetizing buffer
+ with Kerberos encryption translation routines. */
+struct buffer *
+krb_encrypt_buffer_initialize( struct buffer *buf, int input,
+ Key_schedule sched, C_Block block,
+ void *memory( 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);
+}
+
+#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 (const char *str, size_t len)
+{
+ if (len == 0)
+ len = strlen (str);
+#ifdef SERVER_SUPPORT
+ if (error_use_protocol)
+ {
+ if (buf_to_net)
+ {
+ buf_output (saved_output, str, len);
+ buf_copy_lines (buf_to_net, saved_output, 'M');
+ }
+# if HAVE_SYSLOG_H
+ else
+ syslog (LOG_DAEMON | LOG_ERR,
+ "Attempt to write message after close of network buffer. "
+ "Message was: %s",
+ str);
+# endif /* HAVE_SYSLOG_H */
+ }
+ else if (server_active)
+ {
+ if (protocol)
+ {
+ buf_output (saved_output, str, len);
+ buf_copy_lines (protocol, saved_output, 'M');
+ buf_send_counted (protocol);
+ }
+# if HAVE_SYSLOG_H
+ else
+ syslog (LOG_DAEMON | LOG_ERR,
+ "Attempt to write message before initialization of "
+ "protocol buffer. Message was: %s",
+ str);
+# endif /* HAVE_SYSLOG_H */
+ }
+ 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 (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;
+
+ assert (buf);
+
+ 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 (const char *str, size_t len)
+{
+ if (len == 0)
+ len = strlen (str);
+#ifdef SERVER_SUPPORT
+ if (error_use_protocol)
+ {
+ if (buf_to_net)
+ {
+ buf_output (saved_outerr, str, len);
+ buf_copy_lines (buf_to_net, saved_outerr, 'E');
+ }
+# if HAVE_SYSLOG_H
+ else
+ syslog (LOG_DAEMON | LOG_ERR,
+ "Attempt to write error message after close of network "
+ "buffer. Message was: `%s'",
+ str);
+# endif /* HAVE_SYSLOG_H */
+ }
+ else if (server_active)
+ {
+ if (protocol)
+ {
+ buf_output (saved_outerr, str, len);
+ buf_copy_lines (protocol, saved_outerr, 'E');
+ buf_send_counted (protocol);
+ }
+# if HAVE_SYSLOG_H
+ else
+ syslog (LOG_DAEMON | LOG_ERR,
+ "Attempt to write error message before initialization of "
+ "protocol buffer. Message was: `%s'",
+ str);
+# endif /* HAVE_SYSLOG_H */
+ }
+ 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 (void)
+{
+#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 (void)
+{
+#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 (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 /* SERVER_SUPPORT */
+ {
+ /* No MT support or we are using a local repository. */
+ if (strcmp (tag, "newline") == 0)
+ cvs_output ("\n", 1);
+ else if (strcmp (tag, "date") == 0)
+ {
+#ifdef SERVER_SUPPORT
+ if (server_active)
+ /* Output UTC when running as a server without MT support in
+ * the client since it is likely to be more meaningful than
+ * localtime.
+ */
+ cvs_output (text, 0);
+ else
+#endif /* SERVER_SUPPORT */
+ {
+ char *date_in = xstrdup (text);
+ char *date = format_date_alloc (date_in);
+ cvs_output (date, 0);
+ free (date);
+ free (date_in);
+ }
+ }
+ else if (text != NULL)
+ cvs_output (text, 0);
+ }
+}
+
+
+
+/*
+ * void cvs_trace(int level, const char *fmt, ...)
+ *
+ * Print tracing information to stderr on request. Levels are implemented
+ * as with CVSNT.
+ */
+void
+cvs_trace (int level, const char *fmt, ...)
+{
+ if (trace >= level)
+ {
+ va_list va;
+
+ va_start (va, fmt);
+#ifdef SERVER_SUPPORT
+ fprintf (stderr,"%c -> ",server_active?(isProxyServer()?'P':'S'):' ');
+#else /* ! SERVER_SUPPORT */
+ fprintf (stderr," -> ");
+#endif
+ vfprintf (stderr, fmt, va);
+ fprintf (stderr,"\n");
+ va_end (va);
+ }
+}