src/sock_gnutls.c
author Tero Marttila <terom@fixme.fi>
Sat, 28 Feb 2009 19:58:49 +0200
changeset 14 3a70e5901f17
parent 12 4147fae232d9
child 27 e6639132bead
permissions -rw-r--r--
fix sock_gnutls_read/write EAGAIN

#include "sock_gnutls.h"

#include <stdlib.h>
#include <err.h>

// XXX: errors
static err_t sock_gnutls_read (struct sock_stream *base_sock, void *buf, size_t *len)
{
    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
    struct error_info *err = SOCK_GNUTLS_ERR(sock);
    int ret;
    
    // read gnutls record
    ret = gnutls_record_recv(sock->session, buf, *len);
    
    // errors
    if (ret < 0 && ret != GNUTLS_E_AGAIN)
        RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret);
    
    else if (ret == 0)
        return SET_ERROR(err, ERR_READ_EOF);


    // eagain?
    if (ret < 0) {
        *len = 0;

    } else {
        // updated length
        *len = ret;

    }

    return SUCCESS;
}

static err_t sock_gnutls_write (struct sock_stream *base_sock, const void *buf, size_t *len)
{
    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
    struct error_info *err = SOCK_GNUTLS_ERR(sock);
    int ret;
 
    // read gnutls record
    ret = gnutls_record_send(sock->session, buf, *len);
    
    // errors
    if (ret < 0 && ret != GNUTLS_E_AGAIN)
        RETURN_SET_ERROR_EXTRA(err, ERR_GNUTLS_RECORD_RECV, ret);
    
    else if (ret == 0)
        return SET_ERROR(err, ERR_READ_EOF);


    // eagain?
    if (ret < 0) {
        *len = 0;

    } else {
        // updated length
        *len = ret;
    }

    return SUCCESS;
}

static void sock_gnutls_event_handler (int fd, short what, void *arg)
{
    struct sock_gnutls *sock = arg;
    
    // gnutls might be able to proceed now, so ask user to try what didn't work before now, using the mask given to
    // event_enable().
    sock_stream_invoke_callbacks(SOCK_GNUTLS_BASE(sock), sock->ev_mask);
}

static err_t sock_gnutls_event_init (struct sock_stream *base_sock)
{
    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);

    err_t err;

    // set nonblocking
    if ((err = sock_tcp_set_nonblock(SOCK_GNUTLS_TCP(sock), 1)))
        return err;

    // add ourselves as the event handler
    if ((err = sock_tcp_init_ev(SOCK_GNUTLS_TCP(sock), &sock_gnutls_event_handler, sock)))
        return err;

    // ok
    return SUCCESS;
}

static err_t sock_gnutls_event_enable (struct sock_stream *base_sock, short mask)
{
    struct sock_gnutls *sock = SOCK_FROM_BASE(base_sock, struct sock_gnutls);
    int ret;
    
    // store the ev_mask. We don't care about it here, because we assume that event_enable is only called once read or
    // write, respectively, return zero. This is really the only case we can handle with gnutls.
    sock->ev_mask = mask;

    // gnutls_record_get_direction tells us what I/O operation gnutls would have required for the last
    // operation, so we can use that to determine what events to register
    switch ((ret = gnutls_record_get_direction(sock->session))) {
        case 0: 
            // read more data
            sock_tcp_add_event(SOCK_GNUTLS_TCP(sock), EV_READ); 
            break;
        
        case 1:
            // write buffer full
            sock_tcp_add_event(SOCK_GNUTLS_TCP(sock), EV_WRITE);
            break;
        
        default:
            // random error
            RETURN_SET_ERROR_EXTRA(SOCK_GNUTLS_ERR(sock), ERR_GNUTLS_RECORD_GET_DIRECTION, ret);
    }
    
    // ok... wait
    return SUCCESS;
}

/*
 * Our sock_stream_Type
 */
struct sock_stream_type sock_gnutls_type = {
    .methods.read           = &sock_gnutls_read,
    .methods.write          = &sock_gnutls_write,
    .methods.event_init     = &sock_gnutls_event_init,
    .methods.event_enable   = &sock_gnutls_event_enable,
};

/*
 * XXX: global shared sock_gnutls_ctx
 */
struct sock_gnutls_client_ctx _sock_gnutls_client_ctx;

/*
 * Configure the given gnutls socket context to use simple anonymous client credentials
 */
static err_t sock_gnutls_client_ctx_anon (struct sock_gnutls_client_ctx *ctx, struct error_info *err)
{
    // init to use anonymous x509 cert
    if ((ERROR_EXTRA(err) = gnutls_certificate_allocate_credentials(&ctx->xcred)) < 0)
        return SET_ERROR(err, ERR_GNUTLS_CERT_ALLOC_CRED);

    // done
    return SUCCESS;
}

// XXX: log func
void _log (int level, const char *msg)
{
    printf("gnutls: %s\n", msg);
}

err_t sock_gnutls_global_init (struct error_info *err)
{
    // global init
    if ((ERROR_EXTRA(err) = gnutls_global_init()) < 0)
        return SET_ERROR(err, ERR_GNUTLS_GLOBAL_INIT);

    // init _sock_gnutls_ctx
    if (sock_gnutls_client_ctx_anon(&_sock_gnutls_client_ctx, err))
        return ERROR_CODE(err);

    // XXX: debug
//    gnutls_global_set_log_function(&_log);
//    gnutls_global_set_log_level(11);

    // done
    return SUCCESS;
}


err_t sock_ssl_connect (struct sock_stream **sock_ptr, const char *host, const char *service, struct error_info *err)
{
    struct sock_gnutls *sock = NULL;
    struct sock_gnutls_client_ctx *ctx = &_sock_gnutls_client_ctx;

    // alloc
    if ((sock = calloc(1, sizeof(*sock))) == NULL)
        return SET_ERROR(err, ERR_CALLOC);

    // initialize base
    sock_stream_init(SOCK_GNUTLS_BASE(sock), &sock_gnutls_type);

    // initialize client session
    if ((ERROR_EXTRA(err) = gnutls_init(&sock->session, GNUTLS_CLIENT)) < 0)
        JUMP_SET_ERROR(err, ERR_GNUTLS_INIT);

    // ...default priority stuff
    if ((ERROR_EXTRA(err) = gnutls_set_default_priority(sock->session)))
        JUMP_SET_ERROR(err, ERR_GNUTLS_SET_DEFAULT_PRIORITY);

    // bind anon credentials
    if ((ERROR_EXTRA(err) = gnutls_credentials_set(sock->session, GNUTLS_CRD_CERTIFICATE, ctx->xcred)))
        JUMP_SET_ERROR(err, ERR_GNUTLS_CRED_SET);

    // TCP connect
    if (sock_tcp_init_connect(SOCK_GNUTLS_TCP(sock), host, service))
        JUMP_SET_ERROR_INFO(err, SOCK_GNUTLS_ERR(sock));

    // bind default transport functions (recv/send) to use the TCP fd
    gnutls_transport_set_ptr(sock->session, (gnutls_transport_ptr_t) sock->base_tcp.fd);

    // perform the handshake
    if ((ERROR_EXTRA(err) = gnutls_handshake(sock->session)) < 0)
        JUMP_SET_ERROR(err, ERR_GNUTLS_HANDSHAKE);

    // done
    *sock_ptr = SOCK_GNUTLS_BASE(sock);

    return SUCCESS;

error:
    // XXX: cleanup

    return ERROR_CODE(err);    
}