Enable tls psk
[radsecproxy.git] / udp.c
diff --git a/udp.c b/udp.c
index 6b49e49..c00f215 100644 (file)
--- a/udp.c
+++ b/udp.c
-/*
- * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- */
-
-#include <signal.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netdb.h>
-#include <string.h>
-#include <unistd.h>
-#include <limits.h>
-#ifdef SYS_SOLARIS9
-#include <fcntl.h>
+/* Copyright 2011 NORDUnet A/S. All rights reserved.
+   See LICENSE for licensing information. */
+
+#if defined HAVE_CONFIG_H
+#include <config.h>
 #endif
-#include <sys/time.h>
+
+#include <assert.h>
 #include <sys/types.h>
-#include <sys/select.h>
-#include <ctype.h>
-#include <sys/wait.h>
-#include <arpa/inet.h>
-#include <regex.h>
-#include <pthread.h>
-#include <openssl/ssl.h>
+#include <sys/socket.h>
+#include <event2/event.h>
+#include <radius/client.h>
+#include <radsec/radsec.h>
+#include <radsec/radsec-impl.h>
 #include "debug.h"
-#include "list.h"
-#include "util.h"
-#include "radsecproxy.h"
-#include "tls.h"
-
-static int client4_sock = -1;
-static int client6_sock = -1;
-static struct queue *server_replyq = NULL;
-
-/* exactly one of client and server must be non-NULL */
-/* return who we received from in *client or *server */
-/* return from in sa if not NULL */
-unsigned char *radudpget(int s, struct client **client, struct server **server, uint16_t *port) {
-    int cnt, len;
-    unsigned char buf[4], *rad = NULL;
-    struct sockaddr_storage from;
-    struct sockaddr *fromcopy;
-    socklen_t fromlen = sizeof(from);
-    struct clsrvconf *p;
-    struct list_node *node;
-    fd_set readfds;
-    
-    for (;;) {
-       if (rad) {
-           free(rad);
-           rad = NULL;
-       }
-       FD_ZERO(&readfds);
-        FD_SET(s, &readfds);
-       if (select(s + 1, &readfds, NULL, NULL, NULL) < 1)
-           continue;
-       cnt = recvfrom(s, buf, 4, MSG_PEEK | MSG_TRUNC, (struct sockaddr *)&from, &fromlen);
-       if (cnt == -1) {
-           debug(DBG_WARN, "radudpget: recv failed");
-           continue;
-       }
-       if (cnt < 20) {
-           debug(DBG_WARN, "radudpget: length too small");
-           recv(s, buf, 4, 0);
-           continue;
-       }
-       
-       p = client
-           ? find_clconf(RAD_UDP, (struct sockaddr *)&from, NULL)
-           : find_srvconf(RAD_UDP, (struct sockaddr *)&from, NULL);
-       if (!p) {
-           debug(DBG_WARN, "radudpget: got packet from wrong or unknown UDP peer %s, ignoring", addr2string((struct sockaddr *)&from));
-           recv(s, buf, 4, 0);
-           continue;
-       }
-       
-       len = RADLEN(buf);
-       if (len < 20) {
-           debug(DBG_WARN, "radudpget: length too small");
-           recv(s, buf, 4, 0);
-           continue;
-       }
-           
-       rad = malloc(len);
-       if (!rad) {
-           debug(DBG_ERR, "radudpget: malloc failed");
-           recv(s, buf, 4, 0);
-           continue;
-       }
-       
-       cnt = recv(s, rad, len, MSG_TRUNC);
-       debug(DBG_DBG, "radudpget: got %d bytes from %s", cnt, addr2string((struct sockaddr *)&from));
-
-       if (cnt < len) {
-           debug(DBG_WARN, "radudpget: packet smaller than length field in radius header");
-           continue;
-       }
-       if (cnt > len)
-           debug(DBG_DBG, "radudpget: packet was padded with %d bytes", cnt - len);
-
-       if (client) {
-           pthread_mutex_lock(p->lock);
-           for (node = list_first(p->clients); node; node = list_next(node))
-               if (addr_equal((struct sockaddr *)&from, ((struct client *)node->data)->addr))
-                   break;
-           if (node) {
-               *client = (struct client *)node->data;
-               pthread_mutex_unlock(p->lock);
-               break;
-           }
-           fromcopy = addr_copy((struct sockaddr *)&from);
-           if (!fromcopy) {
-               pthread_mutex_unlock(p->lock);
-               continue;
-           }
-           *client = addclient(p, 0);
-           if (!*client) {
-               free(fromcopy);
-               pthread_mutex_unlock(p->lock);
-               continue;
-           }
-           (*client)->addr = fromcopy;
-           pthread_mutex_unlock(p->lock);
-       } else if (server)
-           *server = p->servers;
-       break;
+#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->length, 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));
     }
-    if (port)
-       *port = port_get((struct sockaddr *)&from);
-    return rad;
-}
 
