aboutsummaryrefslogtreecommitdiff
path: root/contrib/cvs/src/server.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/cvs/src/server.c')
-rw-r--r--contrib/cvs/src/server.c4288
1 files changed, 2464 insertions, 1824 deletions
diff --git a/contrib/cvs/src/server.c b/contrib/cvs/src/server.c
index e92445bec6d1..65b1dd38a9b6 100644
--- a/contrib/cvs/src/server.c
+++ b/contrib/cvs/src/server.c
@@ -1,31 +1,81 @@
+/* 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 <assert.h>
#include "cvs.h"
#include "watch.h"
#include "edit.h"
#include "fileattr.h"
+#include "getline.h"
+#include "buffer.h"
#ifdef SERVER_SUPPORT
+#ifdef HAVE_WINSOCK_H
+#include <winsock.h>
+#endif
+
+#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_KERBEROS)
+#include <sys/socket.h>
+#endif
+
+#ifdef HAVE_KERBEROS
+#include <netinet/in.h>
+#include <krb.h>
+#ifndef HAVE_KRB_GET_ERR_TEXT
+#define krb_get_err_text(status) krb_err_txt[status]
+#endif
+
+/* Information we need if we are going to use Kerberos encryption. */
+static C_Block kblock;
+static Key_schedule sched;
+
+#endif
+
/* for select */
#include <sys/types.h>
#ifdef HAVE_SYS_BSDTYPES_H
#include <sys/bsdtypes.h>
#endif
-#include <sys/time.h>
-#if HAVE_SYS_SELECT_H
-#include <sys/select.h>
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
#endif
-#if HAVE_FCNTL_H
-#include <fcntl.h>
+#if HAVE_SYS_SELECT_H
+#include <sys/select.h>
#endif
#ifndef O_NONBLOCK
#define O_NONBLOCK O_NDELAY
#endif
+/* EWOULDBLOCK is not defined by POSIX, but some BSD systems will
+ return it, rather than EAGAIN, for nonblocking writes. */
+#ifdef EWOULDBLOCK
+#define blocking_error(err) ((err) == EWOULDBLOCK || (err) == EAGAIN)
+#else
+#define blocking_error(err) ((err) == EAGAIN)
+#endif
+
#ifdef AUTH_SERVER_SUPPORT
+#ifdef HAVE_GETSPNAM
+#include <shadow.h>
+#endif
/* For initgroups(). */
#if HAVE_INITGROUPS
#include <grp.h>
@@ -33,23 +83,28 @@
#endif /* AUTH_SERVER_SUPPORT */
-/* Functions which the server calls. */
-int add PROTO((int argc, char **argv));
-int admin PROTO((int argc, char **argv));
-int checkout PROTO((int argc, char **argv));
-int commit PROTO((int argc, char **argv));
-int diff PROTO((int argc, char **argv));
-int history PROTO((int argc, char **argv));
-int import PROTO((int argc, char **argv));
-int cvslog PROTO((int argc, char **argv));
-int patch PROTO((int argc, char **argv));
-int release PROTO((int argc, char **argv));
-int cvsremove PROTO((int argc, char **argv));
-int rtag PROTO((int argc, char **argv));
-int status PROTO((int argc, char **argv));
-int tag PROTO((int argc, char **argv));
-int update PROTO((int argc, char **argv));
+#ifdef AUTH_SERVER_SUPPORT
+
+/* 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. */
+static 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. */
+char *Pserver_Repos = NULL;
+
+#endif /* AUTH_SERVER_SUPPORT */
+
+/* While processing requests, this buffer accumulates data to be sent to
+ the client, and then once we are in do_cvs_command, we use it
+ for all the data to be sent. */
+static struct buffer *buf_to_net;
+
+/* This buffer is used to read input from the client. */
+static struct buffer *buf_from_net;
/*
* This is where we stash stuff we are going to use. Format string
@@ -57,64 +112,189 @@ int update PROTO((int argc, char **argv));
*/
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 char no_mem_error;
-#define NO_MEM_ERROR (&no_mem_error)
-
static void server_write_entries PROTO((void));
-/*
- * Read a line from the stream "instream" without command line editing.
- *
- * Action is compatible with "readline", e.g. space for the result is
- * malloc'd and should be freed by the caller.
- *
- * A NULL return means end of file. A return of NO_MEM_ERROR means
- * that we are out of memory.
- */
-static char *read_line PROTO((FILE *));
+/* All server communication goes through buffer structures. Most of
+ the buffers are built on top of a file descriptor. This structure
+ is used as the closure field in a buffer. */
-static char *
-read_line (stream)
- FILE *stream;
+struct fd_buffer
{
- int c;
- char *result;
- int input_index = 0;
- int result_size = 80;
-
- fflush (stdout);
- result = (char *) malloc (result_size);
- if (result == NULL)
- return NO_MEM_ERROR;
-
- while (1)
+ /* The file descriptor. */
+ int fd;
+ /* Nonzero if the file descriptor is in blocking mode. */
+ int blocking;
+};
+
+static struct buffer *fd_buffer_initialize
+ PROTO ((int, int, void (*) (struct buffer *)));
+static int fd_buffer_input PROTO((void *, char *, int, int, int *));
+static int fd_buffer_output PROTO((void *, const char *, int, int *));
+static int fd_buffer_flush PROTO((void *));
+static int fd_buffer_block PROTO((void *, int));
+
+/* Initialize a buffer built on a file descriptor. FD is the file
+ descriptor. INPUT is nonzero if this is for input, zero if this is
+ for output. MEMORY is the function to call when a memory error
+ occurs. */
+
+static struct buffer *
+fd_buffer_initialize (fd, input, memory)
+ int fd;
+ int input;
+ void (*memory) PROTO((struct buffer *));
+{
+ struct fd_buffer *n;
+
+ n = (struct fd_buffer *) xmalloc (sizeof *n);
+ n->fd = fd;
+ n->blocking = 1;
+ return buf_initialize (input ? fd_buffer_input : NULL,
+ input ? NULL : fd_buffer_output,
+ input ? NULL : fd_buffer_flush,
+ fd_buffer_block,
+ (int (*) PROTO((void *))) NULL,
+ memory,
+ n);
+}
+
+/* The buffer input function for a buffer built on a file descriptor. */
+
+static int
+fd_buffer_input (closure, data, need, size, got)
+ void *closure;
+ char *data;
+ int need;
+ int size;
+ int *got;
+{
+ struct fd_buffer *fd = (struct fd_buffer *) closure;
+ int nbytes;
+
+ if (! fd->blocking)
+ nbytes = read (fd->fd, data, size);
+ else
{
- c = fgetc (stream);
-
- if (c == EOF)
- {
- free (result);
- return NULL;
- }
-
- if (c == '\n')
- break;
-
- result[input_index++] = c;
- while (input_index >= result_size)
+ /* This case is not efficient. Fortunately, I don't think it
+ ever actually happens. */
+ nbytes = read (fd->fd, data, need == 0 ? 1 : need);
+ }
+
+ if (nbytes > 0)
+ {
+ *got = nbytes;
+ return 0;
+ }
+
+ *got = 0;
+
+ if (nbytes == 0)
+ {
+ /* End of file. This assumes that we are using POSIX or BSD
+ style nonblocking I/O. On System V we will get a zero
+ return if there is no data, even when not at EOF. */
+ return -1;
+ }
+
+ /* Some error occurred. */
+
+ if (blocking_error (errno))
+ {
+ /* Everything's fine, we just didn't get any data. */
+ return 0;
+ }
+
+ return errno;
+}
+
+/* The buffer output function for a buffer built on a file descriptor. */
+
+static int
+fd_buffer_output (closure, data, have, wrote)
+ void *closure;
+ const char *data;
+ int have;
+ int *wrote;
+{
+ struct fd_buffer *fd = (struct fd_buffer *) closure;
+
+ *wrote = 0;
+
+ while (have > 0)
+ {
+ int nbytes;
+
+ nbytes = write (fd->fd, data, have);
+
+ if (nbytes <= 0)
{
- result_size *= 2;
- result = (char *) realloc (result, result_size);
- if (result == NULL)
- return NO_MEM_ERROR;
+ if (! fd->blocking
+ && (nbytes == 0 || blocking_error (errno)))
+ {
+ /* A nonblocking write failed to write any data. Just
+ return. */
+ return 0;
+ }
+
+ /* Some sort of error occurred. */
+
+ if (nbytes == 0)
+ return EIO;
+
+ return errno;
}
+
+ *wrote += nbytes;
+ data += nbytes;
+ have -= nbytes;
}
-
- result[input_index++] = '\0';
- return result;
+
+ return 0;
+}
+
+/* The buffer flush function for a buffer built on a file descriptor. */
+
+/*ARGSUSED*/
+static int
+fd_buffer_flush (closure)
+ void *closure;
+{
+ /* Nothing to do. File descriptors are always flushed. */
+ return 0;
+}
+
+/* The buffer block function for a buffer built on a file descriptor. */
+
+static int
+fd_buffer_block (closure, block)
+ void *closure;
+ int block;
+{
+ struct fd_buffer *fd = (struct fd_buffer *) closure;
+ int flags;
+
+ flags = fcntl (fd->fd, F_GETFL, 0);
+ if (flags < 0)
+ return errno;
+
+ if (block)
+ flags &= ~O_NONBLOCK;
+ else
+ flags |= O_NONBLOCK;
+
+ if (fcntl (fd->fd, F_SETFL, flags) < 0)
+ return errno;
+
+ fd->blocking = block;
+
+ return 0;
}
/*
@@ -134,6 +314,8 @@ mkdir_p (dir)
if (q == NULL)
return ENOMEM;
+ retval = 0;
+
/*
* Skip over leading slash if present. We won't bother to try to
* make '/'.
@@ -149,10 +331,12 @@ mkdir_p (dir)
q[p - dir] = '\0';
if (CVS_MKDIR (q, 0777) < 0)
{
- if (errno != EEXIST
- && (errno != EACCES || !isdir(q)))
+ int saved_errno = errno;
+
+ if (saved_errno != EEXIST
+ && (saved_errno != EACCES || !isdir (q)))
{
- retval = errno;
+ retval = saved_errno;
goto done;
}
}
@@ -162,8 +346,6 @@ mkdir_p (dir)
{
if (CVS_MKDIR (dir, 0777) < 0)
retval = errno;
- else
- retval = 0;
goto done;
}
}
@@ -176,17 +358,20 @@ mkdir_p (dir)
* Print the error response for error code STATUS. The caller is
* reponsible for making sure we get back to the command loop without
* any further output occuring.
+ * Must be called only in contexts where it is OK to send output.
*/
static void
print_error (status)
int status;
{
char *msg;
- printf ("error ");
+ buf_output0 (buf_to_net, "error ");
msg = strerror (status);
if (msg)
- printf ("%s", msg);
- printf ("\n");
+ buf_output0 (buf_to_net, msg);
+ buf_append_char (buf_to_net, '\n');
+
+ buf_flush (buf_to_net, 0);
}
static int pending_error;
@@ -196,17 +381,22 @@ static int pending_error;
*/
static char *pending_error_text;
-/* If an error is pending, print it and return 1. If not, return 0. */
+/* If an error is pending, print it and return 1. If not, return 0.
+ Must be called only in contexts where it is OK to send output. */
static int
print_pending_error ()
{
if (pending_error_text)
{
- printf ("%s\n", 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
- printf ("error \n");
+ buf_output0 (buf_to_net, "error \n");
+
+ buf_flush (buf_to_net, 0);
+
pending_error = 0;
free (pending_error_text);
pending_error_text = NULL;
@@ -224,8 +414,32 @@ print_pending_error ()
/* Is an error pending? */
#define error_pending() (pending_error || pending_error_text)
+
+static int alloc_pending PROTO ((size_t size));
+
+/* Allocate SIZE bytes for pending_error_text and return nonzero
+ if we could do it. */
+static int
+alloc_pending (size)
+ size_t size;
+{
+ if (error_pending ())
+ /* Probably alloc_pending callers will have already checked for
+ this case. But we might as well handle it if they don't, I
+ guess. */
+ return 0;
+ pending_error_text = malloc (size);
+ if (pending_error_text == NULL)
+ {
+ pending_error = ENOMEM;
+ return 0;
+ }
+ return 1;
+}
-int
+static int supported_response PROTO ((char *));
+
+static int
supported_response (name)
char *name;
{
@@ -270,8 +484,24 @@ serve_valid_responses (arg)
{
if (rs->status == rs_essential)
{
- printf ("E response `%s' not supported by client\nerror \n",
- rs->name);
+ 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);
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
exit (EXIT_FAILURE);
}
else if (rs->status == rs_optional)
@@ -279,20 +509,30 @@ serve_valid_responses (arg)
}
}
-static int use_dir_and_repos = 0;
-
static void
serve_root (arg)
char *arg;
{
char *env;
- extern char *CVSroot;
- char path[PATH_MAX];
+ char *path;
int save_errno;
if (error_pending()) return;
-
- (void) sprintf (path, "%s/%s", arg, CVSROOTADM);
+
+ if (!isabsolute (arg))
+ {
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E Root %s must be an absolute pathname", arg);
+ return;
+ }
+ set_local_cvsroot (arg);
+
+ path = xmalloc (strlen (CVSroot_directory)
+ + sizeof (CVSROOTADM)
+ + sizeof (CVSROOTADM_HISTORY)
+ + 10);
+ (void) sprintf (path, "%s/%s", CVSroot_directory, CVSROOTADM);
if (!isaccessible (path, R_OK | X_OK))
{
save_errno = errno;
@@ -312,26 +552,42 @@ serve_root (arg)
Sorry, you don't have read/write access to the history file %s", path);
pending_error = save_errno;
}
+ free (path);
- CVSroot = malloc (strlen (arg) + 1);
- if (CVSroot == NULL)
- {
- pending_error = ENOMEM;
- return;
- }
- strcpy (CVSroot, arg);
#ifdef HAVE_PUTENV
- env = malloc (strlen (CVSROOT_ENV) + strlen (CVSroot) + 1 + 1);
+ env = malloc (strlen (CVSROOT_ENV) + strlen (CVSroot_directory) + 1 + 1);
if (env == NULL)
{
pending_error = ENOMEM;
return;
}
- (void) sprintf (env, "%s=%s", CVSROOT_ENV, arg);
+ (void) sprintf (env, "%s=%s", CVSROOT_ENV, CVSroot_directory);
(void) putenv (env);
/* do not free env, as putenv has control of it */
#endif
}
+
+static int max_dotdot_limit = 0;
+
+/* Is this pathname OK to recurse into when we are running as the server?
+ If not, call error() with a fatal error. */
+void
+server_pathname_check (path)
+ char *path;
+{
+ /* An absolute pathname is almost surely a path on the *client* machine,
+ and is unlikely to do us any good here. It also is probably capable
+ of being a security hole in the anonymous readonly case. */
+ if (isabsolute (path))
+ error (1, 0, "absolute pathname `%s' illegal for server", path);
+ if (pathname_levels (path) > max_dotdot_limit)
+ {
+ /* Similar to the isabsolute case in security implications. */
+ error (0, 0, "protocol error: `%s' contains more leading ..", path);
+ error (1, 0, "than the %d which Max-dotdot specified",
+ max_dotdot_limit);
+ }
+}
/*
* Add as many directories to the temp directory as the client tells us it
@@ -357,8 +613,10 @@ serve_max_dotdot (arg)
strcpy (p, server_temp_dir);
for (i = 0; i < lim; ++i)
strcat (p, "/d");
- free (server_temp_dir);
+ if (server_temp_dir != orig_server_temp_dir)
+ free (server_temp_dir);
server_temp_dir = p;
+ max_dotdot_limit = lim;
}
static char *dir_name;
@@ -370,6 +628,7 @@ dirswitch (dir, repos)
{
int status;
FILE *f;
+ char *b;
server_write_entries ();
@@ -378,6 +637,19 @@ dirswitch (dir, repos)
if (dir_name != NULL)
free (dir_name);
+ /* Check for a trailing '/'. This is not ISDIRSEP because \ in the
+ protocol is an ordinary character, not a directory separator (of
+ course, it is perhaps unwise to use it in directory names, but that
+ is another issue). */
+ if (strlen (dir) > 0
+ && dir[strlen (dir) - 1] == '/')
+ {
+ if (alloc_pending (80 + strlen (dir)))
+ sprintf (pending_error_text,
+ "E protocol error: illegal directory syntax in %s", dir);
+ return;
+ }
+
dir_name = malloc (strlen (server_temp_dir) + strlen (dir) + 40);
if (dir_name == NULL)
{
@@ -394,15 +666,21 @@ dirswitch (dir, repos)
&& status != EEXIST)
{
pending_error = status;
- pending_error_text = malloc (80 + strlen(dir_name));
- sprintf(pending_error_text, "E cannot mkdir %s", dir_name);
+ if (alloc_pending (80 + strlen (dir_name)))
+ sprintf (pending_error_text, "E cannot mkdir %s", dir_name);
return;
}
- if (chdir (dir_name) < 0)
+
+ b = strrchr (dir_name, '/');
+ *b = '\0';
+ Subdir_Register ((List *) NULL, dir_name, b + 1);
+ *b = '/';
+
+ if ( CVS_CHDIR (dir_name) < 0)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(dir_name));
- sprintf(pending_error_text, "E cannot change to %s", dir_name);
+ if (alloc_pending (80 + strlen (dir_name)))
+ sprintf (pending_error_text, "E cannot change to %s", dir_name);
return;
}
/*
@@ -417,13 +695,33 @@ dirswitch (dir, repos)
pending_error = errno;
return;
}
- f = fopen (CVSADM_REP, "w");
+ f = CVS_FOPEN (CVSADM_REP, "w");
if (f == NULL)
{
pending_error = errno;
return;
}
- if (fprintf (f, "%s\n", repos) < 0)
+ if (fprintf (f, "%s", repos) < 0)
+ {
+ pending_error = 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
+ && CVSroot_directory != NULL
+ && strcmp (CVSroot_directory, repos) == 0)
+ {
+ if (fprintf (f, "/.") < 0)
+ {
+ pending_error = errno;
+ fclose (f);
+ return;
+ }
+ }
+ if (fprintf (f, "\n") < 0)
{
pending_error = errno;
fclose (f);
@@ -434,19 +732,21 @@ dirswitch (dir, repos)
pending_error = errno;
return;
}
- f = fopen (CVSADM_ENT, "w+");
+ /* 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)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT);
+ if (alloc_pending (80 + strlen (CVSADM_ENT)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT);
return;
}
if (fclose (f) == EOF)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT);
+ if (alloc_pending (80 + strlen (CVSADM_ENT)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
return;
}
}
@@ -455,42 +755,50 @@ static void
serve_repository (arg)
char *arg;
{
- dirswitch (arg + 1, arg);
+ pending_error_text = malloc (80);
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
+ else
+ strcpy (pending_error_text,
+ "E Repository request is obsolete; aborted");
+ return;
}
static void
serve_directory (arg)
char *arg;
{
+ int status;
char *repos;
- use_dir_and_repos = 1;
- repos = read_line (stdin);
- if (repos == NULL)
+
+ status = buf_read_line (buf_from_net, &repos, (int *) NULL);
+ if (status == 0)
{
- pending_error_text = malloc (80 + strlen (arg));
- if (pending_error_text)
- {
- if (feof (stdin))
- 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 = errno;
- }
- }
- else
- pending_error = ENOMEM;
+ dirswitch (arg, repos);
+ free (repos);
}
- else if (repos == NO_MEM_ERROR)
+ else if (status == -2)
{
- pending_error = ENOMEM;
+ pending_error = ENOMEM;
}
else
{
- dirswitch (arg, repos);
- free (repos);
+ pending_error_text = malloc (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;
+ }
}
}
@@ -499,19 +807,22 @@ serve_static_directory (arg)
char *arg;
{
FILE *f;
- f = fopen (CVSADM_ENTSTAT, "w+");
+
+ if (error_pending ()) return;
+
+ f = CVS_FOPEN (CVSADM_ENTSTAT, "w+");
if (f == NULL)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_ENTSTAT);
+ if (alloc_pending (80 + strlen (CVSADM_ENTSTAT)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_ENTSTAT);
return;
}
if (fclose (f) == EOF)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENTSTAT));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_ENTSTAT);
+ if (alloc_pending (80 + strlen (CVSADM_ENTSTAT)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_ENTSTAT);
return;
}
}
@@ -521,32 +832,35 @@ serve_sticky (arg)
char *arg;
{
FILE *f;
- f = fopen (CVSADM_TAG, "w+");
+
+ if (error_pending ()) return;
+
+ f = CVS_FOPEN (CVSADM_TAG, "w+");
if (f == NULL)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_TAG));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_TAG);
+ if (alloc_pending (80 + strlen (CVSADM_TAG)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_TAG);
return;
}
if (fprintf (f, "%s\n", arg) < 0)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_TAG));
- sprintf(pending_error_text, "E cannot write to %s", CVSADM_TAG);
+ if (alloc_pending (80 + strlen (CVSADM_TAG)))
+ sprintf (pending_error_text, "E cannot write to %s", CVSADM_TAG);
return;
}
if (fclose (f) == EOF)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_TAG));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_TAG);
+ if (alloc_pending (80 + strlen (CVSADM_TAG)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_TAG);
return;
}
}
/*
- * Read SIZE bytes from stdin, write them to FILE.
+ * 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
@@ -559,62 +873,67 @@ receive_partial_file (size, file)
int size;
int file;
{
- char buf[16*1024], *bufp;
- int toread, nread, nwrote;
while (size > 0)
{
- toread = sizeof (buf);
- if (toread > size)
- toread = size;
+ int status, nread;
+ char *data;
- nread = fread (buf, 1, toread, stdin);
- if (nread <= 0)
+ status = buf_read_data (buf_from_net, size, &data, &nread);
+ if (status != 0)
{
- if (feof (stdin))
+ if (status == -2)
+ pending_error = ENOMEM;
+ else
{
pending_error_text = malloc (80);
- if (pending_error_text)
+ 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
- pending_error = ENOMEM;
- }
- else if (ferror (stdin))
- {
- pending_error_text = malloc (40);
- if (pending_error_text)
+ {
sprintf (pending_error_text,
"E error reading from client");
- pending_error = errno;
- }
- else
- {
- pending_error_text = malloc (40);
- if (pending_error_text)
- sprintf (pending_error_text,
- "E short read from client");
- pending_error = 0;
+ pending_error = status;
+ }
}
return;
}
+
size -= nread;
- bufp = buf;
- while (nread)
+
+ while (nread > 0)
{
- nwrote = write (file, bufp, nread);
+ int nwrote;
+
+ nwrote = write (file, data, nread);
if (nwrote < 0)
{
pending_error_text = malloc (40);
- if (pending_error_text)
+ if (pending_error_text != NULL)
sprintf (pending_error_text, "E unable to write");
pending_error = errno;
+
+ /* Read and discard the file data. */
+ while (size > 0)
+ {
+ int status, nread;
+ char *data;
+
+ status = buf_read_data (buf_from_net, size, &data, &nread);
+ if (status != 0)
+ return;
+ size -= nread;
+ }
+
return;
}
nread -= nwrote;
- bufp += nwrote;
+ data += nwrote;
}
}
}
@@ -632,7 +951,7 @@ receive_file (size, file, gzipped)
int gzip_status;
/* Write the file. */
- fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ fd = CVS_OPEN (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0)
{
pending_error_text = malloc (40 + strlen (arg));
@@ -689,78 +1008,102 @@ static void
serve_modified (arg)
char *arg;
{
- int size;
+ int size, status;
char *size_text;
char *mode_text;
int gzipped = 0;
- if (error_pending ()) return;
+ /*
+ * 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.
+ */
- mode_text = read_line (stdin);
- if (mode_text == NULL)
+ status = buf_read_line (buf_from_net, &mode_text, (int *) NULL);
+ if (status != 0)
{
- pending_error_text = malloc (80 + strlen (arg));
- if (pending_error_text)
+ if (status == -2)
+ pending_error = ENOMEM;
+ else
{
- if (feof (stdin))
- sprintf (pending_error_text,
- "E end of file reading mode for %s", arg);
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
else
{
- sprintf (pending_error_text,
- "E error reading mode for %s", arg);
- pending_error = errno;
+ 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;
+ }
}
}
- else
- pending_error = ENOMEM;
- return;
- }
- else if (mode_text == NO_MEM_ERROR)
- {
- pending_error = ENOMEM;
return;
}
- size_text = read_line (stdin);
- if (size_text == NULL)
+
+ status = buf_read_line (buf_from_net, &size_text, (int *) NULL);
+ if (status != 0)
{
- pending_error_text = malloc (80 + strlen (arg));
- if (pending_error_text)
+ if (status == -2)
+ pending_error = ENOMEM;
+ else
{
- if (feof (stdin))
- sprintf (pending_error_text,
- "E end of file reading size for %s", arg);
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
else
{
- sprintf (pending_error_text,
- "E error reading size for %s", arg);
- pending_error = errno;
+ 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 = errno;
+ }
}
}
- else
- pending_error = ENOMEM;
- return;
- }
- else if (size_text == NO_MEM_ERROR)
- {
- pending_error = ENOMEM;
return;
}
if (size_text[0] == 'z')
- {
+ {
gzipped = 1;
size = atoi (size_text + 1);
- }
+ }
else
- size = atoi (size_text);
+ size = atoi (size_text);
free (size_text);
+ if (error_pending ())
+ {
+ /* Now that we know the size, read and discard the file data. */
+ while (size > 0)
+ {
+ int status, nread;
+ char *data;
+
+ status = buf_read_data (buf_from_net, size, &data, &nread);
+ if (status != 0)
+ return;
+ size -= nread;
+ }
+ return;
+ }
+
if (size >= 0)
- {
+ {
receive_file (size, arg, gzipped);
if (error_pending ()) return;
- }
+ }
{
int status = change_mode (arg, mode_text);
@@ -777,108 +1120,120 @@ serve_modified (arg)
}
}
-#endif /* SERVER_SUPPORT */
-#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
-
-int use_unchanged = 0;
-
-#endif
-#ifdef SERVER_SUPPORT
-
static void
serve_enable_unchanged (arg)
char *arg;
{
- use_unchanged = 1;
}
+struct an_entry {
+ struct an_entry *next;
+ char *entry;
+};
+
+static struct an_entry *entries;
+
+static void serve_unchanged PROTO ((char *));
+
static void
-serve_lost (arg)
+serve_unchanged (arg)
char *arg;
{
- if (use_unchanged)
- {
- /* A missing file already indicates it is nonexistent. */
+ struct an_entry *p;
+ char *name;
+ char *cp;
+ char *timefield;
+
+ if (error_pending ())
return;
- }
- else
+
+ /* Rewrite entries file to have `=' in timestamp field. */
+ for (p = entries; p != NULL; p = p->next)
{
- struct utimbuf ut;
- int fd = open (arg, O_WRONLY | O_CREAT | O_TRUNC, 0666);
- if (fd < 0 || close (fd) < 0)
- {
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(arg));
- sprintf(pending_error_text, "E cannot open %s", arg);
- return;
- }
- /*
- * Set the times to the beginning of the epoch to tell time_stamp()
- * that the file was lost.
- */
- ut.actime = 0;
- ut.modtime = 0;
- if (utime (arg, &ut) < 0)
+ name = p->entry + 1;
+ cp = strchr (name, '/');
+ if (cp != NULL
+ && strlen (arg) == cp - name
+ && strncmp (arg, name, cp - name) == 0)
{
- pending_error = errno;
- pending_error_text = malloc (80 + strlen(arg));
- sprintf(pending_error_text, "E cannot utime %s", arg);
- return;
+ timefield = strchr (cp + 1, '/') + 1;
+ if (*timefield != '=')
+ {
+ cp = timefield + strlen (timefield);
+ cp[1] = '\0';
+ while (cp > timefield)
+ {
+ *cp = cp[-1];
+ --cp;
+ }
+ *timefield = '=';
+ }
+ break;
}
}
}
-struct an_entry {
- struct an_entry *next;
- char *entry;
-};
-
-static struct an_entry *entries;
+static void serve_is_modified PROTO ((char *));
static void
-serve_unchanged (arg)
+serve_is_modified (arg)
char *arg;
{
+ struct an_entry *p;
+ char *name;
+ char *cp;
+ char *timefield;
+ /* Have we found this file in "entries" yet. */
+ int found;
+
if (error_pending ())
return;
- if (!use_unchanged)
- {
- /* A missing file already indicates it is unchanged. */
- return;
- }
- else
- {
- struct an_entry *p;
- char *name;
- char *cp;
- char *timefield;
- /* Rewrite entries file to have `=' in timestamp field. */
- for (p = entries; p != NULL; p = p->next)
+ /* 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)
{
- name = p->entry + 1;
- cp = strchr (name, '/');
- if (cp != NULL
- && strlen (arg) == cp - name
- && strncmp (arg, name, cp - name) == 0)
+ timefield = strchr (cp + 1, '/') + 1;
+ if (!(timefield[0] == 'M' && timefield[1] == '/'))
{
- timefield = strchr (cp + 1, '/') + 1;
- if (*timefield != '=')
+ cp = timefield + strlen (timefield);
+ cp[1] = '\0';
+ while (cp > timefield)
{
- cp = timefield + strlen (timefield);
- cp[1] = '\0';
- while (cp > timefield)
- {
- *cp = cp[-1];
- --cp;
- }
- *timefield = '=';
+ *cp = cp[-1];
+ --cp;
}
- break;
+ *timefield = 'M';
}
+ 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. */
+ struct an_entry *p;
+ p = (struct an_entry *) malloc (sizeof (struct an_entry));
+ if (p == NULL)
+ {
+ pending_error = ENOMEM;
+ return;
+ }
+ p->entry = xmalloc (strlen (arg) + 80);
+ strcpy (p->entry, "/");
+ strcat (p->entry, arg);
+ strcat (p->entry, "//D//");
+ p->next = entries;
+ entries = p;
+ }
}
static void
@@ -921,12 +1276,17 @@ server_write_entries ()
/* Note that we free all the entries regardless of errors. */
if (!error_pending ())
{
- f = fopen (CVSADM_ENT, "w");
+ /* 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)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_ENT);
+ if (alloc_pending (80 + strlen (CVSADM_ENT)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT);
}
}
for (p = entries; p != NULL;)
@@ -936,8 +1296,9 @@ server_write_entries ()
if (fprintf (f, "%s\n", p->entry) < 0)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot write to %s", CVSADM_ENT);
+ if (alloc_pending (80 + strlen(CVSADM_ENT)))
+ sprintf (pending_error_text,
+ "E cannot write to %s", CVSADM_ENT);
}
}
free (p->entry);
@@ -949,8 +1310,8 @@ server_write_entries ()
if (f != NULL && fclose (f) == EOF && !error_pending ())
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_ENT));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_ENT);
+ if (alloc_pending (80 + strlen (CVSADM_ENT)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
}
}
@@ -984,6 +1345,7 @@ serve_notify (arg)
{
struct notify_note *new;
char *data;
+ int status;
if (error_pending ()) return;
@@ -1010,28 +1372,29 @@ serve_notify (arg)
}
strcpy (new->filename, arg);
- data = read_line (stdin);
- if (data == NULL)
+ status = buf_read_line (buf_from_net, &data, (int *) NULL);
+ if (status != 0)
{
- pending_error_text = malloc (80 + strlen (arg));
- if (pending_error_text)
+ if (status == -2)
+ pending_error = ENOMEM;
+ else
{
- if (feof (stdin))
- sprintf (pending_error_text,
- "E end of file reading mode for %s", arg);
+ pending_error_text = malloc (80 + strlen (arg));
+ if (pending_error_text == NULL)
+ pending_error = ENOMEM;
else
{
- sprintf (pending_error_text,
- "E error reading mode for %s", arg);
- pending_error = errno;
+ 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;
+ }
}
}
- else
- pending_error = ENOMEM;
- }
- else if (data == NO_MEM_ERROR)
- {
- pending_error = ENOMEM;
}
else
{
@@ -1092,47 +1455,37 @@ server_notify ()
{
struct notify_note *p;
char *repos;
- List *list;
- Node *node;
- int status;
while (notify_list != NULL)
{
- if (chdir (notify_list->dir) < 0)
+ if ( CVS_CHDIR (notify_list->dir) < 0)
{
error (0, errno, "cannot change to %s", notify_list->dir);
return -1;
}
repos = Name_Repository (NULL, NULL);
- /* Now writelock. */
- list = getlist ();
- node = getnode ();
- node->type = LOCK;
- node->key = xstrdup (repos);
- status = addnode (list, node);
- assert (status == 0);
- Writer_Lock (list);
+ lock_dir_for_write (repos);
fileattr_startdir (repos);
notify_do (*notify_list->type, notify_list->filename, getcaller(),
notify_list->val, notify_list->watches, repos);
- printf ("Notified ");
- if (use_dir_and_repos)
+ buf_output0 (buf_to_net, "Notified ");
{
char *dir = notify_list->dir + strlen (server_temp_dir) + 1;
if (dir[0] == '\0')
- fputs (".", stdout);
+ buf_append_char (buf_to_net, '.');
else
- fputs (dir, stdout);
- fputs ("/\n", stdout);
+ buf_output0 (buf_to_net, dir);
+ buf_append_char (buf_to_net, '/');
+ buf_append_char (buf_to_net, '\n');
}
- fputs (repos, stdout);
- fputs ("/", stdout);
- fputs (notify_list->filename, stdout);
- fputs ("\n", stdout);
+ 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');
p = notify_list->next;
free (notify_list->filename);
@@ -1144,13 +1497,13 @@ server_notify ()
fileattr_write ();
fileattr_free ();
- /* Remove the writelock. */
Lock_Cleanup ();
- dellist (&list);
}
- /* do_cvs_command writes to stdout via write(), not stdio, so better
- flush out the buffer. */
- fflush (stdout);
+
+ /* 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;
}
@@ -1215,9 +1568,10 @@ serve_global_option (arg)
if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0')
{
error_return:
- pending_error_text = malloc (strlen (arg) + 80);
- sprintf (pending_error_text, "E Protocol error: bad global option %s",
- arg);
+ if (alloc_pending (strlen (arg) + 80))
+ sprintf (pending_error_text,
+ "E Protocol error: bad global option %s",
+ arg);
return;
}
switch (arg[1])
@@ -1253,62 +1607,27 @@ serve_set (arg)
put into pending_error. */
variable_set (arg);
}
-
-/*
- * We must read data from a child process and send it across the
- * network. We do not want to block on writing to the network, so we
- * store the data from the child process in memory. A BUFFER
- * structure holds the status of one communication, and uses a linked
- * list of buffer_data structures to hold data.
- */
-
-struct buffer
-{
- /* Data. */
- struct buffer_data *data;
-
- /* Last buffer on data chain. */
- struct buffer_data *last;
- /* File descriptor to write to or read from. */
- int fd;
-
- /* Nonzero if this is an output buffer (sanity check). */
- int output;
-
- /* Nonzero if the file descriptor is in nonblocking mode. */
- int nonblocking;
-
- /* Function to call if we can't allocate memory. */
- void (*memory_error) PROTO((struct buffer *));
-};
+#ifdef ENCRYPTION
+#ifdef HAVE_KERBEROS
-/* Data is stored in lists of these structures. */
-
-struct buffer_data
+static void
+serve_kerberos_encrypt (arg)
+ char *arg;
{
- /* Next buffer in linked list. */
- struct buffer_data *next;
-
- /*
- * A pointer into the data area pointed to by the text field. This
- * is where to find data that has not yet been written out.
- */
- char *bufp;
-
- /* The number of data bytes found at BUFP. */
- int size;
-
- /*
- * Actual buffer. This never changes after the structure is
- * allocated. The buffer is BUFFER_DATA_SIZE bytes.
- */
- char *text;
-};
-
-/* The size we allocate for each buffer_data structure. */
-#define BUFFER_DATA_SIZE (4096)
+ /* 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 */
+#endif /* ENCRYPTION */
+
#ifdef SERVER_FLOWCONTROL
/* The maximum we'll queue to the remote client before blocking. */
# ifndef SERVER_HI_WATER
@@ -1318,274 +1637,9 @@ struct buffer_data
# ifndef SERVER_LO_WATER
# define SERVER_LO_WATER (1 * 1024 * 1024)
# endif /* SERVER_LO_WATER */
-#endif /* SERVER_FLOWCONTROL */
-
-/* Linked list of available buffer_data structures. */
-static struct buffer_data *free_buffer_data;
-
-static void allocate_buffer_datas PROTO((void));
-static inline struct buffer_data *get_buffer_data PROTO((void));
-static int buf_empty_p PROTO((struct buffer *));
-static void buf_output PROTO((struct buffer *, const char *, int));
-static void buf_output0 PROTO((struct buffer *, const char *));
-static inline void buf_append_char PROTO((struct buffer *, int));
-static int buf_send_output PROTO((struct buffer *));
-static int set_nonblock PROTO((struct buffer *));
-static int set_block PROTO((struct buffer *));
-static int buf_send_counted PROTO((struct buffer *));
-static inline void buf_append_data PROTO((struct buffer *,
- struct buffer_data *,
- struct buffer_data *));
-static int buf_read_file PROTO((FILE *, long, struct buffer_data **,
- struct buffer_data **));
-static int buf_input_data PROTO((struct buffer *, int *));
-static void buf_copy_lines PROTO((struct buffer *, struct buffer *, int));
-static int buf_copy_counted PROTO((struct buffer *, struct buffer *));
-#ifdef SERVER_FLOWCONTROL
-static int buf_count_mem PROTO((struct buffer *));
static int set_nonblock_fd PROTO((int));
-#endif /* SERVER_FLOWCONTROL */
-
-/* Allocate more buffer_data structures. */
-
-static void
-allocate_buffer_datas ()
-{
- struct buffer_data *alc;
- char *space;
- int i;
-
- /* Allocate buffer_data structures in blocks of 16. */
-#define ALLOC_COUNT (16)
-
- alc = ((struct buffer_data *)
- malloc (ALLOC_COUNT * sizeof (struct buffer_data)));
- space = (char *) valloc (ALLOC_COUNT * BUFFER_DATA_SIZE);
- if (alc == NULL || space == NULL)
- return;
- for (i = 0; i < ALLOC_COUNT; i++, alc++, space += BUFFER_DATA_SIZE)
- {
- alc->next = free_buffer_data;
- free_buffer_data = alc;
- alc->text = space;
- }
-}
-
-/* Get a new buffer_data structure. */
-
-static inline struct buffer_data *
-get_buffer_data ()
-{
- struct buffer_data *ret;
-
- if (free_buffer_data == NULL)
- {
- allocate_buffer_datas ();
- if (free_buffer_data == NULL)
- return NULL;
- }
-
- ret = free_buffer_data;
- free_buffer_data = ret->next;
- return ret;
-}
-
-/* See whether a buffer is empty. */
-
-static int
-buf_empty_p (buf)
- struct buffer *buf;
-{
- struct buffer_data *data;
-
- for (data = buf->data; data != NULL; data = data->next)
- if (data->size > 0)
- return 0;
- return 1;
-}
-
-#ifdef SERVER_FLOWCONTROL
-/*
- * Count how much data is stored in the buffer..
- * Note that each buffer is a malloc'ed chunk BUFFER_DATA_SIZE.
- */
-
-static int
-buf_count_mem (buf)
- struct buffer *buf;
-{
- struct buffer_data *data;
- int mem = 0;
-
- for (data = buf->data; data != NULL; data = data->next)
- mem += BUFFER_DATA_SIZE;
-
- return mem;
-}
-#endif /* SERVER_FLOWCONTROL */
-
-/* Add data DATA of length LEN to BUF. */
-
-static void
-buf_output (buf, data, len)
- struct buffer *buf;
- const char *data;
- int len;
-{
- if (buf->data != NULL
- && (((buf->last->text + BUFFER_DATA_SIZE)
- - (buf->last->bufp + buf->last->size))
- >= len))
- {
- memcpy (buf->last->bufp + buf->last->size, data, len);
- buf->last->size += len;
- return;
- }
-
- while (1)
- {
- struct buffer_data *newdata;
-
- newdata = get_buffer_data ();
- if (newdata == NULL)
- {
- (*buf->memory_error) (buf);
- return;
- }
-
- if (buf->data == NULL)
- buf->data = newdata;
- else
- buf->last->next = newdata;
- newdata->next = NULL;
- buf->last = newdata;
-
- newdata->bufp = newdata->text;
-
- if (len <= BUFFER_DATA_SIZE)
- {
- newdata->size = len;
- memcpy (newdata->text, data, len);
- return;
- }
-
- newdata->size = BUFFER_DATA_SIZE;
- memcpy (newdata->text, data, BUFFER_DATA_SIZE);
-
- data += BUFFER_DATA_SIZE;
- len -= BUFFER_DATA_SIZE;
- }
-
- /*NOTREACHED*/
-}
-
-/* Add a '\0' terminated string to BUF. */
-
-static void
-buf_output0 (buf, string)
- struct buffer *buf;
- const char *string;
-{
- buf_output (buf, string, strlen (string));
-}
-
-/* Add a single character to BUF. */
-
-static inline void
-buf_append_char (buf, ch)
- struct buffer *buf;
- int ch;
-{
- if (buf->data != NULL
- && (buf->last->text + BUFFER_DATA_SIZE
- != buf->last->bufp + buf->last->size))
- {
- *(buf->last->bufp + buf->last->size) = ch;
- ++buf->last->size;
- }
- else
- {
- char b;
-
- b = ch;
- buf_output (buf, &b, 1);
- }
-}
-
-/*
- * Send all the output we've been saving up. Returns 0 for success or
- * errno code. If the buffer has been set to be nonblocking, this
- * will just write until the write would block.
- */
-
-static int
-buf_send_output (buf)
- struct buffer *buf;
-{
- if (! buf->output)
- abort ();
-
- while (buf->data != NULL)
- {
- struct buffer_data *data;
-
- data = buf->data;
- while (data->size > 0)
- {
- int nbytes;
-
- nbytes = write (buf->fd, data->bufp, data->size);
- if (nbytes <= 0)
- {
- int status;
-
- if (buf->nonblocking
- && (nbytes == 0
-#ifdef EWOULDBLOCK
- || errno == EWOULDBLOCK
-#endif
- || errno == EAGAIN))
- {
- /*
- * A nonblocking write failed to write any data.
- * Just return.
- */
- return 0;
- }
-
- /*
- * An error, or EOF. Throw away all the data and
- * return.
- */
- if (nbytes == 0)
- status = EIO;
- else
- status = errno;
-
- buf->last->next = free_buffer_data;
- free_buffer_data = buf->data;
- buf->data = NULL;
- buf->last = NULL;
-
- return status;
- }
-
- data->size -= nbytes;
- data->bufp += nbytes;
- }
-
- buf->data = data->next;
- data->next = free_buffer_data;
- free_buffer_data = data;
- }
-
- buf->last = NULL;
- return 0;
-}
-
-#ifdef SERVER_FLOWCONTROL
/*
* Set buffer BUF to non-blocking I/O. Returns 0 for success or errno
* code.
@@ -1604,553 +1658,9 @@ set_nonblock_fd (fd)
return errno;
return 0;
}
-#endif /* SERVER_FLOWCONTROL */
-
-static int
-set_nonblock (buf)
- struct buffer *buf;
-{
- int flags;
-
- if (buf->nonblocking)
- return 0;
- flags = fcntl (buf->fd, F_GETFL, 0);
- if (flags < 0)
- return errno;
- if (fcntl (buf->fd, F_SETFL, flags | O_NONBLOCK) < 0)
- return errno;
- buf->nonblocking = 1;
- return 0;
-}
-
-/*
- * Set buffer BUF to blocking I/O. Returns 0 for success or errno
- * code.
- */
-
-static int
-set_block (buf)
- struct buffer *buf;
-{
- int flags;
-
- if (! buf->nonblocking)
- return 0;
- flags = fcntl (buf->fd, F_GETFL, 0);
- if (flags < 0)
- return errno;
- if (fcntl (buf->fd, F_SETFL, flags & ~O_NONBLOCK) < 0)
- return errno;
- buf->nonblocking = 0;
- return 0;
-}
-
-/*
- * Send a character count and some output. Returns errno code or 0 for
- * success.
- *
- * Sending the count in binary is OK since this is only used on a pipe
- * within the same system.
- */
-
-static int
-buf_send_counted (buf)
- struct buffer *buf;
-{
- int size;
- struct buffer_data *data;
-
- if (! buf->output)
- abort ();
-
- size = 0;
- for (data = buf->data; data != NULL; data = data->next)
- size += data->size;
-
- data = get_buffer_data ();
- if (data == NULL)
- {
- (*buf->memory_error) (buf);
- return ENOMEM;
- }
-
- data->next = buf->data;
- buf->data = data;
- if (buf->last == NULL)
- buf->last = data;
-
- data->bufp = data->text;
- data->size = sizeof (int);
-
- *((int *) data->text) = size;
-
- return buf_send_output (buf);
-}
-
-/* Append a list of buffer_data structures to an buffer. */
-
-static inline void
-buf_append_data (buf, data, last)
- struct buffer *buf;
- struct buffer_data *data;
- struct buffer_data *last;
-{
- if (data != NULL)
- {
- if (buf->data == NULL)
- buf->data = data;
- else
- buf->last->next = data;
- buf->last = last;
- }
-}
-
-/*
- * Copy the contents of file F into buffer_data structures. We can't
- * copy directly into an buffer, because we want to handle failure and
- * succeess differently. Returns 0 on success, or -2 if out of
- * memory, or a status code on error. Since the caller happens to
- * know the size of the file, it is passed in as SIZE. On success,
- * this function sets *RETP and *LASTP, which may be passed to
- * buf_append_data.
- */
-
-static int
-buf_read_file (f, size, retp, lastp)
- FILE *f;
- long size;
- struct buffer_data **retp;
- struct buffer_data **lastp;
-{
- int status;
-
- *retp = NULL;
- *lastp = NULL;
-
- while (size > 0)
- {
- struct buffer_data *data;
- int get;
-
- data = get_buffer_data ();
- if (data == NULL)
- {
- status = -2;
- goto error_return;
- }
-
- if (*retp == NULL)
- *retp = data;
- else
- (*lastp)->next = data;
- data->next = NULL;
- *lastp = data;
-
- data->bufp = data->text;
- data->size = 0;
-
- if (size > BUFFER_DATA_SIZE)
- get = BUFFER_DATA_SIZE;
- else
- get = size;
-
- errno = EIO;
- if (fread (data->text, get, 1, f) != 1)
- {
- status = errno;
- goto error_return;
- }
-
- data->size += get;
- size -= get;
- }
-
- return 0;
-
- error_return:
- if (*retp != NULL)
- {
- (*lastp)->next = free_buffer_data;
- free_buffer_data = *retp;
- }
- return status;
-}
-
-static int
-buf_read_file_to_eof (f, retp, lastp)
- FILE *f;
- struct buffer_data **retp;
- struct buffer_data **lastp;
-{
- int status;
-
- *retp = NULL;
- *lastp = NULL;
-
- while (!feof (f))
- {
- struct buffer_data *data;
- int get, nread;
-
- data = get_buffer_data ();
- if (data == NULL)
- {
- status = -2;
- goto error_return;
- }
-
- if (*retp == NULL)
- *retp = data;
- else
- (*lastp)->next = data;
- data->next = NULL;
- *lastp = data;
-
- data->bufp = data->text;
- data->size = 0;
-
- get = BUFFER_DATA_SIZE;
-
- errno = EIO;
- nread = fread (data->text, 1, get, f);
- if (nread == 0 && !feof (f))
- {
- status = errno;
- goto error_return;
- }
-
- data->size = nread;
- }
-
- return 0;
-
- error_return:
- if (*retp != NULL)
- {
- (*lastp)->next = free_buffer_data;
- free_buffer_data = *retp;
- }
- return status;
-}
-
-static int
-buf_chain_length (buf)
- struct buffer_data *buf;
-{
- int size = 0;
- while (buf)
- {
- size += buf->size;
- buf = buf->next;
- }
- return size;
-}
-
-/*
- * Read an arbitrary amount of data from a file descriptor into an
- * input buffer. The file descriptor will be in nonblocking mode, and
- * we just grab what we can. Return 0 on success, or -1 on end of
- * file, or -2 if out of memory, or an error code. If COUNTP is not
- * NULL, *COUNTP is set to the number of bytes read.
- */
-
-static int
-buf_input_data (buf, countp)
- struct buffer *buf;
- int *countp;
-{
- if (buf->output)
- abort ();
-
- if (countp != NULL)
- *countp = 0;
-
- while (1)
- {
- int get;
- int nbytes;
-
- if (buf->data == NULL
- || (buf->last->bufp + buf->last->size
- == buf->last->text + BUFFER_DATA_SIZE))
- {
- struct buffer_data *data;
-
- data = get_buffer_data ();
- if (data == NULL)
- {
- (*buf->memory_error) (buf);
- return -2;
- }
-
- if (buf->data == NULL)
- buf->data = data;
- else
- buf->last->next = data;
- data->next = NULL;
- buf->last = data;
-
- data->bufp = data->text;
- data->size = 0;
- }
-
- get = ((buf->last->text + BUFFER_DATA_SIZE)
- - (buf->last->bufp + buf->last->size));
- nbytes = read (buf->fd, buf->last->bufp + buf->last->size, get);
- if (nbytes <= 0)
- {
- if (nbytes == 0)
- {
- /*
- * This assumes that we are using POSIX or BSD style
- * nonblocking I/O. On System V we will get a zero
- * return if there is no data, even when not at EOF.
- */
- return -1;
- }
-
- if (errno == EAGAIN
-#ifdef EWOULDBLOCK
- || errno == EWOULDBLOCK
-#endif
- )
- return 0;
-
- return errno;
- }
-
- buf->last->size += nbytes;
- if (countp != NULL)
- *countp += nbytes;
- }
-
- /*NOTREACHED*/
-}
-
-/*
- * Copy lines from an input buffer to an output buffer. This copies
- * all complete lines (characters up to a newline) from INBUF to
- * OUTBUF. Each line in OUTBUF is preceded by the character COMMAND
- * and a space.
- */
-
-static void
-buf_copy_lines (outbuf, inbuf, command)
- struct buffer *outbuf;
- struct buffer *inbuf;
- int command;
-{
- if (! outbuf->output || inbuf->output)
- abort ();
-
- while (1)
- {
- struct buffer_data *data;
- struct buffer_data *nldata;
- char *nl;
- int len;
-
- /* See if there is a newline in INBUF. */
- nldata = NULL;
- nl = NULL;
- for (data = inbuf->data; data != NULL; data = data->next)
- {
- nl = memchr (data->bufp, '\n', data->size);
- if (nl != NULL)
- {
- nldata = data;
- break;
- }
- }
-
- if (nldata == NULL)
- {
- /* There are no more lines in INBUF. */
- return;
- }
-
- /* Put in the command. */
- buf_append_char (outbuf, command);
- buf_append_char (outbuf, ' ');
-
- if (inbuf->data != nldata)
- {
- /*
- * Simply move over all the buffers up to the one containing
- * the newline.
- */
- for (data = inbuf->data; data->next != nldata; data = data->next)
- ;
- data->next = NULL;
- buf_append_data (outbuf, inbuf->data, data);
- inbuf->data = nldata;
- }
-
- /*
- * If the newline is at the very end of the buffer, just move
- * the buffer onto OUTBUF. Otherwise we must copy the data.
- */
- len = nl + 1 - nldata->bufp;
- if (len == nldata->size)
- {
- inbuf->data = nldata->next;
- if (inbuf->data == NULL)
- inbuf->last = NULL;
-
- nldata->next = NULL;
- buf_append_data (outbuf, nldata, nldata);
- }
- else
- {
- buf_output (outbuf, nldata->bufp, len);
- nldata->bufp += len;
- nldata->size -= len;
- }
- }
-}
-
-/*
- * Copy counted data from one buffer to another. The count is an
- * integer, host size, host byte order (it is only used across a
- * pipe). If there is enough data, it should be moved over. If there
- * is not enough data, it should remain on the original buffer. This
- * returns the number of bytes it needs to see in order to actually
- * copy something over.
- */
-
-static int
-buf_copy_counted (outbuf, inbuf)
- struct buffer *outbuf;
- struct buffer *inbuf;
-{
- if (! outbuf->output || inbuf->output)
- abort ();
-
- while (1)
- {
- struct buffer_data *data;
- int need;
- union
- {
- char intbuf[sizeof (int)];
- int i;
- } u;
- char *intp;
- int count;
- struct buffer_data *start;
- int startoff;
- struct buffer_data *stop;
- int stopwant;
-
- /* See if we have enough bytes to figure out the count. */
- need = sizeof (int);
- intp = u.intbuf;
- for (data = inbuf->data; data != NULL; data = data->next)
- {
- if (data->size >= need)
- {
- memcpy (intp, data->bufp, need);
- break;
- }
- memcpy (intp, data->bufp, data->size);
- intp += data->size;
- need -= data->size;
- }
- if (data == NULL)
- {
- /* We don't have enough bytes to form an integer. */
- return need;
- }
-
- count = u.i;
- start = data;
- startoff = need;
-
- /*
- * We have an integer in COUNT. We have gotten all the data
- * from INBUF in all buffers before START, and we have gotten
- * STARTOFF bytes from START. See if we have enough bytes
- * remaining in INBUF.
- */
- need = count - (start->size - startoff);
- if (need <= 0)
- {
- stop = start;
- stopwant = count;
- }
- else
- {
- for (data = start->next; data != NULL; data = data->next)
- {
- if (need <= data->size)
- break;
- need -= data->size;
- }
- if (data == NULL)
- {
- /* We don't have enough bytes. */
- return need;
- }
- stop = data;
- stopwant = need;
- }
-
- /*
- * We have enough bytes. Free any buffers in INBUF before
- * START, and remove STARTOFF bytes from START, so that we can
- * forget about STARTOFF.
- */
- start->bufp += startoff;
- start->size -= startoff;
-
- if (start->size == 0)
- start = start->next;
-
- if (stop->size == stopwant)
- {
- stop = stop->next;
- stopwant = 0;
- }
-
- while (inbuf->data != start)
- {
- data = inbuf->data;
- inbuf->data = data->next;
- data->next = free_buffer_data;
- free_buffer_data = data;
- }
- /*
- * We want to copy over the bytes from START through STOP. We
- * only want STOPWANT bytes from STOP.
- */
-
- if (start != stop)
- {
- /* Attach the buffers from START through STOP to OUTBUF. */
- for (data = start; data->next != stop; data = data->next)
- ;
- inbuf->data = stop;
- data->next = NULL;
- buf_append_data (outbuf, start, data);
- }
-
- if (stopwant > 0)
- {
- buf_output (outbuf, stop->bufp, stopwant);
- stop->bufp += stopwant;
- stop->size -= stopwant;
- }
- }
-
- /*NOTREACHED*/
-}
+#endif /* SERVER_FLOWCONTROL */
-/* 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;
-
static void serve_questionable PROTO((char *));
static void
@@ -2169,7 +1679,7 @@ serve_questionable (arg)
if (dir_name == NULL)
{
- buf_output0 (&buf_to_net, "E Protocol error: 'Directory' missing");
+ buf_output0 (buf_to_net, "E Protocol error: 'Directory' missing");
return;
}
@@ -2177,15 +1687,15 @@ serve_questionable (arg)
{
char *update_dir;
- buf_output (&buf_to_net, "M ? ", 4);
+ buf_output (buf_to_net, "M ? ", 4);
update_dir = dir_name + strlen (server_temp_dir) + 1;
if (!(update_dir[0] == '.' && update_dir[1] == '\0'))
{
- buf_output0 (&buf_to_net, update_dir);
- buf_output (&buf_to_net, "/", 1);
+ buf_output0 (buf_to_net, update_dir);
+ buf_output (buf_to_net, "/", 1);
}
- buf_output0 (&buf_to_net, arg);
- buf_output (&buf_to_net, "\n", 1);
+ buf_output0 (buf_to_net, arg);
+ buf_output (buf_to_net, "\n", 1);
}
}
@@ -2198,14 +1708,14 @@ serve_case (arg)
ign_case = 1;
}
-static struct buffer protocol;
+static struct buffer *protocol;
/* This is the output which we are saving up to send to the server, in the
child process. We will push it through, via the `protocol' buffer, when
we have a complete line. */
-static struct buffer saved_output;
+static struct buffer *saved_output;
/* Likewise, but stuff which will go to stderr. */
-static struct buffer saved_outerr;
+static struct buffer *saved_outerr;
static void
protocol_memory_error (buf)
@@ -2237,8 +1747,7 @@ error ENOMEM Virtual memory exhausted.\n";
/* If this gives an error, not much we could do. syslog() it? */
write (STDOUT_FILENO, msg, sizeof (msg) - 1);
- server_cleanup (0);
- exit (EXIT_FAILURE);
+ error_exit ();
}
static void
@@ -2248,6 +1757,155 @@ input_memory_error (buf)
outbuf_memory_error (buf);
}
+
+
+/* If command is legal, return 1.
+ * Else if command is illegal and croak_on_illegal is set, then die.
+ * Else just return 0 to indicate that command is illegal.
+ */
+static int
+check_command_legal_p (cmd_name)
+ char *cmd_name;
+{
+ /* Right now, only pserver notices illegal commands -- namely,
+ * write attempts by a read-only user. Therefore, if CVS_Username
+ * is not set, this just returns 1, because CVS_Username unset
+ * means pserver is not active.
+ */
+#ifdef AUTH_SERVER_SUPPORT
+ if (CVS_Username == NULL)
+ return 1;
+
+ if (lookup_command_attribute (cmd_name) & CVS_CMD_MODIFIES_REPOSITORY)
+ {
+ /* This command has the potential to modify the repository, so
+ * we check if the user have permission to do that.
+ *
+ * (Only relevant for remote users -- local users can do
+ * whatever normal Unix file permissions allow them to do.)
+ *
+ * The decision method:
+ *
+ * If $CVSROOT/CVSADMROOT_READERS exists and user is listed
+ * in it, then read-only access for user.
+ *
+ * Or if $CVSROOT/CVSADMROOT_WRITERS exists and user NOT
+ * listed in it, then also read-only access for user.
+ *
+ * Else read-write access for user.
+ */
+
+ char *linebuf = NULL;
+ int num_red = 0;
+ size_t linebuf_len = 0;
+ char *fname;
+ size_t flen;
+ FILE *fp;
+ int found_it = 0;
+
+ /* else */
+ flen = strlen (CVSroot_directory)
+ + strlen (CVSROOTADM)
+ + strlen (CVSROOTADM_READERS)
+ + 3;
+
+ fname = xmalloc (flen);
+ (void) sprintf (fname, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, CVSROOTADM_READERS);
+
+ fp = fopen (fname, "r");
+ free (fname);
+
+ if (fp == NULL)
+ goto do_writers;
+ 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 (linebuf[num_red - 1] == '\n')
+ linebuf[num_red - 1] = '\0';
+
+ if (strcmp (linebuf, CVS_Username) == 0)
+ goto handle_illegal;
+ }
+
+ /* If not listed specifically as a reader, then this user
+ has write access by default unless writers are also
+ specified in a file . */
+ fclose (fp);
+ goto do_writers;
+ }
+
+ do_writers:
+
+ flen = strlen (CVSroot_directory)
+ + strlen (CVSROOTADM)
+ + strlen (CVSROOTADM_WRITERS)
+ + 3;
+
+ fname = xmalloc (flen);
+ (void) sprintf (fname, "%s/%s/%s", CVSroot_directory,
+ CVSROOTADM, CVSROOTADM_WRITERS);
+
+ fp = fopen (fname, "r");
+ free (fname);
+
+ if (fp == NULL)
+ {
+ /* writers file does not exist, so everyone is a writer,
+ by default */
+ if (linebuf)
+ free (linebuf);
+ return 1;
+ }
+
+ /* else */
+
+ found_it = 0;
+ while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0)
+ {
+ /* Chop newline by hand, for strcmp()'s sake. */
+ if (linebuf[num_red - 1] == '\n')
+ linebuf[num_red - 1] = '\0';
+
+ if (strcmp (linebuf, CVS_Username) == 0)
+ {
+ found_it = 1;
+ break;
+ }
+ }
+
+ if (found_it)
+ {
+ fclose (fp);
+ if (linebuf)
+ free (linebuf);
+ return 1;
+ }
+ else /* writers file exists, but this user not listed in it */
+ {
+ handle_illegal:
+ fclose (fp);
+ if (linebuf)
+ free (linebuf);
+ return 0;
+ }
+ }
+#endif /* AUTH_SERVER_SUPPORT */
+
+ /* If ever reach end of this function, command must be legal. */
+ return 1;
+}
+
+
+
/* Execute COMMAND in a subprocess with the approriate funky things done. */
static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain;
@@ -2258,7 +1916,8 @@ static int flowcontrol_pipe[2];
#endif /* SERVER_FLOWCONTROL */
static void
-do_cvs_command (command)
+do_cvs_command (cmd_name, command)
+ char *cmd_name;
int (*command) PROTO((int argc, char **argv));
{
/*
@@ -2294,6 +1953,21 @@ do_cvs_command (command)
if (print_pending_error ())
goto free_args_and_return;
+ /* Global `command_name' is probably "server" right now -- only
+ serve_export() sets it to anything else. So we will use local
+ parameter `cmd_name' to determine if this command is legal for
+ this user. */
+ if (!check_command_legal_p (cmd_name))
+ {
+ buf_output0 (buf_to_net, "E ");
+ buf_output0 (buf_to_net, program_name);
+ buf_output0 (buf_to_net, " [server aborted]: \"");
+ buf_output0 (buf_to_net, cmd_name);
+ buf_output0 (buf_to_net, "\" requires write access to the repository\n\
+error \n");
+ goto free_args_and_return;
+ }
+
(void) server_notify ();
/*
@@ -2329,13 +2003,29 @@ do_cvs_command (command)
set_nonblock_fd (flowcontrol_pipe[1]);
#endif /* SERVER_FLOWCONTROL */
- dev_null_fd = open ("/dev/null", O_RDONLY);
+ dev_null_fd = CVS_OPEN (DEVNULL, O_RDONLY);
if (dev_null_fd < 0)
{
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. */
+ 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)
@@ -2352,18 +2042,20 @@ do_cvs_command (command)
flag. */
error_use_protocol = 0;
- protocol.data = protocol.last = NULL;
- protocol.fd = protocol_pipe[1];
- protocol.output = 1;
- protocol.nonblocking = 0;
- protocol.memory_error = protocol_memory_error;
+ protocol = fd_buffer_initialize (protocol_pipe[1], 0,
+ protocol_memory_error);
+
+ /* At this point we should no longer be using buf_to_net and
+ buf_from_net. Instead, everything should go through
+ protocol. */
+ buf_to_net = NULL;
+ buf_from_net = NULL;
- saved_output.data = saved_output.last = NULL;
- saved_output.fd = -1;
- saved_output.output = 0;
- saved_output.nonblocking = 0;
- saved_output.memory_error = protocol_memory_error;
- saved_outerr = saved_output;
+ /* 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");
@@ -2399,9 +2091,9 @@ do_cvs_command (command)
/* OK, sit around getting all the input from the child. */
{
- struct buffer stdoutbuf;
- struct buffer stderrbuf;
- struct buffer protocol_inbuf;
+ 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 = 0;
@@ -2428,32 +2120,25 @@ do_cvs_command (command)
++num_to_check;
if (num_to_check > FD_SETSIZE)
{
- printf ("E internal error: FD_SETSIZE not big enough.\nerror \n");
+ buf_output0 (buf_to_net,
+ "E internal error: FD_SETSIZE not big enough.\n\
+error \n");
goto error_exit;
}
- stdoutbuf.data = stdoutbuf.last = NULL;
- stdoutbuf.fd = stdout_pipe[0];
- stdoutbuf.output = 0;
- stdoutbuf.nonblocking = 0;
- stdoutbuf.memory_error = input_memory_error;
-
- stderrbuf.data = stderrbuf.last = NULL;
- stderrbuf.fd = stderr_pipe[0];
- stderrbuf.output = 0;
- stderrbuf.nonblocking = 0;
- stderrbuf.memory_error = input_memory_error;
-
- protocol_inbuf.data = protocol_inbuf.last = NULL;
- protocol_inbuf.fd = protocol_pipe[0];
- protocol_inbuf.output = 0;
- protocol_inbuf.nonblocking = 0;
- protocol_inbuf.memory_error = input_memory_error;
-
- set_nonblock (&buf_to_net);
- set_nonblock (&stdoutbuf);
- set_nonblock (&stderrbuf);
- set_nonblock (&protocol_inbuf);
+ stdoutbuf = fd_buffer_initialize (stdout_pipe[0], 1,
+ input_memory_error);
+
+ stderrbuf = fd_buffer_initialize (stderr_pipe[0], 1,
+ input_memory_error);
+
+ protocol_inbuf = fd_buffer_initialize (protocol_pipe[0], 1,
+ input_memory_error);
+
+ set_nonblock (buf_to_net);
+ set_nonblock (stdoutbuf);
+ set_nonblock (stderrbuf);
+ set_nonblock (protocol_inbuf);
if (close (stdout_pipe[1]) < 0)
{
@@ -2506,7 +2191,7 @@ do_cvs_command (command)
* 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);
+ bufmemsize = buf_count_mem (buf_to_net);
if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER))
{
if (write(flowcontrol_pipe[1], "S", 1) == 1)
@@ -2521,7 +2206,7 @@ do_cvs_command (command)
FD_ZERO (&readfds);
FD_ZERO (&writefds);
- if (! buf_empty_p (&buf_to_net))
+ if (! buf_empty_p (buf_to_net))
FD_SET (STDOUT_FILENO, &writefds);
if (stdout_pipe[0] >= 0)
@@ -2537,6 +2222,14 @@ do_cvs_command (command)
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
@@ -2554,7 +2247,7 @@ do_cvs_command (command)
if (FD_ISSET (STDOUT_FILENO, &writefds))
{
/* What should we do with errors? syslog() them? */
- buf_send_output (&buf_to_net);
+ buf_send_output (buf_to_net);
}
if (stdout_pipe[0] >= 0
@@ -2562,9 +2255,9 @@ do_cvs_command (command)
{
int status;
- status = buf_input_data (&stdoutbuf, (int *) NULL);
+ status = buf_input_data (stdoutbuf, (int *) NULL);
- buf_copy_lines (&buf_to_net, &stdoutbuf, 'M');
+ buf_copy_lines (buf_to_net, stdoutbuf, 'M');
if (status == -1)
stdout_pipe[0] = -1;
@@ -2575,7 +2268,7 @@ do_cvs_command (command)
}
/* What should we do with errors? syslog() them? */
- buf_send_output (&buf_to_net);
+ buf_send_output (buf_to_net);
}
if (stderr_pipe[0] >= 0
@@ -2583,9 +2276,9 @@ do_cvs_command (command)
{
int status;
- status = buf_input_data (&stderrbuf, (int *) NULL);
+ status = buf_input_data (stderrbuf, (int *) NULL);
- buf_copy_lines (&buf_to_net, &stderrbuf, 'E');
+ buf_copy_lines (buf_to_net, stderrbuf, 'E');
if (status == -1)
stderr_pipe[0] = -1;
@@ -2596,7 +2289,7 @@ do_cvs_command (command)
}
/* What should we do with errors? syslog() them? */
- buf_send_output (&buf_to_net);
+ buf_send_output (buf_to_net);
}
if (protocol_pipe[0] >= 0
@@ -2604,8 +2297,17 @@ do_cvs_command (command)
{
int status;
int count_read;
+ int special;
- status = buf_input_data (&protocol_inbuf, &count_read);
+ status = buf_input_data (protocol_inbuf, &count_read);
+
+ if (status == -1)
+ protocol_pipe[0] = -1;
+ else if (status > 0)
+ {
+ print_error (status);
+ goto error_exit;
+ }
/*
* We only call buf_copy_counted if we have read
@@ -2614,20 +2316,30 @@ do_cvs_command (command)
* have.
*/
count_needed -= count_read;
- if (count_needed <= 0)
- count_needed = buf_copy_counted (&buf_to_net,
- &protocol_inbuf);
-
- if (status == -1)
- protocol_pipe[0] = -1;
- else if (status > 0)
+ while (count_needed <= 0)
{
- print_error (status);
- goto error_exit;
- }
+ 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 -1, it means that the child
+ wants us to flush the pipe. We don't want to block
+ on the network, but we flush what we can. If the
+ client supports the 'F' command, we send it. */
+ if (special == -1)
+ {
+ if (supported_response ("F"))
+ {
+ buf_append_char (buf_to_net, 'F');
+ buf_append_char (buf_to_net, '\n');
+ }
- /* What should we do with errors? syslog() them? */
- buf_send_output (&buf_to_net);
+ cvs_flusherr ();
+ }
+ }
}
}
@@ -2636,18 +2348,18 @@ do_cvs_command (command)
* anything left on stdoutbuf or stderrbuf (this could only
* happen if there was no trailing newline), send it over.
*/
- if (! buf_empty_p (&stdoutbuf))
+ if (! buf_empty_p (stdoutbuf))
{
- buf_append_char (&stdoutbuf, '\n');
- buf_copy_lines (&buf_to_net, &stdoutbuf, 'M');
+ buf_append_char (stdoutbuf, '\n');
+ buf_copy_lines (buf_to_net, stdoutbuf, 'M');
}
- if (! buf_empty_p (&stderrbuf))
+ if (! buf_empty_p (stderrbuf))
{
- buf_append_char (&stderrbuf, '\n');
- buf_copy_lines (&buf_to_net, &stderrbuf, 'E');
+ buf_append_char (stderrbuf, '\n');
+ buf_copy_lines (buf_to_net, stderrbuf, 'E');
}
- if (! buf_empty_p (&protocol_inbuf))
- buf_output0 (&buf_to_net,
+ if (! buf_empty_p (protocol_inbuf))
+ buf_output0 (buf_to_net,
"E Protocol error: uncounted data discarded\n");
errs = 0;
@@ -2671,6 +2383,7 @@ do_cvs_command (command)
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
@@ -2678,14 +2391,17 @@ do_cvs_command (command)
* variety). But cvs doesn't currently use libiberty...we
* could roll our own.... FIXME.
*/
- printf ("E Terminated with fatal signal %d\n", sig);
+ 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. Is this portable? */
if (status & 0x80)
{
- printf ("E Core dumped; preserving %s on server.\n\
-E CVS locks may need cleaning up.\n",
- server_temp_dir);
+ 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;
@@ -2698,15 +2414,15 @@ E CVS locks may need cleaning up.\n",
* 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_send_output (&buf_to_net);
+ set_block (buf_to_net);
+ buf_flush (buf_to_net, 1);
}
if (errs)
/* We will have printed an error message already. */
- printf ("error \n");
+ buf_output0 (buf_to_net, "error \n");
else
- printf ("ok\n");
+ buf_output0 (buf_to_net, "ok\n");
goto free_args_and_return;
error_exit:
@@ -2743,6 +2459,11 @@ E CVS locks may need cleaning up.\n",
argument_count = 1;
}
+
+ /* Flush out any data not yet sent. */
+ set_block (buf_to_net);
+ buf_flush (buf_to_net, 1);
+
return;
}
@@ -2787,7 +2508,9 @@ server_pause_check()
if (FD_ISSET (flowcontrol_pipe[0], &fds))
{
- while (read (flowcontrol_pipe[0], buf, 1) == 1)
+ int got;
+
+ while ((got = read (flowcontrol_pipe[0], buf, 1)) == 1)
{
if (*buf == 'S') /* Stop */
paused = 1;
@@ -2796,11 +2519,24 @@ server_pause_check()
else
return; /* ??? */
}
+
+ /* This assumes that we are using BSD or POSIX nonblocking
+ I/O. System V nonblocking I/O returns zero if there is
+ nothing to read. */
+ if (got == 0)
+ error (1, 0, "flow control EOF");
+ if (got < 0 && ! blocking_error (errno))
+ {
+ error (1, errno, "flow control read failed");
+ }
}
}
}
#endif /* SERVER_FLOWCONTROL */
+/* This variable commented in server.h. */
+char *server_dir = NULL;
+
static void output_dir PROTO((char *, char *));
static void
@@ -2808,16 +2544,18 @@ output_dir (update_dir, repository)
char *update_dir;
char *repository;
{
- if (use_dir_and_repos)
+ if (server_dir != NULL)
{
- if (update_dir[0] == '\0')
- buf_output0 (&protocol, ".");
- else
- buf_output0 (&protocol, update_dir);
- buf_output0 (&protocol, "/\n");
+ buf_output0 (protocol, server_dir);
+ buf_output0 (protocol, "/");
}
- buf_output0 (&protocol, repository);
- buf_output0 (&protocol, "/");
+ if (update_dir[0] == '\0')
+ buf_output0 (protocol, ".");
+ else
+ buf_output0 (protocol, update_dir);
+ buf_output0 (protocol, "/\n");
+ buf_output0 (protocol, repository);
+ buf_output0 (protocol, "/");
}
/*
@@ -2929,9 +2667,9 @@ server_scratch (fname)
if (scratched_file != NULL)
{
- buf_output0 (&protocol,
+ buf_output0 (protocol,
"E CVS server internal error: duplicate Scratch_Entry\n");
- buf_send_counted (&protocol);
+ buf_send_counted (protocol);
return;
}
scratched_file = xstrdup (fname);
@@ -2950,22 +2688,23 @@ new_entries_line ()
{
if (entries_line)
{
- buf_output0 (&protocol, entries_line);
- buf_output (&protocol, "\n", 1);
+ buf_output0 (protocol, entries_line);
+ buf_output (protocol, "\n", 1);
}
else
/* Return the error message as the Entries line. */
- buf_output0 (&protocol,
+ buf_output0 (protocol,
"CVS server internal error: Register missing\n");
free (entries_line);
entries_line = NULL;
}
+
static void
serve_ci (arg)
char *arg;
{
- do_cvs_command (commit);
+ do_cvs_command ("commit", commit);
}
static void
@@ -2979,7 +2718,7 @@ checked_in_response (file, update_dir, repository)
struct stat sb;
char *mode_string;
- if (stat (file, &sb) < 0)
+ if ( CVS_STAT (file, &sb) < 0)
{
/* Not clear to me why the file would fail to exist, but it
was happening somewhere in the testsuite. */
@@ -2988,18 +2727,18 @@ checked_in_response (file, update_dir, repository)
}
else
{
- buf_output0 (&protocol, "Mode ");
+ buf_output0 (protocol, "Mode ");
mode_string = mode_to_string (sb.st_mode);
- buf_output0 (&protocol, mode_string);
- buf_output0 (&protocol, "\n");
+ buf_output0 (protocol, mode_string);
+ buf_output0 (protocol, "\n");
free (mode_string);
}
}
- buf_output0 (&protocol, "Checked-in ");
+ buf_output0 (protocol, "Checked-in ");
output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
+ buf_output0 (protocol, file);
+ buf_output (protocol, "\n", 1);
new_entries_line ();
}
@@ -3017,10 +2756,10 @@ server_checked_in (file, update_dir, repository)
* 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 ");
+ buf_output0 (protocol, "Remove-entry ");
output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
+ buf_output0 (protocol, file);
+ buf_output (protocol, "\n", 1);
free (scratched_file);
scratched_file = NULL;
}
@@ -3028,7 +2767,7 @@ server_checked_in (file, update_dir, repository)
{
checked_in_response (file, update_dir, repository);
}
- buf_send_counted (&protocol);
+ buf_send_counted (protocol);
}
void
@@ -3046,105 +2785,105 @@ server_update_entries (file, update_dir, repository, updated)
{
if (!supported_response ("New-entry"))
return;
- buf_output0 (&protocol, "New-entry ");
+ buf_output0 (protocol, "New-entry ");
output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
+ buf_output0 (protocol, file);
+ buf_output (protocol, "\n", 1);
new_entries_line ();
}
- buf_send_counted (&protocol);
+ buf_send_counted (protocol);
}
static void
serve_update (arg)
char *arg;
{
- do_cvs_command (update);
+ do_cvs_command ("update", update);
}
static void
serve_diff (arg)
char *arg;
{
- do_cvs_command (diff);
+ do_cvs_command ("diff", diff);
}
static void
serve_log (arg)
char *arg;
{
- do_cvs_command (cvslog);
+ do_cvs_command ("cvslog", cvslog);
}
static void
serve_add (arg)
char *arg;
{
- do_cvs_command (add);
+ do_cvs_command ("add", add);
}
static void
serve_remove (arg)
char *arg;
{
- do_cvs_command (cvsremove);
+ do_cvs_command ("cvsremove", cvsremove);
}
static void
serve_status (arg)
char *arg;
{
- do_cvs_command (status);
+ do_cvs_command ("status", status);
}
static void
serve_rdiff (arg)
char *arg;
{
- do_cvs_command (patch);
+ do_cvs_command ("patch", patch);
}
static void
serve_tag (arg)
char *arg;
{
- do_cvs_command (tag);
+ do_cvs_command ("cvstag", cvstag);
}
static void
serve_rtag (arg)
char *arg;
{
- do_cvs_command (rtag);
+ do_cvs_command ("rtag", rtag);
}
static void
serve_import (arg)
char *arg;
{
- do_cvs_command (import);
+ do_cvs_command ("import", import);
}
static void
serve_admin (arg)
char *arg;
{
- do_cvs_command (admin);
+ do_cvs_command ("admin", admin);
}
static void
serve_history (arg)
char *arg;
{
- do_cvs_command (history);
+ do_cvs_command ("history", history);
}
static void
serve_release (arg)
char *arg;
{
- do_cvs_command (release);
+ do_cvs_command ("release", release);
}
static void serve_watch_on PROTO ((char *));
@@ -3153,7 +2892,7 @@ static void
serve_watch_on (arg)
char *arg;
{
- do_cvs_command (watch_on);
+ do_cvs_command ("watch_on", watch_on);
}
static void serve_watch_off PROTO ((char *));
@@ -3162,7 +2901,7 @@ static void
serve_watch_off (arg)
char *arg;
{
- do_cvs_command (watch_off);
+ do_cvs_command ("watch_off", watch_off);
}
static void serve_watch_add PROTO ((char *));
@@ -3171,7 +2910,7 @@ static void
serve_watch_add (arg)
char *arg;
{
- do_cvs_command (watch_add);
+ do_cvs_command ("watch_add", watch_add);
}
static void serve_watch_remove PROTO ((char *));
@@ -3180,7 +2919,7 @@ static void
serve_watch_remove (arg)
char *arg;
{
- do_cvs_command (watch_remove);
+ do_cvs_command ("watch_remove", watch_remove);
}
static void serve_watchers PROTO ((char *));
@@ -3189,7 +2928,7 @@ static void
serve_watchers (arg)
char *arg;
{
- do_cvs_command (watchers);
+ do_cvs_command ("watchers", watchers);
}
static void serve_editors PROTO ((char *));
@@ -3198,7 +2937,7 @@ static void
serve_editors (arg)
char *arg;
{
- do_cvs_command (editors);
+ do_cvs_command ("editors", editors);
}
static int noop PROTO ((int, char **));
@@ -3217,7 +2956,7 @@ static void
serve_noop (arg)
char *arg;
{
- do_cvs_command (noop);
+ do_cvs_command ("noop", noop);
}
static void serve_init PROTO ((char *));
@@ -3226,15 +2965,17 @@ static void
serve_init (arg)
char *arg;
{
- CVSroot = malloc (strlen (arg) + 1);
- if (CVSroot == NULL)
+ if (!isabsolute (arg))
{
- pending_error = ENOMEM;
- return;
+ if (alloc_pending (80 + strlen (arg)))
+ sprintf (pending_error_text,
+ "E Root %s must be an absolute pathname", arg);
+ /* Fall through to do_cvs_command which will return the
+ actual error. */
}
- strcpy (CVSroot, arg);
+ set_local_cvsroot (arg);
- do_cvs_command (init);
+ do_cvs_command ("init", init);
}
static void serve_annotate PROTO ((char *));
@@ -3243,7 +2984,7 @@ static void
serve_annotate (arg)
char *arg;
{
- do_cvs_command (annotate);
+ do_cvs_command ("annotate", annotate);
}
static void
@@ -3265,7 +3006,7 @@ serve_co (arg)
tempdir = malloc (strlen (server_temp_dir) + 80);
if (tempdir == NULL)
{
- printf ("E Out of memory\n");
+ buf_output0 (buf_to_net, "E Out of memory\n");
return;
}
strcpy (tempdir, server_temp_dir);
@@ -3273,22 +3014,34 @@ serve_co (arg)
status = mkdir_p (tempdir);
if (status != 0 && status != EEXIST)
{
- printf ("E Cannot create %s\n", tempdir);
+ buf_output0 (buf_to_net, "E Cannot create ");
+ buf_output0 (buf_to_net, tempdir);
+ buf_append_char (buf_to_net, '\n');
print_error (errno);
free (tempdir);
return;
}
- if (chdir (tempdir) < 0)
+ if ( CVS_CHDIR (tempdir) < 0)
{
- printf ("E Cannot change to directory %s\n", tempdir);
+ buf_output0 (buf_to_net, "E Cannot change to directory ");
+ buf_output0 (buf_to_net, tempdir);
+ buf_append_char (buf_to_net, '\n');
print_error (errno);
free (tempdir);
return;
}
free (tempdir);
}
- do_cvs_command (checkout);
+
+ /* Compensate for server_export()'s setting of command_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 (command_name, "export") == 0) ?
+ "export" : "checkout",
+ checkout);
}
static void
@@ -3309,34 +3062,27 @@ server_copy_file (file, update_dir, repository, newfile)
{
if (!supported_response ("Copy-file"))
return;
- buf_output0 (&protocol, "Copy-file ");
+ 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");
+ buf_output0 (protocol, file);
+ buf_output0 (protocol, "\n");
+ buf_output0 (protocol, newfile);
+ buf_output0 (protocol, "\n");
}
+/* See server.h for description. */
+
void
-server_updated (file, update_dir, repository, updated, file_info, checksum)
- char *file;
- char *update_dir;
- char *repository;
+server_updated (finfo, vers, updated, file_info, checksum)
+ struct file_info *finfo;
+ Vers_TS *vers;
enum server_updated_arg4 updated;
struct stat *file_info;
unsigned char *checksum;
{
- char *short_pathname;
-
if (noexec)
return;
- short_pathname = xmalloc (strlen (update_dir) + strlen (file) + 10);
- if (update_dir[0] == '\0')
- strcpy (short_pathname, file);
- else
- sprintf (short_pathname, "%s/%s", update_dir, file);
-
if (entries_line != NULL && scratched_file == NULL)
{
FILE *f;
@@ -3345,7 +3091,7 @@ server_updated (file, update_dir, repository, updated, file_info, checksum)
unsigned long size;
char size_text[80];
- if (stat (file, &sb) < 0)
+ if ( CVS_STAT (finfo->file, &sb) < 0)
{
if (existence_error (errno))
{
@@ -3353,13 +3099,12 @@ server_updated (file, update_dir, repository, updated, file_info, checksum)
* 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.
- */
+ * forget the whole thing. */
free (entries_line);
entries_line = NULL;
goto done;
}
- error (1, errno, "reading %s", short_pathname);
+ error (1, errno, "reading %s", finfo->fullname);
}
if (checksum != NULL)
@@ -3376,27 +3121,52 @@ server_updated (file, update_dir, repository, updated, file_info, checksum)
int i;
char buf[3];
- buf_output0 (&protocol, "Checksum ");
+ buf_output0 (protocol, "Checksum ");
for (i = 0; i < 16; i++)
{
sprintf (buf, "%02x", (unsigned int) checksum[i]);
- buf_output0 (&protocol, buf);
+ buf_output0 (protocol, buf);
}
- buf_append_char (&protocol, '\n');
+ buf_append_char (protocol, '\n');
}
}
if (updated == SERVER_UPDATED)
- buf_output0 (&protocol, "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 = (Entnode *)node->data;
+ free (entnode->timestamp);
+ entnode->timestamp = xstrdup ("=");
+ }
else if (updated == SERVER_MERGED)
- buf_output0 (&protocol, "Merged ");
+ buf_output0 (protocol, "Merged ");
else if (updated == SERVER_PATCHED)
- buf_output0 (&protocol, "Patched ");
+ buf_output0 (protocol, "Patched ");
+ else if (updated == SERVER_RCS_DIFF)
+ buf_output0 (protocol, "Rcs-diff ");
else
abort ();
- output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
+ output_dir (finfo->update_dir, finfo->repository);
+ buf_output0 (protocol, finfo->file);
+ buf_output (protocol, "\n", 1);
new_entries_line ();
@@ -3404,15 +3174,14 @@ server_updated (file, update_dir, repository, updated, file_info, checksum)
char *mode_string;
/* FIXME: When we check out files the umask of the server
- (set in .bashrc if rsh is in use, or set in main.c in
- the kerberos case, I think) affects what mode we send,
- and it shouldn't. */
+ (set in .bashrc if rsh is in use) affects what mode we
+ send, and it shouldn't. */
if (file_info != NULL)
mode_string = mode_to_string (file_info->st_mode);
else
mode_string = mode_to_string (sb.st_mode);
- buf_output0 (&protocol, mode_string);
- buf_output0 (&protocol, "\n");
+ buf_output0 (protocol, mode_string);
+ buf_output0 (protocol, "\n");
free (mode_string);
}
@@ -3424,7 +3193,7 @@ server_updated (file, update_dir, repository, updated, file_info, checksum)
file we are sending. The client handles any line ending
translation if necessary. */
- if (gzip_level
+ if (file_gzip_level
/*
* For really tiny files, the gzip process startup
* time will outweigh the compression savings. This
@@ -3436,51 +3205,51 @@ server_updated (file, update_dir, repository, updated, file_info, checksum)
int status, fd, gzip_status;
pid_t gzip_pid;
- fd = open (file, O_RDONLY | OPEN_BINARY, 0);
+ fd = CVS_OPEN (finfo->file, O_RDONLY | OPEN_BINARY, 0);
if (fd < 0)
- error (1, errno, "reading %s", short_pathname);
- fd = filter_through_gzip (fd, 1, gzip_level, &gzip_pid);
+ error (1, errno, "reading %s", finfo->fullname);
+ fd = filter_through_gzip (fd, 1, file_gzip_level, &gzip_pid);
f = fdopen (fd, "rb");
status = buf_read_file_to_eof (f, &list, &last);
size = buf_chain_length (list);
if (status == -2)
- (*protocol.memory_error) (&protocol);
+ (*protocol->memory_error) (protocol);
else if (status != 0)
error (1, ferror (f) ? errno : 0, "reading %s",
- short_pathname);
+ finfo->fullname);
if (fclose (f) == EOF)
- error (1, errno, "reading %s", short_pathname);
+ error (1, errno, "reading %s", finfo->fullname);
if (waitpid (gzip_pid, &gzip_status, 0) == -1)
error (1, errno, "waiting for gzip process %ld",
(long) gzip_pid);
else if (gzip_status != 0)
error (1, 0, "gzip exited %d", gzip_status);
/* Prepending length with "z" is flag for using gzip here. */
- buf_output0 (&protocol, "z");
+ buf_output0 (protocol, "z");
}
else
{
long status;
size = sb.st_size;
- f = fopen (file, "rb");
+ f = CVS_FOPEN (finfo->file, "rb");
if (f == NULL)
- error (1, errno, "reading %s", short_pathname);
+ error (1, errno, "reading %s", finfo->fullname);
status = buf_read_file (f, sb.st_size, &list, &last);
if (status == -2)
- (*protocol.memory_error) (&protocol);
+ (*protocol->memory_error) (protocol);
else if (status != 0)
error (1, ferror (f) ? errno : 0, "reading %s",
- short_pathname);
+ finfo->fullname);
if (fclose (f) == EOF)
- error (1, errno, "reading %s", short_pathname);
+ error (1, errno, "reading %s", finfo->fullname);
}
}
sprintf (size_text, "%lu\n", size);
- buf_output0 (&protocol, size_text);
+ buf_output0 (protocol, size_text);
- buf_append_data (&protocol, list, last);
+ buf_append_data (protocol, list, last);
/* Note we only send a newline here if the file ended with one. */
/*
@@ -3490,29 +3259,31 @@ server_updated (file, update_dir, repository, updated, file_info, checksum)
* 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)
+ if ((updated == SERVER_UPDATED
+ || updated == SERVER_PATCHED
+ || updated == SERVER_RCS_DIFF)
/* But if we are joining, we'll need the file when we call
join_file. */
&& !joining ())
- unlink (file);
+ CVS_UNLINK (finfo->file);
}
else if (scratched_file != NULL && entries_line == NULL)
{
- if (strcmp (scratched_file, file) != 0)
+ if (strcmp (scratched_file, finfo->file) != 0)
error (1, 0,
"CVS server internal error: `%s' vs. `%s' scratched",
scratched_file,
- file);
+ finfo->file);
free (scratched_file);
scratched_file = NULL;
if (kill_scratched_file)
- buf_output0 (&protocol, "Removed ");
+ buf_output0 (protocol, "Removed ");
else
- buf_output0 (&protocol, "Remove-entry ");
- output_dir (update_dir, repository);
- buf_output0 (&protocol, file);
- buf_output (&protocol, "\n", 1);
+ buf_output0 (protocol, "Remove-entry ");
+ output_dir (finfo->update_dir, finfo->repository);
+ buf_output0 (protocol, finfo->file);
+ buf_output (protocol, "\n", 1);
}
else if (scratched_file == NULL && entries_line == NULL)
{
@@ -3524,9 +3295,16 @@ server_updated (file, update_dir, repository, updated, file_info, checksum)
else
error (1, 0,
"CVS server internal error: Register *and* Scratch_Entry.\n");
- buf_send_counted (&protocol);
- done:
- free (short_pathname);
+ buf_send_counted (protocol);
+ done:;
+}
+
+/* Return whether we should send patches in RCS format. */
+
+int
+server_use_rcs_diff ()
+{
+ return supported_response ("Rcs-diff");
}
void
@@ -3539,10 +3317,10 @@ server_set_entstat (update_dir, repository)
set_static_supported = supported_response ("Set-static-directory");
if (!set_static_supported) return;
- buf_output0 (&protocol, "Set-static-directory ");
+ buf_output0 (protocol, "Set-static-directory ");
output_dir (update_dir, repository);
- buf_output0 (&protocol, "\n");
- buf_send_counted (&protocol);
+ buf_output0 (protocol, "\n");
+ buf_send_counted (protocol);
}
void
@@ -3558,20 +3336,24 @@ server_clear_entstat (update_dir, repository)
if (noexec)
return;
- buf_output0 (&protocol, "Clear-static-directory ");
+ buf_output0 (protocol, "Clear-static-directory ");
output_dir (update_dir, repository);
- buf_output0 (&protocol, "\n");
- buf_send_counted (&protocol);
+ buf_output0 (protocol, "\n");
+ buf_send_counted (protocol);
}
void
-server_set_sticky (update_dir, repository, tag, date)
+server_set_sticky (update_dir, repository, tag, date, nonbranch)
char *update_dir;
char *repository;
char *tag;
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;
@@ -3581,28 +3363,31 @@ server_set_sticky (update_dir, repository, tag, date)
if (tag == NULL && date == NULL)
{
- buf_output0 (&protocol, "Clear-sticky ");
+ buf_output0 (protocol, "Clear-sticky ");
output_dir (update_dir, repository);
- buf_output0 (&protocol, "\n");
+ buf_output0 (protocol, "\n");
}
else
{
- buf_output0 (&protocol, "Set-sticky ");
+ buf_output0 (protocol, "Set-sticky ");
output_dir (update_dir, repository);
- buf_output0 (&protocol, "\n");
+ buf_output0 (protocol, "\n");
if (tag != NULL)
{
- buf_output0 (&protocol, "T");
- buf_output0 (&protocol, tag);
+ 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, "D");
+ buf_output0 (protocol, date);
}
- buf_output0 (&protocol, "\n");
+ buf_output0 (protocol, "\n");
}
- buf_send_counted (&protocol);
+ buf_send_counted (protocol);
}
struct template_proc_data
@@ -3629,11 +3414,11 @@ template_proc (repository, template)
if (!supported_response ("Template"))
/* Might want to warn the user that the rcsinfo feature won't work. */
return 0;
- buf_output0 (&protocol, "Template ");
+ buf_output0 (protocol, "Template ");
output_dir (data->update_dir, data->repository);
- buf_output0 (&protocol, "\n");
+ buf_output0 (protocol, "\n");
- fp = fopen (template, "rb");
+ fp = CVS_FOPEN (template, "rb");
if (fp == NULL)
{
error (0, errno, "Couldn't open rcsinfo template file %s", template);
@@ -3645,11 +3430,11 @@ template_proc (repository, template)
return 1;
}
sprintf (buf, "%ld\n", (long) sb.st_size);
- buf_output0 (&protocol, buf);
+ buf_output0 (protocol, buf);
while (!feof (fp))
{
n = fread (buf, 1, sizeof buf, fp);
- buf_output (&protocol, buf, n);
+ buf_output (protocol, buf, n);
if (ferror (fp))
{
error (0, errno, "cannot read rcsinfo template file %s", template);
@@ -3682,7 +3467,24 @@ serve_gzip_contents (arg)
level = atoi (arg);
if (level == 0)
level = 6;
- gzip_level = level;
+ file_gzip_level = level;
+}
+
+static void
+serve_gzip_stream (arg)
+ char *arg;
+{
+ int level;
+ level = atoi (arg);
+ if (level == 0)
+ level = 6;
+
+ /* All further communication with the client will be compressed. */
+
+ buf_to_net = compress_buffer_initialize (buf_to_net, 0, level,
+ buf_to_net->memory_error);
+ buf_from_net = compress_buffer_initialize (buf_from_net, 1, level,
+ buf_from_net->memory_error);
}
static void
@@ -3720,23 +3522,37 @@ expand_proc (pargc, argv, where, mwhere, mfile, shorten,
if (mwhere != NULL)
{
- printf ("Module-expansion %s", mwhere);
+ buf_output0 (buf_to_net, "Module-expansion ");
+ buf_output0 (buf_to_net, mwhere);
if (mfile != NULL)
{
- printf ("/%s", mfile);
+ buf_append_char (buf_to_net, '/');
+ buf_output0 (buf_to_net, mfile);
}
- printf ("\n");
+ buf_append_char (buf_to_net, '\n');
}
else
- {
+ {
/* We may not need to do this anymore -- check the definition
of aliases before removing */
if (*pargc == 1)
- printf ("Module-expansion %s\n", dir);
+ {
+ buf_output0 (buf_to_net, "Module-expansion ");
+ buf_output0 (buf_to_net, dir);
+ buf_append_char (buf_to_net, '\n');
+ }
else
- for (i = 1; i < *pargc; ++i)
- printf ("Module-expansion %s/%s\n", dir, argv[i]);
- }
+ {
+ for (i = 1; i < *pargc; ++i)
+ {
+ buf_output0 (buf_to_net, "Module-expansion ");
+ 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;
}
@@ -3749,11 +3565,6 @@ serve_expand_modules (arg)
DBM *db;
err = 0;
- /*
- * FIXME: error handling is bogus; do_module can write to stdout and/or
- * stderr and we're not using do_cvs_command.
- */
-
server_expanding = 1;
db = open_module ();
for (i = 1; i < argument_count; i++)
@@ -3775,9 +3586,13 @@ serve_expand_modules (arg)
}
if (err)
/* We will have printed an error message already. */
- printf ("error \n");
+ buf_output0 (buf_to_net, "error \n");
else
- printf ("ok\n");
+ 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);
}
void
@@ -3788,20 +3603,23 @@ server_prog (dir, name, which)
{
if (!supported_response ("Set-checkin-prog"))
{
- printf ("E \
+ buf_output0 (buf_to_net, "E \
warning: this client does not support -i or -u flags in the modules file.\n");
return;
}
switch (which)
{
case PROG_CHECKIN:
- printf ("Set-checkin-prog ");
+ buf_output0 (buf_to_net, "Set-checkin-prog ");
break;
case PROG_UPDATE:
- printf ("Set-update-prog ");
+ buf_output0 (buf_to_net, "Set-update-prog ");
break;
}
- printf ("%s\n%s\n", dir, name);
+ buf_output0 (buf_to_net, dir);
+ buf_append_char (buf_to_net, '\n');
+ buf_output0 (buf_to_net, name);
+ buf_append_char (buf_to_net, '\n');
}
static void
@@ -3809,26 +3627,27 @@ serve_checkin_prog (arg)
char *arg;
{
FILE *f;
- f = fopen (CVSADM_CIPROG, "w+");
+ f = CVS_FOPEN (CVSADM_CIPROG, "w+");
if (f == NULL)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_CIPROG);
+ if (alloc_pending (80 + strlen (CVSADM_CIPROG)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_CIPROG);
return;
}
if (fprintf (f, "%s\n", arg) < 0)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
- sprintf(pending_error_text, "E cannot write to %s", CVSADM_CIPROG);
+ if (alloc_pending (80 + strlen (CVSADM_CIPROG)))
+ sprintf (pending_error_text,
+ "E cannot write to %s", CVSADM_CIPROG);
return;
}
if (fclose (f) == EOF)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_CIPROG));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_CIPROG);
+ if (alloc_pending (80 + strlen (CVSADM_CIPROG)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_CIPROG);
return;
}
}
@@ -3838,26 +3657,39 @@ serve_update_prog (arg)
char *arg;
{
FILE *f;
- f = fopen (CVSADM_UPROG, "w+");
+
+ /* Before we do anything we need to make sure we are not in readonly
+ mode. */
+ if (!check_command_legal_p ("commit"))
+ {
+ /* I might be willing to make this a warning, except we lack the
+ machinery to do so. */
+ if (alloc_pending (80))
+ sprintf (pending_error_text, "\
+E Flag -u in modules not allowed in readonly mode");
+ return;
+ }
+
+ f = CVS_FOPEN (CVSADM_UPROG, "w+");
if (f == NULL)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
- sprintf(pending_error_text, "E cannot open %s", CVSADM_UPROG);
+ if (alloc_pending (80 + strlen (CVSADM_UPROG)))
+ sprintf (pending_error_text, "E cannot open %s", CVSADM_UPROG);
return;
}
if (fprintf (f, "%s\n", arg) < 0)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
- sprintf(pending_error_text, "E cannot write to %s", CVSADM_UPROG);
+ if (alloc_pending (80 + strlen (CVSADM_UPROG)))
+ sprintf (pending_error_text, "E cannot write to %s", CVSADM_UPROG);
return;
}
if (fclose (f) == EOF)
{
pending_error = errno;
- pending_error_text = malloc (80 + strlen(CVSADM_UPROG));
- sprintf(pending_error_text, "E cannot close %s", CVSADM_UPROG);
+ if (alloc_pending (80 + strlen (CVSADM_UPROG)))
+ sprintf (pending_error_text, "E cannot close %s", CVSADM_UPROG);
return;
}
}
@@ -3884,8 +3716,8 @@ struct request requests[] =
REQ_LINE("Root", serve_root, rq_essential),
REQ_LINE("Valid-responses", serve_valid_responses, rq_essential),
REQ_LINE("valid-requests", serve_valid_requests, rq_essential),
- REQ_LINE("Repository", serve_repository, rq_essential),
- REQ_LINE("Directory", serve_directory, rq_optional),
+ REQ_LINE("Repository", serve_repository, rq_optional),
+ REQ_LINE("Directory", serve_directory, rq_essential),
REQ_LINE("Max-dotdot", serve_max_dotdot, rq_optional),
REQ_LINE("Static-directory", serve_static_directory, rq_optional),
REQ_LINE("Sticky", serve_sticky, rq_optional),
@@ -3893,16 +3725,27 @@ struct request requests[] =
REQ_LINE("Update-prog", serve_update_prog, rq_optional),
REQ_LINE("Entry", serve_entry, rq_essential),
REQ_LINE("Modified", serve_modified, rq_essential),
- REQ_LINE("Lost", serve_lost, rq_optional),
+ REQ_LINE("Is-modified", serve_is_modified, rq_optional),
+
+ /* 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),
- REQ_LINE("Unchanged", serve_unchanged, rq_optional),
+
+ REQ_LINE("Unchanged", serve_unchanged, rq_essential),
REQ_LINE("Notify", serve_notify, rq_optional),
REQ_LINE("Questionable", serve_questionable, rq_optional),
REQ_LINE("Case", serve_case, rq_optional),
REQ_LINE("Argument", serve_argument, rq_essential),
REQ_LINE("Argumentx", serve_argumentx, rq_essential),
REQ_LINE("Global_option", serve_global_option, rq_optional),
+ REQ_LINE("Gzip-stream", serve_gzip_stream, rq_optional),
REQ_LINE("Set", serve_set, rq_optional),
+#ifdef ENCRYPTION
+#ifdef HAVE_KERBEROS
+ REQ_LINE("Kerberos-encrypt", serve_kerberos_encrypt, rq_optional),
+#endif
+#endif
REQ_LINE("expand-modules", serve_expand_modules, rq_optional),
REQ_LINE("ci", serve_ci, rq_essential),
REQ_LINE("co", serve_co, rq_essential),
@@ -3946,11 +3789,20 @@ serve_valid_requests (arg)
struct request *rq;
if (print_pending_error ())
return;
- printf ("Valid-requests");
+ buf_output0 (buf_to_net, "Valid-requests");
for (rq = requests; rq->name != NULL; rq++)
+ {
if (rq->func != NULL)
- printf (" %s", rq->name);
- printf ("\nok\n");
+ {
+ buf_append_char (buf_to_net, ' ');
+ buf_output0 (buf_to_net, rq->name);
+ }
+ }
+ buf_output0 (buf_to_net, "\nok\n");
+
+ /* The client is waiting for the list of valid requests, so we
+ must send the output now. */
+ buf_flush (buf_to_net, 1);
}
#ifdef sun
@@ -3962,10 +3814,10 @@ static int command_pid_is_dead;
static void wait_sig (sig)
int sig;
{
- int status;
- pid_t r = wait (&status);
- if (r == command_pid)
- command_pid_is_dead++;
+ int status;
+ pid_t r = wait (&status);
+ if (r == command_pid)
+ command_pid_is_dead++;
}
#endif
@@ -3974,104 +3826,135 @@ server_cleanup (sig)
int sig;
{
/* Do "rm -rf" on the temp directory. */
- int len;
- char *cmd;
- char *temp_dir;
+ int status;
+ int save_noexec;
+
+ if (buf_to_net != NULL)
+ {
+ /* FIXME: If this is not the final call from server, this
+ could deadlock, because the client might be blocked writing
+ to us. This should not be a problem in practice, because
+ we do not generate much output when the client is not
+ waiting for it. */
+ set_block (buf_to_net);
+ buf_flush (buf_to_net, 1);
+
+ /* The calls to buf_shutdown are currently only meaningful
+ when we are using compression. First 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. */
+
+ status = buf_shutdown (buf_from_net);
+ if (status != 0)
+ {
+ error (0, status, "shutting down buffer from client");
+ buf_flush (buf_to_net, 1);
+ }
+ }
if (dont_delete_temp)
+ {
+ if (buf_to_net != NULL)
+ (void) buf_shutdown (buf_to_net);
return;
+ }
/* What a bogus kludge. This disgusting code makes all kinds of
assumptions about SunOS, and is only for a bug in that system.
So only enable it on Suns. */
#ifdef sun
- if (command_pid > 0) {
- /* To avoid crashes on SunOS due to bugs in SunOS tmpfs
- triggered by the use of rename() in RCS, wait for the
- subprocess to die. Unfortunately, this means draining output
- while waiting for it to unblock the signal we sent it. Yuck! */
- int status;
- pid_t r;
-
- signal (SIGCHLD, wait_sig);
- if (sig)
- /* Perhaps SIGTERM would be more correct. But the child
- process will delay the SIGINT delivery until its own
- children have exited. */
- kill (command_pid, SIGINT);
- /* The caller may also have sent a signal to command_pid, so
- always try waiting. First, though, check and see if it's still
- there.... */
+ if (command_pid > 0)
+ {
+ /* To avoid crashes on SunOS due to bugs in SunOS tmpfs
+ triggered by the use of rename() in RCS, wait for the
+ subprocess to die. Unfortunately, this means draining output
+ while waiting for it to unblock the signal we sent it. Yuck! */
+ int status;
+ pid_t r;
+
+ signal (SIGCHLD, wait_sig);
+ if (sig)
+ /* Perhaps SIGTERM would be more correct. But the child
+ process will delay the SIGINT delivery until its own
+ children have exited. */
+ kill (command_pid, SIGINT);
+ /* The caller may also have sent a signal to command_pid, so
+ always try waiting. First, though, check and see if it's still
+ there.... */
do_waitpid:
- r = waitpid (command_pid, &status, WNOHANG);
- if (r == 0)
- ;
- else if (r == command_pid)
- command_pid_is_dead++;
- else if (r == -1)
- switch (errno) {
- case ECHILD:
- command_pid_is_dead++;
- break;
- case EINTR:
- goto do_waitpid;
- }
- else
- /* waitpid should always return one of the above values */
- abort ();
- while (!command_pid_is_dead) {
- struct timeval timeout;
- struct fd_set_wrapper readfds;
- char buf[100];
- int i;
-
- /* Use a non-zero timeout to avoid eating up CPU cycles. */
- timeout.tv_sec = 2;
- timeout.tv_usec = 0;
- readfds = command_fds_to_drain;
- switch (select (max_command_fd + 1, &readfds.fds,
- (fd_set *)0, (fd_set *)0,
- &timeout)) {
- case -1:
- if (errno != EINTR)
+ 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 ();
- case 0:
- /* timeout */
- break;
- case 1:
- for (i = 0; i <= max_command_fd; i++)
+ while (!command_pid_is_dead)
+ {
+ struct timeval timeout;
+ struct fd_set_wrapper readfds;
+ char buf[100];
+ int i;
+
+ /* Use a non-zero timeout to avoid eating up CPU cycles. */
+ timeout.tv_sec = 2;
+ timeout.tv_usec = 0;
+ readfds = command_fds_to_drain;
+ switch (select (max_command_fd + 1, &readfds.fds,
+ (fd_set *)0, (fd_set *)0,
+ &timeout))
{
- if (!FD_ISSET (i, &readfds.fds))
- continue;
- /* this fd is non-blocking */
- while (read (i, buf, sizeof (buf)) >= 1)
- ;
+ 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 ();
}
- break;
- default:
- abort ();
}
- }
}
#endif
- /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */
- temp_dir = getenv ("TMPDIR");
- if (temp_dir == NULL || temp_dir[0] == '\0')
- temp_dir = "/tmp";
- chdir(temp_dir);
-
- len = strlen (server_temp_dir) + 80;
- cmd = malloc (len);
- if (cmd == NULL)
- {
- printf ("E Cannot delete %s on server; out of memory\n",
- server_temp_dir);
- return;
- }
- sprintf (cmd, "rm -rf %s", server_temp_dir);
- system (cmd);
- free (cmd);
+ CVS_CHDIR (Tmpdir);
+ /* Temporarily clear noexec, so that we clean up our temp directory
+ regardless of it (this could more cleanly be handled by moving
+ the noexec check to all the unlink_file_dir callers from
+ unlink_file_dir itself). */
+ save_noexec = noexec;
+ noexec = 0;
+ /* FIXME? Would be nice to not ignore errors. But what should we do?
+ We could try to do this before we shut down the network connection,
+ and try to notify the client (but the client might not be waiting
+ for responses). We could try something like syslog() or our own
+ log file. */
+ unlink_file_dir (orig_server_temp_dir);
+ noexec = save_noexec;
+
+ if (buf_to_net != NULL)
+ (void) buf_shutdown (buf_to_net);
}
int server_active = 0;
@@ -4094,6 +3977,13 @@ server (argc, argv)
}
/* Ignore argc and argv. They might be from .cvsrc. */
+ buf_to_net = fd_buffer_initialize (STDOUT_FILENO, 0,
+ outbuf_memory_error);
+ buf_from_net = stdio_buffer_initialize (stdin, 1, outbuf_memory_error);
+
+ saved_output = buf_nonio_initialize (outbuf_memory_error);
+ saved_outerr = buf_nonio_initialize (outbuf_memory_error);
+
/* Since we're in the server parent process, error should use the
protocol to report error messages. */
error_use_protocol = 1;
@@ -4125,6 +4015,17 @@ server (argc, argv)
{
printf ("E Fatal server error, aborting.\n\
error ENOMEM Virtual memory exhausted.\n");
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
exit (EXIT_FAILURE);
}
putenv (env);
@@ -4135,46 +4036,115 @@ error ENOMEM Virtual memory exhausted.\n");
{
char *p;
- /* This might be set by the user in ~/.bashrc, ~/.cshrc, etc. */
- char *temp_dir = getenv ("TMPDIR");
- if (temp_dir == NULL || temp_dir[0] == '\0')
- temp_dir = "/tmp";
-
- server_temp_dir = malloc (strlen (temp_dir) + 80);
- if (server_temp_dir == NULL)
+ /* The code which wants to chdir into server_temp_dir is not set
+ up to deal with it being a relative path. So give an error
+ for that case. */
+ if (!isabsolute (Tmpdir))
{
+ pending_error_text = malloc (80 + strlen (Tmpdir));
+ if (pending_error_text == NULL)
+ {
+ pending_error = ENOMEM;
+ }
+ else
+ {
+ sprintf (pending_error_text,
+ "E Value of %s for TMPDIR is not absolute", Tmpdir);
+ }
+ /* FIXME: we would like this error to be persistent, that
+ is, not cleared by print_pending_error. The current client
+ will exit as soon as it gets an error, but the protocol spec
+ does not require a client to do so. */
+ }
+ else
+ {
+ int status;
+
+ server_temp_dir = malloc (strlen (Tmpdir) + 80);
+ if (server_temp_dir == NULL)
+ {
+ /*
+ * Strictly speaking, we're not supposed to output anything
+ * now. But we're about to exit(), give it a try.
+ */
+ printf ("E Fatal server error, aborting.\n\
+error ENOMEM Virtual memory exhausted.\n");
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket
+ subsystems on NT and OS2 or dealing with windows
+ and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (EXIT_FAILURE);
+ }
+ strcpy (server_temp_dir, Tmpdir);
+
+ /* Remove a trailing slash from TMPDIR if present. */
+ p = server_temp_dir + strlen (server_temp_dir) - 1;
+ if (*p == '/')
+ *p = '\0';
+
/*
- * Strictly speaking, we're not supposed to output anything
- * now. But we're about to exit(), give it a try.
+ * 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.
*/
- printf ("E Fatal server error, aborting.\n\
-error ENOMEM Virtual memory exhausted.\n");
- exit (EXIT_FAILURE);
- }
- strcpy (server_temp_dir, temp_dir);
+ strcat (server_temp_dir, "/cvs-serv");
- /* Remove a trailing slash from TMPDIR if present. */
- p = server_temp_dir + strlen (server_temp_dir) - 1;
- if (*p == '/')
- *p = '\0';
+ p = server_temp_dir + strlen (server_temp_dir);
+ sprintf (p, "%ld", (long) getpid ());
- /*
- * 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");
+ orig_server_temp_dir = server_temp_dir;
- p = server_temp_dir + strlen (server_temp_dir);
- sprintf (p, "%ld", (long) getpid ());
+ /* Create the temporary directory, and set the mode to
+ 700, to discourage random people from tampering with
+ it. */
+ status = mkdir_p (server_temp_dir);
+ if (status != 0 && status != EEXIST)
+ {
+ if (alloc_pending (80))
+ strcpy (pending_error_text,
+ "E can't create temporary directory");
+ pending_error = status;
+ }
+#ifndef CHMOD_BROKEN
+ else
+ {
+ if (chmod (server_temp_dir, S_IRWXU) < 0)
+ {
+ int save_errno = errno;
+ if (alloc_pending (80))
+ strcpy (pending_error_text, "\
+E cannot change permissions on temporary directory");
+ pending_error = save_errno;
+ }
+ }
+#endif
+ }
}
+#ifdef SIGHUP
(void) SIG_register (SIGHUP, server_cleanup);
+#endif
+#ifdef SIGINT
(void) SIG_register (SIGINT, server_cleanup);
+#endif
+#ifdef SIGQUIT
(void) SIG_register (SIGQUIT, server_cleanup);
+#endif
+#ifdef SIGPIPE
(void) SIG_register (SIGPIPE, server_cleanup);
+#endif
+#ifdef SIGTERM
(void) SIG_register (SIGTERM, server_cleanup);
-
+#endif
+
/* Now initialize our argument vector (for arguments from the client). */
/* Small for testing. */
@@ -4189,17 +4159,30 @@ error ENOMEM Virtual memory exhausted.\n");
*/
printf ("E Fatal server error, aborting.\n\
error ENOMEM Virtual memory exhausted.\n");
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
exit (EXIT_FAILURE);
}
argument_count = 1;
- argument_vector[0] = "Dummy argument 0";
-
- buf_to_net.data = buf_to_net.last = NULL;
- buf_to_net.fd = STDOUT_FILENO;
- buf_to_net.output = 1;
- buf_to_net.nonblocking = 0;
- buf_to_net.memory_error = outbuf_memory_error;
+ /* This gets printed if the client supports an option which the
+ server doesn't, causing the server to print a usage message.
+ FIXME: probably should be using program_name here.
+ 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. */
+ argument_vector[0] = "cvs server";
server_active = 1;
@@ -4207,16 +4190,19 @@ error ENOMEM Virtual memory exhausted.\n");
{
char *cmd, *orig_cmd;
struct request *rq;
+ int status;
- orig_cmd = cmd = read_line (stdin);
- if (cmd == NULL)
- break;
- if (cmd == NO_MEM_ERROR)
+ status = buf_read_line (buf_from_net, &cmd, (int *) NULL);
+ if (status == -2)
{
- printf ("E Fatal server error, aborting.\n\
+ 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)
{
@@ -4238,7 +4224,12 @@ error ENOMEM Virtual memory exhausted.\n");
if (rq->name == NULL)
{
if (!print_pending_error ())
- printf ("error unrecognized request `%s'\n", cmd);
+ {
+ 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);
}
@@ -4247,52 +4238,118 @@ error ENOMEM Virtual memory exhausted.\n");
}
-#ifdef AUTH_SERVER_SUPPORT
+#if defined (HAVE_KERBEROS) || defined (AUTH_SERVER_SUPPORT)
+static void switch_to_user PROTO((const char *));
-extern char *crypt PROTO((const char *, const char *));
+static void
+switch_to_user (username)
+ const char *username;
+{
+ struct passwd *pw;
+
+ pw = getpwnam (username);
+ if (pw == NULL)
+ {
+ printf ("E Fatal error, aborting.\n\
+error 0 %s: no such user\n", username);
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (EXIT_FAILURE);
+ }
+
+#if HAVE_INITGROUPS
+ initgroups (pw->pw_name, pw->pw_gid);
+#endif /* HAVE_INITGROUPS */
-/* This was test code, which we may need again. */
-#if 0
- /* If we were invoked this way, then stdin comes from the
- client and stdout/stderr writes to it. */
- int c;
- while ((c = getc (stdin)) != EOF && c != '*')
+#ifdef SETXID_SUPPORT
+ /* honor the setgid bit iff set*/
+ if (getgid() != getegid())
{
- printf ("%c", toupper (c));
- fflush (stdout);
+ setgid (getegid ());
}
- exit (0);
-#endif /* 1/0 */
+ else
+#else
+ {
+ setgid (pw->pw_gid);
+ }
+#endif
+
+ setuid (pw->pw_uid);
+ /* 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);
+
+#if HAVE_PUTENV
+ /* Set LOGNAME and USER in the environment, in case they are
+ already set to something else. */
+ {
+ char *env;
+
+ env = xmalloc (sizeof "LOGNAME=" + strlen (username));
+ (void) sprintf (env, "LOGNAME=%s", username);
+ (void) putenv (env);
+
+ env = xmalloc (sizeof "USER=" + strlen (username));
+ (void) sprintf (env, "USER=%s", username);
+ (void) putenv (env);
+ }
+#endif /* HAVE_PUTENV */
+}
+#endif
+
+#ifdef AUTH_SERVER_SUPPORT
+
+extern char *crypt PROTO((const char *, const char *));
/*
* 0 means no entry found for this user.
* 1 means entry found and password matches.
* 2 means entry found, but password does not match.
+ *
+ * If success, 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).
*/
-int
+static int
check_repository_password (username, password, repository, host_user_ptr)
char *username, *password, *repository, **host_user_ptr;
{
int retval = 0;
FILE *fp;
char *filename;
- char *linebuf;
+ char *linebuf = NULL;
+ size_t linebuf_len;
int found_it = 0;
int namelen;
+ /* We don't use CVSroot_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 ("CVSROOT")
+ + strlen (CVSROOTADM)
+ 1
- + strlen ("passwd")
+ + strlen (CVSROOTADM_PASSWD)
+ 1);
- strcpy (filename, repository);
- strcat (filename, "/CVSROOT");
- strcat (filename, "/passwd");
-
- fp = fopen (filename, "r");
+ (void) sprintf (filename, "%s/%s/%s", repository,
+ CVSROOTADM, CVSROOTADM_PASSWD);
+
+ fp = CVS_FOPEN (filename, "r");
if (fp == NULL)
{
if (!existence_error (errno))
@@ -4302,27 +4359,14 @@ check_repository_password (username, password, repository, host_user_ptr)
/* Look for a relevant line -- one with this user's name. */
namelen = strlen (username);
- while (1)
+ while (getline (&linebuf, &linebuf_len, fp) >= 0)
{
- linebuf = read_line(fp);
- if (linebuf == NULL)
- {
- free (linebuf);
- break;
- }
- if (linebuf == NO_MEM_ERROR)
- {
- error (0, errno, "out of memory");
- break;
- }
if ((strncmp (linebuf, username, namelen) == 0)
&& (linebuf[namelen] == ':'))
{
found_it = 1;
break;
}
- free (linebuf);
-
}
if (ferror (fp))
error (0, errno, "cannot read %s", filename);
@@ -4332,16 +4376,25 @@ check_repository_password (username, password, repository, host_user_ptr)
/* If found_it != 0, then linebuf contains the information we need. */
if (found_it)
{
- char *found_password;
+ char *found_password, *host_user_tmp;
strtok (linebuf, ":");
found_password = strtok (NULL, ": \n");
- *host_user_ptr = strtok (NULL, ": \n");
- if (*host_user_ptr == NULL) *host_user_ptr = username;
+ host_user_tmp = strtok (NULL, ": \n");
+ if (host_user_tmp == NULL)
+ host_user_tmp = username;
+
if (strcmp (found_password, crypt (password, found_password)) == 0)
+ {
+ /* Give host_user_ptr permanent storage. */
+ *host_user_ptr = xstrdup (host_user_tmp);
retval = 1;
+ }
else
- retval = 2;
+ {
+ *host_user_ptr = NULL;
+ retval = 2;
+ }
}
else
{
@@ -4350,18 +4403,20 @@ check_repository_password (username, password, repository, host_user_ptr)
}
free (filename);
+ if (linebuf)
+ free (linebuf);
return retval;
}
/* Return a hosting username if password matches, else NULL. */
-char *
+static char *
check_password (username, password, repository)
char *username, *password, *repository;
{
int rc;
- char *host_user;
+ 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
@@ -4370,229 +4425,760 @@ check_password (username, password, repository)
rc = check_repository_password (username, password, repository,
&host_user);
+ if (rc == 2)
+ return NULL;
+
+ /* else */
+
if (rc == 1)
- return host_user;
- else if (rc == 2)
- return 0;
+ {
+ /* host_user already set by reference, so just return. */
+ goto handle_return;
+ }
else if (rc == 0)
{
/* No cvs password found, so try /etc/passwd. */
+ const char *found_passwd = NULL;
+#ifdef HAVE_GETSPNAM
+ struct spwd *pw;
+
+ pw = getspnam (username);
+ if (pw != NULL)
+ {
+ found_passwd = pw->sp_pwdp;
+ }
+#else
struct passwd *pw;
- char *found_passwd;
pw = getpwnam (username);
+ if (pw != NULL)
+ {
+ found_passwd = pw->pw_passwd;
+ }
+#endif
+
if (pw == NULL)
- {
+ {
printf ("E Fatal error, aborting.\n\
error 0 %s: no such user\n", username);
+
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
exit (EXIT_FAILURE);
}
- found_passwd = pw->pw_passwd;
-
+
if (found_passwd && *found_passwd)
- return ((! strcmp (found_passwd, crypt (password, found_passwd)))
- ? username : NULL);
+ {
+ host_user = ((! strcmp (found_passwd,
+ crypt (password, found_passwd)))
+ ? username : NULL);
+ goto handle_return;
+ }
else if (password && *password)
- return username;
+ {
+ host_user = username;
+ goto handle_return;
+ }
else
- return NULL;
+ {
+ host_user = NULL;
+ goto handle_return;
+ }
}
else
{
/* Something strange happened. We don't know what it was, but
we certainly won't grant authorization. */
- return NULL;
+ host_user = NULL;
+ goto handle_return;
+ }
+
+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;
+}
/* 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
-authenticate_connection ()
+pserver_authenticate_connection ()
{
- char tmp[PATH_MAX];
- char repository[PATH_MAX];
- char username[PATH_MAX];
- char password[PATH_MAX];
- char *host_user;
- char *descrambled_password;
- struct passwd *pw;
- 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.
- */
-
- /* Since we're in the server parent process, error should use the
- protocol to report error messages. */
- error_use_protocol = 1;
-
- /* Make sure the protocol starts off on the right foot... */
- fgets (tmp, PATH_MAX, stdin);
- if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0)
- verify_and_exit = 1;
- else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") != 0)
- error (1, 0, "bad auth protocol start: %s", tmp);
-
- /* Get the three important pieces of information in order. */
- fgets (repository, PATH_MAX, stdin);
- fgets (username, PATH_MAX, stdin);
- fgets (password, PATH_MAX, stdin);
-
- /* Make them pure. */
- strip_trailing_newlines (repository);
- strip_trailing_newlines (username);
- strip_trailing_newlines (password);
-
- /* ... and make sure the protocol ends on the right foot. */
- fgets (tmp, PATH_MAX, stdin);
- if (strcmp (tmp,
- verify_and_exit ?
- "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n")
- != 0)
- {
- error (1, 0, "bad auth protocol end: %s", tmp);
- }
-
- /* We need the real cleartext before we hash it. */
- descrambled_password = descramble (password);
- host_user = check_password (username, descrambled_password, repository);
- if (host_user)
- {
- printf ("I LOVE YOU\n");
- fflush (stdout);
- memset (descrambled_password, 0, strlen (descrambled_password));
- free (descrambled_password);
- }
- else
- {
- printf ("I HATE YOU\n");
- fflush (stdout);
- memset (descrambled_password, 0, strlen (descrambled_password));
- free (descrambled_password);
- exit (EXIT_FAILURE);
- }
-
- /* Don't go any farther if we're just responding to "cvs login". */
- if (verify_and_exit)
- exit (0);
-
- /* Switch to run as this user. */
- pw = getpwnam (host_user);
- if (pw == NULL)
- {
- error (1, 0,
- "fatal error, aborting.\nerror 0 %s: no such user\n",
- username);
- }
-
-#if HAVE_INITGROUPS
- initgroups (pw->pw_name, pw->pw_gid);
-#endif /* HAVE_INITGROUPS */
+ char *tmp = NULL;
+ size_t tmp_allocated = 0;
+ char *repository = NULL;
+ size_t repository_allocated = 0;
+ char *username = NULL;
+ size_t username_allocated = 0;
+ char *password = NULL;
+ size_t password_allocated = 0;
- setgid (pw->pw_gid);
- setuid (pw->pw_uid);
- /* Inhibit access by randoms. Don't want people randomly
- changing our temporary tree before we check things in. */
- umask (077);
-
-#if HAVE_PUTENV
- /* Set LOGNAME and USER in the environment, in case they are
- already set to something else. */
- {
- char *env;
-
- env = xmalloc (sizeof "LOGNAME=" + strlen (username));
- (void) sprintf (env, "LOGNAME=%s", username);
- (void) putenv (env);
-
- env = xmalloc (sizeof "USER=" + strlen (username));
- (void) sprintf (env, "USER=%s", username);
- (void) putenv (env);
- }
-#endif /* HAVE_PUTENV */
+ char *host_user;
+ char *descrambled_password;
+ int verify_and_exit = 0;
+
+ /* The Authentication Protocol. Client sends:
+ *
+ * BEGIN AUTH REQUEST\n
+ * <REPOSITORY>\n
+ * <USERNAME>\n
+ * <PASSWORD>\n
+ * END AUTH REQUEST\n
+ *
+ * Server uses above information to authenticate, then sends
+ *
+ * I LOVE YOU\n
+ *
+ * if it grants access, else
+ *
+ * I HATE YOU\n
+ *
+ * if it denies access (and it exits if denying).
+ *
+ * When the client is "cvs login", the user does not desire actual
+ * repository access, but would like to confirm the password with
+ * the server. In this case, the start and stop strings are
+ *
+ * BEGIN VERIFICATION REQUEST\n
+ *
+ * and
+ *
+ * END VERIFICATION REQUEST\n
+ *
+ * On a verification request, the server's responses are the same
+ * (with the obvious semantics), but it exits immediately after
+ * sending the response in both cases.
+ *
+ * Why is the repository sent? Well, note that the actual
+ * client/server protocol can't start up until authentication is
+ * successful. But in order to perform authentication, the server
+ * needs to look up the password in the special CVS passwd file,
+ * before trying /etc/passwd. So the client transmits the
+ * repository as part of the "authentication protocol". The
+ * repository will be redundantly retransmitted later, but that's no
+ * big deal.
+ */
+
+#ifdef SO_KEEPALIVE
+ /* Set SO_KEEPALIVE on the socket, so that we don't hang forever
+ if the client dies while we are waiting for input. */
+ {
+ int on = 1;
+
+ (void) setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE,
+ (char *) &on, sizeof on);
+ }
+#endif
+
+ /* Make sure the protocol starts off on the right foot... */
+ if (getline (&tmp, &tmp_allocated, stdin) < 0)
+ /* FIXME: what? We could try writing error/eof, but chances
+ are the network connection is dead bidirectionally. log it
+ somewhere? */
+ ;
+
+ if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0)
+ verify_and_exit = 1;
+ else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") != 0)
+ error (1, 0, "bad auth protocol start: %s", tmp);
+
+ /* Get the three important pieces of information in order. */
+ /* See above comment about error handling. */
+ getline (&repository, &repository_allocated, stdin);
+ getline (&username, &username_allocated, stdin);
+ getline (&password, &password_allocated, stdin);
+
+ /* Make them pure. */
+ strip_trailing_newlines (repository);
+ strip_trailing_newlines (username);
+ strip_trailing_newlines (password);
+
+ /* ... and make sure the protocol ends on the right foot. */
+ /* See above comment about error handling. */
+ getline (&tmp, &tmp_allocated, stdin);
+ if (strcmp (tmp,
+ verify_and_exit ?
+ "END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n")
+ != 0)
+ {
+ error (1, 0, "bad auth protocol end: %s", tmp);
+ }
+
+ /* We need the real cleartext before we hash it. */
+ descrambled_password = descramble (password);
+ host_user = check_password (username, descrambled_password, repository);
+ memset (descrambled_password, 0, strlen (descrambled_password));
+ free (descrambled_password);
+ if (host_user)
+ {
+ printf ("I LOVE YOU\n");
+ fflush (stdout);
+ }
+ else
+ {
+ printf ("I HATE YOU\n");
+ fflush (stdout);
+ /* I'm doing this manually rather than via error_exit ()
+ because I'm not sure whether we want to call server_cleanup.
+ Needs more investigation.... */
+
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (EXIT_FAILURE);
+ }
+
+ /* Don't go any farther if we're just responding to "cvs login". */
+ if (verify_and_exit)
+ {
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+
+ exit (0);
+ }
+
+ /* Set Pserver_Repos so that we can check later that the same
+ repository is sent in later client/server protocol. */
+ Pserver_Repos = xmalloc (strlen (repository) + 1);
+ strcpy (Pserver_Repos, repository);
+
+ /* Switch to run as this user. */
+ switch_to_user (host_user);
+ free (tmp);
+ free (repository);
+ free (username);
+ free (password);
}
#endif /* AUTH_SERVER_SUPPORT */
+#ifdef HAVE_KERBEROS
+void
+kserver_authenticate_connection ()
+{
+ int status;
+ char instance[INST_SZ];
+ struct sockaddr_in peer;
+ struct sockaddr_in laddr;
+ int len;
+ KTEXT_ST ticket;
+ AUTH_DAT auth;
+ char version[KRB_SENDAUTH_VLEN];
+ char user[ANAME_SZ];
+
+ strcpy (instance, "*");
+ len = sizeof peer;
+ if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0
+ || getsockname (STDIN_FILENO, (struct sockaddr *) &laddr,
+ &len) < 0)
+ {
+ printf ("E Fatal error, aborting.\n\
+error %s getpeername or getsockname failed\n", strerror (errno));
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+ exit (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;
+
+ (void) setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE,
+ (char *) &on, sizeof on);
+ }
+#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));
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+ exit (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));
+#ifdef SYSTEM_CLEANUP
+ /* Hook for OS-specific behavior, for example socket subsystems on
+ NT and OS2 or dealing with windows and arguments on Mac. */
+ SYSTEM_CLEANUP ();
+#endif
+ exit (EXIT_FAILURE);
+ }
+
+ /* Switch to run as this user. */
+ switch_to_user (user);
+}
+#endif /* HAVE_KERBEROS */
+
#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;
+
+#ifdef ENCRYPTION
+
+#ifdef HAVE_KERBEROS
+
+/* An encryption interface using Kerberos. This is built on top of
+ the buffer structure. We encrypt using a big endian two byte count
+ field followed by a block of encrypted data. */
+
+/* This structure is the closure field of a Kerberos encryption
+ buffer. */
+
+struct krb_encrypt_buffer
+{
+ /* The underlying buffer. */
+ struct buffer *buf;
+ /* The Kerberos key schedule. */
+ Key_schedule sched;
+ /* The Kerberos DES block. */
+ C_Block block;
+ /* For an input buffer, we may have to buffer up data here. */
+ /* This is non-zero if the buffered data is decrypted. Otherwise,
+ the buffered data is encrypted, and starts with the two byte
+ count. */
+ int clear;
+ /* The amount of buffered data. */
+ int holdsize;
+ /* The buffer allocated to hold the data. */
+ char *holdbuf;
+ /* The size of holdbuf. */
+ int holdbufsize;
+ /* If clear is set, we need another data pointer to track where we
+ are in holdbuf. If clear is zero, then this pointer is not
+ used. */
+ char *holddata;
+};
+
+static int krb_encrypt_buffer_input PROTO((void *, char *, int, int, int *));
+static int krb_encrypt_buffer_output PROTO((void *, const char *, int, int *));
+static int krb_encrypt_buffer_flush PROTO((void *));
+static int krb_encrypt_buffer_block PROTO((void *, int));
+static int krb_encrypt_buffer_shutdown PROTO((void *));
+
+/* Create an encryption buffer. */
+
+struct buffer *
+krb_encrypt_buffer_initialize (buf, input, sched, block, memory)
+ struct buffer *buf;
+ int input;
+ Key_schedule sched;
+ C_Block block;
+ void (*memory) PROTO((struct buffer *));
+{
+ struct krb_encrypt_buffer *kb;
+
+ kb = (struct krb_encrypt_buffer *) xmalloc (sizeof *kb);
+ memset (kb, 0, sizeof *kb);
+
+ kb->buf = buf;
+ memcpy (kb->sched, sched, sizeof (Key_schedule));
+ memcpy (kb->block, block, sizeof (C_Block));
+ if (input)
+ {
+ /* We add some space to the buffer to hold the length. */
+ kb->holdbufsize = BUFFER_DATA_SIZE + 16;
+ kb->holdbuf = xmalloc (kb->holdbufsize);
+ }
+
+ return buf_initialize (input ? krb_encrypt_buffer_input : NULL,
+ input ? NULL : krb_encrypt_buffer_output,
+ input ? NULL : krb_encrypt_buffer_flush,
+ krb_encrypt_buffer_block,
+ krb_encrypt_buffer_shutdown,
+ memory,
+ kb);
+}
+
+/* Input data from a Kerberos encryption buffer. */
+
+static int
+krb_encrypt_buffer_input (closure, data, need, size, got)
+ void *closure;
+ char *data;
+ int need;
+ int size;
+ int *got;
+{
+ struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure;
+
+ *got = 0;
+
+ if (kb->holdsize > 0 && kb->clear)
+ {
+ int copy;
+
+ copy = kb->holdsize;
+
+ if (copy > size)
+ {
+ memcpy (data, kb->holddata, size);
+ kb->holdsize -= size;
+ kb->holddata += size;
+ *got = size;
+ return 0;
+ }
+
+ memcpy (data, kb->holddata, copy);
+ kb->holdsize = 0;
+ kb->clear = 0;
+
+ data += copy;
+ need -= copy;
+ size -= copy;
+ *got = copy;
+ }
+
+ while (need > 0 || *got == 0)
+ {
+ int get, status, nread, count, dcount;
+ char *bytes;
+ char stackoutbuf[BUFFER_DATA_SIZE + 16];
+ char *outbuf;
+
+ /* If we don't already have the two byte count, get it. */
+ if (kb->holdsize < 2)
+ {
+ get = 2 - kb->holdsize;
+ status = buf_read_data (kb->buf, get, &bytes, &nread);
+ if (status != 0)
+ {
+ /* buf_read_data can return -2, but a buffer input
+ function is only supposed to return -1, 0, or an
+ error code. */
+ if (status == -2)
+ status = ENOMEM;
+ return status;
+ }
+
+ if (nread == 0)
+ {
+ /* The buffer is in nonblocking mode, and we didn't
+ manage to read anything. */
+ return 0;
+ }
+
+ if (get == 1)
+ kb->holdbuf[1] = bytes[0];
+ else
+ {
+ kb->holdbuf[0] = bytes[0];
+ if (nread < 2)
+ {
+ /* We only got one byte, but we needed two. Stash
+ the byte we got, and try again. */
+ kb->holdsize = 1;
+ continue;
+ }
+ kb->holdbuf[1] = bytes[1];
+ }
+ kb->holdsize = 2;
+ }
+
+ /* Read the encrypted block of data. */
+
+ count = (((kb->holdbuf[0] & 0xff) << 8)
+ + (kb->holdbuf[1] & 0xff));
+
+ if (count + 2 > kb->holdbufsize)
+ {
+ char *n;
+
+ /* This should be impossible, since we should have
+ allocated space for the largest possible block in the
+ initialize function. However, we handle it just in
+ case something changes in the future, so that a current
+ server can handle a later client. */
+
+ n = realloc (kb->holdbuf, count + 2);
+ if (n == NULL)
+ {
+ (*kb->buf->memory_error) (kb->buf);
+ return ENOMEM;
+ }
+ kb->holdbuf = n;
+ kb->holdbufsize = count + 2;
+ }
+
+ get = count - (kb->holdsize - 2);
+
+ status = buf_read_data (kb->buf, get, &bytes, &nread);
+ if (status != 0)
+ {
+ /* buf_read_data can return -2, but a buffer input
+ function is only supposed to return -1, 0, or an error
+ code. */
+ if (status == -2)
+ status = ENOMEM;
+ return status;
+ }
+
+ if (nread == 0)
+ {
+ /* We did not get any data. Presumably the buffer is in
+ nonblocking mode. */
+ return 0;
+ }
+
+ /* FIXME: We could complicate the code here to avoid this
+ memcpy in the common case of kb->holdsize == 2 && nread ==
+ get. */
+ memcpy (kb->holdbuf + kb->holdsize, bytes, nread);
+ kb->holdsize += nread;
+
+ if (nread < get)
+ {
+ /* We did not get all the data we need. buf_read_data
+ does not promise to return all the bytes requested, so
+ we must try again. */
+ continue;
+ }
+
+ /* We have a complete encrypted block of COUNT bytes at
+ KB->HOLDBUF + 2. Decrypt it. */
+
+ if (count <= sizeof stackoutbuf)
+ outbuf = stackoutbuf;
+ else
+ {
+ /* I believe this is currently impossible, but we handle
+ it for the benefit of future client changes. */
+ outbuf = malloc (count);
+ if (outbuf == NULL)
+ {
+ (*kb->buf->memory_error) (kb->buf);
+ return ENOMEM;
+ }
+ }
+
+ des_cbc_encrypt ((C_Block *) (kb->holdbuf + 2), (C_Block *) outbuf,
+ count, kb->sched, &kb->block, 0);
+
+ /* The first two bytes in the decrypted buffer are the real
+ (unaligned) length. */
+ dcount = ((outbuf[0] & 0xff) << 8) + (outbuf[1] & 0xff);
+
+ if (((dcount + 2 + 7) & ~7) != count)
+ error (1, 0, "Decryption failure");
+
+ if (dcount > size)
+ {
+ /* We have too much data for the buffer. We need to save
+ some of it for the next call. */
+
+ memcpy (data, outbuf + 2, size);
+ *got += size;
+
+ kb->holdsize = dcount - size;
+ memcpy (kb->holdbuf, outbuf + 2 + size, dcount - size);
+ kb->holddata = kb->holdbuf;
+ kb->clear = 1;
+
+ if (outbuf != stackoutbuf)
+ free (outbuf);
+
+ return 0;
+ }
+
+ memcpy (data, outbuf + 2, dcount);
+
+ if (outbuf != stackoutbuf)
+ free (outbuf);
+
+ kb->holdsize = 0;
+
+ data += dcount;
+ need -= dcount;
+ size -= dcount;
+ *got += dcount;
+ }
+
+ return 0;
+}
+
+/* Output data to a Kerberos encryption buffer. */
+
+static int
+krb_encrypt_buffer_output (closure, data, have, wrote)
+ void *closure;
+ const char *data;
+ int have;
+ int *wrote;
+{
+ struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure;
+ char inbuf[BUFFER_DATA_SIZE + 16];
+ char outbuf[BUFFER_DATA_SIZE + 16];
+ int aligned;
+
+ if (have > BUFFER_DATA_SIZE)
+ {
+ /* It would be easy to malloc a buffer, but I don't think this
+ case can ever arise. */
+ abort ();
+ }
+
+ inbuf[0] = (have >> 8) & 0xff;
+ inbuf[1] = have & 0xff;
+ memcpy (inbuf + 2, data, have);
+
+ /* 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 (have + 2) (plus 2 for the count) to an 8 byte boundary. */
+ aligned = (have + 2 + 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 *) inbuf, (C_Block *) (outbuf + 2), aligned,
+ kb->sched, &kb->block, 1);
+
+ outbuf[0] = (aligned >> 8) & 0xff;
+ outbuf[1] = aligned & 0xff;
+
+ /* FIXME: It would be more efficient to get des_cbc_encrypt to put
+ its output directly into a buffer_data structure, which we
+ could then append to kb->buf. That would save a memcpy. */
+
+ buf_output (kb->buf, outbuf, aligned + 2);
+
+ *wrote = have;
+
+ /* We will only be here because buf_send_output was called on the
+ encryption buffer. That means that we should now call
+ buf_send_output on the underlying buffer. */
+ return buf_send_output (kb->buf);
+}
+
+/* Flush data to a Kerberos encryption buffer. */
+
+static int
+krb_encrypt_buffer_flush (closure)
+ void *closure;
+{
+ struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure;
+
+ /* Flush the underlying buffer. Note that if the original call to
+ buf_flush passed 1 for the BLOCK argument, then the buffer will
+ already have been set into blocking mode, so we should always
+ pass 0 here. */
+ return buf_flush (kb->buf, 0);
+}
+
+/* The block routine for a Kerberos encryption buffer. */
+
+static int
+krb_encrypt_buffer_block (closure, block)
+ void *closure;
+ int block;
+{
+ struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure;
+
+ if (block)
+ return set_block (kb->buf);
+ else
+ return set_nonblock (kb->buf);
+}
+
+/* Shut down a Kerberos encryption buffer. */
+
+static int
+krb_encrypt_buffer_shutdown (closure)
+ void *closure;
+{
+ struct krb_encrypt_buffer *kb = (struct krb_encrypt_buffer *) closure;
+
+ return buf_shutdown (kb->buf);
+}
+
+#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. Should not be called from the server parent process
- (yet at least, in the future it might be extended so that works). */
+ the first '\0' byte. */
void
cvs_output (str, len)
- char *str;
+ const char *str;
size_t len;
{
if (len == 0)
len = strlen (str);
- if (error_use_protocol)
- /* Eventually we'll probably want to make it so this case works,
- but for now, callers who want to output something with
- error_use_protocol in effect can just printf the "M foo"
- themselves. */
- abort ();
#ifdef SERVER_SUPPORT
- if (server_active)
+ if (error_use_protocol)
{
- buf_output (&saved_output, str, len);
- buf_copy_lines (&protocol, &saved_output, 'M');
- buf_send_counted (&protocol);
+ buf_output (saved_output, str, len);
+ buf_copy_lines (buf_to_net, saved_output, 'M');
+ }
+ else if (server_active)
+ {
+ buf_output (saved_output, str, len);
+ buf_copy_lines (protocol, saved_output, 'M');
+ buf_send_counted (protocol);
}
else
#endif
{
size_t written;
size_t to_write = len;
- char *p = str;
+ const char *p = str;
while (to_write > 0)
{
- written = fwrite (str, 1, to_write, stdout);
+ written = fwrite (p, 1, to_write, stdout);
if (written == 0)
break;
p += written;
@@ -4605,34 +5191,39 @@ cvs_output (str, len)
void
cvs_outerr (str, len)
- char *str;
+ const char *str;
size_t len;
{
if (len == 0)
len = strlen (str);
- if (error_use_protocol)
- /* Eventually we'll probably want to make it so this case works,
- but for now, callers who want to output something with
- error_use_protocol in effect can just printf the "E foo"
- themselves. */
- abort ();
#ifdef SERVER_SUPPORT
- if (server_active)
+ if (error_use_protocol)
{
- buf_output (&saved_outerr, str, len);
- buf_copy_lines (&protocol, &saved_outerr, 'E');
- buf_send_counted (&protocol);
+ buf_output (saved_outerr, str, len);
+ buf_copy_lines (buf_to_net, saved_outerr, 'E');
+ }
+ else if (server_active)
+ {
+ buf_output (saved_outerr, str, len);
+ buf_copy_lines (protocol, saved_outerr, 'E');
+ buf_send_counted (protocol);
}
else
#endif
{
size_t written;
size_t to_write = len;
- char *p = str;
+ 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 (str, 1, to_write, stderr);
+ written = fwrite (p, 1, to_write, stderr);
if (written == 0)
break;
p += written;
@@ -4640,3 +5231,52 @@ cvs_outerr (str, len)
}
}
}
+
+/* Flush stderr. stderr is normally flushed automatically, of course,
+ but this function is used to flush information from the server back
+ to the client. */
+
+void
+cvs_flusherr ()
+{
+#ifdef SERVER_SUPPORT
+ if (error_use_protocol)
+ {
+ /* Flush what we can to the network, but don't block. */
+ buf_flush (buf_to_net, 0);
+ }
+ else if (server_active)
+ {
+ /* Send a special count to tell the parent to flush. */
+ buf_send_special_count (protocol, -1);
+ }
+ else
+#endif
+ fflush (stderr);
+}
+
+/* Make it possible for the user to see what has been written to
+ stdout (it is up to the implementation to decide exactly how far it
+ should go to ensure this). */
+
+void
+cvs_flushout ()
+{
+#ifdef SERVER_SUPPORT
+ if (error_use_protocol)
+ {
+ /* Flush what we can to the network, but don't block. */
+ buf_flush (buf_to_net, 0);
+ }
+ else if (server_active)
+ {
+ /* Just do nothing. This is because the code which
+ cvs_flushout replaces, setting stdout to line buffering in
+ main.c, didn't get called in the server child process. But
+ in the future it is quite plausible that we'll want to make
+ this case work analogously to cvs_flusherr. */
+ }
+ else
+#endif
+ fflush (stdout);
+}