#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;
(void) fd;
(void) what;
// 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,
.write = &sock_gnutls_write,
.event_init = &sock_gnutls_event_init,
.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: %d: %s", level, 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) (long int) 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);
}