-int clientradputudp(struct server *server, unsigned char *rad) {
-    size_t len;
-    struct sockaddr_storage sa;
-    struct sockaddr *sap;
-    struct clsrvconf *conf = server->conf;
-    uint16_t port;
-    
-    len = RADLEN(rad);
-    port = port_get(conf->addrinfo->ai_addr);
-    
-    if (*rad == RAD_Accounting_Request) {
-       sap = (struct sockaddr *)&sa;
-       memcpy(sap, conf->addrinfo->ai_addr, conf->addrinfo->ai_addrlen);
-       port_set(sap, ++port);
-    } else
-       sap = conf->addrinfo->ai_addr;
-
-    if (sendto(server->sock, rad, len, 0, sap, conf->addrinfo->ai_addrlen) >= 0) {
-       debug(DBG_DBG, "clienradputudp: sent UDP of length %d to %s port %d", len, conf->host, port);
-       return 1;
+  assert (r == pkt->rpkt->length);
+  /* 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__));
     }
 
-    debug(DBG_WARN, "clientradputudp: send failed");
-    return 0;
+  return RSE_OK;
 }
 
-void *udpclientrd(void *arg) {
-    struct server *server;
-    unsigned char *buf;
-    int *s = (int *)arg;
-    
-    for (;;) {
-       server = NULL;
-       buf = radudpget(*s, NULL, &server, NULL);
-       replyh(server, buf);
-    }
-}
+/* 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)
+{
+  int err;
+  struct rs_packet *pkt = (struct rs_packet *) user_data;
+
+  rs_debug (("%s: fd=%d what =", __func__, fd));
+  if (what & EV_TIMEOUT) rs_debug ((" TIMEOUT -- shouldn't happen!"));
+  if (what & EV_READ) rs_debug ((" READ"));
+  if (what & EV_WRITE) rs_debug ((" WRITE"));
+  rs_debug (("\n"));
+
+  assert (pkt);
+  assert (pkt->conn);
+  if (what & EV_READ)
+    {
+      /* Read a single UDP packet and stick it in USER_DATA.  */
+      /* TODO: Verify that unsolicited packets are dropped.  */
+      ssize_t r = 0;
+
+      assert (pkt->rpkt->data);
+
+      r = compat_recv (fd, pkt->rpkt->data, RS_MAX_PACKET_LEN, 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?\n"));
+              goto err_out;
+           }
 
-void *udpserverrd(void *arg) {
-    struct request *rq;
-    int *sp = (int *)arg;
-    
-    for (;;) {
-       rq = newrequest();
-       if (!rq) {
-           sleep(5); /* malloc failed */
-           continue;
+         /* 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);
+          goto err_out;
        }
-       rq->buf = radudpget(*sp, &rq->from, NULL, &rq->udpport);
-       rq->udpsock = *sp;
-       radsrv(rq);
-    }
-    free(sp);
-}
-
-void *udpserverwr(void *arg) {
-    struct queue *replyq = (struct queue *)arg;
-    struct request *reply;
-    struct sockaddr_storage to;
-    
-    for (;;) {
-       pthread_mutex_lock(&replyq->mutex);
-       while (!(reply = (struct request *)list_shift(replyq->entries))) {
-           debug(DBG_DBG, "udp server writer, waiting for signal");
-           pthread_cond_wait(&replyq->cond, &replyq->mutex);
-           debug(DBG_DBG, "udp server writer, got signal");
+      event_del (pkt->conn->tev);
+      if (r < 20 || r > RS_MAX_PACKET_LEN)     /* Short or long packet.  */
+       {
+         rs_err_conn_push (pkt->conn, RSE_INVALID_PKT,
+                            "invalid packet length: %d", r);
+          goto err_out;
        }
-       pthread_mutex_unlock(&replyq->mutex);
-
-       memcpy(&to, reply->from->addr, SOCKADDRP_SIZE(reply->from->addr));
-       port_set((struct sockaddr *)&to, reply->udpport);
-       if (sendto(reply->udpsock, reply->replybuf, RADLEN(reply->replybuf), 0, (struct sockaddr *)&to, SOCKADDR_SIZE(to)) < 0)
-           debug(DBG_WARN, "udpserverwr: send failed");
-       freerq(reply);
+      pkt->rpkt->length = (pkt->rpkt->data[2] << 8) + pkt->rpkt->data[3];
+      err = nr_packet_ok (pkt->rpkt);
+      if (err)
+       {
+         rs_err_conn_push_fl (pkt->conn, -err, __FILE__, __LINE__,
+                              "invalid packet");
+          goto err_out;
+       }
+      /* 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
+        rs_debug (("%s: no received-callback -- dropping packet\n", __func__));
     }
-}
+  else if (what & EV_WRITE)
+    {
+      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);
+    }
+  return;
 
-void addclientudp(struct client *client) {
-    client->replyq = server_replyq;
+ err_out:
+  rs_conn_disconnect (pkt->conn);
 }
 
-void addserverextraudp(struct clsrvconf *conf) {
-    switch (conf->addrinfo->ai_family) {
-    case AF_INET:
-       if (client4_sock < 0) {
-           client4_sock = bindtoaddr(getsrcprotores(RAD_UDP), AF_INET, 0, 1);
-           if (client4_sock < 0)
-               debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->host);
-       }
-       conf->servers->sock = client4_sock;
-       break;
-    case AF_INET6:
-       if (client6_sock < 0) {
-           client6_sock = bindtoaddr(getsrcprotores(RAD_UDP), AF_INET6, 0, 1);
-           if (client6_sock < 0)
-               debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->host);
+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);
+         conn->rev = NULL;
        }
-       conf->servers->sock = client6_sock;
-       break;
-    default:
-       debugx(1, DBG_ERR, "addserver: unsupported address family");
+      /* 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;
 }
 
-void initextraudp() {
-    pthread_t cl4th, cl6th, srvth;
-    
-    if (client4_sock >= 0)
-       if (pthread_create(&cl4th, NULL, udpclientrd, (void *)&client4_sock))
-           debugx(1, DBG_ERR, "pthread_create failed");
-    if (client6_sock >= 0)
-       if (pthread_create(&cl6th, NULL, udpclientrd, (void *)&client6_sock))
-           debugx(1, DBG_ERR, "pthread_create failed");
-
-    if (find_clconf_type(RAD_UDP, NULL)) {
-       server_replyq = newqueue();
-       if (pthread_create(&srvth, NULL, udpserverwr, (void *)server_replyq))
-           debugx(1, DBG_ERR, "pthread_create failed");
-    }
+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;
 }