Пример #1
0
/*
 * 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;
    size_t length;
    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;
    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;
    client->user = NULL;
    client->output = NULL;
    client->hostname = NULL;
    client->ipaddress = NULL;

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

    /* 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;
    }
    major = gss_release_name(&minor, &name);
    client->user = xstrndup(name_buf.value, name_buf.length);
    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);
    if (client->ipaddress != NULL)
        free(client->ipaddress);
    if (client->hostname != NULL)
        free(client->hostname);
    free(client);
    return NULL;
}
Пример #2
0
/*
 * 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, unsigned short port,
              const char *principal)
{
    int status, flags;
    bool port_fallback = false;
    socket_type fd = INVALID_SOCKET;
    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_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);

    /*
     * If port is 0, default to trying the standard port and then falling back
     * on the old port.
     */
    if (port == 0) {
        port = REMCTL_PORT;
        port_fallback = true;
    }

    /* Make the network connection. */
    fd = internal_connect(r, host, port);
    if (fd == INVALID_SOCKET && port_fallback)
        fd = internal_connect(r, host, REMCTL_PORT_OLD);
    if (fd == INVALID_SOCKET)
        goto fail;
    r->fd = fd;

    /* Import the name. */
    if (!internal_import_name(r, host, principal, &name))
        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(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_C_NO_CREDENTIAL, 
                    &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(fd, flags, &send_tok, r->timeout);
            if (status != TOKEN_OK) {
                internal_token_error(r, "sending token", status, major, minor);
                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(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);
    return true;

fail:
    if (fd != INVALID_SOCKET)
        socket_close(fd);
    r->fd = INVALID_SOCKET;
    if (name != GSS_C_NO_NAME)
        gss_release_name(&minor, &name);
    if (gss_context != GSS_C_NO_CONTEXT)
        gss_delete_sec_context(&minor, &gss_context, GSS_C_NO_BUFFER);
    return false;
}
Пример #3
0
int
main(void)
{
    pid_t child;
    socket_type server, client;
    int status, flags;
    char buffer[20];
    ssize_t length;
    gss_buffer_desc result;

    alarm(20);

    plan(12);
    if (chdir(getenv("C_TAP_BUILD")) < 0)
        sysbail("can't chdir to C_TAP_BUILD");

    unlink("server-ready");
    child = fork();
    if (child < 0)
        sysbail("cannot fork");
    else if (child == 0) {
        server = create_server();
        send_regular_token(server);
        socket_close(server);
        exit(0);
    } else {
        client = create_client();
        length = read(client, buffer, 12);
        is_int(10, length, "received token has correct length");
        ok(memcmp(buffer, token, 10) == 0, "...and correct data");
        waitpid(child, NULL, 0);
        socket_close(client);
    }

    unlink("server-ready");
    child = fork();
    if (child < 0)
        sysbail("cannot fork");
    else if (child == 0) {
        server = create_server();
        send_hand_token(server);
        socket_close(server);
        exit(0);
    } else {
        client = create_client();
        status = token_recv(client, &flags, &result, 5, 0);
        is_int(TOKEN_OK, status, "received hand-rolled token");
        is_int(3, flags, "...with right flags");
        is_int(5, result.length, "...and right length");
        ok(memcmp(result.value, "hello", 5) == 0, "...and right data");
        free(result.value);
        waitpid(child, NULL, 0);
        socket_close(client);
    }

    /* Send a token with a length of one, but no following data. */
    unlink("server-ready");
    child = fork();
    if (child < 0)
        sysbail("cannot fork");
    else if (child == 0) {
        server = create_server();
        socket_xwrite(server, "\0\0\0\0\1", 5);
        socket_close(server);
        exit(0);
    } else {
        client = create_client();
        status = token_recv(client, &flags, &result, 200, 0);
        is_int(TOKEN_FAIL_EOF, status, "receive invalid token");
        waitpid(child, NULL, 0);
        socket_close(client);
    }

    /* Send a token larger than our token size limit. */
    unlink("server-ready");
    child = fork();
    if (child < 0)
        sysbail("cannot fork");
    else if (child == 0) {
        server = create_server();
        send_hand_token(server);
        socket_close(server);
        exit(0);
    } else {
        client = create_client();
        status = token_recv(client, &flags, &result, 4, 0);
        is_int(TOKEN_FAIL_LARGE, status, "receive too-large token");
        waitpid(child, NULL, 0);
        socket_close(client);
    }

    /* Send EOF when we were expecting a token. */
    unlink("server-ready");
    child = fork();
    if (child < 0)
        sysbail("cannot fork");
    else if (child == 0) {
        server = create_server();
        socket_close(server);
        exit(0);
    } else {
        client = create_client();
        status = token_recv(client, &flags, &result, 4, 0);
        is_int(TOKEN_FAIL_EOF, status, "receive end of file");
        waitpid(child, NULL, 0);
        socket_close(client);
    }

    /*
     * Test a timeout on sending a token.  We have to send a large enough
     * token that the network layer doesn't just buffer it.
     */
    unlink("server-ready");
    child = fork();
    if (child < 0)
        sysbail("cannot fork");
    else if (child == 0) {
        server = create_server();
        sleep(3);
        socket_close(server);
        exit(0);
    } else {
        result.value = bmalloc(8192 * 1024);
        memset(result.value, 'a', 8192 * 1024);
        result.length = 8192 * 1024;
        client = create_client();
        status = token_send(client, 3, &result, 1);
        free(result.value);
        is_int(TOKEN_FAIL_TIMEOUT, status, "can't send due to timeout");
        socket_close(client);
        waitpid(child, NULL, 0);
    }

    /* Test a timeout on receiving a token. */
    unlink("server-ready");
    child = fork();
    if (child < 0)
        sysbail("cannot fork");
    else if (child == 0) {
        server = create_server();
        sleep(3);
        socket_close(server);
        exit(0);
    } else {
        client = create_client();
        status = token_recv(client, &flags, &result, 200, 1);
        is_int(TOKEN_FAIL_TIMEOUT, status, "can't receive due to timeout");
        socket_close(client);
        waitpid(child, NULL, 0);
    }

    /* Special test for error handling when sending tokens. */
    server = open("/dev/full", O_RDWR);
    if (server < 0)
        skip("/dev/full not available");
    else {
        result.value = bmalloc(5);
        memcpy(result.value, "hello", 5);
        result.length = 5;
        status = token_send(server, 3, &result, 0);
        free(result.value);
        is_int(TOKEN_FAIL_SOCKET, status, "can't send due to system error");
        close(server);
    }

    unlink("server-ready");
    return 0;
}