source: web/old/remctl-2.14/client/client-v2.c @ f6f3e91

web
Last change on this file since f6f3e91 was f6f3e91, checked in by Jessica B. Hamrick <jhamrick@…>, 15 years ago

Preserve directory hierarchy (not sure what happened to it)

  • Property mode set to 100644
File size: 10.4 KB
Line 
1/*
2 * Protocol v2, client implementation.
3 *
4 * This is the client implementation of the new v2 protocol.  It's fairly
5 * close to the regular remctl API.
6 *
7 * Written by Russ Allbery <rra@stanford.edu>
8 * Based on work by Anton Ushakov
9 * Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008
10 *     Board of Trustees, Leland Stanford Jr. University
11 *
12 * See LICENSE for licensing terms.
13 */
14
15#include <config.h>
16#include <portable/system.h>
17#include <portable/gssapi.h>
18#include <portable/socket.h>
19#include <portable/uio.h>
20
21#include <errno.h>
22
23#include <client/internal.h>
24#include <client/remctl.h>
25#include <util/util.h>
26
27
28/*
29 * Send a command to the server using protocol v2.  Returns true on success,
30 * false on failure.
31 *
32 * All of the complexity in this function comes from implementing command
33 * continuation.  The protocol specifies that commands can be continued by
34 * tresting the command as one huge token, chopping it into as many pieces as
35 * desired, and putting the MESSAGE_COMMAND header on each piece with the
36 * appropriate continue status.  We don't take full advantage of that (we
37 * don't, for instance, ever split numbers across token boundaries), but we do
38 * use this to handle commands where all the data is longer than
39 * TOKEN_MAX_DATA.
40 */
41bool
42internal_v2_commandv(struct remctl *r, const struct iovec *command,
43                     size_t count)
44{
45    size_t length, iov, offset, sent, left, delta;
46    gss_buffer_desc token;
47    char *p;
48    OM_uint32 data, major, minor;
49    int status;
50
51    /* Determine the total length of the message. */
52    length = 4;
53    for (iov = 0; iov < count; iov++)
54        length += 4 + command[iov].iov_len;
55
56    /*
57     * Now, loop until we've conveyed the entire message.  Each token we send
58     * to the server must include the standard header and the continue status.
59     * The first token then has the argument count, and the remainder of the
60     * command consists of pairs of argument length and argument data.
61     *
62     * If the entire message length plus the overhead for the header is less
63     * than TOKEN_MAX_DATA, we send it in one go.  Otherwise, each time
64     * through this loop, we pull off as much data as we can.  We break the
65     * tokens either in the middle of an argument or just before an argument
66     * length; we never send part of the argument length number and we always
67     * include at least one byte of the argument after the argument length.
68     * The protocol is more lenient, but those constraints make bookkeeping
69     * easier.
70     *
71     * iov is the index of the argument we're currently sending.  offset is
72     * the amount of that argument data we've already sent.  sent holds the
73     * total length sent so far so that we can tell when we're done.
74     */
75    iov = 0;
76    offset = 0;
77    sent = 0;
78    while (sent < length) {
79        if (length - sent > TOKEN_MAX_DATA - 4)
80            token.length = TOKEN_MAX_DATA;
81        else
82            token.length = length - sent + 4;
83        token.value = malloc(token.length);
84        if (token.value == NULL) {
85            internal_set_error(r, "cannot allocate memory: %s",
86                               strerror(errno));
87            return false;
88        }
89        left = token.length - 4;
90
91        /* Each token begins with the protocol version and message type. */
92        p = token.value;
93        p[0] = 2;
94        p[1] = MESSAGE_COMMAND;
95        p += 2;
96
97        /* Keep-alive flag.  Always set to true for now. */
98        *p = 1;
99        p++;
100
101        /* Continue status. */
102        if (token.length == length - sent + 4)
103            *p = (sent == 0) ? 0 : 3;
104        else
105            *p = (sent == 0) ? 1 : 2;
106        p++;
107
108        /* Argument count if we haven't sent anything yet. */
109        if (sent == 0) {
110            data = htonl(count);
111            memcpy(p, &data, 4);
112            p += 4;
113            sent += 4;
114            left -= 4;
115        }
116
117        /*
118         * Now, as many arguments as will fit.  If offset is 0, we're at the
119         * beginning of an argument and need to send the length.  Make sure,
120         * if we're at the beginning of an argument, that we can add at least
121         * five octets to this token.  The length plus at least one octet must
122         * fit (or just the length if that argument is zero-length).
123         */
124        for (; iov < count; iov++) {
125            if (offset == 0) {
126                if (left < 4 || (left < 5 && command[iov].iov_len > 0))
127                    break;
128                data = htonl(command[iov].iov_len);
129                memcpy(p, &data, 4);
130                p += 4;
131                sent += 4;
132                left -= 4;
133            }
134            if (left >= command[iov].iov_len - offset)
135                delta = command[iov].iov_len - offset;
136            else
137                delta = left;
138            memcpy(p, (char *) command[iov].iov_base + offset, delta);
139            p += delta;
140            sent += delta;
141            offset += delta;
142            left -= delta;
143            if (offset < (size_t) command[iov].iov_len)
144                break;
145            offset = 0;
146        }
147
148        /* Send the result. */
149        token.length -= left;
150        status = token_send_priv(r->fd, r->context,
151                                 TOKEN_DATA | TOKEN_PROTOCOL, &token,
152                                 &major, &minor);
153        if (status != TOKEN_OK) {
154            internal_token_error(r, "sending token", status, major, minor);
155            free(token.value);
156            return false;
157        }
158        free(token.value);
159    }
160    r->ready = true;
161    return true;
162}
163
164
165/*
166 * Send a quit command to the server using protocol v2.  Returns true on
167 * success, false on failure.
168 */
169bool
170internal_v2_quit(struct remctl *r)
171{
172    gss_buffer_desc token;
173    char buffer[2] = { 2, MESSAGE_QUIT };
174    OM_uint32 major, minor;
175    int status;
176
177    token.length = 1 + 1;
178    token.value = buffer;
179    status = token_send_priv(r->fd, r->context, TOKEN_DATA | TOKEN_PROTOCOL,
180                             &token, &major, &minor);
181    if (status != TOKEN_OK) {
182        internal_token_error(r, "sending token", status, major, minor);
183        return false;
184    }
185    return true;
186}
187
188
189/*
190 * Read a string from a server token, with its length starting at the given
191 * offset, and store it in newly allocated memory in the remctl struct.
192 * Returns true on success and false on any failure (also setting the error).
193 */
194static bool
195internal_v2_read_string(struct remctl *r, gss_buffer_t token, size_t offset)
196{
197    size_t size;
198    OM_uint32 data;
199    const char *p;
200
201    p = (const char *) token->value + offset;
202    memcpy(&data, p, 4);
203    p += 4;
204    size = ntohl(data);
205    if (size != token->length - (p - (char *) token->value)) {
206        internal_set_error(r, "malformed result token from server");
207        return false;
208    }
209    r->output->data = malloc(size);
210    if (r->output->data == NULL) {
211        internal_set_error(r, "cannot allocate memory: %s", strerror(errno));
212        return false;
213    }
214    memcpy(r->output->data, p, size);
215    r->output->length = size;
216    return true;
217}
218
219
220/*
221 * Retrieve the output from the server using protocol v2 and return it.  This
222 * function may be called any number of times; if the last packet we got from
223 * the server was a REMCTL_OUT_STATUS or REMCTL_OUT_ERROR, we'll return
224 * REMCTL_OUT_DONE from that point forward.  Returns a remctl output struct on
225 * success and NULL on failure.
226 */
227struct remctl_output *
228internal_v2_output(struct remctl *r)
229{
230    int status, flags;
231    gss_buffer_desc token;
232    OM_uint32 data, major, minor;
233    char *p;
234    int type;
235
236    /*
237     * Initialize our output.  If we're not ready to read more data from the
238     * server, return REMCTL_OUT_DONE.
239     */
240    if (r->output == NULL) {
241        r->output = malloc(sizeof(struct remctl_output));
242        if (r->output == NULL) {
243            internal_set_error(r, "cannot allocate memory: %s",
244                               strerror(errno));
245            return NULL;
246        }
247        r->output->data = NULL;
248    }
249    internal_output_wipe(r->output);
250    if (!r->ready)
251        return r->output;
252
253    /* Otherwise, we have to read the token from the server. */
254    status = token_recv_priv(r->fd, r->context, &flags, &token,
255                             TOKEN_MAX_LENGTH, &major, &minor);
256    if (status != TOKEN_OK) {
257        internal_token_error(r, "receiving token", status, major, minor);
258        if (status == TOKEN_FAIL_EOF) {
259            socket_close(r->fd);
260            r->fd = -1;
261        }
262        return NULL;
263    }
264    if (flags != (TOKEN_DATA | TOKEN_PROTOCOL)) {
265        internal_set_error(r, "unexpected token from server");
266        goto fail;
267    }
268    if (token.length < 2) {
269        internal_set_error(r, "malformed result token from server");
270        goto fail;
271    }
272
273    /* Extract the message protocol and type. */
274    p = token.value;
275    if (p[0] != 2) {
276        internal_set_error(r, "unexpected protocol %d from server", p[0]);
277        goto fail;
278    }
279    type = p[1];
280
281    /* Now, what we do depends on the message type. */
282    switch (type) {
283    case MESSAGE_OUTPUT:
284        if (token.length < 2 + 5) {
285            internal_set_error(r, "malformed result token from server");
286            goto fail;
287        }
288        r->output->type = REMCTL_OUT_OUTPUT;
289        if (p[2] != 1 && p[2] != 2) {
290            internal_set_error(r, "unexpected stream %d from server", p[0]);
291            goto fail;
292        }
293        r->output->stream = p[2];
294        if (!internal_v2_read_string(r, &token, 3))
295            goto fail;
296        break;
297
298    case MESSAGE_STATUS:
299        if (token.length != 2 + 1) {
300            internal_set_error(r, "malformed result token from server");
301            goto fail;
302        }
303        r->output->type = REMCTL_OUT_STATUS;
304        r->output->status = p[2];
305        r->ready = 0;
306        break;
307
308    case MESSAGE_ERROR:
309        if (token.length < 2 + 8) {
310            internal_set_error(r, "malformed result token from server");
311            goto fail;
312        }
313        r->output->type = REMCTL_OUT_ERROR;
314        memcpy(&data, p + 2, 4);
315        r->output->error = ntohl(data);
316        if (!internal_v2_read_string(r, &token, 6))
317            goto fail;
318        r->ready = 0;
319        break;
320
321    default:
322        internal_set_error(r, "unknown message type %d from server", type);
323        goto fail;
324    }
325
326    /* We've finished analyzing the packet.  Return the results. */
327    gss_release_buffer(&minor, &token);
328    return r->output;
329
330fail:
331    gss_release_buffer(&minor, &token);
332    return NULL;
333}
Note: See TracBrowser for help on using the repository browser.