#include "cvs.h"
#include "edit.h"
#include "fileattr.h"
#include "watch.h"
#include "buffer.h"
#include "getline.h"
#include "getnline.h"
int server_active = 0;
#if defined (SERVER_SUPPORT) || defined (CLIENT_SUPPORT)
# include "log-buffer.h"
# include "ms-buffer.h"
#endif
#if defined (HAVE_GSSAPI) && defined (SERVER_SUPPORT)
# include "canon-host.h"
# include "gssapi-client.h"
# include <krb5.h>
static void gserver_authenticate_connection (void);
static int cvs_gssapi_wrapping;
#endif
#ifdef SERVER_SUPPORT
extern char *server_hostname;
# if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_KERBEROS) || defined (HAVE_GSSAPI)
# include <sys/socket.h>
# endif
# ifdef HAVE_SYSLOG_H
# include <syslog.h>
# ifndef LOG_DAEMON
# define LOG_DAEMON 0
# endif
# endif
# ifdef HAVE_KERBEROS
# include <netinet/in.h>
# include <krb.h>
# ifndef HAVE_KRB_GET_ERR_TEXT
# define krb_get_err_text(status) krb_err_txt[status]
# endif
static C_Block kblock;
static Key_schedule sched;
# endif
# include "xselect.h"
# ifndef O_NONBLOCK
# define O_NONBLOCK O_NDELAY
# endif
# if HAVE_INITGROUPS
# include <grp.h>
# endif
# ifdef AUTH_SERVER_SUPPORT
# ifdef HAVE_GETSPNAM
# include <shadow.h>
# endif
char *CVS_Username = NULL;
static char *Pserver_Repos = NULL;
# endif
# ifdef HAVE_PAM
# if defined(HAVE_SECURITY_PAM_APPL_H)
# include <security/pam_appl.h>
# elif defined(HAVE_PAM_PAM_APPL_H)
# include <pam/pam_appl.h>
# endif
static pam_handle_t *pamh = NULL;
static char *pam_username;
static char *pam_password;
# endif
static struct buffer *buf_to_net;
static struct buffer *buf_from_net;
# ifdef PROXY_SUPPORT
static struct buffer *proxy_log;
static struct buffer *proxy_log_out;
static bool reprocessing;
# endif
static int argument_count;
static char **argument_vector;
static int argument_vector_size;
static char *server_temp_dir;
static char *orig_server_temp_dir;
static int dont_delete_temp;
static void server_write_entries (void);
cvsroot_t *referrer;
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;
p = xmalloc (strlen (dir) + 1);
if (p == NULL)
return ENOMEM;
dir_where_cvsadm_lives = xmalloc (strlen (base_dir) + strlen (dir) + 100);
if (dir_where_cvsadm_lives == NULL)
{
free (p);
return ENOMEM;
}
tmp = xmalloc (strlen (base_dir) + strlen (dir) + 100);
if (tmp == NULL)
{
free (p);
free (dir_where_cvsadm_lives);
return ENOMEM;
}
retval = done = 0;
strcpy (p, dir);
strcpy (dir_where_cvsadm_lives, base_dir);
strcat (dir_where_cvsadm_lives, "/");
strcat (dir_where_cvsadm_lives, p);
dir_to_register = NULL;
while (1)
{
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM);
if ((CVS_MKDIR (tmp, 0777) < 0) && (errno != EEXIST))
{
retval = errno;
goto finish;
}
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_REP);
if (! isfile (tmp))
{
char *empty;
empty = xmalloc (strlen (current_parsed_root->directory)
+ sizeof (CVSROOTADM)
+ sizeof (CVSNULLREPOS)
+ 3);
if (! empty)
{
retval = ENOMEM;
goto finish;
}
(void) sprintf (empty, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSNULLREPOS);
if (! isfile (empty))
{
mode_t omask;
omask = umask (cvsumask);
if (CVS_MKDIR (empty, 0777) < 0)
{
retval = errno;
free (empty);
goto finish;
}
(void) umask (omask);
}
f = CVS_FOPEN (tmp, "w");
if (f == NULL)
{
retval = errno;
free (empty);
goto finish;
}
if (fprintf (f, "%s\n", empty) < 0)
{
retval = errno;
fclose (f);
free (empty);
goto finish;
}
if (fclose (f) == EOF)
{
retval = errno;
free (empty);
goto finish;
}
free (empty);
}
(void) sprintf (tmp, "%s/%s", dir_where_cvsadm_lives, CVSADM_ENT);
f = CVS_FOPEN (tmp, "a");
if (f == NULL)
{
retval = errno;
goto finish;
}
if (fclose (f) == EOF)
{
retval = errno;
goto finish;
}
if (dir_to_register != NULL)
{
Subdir_Register (NULL, dir_where_cvsadm_lives, dir_to_register);
}
if (done)
break;
dir_to_register = strrchr (p, '/');
if (dir_to_register == NULL)
{
dir_to_register = p;
strcpy (dir_where_cvsadm_lives, base_dir);
done = 1;
}
else
{
*dir_to_register = '\0';
dir_to_register++;
strcpy (dir_where_cvsadm_lives, base_dir);
strcat (dir_where_cvsadm_lives, "/");
strcat (dir_where_cvsadm_lives, p);
}
}
finish:
free (tmp);
free (dir_where_cvsadm_lives);
free (p);
return retval;
}
static int
mkdir_p (char *dir)
{
char *p;
char *q = xmalloc (strlen (dir) + 1);
int retval;
if (q == NULL)
return ENOMEM;
retval = 0;
p = dir + 1;
while (1)
{
while (*p != '/' && *p != '\0')
++p;
if (*p == '/')
{
strncpy (q, dir, p - dir);
q[p - dir] = '\0';
if (q[p - dir - 1] != '/' && CVS_MKDIR (q, 0777) < 0)
{
int saved_errno = errno;
if (saved_errno != EEXIST
&& ((saved_errno != EACCES && saved_errno != EROFS)
|| !isdir (q)))
{
retval = saved_errno;
goto done;
}
}
++p;
}
else
{
if (CVS_MKDIR (dir, 0777) < 0)
retval = errno;
goto done;
}
}
done:
free (q);
return retval;
}
static void
print_error (int status)
{
char *msg;
char tmpstr[80];
buf_output0 (buf_to_net, "error ");
msg = strerror (status);
if (msg == NULL)
{
sprintf (tmpstr, "unknown error %d", status);
msg = tmpstr;
}
buf_output0 (buf_to_net, msg);
buf_append_char (buf_to_net, '\n');
buf_flush (buf_to_net, 0);
}
static int pending_error;
static char *pending_error_text;
static char *pending_warning_text;
static int
print_pending_error (void)
{
if (!pending_error_text && pending_error)
{
print_error (pending_error);
pending_error = 0;
return 1;
}
if (pending_warning_text)
{
buf_output0 (buf_to_net, pending_warning_text);
buf_append_char (buf_to_net, '\n');
buf_flush (buf_to_net, 0);
free (pending_warning_text);
pending_warning_text = NULL;
}
if (pending_error_text)
{
buf_output0 (buf_to_net, pending_error_text);
buf_append_char (buf_to_net, '\n');
if (pending_error)
print_error (pending_error);
else
buf_output0 (buf_to_net, "error \n");
buf_flush (buf_to_net, 0);
pending_error = 0;
free (pending_error_text);
pending_error_text = NULL;
return 1;
}
return 0;
}
# define error_pending() (pending_error || pending_error_text)
# define warning_pending() (pending_warning_text)
static inline int
alloc_pending_internal (char **dest, size_t size)
{
*dest = malloc (size);
if (!*dest)
{
pending_error = ENOMEM;
return 0;
}
return 1;
}
static int
alloc_pending (size_t size)
{
if (error_pending ())
return 0;
return alloc_pending_internal (&pending_error_text, size);
}
static int
alloc_pending_warning (size_t size)
{
if (warning_pending ())
return 0;
return alloc_pending_internal (&pending_warning_text, size);
}
static int
supported_response (char *name)
{
struct response *rs;
for (rs = responses; rs->name != NULL; ++rs)
if (strcmp (rs->name, name) == 0)
return rs->status == rs_supported;
error (1, 0, "internal error: testing support for unknown response?");
return 0;
}
static inline bool
isProxyServer (void)
{
assert (current_parsed_root);
if (!config || !config->PrimaryServer) return false;
if (!isSamePath (config->PrimaryServer->directory,
current_parsed_root->directory))
return true;
if (config->PrimaryServer->method == fork_method)
return false;
assert (config->PrimaryServer->isremote);
if (isThisHost (config->PrimaryServer->hostname))
return false;
return true;
}
static void
serve_valid_responses (char *arg)
{
char *p = arg;
char *q;
struct response *rs;
# ifdef PROXY_SUPPORT
if (reprocessing) return;
# endif
do
{
q = strchr (p, ' ');
if (q != NULL)
*q++ = '\0';
for (rs = responses; rs->name != NULL; ++rs)
{
if (strcmp (rs->name, p) == 0)
break;
}
if (rs->name == NULL)
;
else
rs->status = rs_supported;
p = q;
} while (q != NULL);
for (rs = responses; rs->name != NULL; ++rs)
{
if (rs->status == rs_essential)
{
buf_output0 (buf_to_net, "E response `");
buf_output0 (buf_to_net, rs->name);
buf_output0 (buf_to_net, "' not supported by client\nerror \n");
buf_flush (buf_to_net, 1);
exit (EXIT_FAILURE);
}
else if (rs->status == rs_optional)
rs->status = rs_not_supported;
}
}
static pid_t command_pid;
static void
outbuf_memory_error (struct buffer *buf)
{
static const char msg[] = "E Fatal server error\n\
error ENOMEM Virtual memory exhausted.\n";
if (command_pid > 0)
kill (command_pid, SIGTERM);
write (STDOUT_FILENO, msg, sizeof (msg) - 1);
# ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR, "virtual memory exhausted");
# endif
exit (EXIT_FAILURE);
}
static void
input_memory_error (struct buffer *buf)
{
outbuf_memory_error (buf);
}
# ifdef PROXY_SUPPORT
static void
rewind_buf_from_net (void)
{
struct buffer *log;
assert (proxy_log);
{
char **cp;
for (cp = argument_vector + 1;
cp < argument_vector + argument_count;
++cp)
free (*cp);
argument_count = 1;
}
log = log_buffer_rewind (proxy_log);
proxy_log = NULL;
buf_free_data (buf_from_net);
buf_from_net = ms_buffer_initialize (outbuf_memory_error, log,
buf_from_net);
reprocessing = true;
}
# endif
char *gConfigPath;
static void
serve_root (char *arg)
{
char *path;
TRACE (TRACE_FUNCTION, "serve_root (%s)", arg ? arg : "(null)");
if (error_pending()
# ifdef PROXY_SUPPORT
|| reprocessing
# endif
) return;
if (!ISABSOLUTE (arg))
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E Root %s must be an absolute pathname", arg);
return;
}
if (current_parsed_root != NULL)
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E Protocol error: Duplicate Root request, for %s", arg);
return;
}
original_parsed_root = current_parsed_root = local_cvsroot (arg);
# ifdef AUTH_SERVER_SUPPORT
if (Pserver_Repos != NULL)
{
if (strcmp (Pserver_Repos, current_parsed_root->directory) != 0)
{
if (alloc_pending (80 + strlen (Pserver_Repos)
+ strlen (current_parsed_root->directory)))
sprintf (pending_error_text, "\
E Protocol error: Root says \"%s\" but pserver says \"%s\"",
current_parsed_root->directory, Pserver_Repos);
return;
}
}
# endif
config = get_root_allow_config (current_parsed_root->directory,
gConfigPath);
# ifdef PROXY_SUPPORT
if (proxy_log && !isProxyServer ())
{
log_buffer_closelog (proxy_log);
log_buffer_closelog (proxy_log_out);
proxy_log = NULL;
}
# endif
push_env_temp_dir ();
{
char *p;
if (!ISABSOLUTE (get_cvs_tmp_dir ()))
{
if (alloc_pending (80 + strlen (get_cvs_tmp_dir ())))
sprintf (pending_error_text,
"E Value of %s for TMPDIR is not absolute",
get_cvs_tmp_dir ());
}
else
{
int status;
int i = 0;
server_temp_dir = xmalloc (strlen (get_cvs_tmp_dir ()) + 80);
if (!server_temp_dir)
{
printf ("E Fatal server error, aborting.\n\
error ENOMEM Virtual memory exhausted.\n");
exit (EXIT_FAILURE);
}
strcpy (server_temp_dir, get_cvs_tmp_dir ());
p = server_temp_dir + strlen (server_temp_dir) - 1;
if (*p == '/')
*p = '\0';
strcat (server_temp_dir, "/cvs-serv");
p = server_temp_dir + strlen (server_temp_dir);
sprintf (p, "%ld", (long) getpid ());
orig_server_temp_dir = server_temp_dir;
while ((status = mkdir_p (server_temp_dir)) == EEXIST)
{
static const char suffix[] = "abcdefghijklmnopqrstuvwxyz";
if (i >= sizeof suffix - 1) break;
if (i == 0) p = server_temp_dir + strlen (server_temp_dir);
p[0] = suffix[i++];
p[1] = '\0';
}
if (status)
{
if (alloc_pending (80 + strlen (server_temp_dir)))
sprintf (pending_error_text,
"E can't create temporary directory %s",
server_temp_dir);
pending_error = status;
}
#ifndef CHMOD_BROKEN
else if (chmod (server_temp_dir, S_IRWXU) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (server_temp_dir)))
sprintf (pending_error_text,
"E cannot change permissions on temporary directory %s",
server_temp_dir);
pending_error = save_errno;
}
#endif
else if (CVS_CHDIR (server_temp_dir) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (server_temp_dir)))
sprintf (pending_error_text,
"E cannot change to temporary directory %s",
server_temp_dir);
pending_error = save_errno;
}
}
}
if (gzip_level)
{
bool forced = false;
if (gzip_level < config->MinCompressionLevel)
{
gzip_level = config->MinCompressionLevel;
forced = true;
}
if (gzip_level > config->MaxCompressionLevel)
{
gzip_level = config->MaxCompressionLevel;
forced = true;
}
if (forced && !quiet
&& alloc_pending_warning (120 + strlen (program_name)))
sprintf (pending_warning_text,
"E %s server: Forcing compression level %d (allowed: %d <= z <= %d).",
program_name, gzip_level, config->MinCompressionLevel,
config->MaxCompressionLevel);
}
path = xmalloc (strlen (current_parsed_root->directory)
+ sizeof (CVSROOTADM)
+ 2);
if (path == NULL)
{
pending_error = ENOMEM;
return;
}
(void) sprintf (path, "%s/%s", current_parsed_root->directory, CVSROOTADM);
if (!isaccessible (path, R_OK | X_OK))
{
int save_errno = errno;
if (alloc_pending (80 + strlen (path)))
sprintf (pending_error_text, "E Cannot access %s", path);
pending_error = save_errno;
}
free (path);
setenv (CVSROOT_ENV, current_parsed_root->directory, 1);
}
static int max_dotdot_limit = 0;
void
server_pathname_check (char *path)
{
TRACE (TRACE_FUNCTION, "server_pathname_check (%s)",
path ? path : "(null)");
if (ISABSOLUTE (path))
error ( 1, 0,
"absolute pathnames invalid for server (specified `%s')",
path );
if (pathname_levels (path) > max_dotdot_limit)
{
error (0, 0, "protocol error: `%s' contains more leading ..", path);
error (1, 0, "than the %d which Max-dotdot specified",
max_dotdot_limit);
}
}
static int
outside_root (char *repos)
{
size_t repos_len = strlen (repos);
size_t root_len = strlen (current_parsed_root->directory);
if (!ISABSOLUTE (repos))
{
if (alloc_pending (repos_len + 80))
sprintf (pending_error_text, "\
E protocol error: %s is not absolute", repos);
return 1;
}
if (repos_len < root_len
|| strncmp (current_parsed_root->directory, repos, root_len) != 0)
{
not_within:
if (alloc_pending (strlen (current_parsed_root->directory)
+ strlen (repos)
+ 80))
sprintf (pending_error_text, "\
E protocol error: directory '%s' not within root '%s'",
repos, current_parsed_root->directory);
return 1;
}
if (repos_len > root_len)
{
if (repos[root_len] != '/')
goto not_within;
if (pathname_levels (repos + root_len + 1) > 0)
goto not_within;
}
return 0;
}
static int
outside_dir (char *file)
{
if (strchr (file, '/') != NULL)
{
if (alloc_pending (strlen (file)
+ 80))
sprintf (pending_error_text, "\
E protocol error: directory '%s' not within current directory",
file);
return 1;
}
return 0;
}
static void
serve_max_dotdot (char *arg)
{
int lim = atoi (arg);
int i;
char *p;
#ifdef PROXY_SUPPORT
if (proxy_log) return;
#endif
if (lim < 0 || lim > 10000)
return;
p = xmalloc (strlen (server_temp_dir) + 2 * lim + 10);
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
strcpy (p, server_temp_dir);
for (i = 0; i < lim; ++i)
strcat (p, "/d");
if (server_temp_dir != orig_server_temp_dir)
free (server_temp_dir);
server_temp_dir = p;
max_dotdot_limit = lim;
}
static char *gDirname;
static char *gupdate_dir;
static void
dirswitch (char *dir, char *repos)
{
int status;
FILE *f;
size_t dir_len;
TRACE (TRACE_FUNCTION, "dirswitch (%s, %s)", dir ? dir : "(null)",
repos ? repos : "(null)");
server_write_entries ();
if (error_pending()) return;
if (ISABSOLUTE (dir))
{
if (alloc_pending (80 + strlen (dir)))
sprintf ( pending_error_text,
"E absolute pathnames invalid for server (specified `%s')",
dir);
return;
}
if (pathname_levels (dir) > max_dotdot_limit)
{
if (alloc_pending (80 + strlen (dir)))
sprintf (pending_error_text,
"E protocol error: `%s' has too many ..", dir);
return;
}
dir_len = strlen (dir);
if (dir_len > 0
&& dir[dir_len - 1] == '/')
{
if (alloc_pending (80 + dir_len))
sprintf (pending_error_text,
"E protocol error: invalid directory syntax in %s", dir);
return;
}
if (gDirname != NULL)
free (gDirname);
if (gupdate_dir != NULL)
free (gupdate_dir);
if (!strcmp (dir, "."))
gupdate_dir = xstrdup ("");
else
gupdate_dir = xstrdup (dir);
gDirname = xmalloc (strlen (server_temp_dir) + dir_len + 40);
if (gDirname == NULL)
{
pending_error = ENOMEM;
return;
}
strcpy (gDirname, server_temp_dir);
strcat (gDirname, "/");
strcat (gDirname, dir);
status = mkdir_p (gDirname);
if (status != 0
&& status != EEXIST)
{
if (alloc_pending (80 + strlen (gDirname)))
sprintf (pending_error_text, "E cannot mkdir %s", gDirname);
pending_error = status;
return;
}
status = create_adm_p (server_temp_dir, dir);
if (status != 0)
{
if (alloc_pending (80 + strlen (gDirname)))
sprintf (pending_error_text, "E cannot create_adm_p %s", gDirname);
pending_error = status;
return;
}
if ( CVS_CHDIR (gDirname) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (gDirname)))
sprintf (pending_error_text, "E cannot change to %s", gDirname);
pending_error = save_errno;
return;
}
if ((CVS_MKDIR (CVSADM, 0777) < 0) && (errno != EEXIST))
{
int save_errno = errno;
if (alloc_pending (80 + strlen (gDirname) + strlen (CVSADM)))
sprintf (pending_error_text,
"E cannot mkdir %s/%s", gDirname, CVSADM);
pending_error = save_errno;
return;
}
f = CVS_FOPEN (CVSADM_REP, "w");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (gDirname) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E cannot open %s/%s", gDirname, CVSADM_REP);
pending_error = save_errno;
return;
}
if (fprintf (f, "%s", repos) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (gDirname) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E error writing %s/%s", gDirname, CVSADM_REP);
pending_error = save_errno;
fclose (f);
return;
}
if (strcmp (dir, ".") == 0
&& current_parsed_root != NULL
&& current_parsed_root->directory != NULL
&& strcmp (current_parsed_root->directory, repos) == 0)
{
if (fprintf (f, "/.") < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (gDirname) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E error writing %s/%s", gDirname, CVSADM_REP);
pending_error = save_errno;
fclose (f);
return;
}
}
if (fprintf (f, "\n") < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (gDirname) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E error writing %s/%s", gDirname, CVSADM_REP);
pending_error = save_errno;
fclose (f);
return;
}
if (fclose (f) == EOF)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (gDirname) + strlen (CVSADM_REP)))
sprintf (pending_error_text,
"E error closing %s/%s", gDirname, CVSADM_REP);
pending_error = save_errno;
return;
}
f = CVS_FOPEN (CVSADM_ENT, "a");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENT)))
sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT);
pending_error = save_errno;
return;
}
if (fclose (f) == EOF)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENT)))
sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
pending_error = save_errno;
return;
}
}
static void
serve_repository (char *arg)
{
# ifdef PROXY_SUPPORT
assert (!proxy_log);
# endif
if (alloc_pending (80))
strcpy (pending_error_text,
"E Repository request is obsolete; aborted");
return;
}
static void
serve_directory (char *arg)
{
int status;
char *repos;
TRACE (TRACE_FUNCTION, "serve_directory (%s)", arg ? arg : "(null)");
status = buf_read_line (buf_from_net, &repos, NULL);
if (status == 0)
{
if (!ISABSOLUTE (repos))
{
char *short_repos;
short_repos = repos;
repos = Xasprintf ("%s/%s",
current_parsed_root->directory, short_repos);
free (short_repos);
}
else
repos = xstrdup (primary_root_translate (repos));
if (
# ifdef PROXY_SUPPORT
!proxy_log &&
# endif
!outside_root (repos))
dirswitch (arg, repos);
free (repos);
}
else if (status == -2)
{
pending_error = ENOMEM;
}
else if (status != 0)
{
pending_error_text = xmalloc (80 + strlen (arg));
if (pending_error_text == NULL)
{
pending_error = ENOMEM;
}
else if (status == -1)
{
sprintf (pending_error_text,
"E end of file reading mode for %s", arg);
}
else
{
sprintf (pending_error_text,
"E error reading mode for %s", arg);
pending_error = status;
}
}
}
static void
serve_static_directory (char *arg)
{
FILE *f;
if (error_pending ()
# ifdef PROXY_SUPPORT
|| proxy_log
# endif
) return;
f = CVS_FOPEN (CVSADM_ENTSTAT, "w+");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENTSTAT)))
sprintf (pending_error_text, "E cannot open %s", CVSADM_ENTSTAT);
pending_error = save_errno;
return;
}
if (fclose (f) == EOF)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENTSTAT)))
sprintf (pending_error_text, "E cannot close %s", CVSADM_ENTSTAT);
pending_error = save_errno;
return;
}
}
static void
serve_sticky (char *arg)
{
FILE *f;
if (error_pending ()
# ifdef PROXY_SUPPORT
|| proxy_log
# endif
) return;
f = CVS_FOPEN (CVSADM_TAG, "w+");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_TAG)))
sprintf (pending_error_text, "E cannot open %s", CVSADM_TAG);
pending_error = save_errno;
return;
}
if (fprintf (f, "%s\n", arg) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_TAG)))
sprintf (pending_error_text, "E cannot write to %s", CVSADM_TAG);
pending_error = save_errno;
return;
}
if (fclose (f) == EOF)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_TAG)))
sprintf (pending_error_text, "E cannot close %s", CVSADM_TAG);
pending_error = save_errno;
return;
}
}
static void
receive_partial_file (size_t size, int file)
{
while (size > 0)
{
int status;
size_t nread;
char *data;
status = buf_read_data (buf_from_net, size, &data, &nread);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80);
if (pending_error_text == NULL)
pending_error = ENOMEM;
else if (status == -1)
{
sprintf (pending_error_text,
"E premature end of file from client");
pending_error = 0;
}
else
{
sprintf (pending_error_text,
"E error reading from client");
pending_error = status;
}
}
return;
}
size -= nread;
while (nread > 0)
{
ssize_t nwrote;
nwrote = write (file, data, nread);
if (nwrote < 0)
{
int save_errno = errno;
if (alloc_pending (40))
strcpy (pending_error_text, "E unable to write");
pending_error = save_errno;
while (size > 0)
{
int status;
size_t nread;
char *data;
status = buf_read_data (buf_from_net, size, &data, &nread);
if (status != 0)
return;
size -= nread;
}
return;
}
nread -= nwrote;
data += nwrote;
}
}
}
static void
receive_file (size_t size, char *file, int gzipped)
{
int fd;
char *arg = file;
fd = CVS_OPEN (arg, O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fd < 0)
{
int save_errno = errno;
if (alloc_pending (40 + strlen (arg)))
sprintf (pending_error_text, "E cannot open %s", arg);
pending_error = save_errno;
return;
}
if (gzipped)
{
size_t toread = size;
char *filebuf;
char *p;
filebuf = xmalloc (size);
p = filebuf;
while (toread > 0)
{
int status;
size_t nread;
char *data;
status = buf_read_data (buf_from_net, toread, &data, &nread);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80);
if (pending_error_text == NULL)
pending_error = ENOMEM;
else if (status == -1)
{
sprintf (pending_error_text,
"E premature end of file from client");
pending_error = 0;
}
else
{
sprintf (pending_error_text,
"E error reading from client");
pending_error = status;
}
}
return;
}
toread -= nread;
if (filebuf != NULL)
{
memcpy (p, data, nread);
p += nread;
}
}
if (filebuf == NULL)
{
pending_error = ENOMEM;
goto out;
}
if (gunzip_and_write (fd, file, (unsigned char *) filebuf, size))
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E aborting due to compression error");
}
free (filebuf);
}
else
receive_partial_file (size, fd);
if (pending_error_text)
{
char *p = xrealloc (pending_error_text,
strlen (pending_error_text) + strlen (arg) + 30);
if (p)
{
pending_error_text = p;
sprintf (p + strlen (p), ", file %s", arg);
}
}
out:
if (close (fd) < 0 && !error_pending ())
{
int save_errno = errno;
if (alloc_pending (40 + strlen (arg)))
sprintf (pending_error_text, "E cannot close %s", arg);
pending_error = save_errno;
return;
}
}
static char *kopt;
static int checkin_time_valid;
static time_t checkin_time;
struct an_entry {
struct an_entry *next;
char *entry;
};
static struct an_entry *entries;
static void
serve_is_modified (char *arg)
{
struct an_entry *p;
char *name;
char *cp;
char *timefield;
int found;
if (error_pending ()
# ifdef PROXY_SUPPORT
|| proxy_log
# endif
) return;
if (outside_dir (arg))
return;
found = 0;
for (p = entries; p != NULL; p = p->next)
{
name = p->entry + 1;
cp = strchr (name, '/');
if (cp != NULL
&& strlen (arg) == cp - name
&& strncmp (arg, name, cp - name) == 0)
{
if (!(timefield = strchr (cp + 1, '/')) || *++timefield == '\0')
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Malformed Entry encountered.");
return;
}
if (*timefield == '/')
{
cp = timefield + strlen (timefield);
cp[1] = '\0';
while (cp > timefield)
{
*cp = cp[-1];
--cp;
}
}
if (*timefield != '+')
*timefield = 'M';
if (kopt != NULL)
{
if (alloc_pending (strlen (name) + 80))
sprintf (pending_error_text,
"E protocol error: both Kopt and Entry for %s",
arg);
free (kopt);
kopt = NULL;
return;
}
found = 1;
break;
}
}
if (!found)
{
p = xmalloc (sizeof (struct an_entry));
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
p->entry = xmalloc (strlen (arg) + 80);
if (p->entry == NULL)
{
pending_error = ENOMEM;
free (p);
return;
}
strcpy (p->entry, "/");
strcat (p->entry, arg);
strcat (p->entry, "//D/");
if (kopt != NULL)
{
strcat (p->entry, kopt);
free (kopt);
kopt = NULL;
}
strcat (p->entry, "/");
p->next = entries;
entries = p;
}
}
static void
serve_modified (char *arg)
{
size_t size;
int read_size;
int status;
char *size_text;
char *mode_text;
int gzipped = 0;
status = buf_read_line (buf_from_net, &mode_text, NULL);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80 + strlen (arg));
if (pending_error_text == NULL)
pending_error = ENOMEM;
else
{
if (status == -1)
sprintf (pending_error_text,
"E end of file reading mode for %s", arg);
else
{
sprintf (pending_error_text,
"E error reading mode for %s", arg);
pending_error = status;
}
}
}
return;
}
status = buf_read_line (buf_from_net, &size_text, NULL);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80 + strlen (arg));
if (pending_error_text == NULL)
pending_error = ENOMEM;
else
{
if (status == -1)
sprintf (pending_error_text,
"E end of file reading size for %s", arg);
else
{
sprintf (pending_error_text,
"E error reading size for %s", arg);
pending_error = status;
}
}
}
free (mode_text);
return;
}
if (size_text[0] == 'z')
{
gzipped = 1;
read_size = atoi (size_text + 1);
}
else
read_size = atoi (size_text);
free (size_text);
if (read_size < 0 && alloc_pending (80))
{
sprintf (pending_error_text,
"E client sent invalid (negative) file size");
return;
}
else
size = read_size;
if (error_pending ())
{
while (size > 0)
{
int status;
size_t nread;
char *data;
status = buf_read_data (buf_from_net, size, &data, &nread);
if (status != 0)
return;
size -= nread;
}
free (mode_text);
return;
}
if (
# ifdef PROXY_SUPPORT
!proxy_log &&
# endif
outside_dir (arg))
{
free (mode_text);
return;
}
receive_file (size,
# ifdef PROXY_SUPPORT
proxy_log ? DEVNULL :
# endif
arg,
gzipped);
if (error_pending ())
{
free (mode_text);
return;
}
# ifdef PROXY_SUPPORT
if (proxy_log) return;
# endif
if (checkin_time_valid)
{
struct utimbuf t;
memset (&t, 0, sizeof (t));
t.modtime = t.actime = checkin_time;
if (utime (arg, &t) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text, "E cannot utime %s", arg);
pending_error = save_errno;
free (mode_text);
return;
}
checkin_time_valid = 0;
}
{
int status = change_mode (arg, mode_text, 0);
free (mode_text);
if (status)
{
if (alloc_pending (40 + strlen (arg)))
sprintf (pending_error_text,
"E cannot change mode for %s", arg);
pending_error = status;
return;
}
}
if (kopt != NULL)
serve_is_modified (arg);
}
static void
serve_enable_unchanged (char *arg)
{
# ifdef PROXY_SUPPORT
# endif
}
static void
serve_unchanged (char *arg)
{
struct an_entry *p;
char *name;
char *cp;
char *timefield;
if (error_pending ()
# ifdef PROXY_SUPPORT
|| proxy_log
# endif
) return;
if (outside_dir (arg))
return;
for (p = entries; p != NULL; p = p->next)
{
name = p->entry + 1;
cp = strchr (name, '/');
if (cp != NULL
&& strlen (arg) == cp - name
&& strncmp (arg, name, cp - name) == 0)
{
if (!(timefield = strchr (cp + 1, '/')) || *++timefield == '\0')
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Malformed Entry encountered.");
return;
}
if (*timefield == '/')
{
cp = timefield + strlen (timefield);
cp[1] = '\0';
while (cp > timefield)
{
*cp = cp[-1];
--cp;
}
}
if (*timefield != '+')
{
if (timefield[1] != '/')
{
char *d = timefield + 1;
if ((cp = strchr (d, '/')))
{
while (*cp)
{
*d++ = *cp++;
}
*d = '\0';
}
}
*timefield = '=';
}
break;
}
}
}
static void
serve_entry (char *arg)
{
struct an_entry *p;
char *cp;
int i = 0;
if (error_pending()
# ifdef PROXY_SUPPORT
|| proxy_log
# endif
) return;
cp = arg;
if (*cp == 'D') cp++;
while (i++ < 5)
{
if (!cp || *cp != '/')
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E protocol error: Malformed Entry");
return;
}
cp = strchr (cp + 1, '/');
}
p = xmalloc (sizeof (struct an_entry));
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
cp = xmalloc (strlen (arg) + 2);
if (cp == NULL)
{
free (p);
pending_error = ENOMEM;
return;
}
strcpy (cp, arg);
p->next = entries;
p->entry = cp;
entries = p;
}
static void
serve_kopt (char *arg)
{
if (error_pending ()
# ifdef PROXY_SUPPORT
|| proxy_log
# endif
)
return;
if (kopt != NULL)
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E protocol error: duplicate Kopt request: %s", arg);
return;
}
if (strlen (arg) > 10)
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E protocol error: invalid Kopt request: %s", arg);
return;
}
kopt = xmalloc (strlen (arg) + 1);
if (kopt == NULL)
{
pending_error = ENOMEM;
return;
}
strcpy (kopt, arg);
}
static void
serve_checkin_time (char *arg)
{
struct timespec t;
if (error_pending ()
# ifdef PROXY_SUPPORT
|| proxy_log
# endif
)
return;
if (checkin_time_valid)
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E protocol error: duplicate Checkin-time request: %s",
arg);
return;
}
if (!get_date (&t, arg, NULL))
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text, "E cannot parse date %s", arg);
return;
}
checkin_time = t.tv_sec;
checkin_time_valid = 1;
}
static void
server_write_entries (void)
{
FILE *f;
struct an_entry *p;
struct an_entry *q;
if (entries == NULL)
return;
f = NULL;
if (!error_pending ())
{
f = CVS_FOPEN (CVSADM_ENT, "a");
if (f == NULL)
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENT)))
sprintf (pending_error_text, "E cannot open %s", CVSADM_ENT);
pending_error = save_errno;
}
}
for (p = entries; p != NULL;)
{
if (!error_pending ())
{
if (fprintf (f, "%s\n", p->entry) < 0)
{
int save_errno = errno;
if (alloc_pending (80 + strlen(CVSADM_ENT)))
sprintf (pending_error_text,
"E cannot write to %s", CVSADM_ENT);
pending_error = save_errno;
}
}
free (p->entry);
q = p->next;
free (p);
p = q;
}
entries = NULL;
if (f != NULL && fclose (f) == EOF && !error_pending ())
{
int save_errno = errno;
if (alloc_pending (80 + strlen (CVSADM_ENT)))
sprintf (pending_error_text, "E cannot close %s", CVSADM_ENT);
pending_error = save_errno;
}
}
# ifdef PROXY_SUPPORT
static int
prepost_proxy_proc (const char *repository, const char *filter, void *closure)
{
char *cmdline;
bool *pre = closure;
TRACE (TRACE_FUNCTION, "prepost_proxy_proc (%s, %s, %s)", repository,
filter, *pre ? "pre" : "post");
cmdline = format_cmdline (
# ifdef SUPPORT_OLD_INFO_FMT_STRINGS
0, ".",
# endif
filter,
"c", "s", cvs_cmd_name,
"R", "s", referrer ? referrer->original : "NONE",
"p", "s", ".",
"r", "s", current_parsed_root->directory,
"P", "s", config->PrimaryServer->original,
(char *) NULL);
if (!cmdline || !strlen (cmdline))
{
if (cmdline) free (cmdline);
if (*pre)
error (0, 0, "preadmin proc resolved to the empty string!");
else
error (0, 0, "postadmin proc resolved to the empty string!");
return 1;
}
run_setup (cmdline);
free (cmdline);
return abs (run_exec (RUN_TTY, RUN_TTY, RUN_TTY,
RUN_NORMAL | RUN_SIGIGNORE));
}
static void
become_proxy (void)
{
struct buffer *buf_to_primary;
struct buffer *buf_from_primary;
struct buffer *buf_clientlog = log_buffer_rewind (proxy_log_out);
int status, to_primary_fd, from_primary_fd, to_net_fd, from_net_fd;
bool pre = true;
char *data;
size_t thispass, got;
int s;
char *newdata;
Parse_Info (CVSROOTADM_PREPROXY, current_parsed_root->directory,
prepost_proxy_proc, PIOPT_ALL, &pre);
open_connection_to_server (config->PrimaryServer, &buf_to_primary,
&buf_from_primary);
setup_logfiles ("CVS_SECONDARY_LOG", &buf_to_primary, &buf_from_primary);
if ((status = set_nonblock (buf_from_primary)))
error (1, status, "failed to set nonblocking io from primary");
if ((status = set_nonblock (buf_from_net)))
error (1, status, "failed to set nonblocking io from client");
if ((status = set_nonblock (buf_to_primary)))
error (1, status, "failed to set nonblocking io to primary");
if ((status = set_nonblock (buf_to_net)))
error (1, status, "failed to set nonblocking io to client");
to_primary_fd = buf_get_fd (buf_to_primary);
from_primary_fd = buf_get_fd (buf_from_primary);
to_net_fd = buf_get_fd (buf_to_net);
assert (to_primary_fd >= 0 && from_primary_fd >= 0 && to_net_fd >= 0);
rewind_buf_from_net ();
while (from_primary_fd >= 0 || to_primary_fd >= 0)
{
fd_set readfds, writefds;
int status, numfds = -1;
struct timeval *timeout_ptr;
struct timeval timeout;
size_t toread;
FD_ZERO (&readfds);
FD_ZERO (&writefds);
from_net_fd = buf_from_net ? buf_get_fd (buf_from_net) : -1;
if ((buf_from_net && !buf_empty_p (buf_from_net))
|| (buf_from_primary && !buf_empty_p (buf_from_primary)))
{
timeout.tv_sec = 0;
timeout.tv_usec = 0;
timeout_ptr = &timeout;
}
else
timeout_ptr = NULL;
if (to_net_fd >= 0 && !buf_empty_p (buf_to_net))
{
FD_SET (to_net_fd, &writefds);
numfds = MAX (numfds, to_net_fd);
}
if (to_primary_fd >= 0 && !buf_empty_p (buf_to_primary))
{
FD_SET (to_primary_fd, &writefds);
numfds = MAX (numfds, to_primary_fd);
}
if (from_net_fd >= 0)
{
FD_SET (from_net_fd, &readfds);
numfds = MAX (numfds, from_net_fd);
}
if (from_primary_fd >= 0)
{
FD_SET (from_primary_fd, &readfds);
numfds = MAX (numfds, from_primary_fd);
}
numfds++;
do {
numfds = select (numfds, &readfds, &writefds,
NULL, timeout_ptr);
if (numfds < 0 && errno != EINTR)
{
buf_output0 (buf_to_net, "E select failed\n");
print_error (errno);
exit (EXIT_FAILURE);
}
} while (numfds < 0);
if (numfds == 0)
{
FD_ZERO (&readfds);
FD_ZERO (&writefds);
}
if (to_net_fd >= 0 && FD_ISSET (to_net_fd, &writefds))
{
buf_send_output (buf_to_net);
buf_flush (buf_to_net, false);
}
status = 0;
if (from_net_fd >= 0 && (FD_ISSET (from_net_fd, &readfds)))
status = buf_input_data (buf_from_net, NULL);
if (buf_from_net && !buf_empty_p (buf_from_net))
{
if (buf_to_primary)
buf_append_buffer (buf_to_primary, buf_from_net);
else
;
}
if (status == -1 )
{
SIG_beginCrSect();
buf_shutdown (buf_from_net);
buf_free (buf_from_net);
buf_from_net = NULL;
from_net_fd = -1;
SIG_endCrSect();
}
else if (status > 0 )
{
buf_output0 (buf_to_net,
"E buf_input_data failed reading from client\n");
print_error (status);
exit (EXIT_FAILURE);
}
if (to_primary_fd >= 0 && FD_ISSET (to_primary_fd, &writefds))
{
buf_send_output (buf_to_primary);
buf_flush (buf_to_primary, false);
}
status = 0;
if (from_primary_fd >= 0 && FD_ISSET (from_primary_fd, &readfds))
status = buf_input_data (buf_from_primary, &toread);
if (buf_clientlog
&& buf_from_primary && !buf_empty_p (buf_from_primary))
{
while (buf_clientlog && toread > 0)
{
s = buf_read_data (buf_clientlog, toread, &data, &got);
if (s == -2)
error (1, ENOMEM, "Failed to read data.");
if (s == -1)
{
buf_shutdown (buf_clientlog);
buf_clientlog = NULL;
}
else if (s)
error (1, s, "Error reading writeproxy log.");
else
{
thispass = got;
while (thispass > 0)
{
buf_read_data (buf_from_primary, thispass, &newdata,
&got);
if (memcmp (data, newdata, got))
error (1, 0, "Secondary out of sync with primary!");
data += got;
thispass -= got;
}
toread -= got;
}
}
}
if (buf_from_primary && !buf_empty_p (buf_from_primary))
{
if (buf_to_net)
buf_append_buffer (buf_to_net, buf_from_primary);
else
;
}
if (status == -1 )
{
buf_shutdown (buf_from_primary);
buf_from_primary = NULL;
from_primary_fd = -1;
}
else if (status > 0 )
{
buf_output0 (buf_to_net,
"E buf_input_data failed reading from primary\n");
print_error (status);
exit (EXIT_FAILURE);
}
if (from_primary_fd < 0
&& buf_to_net && buf_empty_p (buf_to_net))
to_net_fd = -1;
if (buf_to_primary
&& (
(from_primary_fd < 0 && buf_to_primary)
|| (from_net_fd < 0 && buf_empty_p (buf_to_primary))))
{
buf_shutdown (buf_to_primary);
buf_free (buf_to_primary);
buf_to_primary = NULL;
to_primary_fd = -1;
}
}
pre = false;
Parse_Info (CVSROOTADM_POSTPROXY, current_parsed_root->directory,
prepost_proxy_proc, PIOPT_ALL, &pre);
}
# endif
struct notify_note {
char *dir;
char *update_dir;
char *filename;
char *type;
char *val;
char *watches;
struct notify_note *next;
};
static struct notify_note *notify_list;
static struct notify_note *last_node;
static void
serve_notify (char *arg)
{
struct notify_note *new = NULL;
char *data = NULL;
int status;
if (error_pending ()) return;
if (isProxyServer())
{
# ifdef PROXY_SUPPORT
if (!proxy_log)
{
# endif
if (alloc_pending (160) + strlen (program_name))
sprintf (pending_error_text,
"E This CVS server does not support disconnected `%s edit'. For now, remove all `%s' files in your workspace and try your command again.",
program_name, CVSADM_NOTIFY);
return;
# ifdef PROXY_SUPPORT
}
else
{
become_proxy ();
exit (EXIT_SUCCESS);
}
# endif
}
if (outside_dir (arg))
return;
if (gDirname == NULL)
goto error;
new = xmalloc (sizeof (struct notify_note));
if (new == NULL)
{
pending_error = ENOMEM;
return;
}
new->dir = xmalloc (strlen (gDirname) + 1);
new->update_dir = xmalloc (strlen (gupdate_dir) + 1);
new->filename = xmalloc (strlen (arg) + 1);
if (new->dir == NULL || new->update_dir == NULL || new->filename == NULL)
{
pending_error = ENOMEM;
if (new->dir != NULL)
free (new->dir);
free (new);
return;
}
strcpy (new->dir, gDirname);
strcpy (new->update_dir, gupdate_dir);
strcpy (new->filename, arg);
status = buf_read_line (buf_from_net, &data, NULL);
if (status != 0)
{
if (status == -2)
pending_error = ENOMEM;
else
{
pending_error_text = xmalloc (80 + strlen (arg));
if (pending_error_text == NULL)
pending_error = ENOMEM;
else
{
if (status == -1)
sprintf (pending_error_text,
"E end of file reading notification for %s", arg);
else
{
sprintf (pending_error_text,
"E error reading notification for %s", arg);
pending_error = status;
}
}
}
free (new->filename);
free (new->dir);
free (new);
}
else
{
char *cp;
if (!data[0])
goto error;
if (strchr (data, '+'))
goto error;
new->type = data;
if (data[1] != '\t')
goto error;
data[1] = '\0';
cp = data + 2;
new->val = cp;
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*cp++ = '+';
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*cp++ = '+';
cp = strchr (cp, '\t');
if (cp == NULL)
goto error;
*cp++ = '\0';
new->watches = cp;
cp = strchr (cp, '\t');
if (cp != NULL)
*cp = '\0';
new->next = NULL;
if (last_node == NULL)
notify_list = new;
else
last_node->next = new;
last_node = new;
}
return;
error:
pending_error = 0;
if (alloc_pending (80))
strcpy (pending_error_text,
"E Protocol error; misformed Notify request");
if (data != NULL)
free (data);
if (new != NULL)
{
free (new->filename);
free (new->update_dir);
free (new->dir);
free (new);
}
return;
}
static void
serve_hostname (char *arg)
{
free (hostname);
hostname = xstrdup (arg);
return;
}
static void
serve_localdir (char *arg)
{
if (CurDir) free (CurDir);
CurDir = xstrdup (arg);
}
static int
server_notify (void)
{
struct notify_note *p;
char *repos;
TRACE (TRACE_FUNCTION, "server_notify()");
while (notify_list != NULL)
{
if (CVS_CHDIR (notify_list->dir) < 0)
{
error (0, errno, "cannot change to %s", notify_list->dir);
return -1;
}
repos = Name_Repository (NULL, NULL);
lock_dir_for_write (repos);
fileattr_startdir (repos);
notify_do (*notify_list->type, notify_list->filename,
notify_list->update_dir, getcaller(), notify_list->val,
notify_list->watches, repos);
buf_output0 (buf_to_net, "Notified ");
{
char *dir = notify_list->dir + strlen (server_temp_dir) + 1;
if (dir[0] == '\0')
buf_append_char (buf_to_net, '.');
else
buf_output0 (buf_to_net, dir);
buf_append_char (buf_to_net, '/');
buf_append_char (buf_to_net, '\n');
}
buf_output0 (buf_to_net, repos);
buf_append_char (buf_to_net, '/');
buf_output0 (buf_to_net, notify_list->filename);
buf_append_char (buf_to_net, '\n');
free (repos);
p = notify_list->next;
free (notify_list->filename);
free (notify_list->dir);
free (notify_list->type);
free (notify_list);
notify_list = p;
fileattr_write ();
fileattr_free ();
Lock_Cleanup ();
}
last_node = NULL;
return 0;
}
static void
serve_argument (char *arg)
{
char *p;
if (error_pending()) return;
if (argument_count >= 10000)
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Protocol error: too many arguments");
return;
}
if (argument_vector_size <= argument_count)
{
argument_vector_size *= 2;
argument_vector = xnrealloc (argument_vector,
argument_vector_size, sizeof (char *));
if (argument_vector == NULL)
{
pending_error = ENOMEM;
return;
}
}
p = xmalloc (strlen (arg) + 1);
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
strcpy (p, arg);
argument_vector[argument_count++] = p;
}
static void
serve_argumentx (char *arg)
{
char *p;
if (error_pending()) return;
if (argument_count <= 1)
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Protocol error: called argumentx without prior call to argument");
return;
}
p = argument_vector[argument_count - 1];
p = xrealloc (p, strlen (p) + 1 + strlen (arg) + 1);
if (p == NULL)
{
pending_error = ENOMEM;
return;
}
strcat (p, "\n");
strcat (p, arg);
argument_vector[argument_count - 1] = p;
}
static void
serve_global_option (char *arg)
{
# ifdef PROXY_SUPPORT
if (reprocessing) return;
# endif
if (arg[0] != '-' || arg[1] == '\0' || arg[2] != '\0')
{
error_return:
if (alloc_pending (strlen (arg) + 80))
sprintf (pending_error_text,
"E Protocol error: bad global option %s",
arg);
return;
}
switch (arg[1])
{
case 'l':
error(0, 0, "WARNING: global `-l' option ignored.");
break;
case 'n':
noexec = 1;
logoff = 1;
break;
case 'q':
quiet = 1;
break;
case 'r':
cvswrite = 0;
break;
case 'Q':
really_quiet = 1;
break;
case 't':
trace++;
break;
default:
goto error_return;
}
}
static void
serve_set (char *arg)
{
# ifdef PROXY_SUPPORT
if (reprocessing) return;
# endif
variable_set (arg);
}
# ifdef ENCRYPTION
# ifdef HAVE_KERBEROS
static void
serve_kerberos_encrypt( char *arg )
{
# ifdef PROXY_SUPPORT
assert (!proxy_log);
# endif
buf_to_net = krb_encrypt_buffer_initialize (buf_to_net, 0, sched,
kblock,
buf_to_net->memory_error);
buf_from_net = krb_encrypt_buffer_initialize (buf_from_net, 1, sched,
kblock,
buf_from_net->memory_error);
}
# endif
# ifdef HAVE_GSSAPI
static void
serve_gssapi_encrypt( char *arg )
{
# ifdef PROXY_SUPPORT
assert (!proxy_log);
# endif
if (cvs_gssapi_wrapping)
{
buf_flush (buf_to_net, 1);
cvs_gssapi_encrypt = 1;
return;
}
cvs_gssapi_encrypt = 1;
buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0,
gcontext,
buf_to_net->memory_error);
buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1,
gcontext,
buf_from_net->memory_error);
cvs_gssapi_wrapping = 1;
}
# endif
# endif
# ifdef HAVE_GSSAPI
static void
serve_gssapi_authenticate (char *arg)
{
# ifdef PROXY_SUPPORT
assert (!proxy_log);
# endif
if (cvs_gssapi_wrapping)
{
return;
}
buf_to_net = cvs_gssapi_wrap_buffer_initialize (buf_to_net, 0,
gcontext,
buf_to_net->memory_error);
buf_from_net = cvs_gssapi_wrap_buffer_initialize (buf_from_net, 1,
gcontext,
buf_from_net->memory_error);
cvs_gssapi_wrapping = 1;
}
# endif
# ifdef SERVER_FLOWCONTROL
# ifndef SERVER_HI_WATER
# define SERVER_HI_WATER (2 * 1024 * 1024)
# endif
# ifndef SERVER_LO_WATER
# define SERVER_LO_WATER (1 * 1024 * 1024)
# endif
# endif
static void
serve_questionable (char *arg)
{
static int initted;
# ifdef PROXY_SUPPORT
if (proxy_log) return;
# endif
if (error_pending ()) return;
if (!initted)
{
ign_setup ();
initted = 1;
}
if (gDirname == NULL)
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Protocol error: `Directory' missing");
return;
}
if (outside_dir (arg))
return;
if (!ign_name (arg))
{
char *update_dir;
buf_output (buf_to_net, "M ? ", 4);
update_dir = gDirname + strlen (server_temp_dir) + 1;
if (!(update_dir[0] == '.' && update_dir[1] == '\0'))
{
buf_output0 (buf_to_net, update_dir);
buf_output (buf_to_net, "/", 1);
}
buf_output0 (buf_to_net, arg);
buf_output (buf_to_net, "\n", 1);
}
}
static struct buffer *protocol = NULL;
static struct buffer *saved_output;
static struct buffer *saved_outerr;
static void
protocol_memory_error (struct buffer *buf)
{
error (1, ENOMEM, "Virtual memory exhausted");
}
static bool
check_command_valid_p (char *cmd_name)
{
# ifdef AUTH_SERVER_SUPPORT
if (CVS_Username == NULL)
return true;
if (lookup_command_attribute (cmd_name) & CVS_CMD_MODIFIES_REPOSITORY)
{
char *linebuf = NULL;
int num_red = 0;
size_t linebuf_len = 0;
char *fname;
size_t flen;
FILE *fp;
int found_it = 0;
flen = strlen (current_parsed_root->directory)
+ strlen (CVSROOTADM)
+ strlen (CVSROOTADM_READERS)
+ 3;
fname = xmalloc (flen);
(void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSROOTADM_READERS);
fp = fopen (fname, "r");
if (fp == NULL)
{
if (!existence_error (errno))
{
error (0, errno, "cannot open %s", fname);
free (fname);
return false;
}
}
else
{
while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0)
{
if (num_red > 0 && linebuf[num_red - 1] == '\n')
linebuf[num_red - 1] = '\0';
if (strcmp (linebuf, CVS_Username) == 0)
goto handle_invalid;
}
if (num_red < 0 && !feof (fp))
error (0, errno, "cannot read %s", fname);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", fname);
}
free (fname);
flen = strlen (current_parsed_root->directory)
+ strlen (CVSROOTADM)
+ strlen (CVSROOTADM_WRITERS)
+ 3;
fname = xmalloc (flen);
(void) sprintf (fname, "%s/%s/%s", current_parsed_root->directory,
CVSROOTADM, CVSROOTADM_WRITERS);
fp = fopen (fname, "r");
if (fp == NULL)
{
if (linebuf)
free (linebuf);
if (existence_error (errno))
{
free (fname);
return true;
}
else
{
error (0, errno, "cannot read %s", fname);
free (fname);
return false;
}
}
found_it = 0;
while ((num_red = getline (&linebuf, &linebuf_len, fp)) >= 0)
{
if (num_red > 0 && linebuf[num_red - 1] == '\n')
linebuf[num_red - 1] = '\0';
if (strcmp (linebuf, CVS_Username) == 0)
{
found_it = 1;
break;
}
}
if (num_red < 0 && !feof (fp))
error (0, errno, "cannot read %s", fname);
if (found_it)
{
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", fname);
if (linebuf)
free (linebuf);
free (fname);
return true;
}
else
{
handle_invalid:
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", fname);
if (linebuf)
free (linebuf);
free (fname);
return false;
}
}
# endif
return true;
}
static struct fd_set_wrapper { fd_set fds; } command_fds_to_drain;
# ifdef SUNOS_KLUDGE
static int max_command_fd;
# endif
# ifdef SERVER_FLOWCONTROL
static int flowcontrol_pipe[2];
# endif
int
set_nonblock_fd (int fd)
{
# if defined (F_GETFL) && defined (O_NONBLOCK) && defined (F_SETFL)
int flags;
flags = fcntl (fd, F_GETFL, 0);
if (flags < 0)
return errno;
if (fcntl (fd, F_SETFL, flags | O_NONBLOCK) < 0)
return errno;
# endif
return 0;
}
static void
do_cvs_command (char *cmd_name, int (*command) (int, char **))
{
int stdout_pipe[2];
int stderr_pipe[2];
int protocol_pipe[2];
int dev_null_fd = -1;
int errs;
TRACE (TRACE_FUNCTION, "do_cvs_command (%s)", cmd_name);
if (isProxyServer())
{
# ifdef PROXY_SUPPORT
if (reprocessing)
reprocessing = false;
else
{
if (lookup_command_attribute (cmd_name)
& CVS_CMD_MODIFIES_REPOSITORY)
{
become_proxy ();
exit (EXIT_SUCCESS);
}
else if (
proxy_log)
{
rewind_buf_from_net ();
return;
}
}
# else
if (lookup_command_attribute (cmd_name)
& CVS_CMD_MODIFIES_REPOSITORY
&& alloc_pending (120))
sprintf (pending_error_text,
"E You need a CVS client that supports the `Redirect' response for write requests to this server.");
return;
# endif
}
command_pid = -1;
stdout_pipe[0] = -1;
stdout_pipe[1] = -1;
stderr_pipe[0] = -1;
stderr_pipe[1] = -1;
protocol_pipe[0] = -1;
protocol_pipe[1] = -1;
server_write_entries ();
if (print_pending_error ())
goto free_args_and_return;
if (!check_command_valid_p (cmd_name))
{
buf_output0 (buf_to_net, "E ");
buf_output0 (buf_to_net, program_name);
buf_output0 (buf_to_net, " [server aborted]: \"");
buf_output0 (buf_to_net, cmd_name);
buf_output0 (buf_to_net,
"\" requires write access to the repository\n\
error \n");
goto free_args_and_return;
}
cvs_cmd_name = cmd_name;
(void) server_notify ();
if (pipe (stdout_pipe) < 0)
{
buf_output0 (buf_to_net, "E pipe failed\n");
print_error (errno);
goto error_exit;
}
if (pipe (stderr_pipe) < 0)
{
buf_output0 (buf_to_net, "E pipe failed\n");
print_error (errno);
goto error_exit;
}
if (pipe (protocol_pipe) < 0)
{
buf_output0 (buf_to_net, "E pipe failed\n");
print_error (errno);
goto error_exit;
}
# ifdef SERVER_FLOWCONTROL
if (pipe (flowcontrol_pipe) < 0)
{
buf_output0 (buf_to_net, "E pipe failed\n");
print_error (errno);
goto error_exit;
}
set_nonblock_fd (flowcontrol_pipe[0]);
set_nonblock_fd (flowcontrol_pipe[1]);
# endif
dev_null_fd = CVS_OPEN (DEVNULL, O_RDONLY);
if (dev_null_fd < 0)
{
buf_output0 (buf_to_net, "E open /dev/null failed\n");
print_error (errno);
goto error_exit;
}
if (! buf_empty_p (saved_output))
{
buf_append_char (saved_output, '\n');
buf_copy_lines (buf_to_net, saved_output, 'M');
}
if (! buf_empty_p (saved_outerr))
{
buf_append_char (saved_outerr, '\n');
buf_copy_lines (buf_to_net, saved_outerr, 'E');
}
buf_flush (buf_to_net, 1);
command_pid = fork ();
if (command_pid < 0)
{
buf_output0 (buf_to_net, "E fork failed\n");
print_error (errno);
goto error_exit;
}
if (command_pid == 0)
{
int exitstatus;
error_use_protocol = 0;
protocol = fd_buffer_initialize (protocol_pipe[1], 0, NULL, false,
protocol_memory_error);
if (buf_to_net != NULL)
{
buf_free (buf_to_net);
buf_to_net = NULL;
}
if (buf_from_net != NULL)
{
buf_free (buf_from_net);
buf_from_net = NULL;
}
saved_output->memory_error = protocol_memory_error;
saved_outerr->memory_error = protocol_memory_error;
if (dup2 (dev_null_fd, STDIN_FILENO) < 0)
error (1, errno, "can't set up pipes");
if (dup2 (stdout_pipe[1], STDOUT_FILENO) < 0)
error (1, errno, "can't set up pipes");
if (dup2 (stderr_pipe[1], STDERR_FILENO) < 0)
error (1, errno, "can't set up pipes");
close (dev_null_fd);
close (stdout_pipe[0]);
close (stdout_pipe[1]);
close (stderr_pipe[0]);
close (stderr_pipe[1]);
close (protocol_pipe[0]);
close_on_exec (protocol_pipe[1]);
# ifdef SERVER_FLOWCONTROL
close_on_exec (flowcontrol_pipe[0]);
close (flowcontrol_pipe[1]);
# endif
if (getenv ("CVS_SERVER_SLEEP"))
{
int secs = atoi (getenv ("CVS_SERVER_SLEEP"));
TRACE (TRACE_DATA, "Sleeping CVS_SERVER_SLEEP (%d) seconds", secs);
sleep (secs);
}
else
TRACE (TRACE_DATA, "CVS_SERVER_SLEEP not set.");
exitstatus = (*command) (argument_count, argument_vector);
if (! buf_empty_p (saved_output))
{
buf_output0 (protocol, supported_response ("MT") ? "MT text " : "M ");
buf_append_buffer (protocol, saved_output);
buf_output (protocol, "\n", 1);
buf_send_counted (protocol);
}
buf_free (protocol);
fclose (stderr);
fclose (stdout);
close (protocol_pipe[1]);
# ifdef SERVER_FLOWCONTROL
{
char junk;
ssize_t status;
while ((status = read (flowcontrol_pipe[0], &junk, 1)) > 0
|| (status == -1 && errno == EAGAIN));
}
# endif
exit (exitstatus);
}
{
struct buffer *stdoutbuf;
struct buffer *stderrbuf;
struct buffer *protocol_inbuf;
int num_to_check;
int count_needed = 1;
# ifdef SERVER_FLOWCONTROL
int have_flowcontrolled = 0;
# endif
FD_ZERO (&command_fds_to_drain.fds);
num_to_check = stdout_pipe[0];
FD_SET (stdout_pipe[0], &command_fds_to_drain.fds);
num_to_check = MAX (num_to_check, stderr_pipe[0]);
FD_SET (stderr_pipe[0], &command_fds_to_drain.fds);
num_to_check = MAX (num_to_check, protocol_pipe[0]);
FD_SET (protocol_pipe[0], &command_fds_to_drain.fds);
num_to_check = MAX (num_to_check, STDOUT_FILENO);
# ifdef SUNOS_KLUDGE
max_command_fd = num_to_check;
# endif
++num_to_check;
if (num_to_check > FD_SETSIZE)
{
buf_output0 (buf_to_net,
"E internal error: FD_SETSIZE not big enough.\n\
error \n");
goto error_exit;
}
stdoutbuf = fd_buffer_initialize (stdout_pipe[0], 0, NULL, true,
input_memory_error);
stderrbuf = fd_buffer_initialize (stderr_pipe[0], 0, NULL, true,
input_memory_error);
protocol_inbuf = fd_buffer_initialize (protocol_pipe[0], 0, NULL, true,
input_memory_error);
set_nonblock (buf_to_net);
set_nonblock (stdoutbuf);
set_nonblock (stderrbuf);
set_nonblock (protocol_inbuf);
if (close (stdout_pipe[1]) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
stdout_pipe[1] = -1;
if (close (stderr_pipe[1]) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
stderr_pipe[1] = -1;
if (close (protocol_pipe[1]) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
protocol_pipe[1] = -1;
# ifdef SERVER_FLOWCONTROL
if (close (flowcontrol_pipe[0]) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
flowcontrol_pipe[0] = -1;
# endif
if (close (dev_null_fd) < 0)
{
buf_output0 (buf_to_net, "E close failed\n");
print_error (errno);
goto error_exit;
}
dev_null_fd = -1;
while (stdout_pipe[0] >= 0
|| stderr_pipe[0] >= 0
|| protocol_pipe[0] >= 0
|| count_needed <= 0)
{
fd_set readfds;
fd_set writefds;
int numfds;
struct timeval *timeout_ptr;
struct timeval timeout;
# ifdef SERVER_FLOWCONTROL
int bufmemsize;
bufmemsize = buf_count_mem (buf_to_net);
if (!have_flowcontrolled && (bufmemsize > SERVER_HI_WATER))
{
if (write(flowcontrol_pipe[1], "S", 1) == 1)
have_flowcontrolled = 1;
}
else if (have_flowcontrolled && (bufmemsize < SERVER_LO_WATER))
{
if (write(flowcontrol_pipe[1], "G", 1) == 1)
have_flowcontrolled = 0;
}
# endif
FD_ZERO (&readfds);
FD_ZERO (&writefds);
if (count_needed <= 0)
{
timeout.tv_sec = 0;
timeout.tv_usec = 0;
timeout_ptr = &timeout;
}
else
{
timeout_ptr = NULL;
}
if (! buf_empty_p (buf_to_net))
FD_SET (STDOUT_FILENO, &writefds);
if (stdout_pipe[0] >= 0)
{
FD_SET (stdout_pipe[0], &readfds);
}
if (stderr_pipe[0] >= 0)
{
FD_SET (stderr_pipe[0], &readfds);
}
if (protocol_pipe[0] >= 0)
{
FD_SET (protocol_pipe[0], &readfds);
}
do {
numfds = select (num_to_check, &readfds, &writefds,
NULL, timeout_ptr);
if (numfds < 0
&& errno != EINTR)
{
buf_output0 (buf_to_net, "E select failed\n");
print_error (errno);
goto error_exit;
}
} while (numfds < 0);
if (numfds == 0)
{
FD_ZERO (&readfds);
FD_ZERO (&writefds);
}
if (FD_ISSET (STDOUT_FILENO, &writefds))
{
buf_send_output (buf_to_net);
}
if (protocol_pipe[0] >= 0
&& (FD_ISSET (protocol_pipe[0], &readfds)))
{
int status;
size_t count_read;
status = buf_input_data (protocol_inbuf, &count_read);
if (status == -1)
{
close (protocol_pipe[0]);
protocol_pipe[0] = -1;
}
else if (status > 0)
{
buf_output0 (buf_to_net, "E buf_input_data failed\n");
print_error (status);
goto error_exit;
}
count_needed -= count_read;
}
while (count_needed <= 0)
{
int special = 0;
count_needed = buf_copy_counted (buf_to_net,
protocol_inbuf,
&special);
buf_send_output (buf_to_net);
if (special == -1)
{
cvs_flushout();
break;
}
if (special == -2)
{
if (supported_response ("F"))
{
buf_append_char (buf_to_net, 'F');
buf_append_char (buf_to_net, '\n');
}
cvs_flusherr ();
break;
}
}
if (stdout_pipe[0] >= 0
&& (FD_ISSET (stdout_pipe[0], &readfds)))
{
int status;
status = buf_input_data (stdoutbuf, NULL);
buf_copy_lines (buf_to_net, stdoutbuf, 'M');
if (status == -1)
{
close (stdout_pipe[0]);
stdout_pipe[0] = -1;
}
else if (status > 0)
{
buf_output0 (buf_to_net, "E buf_input_data failed\n");
print_error (status);
goto error_exit;
}
buf_send_output (buf_to_net);
}
if (stderr_pipe[0] >= 0
&& (FD_ISSET (stderr_pipe[0], &readfds)))
{
int status;
status = buf_input_data (stderrbuf, NULL);
buf_copy_lines (buf_to_net, stderrbuf, 'E');
if (status == -1)
{
close (stderr_pipe[0]);
stderr_pipe[0] = -1;
}
else if (status > 0)
{
buf_output0 (buf_to_net, "E buf_input_data failed\n");
print_error (status);
goto error_exit;
}
buf_send_output (buf_to_net);
}
}
if (! buf_empty_p (stdoutbuf))
{
buf_append_char (stdoutbuf, '\n');
buf_copy_lines (buf_to_net, stdoutbuf, 'M');
}
if (! buf_empty_p (stderrbuf))
{
buf_append_char (stderrbuf, '\n');
buf_copy_lines (buf_to_net, stderrbuf, 'E');
}
if (! buf_empty_p (protocol_inbuf))
buf_output0 (buf_to_net,
"E Protocol error: uncounted data discarded\n");
# ifdef SERVER_FLOWCONTROL
close (flowcontrol_pipe[1]);
flowcontrol_pipe[1] = -1;
# endif
errs = 0;
while (command_pid > 0)
{
int status;
pid_t waited_pid;
waited_pid = waitpid (command_pid, &status, 0);
if (waited_pid < 0)
{
continue;
}
if (WIFEXITED (status))
errs += WEXITSTATUS (status);
else
{
int sig = WTERMSIG (status);
char buf[50];
buf_output0 (buf_to_net, "E Terminated with fatal signal ");
sprintf (buf, "%d\n", sig);
buf_output0 (buf_to_net, buf);
if (WCOREDUMP (status))
{
buf_output0 (buf_to_net, "E Core dumped; preserving ");
buf_output0 (buf_to_net, orig_server_temp_dir);
buf_output0 (buf_to_net, " on server.\n\
E CVS locks may need cleaning up.\n");
dont_delete_temp = 1;
}
++errs;
}
if (waited_pid == command_pid)
command_pid = -1;
}
set_block (buf_to_net);
buf_flush (buf_to_net, 1);
buf_shutdown (protocol_inbuf);
buf_free (protocol_inbuf);
protocol_inbuf = NULL;
buf_shutdown (stderrbuf);
buf_free (stderrbuf);
stderrbuf = NULL;
buf_shutdown (stdoutbuf);
buf_free (stdoutbuf);
stdoutbuf = NULL;
}
if (errs)
buf_output0 (buf_to_net, "error \n");
else
buf_output0 (buf_to_net, "ok\n");
goto free_args_and_return;
error_exit:
if (command_pid > 0)
kill (command_pid, SIGTERM);
while (command_pid > 0)
{
pid_t waited_pid;
waited_pid = waitpid (command_pid, NULL, 0);
if (waited_pid < 0 && errno == EINTR)
continue;
if (waited_pid == command_pid)
command_pid = -1;
}
close (dev_null_fd);
close (protocol_pipe[0]);
close (protocol_pipe[1]);
close (stderr_pipe[0]);
close (stderr_pipe[1]);
close (stdout_pipe[0]);
close (stdout_pipe[1]);
# ifdef SERVER_FLOWCONTROL
close (flowcontrol_pipe[0]);
close (flowcontrol_pipe[1]);
# endif
free_args_and_return:
{
char **cp;
for (cp = argument_vector + 1;
cp < argument_vector + argument_count;
++cp)
free (*cp);
argument_count = 1;
}
set_block (buf_to_net);
buf_flush (buf_to_net, 1);
return;
}
# ifdef SERVER_FLOWCONTROL
void
server_pause_check(void)
{
int paused = 0;
char buf[1];
while (read (flowcontrol_pipe[0], buf, 1) == 1)
{
if (*buf == 'S')
paused = 1;
else if (*buf == 'G')
paused = 0;
else
return;
}
while (paused) {
int numfds, numtocheck;
fd_set fds;
FD_ZERO (&fds);
FD_SET (flowcontrol_pipe[0], &fds);
numtocheck = flowcontrol_pipe[0] + 1;
do {
numfds = select (numtocheck, &fds, NULL, NULL, NULL);
if (numfds < 0
&& errno != EINTR)
{
buf_output0 (buf_to_net, "E select failed\n");
print_error (errno);
return;
}
} while (numfds < 0);
if (FD_ISSET (flowcontrol_pipe[0], &fds))
{
int got;
while ((got = read (flowcontrol_pipe[0], buf, 1)) == 1)
{
if (*buf == 'S')
paused = 1;
else if (*buf == 'G')
paused = 0;
else
return;
}
if (got == 0)
error (1, 0, "flow control EOF");
if (got < 0 && ! blocking_error (errno))
{
error (1, errno, "flow control read failed");
}
}
}
}
# endif
char *server_dir = NULL;
static void
output_dir (const char *update_dir, const char *repository)
{
const char *short_repos = Short_Repository (repository);
if (server_dir != NULL)
{
buf_output0 (protocol, server_dir);
buf_output0 (protocol, "/");
}
if (update_dir[0] == '\0')
buf_output0 (protocol, ".");
else
buf_output0 (protocol, update_dir);
buf_output0 (protocol, "/\n");
if (short_repos[0] == '\0')
buf_output0 (protocol, ".");
else
buf_output0 (protocol, short_repos);
buf_output0 (protocol, "/");
}
static char *entries_line;
static char *scratched_file;
static int kill_scratched_file;
void
server_register (const char *name, const char *version, const char *timestamp,
const char *options, const char *tag, const char *date,
const char *conflict)
{
int len;
if (options == NULL)
options = "";
TRACE (TRACE_FUNCTION, "server_register(%s, %s, %s, %s, %s, %s, %s)",
name, version, timestamp ? timestamp : "", options,
tag ? tag : "", date ? date : "",
conflict ? conflict : "");
if (entries_line != NULL)
{
free (entries_line);
}
if (scratched_file != NULL)
{
free (scratched_file);
scratched_file = NULL;
}
len = (strlen (name) + strlen (version) + strlen (options) + 80);
if (tag)
len += strlen (tag);
if (date)
len += strlen (date);
entries_line = xmalloc (len);
sprintf (entries_line, "/%s/%s/", name, version);
if (conflict != NULL)
{
strcat (entries_line, "+=");
}
strcat (entries_line, "/");
strcat (entries_line, options);
strcat (entries_line, "/");
if (tag != NULL)
{
strcat (entries_line, "T");
strcat (entries_line, tag);
}
else if (date != NULL)
{
strcat (entries_line, "D");
strcat (entries_line, date);
}
}
void
server_scratch (const char *fname)
{
if (entries_line != NULL)
{
free (entries_line);
entries_line = NULL;
}
if (scratched_file != NULL)
{
buf_output0 (protocol,
"E CVS server internal error: duplicate Scratch_Entry\n");
buf_send_counted (protocol);
return;
}
scratched_file = xstrdup (fname);
kill_scratched_file = 1;
}
void
server_scratch_entry_only (void)
{
kill_scratched_file = 0;
}
static void
new_entries_line (void)
{
if (entries_line)
{
buf_output0 (protocol, entries_line);
buf_output (protocol, "\n", 1);
}
else
buf_output0 (protocol,
"CVS server internal error: Register missing\n");
free (entries_line);
entries_line = NULL;
}
static void
serve_ci (char *arg)
{
do_cvs_command ("commit", commit);
}
static void
checked_in_response (const char *file, const char *update_dir,
const char *repository)
{
if (supported_response ("Mode"))
{
struct stat sb;
char *mode_string;
if (stat (file, &sb) < 0)
{
if (!existence_error (errno))
error (0, errno, "cannot stat %s", file);
}
else
{
buf_output0 (protocol, "Mode ");
mode_string = mode_to_string (sb.st_mode);
buf_output0 (protocol, mode_string);
buf_output0 (protocol, "\n");
free (mode_string);
}
}
buf_output0 (protocol, "Checked-in ");
output_dir (update_dir, repository);
buf_output0 (protocol, file);
buf_output (protocol, "\n", 1);
new_entries_line ();
}
void
server_checked_in (const char *file, const char *update_dir,
const char *repository)
{
if (noexec)
return;
if (scratched_file != NULL && entries_line == NULL)
{
buf_output0 (protocol, "Remove-entry ");
output_dir (update_dir, repository);
buf_output0 (protocol, file);
buf_output (protocol, "\n", 1);
free (scratched_file);
scratched_file = NULL;
}
else
{
checked_in_response (file, update_dir, repository);
}
buf_send_counted (protocol);
}
void
server_update_entries (const char *file, const char *update_dir,
const char *repository,
enum server_updated_arg4 updated)
{
if (noexec)
return;
if (updated == SERVER_UPDATED)
checked_in_response (file, update_dir, repository);
else
{
if (!supported_response ("New-entry"))
return;
buf_output0 (protocol, "New-entry ");
output_dir (update_dir, repository);
buf_output0 (protocol, file);
buf_output (protocol, "\n", 1);
new_entries_line ();
}
buf_send_counted (protocol);
}
static void
serve_update (char *arg)
{
do_cvs_command ("update", update);
}
static void
serve_diff (char *arg)
{
do_cvs_command ("diff", diff);
}
static void
serve_log (char *arg)
{
do_cvs_command ("log", cvslog);
}
static void
serve_rlog (char *arg)
{
do_cvs_command ("rlog", cvslog);
}
static void
serve_ls (char *arg)
{
do_cvs_command ("ls", ls);
}
static void
serve_rls (char *arg)
{
do_cvs_command ("rls", ls);
}
static void
serve_add (char *arg)
{
do_cvs_command ("add", add);
}
static void
serve_remove (char *arg)
{
do_cvs_command ("remove", cvsremove);
}
static void
serve_status (char *arg)
{
do_cvs_command ("status", cvsstatus);
}
static void
serve_rdiff (char *arg)
{
do_cvs_command ("rdiff", patch);
}
static void
serve_tag (char *arg)
{
do_cvs_command ("tag", cvstag);
}
static void
serve_rtag (char *arg)
{
do_cvs_command ("rtag", cvstag);
}
static void
serve_import (char *arg)
{
do_cvs_command ("import", import);
}
static void
serve_admin (char *arg)
{
do_cvs_command ("admin", admin);
}
static void
serve_history (char *arg)
{
do_cvs_command ("history", history);
}
static void
serve_release (char *arg)
{
do_cvs_command ("release", release);
}
static void
serve_watch_on (char *arg)
{
do_cvs_command ("watch", watch_on);
}
static void
serve_watch_off (char *arg)
{
do_cvs_command ("watch", watch_off);
}
static void
serve_watch_add (char *arg)
{
do_cvs_command ("watch", watch_add);
}
static void
serve_watch_remove (char *arg)
{
do_cvs_command ("watch", watch_remove);
}
static void
serve_watchers (char *arg)
{
do_cvs_command ("watchers", watchers);
}
static void
serve_editors (char *arg)
{
do_cvs_command ("editors", editors);
}
static void
serve_edit (char *arg)
{
do_cvs_command ("edit", edit);
}
# ifdef PROXY_SUPPORT
# endif
static void
serve_noop (char *arg)
{
bool pe = print_pending_error();
# ifdef PROXY_SUPPORT
if (!proxy_log)
# endif
{
server_write_entries ();
if (!pe)
(void) server_notify ();
}
if (!pe
# ifdef PROXY_SUPPORT
&& !reprocessing
# endif
)
buf_output0 (buf_to_net, "ok\n");
buf_flush (buf_to_net, 1);
}
static void
serve_version (char *arg)
{
do_cvs_command ("version", version);
}
static void
serve_init (char *arg)
{
cvsroot_t *saved_parsed_root;
if (!ISABSOLUTE (arg))
{
if (alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E init %s must be an absolute pathname", arg);
}
# ifdef AUTH_SERVER_SUPPORT
else if (Pserver_Repos != NULL)
{
if (strcmp (Pserver_Repos, arg) != 0)
{
if (alloc_pending (80 + strlen (Pserver_Repos) + strlen (arg)))
sprintf (pending_error_text, "\
E Protocol error: init says \"%s\" but pserver says \"%s\"",
arg, Pserver_Repos);
}
}
# endif
if (print_pending_error ())
return;
saved_parsed_root = current_parsed_root;
current_parsed_root = local_cvsroot (arg);
do_cvs_command ("init", init);
current_parsed_root = saved_parsed_root;
}
static void
serve_annotate (char *arg)
{
do_cvs_command ("annotate", annotate);
}
static void
serve_rannotate (char *arg)
{
do_cvs_command ("rannotate", annotate);
}
static void
serve_co (char *arg)
{
if (print_pending_error ())
return;
# ifdef PROXY_SUPPORT
if (isProxyServer ())
{
if (reprocessing)
reprocessing = false;
else if (
proxy_log)
{
rewind_buf_from_net ();
return;
}
}
# endif
do_cvs_command (!strcmp (cvs_cmd_name, "export") ? "export" : "checkout",
checkout);
}
static void
serve_export (char *arg)
{
cvs_cmd_name = "export";
serve_co (arg);
}
void
server_copy_file (const char *file, const char *update_dir,
const char *repository, const char *newfile)
{
if (!supported_response ("Copy-file"))
return;
buf_output0 (protocol, "Copy-file ");
output_dir (update_dir, repository);
buf_output0 (protocol, file);
buf_output0 (protocol, "\n");
buf_output0 (protocol, newfile);
buf_output0 (protocol, "\n");
}
void
server_modtime (struct file_info *finfo, Vers_TS *vers_ts)
{
char date[MAXDATELEN];
char outdate[MAXDATELEN];
assert (vers_ts->vn_rcs != NULL);
if (!supported_response ("Mod-time"))
return;
if (RCS_getrevtime (finfo->rcs, vers_ts->vn_rcs, date, 0) == (time_t) -1)
return;
date_to_internet (outdate, date);
buf_output0 (protocol, "Mod-time ");
buf_output0 (protocol, outdate);
buf_output0 (protocol, "\n");
}
void
server_updated (
struct file_info *finfo,
Vers_TS *vers,
enum server_updated_arg4 updated,
mode_t mode,
unsigned char *checksum,
struct buffer *filebuf)
{
if (noexec)
{
if (scratched_file)
{
free (scratched_file);
scratched_file = NULL;
}
buf_send_counted (protocol);
return;
}
if (entries_line != NULL && scratched_file == NULL)
{
FILE *f;
struct buffer_data *list, *last;
unsigned long size;
char size_text[80];
unsigned char *file;
size_t file_allocated;
size_t file_used;
if (filebuf != NULL)
{
size = buf_length (filebuf);
if (mode == (mode_t) -1)
error (1, 0, "\
CVS server internal error: no mode in server_updated");
}
else
{
struct stat sb;
if (stat (finfo->file, &sb) < 0)
{
if (existence_error (errno))
{
free (entries_line);
entries_line = NULL;
goto done;
}
error (1, errno, "reading %s", finfo->fullname);
}
size = sb.st_size;
if (mode == (mode_t) -1)
{
mode = sb.st_mode;
}
}
if (checksum != NULL)
{
static int checksum_supported = -1;
if (checksum_supported == -1)
{
checksum_supported = supported_response ("Checksum");
}
if (checksum_supported)
{
int i;
char buf[3];
buf_output0 (protocol, "Checksum ");
for (i = 0; i < 16; i++)
{
sprintf (buf, "%02x", (unsigned int) checksum[i]);
buf_output0 (protocol, buf);
}
buf_append_char (protocol, '\n');
}
}
if (updated == SERVER_UPDATED)
{
Node *node;
Entnode *entnode;
if (!(supported_response ("Created")
&& supported_response ("Update-existing")))
buf_output0 (protocol, "Updated ");
else
{
assert (vers != NULL);
if (vers->ts_user == NULL)
buf_output0 (protocol, "Created ");
else
buf_output0 (protocol, "Update-existing ");
}
node = findnode_fn (finfo->entries, finfo->file);
entnode = node->data;
free (entnode->timestamp);
entnode->timestamp = xstrdup ("=");
}
else if (updated == SERVER_MERGED)
buf_output0 (protocol, "Merged ");
else if (updated == SERVER_PATCHED)
buf_output0 (protocol, "Patched ");
else if (updated == SERVER_RCS_DIFF)
buf_output0 (protocol, "Rcs-diff ");
else
abort ();
output_dir (finfo->update_dir, finfo->repository);
buf_output0 (protocol, finfo->file);
buf_output (protocol, "\n", 1);
new_entries_line ();
{
char *mode_string;
mode_string = mode_to_string (mode);
buf_output0 (protocol, mode_string);
buf_output0 (protocol, "\n");
free (mode_string);
}
list = last = NULL;
file = NULL;
file_allocated = 0;
file_used = 0;
if (size > 0)
{
if (file_gzip_level
&& size > 100)
{
int fd;
if (filebuf != NULL)
error (1, 0, "\
CVS server internal error: unhandled case in server_updated");
fd = CVS_OPEN (finfo->file, O_RDONLY | OPEN_BINARY, 0);
if (fd < 0)
error (1, errno, "reading %s", finfo->fullname);
if (read_and_gzip (fd, finfo->fullname, &file,
&file_allocated, &file_used,
file_gzip_level))
error (1, 0, "aborting due to compression error");
size = file_used;
if (close (fd) < 0)
error (1, errno, "reading %s", finfo->fullname);
buf_output0 (protocol, "z");
}
else if (filebuf == NULL)
{
long status;
f = CVS_FOPEN (finfo->file, "rb");
if (f == NULL)
error (1, errno, "reading %s", finfo->fullname);
status = buf_read_file (f, size, &list, &last);
if (status == -2)
(*protocol->memory_error) (protocol);
else if (status != 0)
error (1, ferror (f) ? errno : 0, "reading %s",
finfo->fullname);
if (fclose (f) == EOF)
error (1, errno, "reading %s", finfo->fullname);
}
}
sprintf (size_text, "%lu\n", size);
buf_output0 (protocol, size_text);
if (file != NULL)
{
buf_output (protocol, (char *) file, file_used);
free (file);
file = NULL;
}
else if (filebuf == NULL)
buf_append_data (protocol, list, last);
else
buf_append_buffer (protocol, filebuf);
if ((updated == SERVER_UPDATED
|| updated == SERVER_PATCHED
|| updated == SERVER_RCS_DIFF)
&& filebuf == NULL
&& !joining ())
{
if (CVS_UNLINK (finfo->file) < 0)
error (0, errno, "cannot remove temp file for %s",
finfo->fullname);
}
}
else if (scratched_file != NULL && entries_line == NULL)
{
if (strcmp (scratched_file, finfo->file) != 0)
error (1, 0,
"CVS server internal error: `%s' vs. `%s' scratched",
scratched_file,
finfo->file);
free (scratched_file);
scratched_file = NULL;
if (kill_scratched_file)
buf_output0 (protocol, "Removed ");
else
buf_output0 (protocol, "Remove-entry ");
output_dir (finfo->update_dir, finfo->repository);
buf_output0 (protocol, finfo->file);
buf_output (protocol, "\n", 1);
if (vers && vers->vn_user != NULL)
{
free (vers->vn_user);
vers->vn_user = NULL;
}
if (vers && vers->ts_user != NULL)
{
free (vers->ts_user);
vers->ts_user = NULL;
}
}
else if (scratched_file == NULL && entries_line == NULL)
{
}
else
error (1, 0,
"CVS server internal error: Register *and* Scratch_Entry.\n");
buf_send_counted (protocol);
done:;
}
int
server_use_rcs_diff (void)
{
return supported_response ("Rcs-diff");
}
void
server_set_entstat (const char *update_dir, const char *repository)
{
static int set_static_supported = -1;
if (set_static_supported == -1)
set_static_supported = supported_response ("Set-static-directory");
if (!set_static_supported) return;
buf_output0 (protocol, "Set-static-directory ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
buf_send_counted (protocol);
}
void
server_clear_entstat (const char *update_dir, const char *repository)
{
static int clear_static_supported = -1;
if (clear_static_supported == -1)
clear_static_supported = supported_response ("Clear-static-directory");
if (!clear_static_supported) return;
if (noexec)
return;
buf_output0 (protocol, "Clear-static-directory ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
buf_send_counted (protocol);
}
void
server_set_sticky (const char *update_dir, const char *repository,
const char *tag, const char *date, int nonbranch)
{
static int set_sticky_supported = -1;
assert (update_dir != NULL);
if (set_sticky_supported == -1)
set_sticky_supported = supported_response ("Set-sticky");
if (!set_sticky_supported) return;
if (noexec)
return;
if (tag == NULL && date == NULL)
{
buf_output0 (protocol, "Clear-sticky ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
}
else
{
buf_output0 (protocol, "Set-sticky ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
if (tag != NULL)
{
if (nonbranch)
buf_output0 (protocol, "N");
else
buf_output0 (protocol, "T");
buf_output0 (protocol, tag);
}
else
{
buf_output0 (protocol, "D");
buf_output0 (protocol, date);
}
buf_output0 (protocol, "\n");
}
buf_send_counted (protocol);
}
void
server_edit_file (struct file_info *finfo)
{
buf_output (protocol, "Edit-file ", 10);
output_dir (finfo->update_dir, finfo->repository);
buf_output0 (protocol, finfo->file);
buf_output (protocol, "\n", 1);
buf_send_counted (protocol);
}
struct template_proc_data
{
const char *update_dir;
const char *repository;
};
static int
template_proc (const char *repository, const char *template, void *closure)
{
FILE *fp;
char buf[1024];
size_t n;
struct stat sb;
struct template_proc_data *data = (struct template_proc_data *)closure;
if (!supported_response ("Template"))
return 0;
buf_output0 (protocol, "Template ");
output_dir (data->update_dir, data->repository);
buf_output0 (protocol, "\n");
fp = CVS_FOPEN (template, "rb");
if (fp == NULL)
{
error (0, errno, "Couldn't open rcsinfo template file %s", template);
return 1;
}
if (fstat (fileno (fp), &sb) < 0)
{
error (0, errno, "cannot stat rcsinfo template file %s", template);
return 1;
}
sprintf (buf, "%ld\n", (long) sb.st_size);
buf_output0 (protocol, buf);
while (!feof (fp))
{
n = fread (buf, 1, sizeof buf, fp);
buf_output (protocol, buf, n);
if (ferror (fp))
{
error (0, errno, "cannot read rcsinfo template file %s", template);
(void) fclose (fp);
return 1;
}
}
buf_send_counted (protocol);
if (fclose (fp) < 0)
error (0, errno, "cannot close rcsinfo template file %s", template);
return 0;
}
void
server_clear_template (const char *update_dir, const char *repository)
{
assert (update_dir != NULL);
if (noexec)
return;
if (!supported_response ("Clear-template") &&
!supported_response ("Template"))
return;
if (supported_response ("Clear-template"))
{
buf_output0 (protocol, "Clear-template ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
buf_send_counted (protocol);
}
else
{
buf_output0 (protocol, "Template ");
output_dir (update_dir, repository);
buf_output0 (protocol, "\n");
buf_output0 (protocol, "0\n");
buf_send_counted (protocol);
}
}
void
server_template (const char *update_dir, const char *repository)
{
struct template_proc_data data;
data.update_dir = update_dir;
data.repository = repository;
(void) Parse_Info (CVSROOTADM_RCSINFO, repository, template_proc,
PIOPT_ALL, &data);
}
static void
serve_gzip_contents (char *arg)
{
int level;
bool forced = false;
# ifdef PROXY_SUPPORT
assert (!proxy_log);
# endif
level = atoi (arg);
if (level == 0)
level = 6;
if (config && level < config->MinCompressionLevel)
{
level = config->MinCompressionLevel;
forced = true;
}
if (config && level > config->MaxCompressionLevel)
{
level = config->MaxCompressionLevel;
forced = true;
}
if (forced && !quiet
&& alloc_pending_warning (120 + strlen (program_name)))
sprintf (pending_warning_text,
"E %s server: Forcing compression level %d (allowed: %d <= z <= %d).",
program_name, level, config->MinCompressionLevel,
config->MaxCompressionLevel);
gzip_level = file_gzip_level = level;
}
static void
serve_gzip_stream (char *arg)
{
int level;
bool forced = false;
level = atoi (arg);
if (config && level < config->MinCompressionLevel)
{
level = config->MinCompressionLevel;
forced = true;
}
if (config && level > config->MaxCompressionLevel)
{
level = config->MaxCompressionLevel;
forced = true;
}
if (forced && !quiet
&& alloc_pending_warning (120 + strlen (program_name)))
sprintf (pending_warning_text,
"E %s server: Forcing compression level %d (allowed: %d <= z <= %d).",
program_name, level, config->MinCompressionLevel,
config->MaxCompressionLevel);
gzip_level = level;
buf_from_net = compress_buffer_initialize (buf_from_net, 1,
0 ,
buf_from_net->memory_error);
# ifdef PROXY_SUPPORT
if (reprocessing) return;
# endif
buf_to_net = compress_buffer_initialize (buf_to_net, 0, level,
buf_to_net->memory_error);
}
static void
serve_wrapper_sendme_rcs_options (char *arg)
{
char *wrapper_line = NULL;
# ifdef PROXY_SUPPORT
if (reprocessing) return;
# endif
wrap_setup ();
for (wrap_unparse_rcs_options (&wrapper_line, 1);
wrapper_line;
wrap_unparse_rcs_options (&wrapper_line, 0))
{
buf_output0 (buf_to_net, "Wrapper-rcsOption ");
buf_output0 (buf_to_net, wrapper_line);
buf_output0 (buf_to_net, "\012");;
free (wrapper_line);
}
buf_output0 (buf_to_net, "ok\012");
buf_flush (buf_to_net, 1);
}
static void
serve_ignore (char *arg)
{
# ifdef PROXY_SUPPORT
assert (!proxy_log);
# endif
}
static int
expand_proc (int argc, char **argv, char *where, char *mwhere, char *mfile, int shorten, int local_specified, char *omodule, char *msg)
{
int i;
char *dir = argv[0];
if (mwhere != NULL)
{
buf_output0 (buf_to_net, "Module-expansion ");
if (server_dir != NULL)
{
buf_output0 (buf_to_net, server_dir);
buf_output0 (buf_to_net, "/");
}
buf_output0 (buf_to_net, mwhere);
if (mfile != NULL)
{
buf_append_char (buf_to_net, '/');
buf_output0 (buf_to_net, mfile);
}
buf_append_char (buf_to_net, '\n');
}
else
{
if (argc == 1)
{
buf_output0 (buf_to_net, "Module-expansion ");
if (server_dir != NULL)
{
buf_output0 (buf_to_net, server_dir);
buf_output0 (buf_to_net, "/");
}
buf_output0 (buf_to_net, dir);
buf_append_char (buf_to_net, '\n');
}
else
{
for (i = 1; i < argc; ++i)
{
buf_output0 (buf_to_net, "Module-expansion ");
if (server_dir != NULL)
{
buf_output0 (buf_to_net, server_dir);
buf_output0 (buf_to_net, "/");
}
buf_output0 (buf_to_net, dir);
buf_append_char (buf_to_net, '/');
buf_output0 (buf_to_net, argv[i]);
buf_append_char (buf_to_net, '\n');
}
}
}
return 0;
}
static void
serve_expand_modules (char *arg)
{
int i;
int err = 0;
DBM *db;
# ifdef PROXY_SUPPORT
if (!reprocessing)
# endif
{
err = 0;
db = open_module ();
for (i = 1; i < argument_count; i++)
err += do_module (db, argument_vector[i],
CHECKOUT, "Updating", expand_proc,
NULL, 0, 0, 0, 0, NULL);
close_module (db);
}
{
char **cp;
for (cp = argument_vector + 1;
cp < argument_vector + argument_count;
++cp)
free (*cp);
argument_count = 1;
}
# ifdef PROXY_SUPPORT
if (!reprocessing)
# endif
{
if (err)
buf_output0 (buf_to_net, "error \n");
else
buf_output0 (buf_to_net, "ok\n");
buf_flush (buf_to_net, 1);
}
}
static void
serve_command_prep (char *arg)
{
bool redirect_supported;
# ifdef PROXY_SUPPORT
bool ditch_log;
# endif
if (print_pending_error ()) return;
redirect_supported = supported_response ("Redirect");
if (redirect_supported
&& lookup_command_attribute (arg) & CVS_CMD_MODIFIES_REPOSITORY
&& isProxyServer ())
{
if (supported_response ("Referrer"))
{
char *referrer = Xasprintf (":ext:%s@%s%s", getcaller(),
server_hostname,
current_parsed_root->directory);
buf_output0 (buf_to_net, "Referrer ");
buf_output0 (buf_to_net, referrer);
buf_output0 (buf_to_net, "\n");
free (referrer);
}
buf_output0 (buf_to_net, "Redirect ");
buf_output0 (buf_to_net, config->PrimaryServer->original);
buf_output0 (buf_to_net, "\n");
buf_flush (buf_to_net, 1);
# ifdef PROXY_SUPPORT
ditch_log = true;
# endif
}
else
{
buf_output0 (buf_to_net, "ok\n");
buf_flush (buf_to_net, 1);
# ifdef PROXY_SUPPORT
if (lookup_command_attribute (arg) & CVS_CMD_MODIFIES_REPOSITORY
&& isProxyServer ())
ditch_log = false;
else
ditch_log = true;
# endif
}
# ifdef PROXY_SUPPORT
if (proxy_log && ditch_log)
{
log_buffer_closelog (proxy_log);
log_buffer_closelog (proxy_log_out);
proxy_log = NULL;
}
# endif
}
static void
serve_referrer (char *arg)
{
if (error_pending ()) return;
referrer = parse_cvsroot (arg);
if (!referrer
&& alloc_pending (80 + strlen (arg)))
sprintf (pending_error_text,
"E Protocol error: Invalid Referrer: `%s'",
arg);
}
static void serve_valid_requests (char *arg);
#endif
#if defined(SERVER_SUPPORT) || defined(CLIENT_SUPPORT)
struct request requests[] =
{
#ifdef SERVER_SUPPORT
#define REQ_LINE(n, f, s) {n, f, s}
#else
#define REQ_LINE(n, f, s) {n, s}
#endif
REQ_LINE("Root", serve_root, RQ_ESSENTIAL | RQ_ROOTLESS),
REQ_LINE("Valid-responses", serve_valid_responses,
RQ_ESSENTIAL | RQ_ROOTLESS),
REQ_LINE("valid-requests", serve_valid_requests,
RQ_ESSENTIAL | RQ_ROOTLESS),
REQ_LINE("Command-prep", serve_command_prep, 0),
REQ_LINE("Referrer", serve_referrer, 0),
REQ_LINE("Repository", serve_repository, 0),
REQ_LINE("Directory", serve_directory, RQ_ESSENTIAL),
REQ_LINE("Relative-directory", serve_directory, 0),
REQ_LINE("Max-dotdot", serve_max_dotdot, 0),
REQ_LINE("Static-directory", serve_static_directory, 0),
REQ_LINE("Sticky", serve_sticky, 0),
REQ_LINE("Entry", serve_entry, RQ_ESSENTIAL),
REQ_LINE("Kopt", serve_kopt, 0),
REQ_LINE("Checkin-time", serve_checkin_time, 0),
REQ_LINE("Modified", serve_modified, RQ_ESSENTIAL),
REQ_LINE("Is-modified", serve_is_modified, 0),
REQ_LINE("UseUnchanged", serve_enable_unchanged, RQ_ENABLEME | RQ_ROOTLESS),
REQ_LINE("Unchanged", serve_unchanged, RQ_ESSENTIAL),
REQ_LINE("Notify", serve_notify, 0),
REQ_LINE("Hostname", serve_hostname, 0),
REQ_LINE("LocalDir", serve_localdir, 0),
REQ_LINE("Questionable", serve_questionable, 0),
REQ_LINE("Argument", serve_argument, RQ_ESSENTIAL),
REQ_LINE("Argumentx", serve_argumentx, RQ_ESSENTIAL),
REQ_LINE("Global_option", serve_global_option, RQ_ROOTLESS),
REQ_LINE("Gzip-stream", serve_gzip_stream, RQ_ROOTLESS),
REQ_LINE("wrapper-sendme-rcsOptions",
serve_wrapper_sendme_rcs_options,
0),
REQ_LINE("Set", serve_set, RQ_ROOTLESS),
#ifdef ENCRYPTION
# ifdef HAVE_KERBEROS
REQ_LINE("Kerberos-encrypt", serve_kerberos_encrypt, RQ_ROOTLESS),
# endif
# ifdef HAVE_GSSAPI
REQ_LINE("Gssapi-encrypt", serve_gssapi_encrypt, RQ_ROOTLESS),
# endif
#endif
#ifdef HAVE_GSSAPI
REQ_LINE("Gssapi-authenticate", serve_gssapi_authenticate, RQ_ROOTLESS),
#endif
REQ_LINE("expand-modules", serve_expand_modules, 0),
REQ_LINE("ci", serve_ci, RQ_ESSENTIAL),
REQ_LINE("co", serve_co, RQ_ESSENTIAL),
REQ_LINE("update", serve_update, RQ_ESSENTIAL),
REQ_LINE("diff", serve_diff, 0),
REQ_LINE("log", serve_log, 0),
REQ_LINE("rlog", serve_rlog, 0),
REQ_LINE("list", serve_ls, 0),
REQ_LINE("rlist", serve_rls, 0),
REQ_LINE("global-list-quiet", serve_noop, RQ_ROOTLESS),
REQ_LINE("ls", serve_rls, 0),
REQ_LINE("add", serve_add, 0),
REQ_LINE("remove", serve_remove, 0),
REQ_LINE("update-patches", serve_ignore, 0),
REQ_LINE("gzip-file-contents", serve_gzip_contents, RQ_ROOTLESS),
REQ_LINE("status", serve_status, 0),
REQ_LINE("rdiff", serve_rdiff, 0),
REQ_LINE("tag", serve_tag, 0),
REQ_LINE("rtag", serve_rtag, 0),
REQ_LINE("import", serve_import, 0),
REQ_LINE("admin", serve_admin, 0),
REQ_LINE("export", serve_export, 0),
REQ_LINE("history", serve_history, 0),
REQ_LINE("release", serve_release, 0),
REQ_LINE("watch-on", serve_watch_on, 0),
REQ_LINE("watch-off", serve_watch_off, 0),
REQ_LINE("watch-add", serve_watch_add, 0),
REQ_LINE("watch-remove", serve_watch_remove, 0),
REQ_LINE("watchers", serve_watchers, 0),
REQ_LINE("editors", serve_editors, 0),
REQ_LINE("edit", serve_edit, 0),
REQ_LINE("init", serve_init, RQ_ROOTLESS),
REQ_LINE("annotate", serve_annotate, 0),
REQ_LINE("rannotate", serve_rannotate, 0),
REQ_LINE("noop", serve_noop, RQ_ROOTLESS),
REQ_LINE("version", serve_version, RQ_ROOTLESS),
REQ_LINE(NULL, NULL, 0)
#undef REQ_LINE
};
#endif
#ifdef SERVER_SUPPORT
static void
serve_valid_requests (char *arg)
{
struct request *rq;
if (print_pending_error ()
#ifdef PROXY_SUPPORT
|| reprocessing
#endif
)
return;
buf_output0 (buf_to_net, "Valid-requests");
for (rq = requests; rq->name != NULL; rq++)
{
if (rq->func != NULL)
{
buf_append_char (buf_to_net, ' ');
buf_output0 (buf_to_net, rq->name);
}
}
if (config && config->MinCompressionLevel
&& supported_response ("Force-gzip"))
{
buf_output0 (buf_to_net, "\n");
buf_output0 (buf_to_net, "Force-gzip");
}
buf_output0 (buf_to_net, "\nok\n");
buf_flush (buf_to_net, 1);
}
#ifdef SUNOS_KLUDGE
static int command_pid_is_dead;
static void wait_sig (int sig)
{
int status;
pid_t r = wait (&status);
if (r == command_pid)
command_pid_is_dead++;
}
#endif
void
server_cleanup (void)
{
TRACE (TRACE_FUNCTION, "server_cleanup()");
assert (server_active);
if (error_use_protocol)
{
if (buf_to_net != NULL)
{
int status;
set_block (buf_to_net);
buf_flush (buf_to_net, 1);
if (buf_from_net)
{
status = buf_shutdown (buf_from_net);
if (status != 0)
error (0, status, "shutting down buffer from client");
buf_free (buf_from_net);
buf_from_net = NULL;
}
}
if (!dont_delete_temp)
{
int save_noexec;
#ifdef SUNOS_KLUDGE
if (command_pid > 0)
{
int status;
pid_t r;
signal (SIGCHLD, wait_sig);
kill (command_pid, SIGINT);
do_waitpid:
r = waitpid (command_pid, &status, WNOHANG);
if (r == 0)
;
else if (r == command_pid)
command_pid_is_dead++;
else if (r == -1)
switch (errno)
{
case ECHILD:
command_pid_is_dead++;
break;
case EINTR:
goto do_waitpid;
}
else
abort ();
while (!command_pid_is_dead)
{
struct timeval timeout;
struct fd_set_wrapper readfds;
char buf[100];
int i;
timeout.tv_sec = 2;
timeout.tv_usec = 0;
readfds = command_fds_to_drain;
switch (select (max_command_fd + 1, &readfds.fds,
NULL, NULL &timeout))
{
case -1:
if (errno != EINTR)
abort ();
case 0:
break;
case 1:
for (i = 0; i <= max_command_fd; i++)
{
if (!FD_ISSET (i, &readfds.fds))
continue;
while (read (i, buf, sizeof (buf)) >= 1)
;
}
break;
default:
abort ();
}
}
}
#endif
CVS_CHDIR (get_cvs_tmp_dir ());
save_noexec = noexec;
noexec = 0;
unlink_file_dir (orig_server_temp_dir);
noexec = save_noexec;
}
if (buf_to_net != NULL)
{
struct buffer *buf_to_net_save = buf_to_net;
buf_to_net = NULL;
(void) buf_flush (buf_to_net_save, 1);
(void) buf_shutdown (buf_to_net_save);
buf_free (buf_to_net_save);
error_use_protocol = 0;
}
}
server_active = 0;
}
#ifdef PROXY_SUPPORT
size_t MaxProxyBufferSize = (size_t)(8 * 1024 * 1024);
#endif
static const char *const server_usage[] =
{
"Usage: %s %s [-c config-file]\n",
"\t-c config-file\tPath to an alternative CVS config file.\n",
"Normally invoked by a cvs client on a remote machine.\n",
NULL
};
void
parseServerOptions (int argc, char **argv)
{
int c;
optind = 0;
while ((c = getopt (argc, argv, "+c:")) != -1)
{
switch (c)
{
#ifdef ALLOW_CONFIG_OVERRIDE
case 'c':
if (gConfigPath) free (gConfigPath);
gConfigPath = xstrdup (optarg);
break;
#endif
case '?':
default:
usage (server_usage);
break;
}
}
}
int
server (int argc, char **argv)
{
char *error_prog_name;
if (argc == -1)
usage (server_usage);
if (getenv ("CVS_PARENT_SERVER_SLEEP"))
{
int secs = atoi (getenv ("CVS_PARENT_SERVER_SLEEP"));
TRACE (TRACE_DATA, "Sleeping CVS_PARENT_SERVER_SLEEP (%d) seconds",
secs);
sleep (secs);
}
else
TRACE (TRACE_DATA, "CVS_PARENT_SERVER_SLEEP not set.");
if (!buf_to_net)
{
buf_to_net = fd_buffer_initialize (STDOUT_FILENO, 0, NULL, false,
outbuf_memory_error);
buf_from_net = fd_buffer_initialize (STDIN_FILENO, 0, NULL, true,
outbuf_memory_error);
}
setup_logfiles ("CVS_SERVER_LOG", &buf_to_net, &buf_from_net);
#ifdef PROXY_SUPPORT
{
buf_from_net = log_buffer_initialize (buf_from_net, NULL,
# ifdef PROXY_SUPPORT
true,
config
? config->MaxProxyBufferSize
: MaxProxyBufferSize,
# endif
true, outbuf_memory_error);
proxy_log = buf_from_net;
buf_to_net = log_buffer_initialize (buf_to_net, NULL,
# ifdef PROXY_SUPPORT
true,
config
? config->MaxProxyBufferSize
: MaxProxyBufferSize,
# endif
false, outbuf_memory_error);
proxy_log_out = buf_to_net;
}
#endif
saved_output = buf_nonio_initialize (outbuf_memory_error);
saved_outerr = buf_nonio_initialize (outbuf_memory_error);
error_use_protocol = 1;
argument_vector_size = 1;
argument_vector = xmalloc (argument_vector_size * sizeof (char *));
argument_count = 1;
error_prog_name = xmalloc (strlen (program_name) + 8);
sprintf(error_prog_name, "%s server", program_name);
argument_vector[0] = error_prog_name;
while (1)
{
char *cmd, *orig_cmd;
struct request *rq;
int status;
status = buf_read_line (buf_from_net, &cmd, NULL);
if (status == -2)
{
buf_output0 (buf_to_net, "E Fatal server error, aborting.\n\
error ENOMEM Virtual memory exhausted.\n");
break;
}
if (status != 0)
break;
orig_cmd = cmd;
for (rq = requests; rq->name != NULL; ++rq)
if (strncmp (cmd, rq->name, strlen (rq->name)) == 0)
{
int len = strlen (rq->name);
if (cmd[len] == '\0')
cmd += len;
else if (cmd[len] == ' ')
cmd += len + 1;
else
continue;
if (!(rq->flags & RQ_ROOTLESS)
&& current_parsed_root == NULL)
{
if (alloc_pending (80))
sprintf (pending_error_text,
"E Protocol error: Root request missing");
}
else
{
if (config && config->MinCompressionLevel && !gzip_level
&& !(rq->flags & RQ_ROOTLESS))
{
if (alloc_pending (80 + strlen (program_name)))
sprintf (pending_error_text,
"E %s [server aborted]: Compression must be used with this server.",
program_name);
}
(*rq->func) (cmd);
}
break;
}
if (rq->name == NULL)
{
if (!print_pending_error ())
{
buf_output0 (buf_to_net, "error unrecognized request `");
buf_output0 (buf_to_net, cmd);
buf_append_char (buf_to_net, '\'');
buf_append_char (buf_to_net, '\n');
}
}
free (orig_cmd);
}
free (error_prog_name);
if (!buf_empty (buf_from_net))
{
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR, "Dying gasps received from client.");
#endif
error (0, 0, "Dying gasps received from client.");
}
#ifdef HAVE_PAM
if (pamh)
{
int retval;
retval = pam_close_session (pamh, 0);
# ifdef HAVE_SYSLOG_H
if (retval != PAM_SUCCESS)
syslog (LOG_DAEMON | LOG_ERR,
"PAM close session error: %s",
pam_strerror (pamh, retval));
# endif
retval = pam_end (pamh, retval);
# ifdef HAVE_SYSLOG_H
if (retval != PAM_SUCCESS)
syslog (LOG_DAEMON | LOG_ERR,
"PAM failed to release authenticator, error: %s",
pam_strerror (pamh, retval));
# endif
}
#endif
return 0;
}
#if defined (HAVE_KERBEROS) || defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
static void
switch_to_user (const char *cvs_username, const char *username)
{
struct passwd *pw;
#ifdef HAVE_PAM
int retval;
char *pam_stage = "open session";
if (pamh)
{
retval = pam_open_session (pamh, 0);
if (retval == PAM_SUCCESS)
{
pam_stage = "get pam user";
retval = pam_get_item (pamh, PAM_USER, (const void **)&username);
}
if (retval != PAM_SUCCESS)
{
printf("E PAM %s error: %s\n", pam_stage,
pam_strerror (pamh, retval));
exit (EXIT_FAILURE);
}
}
#endif
pw = getpwnam (username);
if (pw == NULL)
{
printf ("E Fatal error, aborting.\n\
error 0 %s: no such system user\n", username);
exit (EXIT_FAILURE);
}
if (pw->pw_uid == 0)
{
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ALERT,
"attempt to root from account: %s", cvs_username
);
#endif
printf("error 0: root not allowed\n");
exit (EXIT_FAILURE);
}
#if HAVE_INITGROUPS
if (initgroups (pw->pw_name, pw->pw_gid) < 0
# ifdef EPERM
&& errno != EPERM
# endif
)
{
printf ("error 0 initgroups failed: %s\n", strerror (errno));
exit (EXIT_FAILURE);
}
#endif
#ifdef HAVE_PAM
if (pamh)
{
retval = pam_setcred (pamh, PAM_ESTABLISH_CRED);
if (retval != PAM_SUCCESS)
{
printf("E PAM reestablish credentials error: %s\n",
pam_strerror (pamh, retval));
exit (EXIT_FAILURE);
}
}
#endif
#ifdef SETXID_SUPPORT
if (getgid() != getegid())
{
if (setgid (getegid ()) < 0)
{
printf ("error 0 setgid failed: %s\n", strerror (errno));
exit (EXIT_FAILURE);
}
}
else
#endif
{
if (setgid (pw->pw_gid) < 0)
{
printf ("error 0 setgid failed: %s\n", strerror (errno));
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR,
"setgid to %d failed (%m): real %d/%d, effective %d/%d ",
pw->pw_gid, getuid(), getgid(), geteuid(), getegid());
#endif
exit (EXIT_FAILURE);
}
}
#if HAVE_INITGROUPS
if (initgroups (pw->pw_name, pw->pw_gid) < 0
# ifdef EPERM
&& errno != EPERM
# endif
)
{
printf ("error 0 initgroups failed: %s\n", strerror (errno));
exit (EXIT_FAILURE);
}
#endif
if (setuid (pw->pw_uid) < 0)
{
printf ("error 0 setuid failed: %s\n", strerror (errno));
#ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR,
"setuid to %d failed (%m): real %d/%d, effective %d/%d ",
pw->pw_uid, getuid(), getgid(), geteuid(), getegid());
#endif
exit (EXIT_FAILURE);
}
umask (0);
#ifdef AUTH_SERVER_SUPPORT
if (CVS_Username == NULL)
CVS_Username = xstrdup (username);
#endif
setenv ("LOGNAME", username, 1);
setenv ("USER", username, 1);
# ifdef AUTH_SERVER_SUPPORT
setenv ("CVS_USER", CVS_Username, 1);
# endif
}
#endif
#ifdef AUTH_SERVER_SUPPORT
extern char *crypt (const char *, const char *);
static int
check_repository_password (char *username, char *password, char *repository, char **host_user_ptr)
{
int retval = 0;
FILE *fp;
char *filename;
char *linebuf = NULL;
size_t linebuf_len;
int found_it = 0;
int namelen;
filename = xmalloc (strlen (repository)
+ 1
+ strlen (CVSROOTADM)
+ 1
+ strlen (CVSROOTADM_PASSWD)
+ 1);
(void) sprintf (filename, "%s/%s/%s", repository,
CVSROOTADM, CVSROOTADM_PASSWD);
fp = CVS_FOPEN (filename, "r");
if (fp == NULL)
{
if (!existence_error (errno))
error (0, errno, "cannot open %s", filename);
free (filename);
return 0;
}
namelen = strlen (username);
while (getline (&linebuf, &linebuf_len, fp) >= 0)
{
if ((strncmp (linebuf, username, namelen) == 0)
&& (linebuf[namelen] == ':'))
{
found_it = 1;
break;
}
}
if (ferror (fp))
error (0, errno, "cannot read %s", filename);
if (fclose (fp) < 0)
error (0, errno, "cannot close %s", filename);
if (found_it)
{
char *found_password, *host_user_tmp;
char *non_cvsuser_portion;
non_cvsuser_portion = linebuf + namelen;
strtok (non_cvsuser_portion, "\n");
if (strchr (non_cvsuser_portion, ':') == non_cvsuser_portion)
non_cvsuser_portion++;
if ((non_cvsuser_portion == NULL)
|| (strlen (non_cvsuser_portion) == 0)
|| ((strspn (non_cvsuser_portion, " \t"))
== strlen (non_cvsuser_portion)))
{
found_password = NULL;
host_user_tmp = NULL;
}
else if (strncmp (non_cvsuser_portion, ":", 1) == 0)
{
found_password = NULL;
host_user_tmp = non_cvsuser_portion + 1;
if (strlen (host_user_tmp) == 0)
host_user_tmp = NULL;
}
else
{
found_password = strtok (non_cvsuser_portion, ":");
host_user_tmp = strtok (NULL, ":");
}
if (host_user_tmp == NULL)
host_user_tmp = username;
if ((found_password == NULL)
|| ((strcmp (found_password, crypt (password, found_password))
== 0)))
{
*host_user_ptr = xstrdup (host_user_tmp);
retval = 1;
}
else
{
#ifdef LOG_AUTHPRIV
syslog (LOG_AUTHPRIV | LOG_NOTICE,
"password mismatch for %s in %s: %s vs. %s", username,
repository, crypt(password, found_password), found_password);
#endif
*host_user_ptr = NULL;
retval = 2;
}
}
else
{
*host_user_ptr = NULL;
retval = 0;
}
free (filename);
if (linebuf)
free (linebuf);
return retval;
}
#ifdef HAVE_PAM
static int
cvs_pam_conv (int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
int i;
struct pam_response *response;
assert (msg && resp);
response = xnmalloc (num_msg, sizeof (struct pam_response));
memset (response, 0, num_msg * sizeof (struct pam_response));
for (i = 0; i < num_msg; i++)
{
switch (msg[i]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
assert (pam_username != 0);
response[i].resp = xstrdup (pam_username);
break;
case PAM_PROMPT_ECHO_OFF:
assert (pam_password != 0);
response[i].resp = xstrdup (pam_password);
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
printf ("E %s\n", msg[i]->msg);
break;
default:
goto cleanup;
}
}
*resp = response;
return PAM_SUCCESS;
cleanup:
for (i = 0; i < num_msg; i++)
{
if (response[i].resp)
{
free (response[i].resp);
response[i].resp = 0;
}
}
free (response);
return PAM_CONV_ERR;
}
static int
check_pam_password (char **username, char *password)
{
int retval, err;
struct pam_conv conv = { cvs_pam_conv, 0 };
char *pam_stage = "start";
pam_username = *username;
pam_password = password;
retval = pam_start (PAM_SERVICE_NAME, *username, &conv, &pamh);
if (retval == PAM_SUCCESS)
{
pam_stage = "set dummy tty";
retval = pam_set_item (pamh, PAM_TTY, PAM_SERVICE_NAME);
}
if (retval == PAM_SUCCESS)
{
pam_stage = "authenticate";
retval = pam_authenticate (pamh, 0);
}
if (retval == PAM_SUCCESS)
{
pam_stage = "account";
retval = pam_acct_mgmt (pamh, 0);
}
if (retval == PAM_SUCCESS)
{
pam_stage = "get pam user";
retval = pam_get_item (pamh, PAM_USER, (const void **)username);
}
if (retval != PAM_SUCCESS)
printf ("E PAM %s error: %s\n", pam_stage, pam_strerror (pamh, retval));
pam_username = 0;
pam_password = 0;
return retval == PAM_SUCCESS;
}
#endif
static int
check_system_password (char *username, char *password)
{
char *found_passwd = NULL;
struct passwd *pw;
#ifdef HAVE_GETSPNAM
{
struct spwd *spw;
spw = getspnam (username);
if (spw != NULL)
found_passwd = spw->sp_pwdp;
}
#endif
if (found_passwd == NULL && (pw = getpwnam (username)) != NULL)
found_passwd = pw->pw_passwd;
if (found_passwd == NULL)
{
printf ("E Fatal error, aborting.\n\
error 0 %s: no such user\n", username);
exit (EXIT_FAILURE);
}
strtok (found_passwd, ",");
if (*found_passwd)
{
if (strcmp (found_passwd, crypt (password, found_passwd)) == 0)
return 1;
else
{
#ifdef LOG_AUTHPRIV
syslog (LOG_AUTHPRIV | LOG_NOTICE,
"password mismatch for %s: %s vs. %s", username,
crypt(password, found_passwd), found_passwd);
#endif
return 0;
}
}
#ifdef LOG_AUTHPRIV
syslog (LOG_AUTHPRIV | LOG_NOTICE,
"user %s authenticated because of blank system password",
username);
#endif
return 1;
}
static char *
check_password (char *username, char *password, char *repository)
{
int rc;
char *host_user = NULL;
rc = check_repository_password (username, password, repository,
&host_user);
if (rc == 2)
return NULL;
if (rc == 1)
goto handle_return;
assert (rc == 0);
if (!config->system_auth)
{
printf ("error 0 no such user %s in CVSROOT/passwd\n", username);
exit (EXIT_FAILURE);
}
#ifdef HAVE_PAM
if (check_pam_password (&username, password))
#else
if (check_system_password (username, password))
#endif
host_user = xstrdup (username);
else
host_user = NULL;
#ifdef LOG_AUTHPRIV
if (!host_user)
syslog (LOG_AUTHPRIV | LOG_NOTICE,
"login refused for %s: user has no password", username);
#endif
handle_return:
if (host_user)
{
CVS_Username = xmalloc (strlen (username) + 1);
strcpy (CVS_Username, username);
}
return host_user;
}
#endif
#if defined (AUTH_SERVER_SUPPORT) || defined (HAVE_GSSAPI)
static void
pserver_read_line (char **tmp, size_t *tmp_len)
{
int status;
status = buf_read_short_line (buf_from_net, tmp, tmp_len, PATH_MAX);
if (status == -1)
{
# ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_NOTICE,
"unexpected EOF encountered during authentication");
# endif
error (1, 0, "unexpected EOF encountered during authentication");
}
if (status == -2)
status = ENOMEM;
if (status != 0)
{
# ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_NOTICE,
"error reading from net while validating pserver");
# endif
error (1, status, "error reading from net while validating pserver");
}
}
void
pserver_authenticate_connection (void)
{
char *tmp;
#ifdef AUTH_SERVER_SUPPORT
char *repository = NULL;
char *username = NULL;
char *password = NULL;
char *host_user;
char *descrambled_password;
#endif
int verify_and_exit = 0;
buf_to_net = fd_buffer_initialize (STDOUT_FILENO, 0, NULL, false,
outbuf_memory_error);
buf_from_net = fd_buffer_initialize (STDIN_FILENO, 0, NULL, true,
outbuf_memory_error);
#ifdef SO_KEEPALIVE
{
int on = 1;
if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE,
&on, sizeof on) < 0)
{
# ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR, "error setting KEEPALIVE: %m");
# endif
}
}
#endif
pserver_read_line (&tmp, NULL);
if (strcmp (tmp, "BEGIN VERIFICATION REQUEST") == 0)
verify_and_exit = 1;
else if (strcmp (tmp, "BEGIN AUTH REQUEST") == 0)
;
else if (strcmp (tmp, "BEGIN GSSAPI REQUEST") == 0)
{
#ifdef HAVE_GSSAPI
free (tmp);
gserver_authenticate_connection ();
return;
#else
error (1, 0, "GSSAPI authentication not supported by this server");
#endif
}
else
error (1, 0, "bad auth protocol start: %s", tmp);
#ifndef AUTH_SERVER_SUPPORT
error (1, 0, "Password authentication not supported by this server");
#else
free (tmp);
pserver_read_line (&repository, NULL);
pserver_read_line (&username, NULL);
pserver_read_line (&password, NULL);
pserver_read_line (&tmp, NULL);
if (strcmp (tmp,
verify_and_exit ?
"END VERIFICATION REQUEST" : "END AUTH REQUEST")
!= 0)
{
error (1, 0, "bad auth protocol end: %s", tmp);
}
free (tmp);
if (!root_allow_ok (repository))
{
error (1, 0, "%s: no such repository", repository);
# ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_NOTICE, "login refused for %s", repository);
# endif
goto i_hate_you;
}
config = get_root_allow_config (repository, gConfigPath);
descrambled_password = descramble (password);
host_user = check_password (username, descrambled_password, repository);
if (host_user == NULL)
{
# ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_NOTICE, "login failure (for %s)", repository);
# endif
memset (descrambled_password, 0, strlen (descrambled_password));
free (descrambled_password);
i_hate_you:
buf_output0 (buf_to_net, "I HATE YOU\n");
buf_flush (buf_to_net, true);
exit (EXIT_FAILURE);
}
memset (descrambled_password, 0, strlen (descrambled_password));
free (descrambled_password);
if (verify_and_exit)
{
buf_output0 (buf_to_net, "I LOVE YOU\n");
buf_flush (buf_to_net, true);
exit (EXIT_SUCCESS);
}
Pserver_Repos = xmalloc (strlen (repository) + 1);
strcpy (Pserver_Repos, repository);
switch_to_user (username, host_user);
free (host_user);
free (repository);
free (username);
free (password);
buf_output0 (buf_to_net, "I LOVE YOU\n");
buf_flush (buf_to_net, true);
#endif
}
#endif
#ifdef HAVE_KERBEROS
void
kserver_authenticate_connection( void )
{
int status;
char instance[INST_SZ];
struct sockaddr_in peer;
struct sockaddr_in laddr;
int len;
KTEXT_ST ticket;
AUTH_DAT auth;
char version[KRB_SENDAUTH_VLEN];
char user[ANAME_SZ];
strcpy (instance, "*");
len = sizeof peer;
if (getpeername (STDIN_FILENO, (struct sockaddr *) &peer, &len) < 0
|| getsockname (STDIN_FILENO, (struct sockaddr *) &laddr,
&len) < 0)
{
printf ("E Fatal error, aborting.\n\
error %s getpeername or getsockname failed\n", strerror (errno));
exit (EXIT_FAILURE);
}
#ifdef SO_KEEPALIVE
{
int on = 1;
if (setsockopt (STDIN_FILENO, SOL_SOCKET, SO_KEEPALIVE,
(char *) &on, sizeof on) < 0)
{
# ifdef HAVE_SYSLOG_H
syslog (LOG_DAEMON | LOG_ERR, "error setting KEEPALIVE: %m");
# endif
}
}
#endif
status = krb_recvauth (KOPT_DO_MUTUAL, STDIN_FILENO, &ticket, "rcmd",
instance, &peer, &laddr, &auth, "", sched,
version);
if (status != KSUCCESS)
{
printf ("E Fatal error, aborting.\n\
error 0 kerberos: %s\n", krb_get_err_text(status));
exit (EXIT_FAILURE);
}
memcpy (kblock, auth.session, sizeof (C_Block));
status = krb_kntoln (&auth, user);
if (status != KSUCCESS)
{
printf ("E Fatal error, aborting.\n"
"error 0 kerberos: can't get local name: %s\n",
krb_get_err_text(status));
exit (EXIT_FAILURE);
}
switch_to_user ("Kerberos 4", user);
}
#endif
# ifdef HAVE_GSSAPI
static void
gserver_authenticate_connection (void)
{
char *hn;
gss_buffer_desc tok_in, tok_out;
char buf[1024];
char *credbuf;
size_t credbuflen;
OM_uint32 stat_min, ret;
gss_name_t server_name, client_name;
gss_cred_id_t server_creds;
int nbytes;
gss_OID mechid;
hn = canon_host (server_hostname);
if (!hn)
error (1, 0, "can't get canonical hostname for `%s': %s",
server_hostname, ch_strerror ());
sprintf (buf, "cvs@%s", hn);
free (hn);
tok_in.value = buf;
tok_in.length = strlen (buf);
if (gss_import_name (&stat_min, &tok_in, GSS_C_NT_HOSTBASED_SERVICE,
&server_name) != GSS_S_COMPLETE)
error (1, 0, "could not import GSSAPI service name %s", buf);
if (gss_acquire_cred (&stat_min, server_name, 0, GSS_C_NULL_OID_SET,
GSS_C_ACCEPT, &server_creds,
NULL, NULL) != GSS_S_COMPLETE)
error (1, 0, "could not acquire GSSAPI server credentials");
gss_release_name (&stat_min, &server_name);
if (fread (buf, 1, 2, stdin) != 2)
error (1, errno, "read of length failed");
nbytes = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff);
if (nbytes <= sizeof buf)
{
credbuf = buf;
credbuflen = sizeof buf;
}
else
{
credbuflen = nbytes;
credbuf = xmalloc (credbuflen);
}
if (fread (credbuf, 1, nbytes, stdin) != nbytes)
error (1, errno, "read of data failed");
gcontext = GSS_C_NO_CONTEXT;
tok_in.length = nbytes;
tok_in.value = credbuf;
if (gss_accept_sec_context (&stat_min,
&gcontext,
server_creds,
&tok_in,
NULL,
&client_name,
&mechid,
&tok_out,
&ret,
NULL,
NULL)
!= GSS_S_COMPLETE)
{
error (1, 0, "could not verify credentials");
}
{
krb5_context kc;
krb5_principal p;
gss_buffer_desc desc;
krb5_init_context (&kc);
if (gss_display_name (&stat_min, client_name, &desc,
&mechid) != GSS_S_COMPLETE
|| krb5_parse_name (kc, ((gss_buffer_t) &desc)->value, &p) != 0
|| krb5_aname_to_localname (kc, p, sizeof buf, buf) != 0
|| krb5_kuserok (kc, p, buf) != TRUE)
{
error (1, 0, "access denied");
}
krb5_free_principal (kc, p);
krb5_free_context (kc);
}
if (tok_out.length != 0)
{
char cbuf[2];
cbuf[0] = (tok_out.length >> 8) & 0xff;
cbuf[1] = tok_out.length & 0xff;
if (fwrite (cbuf, 1, 2, stdout) != 2
|| (fwrite (tok_out.value, 1, tok_out.length, stdout)
!= tok_out.length))
error (1, errno, "fwrite failed");
}
switch_to_user ("GSSAPI", buf);
if (credbuf != buf)
free (credbuf);
printf ("I LOVE YOU\n");
fflush (stdout);
}
# endif
#endif
#if defined (CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
int cvsencrypt;
int cvsauthenticate;
#ifdef ENCRYPTION
#ifdef HAVE_KERBEROS
struct krb_encrypt_data
{
Key_schedule sched;
C_Block block;
};
static int
krb_encrypt_input( void *fnclosure, const char *input, char *output, int size )
{
struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure;
int tcount;
des_cbc_encrypt ((C_Block *) input, (C_Block *) output,
size, kd->sched, &kd->block, 0);
tcount = ((output[0] & 0xff) << 8) + (output[1] & 0xff);
if (((tcount + 2 + 7) & ~7) != size)
error (1, 0, "Decryption failure");
return 0;
}
static int
krb_encrypt_output( void *fnclosure, const char *input, char *output,
int size, int *translated )
{
struct krb_encrypt_data *kd = (struct krb_encrypt_data *) fnclosure;
int aligned;
aligned = (size + 7) & ~7;
des_cbc_encrypt ((C_Block *) input, (C_Block *) output, aligned,
kd->sched, &kd->block, 1);
*translated = aligned;
return 0;
}
struct buffer *
krb_encrypt_buffer_initialize( struct buffer *buf, int input,
Key_schedule sched, C_Block block,
void *memory( struct buffer * ) )
{
struct krb_encrypt_data *kd;
kd = (struct krb_encrypt_data *) xmalloc (sizeof *kd);
memcpy (kd->sched, sched, sizeof (Key_schedule));
memcpy (kd->block, block, sizeof (C_Block));
return packetizing_buffer_initialize (buf,
input ? krb_encrypt_input : NULL,
input ? NULL : krb_encrypt_output,
kd,
memory);
}
#endif
#endif
#endif
void
cvs_output (const char *str, size_t len)
{
if (len == 0)
len = strlen (str);
#ifdef SERVER_SUPPORT
if (error_use_protocol)
{
if (buf_to_net)
{
buf_output (saved_output, str, len);
buf_copy_lines (buf_to_net, saved_output, 'M');
}
# if HAVE_SYSLOG_H
else
syslog (LOG_DAEMON | LOG_ERR,
"Attempt to write message after close of network buffer. "
"Message was: %s",
str);
# endif
}
else if (server_active)
{
if (protocol)
{
buf_output (saved_output, str, len);
buf_copy_lines (protocol, saved_output, 'M');
buf_send_counted (protocol);
}
# if HAVE_SYSLOG_H
else
syslog (LOG_DAEMON | LOG_ERR,
"Attempt to write message before initialization of "
"protocol buffer. Message was: %s",
str);
# endif
}
else
#endif
{
size_t written;
size_t to_write = len;
const char *p = str;
fflush (stderr);
while (to_write > 0)
{
written = fwrite (p, 1, to_write, stdout);
if (written == 0)
break;
p += written;
to_write -= written;
}
}
}
void
cvs_output_binary (char *str, size_t len)
{
#ifdef SERVER_SUPPORT
if (error_use_protocol || server_active)
{
struct buffer *buf;
char size_text[40];
if (error_use_protocol)
buf = buf_to_net;
else
buf = protocol;
assert (buf);
if (!supported_response ("Mbinary"))
{
error (0, 0, "\
this client does not support writing binary files to stdout");
return;
}
buf_output0 (buf, "Mbinary\012");
sprintf (size_text, "%lu\012", (unsigned long) len);
buf_output0 (buf, size_text);
buf_output (buf, str, len);
if (!error_use_protocol)
buf_send_counted (protocol);
}
else
#endif
{
size_t written;
size_t to_write = len;
const char *p = str;
#ifdef USE_SETMODE_STDOUT
int oldmode;
#endif
fflush (stderr);
#ifdef USE_SETMODE_STDOUT
fflush (stdout);
oldmode = _setmode (_fileno (stdout), OPEN_BINARY);
if (oldmode < 0)
error (0, errno, "failed to setmode on stdout");
#endif
while (to_write > 0)
{
written = fwrite (p, 1, to_write, stdout);
if (written == 0)
break;
p += written;
to_write -= written;
}
#ifdef USE_SETMODE_STDOUT
fflush (stdout);
if (_setmode (_fileno (stdout), oldmode) != OPEN_BINARY)
error (0, errno, "failed to setmode on stdout");
#endif
}
}
void
cvs_outerr (const char *str, size_t len)
{
if (len == 0)
len = strlen (str);
#ifdef SERVER_SUPPORT
if (error_use_protocol)
{
if (buf_to_net)
{
buf_output (saved_outerr, str, len);
buf_copy_lines (buf_to_net, saved_outerr, 'E');
}
# if HAVE_SYSLOG_H
else
syslog (LOG_DAEMON | LOG_ERR,
"Attempt to write error message after close of network "
"buffer. Message was: `%s'",
str);
# endif
}
else if (server_active)
{
if (protocol)
{
buf_output (saved_outerr, str, len);
buf_copy_lines (protocol, saved_outerr, 'E');
buf_send_counted (protocol);
}
# if HAVE_SYSLOG_H
else
syslog (LOG_DAEMON | LOG_ERR,
"Attempt to write error message before initialization of "
"protocol buffer. Message was: `%s'",
str);
# endif
}
else
#endif
{
size_t written;
size_t to_write = len;
const char *p = str;
fflush (stdout);
while (to_write > 0)
{
written = fwrite (p, 1, to_write, stderr);
if (written == 0)
break;
p += written;
to_write -= written;
}
}
}
void
cvs_flusherr (void)
{
#ifdef SERVER_SUPPORT
if (error_use_protocol)
{
buf_flush (buf_to_net, 0);
}
else if (server_active)
{
fflush (stderr);
buf_send_special_count (protocol, -2);
}
else
#endif
fflush (stderr);
}
void
cvs_flushout (void)
{
#ifdef SERVER_SUPPORT
if (error_use_protocol)
{
buf_flush (buf_to_net, 0);
}
else if (server_active)
{
buf_send_special_count (protocol, -1);
}
else
#endif
fflush (stdout);
}
void
cvs_output_tagged (const char *tag, const char *text)
{
if (text != NULL && strchr (text, '\n') != NULL)
assert (0);
if (tag[0] == '+' || tag[0] == '-')
assert (text == NULL);
#ifdef SERVER_SUPPORT
if (server_active && supported_response ("MT"))
{
struct buffer *buf;
if (error_use_protocol)
buf = buf_to_net;
else
buf = protocol;
buf_output0 (buf, "MT ");
buf_output0 (buf, tag);
if (text != NULL)
{
buf_output (buf, " ", 1);
buf_output0 (buf, text);
}
buf_output (buf, "\n", 1);
if (!error_use_protocol)
buf_send_counted (protocol);
}
else
#endif
{
if (strcmp (tag, "newline") == 0)
cvs_output ("\n", 1);
else if (strcmp (tag, "date") == 0)
{
#ifdef SERVER_SUPPORT
if (server_active)
cvs_output (text, 0);
else
#endif
{
char *date_in = xstrdup (text);
char *date = format_date_alloc (date_in);
cvs_output (date, 0);
free (date);
free (date_in);
}
}
else if (text != NULL)
cvs_output (text, 0);
}
}
void
cvs_trace (int level, const char *fmt, ...)
{
if (trace >= level)
{
va_list va;
va_start (va, fmt);
#ifdef SERVER_SUPPORT
fprintf (stderr,"%c -> ",server_active?(isProxyServer()?'P':'S'):' ');
#else
fprintf (stderr," -> ");
#endif
vfprintf (stderr, fmt, va);
fprintf (stderr,"\n");
va_end (va);
}
}