File: open.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 (312 lines) | stat: -rw-r--r-- 10,996 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
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
300
301
302
303
304
305
306
307
308
309
310
311
312
/*
 * Open a connection to a remote server.
 *
 * This is the client implementation of opening a connection to a remote
 * server and doing the initial GSS-API negotiation.  This function is shared
 * between the v1 and v2 implementations.  One of the things it establishes is
 * what protocol is being used.
 *
 * Written by Russ Allbery <eagle@eyrie.org>
 * Based on work by Anton Ushakov
 * Copyright 2002-2010, 2012-2014
 *     The Board of Trustees of the Leland Stanford Junior University
 *
 * SPDX-License-Identifier: MIT
 */

#include <config.h>
#include <portable/gssapi.h>
#ifdef HAVE_KRB5
#    include <portable/krb5.h>
#endif
#include <portable/socket.h>
#include <portable/system.h>

#include <errno.h>

#include <client/internal.h>
#include <client/remctl.h>
#include <util/macros.h>
#include <util/network.h>
#include <util/protocol.h>
#include <util/tokens.h>


/*
 * Given the remctl object (for error reporting), host, and port, attempt a
 * network connection.  Returns the file descriptor if successful or
 * INVALID_SOCKET on failure.
 */
socket_type
internal_connect(struct remctl *r, const char *host, unsigned short port)
{
    struct addrinfo hints, *ai;
    char portbuf[16];
    int status;
    socket_type fd;

    /*
     * Look up the remote host and open a TCP connection.  Call getaddrinfo
     * and network_connect instead of network_connect_host so that we can
     * report the complete error on host resolution.
     */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    snprintf(portbuf, sizeof(portbuf), "%hu", port);
    status = getaddrinfo(host, portbuf, &hints, &ai);
    if (status != 0) {
        internal_set_error(r, "unknown host %s: %s", host,
                           gai_strerror(status));
        return INVALID_SOCKET;
    }
    fd = network_connect(ai, r->source, r->timeout);
    freeaddrinfo(ai);
    if (fd == INVALID_SOCKET) {
        internal_set_error(r, "cannot connect to %s (port %hu): %s", host,
                           port, socket_strerror(socket_errno));
        return INVALID_SOCKET;
    }
    return fd;
}


/*
 * Given the remctl struct, the host to connect to, the principal name (which
 * may be NULL to use the default), and a pointer to a gss_name_t, import that
 * principal into a GSS-API name.  We want to use a host-based name if
 * possible since that will trigger domain to realm mapping and name
 * canonicalization if desired, but given an arbitrary Kerberos principal, we
 * don't know whether it's host-based or not.  Therefore, if the principal was
 * specified explicitly, always just use it.
 *
 * Returns true on success and false on failure.
 */
static bool
internal_import_name(struct remctl *r, const char *host, const char *principal,
                     gss_name_t *name)
{
    gss_buffer_desc name_buffer;
    char *defprinc = NULL;
    OM_uint32 major, minor;
    gss_OID oid;

    /*
     * If principal is NULL, use host@<host>.  Don't use xmalloc here since it
     * dies on failure and that's rude for a library.
     */
    if (principal == NULL) {
        if (asprintf(&defprinc, "host@%s", host) < 0) {
            internal_set_error(r, "cannot allocate memory: %s",
                               strerror(errno));
            return false;
        }
        principal = defprinc;
    }

    /*
     * Import the name.  If principal was null, we use a host-based OID;
     * otherwise, specify that the name is a Kerberos principal.
     */
    name_buffer.value = (char *) principal;
    name_buffer.length = strlen(principal) + 1;
    if (defprinc == NULL)
        oid = GSS_C_NT_USER_NAME;
    else
        oid = GSS_C_NT_HOSTBASED_SERVICE;
    major = gss_import_name(&minor, &name_buffer, oid, name);
    free(defprinc);
    if (major != GSS_S_COMPLETE) {
        internal_gssapi_error(r, "parsing name", major, minor);
        return false;
    }
    return true;
}


/*
 * Import the client credentials from a designated Kerberos ticket cache.
 *
 * This code is used if we have Kerberos libraries available and the GSS-API
 * implementation supports gss_krb5_import_cred.  In that case, we can tell
 * GSS-API which ticket cache to use.  Otherwise, we have to either set a
 * global GSS-API variable with gss_krb5_ccache_name or just use whatever the
 * default is.  The other cases are handled in remctl_set_ccache.
 */
#if defined(HAVE_GSS_KRB5_IMPORT_CRED) && defined(HAVE_KRB5)
static bool
internal_set_cred(struct remctl *r, gss_cred_id_t *gss_cred)
{
    krb5_error_code code;
    OM_uint32 major, minor;

    if (r->krb_ctx == NULL) {
        code = krb5_init_context(&r->krb_ctx);
        if (code != 0) {
            internal_krb5_error(r, "opening ticket cache", code);
            return false;
        }
    }
    if (r->krb_ccache != NULL)
        krb5_cc_close(r->krb_ctx, r->krb_ccache);
    code = krb5_cc_resolve(r->krb_ctx, r->ccache, &r->krb_ccache);
    if (code != 0) {
        internal_krb5_error(r, "opening ticket cache", code);
        return false;
    }
    major = gss_krb5_import_cred(&minor, r->krb_ccache, NULL, NULL, gss_cred);
    if (major != GSS_S_COMPLETE) {
        internal_gssapi_error(r, "importing ticket cache", major, minor);
        return false;
    }
    return true;
}
#else  /* !HAVE_GSS_KRB5_IMPORT_CRED || !HAVE_KRB5 */
static bool
internal_set_cred(struct remctl *r UNUSED, gss_cred_id_t *gss_cred UNUSED)
{
    return false;
}
#endif /* !HAVE_GSS_KRB5_IMPORT_CRED */


