Index: Makefile =================================================================== RCS file: /cvs/src/usr.sbin/radiusctl/Makefile,v retrieving revision 1.2 diff -u -p -r1.2 Makefile --- Makefile 3 Aug 2015 04:10:21 -0000 1.2 +++ Makefile 21 Feb 2020 05:15:14 -0000 @@ -3,7 +3,7 @@ PROG= radiusctl SRCS= radiusctl.c parser.c chap_ms.c MAN= radiusctl.8 CFLAGS+= -Wall -Wextra -Wno-unused-parameter -LDADD+= -lradius -lcrypto -DPADD+= ${LIBRADIUS} ${LIBCRYPTO} +LDADD+= -lradius -lcrypto -levent +DPADD+= ${LIBRADIUS} ${LIBCRYPTO} ${LIBEVENT} .include Index: parser.c =================================================================== RCS file: /cvs/src/usr.sbin/radiusctl/parser.c,v retrieving revision 1.1 diff -u -p -r1.1 parser.c --- parser.c 21 Jul 2015 04:06:04 -0000 1.1 +++ parser.c 21 Feb 2020 05:15:14 -0000 @@ -18,6 +18,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include + #include #include #include @@ -35,6 +37,9 @@ enum token_type { PORT, METHOD, NAS_PORT, + TRIES, + INTERVAL, + MAXWAIT, ENDTOKEN }; @@ -45,7 +50,11 @@ struct token { const struct token *next; }; -static struct parse_result res; +static struct parse_result res = { + .tries = TEST_TRIES_DEFAULT, + .interval = { TEST_INTERVAL_DEFAULT, 0 }, + .maxwait = { TEST_MAXWAIT_DEFAULT, 0 }, +}; static const struct token t_test[]; static const struct token t_secret[]; @@ -55,6 +64,9 @@ static const struct token t_password[]; static const struct token t_port[]; static const struct token t_method[]; static const struct token t_nas_port[]; +static const struct token t_tries[]; +static const struct token t_interval[]; +static const struct token t_maxwait[]; static const struct token t_main[] = { { KEYWORD, "test", TEST, t_test }, @@ -82,6 +94,9 @@ static const struct token t_test_opts[] { KEYWORD, "port", NONE, t_port }, { KEYWORD, "method", NONE, t_method }, { KEYWORD, "nas-port", NONE, t_nas_port }, + { KEYWORD, "interval", NONE, t_interval }, + { KEYWORD, "tries", NONE, t_tries }, + { KEYWORD, "maxwait", NONE, t_maxwait }, { ENDTOKEN, "", NONE, NULL } }; @@ -105,6 +120,21 @@ static const struct token t_nas_port[] = { ENDTOKEN, "", NONE, NULL } }; +static const struct token t_tries[] = { + { TRIES, "", NONE, t_test_opts }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_interval[] = { + { INTERVAL, "", NONE, t_test_opts }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_maxwait[] = { + { MAXWAIT, "", NONE, t_test_opts }, + { ENDTOKEN, "", NONE, NULL } +}; + static const struct token *match_token(char *, const struct token []); static void show_valid_args(const struct token []); @@ -115,8 +145,6 @@ parse(int argc, char *argv[]) const struct token *table = t_main; const struct token *match; - bzero(&res, sizeof(res)); - while (argc >= 0) { if ((match = match_token(argv[0], table)) == NULL) { fprintf(stderr, "valid commands/args:\n"); @@ -138,6 +166,12 @@ parse(int argc, char *argv[]) return (NULL); } + if (res.tries * res.interval.tv_sec > res.maxwait.tv_sec) { + fprintf(stderr, "tries %u by interval %lld > maxwait %lld", + res.tries, res.interval.tv_sec, res.maxwait.tv_sec); + return (NULL); + } + return (&res); } @@ -240,6 +274,50 @@ match_token(char *word, const struct tok res.nas_port = num; t = &table[i]; break; + + case TRIES: + if (word == NULL) + break; + num = strtonum(word, + TEST_TRIES_MIN, TEST_TRIES_MAX, &errstr); + if (errstr != NULL) { + printf("invalid argument: %s is %s" + " for \"tries\"\n", word, errstr); + return (NULL); + } + match++; + res.tries = num; + t = &table[i]; + break; + case INTERVAL: + if (word == NULL) + break; + num = strtonum(word, + TEST_INTERVAL_MIN, TEST_INTERVAL_MAX, &errstr); + if (errstr != NULL) { + printf("invalid argument: %s is %s" + " for \"interval\"\n", word, errstr); + return (NULL); + } + match++; + res.interval.tv_sec = num; + t = &table[i]; + break; + case MAXWAIT: + if (word == NULL) + break; + num = strtonum(word, + TEST_MAXWAIT_MIN, TEST_MAXWAIT_MAX, &errstr); + if (errstr != NULL) { + printf("invalid argument: %s is %s" + " for \"maxwait\"\n", word, errstr); + return (NULL); + } + match++; + res.maxwait.tv_sec = num; + t = &table[i]; + break; + case ENDTOKEN: break; } @@ -292,6 +370,18 @@ show_valid_args(const struct token table break; case NAS_PORT: fprintf(stderr, " \n"); + break; + case TRIES: + fprintf(stderr, " \n", + TEST_TRIES_MIN, TEST_TRIES_MAX); + break; + case INTERVAL: + fprintf(stderr, " \n", + TEST_INTERVAL_MIN, TEST_INTERVAL_MAX); + break; + case MAXWAIT: + fprintf(stderr, " \n", + TEST_MAXWAIT_MIN, TEST_MAXWAIT_MAX); break; case ENDTOKEN: break; Index: parser.h =================================================================== RCS file: /cvs/src/usr.sbin/radiusctl/parser.h,v retrieving revision 1.1 diff -u -p -r1.1 parser.h --- parser.h 21 Jul 2015 04:06:04 -0000 1.1 +++ parser.h 21 Feb 2020 05:15:14 -0000 @@ -31,6 +31,18 @@ enum auth_method { MSCHAPV2 }; +#define TEST_TRIES_MIN 1 +#define TEST_TRIES_MAX 32 +#define TEST_TRIES_DEFAULT 3 + +#define TEST_INTERVAL_MIN 1 +#define TEST_INTERVAL_MAX 10 +#define TEST_INTERVAL_DEFAULT 2 + +#define TEST_MAXWAIT_MIN 3 +#define TEST_MAXWAIT_MAX 60 +#define TEST_MAXWAIT_DEFAULT 8 + struct parse_result { enum actions action; const char *hostname; @@ -40,6 +52,13 @@ struct parse_result { u_short port; int nas_port; enum auth_method auth_method; + + /* number of packets to try sending */ + unsigned int tries; + /* how long between packet sends */ + struct timeval interval; + /* overall process wait time for a reply */ + struct timeval maxwait; }; struct parse_result *parse(int, char *[]); Index: radiusctl.8 =================================================================== RCS file: /cvs/src/usr.sbin/radiusctl/radiusctl.8,v retrieving revision 1.3 diff -u -p -r1.3 radiusctl.8 --- radiusctl.8 25 Aug 2015 01:21:57 -0000 1.3 +++ radiusctl.8 21 Feb 2020 05:15:14 -0000 @@ -77,6 +77,19 @@ when sending a packet to .Ar hostname . If the port is omitted, the default port number 1812 is used. +.It Cm tries Ar number +Specifies the number of packets to try sending. +By default +.Nm +will send 3 packets before giving up. +.It Cm interval Ar seconds +Specifies how many seconds to wait before resending a packet. +The default interval between packet retries is 2 seconds. +.It Cm maxwait Ar seconds +Specifies the maximum amount of time to wait for a valid reply packet. +By default +.Nm +will wait 8 seconds for a valid reply. .El .El .Sh SEE ALSO Index: radiusctl.c =================================================================== RCS file: /cvs/src/usr.sbin/radiusctl/radiusctl.c,v retrieving revision 1.7 diff -u -p -r1.7 radiusctl.c --- radiusctl.c 1 Apr 2019 09:51:56 -0000 1.7 +++ radiusctl.c 21 Feb 2020 05:15:14 -0000 @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -30,11 +31,13 @@ #include +#include + #include "parser.h" #include "chap_ms.h" -static void radius_test (struct parse_result *); +static int radius_test (struct parse_result *); static void radius_dump (FILE *, RADIUS_PACKET *, bool, const char *); static const char *radius_code_str (int code); @@ -53,6 +56,7 @@ main(int argc, char *argv[]) { int ch; struct parse_result *result; + int ecode = EXIT_SUCCESS; while ((ch = getopt(argc, argv, "")) != -1) switch (ch) { @@ -72,21 +76,38 @@ main(int argc, char *argv[]) case TEST: if (pledge("stdio dns inet", NULL) == -1) err(EXIT_FAILURE, "pledge"); - radius_test(result); + ecode = radius_test(result); break; } - return (EXIT_SUCCESS); + return (ecode); } -static void +struct radius_test { + const struct parse_result *res; + int ecode; + + RADIUS_PACKET *reqpkt; + int sock; + unsigned int tries; + struct event ev_send; + struct event ev_recv; + struct event ev_timedout; +}; + +static void radius_test_send(int, short, void *); +static void radius_test_recv(int, short, void *); +static void radius_test_timedout(int, short, void *); + +static int radius_test(struct parse_result *res) { + struct radius_test test = { .res = res }; + RADIUS_PACKET *reqpkt; struct addrinfo hints, *ai; int sock, retval; struct sockaddr_storage sockaddr; socklen_t sockaddrlen; - RADIUS_PACKET *reqpkt, *respkt; struct sockaddr_in *sin4; struct sockaddr_in6 *sin6; uint32_t u32val; @@ -110,7 +131,8 @@ radius_test(struct parse_result *res) ((struct sockaddr_in *)ai->ai_addr)->sin_port = htons(res->port); - sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + sock = socket(ai->ai_family, ai->ai_socktype | SOCK_NONBLOCK, + ai->ai_protocol); if (sock == -1) err(1, "socket"); @@ -211,24 +233,31 @@ radius_test(struct parse_result *res) radius_put_message_authenticator(reqpkt, res->secret); + event_init(); + + test.ecode = EXIT_FAILURE; + test.res = res; + test.sock = sock; + test.reqpkt = reqpkt; + + event_set(&test.ev_recv, sock, EV_READ|EV_PERSIST, + radius_test_recv, &test); + + evtimer_set(&test.ev_send, radius_test_send, &test); + evtimer_set(&test.ev_timedout, radius_test_timedout, &test); + + event_add(&test.ev_recv, NULL); + evtimer_add(&test.ev_timedout, &res->maxwait); + /* Send! */ fprintf(stderr, "Sending:\n"); radius_dump(stdout, reqpkt, false, res->secret); - if (send(sock, radius_get_data(reqpkt), radius_get_length(reqpkt), 0) - == -1) - warn("send"); - if ((respkt = radius_recv(sock, 0)) == NULL) - warn("recv"); - else { - radius_set_request_packet(respkt, reqpkt); - fprintf(stderr, "\nReceived:\n"); - radius_dump(stdout, respkt, true, res->secret); - } + radius_test_send(0, EV_TIMEOUT, &test); + + event_dispatch(); /* Release the resources */ radius_delete_packet(reqpkt); - if (respkt) - radius_delete_packet(respkt); close(sock); freeaddrinfo(ai); @@ -236,7 +265,79 @@ radius_test(struct parse_result *res) if (res->password) explicit_bzero((char *)res->password, strlen(res->password)); - return; + return (test.ecode); +} + +static void +radius_test_send(int thing, short revents, void *arg) +{ + struct radius_test *test = arg; + RADIUS_PACKET *reqpkt = test->reqpkt; + ssize_t rv; + +retry: + rv = send(test->sock, + radius_get_data(reqpkt), radius_get_length(reqpkt), 0); + if (rv == -1) { + switch (errno) { + case EINTR: + case EAGAIN: + goto retry; + default: + break; + } + + warn("send"); + } + + if (++test->tries >= test->res->tries) + return; + + evtimer_add(&test->ev_send, &test->res->interval); +} + +static void +radius_test_recv(int sock, short revents, void *arg) +{ + struct radius_test *test = arg; + RADIUS_PACKET *respkt; + RADIUS_PACKET *reqpkt = test->reqpkt; + +retry: + respkt = radius_recv(sock, 0); + if (respkt == NULL) { + switch (errno) { + case EINTR: + case EAGAIN: + goto retry; + default: + break; + } + + warn("recv"); + return; + } + + radius_set_request_packet(respkt, reqpkt); + if (radius_get_id(respkt) == radius_get_id(reqpkt)) { + fprintf(stderr, "\nReceived:\n"); + radius_dump(stdout, respkt, true, test->res->secret); + + event_del(&test->ev_recv); + evtimer_del(&test->ev_send); + evtimer_del(&test->ev_timedout); + test->ecode = EXIT_SUCCESS; + } + + radius_delete_packet(respkt); +} + +static void +radius_test_timedout(int thing, short revents, void *arg) +{ + struct radius_test *test = arg; + + event_del(&test->ev_recv); } static void