diff options
| -rw-r--r-- | lib/COPYING | 2 | ||||
| -rw-r--r-- | lib/Makefile.am | 9 | ||||
| -rw-r--r-- | lib/attr.c | 8 | ||||
| -rw-r--r-- | lib/compat.c | 22 | ||||
| -rw-r--r-- | lib/compat.h | 5 | ||||
| -rw-r--r-- | lib/conf.c | 20 | ||||
| -rw-r--r-- | lib/conn.c | 211 | ||||
| -rw-r--r-- | lib/conn.h | 6 | ||||
| -rw-r--r-- | lib/debug.c | 3 | ||||
| -rw-r--r-- | lib/debug.h | 3 | ||||
| -rw-r--r-- | lib/err.c | 97 | ||||
| -rw-r--r-- | lib/err.h | 9 | ||||
| -rw-r--r-- | lib/event.c | 255 | ||||
| -rw-r--r-- | lib/event.h | 12 | ||||
| -rw-r--r-- | lib/examples/Makefile.am | 2 | ||||
| -rw-r--r-- | lib/examples/client-blocking.c | 72 | ||||
| -rw-r--r-- | lib/examples/client-udp.conf | 6 | ||||
| -rw-r--r-- | lib/include/radsec/radsec-impl.h | 44 | ||||
| -rw-r--r-- | lib/include/radsec/radsec.h | 14 | ||||
| -rw-r--r-- | lib/include/radsec/request-impl.h | 1 | ||||
| -rw-r--r-- | lib/packet.c | 717 | ||||
| -rw-r--r-- | lib/packet.h | 7 | ||||
| -rw-r--r-- | lib/peer.c | 50 | ||||
| -rw-r--r-- | lib/peer.h | 5 | ||||
| -rw-r--r-- | lib/radsec.c | 90 | ||||
| -rw-r--r-- | lib/request.c | 130 | ||||
| -rw-r--r-- | lib/send.c | 138 | ||||
| -rw-r--r-- | lib/tcp.c | 254 | ||||
| -rw-r--r-- | lib/tcp.h | 7 | ||||
| -rw-r--r-- | lib/tls.c | 3 | ||||
| -rw-r--r-- | lib/tls.h | 3 | ||||
| -rw-r--r-- | lib/udp.c | 179 | ||||
| -rw-r--r-- | lib/udp.h | 5 | 
33 files changed, 1464 insertions, 925 deletions
| diff --git a/lib/COPYING b/lib/COPYING index 0a1858c..7256aa4 100644 --- a/lib/COPYING +++ b/lib/COPYING @@ -1,5 +1,3 @@ -Copyright 2010, 2011 NORDUnet A/S. All rights reserved. -  Redistribution and use in source and binary forms, with or without  modification, are permitted provided that the following conditions are  met: diff --git a/lib/Makefile.am b/lib/Makefile.am index 311d3cc..d4d9b78 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -10,14 +10,19 @@ lib_LTLIBRARIES = libradsec.la  libradsec_la_SOURCES = \  	attr.c \ +	compat.c \  	conf.c \  	conn.c \  	debug.c \  	err.c \ +	event.c \  	packet.c \  	peer.c \  	radsec.c \ -	request.c +	request.c \ +	send.c \ +	tcp.c \ +	udp.c  libradsec_la_SOURCES += \  	rsp_debug.c \ @@ -32,4 +37,4 @@ libradsec_la_SOURCES += \  endif  libradsec_la_LDFLAGS = -version-info 0:0:0 -libradsec_la_CFLAGS = $(AM_CFLAGS) #-DDEBUG -DDEBUG_LEVENT -Werror +libradsec_la_CFLAGS = $(AM_CFLAGS) #-DDEBUG -DDEBUG_LEVENT #-Werror @@ -1,4 +1,5 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined HAVE_CONFIG_H  #include <config.h> @@ -9,7 +10,10 @@  #include <radsec/radsec-impl.h>  int -rs_attr_create(struct rs_connection *conn, struct rs_attr **attr, const char *type, const char *val) +rs_attr_create(struct rs_connection *conn, +	       struct rs_attr **attr, +	       const char *type, +	       const char *val)  {    VALUE_PAIR *vp;    struct rs_attr *a; diff --git a/lib/compat.c b/lib/compat.c new file mode 100644 index 0000000..ccc6388 --- /dev/null +++ b/lib/compat.c @@ -0,0 +1,22 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +#if defined HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <sys/socket.h> +#include "compat.h" + +ssize_t +compat_send (int sockfd, const void *buf, size_t len, int flags) +{ +  return send (sockfd, buf, len, flags); +} + +ssize_t +compat_recv (int sockfd, void *buf, size_t len, int flags) +{ +  return recv (sockfd, buf, len, flags); +} diff --git a/lib/compat.h b/lib/compat.h new file mode 100644 index 0000000..125f651 --- /dev/null +++ b/lib/compat.h @@ -0,0 +1,5 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +ssize_t compat_send (int sockfd, const void *buf, size_t len, int flags); +ssize_t compat_recv (int sockfd, void *buf, size_t len, int flags); @@ -1,4 +1,5 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined HAVE_CONFIG_H  #include <config.h> @@ -8,6 +9,7 @@  #include <string.h>  #include <radsec/radsec.h>  #include <radsec/radsec-impl.h> +#include "peer.h"  #include "debug.h"  #if 0 @@ -40,15 +42,15 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file)    cfg_opt_t server_opts[] =      {        CFG_STR ("hostname", NULL, CFGF_NONE), -      CFG_STR ("service", "radius", CFGF_NONE), -      CFG_STR ("secret", NULL, CFGF_NONE), +      CFG_STR ("service", "2083", CFGF_NONE), +      CFG_STR ("secret", "radsec", CFGF_NONE),        CFG_END ()      };    cfg_opt_t config_opts[] =      {        CFG_STR ("type", "UDP", CFGF_NONE), -      CFG_INT ("timeout", 2, CFGF_NONE), -      CFG_INT ("retries", 2, CFGF_NONE), +      CFG_INT ("timeout", 2, CFGF_NONE), /* FIXME: Remove?  */ +      CFG_INT ("retries", 2, CFGF_NONE), /* FIXME: Remove?  */        CFG_STR ("cacertfile", NULL, CFGF_NONE),        /*CFG_STR ("cacertpath", NULL, CFGF_NONE),*/        CFG_STR ("certfile", NULL, CFGF_NONE), @@ -87,6 +89,8 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file)  	return rs_err_ctx_push_fl (ctx, RSE_CONFIG, __FILE__, __LINE__,  				   "missing config name");        r->name = strdup (s); +      if (!r->name) +	return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__, NULL);        typestr = cfg_getstr (cfg_config, "type");        if (!strcmp (typestr, "UDP")) @@ -111,15 +115,15 @@ rs_context_read_config(struct rs_context *ctx, const char *config_file)        /* Add peers, one per server stanza.  */        for (j = 0; j < cfg_size (cfg_config, "server"); j++)  	{ -	  struct rs_peer *p = _rs_peer_create (ctx, &r->peers); +	  struct rs_peer *p = peer_create (ctx, &r->peers);  	  if (!p)  	    return rs_err_ctx_push_fl (ctx, RSE_NOMEM, __FILE__, __LINE__,  				       NULL);  	  p->realm = r;  	  cfg_server = cfg_getnsec (cfg_config, "server", j); -	  _rs_resolv (&p->addr, r->type, cfg_getstr (cfg_server, "hostname"), -		      cfg_getstr (cfg_server, "service")); +	  rs_resolv (&p->addr, r->type, cfg_getstr (cfg_server, "hostname"), +		     cfg_getstr (cfg_server, "service"));  	  p->secret = cfg_getstr (cfg_server, "secret");  	}      } @@ -1,4 +1,5 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined HAVE_CONFIG_H  #include <config.h> @@ -6,10 +7,38 @@  #include <string.h>  #include <assert.h> -#include <debug.h>  #include <event2/event.h> +#include <event2/bufferevent.h>  #include <radsec/radsec.h>  #include <radsec/radsec-impl.h> +#include "debug.h" +#include "conn.h" +#include "event.h" +#include "packet.h" +#include "tcp.h" + +int +conn_close (struct rs_connection **connp) +{ +  int r; +  assert (connp); +  assert (*connp); +  r = rs_conn_destroy (*connp); +  if (!r) +    *connp = NULL; +  return r; +} + +int +conn_user_dispatch_p (const struct rs_connection *conn) +{ +  assert (conn); + +  return (conn->callbacks.connected_cb || +	  conn->callbacks.disconnected_cb || +	  conn->callbacks.received_cb || +	  conn->callbacks.sent_cb); +}  int  rs_conn_create (struct rs_context *ctx, struct rs_connection **conn, @@ -35,6 +64,7 @@ rs_conn_create (struct rs_context *ctx, struct rs_connection **conn,  	  c->peers = r->peers;	/* FIXME: Copy instead?  */  	  for (p = c->peers; p; p = p->next)  	    p->conn = c; +	  c->timeout.tv_sec = r->timeout;  	  c->tryagain = r->retries;  	}        else @@ -60,45 +90,6 @@ rs_conn_set_type (struct rs_connection *conn, rs_conn_type_t type)    conn->realm->type = type;  } - -struct rs_error *	   /* FIXME: Return int as all the others?  */ -_rs_resolv (struct evutil_addrinfo **addr, rs_conn_type_t type, -	    const char *hostname, const char *service) -{ -  int err; -  struct evutil_addrinfo hints, *res = NULL; - -  memset (&hints, 0, sizeof(struct evutil_addrinfo)); -  hints.ai_family = AF_INET;   /* IPv4 only.  TODO: Set AF_UNSPEC.  */ -  hints.ai_flags = AI_ADDRCONFIG; -  switch (type) -    { -    case RS_CONN_TYPE_NONE: -      return _rs_err_create (RSE_INVALID_CONN, __FILE__, __LINE__, NULL, NULL); -    case RS_CONN_TYPE_TCP: -      /* Fall through.  */ -    case RS_CONN_TYPE_TLS: -      hints.ai_socktype = SOCK_STREAM; -      hints.ai_protocol = IPPROTO_TCP; -      break; -    case RS_CONN_TYPE_UDP: -      /* Fall through.  */ -    case RS_CONN_TYPE_DTLS: -      hints.ai_socktype = SOCK_DGRAM; -      hints.ai_protocol = IPPROTO_UDP; -      break; -    default: -      return _rs_err_create (RSE_INVALID_CONN, __FILE__, __LINE__, NULL, NULL); -    } -  err = evutil_getaddrinfo (hostname, service, &hints, &res); -  if (err) -    return _rs_err_create (RSE_BADADDR, __FILE__, __LINE__, -			   "%s:%s: bad host name or service name (%s)", -			   hostname, service, evutil_gai_strerror(err)); -  *addr = res;			/* Simply use first result.  */ -  return NULL; -} -  int  rs_conn_add_listener (struct rs_connection *conn, rs_conn_type_t type,  		      const char *hostname, int port) @@ -126,25 +117,24 @@ rs_conn_destroy (struct rs_connection *conn)    assert (conn); -  if (conn->is_connected) -    { -      err = rs_conn_disconnect (conn); -      if (err) -	return err; -    } -    /* NOTE: conn->realm is owned by context.  */    /* NOTE: conn->peers is owned by context.  */ +  if (conn->is_connected) +    err = rs_conn_disconnect (conn);    if (conn->tev)      event_free (conn->tev); +  if (conn->bev) +    bufferevent_free (conn->bev);    if (conn->evb)      event_base_free (conn->evb);    /* TODO: free tls_ctx  */    /* TODO: free tls_ssl  */ -  return 0; +  rs_free (conn->ctx, conn); + +  return err;  }  int @@ -157,7 +147,6 @@ void  rs_conn_set_callbacks (struct rs_connection *conn, struct rs_conn_callbacks *cb)  {    assert (conn); -  conn->user_dispatch_flag = 1;    memcpy (&conn->callbacks, cb, sizeof (conn->callbacks));  } @@ -165,7 +154,6 @@ void  rs_conn_del_callbacks (struct rs_connection *conn)  {    assert (conn); -  conn->user_dispatch_flag = 0;    memset (&conn->callbacks, 0, sizeof (conn->callbacks));  } @@ -195,3 +183,124 @@ int rs_conn_fd (struct rs_connection *conn)    assert (conn->active_peer);    return conn->fd;  } + +static void +_rcb (struct rs_packet *packet, void *user_data) +{ +  struct rs_packet *pkt = (struct rs_packet *) user_data; +  assert (pkt); +  assert (pkt->conn); + +  pkt->flags |= rs_packet_received_flag; +  if (pkt->conn->bev) +    bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ); +  else +    event_del (pkt->conn->rev); +} + +/* Special function used in libradsec blocking dispatching mode, +   i.e. with socket set to block on read/write and with no libradsec +   callbacks registered. + +   For any other use of libradsec, a the received_cb callback should +   be registered in the callbacks member of struct rs_connection. + +   On successful reception of a RADIUS message it will be verified +   against REQ_MSG, if !NULL. + +   If PKT_OUT is !NULL it will upon return point at a pointer to a +   struct rs_packet containing the message. + +   If anything goes wrong or if the read times out (TODO: explain), +   PKT_OUT will not be changed and one or more errors are pushed on +   the connection (available through rs_err_conn_pop()).  */ +int +rs_conn_receive_packet (struct rs_connection *conn, +		        struct rs_packet *req_msg, +		        struct rs_packet **pkt_out) +{ +  int err = 0; +  struct rs_packet *pkt = NULL; + +  assert (conn); +  assert (conn->realm); +  assert (!conn_user_dispatch_p (conn)); /* Dispatching mode only.  */ + +  if (rs_packet_create (conn, &pkt)) +    return -1; + +  assert (conn->evb); +  assert (conn->fd >= 0); + +  conn->callbacks.received_cb = _rcb; +  conn->user_data = pkt; +  pkt->flags &= ~rs_packet_received_flag; + +  if (conn->bev)		/* TCP.  */ +    { +      bufferevent_setwatermark (conn->bev, EV_READ, RS_HEADER_LEN, 0); +      bufferevent_setcb (conn->bev, tcp_read_cb, NULL, tcp_event_cb, pkt); +      bufferevent_enable (conn->bev, EV_READ); +    } +  else				/* UDP.  */ +    { +      /* Put fresh packet in user_data for the callback and enable the +	 read event.  */ +      event_assign (conn->rev, conn->evb, event_get_fd (conn->rev), +		    EV_READ, event_get_callback (conn->rev), pkt); +      err = event_add (conn->rev, NULL); +      if (err < 0) +	return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, +				    "event_add: %s", +				    evutil_gai_strerror (err)); + +      /* Activae retransmission timer.  */ +      conn_activate_timeout (pkt->conn); +    } + +  rs_debug (("%s: entering event loop\n", __func__)); +  err = event_base_dispatch (conn->evb); +  conn->callbacks.received_cb = NULL; +  if (err < 0) +    return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, +				"event_base_dispatch: %s", +				evutil_gai_strerror (err)); +  rs_debug (("%s: event loop done\n", __func__)); + +  if ((pkt->flags & rs_packet_received_flag) == 0 +      || (req_msg +	  && packet_verify_response (pkt->conn, pkt, req_msg) != RSE_OK)) +    { +      assert (rs_err_conn_peek_code (pkt->conn)); +      return rs_err_conn_peek_code (conn); +    } + +  if (pkt_out) +    *pkt_out = pkt; +  return RSE_OK; +} + +void +rs_conn_set_timeout(struct rs_connection *conn, struct timeval *tv) +{ +  assert (conn); +  assert (tv); +  conn->timeout = *tv; +} + +int +conn_activate_timeout (struct rs_connection *conn) +{ +  assert (conn); +  assert (conn->tev); +  assert (conn->evb); +  if (conn->timeout.tv_sec || conn->timeout.tv_usec) +    { +      rs_debug (("%s: activating timer: %d.%d\n", __func__, +		 conn->timeout.tv_sec, conn->timeout.tv_usec)); +      if (evtimer_add (conn->tev, &conn->timeout)) +	return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, +				    "evtimer_add: %d", errno); +    } +  return RSE_OK; +} diff --git a/lib/conn.h b/lib/conn.h new file mode 100644 index 0000000..18d2da3 --- /dev/null +++ b/lib/conn.h @@ -0,0 +1,6 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +int conn_user_dispatch_p (const struct rs_connection *conn); +int conn_close (struct rs_connection **connp); +int conn_activate_timeout (struct rs_connection *conn); diff --git a/lib/debug.c b/lib/debug.c index 4544f3c..59f25c1 100644 --- a/lib/debug.c +++ b/lib/debug.c @@ -1,4 +1,5 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined HAVE_CONFIG_H  #include <config.h> diff --git a/lib/debug.h b/lib/debug.h index 4a899b2..a8d8632 100644 --- a/lib/debug.h +++ b/lib/debug.h @@ -1,4 +1,5 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #define hd(p, l) { int i;		\      for (i = 1; i <= l; i++) {		\ @@ -1,4 +1,5 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined HAVE_CONFIG_H  #include <config.h> @@ -30,6 +31,8 @@ static const char *_errtxt[] = {    "connect timeout",		/* 16 RSE_TIMEOUT_CONN */    "invalid argument",		/* 17 RSE_INVAL */    "I/O timeout",		/* 18 RSE_TIMEOUT_IO */ +  "timeout",			/* 19 RSE_TIMEOUT */ +  "peer disconnected",		/* 20 RSE_DISCO */  };  #define ERRTXT_SIZE (sizeof(_errtxt) / sizeof(*_errtxt)) @@ -37,7 +40,7 @@ static struct rs_error *  _err_vcreate (unsigned int code, const char *file, int line, const char *fmt,  	      va_list args)  { -  struct rs_error *err; +  struct rs_error *err = NULL;    err = malloc (sizeof(struct rs_error));    if (err) @@ -67,15 +70,19 @@ _err_vcreate (unsigned int code, const char *file, int line, const char *fmt,  }  struct rs_error * -_rs_err_create (unsigned int code, const char *file, int line, const char *fmt, -		...) +err_create (unsigned int code, +	    const char *file, +	    int line, +	    const char *fmt, +	    ...)  { -  struct rs_error *err; +  struct rs_error *err = NULL;    va_list args;    va_start (args, fmt);    err = _err_vcreate (code, file, line, fmt, args);    va_end (args); +    return err;  } @@ -85,36 +92,52 @@ _ctx_err_vpush_fl (struct rs_context *ctx, int code, const char *file,  {    struct rs_error *err = _err_vcreate (code, file, line, fmt, args); -  if (err) -    ctx->err = err; -  return code; +  if (!err) +    return RSE_NOMEM; + +  /* TODO: Implement a stack.  */ +  if (ctx->err) +    rs_err_free (ctx->err); +  ctx->err = err; + +  return err->code;  }  int  rs_err_ctx_push (struct rs_context *ctx, int code, const char *fmt, ...)  { +  int r = 0;    va_list args; +    va_start (args, fmt); -  _ctx_err_vpush_fl (ctx, code, NULL, 0, fmt, args); +  r = _ctx_err_vpush_fl (ctx, code, NULL, 0, fmt, args);    va_end (args); -  return code; + +  return r;  }  int  rs_err_ctx_push_fl (struct rs_context *ctx, int code, const char *file,  		    int line, const char *fmt, ...)  { +  int r = 0;    va_list args; +    va_start (args, fmt); -  _ctx_err_vpush_fl (ctx, code, file, line, fmt, args); +  r = _ctx_err_vpush_fl (ctx, code, file, line, fmt, args);    va_end (args); -  return code; + +  return r;  }  int -_rs_err_conn_push_err (struct rs_connection *conn, struct rs_error *err) +err_conn_push_err (struct rs_connection *conn, struct rs_error *err)  { + +  if (conn->err) +    rs_err_free (conn->err);    conn->err = err;		/* FIXME: use a stack */ +    return err->code;  } @@ -124,30 +147,37 @@ _conn_err_vpush_fl (struct rs_connection *conn, int code, const char *file,  {    struct rs_error *err = _err_vcreate (code, file, line, fmt, args); -  if (err) -    _rs_err_conn_push_err (conn, err); -  return code; +  if (!err) +    return RSE_NOMEM; + +  return err_conn_push_err (conn, err);  }  int  rs_err_conn_push (struct rs_connection *conn, int code, const char *fmt, ...)  { +  int r = 0; +    va_list args;    va_start (args, fmt); -  _conn_err_vpush_fl (conn, code, NULL, 0, fmt, args); +  r = _conn_err_vpush_fl (conn, code, NULL, 0, fmt, args);    va_end (args); -  return code; + +  return r;  }  int  rs_err_conn_push_fl (struct rs_connection *conn, int code, const char *file,  		     int line, const char *fmt, ...)  { +  int r = 0; +    va_list args;    va_start (args, fmt); -  _conn_err_vpush_fl (conn, code, file, line, fmt, args); +  r = _conn_err_vpush_fl (conn, code, file, line, fmt, args);    va_end (args); -  return code; + +  return r;  }  struct rs_error * @@ -159,6 +189,7 @@ rs_err_ctx_pop (struct rs_context *ctx)      return NULL;		/* FIXME: RSE_INVALID_CTX.  */    err = ctx->err;    ctx->err = NULL; +    return err;  } @@ -171,42 +202,35 @@ rs_err_conn_pop (struct rs_connection *conn)      return NULL;		/* FIXME: RSE_INVALID_CONN */    err = conn->err;    conn->err = NULL; +    return err;  }  int  rs_err_conn_peek_code (struct rs_connection *conn)  { -  if (conn && conn->err) +  if (!conn) +    return -1;			/* FIXME: RSE_INVALID_CONN */ +  if (conn->err)      return conn->err->code; -  else -    return RSE_OK; + +  return RSE_OK;  }  void  rs_err_free (struct rs_error *err)  {    assert (err); -  if (err->msg) -    free (err->msg);    free (err);  }  char * -rs_err_msg (struct rs_error *err, int dofree_flag) +rs_err_msg (struct rs_error *err)  { -  char *msg; -    if (!err)      return NULL; -  if (err->msg) -    msg = err->msg; -  else -    msg = strdup (err->buf); -  if (dofree_flag) -    rs_err_free (err); -  return msg; +  return err->buf;  }  int @@ -219,6 +243,7 @@ rs_err_code (struct rs_error *err, int dofree_flag)    code = err->code;    if (dofree_flag) -    rs_err_free(err); +    rs_err_free (err); +    return code;  } diff --git a/lib/err.h b/lib/err.h new file mode 100644 index 0000000..5e1c9c9 --- /dev/null +++ b/lib/err.h @@ -0,0 +1,9 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +struct rs_error *err_create (unsigned int code, +			     const char *file, +			     int line, +			     const char *fmt, +			     ...); +int err_conn_push_err (struct rs_connection *conn, struct rs_error *err); diff --git a/lib/event.c b/lib/event.c new file mode 100644 index 0000000..5afba98 --- /dev/null +++ b/lib/event.c @@ -0,0 +1,255 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +#if defined HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <event2/event.h> +#include <event2/bufferevent.h> +#if defined (RS_ENABLE_TLS) +#include <event2/bufferevent_ssl.h> +#include <openssl/err.h> +#endif +#include <radsec/radsec.h> +#include <radsec/radsec-impl.h> +#include "tcp.h" +#include "udp.h" +#if defined (RS_ENABLE_TLS) +#include "tls.h" +#endif +#include "event.h" +#include "packet.h" +#include "conn.h" +#include "debug.h" + +static void +_evlog_cb (int severity, const char *msg) +{ +  const char *sevstr; +  switch (severity) +    { +    case _EVENT_LOG_DEBUG: +#if !defined (DEBUG_LEVENT) +      return; +#endif +      sevstr = "debug"; +      break; +    case _EVENT_LOG_MSG: +      sevstr = "msg"; +      break; +    case _EVENT_LOG_WARN: +      sevstr = "warn"; +      break; +    case _EVENT_LOG_ERR: +      sevstr = "err"; +      break; +    default: +      sevstr = "???"; +      break; +    } +  fprintf (stderr, "libevent: [%s] %s\n", sevstr, msg); /* FIXME: stderr?  */ +} + +void +event_conn_timeout_cb (int fd, short event, void *data) +{ +  struct rs_connection *conn = NULL; + +  assert (data); +  conn = (struct rs_connection *) data; + +  if (event & EV_TIMEOUT) +    { +      rs_debug (("%s: connection timeout on %p (fd %d) connecting to %p\n", +		 __func__, conn, conn->fd, conn->active_peer)); +      conn->is_connecting = 0; +      rs_err_conn_push_fl (conn, RSE_TIMEOUT_CONN, __FILE__, __LINE__, NULL); +      event_loopbreak (conn); +    } +} + +void +event_retransmit_timeout_cb (int fd, short event, void *data) +{ +  struct rs_connection *conn = NULL; + +  assert (data); +  conn = (struct rs_connection *) data; + +  if (event & EV_TIMEOUT) +    { +      rs_debug (("%s: retransmission timeout on %p (fd %d) sending to %p\n", +		 __func__, conn, conn->fd, conn->active_peer)); +      rs_err_conn_push_fl (conn, RSE_TIMEOUT_IO, __FILE__, __LINE__, NULL); +      event_loopbreak (conn); +    } +} + +int +event_init_socket (struct rs_connection *conn, struct rs_peer *p) +{ +  if (conn->fd != -1) +    return RSE_OK; + +  assert (p->addr); +  conn->fd = socket (p->addr->ai_family, p->addr->ai_socktype, +		     p->addr->ai_protocol); +  if (conn->fd < 0) +    return rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__, +				"socket: %d (%s)", +				errno, strerror (errno)); +  if (evutil_make_socket_nonblocking (conn->fd) < 0) +    { +      evutil_closesocket (conn->fd); +      conn->fd = -1; +      return rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__, +				  "evutil_make_socket_nonblocking: %d (%s)", +				  errno, strerror (errno)); +    } +  return RSE_OK; +} + +int +event_init_bufferevent (struct rs_connection *conn, struct rs_peer *peer) +{ +  if (conn->bev) +    return RSE_OK; + +  if (conn->realm->type == RS_CONN_TYPE_TCP) +    { +      conn->bev = bufferevent_socket_new (conn->evb, conn->fd, 0); +      if (!conn->bev) +	return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, +				    "bufferevent_socket_new"); +    } +#if defined (RS_ENABLE_TLS) +  else if (conn->realm->type == RS_CONN_TYPE_TLS) +    { +      if (rs_tls_init (conn)) +	return -1; +      /* Would be convenient to pass BEV_OPT_CLOSE_ON_FREE but things +	 seem to break when be_openssl_ctrl() (in libevent) calls +	 SSL_set_bio() after BIO_new_socket() with flag=1.  */ +      conn->bev = +	bufferevent_openssl_socket_new (conn->evb, conn->fd, conn->tls_ssl, +					BUFFEREVENT_SSL_CONNECTING, 0); +      if (!conn->bev) +	return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, +				    "bufferevent_openssl_socket_new"); +    } +#endif	/* RS_ENABLE_TLS */ +  else +    { +      return rs_err_conn_push_fl (conn, RSE_INTERNAL, __FILE__, __LINE__, +				  "%s: unknown connection type: %d", __func__, +				  conn->realm->type); +    } + +  return RSE_OK; +} + +void +event_do_connect (struct rs_connection *conn) +{ +  struct rs_peer *p; +  int err, sockerr; + +  assert (conn); +  assert (conn->active_peer); +  p = conn->active_peer; + +#if defined (DEBUG) +  { +    char host[80], serv[80]; + +    getnameinfo (p->addr->ai_addr, +		 p->addr->ai_addrlen, +		 host, sizeof(host), serv, sizeof(serv), +		 0 /* NI_NUMERICHOST|NI_NUMERICSERV*/); +    rs_debug (("%s: connecting to %s:%s\n", __func__, host, serv)); +  } +#endif + +  if (p->conn->bev)		/* TCP */ +    { +      conn_activate_timeout (conn); /* Connect timeout.  */ +      err = bufferevent_socket_connect (p->conn->bev, p->addr->ai_addr, +					p->addr->ai_addrlen); +      if (err < 0) +	rs_err_conn_push_fl (p->conn, RSE_EVENT, __FILE__, __LINE__, +			     "bufferevent_socket_connect: %s", +			     evutil_gai_strerror (err)); +      else +	p->conn->is_connecting = 1; +    } +  else				/* UDP */ +    { +      err = connect (p->conn->fd, p->addr->ai_addr, p->addr->ai_addrlen); +      if (err < 0) +	{ +	  sockerr = evutil_socket_geterror (p->conn->fd); +	  rs_debug (("%s: %d: connect: %d (%s)\n", __func__, p->conn->fd, +		     sockerr, evutil_socket_error_to_string (sockerr))); +	  rs_err_conn_push_fl (p->conn, RSE_SOCKERR, __FILE__, __LINE__, +			       "%d: connect: %d (%s)", p->conn->fd, sockerr, +			       evutil_socket_error_to_string (sockerr)); +	} +    } +} + +int +event_loopbreak (struct rs_connection *conn) +{ +  int err = event_base_loopbreak (conn->evb); +  if (err < 0) +    rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, +			 "event_base_loopbreak: %s", +			 evutil_gai_strerror (err)); +  return err; +} + + +void +event_on_disconnect (struct rs_connection *conn) +{ +  conn->is_connecting = 0; +  conn->is_connected = 0; +  rs_debug (("%s: %p disconnected\n", __func__, conn->active_peer)); +  if (conn->callbacks.disconnected_cb) +    conn->callbacks.disconnected_cb (conn->user_data); +} + +void +event_on_connect (struct rs_connection *conn, struct rs_packet *pkt) +{ +  assert (!conn->is_connecting); +  conn->is_connected = 1; +  rs_debug (("%s: %p connected\n", __func__, conn->active_peer)); + +  if (conn->callbacks.connected_cb) +    conn->callbacks.connected_cb (conn->user_data); + +  if (pkt) +    packet_do_send (pkt); +} + +int +event_init_eventbase (struct rs_connection *conn) +{ +  assert (conn); +  if (conn->evb) +    return RSE_OK; + +#if defined (DEBUG) +  event_enable_debug_mode (); +#endif +  event_set_log_callback (_evlog_cb); +  conn->evb = event_base_new (); +  if (!conn->evb) +    return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, +				"event_base_new"); + +  return RSE_OK; +} diff --git a/lib/event.h b/lib/event.h new file mode 100644 index 0000000..e042599 --- /dev/null +++ b/lib/event.h @@ -0,0 +1,12 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +void event_on_disconnect (struct rs_connection *conn); +void event_on_connect (struct rs_connection *conn, struct rs_packet *pkt); +int event_loopbreak (struct rs_connection *conn); +int event_init_eventbase (struct rs_connection *conn); +int event_init_socket (struct rs_connection *conn, struct rs_peer *p); +int event_init_bufferevent (struct rs_connection *conn, struct rs_peer *peer); +void event_do_connect (struct rs_connection *conn); +void event_conn_timeout_cb (int fd, short event, void *data); +void event_retransmit_timeout_cb (int fd, short event, void *data); diff --git a/lib/examples/Makefile.am b/lib/examples/Makefile.am index 49d3409..ee0787b 100644 --- a/lib/examples/Makefile.am +++ b/lib/examples/Makefile.am @@ -4,5 +4,5 @@ AM_CFLAGS = -Wall -g  bin_PROGRAMS = client  client_SOURCES = client-blocking.c -client_LDADD = ../libradsec.la +client_LDADD = ../libradsec.la #-lefence  client_CFLAGS = $(AM_CFLAGS) -DUSE_CONFIG_FILE diff --git a/lib/examples/client-blocking.c b/lib/examples/client-blocking.c index 27f87ca..15152b6 100644 --- a/lib/examples/client-blocking.c +++ b/lib/examples/client-blocking.c @@ -8,6 +8,7 @@  #include <freeradius/libradius.h>  #include <radsec/radsec.h>  #include <radsec/request.h> +#include "debug.h"		/* For rs_dump_packet().  */  #define SECRET "sikrit"  #define USER_NAME "molgan" @@ -16,9 +17,11 @@  struct rs_error *  blocking_client (const char *av1, const char *av2, int use_request_object_flag)  { -  struct rs_context *h; -  struct rs_connection *conn; -  struct rs_packet *req, *resp = NULL; +  struct rs_context *h = NULL; +  struct rs_connection *conn = NULL; +  struct rs_request *request = NULL; +  struct rs_packet *req = NULL, *resp = NULL; +  struct rs_error *err = NULL;    if (rs_context_create (&h, "/usr/share/freeradius/dictionary"))      return NULL; @@ -28,67 +31,60 @@ blocking_client (const char *av1, const char *av2, int use_request_object_flag)      struct rs_peer *server;      if (rs_conn_create (h, &conn, NULL)) -      return rs_err_conn_pop (conn); +      goto cleanup;      rs_conn_set_type (conn, RS_CONN_TYPE_UDP);      if (rs_peer_create (conn, &server)) -      return rs_err_conn_pop (conn); +      goto cleanup;      if (rs_peer_set_address (server, av1, av2)) -      return rs_err_conn_pop (conn); +      goto cleanup;      rs_peer_set_timeout (server, 1);      rs_peer_set_retries (server, 3);      if (rs_peer_set_secret (server, SECRET)) -      return rs_err_conn_pop (conn); +      goto cleanup;    }  #else    if (rs_context_read_config (h, av1)) -    return rs_err_ctx_pop (h); +    goto cleanup;    if (rs_conn_create (h, &conn, av2)) -    return rs_err_conn_pop (conn); +    goto cleanup;  #endif	/* USE_CONFIG_FILE */    if (use_request_object_flag)      { -      struct rs_request *request; -        if (rs_request_create_authn (conn, &request, USER_NAME, USER_PW)) -	return rs_err_conn_pop (conn); +	goto cleanup;        if (rs_request_send (request, &resp)) -	return rs_err_conn_pop (conn); -      rs_request_destroy (request); +	goto cleanup;      }    else      {        if (rs_packet_create_authn_request (conn, &req, USER_NAME, USER_PW)) -	return rs_err_conn_pop (conn); - +	goto cleanup;        if (rs_packet_send (req, NULL)) -	{ -	  rs_packet_destroy (req); -	  return rs_err_conn_pop (conn); -	} +	goto cleanup;        if (rs_conn_receive_packet (conn, req, &resp)) -	{ -	  rs_packet_destroy (req); -	  return rs_err_conn_pop (conn); -	} -      rs_packet_destroy (req); +	goto cleanup;      }    if (resp) -    { -      RADIUS_PACKET *fr_pkt = NULL; -      VALUE_PAIR *fr_vp = NULL; +    rs_dump_packet (resp); +  else +    fprintf (stderr, "%s: no response\n", __func__); -      fr_pkt = rs_packet_frpkt (resp); -      fr_vp = fr_pkt->vps;	/* FIXME: Is there an accessor?  */ -      if (fr_vp) -	vp_printlist(stdout, fr_vp); -      rs_packet_destroy (resp); -    } + cleanup: +  err = rs_err_conn_pop (conn); +  if (resp) +    rs_packet_destroy (resp); +  if (req) +    rs_packet_destroy (req); +  if (conn) +    rs_conn_destroy (conn); +  if (request) +    rs_request_destroy (request); +  if (h) +    rs_context_destroy (h); -  rs_conn_destroy (conn); -  rs_context_destroy (h); -  return NULL; +  return err;  }  int @@ -106,7 +102,7 @@ main (int argc, char *argv[])    err = blocking_client (argv[1], argv[2], use_request_object_flag);    if (err)      { -      fprintf (stderr, "%s\n", rs_err_msg (err, 0)); +      fprintf (stderr, "%s\n", rs_err_msg (err));        return rs_err_code (err, 1);      }    return 0; diff --git a/lib/examples/client-udp.conf b/lib/examples/client-udp.conf index a83fb26..8578e8b 100644 --- a/lib/examples/client-udp.conf +++ b/lib/examples/client-udp.conf @@ -1,10 +1,10 @@  config blocking-udp {      type = "UDP" +    timeout = 2 +    retries = 2      server { -        hostname = "localhost" +        hostname = "127.0.0.1"  	service = "1820"  	secret = "sikrit" -	timeout = 1         /* optional */ -	tries = 10	      /* optional */      }  } diff --git a/lib/include/radsec/radsec-impl.h b/lib/include/radsec/radsec-impl.h index 963c821..9bcd208 100644 --- a/lib/include/radsec/radsec-impl.h +++ b/lib/include/radsec/radsec-impl.h @@ -32,7 +32,6 @@ struct rs_credentials {  struct rs_error {      int code; -    char *msg;      char buf[1024];  }; @@ -69,7 +68,6 @@ struct rs_connection {      struct rs_context *ctx;      struct rs_realm *realm;	/* Owned by ctx.  */      struct event_base *evb;	/* Event base.  */ -    struct bufferevent *bev;	/* Buffer event.  */      struct event *tev;		/* Timeout event.  */      struct rs_credentials transport_credentials;      struct rs_conn_callbacks callbacks; @@ -77,26 +75,37 @@ struct rs_connection {      struct rs_peer *peers;      struct rs_peer *active_peer;      struct rs_error *err; +    struct timeval timeout;      char is_connecting;		/* FIXME: replace with a single state member */      char is_connected;		/* FIXME: replace with a single state member */      int fd;			/* Socket.  */ -    int tryagain; -    int nextid; -    int user_dispatch_flag : 1;	/* User does the dispatching.  */ +    int tryagain;		/* For server failover.  */ +    int nextid;			/* Next RADIUS packet identifier.  */ +    /* TCP transport specifics.  */ +    struct bufferevent *bev;	/* Buffer event.  */ +    /* UDP transport specifics.  */ +    struct event *wev;		/* Write event (for UDP).  */ +    struct event *rev;		/* Read event (for UDP).  */ +    struct rs_packet *out_queue; /* Queue for outgoing UDP packets.  */  #if defined(RS_ENABLE_TLS) +    /* TLS specifics.  */      SSL_CTX *tls_ctx;      SSL *tls_ssl;  #endif  }; +enum rs_packet_flags { +    rs_packet_hdr_read_flag, +    rs_packet_received_flag, +    rs_packet_sent_flag, +}; +  struct rs_packet {      struct rs_connection *conn; -    char hdr_read_flag; -    uint8_t hdr[4]; +    unsigned int flags; +    uint8_t hdr[RS_HEADER_LEN];      RADIUS_PACKET *rpkt; -    struct rs_packet *original; -    char valid_flag; -    char written_flag; +    struct rs_packet *next;	/* Used for UDP output queue.  */  };  struct rs_attr { @@ -104,18 +113,11 @@ struct rs_attr {      VALUE_PAIR *vp;  }; -/* Nonpublic functions.  */ -struct rs_error *_rs_resolv(struct evutil_addrinfo **addr, -			    rs_conn_type_t type, const char *hostname, +/* Nonpublic functions (in radsec.c -- FIXME: move?).  */ +struct rs_error *rs_resolv (struct evutil_addrinfo **addr, +			    rs_conn_type_t type, +			    const char *hostname,  			    const char *service); -struct rs_peer *_rs_peer_create(struct rs_context *ctx, -				struct rs_peer **rootp); -struct rs_error *_rs_err_create(unsigned int code, const char *file, -				int line, const char *fmt, ...); -int _rs_err_conn_push_err(struct rs_connection *conn, -			  struct rs_error *err); - -  #if defined (__cplusplus)  }  #endif diff --git a/lib/include/radsec/radsec.h b/lib/include/radsec/radsec.h index fcd391d..971fc17 100644 --- a/lib/include/radsec/radsec.h +++ b/lib/include/radsec/radsec.h @@ -3,8 +3,9 @@  /* See the file COPYING for licensing information.  */  #include <unistd.h> +#include <sys/time.h> -enum rs_err_code { +enum rs_error_code {      RSE_OK = 0,      RSE_NOMEM = 1,      RSE_NOSYS = 2, @@ -21,9 +22,11 @@ enum rs_err_code {      RSE_INTERNAL = 13,      RSE_SSLERR = 14,		/* OpenSSL error.  */      RSE_INVALID_PKT = 15, -    RSE_TIMEOUT_CONN = 16, -    RSE_INVAL = 17, -    RSE_TIMEOUT_IO = 18, +    RSE_TIMEOUT_CONN = 16,	/* Connection timeout.  */ +    RSE_INVAL = 17,		/* Invalid argument.  */ +    RSE_TIMEOUT_IO = 18,	/* I/O timeout.  */ +    RSE_TIMEOUT = 19,		/* High level timeout.  */ +    RSE_DISCO = 20,  };  enum rs_conn_type { @@ -111,6 +114,7 @@ int rs_conn_receive_packet(struct rs_connection *conn,  			   struct rs_packet *request,  			   struct rs_packet **pkt_out);  int rs_conn_fd(struct rs_connection *conn); +void rs_conn_set_timeout(struct rs_connection *conn, struct timeval *tv);  /* Peer -- client and server.  */  int rs_peer_create(struct rs_connection *conn, struct rs_peer **peer_out); @@ -164,7 +168,7 @@ int rs_err_conn_push_fl(struct rs_connection *conn,  struct rs_error *rs_err_conn_pop(struct rs_connection *conn);  int rs_err_conn_peek_code (struct rs_connection *conn);  void rs_err_free(struct rs_error *err); -char *rs_err_msg(struct rs_error *err, int dofree_flag); +char *rs_err_msg(struct rs_error *err);  int rs_err_code(struct rs_error *err, int dofree_flag);  #if defined (__cplusplus) diff --git a/lib/include/radsec/request-impl.h b/lib/include/radsec/request-impl.h index 4f50d44..19aef66 100644 --- a/lib/include/radsec/request-impl.h +++ b/lib/include/radsec/request-impl.h @@ -11,6 +11,7 @@ struct rs_request    struct rs_packet *req_msg;    struct rs_packet *resp_msg;    struct rs_conn_callbacks saved_cb; +  void *saved_user_data;  };  #if defined (__cplusplus) diff --git a/lib/packet.c b/lib/packet.c index 89b1eca..48fb55e 100644 --- a/lib/packet.c +++ b/lib/packet.c @@ -1,60 +1,83 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined HAVE_CONFIG_H  #include <config.h>  #endif -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <sys/time.h>  #include <assert.h> -#include <freeradius/libradius.h> -#include <event2/event.h>  #include <event2/bufferevent.h>  #include <radsec/radsec.h>  #include <radsec/radsec-impl.h> -#include "tls.h" +#include "conn.h"  #include "debug.h" -#if defined (RS_ENABLE_TLS) -#include <event2/bufferevent_ssl.h> -#include <openssl/err.h> -#endif +#include "packet.h" +  #if defined (DEBUG)  #include <netdb.h>  #include <sys/socket.h>  #include <event2/buffer.h>  #endif -static int -_loopbreak (struct rs_connection *conn) +int +packet_verify_response (struct rs_connection *conn, +			struct rs_packet *response, +			struct rs_packet *request)  { -  int err = event_base_loopbreak (conn->evb); -  if (err < 0) -    rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, -			 "event_base_loopbreak: %s", -			 evutil_gai_strerror (err)); -  return err; +  assert (conn); +  assert (conn->active_peer); +  assert (conn->active_peer->secret); +  assert (response); +  assert (response->rpkt); +  assert (request); +  assert (request->rpkt); + +  /* Verify header and message authenticator.  */ +  if (rad_verify (response->rpkt, request->rpkt, conn->active_peer->secret)) +    { +      conn_close (&conn); +      return rs_err_conn_push_fl (conn, RSE_FR, __FILE__, __LINE__, +				  "rad_verify: %s", fr_strerror ()); +    } + +  /* Decode and decrypt.  */ +  if (rad_decode (response->rpkt, request->rpkt, conn->active_peer->secret)) +    { +      conn_close (&conn); +      return rs_err_conn_push_fl (conn, RSE_FR, __FILE__, __LINE__, +				  "rad_decode: %s", fr_strerror ()); +    } + +  return RSE_OK;  } -static int -_do_send (struct rs_packet *pkt) + +/* Badly named function for preparing a RADIUS message and queue it. +   FIXME: Rename.  */ +int +packet_do_send (struct rs_packet *pkt)  { -  int err; -  VALUE_PAIR *vp; +  VALUE_PAIR *vp = NULL; +  assert (pkt); +  assert (pkt->conn); +  assert (pkt->conn->active_peer); +  assert (pkt->conn->active_peer->secret);    assert (pkt->rpkt); -  assert (!pkt->original); +  /* Add a Message-Authenticator, RFC 2869, if not already present.  */ +  /* FIXME: Make Message-Authenticator optional?  */    vp = paircreate (PW_MESSAGE_AUTHENTICATOR, PW_TYPE_OCTETS);    if (!vp) -    return rs_err_conn_push_fl (pkt->conn, RSE_NOMEM, __FILE__, __LINE__, +    return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__,  				"paircreate: %s", fr_strerror ()); -  pairadd (&pkt->rpkt->vps, vp); +  pairreplace (&pkt->rpkt->vps, vp); +  /* Encode message.  */    if (rad_encode (pkt->rpkt, NULL, pkt->conn->active_peer->secret))      return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__,  				"rad_encode: %s", fr_strerror ()); +  /* Sign message.  */    if (rad_sign (pkt->rpkt, NULL, pkt->conn->active_peer->secret))      return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__,  				"rad_sign: %s", fr_strerror ()); @@ -71,506 +94,28 @@ _do_send (struct rs_packet *pkt)    }  #endif -  err = bufferevent_write (pkt->conn->bev, pkt->rpkt->data, -			   pkt->rpkt->data_len); -  if (err < 0) -    return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, -				"bufferevent_write: %s", -				evutil_gai_strerror (err)); -  return RSE_OK; -} - -static void -_on_connect (struct rs_connection *conn) -{ -  conn->is_connected = 1; -  rs_debug (("%s: %p connected\n", __func__, conn->active_peer)); -  evtimer_del (conn->tev); -  if (conn->callbacks.connected_cb) -    conn->callbacks.connected_cb (conn->user_data); -} - -static void -_on_disconnect (struct rs_connection *conn) -{ -  conn->is_connecting = 0; -  conn->is_connected = 0; -  rs_debug (("%s: %p disconnected\n", __func__, conn->active_peer)); -  if (conn->callbacks.disconnected_cb) -    conn->callbacks.disconnected_cb (conn->user_data); -} - -static void -_event_cb (struct bufferevent *bev, short events, void *ctx) -{ -  struct rs_packet *pkt = (struct rs_packet *)ctx; -  struct rs_connection *conn = NULL; -  struct rs_peer *p = NULL; -  int sockerr = 0; -#if defined (RS_ENABLE_TLS) -  unsigned long tlserr = 0; -#endif - -  assert (pkt); -  assert (pkt->conn); -  assert (pkt->conn->active_peer); -  conn = pkt->conn; -  p = conn->active_peer; - -  conn->is_connecting = 0; -  if (events & BEV_EVENT_CONNECTED) -    { -      _on_connect (conn); -      if (_do_send (pkt)) -	rs_debug (("%s: error sending\n", __func__)); -    } -  else if (events & BEV_EVENT_EOF) -    { -      _on_disconnect (conn); -    } -  else if (events & BEV_EVENT_TIMEOUT) +  /* Put message in output buffer.  */ +  if (pkt->conn->bev)		/* TCP.  */      { -      rs_debug (("%s: %p times out on %s\n", __func__, p, -		 (events & BEV_EVENT_READING) ? "read" : "write")); -      rs_err_conn_push_fl (pkt->conn, RSE_TIMEOUT_IO, __FILE__, __LINE__, NULL); -    } -  else if (events & BEV_EVENT_ERROR) -    { -      sockerr = evutil_socket_geterror (conn->active_peer->fd); -      if (sockerr == 0)	/* FIXME: True that errno == 0 means closed? */ -	{ -	  _on_disconnect (conn); -	} -      else -	{ -	  rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__, -			       "%d: socket error %d (%s)", -			       conn->fd, -			       sockerr, -			       evutil_socket_error_to_string (sockerr)); -	  rs_debug (("%s: socket error on fd %d: %s (%d)\n", __func__, -		     conn->fd, -		     evutil_socket_error_to_string (sockerr), -		     sockerr)); -	} -#if defined (RS_ENABLE_TLS) -      if (conn->tls_ssl)	/* FIXME: correct check?  */ -	{ -	  for (tlserr = bufferevent_get_openssl_error (conn->bev); -	       tlserr; -	       tlserr = bufferevent_get_openssl_error (conn->bev)) -	    { -	      rs_debug (("%s: openssl error: %s\n", __func__, -			 ERR_error_string (tlserr, NULL))); -	      rs_err_conn_push_fl (pkt->conn, RSE_SSLERR, __FILE__, __LINE__, -				   ERR_error_string (tlserr, NULL)); -	    } -	} -#endif	/* RS_ENABLE_TLS */ -      _loopbreak (conn); -    } - -#if defined (DEBUG) -  if (events & BEV_EVENT_ERROR && events != BEV_EVENT_ERROR) -    rs_debug (("%s: BEV_EVENT_ERROR and more: 0x%x\n", __func__, events)); -#endif -} - -static void -_write_cb (struct bufferevent *bev, void *ctx) -{ -  struct rs_packet *pkt = (struct rs_packet *) ctx; - -  assert (pkt); -  assert (pkt->conn); - -  if (pkt->conn->callbacks.sent_cb) -    pkt->conn->callbacks.sent_cb (pkt->conn->user_data); -} - -/* Read one RADIUS packet header.  Return !0 on error.  A return value -   of 0 means that we need more data.  */ -static int -_read_header (struct rs_packet *pkt) -{ -  size_t n = 0; - -  n = bufferevent_read (pkt->conn->bev, pkt->hdr, RS_HEADER_LEN); -  if (n == RS_HEADER_LEN) -    { -      pkt->hdr_read_flag = 1; -      pkt->rpkt->data_len = (pkt->hdr[2] << 8) + pkt->hdr[3]; -      if (pkt->rpkt->data_len < 20 || pkt->rpkt->data_len > 4096) -	{ -	  bufferevent_free (pkt->conn->bev); /* Close connection.  */ -	  return rs_err_conn_push (pkt->conn, RSE_INVALID_PKT, -				   "invalid packet length: %d", +      int err = bufferevent_write (pkt->conn->bev, pkt->rpkt->data,  				   pkt->rpkt->data_len); -	} -      pkt->rpkt->data = rs_malloc (pkt->conn->ctx, pkt->rpkt->data_len); -      if (!pkt->rpkt->data) -	{ -	  bufferevent_free (pkt->conn->bev); /* Close connection.  */ -	  return rs_err_conn_push_fl (pkt->conn, RSE_NOMEM, __FILE__, __LINE__, -				      NULL); -	} -      memcpy (pkt->rpkt->data, pkt->hdr, RS_HEADER_LEN); -      bufferevent_setwatermark (pkt->conn->bev, EV_READ, -				pkt->rpkt->data_len - RS_HEADER_LEN, 0); -      rs_debug (("%s: packet header read, total pkt len=%d\n", -		 __func__, pkt->rpkt->data_len)); -    } -  else if (n < 0) -    { -      rs_debug (("%s: buffer frozen while reading header\n", __func__)); -    } -  else	    /* Error: libevent gave us less than the low watermark. */ -    { -      bufferevent_free (pkt->conn->bev); /* Close connection.  */ -      return rs_err_conn_push_fl (pkt->conn, RSE_INTERNAL, __FILE__, __LINE__, -				  "got %d octets reading header", n); -    } - -  return 0; -} - -static int -_read_packet (struct rs_packet *pkt) -{ -  size_t n = 0; - -  rs_debug (("%s: trying to read %d octets of packet data\n", __func__, -	     pkt->rpkt->data_len - RS_HEADER_LEN)); - -  n = bufferevent_read (pkt->conn->bev, -			pkt->rpkt->data + RS_HEADER_LEN, -			pkt->rpkt->data_len - RS_HEADER_LEN); - -  rs_debug (("%s: read %ld octets of packet data\n", __func__, n)); - -  if (n == pkt->rpkt->data_len - RS_HEADER_LEN) -    { -      bufferevent_disable (pkt->conn->bev, EV_READ); -      rs_debug (("%s: complete packet read\n", __func__)); -      pkt->hdr_read_flag = 0; -      memset (pkt->hdr, 0, sizeof(*pkt->hdr)); - -      /* Checks done by rad_packet_ok: -	 - lenghts (FIXME: checks really ok for tcp?) -	 - invalid code field -	 - attribute lengths >= 2 -	 - attribute sizes adding up correctly  */ -      if (!rad_packet_ok (pkt->rpkt, 0) != 0) -	{ -	  bufferevent_free (pkt->conn->bev); /* Close connection.  */ -	  return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__, -				      "invalid packet: %s", fr_strerror ()); -	} - -      /* TODO: Verify that reception of an unsolicited response packet -	 results in connection being closed.  */ - -      /* If we have a request to match this response against, verify -	 and decode the response.  */ -      if (pkt->original) -	{ -	  /* Verify header and message authenticator.  */ -	  if (rad_verify (pkt->rpkt, pkt->original->rpkt, -			  pkt->conn->active_peer->secret)) -	    { -	      bufferevent_free (pkt->conn->bev); /* Close connection.  */ -	      return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__, -					  "rad_verify: %s", fr_strerror ()); -	    } - -	  /* Decode and decrypt.  */ -	  if (rad_decode (pkt->rpkt, pkt->original->rpkt, -			  pkt->conn->active_peer->secret)) -	    { -	      bufferevent_free (pkt->conn->bev); /* Close connection.  */ -	      return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__, -					  "rad_decode: %s", fr_strerror ()); -	    } -	} - -#if defined (DEBUG) -      /* Find out what happens if there's data left in the buffer.  */ -      { -	size_t rest = 0; -	rest = evbuffer_get_length (bufferevent_get_input (pkt->conn->bev)); -	if (rest) -	  rs_debug (("%s: returning with %d octets left in buffer\n", __func__, -		     rest)); -      } -#endif - -      /* Hand over message to user, changes ownership of pkt.  Don't -	 touch it afterwards -- it might have been freed.  */ -      if (pkt->conn->callbacks.received_cb) -	pkt->conn->callbacks.received_cb (pkt, pkt->conn->user_data); -    } -  else if (n < 0)		/* Buffer frozen.  */ -    rs_debug (("%s: buffer frozen when reading packet\n", __func__)); -  else				/* Short packet.  */ -    rs_debug (("%s: waiting for another %d octets\n", __func__, -	       pkt->rpkt->data_len - RS_HEADER_LEN - n)); - -  return 0; -} - -/* Read callback for TCP. - -   Read exactly one RADIUS message from BEV and store it in struct -   rs_packet passed in CTX (hereby called 'pkt'). - -   Verify the received packet against pkt->original, if !NULL. - -   Inform upper layer about successful reception of valid RADIUS -   message by invoking conn->callbacks.recevied_cb(), if !NULL.  */ -static void -_read_cb (struct bufferevent *bev, void *ctx) -{ -  struct rs_packet *pkt = (struct rs_packet *) ctx; - -  assert (pkt); -  assert (pkt->conn); -  assert (pkt->rpkt); - -  pkt->rpkt->sockfd = pkt->conn->fd; -  pkt->rpkt->vps = NULL; - -  if (!pkt->hdr_read_flag) -    if (_read_header (pkt)) -      return; -  _read_packet (pkt); -} - -static void -_evlog_cb (int severity, const char *msg) -{ -  const char *sevstr; -  switch (severity) -    { -    case _EVENT_LOG_DEBUG: -#if !defined (DEBUG_LEVENT) -      return; -#endif -      sevstr = "debug"; -      break; -    case _EVENT_LOG_MSG: -      sevstr = "msg"; -      break; -    case _EVENT_LOG_WARN: -      sevstr = "warn"; -      break; -    case _EVENT_LOG_ERR: -      sevstr = "err"; -      break; -    default: -      sevstr = "???"; -      break; -    } -  rs_debug (("libevent: [%s] %s\n", sevstr, msg)); -} - -static int -_init_evb (struct rs_connection *conn) -{ -  if (conn->evb) -    return RSE_OK; - -#if defined (DEBUG) -  event_enable_debug_mode (); -#endif -  event_set_log_callback (_evlog_cb); -  conn->evb = event_base_new (); -  if (!conn->evb) -    return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, -				"event_base_new"); - -  return RSE_OK; -} - -static int -_init_socket (struct rs_connection *conn, struct rs_peer *p) -{ -  if (conn->fd != -1) -    return RSE_OK; - -  assert (p->addr); -  conn->fd = socket (p->addr->ai_family, p->addr->ai_socktype, -		     p->addr->ai_protocol); -  if (conn->fd < 0) -    return rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__, -				strerror (errno)); -  if (evutil_make_socket_nonblocking (conn->fd) < 0) -    { -      evutil_closesocket (conn->fd); -      conn->fd = -1; -      return rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__, -				  strerror (errno)); -    } -  return RSE_OK; -} - -static struct rs_peer * -_pick_peer (struct rs_connection *conn) -{ -  assert (conn); - -  if (conn->active_peer) -    conn->active_peer = conn->active_peer->next; /* Next.  */ -  if (!conn->active_peer) -    conn->active_peer = conn->peers; /* From the top.  */ - -  return conn->active_peer; -} - -static void -_conn_timeout_cb (int fd, short event, void *data) -{ -  struct rs_connection *conn; - -  assert (data); -  conn = (struct rs_connection *) data; - -  if (event & EV_TIMEOUT) -    { -      rs_debug (("%s: connection timeout on %p (fd %d) connecting to %p\n", -		 __func__, conn, conn->fd, conn->active_peer)); -      conn->is_connecting = 0; -      rs_err_conn_push_fl (conn, RSE_TIMEOUT_IO, __FILE__, __LINE__, NULL); -      _loopbreak (conn); +      if (err < 0) +	return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, +				    "bufferevent_write: %s", +				    evutil_gai_strerror (err));      } -} -static int -_set_timeout (struct rs_connection *conn) -{ -  struct timeval tv; - -  if (!conn->tev) -    conn->tev = evtimer_new (conn->evb, _conn_timeout_cb, conn); -  if (!conn->tev) -    return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, -				"event_new"); -  tv.tv_sec = conn->realm->timeout; -  tv.tv_usec = 0; -  evtimer_add (conn->tev, &tv); - -  return RSE_OK; -} - -static int -_init_bev (struct rs_connection *conn, struct rs_peer *peer) -{ -  if (conn->bev) -    return RSE_OK; - -  switch (conn->realm->type) +  else				/* UDP.  */      { -    case RS_CONN_TYPE_UDP: -      /* Fall through.  */ -      /* NOTE: We know this is wrong for several reasons, most notably -	 because libevent doesn't work as expected with UDP.  The -	 timeout handling is wrong too.  */ -    case RS_CONN_TYPE_TCP: -      conn->bev = bufferevent_socket_new (conn->evb, conn->fd, 0); -      if (!conn->bev) -	return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, -				    "bufferevent_socket_new"); -      break; - -#if defined (RS_ENABLE_TLS) -    case RS_CONN_TYPE_TLS: -      if (rs_tls_init (conn)) -	return -1; -      /* Would be convenient to pass BEV_OPT_CLOSE_ON_FREE but things -	 seem to break when be_openssl_ctrl() (in libevent) calls -	 SSL_set_bio() after BIO_new_socket() with flag=1.  */ -      conn->bev = -	bufferevent_openssl_socket_new (conn->evb, conn->fd, conn->tls_ssl, -					BUFFEREVENT_SSL_CONNECTING, 0); -      if (!conn->bev) -	return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, -				    "bufferevent_openssl_socket_new"); -      break; - -    case RS_CONN_TYPE_DTLS: -      return rs_err_conn_push_fl (conn, RSE_NOSYS, __FILE__, __LINE__, NULL); -#endif	/* RS_ENABLE_TLS */ +      struct rs_packet **pp = &pkt->conn->out_queue; -    default: -      return rs_err_conn_push_fl (conn, RSE_INTERNAL, __FILE__, __LINE__, -				  "%s: unknown connection type: %d", __func__, -				  conn->realm->type); +      while (*pp && (*pp)->next) +	*pp = (*pp)->next; +      *pp = pkt;      }    return RSE_OK;  } -static void -_do_connect (struct rs_connection *conn) -{ -  struct rs_peer *p; -  int err; - -  assert (conn); -  assert (conn->active_peer); -  p = conn->active_peer; - -#if defined (DEBUG) -  { -    char host[80], serv[80]; - -    getnameinfo (p->addr->ai_addr, -		 p->addr->ai_addrlen, -		 host, sizeof(host), serv, sizeof(serv), -		 0 /* NI_NUMERICHOST|NI_NUMERICSERV*/); -    rs_debug (("%s: connecting to %s:%s\n", __func__, host, serv)); -  } -#endif - -  _set_timeout (conn); -  err = bufferevent_socket_connect (p->conn->bev, p->addr->ai_addr, -				    p->addr->ai_addrlen); -  if (err < 0) -    rs_err_conn_push_fl (p->conn, RSE_EVENT, __FILE__, __LINE__, -			 "bufferevent_socket_connect: %s", -			 evutil_gai_strerror (err)); -  else -    p->conn->is_connecting = 1; -} - -static int -_conn_open(struct rs_connection *conn, struct rs_packet *pkt) -{ -  if (_init_evb (conn)) -    return -1; - -  if (!conn->active_peer) -    _pick_peer (conn); -  if (!conn->active_peer) -    return rs_err_conn_push_fl (conn, RSE_NOPEER, __FILE__, __LINE__, NULL); - -  if (_init_socket (conn, conn->active_peer)) -    return -1; - -  if (_init_bev (conn, conn->active_peer)) -    return -1; - -  if (!conn->is_connected) -    if (!conn->is_connecting) -      _do_connect (conn); - -  return RSE_OK; -} - -static int -_conn_is_open_p (struct rs_connection *conn) -{ -  return conn->active_peer && conn->is_connected; -} -  /* Public functions.  */  int  rs_packet_create (struct rs_connection *conn, struct rs_packet **pkt_out) @@ -629,153 +174,22 @@ rs_packet_create_authn_request (struct rs_connection *conn,    return RSE_OK;  } -/* User callback used when we're dispatching for user.  */ -static void -_wcb (void *user_data) -{ -  struct rs_packet *pkt = (struct rs_packet *) user_data; -  assert (pkt); -  pkt->written_flag = 1; -  bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ); -} - -int -rs_packet_send (struct rs_packet *pkt, void *user_data) -{ -  struct rs_connection *conn = NULL; -  int err = 0; - -  assert (pkt); -  assert (pkt->conn); -  conn = pkt->conn; - -  if (_conn_is_open_p (conn)) -    _do_send (pkt); -  else -    if (_conn_open (conn, pkt)) -      return -1; - -  assert (conn->evb); -  assert (conn->bev); -  assert (conn->active_peer); -  assert (conn->fd >= 0); - -  conn->user_data = user_data; -  bufferevent_setcb (conn->bev, NULL, _write_cb, _event_cb, pkt); -  bufferevent_enable (conn->bev, EV_WRITE); - -  /* Do dispatch, unless the user wants to do it herself.  */ -  if (!conn->user_dispatch_flag) -    { -      conn->callbacks.sent_cb = _wcb; -      conn->user_data = pkt; -      rs_debug (("%s: entering event loop\n", __func__)); -      err = event_base_dispatch (conn->evb); -      if (err < 0) -	return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, -				    "event_base_dispatch: %s", -				    evutil_gai_strerror (err)); -      rs_debug (("%s: event loop done\n", __func__)); -      conn->callbacks.sent_cb = NULL; -      conn->user_data = NULL; - -      if (!pkt->written_flag) -	return -1; -    } - -  return RSE_OK; -} - -static void -_rcb (struct rs_packet *packet, void *user_data) -{ -  struct rs_packet *pkt = (struct rs_packet *) user_data; -  assert (pkt); -  pkt->valid_flag = 1; -  bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ); -} - -/* Special function used in libradsec blocking dispatching mode, -   i.e. with socket set to block on read/write and with no libradsec -   callbacks registered. - -   For any other use of libradsec, a the received_cb callback should -   be registered in the callbacks member of struct rs_connection. - -   On successful reception, verification and decoding of a RADIUS -   message, PKT_OUT will upon return point at a pointer to a struct -   rs_packet containing the message. - -   If anything goes wrong or if the read times out (TODO: explain), -   PKT_OUT will point at the NULL pointer and one or more errors are -   pushed on the connection (available through rs_err_conn_pop()).  */ - -int -rs_conn_receive_packet (struct rs_connection *conn, -		        struct rs_packet *request, -		        struct rs_packet **pkt_out) -{ -  int err = 0; -  struct rs_packet *pkt = NULL; - -  assert (conn); -  assert (conn->realm); -  assert (!conn->user_dispatch_flag); /* Dispatching mode only.  */ - -  if (rs_packet_create (conn, pkt_out)) -    return -1; -  pkt = *pkt_out; -  pkt->conn = conn; -  pkt->original = request; - -  assert (conn->evb); -  assert (conn->bev); -  assert (conn->active_peer); -  assert (conn->fd >= 0); - -  bufferevent_setwatermark (conn->bev, EV_READ, RS_HEADER_LEN, 0); -  bufferevent_setcb (conn->bev, _read_cb, NULL, _event_cb, pkt); -  bufferevent_enable (conn->bev, EV_READ); -  conn->callbacks.received_cb = _rcb; -  conn->user_data = pkt; - -  /* Dispatch.  */ -  rs_debug (("%s: entering event loop\n", __func__)); -  err = event_base_dispatch (conn->evb); -  conn->callbacks.received_cb = NULL; -  if (err < 0) -    return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, -				"event_base_dispatch: %s", -				evutil_gai_strerror (err)); -  rs_debug (("%s: event loop done\n", __func__)); - -  if (!pkt->valid_flag) -    return -1; - -#if defined (DEBUG) -      rs_dump_packet (pkt); -#endif - -  pkt->original = NULL;		/* FIXME: Why?  */ -  return RSE_OK; -} -  void -rs_packet_add_attr(struct rs_packet *pkt, struct rs_attr *attr) +rs_packet_add_attr (struct rs_packet *pkt, struct rs_attr *attr)  {    pairadd (&pkt->rpkt->vps, attr->vp);    attr->pkt = pkt;  }  struct radius_packet * -rs_packet_frpkt(struct rs_packet *pkt) +rs_packet_frpkt (struct rs_packet *pkt)  {    assert (pkt);    return pkt->rpkt;  }  void -rs_packet_destroy(struct rs_packet *pkt) +rs_packet_destroy (struct rs_packet *pkt)  {    if (pkt)      { @@ -784,3 +198,4 @@ rs_packet_destroy(struct rs_packet *pkt)        rs_free (pkt->conn->ctx, pkt);      }  } + diff --git a/lib/packet.h b/lib/packet.h new file mode 100644 index 0000000..edff9de --- /dev/null +++ b/lib/packet.h @@ -0,0 +1,7 @@ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +int packet_do_send (struct rs_packet *pkt); +int packet_verify_response (struct rs_connection *conn, +			    struct rs_packet *response, +			    struct rs_packet *request); @@ -1,4 +1,6 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ +  #if defined HAVE_CONFIG_H  #include <config.h>  #endif @@ -6,18 +8,54 @@  #include <assert.h>  #include <radsec/radsec.h>  #include <radsec/radsec-impl.h> +#include "err.h" +#include "peer.h" + +struct rs_peer * +peer_pick_peer (struct rs_connection *conn) +{ +  assert (conn); + +  if (conn->active_peer) +    conn->active_peer = conn->active_peer->next; /* Next.  */ +  if (!conn->active_peer) +    conn->active_peer = conn->peers; /* From the top.  */ + +  return conn->active_peer; +} + +struct rs_peer * +peer_create (struct rs_context *ctx, struct rs_peer **rootp) +{ +  struct rs_peer *p; + +  p = (struct rs_peer *) rs_malloc (ctx, sizeof(*p)); +  if (p) +    { +      memset (p, 0, sizeof(struct rs_peer)); +      if (*rootp) +	{ +	  p->next = (*rootp)->next; +	  (*rootp)->next = p; +	} +      else +	*rootp = p; +    } +  return p; +} +/* Public functions.  */  int  rs_peer_create (struct rs_connection *conn, struct rs_peer **peer_out)  {    struct rs_peer *peer; -  peer = _rs_peer_create (conn->ctx, &conn->peers); +  peer = peer_create (conn->ctx, &conn->peers);    if (peer)      {        peer->conn = conn; -      peer->realm->timeout = 2; -      peer->realm->retries = 2; +      peer->realm->timeout = 2;	/* FIXME: Why?  */ +      peer->realm->retries = 2;	/* FIXME: Why?  */      }    else      return rs_err_conn_push_fl (conn, RSE_NOMEM, __FILE__, __LINE__, NULL); @@ -35,9 +73,9 @@ rs_peer_set_address (struct rs_peer *peer, const char *hostname,    assert (peer);    assert (peer->realm); -  err = _rs_resolv (&peer->addr, peer->realm->type, hostname, service); +  err = rs_resolv (&peer->addr, peer->realm->type, hostname, service);    if (err) -    return _rs_err_conn_push_err (peer->conn, err); +    return err_conn_push_err (peer->conn, err);    return RSE_OK;  } diff --git a/lib/peer.h b/lib/peer.h new file mode 100644 index 0000000..a326325 --- /dev/null +++ b/lib/peer.h @@ -0,0 +1,5 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +struct rs_peer *peer_create (struct rs_context *ctx, struct rs_peer **rootp); +struct rs_peer *peer_pick_peer (struct rs_connection *conn); diff --git a/lib/radsec.c b/lib/radsec.c index b771dc8..a05a22b 100644 --- a/lib/radsec.c +++ b/lib/radsec.c @@ -1,4 +1,5 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined HAVE_CONFIG_H  #include <config.h> @@ -16,16 +17,18 @@  #include <event2/util.h>  #include <radsec/radsec.h>  #include <radsec/radsec-impl.h> +#include "err.h" +#include "debug.h" +#include "rsp_debug.h"  #if defined (RS_ENABLE_TLS)  #include <regex.h> -#include "debug.h"  #include "rsp_list.h"  #include "../radsecproxy.h"  #endif -#include "rsp_debug.h" +/* Public functions.  */  int -rs_context_create(struct rs_context **ctx, const char *dict) +rs_context_create (struct rs_context **ctx, const char *dict)  {    int err = RSE_OK;    struct rs_context *h; @@ -88,44 +91,48 @@ rs_context_create(struct rs_context **ctx, const char *dict)    return err;  } -struct rs_peer * -_rs_peer_create (struct rs_context *ctx, struct rs_peer **rootp) +struct rs_error *	   /* FIXME: Return int as all the others?  */ +rs_resolv (struct evutil_addrinfo **addr, +	   rs_conn_type_t type, +	   const char *hostname, +	   const char *service)  { -  struct rs_peer *p; +  int err; +  struct evutil_addrinfo hints, *res = NULL; -  p = (struct rs_peer *) rs_malloc (ctx, sizeof(*p)); -  if (p) +  memset (&hints, 0, sizeof(struct evutil_addrinfo)); +  hints.ai_family = AF_INET;   /* IPv4 only.  TODO: Set AF_UNSPEC.  */ +  hints.ai_flags = AI_ADDRCONFIG; +  switch (type)      { -      memset (p, 0, sizeof(struct rs_peer)); -      if (*rootp) -	{ -	  p->next = (*rootp)->next; -	  (*rootp)->next = p; -	} -      else -	*rootp = p; +    case RS_CONN_TYPE_NONE: +      return err_create (RSE_INVALID_CONN, __FILE__, __LINE__, NULL, NULL); +    case RS_CONN_TYPE_TCP: +      /* Fall through.  */ +    case RS_CONN_TYPE_TLS: +      hints.ai_socktype = SOCK_STREAM; +      hints.ai_protocol = IPPROTO_TCP; +      break; +    case RS_CONN_TYPE_UDP: +      /* Fall through.  */ +    case RS_CONN_TYPE_DTLS: +      hints.ai_socktype = SOCK_DGRAM; +      hints.ai_protocol = IPPROTO_UDP; +      break; +    default: +      return err_create (RSE_INVALID_CONN, __FILE__, __LINE__, NULL, NULL);      } -  return p; +  err = evutil_getaddrinfo (hostname, service, &hints, &res); +  if (err) +    return err_create (RSE_BADADDR, __FILE__, __LINE__, +		       "%s:%s: bad host name or service name (%s)", +		       hostname, service, evutil_gai_strerror(err)); +  *addr = res;			/* Simply use first result.  */ +  return NULL;  } -static void -_rs_peer_destroy (struct rs_peer *p) -{ -  assert (p); -  assert (p->conn); -  assert (p->conn->ctx); -  /* NOTE: The peer object doesn't own its connection (conn), nor its -     realm.  */ -  /* NOTE: secret is owned by config object.  */ -  if (p->addr) -    { -      evutil_freeaddrinfo (p->addr); -      p->addr = NULL; -    } -  rs_free (p->conn->ctx, p); -} - -void rs_context_destroy(struct rs_context *ctx) +void +rs_context_destroy (struct rs_context *ctx)  {    struct rs_realm *r = NULL;    struct rs_peer *p = NULL; @@ -136,9 +143,12 @@ void rs_context_destroy(struct rs_context *ctx)        for (p = r->peers; p; )  	{  	  struct rs_peer *tmp = p; +	  if (p->addr) +	    evutil_freeaddrinfo (p->addr);  	  p = p->next; -	  _rs_peer_destroy (tmp); +	  rs_free (ctx, tmp);  	} +      rs_free (ctx, r->name);        r = r->next;        rs_free (ctx, tmp);      } @@ -150,8 +160,10 @@ void rs_context_destroy(struct rs_context *ctx)    rs_free (ctx, ctx);  } -int rs_context_set_alloc_scheme(struct rs_context *ctx, -				struct rs_alloc_scheme *scheme) +int +rs_context_set_alloc_scheme (struct rs_context *ctx, +			     struct rs_alloc_scheme *scheme)  {    return rs_err_ctx_push_fl (ctx, RSE_NOSYS, __FILE__, __LINE__, NULL);  } + diff --git a/lib/request.c b/lib/request.c index 9aa29cb..f354382 100644 --- a/lib/request.c +++ b/lib/request.c @@ -1,16 +1,31 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined HAVE_CONFIG_H  #include <config.h>  #endif -#include <time.h> +#include <stdint.h> +#include <stdlib.h>  #include <assert.h> +#include <sys/time.h>  #include <event2/event.h>  #include <radsec/radsec.h>  #include <radsec/radsec-impl.h>  #include <radsec/request.h>  #include <radsec/request-impl.h> +#include <freeradius/libradius.h> +#include "debug.h" +#include "conn.h" +#include "tcp.h" +#include "udp.h" + +/* RFC 5080 2.2.1.  Retransmission Behavior.  */ +#define IRT 2 +#define MRC 5 +#define MRT 16 +#define MRD 30 +#define RAND 100		/* Rand factor, milliseconds. */  int  rs_request_create (struct rs_connection *conn, struct rs_request **req_out) @@ -59,68 +74,71 @@ rs_request_destroy (struct rs_request *request)    rs_free (request->conn->ctx, request);  } -#if 0  static void -_timer_cb(evutil_socket_t fd, short what, void *arg) - -{ -} -#endif - -static void -_rs_req_connected(void *user_data) -{ -  //struct rs_request *request = (struct rs_request *)user_data; -} - -static void -_rs_req_disconnected(void *user_data) -{ -  //struct rs_request *request = (struct rs_request *)user_data; -} - -static void -_rs_req_packet_received(struct rs_packet *msg, void *user_data) +_rand_rt (struct timeval *res, uint32_t rtprev, uint32_t factor)  { -  //struct rs_request *request = (struct rs_request *)user_data; -} - -static void -_rs_req_packet_sent(void *user_data) -{ -  //struct rs_request *request = (struct rs_request *)user_data; +  uint32_t ms = rtprev * (fr_rand () % factor); +  res->tv_sec = rtprev + ms / 1000; +  res->tv_usec = (ms % 1000) * 1000;  }  int  rs_request_send (struct rs_request *request, struct rs_packet **resp_msg)  { -  int err; -  struct rs_connection *conn; - -  assert (request); -  assert (request->conn); -  assert (request->req_msg); -  conn = request->conn; +  int r = 0; +  struct rs_connection *conn = NULL; +  int count = 0; +  struct timeval rt = {0,0}; +  struct timeval end = {0,0}; +  struct timeval now = {0,0}; +  struct timeval tmp_tv = {0,0}; +  const struct timeval mrt_tv = {MRT,0};    if (!request || !request->conn || !request->req_msg || !resp_msg)      return rs_err_conn_push_fl (conn, RSE_INVAL, __FILE__, __LINE__, NULL); - -  request->saved_cb = conn->callbacks; - -  conn->callbacks.connected_cb = _rs_req_connected; -  conn->callbacks.disconnected_cb = _rs_req_disconnected; -  conn->callbacks.received_cb = _rs_req_packet_received; -  conn->callbacks.sent_cb = _rs_req_packet_sent; - -  err = rs_packet_send(request->req_msg, request); -  if (err) -    goto cleanup; - -  err = rs_conn_receive_packet(request->conn, request->req_msg, resp_msg); -  if (err) -    goto cleanup; - -cleanup: -  conn->callbacks = request->saved_cb; -  return err; +  conn = request->conn; +  assert (!conn_user_dispatch_p (conn)); /* This function is high level.  */ + +  gettimeofday (&end, NULL); +  end.tv_sec += MRD; +  _rand_rt (&rt, IRT, RAND); +  while (1) +    { +      rs_conn_set_timeout (conn, &rt); + +      r = rs_packet_send (request->req_msg, NULL); +      if (r == RSE_OK) +	{ +	  r = rs_conn_receive_packet (request->conn, +				      request->req_msg, +				      resp_msg); +	  if (r == RSE_OK) +	    break;		/* Success.  */ + +	  if (r != RSE_TIMEOUT_CONN && r != RSE_TIMEOUT_IO) +	    break;		/* Error.  */ +	} +      else if (r != RSE_TIMEOUT_CONN && r != RSE_TIMEOUT_IO) +	break;			/* Error.  */ + +      gettimeofday (&now, NULL); +      if (++count > MRC || timercmp (&now, &end, >)) +	{ +	  r = RSE_TIMEOUT; +	  break;		/* Timeout.  */ +	} + +      /* rt = 2 * rt + rand_rt (rt, RAND); */ +      timeradd (&rt, &rt, &rt); +      _rand_rt (&tmp_tv, IRT, RAND); +      timeradd (&rt, &tmp_tv, &rt); +      if (timercmp (&rt, &mrt_tv, >)) +	_rand_rt (&rt, MRT, RAND); +    } + +  timerclear (&rt); +  rs_conn_set_timeout (conn, &rt); + +  rs_debug (("%s: returning %d\n", __func__, r)); +  return r;  } diff --git a/lib/send.c b/lib/send.c new file mode 100644 index 0000000..a8ad1d5 --- /dev/null +++ b/lib/send.c @@ -0,0 +1,138 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +#if defined HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <event2/event.h> +#include <event2/bufferevent.h> +#include <radsec/radsec.h> +#include <radsec/radsec-impl.h> +#include "debug.h" +#include "packet.h" +#include "event.h" +#include "peer.h" +#include "conn.h" +#include "tcp.h" +#include "udp.h" + +static int +_conn_open (struct rs_connection *conn, struct rs_packet *pkt) +{ +  if (event_init_eventbase (conn)) +    return -1; + +  if (!conn->active_peer) +    peer_pick_peer (conn); +  if (!conn->active_peer) +    return rs_err_conn_push_fl (conn, RSE_NOPEER, __FILE__, __LINE__, NULL); + +  if (event_init_socket (conn, conn->active_peer)) +    return -1; + +  if (conn->realm->type == RS_CONN_TYPE_TCP +      || conn->realm->type == RS_CONN_TYPE_TLS) +    { +      if (tcp_init_connect_timer (conn)) +	return -1; +      if (event_init_bufferevent (conn, conn->active_peer)) +	return -1; +    } +  else +    { +      if (udp_init (conn, pkt)) +	return -1; +      if (udp_init_retransmit_timer (conn)) +	return -1; +    } + +  if (!conn->is_connected) +    if (!conn->is_connecting) +      event_do_connect (conn); + +  return RSE_OK; +} + +static int +_conn_is_open_p (struct rs_connection *conn) +{ +  return conn->active_peer && conn->is_connected; +} + +/* User callback used when we're dispatching for user.  */ +static void +_wcb (void *user_data) +{ +  struct rs_packet *pkt = (struct rs_packet *) user_data; +  assert (pkt); +  pkt->flags |= rs_packet_sent_flag; +  if (pkt->conn->bev) +    bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ); +  else +    event_del (pkt->conn->wev); +} + +int +rs_packet_send (struct rs_packet *pkt, void *user_data) +{ +  struct rs_connection *conn = NULL; +  int err = 0; + +  assert (pkt); +  assert (pkt->conn); +  conn = pkt->conn; + +  if (_conn_is_open_p (conn)) +    packet_do_send (pkt); +  else +    if (_conn_open (conn, pkt)) +      return -1; + +  assert (conn->evb); +  assert (conn->active_peer); +  assert (conn->fd >= 0); + +  conn->user_data = user_data; + +  if (conn->bev)		/* TCP */ +    { +      bufferevent_setcb (conn->bev, NULL, tcp_write_cb, tcp_event_cb, pkt); +      bufferevent_enable (conn->bev, EV_WRITE); +    } +  else				/* UDP */ +    { +      event_assign (conn->wev, conn->evb, event_get_fd (conn->wev), +		    EV_WRITE, event_get_callback (conn->wev), pkt); +      err = event_add (conn->wev, NULL); +      if (err < 0) +	return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, +				    "event_add: %s", +				    evutil_gai_strerror (err)); +    } + +  /* Do dispatch, unless the user wants to do it herself.  */ +  if (!conn_user_dispatch_p (conn)) +    { +      conn->callbacks.sent_cb = _wcb; +      conn->user_data = pkt; +      rs_debug (("%s: entering event loop\n", __func__)); +      err = event_base_dispatch (conn->evb); +      if (err < 0) +	return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, +				    "event_base_dispatch: %s", +				    evutil_gai_strerror (err)); +      rs_debug (("%s: event loop done\n", __func__)); +      conn->callbacks.sent_cb = NULL; +      conn->user_data = NULL; + +      if ((pkt->flags & rs_packet_sent_flag) == 0) +	{ +	  assert (rs_err_conn_peek_code (conn)); +	  return rs_err_conn_peek_code (conn); +	} +    } + +  return RSE_OK; +} diff --git a/lib/tcp.c b/lib/tcp.c new file mode 100644 index 0000000..ce071cd --- /dev/null +++ b/lib/tcp.c @@ -0,0 +1,254 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +#if defined HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <event2/event.h> +#include <event2/bufferevent.h> +#if defined (RS_ENABLE_TLS) +#include <event2/bufferevent_ssl.h> +#include <openssl/err.h> +#endif +#include <radsec/radsec.h> +#include <radsec/radsec-impl.h> +#include "tcp.h" +#include "packet.h" +#include "conn.h" +#include "debug.h" +#include "event.h" + +#if defined (DEBUG) +#include <event2/buffer.h> +#endif + +/* Read one RADIUS packet header.  Return !0 on error.  A return value +   of 0 means that we need more data.  */ +static int +_read_header (struct rs_packet *pkt) +{ +  size_t n = 0; + +  n = bufferevent_read (pkt->conn->bev, pkt->hdr, RS_HEADER_LEN); +  if (n == RS_HEADER_LEN) +    { +      pkt->flags |= rs_packet_hdr_read_flag; +      pkt->rpkt->data_len = (pkt->hdr[2] << 8) + pkt->hdr[3]; +      if (pkt->rpkt->data_len < 20 || pkt->rpkt->data_len > 4096) +	{ +	  conn_close (&pkt->conn); +	  return rs_err_conn_push (pkt->conn, RSE_INVALID_PKT, +				   "invalid packet length: %d", +				   pkt->rpkt->data_len); +	} +      pkt->rpkt->data = rs_malloc (pkt->conn->ctx, pkt->rpkt->data_len); +      if (!pkt->rpkt->data) +	{ +	  conn_close (&pkt->conn); +	  return rs_err_conn_push_fl (pkt->conn, RSE_NOMEM, __FILE__, __LINE__, +				      NULL); +	} +      memcpy (pkt->rpkt->data, pkt->hdr, RS_HEADER_LEN); +      bufferevent_setwatermark (pkt->conn->bev, EV_READ, +				pkt->rpkt->data_len - RS_HEADER_LEN, 0); +      rs_debug (("%s: packet header read, total pkt len=%d\n", +		 __func__, pkt->rpkt->data_len)); +    } +  else if (n < 0) +    { +      rs_debug (("%s: buffer frozen while reading header\n", __func__)); +    } +  else	    /* Error: libevent gave us less than the low watermark. */ +    { +      conn_close (&pkt->conn); +      return rs_err_conn_push_fl (pkt->conn, RSE_INTERNAL, __FILE__, __LINE__, +				  "got %d octets reading header", n); +    } + +  return 0; +} + +static int +_read_packet (struct rs_packet *pkt) +{ +  size_t n = 0; + +  rs_debug (("%s: trying to read %d octets of packet data\n", __func__, +	     pkt->rpkt->data_len - RS_HEADER_LEN)); + +  n = bufferevent_read (pkt->conn->bev, +			pkt->rpkt->data + RS_HEADER_LEN, +			pkt->rpkt->data_len - RS_HEADER_LEN); + +  rs_debug (("%s: read %ld octets of packet data\n", __func__, n)); + +  if (n == pkt->rpkt->data_len - RS_HEADER_LEN) +    { +      bufferevent_disable (pkt->conn->bev, EV_READ); +      rs_debug (("%s: complete packet read\n", __func__)); +      pkt->flags &= ~rs_packet_hdr_read_flag; +      memset (pkt->hdr, 0, sizeof(*pkt->hdr)); + +      /* Checks done by rad_packet_ok: +	 - lenghts (FIXME: checks really ok for tcp?) +	 - invalid code field +	 - attribute lengths >= 2 +	 - attribute sizes adding up correctly  */ +      if (!rad_packet_ok (pkt->rpkt, 0)) +	{ +	  conn_close (&pkt->conn); +	  return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__, +				      "invalid packet: %s", fr_strerror ()); +	} + +#if defined (DEBUG) +      /* Find out what happens if there's data left in the buffer.  */ +      { +	size_t rest = 0; +	rest = evbuffer_get_length (bufferevent_get_input (pkt->conn->bev)); +	if (rest) +	  rs_debug (("%s: returning with %d octets left in buffer\n", __func__, +		     rest)); +      } +#endif + +      /* Hand over message to user.  This changes ownership of pkt. +	 Don't touch it afterwards -- it might have been freed.  */ +      if (pkt->conn->callbacks.received_cb) +	pkt->conn->callbacks.received_cb (pkt, pkt->conn->user_data); +    } +  else if (n < 0)		/* Buffer frozen.  */ +    rs_debug (("%s: buffer frozen when reading packet\n", __func__)); +  else				/* Short packet.  */ +    rs_debug (("%s: waiting for another %d octets\n", __func__, +	       pkt->rpkt->data_len - RS_HEADER_LEN - n)); + +  return 0; +} + +/* The read callback for TCP. + +   Read exactly one RADIUS message from BEV and store it in struct +   rs_packet passed in USER_DATA. + +   Inform upper layer about successful reception of received RADIUS +   message by invoking conn->callbacks.recevied_cb(), if !NULL.  */ +void +tcp_read_cb (struct bufferevent *bev, void *user_data) +{ +  struct rs_packet *pkt = (struct rs_packet *) user_data; + +  assert (pkt); +  assert (pkt->conn); +  assert (pkt->rpkt); + +  pkt->rpkt->sockfd = pkt->conn->fd; +  pkt->rpkt->vps = NULL; + +  if ((pkt->flags & rs_packet_hdr_read_flag) == 0) +    if (_read_header (pkt)) +      return;			/* Error.  */ +  _read_packet (pkt); +} + +void +tcp_event_cb (struct bufferevent *bev, short events, void *user_data) +{ +  struct rs_packet *pkt = (struct rs_packet *) user_data; +  struct rs_connection *conn = NULL; +  struct rs_peer *p = NULL; +  int sockerr = 0; +#if defined (RS_ENABLE_TLS) +  unsigned long tlserr = 0; +#endif + +  assert (pkt); +  assert (pkt->conn); +  assert (pkt->conn->active_peer); +  conn = pkt->conn; +  p = conn->active_peer; + +  conn->is_connecting = 0; +  if (events & BEV_EVENT_CONNECTED) +    { +      if (conn->tev) +	evtimer_del (conn->tev); /* Cancel connect timer.  */ +      event_on_connect (conn, pkt); +    } +  else if (events & BEV_EVENT_EOF) +    { +      event_on_disconnect (conn); +    } +  else if (events & BEV_EVENT_TIMEOUT) +    { +      rs_debug (("%s: %p times out on %s\n", __func__, p, +		 (events & BEV_EVENT_READING) ? "read" : "write")); +      rs_err_conn_push_fl (pkt->conn, RSE_TIMEOUT_IO, __FILE__, __LINE__, NULL); +    } +  else if (events & BEV_EVENT_ERROR) +    { +      sockerr = evutil_socket_geterror (conn->active_peer->fd); +      if (sockerr == 0)	/* FIXME: True that errno == 0 means closed? */ +	{ +	  event_on_disconnect (conn); +	  rs_err_conn_push_fl (pkt->conn, RSE_DISCO, __FILE__, __LINE__, NULL); +	} +      else +	{ +	  rs_debug (("%s: %d: %d (%s)\n", __func__, conn->fd, sockerr, +		     evutil_socket_error_to_string (sockerr))); +	  rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__, +			       "%d: %d (%s)", conn->fd, sockerr, +			       evutil_socket_error_to_string (sockerr)); +	} +#if defined (RS_ENABLE_TLS) +      if (conn->tls_ssl)	/* FIXME: correct check?  */ +	{ +	  for (tlserr = bufferevent_get_openssl_error (conn->bev); +	       tlserr; +	       tlserr = bufferevent_get_openssl_error (conn->bev)) +	    { +	      rs_debug (("%s: openssl error: %s\n", __func__, +			 ERR_error_string (tlserr, NULL))); +	      rs_err_conn_push_fl (pkt->conn, RSE_SSLERR, __FILE__, __LINE__, +				   ERR_error_string (tlserr, NULL)); +	    } +	} +#endif	/* RS_ENABLE_TLS */ +      event_loopbreak (conn); +    } + +#if defined (DEBUG) +  if (events & BEV_EVENT_ERROR && events != BEV_EVENT_ERROR) +    rs_debug (("%s: BEV_EVENT_ERROR and more: 0x%x\n", __func__, events)); +#endif +} + +void +tcp_write_cb (struct bufferevent *bev, void *ctx) +{ +  struct rs_packet *pkt = (struct rs_packet *) ctx; + +  assert (pkt); +  assert (pkt->conn); + +  if (pkt->conn->callbacks.sent_cb) +    pkt->conn->callbacks.sent_cb (pkt->conn->user_data); +} + +int +tcp_init_connect_timer (struct rs_connection *conn) +{ +  assert (conn); + +  if (conn->tev) +    event_free (conn->tev); +  conn->tev = evtimer_new (conn->evb, event_conn_timeout_cb, conn); +  if (!conn->tev) +    return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, +				"evtimer_new"); + +  return RSE_OK; +} diff --git a/lib/tcp.h b/lib/tcp.h new file mode 100644 index 0000000..fc2c4df --- /dev/null +++ b/lib/tcp.h @@ -0,0 +1,7 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +void tcp_event_cb (struct bufferevent *bev, short events, void *user_data); +void tcp_read_cb (struct bufferevent *bev, void *user_data); +void tcp_write_cb (struct bufferevent *bev, void *ctx); +int tcp_init_connect_timer (struct rs_connection *conn); @@ -1,4 +1,5 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010, 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined HAVE_CONFIG_H  #include <config.h> @@ -1,4 +1,5 @@ -/* See the file COPYING for licensing information.  */ +/* Copyright 2010 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */  #if defined (__cplusplus)  extern "C" { diff --git a/lib/udp.c b/lib/udp.c new file mode 100644 index 0000000..911616d --- /dev/null +++ b/lib/udp.c @@ -0,0 +1,179 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +#if defined HAVE_CONFIG_H +#include <config.h> +#endif + +#include <assert.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <event2/event.h> +#include <radsec/radsec.h> +#include <radsec/radsec-impl.h> +#include "debug.h" +#include "event.h" +#include "compat.h" +#include "udp.h" + +/* Send one packet, the first in queue.  */ +static int +_send (struct rs_connection *conn, int fd) +{ +  ssize_t r = 0; +  struct rs_packet *pkt = conn->out_queue; + +  assert (pkt->rpkt); +  assert (pkt->rpkt->data); + +  /* Send.  */ +  r = compat_send (fd, pkt->rpkt->data, pkt->rpkt->data_len, 0); +  if (r == -1) +    { +      int sockerr = evutil_socket_geterror (pkt->conn->fd); +      if (sockerr != EAGAIN) +	return rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__, +				    "%d: send: %d (%s)", fd, sockerr, +				    evutil_socket_error_to_string (sockerr)); +    } + +  assert (r == pkt->rpkt->data_len); +  /* Unlink the packet.  */ +  conn->out_queue = pkt->next; + +  /* If there are more packets in queue, add the write event again.  */ +  if (pkt->conn->out_queue) +    { +      r = event_add (pkt->conn->wev, NULL); +      if (r < 0) +	return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, +				    "event_add: %s", evutil_gai_strerror (r)); +      rs_debug (("%s: re-adding the write event\n", __func__)); +    } + +  return RSE_OK; +} + +/* Callback for conn->wev and conn->rev.  FIXME: Rename. + +   USER_DATA contains connection for EV_READ and a packet for +   EV_WRITE.  This is because we don't have a connect/establish entry +   point at the user level -- send implies connect so when we're +   connected we need the packet to send.  */ +static void +_evcb (evutil_socket_t fd, short what, void *user_data) +{ +  rs_debug (("%s: fd=%d what =", __func__, fd)); +  if (what & EV_TIMEOUT) rs_debug ((" TIMEOUT")); +  if (what & EV_READ) rs_debug ((" READ")); +  if (what & EV_WRITE) rs_debug ((" WRITE")); +  rs_debug (("\n")); + +  if (what & EV_READ) +    { +      /* Read a single UDP packet and stick it in USER_DATA.  */ +      /* TODO: Verify that unsolicited packets are dropped.  */ +      struct rs_packet *pkt = (struct rs_packet *) user_data; +      ssize_t r = 0; + +      assert (pkt); +      assert (pkt->conn); + +      pkt->rpkt->data = rs_malloc (pkt->conn->ctx, 4096); +      if (pkt->rpkt->data == NULL) +	{ +	  rs_err_conn_push_fl (pkt->conn, RSE_NOMEM, __FILE__, __LINE__, NULL); +	  return; +	} +      r = compat_recv (fd, pkt->rpkt->data, 4096, MSG_TRUNC); +      if (r == -1) +	{ +	  int sockerr = evutil_socket_geterror (pkt->conn->fd); +	  if (sockerr == EAGAIN) +	    { +	      /* FIXME: Really shouldn't happen since we've been told +		 that fd is readable!  */ +	      rs_debug (("%s: EAGAIN reading UDP packet -- wot?")); +	      return; +	    } + +	  /* Hard error.  */ +	  rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__, +			       "%d: recv: %d (%s)", fd, sockerr, +			       evutil_socket_error_to_string (sockerr)); +	  event_del (pkt->conn->tev); +	  return; +	} +      event_del (pkt->conn->tev); +      if (r < 20 || r > 4096)	/* Short or long packet.  */ +	{ +	  rs_err_conn_push (pkt->conn, RSE_INVALID_PKT, +			    "invalid packet length: %d", +			    pkt->rpkt->data_len); +	  return; +	} +      pkt->rpkt->data_len = (pkt->rpkt->data[2] << 8) + pkt->rpkt->data[3]; +      if (!rad_packet_ok (pkt->rpkt, 0)) +	{ +	  rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__, +			       "invalid packet: %s", fr_strerror ()); +	  return; +	} +      /* Hand over message to user.  This changes ownership of pkt. +	 Don't touch it afterwards -- it might have been freed.  */ +      if (pkt->conn->callbacks.received_cb) +	pkt->conn->callbacks.received_cb (pkt, pkt->conn->user_data); +    } +  else if (what & EV_WRITE) +    { +      struct rs_packet *pkt = (struct rs_packet *) user_data; +      assert (pkt); +      assert (pkt->conn); + +      if (!pkt->conn->is_connected) +	event_on_connect (pkt->conn, pkt); + +      if (pkt->conn->out_queue) +	if (_send (pkt->conn, fd) == RSE_OK) +	  if (pkt->conn->callbacks.sent_cb) +	    pkt->conn->callbacks.sent_cb (pkt->conn->user_data); +    } + +#if defined (DEBUG) +  if (what & EV_TIMEOUT) +    rs_debug (("%s: timeout on UDP event, shouldn't happen\n", __func__)); +#endif +} + +int +udp_init (struct rs_connection *conn, struct rs_packet *pkt) +{ +  assert (!conn->bev); + +  conn->rev = event_new (conn->evb, conn->fd, EV_READ|EV_PERSIST, _evcb, NULL); +  conn->wev = event_new (conn->evb, conn->fd, EV_WRITE, _evcb, NULL); +  if (!conn->rev || !conn->wev) +    { +      if (conn->rev) +	event_free (conn->rev); +      /* ENOMEM _or_ EINVAL but EINVAL only if we use EV_SIGNAL, at +	 least for now (libevent-2.0.5).  */ +      return rs_err_conn_push_fl (conn, RSE_NOMEM, __FILE__, __LINE__, NULL); +    } +  return RSE_OK; +} + +int +udp_init_retransmit_timer (struct rs_connection *conn) +{ +  assert (conn); + +  if (conn->tev) +    event_free (conn->tev); +  conn->tev = evtimer_new (conn->evb, event_retransmit_timeout_cb, conn); +  if (!conn->tev) +    return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, +				"evtimer_new"); + +  return RSE_OK; +} diff --git a/lib/udp.h b/lib/udp.h new file mode 100644 index 0000000..338e7c2 --- /dev/null +++ b/lib/udp.h @@ -0,0 +1,5 @@ +/* Copyright 2011 NORDUnet A/S. All rights reserved. +   See the file COPYING for licensing information.  */ + +int udp_init (struct rs_connection *conn, struct rs_packet *pkt); +int udp_init_retransmit_timer (struct rs_connection *conn); | 
