X-Git-Url: http://www.project-moonshot.org/gitweb/?p=libradsec.git;a=blobdiff_plain;f=tcp.c;h=07bc109783f3de14486873341e8c0e655d677cfd;hp=cddd5547b57a9b7f5a789b9eb8c9ecdb80a030c8;hb=HEAD;hpb=be2e70adfd0793b34e86e2a7463514923d1882cd diff --git a/tcp.c b/tcp.c index cddd554..07bc109 100644 --- a/tcp.c +++ b/tcp.c @@ -1,376 +1,274 @@ -/* - * Copyright (C) 2008 Stig Venaas - * - * 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. - */ - -#ifdef RADPROT_TCP -#include -#include -#include -#include -#include -#include -#include -#ifdef SYS_SOLARIS9 -#include +/* Copyright 2011-2013 NORDUnet A/S. All rights reserved. + See LICENSE for licensing information. */ + +#if defined HAVE_CONFIG_H +#include #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "debug.h" -#include "list.h" -#include "util.h" -#include "radsecproxy.h" - -static void setprotoopts(struct commonprotoopts *opts); -static char **getlistenerargs(); -void *tcplistener(void *arg); -int tcpconnect(struct server *server, struct timeval *when, int timeout, char * text); -void *tcpclientrd(void *arg); -int clientradputtcp(struct server *server, unsigned char *rad); -void tcpsetsrcres(); - -static const struct protodefs protodefs = { - "tcp", - NULL, /* secretdefault */ - SOCK_STREAM, /* socktype */ - "1812", /* portdefault */ - 0, /* retrycountdefault */ - 0, /* retrycountmax */ - REQUEST_RETRY_INTERVAL * REQUEST_RETRY_COUNT, /* retryintervaldefault */ - 60, /* retryintervalmax */ - DUPLICATE_INTERVAL, /* duplicateintervaldefault */ - setprotoopts, /* setprotoopts */ - getlistenerargs, /* getlistenerargs */ - tcplistener, /* listener */ - tcpconnect, /* connecter */ - tcpclientrd, /* clientconnreader */ - clientradputtcp, /* clientradput */ - NULL, /* addclient */ - NULL, /* addserverextra */ - tcpsetsrcres, /* setsrcres */ - NULL /* initextra */ -}; - -static struct addrinfo *srcres = NULL; -static uint8_t handle; -static struct commonprotoopts *protoopts = NULL; -const struct protodefs *tcpinit(uint8_t h) { - handle = h; - return &protodefs; -} -static void setprotoopts(struct commonprotoopts *opts) { - protoopts = opts; -} +#include +#include +#include +#if defined (RS_ENABLE_TLS) +#include +#include +#endif +#include +#include +#include +#include "tcp.h" +#include "packet.h" +#include "conn.h" +#include "debug.h" +#include "event.h" -static char **getlistenerargs() { - return protoopts ? protoopts->listenargs : NULL; -} +#if defined (DEBUG) +#include +#endif -void tcpsetsrcres() { - if (!srcres) - srcres = resolve_hostport_addrinfo(handle, protoopts ? protoopts->sourcearg : NULL); -} - -int tcpconnect(struct server *server, struct timeval *when, int timeout, char *text) { - struct timeval now; - time_t elapsed; - - debug(DBG_DBG, "tcpconnect: called from %s", text); - pthread_mutex_lock(&server->lock); - if (when && memcmp(&server->lastconnecttry, when, sizeof(struct timeval))) { - /* already reconnected, nothing to do */ - debug(DBG_DBG, "tcpconnect(%s): seems already reconnected", text); - pthread_mutex_unlock(&server->lock); - return 1; - } +/** Read one RADIUS packet header. Return !0 on error. */ +static int +_read_header (struct rs_packet *pkt) +{ + size_t n = 0; - for (;;) { - gettimeofday(&now, NULL); - elapsed = now.tv_sec - server->lastconnecttry.tv_sec; - if (timeout && server->lastconnecttry.tv_sec && elapsed > timeout) { - debug(DBG_DBG, "tcpconnect: timeout"); - if (server->sock >= 0) - close(server->sock); - pthread_mutex_unlock(&server->lock); - return 0; + n = bufferevent_read (pkt->conn->bev, pkt->hdr, RS_HEADER_LEN); + if (n == RS_HEADER_LEN) + { + pkt->flags |= RS_PACKET_HEADER_READ; + pkt->rpkt->length = (pkt->hdr[2] << 8) + pkt->hdr[3]; + if (pkt->rpkt->length < 20 || pkt->rpkt->length > RS_MAX_PACKET_LEN) + { + rs_debug (("%s: invalid packet length: %d\n", + __func__, pkt->rpkt->length)); + rs_conn_disconnect (pkt->conn); + return rs_err_conn_push (pkt->conn, RSE_INVALID_PKT, + "invalid packet length: %d", + pkt->rpkt->length); } - if (server->connectionok) { - server->connectionok = 0; - sleep(2); - } else if (elapsed < 1) - sleep(2); - else if (elapsed < 60) { - debug(DBG_INFO, "tcpconnect: sleeping %lds", elapsed); - sleep(elapsed); - } else if (elapsed < 100000) { - debug(DBG_INFO, "tcpconnect: sleeping %ds", 60); - sleep(60); - } else - server->lastconnecttry.tv_sec = now.tv_sec; /* no sleep at startup */ - debug(DBG_WARN, "tcpconnect: trying to open TCP connection to %s port %s", server->conf->host, server->conf->port); - if (server->sock >= 0) - close(server->sock); - if ((server->sock = connecttcp(server->conf->addrinfo, srcres)) >= 0) - break; - debug(DBG_ERR, "tcpconnect: connecttcp failed"); + memcpy (pkt->rpkt->data, pkt->hdr, RS_HEADER_LEN); + bufferevent_setwatermark (pkt->conn->bev, EV_READ, + pkt->rpkt->length - RS_HEADER_LEN, 0); + rs_debug (("%s: packet header read, total pkt len=%d\n", + __func__, pkt->rpkt->length)); + } + else if (n < 0) + { + rs_debug (("%s: buffer frozen while reading header\n", __func__)); } - debug(DBG_WARN, "tcpconnect: TCP connection to %s port %s up", server->conf->host, server->conf->port); - server->connectionok = 1; - gettimeofday(&server->lastconnecttry, NULL); - pthread_mutex_unlock(&server->lock); - return 1; + else /* Error: libevent gave us less than the low watermark. */ + { + rs_debug (("%s: got: %d octets reading header\n", __func__, n)); + rs_conn_disconnect (pkt->conn); + return rs_err_conn_push_fl (pkt->conn, RSE_INTERNAL, __FILE__, __LINE__, + "got %d octets reading header", n); + } + + return 0; } -/* timeout in seconds, 0 means no timeout (blocking), returns when num bytes have been read, or timeout */ -/* returns 0 on timeout, -1 on error and num if ok */ -int tcpreadtimeout(int s, unsigned char *buf, int num, int timeout) { - int ndesc, cnt, len; - fd_set readfds, writefds; - struct timeval timer; - - if (s < 0) - return -1; - /* make socket non-blocking? */ - for (len = 0; len < num; len += cnt) { - FD_ZERO(&readfds); - FD_SET(s, &readfds); - writefds = readfds; - if (timeout) { - timer.tv_sec = timeout; - timer.tv_usec = 0; - } - ndesc = select(s + 1, &readfds, &writefds, NULL, timeout ? &timer : NULL); - if (ndesc < 1) - return ndesc; +/** Read a message, check that it's valid RADIUS and hand it off to + registered user callback. - cnt = read(s, buf + len, num - len); - if (cnt <= 0) - return -1; - } - return num; -} + The packet is read from the bufferevent associated with \a pkt and + the data is stored in \a pkt->rpkt. -/* timeout in seconds, 0 means no timeout (blocking) */ -unsigned char *radtcpget(int s, int timeout) { - int cnt, len; - unsigned char buf[4], *rad; + Return 0 on success and !0 on failure. */ +static int +_read_packet (struct rs_packet *pkt) +{ + size_t n = 0; + int err; - for (;;) { - cnt = tcpreadtimeout(s, buf, 4, timeout); - if (cnt < 1) { - debug(DBG_DBG, cnt ? "radtcpget: connection lost" : "radtcpget: timeout"); - return NULL; - } + rs_debug (("%s: trying to read %d octets of packet data\n", __func__, + pkt->rpkt->length - RS_HEADER_LEN)); - len = RADLEN(buf); - rad = malloc(len); - if (!rad) { - debug(DBG_ERR, "radtcpget: malloc failed"); - continue; - } - memcpy(rad, buf, 4); - - cnt = tcpreadtimeout(s, rad + 4, len - 4, timeout); - if (cnt < 1) { - debug(DBG_DBG, cnt ? "radtcpget: connection lost" : "radtcpget: timeout"); - free(rad); - return NULL; - } - - if (len >= 20) - break; - - free(rad); - debug(DBG_WARN, "radtcpget: packet smaller than minimum radius size"); - } - - debug(DBG_DBG, "radtcpget: got %d bytes", len); - return rad; -} + n = bufferevent_read (pkt->conn->bev, + pkt->rpkt->data + RS_HEADER_LEN, + pkt->rpkt->length - RS_HEADER_LEN); -int clientradputtcp(struct server *server, unsigned char *rad) { - int cnt; - size_t len; - struct clsrvconf *conf = server->conf; - - if (!server->connectionok) - return 0; - len = RADLEN(rad); - if ((cnt = write(server->sock, rad, len)) <= 0) { - debug(DBG_ERR, "clientradputtcp: write error"); - return 0; - } - debug(DBG_DBG, "clientradputtcp: Sent %d bytes, Radius packet of length %d to TCP peer %s", cnt, len, conf->host); - return 1; -} + rs_debug (("%s: read %ld octets of packet data\n", __func__, n)); -void *tcpclientrd(void *arg) { - struct server *server = (struct server *)arg; - unsigned char *buf; - struct timeval lastconnecttry; - - for (;;) { - /* yes, lastconnecttry is really necessary */ - lastconnecttry = server->lastconnecttry; - buf = radtcpget(server->sock, 0); - if (!buf) { - tcpconnect(server, &lastconnecttry, 0, "tcpclientrd"); - continue; + if (n == pkt->rpkt->length - RS_HEADER_LEN) + { + bufferevent_disable (pkt->conn->bev, EV_READ); + rs_debug (("%s: complete packet read\n", __func__)); + pkt->flags &= ~RS_PACKET_HEADER_READ; + 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 */ + err = nr_packet_ok (pkt->rpkt); + if (err != RSE_OK) + { + rs_debug (("%s: %d: invalid packet\n", __func__, -err)); + rs_conn_disconnect (pkt->conn); + return rs_err_conn_push_fl (pkt->conn, -err, __FILE__, __LINE__, + "invalid packet"); } - replyh(server, buf); +#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); } - server->clientrdgone = 1; - return NULL; + 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->length - RS_HEADER_LEN - n)); + + return 0; } -void *tcpserverwr(void *arg) { - int cnt; - struct client *client = (struct client *)arg; - struct queue *replyq; - struct request *reply; - - debug(DBG_DBG, "tcpserverwr: starting for %s", addr2string(client->addr)); - replyq = client->replyq; - for (;;) { - pthread_mutex_lock(&replyq->mutex); - while (!list_first(replyq->entries)) { - if (client->sock >= 0) { - debug(DBG_DBG, "tcpserverwr: waiting for signal"); - pthread_cond_wait(&replyq->cond, &replyq->mutex); - debug(DBG_DBG, "tcpserverwr: got signal"); - } - if (client->sock < 0) { - /* s might have changed while waiting */ - pthread_mutex_unlock(&replyq->mutex); - debug(DBG_DBG, "tcpserverwr: exiting as requested"); - pthread_exit(NULL); - } - } - reply = (struct request *)list_shift(replyq->entries); - pthread_mutex_unlock(&replyq->mutex); - cnt = write(client->sock, reply->replybuf, RADLEN(reply->replybuf)); - if (cnt > 0) - debug(DBG_DBG, "tcpserverwr: sent %d bytes, Radius packet of length %d to %s", - cnt, RADLEN(reply->replybuf), addr2string(client->addr)); - else - debug(DBG_ERR, "tcpserverwr: write error for %s", addr2string(client->addr)); - freerq(reply); - } +/* 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; /* FIXME: can this be done when initializing pkt? */ + + /* Read a message header if not already read, return if that + fails. Read a message and have it dispatched to the user + registered callback. + + Room for improvement: Peek inside buffer (evbuffer_copyout()) to + avoid the extra copying. */ + if ((pkt->flags & RS_PACKET_HEADER_READ) == 0) + if (_read_header (pkt)) + return; /* Error. */ + _read_packet (pkt); } -void tcpserverrd(struct client *client) { - struct request *rq; - uint8_t *buf; - pthread_t tcpserverwrth; - - debug(DBG_DBG, "tcpserverrd: starting for %s", addr2string(client->addr)); - - if (pthread_create(&tcpserverwrth, NULL, tcpserverwr, (void *)client)) { - debug(DBG_ERR, "tcpserverrd: pthread_create failed"); - return; - } +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; + int sockerr = 0; +#if defined (RS_ENABLE_TLS) + unsigned long tlserr = 0; +#endif +#if defined (DEBUG) + struct rs_peer *p = NULL; +#endif + + assert (pkt); + assert (pkt->conn); + conn = pkt->conn; +#if defined (DEBUG) + assert (pkt->conn->active_peer); + p = conn->active_peer; +#endif - for (;;) { - buf = radtcpget(client->sock, 0); - if (!buf) { - debug(DBG_ERR, "tcpserverrd: connection from %s lost", addr2string(client->addr)); - break; + conn->is_connecting = 0; + if (events & BEV_EVENT_CONNECTED) + { + if (conn->tev) + evtimer_del (conn->tev); /* Cancel connect timer. */ + if (event_on_connect (conn, pkt)) + { + event_on_disconnect (conn); + event_loopbreak (conn); + } + } + 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 (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 (conn, RSE_DISCO, __FILE__, __LINE__, NULL); } - debug(DBG_DBG, "tcpserverrd: got Radius message from %s", addr2string(client->addr)); - rq = newrequest(); - if (!rq) { - free(buf); - continue; + else + { + rs_debug (("%s: %d: %d (%s)\n", __func__, conn->fd, sockerr, + evutil_socket_error_to_string (sockerr))); + rs_err_conn_push_fl (conn, RSE_SOCKERR, __FILE__, __LINE__, + "%d: %d (%s)", conn->fd, sockerr, + evutil_socket_error_to_string (sockerr)); } - rq->buf = buf; - rq->from = client; - if (!radsrv(rq)) { - debug(DBG_ERR, "tcpserverrd: message authentication/validation failed, closing connection from %s", addr2string(client->addr)); - break; +#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 (conn, RSE_SSLERR, __FILE__, __LINE__, + ERR_error_string (tlserr, NULL)); + } } +#endif /* RS_ENABLE_TLS */ + event_loopbreak (conn); } - /* stop writer by setting s to -1 and give signal in case waiting for data */ - client->sock = -1; - pthread_mutex_lock(&client->replyq->mutex); - pthread_cond_signal(&client->replyq->cond); - pthread_mutex_unlock(&client->replyq->mutex); - debug(DBG_DBG, "tcpserverrd: waiting for writer to end"); - pthread_join(tcpserverwrth, NULL); - debug(DBG_DBG, "tcpserverrd: reader for %s exiting", addr2string(client->addr)); -} -void *tcpservernew(void *arg) { - int s; - struct sockaddr_storage from; - socklen_t fromlen = sizeof(from); - struct clsrvconf *conf; - struct client *client; - - s = *(int *)arg; - if (getpeername(s, (struct sockaddr *)&from, &fromlen)) { - debug(DBG_DBG, "tcpservernew: getpeername failed, exiting"); - goto exit; - } - debug(DBG_WARN, "tcpservernew: incoming TCP connection from %s", addr2string((struct sockaddr *)&from)); - - conf = find_clconf(handle, (struct sockaddr *)&from, NULL); - if (conf) { - client = addclient(conf, 1); - if (client) { - client->sock = s; - client->addr = addr_copy((struct sockaddr *)&from); - tcpserverrd(client); - removeclient(client); - } else - debug(DBG_WARN, "tcpservernew: failed to create new client instance"); - } else - debug(DBG_WARN, "tcpservernew: ignoring request, no matching TCP client"); - - exit: - shutdown(s, SHUT_RDWR); - close(s); - pthread_exit(NULL); +#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 *tcplistener(void *arg) { - pthread_t tcpserverth; - int s, *sp = (int *)arg; - struct sockaddr_storage from; - socklen_t fromlen = sizeof(from); +void +tcp_write_cb (struct bufferevent *bev, void *ctx) +{ + struct rs_packet *pkt = (struct rs_packet *) ctx; - listen(*sp, 0); + assert (pkt); + assert (pkt->conn); - for (;;) { - s = accept(*sp, (struct sockaddr *)&from, &fromlen); - if (s < 0) { - debug(DBG_WARN, "accept failed"); - continue; - } - if (pthread_create(&tcpserverth, NULL, tcpservernew, (void *)&s)) { - debug(DBG_ERR, "tcplistener: pthread_create failed"); - shutdown(s, SHUT_RDWR); - close(s); - continue; - } - pthread_detach(tcpserverth); - } - free(sp); - return NULL; + if (pkt->conn->callbacks.sent_cb) + pkt->conn->callbacks.sent_cb (pkt->conn->user_data); } -#else -const struct protodefs *tcpinit(uint8_t h) { - return NULL; + +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; } -#endif