summaryrefslogtreecommitdiff
path: root/server/generic.c
blob: 7a1885c4b6b01d366c619b5b416445c44dd9dff0 (plain)
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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/*
 * Server implementation of generic GSS-API protocol functions.
 *
 * 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 2015, 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 <time.h>

#include <server/internal.h>
#include <util/messages.h>
#include <util/protocol.h>
#include <util/tokens.h>
#include <util/xmalloc.h>


/*
 * Create a new client struct from a file descriptor and establish a GSS-API
 * context as a specified service with an incoming client and fills out the
 * client struct.  Returns a new client struct on success and NULL on failure,
 * logging an appropriate error message.
 */
struct client *
server_new_client(int fd, gss_cred_id_t creds)
{
    struct client *client;
    struct sockaddr_storage ss;
    socklen_t socklen, buflen;
    char *buffer;
    gss_buffer_desc send_tok, recv_tok, name_buf;
    gss_name_t name = GSS_C_NO_NAME;
    gss_OID doid;
    OM_uint32 major = 0;
    OM_uint32 minor = 0;
    OM_uint32 acc_minor, time_rec;
    int flags, status;
    static const OM_uint32 req_gss_flags
        = (GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG);

    /* Create and initialize a new client struct. */
    client = xcalloc(1, sizeof(struct client));
    client->fd = fd;
    client->context = GSS_C_NO_CONTEXT;

    /* Fill in hostname and IP address. */
    socklen = sizeof(ss);
    if (getpeername(fd, (struct sockaddr *) &ss, &socklen) != 0) {
        syswarn("cannot get peer address");
        goto fail;
    }
    buflen = INET6_ADDRSTRLEN;
    buffer = xmalloc(buflen);
    client->ipaddress = buffer;
    status = getnameinfo((struct sockaddr *) &ss, socklen, buffer, buflen,
                         NULL, 0, NI_NUMERICHOST);
    if (status != 0) {
        syswarn("cannot translate IP address of client: %s",
                gai_strerror(status));
        goto fail;
    }
    buflen = NI_MAXHOST;
    buffer = xmalloc(buflen);
    status = getnameinfo((struct sockaddr *) &ss, socklen, buffer, buflen,
                         NULL, 0, NI_NAMEREQD);
    if (status == 0)
        client->hostname = buffer;
    else
        free(buffer);

    /* Accept the initial (worthless) token. */
    status = token_recv(client->fd, &flags, &recv_tok, TOKEN_MAX_LENGTH,
                        TIMEOUT);
    if (status != TOKEN_OK) {
        warn_token("receiving initial token", status, major, minor);
        goto fail;
    }
    free(recv_tok.value);
    if (flags == (TOKEN_NOOP | TOKEN_CONTEXT_NEXT | TOKEN_PROTOCOL))
        client->protocol = 2;
    else if (flags == (TOKEN_NOOP | TOKEN_CONTEXT_NEXT))
        client->protocol = 1;
    else {
        warn("bad token flags %d in initial token", flags);
        goto fail;
    }

    /* Now, do the real work of negotiating the context. */
    do {
        status = token_recv(client->fd, &flags, &recv_tok, TOKEN_MAX_LENGTH,
                            TIMEOUT);
        if (status != TOKEN_OK) {
            warn_token("receiving context token", status, major, minor);
            goto fail;
        }
        if (flags == TOKEN_CONTEXT)
            client->protocol = 1;
        else if (flags != (TOKEN_CONTEXT | TOKEN_PROTOCOL)) {
            warn("bad token flags %d in context token", flags);
            free(recv_tok.value);
            goto fail;
        }
        debug("received context token (size=%lu)",
              (unsigned long) recv_tok.length);
        major = gss_accept_sec_context(&acc_minor, &client->context, creds,
                    &recv_tok, GSS_C_NO_CHANNEL_BINDINGS, &name, &doid,
                    &send_tok, &client->flags, &time_rec, NULL);
        free(recv_tok.value);

        /* Send back a token if we need to. */
        if (send_tok.length != 0) {
            debug("sending context token (size=%lu)",
                  (unsigned long) send_tok.length);
            flags = TOKEN_CONTEXT;
            if (client->protocol > 1)
                flags |= TOKEN_PROTOCOL;
            status = token_send(client->fd, flags, &send_tok, TIMEOUT);
            if (status != TOKEN_OK) {
                warn_token("sending context token", status, major, minor);
                gss_release_buffer(&minor, &send_tok);
                goto fail;
            }
            gss_release_buffer(&minor, &send_tok);
        }

        /* Bail out if we lose. */
        if (major != GSS_S_COMPLETE && major != GSS_S_CONTINUE_NEEDED) {
            warn_gssapi("while accepting context", major, acc_minor);
            goto fail;
        }
        if (major == GSS_S_CONTINUE_NEEDED)
            debug("continue needed while accepting context");
    } while (major == GSS_S_CONTINUE_NEEDED);

    /* Make sure that the appropriate context flags are set. */
    if (client->protocol > 1) {
        if ((client->flags & req_gss_flags) != req_gss_flags) {
            warn("client did not negotiate appropriate GSS-API flags");
            goto fail;
        }
    }

    /* 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) {
        warn_gssapi("while displaying client name", major, minor);
        goto fail;
    }
    gss_release_name(&minor, &name);
    if (gss_oid_equal(doid, GSS_C_NT_ANONYMOUS))
        client->anonymous = true;
    client->user = xstrndup(name_buf.value, name_buf.length);
    client->expires = time(NULL) + time_rec;
    gss_release_buffer(&minor, &name_buf);
    return client;

fail:
    if (client->context != GSS_C_NO_CONTEXT)
        gss_delete_sec_context(&minor, &client->context, GSS_C_NO_BUFFER);
    if (name != GSS_C_NO_NAME)
        gss_release_name(&minor, &name);
    free(client->ipaddress);
    free(client->hostname);
    free(client);
    return NULL;
}


/*
 * Free a client struct, including any resources that it holds.
 */
void
server_free_client(struct client *client)
{
    OM_uint32 major, minor;

    if (client == NULL)
        return;
    if (client->context != GSS_C_NO_CONTEXT) {
        major = gss_delete_sec_context(&minor, &client->context, NULL);
        if (major != GSS_S_COMPLETE)
            warn_gssapi("while deleting context", major, minor);
    }
    if (client->fd >= 0)
        close(client->fd);
    free(client->user);
    free(client->hostname);
    free(client->ipaddress);
    free(client);
}


/*
 * 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
 * error, and then returns NULL.  Otherwise, returns the struct iovec array.
 */
struct iovec **
server_parse_command(struct client *client, const char *buffer, size_t length)
{
    OM_uint32 tmp;
    size_t argc, arglen, count;
    struct iovec **argv = NULL;
    const char *p = buffer;

    /* Read the argument count. */
    memcpy(&tmp, p, 4);
    argc = ntohl(tmp);
    p += 4;
    debug("argc is %lu", (unsigned long) argc);
    if (argc == 0) {
        warn("command with no arguments");
        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);
        client->error(client, ERROR_TOOMANY_ARGS, "Too many arguments");
        return NULL;
    }
    if (length - (p - buffer) < 4 * argc) {
        warn("command data too short");
        client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
        return NULL;
    }
    argv = xcalloc(argc + 1, sizeof(struct iovec *));

    /*
     * Parse out the arguments and store them into a vector.  Arguments are
     * packed: (<arglength><argument>)+.  Make sure each time through the loop
     * that they didn't send more arguments than they claimed to have.
     */
    count = 0;
    while (p <= buffer + length - 4) {
        if (count >= argc) {
            warn("sent more arguments than argc %lu", (unsigned long) argc);
            client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
            goto fail;
        }
        memcpy(&tmp, p, 4);
        arglen = ntohl(tmp);
        p += 4;
        if ((length - (p - buffer)) < arglen) {
            warn("command data invalid");
            client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
            goto fail;
        }
        argv[count] = xmalloc(sizeof(struct iovec));
        argv[count]->iov_len = arglen;
        if (arglen == 0)
            argv[count]->iov_base = NULL;
        else {
            argv[count]->iov_base = xmalloc(arglen);
            memcpy(argv[count]->iov_base, p, arglen);
        }
        count++;
        p += arglen;
    }
    if (count != argc || p != buffer + length) {
        warn("argument count differs from arguments seen");
        client->error(client, ERROR_BAD_COMMAND, "Invalid command token");
        goto fail;
    }
    argv[count] = NULL;
    return argv;

fail:
    server_free_command(argv);
    return NULL;
}