File: server-v1.c

package info (click to toggle)
remctl 3.18-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,612 kB
  • sloc: ansic: 19,504; sh: 5,386; perl: 1,778; java: 740; makefile: 715; xml: 502; python: 430
file content (220 lines) | stat: -rw-r--r-- 7,152 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
 * Protocol v1, server implementation.
 *
 * This is the server implementation of the old v1 protocol, which doesn't
 * support streaming, keep-alive, or many of the other features of the
 * current protocol.
 *
 * Written by Russ Allbery <eagle@eyrie.org>
 * Based on work by Anton Ushakov
 * Copyright 2018 Russ Allbery <eagle@eyrie.org>
 * Copyright 2016 Dropbox, Inc.
 * Copyright 2002-2010, 2012, 2014
 *     The Board of Trustees of the Leland Stanford Junior University
 *
 * SPDX-License-Identifier: MIT
 */

#include <config.h>
#include <portable/event.h>
#include <portable/gssapi.h>
#include <portable/socket.h>
#include <portable/system.h>
#include <portable/uio.h>

#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).
 */
bool
server_v1_send_output(struct client *client, struct evbuffer *output,
                      int exit_status)
{
    gss_buffer_desc token;
    size_t outlen;
    char *p;
    OM_uint32 tmp, major, minor;
    int status;

    /* Allocate room for the total message. */
    outlen = evbuffer_get_length(output);
    if (outlen >= UINT32_MAX - 4 - 4)
        die("internal error: memory allocation too large");
    token.length = 4 + 4 + outlen;
    token.value = xmalloc(token.length);

    /* Fill in the token. */
    p = token.value;
    tmp = htonl(exit_status);
    memcpy(p, &tmp, 4);
    p += 4;
    tmp = htonl((OM_uint32) outlen);
    memcpy(p, &tmp, 4);
    p += 4;
    if (evbuffer_remove(output, p, outlen) < 0)
        die("internal error: cannot move data from output buffer");

    /* Send the token. */
    status = token_send_priv(client->fd, client->context, TOKEN_DATA, &token,
                             TIMEOUT, &major, &minor);
    if (status != TOKEN_OK) {
        warn_token("sending output token", status, major, minor);
        free(token.value);
        return false;
    }
    free(token.value);
    return true;
}


/*
 * 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.
 */
void
server_v1_handle_messages(struct client *client, struct config *config)
{
    gss_buffer_desc token;
    OM_uint32 major, minor;
    struct iovec **argv = NULL;
    int status, flags;

    /* Receive the message. */
    status = token_recv_priv(client->fd, client->context, &flags, &token,
                             TOKEN_MAX_LENGTH, TIMEOUT, &major, &minor);
    if (status != TOKEN_OK) {
        warn_token("receiving command token", status, major, minor);
        if (status == TOKEN_FAIL_LARGE)
            client->error(client, ERROR_TOOMUCH_DATA, "Too much data");
        else if (status != TOKEN_FAIL_EOF)
            client->error(client, ERROR_BAD_TOKEN, "Invalid token");
        return;
    }

    /* Check the data size. */
    if (token.length > TOKEN_MAX_DATA) {
        warn("command data length %lu exceeds 64KB",
             (unsigned long) token.length);
        client->error(client, ERROR_TOOMUCH_DATA, "Too much data");
        gss_release_buffer(&minor, &token);
        return;
    }

    /*
     * Do the shared parsing of the message.  This code is identical to the
     * code for v2 (v2 just pulls more data off the front of the token first).
     */
    argv = server_parse_command(client, token.value, token.length);
    gss_release_buffer(&minor, &token);
    if (argv == NULL)
        return;

    /*
     * Check the ACL and existence of the command, run the command if
     * possible, and accumulate the output in the client struct.
     */
    server_run_command(client, config, argv);
    server_free_command(argv);
}