/* This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. */
#include "cvs.h"
#include "watch.h"
#include "edit.h"
#include "fileattr.h"
#include "getline.h"
#include "buffer.h"
#include "savecwd.h"
#include "../version.h"
/* For debug logs (in client.c) */
struct buffer *log_buffer_initialize(struct buffer *, FILE *, int, void (*) (struct buffer *));
/* The cvs username sent by the client, which might or might not be
the same as the system username the server eventually switches to
run as. CVS_Username gets set iff password authentication is
successful. */
const char *CVS_Username = NULL;
extern unsigned global_edit_bugnum_hack;
extern unsigned global_editors_bugnum_hack;
/* Should we check for system usernames/passwords? Can be changed by
CVSROOT/config. */
int system_auth = 1;
/* We need to check this */
extern int root_allow_count;
/* Name of server command */
const char *server_command_name;
/* Do we chroot? */
char *chroot_base = NULL;
int chroot_done = 0;
/* Force run as a user */
char *runas_user = NULL;
/* Client version sent from system */
const char *serv_client_version = NULL;
#ifdef SERVER_SUPPORT
extern int server_io_socket;
#ifdef _WIN32
#include <fcntl.h>
#endif
/* EWOULDBLOCK is not defined by POSIX, but some BSD systems will
return it, rather than EAGAIN, for nonblocking writes. */
#ifdef EWOULDBLOCK
#define blocking_error(err) ((err) == EWOULDBLOCK || (err) == EAGAIN)
#else
#define blocking_error(err) ((err) == EAGAIN)
#endif
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif
/* For initgroups(). */
#if HAVE_INITGROUPS
#include <grp.h>
#endif /* HAVE_INITGROUPS */
# ifdef SERVER_SUPPORT
# ifdef HAVE_GETSPNAM
#ifdef __hpux
#define command_name __hpux_command_name
#endif
# include <shadow.h>
#ifdef __hpux
#undef command_name
#endif
# endif
/* Redhat doesn't handle this correctly */
extern "C" {
#ifdef HAVE_PAM
#ifdef HAVE_SECURITY_PAM_MISC_H
#include <security/pam_misc.h>
#endif
#ifdef HAVE_PAM_PAM_MISC_H
#include <pam/pam_misc.h>
#endif
#ifdef HAVE_SECURITY_PAM_APPL_H
#include <security/pam_appl.h>
#endif
#ifdef HAVE_PAM_PAM_APPL_H
#include <pam/pam_appl.h>
#endif
#endif
} /* End of redhat extern */
# endif /* SERVER_SUPPORT */
#ifdef HAVE_SYSLOG_H
#ifndef LOG_AUTHPRIV
#define LOG_AUTHPRIV LOG_DAEMON
#endif
#endif
static int checksum_valid;
static char stored_checksum[33];
/* While processing requests, this buffer accumulates data to be sent to
the client, and then once we are in do_cvs_command, we use it
for all the data to be sent. */
static struct buffer *buf_to_net;
/* This buffer is used to read input from the client. */
static struct buffer *buf_from_net;
/* Buffer where output begins with 'T ' - linked to buf_to_net */
static struct buffer *stdout_buf;
/* Buffer where output begins with 'E ' - linked to buf_to_net */
static struct buffer *stderr_buf;
/*
* This is where we stash stuff we are going to use. Format string
* which expects a single directory within it, starting with a slash.
*/
static char *server_temp_dir;
/*
* Temporary protocol used during authentication
*/
static const protocol_interface *temp_protocol;
/* This is the original value of server_temp_dir, before any possible
changes inserted by serve_max_dotdot. */
static char *orig_server_temp_dir;
/* If Valid-RcsOptions is sent, this contains the list */
static char *valid_rcsoptions;
/* Nonzero if we should keep the temp directory around after we exit. */
static int dont_delete_temp;
#define PROTOCOL_ENCRYPTION 2
#define PROTOCOL_AUTHENTICATION 1
/* This is set when encryption or authentication is enabled,
It will be set to 0, PROTOCOL_ENCRYPTION, or PROTOCOL_AUTHENTICATION
depending on what protocol-specific encryption/authentication requests
are received.
*/
static int protocol_encryption_enabled = 0;
// Set to 1 if the data stream is compressed
static int protocol_compression_enabled = 0;
/* This is set to request/force encryption/compression */
/* 0=Any, 1=Request auth., 2=Request Encr., 3=Require Auth., 4=Require Encr. */
int encryption_level = 0;
/* 0=Any, 1=Request compr., 2=Require compr. */
int compression_level = 0;
/* Regexp of clients allowed to connect */
const char *allowed_clients = NULL;
static void server_write_entries();
static void server_write_renames();
/* All server communication goes through buffer structures. Most of
the buffers are built on top of a file descriptor. This structure
is used as the closure field in a buffer. */
struct fd_buffer
{
/* The file descriptor. */
int fd;
/* Nonzero if the file descriptor is in blocking mode. */
int blocking;
};
static int check_command_legal_p (const char *cmd_name);
int server_main(const char *cmd_name, int (*command)(int argc, char **argv));
static void do_chroot();
static struct buffer *fd_buffer_initialize(int, int, void (*) (struct buffer *));
static int fd_buffer_input(void *, char *, int, int, int *);
static int fd_buffer_output(void *, const char *, int, int *);
static int fd_buffer_flush(void *);
static int fd_buffer_block(void *, int);
static int fd_buffer_shutdown(void *);
static struct buffer *client_protocol_buffer_initialize(const struct protocol_interface *, int, void (*) (struct buffer *));
static int client_protocol_buffer_input(void *, char *, int, int, int *);
static int client_protocol_buffer_output(void *, const char *, int, int *);
static int client_protocol_buffer_flush(void *);
static int client_protocol_buffer_block(void *, int);
static int client_protocol_buffer_shutdown(void *);
static struct buffer *cvs_protocol_wrap_buffer_initialize (struct buffer *buf, char prefix);
static void cvs_protocol_wrap_set_buffer(struct buffer *buf, struct buffer *wrap);
static char *check_password (const char *username, const char *password, const char *repository, void **user_token);
static int io_getc(int fd)
{
char c;
if(read(fd,&c,1)<1)
return EOF;
return c;
}
int io_getline(int fd, char** buffer, int buffer_max)
{
char *p;
int l,c;
*buffer=(char*)malloc(buffer_max);
if(!*buffer)
return -1;
l=0;
p=*buffer;
*p='\0';
while(l<buffer_max-1 && (c=io_getc(fd))!=EOF)
{
if(c=='\n')
break;
*(p++)=(char)c;
l++;
}
if(l==0 && c==EOF)
return -1; /* EOF */
*p='\0';
return l;
}
/* Initialize a buffer built on a file descriptor. FD is the file
descriptor. INPUT is nonzero if this is for input, zero if this is
for output. MEMORY is the function to call when a memory error
occurs. */
static struct buffer *fd_buffer_initialize (int fd, int input, void (*memory)(struct buffer *))
{
struct fd_buffer *n;
n = (struct fd_buffer *) xmalloc (sizeof *n);
n->fd = fd;
n->blocking = 1;
return buf_initialize (input ? fd_buffer_input : NULL,
input ? NULL : fd_buffer_output,
input ? NULL : fd_buffer_flush,
fd_buffer_block,
fd_buffer_shutdown,
memory,
n);
}
/* The buffer input function for a buffer built on a file descriptor. */
static int fd_buffer_input (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, need);
else
{
/* This case is not efficient. Fortunately, I don't think it
ever actually happens. */
error(1,0,"nonblocking fd - inneficient server");
}
if (nbytes > 0)
{
*got = nbytes;
return 0;
}
*got = 0;
if (nbytes == 0)
{
/* End of file. This assumes that we are using POSIX or BSD
style nonblocking I/O. On System V we will get a zero
return if there is no data, even when not at EOF. */
return -1;
}
/* Some error occurred. */
#ifdef WIN32
// Win32 always returns einval for a blocking read...
// .. and for an error condition.. arrgh!
if(errno == EINVAL)
errno=EAGAIN;
#endif
if (blocking_error (errno))
{
/* Everything's fine, we just didn't get any data. */
return 0;
}
/* Some other error, but no errno set */
if (!errno)
errno = EIO;
return errno;
}
/* The buffer output function for a buffer built on a file descriptor. */
static int fd_buffer_output (void *closure, const char *data, int have, int *wrote)
{
struct fd_buffer *fd = (struct fd_buffer *) closure;
*wrote = 0;
while (have > 0)
{
int nbytes;
nbytes = write (fd->fd, data, have);
if (nbytes <= 0)
{
if (! fd->blocking
&& (nbytes == 0 || blocking_error (errno))
)
{
/* A nonblocking write failed to write any data. Just
return. */
return 0;
}
/* Some sort of error occurred. */
if (nbytes == 0)
return EIO;
return errno;
}
*wrote += nbytes;
data += nbytes;
have -= nbytes;
}
return 0;
}
/* The buffer flush function for a buffer built on a file descriptor. */
/*ARGSUSED*/
static int fd_buffer_flush (void *closure)
{
/* Nothing to do. File descriptors are always flushed. */
/* .. except on Win32, which is wierd */
#ifdef _WIN32
struct fd_buffer *fd = (struct fd_buffer *) closure;
win32flush(fd->fd);
#endif
return 0;
}
/* The buffer block function for a buffer built on a file descriptor. */
static int fd_buffer_block (void *closure, int block)
{
#ifdef _WIN32
/* This should only be called for pipes... */
struct fd_buffer *fd = (struct fd_buffer *) closure;
fd->blocking = block;
win32setblock(fd->fd,block);
return 0;
#else
struct fd_buffer *fd = (struct fd_buffer *) closure;
int flags;
#ifdef F_GETFL
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;
#else
if (!block)
return 1;
#endif
fd->blocking = block;
return 0;
#endif
}
/* The buffer shutdown function for a buffer built on a file descriptor. */
static int fd_buffer_shutdown (void *closure)
{
xfree (closure);
return 0;
}
/* Initialize a buffer built on a custom client protocol. protocol is the
descriptor. INPUT is nonzero if this is for input, zero if this is
for output. MEMORY is the function to call when a memory error
occurs. */
static struct buffer *client_protocol_buffer_initialize (const struct protocol_interface *protocol, int input, void (*memory)(struct buffer *))
{
return buf_initialize (input ? client_protocol_buffer_input : NULL,
input ? NULL : client_protocol_buffer_output,
input ? NULL : client_protocol_buffer_flush,
client_protocol_buffer_block,
input ? NULL : client_protocol_buffer_shutdown, /* We only shutdown when buf_to_net is closed */
memory,
(void*)protocol);
}
/* The buffer input function for a client protocol buffer. */
static int client_protocol_buffer_input (void *closure, char *data, int need, int size, int *got)
{
const struct protocol_interface *protocol = (const struct protocol_interface *)closure;
int nbytes;
if(protocol && protocol->server_read_data)
nbytes = protocol->server_read_data(protocol, data, size);
else
nbytes = read(server_io_socket, data, size);
if (nbytes > 0)
{
*got = nbytes;
return 0;
}
*got = 0;
if (nbytes == 0)
{
/* End of file. This assumes that we are using POSIX or BSD
style nonblocking I/O. On System V we will get a zero
return if there is no data, even when not at EOF. */
return -1;
}
/* Some error occurred. */
if (blocking_error (errno))
{
/* Everything's fine, we just didn't get any data. */
return 0;
}
/* Some other error, but no errno set */
if (!errno)
errno = EIO;
return errno;
}
/* The buffer output function for a client prototocol buffer. */
static int client_protocol_buffer_output (void *closure, const char *data, int have, int *wrote)
{
const struct protocol_interface *protocol = (const struct protocol_interface *)closure;
*wrote = 0;
while (have > 0)
{
int nbytes;
if(protocol && protocol->server_write_data)
nbytes = protocol->server_write_data (protocol, data, have);
else
nbytes = write (server_io_socket?server_io_socket:STDOUT_FILENO, data, have);
if (nbytes <= 0)
{
/* Some sort of error occurred. */
if (nbytes == 0)
return EIO;
return errno;
}
*wrote += nbytes;
data += nbytes;
have -= nbytes;
}
return 0;
}
/* The buffer flush function for a buffer built on a file descriptor. */
/*ARGSUSED*/
static int client_protocol_buffer_flush (void *closure)
{
const struct protocol_interface *protocol = (const struct protocol_interface *)closure;
if(protocol && protocol->server_flush_data)
protocol->server_flush_data(protocol);
return 0;
}
/* The buffer block function for a client protocol buffer. */
static int client_protocol_buffer_block (void *closure, int block)
{
/* not sure what to do here... */
if (!block)
return 1;
return 0;
}
/* The buffer shutdown function for a client protocol buffer. */
static int client_protocol_buffer_shutdown (void *closure)
{
const struct protocol_interface *protocol = (const struct protocol_interface *)closure;
if(protocol && protocol->server_shutdown)
protocol->server_shutdown(protocol);
else if(server_io_socket)
{
#ifdef _WIN32
shutdown(_get_osfhandle(server_io_socket),2);
closesocket(_get_osfhandle(server_io_socket));
#else
shutdown(server_io_socket,2);
close(server_io_socket);
#endif
}
return 0;
}
/* Populate all of the directories between BASE_DIR and its relative
subdirectory DIR with CVSADM directories. Return 0 for success or
errno value. */
static int create_adm_p (char *base_dir, char *dir)
{
char *dir_where_cvsadm_lives, *dir_to_register, *p, *tmp;
int retval, done;
FILE *f;
if (strcmp (dir, ".") == 0)
return 0; /* nothing to do */
/* Allocate some space for our directory-munging string. */
p = (char*)xmalloc (strlen (dir) + 1);
if (p == NULL)
return ENOMEM;
dir_where_cvsadm_lives = (char*)xmalloc (strlen (base_dir) + strlen (dir) + 100);
if (dir_where_cvsadm_lives == NULL)
return ENOMEM;
/* Allocate some space for the temporary string in which we will
construct filenames. */
tmp = (char*)xmalloc (strlen (base_dir) + strlen (dir) + 100);
if (tmp == NULL)
return ENOMEM;
/* We make several passes through this loop. On the first pass,
we simply create the CVSADM directory in the deepest directory.
For each subsequent pass, we try to remove the last path
element from DIR, create the CVSADM directory in the remaining
pathname, and register the subdirectory in the newly created
CVSADM directory. */
retval = done = 0;
strcpy (p, dir);
strcpy (dir_where_cvsadm_lives, base_dir);
strcat (dir_where_cvsadm_lives, "/");
strcat (dir_where_cvsadm_lives, p);
dir_to_register = NULL;
while (1)
{
/* Create CVSADM. */
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM);
if ((CVS_MKDIR (tmp, 0777) < 0) && (errno != EEXIST))
{
retval = errno;
goto finish;
}
/* Create CVSADM_REP. */
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_REP);
if (! isfile (tmp))
{
/* Use Emptydir as the placeholder until the client sends
us the real value. This code is similar to checkout.c
(emptydir_name), but the code below returns errors
differently. */
char *empty;
empty = (char*)xmalloc (strlen (current_parsed_root->directory)
+ sizeof (CVSROOTADM)
+ sizeof (CVSNULLREPOS)
+ 3);
if (! empty)
{
retval = ENOMEM;
goto finish;
}
/* Create the directory name. */
(void) sprintf (empty, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSNULLREPOS);
/* Create the directory if it doesn't exist. */
if (! isfile (empty))
{
mode_t omask;
omask = umask (cvsumask);
if (CVS_MKDIR (empty, 0777) < 0)
{
retval = errno;
xfree (empty);
goto finish;
}
(void) umask (omask);
}
f = CVS_FOPEN (tmp, "w");
if (f == NULL)
{
retval = errno;
xfree (empty);
goto finish;
}
/* Write the directory name to CVSADM_REP. */
if (fprintf (f, "%s\n", empty) < 0)
{
retval = errno;
fclose (f);
xfree (empty);
goto finish;
}
if (fclose (f) == EOF)
{
retval = errno;
xfree (empty);
goto finish;
}
/* Clean up after ourselves. */
xfree (empty);
}
/* Create CVSADM_ENT. We open in append mode because we
don't want to clobber an existing Entries file. */
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_ENT);
f = CVS_FOPEN (tmp, "a");
if (f == NULL)
{
retval = errno;
goto finish;
}
if (fclose (f) == EOF)
{
retval = errno;
goto finish;
}
/* Create CVSADM_ENTEXT. We open in append mode because we
don't want to clobber an existing Entries.Extra file. */
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_ENTEXT);
f = CVS_FOPEN (tmp, "a");
if (f == NULL)
{
retval = errno;
goto finish;
}
if (fclose (f) == EOF)
{
retval = errno;
goto finish;
}
if (dir_to_register != NULL)
{
/* FIXME: Yes, this results in duplicate entries in the
Entries.Log file, but it doesn't currently matter. We
might need to change this later on to make sure that we
only write one entry. */
Subdir_Register ((List *) NULL, dir_where_cvsadm_lives,
dir_to_register);
}
if (done)
break;
dir_to_register = strrchr (p, '/');
if (dir_to_register == NULL)
{
dir_to_register = p;
strcpy (dir_where_cvsadm_lives, base_dir);
done = 1;
}
else
{
*dir_to_register = '\0';
dir_to_register++;
strcpy (dir_where_cvsadm_lives, base_dir);
strcat (dir_where_cvsadm_lives, "/");
strcat (dir_where_cvsadm_lives, p);
}
}
finish:
xfree (tmp);
xfree (dir_where_cvsadm_lives);
xfree (p);
return retval;
}
/*
* Make directory DIR, including all intermediate directories if necessary.
* Returns 0 for success or errno code.
*/
static int mkdir_p (char *dir)
{
char *p;
char *q = (char*)xmalloc (strlen (dir) + 1);
int retval;
if (q == NULL)
return ENOMEM;
retval = 0;
/*
* Skip over leading slash if present. We won't bother to try to
* make '/'.
*/
p = dir + 1;
while (1)
{
while (*p != '/' && *p != '\0')
++p;
if (*p == '/')
{
strncpy (q, dir, p - dir);
q[p - dir] = '\0';
if (q[p - dir - 1] != '/' && CVS_MKDIR (q, 0777) < 0)
{
int saved_errno = errno;
if (saved_errno != EEXIST
&& ((saved_errno != EACCES && saved_errno != EROFS)
|| !isdir (q)))
{
retval = saved_errno;
goto done;
}
}
++p;
}
else
{
if (CVS_MKDIR (dir, 0777) < 0)
retval = errno;
goto done;
}
}
done:
xfree (q);
return retval;
}
/*
* Print the error response for error code STATUS. The caller is
* reponsible for making sure we get back to the command loop without
* any further output occuring.
* Must be called only in contexts where it is OK to send output.
*/
static void print_error (int status)
{
char *msg;
char tmpstr[80];
buf_output0 (buf_to_net, "error ");
msg = strerror (status);
if (msg == NULL)
{
sprintf (tmpstr, "unknown error %d", status);
msg = tmpstr;
}
buf_output0 (buf_to_net, msg);
buf_append_char (buf_to_net, '\n');
buf_flush (buf_to_net, 0);
}
static int supported_response (char *name)
{
struct response *rs;
for (rs = responses; rs->name != NULL; ++rs)
if (strcmp (rs->name, name) == 0)
return rs->status == rs_supported;
error (1, 0, "internal error: testing support for unknown response %s?",name);
/* NOTREACHED */
return 0;
}
static void serve_valid_responses (char *arg)
{
char *p = arg;
char *q;
struct response *rs;
do
{
q = strchr (p, ' ');
if (q != NULL)
*q++ = '\0';
for (rs = responses; rs->name != NULL; ++rs)
{
if (strcmp (rs->name, p) == 0)
break;
}
if (rs->name == NULL)
/*
* It is a response we have never heard of (and thus never
* will want to use). So don't worry about it.
*/
;
else
rs->status = rs_supported;
p = q;
} while (q != NULL);
for (rs = responses; rs->name != NULL; ++rs)
{
if (rs->status == rs_essential)
{
buf_output0 (buf_to_net, "E response `");
buf_output0 (buf_to_net, rs->name);
buf_output0 (buf_to_net, "' not supported by client\nerror \n");
/* FIXME: This call to buf_flush could conceivably
cause deadlock, as noted in server_cleanup. */
buf_flush (buf_to_net, 1);
/* I'm doing this manually rather than via error_exit ()
because I'm not sure whether we want to call server_cleanup.
Needs more investigation.... */
#ifdef SYSTEM_CLEANUP
/* Hook for OS-specific behavior, for example socket subsystems on
NT and OS2 or dealing with windows and arguments on Mac. */
SYSTEM_CLEANUP ();
#endif
CCvsgui::Close(EXIT_FAILURE);
exit (EXIT_FAILURE);
}
else if (rs->status == rs_optional)
rs->status = rs_not_supported;
}
if(supported_response("EntriesExtra"))
compat_level = 1; /* CVSNT client */
else
compat_level = 0; /* Legacy client */
TRACE(3,"Client compatibility level is %d",compat_level);
}
static void serve_root (char *arg)
{
char *path;
const char *real_repository;
if(protocol_encryption_enabled != PROTOCOL_ENCRYPTION && client_protocol && client_protocol->valid_elements&flagAlwaysEncrypted)
{
buf_flush(stderr_buf,1);
buf_flush(stdout_buf,1);
protocol_encryption_enabled = PROTOCOL_ENCRYPTION;
}
if (!isabsolute (arg))
{
error(1,0,"Root %s must be an absolute pathname", arg);
return;
}
/* Sending "Root" twice is illegal.
The other way to handle a duplicate Root requests would be as a
request to clear out all state and start over as if it was a
new connection. Doing this would cause interoperability
headaches, so it should be a different request, if there is
any reason why such a feature is needed. */
if (current_parsed_root != NULL)
{
error(1,0,"Protocol error: Duplicate Root request, for %s", arg);
return;
}
#ifdef SERVER_SUPPORT
if (client_protocol && client_protocol->auth_repository != NULL)
{
if (fncmp (client_protocol->auth_repository, arg) != 0)
{
/* The explicitness is to aid people who are writing clients.
I don't see how this information could help an
attacker. */
error(1,0,"Protocol error: Root says \"%s\" but protocol says \"%s\"",
arg, client_protocol->auth_repository);
}
}
#endif
if (current_parsed_root != NULL)
free_cvsroot_t (current_parsed_root);
/* For gserver, which doesn't pass a root as part of its protocol, check for a valid root here */
/* For other protocols (pserver, ntserver) this is harmless duplication */
/* We can be called with 'cvs server' one of two ways. Either directly, from
a script or inetd, or from a wrapper which sets the allow-root directives. */
real_repository = arg;
int online = 1;
if(!root_allow_ok(arg,&real_repository,&online))
{
error(1,0,"%s: no such repository", arg);
}
if(!online)
{
error(1,0,"%s: repository is offline during serve_root", arg);
}
current_parsed_root = local_cvsroot(arg,real_repository);
umask(0);
do_chroot();
if (!client_protocol || !client_protocol->auth_repository )
{
parse_config( current_parsed_root->directory );
#ifdef _WIN32
if(!filenames_case_insensitive)
add_to_ci_directory_list(current_parsed_root->directory);
#endif
}
/* For pserver, this will already have happened, and the call will do
nothing. But for other protocols, we need to do it now. */
if(client_protocol && !client_protocol->auth_repository)
{
char *host_user;
/* If we haven't verified the user before, then do it here. The password will be NULL but we pass
it anyway just in case this changes in the future. */
host_user = check_password (client_protocol->auth_username, client_protocol->auth_password,
current_parsed_root->directory, /*user_token*/NULL);
if(!host_user)
{
CServerIo::log(CServerIo::logAuth,"login failure for %s on %s", client_protocol->auth_username, client_protocol->auth_repository);
error (1, 0,
"authorization failed: server %s rejected access to %s for user %s",
hostname, current_parsed_root->unparsed_directory, client_protocol->auth_username);
}
xfree(host_user);
}
path = (char*)xmalloc (strlen (current_parsed_root->directory)
+ sizeof (CVSROOTADM)
+ 2);
if (path == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
(void) sprintf (path, "%s/%s", current_parsed_root->directory, CVSROOTADM);
if (!isaccessible (path, R_OK | X_OK))
{
error(1,errno,"Cannot access %s", path);
}
xfree (path);
lock_register_client(CVS_Username,current_parsed_root->directory);
#ifdef HAVE_PUTENV
cvs_putenv(CVSROOT_ENV, current_parsed_root->directory);
#endif
/* Setup internal cvsignore/cvswrappers that will have been skipped earlier in main() */
ign_setup();
wrap_setup();
}
static void serve_valid_rcsoptions(char *arg)
{
if(valid_rcsoptions)
error(1,0,"Protocol error - Valid-RcsOptions sent more than once");
valid_rcsoptions = xstrdup(arg);
}
static int max_dotdot_limit = 0;
/* Is this pathname OK to recurse into when we are running as the server?
If not, call error() with a fatal error. */
void server_pathname_check (const char *path)
{
/* An absolute pathname is almost surely a path on the *client* machine,
and is unlikely to do us any good here. It also is probably capable
of being a security hole in the anonymous readonly case. */
if (isabsolute (path))
/* Giving an error is actually kind of a cop-out, in the sense
that it would be nice for "cvs co -d /foo/bar/baz" to work.
A quick fix in the server would be requiring Max-dotdot of
at least one if pathnames are absolute, and then putting
/abs/foo/bar/baz in the temp dir beside the /d/d/d stuff.
A cleaner fix in the server might be to decouple the
pathnames we pass back to the client from pathnames in our
temp directory (this would also probably remove the need
for Max-dotdot). A fix in the client would have the client
turn it into "cd /foo/bar; cvs co -d baz" (more or less).
This probably has some problems with pathnames which appear
in messages. */
error (1, 0, "absolute pathname `%s' illegal for server", path);
if (pathname_levels (path) > max_dotdot_limit)
{
/* Similar to the isabsolute case in security implications. */
error (0, 0, "protocol error: `%s' contains more leading ..", path);
error (1, 0, "than the %d which Max-dotdot specified",
max_dotdot_limit);
}
}
/* Is file or directory REPOS an absolute pathname within the
current_parsed_root->directory? If yes, return 0. If not, abort. */
static int outside_root (const char *repos)
{
size_t repos_len;
size_t root_len = strlen (current_parsed_root->unparsed_directory);
const char *cp;
repos_len = strlen(repos);
/* I think isabsolute (repos) should always be true, and that
any RELATIVE_REPOS stuff should only be in CVS/Repository
files, not the protocol (for compatibility), but I'm putting
in the isabsolute check just in case. */
if (!isabsolute (repos))
{
error(1,0,"protocol error: %s is not absolute", repos);
return 1;
}
if (repos_len < root_len || fnncmp (repos, current_parsed_root->unparsed_directory, root_len) != 0)
{
not_within:
error(1,0,"protocol error: directory '%s' not within root '%s'",
repos, current_parsed_root->unparsed_directory);
return 1;
}
cp=repos+root_len;
if (*cp)
{
if ((*cp) != '/')
goto not_within;
if (pathname_levels (cp) > 0)
goto not_within;
}
return 0;
}
/* Is file or directory FILE outside the current directory (that is, does
it contain '/')? If no, return 0. If yes, abort. */
static int outside_dir (char *file)
{
if (strchr (file, '/') != NULL)
{
error(1,0,"protocol error: directory '%s' not within current directory",
file);
return 1;
}
return 0;
}
/*
* Add as many directories to the temp directory as the client tells us it
* will use "..", so we never try to access something outside the temp
* directory via "..".
*/
static void serve_max_dotdot (char *arg)
{
int lim = atoi (arg);
int i;
char *p;
if (lim < 0 || lim > 20)
{
error(1,0,"Invalid value for max_dotdot");
return;
}
p = (char*)xmalloc (strlen (server_temp_dir) + (sizeof(CVSDUMMY)+5) * lim + 10);
if (p == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
strcpy (p, server_temp_dir);
for (i = 0; i < lim; ++i)
strcat (p, "/"CVSDUMMY);
if (server_temp_dir != orig_server_temp_dir)
xfree (server_temp_dir);
server_temp_dir = p;
max_dotdot_limit = lim;
}
static char *dir_name;
static void dirswitch (char *dir, char *repos)
{
int status;
FILE *f;
size_t dir_len;
server_write_entries();
server_write_renames();
/* Check for bad directory name.
FIXME: could/should unify these checks with server_pathname_check
except they need to report errors differently. */
if (isabsolute (dir))
{
error(1,0,"absolute pathname `%s' illegal for server", dir);
return;
}
if (pathname_levels (dir) > max_dotdot_limit)
{
error(1,0,"protocol error: `%s' has too many ..", dir);
return;
}
if(!strcmp(dir,".") && !strcmp(repos,current_parsed_root->unparsed_directory) && max_dotdot_limit > 0)
{
/* The client is in a subdirectory and has set us to the root. To stop the server parsing
outside the repository root and potentially leaking information to a hacker, we add
CVSDUMMY onto the end of the repository */
char *r, *p;
int n;
struct saved_cwd cwd;
save_cwd(&cwd);
if(CVS_CHDIR(server_temp_dir))
error(1,errno,"Couldn't change directory to %s",server_temp_dir);
for(n=0; n<max_dotdot_limit; n++)
CVS_CHDIR("..");
repos = Name_Repository(NULL,NULL);
restore_cwd(&cwd,NULL);
r = (char*)xmalloc(strlen(repos)+strlen(current_parsed_root->unparsed_directory)+(sizeof(CVSDUMMY)+2)*max_dotdot_limit+100);
sprintf(r,"%s/%s",current_parsed_root->unparsed_directory,relative_repos(repos));
xfree(repos);
p=r+strlen(r);
for(n=0; n<max_dotdot_limit; n++)
{
*(p++)='/';
strcpy(p,CVSDUMMY);
p+=sizeof(CVSDUMMY)-1;
}
*p='\0';
repos = r; /* We leak this, but it should only ever be once per session */
}
dir_len = strlen (dir);
/* Check for a trailing '/'. This is not ISDIRSEP because \ in the
protocol is an ordinary character, not a directory separator (of
course, it is perhaps unwise to use it in directory names, but that
is another issue). */
if (dir_len > 0 && dir[dir_len - 1] == '/')
{
error(1,0,"protocol error: invalid directory syntax in %s", dir);
return;
}
xfree (dir_name);
dir_name = (char*)xmalloc (strlen (server_temp_dir) + dir_len + 40);
if (dir_name == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
strcpy (dir_name, server_temp_dir);
if(*dir)
{
strcat (dir_name, "/");
strcat (dir_name, dir);
}
status = mkdir_p (dir_name);
if (status != 0
&& status != EEXIST)
{
error(1,errno,"cannot mkdir %s", dir_name);
return;
}
/* We need to create adm directories in all path elements because
we want the server to descend them, even if the client hasn't
sent the appropriate "Argument xxx" command to match the
already-sent "Directory xxx" command. See recurse.c
(start_recursion) for a big discussion of this. */
status = create_adm_p (server_temp_dir, dir);
if (status != 0)
{
error(1,errno,"cannot create_adm_p %s", dir_name);
return;
}
if ( CVS_CHDIR (dir_name) < 0)
{
error(1,errno,"cannot change to %s", fn_root(dir_name));
return;
}
/*
* This is pretty much like calling Create_Admin, but Create_Admin doesn't
* report errors in the right way for us.
*/
if ((CVS_MKDIR (CVSADM, 0777) < 0) && (errno != EEXIST))
{
error(1,errno,"cannot mkdir %s/%s",fn_root(dir_name),CVSADM);
return;
}
/* The following will overwrite the contents of CVSADM_REP. This
is the correct behavior -- mkdir_p may have written a
placeholder value to this file and we need to insert the
correct value. */
f = CVS_FOPEN (CVSADM_REP, "w");
if (f == NULL)
{
error(1,errno,"cannot open %s/%s", fn_root(dir_name), CVSADM_REP);
return;
}
if (fprintf (f, "%s", current_parsed_root->directory) < 0)
{
fclose (f);
error(1,errno,"error writing %s/%s", fn_root(dir_name), CVSADM_REP);
return;
}
if (fprintf (f, "%s", repos + strlen(current_parsed_root->unparsed_directory)) < 0)
{
fclose (f);
error(1,errno,"error writing %s/%s", fn_root(dir_name), CVSADM_REP);
return;
}
/* Non-remote CVS handles a module representing the entire tree
(e.g., an entry like ``world -a .'') by putting /. at the end
of the Repository file, so we do the same. */
if (strcmp (dir, ".") == 0
&& current_parsed_root != NULL
&& current_parsed_root->unparsed_directory != NULL
&& strcmp (current_parsed_root->unparsed_directory, repos) == 0)
{
if (fprintf (f, "/.") < 0)
{
fclose (f);
error(1,errno,"error writing %s/%s",fn_root(dir_name), CVSADM_REP);
return;
}
}
if (fprintf (f, "\n") < 0)
{
fclose (f);
error(1,errno,"error writing %s/%s",fn_root(dir_name), CVSADM_REP);
return;
}
if (fclose (f) == EOF)
{
error(1,errno,"error closing %s/%s",fn_root(dir_name), CVSADM_REP);
return;
}
/* We open in append mode because we don't want to clobber an
existing Entries file. */
f = CVS_FOPEN (CVSADM_ENT, "a");
if (f == NULL)
{
error(1,errno, "cannot open %s", CVSADM_ENT);
return;
}
if (fclose (f) == EOF)
{
error(1,errno,"cannot close %s",CVSADM_ENT);
return;
}
/* We open in append mode because we don't want to clobber an
existing Entries file. */
f = CVS_FOPEN (CVSADM_ENTEXT, "a");
if (f == NULL)
{
error(1,errno,"cannot open %s",CVSADM_ENTEXT);
return;
}
if (fclose (f) == EOF)
{
error(1,errno,"cannot close %s",CVSADM_ENTEXT);
return;
}
}
static void
serve_protocol_encrypt(char *arg)
{
if(protocol_encryption_enabled)
return;
if(client_protocol && !client_protocol->wrap && !(client_protocol->valid_elements&flagAlwaysEncrypted))
{
error(1,0,"Requested protocol does not support encryption");
return;
}
buf_flush(stderr_buf, 1);
buf_flush(stdout_buf, 1);
if(client_protocol->wrap)
{
buf_to_net = cvs_encrypt_wrap_buffer_initialize (buf_to_net, 0,
1,
buf_to_net->memory_error);
buf_from_net = cvs_encrypt_wrap_buffer_initialize (buf_from_net, 1,
1,
buf_from_net->memory_error);
cvs_protocol_wrap_set_buffer(stdout_buf, buf_to_net);
cvs_protocol_wrap_set_buffer(stderr_buf, buf_to_net);
}
protocol_encryption_enabled = PROTOCOL_ENCRYPTION;
}
static void
serve_protocol_authenticate(char *arg)
{
if(protocol_encryption_enabled)
return;
if(!client_protocol->wrap && !(client_protocol->valid_elements&flagAlwaysEncrypted))
{
error(1,0,"Requested protocol does not support authentication");
return;
}
buf_flush(stderr_buf, 1);
buf_flush(stdout_buf, 1);
if(client_protocol->wrap)
{
buf_to_net = cvs_encrypt_wrap_buffer_initialize (buf_to_net, 0,
0,
buf_to_net->memory_error);
buf_from_net = cvs_encrypt_wrap_buffer_initialize (buf_from_net, 1,
0,
buf_from_net->memory_error);
cvs_protocol_wrap_set_buffer(stdout_buf, buf_to_net);
cvs_protocol_wrap_set_buffer(stderr_buf, buf_to_net);
}
protocol_encryption_enabled = PROTOCOL_AUTHENTICATION;
}
static void serve_directory (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);
xfree (repos);
}
else if (status == -2)
{
error(1,ENOMEM,"Alloc failed");
return;
}
else
{
if (status == -1)
{
error(1,0,"end of file reading mode for %s", arg);
}
else
{
error(1,0,"error reading mode for %s", arg);
}
}
}
static void serve_static_directory (char *arg)
{
FILE *f;
f = CVS_FOPEN (CVSADM_ENTSTAT, "w+");
if (f == NULL)
{
error(1,errno,"cannot open %s", CVSADM_ENTSTAT);
return;
}
if (fclose (f) == EOF)
{
error(1,errno,"cannot close %s", CVSADM_ENTSTAT);
return;
}
}
static void serve_sticky (char *arg)
{
FILE *f;
/* Just in case of hand-editing... probably harmless but best to ignore it */
if(!arg)
return;
if((arg[0]=='T' || arg[0]=='N') && !RCS_check_tag(arg+1,true,true,false))
{
error(0,0,"Invalid directory sticky tag '%s' ignored",arg+1);
return;
}
else if(arg[0]!='D' && arg[0]!='T' && arg[0]!='N')
{
error(0,0,"Invalid directory sticky tag '%s' ignored",arg);
return;
}
f = CVS_FOPEN (CVSADM_TAG, "w+");
if (f == NULL)
{
error(1,errno,"cannot open %s", CVSADM_TAG);
return;
}
if (fprintf (f, "%s\n", arg) < 0)
{
error(1,errno,"cannot write to %s", CVSADM_TAG);
return;
}
if (fclose (f) == EOF)
{
error(1,errno,"cannot close %s", CVSADM_TAG);
return;
}
}
/*
* Read SIZE bytes from buf_from_net, write them to FILE.
*
* Currently this isn't really used for receiving parts of a file --
* the file is still sent over in one chunk. But if/when we get
* spiffy in-process gzip support working, perhaps the compressed
* pieces could be sent over as they're ready, if the network is fast
* enough. Or something.
*/
static void receive_partial_file (int size, int file, bool check_textfile, CMD5Calc* md5, bool& modified)
{
while (size > 0)
{
int status;
int nread;
char *data;
status = buf_read_data (buf_from_net, size, &data, &nread);
if (status != 0)
{
if (status == -2)
error(1,ENOMEM,"Alloc failed");
else
{
if (status == -1)
{
error(1,0,"premature end of file from client");
}
else
{
error(1,0,"error reading from client");
}
}
return;
}
size -= nread;
if(check_textfile && nread)
{
/* Don't allow people to corrupt their repositories, even accidentally */
if(memchr(data,'\r',nread)!=NULL)
{
CCodepage cdp;
char buffer2[MAX_PATH]="\0";
int setmem=1024000, checkit=0;
size_t nr = (size_t)nread;
if(!CGlobalSettings::GetGlobalValue("cvsnt","PServer","StripCrLf",buffer2,sizeof(buffer2)))
setmem = atoi(buffer2);
if(!CGlobalSettings::GetGlobalValue("cvsnt","PServer","StripCrLfCheck",buffer2,sizeof(buffer2)))
checkit = atoi(buffer2);
cdp.StripCrLf(data,nr,setmem,checkit);
nread = (int)nr;
modified = true;
}
}
if(md5)
md5->Update(data,nread);
while (nread > 0)
{
int nwrote;
nwrote = write (file, data, nread);
if (nwrote < 0)
{
error(1,errno,"unable to write");
/* Read and discard the file data. */
while (size > 0)
{
int status, nread;
char *data;
status = buf_read_data (buf_from_net, size, &data, &nread);
if (status != 0)
return;
size -= nread;
}
return;
}
nread -= nwrote;
data += nwrote;
}
}
}
/* Receive SIZE bytes, write to filename FILE. */
static void receive_file (int size, char *file, bool check_textfile)
{
int fd;
char *arg = file;
bool modified = false;
CMD5Calc *md5 = NULL;
if(checksum_valid)
md5 = new CMD5Calc;
/* Write the file. */
fd = CVS_OPEN (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0)
{
delete md5;
error(1,errno,"cannot open %s",arg);
return;
}
receive_partial_file (size, fd, check_textfile, md5, modified);
if (close (fd) < 0)
{
delete md5;
error(1,errno,"cannot close %s", arg);
return;
}
if(md5)
{
const char *chk = md5->Final();
/* If the file has been modified (client sent corrupt data) then we
can't check for corruption in the data stream. This case can't happen
with cvsnt clients, which currently are the only ones that send the checksum */
if(!modified && checksum_valid && strcmp(stored_checksum,chk))
{
error(1,errno,"Corrupt transmission of file '%s'. Check network.",fn_root(file));
}
delete md5;
}
checksum_valid = 0;
}
/* Kopt for the next file sent in Modified or Is-modified. */
static char *kopt;
/* Timestamp (Checkin-time) for next file sent in Modified or
Is-modified. */
static int checkin_time_valid;
static time_t checkin_time;
static void serve_is_modified(char *arg);
static kflag serve_file_kopt (char *arg);
static void serve_modified (char *arg)
{
int size, status;
char *size_text;
char *mode_text;
/*
* This used to return immediately if error_pending () was true.
* However, that fails, because it causes each line of the file to
* be echoed back to the client as an unrecognized command. The
* client isn't reading from the socket, so eventually both
* processes block trying to write to the other. Now, we try to
* read the file if we can.
*/
status = buf_read_line (buf_from_net, &mode_text, (int *) NULL);
if (status != 0)
{
if (status == -2)
error(1,ENOMEM,"Alloc failed");
else
{
if (status == -1)
error(1,0,"end of file reading mode for %s", arg);
else
error(1,0,"error reading mode for %s", arg);
}
return;
}
status = buf_read_line (buf_from_net, &size_text, (int *) NULL);
if (status != 0)
{
if (status == -2)
error(1,ENOMEM,"Alloc failed");
else
{
if (status == -1)
error(1,0,"end of file reading size for %s", arg);
else
error(1,0,"error reading size for %s", arg);
}
xfree (mode_text);
return;
}
size = atoi (size_text);
xfree (size_text);
if (outside_dir (arg))
{
xfree (mode_text);
return;
}
if (size >= 0)
receive_file (size, arg, !(serve_file_kopt(arg).flags&(KFLAG_BINARY|KFLAG_ENCODED)));
if (checkin_time_valid)
{
struct utimbuf t;
memset (&t, 0, sizeof (t));
t.modtime = t.actime = checkin_time;
if (utime (arg, &t) < 0)
{
xfree (mode_text);
error(1,errno,"cannot utime %s", arg);
return;
}
checkin_time_valid = 0;
}
{
int status = change_mode (arg, mode_text, 0);
xfree (mode_text);
if (status)
{
error(1,0,"cannot change mode for %s", fn_root(arg));
return;
}
}
/* Make sure that the Entries indicate the right kopt. We probably
could do this even in the non-kopt case and, I think, save a stat()
call in time_stamp_server. But for conservatism I'm leaving the
non-kopt case alone. */
if (kopt != NULL)
serve_is_modified (arg);
}
static void serve_enable_unchanged (char *arg)
{
}
struct an_entry {
struct an_entry *next;
char *entry;
char *entry_extra;
};
static struct an_entry *entries;
static rename_struct *server_renames;
static const char *virtual_repository;
static void serve_unchanged (char *arg)
{
struct an_entry *p;
char *name;
char *cp;
char *timefield;
if (outside_dir (arg))
return;
/* Rewrite entries file to have `=' in timestamp field. */
for (p = entries; p != NULL; p = p->next)
{
name = p->entry + 1;
cp = strchr (name, '/');
if (cp != NULL
&& strlen (arg) == cp - name
&& strncmp (arg, name, cp - name) == 0)
{
timefield = strchr (cp + 1, '/') + 1;
if (*timefield != UNCHANGED_CHAR && *timefield!=MODIFIED_CHAR && *timefield!=DATE_CHAR)
{
cp = timefield + strlen (timefield);
cp[1] = '\0';
while (cp > timefield)
{
*cp = cp[-1];
--cp;
}
*timefield = UNCHANGED_CHAR;
}
break;
}
}
}
static void serve_is_modified(char *arg)
{
struct an_entry *p;
char *name;
char *cp;
char *timefield;
/* Have we found this file in "entries" yet. */
int found;
if (outside_dir (arg))
return;
/* Rewrite entries file to have `!' in timestamp field. */
found = 0;
for (p = entries; p != NULL; p = p->next)
{
name = p->entry + 1;
cp = strchr (name, '/');
if (cp != NULL
&& strlen (arg) == cp - name
&& strncmp (arg, name, cp - name) == 0)
{
timefield = strchr (cp + 1, '/') + 1;
if (*timefield != UNCHANGED_CHAR && *timefield!=MODIFIED_CHAR && *timefield!=DATE_CHAR)
{
cp = timefield + strlen (timefield);
cp[1] = '\0';
while (cp > timefield)
{
*cp = cp[-1];
--cp;
}
*timefield = MODIFIED_CHAR;
}
if (kopt != NULL)
{
xfree (kopt);
error(1,0,"protocol error: both Kopt and Entry for %s",
arg);
return;
}
found = 1;
break;
}
}
if (!found)
{
/* We got Is-modified but no Entry. Add a dummy entry.
The "D" timestamp is what makes it a dummy. */
p = (struct an_entry *) xmalloc (sizeof (struct an_entry));
if (p == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
p->entry = (char*)xmalloc (strlen (arg) + 80);
if (p->entry == NULL)
{
xfree (p);
error(1,ENOMEM,"Alloc failed");
return;
}
strcpy (p->entry, "/");
strcat (p->entry, arg);
strcat (p->entry, "//D/");
if (kopt != NULL)
{
strcat (p->entry, kopt);
xfree (kopt);
kopt = NULL;
}
strcat (p->entry, "/");
p->entry_extra = (char*)xmalloc (strlen (arg) + 80);
if (p->entry_extra == NULL)
{
xfree (p);
error(1,ENOMEM,"Alloc failed");
return;
}
strcpy (p->entry_extra, "/");
strcat (p->entry_extra, arg);
strcat (p->entry_extra, "/");
p->next = entries;
entries = p;
}
}
static void serve_entry (char *arg)
{
struct an_entry *p;
char *cp;
p = (struct an_entry *) xmalloc (sizeof (struct an_entry));
if (p == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
/* Leave space for serve_unchanged to write '=' if it wants. */
cp = (char*)xmalloc (strlen (arg) + 2);
if (cp == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
strcpy (cp, arg);
p->next = entries;
p->entry = cp;
p->entry_extra = (char*)xmalloc (strlen (arg) + 80);
if (p->entry_extra == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
strcpy (p->entry_extra, "/");
strcat (p->entry_extra, arg);
strcat (p->entry_extra, "/");
entries = p;
}
/* This must be sent directly after the Entry line above to work properly */
static void serve_entry_extra(char *arg)
{
struct an_entry *p = entries;
xfree(p->entry_extra);
p->entry_extra = xstrdup(arg);
}
static void serve_kopt (char *arg)
{
if (kopt != NULL)
{
error(1,0,"protocol error: duplicate Kopt request: %s", arg);
return;
}
/* Do some sanity checks. In particular, that it is not too long.
This lets the rest of the code not worry so much about buffer
overrun attacks. Probably should call RCS_check_kflag here,
but that would mean changing RCS_check_kflag to handle errors
other than via exit(), fprintf(), and such. */
if (strlen (arg) > 30)
{
error(1,0,"protocol error: invalid Kopt request: %s", arg);
return;
}
if(arg[0]=='-' && arg[1]=='k')
arg+=2;
kopt = (char*)xmalloc (strlen (arg) + 1);
if (kopt == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
strcpy (kopt, arg);
}
static kflag serve_file_kopt (char *arg)
{
struct an_entry *p;
char *name;
char *cp;
char *timefield, *optfield;
kflag kf;
if(kopt)
{
RCS_get_kflags(kopt,false,kf);
return kf;
}
if (outside_dir (arg))
{
RCS_get_kflags(NULL,false,kf);
return kf;
}
/* Find entry and return kopt */
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)
{
timefield = strchr (cp + 1, '/') + 1;
optfield = timefield?strchr(timefield,'/')+1:NULL;
if(optfield)
{
cp = strchr(optfield,'/');
if(cp) *cp='\0';
if(optfield[0]=='-' && optfield[1]=='k')
optfield+=2;
RCS_get_kflags(optfield,false,kf);
if(cp) *cp='/';
return kf;
}
}
break;
}
RCS_get_kflags(NULL,false,kf);
return kf;
}
static void serve_checkin_time (char *arg)
{
if (checkin_time_valid)
{
error(1,0,"protocol error: duplicate Checkin-time request: %s",
arg);
return;
}
checkin_time = get_date (arg, NULL);
if (checkin_time == (time_t)-1)
{
error(1,0,"cannot parse date %s", arg);
return;
}
checkin_time_valid = 1;
}
static void serve_checksum (char *arg)
{
if (checksum_valid)
{
error(1,0,"protocol error: duplicate Checksum request: %s", arg);
return;
}
if(!arg || strlen(arg)!=32)
{
error(1,0,"protocol error: invalid Checksum request: %s",arg);
return;
}
strcpy(stored_checksum,arg);
checksum_valid = 1;
}
static void server_write_entries()
{
FILE *f,*fx;
struct an_entry *p;
struct an_entry *q;
if (entries == NULL)
return;
f = fx = NULL;
/* Note that we free all the entries regardless of errors. */
/* We open in append mode because we don't want to clobber an
existing Entries file. If we are checking out a module
which explicitly lists more than one file in a particular
directory, then we will wind up calling
server_write_entries for each such file. */
f = CVS_FOPEN (CVSADM_ENT, "a");
if (f == NULL)
{
error(1,errno,"cannot open %s", CVSADM_ENT);
}
fx = CVS_FOPEN (CVSADM_ENTEXT, "a");
if (fx == NULL)
{
error(1,errno,"cannot open %s", CVSADM_ENTEXT);
}
for (p = entries; p != NULL;)
{
if (fprintf (f, "%s\n", p->entry) < 0)
{
error(1,errno,"cannot write to %s", CVSADM_ENT);
}
if (fprintf (fx, "%s\n", p->entry_extra) < 0)
{
error(1,errno,"cannot write to %s", CVSADM_ENTEXT);
}
xfree (p->entry);
q = p->next;
xfree (p);
p = q;
}
entries = NULL;
if (f != NULL && fclose (f) == EOF)
{
error(1,errno,"cannot close %s", CVSADM_ENT);
}
if (fx != NULL && fclose (fx) == EOF)
{
error(1,errno,"cannot close %s", CVSADM_ENTEXT);
}
}
static void server_write_renames()
{
FILE *f;
rename_struct *p;
rename_struct *q;
if (server_renames)
{
f = CVS_FOPEN (CVSADM_RENAME, "w");
if (f == NULL)
{
error(1,errno,"cannot open %s", CVSADM_RENAME);
}
for (p = server_renames; p != NULL;)
{
if (fprintf (f, "%s\n", p->from) < 0)
{
error(1,errno,"cannot write to %s", CVSADM_RENAME);
}
if (fprintf (f, "%s\n", p->to) < 0)
{
error(1,errno,"cannot write to %s", CVSADM_RENAME);
}
xfree (p->from);
xfree (p->to);
q = p->next;
xfree (p);
p = q;
}
server_renames = NULL;
if (f != NULL && fclose (f) == EOF)
{
error(1,errno,"cannot close %s", CVSADM_RENAME);
}
}
if(virtual_repository)
{
f = CVS_FOPEN (CVSADM_VIRTREPOS, "w");
if (f == NULL)
{
error(1,errno,"cannot open %s", CVSADM_VIRTREPOS);
}
fprintf(f,"%s\n", virtual_repository);
xfree(virtual_repository);
if (f != NULL && fclose (f) == EOF)
{
error(1,errno,"cannot close %s", CVSADM_VIRTREPOS);
}
}
}
struct notify_note {
/* Directory in which this notification happens. malloc'd*/
char *dir;
/* malloc'd. */
char *filename;
/* The following three all in one malloc'd block, pointed to by TYPE.
Each '\0' terminated. */
/* "E" or "U". */
char *type;
char *time;
char *hostname;
char *pathname;
char *watches;
char *tag;
char *flags;
char *bugid;
char *message;
/* User doing notify */
char *user;
struct notify_note *next;
};
static struct notify_note *notify_list;
/* Used while building list, to point to the last node that already exists. */
static struct notify_note *last_node;
static char *notify_user;
static void serve_notify_user(char *arg)
{
if(!verify_admin())
error(1,0,"Only repository administrators can unedit other users.");
notify_user = xstrdup(arg);
}
static void serve_notify (char *arg)
{
char buffer2[MAX_PATH]="\0";
int bugsmandatory=0;
struct notify_note *pnew = NULL;
char *data = NULL;
int status;
if (outside_dir (arg))
return;
if (dir_name == NULL)
goto error;
pnew = (struct notify_note *) xmalloc (sizeof (struct notify_note));
if (pnew == NULL)
{
error(1,ENOMEM,"Alloc failed");
}
pnew->dir = (char*)xmalloc (strlen (dir_name) + 1);
pnew->filename = (char*)xmalloc (strlen (arg) + 1);
if (pnew->dir == NULL || pnew->filename == NULL)
{
if (pnew->dir != NULL)
xfree (pnew->dir);
xfree (pnew);
error(1,ENOMEM,"Alloc failed");
return;
}
strcpy (pnew->dir, dir_name);
strcpy (pnew->filename, arg);
pnew->user = notify_user?xstrdup(notify_user):xstrdup(getcaller());
status = buf_read_line (buf_from_net, &data, (int *) NULL);
if (status != 0)
{
if (status == -2)
error(1,ENOMEM,"Alloc failed");
else
{
if (status == -1)
error(1,0,"end of file reading notification for %s", arg);
else
error(1,0,"error reading notification for %s", arg);
}
xfree (pnew->filename);
xfree (pnew->dir);
xfree (pnew->user);
xfree (pnew);
}
else
{
char *cp;
pnew->type = data;
if (data[1] != '\t')
goto error;
data[1] = '\0';
cp = data + 2;
pnew->time = cp;
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*(cp++)='\0';
pnew->hostname = cp;
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*(cp++)='\0';
pnew->pathname = cp;
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*cp++ = '\0';
pnew->watches = cp;
cp = strchr (cp, '\t');
if (cp != NULL) /* Old-style notifies stopped here */
{
*(cp++)='\0';
pnew->tag = cp;
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*(cp++)='\0';
pnew->flags = cp;
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*cp++ = '\0';
pnew->bugid = cp;
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*cp++ = '\0';
pnew->message = cp;
cp = strchr (cp, '\t');
if (cp != NULL)
{
*(cp++)='\0';
}
if(!*pnew->bugid)
pnew->bugid=NULL;
if(!*pnew->tag)
pnew->tag=NULL;
}
else
{
pnew->flags = NULL;
pnew->tag = NULL;
pnew->bugid = NULL;
pnew->message = NULL;
}
TRACE(3,"serve_notify(type = %s, time = %s, hostname = %s, bug = %s, tag = %s, message = %s)",
PATCH_NULL(pnew->type), PATCH_NULL(pnew->time), PATCH_NULL(pnew->hostname),
PATCH_NULL(pnew->bugid), PATCH_NULL(pnew->tag), PATCH_NULL(pnew->message));
// if the type is E and the bug number is not supplied and the bug number is mandatory then issue an error
// what the client does with the error is not considered here
if (pnew->type!=NULL)
{
if (*pnew->type=='E')
{
int isbugerr=0;
if (pnew->bugid==NULL)
isbugerr=1;
else
if (*pnew->bugid=='\0')
isbugerr=1;
if (isbugerr)
{
// this is an "edit" request
if(!CGlobalSettings::GetGlobalValue("cvsnt","PServer","BugsMandatory",buffer2,sizeof(buffer2)))
bugsmandatory = atoi(buffer2);
if (bugsmandatory!=0)
{
global_edit_bugnum_hack=1;
if (global_editors_bugnum_hack)
error(1,0,"Protocol error; bug number is required.");
}
}
}
}
pnew->next = NULL;
if (last_node == NULL)
{
notify_list = pnew;
}
else
last_node->next = pnew;
last_node = pnew;
}
return;
error:
xfree (data);
xfree (pnew);
error(1,0,"Protocol error; misformed Notify request");
}
/* Process all the Notify requests that we have stored up. Returns 0
if successful, if not prints error message (via error()) and
returns negative value. */
static int server_notify ()
{
struct notify_note *p;
char *repos;
const char *mapped_repos;
int can_notify;
int err = 0;
if(!notify_list)
return 0;
can_notify = check_command_legal_p("edit"); /* Readers can't notify */
err = 0;
/* For edit/unedit these are sent without a prior command */
if(!server_command_name)
server_command_name=(*notify_list->type=='U')?"unedit":"edit";
while (notify_list != NULL)
{
if ( CVS_CHDIR (notify_list->dir) < 0)
{
error (0, errno, "cannot change to %s", fn_root(notify_list->dir));
return -1;
}
repos = Name_Repository (NULL, NULL);
mapped_repos = map_repository(repos);
xfree(repos);
repos = (char*)mapped_repos;
if (can_notify)
{
lock_dir_for_write (repos);
fileattr_startdir (repos);
err += notify_do (*notify_list->type, notify_list->filename, notify_list->user,
notify_list->time, notify_list->hostname, notify_list->pathname, notify_list->watches, repos, notify_list->tag, notify_list->flags, notify_list->bugid, notify_list->message);
}
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');
xfree (repos);
if (can_notify)
{
fileattr_write ();
fileattr_free ();
Lock_Cleanup ();
}
p = notify_list->next;
xfree (notify_list->type); /* Start of buffer, all other pointers are relative to this */
xfree (notify_list);
notify_list = p;
}
last_node = NULL;
xfree(notify_user);
/* The code used to call fflush (stdout) here, but that is no
longer necessary. The data is now buffered in buf_to_net,
which will be flushed by the caller, do_cvs_command. */
return err;
}
static int argument_count;
static char **argument_vector;
static int argument_vector_size;
static void serve_argument (char *arg)
{
char *p;
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)
{
error(1,ENOMEM,"Alloc failed");
return;
}
}
p = (char*)xmalloc (strlen (arg) + 1);
if (p == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
strcpy (p, arg);
argument_vector[argument_count++] = p;
}
static void serve_argumentx (char *arg)
{
char *p;
if(argument_count<=1)
{
error(1,0,"Argumentx protocol violation");
}
p = argument_vector[argument_count - 1];
p = (char*)xrealloc(p, strlen (p) + 1 + strlen (arg) + 1);
if (p == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
strcat (p, "\n");
strcat (p, arg);
argument_vector[argument_count - 1] = p;
}
static void serve_global_option (char *arg)
{
if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0')
{
error_return:
error(1,0,"Protocol error: bad global option %s",
arg);
return;
}
switch (arg[1])
{
case 'n':
noexec = 1;
break;
case 'q':
quiet = 1;
break;
case 'r':
cvswrite = 0;
break;
case 'Q':
really_quiet = 1;
break;
case 'l':
break;
case 't':
trace++;
CServerIo::loglevel(trace);
break;
default:
goto error_return;
}
}
static void serve_set (char *arg)
{
variable_set (arg);
}
static void serve_questionable (char *arg)
{
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 void serve_utf8 (char *arg)
{
}
static void authentication_requested(char *flag)
{
if(encryption_level == 1 && client_protocol && client_protocol->wrap)
*flag = 1;
else
*flag = 0;
}
static void encryption_requested(char *flag)
{
if(encryption_level == 2 && client_protocol && client_protocol->wrap)
*flag = 1;
else
*flag = 0;
}
static void authentication_required(char *flag)
{
if(encryption_level == 3)
*flag = 1;
else
*flag = 0;
}
static void encryption_required(char *flag)
{
if(encryption_level == 4)
*flag = 1;
else
*flag = 0;
}
static void compression_requested(char *flag)
{
if(compression_level == 1)
*flag = 1;
else
*flag = 0;
}
static void compression_required(char *flag)
{
if(compression_level == 2)
*flag = 1;
else
*flag = 0;
}
static void server_can_rename(char *flag)
{
*flag = 1;
}
static void outbuf_memory_error(struct buffer *buf)
{
static const char msg[] = "E Fatal server error\n\
error ENOMEM Virtual memory exhausted.\n";
/*
* We have arranged things so that printing this now either will
* be legal, or the "E fatal error" line will get glommed onto the
* end of an existing "E" or "M" response.
*/
/* If this gives an error, not much we could do. syslog() it? */
write (STDOUT_FILENO, msg, sizeof (msg) - 1);
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR, "virtual memory exhausted");
#endif
error_exit ();
}
/* If command is legal, return 1.
* Else if command is illegal and croak_on_illegal is set, then die.
* Else just return 0 to indicate that command is illegal.
*/
static int check_command_legal_p (const char *cmd_name)
{
/* Right now, only pserver notices illegal commands -- namely,
* write attempts by a read-only user. Therefore, if CVS_Username
* is not set, this just returns 1, because CVS_Username unset
* means pserver is not active.
*/
#ifdef SERVER_SUPPORT
if (CVS_Username == NULL)
return 1;
if (lookup_command_attribute (cmd_name) & CVS_CMD_MODIFIES_REPOSITORY)
{
/* This command has the potential to modify the repository, so
* we check if the user have permission to do that.
*
* (Only relevant for remote users -- local users can do
* whatever normal Unix file permissions allow them to do.)
*
* The decision method:
*
* If $CVSROOT/CVSADMROOT_READERS exists and user is listed
* in it, then read-only access for user.
*
* Or if $CVSROOT/CVSADMROOT_WRITERS exists and user NOT
* listed in it, then also read-only access for user.
*
* Else read-write access for user.
*/
if(read_only_server) /* Server is running read only */
return 0;
char *linebuf = NULL;
int num_red = 0;
size_t linebuf_len = 0;
char *fname;
size_t flen;
FILE *fp;
int found_it = 0;
/* else */
flen = strlen (current_parsed_root->directory)
+ strlen (CVSROOTADM)
+ strlen (CVSROOTADM_READERS)
+ 3;
fname = (char*)xmalloc (flen);
(void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSROOTADM_READERS);
fp = fopen (fname, "r");
if (fp == NULL)
{
if (!existence_error (errno))
{
/* Need to deny access, so that attackers can't fool
us with some sort of denial of service attack. */
error (0, errno, "cannot open %s", fname);
xfree (fname);
return 0;
}
}
else /* successfully opened readers file */
{
while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0)
{
/* Hmmm, is it worth importing my own readline
library into CVS? It takes care of chopping
leading and trailing whitespace, "#" comments, and
newlines automatically when so requested. Would
save some code here... -kff */
/* Chop newline by hand, for strcmp()'s sake. */
if (linebuf[num_red - 1] == '\n')
linebuf[num_red - 1] = '\0';
if (fncmp (linebuf, CVS_Username) == 0)
goto handle_illegal;
}
if (num_red < 0 && !feof (fp))
error (0, errno, "cannot read %s", fname);
/* If not listed specifically as a reader, then this user
has write access by default unless writers are also
specified in a file . */
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", fname);
}
xfree (fname);
/* Now check the writers file. */
flen = strlen (current_parsed_root->directory)
+ strlen (CVSROOTADM)
+ strlen (CVSROOTADM_WRITERS)
+ 3;
fname = (char*)xmalloc (flen);
(void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSROOTADM_WRITERS);
fp = fopen (fname, "r");
if (fp == NULL)
{
if (linebuf)
xfree (linebuf);
if (existence_error (errno))
{
/* Writers file does not exist, so everyone is a writer,
by default. */
xfree (fname);
return 1;
}
else
{
/* Need to deny access, so that attackers can't fool
us with some sort of denial of service attack. */
error (0, errno, "cannot read %s", fname);
xfree (fname);
return 0;
}
}
found_it = 0;
while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0)
{
/* Chop newline by hand, for strcmp()'s sake. */
if (linebuf[num_red - 1] == '\n')
linebuf[num_red - 1] = '\0';
if (fncmp (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)
xfree (linebuf);
xfree (fname);
return 1;
}
else /* writers file exists, but this user not listed in it */
{
handle_illegal:
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", fname);
if (linebuf)
xfree (linebuf);
xfree (fname);
return 0;
}
}
#endif /* SERVER_SUPPORT */
/* If ever reach end of this function, command must be legal. */
return 1;
}
static void do_cvs_command (const char *cmd_name, int (*command)(int argc, char **argv))
{
int errs;
CTriggerLibrary lib;
server_write_entries();
server_write_renames();
/* Global `command_name' is probably "server" right now -- only
serve_export() sets it to anything else. So we will use local
parameter `cmd_name' to determine if this command is legal for
this user. */
if (!check_command_legal_p (cmd_name))
{
buf_output0 (buf_to_net, "E ");
buf_output0 (buf_to_net, program_name);
buf_output0 (buf_to_net, " [server aborted]: \"");
buf_output0 (buf_to_net, cmd_name);
buf_output0 (buf_to_net, "\" requires write access to the repository\n\
error \n");
goto free_args_and_return;
}
server_command_name = cmd_name; /* This is set in server_main too... just to be safe */
server_notify ();
/* Flush out any pending data. */
buf_flush (buf_to_net, 1);
errs = server_main(cmd_name, command);
buf_flush(stderr_buf, 1);
buf_flush(stdout_buf, 1);
server_notify ();
lib.CloseAllTriggers(); /* This command has finished */
tf_loaded=false;
if (errs)
/* We will have printed an error message already. */
buf_output0 (buf_to_net, "error \n");
else
buf_output0 (buf_to_net, "ok\n");
free_args_and_return:
/* Now free the arguments. */
{
/* argument_vector[0] is a dummy argument, we don't mess with it. */
char **cp;
for (cp = argument_vector + 1;
cp < argument_vector + argument_count;
++cp)
xfree (*cp);
argument_count = 1;
}
/* Flush out any data not yet sent. */
set_block (buf_to_net);
buf_flush (buf_to_net, 1);
return;
}
/* Called by error_exit() to flush final error messages before closedown */
void server_error_exit()
{
buf_output0 (buf_to_net, "error \n");
set_block (buf_to_net);
buf_flush (buf_to_net, 1);
}
/* This variable commented in server.h. */
char *server_dir = NULL;
static void output_dir(const char *update_dir, const char *repository)
{
/* If we have a CVS root prefix set, don't transmit it back to the
client. */
if (server_dir != NULL)
{
buf_output0(buf_to_net,client_where(server_dir));
buf_output0(buf_to_net,"/");
}
if (update_dir[0] == '\0')
buf_output0(buf_to_net,".");
else
buf_output0(buf_to_net,client_where(update_dir));
buf_output0(buf_to_net,"/\n");
buf_output0(buf_to_net,current_parsed_root->unparsed_directory);
if(strlen(repository)>strlen(current_parsed_root->directory))
buf_output0(buf_to_net,repository+strlen(current_parsed_root->directory));
buf_output0(buf_to_net,"/");
}
/* This feeds temporary renames back to the client */
int send_rename_to_client(const char *oldfile, const char *newfile)
{
if(supported_response("Rename"))
{
buf_output0(buf_to_net,"Rename ");
buf_output0(buf_to_net,oldfile);
buf_output0(buf_to_net,"\n");
buf_output0(buf_to_net,newfile);
buf_output0(buf_to_net,"\n");
}
else
{
error(0,0,"Client doesn't support rename response.");
}
return 0;
}
/* Cause the client to physically rename a file */
int server_rename_file(const char *oldfile, const char *newfile)
{
if(supported_response("Renamed"))
{
buf_output0(buf_to_net,"Renamed ./\n");
buf_output0(buf_to_net,oldfile);
buf_output0(buf_to_net,"\n");
buf_output0(buf_to_net,newfile);
buf_output0(buf_to_net,"\n");
}
return 0;
}
int client_can_rename()
{
return supported_response("Renamed");
}
int reset_client_mapping(const char *update_dir, const char *repository)
{
if(supported_response("Clear-rename"))
{
buf_output0(buf_to_net,"Clear-rename ");
output_dir (update_dir, repository);
buf_output0(buf_to_net,"\n");
}
else
{
error(0,0,"Client doesn't support clear-rename response. How did we get here???");
}
return 0;
}
/*
* Entries line that we are squirreling away to send to the client when
* we are ready.
*/
static char *entries_line;
static char *entries_ex_line;
/*
* File which has been Scratch_File'd, we are squirreling away that fact
* to inform the client when we are ready.
*/
static char *scratched_file;
/*
* The scratched_file will need to be removed as well as having its entry
* removed.
*/
static int kill_scratched_file;
void server_register(const char *name, const char *version, const char *timestamp, const char *options,
const char *tag, const char *date, const char *conflict, const char *merge_from_tag_1,
const char *merge_from_tag_2, time_t rcs_timestamp,
const char *edit_revision, const char *edit_tag, const char *edit_bugid)
{
int len;
char *temp_options;
if (options == NULL)
options = "";
TRACE(1,"server_register(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
PATCH_NULL(name), PATCH_NULL(version), timestamp ? timestamp : "", PATCH_NULL(options),
tag ? tag : "", date ? date : "",
conflict ? conflict : "",
merge_from_tag_1 ? merge_from_tag_1 : "",
merge_from_tag_2 ? merge_from_tag_2 : "",
edit_revision?edit_revision:"",
edit_tag?edit_tag:"",
edit_bugid?edit_bugid:"");
if (entries_line != NULL)
{
/*
* If CVS decides to Register it more than once (which happens
* on "cvs update foo/foo.c" where foo and foo.c are already
* checked out), use the last of the entries lines Register'd.
*/
xfree (entries_line);
}
if (entries_ex_line != NULL)
{
xfree (entries_ex_line);
}
/*
* I have reports of Scratch_Entry and Register both happening, in
* two different cases. Using the last one which happens is almost
* surely correct; I haven't tracked down why they both happen (or
* even verified that they are for the same file).
*/
if (scratched_file != NULL)
{
xfree (scratched_file);
scratched_file = NULL;
}
len = (strlen (name) + strlen (version) + strlen (options) + 80);
if (tag)
len += strlen (tag);
if (date)
len += strlen (date);
if (timestamp)
len += strlen (timestamp);
entries_line = (char*)xmalloc (len);
entries_ex_line = (char*)xmalloc(len);
sprintf (entries_line, "/%s/%s/", name, version);
if (conflict != NULL)
strcat (entries_line, "+=");
strcat (entries_line, "/");
/* Filter out any unrecognised client options */
if(options[0])
{
temp_options = normalise_options(options,0,name);
if(temp_options)
{
strcat (entries_line, "-k");
strcat (entries_line, temp_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);
}
/* Entries.Extra stuff */
sprintf (entries_ex_line, "/%s/%s/%s/", name, merge_from_tag_1 ? merge_from_tag_1 : "",merge_from_tag_2 ? merge_from_tag_2 : "");
if(rcs_timestamp!=(time_t)-1)
sprintf (entries_ex_line+strlen(entries_ex_line), "%"TIME_T_SPRINTF"d", rcs_timestamp);
sprintf(entries_ex_line+strlen(entries_ex_line),"/%s/%s/%s/", edit_revision?edit_revision:"",edit_tag?edit_tag:"",edit_bugid?edit_bugid:"");
}
void server_scratch (const char *fname)
{
/*
* I have reports of Scratch_Entry and Register both happening, in
* two different cases. Using the last one which happens is almost
* surely correct; I haven't tracked down why they both happen (or
* even verified that they are for the same file).
*
* Don't know if this is what whoever wrote the above comment was
* talking about, but this can happen in the case where a join
* removes a file - the call to Register puts the '-vers' into the
* Entries file after the file is removed
*/
if (entries_line != NULL)
{
xfree (entries_line);
entries_line = NULL;
}
if (entries_ex_line != NULL)
{
xfree (entries_ex_line);
entries_ex_line = NULL;
}
if (scratched_file != NULL)
{
buf_output0(buf_to_net,"E CVS server internal error: duplicate Scratch_Entry\n");
return;
}
scratched_file = xstrdup (fname);
kill_scratched_file = 1;
}
void
server_scratch_entry_only ()
{
kill_scratched_file = 0;
}
/* Print a new entries line, from a previous server_register. */
static void new_entries_line ()
{
if (entries_line)
{
buf_output0(buf_to_net,entries_line);
buf_output0(buf_to_net,"\n");
}
else
{
/* Return the error message as the Entries line. */
buf_output0(buf_to_net,"E CVS server internal error: Register missing\n");
}
xfree (entries_line);
entries_line = NULL;
}
static void new_entries_ex_line ()
{
if(supported_response("EntriesExtra"))
{
if (entries_ex_line)
{
buf_output0(buf_to_net,"EntriesExtra ");
buf_output0(buf_to_net,entries_ex_line);
buf_output0(buf_to_net,"\n");
}
}
xfree (entries_ex_line);
entries_ex_line = NULL;
}
static void serve_ci (char *arg)
{
if (global_edit_bugnum_hack)
error(1,0,"Protocol error; bug number is required on commit.");
global_editors_bugnum_hack=1;
do_cvs_command ("commit", commit);
}
static void checked_in_response (const char *file, const char *update_dir, const char *repository)
{
if (supported_response ("Mode"))
{
struct stat sb;
char *mode_string;
if ( CVS_STAT (file, &sb) < 0)
{
/* Not clear to me why the file would fail to exist, but it
was happening somewhere in the testsuite. */
if (!existence_error (errno))
error (0, errno, "cannot stat %s", file);
}
else
{
buf_output0(buf_to_net,"Mode ");
mode_string = mode_to_string (sb.st_mode);
buf_output0(buf_to_net,mode_string);
buf_output0(buf_to_net,"\n");
xfree (mode_string);
}
}
new_entries_ex_line();
buf_output0(buf_to_net,"Checked-in ");
output_dir (update_dir, repository);
buf_output0(buf_to_net,file);
buf_output0(buf_to_net,"\n");
new_entries_line ();
}
void server_checked_in (const char *file, const char *update_dir, const char *repository)
{
if (noexec)
return;
if (scratched_file != NULL && entries_line == NULL)
{
/*
* This happens if we are now doing a "cvs remove" after a previous
* "cvs add" (without a "cvs ci" in between).
*/
buf_output0(buf_to_net,"Remove-entry ");
output_dir (update_dir, repository);
buf_output0(buf_to_net,file);
buf_output0(buf_to_net,"\n");
xfree (scratched_file);
scratched_file = NULL;
}
else
{
checked_in_response (file, update_dir, repository);
}
}
void serve_chown (char *arg)
{
do_cvs_command ("chown", chowner);
}
void serve_rchown (char *arg)
{
command_name = "rchown";
do_cvs_command ("rchown", chowner);
}
void serve_chacl (char *arg)
{
do_cvs_command ("chacl", chacl);
}
void serve_rchacl (char *arg)
{
command_name = "rchacl";
do_cvs_command ("rchacl", chacl);
}
void serve_lsacl (char *arg)
{
do_cvs_command ("lsacl", lsacl);
}
void serve_rlsacl (char *arg)
{
command_name = "rlsacl";
do_cvs_command ("rlsacl", lsacl);
}
void serve_passwd (char *arg)
{
do_cvs_command ("passwd", passwd);
}
void serve_info (char *arg)
{
do_cvs_command ("info", info);
}
void server_update_entries (const char *file, const char *update_dir, const char *repository, enum server_updated_arg4 updated)
{
if (noexec)
return;
if (updated == SERVER_UPDATED)
checked_in_response (file, update_dir, repository);
else
{
if (!supported_response ("New-entry"))
return;
buf_output0(buf_to_net,"New-entry ");
output_dir (update_dir, repository);
buf_output0(buf_to_net,file);
buf_output0(buf_to_net,"\n");
new_entries_line ();
}
}
static void serve_update (char *arg)
{
do_cvs_command ("update", update);
}
static void serve_diff (char *arg)
{
do_cvs_command ("diff", diff);
}
static void serve_log (char *arg)
{
do_cvs_command ("log", cvslog);
}
static void serve_rlog (char *arg)
{
/* Tell cvslog() to behave like rlog not log. */
command_name = "rlog";
do_cvs_command ("rlog", cvslog);
}
static void serve_add (char *arg)
{
do_cvs_command ("add", add);
}
static void serve_remove(char *arg)
{
do_cvs_command("remove", cvsremove);
}
static void serve_status (char *arg)
{
do_cvs_command ("status", cvsstatus);
}
static void serve_ls (char *arg)
{
do_cvs_command ("ls", ls);
}
static void serve_rdiff (char *arg)
{
do_cvs_command ("rdiff", patch);
}
static void serve_tag (char *arg)
{
do_cvs_command ("tag", cvstag);
}
static void serve_rtag (char *arg)
{
/* Tell cvstag() to behave like rtag not tag. */
command_name = "rtag";
do_cvs_command ("rtag", cvstag);
}
static void serve_import (char *arg)
{
do_cvs_command ("import", import);
}
static void serve_admin (char *arg)
{
do_cvs_command ("admin", admin);
}
static void serve_history (char *arg)
{
do_cvs_command ("history", history);
}
static void serve_release (char *arg)
{
do_cvs_command ("release", release);
}
static void serve_watch_on (char *arg)
{
do_cvs_command ("watch", watch_on);
}
static void serve_watch_off (char *arg)
{
do_cvs_command ("watch", watch_off);
}
static void serve_watch_add (char *arg)
{
do_cvs_command ("watch", watch_add);
}
static void serve_watch_remove (char *arg)
{
do_cvs_command ("watch", watch_remove);
}
static void serve_watchers (char *arg)
{
do_cvs_command ("watchers", watchers);
}
static void serve_editors (char *arg)
{
do_cvs_command ("editors", editors);
}
static void serve_editors_edit (char *arg)
{
if (global_edit_bugnum_hack)
error(1,0,"Protocol error; bug number is required on edit.");
global_editors_bugnum_hack=1;
serve_argument("-c");
do_cvs_command ("editors", editors);
}
static void serve_rename(char *arg)
{
rename_struct *p, *r;
int status;
p = (rename_struct *) xmalloc (sizeof (rename_struct));
if (p == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
p->next = NULL;
p->from = xstrdup(arg);
if (p->from == NULL)
{
error(1,ENOMEM,"Alloc failed");
return;
}
if (isabsolute(p->from) || strstr(p->from,".."))
{
error(1,0,"Protocol error: Bad rename %s",p->from);
xfree(p);
return;
}
status = buf_read_line (buf_from_net, &p->to, (int *) NULL);
if (status == 0)
{
if (isabsolute(p->to) || strstr(p->to,".."))
{
error(1,0,"Protocol error: Bad rename %s",p->to);
xfree(p);
return;
}
}
else if (status == -2)
{
xfree(p);
error(1,ENOMEM,"Alloc failed");
return;
}
else
{
if (status == -1)
{
xfree(p);
error(1,0,"end of file reading rename for %s", arg);
}
else
{
xfree(p);
error(1,0,"error reading rename for %s", arg);
}
}
if(!server_renames)
server_renames = p;
else
{
r=server_renames;
while(r->next)
r=r->next;
r->next=p;
}
}
static void serve_virtual_repository(char *arg)
{
virtual_repository = xstrdup(arg);
if (isabsolute(virtual_repository) || strstr(virtual_repository,".."))
{
error(1,0,"Protocol error: Bad virtual repository %s",virtual_repository);
xfree(virtual_repository);
return;
}
}
// Duplicate the old modules expansion
// We don't want to call do_module, as it's overkill plus it isn't compatible
// with future core versions, which work entirely differently
static void serve_expand_modules(char *arg)
{
int err = 0;
cvs::filename fn;
std::map<cvs::filename,std::vector<cvs::filename> > alias_map;
std::map<cvs::filename,cvs::filename> dir_map;
cvs::sprintf(fn,128,"%s/%s/%s",current_parsed_root->directory,CVSROOTADM,CVSROOTADM_MODULES);
CFileAccess acc;
std::vector<cvs::filename> args;
if(acc.open(fn.c_str(),"r"))
{
cvs::string line;
while(acc.getline(line))
{
if(!line.size() || line[0]=='#')
continue;
CTokenLine tok(line.c_str());
if(tok.size()<3)
continue;
if(!strcmp(tok[1],"-a"))
{
std::vector<cvs::filename>&v = alias_map[tok[0]];
for(int j=2; j<tok.size(); j++)
v.push_back(tok[j]);
}
else for(int j=1; j<tok.size() && tok[j][0]=='-'; j++)
{
if(!strcmp(tok[j],"-d"))
{
dir_map[tok[0]]=tok[j+1];
break;
}
}
}
acc.close();
}
for (int i = 1; i < argument_count; i++)
args.push_back(argument_vector[i]);
for (size_t i = 0; i < args.size(); i++)
{
cvs::filename frst,nxt;
size_t p = args[i].find_first_of('/');
if(p!=cvs::filename::npos)
{
frst=args[i].substr(0,p);
nxt=args[i].substr(p+1);
}
else
{
frst=args[i];
nxt="";
}
/* Alias modules ditch the filename/directory parts of the expansion. This is consistent
with cvshome 1.11 behaviour, although I'm not sure it's really correct */
if(alias_map.find(frst)!=alias_map.end())
{
std::vector<cvs::filename>&v = alias_map[frst];
args.insert(args.begin()+i+1,v.begin(),v.end());
}
else if(dir_map.find(frst)!=dir_map.end())
{
buf_output0 (buf_to_net, "Module-expansion ");
buf_output0 (buf_to_net, dir_map[frst].c_str());
if(nxt.size())
{
buf_output0 (buf_to_net, "/");
buf_output0 (buf_to_net, nxt.c_str());
}
buf_output0 (buf_to_net, "\n");
}
else
{
/* Note that we don't check for module existence here... that is done
in the main checkout routine. */
buf_output0 (buf_to_net, "Module-expansion ");
buf_output0 (buf_to_net, args[i].c_str());
buf_output0 (buf_to_net, "\n");
}
}
/* argument_vector[0] is a dummy argument, we don't mess with it. */
/* Reset the argument vector */
char **cp;
for (cp = argument_vector + 1; cp < argument_vector + argument_count; ++cp)
xfree (*cp);
argument_count = 1;
buf_output0 (buf_to_net, "ok\n");
/* The client is waiting for the module expansions, so we must
send the output now. */
buf_flush (buf_to_net, 1);
}
static void serve_noop (char *arg)
{
server_write_entries();
server_write_renames();
if(server_notify ())
buf_output0 (buf_to_net, "error \n");
else
buf_output0 (buf_to_net, "ok\n");
buf_flush (buf_to_net, 1);
}
static void serve_version (char *arg)
{
do_cvs_command ("version", version);
}
static void serve_read_cvsrc(char *arg)
{
char *cvsrc;
cvsrc = (char*)xmalloc (strlen (current_parsed_root->directory)
+ sizeof (CVSROOTADM)
+ sizeof (CVSROOTADM_CVSRC)
+ 3);
sprintf (cvsrc, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSROOTADM_CVSRC);
cvs_flushout();
if(isfile (cvsrc))
{
FILE *f = fopen(cvsrc,"r");
if(f)
{
char *buf;
off_t len;
CVS_FSEEK(f,0,SEEK_END);
len=CVS_FTELL(f);
if(len>0)
{
CVS_FSEEK(f,0,SEEK_SET);
buf=(char*)xmalloc(len+1);
fread(buf,1,len,f);
if(buf[len-1]!='\n')
buf[len++]='\n';
buf_output(buf_to_net,buf, len);
xfree(buf);
}
fclose(f);
}
}
buf_output0(buf_to_net,"end-cvsrc\n");
cvs_flushout();
xfree(cvsrc);
}
static void serve_read_generic(char *file)
{
char *cvsfile;
cvsfile = (char*)xmalloc (strlen (current_parsed_root->directory)
+ sizeof (CVSROOTADM)
+ strlen(file)
+ 3);
sprintf (cvsfile, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, file);
cvs_flushout();
if(isfile (cvsfile))
{
FILE *f = fopen(cvsfile,"r");
if(f)
{
char *buf;
char num[16];
off_t len;
fseek(f,0,SEEK_END);
len=ftell(f);
if(len>0)
{
fseek(f,0,SEEK_SET);
buf=(char*)xmalloc(len+1);
fread(buf,1,len,f);
if(buf[len-1]!='\n')
buf[len++]='\n';
snprintf(num,sizeof(num),"%lu\n",(long)len);
buf_output0(buf_to_net, num);
buf_output(buf_to_net,buf, len);
xfree(buf);
}
else
buf_output0(buf_to_net,"0\n");
fclose(f);
}
else
buf_output0(buf_to_net,"0\n");
}
else
buf_output0(buf_to_net,"0\n");
cvs_flushout();
xfree(cvsfile);
}
static void serve_read_cvsrc2(char *arg)
{
serve_read_generic(CVSROOTADM_CVSRC);
}
static void serve_server_codepage(char *arg)
{
#if defined(_WIN32) && defined(_UNICODE)
if(win32_global_codepage==CP_UTF8)
buf_output0(buf_to_net,"UTF-8");
else
#endif
buf_output0(buf_to_net,CCodepage::GetDefaultCharset());
buf_output0(buf_to_net,"\n");
buf_flush(buf_to_net,0);
}
static void serve_client_version(char *arg)
{
buf_output0(buf_to_net,"CVSNT "CVSNT_PRODUCTVERSION_STRING"\n");
buf_flush(buf_to_net,0);
serv_client_version = xstrdup(arg);
}
static void serve_read_cvswrappers(char *arg)
{
serve_read_generic(CVSROOTADM_WRAPPER);
}
static void serve_read_cvsignore(char *arg)
{
serve_read_generic(CVSROOTADM_IGNORE);
}
static void serve_error_if_reader(char *arg)
{
if(!check_command_legal_p("edit"))
error(1,0,"%s",arg);
}
static void serve_init (char *arg)
{
do_cvs_command ("init", init);
}
static void serve_annotate (char *arg)
{
do_cvs_command ("annotate", annotate);
}
static void serve_rannotate (char *arg)
{
/* Tell annotate() to behave like rannotate not annotate. */
command_name = "rannotate";
do_cvs_command ("rannotate", annotate);
}
static void serve_co (char *arg)
{
char *tempdir;
int status;
if (!isdir (CVSADM))
{
/*
* The client has not sent a "Repository" line. Check out
* into a pristine directory.
*/
tempdir = (char*)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);
xfree (tempdir);
return;
}
if ( CVS_CHDIR (tempdir) < 0)
{
buf_output0 (buf_to_net, "E Cannot change to directory ");
buf_output0 (buf_to_net, fn_root(tempdir));
buf_append_char (buf_to_net, '\n');
print_error (errno);
xfree (tempdir);
return;
}
xfree (tempdir);
}
/* Compensate for server_export()'s setting of command_name.
*
* [It probably doesn't matter if do_cvs_command() gets "export"
* or "checkout", but we ought to be accurate where possible.]
*/
do_cvs_command ((strcmp (command_name, "export") == 0) ?
"export" : "checkout",
checkout);
}
static void serve_export (char *arg)
{
/* Tell checkout() to behave like export not checkout. */
command_name = "export";
serve_co (arg);
}
void server_copy_file (const char *file, const char *update_dir, const char *repository, const char *newfile)
{
/* At least for now, our practice is to have the server enforce
noexec for the repository and the client enforce it for the
working directory. This might want more thought, and/or
documentation in cvsclient.texi (other responses do it
differently). */
if (!supported_response ("Copy-file"))
return;
buf_output0(buf_to_net,"Copy-file ");
output_dir (update_dir, repository);
buf_output0(buf_to_net,file);
buf_output0(buf_to_net,"\n");
buf_output0(buf_to_net,newfile);
buf_output0(buf_to_net,"\n");
}
/* See server.h for description. */
void server_modtime (struct file_info *finfo, Vers_TS *vers_ts)
{
char date[MAXDATELEN];
char outdate[MAXDATELEN];
assert (vers_ts->vn_rcs != NULL);
if (!supported_response ("Mod-time"))
return;
if (RCS_getrevtime (finfo->rcs, vers_ts->vn_rcs, date, 0) == (time_t) -1)
/* FIXME? should we be printing some kind of warning? For one
thing I'm not 100% sure whether this happens in non-error
circumstances. */
return;
date_to_internet (outdate, date);
buf_output0(buf_to_net,"Mod-time ");
buf_output0(buf_to_net,outdate);
buf_output0(buf_to_net,"\n");
}
/* See server.h for description. */
void server_updated (
struct file_info *finfo,
Vers_TS *vers,
enum server_updated_arg4 updated,
mode_t mode,
CMD5Calc* md5,
struct buffer *filebuf)
{
char *mode_string;
TRACE(3,"server_updated(%s,%04o)",PATCH_NULL(finfo->file),mode);
if (noexec)
{
/* Hmm, maybe if we did the same thing for entries_file, we
could get rid of the kludges in server_register and
server_scratch which refrain from warning if both
Scratch_Entry and Register get called. Maybe. */
if (scratched_file)
{
xfree (scratched_file);
scratched_file = NULL;
}
return;
}
if (entries_line != NULL && scratched_file == NULL)
{
FILE *f;
struct buffer_data *list, *last;
unsigned long size;
/* The contents of the file will be in one of filebuf,
list/last, or here. */
unsigned char *file;
size_t file_allocated;
size_t file_used;
if (filebuf != NULL)
{
size = buf_length (filebuf);
if (mode == (mode_t) -1)
error (1, 0, "CVS server internal error: no mode in server_updated");
}
else
{
struct stat sb;
if ( CVS_STAT (finfo->file, &sb) < 0)
{
if (existence_error (errno))
{
/* If we have a sticky tag for a branch on which
the file is dead, and cvs update the directory,
it gets a T_CHECKOUT but no file. So in this
case just forget the whole thing. */
xfree (entries_line);
entries_line = NULL;
goto done;
}
error (1, errno, "reading %s", fn_root(finfo->fullname));
}
size = sb.st_size;
if (mode == (mode_t) -1)
{
/* FIXME: When we check out files the umask of the
server (set in .bashrc if rsh is in use) affects
what mode we send, and it shouldn't. */
mode = sb.st_mode;
}
}
if (md5)
{
static int checksum_supported = -1;
if (checksum_supported == -1)
{
checksum_supported = supported_response ("Checksum");
}
if (checksum_supported)
{
buf_output0(buf_to_net,"Checksum ");
buf_output0(buf_to_net,md5->Final());
buf_output0(buf_to_net,"\n");
}
}
new_entries_ex_line();
if (updated == SERVER_UPDATED)
{
Node *node;
Entnode *entnode;
if (!(supported_response ("Created")
&& supported_response ("Update-existing")))
buf_output0(buf_to_net,"Updated ");
else
{
assert (vers != NULL);
if (vers->ts_user == NULL)
buf_output0(buf_to_net,"Created ");
else
buf_output0(buf_to_net,"Update-existing ");
}
/* Now munge the entries to say that the file is unmodified,
in case we end up processing it again (e.g. modules3-6
in the testsuite). */
node = findnode_fn (finfo->entries, finfo->file);
entnode = (Entnode *)node->data;
xfree (entnode->timestamp);
entnode->timestamp = xstrdup (UNCHANGED_CHAR_S);
}
else if (updated == SERVER_MERGED)
buf_output0(buf_to_net,"Merged ");
else if (updated == SERVER_PATCHED)
buf_output0(buf_to_net,"Patched ");
else if (updated == SERVER_RCS_DIFF)
buf_output0(buf_to_net,"Rcs-diff ");
else
error(1,0,"Internal error - invalid update type");
output_dir (finfo->update_dir, finfo->repository);
buf_output0(buf_to_net,finfo->file);
buf_output0(buf_to_net,"\n");
new_entries_line ();
mode_string = mode_to_string (mode);
buf_output0(buf_to_net,mode_string);
buf_output0(buf_to_net,"\n");
xfree (mode_string);
list = last = NULL;
file = NULL;
file_allocated = 0;
file_used = 0;
if (size > 0)
{
/* Throughout this section we use binary mode to read the
file we are sending. The client handles any line ending
translation if necessary. */
if (filebuf == NULL)
{
long status;
f = CVS_FOPEN (finfo->file, "rb");
if (f == NULL)
error (1, errno, "reading %s", fn_root(finfo->fullname));
status = buf_read_file (f, size, &list, &last);
if (status != 0)
error (1, ferror (f) ? errno : 0, "reading %s",
fn_root(finfo->fullname));
if (fclose (f) == EOF)
error (1, errno, "reading %s", fn_root(finfo->fullname));
}
}
{
char text[16];
sprintf(text,"%lu\n",size);
buf_output0(buf_to_net,text);
}
if (file != NULL)
{
buf_output (buf_to_net, (char *) file, file_used);
xfree (file);
file = NULL;
}
else if (filebuf == NULL)
buf_append_data (buf_to_net, list, last);
else
{
buf_append_buffer (buf_to_net, filebuf);
buf_free (filebuf);
}
/* Note we only send a newline here if the file ended with one. */
/*
* Avoid using up too much disk space for temporary files.
* A file which does not exist indicates that the file is up-to-date,
* which is now the case. If this is SERVER_MERGED, the file is
* not up-to-date, and we indicate that by leaving the file there.
* I'm thinking of cases like "cvs update foo/foo.c foo".
*/
if ((updated == SERVER_UPDATED
|| updated == SERVER_PATCHED
|| updated == SERVER_RCS_DIFF)
&& filebuf == NULL
/* But if we are joining, we'll need the file when we call
join_file. */
&& !joining ())
{
if (CVS_UNLINK (finfo->file) < 0)
error (0, errno, "cannot remove temp file for %s",
fn_root(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);
xfree (scratched_file);
scratched_file = NULL;
if (kill_scratched_file)
buf_output0(buf_to_net,"Removed ");
else
buf_output0(buf_to_net,"Remove-entry ");
output_dir (finfo->update_dir, finfo->repository);
buf_output0(buf_to_net,finfo->file);
buf_output0(buf_to_net,"\n");
/* keep the vers structure up to date in case we do a join
* - if there isn't a file, it can't very well have a version number, can it?
*
* we do it here on the assumption that since we just told the client
* to remove the file/entry, it will, and we want to remember that.
* If it fails, that's the client's problem, not ours
*/
if (vers && vers->vn_user != NULL)
{
xfree (vers->vn_user);
vers->vn_user = NULL;
}
if (vers && vers->ts_user != NULL)
{
xfree (vers->ts_user);
vers->ts_user = NULL;
}
}
else if (scratched_file == NULL && entries_line == NULL)
{
/*
* This can happen with death support if we were processing
* a dead file in a checkout.
*/
}
else
error (1, 0,
"CVS server internal error: Register *and* Scratch_Entry.\n");
done:;
}
/* Return whether we should send patches in RCS format. */
int server_use_rcs_diff ()
{
return supported_response ("Rcs-diff");
}
void server_set_entstat (const char *update_dir, const char *repository)
{
static int set_static_supported = -1;
if (set_static_supported == -1)
set_static_supported = supported_response ("Set-static-directory");
if (!set_static_supported) return;
buf_output0(buf_to_net,"Set-static-directory ");
output_dir(update_dir, repository);
buf_output0(buf_to_net,"\n");
}
void server_clear_entstat (const char *update_dir, const char *repository)
{
static int clear_static_supported = -1;
if (clear_static_supported == -1)
clear_static_supported = supported_response ("Clear-static-directory");
if (!clear_static_supported) return;
if (noexec)
return;
buf_output0(buf_to_net,"Clear-static-directory ");
output_dir (update_dir, repository);
buf_output0(buf_to_net,"\n");
}
void server_set_sticky (const char *update_dir, const char *repository, const char *tag, const char *date, int nonbranch, const char *version)
{
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 && version==NULL)
{
buf_output0(buf_to_net,"Clear-sticky ");
output_dir (update_dir, repository);
buf_output0(buf_to_net,"\n");
}
else
{
buf_output0(buf_to_net,"Set-sticky ");
output_dir (update_dir, repository);
buf_output0(buf_to_net,"\n");
if (tag != NULL)
{
if (nonbranch)
buf_output0(buf_to_net,"N");
else
buf_output0(buf_to_net,"T");
buf_output0(buf_to_net,tag);
if(!nonbranch && version)
{
buf_output0(buf_to_net,":");
buf_output0(buf_to_net,version);
}
}
else if(date != NULL)
{
buf_output0(buf_to_net,"D");
buf_output0(buf_to_net,date);
}
else
{
buf_output0(buf_to_net,"T:");
buf_output0(buf_to_net,version);
}
buf_output0(buf_to_net,"\n");
}
}
struct template_proc_data
{
const char *directory;
const char *repository;
const char *update_dir;
};
static int template_proc (void *params, const trigger_interface *cb)
{
struct template_proc_data *data = (template_proc_data *)params;
TRACE(1,"template_proc(%s)",PATCH_NULL(data->directory));
if(cb->get_template)
{
const char *template_ptr = NULL;
if (!supported_response ("Template"))
/* Might want to warn the user that the rcsinfo feature won't work. */
return 0;
if(!cb->get_template(cb, data->directory, &template_ptr) && template_ptr)
{
char buf[32];
buf_output0(buf_to_net,"Template ");
output_dir (data->update_dir, data->repository);
buf_output0(buf_to_net,"\n");
snprintf(buf,sizeof(buf),"%ld\n",(long)strlen(template_ptr));
buf_output0(buf_to_net,buf);
buf_output0(buf_to_net,template_ptr);
}
else
TRACE(1,"get_template returned failure");
}
return 0;
}
void server_template (const char *update_dir, const char *repository)
{
struct template_proc_data data;
data.directory = Short_Repository(repository);
data.repository = repository;
data.update_dir = update_dir;
TRACE(3,"run template proc");
run_trigger(&data,template_proc);
}
static void serve_gzip_stream (char *arg)
{
int level;
level = atoi (arg);
if (level == 0)
level = 6;
if(protocol_compression_enabled)
return;
/* All further communication with the client will be compressed. */
buf_flush(stderr_buf, 1);
buf_flush(stdout_buf, 1);
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);
cvs_protocol_wrap_set_buffer(stdout_buf, buf_to_net);
cvs_protocol_wrap_set_buffer(stderr_buf, buf_to_net);
protocol_compression_enabled = 1;
}
/* Tell the client about RCS options set in CVSROOT/cvswrappers. */
static void serve_wrapper_sendme_rcs_options(char *arg)
{
/* Actually, this is kind of sdrawkcab-ssa: the client wants
* verbatim lines from a cvswrappers file, but the server has
* already parsed the cvswrappers file into the wrap_list struct.
* Therefore, the server loops over wrap_list, unparsing each
* entry before sending it.
*/
cvs::string wrapper_line;
/* We do this here & make the command RQ_ROOTLESS because all
* commands send before encryption is enabled must be rootless -
* old clients sent this command before enabling encryption (doh!)
*/
if (current_parsed_root == NULL)
error(1,0,"Protocol error: Root request missing");
bool first=true;
while(wrap_unparse_rcs_options(wrapper_line,first))
{
buf_output0 (buf_to_net, "Wrapper-rcsOption ");
buf_output0 (buf_to_net, wrapper_line.c_str());
buf_output0 (buf_to_net, "\n");;
}
buf_output0 (buf_to_net, "ok\n");
/* The client is waiting for us, so we better send the data now. */
buf_flush (buf_to_net, 1);
}
static void serve_ignore (char *arg)
{
/*
* Just ignore this command. This is used to support the
* update-patches command, which is not a real command, but a signal
* to the client that update will accept the -u argument.
*/
}
#endif // SERVER_SUPPORT
static void serve_valid_requests(char *arg);
/*
* Parts of this table are shared with the client code,
* but the client doesn't need to know about the handler
* functions.
*/
/* The code lets all RQ_ROOTLESS requests pass through unencrypted
* and unsigned even when message encryption/authentication is required
* (option "MessageProtection"). This was done for compatibility with
* CVS 1.11.1.1p1 and earlier (which send the encryption/authentication
* request after Root, UseUnchanged, and the gzip requests). Those requests
* are "safe enough" to not require encryption or authentication (although
* CVSNT 1.11.1.4+ will try to encrypt the Root request because I suppose
* it might be sensitive). If any RQ_ROOTLESS requests are added
* that need to be encrypted, additional flags will need to be introduced
* to identify which commands may be safely processed without integrity
* checks. Similarly, if CVS 1.11.1.1p1 compatibility is not required,
* then the additional flags could be added to require that Root, etc.
* come after the encryption/authentication request.
*/
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("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("EntryExtra", serve_entry_extra, 0),
REQ_LINE("Kopt", serve_kopt, 0),
REQ_LINE("Checkin-time", serve_checkin_time, 0),
REQ_LINE("Checksum", serve_checksum, 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("NotifyUser", serve_notify_user, 0),
REQ_LINE("Questionable", serve_questionable, 0),
REQ_LINE("Case", NULL, 0), /* Depreciated */
REQ_LINE("Utf8", serve_utf8, RQ_ROOTLESS), /* Depreciated, but checked for option support by server */
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, RQ_ROOTLESS),
REQ_LINE("wrapper-sendme-rcsOptions", serve_wrapper_sendme_rcs_options, RQ_ROOTLESS),
REQ_LINE("Set", serve_set, RQ_ROOTLESS),
REQ_LINE("Rename", serve_rename, 0),
REQ_LINE("VirtualRepository", serve_virtual_repository, 0),
REQ_LINE("expand-modules", serve_expand_modules, 0),
REQ_LINE("ci", serve_ci, RQ_ESSENTIAL),
REQ_LINE("co", serve_co, RQ_ESSENTIAL),
REQ_LINE("chown", serve_chown, 0),
REQ_LINE("rchown", serve_rchown, 0),
REQ_LINE("chacl2", serve_chacl, 0),
REQ_LINE("rchacl2", serve_rchacl, 0),
REQ_LINE("lsacl", serve_lsacl, 0),
REQ_LINE("rlsacl", serve_rlsacl, 0),
REQ_LINE("passwd", serve_passwd, 0),
REQ_LINE("info", serve_info, 0),
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("status", serve_status, 0),
REQ_LINE("ls", serve_ls, 0),
REQ_LINE("rls", serve_ls, 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("editors-edit", serve_editors_edit, 0),
REQ_LINE("init", serve_init, 0),
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),
/* The client should never actually send this request */
REQ_LINE("Rootless-stream-modification", serve_noop, 0),
REQ_LINE("Kerberos-encrypt", serve_protocol_encrypt, RQ_ROOTLESS),
REQ_LINE("Gssapi-encrypt", serve_protocol_encrypt, RQ_ROOTLESS),
REQ_LINE("Protocol-encrypt", serve_protocol_encrypt, RQ_ROOTLESS),
REQ_LINE("Gssapi-authenticate", serve_protocol_authenticate, RQ_ROOTLESS),
REQ_LINE("Protocol-authenticate", serve_protocol_authenticate, RQ_ROOTLESS),
REQ_LINE("read-cvsrc", serve_read_cvsrc, 0),
REQ_LINE("read-cvsrc2", serve_read_cvsrc2, 0),
REQ_LINE("read-cvsignore", serve_read_cvsignore, 0),
REQ_LINE("read-cvswrappers", serve_read_cvswrappers, 0),
REQ_LINE("Error-If-Reader", serve_error_if_reader, 0),
REQ_LINE("server-codepage", serve_server_codepage, 0),
REQ_LINE("client-version", serve_client_version, 0),
REQ_LINE("Authentication-Requested", authentication_requested, RQ_SERVER_REQUEST),
REQ_LINE("Authentication-Required", authentication_required, RQ_SERVER_REQUEST),
REQ_LINE("Encryption-Requested", encryption_requested, RQ_SERVER_REQUEST),
REQ_LINE("Encryption-Required", encryption_required, RQ_SERVER_REQUEST),
REQ_LINE("Compression-Requested", compression_requested, RQ_SERVER_REQUEST),
REQ_LINE("Compression-Required", compression_required, RQ_SERVER_REQUEST),
REQ_LINE("Can-Rename", server_can_rename, RQ_SERVER_REQUEST),
REQ_LINE("Valid-RcsOptions", serve_valid_rcsoptions, 0),
REQ_LINE(NULL,NULL,0)
#undef REQ_LINE
};
#ifdef _WIN32
#ifndef CVS95
HANDLE win32_memory_heap = 0;
#endif
#endif
#ifdef SERVER_SUPPORT
static void serve_valid_requests (char *arg)
{
struct request *rq;
buf_output0 (buf_to_net, "Valid-requests");
for (rq = requests; rq->name != NULL; rq++)
{
if (rq->func != NULL)
{
char flag = 0;
buf_append_char (buf_to_net, ' ');
if(rq->flags & RQ_SERVER_REQUEST)
{
rq->func(&flag);
if(!flag)
continue;
}
buf_output0 (buf_to_net, rq->name);
}
}
buf_output0 (buf_to_net, "\nok\n");
/* The client is waiting for the list of valid requests, so we
must send the output now. */
buf_flush (buf_to_net, 1);
}
void server_cleanup (int sig)
{
/* Do "rm -rf" on the temp directory. */
int status;
int save_noexec;
if (buf_to_net != NULL)
{
/* FIXME: If this is not the final call from server, this
could deadlock, because the client might be blocked writing
to us. This should not be a problem in practice, because
we do not generate much output when the client is not
waiting for it. */
set_block (buf_to_net);
buf_flush (buf_to_net, 1);
/* The calls to buf_shutdown are currently only meaningful
when we are using compression. First we shut down
BUF_FROM_NET. That will pick up the checksum generated
when the client shuts down its buffer. Then, after we have
generated any final output, we shut down BUF_TO_NET. */
status = buf_shutdown (buf_from_net);
buf_from_net = NULL;
if (status != 0)
{
error (0, status, "shutting down buffer from client");
buf_flush (buf_to_net, 1);
}
}
if (dont_delete_temp)
{
if (buf_to_net != NULL)
(void) buf_shutdown (buf_to_net);
return;
}
CVS_CHDIR (Tmpdir);
/* Temporarily clear noexec, so that we clean up our temp directory
regardless of it (this could more cleanly be handled by moving
the noexec check to all the unlink_file_dir callers from
unlink_file_dir itself). */
save_noexec = noexec;
noexec = 0;
/* FIXME? Would be nice to not ignore errors. But what should we do?
We could try to do this before we shut down the network connection,
and try to notify the client (but the client might not be waiting
for responses). We could try something like syslog() or our own
log file. */
trace = 0;
#ifdef _WIN32
if(!filenames_case_insensitive && orig_server_temp_dir)
remove_from_ci_directory_list(orig_server_temp_dir);
#endif
if(orig_server_temp_dir)
unlink_file_dir (orig_server_temp_dir);
noexec = save_noexec;
if (buf_to_net != NULL)
{
buf_shutdown (buf_to_net);
buf_to_net = NULL;
}
CProtocolLibrary lib;
lib.UnloadProtocol(client_protocol);
}
int server_active = 0;
int pserver_active = 0;
int server (int argc, char **argv)
{
const char *log = CProtocolLibrary::GetEnvironment("CVS_SERVER_LOG");
global_edit_bugnum_hack = 0;
global_editors_bugnum_hack = 0;
if (argc == -1)
{
static const char *const msg[] =
{
"Usage: %s %s\n",
" Normally invoked by a cvs client on a remote machine.\n",
NULL
};
usage (msg);
}
/* Ignore argc and argv. They might be from .cvsrc. */
if(!buf_to_net)
buf_to_net = fd_buffer_initialize (STDOUT_FILENO, 0, outbuf_memory_error);
if(!buf_from_net)
buf_from_net = stdio_buffer_initialize (stdin, 1, outbuf_memory_error);
/* Set up logfiles, if any. */
if (log)
{
int len = strlen (log);
char *buf = (char*)xmalloc (len + 64);
char *p;
FILE *fp;
sprintf(buf,"%s-%d",log,(int)getpid());
p = buf + strlen(buf);
/* Open logfiles in binary mode so that they reflect
exactly what was transmitted and received (that is
more important than that they be maximally
convenient to view). */
strcpy (p, ".out");
fp = open_file (buf, "wb");
if (fp == NULL)
error (0, errno, "opening to-server logfile %s", buf);
else
buf_to_net = log_buffer_initialize (buf_to_net, fp, 0,
(BUFMEMERRPROC) NULL);
strcpy (p, ".in");
fp = open_file (buf, "wb");
if (fp == NULL)
error (0, errno, "opening from-server logfile %s", buf);
else
buf_from_net = log_buffer_initialize (buf_from_net, fp, 1,
(BUFMEMERRPROC) NULL);
xfree (buf);
}
stdout_buf = cvs_protocol_wrap_buffer_initialize(buf_to_net, 'M');
stderr_buf = cvs_protocol_wrap_buffer_initialize(buf_to_net, 'E');
/* OK, now figure out where we stash our temporary files. */
{
char *p;
/* The code which wants to chdir into server_temp_dir is not set
up to deal with it being a relative path. So give an error
for that case. */
if (!isabsolute (Tmpdir))
{
error(1,0,"Value of %s for TMPDIR is not absolute", Tmpdir);
}
else
{
int status;
int i = 0;
server_temp_dir = (char*)xmalloc (strlen (Tmpdir) + 256);
if (server_temp_dir == NULL)
{
error(1,ENOMEM,"Fatal server error, aborting.");
}
strcpy (server_temp_dir, Tmpdir);
/* Remove a trailing slash from TMPDIR if present. */
p = server_temp_dir + strlen (server_temp_dir) - 1;
if (*p == '/')
*p = '\0';
/*
* I wanted to use cvs-serv/PID, but then you have to worry about
* the permissions on the cvs-serv directory being right. So
* use cvs-servPID.
*/
strcat (server_temp_dir, "/cvs-serv");
p = server_temp_dir + strlen (server_temp_dir);
sprintf (p, "%ld", (long) getpid ());
orig_server_temp_dir = server_temp_dir;
/* Create the temporary directory, and set the mode to
700, to discourage random people from tampering with
it. */
while ((status = mkdir_p (server_temp_dir)) == EEXIST)
{
static const char suffix[] = "abcdefghijklmnopqrstuvwxyz";
if (i >= sizeof suffix - 1) break;
if (i == 0) p = server_temp_dir + strlen (server_temp_dir);
p[0] = suffix[i++];
p[1] = '\0';
}
if (status != 0)
{
error(1,errno,"can't create temporary directory %s",server_temp_dir);
}
#ifndef CHMOD_BROKEN
else if (chmod (server_temp_dir, S_IRWXU) < 0)
{
error(1,errno,"cannot change permissions on temporary directory %s",
fn_root(server_temp_dir));
}
#endif
else if (CVS_CHDIR (server_temp_dir) < 0)
{
error(1,errno,"cannot change to temporary directory %s",
fn_root(server_temp_dir));
}
#ifdef _WIN32
if(!filenames_case_insensitive)
add_to_ci_directory_list(server_temp_dir);
#endif
}
}
#ifdef SIGABRT
(void) SIG_register (SIGABRT, server_cleanup);
#endif
#ifdef SIGHUP
(void) SIG_register (SIGHUP, server_cleanup);
#endif
#ifdef SIGINT
(void) SIG_register (SIGINT, server_cleanup);
#endif
#ifdef SIGQUIT
(void) SIG_register (SIGQUIT, server_cleanup);
#endif
#ifdef SIGPIPE
(void) SIG_register (SIGPIPE, server_cleanup);
#endif
#ifdef SIGTERM
(void) SIG_register (SIGTERM, server_cleanup);
#endif
/* Now initialize our argument vector (for arguments from the client). */
/* Small for testing. */
argument_vector_size = 1;
argument_vector =
(char **) xmalloc (argument_vector_size * sizeof (char *));
if (argument_vector == NULL)
{
/*
* Strictly speaking, we're not supposed to output anything
* now. But we're about to exit(), give it a try.
*/
printf ("E Fatal server error, aborting.\nerror ENOMEM Virtual memory exhausted.\n");
/* I'm doing this manually rather than via error_exit ()
because I'm not sure whether we want to call server_cleanup.
Needs more investigation.... */
#ifdef SYSTEM_CLEANUP
/* Hook for OS-specific behavior, for example socket subsystems on
NT and OS2 or dealing with windows and arguments on Mac. */
SYSTEM_CLEANUP ();
#endif
CCvsgui::Close(EXIT_FAILURE);
exit (EXIT_FAILURE);
}
argument_count = 1;
/* This gets printed if the client supports an option which the
server doesn't, causing the server to print a usage message.
FIXME: probably should be using program_name here.
FIXME: just a nit, I suppose, but the usage message the server
prints isn't literally true--it suggests "cvs server" followed
by options which are for a particular command. Might be nice to
say something like "client apparently supports an option not supported
by this server" or something like that instead of usage message. */
argument_vector[0] = "cvs server";
while (1)
{
char *cmd, *orig_cmd;
struct request *rq;
int status;
status = buf_read_line (buf_from_net, &cmd, (int *) 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;
if(!*cmd)
continue;
orig_cmd = cmd;
for (rq = requests; rq->name != NULL; ++rq)
if (strncmp (cmd, rq->name, strlen (rq->name)) == 0)
{
int len = strlen (rq->name);
if (cmd[len] == '\0')
cmd += len;
else if (cmd[len] == ' ')
cmd += len + 1;
else
/*
* The first len characters match, but it's a different
* command. e.g. the command is "cooperate" but we matched
* "co".
*/
continue;
/* No longer supported by the server */
if(!rq->func)
continue;
if (!(rq->flags & RQ_ROOTLESS))
{
if (current_parsed_root == NULL)
error(1,0,"Protocol error: Root request missing");
}
/* By convention 'commands' start with lowercase, and 'flags' start with
uppercase. The extra check allows us to look for things like
'version' and 'init */
/* valid-requests is an oddity */
if (!(rq->flags & RQ_ROOTLESS) || ((rq->name[0]>='a' && rq->name[0]<='z') && strcmp(rq->name,"valid-requests")))
{
if(encryption_level==4 && protocol_encryption_enabled!=PROTOCOL_ENCRYPTION)
error(1,0,"This server requires an encrypted connection");
if(encryption_level==3 && protocol_encryption_enabled==0)
error(1,0,"This server reqires a signed or encrypted connection");
if(compression_level==2 && protocol_compression_enabled==0)
error(1,0,"This server requries a compressed connection");
if(allowed_clients && *allowed_clients && strcmp(rq->name,"server-codepage") && strcmp(rq->name,"client-version")) // This check happens late
{
static int check_done=0;
if(!check_done)
{
cvs::wildcard_filename ac = serv_client_version?serv_client_version:"";
if(!ac.length() && compat_level==1)
ac="CVSNT";
if(!ac.matches_regexp(allowed_clients))
{
TRACE(3,"Failed allowed client match: %s != %s",allowed_clients,serv_client_version);
error(1,0,"This server does not allow %s clients to connect.",serv_client_version?serv_client_version:compat_level?"old cvsnt":"legacy cvs");
}
check_done=1;
}
}
}
(*rq->func) (cmd);
buf_send_output(stderr_buf);
buf_send_output(stdout_buf);
break;
}
if (rq->name == NULL)
{
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');
}
xfree (orig_cmd);
}
server_cleanup (0);
return 0;
}
#if defined (SERVER_SUPPORT)
static void switch_to_user(const char *, int username_switch);
#ifndef _WIN32 // Win32 has a totally different authentication mechanism
static void do_chroot()
{
const char *d;
if(!chroot_done)
{
if(chroot_base && strlen(chroot_base))
{
if(ISDIRSEP(chroot_base[strlen(chroot_base)-1]))
chroot_base[strlen(chroot_base)-1]='\0';
if(current_parsed_root)
{
if(fnncmp(current_parsed_root->directory,chroot_base,strlen(chroot_base)) || strlen(chroot_base)>strlen(current_parsed_root->directory))
{
printf("error 0 Repository is not within chroot\n");
fflush(stdout);
error_exit();
}
d = current_parsed_root->directory;
if(fncmp(current_parsed_root->directory,chroot_base))
current_parsed_root->directory = xstrdup(d+strlen(chroot_base));
else
current_parsed_root->directory = xstrdup("/");
xfree(d);
}
if(CVS_CHDIR(chroot_base)<0)
{
printf("error 0 Cannot change directory to chroot\n");
fflush(stdout);
error_exit();
}
if(chroot(chroot_base) < 0)
{
printf("error 0 chroot failed: %s\n", strerror (errno));
fflush(stdout);
error_exit();
}
chroot_done = 1;
}
}
}
static void switch_to_user (const char *username, int username_switch, void *user_token)
{
struct passwd *pw;
pw = getpwnam ((char*)username);
if (pw == NULL)
{
/* Normally this won't be reached; check_password contains
a similar check. */
printf ("E Fatal error, aborting.\nerror 0 %s: no such user\n", username);
fflush(stdout);
/* Don't worry about server_cleanup; server_active isn't set yet. */
error_exit ();
}
#if HAVE_INITGROUPS
if (initgroups (pw->pw_name, pw->pw_gid) < 0
# ifdef EPERM
/* At least on the system I tried, initgroups() only works as root.
But we do still want to report ENOMEM and whatever other
errors initgroups() might dish up. */
&& errno != EPERM
# endif
)
{
/* This could be a warning, but I'm not sure I see the point
in doing that instead of an error given that it would happen
on every connection. We could log it somewhere and not tell
the user. But at least for now make it an error. */
printf ("error 0 initgroups failed: %s\n", strerror (errno));
fflush(stdout);
/* Don't worry about server_cleanup; server_active isn't set yet. */
error_exit ();
}
#endif /* HAVE_INITGROUPS */
do_chroot();
#ifdef SETXID_SUPPORT
/* honor the setgid bit iff set*/
if (getgid() != getegid())
{
if (setgid (getegid ()) < 0)
{
/* See comments at setuid call below for more discussion. */
printf ("error 0 setgid failed: %s\n", strerror (errno));
fflush(stdout);
/* Don't worry about server_cleanup;
server_active isn't set yet. */
error_exit ();
}
}
else
#endif
{
if (setgid (pw->pw_gid) < 0)
{
/* See comments at setuid call below for more discussion. */
printf ("error 0 setgid failed: %s\n", strerror (errno));
/* Don't worry about server_cleanup;
server_active isn't set yet. */
error_exit ();
}
}
if (setuid (pw->pw_uid) < 0)
{
/* Note that this means that if run as a non-root user,
CVSROOT/passwd must contain the user we are running as
(e.g. "joe:FsEfVcu:cvs" if run as "cvs" user). This seems
cleaner than ignoring the error like CVS 1.10 and older but
it does mean that some people might need to update their
CVSROOT/passwd file. */
printf ("error 0 setuid failed: %s\n", strerror (errno));
fflush(stdout);
/* Don't worry about server_cleanup; server_active isn't set yet. */
error_exit ();
}
/* We don't want our umask to change file modes. The modes should
be set by the modes used in the repository, and by the umask of
the client. */
umask (0);
#ifdef SERVER_SUPPORT
/* Make sure our CVS_Username has been set. */
if (CVS_Username == NULL)
#ifdef _WIN32
CVS_Username = sanitise_username(username);
#else
CVS_Username = xstrdup(username);
#endif
#endif
#if HAVE_PUTENV
/* Set LOGNAME, USER and CVS_USER in the environment, in case they
are already set to something else. */
{
const char *cvs_user;
cvs_putenv("LOGNAME", username);
cvs_putenv("USER", username);
cvs_user = NULL != CVS_Username ? CVS_Username : "";
cvs_putenv("CVS_USER", cvs_user);
}
#endif /* HAVE_PUTENV */
}
#else
static void do_chroot() { }
/* If username_switch is set, then we don't used any cached credential information. This
lets us become any arbitrary user */
static void switch_to_user (const char *username, int username_switch, void *user_token)
{
if(!username_switch && client_protocol->impersonate)
{
TRACE(3,"Impersonating preauthenticated user using protocol specific impersionation");
if(client_protocol->impersonate(client_protocol, username, NULL)!=CVSPROTO_SUCCESS)
{
printf ("E Fatal error, aborting.\nerror 0 %s: Impersonation for :%s: protocol failed.\n", username, client_protocol->name);
error_exit();
}
}
else
{
switch(win32switchtouser(username, user_token))
{
case 4: // No such domain
printf ("E Fatal error, aborting.\nerror 0 %s: no such domain\n", username);
error_exit ();
break;
case 3: // Account disabled
printf ("E Fatal error, aborting.\nerror 0 %s: user account disabled\n", username);
error_exit ();
break;
case 2: // No such user
printf ("E Fatal error, aborting.\nerror 0 %s: no such user\n", username);
error_exit ();
break;
case 1: // Not enough privileges
printf ("E Fatal error, aborting.\nerror 0 %s: Switch to user failed due to configuration error. Contact your System Administrator.\n", username);
error_exit ();
break;
case 0: // Ok
break;
}
}
#ifdef SERVER_SUPPORT
/* Make sure our CVS_Username has been set. */
if (CVS_Username == NULL)
CVS_Username = xstrdup(username);
#endif
#ifdef HAVE_PUTENV
/* Set LOGNAME, USER and CVS_USER in the environment, in case they
are already set to something else. */
{
const char *cvs_user;
cvs_putenv("LOGNAME", username);
cvs_putenv("USER", username);
cvs_user = NULL != CVS_Username ? CVS_Username : "";
cvs_putenv("CVS_USER", cvs_user);
}
#endif /* HAVE_PUTENV */
}
#endif /* _WIN32 */
extern char *crypt(const char *, const char *);
/*
* 0 means no entry found for this user.
* 1 means entry found and password matches (or found password is empty)
* 2 means entry found, but password does not match.
*
* If 1, host_user_ptr will be set to point at the system
* username (i.e., the "real" identity, which may or may not be the
* CVS username) of this user; caller may free this. Global
* CVS_Username will point at an allocated copy of cvs username (i.e.,
* the username argument below).
* kff todo: FIXME: last sentence is not true, it applies to caller.
*/
static int check_repository_password (const char *username, const char *password, const char *repository, char **host_user_ptr, void **user_token)
{
int retval = 0;
FILE *fp;
char *filename;
char *linebuf = NULL;
size_t linebuf_len;
int found_it = 0;
int namelen;
/* We don't use current_parsed_root->directory because it hasn't been set yet
* -- our `repository' argument came from the authentication
* protocol, not the regular CVS protocol.
*/
filename = (char*)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);
return 0;
}
/* Look for a relevant line -- one with this user's name. */
namelen = strlen (username);
while (getline (&linebuf, &linebuf_len, fp) >= 0)
{
if ((strncmp (linebuf, username, namelen) == 0)
&& ((linebuf[namelen] == ':') || linebuf[namelen] == '\n' || linebuf[namelen] == '\0'))
{
found_it = 1;
break;
}
}
if (ferror (fp))
error (0, errno, "cannot read %s", filename);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", filename);
/* If found_it, then linebuf contains the information we need. */
if (found_it)
{
char *found_password, *host_user_tmp;
char *non_cvsuser_portion;
/* We need to make sure lines such as
*
* "username::sysuser\n"
* "username:\n"
* "username: \n"
*
* all result in a found_password of NULL, but we also need to
* make sure that
*
* "username: :sysuser\n"
* "username: <whatever>:sysuser\n"
*
* continues to result in an impossible password. That way,
* an admin would be on safe ground by going in and tacking a
* space onto the front of a password to disable the account
* (a technique some people use to close accounts
* temporarily).
*/
/* Make `non_cvsuser_portion' contain everything after the CVS
username, but null out any final newline. */
non_cvsuser_portion = linebuf + namelen;
strtok (non_cvsuser_portion, "\n");
/* If there's a colon now, we just want to inch past it. */
if (strchr (non_cvsuser_portion, ':') == non_cvsuser_portion)
non_cvsuser_portion++;
/* Okay, after this conditional chain, found_password and
host_user_tmp will have useful values: */
if ((non_cvsuser_portion == NULL)
|| (strlen (non_cvsuser_portion) == 0)
|| ((strspn (non_cvsuser_portion, " \t"))
== strlen (non_cvsuser_portion)))
{
found_password = NULL;
host_user_tmp = NULL;
}
else if (strncmp (non_cvsuser_portion, ":", 1) == 0)
{
found_password = NULL;
host_user_tmp = non_cvsuser_portion + 1;
if (strlen (host_user_tmp) == 0)
host_user_tmp = NULL;
}
else
{
found_password = strtok (non_cvsuser_portion, ":");
host_user_tmp = strtok (NULL, ":");
}
/* Of course, maybe there was no system user portion... */
if (host_user_tmp == NULL)
host_user_tmp = (char*)username;
/* Verify blank passwords directly, otherwise use crypt(). */
if ((found_password == NULL) || (password==NULL)
#ifdef _WIN32 // NTServer mode sets password==NULL for authentication
|| (found_password[0]=='!' && win32_valid_user(username,password,found_password+1, user_token))
#endif
|| ((strcmp (found_password, crypt(password,found_password)))
== 0))
{
/* Give host_user_ptr permanent storage. */
*host_user_ptr = xstrdup (host_user_tmp);
retval = 1;
}
else
{
*host_user_ptr = NULL;
retval = 2;
}
}
else /* Didn't find this user, so deny access. */
{
*host_user_ptr = NULL;
retval = 0;
}
xfree (filename);
if (linebuf)
xfree (linebuf);
return retval;
}
#ifdef HAVE_PAM
struct cvs_pam_userdata
{
char *username;
char *password;
};
#if defined(sun) || defined(__hpux)
/* Solaris has a different definition of this function */
int cvs_conv(int num_msg, struct pam_message **msgm,
struct pam_response **response, void *appdata_ptr)
#else
int cvs_conv(int num_msg, const struct pam_message **msgm,
struct pam_response **response, void *appdata_ptr)
#endif
{
int count=0;
struct pam_response *reply;
struct cvs_pam_userdata *udp = (struct cvs_pam_userdata *)appdata_ptr;
if (num_msg <= 0)
return PAM_CONV_ERR;
reply = (struct pam_response *) xmalloc(num_msg*sizeof(struct pam_response));
if (reply == NULL)
return PAM_CONV_ERR;
if (udp == NULL)
{
error(0,0,"PAM on this system is broken - appdata_ptr == NULL !");
return PAM_CONV_ERR;
}
memset(reply, '\0', sizeof(struct pam_response) * num_msg);
for (count=0; count < num_msg; ++count)
{
char *string=NULL;
switch (msgm[count]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
string = xstrdup(udp->username);
break;
case PAM_PROMPT_ECHO_OFF:
string = xstrdup(udp->password);
break;
case PAM_TEXT_INFO:
case PAM_ERROR_MSG:
break;
default:
xfree(reply);
return PAM_CONV_ERR;
}
if (string) /* must add to reply array */
{
/* add string to list of responses */
reply[count].resp_retcode = 0;
reply[count].resp = string;
string = NULL;
}
}
*response = reply;
reply = NULL;
return PAM_SUCCESS;
}
static struct pam_conv conv = {
cvs_conv,
NULL
};
/* Modelled very closely on the example code in "The Linux-PAM
Application Developers' Guide" by Andrew G. Morgan. */
/* Return a hosting username if password matches, else NULL. */
static char *check_pam_password (const char *username, const char *password)
{
pam_handle_t *pamh = NULL;
int retval;
char *host_user = NULL;
struct cvs_pam_userdata ud;
ud.username = (char*)username;
ud.password = (char*)password;
conv.appdata_ptr = &ud;
retval = pam_start("cvsnt", username, &conv, &pamh);
if (retval == PAM_SUCCESS)
retval = pam_authenticate(pamh, 0); /* is user really user? */
if (retval == PAM_SUCCESS)
retval = pam_acct_mgmt(pamh, 0); /* permitted access? */
/* This is where we have been authorized or not. */
switch(retval)
{
case PAM_SUCCESS:
host_user = xstrdup(username);
break;
case PAM_AUTH_ERR:
default:
host_user = NULL;
break;
}
/* now close PAM */
if (pam_end(pamh,retval) != PAM_SUCCESS)
{
pamh = NULL;
error(1, 0, "PAM failed to release authenticator\n");
}
return host_user;
}
#else /* HAVE_PAM */
static char *check_system_password(const char *username, const char *password, void **user_token)
{
const char *found_passwd = NULL;
struct passwd *pw;
char *host_user = NULL;
#ifdef HAVE_GETSPNAM
struct spwd *spw;
spw = getspnam (username);
if (spw != NULL)
{
found_passwd = spw->sp_pwdp;
}
#endif /* HAVE_GETSPNAM */
if (found_passwd == NULL && (pw = getpwnam (username)) != NULL)
{
found_passwd = pw->pw_passwd;
}
if(found_passwd && *found_passwd)
{
#ifdef _WIN32
host_user = win32_valid_user(username,password,NULL,user_token)
? xstrdup(username) : NULL;
#else
/* user exists and has a password */
host_user = ((! strcmp (found_passwd, crypt (password, found_passwd)))
? xstrdup (username) : NULL);
#endif
}
else if (found_passwd && password && *password)
{
/* user exists and has no system password, but we got
one as parameter */
host_user = xstrdup (username);
}
return host_user;
}
#endif /* HAVE_PAM */
/* Return a hosting username if password matches, else NULL. */
static char *check_password (const char *username, const char *password, const char *repository, void **user_token)
{
int rc;
char *host_user = NULL;
if(user_token)
*user_token = NULL;
/* First we see if this user has a password in the CVS-specific
password file. If so, that's enough to authenticate with. If
not, we'll check /etc/passwd. */
rc = check_repository_password (username, password, repository,
&host_user, user_token);
if (rc == 2)
return NULL;
/* else */
if (rc == 1)
{
/* host_user already set by reference, so just return. */
goto handle_return;
}
else if (rc == 0 && system_auth && !password)
{
/* ntserver/sspi uses password=NULL */
host_user=xstrdup(username);
goto handle_return;
}
else if (rc == 0 && system_auth)
{
#ifdef HAVE_PAM
host_user = check_pam_password (username, password);
#else
host_user = check_system_password (username, password, user_token);
#endif
}
else if (rc == 0)
{
/* Note that the message _does_ distinguish between the case in
which we check for a system password and the case in which
we do not. It is a real pain to track down why it isn't
letting you in if it won't say why, and I am not convinced
that the potential information disclosure to an attacker
outweighs this. */
printf ("error 0 no such user %s in CVSROOT/passwd\n", username);
fflush(stdout);
/* I'm doing this manually rather than via error_exit ()
because I'm not sure whether we want to call server_cleanup.
Needs more investigation.... */
#ifdef SYSTEM_CLEANUP
/* Hook for OS-specific behavior, for example socket subsystems on
NT and OS2 or dealing with windows and arguments on Mac. */
SYSTEM_CLEANUP ();
#endif
CCvsgui::Close(EXIT_FAILURE);
exit (EXIT_FAILURE);
}
else
{
/* Something strange happened. We don't know what it was, but
we certainly won't grant authorization. */
host_user = NULL;
goto handle_return;
}
handle_return:
if (host_user)
{
/* Set CVS_Username here, in allocated space.
It might or might not be the same as host_user. */
CVS_Username = xstrdup(username);
}
return host_user;
}
/* Read username and password from client (i.e., stdin).
If correct, then switch to run as that user and send an ACK to the
client via stdout, else send NACK and die. */
void server_authenticate_connection ()
{
char *tmp = NULL;
void *user_token = NULL;
char *host_user;
const char *real_repository = NULL;
/* Make sure the protocol starts off on the right foot... */
if(io_getline(server_io_socket, &tmp, PATH_MAX)<0)
/* FIXME: what? We could try writing error/eof, but chances
are the network connection is dead bidirectionally. log it
somewhere? */
TRACE(3,"io_getline failed");
/* We want errors to go over the server protocol channel, so create a fake
io buffer */
buf_from_net = client_protocol_buffer_initialize (NULL, 1, outbuf_memory_error);
buf_to_net = client_protocol_buffer_initialize (NULL, 0, outbuf_memory_error);
TRACE(3,"Client sent '%s'",tmp);
bool badauth;
CProtocolLibrary lib;
client_protocol = lib.FindProtocol(tmp,badauth,server_io_socket,encryption_level==4,&temp_protocol);
if(badauth)
goto i_hate_you;
if(!client_protocol)
{
TRACE(3,"Couldn't find a matching authentication protocol");
error (1, 0, "bad auth protocol start: %s", tmp);
}
xfree (tmp);
temp_protocol = NULL;
TRACE(3,"Authentication protocol returned user(%s)",PATCH_NULL(client_protocol->auth_username));
#ifdef _WIN32
win32_sanitize_username((const char **)&client_protocol->auth_username);
#endif
/* If we're now in 'wrap' mode, setup a wrap buffer for the relevant I/O */
buf_from_net->closure=(void*)client_protocol;
buf_to_net->closure=(void*)client_protocol;
if(client_protocol->auth_repository)
{
// If no repository is sent, we can't work out where the passwd, config, etc. file
// are until the first 'Root' command
int online = 1;
if (!root_allow_ok (client_protocol->auth_repository,&real_repository,&online))
{
printf ("error 0 %s: no such repository\n", client_protocol->auth_repository);
fflush(stdout);
CServerIo::log(CServerIo::logAuth,"login failure for %s on %s", client_protocol->auth_username, client_protocol->auth_repository);
goto i_hate_you;
}
if(!online)
{
printf ("error 0 %s: repository is offline during server authenticate connection\n", client_protocol->auth_repository);
fflush(stdout);
CServerIo::log(CServerIo::logAuth,"login failure for %s on %s", client_protocol->auth_username, client_protocol->auth_repository);
goto i_hate_you;
}
// OK, now parse the config file, so we can use it to control how
// to check passwords. If there was an error parsing the config
// file, parse_config already printed an error. We keep going.
// Why? Because if we didn't, then there would be no way to check
// in a new CVSROOT/config file to fix the broken one!
parse_config (real_repository);
#ifdef _WIN32
if(!filenames_case_insensitive)
add_to_ci_directory_list(real_repository);
#endif
// We need the real cleartext before we hash it.
host_user = check_password (client_protocol->auth_username, client_protocol->auth_password,
real_repository, &user_token);
}
else
{
if(client_protocol->auth_username)
host_user = xstrdup(client_protocol->auth_username);
}
if (host_user == NULL)
{
CServerIo::log(CServerIo::logAuth,"login failure for %s on %s", client_protocol->auth_username, client_protocol->auth_repository);
i_hate_you:
printf ("I HATE YOU\n");
fflush (stdout);
/* Don't worry about server_cleanup, server_active isn't set
yet. */
error_exit ();
}
/* Switch to run as this user. */
if(runas_user && *runas_user)
switch_to_user (runas_user, 1, NULL);
else
switch_to_user (host_user, 0, user_token);
xfree (host_user);
printf ("I LOVE YOU\n");
fflush (stdout);
if(!CVS_Username)
{
if(host_user)
CVS_Username = xstrdup(host_user);
else
CVS_Username = xstrdup(getcaller());
}
/* Don't go any farther if we're just responding to "cvs login". */
/* Moved from above as I'm not sure it makes sense to be able to
login then not do anything (because you don't have a valid
alias) */
if (client_protocol->verify_only)
{
#ifdef SYSTEM_CLEANUP
/* Hook for OS-specific behavior, for example socket subsystems on
NT and OS2 or dealing with windows and arguments on Mac. */
SYSTEM_CLEANUP ();
#endif
CCvsgui::Close(0);
exit (0);
}
}
#endif /* SERVER_SUPPORT */
#endif /* SERVER_SUPPORT */
/* This global variable is non-zero if the user requests encryption on
the command line. */
int cvsencrypt;
/* This global variable is non-zero if the user requests authentication on
the command line. */
int cvsauthenticate;
/* An buffer interface. This is built on top of a
packetizing buffer. */
/* This structure is the closure field of the protocol wrapping. */
struct cvs_encrypt_wrap_data
{
int encrypt; /* 1 = wrap & encrypt, 0 = wrap only */
};
struct cvs_protocol_wrap_data
{
char prefix; /* 'E' or 'M' */
int last_was_linefeed;
};
static int cvs_encrypt_wrap_input(void *, const char *, char *, int);
static int cvs_encrypt_wrap_output(void *, const char *, char *, int, int *);
static int cvs_protocol_wrap_output(void *, const char *, char *, int, int *);
/* Create a protocol wrapping buffer. We use a packetizing buffer. */
struct buffer *
cvs_encrypt_wrap_buffer_initialize (struct buffer *buf,
int input, int encrypt, void (*memory)(struct buffer *))
{
struct cvs_encrypt_wrap_data *ed;
ed = (struct cvs_encrypt_wrap_data *) xmalloc (sizeof *ed);
ed->encrypt = encrypt;
return (packetizing_buffer_initialize
(buf,
input ? cvs_encrypt_wrap_input : NULL,
input ? NULL : cvs_encrypt_wrap_output,
ed,
memory));
}
/* Unwrap data using GSSAPI. */
static int
cvs_encrypt_wrap_input (void *fnclosure, const char *input,
char *output, int size)
{
struct cvs_encrypt_wrap_data *ed =
(struct cvs_encrypt_wrap_data *) fnclosure;
int newsize;
if(client_protocol && client_protocol->wrap)
{
if(client_protocol->wrap(client_protocol, 1, ed->encrypt, input, size, output, &newsize))
{
error(1, 0, "cvs_encrypt_wrap_input failed: error");
}
if (newsize > size)
{
error(1, 0, "cvs_encrypt_wrap_input failed: newsize > size");
}
}
else
{
/* We're being called from 'cvs server', which means encryption is being handled by
an external protocol (probably ssh). Just act as a passthrough. */
memcpy(output,input,size);
}
return 0;
}
/* Wrap data using GSSAPI. */
static int
cvs_encrypt_wrap_output (void *fnclosure, const char *input,
char *output, int size, int *translated)
{
struct cvs_encrypt_wrap_data *ed =
(struct cvs_encrypt_wrap_data *) fnclosure;
int newsize;
if(client_protocol)
{
if(client_protocol->wrap(client_protocol, 0, ed->encrypt, input, size, output, &newsize))
{
error(1, 0, "cvs_encrypt_wrap_output failed: error");
}
if (newsize > size + 100)
{
error(1, 0, "cvs_encrypt_wrap_output failed: newsize > size + 100");
}
}
else
{
/* External protocol - just passthrough */
memcpy(output,input,size);
newsize=size;
}
*translated = newsize;
return 0;
}
struct buffer *cvs_protocol_wrap_buffer_initialize (struct buffer *buf, char prefix)
{
struct cvs_protocol_wrap_data *ed;
ed = (struct cvs_protocol_wrap_data *) xmalloc (sizeof *ed);
ed->prefix=prefix;
ed->last_was_linefeed = 1;
return (nonpacketizing_buffer_initialize
(buf,
NULL,
cvs_protocol_wrap_output,
ed,
NULL));
}
static void cvs_protocol_wrap_set_buffer(struct buffer *buf, struct buffer *wrap)
{
packetizing_buffer_set_wrap(buf,wrap);
}
static int cvs_protocol_wrap_output (void *fnclosure, const char *input,
char *output, int size, int *translated)
{
struct cvs_protocol_wrap_data *ed = (struct cvs_protocol_wrap_data *) fnclosure;
const char *p=input;
char *q=output;
int lf = ed->last_was_linefeed;
for(; p<input+size; p++,q++)
{
if(lf)
{
*(q++)=ed->prefix;
*(q++)=' ';
lf=0;
}
*q=*p;
if(*p=='\n')
lf = 1;
if((q-output) > size + PACKET_SLOP)
{
abort(); // Can't print an error here as we're in the output calling path
//error(1,0, "cvs_protocol_wrap_output overflowed the output buffer");
}
}
ed->last_was_linefeed = lf;
*translated = q-output;
return 0;
}
/* Output LEN bytes at STR. If LEN is zero, then output up to (not including)
the first '\0' byte. */
int cvs_output (const char *str, size_t len)
{
cvs_flusherr();
if (len == 0)
len = strlen (str);
if(!len)
return 0;
#ifdef SERVER_SUPPORT
if(temp_protocol && temp_protocol->server_write_data)
len = temp_protocol->server_write_data(temp_protocol,str,len?len:strlen(str));
else if (server_active)
{
buf_output (stdout_buf?stdout_buf:buf_to_net, str, len);
if(str[len-1]=='\n')
buf_send_output(stdout_buf?stdout_buf:buf_to_net);
}
else
#endif
{
#if defined(_WIN32) && !defined(CVS95)
// Convert the UTF8 string to Unicode/ANSI for console output
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
if(GetFileType(hConsole)==FILE_TYPE_CHAR)
{
int wlen = len*4;
wchar_t *wstr = (wchar_t*)xmalloc(wlen);
wlen = MultiByteToWideChar(win32_global_codepage,0,str,len,wstr,wlen);
WriteConsoleW(hConsole, wstr, wlen, NULL, NULL);
xfree(wstr);
return len;
}
#endif
if(CCvsgui::Active())
len = CCvsgui::write(str,len,false,false);
else
{
size_t written;
size_t to_write = len;
const char *p = str;
/* For symmetry with cvs_outerr we would call fflush (stderr)
here. I guess the assumption is that stderr will be
unbuffered, so we don't need to. That sounds like a sound
assumption from the manpage I looked at, but if there was
something fishy about it, my guess is that calling fflush
would not produce a significant performance problem. */
while (to_write > 0)
{
written = fwrite (p, 1, to_write, stdout);
if (written == 0)
break;
p += written;
to_write -= written;
}
fflush(stdout);
}
}
return len;
}
#ifdef SERVER_SUPPORT
int cvs_no_translate_begin()
{
if(!server_active)
return 0;
if(supported_response("NoTranslateBegin"))
buf_output0 (buf_to_net, "NoTranslateBegin\n");
return 0;
}
int cvs_no_translate_end()
{
if(!server_active)
return 0;
if(supported_response("NoTranslateEnd"))
buf_output0 (buf_to_net, "NoTranslateEnd\n");
return 0;
}
#endif
/* Output LEN bytes at STR in binary mode. If LEN is zero, then
output zero bytes. */
int cvs_output_binary (char *str, size_t len)
{
cvs_flusherr();
#ifdef SERVER_SUPPORT
if (server_active)
{
char size_text[40];
if (!supported_response ("Mbinary"))
{
error (0, 0, "\
this client does not support writing binary files to stdout");
return 0;
}
buf_output0 (buf_to_net, "Mbinary\n");
sprintf (size_text, "%lu\n", (unsigned long) len);
buf_output0 (buf_to_net, size_text);
/* Not sure what would be involved in using buf_append_data here
without stepping on the toes of our caller (which is responsible
for the memory allocation of STR). */
buf_output (buf_to_net, str, len);
return len;
}
else if(temp_protocol && temp_protocol->server_write_data)
return temp_protocol->server_write_data(temp_protocol,str,len?len:strlen(str));
else
#endif
if(CCvsgui::Active())
return CCvsgui::write(str,len,false,true);
else
{
size_t written;
size_t to_write = len;
const char *p = str;
/* For symmetry with cvs_outerr we would call fflush (stderr)
here. I guess the assumption is that stderr will be
unbuffered, so we don't need to. That sounds like a sound
assumption from the manpage I looked at, but if there was
something fishy about it, my guess is that calling fflush
would not produce a significant performance problem. */
#ifdef USE_SETMODE_STDOUT
int oldmode;
/* It is possible that this should be the same ifdef as
USE_SETMODE_BINARY but at least for the moment we keep them
separate. Mostly this is just laziness and/or a question
of what has been tested where. Also there might be an
issue of setmode vs. _setmode. */
/* The Windows doc says to call setmode only right after startup.
I assume that what they are talking about can also be helped
by flushing the stream before changing the mode. */
fflush (stdout);
oldmode = _setmode (_fileno (stdout), OPEN_BINARY);
if (oldmode < 0)
error (0, errno, "failed to setmode on stdout");
#endif
while (to_write > 0)
{
written = fwrite (p, 1, to_write, stdout);
if (written == 0)
break;
p += written;
to_write -= written;
}
#ifdef USE_SETMODE_STDOUT
fflush (stdout);
if (_setmode (_fileno (stdout), oldmode) != OPEN_BINARY)
error (0, errno, "failed to setmode on stdout");
#endif
return len - to_write;
}
}
/* Like CVS_OUTPUT but output is for stderr not stdout. */
int cvs_outerr (const char *str, size_t len)
{
/* Make sure that output appears in order if stdout and stderr
point to the same place. For the server case this is taken
care of by the fact that saved_outerr always holds less
than a line. */
cvs_flushout();
if (len == 0)
len = strlen (str);
if(!len)
return 0;
#ifdef SERVER_SUPPORT
if(temp_protocol && temp_protocol->server_write_data)
return temp_protocol->server_write_data(temp_protocol,str,len);
else if (server_active && (stderr_buf || buf_to_net))
{
buf_output (stderr_buf?stderr_buf:buf_to_net, str, len);
if(str[len-1]=='\n')
buf_send_output(stderr_buf?stderr_buf:buf_to_net);
return len;
}
else
#endif
{
#if defined(_WIN32) && !defined(CVS95)
// Convert the UTF8 string to Unicode/ANSI for console output
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
if(GetFileType(hConsole)==FILE_TYPE_CHAR)
{
int wlen = len*4;
wchar_t *wstr = (wchar_t*)xmalloc(wlen);
wlen = MultiByteToWideChar(win32_global_codepage,0,str,len,wstr,wlen);
WriteConsoleW(hConsole, wstr, wlen, NULL, NULL);
xfree(wstr);
return len;
}
#endif
if(CCvsgui::Active())
return CCvsgui::write(str,len,true,false);
else
{
size_t written;
size_t to_write = len;
const char *p = str;
while (to_write > 0)
{
written = fwrite (p, 1, to_write, stderr);
if (written == 0)
break;
p += written;
to_write -= written;
}
return len - to_write;
}
}
}
/* Flush stderr. stderr is normally flushed automatically, of course,
but this function is used to flush information from the server back
to the client. */
void
cvs_flusherr ()
{
#ifdef SERVER_SUPPORT
if (server_active)
{
/* skip the actual stderr flush in this case since the parent process
* on the server should only be writing to stdout anyhow
*/
/* Flush what we can to the network, but don't block. */
if(stderr_buf)
buf_flush (stderr_buf, 0);
}
else if(temp_protocol && temp_protocol->server_flush_data)
temp_protocol->server_flush_data(temp_protocol);
else
#endif
fflush (stderr);
}
/* Make it possible for the user to see what has been written to
stdout (it is up to the implementation to decide exactly how far it
should go to ensure this). */
void
cvs_flushout ()
{
#ifdef SERVER_SUPPORT
if(temp_protocol && temp_protocol->server_flush_data)
temp_protocol->server_flush_data(temp_protocol);
else if (server_active)
{
/* Flush what we can to the network, but don't block. */
if(stdout_buf)
buf_flush (stdout_buf, 0);
}
else
#endif
fflush (stdout);
}
/* Output TEXT, tagging it according to TAG. There are lots more
details about what TAG means in cvsclient.texi but for the simple
case (e.g. non-client/server), TAG is just "newline" to output a
newline (in which case TEXT must be NULL), and any other tag to
output normal text.
Note that there is no way to output either \0 or \n as part of TEXT. */
void cvs_output_tagged (const char *tag, const char *text)
{
if (text != NULL && strchr (text, '\n') != NULL)
/* Uh oh. The protocol has no way to cope with this. For now
we dump core, although that really isn't such a nice
response given that this probably can be caused by newlines
in filenames and other causes other than bugs in CVS. Note
that we don't want to turn this into "MT newline" because
this case is a newline within a tagged item, not a newline
as extraneous sugar for the user. */
assert (0);
/* Start and end tags don't take any text, per cvsclient.texi. */
if (tag[0] == '+' || tag[0] == '-')
assert (text == NULL);
#ifdef SERVER_SUPPORT
if (server_active && supported_response ("MT"))
{
buf_output0 (buf_to_net, "MT ");
buf_output0 (buf_to_net, tag);
if (text != NULL)
{
buf_output (buf_to_net, " ", 1);
buf_output0 (buf_to_net, text);
}
buf_output (buf_to_net, "\n", 1);
}
else
#endif
{
if (strcmp (tag, "newline") == 0)
cvs_output ("\n", 1);
else if (text != NULL)
cvs_output (text, 0);
}
}
#if defined(SERVER_SUPPORT)
int server_main(const char *cmd_name, int (*command)(int argc, char **argv))
{
int exitstatus = -1;
TRACE(3,"server_main started");
/*
* Set this in .bashrc if you want to give yourself time to attach
* to the subprocess with a debugger.
*/
if (CProtocolLibrary::GetEnvironment ("CVS_SERVER_SLEEP"))
{
int secs = atoi (CProtocolLibrary::GetEnvironment ("CVS_SERVER_SLEEP"));
sleep (secs);
}
server_command_name = cmd_name;
precommand_args_t args;
args.argc = argument_count-1;
args.argv = (const char **)argument_vector+1;
args.command = server_command_name;
TRACE(3,"run precommand proc server");
if(run_trigger(&args, precommand_proc))
{
error (0, 0, "Pre-command check failed");
exitstatus = 1;
}
else
exitstatus = (*command) (argument_count, argument_vector);
TRACE(3,"run postcommand proc server");
run_trigger(&args, postcommand_proc);
xfree(last_repository);
return exitstatus;
}
#endif /* SERVER_SUPPORT */
char *normalise_options(const char *options, int quiet, const char *file)
{
#ifdef SERVER_SUPPORT
char *o;
const kflag_t *p;
const char *v = valid_rcsoptions;
char *temp_options, *tmp;
if(!server_active)
#endif
return xstrdup(options);
#ifdef SERVER_SUPPORT
if(!options || !*options)
return NULL;
if(!v)
{
if(supported_response("EntriesExtra")) /* This is a hack, but basically any CVSNT client will support this,
so you can add things like unicode and -kL support */
v="butkvloL";
else
v="bkvlo";
}
temp_options = xstrdup(options);
tmp=temp_options;
if(*tmp=='{' && !strchr(tmp,'}'))
{
/* Can't really process this, but it's better not to fall over... */
tmp++;
}
if(*tmp=='{' && !strchr(v,'{'))
{
if(!quiet)
{
error(0,0,"`%s' is marked with an expanded encoding {}.",file);
error(0,0,"Your client cannot understand this option");
error(0,0,"so the checked out file will not be an");
error(0,0,"accurate representation of the original");
error(0,0,"file. Please contact the repository");
error(0,0,"administrator if you think that this is");
error(0,0,"in error");
}
strcpy(tmp,strchr(tmp,'}')+1);
}
else if(*tmp=='{')
tmp=strchr(tmp,'}')+1;
for(p=kflag_encoding;p->flag;p++)
{
if((p->type&KFLAG_LEGACY) || (p->type&KFLAG_INTERNAL))
continue;
o=strchr(tmp,p->flag);
if(o && !strchr(v,p->flag))
{
if(!quiet && p->type&KFLAG_ESSENTIAL)
{
error(0,0,"`%s' is marked with expansion option `%c'.",file,p->flag);
error(0,0,"Your client cannot understand this option");
error(0,0,"so the checked out file will not be an");
error(0,0,"accurate representation of the original");
error(0,0,"file. Please contact the repository");
error(0,0,"administrator if you think that this is");
error(0,0,"in error");
}
if(p->alternate)
*o=p->alternate;
else
strcpy(o,o+1);
}
}
for(p=kflag_flags;p->flag;p++)
{
if((p->type&KFLAG_LEGACY) || (p->type&KFLAG_INTERNAL))
continue;
o=strchr(tmp,p->flag);
if(o && !strchr(v,p->flag))
{
if(!quiet && p->type&KFLAG_ESSENTIAL)
{
error(0,0,"`%s' is marked with expansion option `%c'.",file,p->flag);
error(0,0,"Your client cannot understand this option");
error(0,0,"so the checked out file will not be an");
error(0,0,"accurate representation of the original");
error(0,0,"file. Please contact the repository");
error(0,0,"administrator if you think that this is");
error(0,0,"in error");
}
if(p->alternate)
*o=p->alternate;
else
strcpy(o,o+1);
}
}
if(!*temp_options)
xfree(temp_options); /* None left */
return temp_options;
#endif /* Server_support */
}
#ifdef SERVER_SUPPORT
void server_send_baserev(struct file_info *finfo, const char *basefile, const char *type)
{
FILE *f;
unsigned long len;
buf_output0(buf_to_net,"Update-baserev ");
output_dir (finfo->update_dir, finfo->repository);
buf_output0(buf_to_net,finfo->file);
buf_output0(buf_to_net,"\n");
buf_output0(buf_to_net,type);
buf_output0(buf_to_net,"\n");
if(type[0]=='U')
{
f = fopen(basefile,"rb");
if(!f)
buf_output0(buf_to_net,"0\n");
else
{
char tmp[BUFSIZ];
fseek(f,0,SEEK_END);
len = (unsigned long)ftell(f);
fseek(f,0,0);
snprintf(tmp,sizeof(tmp),"%lu\n",len);
buf_output0(buf_to_net,tmp);
while((len=fread(tmp,1,sizeof(tmp),f))>0)
buf_output(buf_to_net,tmp,(int)len);
fclose(f);
}
}
}
#endif
int precommand_proc(void *param, const trigger_interface *cb)
{
int ret = 0;
precommand_args_t *args = (precommand_args_t*)param;
TRACE(1,"precommand_proc()");
if(cb->precommand)
ret = cb->precommand(cb,args->argc,args->argv);
return ret;
}
int postcommand_proc(void *param, const trigger_interface *cb)
{
int ret = 0;
precommand_args_t *args = (precommand_args_t*)param;
TRACE(1,"postcommand_proc()");
if(cb->postcommit && !strcmp(args->command,"commit"))
ret = cb->postcommit(cb,last_repository?Short_Repository(last_repository):"");
if(!ret && cb->postcommand)
ret = cb->postcommand(cb,last_repository?Short_Repository(last_repository):"");
return ret;
}
syntax highlighted by Code2HTML, v. 0.9.1