-/*
- * 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.
- */
+/* Copyright 2011 NORDUnet A/S. All rights reserved.
+ See LICENSE for licensing information. */
-#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>
+#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"
+#include "event.h"
+#include "compat.h"
+#include "udp.h"
-static int client4_sock = -1;
-static int client6_sock = -1;
-static struct queue *server_replyq = NULL;
+/* 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;
-void removeudpclientfromreplyq(struct client *c) {
- struct list_node *n;
- struct request *r;
-
- /* lock the common queue and remove replies for this client */
- pthread_mutex_lock(&c->replyq->mutex);
- for (n = list_first(c->replyq->entries); n; n = list_next(n)) {
- r = (struct request *)n->data;
- if (r->from == c)
- r->from = NULL;
- }
- pthread_mutex_unlock(&c->replyq->mutex);
-}
+ assert (pkt->rpkt);
+ assert (pkt->rpkt->data);
-/* 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;
- struct client *c = NULL;
- struct timeval now;
-
- 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));
+ /* 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 (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);
+ assert (r == pkt->rpkt->length);
+ /* Unlink the packet. */
+ conn->out_queue = pkt->next;
- if (client) {
- *client = NULL;
- pthread_mutex_lock(p->lock);
- for (node = list_first(p->clients); node;) {
- c = (struct client *)node->data;
- node = list_next(node);
- if (s != c->sock)
- continue;
- gettimeofday(&now, NULL);
- if (!*client && addr_equal((struct sockaddr *)&from, c->addr)) {
- c->expiry = now.tv_sec + 60;
- *client = c;
- }
- if (c->expiry >= now.tv_sec)
- continue;
-
- debug(DBG_DBG, "radudpget: removing expired client (%s)", addr2string(c->addr));
- removeudpclientfromreplyq(c);
- c->replyq = NULL; /* stop removeclient() from removing common udp replyq */
- removelockedclient(c);
- break;
- }
- if (!*client) {
- fromcopy = addr_copy((struct sockaddr *)&from);
- if (!fromcopy) {
- pthread_mutex_unlock(p->lock);
- continue;
- }
- c = addclient(p, 0);
- if (!c) {
- free(fromcopy);
- pthread_mutex_unlock(p->lock);
- continue;
- }
- c->sock = s;
- c->addr = fromcopy;
- gettimeofday(&now, NULL);
- c->expiry = now.tv_sec + 60;
- *client = c;
- }
- pthread_mutex_unlock(p->lock);
- } else if (server)
- *server = p->servers;
- break;
+ /* 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__));
}
- if (port)
- *port = port_get((struct sockaddr *)&from);
- return rad;
+
+ return RSE_OK;
}
-int clientradputudp(struct server *server, unsigned char *rad) {
- size_t len;
- struct clsrvconf *conf = server->conf;
-
- len = RADLEN(rad);
- if (sendto(server->sock, rad, len, 0, conf->addrinfo->ai_addr, conf->addrinfo->ai_addrlen) >= 0) {
- debug(DBG_DBG, "clienradputudp: sent UDP of length %d to %s port %d", len, conf->host, port_get(conf->addrinfo->ai_addr));
- return 1;
- }
+/* Callback for conn->wev and conn->rev. FIXME: Rename.
- debug(DBG_WARN, "clientradputudp: send failed");
- return 0;
-}
+ 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;
-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);
- }
-}
+ 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"));
-void *udpserverrd(void *arg) {
- struct request *rq;
- int *sp = (int *)arg;
-
- for (;;) {
- rq = newrequest();
- if (!rq) {
- sleep(5); /* malloc failed */
- continue;
- }
- rq->buf = radudpget(*sp, &rq->from, NULL, &rq->udpport);
- rq->udpsock = *sp;
- radsrv(rq);
- }
- free(sp);
-}
+ 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 *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");
+ /* 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;
}
- /* do this with lock, udpserverrd may set from = NULL if from expires */
- if (reply->from)
- memcpy(&to, reply->from->addr, SOCKADDRP_SIZE(reply->from->addr));
- pthread_mutex_unlock(&replyq->mutex);
- if (reply->from) {
- 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");
+ 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;
}
- debug(DBG_DBG, "udpserverwr: refcount %d", reply->refcount);
- 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");
+int
+udp_init_retransmit_timer (struct rs_connection *conn)
+{
+ assert (conn);
- 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");
- }
+ 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;
}