#include <assert.h>
#include "cvs.h"
#include "watch.h"
#include "edit.h"
#include "fileattr.h"
#include "getline.h"
#include "buffer.h"
#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
# ifdef HAVE_GSSAPI
# include <netdb.h>
# include "xgssapi.h"
# include <krb5.h>
static gss_ctx_id_t gcontext;
static void gserver_authenticate_connection PROTO((void));
static int cvs_gssapi_wrapping;
# ifdef ENCRYPTION
int cvs_gssapi_encrypt;
# endif
# endif
#endif
#ifdef SERVER_SUPPORT
#ifdef HAVE_WINSOCK_H
#include <winsock.h>
#endif
#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_KERBEROS) || defined (HAVE_GSSAPI)
#include <sys/socket.h>
#endif
#ifdef HAVE_SYSLOG_H
# include <syslog.h>
# ifndef LOG_DAEMON
# define LOG_DAEMON 0
# endif
#endif
#ifdef HAVE_KERBEROS
# include <netinet/in.h>
# include <krb.h>
# ifndef HAVE_KRB_GET_ERR_TEXT
# define krb_get_err_text(status) krb_err_txt[status]
# endif
static C_Block kblock;
static Key_schedule sched;
#endif
#include "xselect.h"
#ifndef O_NONBLOCK
#define O_NONBLOCK O_NDELAY
#endif
#ifdef EWOULDBLOCK
#define blocking_error(err) ((err) == EWOULDBLOCK || (err) == EAGAIN)
#else
#define blocking_error(err) ((err) == EAGAIN)
#endif
#if HAVE_INITGROUPS
#include <grp.h>
#endif
# ifdef AUTH_SERVER_SUPPORT
# ifdef HAVE_GETSPNAM
# include <shadow.h>
# endif
char *CVS_Username = NULL;
static char *Pserver_Repos = NULL;
int system_auth = 1;
# endif
static struct buffer *buf_to_net;
static struct buffer *buf_from_net;
static char *server_temp_dir;
static char *orig_server_temp_dir;
static int dont_delete_temp;
static void server_write_entries PROTO((void));
struct fd_buffer
{
int fd;
int blocking;
};
static struct buffer *fd_buffer_initialize
PROTO ((int, int, void (*) (struct buffer *)));
static int fd_buffer_input PROTO((void *, char *, int, int, int *));
static int fd_buffer_output PROTO((void *, const char *, int, int *));
static int fd_buffer_flush PROTO((void *));
static int fd_buffer_block PROTO((void *, int));
static int fd_buffer_shutdown PROTO((struct buffer *));
static struct buffer *
fd_buffer_initialize (fd, input, memory)
int fd;
int input;
void (*memory) PROTO((struct buffer *));
{
struct fd_buffer *n;
n = (struct fd_buffer *) xmalloc (sizeof *n);
n->fd = fd;
n->blocking = 1;
return buf_initialize (input ? fd_buffer_input : NULL,
input ? NULL : fd_buffer_output,
input ? NULL : fd_buffer_flush,
fd_buffer_block,
fd_buffer_shutdown,
memory,
n);
}
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
{
nbytes = read (fd->fd, data, need == 0 ? 1 : need);
}
if (nbytes > 0)
{
*got = nbytes;
return 0;
}
*got = 0;
if (nbytes == 0)
{
return -1;
}
if (blocking_error (errno))
{
return 0;
}
return errno;
}
static int
fd_buffer_output (closure, data, have, wrote)
void *closure;
const char *data;
int have;
int *wrote;
{
struct fd_buffer *fd = (struct fd_buffer *) closure;
*wrote = 0;
while (have > 0)
{
int nbytes;
nbytes = write (fd->fd, data, have);
if (nbytes <= 0)
{
if (! fd->blocking
&& (nbytes == 0 || blocking_error (errno)))
{
return 0;
}
if (nbytes == 0)
return EIO;
return errno;
}
*wrote += nbytes;
data += nbytes;
have -= nbytes;
}
return 0;
}
static int
fd_buffer_flush (closure)
void *closure;
{
return 0;
}
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;
}
static int
fd_buffer_shutdown (buf)
struct buffer *buf;
{
free (buf->closure);
buf->closure = NULL;
return 0;
}
static int create_adm_p PROTO((char *, char *));
static int
create_adm_p (base_dir, dir)
char *base_dir;
char *dir;
{
char *dir_where_cvsadm_lives, *dir_to_register, *p, *tmp;
int retval, done;
FILE *f;
if (strcmp (dir, ".") == 0)
return 0;
p = xmalloc (strlen (dir) + 1);
if (p == NULL)
return ENOMEM;
dir_where_cvsadm_lives = xmalloc (strlen (base_dir) + strlen (dir) + 100);
if (dir_where_cvsadm_lives == NULL)
{
free (p);
return ENOMEM;
}
tmp = xmalloc (strlen (base_dir) + strlen (dir) + 100);
if (tmp == NULL)
{
free (p);
free (dir_where_cvsadm_lives);
return ENOMEM;
}
retval = done = 0;
strcpy (p, dir);
strcpy (dir_where_cvsadm_lives, base_dir);
strcat (dir_where_cvsadm_lives, "/");
strcat (dir_where_cvsadm_lives, p);
dir_to_register = NULL;
while (1)
{
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM);
if ((CVS_MKDIR (tmp, 0777) < 0) && (errno != EEXIST))
{
retval = errno;
goto finish;
}
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_REP);
if (! isfile (tmp))
{
char *empty;
empty = xmalloc (strlen (current_parsed_root->directory)
+ sizeof (CVSROOTADM)
+ sizeof (CVSNULLREPOS)
+ 3);
if (! empty)
{
retval = ENOMEM;
goto finish;
}
(void) sprintf (empty, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSNULLREPOS);
if (! isfile (empty))
{
mode_t omask;
omask = umask (cvsumask);
if (CVS_MKDIR (empty, 0777) < 0)
{
retval = errno;
free (empty);
goto finish;
}
(void) umask (omask);
}
f = CVS_FOPEN (tmp, "w");
if (f == NULL)
{
retval = errno;
free (empty);
goto finish;
}
if (fprintf (f, "%s\n", empty) < 0)
{
retval = errno;
fclose (f);
free (empty);
goto finish;
}
if (fclose (f) == EOF)
{
retval = errno;
free (empty);
goto finish;
}
free (empty);
}
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_ENT);
f = CVS_FOPEN (tmp, "a");
if (f == NULL)
{
retval = errno;
goto finish;
}
if (fclose (f) == EOF)
{
retval = errno;
goto finish;
}
if (dir_to_register != NULL)
{
Subdir_Register ((List *) NULL, dir_where_cvsadm_lives,
dir_to_register);
}
if (done)
break;
dir_to_register = strrchr (p, '/');
if (dir_to_register == NULL)
{
dir_to_register = p;
strcpy (dir_where_cvsadm_lives, base_dir);
done = 1;
}
else
{
*dir_to_register = '\0';
dir_to_register++;
strcpy (dir_where_cvsadm_lives, base_dir);
strcat (dir_where_cvsadm_lives, "/");
strcat (dir_where_cvsadm_lives, p);
}
}
finish:
free (tmp);
free (dir_where_cvsadm_lives);
free (p);
return retval;
}
static int mkdir_p PROTO((char *));
static int
mkdir_p (dir)
char *dir;
{
char *p;
char *q = xmalloc (strlen (dir) + 1);
int retval;
if (q == NULL)
return ENOMEM;
retval = 0;
p = dir + 1;
while (1)
{
while (*p != '/' && *p != '\0')
++p;
if (*p == '/')
{
strncpy (q, dir, p - dir);
q[p - dir] = '\0';
if (q[p - dir - 1] != '/' && CVS_MKDIR (q, 0777) < 0)
{
int saved_errno = errno;
if (saved_errno != EEXIST
&& ((saved_errno != EACCES && saved_errno != EROFS)
|| !isdir (q)))
{
retval = saved_errno;
goto done;
}
}
++p;
}
else
{
if (CVS_MKDIR (dir, 0777) < 0)
retval = errno;
goto done;
}
}
done:
free (q);
return retval;
}
static void
print_error (status)
int status;
{
char *msg;
char tmpstr[80];
buf_output0 (buf_to_net, "error ");
msg = strerror (status);
if (msg == NULL)
{
sprintf (tmpstr, "unknown error %d", status);
msg = tmpstr;
}
buf_output0 (buf_to_net, msg);
buf_append_char (buf_to_net, '\n');
buf_flush (buf_to_net, 0);
}
static int pending_error;
static char *pending_error_text;
static int
print_pending_error ()
{
if (pending_error_text)
{
buf_output0 (buf_to_net, pending_error_text);
buf_append_char (buf_to_net, '\n');
if (pending_error)
print_error (pending_error);
else
buf_output0 (buf_to_net, "error \n");
buf_flush (buf_to_net, 0);
pending_error = 0;
free (pending_error_text);
pending_error_text = NULL;
return 1;
}
else if (pending_error)
{
print_error (pending_error);
pending_error = 0;
return 1;
}
else
return 0;
}
#define error_pending() (pending_error || pending_error_text)
static int alloc_pending PROTO ((size_t size));
static int
alloc_pending (size)
size_t size;
{
if (error_pending ())
return 0;
pending_error_text = xmalloc (size);
if (pending_error_text == NULL)
{
pending_error = ENOMEM;
return 0;
}
return 1;
}
static void serve_is_modified PROTO ((char *));
static int supported_response PROTO ((char *));
static int
supported_response (name)
char *name;
{
struct response *rs;
for (rs = responses; rs->name != NULL; ++rs)
if (strcmp (rs->name, name) == 0)
return rs->status == rs_supported;
error (1, 0, "internal error: testing support for unknown response?");
return 0;
}
static void
serve_valid_responses (arg)
char *arg;
{
char *p = arg;
char *q;
struct response *rs;
do
{
q = strchr (p, ' ');
if (q != NULL)
*q++ = '\0';
for (rs = responses; rs->name != NULL; ++rs)
{
if (strcmp (rs->name, p) == 0)
break;
}
if (rs->name == NULL)
;
else
rs->status = rs_supported;
p = q;
} while (q != NULL);
for (rs = responses; rs->name != NULL; ++rs)
{
if (rs->status == rs_essential)
{
buf_output0 (buf_to_net, "E response `");
buf_output0 (buf_to_net, rs->name);
buf_output0 (buf_to_net, "' not supported by client\nerror \n");
buf_flush (buf_to_net, 1);
error_exit ();
}
else if (rs->status == rs_optional)
rs->status = rs_not_supported;
}
}
static void
serve_root (arg)
char *arg;
{
char *env;
char *path;
if (error_pending()) return;
if (!isabsolute (arg))
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E Root %s must be an absolute pathname", arg);
return;
}
if (current_parsed_root != NULL)
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E Protocol error: Duplicate Root request, for %s", arg);
return;
}
#ifdef AUTH_SERVER_SUPPORT
if (Pserver_Repos != NULL)
{
if (strcmp (Pserver_Repos, arg) != 0)
{
if (alloc_pending (80 + strlen (Pserver_Repos) + strlen (arg)))
sprintf (pending_error_text, "\
E Protocol error: Root says \"%s\" but pserver says \"%s\"",
arg, Pserver_Repos);
return;
}
}
#endif
current_parsed_root = local_cvsroot (arg);
parse_config (current_parsed_root->directory);
path = xmalloc (strlen (current_parsed_root->directory)
+ sizeof (CVSROOTADM)
+ 2);
if (path == NULL)
{
pending_error = ENOMEM;
return;
}
(void) sprintf (path, "%s/%s", current_parsed_root->directory, CVSROOTADM);
if (!isaccessible (path, R_OK | X_OK))
{
int save_errno = errno;
if (alloc_pending (80 + strlen (path)))
sprintf (pending_error_text, "E Cannot access %s", path);
pending_error = save_errno;
}
free (path);
#ifdef HAVE_PUTENV
env = xmalloc (strlen (CVSROOT_ENV) + strlen (current_parsed_root->directory) + 2);
if (env == NULL)
{
pending_error = ENOMEM;
return;
}
(void) sprintf (env, "%s=%s", CVSROOT_ENV, current_parsed_root->directory);
(void) putenv (env);
#endif
}
static int max_dotdot_limit = 0;
void
server_pathname_check (path)
char *path;
{
if (isabsolute (path))
error (1, 0, "absolute pathname `%s' illegal for server", path);
if (pathname_levels (path) > max_dotdot_limit)
{
error (0, 0, "protocol error: `%s' contains more leading ..", path);
error (1, 0, "than the %d which Max-dotdot specified",
max_dotdot_limit);
}
}
static int outside_root PROTO ((char *));
static int
outside_root (repos)
char *repos;
{
size_t repos_len = strlen (repos);
size_t root_len = strlen (current_parsed_root->directory);
if (!isabsolute (repos))
{
if (alloc_pending (repos_len + 80))
sprintf (pending_error_text, "\
E protocol error: %s is not absolute", repos);
return 1;
}
if (repos_len < root_len
|| strncmp (current_parsed_root->directory, repos, root_len) != 0)
{
not_within:
if (alloc_pending (strlen (current_parsed_root->directory)
+ strlen (repos)
+ 80))
sprintf (pending_error_text, "\
E protocol error: directory '%s' not within root '%s'",
repos, current_parsed_root->directory);
return 1;
}
if (repos_len > root_len)
{
if (repos[root_len] != '/')
goto not_within;
if (pathname_levels (repos + root_len + 1) > 0)
goto not_within;
}
return 0;
}
static int outside_dir PROTO ((char *));
static int
outside_dir (file)
char *file;
{
if (strchr (file, '/') != NULL)
{
if (alloc_pending (strlen (file)
+ 80))
sprintf (pending_error_text, "\
E protocol error: directory '%s' not within current directory",
file);
return 1;
}
return 0;
}
static void
serve_max_dotdot (arg)
char *arg;
{
int lim = atoi (arg);
int i;
char *p;
if (lim < 0 || lim > 10000)
return;
p = xmalloc (strlen (server_temp_dir) + 2 * lim + 10);
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
strcpy (p, server_temp_dir);
for (i = 0; i < lim; ++i)
strcat (p, "/d");
if (server_temp_dir != orig_server_temp_dir)
free (server_temp_dir);
server_temp_dir = p;
max_dotdot_limit = lim;
}
static char *dir_name;
static void
dirswitch (dir, repos)
char *dir;
char *repos;
{
int status;
FILE *f;
size_t dir_len;
server_write_entries ();
if (error_pending()) return;
if (isabsolute (dir))
{
if (alloc_pending (80 + strlen (dir)))
sprintf (pending_error_text,
"E absolute pathname `%s' illegal for server", dir);
return;
}
if (pathname_levels (dir) > max_dotdot_limit)
{
if (alloc_pending (80 + strlen (dir)))
sprintf (pending_error_text,
"E protocol error: `%s' has too many ..", dir);
return;
}
dir_len = strlen (dir);
if (dir_len > 0
&& dir[dir_len - 1] == '/')
{
if (alloc_pending (80 + dir_len))
sprintf (pending_error_text,
"E protocol error: invalid directory syntax in %s", dir);
return;
}
if (dir_name != NULL)
free (dir_name);
dir_name = xmalloc (strlen (server_temp_dir) + dir_len + 40);
if (dir_name == NULL)
{
pending_error = ENOMEM;
return;
}
strcpy (dir_name, server_temp_dir);
strcat (dir_name, "/");
strcat (dir_name, dir);
status = mkdir_p (dir_name);
if (status != 0
&& status != EEXIST)
{
if (alloc_pending (80 + strlen (dir_name)))
sprintf (pending_error_text, "E cannot mkdir %s", dir_name);
pending_error = status;
return;
}
status = create_adm_p (server_temp_dir, dir);
if (status != 0)
{
if (alloc_pending (80 + strlen (dir_name)))
sprintf (pending_error_text, "E cannot create_adm_p %s", dir_name);
pending_error = status;
return;
}
if ( CVS_CHDIR (dir_name) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (dir_name)))
sprintf (pending_error_text, "E cannot change to %s", dir_name);
pending_error = save_errno;
return;
}
if ((CVS_MKDIR (CVSADM, 0777) < 0) && (errno != EEXIST))
{
int save_errno = errno;
if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM)))
sprintf (pending_error_text,
"E cannot mkdir %s/%s", dir_name, CVSADM);
pending_error = save_errno;
return;
}
f = CVS_FOPEN (CVSADM_REP, "w");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E cannot open %s/%s", dir_name, CVSADM_REP);
pending_error = save_errno;
return;
}
if (fprintf (f, "%s", repos) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E error writing %s/%s", dir_name, CVSADM_REP);
pending_error = save_errno;
fclose (f);
return;
}
if (strcmp (dir, ".") == 0
&& current_parsed_root != NULL
&& current_parsed_root->directory != NULL
&& strcmp (current_parsed_root->directory, repos) == 0)
{
if (fprintf (f, "/.") < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E error writing %s/%s", dir_name, CVSADM_REP);
pending_error = save_errno;
fclose (f);
return;
}
}
if (fprintf (f, "\n") < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E error writing %s/%s", dir_name, CVSADM_REP);
pending_error = save_errno;
fclose (f);
return;
}
if (fclose (f) == EOF)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (dir_name) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E error closing %s/%s", dir_name, CVSADM_REP);
pending_error = save_errno;
return;
}
f = CVS_FOPEN (CVSADM_ENT, "a");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENT)))
sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT);
pending_error = save_errno;
return;
}
if (fclose (f) == EOF)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENT)))
sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
pending_error = save_errno;
return;
}
}
static void
serve_repository (arg)
char *arg;
{
if (alloc_pending (80))
strcpy (pending_error_text,
"E Repository request is obsolete; aborted");
return;
}
static void
serve_directory (arg)
char *arg;
{
int status;
char *repos;
status = buf_read_line (buf_from_net, &repos, (int *) NULL);
if (status == 0)
{
if (!outside_root (repos))
dirswitch (arg, repos);
free (repos);
}
else if (status == -2)
{
pending_error = ENOMEM;
}
else
{
pending_error_text = xmalloc (80 + strlen (arg));
if (pending_error_text == NULL)
{
pending_error = ENOMEM;
}
else if (status == -1)
{
sprintf (pending_error_text,
"E end of file reading mode for %s", arg);
}
else
{
sprintf (pending_error_text,
"E error reading mode for %s", arg);
pending_error = status;
}
}
}
static void
serve_static_directory (arg)
char *arg;
{
FILE *f;
if (error_pending ()) return;
f = CVS_FOPEN (CVSADM_ENTSTAT, "w+");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENTSTAT)))
sprintf (pending_error_text, "E cannot open %s", CVSADM_ENTSTAT);
pending_error = save_errno;
return;
}
if (fclose (f) == EOF)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENTSTAT)))
sprintf (pending_error_text, "E cannot close %s", CVSADM_ENTSTAT);
pending_error = save_errno;
return;
}
}
static void
serve_sticky (arg)
char *arg;
{
FILE *f;
if (error_pending ()) return;
f = CVS_FOPEN (CVSADM_TAG, "w+");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_TAG)))
sprintf (pending_error_text, "E cannot open %s", CVSADM_TAG);
pending_error = save_errno;
return;
}
if (fprintf (f, "%s\n", arg) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_TAG)))
sprintf (pending_error_text, "E cannot write to %s", CVSADM_TAG);
pending_error = save_errno;
return;
}
if (fclose (f) == EOF)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_TAG)))
sprintf (pending_error_text, "E cannot close %s", CVSADM_TAG);
pending_error = save_errno;
return;
}
}
static void
receive_partial_file (size, file)
int size;
int file;
{
while (size > 0)
{
int status, nread;
char *data;
status = buf_read_data (buf_from_net, size, &data, &nread);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80);
if (pending_error_text == NULL)
pending_error = ENOMEM;
else if (status == -1)
{
sprintf (pending_error_text,
"E premature end of file from client");
pending_error = 0;
}
else
{
sprintf (pending_error_text,
"E error reading from client");
pending_error = status;
}
}
return;
}
size -= nread;
while (nread > 0)
{
int nwrote;
nwrote = write (file, data, nread);
if (nwrote < 0)
{
int save_errno = errno;
if (alloc_pending (40))
strcpy (pending_error_text, "E unable to write");
pending_error = save_errno;
while (size > 0)
{
int status, nread;
char *data;
status = buf_read_data (buf_from_net, size, &data, &nread);
if (status != 0)
return;
size -= nread;
}
return;
}
nread -= nwrote;
data += nwrote;
}
}
}
static void
receive_file (size, file, gzipped)
int size;
char *file;
int gzipped;
{
int fd;
char *arg = file;
fd = CVS_OPEN (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0)
{
int save_errno = errno;
if (alloc_pending (40 + strlen (arg)))
sprintf (pending_error_text, "E cannot open %s", arg);
pending_error = save_errno;
return;
}
if (gzipped)
{
int toread = size;
char *filebuf;
char *p;
filebuf = xmalloc (size);
p = filebuf;
while (toread > 0)
{
int status, nread;
char *data;
status = buf_read_data (buf_from_net, toread, &data, &nread);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80);
if (pending_error_text == NULL)
pending_error = ENOMEM;
else if (status == -1)
{
sprintf (pending_error_text,
"E premature end of file from client");
pending_error = 0;
}
else
{
sprintf (pending_error_text,
"E error reading from client");
pending_error = status;
}
}
return;
}
toread -= nread;
if (filebuf != NULL)
{
memcpy (p, data, nread);
p += nread;
}
}
if (filebuf == NULL)
{
pending_error = ENOMEM;
goto out;
}
if (gunzip_and_write (fd, file, (unsigned char *) filebuf, size))
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E aborting due to compression error");
}
free (filebuf);
}
else
receive_partial_file (size, fd);
if (pending_error_text)
{
char *p = xrealloc (pending_error_text,
strlen (pending_error_text) + strlen (arg) + 30);
if (p)
{
pending_error_text = p;
sprintf (p + strlen (p), ", file %s", arg);
}
}
out:
if (close (fd) < 0 && !error_pending ())
{
int save_errno = errno;
if (alloc_pending (40 + strlen (arg)))
sprintf (pending_error_text, "E cannot close %s", arg);
pending_error = save_errno;
return;
}
}
static char *kopt;
static int checkin_time_valid;
static time_t checkin_time;
static void serve_modified PROTO ((char *));
static void
serve_modified (arg)
char *arg;
{
int size, status;
char *size_text;
char *mode_text;
int gzipped = 0;
status = buf_read_line (buf_from_net, &mode_text, (int *) NULL);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80 + strlen (arg));
if (pending_error_text == NULL)
pending_error = ENOMEM;
else
{
if (status == -1)
sprintf (pending_error_text,
"E end of file reading mode for %s", arg);
else
{
sprintf (pending_error_text,
"E error reading mode for %s", arg);
pending_error = status;
}
}
}
return;
}
status = buf_read_line (buf_from_net, &size_text, (int *) NULL);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80 + strlen (arg));
if (pending_error_text == NULL)
pending_error = ENOMEM;
else
{
if (status == -1)
sprintf (pending_error_text,
"E end of file reading size for %s", arg);
else
{
sprintf (pending_error_text,
"E error reading size for %s", arg);
pending_error = status;
}
}
}
free (mode_text);
return;
}
if (size_text[0] == 'z')
{
gzipped = 1;
size = atoi (size_text + 1);
}
else
size = atoi (size_text);
free (size_text);
if (error_pending ())
{
while (size > 0)
{
int status, nread;
char *data;
status = buf_read_data (buf_from_net, size, &data, &nread);
if (status != 0)
return;
size -= nread;
}
free (mode_text);
return;
}
if (outside_dir (arg))
{
free (mode_text);
return;
}
if (size >= 0)
{
receive_file (size, arg, gzipped);
if (error_pending ())
{
free (mode_text);
return;
}
}
if (checkin_time_valid)
{
struct utimbuf t;
memset (&t, 0, sizeof (t));
t.modtime = t.actime = checkin_time;
if (utime (arg, &t) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text, "E cannot utime %s", arg);
pending_error = save_errno;
free (mode_text);
return;
}
checkin_time_valid = 0;
}
{
int status = change_mode (arg, mode_text, 0);
free (mode_text);
if (status)
{
if (alloc_pending (40 + strlen (arg)))
sprintf (pending_error_text,
"E cannot change mode for %s", arg);
pending_error = status;
return;
}
}
if (kopt != NULL)
serve_is_modified (arg);
}
static void
serve_enable_unchanged (arg)
char *arg;
{
}
struct an_entry {
struct an_entry *next;
char *entry;
};
static struct an_entry *entries;
static void serve_unchanged PROTO ((char *));
static void
serve_unchanged (arg)
char *arg;
{
struct an_entry *p;
char *name;
char *cp;
char *timefield;
if (error_pending ()) return;
if (outside_dir (arg))
return;
for (p = entries; p != NULL; p = p->next)
{
name = p->entry + 1;
cp = strchr (name, '/');
if (cp != NULL
&& strlen (arg) == cp - name
&& strncmp (arg, name, cp - name) == 0)
{
if (!(timefield = strchr (cp + 1, '/')) || *++timefield == '\0')
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Malformed Entry encountered.");
return;
}
if (*timefield == '/')
{
cp = timefield + strlen (timefield);
cp[1] = '\0';
while (cp > timefield)
{
*cp = cp[-1];
--cp;
}
}
*timefield = '=';
break;
}
}
}
static void
serve_is_modified (arg)
char *arg;
{
struct an_entry *p;
char *name;
char *cp;
char *timefield;
int found;
if (error_pending ()) return;
if (outside_dir (arg))
return;
found = 0;
for (p = entries; p != NULL; p = p->next)
{
name = p->entry + 1;
cp = strchr (name, '/');
if (cp != NULL
&& strlen (arg) == cp - name
&& strncmp (arg, name, cp - name) == 0)
{
if (!(timefield = strchr (cp + 1, '/')) || *++timefield == '\0')
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Malformed Entry encountered.");
return;
}
if (*timefield == '/')
{
cp = timefield + strlen (timefield);
cp[1] = '\0';
while (cp > timefield)
{
*cp = cp[-1];
--cp;
}
}
*timefield = 'M';
if (kopt != NULL)
{
if (alloc_pending (strlen (name) + 80))
sprintf (pending_error_text,
"E protocol error: both Kopt and Entry for %s",
arg);
free (kopt);
kopt = NULL;
return;
}
found = 1;
break;
}
}
if (!found)
{
p = (struct an_entry *) xmalloc (sizeof (struct an_entry));
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
p->entry = xmalloc (strlen (arg) + 80);
if (p->entry == NULL)
{
pending_error = ENOMEM;
free (p);
return;
}
strcpy (p->entry, "/");
strcat (p->entry, arg);
strcat (p->entry, "//D/");
if (kopt != NULL)
{
strcat (p->entry, kopt);
free (kopt);
kopt = NULL;
}
strcat (p->entry, "/");
p->next = entries;
entries = p;
}
}
static void serve_entry PROTO ((char *));
static void
serve_entry (arg)
char *arg;
{
struct an_entry *p;
char *cp;
int i = 0;
if (error_pending()) return;
cp = arg;
if (*cp == 'D') cp++;
while (i++ < 5)
{
if (!cp || *cp != '/')
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E protocol error: Malformed Entry");
return;
}
cp = strchr (cp + 1, '/');
}
p = xmalloc (sizeof (struct an_entry));
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
cp = xmalloc (strlen (arg) + 2);
if (cp == NULL)
{
free (p);
pending_error = ENOMEM;
return;
}
strcpy (cp, arg);
p->next = entries;
p->entry = cp;
entries = p;
}
static void serve_kopt PROTO ((char *));
static void
serve_kopt (arg)
char *arg;
{
if (error_pending ())
return;
if (kopt != NULL)
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E protocol error: duplicate Kopt request: %s", arg);
return;
}
if (strlen (arg) > 10)
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E protocol error: invalid Kopt request: %s", arg);
return;
}
kopt = xmalloc (strlen (arg) + 1);
if (kopt == NULL)
{
pending_error = ENOMEM;
return;
}
strcpy (kopt, arg);
}
static void serve_checkin_time PROTO ((char *));
static void
serve_checkin_time (arg)
char *arg;
{
if (error_pending ())
return;
if (checkin_time_valid)
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E protocol error: duplicate Checkin-time request: %s",
arg);
return;
}
checkin_time = get_date (arg, NULL);
if (checkin_time == (time_t)-1)
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text, "E cannot parse date %s", arg);
return;
}
checkin_time_valid = 1;
}
static void
server_write_entries ()
{
FILE *f;
struct an_entry *p;
struct an_entry *q;
if (entries == NULL)
return;
f = NULL;
if (!error_pending ())
{
f = CVS_FOPEN (CVSADM_ENT, "a");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENT)))
sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT);
pending_error = save_errno;
}
}
for (p = entries; p != NULL;)
{
if (!error_pending ())
{
if (fprintf (f, "%s\n", p->entry) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen(CVSADM_ENT)))
sprintf (pending_error_text,
"E cannot write to %s", CVSADM_ENT);
pending_error = save_errno;
}
}
free (p->entry);
q = p->next;
free (p);
p = q;
}
entries = NULL;
if (f != NULL && fclose (f) == EOF && !error_pending ())
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENT)))
sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
pending_error = save_errno;
}
}
struct notify_note {
char *dir;
char *filename;
char *type;
char *val;
char *watches;
struct notify_note *next;
};
static struct notify_note *notify_list;
static struct notify_note *last_node;
static void serve_notify PROTO ((char *));
static void
serve_notify (arg)
char *arg;
{
struct notify_note *new = NULL;
char *data = NULL;
int status;
if (error_pending ()) return;
if (outside_dir (arg))
return;
if (dir_name == NULL)
goto error;
new = (struct notify_note *) xmalloc (sizeof (struct notify_note));
if (new == NULL)
{
pending_error = ENOMEM;
return;
}
new->dir = xmalloc (strlen (dir_name) + 1);
new->filename = xmalloc (strlen (arg) + 1);
if (new->dir == NULL || new->filename == NULL)
{
pending_error = ENOMEM;
if (new->dir != NULL)
free (new->dir);
free (new);
return;
}
strcpy (new->dir, dir_name);
strcpy (new->filename, arg);
status = buf_read_line (buf_from_net, &data, (int *) NULL);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80 + strlen (arg));
if (pending_error_text == NULL)
pending_error = ENOMEM;
else
{
if (status == -1)
sprintf (pending_error_text,
"E end of file reading notification for %s", arg);
else
{
sprintf (pending_error_text,
"E error reading notification for %s", arg);
pending_error = status;
}
}
}
free (new->filename);
free (new->dir);
free (new);
}
else
{
char *cp;
if (!data[0])
goto error;
if (strchr (data, '+'))
goto error;
new->type = data;
if (data[1] != '\t')
goto error;
data[1] = '\0';
cp = data + 2;
new->val = cp;
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*cp++ = '+';
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*cp++ = '+';
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*cp++ = '\0';
new->watches = cp;
cp = strchr (cp, '\t');
if (cp != NULL)
{
*cp = '\0';
}
new->next = NULL;
if (last_node == NULL)
{
notify_list = new;
}
else
last_node->next = new;
last_node = new;
}
return;
error:
pending_error = 0;
if (alloc_pending (80))
strcpy (pending_error_text,
"E Protocol error; misformed Notify request");
if (data != NULL)
free (data);
if (new != NULL)
{
free (new->filename);
free (new->dir);
free (new);
}
return;
}
static int
server_notify ()
{
struct notify_note *p;
char *repos;
while (notify_list != NULL)
{
if ( CVS_CHDIR (notify_list->dir) < 0)
{
error (0, errno, "cannot change to %s", notify_list->dir);
return -1;
}
repos = Name_Repository (NULL, NULL);
lock_dir_for_write (repos);
fileattr_startdir (repos);
notify_do (*notify_list->type, notify_list->filename, getcaller(),
notify_list->val, notify_list->watches, repos);
buf_output0 (buf_to_net, "Notified ");
{
char *dir = notify_list->dir + strlen (server_temp_dir) + 1;
if (dir[0] == '\0')
buf_append_char (buf_to_net, '.');
else
buf_output0 (buf_to_net, dir);
buf_append_char (buf_to_net, '/');
buf_append_char (buf_to_net, '\n');
}
buf_output0 (buf_to_net, repos);
buf_append_char (buf_to_net, '/');
buf_output0 (buf_to_net, notify_list->filename);
buf_append_char (buf_to_net, '\n');
free (repos);
p = notify_list->next;
free (notify_list->filename);
free (notify_list->dir);
free (notify_list->type);
free (notify_list);
notify_list = p;
fileattr_write ();
fileattr_free ();
Lock_Cleanup ();
}
last_node = NULL;
return 0;
}
static int argument_count;
static char **argument_vector;
static int argument_vector_size;
static void
serve_argument (arg)
char *arg;
{
char *p;
if (error_pending()) return;
if (argument_count >= 10000)
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Protocol error: too many arguments");
return;
}
if (argument_vector_size <= argument_count)
{
argument_vector_size *= 2;
argument_vector =
(char **) xrealloc ((char *)argument_vector,
argument_vector_size * sizeof (char *));
if (argument_vector == NULL)
{
pending_error = ENOMEM;
return;
}
}
p = xmalloc (strlen (arg) + 1);
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
strcpy (p, arg);
argument_vector[argument_count++] = p;
}
static void
serve_argumentx (arg)
char *arg;
{
char *p;
if (error_pending()) return;
if (argument_count <= 1)
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Protocol error: called argumentx without prior call to argument");
return;
}
p = argument_vector[argument_count - 1];
p = xrealloc (p, strlen (p) + 1 + strlen (arg) + 1);
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
strcat (p, "\n");
strcat (p, arg);
argument_vector[argument_count - 1] = p;
}
static void
serve_global_option (arg)
char *arg;
{
if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0')
{
error_return:
if (alloc_pending (strlen (arg) + 80))
sprintf (pending_error_text,
"E Protocol error: bad global option %s",
arg);
return;
}
switch (arg[1])
{
case 'l':
error(0, 0, "WARNING: global `-l' option ignored.");
break;
case 'n':
noexec = 1;
logoff = 1;
break;
case 'q':
quiet = 1;
break;
case 'r':
cvswrite = 0;
break;
case 'Q':
really_quiet = 1;
break;
case 't':
trace = 1;
break;
default:
goto error_return;
}
}
static void
serve_set (arg)
char *arg;
{
variable_set (arg);
}
#ifdef ENCRYPTION
#ifdef HAVE_KERBEROS
static void
serve_kerberos_encrypt (arg)
char *arg;
{
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
#ifdef HAVE_GSSAPI
static void
serve_gssapi_encrypt (arg)
char *arg;
{
if (cvs_gssapi_wrapping)
{
buf_flush (buf_to_net, 1);
cvs_gssapi_encrypt = 1;
return;
}
cvs_gssapi_encrypt = 1;
buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0,
gcontext,
buf_to_net->memory_error);
buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1,
gcontext,
buf_from_net->memory_error);
cvs_gssapi_wrapping = 1;
}
#endif
#endif
#ifdef HAVE_GSSAPI
static void
serve_gssapi_authenticate (arg)
char *arg;
{
if (cvs_gssapi_wrapping)
{
return;
}
buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0,
gcontext,
buf_to_net->memory_error);
buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1,
gcontext,
buf_from_net->memory_error);
cvs_gssapi_wrapping = 1;
}
#endif
#ifdef SERVER_FLOWCONTROL
# ifndef SERVER_HI_WATER
# define SERVER_HI_WATER (2 * 1024 * 1024)
# endif
# ifndef SERVER_LO_WATER
# define SERVER_LO_WATER (1 * 1024 * 1024)
# endif
#endif
static void serve_questionable PROTO((char *));
static void
serve_questionable (arg)
char *arg;
{
static int initted;
if (!initted)
{
ign_setup ();
initted = 1;
}
if (dir_name == NULL)
{
buf_output0 (buf_to_net, "E Protocol error: 'Directory' missing");
return;
}
if (outside_dir (arg))
return;
if (!ign_name (arg))
{
char *update_dir;
buf_output (buf_to_net, "M ? ", 4);
update_dir = dir_name + strlen (server_temp_dir) + 1;
if (!(update_dir[0] == '.' && update_dir[1] == '\0'))
{
buf_output0 (buf_to_net, update_dir);
buf_output (buf_to_net, "/", 1);
}
buf_output0 (buf_to_net, arg);
buf_output (buf_to_net, "\n", 1);
}
}
static struct buffer *protocol;
static struct buffer *saved_output;
static struct buffer *saved_outerr;
static void
protocol_memory_error (buf)
struct buffer *buf;
{
error (1, ENOMEM, "Virtual memory exhausted");
}
static pid_t command_pid;
static void
outbuf_memory_error (buf)
struct buffer *buf;
{
static const char msg[] = "E Fatal server error\n\
error ENOMEM Virtual memory exhausted.\n";
if (command_pid > 0)
kill (command_pid, SIGTERM);
write (STDOUT_FILENO, msg, sizeof (msg) - 1);
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR, "virtual memory exhausted");
#endif
error_exit ();
}
static void
input_memory_error (buf)
struct buffer *buf;
{
outbuf_memory_error (buf);
}
static int
check_command_legal_p (cmd_name)
char *cmd_name;
{
#ifdef AUTH_SERVER_SUPPORT
if (CVS_Username == NULL)
return 1;
if (lookup_command_attribute (cmd_name) & CVS_CMD_MODIFIES_REPOSITORY)
{
char *linebuf = NULL;
int num_red = 0;
size_t linebuf_len = 0;
char *fname;
size_t flen;
FILE *fp;
int found_it = 0;
flen = strlen (current_parsed_root->directory)
+ strlen (CVSROOTADM)
+ strlen (CVSROOTADM_READERS)
+ 3;
fname = xmalloc (flen);
(void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSROOTADM_READERS);
fp = fopen (fname, "r");
if (fp == NULL)
{
if (!existence_error (errno))
{
error (0, errno, "cannot open %s", fname);
free (fname);
return 0;
}
}
else
{
while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0)
{
if (num_red > 0 && linebuf[num_red - 1] == '\n')
linebuf[num_red - 1] = '\0';
if (strcmp (linebuf, CVS_Username) == 0)
goto handle_illegal;
}
if (num_red < 0 && !feof (fp))
error (0, errno, "cannot read %s", fname);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", fname);
}
free (fname);
flen = strlen (current_parsed_root->directory)
+ strlen (CVSROOTADM)
+ strlen (CVSROOTADM_WRITERS)
+ 3;
fname = xmalloc (flen);
(void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSROOTADM_WRITERS);
fp = fopen (fname, "r");
if (fp == NULL)
{
if (linebuf)
free (linebuf);
if (existence_error (errno))
{
free (fname);
return 1;
}
else
{
error (0, errno, "cannot read %s", fname);
free (fname);
return 0;
}
}
found_it = 0;
while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0)
{
if (num_red > 0 && linebuf[num_red - 1] == '\n')
linebuf[num_red - 1] = '\0';
if (strcmp (linebuf, CVS_Username) == 0)
{
found_it = 1;
break;
}
}
if (num_red < 0 && !feof (fp))
error (0, errno, "cannot read %s", fname);
if (found_it)
{
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", fname);
if (linebuf)
free (linebuf);
free (fname);
return 1;
}
else
{
handle_illegal:
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", fname);
if (linebuf)
free (linebuf);
free (fname);
return 0;
}
}
#endif
return 1;
}
static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain;
#ifdef SUNOS_KLUDGE
static int max_command_fd;
#endif
#ifdef SERVER_FLOWCONTROL
static int flowcontrol_pipe[2];
#endif
int
set_nonblock_fd (fd)
int fd;
{
int flags;
flags = fcntl (fd, F_GETFL, 0);
if (flags < 0)
return errno;
if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0)
return errno;
return 0;
}
static void
do_cvs_command (cmd_name, command)
char *cmd_name;
int (*command) PROTO((int argc, char **argv));
{
int stdout_pipe[2];
int stderr_pipe[2];
int protocol_pipe[2];
int dev_null_fd = -1;
int errs;
command_pid = -1;
stdout_pipe[0] = -1;
stdout_pipe[1] = -1;
stderr_pipe[0] = -1;
stderr_pipe[1] = -1;
protocol_pipe[0] = -1;
protocol_pipe[1] = -1;
server_write_entries ();
if (print_pending_error ())
goto free_args_and_return;
if (!check_command_legal_p (cmd_name))
{
buf_output0 (buf_to_net, "E ");
buf_output0 (buf_to_net, program_name);
buf_output0 (buf_to_net, " [server aborted]: \"");
buf_output0 (buf_to_net, cmd_name);
buf_output0 (buf_to_net, "\" requires write access to the repository\n\
error \n");
goto free_args_and_return;
}
cvs_cmd_name = cmd_name;
(void) server_notify ();
if (pipe (stdout_pipe) < 0)
{
buf_output0 (buf_to_net, "E pipe failed\n");
print_error (errno);
goto error_exit;
}
if (pipe (stderr_pipe) < 0)
{
buf_output0 (buf_to_net, "E pipe failed\n");
print_error (errno);
goto error_exit;
}
if (pipe (protocol_pipe) < 0)
{
buf_output0 (buf_to_net, "E pipe failed\n");
print_error (errno);
goto error_exit;
}
#ifdef SERVER_FLOWCONTROL
if (pipe (flowcontrol_pipe) < 0)
{
buf_output0 (buf_to_net, "E pipe failed\n");
print_error (errno);
goto error_exit;
}
set_nonblock_fd (flowcontrol_pipe[0]);
set_nonblock_fd (flowcontrol_pipe[1]);
#endif
dev_null_fd = CVS_OPEN (DEVNULL, O_RDONLY);
if (dev_null_fd < 0)
{
buf_output0 (buf_to_net, "E open /dev/null failed\n");
print_error (errno);
goto error_exit;
}
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');
}
buf_flush (buf_to_net, 1);
command_pid = fork ();
if (command_pid < 0)
{
buf_output0 (buf_to_net, "E fork failed\n");
print_error (errno);
goto error_exit;
}
if (command_pid == 0)
{
int exitstatus;
error_use_protocol = 0;
protocol = fd_buffer_initialize (protocol_pipe[1], 0,
protocol_memory_error);
if (buf_to_net != NULL)
{
buf_free (buf_to_net);
buf_to_net = NULL;
}
if (buf_from_net != NULL)
{
buf_free (buf_from_net);
buf_from_net = NULL;
}
saved_output->memory_error = protocol_memory_error;
saved_outerr->memory_error = protocol_memory_error;
if (dup2 (dev_null_fd, STDIN_FILENO) < 0)
error (1, errno, "can't set up pipes");
if (dup2 (stdout_pipe[1], STDOUT_FILENO) < 0)
error (1, errno, "can't set up pipes");
if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0)
error (1, errno, "can't set up pipes");
close (dev_null_fd);
close (stdout_pipe[0]);
close (stdout_pipe[1]);
close (stderr_pipe[0]);
close (stderr_pipe[1]);
close (protocol_pipe[0]);
close_on_exec (protocol_pipe[1]);
#ifdef SERVER_FLOWCONTROL
close_on_exec (flowcontrol_pipe[0]);
close (flowcontrol_pipe[1]);
#endif
if (getenv ("CVS_SERVER_SLEEP"))
{
int secs = atoi (getenv ("CVS_SERVER_SLEEP"));
sleep (secs);
}
exitstatus = (*command) (argument_count, argument_vector);
if (! buf_empty_p (saved_output))
{
buf_output0 (protocol, supported_response ("MT") ? "MT text " : "M ");
buf_append_buffer (protocol, saved_output);
buf_output (protocol, "\n", 1);
buf_send_counted (protocol);
}
buf_free (protocol);
fclose (stderr);
fclose (stdout);
close (protocol_pipe[1]);
#ifdef SERVER_FLOWCONTROL
{
char junk;
ssize_t status;
while ((status = read (flowcontrol_pipe[0], &junk, 1)) > 0
|| (status == -1 && errno == EAGAIN));
}
#endif
exit (exitstatus);
}
{
struct buffer *stdoutbuf;
struct buffer *stderrbuf;
struct buffer *protocol_inbuf;
int num_to_check;
int count_needed = 1;
#ifdef SERVER_FLOWCONTROL
int have_flowcontrolled = 0;
#endif
FD_ZERO (&command_fds_to_drain.fds);
num_to_check = stdout_pipe[0];
FD_SET (stdout_pipe[0], &command_fds_to_drain.fds);
if (stderr_pipe[0] > num_to_check)
num_to_check = stderr_pipe[0];
FD_SET (stderr_pipe[0], &command_fds_to_drain.fds);
if (protocol_pipe[0] > num_to_check)
num_to_check = protocol_pipe[0];
FD_SET (protocol_pipe[0], &command_fds_to_drain.fds);
if (STDOUT_FILENO > num_to_check)
num_to_check = STDOUT_FILENO;
#ifdef SUNOS_KLUDGE
max_command_fd = num_to_check;
#endif
++num_to_check;
if (num_to_check > FD_SETSIZE)
{
buf_output0 (buf_to_net,
"E internal error: FD_SETSIZE not big enough.\n\
error \n");
goto error_exit;
}
stdoutbuf = fd_buffer_initialize (stdout_pipe[0], 1,
input_memory_error);
stderrbuf = fd_buffer_initialize (stderr_pipe[0], 1,
input_memory_error);
protocol_inbuf = fd_buffer_initialize (protocol_pipe[0], 1,
input_memory_error);
set_nonblock (buf_to_net);
set_nonblock (stdoutbuf);
set_nonblock (stderrbuf);
set_nonblock (protocol_inbuf);
if (close (stdout_pipe[1]) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
stdout_pipe[1] = -1;
if (close (stderr_pipe[1]) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
stderr_pipe[1] = -1;
if (close (protocol_pipe[1]) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
protocol_pipe[1] = -1;
#ifdef SERVER_FLOWCONTROL
if (close (flowcontrol_pipe[0]) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
flowcontrol_pipe[0] = -1;
#endif
if (close (dev_null_fd) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
dev_null_fd = -1;
while (stdout_pipe[0] >= 0
|| stderr_pipe[0] >= 0
|| protocol_pipe[0] >= 0
|| count_needed <= 0)
{
fd_set readfds;
fd_set writefds;
int numfds;
struct timeval *timeout_ptr;
struct timeval timeout;
#ifdef SERVER_FLOWCONTROL
int bufmemsize;
bufmemsize = buf_count_mem (buf_to_net);
if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER))
{
if (write(flowcontrol_pipe[1], "S", 1) == 1)
have_flowcontrolled = 1;
}
else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER))
{
if (write(flowcontrol_pipe[1], "G", 1) == 1)
have_flowcontrolled = 0;
}
#endif
FD_ZERO (&readfds);
FD_ZERO (&writefds);
if (count_needed <= 0)
{
timeout.tv_sec = 0;
timeout.tv_usec = 0;
timeout_ptr = &timeout;
}
else
{
timeout_ptr = NULL;
}
if (! buf_empty_p (buf_to_net))
FD_SET (STDOUT_FILENO, &writefds);
if (stdout_pipe[0] >= 0)
{
FD_SET (stdout_pipe[0], &readfds);
}
if (stderr_pipe[0] >= 0)
{
FD_SET (stderr_pipe[0], &readfds);
}
if (protocol_pipe[0] >= 0)
{
FD_SET (protocol_pipe[0], &readfds);
}
do {
numfds = select (num_to_check, &readfds, &writefds,
(fd_set *)0, timeout_ptr);
if (numfds < 0
&& errno != EINTR)
{
buf_output0 (buf_to_net, "E select failed\n");
print_error (errno);
goto error_exit;
}
} while (numfds < 0);
if (numfds == 0)
{
FD_ZERO (&readfds);
FD_ZERO (&writefds);
}
if (FD_ISSET (STDOUT_FILENO, &writefds))
{
buf_send_output (buf_to_net);
}
if (protocol_pipe[0] >= 0
&& (FD_ISSET (protocol_pipe[0], &readfds)))
{
int status;
int count_read;
status = buf_input_data (protocol_inbuf, &count_read);
if (status == -1)
{
close (protocol_pipe[0]);
protocol_pipe[0] = -1;
}
else if (status > 0)
{
buf_output0 (buf_to_net, "E buf_input_data failed\n");
print_error (status);
goto error_exit;
}
count_needed -= count_read;
}
while (count_needed <= 0)
{
int special = 0;
count_needed = buf_copy_counted (buf_to_net,
protocol_inbuf,
&special);
buf_send_output (buf_to_net);
if (special == -1)
{
cvs_flushout();
break;
}
if (special == -2)
{
if (supported_response ("F"))
{
buf_append_char (buf_to_net, 'F');
buf_append_char (buf_to_net, '\n');
}
cvs_flusherr ();
break;
}
}
if (stdout_pipe[0] >= 0
&& (FD_ISSET (stdout_pipe[0], &readfds)))
{
int status;
status = buf_input_data (stdoutbuf, (int *) NULL);
buf_copy_lines (buf_to_net, stdoutbuf, 'M');
if (status == -1)
{
close (stdout_pipe[0]);
stdout_pipe[0] = -1;
}
else if (status > 0)
{
buf_output0 (buf_to_net, "E buf_input_data failed\n");
print_error (status);
goto error_exit;
}
buf_send_output (buf_to_net);
}
if (stderr_pipe[0] >= 0
&& (FD_ISSET (stderr_pipe[0], &readfds)))
{
int status;
status = buf_input_data (stderrbuf, (int *) NULL);
buf_copy_lines (buf_to_net, stderrbuf, 'E');
if (status == -1)
{
close (stderr_pipe[0]);
stderr_pipe[0] = -1;
}
else if (status > 0)
{
buf_output0 (buf_to_net, "E buf_input_data failed\n");
print_error (status);
goto error_exit;
}
buf_send_output (buf_to_net);
}
}
if (! buf_empty_p (stdoutbuf))
{
buf_append_char (stdoutbuf, '\n');
buf_copy_lines (buf_to_net, stdoutbuf, 'M');
}
if (! buf_empty_p (stderrbuf))
{
buf_append_char (stderrbuf, '\n');
buf_copy_lines (buf_to_net, stderrbuf, 'E');
}
if (! buf_empty_p (protocol_inbuf))
buf_output0 (buf_to_net,
"E Protocol error: uncounted data discarded\n");
#ifdef SERVER_FLOWCONTROL
close (flowcontrol_pipe[1]);
flowcontrol_pipe[1] = -1;
#endif
errs = 0;
while (command_pid > 0)
{
int status;
pid_t waited_pid;
waited_pid = waitpid (command_pid, &status, 0);
if (waited_pid < 0)
{
continue;
}
if (WIFEXITED (status))
errs += WEXITSTATUS (status);
else
{
int sig = WTERMSIG (status);
char buf[50];
buf_output0 (buf_to_net, "E Terminated with fatal signal ");
sprintf (buf, "%d\n", sig);
buf_output0 (buf_to_net, buf);
if (WCOREDUMP (status))
{
buf_output0 (buf_to_net, "E Core dumped; preserving ");
buf_output0 (buf_to_net, orig_server_temp_dir);
buf_output0 (buf_to_net, " on server.\n\
E CVS locks may need cleaning up.\n");
dont_delete_temp = 1;
}
++errs;
}
if (waited_pid == command_pid)
command_pid = -1;
}
set_block (buf_to_net);
buf_flush (buf_to_net, 1);
buf_shutdown (protocol_inbuf);
buf_free (protocol_inbuf);
protocol_inbuf = NULL;
buf_shutdown (stderrbuf);
buf_free (stderrbuf);
stderrbuf = NULL;
buf_shutdown (stdoutbuf);
buf_free (stdoutbuf);
stdoutbuf = NULL;
}
if (errs)
buf_output0 (buf_to_net, "error \n");
else
buf_output0 (buf_to_net, "ok\n");
goto free_args_and_return;
error_exit:
if (command_pid > 0)
kill (command_pid, SIGTERM);
while (command_pid > 0)
{
pid_t waited_pid;
waited_pid = waitpid (command_pid, (int *) 0, 0);
if (waited_pid < 0 && errno == EINTR)
continue;
if (waited_pid == command_pid)
command_pid = -1;
}
close (dev_null_fd);
close (protocol_pipe[0]);
close (protocol_pipe[1]);
close (stderr_pipe[0]);
close (stderr_pipe[1]);
close (stdout_pipe[0]);
close (stdout_pipe[1]);
#ifdef SERVER_FLOWCONTROL
close (flowcontrol_pipe[0]);
close (flowcontrol_pipe[1]);
#endif
free_args_and_return:
{
char **cp;
for (cp = argument_vector + 1;
cp < argument_vector + argument_count;
++cp)
free (*cp);
argument_count = 1;
}
set_block (buf_to_net);
buf_flush (buf_to_net, 1);
return;
}
#ifdef SERVER_FLOWCONTROL
void
server_pause_check()
{
int paused = 0;
char buf[1];
while (read (flowcontrol_pipe[0], buf, 1) == 1)
{
if (*buf == 'S')
paused = 1;
else if (*buf == 'G')
paused = 0;
else
return;
}
while (paused) {
int numfds, numtocheck;
fd_set fds;
FD_ZERO (&fds);
FD_SET (flowcontrol_pipe[0], &fds);
numtocheck = flowcontrol_pipe[0] + 1;
do {
numfds = select (numtocheck, &fds, (fd_set *)0,
(fd_set *)0, (struct timeval *)NULL);
if (numfds < 0
&& errno != EINTR)
{
buf_output0 (buf_to_net, "E select failed\n");
print_error (errno);
return;
}
} while (numfds < 0);
if (FD_ISSET (flowcontrol_pipe[0], &fds))
{
int got;
while ((got = read (flowcontrol_pipe[0], buf, 1)) == 1)
{
if (*buf == 'S')
paused = 1;
else if (*buf == 'G')
paused = 0;
else
return;
}
if (got == 0)
error (1, 0, "flow control EOF");
if (got < 0 && ! blocking_error (errno))
{
error (1, errno, "flow control read failed");
}
}
}
}
#endif
char *server_dir = NULL;
static void output_dir PROTO((const char *, const char *));
static void
output_dir (update_dir, repository)
const char *update_dir;
const char *repository;
{
if (server_dir != NULL)
{
buf_output0 (protocol, server_dir);
buf_output0 (protocol, "/");
}
if (update_dir[0] == '\0')
buf_output0 (protocol, ".");
else
buf_output0 (protocol, update_dir);
buf_output0 (protocol, "/\n");
buf_output0 (protocol, repository);
buf_output0 (protocol, "/");
}
static char *entries_line;
static char *scratched_file;
static int kill_scratched_file;
void
server_register (name, version, timestamp, options, tag, date, conflict)
const char *name;
const char *version;
const char *timestamp;
const char *options;
const char *tag;
const char *date;
const char *conflict;
{
int len;
if (options == NULL)
options = "";
if (trace)
{
(void) fprintf (stderr,
"%s-> server_register(%s, %s, %s, %s, %s, %s, %s)\n",
CLIENT_SERVER_STR,
name, version, timestamp ? timestamp : "", options,
tag ? tag : "", date ? date : "",
conflict ? conflict : "");
}
if (entries_line != NULL)
{
free (entries_line);
}
if (scratched_file != NULL)
{
free (scratched_file);
scratched_file = NULL;
}
len = (strlen (name) + strlen (version) + strlen (options) + 80);
if (tag)
len += strlen (tag);
if (date)
len += strlen (date);
entries_line = xmalloc (len);
sprintf (entries_line, "/%s/%s/", name, version);
if (conflict != NULL)
{
strcat (entries_line, "+=");
}
strcat (entries_line, "/");
strcat (entries_line, options);
strcat (entries_line, "/");
if (tag != NULL)
{
strcat (entries_line, "T");
strcat (entries_line, tag);
}
else if (date != NULL)
{
strcat (entries_line, "D");
strcat (entries_line, date);
}
}
void
server_scratch (fname)
const char *fname;
{
if (entries_line != NULL)
{
free (entries_line);
entries_line = NULL;
}
if (scratched_file != NULL)
{
buf_output0 (protocol,
"E CVS server internal error: duplicate Scratch_Entry\n");
buf_send_counted (protocol);
return;
}
scratched_file = xstrdup (fname);
kill_scratched_file = 1;
}
void
server_scratch_entry_only ()
{
kill_scratched_file = 0;
}
static void
new_entries_line ()
{
if (entries_line)
{
buf_output0 (protocol, entries_line);
buf_output (protocol, "\n", 1);
}
else
buf_output0 (protocol,
"CVS server internal error: Register missing\n");
free (entries_line);
entries_line = NULL;
}
static void
serve_ci (arg)
char *arg;
{
do_cvs_command ("commit", commit);
}
static void
checked_in_response (file, update_dir, repository)
char *file;
char *update_dir;
char *repository;
{
if (supported_response ("Mode"))
{
struct stat sb;
char *mode_string;
if ( CVS_STAT (file, &sb) < 0)
{
if (!existence_error (errno))
error (0, errno, "cannot stat %s", file);
}
else
{
buf_output0 (protocol, "Mode ");
mode_string = mode_to_string (sb.st_mode);
buf_output0 (protocol, mode_string);
buf_output0 (protocol, "\n");
free (mode_string);
}
}
buf_output0 (protocol, "Checked-in ");
output_dir (update_dir, repository);
buf_output0 (protocol, file);
buf_output (protocol, "\n", 1);
new_entries_line ();
}
void
server_checked_in (file, update_dir, repository)
const char *file;
const char *update_dir;
const char *repository;
{
if (noexec)
return;
if (scratched_file != NULL && entries_line == NULL)
{
buf_output0 (protocol, "Remove-entry ");
output_dir (update_dir, repository);
buf_output0 (protocol, file);
buf_output (protocol, "\n", 1);
free (scratched_file);
scratched_file = NULL;
}
else
{
checked_in_response (file, update_dir, repository);
}
buf_send_counted (protocol);
}
void
server_update_entries (file, update_dir, repository, updated)
const char *file;
const char *update_dir;
const char *repository;
enum server_updated_arg4 updated;
{
if (noexec)
return;
if (updated == SERVER_UPDATED)
checked_in_response (file, update_dir, repository);
else
{
if (!supported_response ("New-entry"))
return;
buf_output0 (protocol, "New-entry ");
output_dir (update_dir, repository);
buf_output0 (protocol, file);
buf_output (protocol, "\n", 1);
new_entries_line ();
}
buf_send_counted (protocol);
}
static void
serve_update (arg)
char *arg;
{
do_cvs_command ("update", update);
}
static void
serve_diff (arg)
char *arg;
{
do_cvs_command ("diff", diff);
}
static void
serve_log (arg)
char *arg;
{
do_cvs_command ("log", cvslog);
}
static void
serve_rlog (arg)
char *arg;
{
do_cvs_command ("rlog", cvslog);
}
static void
serve_add (arg)
char *arg;
{
do_cvs_command ("add", add);
}
static void
serve_remove (arg)
char *arg;
{
do_cvs_command ("remove", cvsremove);
}
static void
serve_status (arg)
char *arg;
{
do_cvs_command ("status", cvsstatus);
}
static void
serve_rdiff (arg)
char *arg;
{
do_cvs_command ("rdiff", patch);
}
static void
serve_tag (arg)
char *arg;
{
do_cvs_command ("tag", cvstag);
}
static void
serve_rtag (arg)
char *arg;
{
do_cvs_command ("rtag", cvstag);
}
static void
serve_import (arg)
char *arg;
{
do_cvs_command ("import", import);
}
static void
serve_admin (arg)
char *arg;
{
do_cvs_command ("admin", admin);
}
static void
serve_history (arg)
char *arg;
{
do_cvs_command ("history", history);
}
static void
serve_release (arg)
char *arg;
{
do_cvs_command ("release", release);
}
static void serve_watch_on PROTO ((char *));
static void
serve_watch_on (arg)
char *arg;
{
do_cvs_command ("watch", watch_on);
}
static void serve_watch_off PROTO ((char *));
static void
serve_watch_off (arg)
char *arg;
{
do_cvs_command ("watch", watch_off);
}
static void serve_watch_add PROTO ((char *));
static void
serve_watch_add (arg)
char *arg;
{
do_cvs_command ("watch", watch_add);
}
static void serve_watch_remove PROTO ((char *));
static void
serve_watch_remove (arg)
char *arg;
{
do_cvs_command ("watch", watch_remove);
}
static void serve_watchers PROTO ((char *));
static void
serve_watchers (arg)
char *arg;
{
do_cvs_command ("watchers", watchers);
}
static void serve_editors PROTO ((char *));
static void
serve_editors (arg)
char *arg;
{
do_cvs_command ("editors", editors);
}
static void serve_noop PROTO ((char *));
static void
serve_noop (arg)
char *arg;
{
server_write_entries ();
if (!print_pending_error ())
{
(void) server_notify ();
buf_output0 (buf_to_net, "ok\n");
}
buf_flush (buf_to_net, 1);
}
static void serve_version PROTO ((char *));
static void
serve_version (arg)
char *arg;
{
do_cvs_command ("version", version);
}
static void serve_init PROTO ((char *));
static void
serve_init (arg)
char *arg;
{
cvsroot_t *saved_parsed_root;
if (!isabsolute (arg))
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E init %s must be an absolute pathname", arg);
}
#ifdef AUTH_SERVER_SUPPORT
else if (Pserver_Repos != NULL)
{
if (strcmp (Pserver_Repos, arg) != 0)
{
if (alloc_pending (80 + strlen (Pserver_Repos) + strlen (arg)))
sprintf (pending_error_text, "\
E Protocol error: init says \"%s\" but pserver says \"%s\"",
arg, Pserver_Repos);
}
}
#endif
if (print_pending_error ())
return;
saved_parsed_root = current_parsed_root;
current_parsed_root = local_cvsroot (arg);
do_cvs_command ("init", init);
free_cvsroot_t (current_parsed_root);
current_parsed_root = saved_parsed_root;
}
static void serve_annotate PROTO ((char *));
static void
serve_annotate (arg)
char *arg;
{
do_cvs_command ("annotate", annotate);
}
static void serve_rannotate PROTO ((char *));
static void
serve_rannotate (arg)
char *arg;
{
do_cvs_command ("rannotate", annotate);
}
static void
serve_co (arg)
char *arg;
{
char *tempdir;
int status;
if (print_pending_error ())
return;
if (!isdir (CVSADM))
{
tempdir = xmalloc (strlen (server_temp_dir) + 80);
if (tempdir == NULL)
{
buf_output0 (buf_to_net, "E Out of memory\n");
return;
}
strcpy (tempdir, server_temp_dir);
strcat (tempdir, "/checkout-dir");
status = mkdir_p (tempdir);
if (status != 0 && status != EEXIST)
{
buf_output0 (buf_to_net, "E Cannot create ");
buf_output0 (buf_to_net, tempdir);
buf_append_char (buf_to_net, '\n');
print_error (errno);
free (tempdir);
return;
}
if ( CVS_CHDIR (tempdir) < 0)
{
buf_output0 (buf_to_net, "E Cannot change to directory ");
buf_output0 (buf_to_net, tempdir);
buf_append_char (buf_to_net, '\n');
print_error (errno);
free (tempdir);
return;
}
free (tempdir);
}
do_cvs_command ((strcmp (cvs_cmd_name, "export") == 0) ?
"export" : "checkout",
checkout);
}
static void
serve_export (arg)
char *arg;
{
cvs_cmd_name = "export";
serve_co (arg);
}
void
server_copy_file (file, update_dir, repository, newfile)
const char *file;
const char *update_dir;
const char *repository;
const char *newfile;
{
if (!supported_response ("Copy-file"))
return;
buf_output0 (protocol, "Copy-file ");
output_dir (update_dir, repository);
buf_output0 (protocol, file);
buf_output0 (protocol, "\n");
buf_output0 (protocol, newfile);
buf_output0 (protocol, "\n");
}
void
server_modtime (finfo, vers_ts)
struct file_info *finfo;
Vers_TS *vers_ts;
{
char date[MAXDATELEN];
char outdate[MAXDATELEN];
assert (vers_ts->vn_rcs != NULL);
if (!supported_response ("Mod-time"))
return;
if (RCS_getrevtime (finfo->rcs, vers_ts->vn_rcs, date, 0) == (time_t) -1)
return;
date_to_internet (outdate, date);
buf_output0 (protocol, "Mod-time ");
buf_output0 (protocol, outdate);
buf_output0 (protocol, "\n");
}
#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
void
server_updated (
struct file_info *finfo,
Vers_TS *vers,
enum server_updated_arg4 updated,
mode_t mode,
unsigned char *checksum,
struct buffer *filebuf)
#else
void
server_updated (finfo, vers, updated, mode, checksum, filebuf)
struct file_info *finfo;
Vers_TS *vers;
enum server_updated_arg4 updated;
mode_t mode;
unsigned char *checksum;
struct buffer *filebuf;
#endif
{
if (noexec)
{
if (scratched_file)
{
free (scratched_file);
scratched_file = NULL;
}
buf_send_counted (protocol);
return;
}
if (entries_line != NULL && scratched_file == NULL)
{
FILE *f;
struct buffer_data *list, *last;
unsigned long size;
char size_text[80];
unsigned char *file;
size_t file_allocated;
size_t file_used;
if (filebuf != NULL)
{
size = buf_length (filebuf);
if (mode == (mode_t) -1)
error (1, 0, "\
CVS server internal error: no mode in server_updated");
}
else
{
struct stat sb;
if ( CVS_STAT (finfo->file, &sb) < 0)
{
if (existence_error (errno))
{
free (entries_line);
entries_line = NULL;
goto done;
}
error (1, errno, "reading %s", finfo->fullname);
}
size = sb.st_size;
if (mode == (mode_t) -1)
{
mode = sb.st_mode;
}
}
if (checksum != NULL)
{
static int checksum_supported = -1;
if (checksum_supported == -1)
{
checksum_supported = supported_response ("Checksum");
}
if (checksum_supported)
{
int i;
char buf[3];
buf_output0 (protocol, "Checksum ");
for (i = 0; i < 16; i++)
{
sprintf (buf, "%02x", (unsigned int) checksum[i]);
buf_output0 (protocol, buf);
}
buf_append_char (protocol, '\n');
}
}
if (updated == SERVER_UPDATED)
{
Node *node;
Entnode *entnode;
if (!(supported_response ("Created")
&& supported_response ("Update-existing")))
buf_output0 (protocol, "Updated ");
else
{
assert (vers != NULL);
if (vers->ts_user == NULL)
buf_output0 (protocol, "Created ");
else
buf_output0 (protocol, "Update-existing ");
}
node = findnode_fn (finfo->entries, finfo->file);
entnode = node->data;
free (entnode->timestamp);
entnode->timestamp = xstrdup ("=");
}
else if (updated == SERVER_MERGED)
buf_output0 (protocol, "Merged ");
else if (updated == SERVER_PATCHED)
buf_output0 (protocol, "Patched ");
else if (updated == SERVER_RCS_DIFF)
buf_output0 (protocol, "Rcs-diff ");
else
abort ();
output_dir (finfo->update_dir, finfo->repository);
buf_output0 (protocol, finfo->file);
buf_output (protocol, "\n", 1);
new_entries_line ();
{
char *mode_string;
mode_string = mode_to_string (mode);
buf_output0 (protocol, mode_string);
buf_output0 (protocol, "\n");
free (mode_string);
}
list = last = NULL;
file = NULL;
file_allocated = 0;
file_used = 0;
if (size > 0)
{
if (file_gzip_level
&& size > 100)
{
int fd;
if (filebuf != NULL)
error (1, 0, "\
CVS server internal error: unhandled case in server_updated");
fd = CVS_OPEN (finfo->file, O_RDONLY | OPEN_BINARY, 0);
if (fd < 0)
error (1, errno, "reading %s", finfo->fullname);
if (read_and_gzip (fd, finfo->fullname, &file,
&file_allocated, &file_used,
file_gzip_level))
error (1, 0, "aborting due to compression error");
size = file_used;
if (close (fd) < 0)
error (1, errno, "reading %s", finfo->fullname);
buf_output0 (protocol, "z");
}
else if (filebuf == NULL)
{
long status;
f = CVS_FOPEN (finfo->file, "rb");
if (f == NULL)
error (1, errno, "reading %s", finfo->fullname);
status = buf_read_file (f, size, &list, &last);
if (status == -2)
(*protocol->memory_error) (protocol);
else if (status != 0)
error (1, ferror (f) ? errno : 0, "reading %s",
finfo->fullname);
if (fclose (f) == EOF)
error (1, errno, "reading %s", finfo->fullname);
}
}
sprintf (size_text, "%lu\n", size);
buf_output0 (protocol, size_text);
if (file != NULL)
{
buf_output (protocol, (char *) file, file_used);
free (file);
file = NULL;
}
else if (filebuf == NULL)
buf_append_data (protocol, list, last);
else
{
buf_append_buffer (protocol, filebuf);
}
if ((updated == SERVER_UPDATED
|| updated == SERVER_PATCHED
|| updated == SERVER_RCS_DIFF)
&& filebuf == NULL
&& !joining ())
{
if (CVS_UNLINK (finfo->file) < 0)
error (0, errno, "cannot remove temp file for %s",
finfo->fullname);
}
}
else if (scratched_file != NULL && entries_line == NULL)
{
if (strcmp (scratched_file, finfo->file) != 0)
error (1, 0,
"CVS server internal error: `%s' vs. `%s' scratched",
scratched_file,
finfo->file);
free (scratched_file);
scratched_file = NULL;
if (kill_scratched_file)
buf_output0 (protocol, "Removed ");
else
buf_output0 (protocol, "Remove-entry ");
output_dir (finfo->update_dir, finfo->repository);
buf_output0 (protocol, finfo->file);
buf_output (protocol, "\n", 1);
if (vers && vers->vn_user != NULL)
{
free (vers->vn_user);
vers->vn_user = NULL;
}
if (vers && vers->ts_user != NULL)
{
free (vers->ts_user);
vers->ts_user = NULL;
}
}
else if (scratched_file == NULL && entries_line == NULL)
{
}
else
error (1, 0,
"CVS server internal error: Register *and* Scratch_Entry.\n");
buf_send_counted (protocol);
done:;
}
int
server_use_rcs_diff ()
{
return supported_response ("Rcs-diff");
}
void
server_set_entstat (update_dir, repository)
const char *update_dir;
const char *repository;
{
static int set_static_supported = -1;
if (set_static_supported == -1)
set_static_supported = supported_response ("Set-static-directory");
if (!set_static_supported) return;
buf_output0 (protocol, "Set-static-directory ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
buf_send_counted (protocol);
}
void
server_clear_entstat (update_dir, repository)
const char *update_dir;
const char *repository;
{
static int clear_static_supported = -1;
if (clear_static_supported == -1)
clear_static_supported = supported_response ("Clear-static-directory");
if (!clear_static_supported) return;
if (noexec)
return;
buf_output0 (protocol, "Clear-static-directory ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
buf_send_counted (protocol);
}
void
server_set_sticky (update_dir, repository, tag, date, nonbranch)
const char *update_dir;
const char *repository;
const char *tag;
const char *date;
int nonbranch;
{
static int set_sticky_supported = -1;
assert (update_dir != NULL);
if (set_sticky_supported == -1)
set_sticky_supported = supported_response ("Set-sticky");
if (!set_sticky_supported) return;
if (noexec)
return;
if (tag == NULL && date == NULL)
{
buf_output0 (protocol, "Clear-sticky ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
}
else
{
buf_output0 (protocol, "Set-sticky ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
if (tag != NULL)
{
if (nonbranch)
buf_output0 (protocol, "N");
else
buf_output0 (protocol, "T");
buf_output0 (protocol, tag);
}
else
{
buf_output0 (protocol, "D");
buf_output0 (protocol, date);
}
buf_output0 (protocol, "\n");
}
buf_send_counted (protocol);
}
struct template_proc_data
{
const char *update_dir;
const char *repository;
};
static struct template_proc_data *tpd;
static int
template_proc (repository, template)
char *repository;
char *template;
{
FILE *fp;
char buf[1024];
size_t n;
struct stat sb;
struct template_proc_data *data = tpd;
if (!supported_response ("Template"))
return 0;
buf_output0 (protocol, "Template ");
output_dir (data->update_dir, data->repository);
buf_output0 (protocol, "\n");
fp = CVS_FOPEN (template, "rb");
if (fp == NULL)
{
error (0, errno, "Couldn't open rcsinfo template file %s", template);
return 1;
}
if (fstat (fileno (fp), &sb) < 0)
{
error (0, errno, "cannot stat rcsinfo template file %s", template);
return 1;
}
sprintf (buf, "%ld\n", (long) sb.st_size);
buf_output0 (protocol, buf);
while (!feof (fp))
{
n = fread (buf, 1, sizeof buf, fp);
buf_output (protocol, buf, n);
if (ferror (fp))
{
error (0, errno, "cannot read rcsinfo template file %s", template);
(void) fclose (fp);
return 1;
}
}
buf_send_counted (protocol);
if (fclose (fp) < 0)
error (0, errno, "cannot close rcsinfo template file %s", template);
return 0;
}
void
server_template (update_dir, repository)
const char *update_dir;
const char *repository;
{
struct template_proc_data data;
data.update_dir = update_dir;
data.repository = repository;
tpd = &data;
(void) Parse_Info (CVSROOTADM_RCSINFO, repository, template_proc, 1);
}
static void
serve_gzip_contents (arg)
char *arg;
{
int level;
level = atoi (arg);
if (level == 0)
level = 6;
file_gzip_level = level;
}
static void
serve_gzip_stream (arg)
char *arg;
{
int level;
level = atoi (arg);
if (level == 0)
level = 6;
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
serve_wrapper_sendme_rcs_options (arg)
char *arg;
{
char *wrapper_line = NULL;
wrap_setup ();
for (wrap_unparse_rcs_options (&wrapper_line, 1);
wrapper_line;
wrap_unparse_rcs_options (&wrapper_line, 0))
{
buf_output0 (buf_to_net, "Wrapper-rcsOption ");
buf_output0 (buf_to_net, wrapper_line);
buf_output0 (buf_to_net, "\012");;
free (wrapper_line);
}
buf_output0 (buf_to_net, "ok\012");
buf_flush (buf_to_net, 1);
}
static void
serve_ignore (arg)
char *arg;
{
}
static int
expand_proc (argc, argv, where, mwhere, mfile, shorten,
local_specified, omodule, msg)
int argc;
char **argv;
char *where;
char *mwhere;
char *mfile;
int shorten;
int local_specified;
char *omodule;
char *msg;
{
int i;
char *dir = argv[0];
if (mwhere != NULL)
{
buf_output0 (buf_to_net, "Module-expansion ");
if (server_dir != NULL)
{
buf_output0 (buf_to_net, server_dir);
buf_output0 (buf_to_net, "/");
}
buf_output0 (buf_to_net, mwhere);
if (mfile != NULL)
{
buf_append_char (buf_to_net, '/');
buf_output0 (buf_to_net, mfile);
}
buf_append_char (buf_to_net, '\n');
}
else
{
if (argc == 1)
{
buf_output0 (buf_to_net, "Module-expansion ");
if (server_dir != NULL)
{
buf_output0 (buf_to_net, server_dir);
buf_output0 (buf_to_net, "/");
}
buf_output0 (buf_to_net, dir);
buf_append_char (buf_to_net, '\n');
}
else
{
for (i = 1; i < argc; ++i)
{
buf_output0 (buf_to_net, "Module-expansion ");
if (server_dir != NULL)
{
buf_output0 (buf_to_net, server_dir);
buf_output0 (buf_to_net, "/");
}
buf_output0 (buf_to_net, dir);
buf_append_char (buf_to_net, '/');
buf_output0 (buf_to_net, argv[i]);
buf_append_char (buf_to_net, '\n');
}
}
}
return 0;
}
static void
serve_expand_modules (arg)
char *arg;
{
int i;
int err;
DBM *db;
err = 0;
db = open_module ();
for (i = 1; i < argument_count; i++)
err += do_module (db, argument_vector[i],
CHECKOUT, "Updating", expand_proc,
NULL, 0, 0, 0, 0,
(char *) NULL);
close_module (db);
{
char **cp;
for (cp = argument_vector + 1;
cp < argument_vector + argument_count;
++cp)
free (*cp);
argument_count = 1;
}
if (err)
buf_output0 (buf_to_net, "error \n");
else
buf_output0 (buf_to_net, "ok\n");
buf_flush (buf_to_net, 1);
}
static void serve_valid_requests PROTO((char *arg));
#endif
#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
struct request requests[] =
{
#ifdef SERVER_SUPPORT
#define REQ_LINE(n, f, s) {n, f, s}
#else
#define REQ_LINE(n, f, s) {n, s}
#endif
REQ_LINE("Root", serve_root, RQ_ESSENTIAL | RQ_ROOTLESS),
REQ_LINE("Valid-responses", serve_valid_responses,
RQ_ESSENTIAL | RQ_ROOTLESS),
REQ_LINE("valid-requests", serve_valid_requests,
RQ_ESSENTIAL | RQ_ROOTLESS),
REQ_LINE("Repository", serve_repository, 0),
REQ_LINE("Directory", serve_directory, RQ_ESSENTIAL),
REQ_LINE("Max-dotdot", serve_max_dotdot, 0),
REQ_LINE("Static-directory", serve_static_directory, 0),
REQ_LINE("Sticky", serve_sticky, 0),
REQ_LINE("Entry", serve_entry, RQ_ESSENTIAL),
REQ_LINE("Kopt", serve_kopt, 0),
REQ_LINE("Checkin-time", serve_checkin_time, 0),
REQ_LINE("Modified", serve_modified, RQ_ESSENTIAL),
REQ_LINE("Is-modified", serve_is_modified, 0),
REQ_LINE("UseUnchanged", serve_enable_unchanged, RQ_ENABLEME | RQ_ROOTLESS),
REQ_LINE("Unchanged", serve_unchanged, RQ_ESSENTIAL),
REQ_LINE("Notify", serve_notify, 0),
REQ_LINE("Questionable", serve_questionable, 0),
REQ_LINE("Argument", serve_argument, RQ_ESSENTIAL),
REQ_LINE("Argumentx", serve_argumentx, RQ_ESSENTIAL),
REQ_LINE("Global_option", serve_global_option, RQ_ROOTLESS),
REQ_LINE("Gzip-stream", serve_gzip_stream, 0),
REQ_LINE("wrapper-sendme-rcsOptions",
serve_wrapper_sendme_rcs_options,
0),
REQ_LINE("Set", serve_set, RQ_ROOTLESS),
#ifdef ENCRYPTION
# ifdef HAVE_KERBEROS
REQ_LINE("Kerberos-encrypt", serve_kerberos_encrypt, 0),
# endif
# ifdef HAVE_GSSAPI
REQ_LINE("Gssapi-encrypt", serve_gssapi_encrypt, 0),
# endif
#endif
#ifdef HAVE_GSSAPI
REQ_LINE("Gssapi-authenticate", serve_gssapi_authenticate, 0),
#endif
REQ_LINE("expand-modules", serve_expand_modules, 0),
REQ_LINE("ci", serve_ci, RQ_ESSENTIAL),
REQ_LINE("co", serve_co, RQ_ESSENTIAL),
REQ_LINE("update", serve_update, RQ_ESSENTIAL),
REQ_LINE("diff", serve_diff, 0),
REQ_LINE("log", serve_log, 0),
REQ_LINE("rlog", serve_rlog, 0),
REQ_LINE("add", serve_add, 0),
REQ_LINE("remove", serve_remove, 0),
REQ_LINE("update-patches", serve_ignore, 0),
REQ_LINE("gzip-file-contents", serve_gzip_contents, 0),
REQ_LINE("status", serve_status, 0),
REQ_LINE("rdiff", serve_rdiff, 0),
REQ_LINE("tag", serve_tag, 0),
REQ_LINE("rtag", serve_rtag, 0),
REQ_LINE("import", serve_import, 0),
REQ_LINE("admin", serve_admin, 0),
REQ_LINE("export", serve_export, 0),
REQ_LINE("history", serve_history, 0),
REQ_LINE("release", serve_release, 0),
REQ_LINE("watch-on", serve_watch_on, 0),
REQ_LINE("watch-off", serve_watch_off, 0),
REQ_LINE("watch-add", serve_watch_add, 0),
REQ_LINE("watch-remove", serve_watch_remove, 0),
REQ_LINE("watchers", serve_watchers, 0),
REQ_LINE("editors", serve_editors, 0),
REQ_LINE("init", serve_init, RQ_ROOTLESS),
REQ_LINE("annotate", serve_annotate, 0),
REQ_LINE("rannotate", serve_rannotate, 0),
REQ_LINE("noop", serve_noop, RQ_ROOTLESS),
REQ_LINE("version", serve_version, RQ_ROOTLESS),
REQ_LINE(NULL, NULL, 0)
#undef REQ_LINE
};
#endif
#ifdef SERVER_SUPPORT
static void
serve_valid_requests (arg)
char *arg;
{
struct request *rq;
if (print_pending_error ())
return;
buf_output0 (buf_to_net, "Valid-requests");
for (rq = requests; rq->name != NULL; rq++)
{
if (rq->func != NULL)
{
buf_append_char (buf_to_net, ' ');
buf_output0 (buf_to_net, rq->name);
}
}
buf_output0 (buf_to_net, "\nok\n");
buf_flush (buf_to_net, 1);
}
#ifdef SUNOS_KLUDGE
static int command_pid_is_dead;
static void wait_sig (sig)
int sig;
{
int status;
pid_t r = wait (&status);
if (r == command_pid)
command_pid_is_dead++;
}
#endif
void
server_cleanup (sig)
int sig;
{
int status;
int save_noexec;
if (buf_to_net != NULL)
{
set_block (buf_to_net);
buf_flush (buf_to_net, 1);
if (buf_from_net != NULL)
{
status = buf_shutdown (buf_from_net);
if (status != 0)
error (0, status, "shutting down buffer from client");
buf_free (buf_from_net);
buf_from_net = NULL;
}
if (dont_delete_temp)
{
(void) buf_flush (buf_to_net, 1);
(void) buf_shutdown (buf_to_net);
buf_free (buf_to_net);
buf_to_net = NULL;
error_use_protocol = 0;
return;
}
}
else if (dont_delete_temp)
return;
#ifdef SUNOS_KLUDGE
if (command_pid > 0)
{
int status;
pid_t r;
signal (SIGCHLD, wait_sig);
if (sig)
kill (command_pid, SIGINT);
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
abort ();
while (!command_pid_is_dead)
{
struct timeval timeout;
struct fd_set_wrapper readfds;
char buf[100];
int i;
timeout.tv_sec = 2;
timeout.tv_usec = 0;
readfds = command_fds_to_drain;
switch (select (max_command_fd + 1, &readfds.fds,
(fd_set *)0, (fd_set *)0,
&timeout))
{
case -1:
if (errno != EINTR)
abort ();
case 0:
break;
case 1:
for (i = 0; i <= max_command_fd; i++)
{
if (!FD_ISSET (i, &readfds.fds))
continue;
while (read (i, buf, sizeof (buf)) >= 1)
;
}
break;
default:
abort ();
}
}
}
#endif
CVS_CHDIR (Tmpdir);
save_noexec = noexec;
noexec = 0;
unlink_file_dir (orig_server_temp_dir);
noexec = save_noexec;
if (buf_to_net != NULL)
{
(void) buf_flush (buf_to_net, 1);
(void) buf_shutdown (buf_to_net);
buf_free (buf_to_net);
buf_to_net = NULL;
error_use_protocol = 0;
}
}
int server_active = 0;
int
server (argc, argv)
int argc;
char **argv;
{
char *error_prog_name;
if (argc == -1)
{
static const char *const msg[] =
{
"Usage: %s %s\n",
" Normally invoked by a cvs client on a remote machine.\n",
NULL
};
usage (msg);
}
buf_to_net = fd_buffer_initialize (STDOUT_FILENO, 0,
outbuf_memory_error);
buf_from_net = stdio_buffer_initialize (stdin, 0, 1, outbuf_memory_error);
saved_output = buf_nonio_initialize (outbuf_memory_error);
saved_outerr = buf_nonio_initialize (outbuf_memory_error);
error_use_protocol = 1;
{
char *p;
if (!isabsolute (Tmpdir))
{
if (alloc_pending (80 + strlen (Tmpdir)))
sprintf (pending_error_text,
"E Value of %s for TMPDIR is not absolute", Tmpdir);
}
else
{
int status;
int i = 0;
server_temp_dir = xmalloc (strlen (Tmpdir) + 80);
if (server_temp_dir == NULL)
{
printf ("E Fatal server error, aborting.\n\
error ENOMEM Virtual memory exhausted.\n");
error_exit ();
}
strcpy (server_temp_dir, Tmpdir);
p = server_temp_dir + strlen (server_temp_dir) - 1;
if (*p == '/')
*p = '\0';
strcat (server_temp_dir, "/cvs-serv");
p = server_temp_dir + strlen (server_temp_dir);
sprintf (p, "%ld", (long) getpid ());
orig_server_temp_dir = server_temp_dir;
while ((status = mkdir_p (server_temp_dir)) == EEXIST)
{
static const char suffix[] = "abcdefghijklmnopqrstuvwxyz";
if (i >= sizeof suffix - 1) break;
if (i == 0) p = server_temp_dir + strlen (server_temp_dir);
p[0] = suffix[i++];
p[1] = '\0';
}
if (status != 0)
{
if (alloc_pending (80 + strlen (server_temp_dir)))
sprintf (pending_error_text,
"E can't create temporary directory %s",
server_temp_dir);
pending_error = status;
}
#ifndef CHMOD_BROKEN
else if (chmod (server_temp_dir, S_IRWXU) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (server_temp_dir)))
sprintf (pending_error_text,
"E cannot change permissions on temporary directory %s",
server_temp_dir);
pending_error = save_errno;
}
#endif
else if (CVS_CHDIR (server_temp_dir) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (server_temp_dir)))
sprintf (pending_error_text,
"E cannot change to temporary directory %s",
server_temp_dir);
pending_error = save_errno;
}
}
}
argument_vector_size = 1;
argument_vector = xmalloc (argument_vector_size * sizeof (char *));
argument_count = 1;
error_prog_name = xmalloc (strlen (program_name) + 8);
sprintf(error_prog_name, "%s server", program_name);
argument_vector[0] = error_prog_name;
while (1)
{
char *cmd, *orig_cmd;
struct request *rq;
int status;
status = buf_read_line (buf_from_net, &cmd, NULL);
if (status == -2)
{
buf_output0 (buf_to_net, "E Fatal server error, aborting.\n\
error ENOMEM Virtual memory exhausted.\n");
break;
}
if (status != 0)
break;
orig_cmd = cmd;
for (rq = requests; rq->name != NULL; ++rq)
if (strncmp (cmd, rq->name, strlen (rq->name)) == 0)
{
int len = strlen (rq->name);
if (cmd[len] == '\0')
cmd += len;
else if (cmd[len] == ' ')
cmd += len + 1;
else
continue;
if (!(rq->flags & RQ_ROOTLESS)
&& current_parsed_root == NULL)
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Protocol error: Root request missing");
}
else
(*rq->func) (cmd);
break;
}
if (rq->name == NULL)
{
if (!print_pending_error ())
{
buf_output0 (buf_to_net, "error unrecognized request `");
buf_output0 (buf_to_net, cmd);
buf_append_char (buf_to_net, '\'');
buf_append_char (buf_to_net, '\n');
}
}
free (orig_cmd);
}
free (error_prog_name);
if (!buf_empty (buf_from_net))
{
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR, "Dying gasps received from client.");
#endif
error (0, 0, "Dying gasps received from client.");
}
server_cleanup (0);
return 0;
}
#if defined (HAVE_KERBEROS) || defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
static void switch_to_user PROTO((const char *, const char *));
static void
switch_to_user (cvs_username, username)
const char *cvs_username;
const char *username;
{
struct passwd *pw;
pw = getpwnam (username);
if (pw == NULL)
{
printf ("E Fatal error, aborting.\n\
error 0 %s: no such system user\n", username);
error_exit ();
}
if (pw->pw_uid == 0)
{
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ALERT,
"attempt to root from account: %s", cvs_username
);
#endif
printf("error 0: root not allowed\n");
error_exit ();
}
#if HAVE_INITGROUPS
if (initgroups (pw->pw_name, pw->pw_gid) < 0
# ifdef EPERM
&& errno != EPERM
# endif
)
{
printf ("error 0 initgroups failed: %s\n", strerror (errno));
error_exit ();
}
#endif
#ifdef SETXID_SUPPORT
if (getgid() != getegid())
{
if (setgid (getegid ()) < 0)
{
printf ("error 0 setgid failed: %s\n", strerror (errno));
error_exit ();
}
}
else
#endif
{
if (setgid (pw->pw_gid) < 0)
{
printf ("error 0 setgid failed: %s\n", strerror (errno));
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR,
"setgid to %d failed (%m): real %d/%d, effective %d/%d ",
pw->pw_gid, getuid(), getgid(), geteuid(), getegid());
#endif
error_exit ();
}
}
if (setuid (pw->pw_uid) < 0)
{
printf ("error 0 setuid failed: %s\n", strerror (errno));
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR,
"setuid to %d failed (%m): real %d/%d, effective %d/%d ",
pw->pw_uid, getuid(), getgid(), geteuid(), getegid());
#endif
error_exit ();
}
umask (0);
#ifdef AUTH_SERVER_SUPPORT
if (CVS_Username == NULL)
CVS_Username = xstrdup (username);
#endif
#if HAVE_PUTENV
{
char *env;
env = xmalloc (sizeof "LOGNAME=" + strlen (username));
(void) sprintf (env, "LOGNAME=%s", username);
(void) putenv (env);
env = xmalloc (sizeof "USER=" + strlen (username));
(void) sprintf (env, "USER=%s", username);
(void) putenv (env);
#ifdef AUTH_SERVER_SUPPORT
env = xmalloc (sizeof "CVS_USER=" + strlen (CVS_Username));
(void) sprintf (env, "CVS_USER=%s", CVS_Username);
(void) putenv (env);
#endif
}
#endif
}
#endif
#ifdef AUTH_SERVER_SUPPORT
extern char *crypt PROTO((const char *, const char *));
static int
check_repository_password (username, password, repository, host_user_ptr)
char *username, *password, *repository, **host_user_ptr;
{
int retval = 0;
FILE *fp;
char *filename;
char *linebuf = NULL;
size_t linebuf_len;
int found_it = 0;
int namelen;
filename = xmalloc (strlen (repository)
+ 1
+ strlen (CVSROOTADM)
+ 1
+ strlen (CVSROOTADM_PASSWD)
+ 1);
(void) sprintf (filename, "%s/%s/%s", repository,
CVSROOTADM, CVSROOTADM_PASSWD);
fp = CVS_FOPEN (filename, "r");
if (fp == NULL)
{
if (!existence_error (errno))
error (0, errno, "cannot open %s", filename);
free (filename);
return 0;
}
namelen = strlen (username);
while (getline (&linebuf, &linebuf_len, fp) >= 0)
{
if ((strncmp (linebuf, username, namelen) == 0)
&& (linebuf[namelen] == ':'))
{
found_it = 1;
break;
}
}
if (ferror (fp))
error (0, errno, "cannot read %s", filename);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", filename);
if (found_it)
{
char *found_password, *host_user_tmp;
char *non_cvsuser_portion;
non_cvsuser_portion = linebuf + namelen;
strtok (non_cvsuser_portion, "\n");
if (strchr (non_cvsuser_portion, ':') == non_cvsuser_portion)
non_cvsuser_portion++;
if ((non_cvsuser_portion == NULL)
|| (strlen (non_cvsuser_portion) == 0)
|| ((strspn (non_cvsuser_portion, " \t"))
== strlen (non_cvsuser_portion)))
{
found_password = NULL;
host_user_tmp = NULL;
}
else if (strncmp (non_cvsuser_portion, ":", 1) == 0)
{
found_password = NULL;
host_user_tmp = non_cvsuser_portion + 1;
if (strlen (host_user_tmp) == 0)
host_user_tmp = NULL;
}
else
{
found_password = strtok (non_cvsuser_portion, ":");
host_user_tmp = strtok (NULL, ":");
}
if (host_user_tmp == NULL)
host_user_tmp = username;
if ((found_password == NULL)
|| ((strcmp (found_password, crypt (password, found_password))
== 0)))
{
*host_user_ptr = xstrdup (host_user_tmp);
retval = 1;
}
else
{
#ifdef LOG_AUTHPRIV
syslog (LOG_AUTHPRIV | LOG_NOTICE,
"password mismatch for %s in %s: %s vs. %s", username,
repository, crypt(password, found_password), found_password);
#endif
*host_user_ptr = NULL;
retval = 2;
}
}
else
{
*host_user_ptr = NULL;
retval = 0;
}
free (filename);
if (linebuf)
free (linebuf);
return retval;
}
static char *
check_password (username, password, repository)
char *username, *password, *repository;
{
int rc;
char *host_user = NULL;
char *found_passwd = NULL;
struct passwd *pw;
rc = check_repository_password (username, password, repository,
&host_user);
if (rc == 2)
return NULL;
if (rc == 1)
{
goto handle_return;
}
assert (rc == 0);
if (!system_auth)
{
printf ("error 0 no such user %s in CVSROOT/passwd\n", username);
error_exit ();
}
#ifdef HAVE_GETSPNAM
{
struct spwd *spw;
spw = getspnam (username);
if (spw != NULL)
{
found_passwd = spw->sp_pwdp;
}
}
#endif
if (found_passwd == NULL && (pw = getpwnam (username)) != NULL)
{
found_passwd = pw->pw_passwd;
}
if (found_passwd == NULL)
{
printf ("E Fatal error, aborting.\n\
error 0 %s: no such user\n", username);
error_exit ();
}
strtok (found_passwd, ",");
if (*found_passwd)
{
if (strcmp (found_passwd, crypt (password, found_passwd)) == 0)
{
host_user = xstrdup (username);
}
else
{
host_user = NULL;
#ifdef LOG_AUTHPRIV
syslog (LOG_AUTHPRIV | LOG_NOTICE,
"password mismatch for %s: %s vs. %s", username,
crypt(password, found_passwd), found_passwd);
#endif
}
goto handle_return;
}
if (password && *password)
{
host_user = xstrdup (username);
goto handle_return;
}
host_user = NULL;
#ifdef LOG_AUTHPRIV
syslog (LOG_AUTHPRIV | LOG_NOTICE,
"login refused for %s: user has no password", username);
#endif
handle_return:
if (host_user)
{
CVS_Username = xmalloc (strlen (username) + 1);
strcpy (CVS_Username, username);
}
return host_user;
}
#endif
#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
void
pserver_authenticate_connection ()
{
char *tmp = NULL;
size_t tmp_allocated = 0;
#ifdef AUTH_SERVER_SUPPORT
char *repository = NULL;
size_t repository_allocated = 0;
char *username = NULL;
size_t username_allocated = 0;
char *password = NULL;
size_t password_allocated = 0;
char *host_user;
char *descrambled_password;
#endif
int verify_and_exit = 0;
#ifdef SO_KEEPALIVE
{
int on = 1;
if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE,
(char *) &on, sizeof on) < 0)
{
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR, "error setting KEEPALIVE: %m");
#endif
}
}
#endif
if (getline_safe (&tmp, &tmp_allocated, stdin, PATH_MAX) < 0)
{
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_NOTICE, "bad auth protocol start: EOF");
#endif
error (1, 0, "bad auth protocol start: EOF");
}
if (strcmp (tmp, "BEGIN VERIFICATION REQUEST\n") == 0)
verify_and_exit = 1;
else if (strcmp (tmp, "BEGIN AUTH REQUEST\n") == 0)
;
else if (strcmp (tmp, "BEGIN GSSAPI REQUEST\n") == 0)
{
#ifdef HAVE_GSSAPI
free (tmp);
gserver_authenticate_connection ();
return;
#else
error (1, 0, "GSSAPI authentication not supported by this server");
#endif
}
else
error (1, 0, "bad auth protocol start: %s", tmp);
#ifndef AUTH_SERVER_SUPPORT
error (1, 0, "Password authentication not supported by this server");
#else
getline_safe (&repository, &repository_allocated, stdin, PATH_MAX);
getline_safe (&username, &username_allocated, stdin, PATH_MAX);
getline_safe (&password, &password_allocated, stdin, PATH_MAX);
if (!strip_trailing_newlines (repository)
|| !strip_trailing_newlines (username)
|| !strip_trailing_newlines (password))
error (1, 0, "Maximum line length exceeded during authentication.");
getline_safe (&tmp, &tmp_allocated, stdin, PATH_MAX);
if (strcmp (tmp,
verify_and_exit ?
"END VERIFICATION REQUEST\n" : "END AUTH REQUEST\n")
!= 0)
{
error (1, 0, "bad auth protocol end: %s", tmp);
}
if (!root_allow_ok (repository))
{
printf ("error 0 %s: no such repository\n", repository);
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_NOTICE, "login refused for %s", repository);
#endif
goto i_hate_you;
}
parse_config (repository);
descrambled_password = descramble (password);
host_user = check_password (username, descrambled_password, repository);
if (host_user == NULL)
{
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_NOTICE, "login failure (for %s)", repository);
#endif
memset (descrambled_password, 0, strlen (descrambled_password));
free (descrambled_password);
i_hate_you:
printf ("I HATE YOU\n");
fflush (stdout);
error_exit ();
}
memset (descrambled_password, 0, strlen (descrambled_password));
free (descrambled_password);
if (verify_and_exit)
{
printf ("I LOVE YOU\n");
fflush (stdout);
#ifdef SYSTEM_CLEANUP
SYSTEM_CLEANUP ();
#endif
exit (0);
}
Pserver_Repos = xmalloc (strlen (repository) + 1);
strcpy (Pserver_Repos, repository);
switch_to_user (username, host_user);
free (host_user);
free (tmp);
free (repository);
free (username);
free (password);
printf ("I LOVE YOU\n");
fflush (stdout);
#endif
}
#endif
#ifdef HAVE_KERBEROS
void
kserver_authenticate_connection ()
{
int status;
char instance[INST_SZ];
struct sockaddr_in peer;
struct sockaddr_in laddr;
int len;
KTEXT_ST ticket;
AUTH_DAT auth;
char version[KRB_SENDAUTH_VLEN];
char user[ANAME_SZ];
strcpy (instance, "*");
len = sizeof peer;
if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0
|| getsockname (STDIN_FILENO, (struct sockaddr *) &laddr,
&len) < 0)
{
printf ("E Fatal error, aborting.\n\
error %s getpeername or getsockname failed\n", strerror (errno));
error_exit ();
}
#ifdef SO_KEEPALIVE
{
int on = 1;
if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE,
(char *) &on, sizeof on) < 0)
{
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR, "error setting KEEPALIVE: %m");
#endif
}
}
#endif
status = krb_recvauth (KOPT_DO_MUTUAL, STDIN_FILENO, &ticket, "rcmd",
instance, &peer, &laddr, &auth, "", sched,
version);
if (status != KSUCCESS)
{
printf ("E Fatal error, aborting.\n\
error 0 kerberos: %s\n", krb_get_err_text(status));
error_exit ();
}
memcpy (kblock, auth.session, sizeof (C_Block));
status = krb_kntoln (&auth, user);
if (status != KSUCCESS)
{
printf ("E Fatal error, aborting.\n\
error 0 kerberos: can't get local name: %s\n", krb_get_err_text(status));
error_exit ();
}
switch_to_user ("Kerberos 4", user);
}
#endif
#ifdef HAVE_GSSAPI
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN (256)
#endif
static void
gserver_authenticate_connection ()
{
char hostname[MAXHOSTNAMELEN];
struct hostent *hp;
gss_buffer_desc tok_in, tok_out;
char buf[1024];
char *credbuf;
size_t credbuflen;
OM_uint32 stat_min, ret;
gss_name_t server_name, client_name;
gss_cred_id_t server_creds;
int nbytes;
gss_OID mechid;
gethostname (hostname, sizeof hostname);
hp = gethostbyname (hostname);
if (hp == NULL)
error (1, 0, "can't get canonical hostname");
sprintf (buf, "cvs@%s", hp->h_name);
tok_in.value = buf;
tok_in.length = strlen (buf);
if (gss_import_name (&stat_min, &tok_in, GSS_C_NT_HOSTBASED_SERVICE,
&server_name) != GSS_S_COMPLETE)
error (1, 0, "could not import GSSAPI service name %s", buf);
if (gss_acquire_cred (&stat_min, server_name, 0, GSS_C_NULL_OID_SET,
GSS_C_ACCEPT, &server_creds,
NULL, NULL) != GSS_S_COMPLETE)
error (1, 0, "could not acquire GSSAPI server credentials");
gss_release_name (&stat_min, &server_name);
if (fread (buf, 1, 2, stdin) != 2)
error (1, errno, "read of length failed");
nbytes = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff);
if (nbytes <= sizeof buf)
{
credbuf = buf;
credbuflen = sizeof buf;
}
else
{
credbuflen = nbytes;
credbuf = xmalloc (credbuflen);
}
if (fread (credbuf, 1, nbytes, stdin) != nbytes)
error (1, errno, "read of data failed");
gcontext = GSS_C_NO_CONTEXT;
tok_in.length = nbytes;
tok_in.value = credbuf;
if (gss_accept_sec_context (&stat_min,
&gcontext,
server_creds,
&tok_in,
NULL,
&client_name,
&mechid,
&tok_out,
&ret,
NULL,
NULL)
!= GSS_S_COMPLETE)
{
error (1, 0, "could not verify credentials");
}
{
krb5_context kc;
krb5_principal p;
gss_buffer_desc desc;
krb5_init_context (&kc);
if (gss_display_name (&stat_min, client_name, &desc,
&mechid) != GSS_S_COMPLETE
|| krb5_parse_name (kc, ((gss_buffer_t) &desc)->value, &p) != 0
|| krb5_aname_to_localname (kc, p, sizeof buf, buf) != 0
|| krb5_kuserok (kc, p, buf) != TRUE)
{
error (1, 0, "access denied");
}
krb5_free_principal (kc, p);
krb5_free_context (kc);
}
if (tok_out.length != 0)
{
char cbuf[2];
cbuf[0] = (tok_out.length >> 8) & 0xff;
cbuf[1] = tok_out.length & 0xff;
if (fwrite (cbuf, 1, 2, stdout) != 2
|| (fwrite (tok_out.value, 1, tok_out.length, stdout)
!= tok_out.length))
error (1, errno, "fwrite failed");
}
switch_to_user ("GSSAPI", buf);
if (credbuf != buf)
free (credbuf);
printf ("I LOVE YOU\n");
fflush (stdout);
}
#endif
#endif
#if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
int cvsencrypt;
int cvsauthenticate;
#ifdef HAVE_GSSAPI
struct cvs_gssapi_wrap_data
{
gss_ctx_id_t gcontext;
};
static int cvs_gssapi_wrap_input PROTO((void *, const char *, char *, int));
static int cvs_gssapi_wrap_output PROTO((void *, const char *, char *, int,
int *));
struct buffer *
cvs_gssapi_wrap_buffer_initialize (buf, input, gcontext, memory)
struct buffer *buf;
int input;
gss_ctx_id_t gcontext;
void (*memory) PROTO((struct buffer *));
{
struct cvs_gssapi_wrap_data *gd;
gd = (struct cvs_gssapi_wrap_data *) xmalloc (sizeof *gd);
gd->gcontext = gcontext;
return (packetizing_buffer_initialize
(buf,
input ? cvs_gssapi_wrap_input : NULL,
input ? NULL : cvs_gssapi_wrap_output,
gd,
memory));
}
static int
cvs_gssapi_wrap_input (fnclosure, input, output, size)
void *fnclosure;
const char *input;
char *output;
int size;
{
struct cvs_gssapi_wrap_data *gd =
(struct cvs_gssapi_wrap_data *) fnclosure;
gss_buffer_desc inbuf, outbuf;
OM_uint32 stat_min;
int conf;
inbuf.value = (void *) input;
inbuf.length = size;
if (gss_unwrap (&stat_min, gd->gcontext, &inbuf, &outbuf, &conf, NULL)
!= GSS_S_COMPLETE)
{
error (1, 0, "gss_unwrap failed");
}
if (outbuf.length > size)
abort ();
memcpy (output, outbuf.value, outbuf.length);
gss_release_buffer (&stat_min, &outbuf);
return 0;
}
static int
cvs_gssapi_wrap_output (fnclosure, input, output, size, translated)
void *fnclosure;
const char *input;
char *output;
int size;
int *translated;
{
struct cvs_gssapi_wrap_data *gd =
(struct cvs_gssapi_wrap_data *) fnclosure;
gss_buffer_desc inbuf, outbuf;
OM_uint32 stat_min;
int conf_req, conf;
inbuf.value = (void *) input;
inbuf.length = size;
#ifdef ENCRYPTION
conf_req = cvs_gssapi_encrypt;
#else
conf_req = 0;
#endif
if (gss_wrap (&stat_min, gd->gcontext, conf_req, GSS_C_QOP_DEFAULT,
&inbuf, &conf, &outbuf) != GSS_S_COMPLETE)
error (1, 0, "gss_wrap failed");
if (outbuf.length > size + 100)
abort ();
memcpy (output, outbuf.value, outbuf.length);
*translated = outbuf.length;
gss_release_buffer (&stat_min, &outbuf);
return 0;
}
#endif
#ifdef ENCRYPTION
#ifdef HAVE_KERBEROS
struct krb_encrypt_data
{
Key_schedule sched;
C_Block block;
};
static int krb_encrypt_input PROTO((void *, const char *, char *, int));
static int krb_encrypt_output PROTO((void *, const char *, char *, int,
int *));
struct buffer *
krb_encrypt_buffer_initialize (buf, input, sched, block, memory)
struct buffer *buf;
int input;
Key_schedule sched;
C_Block block;
void (*memory) PROTO((struct buffer *));
{
struct krb_encrypt_data *kd;
kd = (struct krb_encrypt_data *) xmalloc (sizeof *kd);
memcpy (kd->sched, sched, sizeof (Key_schedule));
memcpy (kd->block, block, sizeof (C_Block));
return packetizing_buffer_initialize (buf,
input ? krb_encrypt_input : NULL,
input ? NULL : krb_encrypt_output,
kd,
memory);
}
static int
krb_encrypt_input (fnclosure, input, output, size)
void *fnclosure;
const char *input;
char *output;
int size;
{
struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure;
int tcount;
des_cbc_encrypt ((C_Block *) input, (C_Block *) output,
size, kd->sched, &kd->block, 0);
tcount = ((output[0] & 0xff) << 8) + (output[1] & 0xff);
if (((tcount + 2 + 7) & ~7) != size)
error (1, 0, "Decryption failure");
return 0;
}
static int
krb_encrypt_output (fnclosure, input, output, size, translated)
void *fnclosure;
const char *input;
char *output;
int size;
int *translated;
{
struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure;
int aligned;
aligned = (size + 7) & ~7;
des_cbc_encrypt ((C_Block *) input, (C_Block *) output, aligned,
kd->sched, &kd->block, 1);
*translated = aligned;
return 0;
}
#endif
#endif
#endif
void
cvs_output (str, len)
const char *str;
size_t len;
{
if (len == 0)
len = strlen (str);
#ifdef SERVER_SUPPORT
if (error_use_protocol && buf_to_net != NULL)
{
buf_output (saved_output, str, len);
buf_copy_lines (buf_to_net, saved_output, 'M');
}
else if (server_active && protocol != NULL)
{
buf_output (saved_output, str, len);
buf_copy_lines (protocol, saved_output, 'M');
buf_send_counted (protocol);
}
else
#endif
{
size_t written;
size_t to_write = len;
const char *p = str;
fflush (stderr);
while (to_write > 0)
{
written = fwrite (p, 1, to_write, stdout);
if (written == 0)
break;
p += written;
to_write -= written;
}
}
}
void
cvs_output_binary (str, len)
char *str;
size_t len;
{
#ifdef SERVER_SUPPORT
if (error_use_protocol || server_active)
{
struct buffer *buf;
char size_text[40];
if (error_use_protocol)
buf = buf_to_net;
else
buf = protocol;
if (!supported_response ("Mbinary"))
{
error (0, 0, "\
this client does not support writing binary files to stdout");
return;
}
buf_output0 (buf, "Mbinary\012");
sprintf (size_text, "%lu\012", (unsigned long) len);
buf_output0 (buf, size_text);
buf_output (buf, str, len);
if (!error_use_protocol)
buf_send_counted (protocol);
}
else
#endif
{
size_t written;
size_t to_write = len;
const char *p = str;
#ifdef USE_SETMODE_STDOUT
int oldmode;
#endif
fflush (stderr);
#ifdef USE_SETMODE_STDOUT
fflush (stdout);
oldmode = _setmode (_fileno (stdout), OPEN_BINARY);
if (oldmode < 0)
error (0, errno, "failed to setmode on stdout");
#endif
while (to_write > 0)
{
written = fwrite (p, 1, to_write, stdout);
if (written == 0)
break;
p += written;
to_write -= written;
}
#ifdef USE_SETMODE_STDOUT
fflush (stdout);
if (_setmode (_fileno (stdout), oldmode) != OPEN_BINARY)
error (0, errno, "failed to setmode on stdout");
#endif
}
}
void
cvs_outerr (str, len)
const char *str;
size_t len;
{
if (len == 0)
len = strlen (str);
#ifdef SERVER_SUPPORT
if (error_use_protocol)
{
buf_output (saved_outerr, str, len);
buf_copy_lines (buf_to_net, saved_outerr, 'E');
}
else if (server_active)
{
buf_output (saved_outerr, str, len);
buf_copy_lines (protocol, saved_outerr, 'E');
buf_send_counted (protocol);
}
else
#endif
{
size_t written;
size_t to_write = len;
const char *p = str;
fflush (stdout);
while (to_write > 0)
{
written = fwrite (p, 1, to_write, stderr);
if (written == 0)
break;
p += written;
to_write -= written;
}
}
}
void
cvs_flusherr ()
{
#ifdef SERVER_SUPPORT
if (error_use_protocol)
{
buf_flush (buf_to_net, 0);
}
else if (server_active)
{
fflush (stderr);
buf_send_special_count (protocol, -2);
}
else
#endif
fflush (stderr);
}
void
cvs_flushout ()
{
#ifdef SERVER_SUPPORT
if (error_use_protocol)
{
buf_flush (buf_to_net, 0);
}
else if (server_active)
{
buf_send_special_count (protocol, -1);
}
else
#endif
fflush (stdout);
}
void
cvs_output_tagged (tag, text)
const char *tag;
const char *text;
{
if (text != NULL && strchr (text, '\n') != NULL)
assert (0);
if (tag[0] == '+' || tag[0] == '-')
assert (text == NULL);
#ifdef SERVER_SUPPORT
if (server_active && supported_response ("MT"))
{
struct buffer *buf;
if (error_use_protocol)
buf = buf_to_net;
else
buf = protocol;
buf_output0 (buf, "MT ");
buf_output0 (buf, tag);
if (text != NULL)
{
buf_output (buf, " ", 1);
buf_output0 (buf, text);
}
buf_output (buf, "\n", 1);
if (!error_use_protocol)
buf_send_counted (protocol);
}
else
#endif
{
if (strcmp (tag, "newline") == 0)
cvs_output ("\n", 1);
else if (text != NULL)
cvs_output (text, 0);
}
}