summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorRuss Allbery <rra@dropbox.com>2016-07-26 17:35:14 -0700
committerRuss Allbery <rra@dropbox.com>2016-07-26 17:35:14 -0700
commit36b8178f28448afccfa0535396095bc72da3e0d4 (patch)
tree2aa27d1c8b55fc275b8a1ef38689d0c05b169de7 /server
parent7a1647ebc8e672cb826dd4ae91adb434d7d13aa5 (diff)
Initial refactoring for ssh shell support
The first step towards adding a new server mode that can be run as a shell via ssh. Refactor the code to more cleanly separate the protocol implementation and the GSS-API and non-GSS-API bits, and add the remctl-shell binary. This is currently entirely untested, apart from ensuring that it doesn't break the existing server implementation.
Diffstat (limited to 'server')
-rw-r--r--server/commands.c59
-rw-r--r--server/event-util.c55
-rw-r--r--server/generic.c63
-rw-r--r--server/internal.h33
-rw-r--r--server/process.c144
-rw-r--r--server/remctl-shell.c153
-rw-r--r--server/remctld.c39
-rw-r--r--server/server-ssh.c281
-rw-r--r--server/server-v1.c101
-rw-r--r--server/server-v2.c76
10 files changed, 758 insertions, 246 deletions
diff --git a/server/commands.c b/server/commands.c
index 36ec950..7815c42 100644
--- a/server/commands.c
+++ b/server/commands.c
@@ -6,6 +6,7 @@
*
* Written by Russ Allbery <eagle@eyrie.org>
* Based on work by Anton Ushakov
+ * Copyright 2016 Dropbox, Inc.
* Copyright 2015 Russ Allbery <eagle@eyrie.org>
* Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2013,
* 2014 The Board of Trustees of the Leland Stanford Junior University
@@ -161,15 +162,12 @@ server_send_summary(struct client *client, struct config *config)
status_all = (int) WEXITSTATUS(process.status);
else
status_all = -1;
- if (ok_any) {
- if (client->protocol == 1)
- server_v1_send_output(client, output, status_all);
- else
- server_v2_send_status(client, status_all);
- } else {
+ if (ok_any)
+ client->finish(client, output, status_all);
+ else {
notice("summary request from user %s, but no defined summaries",
client->user);
- server_send_error(client, ERROR_UNKNOWN_COMMAND, "Unknown command");
+ client->error(client, ERROR_UNKNOWN_COMMAND, "Unknown command");
}
if (output != NULL)
evbuffer_free(output);
@@ -278,21 +276,16 @@ create_argv_help(const char *path, const char *command, const char *subcommand)
* Process an incoming command. Check the configuration files and the ACL
* file, and if appropriate, forks off the command. Takes the argument vector
* and the user principal, and a buffer into which to put the output from the
- * executable or any error message. Returns 0 on success and a negative
- * integer on failure.
+ * executable or any error message. Returns the exit status of the command.
*
* Using the command and the subcommand, the following argument, a lookup in
- * the conf data structure is done to find the command executable and acl
- * file. If the conf file, and subsequently the conf data structure contains
- * an entry for this command with subcommand equal to "ALL", that is a
- * wildcard match for any given subcommand. The first argument is then
- * replaced with the actual program name to be executed.
- *
- * After checking the acl permissions, the process forks and the child execv's
- * the command with pipes arranged to gather output. The parent waits for the
- * return code and gathers stdout and stderr pipes.
+ * the configuration data structure is done to find the command executable and
+ * ACL file. If the configuration contains an entry for this command with
+ * subcommand equal to "ALL", that is a wildcard match for any given
+ * subcommand. The first argument is then replaced with the actual program
+ * name to be executed.
*/
-void
+int
server_run_command(struct client *client, struct config *config,
struct iovec **argv)
{
@@ -302,6 +295,7 @@ server_run_command(struct client *client, struct config *config,
struct rule *rule = NULL;
char **req_argv = NULL;
size_t i;
+ int status = -1;
bool ok = false;
bool help = false;
const char *user = client->user;
@@ -317,7 +311,7 @@ server_run_command(struct client *client, struct config *config,
*/
if (argv[0] == NULL) {
notice("empty command from user %s", user);
- server_send_error(client, ERROR_BAD_COMMAND, "Invalid command token");
+ client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
goto done;
}
@@ -326,8 +320,7 @@ server_run_command(struct client *client, struct config *config,
if (memchr(argv[i]->iov_base, '\0', argv[i]->iov_len)) {
notice("%s from user %s contains nul octet",
(i == 0) ? "command" : "subcommand", user);
- server_send_error(client, ERROR_BAD_COMMAND,
- "Invalid command token");
+ client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
goto done;
}
}
@@ -351,8 +344,8 @@ server_run_command(struct client *client, struct config *config,
if (argv[1] != NULL && argv[2] != NULL && argv[3] != NULL) {
notice("help command from user %s has more than three arguments",
user);
- server_send_error(client, ERROR_TOOMANY_ARGS,
- "Too many arguments for help command");
+ client->error(client, ERROR_TOOMANY_ARGS,
+ "Too many arguments for help command");
}
if (subcommand == NULL) {
@@ -381,8 +374,7 @@ server_run_command(struct client *client, struct config *config,
if (memchr(argv[i]->iov_base, '\0', argv[i]->iov_len)) {
notice("argument %lu from user %s contains nul octet",
(unsigned long) i, user);
- server_send_error(client, ERROR_BAD_COMMAND,
- "Invalid command token");
+ client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
goto done;
}
}
@@ -398,14 +390,14 @@ server_run_command(struct client *client, struct config *config,
notice("unknown command %s%s%s from user %s", command,
(subcommand == NULL) ? "" : " ",
(subcommand == NULL) ? "" : subcommand, user);
- server_send_error(client, ERROR_UNKNOWN_COMMAND, "Unknown command");
+ client->error(client, ERROR_UNKNOWN_COMMAND, "Unknown command");
goto done;
}
if (!server_config_acl_permit(rule, client)) {
notice("access denied: user %s, command %s%s%s", user, command,
(subcommand == NULL) ? "" : " ",
(subcommand == NULL) ? "" : subcommand);
- server_send_error(client, ERROR_ACCESS, "Access denied");
+ client->error(client, ERROR_ACCESS, "Access denied");
goto done;
}
@@ -417,8 +409,8 @@ server_run_command(struct client *client, struct config *config,
if (rule->help == NULL) {
notice("command %s from user %s has no defined help",
command, user);
- server_send_error(client, ERROR_NO_HELP,
- "No help defined for command");
+ client->error(client, ERROR_NO_HELP,
+ "No help defined for command");
goto done;
} else {
free(subcommand);
@@ -442,11 +434,9 @@ server_run_command(struct client *client, struct config *config,
process.status = (signed int) WEXITSTATUS(process.status);
else
process.status = -1;
- if (client->protocol == 1)
- server_v1_send_output(client, process.output, process.status);
- else
- server_v2_send_status(client, process.status);
+ client->finish(client, process.output, process.status);
}
+ status = process.status;
done:
free(command);
@@ -461,6 +451,7 @@ server_run_command(struct client *client, struct config *config,
evbuffer_free(process.input);
if (process.output != NULL)
evbuffer_free(process.output);
+ return status;
}
diff --git a/server/event-util.c b/server/event-util.c
new file mode 100644
index 0000000..1c786b1
--- /dev/null
+++ b/server/event-util.c
@@ -0,0 +1,55 @@
+/*
+ * Utility functions for libevent.
+ *
+ * Provides some utility functions used with libevent in both remctld and
+ * remctl-shell.
+ *
+ * Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2016 Dropbox, Inc.
+ * Copyright 2014
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/event.h>
+#include <portable/socket.h>
+
+#include <server/internal.h>
+#include <util/messages.h>
+
+
+/*
+ * The logging callback for libevent. We hook this into our message system so
+ * that libevent messages are handled the same way as our other internal
+ * messages. This function should be passed to event_set_log_callback at the
+ * start of libevent initialization.
+ */
+void
+server_event_log_callback(int severity, const char *message)
+{
+ switch (severity) {
+ case EVENT_LOG_DEBUG:
+ debug("%s", message);
+ break;
+ case EVENT_LOG_MSG:
+ notice("%s", message);
+ break;
+ default:
+ warn("%s", message);
+ break;
+ }
+}
+
+
+/*
+ * The fatal callback for libevent. Convert this to die, so that it's logged
+ * the same as our other messages. This function should be passed to
+ * event_set_fatal_callback at the start of libevent initialization.
+ */
+void
+server_event_fatal_callback(int err)
+{
+ die("fatal libevent error (%d)", err);
+}
diff --git a/server/generic.c b/server/generic.c
index 6980636..f26974d 100644
--- a/server/generic.c
+++ b/server/generic.c
@@ -1,11 +1,12 @@
/*
- * Server implementation of generic protocol functions.
+ * Server implementation of generic GSS-API protocol functions.
*
- * These are the server protocol functions that can be shared between the v1
- * and v2 protocol.
+ * These are the server protocol functions that can use GSS-API but can be
+ * shared between the v1 and v2 protocol.
*
* Written by Russ Allbery <eagle@eyrie.org>
* Based on work by Anton Ushakov
+ * Copyright 2016 Dropbox, Inc.
* Copyright 2015 Russ Allbery <eagle@eyrie.org>
* Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2013,
* 2014 The Board of Trustees of the Leland Stanford Junior University
@@ -155,6 +156,17 @@ server_new_client(int fd, gss_cred_id_t creds)
}
}
+ /* Based on the protocol, set up the callbacks. */
+ if (client->protocol == 1) {
+ client->setup = server_v1_command_setup;
+ client->finish = server_v1_send_output;
+ client->error = server_v1_send_error;
+ } else {
+ client->setup = server_v2_command_setup;
+ client->finish = server_v2_command_finish;
+ client->error = server_v2_send_error;
+ }
+
/* Get the display version of the client name and store it. */
major = gss_display_name(&minor, name, &name_buf, &doid);
if (major != GSS_S_COMPLETE) {
@@ -206,8 +218,8 @@ server_free_client(struct client *client)
/*
- * Receives a command token payload and builds an argv structure for it,
- * returning that as NULL-terminated array of pointers to struct iovecs.
+ * Parses a complete command token payload and builds an argv structure for
+ * it, returning that as NULL-terminated array of pointers to struct iovecs.
* Takes the client struct, a pointer to the beginning of the payload
* (starting with the argument count), and the length of the payload. If
* there are any problems with the request, sends an error token, logs the
@@ -228,17 +240,17 @@ server_parse_command(struct client *client, const char *buffer, size_t length)
debug("argc is %lu", (unsigned long) argc);
if (argc == 0) {
warn("command with no arguments");
- server_send_error(client, ERROR_UNKNOWN_COMMAND, "Unknown command");
+ client->error(client, ERROR_UNKNOWN_COMMAND, "Unknown command");
return NULL;
}
if (argc > COMMAND_MAX_ARGS) {
warn("too large argc (%lu) in request message", (unsigned long) argc);
- server_send_error(client, ERROR_TOOMANY_ARGS, "Too many arguments");
+ client->error(client, ERROR_TOOMANY_ARGS, "Too many arguments");
return NULL;
}
if (length - (p - buffer) < 4 * argc) {
warn("command data too short");
- server_send_error(client, ERROR_BAD_COMMAND, "Invalid command token");
+ client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
return NULL;
}
argv = xcalloc(argc + 1, sizeof(struct iovec *));
@@ -252,8 +264,7 @@ server_parse_command(struct client *client, const char *buffer, size_t length)
while (p <= buffer + length - 4) {
if (count >= argc) {
warn("sent more arguments than argc %lu", (unsigned long) argc);
- server_send_error(client, ERROR_BAD_COMMAND,
- "Invalid command token");
+ client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
goto fail;
}
memcpy(&tmp, p, 4);
@@ -261,8 +272,7 @@ server_parse_command(struct client *client, const char *buffer, size_t length)
p += 4;
if ((length - (p - buffer)) < arglen) {
warn("command data invalid");
- server_send_error(client, ERROR_BAD_COMMAND,
- "Invalid command token");
+ client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
goto fail;
}
argv[count] = xmalloc(sizeof(struct iovec));
@@ -278,7 +288,7 @@ server_parse_command(struct client *client, const char *buffer, size_t length)
}
if (count != argc || p != buffer + length) {
warn("argument count differs from arguments seen");
- server_send_error(client, ERROR_BAD_COMMAND, "Invalid command token");
+ client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
goto fail;
}
argv[count] = NULL;
@@ -288,30 +298,3 @@ fail:
server_free_command(argv);
return NULL;
}
-
-
-/*
- * Send an error back to the client. Takes the client struct, the error code,
- * and the message to send and dispatches to the appropriate protocol-specific
- * function. Returns true on success, false on failure.
- */
-bool
-server_send_error(struct client *client, enum error_codes error,
- const char *message)
-{
- struct evbuffer *buf;
- bool result;
-
- if (client->protocol > 1)
- return server_v2_send_error(client, error, message);
- else {
- buf = evbuffer_new();
- if (buf == NULL)
- die("internal error: cannot create output buffer");
- if (evbuffer_add_printf(buf, "%s\n", message) < 0)
- die("internal error: cannot add error message to buffer");
- result = server_v1_send_output(client, buf, -1);
- evbuffer_free(buf);
- return result;
- }
-}
diff --git a/server/internal.h b/server/internal.h
index f64a782..47e9857 100644
--- a/server/internal.h
+++ b/server/internal.h
@@ -2,6 +2,7 @@
* Internal support functions for the remctld daemon.
*
* Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2016 Dropbox, Inc.
* Copyright 2015 Russ Allbery <eagle@eyrie.org>
* Copyright 2006, 2007, 2008, 2009, 2010, 2012, 2014
* The Board of Trustees of the Leland Stanford Junior University
@@ -28,6 +29,7 @@ struct evbuffer;
struct event;
struct event_base;
struct iovec;
+struct process;
/*
* The maximum size of argc passed to the server (4K arguments), and the
@@ -47,6 +49,7 @@ struct iovec;
/* Holds the information about a client connection. */
struct client {
int fd; /* File descriptor of client connection. */
+ int stderr_fd; /* stderr file descriptor for remctl-shell. */
char *hostname; /* Hostname of client (if available). */
char *ipaddress; /* IP address of client as a string. */
int protocol; /* Protocol version number. */
@@ -57,6 +60,14 @@ struct client {
time_t expires; /* Expiration time of GSS-API session. */
bool keepalive; /* Whether keep-alive was set. */
bool fatal; /* Whether a fatal error has occurred. */
+
+ /*
+ * Callbacks used by generic server code handle the separate protocols,
+ * set up when the client opens the connection.
+ */
+ void (*setup)(struct process *);
+ bool (*finish)(struct client *, struct evbuffer *, int);
+ bool (*error)(struct client *, enum error_codes, const char *);
};
/* Holds the configuration for a single command. */
@@ -136,30 +147,42 @@ bool server_config_acl_permit(const struct rule *, const struct client *);
void server_config_set_gput_file(char *file);
/* Running commands. */
-void server_run_command(struct client *, struct config *, struct iovec **);
+int server_run_command(struct client *, struct config *, struct iovec **);
/* Freeing the command structure. */
void server_free_command(struct iovec **);
/* Running processes. */
bool server_process_run(struct process *process);
+void server_handle_io_event(struct bufferevent *, short, void *);
+void server_handle_input_end(struct bufferevent *, void *);
-/* Generic protocol functions. */
+/* Generic GSS-API protocol functions. */
struct client *server_new_client(int fd, gss_cred_id_t creds);
void server_free_client(struct client *);
struct iovec **server_parse_command(struct client *, const char *, size_t);
-bool server_send_error(struct client *, enum error_codes, const char *);
/* Protocol v1 functions. */
+void server_v1_command_setup(struct process *);
bool server_v1_send_output(struct client *, struct evbuffer *, int status);
+bool server_v1_send_error(struct client *, enum error_codes, const char *);
void server_v1_handle_messages(struct client *, struct config *);
/* Protocol v2 functions. */
-bool server_v2_send_output(struct client *, int stream, struct evbuffer *);
-bool server_v2_send_status(struct client *, int);
+void server_v2_command_setup(struct process *);
+bool server_v2_command_finish(struct client *, struct evbuffer *, int status);
bool server_v2_send_error(struct client *, enum error_codes, const char *);
void server_v2_handle_messages(struct client *, struct config *);
+/* ssh protocol functions. */
+struct client *server_ssh_new_client(void);
+void server_ssh_free_client(struct client *);
+struct iovec **server_ssh_parse_command(const char *);
+
+/* libevent utility functions. */
+void server_event_log_callback(int, const char *);
+void server_event_fatal_callback(int);
+
END_DECLS
#endif /* !SERVER_INTERNAL_H */
diff --git a/server/process.c b/server/process.c
index c1b68f5..d6b0dab 100644
--- a/server/process.c
+++ b/server/process.c
@@ -6,6 +6,7 @@
* with the child process.
*
* Written by Russ Allbery <eagle@eyrie.org>
+ * Copyright 2016 Dropbox, Inc.
* Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2013,
* 2014 The Board of Trustees of the Leland Stanford Junior University
*
@@ -48,13 +49,16 @@
/*
- * Callback for events in input or output handling. This means either an
- * error or EOF. On EOF or an EPIPE or ECONNRESET error, just deactivate the
- * bufferevent. On other errors, send an error message to the client and then
- * break out of the event loop.
+ * Callback for events in input or output handling while running a process.
+ * This means either an error or EOF. On EOF or an EPIPE or ECONNRESET error,
+ * just deactivate the bufferevent. On other errors, send an error message to
+ * the client and then break out of the event loop.
+ *
+ * This has to be public so that it can be referenced by the setup code for
+ * the various protocols.
*/
-static void
-handle_io_event(struct bufferevent *bev, short events, void *data)
+void
+server_handle_io_event(struct bufferevent *bev, short events, void *data)
{
struct process *process = data;
struct client *client = process->client;
@@ -81,7 +85,7 @@ handle_io_event(struct bufferevent *bev, short events, void *data)
syswarn("read from process failed");
else
syswarn("write to standard input failed");
- server_send_error(client, ERROR_INTERNAL, "Internal failure");
+ client->error(client, ERROR_INTERNAL, "Internal failure");
process->saw_error = true;
event_base_loopbreak(process->loop);
}
@@ -90,10 +94,11 @@ handle_io_event(struct bufferevent *bev, short events, void *data)
/*
* Callback when all stdin data has been sent. We only have a callback to
* shut down our end of the socketpair so that the process gets EOF on its
- * next read.
+ * next read. Also has to be public so that it can be referenced in the
+ * per-protocol startup callbacks.
*/
-static void
-handle_input_end(struct bufferevent *bev, void *data)
+void
+server_handle_input_end(struct bufferevent *bev, void *data)
{
struct process *process = data;
@@ -104,84 +109,6 @@ handle_input_end(struct bufferevent *bev, void *data)
/*
- * Callback used to handle output from a process (protocol version two or
- * later). We use the same handler for both standard output and standard
- * error and check the bufferevent to determine which stream we're seeing.
- *
- * When called, note that we saw some output, which is a flag to continue
- * processing when running the event loop after the child has exited.
- */
-static void
-handle_output(struct bufferevent *bev, void *data)
-{
- int stream;
- struct evbuffer *buf;
- struct process *process = data;
-
- process->saw_output = true;
- stream = (bev == process->inout) ? 1 : 2;
- buf = bufferevent_get_input(bev);
- if (!server_v2_send_output(process->client, stream, buf)) {
- process->saw_error = true;
- event_base_loopbreak(process->loop);
- }
-}
-
-
-/*
- * Discard all data in the evbuffer. This handler is used with protocol
- * version one when we've already read as much data as we can return to the
- * remctl client.
- */
-static void
-handle_output_discard(struct bufferevent *bev, void *data UNUSED)
-{
- size_t length;
- struct evbuffer *buf;
-
- buf = bufferevent_get_input(bev);
- length = evbuffer_get_length(buf);
- if (evbuffer_drain(buf, length) < 0)
- sysdie("internal error: cannot discard extra output");
-}
-
-
-/*
- * Callback used to handle filling the output buffer with protocol version
- * one. When this happens, we pull all of the data out into a separate
- * evbuffer and then change our read callback to handle_output_discard, which
- * just drains (discards) all subsequent data from the process.
- */
-static void
-handle_output_full(struct bufferevent *bev, void *data)
-{
- struct process *process = data;
- bufferevent_data_cb writecb;
-
- process->output = evbuffer_new();
- if (process->output == NULL)
- die("internal error: cannot create discard evbuffer");
- if (bufferevent_read_buffer(bev, process->output) < 0)
- die("internal error: cannot move data into output buffer");
-
- /*
- * Change the output callback. We need to be sure not to dump our input
- * callback if it exists.
- *
- * After we see all the output that we can send to the client, we no
- * longer care about error and EOF events, but if we set the callback to
- * NULL here, we cause segfaults in libevent 1.4.x when we have both read
- * and EOF events in the same event loop. So keep the error event handler
- * since it doesn't hurt anything. This can safely be set to NULL once we
- * require libevent 2.x.
- */
- writecb = (process->input == NULL) ? NULL : handle_input_end;
- bufferevent_setcb(bev, handle_output_discard, writecb, handle_io_event,
- data);
-}
-
-
-/*
* Called when the process has exited. Here we reap the status and then tell
* the event loop to complete. Ignore SIGCHLD if our child process wasn't the
* one that exited.
@@ -222,7 +149,6 @@ start(evutil_socket_t junk UNUSED, short what UNUSED, void *data)
struct process *process = data;
struct client *client = process->client;
struct event_base *loop = process->loop;
- bufferevent_data_cb writecb = NULL;
socket_type stdinout_fds[2] = { INVALID_SOCKET, INVALID_SOCKET };
socket_type stderr_fds[2] = { INVALID_SOCKET, INVALID_SOCKET };
socket_type fd;
@@ -372,16 +298,18 @@ start(evutil_socket_t junk UNUSED, short what UNUSED, void *data)
}
/*
- * Set up a bufferevent to consume output from the process.
+ * Set up bufferevents to send input to and consume output from the
+ * process. There are two possibilities here.
*
- * There are two possibilities here. For protocol version two, we use two
- * bufferevents, one for standard input and output and one for standard
- * error, that turn each chunk of data into a MESSAGE_OUTPUT token to the
- * client. For protocol version one, we use a single bufferevent, which
- * sends standard intput and collects both standard output and standard
- * error, queuing it to send on process exit. In this case, stdinout_fd
- * gets both streams, since there's no point in distinguishing, and we
- * only need one bufferevent.
+ * For protocol version two, we use two bufferevents, one for standard
+ * input and output and one for standard error, that turn each chunk of
+ * data into a MESSAGE_OUTPUT token to the client.
+ *
+ * For protocol version one, we use a single bufferevent, which sends
+ * standard intput and collects both standard output and standard error,
+ * queuing it to send on process exit. In this case, stdinout_fd gets
+ * both streams, since there's no point in distinguishing, and we only
+ * need one bufferevent.
*/
fdflag_nonblocking(stdinout_fds[0], true);
process->inout = bufferevent_socket_new(loop, process->stdinout_fd, 0);
@@ -390,29 +318,19 @@ start(evutil_socket_t junk UNUSED, short what UNUSED, void *data)
if (process->input == NULL)
bufferevent_enable(process->inout, EV_READ);
else {
- writecb = handle_input_end;
bufferevent_enable(process->inout, EV_READ | EV_WRITE);
if (bufferevent_write_buffer(process->inout, process->input) < 0)
die("internal error: cannot queue input for process");
}
- if (client->protocol == 1) {
- bufferevent_setcb(process->inout, handle_output_full, writecb,
- handle_io_event, process);
- bufferevent_setwatermark(process->inout, EV_READ, TOKEN_MAX_OUTPUT_V1,
- TOKEN_MAX_OUTPUT_V1);
- } else {
- bufferevent_setcb(process->inout, handle_output, writecb,
- handle_io_event, process);
- bufferevent_setwatermark(process->inout, EV_READ, 0, TOKEN_MAX_OUTPUT);
+ if (client->protocol > 1) {
fdflag_nonblocking(stderr_fds[0], true);
process->err = bufferevent_socket_new(loop, process->stderr_fd, 0);
if (process->err == NULL)
die("internal error: cannot create stderr bufferevent");
- bufferevent_enable(process->err, EV_READ);
- bufferevent_setcb(process->err, handle_output, NULL,
- handle_io_event, process);
- bufferevent_setwatermark(process->err, EV_READ, 0, TOKEN_MAX_OUTPUT);
}
+
+ /* Set up the event hooks for the different protocols. */
+ client->setup(process);
return;
fail:
@@ -424,7 +342,7 @@ fail:
close(stderr_fds[0]);
if (stderr_fds[1] != INVALID_SOCKET)
close(stderr_fds[1]);
- server_send_error(client, ERROR_INTERNAL, "Internal failure");
+ client->error(client, ERROR_INTERNAL, "Internal failure");
process->saw_error = true;
event_base_loopbreak(process->loop);
}
diff --git a/server/remctl-shell.c b/server/remctl-shell.c
new file mode 100644
index 0000000..722432a
--- /dev/null
+++ b/server/remctl-shell.c
@@ -0,0 +1,153 @@
+/*
+ * The remctl server as a restricted shell.
+ *
+ * This is a varient of remctld that, rather than listening to the network for
+ * the remctl protocol, runs as a restricted shell under ssh. It uses the
+ * same configuration and the same command semantics as the normal remctl
+ * server, but uses ssh as the transport mechanism and must be run under sshd.
+ *
+ * This file handles parsing of the user's command and the main control flow.
+ *
+ * Note that, because there's no good way to pass command-line options into a
+ * shell, it's not possible to reconfigure the configuration file path used by
+ * remctl-shell.
+ *
+ * Written by Russ Allbery
+ * Copyright 2016 Dropbox, Inc.
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/event.h>
+#include <portable/system.h>
+
+#include <signal.h>
+#include <syslog.h>
+
+#include <server/internal.h>
+#include <util/messages.h>
+#include <util/xmalloc.h>
+
+/* Usage message. */
+static const char usage_message[] = "\
+Usage: remctl-shell [-dhS] -c <command>\n\
+\n\
+Options:\n\
+ -c <command> Specifies the command to run\n\
+ -d Log verbose debugging information\n\
+ -h Display this help\n\
+ -S Log to standard output/error rather than syslog\n\
+\n\
+This is meant to be used as the shell for a dedicated account and handles\n\
+incoming commands via ssh. It must be run under ssh or with the same\n\
+environment variables ssh would set.\n";
+
+
+/*
+ * Display the usage message for remctl-shell.
+ */
+static void
+usage(int status)
+{
+ FILE *output;
+
+ output = (status == 0) ? stdout : stderr;
+ if (status != 0)
+ fprintf(output, "\n");
+ fprintf(output, usage_message);
+ exit(status);
+}
+
+
+/*
+ * Main routine. Parses the configuration file and the user's command and
+ * then dispatches running the command.
+ */
+int
+main(int argc, char *argv[])
+{
+ int option, status;
+ bool debug = false;
+ bool log_stdout = false;
+ struct sigaction sa;
+ const char *command_string = NULL;
+ struct iovec **command;
+ struct client *client;
+ struct config *config;
+
+ /* Ignore SIGPIPE errors from our children. */
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = SIG_IGN;
+ if (sigaction(SIGPIPE, &sa, NULL) < 0)
+ sysdie("cannot set SIGPIPE handler");
+
+ /* Establish identity for logging. */
+ message_program_name = "remctl-shell";
+
+ /* Initialize the logging and fatal callbacks for libevent. */
+ event_set_log_callback(server_event_log_callback);
+ event_set_fatal_callback(server_event_fatal_callback);
+
+ /*
+ * Parse options. Since we're being run as a shell, there isn't all that
+ * much here.
+ */
+ while ((option = getopt(argc, argv, "dc:hS")) != EOF) {
+ switch (option) {
+ case 'c':
+ command_string = optarg;
+ break;
+ case 'd':
+ debug = true;
+ break;
+ case 'h':
+ usage(0);
+ break;
+ case 'S':
+ log_stdout = true;
+ break;
+ default:
+ warn("unknown option -%c", optopt);
+ usage(1);
+ break;
+ }
+ }
+ if (command_string == NULL)
+ die("no command specified");
+
+ /* Set up logging. */
+ if (log_stdout) {
+ if (debug)
+ message_handlers_debug(1, message_log_stdout);
+ } else {
+ openlog("remctl-shell", LOG_PID | LOG_NDELAY, LOG_DAEMON);
+ message_handlers_notice(1, message_log_syslog_info);
+ message_handlers_warn(1, message_log_syslog_warning);
+ message_handlers_die(1, message_log_syslog_err);
+ if (debug)
+ message_handlers_debug(1, message_log_syslog_debug);
+ }
+
+ /* Read the configuration file. */
+ config = server_config_load(CONFIG_FILE);
+ if (config == NULL)
+ die("cannot read configuration file %s", CONFIG_FILE);
+
+ /* Create the client struct based on the ssh environment. */
+ client = server_ssh_new_client();
+
+ /* Parse and execute the command. */
+ command = server_ssh_parse_command(command_string);
+ if (command == NULL)
+ die("cannot parse command: %s", command_string);
+ status = server_run_command(client, config, command);
+ server_free_command(command);
+
+ /* Clean up and exit. */
+ server_ssh_free_client(client);
+ server_config_free(config);
+ libevent_global_shutdown();
+ message_handlers_reset();
+ return status;
+}
diff --git a/server/remctld.c b/server/remctld.c
index 5fac8c7..bd561a8 100644
--- a/server/remctld.c
+++ b/server/remctld.c
@@ -152,41 +152,6 @@ exit_handler(int sig UNUSED)
/*
- * The logging callback for libevent. We hook this into our message system so
- * that libevent messages are handled the same way as our other internal
- * messages. This function should be passed to event_set_log_callback at the
- * start of libevent initialization.
- */
-static void
-event_log_callback(int severity, const char *message)
-{
- switch (severity) {
- case EVENT_LOG_DEBUG:
- debug("%s", message);
- break;
- case EVENT_LOG_MSG:
- notice("%s", message);
- break;
- default:
- warn("%s", message);
- break;
- }
-}
-
-
-/*
- * The fatal callback for libevent. Convert this to die, so that it's logged
- * the same as our other messages. This function should be passed to
- * event_set_fatal_callback at the start of libevent initialization.
- */
-static void
-event_fatal_callback(int err)
-{
- die("fatal libevent error (%d)", err);
-}
-
-
-/*
* Given a service name, imports it and acquires credentials for it, storing
* them in the second argument. Returns true on success and false on failure,
* logging an error message.
@@ -559,8 +524,8 @@ main(int argc, char *argv[])
message_program_name = "remctld";
/* Initialize the logging and fatal callbacks for libevent. */
- event_set_log_callback(event_log_callback);
- event_set_fatal_callback(event_fatal_callback);
+ event_set_log_callback(server_event_log_callback);
+ event_set_fatal_callback(server_event_fatal_callback);
/* Initialize options. */
memset(&options, 0, sizeof(options));
diff --git a/server/server-ssh.c b/server/server-ssh.c
new file mode 100644
index 0000000..07bfb01
--- /dev/null
+++ b/server/server-ssh.c
@@ -0,0 +1,281 @@
+/*
+ * ssh protocol, server implementation.
+ *
+ * Implements remctl over ssh using regular commands and none of the remctl
+ * protocol. The only part of the normal remctl server reused here is the
+ * configuration and command running code.
+ *
+ * Written by Russ Allbery
+ * Copyright 2016 Dropbox, Inc.
+ *
+ * See LICENSE for licensing terms.
+ */
+
+#include <config.h>
+#include <portable/event.h>
+#include <portable/system.h>
+
+#include <ctype.h>
+
+#include <server/internal.h>
+#include <util/buffer.h>
+#include <util/macros.h>
+#include <util/messages.h>
+#include <util/vector.h>
+#include <util/xmalloc.h>
+#include <util/xwrite.h>
+
+
+/*
+ * Parse a command string into a remctl command. This is much more complex
+ * for remctl-shell than it is for remctld since we get the command as a
+ * string with shell quoting and have to understand and undo the quoting.
+ *
+ * Implements single and double quotes, with backslash escaping any character.
+ */
+struct iovec **
+server_ssh_parse_command(const char *command)
+{
+ struct vector *args;
+ struct buffer *arg;
+ struct iovec **argv;
+ const char *p;
+ size_t i, length;
+ char quote = '\0';
+ enum state {
+ SEPARATOR,
+ ARG,
+ QUOTE
+ } state;
+
+ /*
+ * Parse the string using a state engine. We can be in one of three
+ * states: in the separator between arguments, or inside a quoted string.
+ * If inside a quoted string, the quote used to terminate the string is
+ * stored in quote.
+ *
+ * Backslash escapes any character inside or outside quotes. If backslash
+ * is at the end of the string, we just treat it as a literal backslash.
+ */
+ args = vector_new();
+ arg = buffer_new();
+ state = SEPARATOR;
+ for (p = command; p != '\0'; p++) {
+ if (*p == '\\' && p[1] != '\0') {
+ buffer_append(arg, p + 1, 1);
+ if (state == SEPARATOR)
+ state = ARG;
+ continue;
+ }
+ switch (state) {
+ case SEPARATOR:
+ if (!isspace((int) *p)) {
+ switch (*p) {
+ case '\'':
+ case '"':
+ state = QUOTE;
+ quote = *p;
+ break;
+ default:
+ state = ARG;
+ buffer_append(arg, p, 1);
+ break;
+ }
+ }
+ break;
+ case QUOTE:
+ if (*p == quote)
+ state = ARG;
+ else
+ buffer_append(arg, p, 1);
+ break;
+ case ARG:
+ if (isspace((int) *p)) {
+ vector_addn(args, arg->data, arg->left);
+ buffer_set(arg, NULL, 0);
+ state = SEPARATOR;
+ } else {
+ switch (*p) {
+ case '\'':
+ case '"':
+ state = QUOTE;
+ quote = *p;
+ break;
+ default:
+ buffer_append(arg, p, 1);
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ /*
+ * Ending inside a quoted string is an error. Otherwise, recover the last
+ * argument and clean up.
+ */
+ if (state == QUOTE) {
+ warn("unterminated %c quote in command", quote);
+ goto fail;
+ } else if (state == ARG) {
+ vector_addn(args, arg->data, arg->left);
+ }
+ buffer_free(arg);
+
+ /* Turn the vector into the iovec we need for everything else. */
+ argv = xcalloc(args->count + 1, sizeof(struct iovec *));
+ for (i = 0; i < args->count; i++) {
+ argv[i] = xcalloc(1, sizeof(struct iovec));
+ length = strlen(args->strings[i]);
+ argv[i]->iov_base = xmalloc(length);
+ memcpy(argv[i]->iov_base, args->strings[i], length);
+ argv[i]->iov_len = length;
+ }
+ argv[args->count] = NULL;
+ vector_free(args);
+ return argv;
+
+fail:
+ vector_free(args);
+ buffer_free(arg);
+ return NULL;
+}
+
+
+/*
+ * Handle one block of output from the running command.
+ */
+static void
+handle_output(struct bufferevent *bev, void *data)
+{
+ int fd;
+ struct evbuffer *buf;
+ struct process *process = data;
+ struct client *client = process->client;
+
+ process->saw_output = true;
+ fd = (bev == process->inout) ? client->fd : client->stderr_fd;
+ buf = bufferevent_get_input(bev);
+ if (evbuffer_write(buf, fd) < 0) {
+ syswarn("error sending output");
+ client->fatal = true;
+ process->saw_error = true;
+ event_base_loopbreak(process->loop);
+ }
+}
+
+
+/*
+ * Set up to execute a command. For the ssh protocol, all we need to do is
+ * install output handlers for both stdout and stderr that just send the
+ * output back to our stdout and stderr.
+ */
+static void
+command_setup(struct process *process)
+{
+ bufferevent_data_cb writecb;
+
+ writecb = (process->input == NULL) ? NULL : server_handle_input_end;
+ bufferevent_setcb(process->inout, handle_output, writecb,
+ server_handle_io_event, process);
+ bufferevent_setwatermark(process->inout, EV_READ, 0, TOKEN_MAX_OUTPUT);
+ bufferevent_enable(process->err, EV_READ);
+ bufferevent_setcb(process->err, handle_output, NULL,
+ server_handle_io_event, process);
+ bufferevent_setwatermark(process->err, EV_READ, 0, TOKEN_MAX_OUTPUT);
+}
+
+
+/*
+ * Handle the end of the command. For the ssh protocol, we do nothing, since
+ * the main program will collect the exit status and exit with the appropriate
+ * status in order to communicate it back to the caller.
+ */
+static bool
+command_finish(struct client *client UNUSED, struct evbuffer *output UNUSED,
+ int exit_status UNUSED)
+{
+ return true;
+}
+
+
+/*
+ * Send an error back over an ssh channel. This just writes the error message
+ * with a trailing newline to standard error.
+ */
+static bool
+send_error(struct client *client, enum error_codes code UNUSED,
+ const char *message)
+{
+ ssize_t status;
+
+ status = xwrite(client->stderr_fd, message, strlen(message));
+ if (status >= 0)
+ status = xwrite(client->stderr_fd, "\n", 1);
+ if (status < 0) {
+ syswarn("error sending error message");
+ client->fatal = true;
+ return false;
+ }
+ return true;
+}
+
+
+/*
+ * Create a client struct for a remctl-shell invocation based on the ssh
+ * environment. Abort here if the expected ssh environment variables aren't
+ * set. Caller is responsible for freeing the allocated client struct.
+ */
+struct client *
+server_ssh_new_client(void)
+{
+ struct client *client;
+ const char *ssh_client, *user;
+ struct vector *client_info;
+
+ /* Parse client identity from ssh environment variables. */
+ user = getenv("REMCTL_USER");
+ if (user == NULL)
+ die("REMCTL_USER must be set in the environment via authorized_keys");
+ ssh_client = getenv("SSH_CLIENT");
+ if (ssh_client == NULL)
+ die("SSH_CLIENT not set (remctl-shell must be run via ssh)");
+ client_info = vector_split_space(ssh_client, NULL);
+
+ /* Create basic client struct. */
+ client = xcalloc(1, sizeof(struct client));
+ client->fd = STDOUT_FILENO;
+ client->stderr_fd = STDERR_FILENO;
+ client->ipaddress = xstrdup(client_info->strings[0]);
+ client->protocol = 3;
+ client->user = xstrdup(user);
+
+ /* Add ssh protocol callbacks. */
+ client->setup = command_setup;
+ client->finish = command_finish;
+ client->error = send_error;
+
+ /* Free allocated data and return. */
+ vector_free(client_info);
+ return client;
+}
+
+
+/*
+ * Free a client struct, including any resources that it holds. This is a
+ * subset of server_free_client that doesn't do the GSS-API actions.
+ */
+void
+server_ssh_free_client(struct client *client)
+{
+ if (client == NULL)
+ return;
+ if (client->fd >= 0)
+ close(client->fd);
+ if (client->stderr_fd >= 0)
+ close(client->stderr_fd);
+ free(client->user);
+ free(client->hostname);
+ free(client->ipaddress);
+ free(client);
+}
diff --git a/server/server-v1.c b/server/server-v1.c
index 0af9ab2..171b904 100644
--- a/server/server-v1.c
+++ b/server/server-v1.c
@@ -7,6 +7,7 @@
*
* Written by Russ Allbery <eagle@eyrie.org>
* Based on work by Anton Ushakov
+ * Copyright 2016 Dropbox, Inc.
* Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2014
* The Board of Trustees of the Leland Stanford Junior University
*
@@ -22,11 +23,82 @@
#include <server/internal.h>
#include <util/gss-tokens.h>
+#include <util/macros.h>
#include <util/messages.h>
#include <util/xmalloc.h>
/*
+ * Discard all data in the evbuffer. This handler is used with protocol
+ * version one when we've already read as much data as we can return to the
+ * remctl client.
+ */
+static void
+handle_output_discard(struct bufferevent *bev, void *data UNUSED)
+{
+ size_t length;
+ struct evbuffer *buf;
+
+ buf = bufferevent_get_input(bev);
+ length = evbuffer_get_length(buf);
+ if (evbuffer_drain(buf, length) < 0)
+ sysdie("internal error: cannot discard extra output");
+}
+
+
+/*
+ * Callback used to handle filling the output buffer with protocol version
+ * one. When this happens, we pull all of the data out into a separate
+ * evbuffer and then change our read callback to handle_output_discard, which
+ * just drains (discards) all subsequent data from the process.
+ */
+static void
+handle_output_full(struct bufferevent *bev, void *data)
+{
+ struct process *process = data;
+ bufferevent_data_cb writecb;
+
+ process->output = evbuffer_new();
+ if (process->output == NULL)
+ die("internal error: cannot create discard evbuffer");
+ if (bufferevent_read_buffer(bev, process->output) < 0)
+ die("internal error: cannot move data into output buffer");
+
+ /*
+ * Change the output callback. We need to be sure not to dump our input
+ * callback if it exists.
+ *
+ * After we see all the output that we can send to the client, we no
+ * longer care about error and EOF events, but if we set the callback to
+ * NULL here, we cause segfaults in libevent 1.4.x when we have both read
+ * and EOF events in the same event loop. So keep the error event handler
+ * since it doesn't hurt anything. This can safely be set to NULL once we
+ * require libevent 2.x.
+ */
+ writecb = (process->input == NULL) ? NULL : server_handle_input_end;
+ bufferevent_setcb(bev, handle_output_discard, writecb,
+ server_handle_io_event, data);
+}
+
+
+/*
+ * Set up handling of a child process with the v1 protocol. Takes the process
+ * struct sets up the necessary event loop hooks.
+ */
+void
+server_v1_command_setup(struct process *process)
+{
+ bufferevent_data_cb writecb;
+
+ writecb = (process->input == NULL) ? NULL : server_handle_input_end;
+ bufferevent_setcb(process->inout, handle_output_full, writecb,
+ server_handle_io_event, process);
+ bufferevent_setwatermark(process->inout, EV_READ, TOKEN_MAX_OUTPUT_V1,
+ TOKEN_MAX_OUTPUT_V1);
+}
+
+
+/*
* Given the client struct, a buffer of data to send, and the exit status of a
* command, send a protocol v1 output token back to the client. Returns true
* on success and false on failure (and logs a message on failure).
@@ -73,6 +145,29 @@ server_v1_send_output(struct client *client, struct evbuffer *output,
/*
+ * Given the client struct, an error code, and an error message, send a
+ * protocol v1 error token to the client. Returns true on success, false on
+ * failure (and logs a message on failure).
+ */
+bool
+server_v1_send_error(struct client *client, enum error_codes code UNUSED,
+ const char *message)
+{
+ struct evbuffer *buf;
+ bool result;
+
+ buf = evbuffer_new();
+ if (buf == NULL)
+ die("internal error: cannot create output buffer");
+ if (evbuffer_add_printf(buf, "%s\n", message) < 0)
+ die("internal error: cannot add error message to buffer");
+ result = server_v1_send_output(client, buf, -1);
+ evbuffer_free(buf);
+ return result;
+}
+
+
+/*
* Takes the client struct and the server configuration and handles a client
* request. Reads a command from the client, checks the ACL, runs the command
* if appropriate, and sends any output back to the client.
@@ -91,9 +186,9 @@ server_v1_handle_messages(struct client *client, struct config *config)
if (status != TOKEN_OK) {
warn_token("receiving command token", status, major, minor);
if (status == TOKEN_FAIL_LARGE)
- server_send_error(client, ERROR_TOOMUCH_DATA, "Too much data");
+ client->error(client, ERROR_TOOMUCH_DATA, "Too much data");
else if (status != TOKEN_FAIL_EOF)
- server_send_error(client, ERROR_BAD_TOKEN, "Invalid token");
+ client->error(client, ERROR_BAD_TOKEN, "Invalid token");
return;
}
@@ -101,7 +196,7 @@ server_v1_handle_messages(struct client *client, struct config *config)
if (token.length > TOKEN_MAX_DATA) {
warn("command data length %lu exceeds 64KB",
(unsigned long) token.length);
- server_send_error(client, ERROR_TOOMUCH_DATA, "Too much data");
+ client->error(client, ERROR_TOOMUCH_DATA, "Too much data");
gss_release_buffer(&minor, &token);
return;
}
diff --git a/server/server-v2.c b/server/server-v2.c
index c609815..c208d23 100644
--- a/server/server-v2.c
+++ b/server/server-v2.c
@@ -5,6 +5,7 @@
*
* Written by Russ Allbery <eagle@eyrie.org>
* Based on work by Anton Ushakov
+ * Copyright 2016 Dropbox, Inc.
* Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2014
* The Board of Trustees of the Leland Stanford Junior University
*
@@ -20,6 +21,7 @@
#include <server/internal.h>
#include <util/gss-tokens.h>
+#include <util/macros.h>
#include <util/messages.h>
#include <util/xmalloc.h>
@@ -30,7 +32,7 @@
* buffer in the client struct. Returns true on success, false on failure
* (and logs a message on failure).
*/
-bool
+static bool
server_v2_send_output(struct client *client, int stream,
struct evbuffer *output)
{
@@ -80,12 +82,59 @@ server_v2_send_output(struct client *client, int stream,
/*
+ * Callback used to handle output from a process (protocol version two or
+ * later). We use the same handler for both standard output and standard
+ * error and check the bufferevent to determine which stream we're seeing.
+ *
+ * When called, note that we saw some output, which is a flag to continue
+ * processing when running the event loop after the child has exited.
+ */
+static void
+handle_output(struct bufferevent *bev, void *data)
+{
+ int stream;
+ struct evbuffer *buf;
+ struct process *process = data;
+
+ process->saw_output = true;
+ stream = (bev == process->inout) ? 1 : 2;
+ buf = bufferevent_get_input(bev);
+ if (!server_v2_send_output(process->client, stream, buf)) {
+ process->saw_error = true;
+ event_base_loopbreak(process->loop);
+ }
+}
+
+
+/*
+ * Set up handling of a child process with the v2 protocol. Takes the process
+ * struct and sets up the necessary event loop hooks.
+ */
+void
+server_v2_command_setup(struct process *process)
+{
+ bufferevent_data_cb writecb;
+
+ writecb = (process->input == NULL) ? NULL : server_handle_input_end;
+ bufferevent_setcb(process->inout, handle_output, writecb,
+ server_handle_io_event, process);
+ bufferevent_setwatermark(process->inout, EV_READ, 0, TOKEN_MAX_OUTPUT);
+ bufferevent_enable(process->err, EV_READ);
+ bufferevent_setcb(process->err, handle_output, NULL,
+ server_handle_io_event, process);
+ bufferevent_setwatermark(process->err, EV_READ, 0, TOKEN_MAX_OUTPUT);
+}
+
+
+/*
* Given the client struct and the exit status, send a protocol v2 status
* token to the client. Returns true on success, false on failure (and logs a
- * message on failure).
+ * message on failure). Takes an ignored buffer argument for call
+ * compatibility with protocol v1.
*/
bool
-server_v2_send_status(struct client *client, int exit_status)
+server_v2_command_finish(struct client *client, struct evbuffer *output UNUSED,
+ int exit_status)
{
gss_buffer_desc token;
char buffer[1 + 1 + 1];
@@ -241,7 +290,7 @@ server_v2_read_token(struct client *client, gss_buffer_t token)
if (status != TOKEN_OK) {
warn_token("receiving token", status, major, minor);
if (status != TOKEN_FAIL_EOF && status != TOKEN_FAIL_SOCKET)
- server_send_error(client, ERROR_BAD_TOKEN, "Invalid token");
+ client->error(client, ERROR_BAD_TOKEN, "Invalid token");
}
return status;
}
@@ -278,8 +327,7 @@ server_v2_read_continuation(struct client *client, gss_buffer_t token)
return false;
} else if (p[1] != MESSAGE_COMMAND) {
warn("unexpected message type %d from client", (int) p[1]);
- server_send_error(client, ERROR_UNEXPECTED_MESSAGE,
- "Unexpected message");
+ client->error(client, ERROR_UNEXPECTED_MESSAGE, "Unexpected message");
return false;
}
return true;
@@ -320,16 +368,16 @@ server_v2_handle_command(struct client *client, struct config *config,
if (token->length > TOKEN_MAX_DATA) {
warn("command data length %lu exceeds 64KB",
(unsigned long) token->length);
- result = server_send_error(client, ERROR_TOOMUCH_DATA,
- "Too much data");
+ result = client->error(client, ERROR_TOOMUCH_DATA,
+ "Too much data");
goto fail;
}
/* Make sure the continuation is sane. */
if ((p[3] == 1 && continued) || (p[3] > 1 && !continued) || p[3] > 3) {
warn("bad continue status %d", (int) p[3]);
- result = server_send_error(client, ERROR_BAD_COMMAND,
- "Invalid command token");
+ result = client->error(client, ERROR_BAD_COMMAND,
+ "Invalid command token");
goto fail;
}
continued = (p[3] == 1 || p[3] == 2);
@@ -344,8 +392,8 @@ server_v2_handle_command(struct client *client, struct config *config,
if (length >= COMMAND_MAX_DATA - total) {
warn("total command length %lu exceeds %lu", length + total,
COMMAND_MAX_DATA);
- result = server_send_error(client, ERROR_TOOMUCH_DATA,
- "Too much data");
+ result = client->error(client, ERROR_TOOMUCH_DATA,
+ "Too much data");
goto fail;
}
if (continued || buffer != NULL) {
@@ -426,8 +474,8 @@ server_v2_handle_token(struct client *client, struct config *config,
break;
default:
warn("unknown message type %d from client", (int) p[1]);
- result = server_send_error(client, ERROR_UNKNOWN_MESSAGE,
- "Unknown message");
+ result = client->error(client, ERROR_UNKNOWN_MESSAGE,
+ "Unknown message");
break;
}
return result;