/*
 * Open a new connection to a server.  Returns true on success, false on
 * failure.  On failure, sets the error message appropriately.
 */
bool
internal_open(struct remctl *r, const char *host, const char *principal)
{
    int status, flags;
    gss_buffer_desc send_tok, recv_tok, *token_ptr;
    gss_buffer_desc empty_token = {0, (void *) ""};
    gss_name_t name = GSS_C_NO_NAME;
    gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
    gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;
    OM_uint32 major, minor, init_minor, gss_flags;
    static const OM_uint32 wanted_gss_flags =
        (GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG
         | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG);
    static const OM_uint32 req_gss_flags =
        (GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG);

    /* Import the name. */
    if (!internal_import_name(r, host, principal, &name))
        goto fail;

    /* If the user has specified a Kerberos ticket cache, import it. */
    if (r->ccache != NULL)
        if (!internal_set_cred(r, &gss_cred))
            goto fail;

    /*
     * Default to protocol version two, but if some other protocol is already
     * set in the remctl struct, don't override.  This facility is used only
     * for testing currently.
     */
    if (r->protocol == 0)
        r->protocol = 2;

    /* Send the initial negotiation token. */
    status =
        token_send(r->fd, TOKEN_NOOP | TOKEN_CONTEXT_NEXT | TOKEN_PROTOCOL,
                   &empty_token, r->timeout);
    if (status != TOKEN_OK) {
        internal_token_error(r, "sending initial token", status, 0, 0);
        goto fail;
    }

    /*
     * Perform the context-establishment loop.
     *
     * On each pass through the loop, token_ptr points to the token to send to
     * the server (or GSS_C_NO_BUFFER on the first pass).  Every generated
     * token is stored in send_tok which is then transmitted to the server;
     * every received token is stored in recv_tok, which token_ptr is then set
     * to, to be processed by the next call to gss_init_sec_context.
     *
     * GSS-API guarantees that send_tok's length will be non-zero if and only
     * if the server is expecting another token from us, and that
     * gss_init_sec_context returns GSS_S_CONTINUE_NEEDED if and only if the
     * server has another token to send us.
     *
     * We start with the assumption that we're going to do protocol v2, but if
     * the server ever drops TOKEN_PROTOCOL from the response, we fall back to
     * v1.
     */
    token_ptr = GSS_C_NO_BUFFER;
    do {
        major = gss_init_sec_context(&init_minor, gss_cred, &gss_context, name,
                                     (const gss_OID) GSS_KRB5_MECHANISM,
                                     wanted_gss_flags, 0, NULL, token_ptr,
                                     NULL, &send_tok, &gss_flags, NULL);
        if (token_ptr != GSS_C_NO_BUFFER)
            free(recv_tok.value);

        /* If we have anything more to say, send it. */
        if (send_tok.length != 0) {
            flags = TOKEN_CONTEXT;
            if (r->protocol > 1)
                flags |= TOKEN_PROTOCOL;
            status = token_send(r->fd, flags, &send_tok, r->timeout);
            if (status != TOKEN_OK) {
                internal_token_error(r, "sending token", status, 0, 0);
                gss_release_buffer(&minor, &send_tok);
                goto fail;
            }
        }
        gss_release_buffer(&minor, &send_tok);

        /* On error, report the error and abort. */
        if (major != GSS_S_COMPLETE && major != GSS_S_CONTINUE_NEEDED) {
            internal_gssapi_error(r, "initializing context", major,
                                  init_minor);
            goto fail;
        }

        /* If we're still expecting more, retrieve it. */
        if (major == GSS_S_CONTINUE_NEEDED) {
            status = token_recv(r->fd, &flags, &recv_tok, TOKEN_MAX_LENGTH,
                                r->timeout);
            if (status != TOKEN_OK) {
                internal_token_error(r, "receiving token", status, major,
                                     minor);
                goto fail;
            }
            if (r->protocol > 1 && (flags & TOKEN_PROTOCOL) != TOKEN_PROTOCOL)
                r->protocol = 1;
            token_ptr = &recv_tok;
        }
    } while (major == GSS_S_CONTINUE_NEEDED);

    /*
     * If the flags we get back from the server are bad and we're doing
     * protocol v2, report an error and abort.  This must be done after
     * establishing the context, since Heimdal doesn't report all flags until
     * context negotiation is complete.
     */
    if (r->protocol > 1 && (gss_flags & req_gss_flags) != req_gss_flags) {
        internal_set_error(r, "server did not negotiate acceptable GSS-API"
                              " flags");
        goto fail;
    }

    /* Success.  Set the context in the struct remctl object. */
    r->context = gss_context;
    r->ready = 0;
    gss_release_name(&minor, &name);
    if (gss_cred != GSS_C_NO_CREDENTIAL)
        gss_release_cred(&minor, &gss_cred);
    return true;

fail:
    socket_close(r->fd);
    r->fd = INVALID_SOCKET;
    if (name != GSS_C_NO_NAME)
        gss_release_name(&minor, &name);
    if (gss_cred != GSS_C_NO_CREDENTIAL)
        gss_release_cred(&minor, &gss_cred);
    if (gss_context != GSS_C_NO_CONTEXT)
        gss_delete_sec_context(&minor, &gss_context, GSS_C_NO_BUFFER);
    return false;
}