From: yasuoka Date: Tue, 9 Jul 2024 17:26:14 +0000 (+0000) Subject: Add radiusd_ipcp(8). A module which provides IP configuration through X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=842565f2fef6c7b592857fee41ae740427b3582f;p=openbsd Add radiusd_ipcp(8). A module which provides IP configuration through RADIUS Access-Accept messages and manages IP address pool through RADIUS accounting messages. --- diff --git a/usr.sbin/radiusctl/Makefile b/usr.sbin/radiusctl/Makefile index 48cec71affc..c83db48756f 100644 --- a/usr.sbin/radiusctl/Makefile +++ b/usr.sbin/radiusctl/Makefile @@ -1,9 +1,10 @@ -# $OpenBSD: Makefile,v 1.3 2020/02/24 07:07:11 dlg Exp $ +# $OpenBSD: Makefile,v 1.4 2024/07/09 17:26:14 yasuoka Exp $ PROG= radiusctl -SRCS= radiusctl.c parser.c chap_ms.c +SRCS= radiusctl.c parser.c chap_ms.c json.c MAN= radiusctl.8 CFLAGS+= -Wall -Wextra -Wno-unused-parameter -LDADD+= -lradius -lcrypto -levent -DPADD+= ${LIBRADIUS} ${LIBCRYPTO} ${LIBEVENT} +CFLAGS+= -I${.CURDIR}/../radiusd +LDADD+= -lradius -lcrypto -levent -lutil +DPADD+= ${LIBRADIUS} ${LIBCRYPTO} ${LIBEVENT} ${LIBUTIL} .include diff --git a/usr.sbin/radiusctl/json.c b/usr.sbin/radiusctl/json.c new file mode 100644 index 00000000000..1a653ac55d2 --- /dev/null +++ b/usr.sbin/radiusctl/json.c @@ -0,0 +1,324 @@ +/* $OpenBSD: json.c,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */ + +/* + * Copyright (c) 2020 Claudio Jeker + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "json.h" + +#define JSON_MAX_STACK 16 + +enum json_type { + NONE, + START, + ARRAY, + OBJECT +}; + +static struct json_stack { + const char *name; + unsigned int count; + int compact; + enum json_type type; +} stack[JSON_MAX_STACK]; + +static char indent[JSON_MAX_STACK + 1]; +static int level; +static int eb; +static FILE *jsonfh; + +static void +do_comma_indent(void) +{ + char sp = '\n'; + + if (stack[level].compact) + sp = ' '; + + if (stack[level].count++ > 0) { + if (!eb) + eb = fprintf(jsonfh, ",%c", sp) < 0; + } + + if (stack[level].compact) + return; + if (!eb) + eb = fprintf(jsonfh, "\t%.*s", level, indent) < 0; +} + +static void +do_name(const char *name) +{ + if (stack[level].type == ARRAY) + return; + if (!eb) + eb = fprintf(jsonfh, "\"%s\": ", name) < 0; +} + +static int +do_find(enum json_type type, const char *name) +{ + int i; + + for (i = level; i > 0; i--) + if (type == stack[i].type && + strcmp(name, stack[i].name) == 0) + return i; + + /* not found */ + return -1; +} + +void +json_do_start(FILE *fh) +{ + memset(indent, '\t', JSON_MAX_STACK); + memset(stack, 0, sizeof(stack)); + level = 0; + stack[level].type = START; + jsonfh = fh; + eb = 0; + + eb = fprintf(jsonfh, "{\n") < 0; +} + +int +json_do_finish(void) +{ + while (level > 0) + json_do_end(); + if (!eb) + eb = fprintf(jsonfh, "\n}\n") < 0; + + return -eb; +} + +void +json_do_array(const char *name) +{ + int i, l; + char sp = '\n'; + + if ((l = do_find(ARRAY, name)) > 0) { + /* array already in use, close element and move on */ + for (i = level - l; i > 0; i--) + json_do_end(); + return; + } + /* Do not stack arrays, while allowed this is not needed */ + if (stack[level].type == ARRAY) + json_do_end(); + + if (stack[level].compact) + sp = ' '; + do_comma_indent(); + do_name(name); + if (!eb) + eb = fprintf(jsonfh, "[%c", sp) < 0; + + if (++level >= JSON_MAX_STACK) + errx(1, "json stack too deep"); + + stack[level].name = name; + stack[level].type = ARRAY; + stack[level].count = 0; + /* inherit compact setting from above level */ + stack[level].compact = stack[level - 1].compact; +} + +void +json_do_object(const char *name, int compact) +{ + int i, l; + char sp = '\n'; + + if ((l = do_find(OBJECT, name)) > 0) { + /* roll back to that object and close it */ + for (i = level - l; i >= 0; i--) + json_do_end(); + } + + if (compact) + sp = ' '; + do_comma_indent(); + do_name(name); + if (!eb) + eb = fprintf(jsonfh, "{%c", sp) < 0; + + if (++level >= JSON_MAX_STACK) + errx(1, "json stack too deep"); + + stack[level].name = name; + stack[level].type = OBJECT; + stack[level].count = 0; + stack[level].compact = compact; +} + +void +json_do_end(void) +{ + char c; + + if (stack[level].type == ARRAY) + c = ']'; + else if (stack[level].type == OBJECT) + c = '}'; + else + errx(1, "json bad stack state"); + + if (!stack[level].compact) { + if (!eb) + eb = fprintf(jsonfh, "\n%.*s%c", level, indent, c) < 0; + } else { + if (!eb) + eb = fprintf(jsonfh, " %c", c) < 0; + } + + stack[level].name = NULL; + stack[level].type = NONE; + stack[level].count = 0; + stack[level].compact = 0; + + if (level-- <= 0) + errx(1, "json stack underflow"); + + stack[level].count++; +} + +void +json_do_printf(const char *name, const char *fmt, ...) +{ + va_list ap; + char *str; + + va_start(ap, fmt); + if (!eb) { + if (vasprintf(&str, fmt, ap) == -1) + errx(1, "json printf failed"); + json_do_string(name, str); + free(str); + } + va_end(ap); +} + +void +json_do_string(const char *name, const char *v) +{ + unsigned char c; + + do_comma_indent(); + do_name(name); + if (!eb) + eb = fprintf(jsonfh, "\"") < 0; + while ((c = *v++) != '\0' && !eb) { + /* skip escaping '/' since our use case does not require it */ + switch (c) { + case '"': + eb = fprintf(jsonfh, "\\\"") < 0; + break; + case '\\': + eb = fprintf(jsonfh, "\\\\") < 0; + break; + case '\b': + eb = fprintf(jsonfh, "\\b") < 0; + break; + case '\f': + eb = fprintf(jsonfh, "\\f") < 0; + break; + case '\n': + eb = fprintf(jsonfh, "\\n") < 0; + break; + case '\r': + eb = fprintf(jsonfh, "\\r") < 0; + break; + case '\t': + eb = fprintf(jsonfh, "\\t") < 0; + break; + default: + if (iscntrl(c)) + errx(1, "bad control character in string"); + eb = putc(c, jsonfh) == EOF; + break; + } + } + if (!eb) + eb = fprintf(jsonfh, "\"") < 0; +} + +void +json_do_hexdump(const char *name, void *buf, size_t len) +{ + uint8_t *data = buf; + size_t i; + + do_comma_indent(); + do_name(name); + if (!eb) + eb = fprintf(jsonfh, "\"") < 0; + for (i = 0; i < len; i++) + if (!eb) + eb = fprintf(jsonfh, "%02x", *(data + i)) < 0; + if (!eb) + eb = fprintf(jsonfh, "\"") < 0; +} + +void +json_do_bool(const char *name, int v) +{ + do_comma_indent(); + do_name(name); + if (v) { + if (!eb) + eb = fprintf(jsonfh, "true") < 0; + } else { + if (!eb) + eb = fprintf(jsonfh, "false") < 0; + } +} + +void +json_do_uint(const char *name, unsigned long long v) +{ + do_comma_indent(); + do_name(name); + if (!eb) + eb = fprintf(jsonfh, "%llu", v) < 0; +} + +void +json_do_int(const char *name, long long v) +{ + do_comma_indent(); + do_name(name); + if (!eb) + eb = fprintf(jsonfh, "%lld", v) < 0; +} + +void +json_do_double(const char *name, double v) +{ + do_comma_indent(); + do_name(name); + if (!eb) + eb = fprintf(jsonfh, "%f", v) < 0; +} diff --git a/usr.sbin/radiusctl/json.h b/usr.sbin/radiusctl/json.h new file mode 100644 index 00000000000..2c134311d0c --- /dev/null +++ b/usr.sbin/radiusctl/json.h @@ -0,0 +1,34 @@ +/* $OpenBSD: json.h,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */ + +/* + * Copyright (c) 2020 Claudio Jeker + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +void json_do_start(FILE *); +int json_do_finish(void); +void json_do_array(const char *); +void json_do_object(const char *, int); +void json_do_end(void); +void json_do_printf(const char *, const char *, ...) + __attribute__((__format__ (printf, 2, 3))); +void json_do_string(const char *, const char *); +void json_do_hexdump(const char *, void *, size_t); +void json_do_bool(const char *, int); +void json_do_uint(const char *, unsigned long long); +void json_do_int(const char *, long long); +void json_do_double(const char *, double); diff --git a/usr.sbin/radiusctl/parser.c b/usr.sbin/radiusctl/parser.c index 3b97790c530..c43d7e42fde 100644 --- a/usr.sbin/radiusctl/parser.c +++ b/usr.sbin/radiusctl/parser.c @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.c,v 1.2 2020/02/24 07:07:11 dlg Exp $ */ +/* $OpenBSD: parser.c,v 1.3 2024/07/09 17:26:14 yasuoka Exp $ */ /* * Copyright (c) 2010 Reyk Floeter @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include "parser.h" @@ -40,6 +42,8 @@ enum token_type { TRIES, INTERVAL, MAXWAIT, + FLAGS, + SESSION_SEQ, ENDTOKEN }; @@ -67,9 +71,13 @@ 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_ipcp[]; +static const struct token t_ipcp_flags[]; +static const struct token t_ipcp_session_seq[]; static const struct token t_main[] = { { KEYWORD, "test", TEST, t_test }, + { KEYWORD, "ipcp", NONE, t_ipcp }, { ENDTOKEN, "", NONE, NULL } }; @@ -135,6 +143,24 @@ static const struct token t_maxwait[] = { { ENDTOKEN, "", NONE, NULL } }; +static const struct token t_ipcp[] = { + { KEYWORD, "show", IPCP_SHOW, NULL }, + { KEYWORD, "dump", IPCP_DUMP, t_ipcp_flags }, + { KEYWORD, "monitor", IPCP_MONITOR, t_ipcp_flags }, + { KEYWORD, "disconnect", IPCP_DISCONNECT,t_ipcp_session_seq }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_ipcp_flags[] = { + { NOTOKEN, "", NONE, NULL }, + { FLAGS, "-json", FLAGS_JSON, NULL }, + { ENDTOKEN, "", NONE, NULL } +}; + +static const struct token t_ipcp_session_seq[] = { + { SESSION_SEQ, "", NONE, NULL }, + { ENDTOKEN, "", NONE, NULL } +}; static const struct token *match_token(char *, const struct token []); static void show_valid_args(const struct token []); @@ -182,6 +208,10 @@ match_token(char *word, const struct token table[]) const struct token *t = NULL; long long num; const char *errstr; + size_t wordlen = 0; + + if (word != NULL) + wordlen = strlen(word); for (i = 0; table[i].type != ENDTOKEN; i++) { switch (table[i].type) { @@ -193,7 +223,7 @@ match_token(char *word, const struct token table[]) break; case KEYWORD: if (word != NULL && strncmp(word, table[i].keyword, - strlen(word)) == 0) { + wordlen) == 0) { match++; t = &table[i]; if (t->value) @@ -317,7 +347,24 @@ match_token(char *word, const struct token table[]) res.maxwait.tv_sec = num; t = &table[i]; break; - + case FLAGS: + if (word != NULL && wordlen >= 2 && + strncmp(word, table[i].keyword, wordlen) == 0) { + match++; + t = &table[i]; + if (t->value) + res.flags |= t->value; + } + break; + case SESSION_SEQ: + if (word == NULL) + break; + match++; + res.session_seq = strtonum(word, 1, UINT_MAX, &errstr); + if (errstr != NULL) + printf("invalid argument: %s is %s for " + "\"session-id\"", word, errstr); + t = &table[i]; case ENDTOKEN: break; } @@ -383,6 +430,12 @@ show_valid_args(const struct token table[]) fprintf(stderr, " \n", TEST_MAXWAIT_MIN, TEST_MAXWAIT_MAX); break; + case FLAGS: + fprintf(stderr, " %s\n", table[i].keyword); + break; + case SESSION_SEQ: + fprintf(stderr, " \n"); + break; case ENDTOKEN: break; } diff --git a/usr.sbin/radiusctl/parser.h b/usr.sbin/radiusctl/parser.h index 6fd0a6c30f7..3f5e271bf6e 100644 --- a/usr.sbin/radiusctl/parser.h +++ b/usr.sbin/radiusctl/parser.h @@ -1,4 +1,4 @@ -/* $OpenBSD: parser.h,v 1.2 2020/02/24 07:07:11 dlg Exp $ */ +/* $OpenBSD: parser.h,v 1.3 2024/07/09 17:26:14 yasuoka Exp $ */ /* This file is derived from OpenBSD:src/usr.sbin/ikectl/parser.h 1.9 */ /* @@ -20,9 +20,16 @@ #ifndef _RADIUSCTL_PARSER_H #define _RADIUSCTL_PARSER_H +#include +#include + enum actions { NONE, - TEST + TEST, + IPCP_SHOW, + IPCP_DUMP, + IPCP_MONITOR, + IPCP_DISCONNECT }; enum auth_method { @@ -43,6 +50,8 @@ enum auth_method { #define TEST_MAXWAIT_MAX 60 #define TEST_MAXWAIT_DEFAULT 8 +#define FLAGS_JSON 0x01 + struct parse_result { enum actions action; const char *hostname; @@ -59,6 +68,9 @@ struct parse_result { struct timeval interval; /* overall process wait time for a reply */ struct timeval maxwait; + + unsigned flags; + unsigned session_seq; }; struct parse_result *parse(int, char *[]); diff --git a/usr.sbin/radiusctl/radiusctl.8 b/usr.sbin/radiusctl/radiusctl.8 index 9bebe4c9bdd..c9970aa6dba 100644 --- a/usr.sbin/radiusctl/radiusctl.8 +++ b/usr.sbin/radiusctl/radiusctl.8 @@ -1,4 +1,4 @@ -.\" $OpenBSD: radiusctl.8,v 1.5 2020/02/25 06:57:36 jmc Exp $ +.\" $OpenBSD: radiusctl.8,v 1.6 2024/07/09 17:26:14 yasuoka Exp $ .\" .\" Copyright (c) YASUOKA Masahiko .\" @@ -15,7 +15,7 @@ .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" -.Dd $Mdocdate: February 25 2020 $ +.Dd $Mdocdate: July 9 2024 $ .Dt RADIUSCTL 8 .Os .Sh NAME @@ -87,6 +87,46 @@ the default port number 1812 is used. Specifies the number of packets to try sending. The default is 3. .El +.It Cm ipcp show +Show all ipcp sessions in the database of +.Xr radiusd_ipcp 8 +briefly. +.It Cm ipcp dump Op Cm -json +Dump all ipcp sessions in the database of +.Xr radiusd_ipcp 8 . +When +.Cm -json +is specified, +.Nm +shows the sessions in JSON format. +.It Cm ipcp monitor Op Cm -json +Monitor the database of +.Xr radiusd_ipcp 8 , +show newly created sessions and deleted sessions. +When +.Cm -json +is specified, +.Nm +shows the sessions in JSON format. +.It Cm ipcp disconnect Ar sequence +Request to disconnect the session specfied by the +.Ar sequence . +.Xc .El +.Sh EXAMPLES +.Bd -literal -offset indent +(show all sessions) +$ doas radiusctl ipcp show +Seq Assigned Username Start Tunnel From +--- --------------- ---------------------- -------- ------------------------- + 21 192.168.1.99 mifune@example.jp 11:35AM 203.0.113.32:34859 + 22 192.168.1.103 nakadai@example.jp 11:56AM 192.0.2.4:61794 +$ + +(disconnect Nakadai's session) +$ doas radiusctl ipcp disconnect 22 +$ +.Ed .Sh SEE ALSO -.Xr radiusd 8 +.Xr radiusd 8 , +.Xr radiusd_ipcp 8 diff --git a/usr.sbin/radiusctl/radiusctl.c b/usr.sbin/radiusctl/radiusctl.c index c374884eb01..8c46815c0de 100644 --- a/usr.sbin/radiusctl/radiusctl.c +++ b/usr.sbin/radiusctl/radiusctl.c @@ -1,4 +1,4 @@ -/* $OpenBSD: radiusctl.c,v 1.8 2020/02/24 07:07:11 dlg Exp $ */ +/* $OpenBSD: radiusctl.c,v 1.9 2024/07/09 17:26:14 yasuoka Exp $ */ /* * Copyright (c) 2015 YASUOKA Masahiko * @@ -15,33 +15,73 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include +#include #include +#include +#include +#include #include - #include -#include + #include +#include +#include +#include +#include #include #include +#include #include +#include +#include #include #include #include +#include +#include #include -#include - -#include - #include "parser.h" +#include "radiusd.h" +#include "radiusd_ipcp.h" #include "chap_ms.h" +#include "json.h" +#ifndef MAXIMUM +#define MAXIMUM(_a, _b) (((_a) > (_b))? (_a) : (_b)) +#endif -static int radius_test (struct parse_result *); -static void radius_dump (FILE *, RADIUS_PACKET *, bool, +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); + +static int ipcp_handle_imsg(struct parse_result *, struct imsg *, + int); +static void ipcp_handle_show(struct radiusd_ipcp_db_dump *, + size_t, int); +static void ipcp_handle_dumps(struct radiusd_ipcp_db_dump *, + size_t, int); +static void ipcp_handle_dump(struct radiusd_ipcp_db_dump *, + size_t, int); +static void ipcp_handle_dump0(struct radiusd_ipcp_db_dump *, + size_t, struct timespec *, struct timespec *, + struct timespec *, int); +static void ipcp_handle_stat(struct radiusd_ipcp_statistics *); +static void ipcp_handle_jsons(struct radiusd_ipcp_db_dump *, + size_t, int); +static void ipcp_handle_json(struct radiusd_ipcp_db_dump *, + size_t, struct radiusd_ipcp_statistics *, int); +static void ipcp_handle_json0(struct radiusd_ipcp_db_dump *, + size_t, struct timespec *, struct timespec *, + struct timespec *, int); + +static const char *radius_code_str(int code); static const char *hexstr(const u_char *, int, char *, int); +static const char *sockaddr_str(struct sockaddr *, char *, size_t); +static const char *time_long_str(struct timespec *, char *, size_t); +static const char *time_short_str(struct timespec *, struct timespec *, + char *, size_t); +static const char *humanize_seconds(long, char *, size_t); static void usage(void) @@ -54,9 +94,15 @@ usage(void) int main(int argc, char *argv[]) { - int ch; - struct parse_result *result; - int ecode = EXIT_SUCCESS; + int ch, sock, done = 0; + ssize_t n; + struct parse_result *res; + struct sockaddr_un sun; + struct imsgbuf ibuf; + struct imsg imsg; + struct iovec iov[5]; + int niov = 0, cnt = 0; + char module_name[RADIUSD_MODULE_NAME_LEN + 1]; while ((ch = getopt(argc, argv, "")) != -1) switch (ch) { @@ -67,22 +113,112 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; - if ((result = parse(argc, argv)) == NULL) - return (EXIT_FAILURE); + if (unveil(RADIUSD_SOCK, "rw") == -1) + err(EX_OSERR, "unveil"); + if (pledge("stdio unix rpath dns inet", NULL) == -1) + err(EX_OSERR, "pledge"); + + res = parse(argc, argv); + if (res == NULL) + exit(EX_USAGE); - switch (result->action) { + switch (res->action) { + default: + break; case NONE: + exit(EXIT_SUCCESS); break; case TEST: if (pledge("stdio dns inet", NULL) == -1) err(EXIT_FAILURE, "pledge"); - ecode = radius_test(result); + exit(radius_test(res)); + break; + } + + if (pledge("stdio unix rpath", NULL) == -1) + err(EX_OSERR, "pledge"); + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + sun.sun_len = sizeof(sun); + strlcpy(sun.sun_path, RADIUSD_SOCK, sizeof(sun.sun_path)); + + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + err(EX_OSERR, "socket"); + if (connect(sock, (struct sockaddr *)&sun, sizeof(sun)) == -1) + err(EX_OSERR, "connect"); + imsg_init(&ibuf, sock); + + res = parse(argc, argv); + if (res == NULL) + exit(EX_USAGE); + + switch (res->action) { + case TEST: + case NONE: + abort(); + break; + case IPCP_SHOW: + case IPCP_DUMP: + case IPCP_MONITOR: + memset(module_name, 0, sizeof(module_name)); + strlcpy(module_name, "ipcp", + sizeof(module_name)); + iov[niov].iov_base = module_name; + iov[niov++].iov_len = RADIUSD_MODULE_NAME_LEN; + imsg_composev(&ibuf, (res->action == IPCP_MONITOR)? + IMSG_RADIUSD_MODULE_IPCP_MONITOR : + IMSG_RADIUSD_MODULE_IPCP_DUMP, 0, 0, -1, iov, niov); break; + case IPCP_DISCONNECT: + memset(module_name, 0, sizeof(module_name)); + strlcpy(module_name, "ipcp", + sizeof(module_name)); + iov[niov].iov_base = module_name; + iov[niov++].iov_len = RADIUSD_MODULE_NAME_LEN; + iov[niov].iov_base = &res->session_seq; + iov[niov++].iov_len = sizeof(res->session_seq); + imsg_composev(&ibuf, IMSG_RADIUSD_MODULE_IPCP_DISCONNECT, 0, 0, + -1, iov, niov); + done = 1; + break; + } + while (ibuf.w.queued) { + if (msgbuf_write(&ibuf.w) <= 0 && errno != EAGAIN) + err(1, "ibuf_ctl: msgbuf_write error"); } + while (!done) { + if (((n = imsg_read(&ibuf)) == -1 && errno != EAGAIN) || n == 0) + break; + for (;;) { + if ((n = imsg_get(&ibuf, &imsg)) <= 0) { + if (n != 0) + done = 1; + break; + } + switch (res->action) { + case IPCP_SHOW: + case IPCP_DUMP: + case IPCP_MONITOR: + done = ipcp_handle_imsg(res, &imsg, cnt++); + break; + default: + break; + } + imsg_free(&imsg); + if (done) + break; - return (ecode); + } + } + close(sock); + + exit(EXIT_SUCCESS); } +/*********************************************************************** + * "test" + ***********************************************************************/ struct radius_test { const struct parse_result *res; int ecode; @@ -239,7 +375,7 @@ radius_test(struct parse_result *res) test.res = res; test.sock = sock; test.reqpkt = reqpkt; - + event_set(&test.ev_recv, sock, EV_READ|EV_PERSIST, radius_test_recv, &test); @@ -443,7 +579,6 @@ radius_dump(FILE *out, RADIUS_PACKET *pkt, bool resp, const char *secret) fprintf(out, " MS-MPPE-Encryption-Policy = 0x%08x\n", ntohl(*(u_long *)buf)); - memset(buf, 0, sizeof(buf)); len = sizeof(buf); if (radius_get_vs_raw_attr(pkt, RADIUS_VENDOR_MICROSOFT, @@ -476,7 +611,315 @@ radius_dump(FILE *out, RADIUS_PACKET *pkt, bool resp, const char *secret) } -static const char * +/*********************************************************************** + * ipcp + ***********************************************************************/ +int +ipcp_handle_imsg(struct parse_result *res, struct imsg *imsg, int cnt) +{ + ssize_t datalen; + struct radiusd_ipcp_db_dump *dump; + struct radiusd_ipcp_statistics *stat; + int done = 0; + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + switch (imsg->hdr.type) { + case IMSG_NG: + if (datalen > 0 && *((char *)imsg->data + datalen - 1) == '\0') + fprintf(stderr, "error: %s\n", (char *)imsg->data); + else + fprintf(stderr, "error\n"); + exit(EXIT_FAILURE); + case IMSG_RADIUSD_MODULE_IPCP_DUMP: + if ((size_t)datalen < sizeof(struct + radiusd_ipcp_db_dump)) + errx(1, "received a message which size is invalid"); + dump = imsg->data; + if (res->action == IPCP_SHOW) + ipcp_handle_show(dump, datalen, (cnt++ == 0)? 1 : 0); + else { + if (res->flags & FLAGS_JSON) + ipcp_handle_jsons(dump, datalen, + (cnt++ == 0)? 1 : 0); + else + ipcp_handle_dumps(dump, datalen, + (cnt++ == 0)? 1 : 0); + } + if (dump->islast && + (res->action == IPCP_SHOW || res->action == IPCP_DUMP)) + done = 1; + break; + case IMSG_RADIUSD_MODULE_IPCP_START: + if ((size_t)datalen < offsetof(struct + radiusd_ipcp_db_dump, records[1])) + errx(1, "received a message which size is invalid"); + dump = imsg->data; + if (res->flags & FLAGS_JSON) + ipcp_handle_json(dump, datalen, NULL, 0); + else { + printf("Start\n"); + ipcp_handle_dump(dump, datalen, 0); + } + break; + case IMSG_RADIUSD_MODULE_IPCP_STOP: + if ((size_t)datalen < offsetof( + struct radiusd_ipcp_db_dump, + records[1]) + + sizeof(struct + radiusd_ipcp_statistics)) + errx(1, "received a message which size is invalid"); + dump = imsg->data; + stat = (struct radiusd_ipcp_statistics *) + ((char *)imsg->data + offsetof( + struct radiusd_ipcp_db_dump, records[1])); + if (res->flags & FLAGS_JSON) + ipcp_handle_json(dump, datalen, stat, 0); + else { + printf("Stop\n"); + ipcp_handle_dump(dump, datalen, 0); + ipcp_handle_stat(stat); + } + break; + } + + return (done); +} + +static void +ipcp_handle_show(struct radiusd_ipcp_db_dump *dump, size_t dumpsiz, int first) +{ + int i, width; + uint32_t maxseq = 999; + char buf0[128], buf1[NI_MAXHOST + NI_MAXSERV + 4], buf2[80]; + struct timespec upt, now, dif, start; + + clock_gettime(CLOCK_BOOTTIME, &upt); + clock_gettime(CLOCK_REALTIME, &now); + timespecsub(&now, &upt, &upt); + + for (i = 0; ; i++) { + if (offsetof(struct radiusd_ipcp_db_dump, records[i]) + >= dumpsiz) + break; + maxseq = MAXIMUM(maxseq, dump->records[i].rec.seq); + } + for (width = 0; maxseq != 0; maxseq /= 10, width++) + ; + + for (i = 0; ; i++) { + if (offsetof(struct radiusd_ipcp_db_dump, records[i]) + >= dumpsiz) + break; + if (i == 0 && first) + printf("%-*s Assigned Username " + "Start Tunnel From\n" + "%.*s --------------- ---------------------- " + "-------- %.*s\n", width, "Seq", width, + "----------", 28 - width, + "-------------------------"); + timespecadd(&upt, &dump->records[i].rec.start, &start); + timespecsub(&now, &start, &dif); + printf("%*d %-15s %-22s %-8s %s\n", + width, dump->records[i].rec.seq, + inet_ntop(dump->records[i].af, &dump->records[i].addr, + buf0, sizeof(buf0)), dump->records[i].rec.username, + time_short_str(&start, &dif, buf2, sizeof(buf2)), + sockaddr_str( + (struct sockaddr *)&dump->records[i].rec.tun_client, buf1, + sizeof(buf1))); + } +} +static void +ipcp_handle_dump(struct radiusd_ipcp_db_dump *dump, size_t dumpsiz, int idx) +{ + struct timespec upt, now, dif, start, timeout; + + clock_gettime(CLOCK_BOOTTIME, &upt); + clock_gettime(CLOCK_REALTIME, &now); + timespecsub(&now, &upt, &upt); + + timespecadd(&upt, &dump->records[idx].rec.start, &start); + timespecsub(&now, &start, &dif); + + if (dump->records[idx].rec.start.tv_sec == 0) + ipcp_handle_dump0(dump, dumpsiz, &dif, &start, NULL, idx); + else { + timespecadd(&upt, &dump->records[idx].rec.timeout, &timeout); + ipcp_handle_dump0(dump, dumpsiz, &dif, &start, &timeout, idx); + } +} + +static void +ipcp_handle_dump0(struct radiusd_ipcp_db_dump *dump, size_t dumpsiz, + struct timespec *dif, struct timespec *start, struct timespec *timeout, + int idx) +{ + char buf0[128], buf1[NI_MAXHOST + NI_MAXSERV + 4], buf2[80]; + + printf( + " Sequence Number : %u\n" + " Session Id : %s\n" + " Username : %s\n" + " Auth Method : %s\n" + " Assigned IP Address : %s\n" + " Start Time : %s\n" + " Elapsed Time : %lld second%s%s\n", + dump->records[idx].rec.seq, dump->records[idx].rec.session_id, + dump->records[idx].rec.username, dump->records[idx].rec.auth_method, + inet_ntop(dump->records[idx].af, &dump->records[idx].addr, buf0, + sizeof(buf0)), time_long_str(start, buf1, sizeof(buf1)), + (long long)dif->tv_sec, (dif->tv_sec == 0)? "" : "s", + humanize_seconds(dif->tv_sec, buf2, sizeof(buf2))); + if (timeout != NULL) + printf(" Timeout : %s\n", + time_long_str(timeout, buf0, sizeof(buf0))); + printf( + " NAS Identifier : %s\n" + " Tunnel Type : %s\n" + " Tunnel From : %s\n", + dump->records[idx].rec.nas_id, dump->records[idx].rec.tun_type, + sockaddr_str((struct sockaddr *) + &dump->records[idx].rec.tun_client, buf1, sizeof(buf1))); +} + +void +ipcp_handle_stat(struct radiusd_ipcp_statistics *stat) +{ + printf( + " Terminate Cause : %s\n" + " Input Packets : %"PRIu32"\n" + " Output Packets : %"PRIu32"\n" + " Input Bytes : %"PRIu64"\n" + " Output Bytes : %"PRIu64"\n", + stat->cause, stat->ipackets, stat->opackets, stat->ibytes, + stat->obytes); +} + +static void +ipcp_handle_jsons(struct radiusd_ipcp_db_dump *dump, size_t dumpsiz, int first) +{ + int i; + struct timespec upt, now, dif, start, timeout; + + clock_gettime(CLOCK_BOOTTIME, &upt); + clock_gettime(CLOCK_REALTIME, &now); + timespecsub(&now, &upt, &upt); + + for (i = 0; ; i++) { + if (offsetof(struct radiusd_ipcp_db_dump, records[i]) + >= dumpsiz) + break; + timespecadd(&upt, &dump->records[i].rec.start, &start); + timespecsub(&now, &start, &dif); + json_do_start(stdout); + json_do_string("action", "start"); + if (dump->records[i].rec.timeout.tv_sec == 0) + ipcp_handle_json0(dump, dumpsiz, &dif, &start, NULL, i); + else { + timespecadd(&upt, &dump->records[i].rec.timeout, + &timeout); + ipcp_handle_json0(dump, dumpsiz, &dif, &start, &timeout, + i); + } + json_do_finish(); + } + fflush(stdout); +} + +static void +ipcp_handle_json(struct radiusd_ipcp_db_dump *dump, size_t dumpsiz, + struct radiusd_ipcp_statistics *stat, int idx) +{ + struct timespec upt, now, dif, start, timeout; + + json_do_start(stdout); + clock_gettime(CLOCK_BOOTTIME, &upt); + clock_gettime(CLOCK_REALTIME, &now); + timespecsub(&now, &upt, &upt); + timespecadd(&upt, &dump->records[idx].rec.start, &start); + timespecsub(&now, &start, &dif); + + if (stat == NULL) + json_do_string("action", "start"); + else + json_do_string("action", "stop"); + if (dump->records[idx].rec.timeout.tv_sec == 0) + ipcp_handle_json0(dump, dumpsiz, &dif, &start, NULL, idx); + else { + timespecadd(&upt, &dump->records[idx].rec.timeout, &timeout); + ipcp_handle_json0(dump, dumpsiz, &dif, &start, &timeout, idx); + } + if (stat != NULL) { + json_do_string("terminate-cause", stat->cause); + json_do_uint("input-packets", stat->ipackets); + json_do_uint("output-packets", stat->opackets); + json_do_uint("input-bytes", stat->ibytes); + json_do_uint("output-bytes", stat->obytes); + } + json_do_finish(); + fflush(stdout); +} + +static void +ipcp_handle_json0(struct radiusd_ipcp_db_dump *dump, size_t dumpsiz, + struct timespec *dif, struct timespec *start, struct timespec *timeout, + int idx) +{ + char buf[128]; + + json_do_uint("sequence-number", dump->records[idx].rec.seq); + json_do_string("session-id", dump->records[idx].rec.session_id); + json_do_string("username", dump->records[idx].rec.username); + json_do_string("auth-method", dump->records[idx].rec.auth_method); + json_do_string("assigned-ip-address", inet_ntop(dump->records[idx].af, + &dump->records[idx].addr, buf, sizeof(buf))); + json_do_uint("start", start->tv_sec); + json_do_uint("elapsed", dif->tv_sec); + if (timeout != NULL) + json_do_uint("timeout", timeout->tv_sec); + json_do_string("nas-identifier", dump->records[idx].rec.nas_id); + json_do_string("tunnel-type", dump->records[idx].rec.tun_type); + json_do_string("tunnel-from", + sockaddr_str((struct sockaddr *)&dump->records[idx].rec.tun_client, + buf, sizeof(buf))); +} + +static void +ipcp_handle_dumps(struct radiusd_ipcp_db_dump *dump, size_t dumpsiz, int first) +{ + static int cnt = 0; + int i; + struct timespec upt, now, dif, start, timeout; + + clock_gettime(CLOCK_BOOTTIME, &upt); + clock_gettime(CLOCK_REALTIME, &now); + timespecsub(&now, &upt, &upt); + + if (first) + cnt = 0; + for (i = 0; ; i++, cnt++) { + if (offsetof(struct radiusd_ipcp_db_dump, records[i]) + >= dumpsiz) + break; + timespecadd(&upt, &dump->records[i].rec.start, &start); + timespecsub(&now, &start, &dif); + printf("#%d\n", cnt + 1); + if (dump->records[i].rec.timeout.tv_sec == 0) + ipcp_handle_dump0(dump, dumpsiz, &dif, &start, NULL, i); + else { + timespecadd(&upt, &dump->records[i].rec.timeout, + &timeout); + ipcp_handle_dump0(dump, dumpsiz, &dif, &start, + &timeout, i); + } + } +} + + +/*********************************************************************** + * Miscellaneous functions + ***********************************************************************/ +const char * radius_code_str(int code) { int i; @@ -523,3 +966,90 @@ hexstr(const u_char *data, int len, char *str, int strsiz) return (str); } + +const char * +sockaddr_str(struct sockaddr *sa, char *buf, size_t bufsiz) +{ + int noport, ret; + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + + if (ntohs(((struct sockaddr_in *)sa)->sin_port) == 0) { + noport = 1; + ret = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST); + } else { + noport = 0; + ret = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), sbuf, + sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); + } + if (ret != 0) + return ""; + if (noport) + strlcpy(buf, hbuf, bufsiz); + else if (sa->sa_family == AF_INET6) + snprintf(buf, bufsiz, "[%s]:%s", hbuf, sbuf); + else + snprintf(buf, bufsiz, "%s:%s", hbuf, sbuf); + + return (buf); +} + +const char * +time_long_str(struct timespec *tim, char *buf, size_t bufsiz) +{ + struct tm tm; + + localtime_r(&tim->tv_sec, &tm); + strftime(buf, bufsiz, "%F %T", &tm); + + return (buf); +} + +const char * +time_short_str(struct timespec *tim, struct timespec *dif, char *buf, + size_t bufsiz) +{ + struct tm tm; + + localtime_r(&tim->tv_sec, &tm); + if (dif->tv_sec < 12 * 60 * 60) + strftime(buf, bufsiz, "%l:%M%p", &tm); + else if (dif->tv_sec < 7 * 24 * 60 * 60) + strftime(buf, bufsiz, "%e%b%y", &tm); + else + strftime(buf, bufsiz, "%m/%d", &tm); + + return (buf); +} + +const char * +humanize_seconds(long seconds, char *buf, size_t bufsiz) +{ + char fbuf[80]; + int hour, min; + + hour = seconds / 3600; + min = (seconds % 3600) / 60; + + if (bufsiz == 0) + return NULL; + buf[0] = '\0'; + if (hour != 0 || min != 0) { + strlcat(buf, " (", bufsiz); + if (hour != 0) { + snprintf(fbuf, sizeof(fbuf), "%d hour%s", hour, + (hour == 1)? "" : "s"); + strlcat(buf, fbuf, bufsiz); + } + if (hour != 0 && min != 0) + strlcat(buf, " and ", bufsiz); + if (min != 0) { + snprintf(fbuf, sizeof(fbuf), "%d minute%s", min, + (min == 1)? "" : "s"); + strlcat(buf, fbuf, bufsiz); + } + strlcat(buf, ")", bufsiz); + } + + return (buf); +} diff --git a/usr.sbin/radiusd/Makefile b/usr.sbin/radiusd/Makefile index 437b7e28dc1..61ba31825f3 100644 --- a/usr.sbin/radiusd/Makefile +++ b/usr.sbin/radiusd/Makefile @@ -1,7 +1,8 @@ -# $OpenBSD: Makefile,v 1.3 2024/07/02 16:18:11 deraadt Exp $ +# $OpenBSD: Makefile,v 1.4 2024/07/09 17:26:14 yasuoka Exp $ SUBDIR= radiusd SUBDIR+= radiusd_bsdauth +SUBDIR+= radiusd_ipcp SUBDIR+= radiusd_radius SUBDIR+= radiusd_standard diff --git a/usr.sbin/radiusd/control.c b/usr.sbin/radiusd/control.c new file mode 100644 index 00000000000..45c243afe8d --- /dev/null +++ b/usr.sbin/radiusd/control.c @@ -0,0 +1,337 @@ +/* $OpenBSD: control.c,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "radiusd.h" +#include "radiusd_local.h" +#include "log.h" +#include "control.h" + +static TAILQ_HEAD(, ctl_conn) ctl_conns = TAILQ_HEAD_INITIALIZER(ctl_conns); + +#define CONTROL_BACKLOG 5 +static int idseq = 0; + +struct ctl_conn *control_connbyfd(int); +struct ctl_conn *control_connbyid(uint32_t); +void control_close(int); +void control_connfree(struct ctl_conn *); +void control_event_add(struct ctl_conn *); + +struct { + struct event ev; + struct event evt; + int fd; +} control_state; + +int +control_init(const char *path) +{ + struct sockaddr_un sun; + int fd; + mode_t old_umask; + + if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, + 0)) == -1) { + log_warn("control_init: socket"); + return (-1); + } + + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strlcpy(sun.sun_path, path, sizeof(sun.sun_path)); + + if (unlink(path) == -1) + if (errno != ENOENT) { + log_warn("control_init: unlink %s", path); + close(fd); + return (-1); + } + + old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH); + if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) { + log_warn("control_init: bind: %s", path); + close(fd); + umask(old_umask); + return (-1); + } + umask(old_umask); + + if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) { + log_warn("control_init: chmod"); + close(fd); + (void)unlink(path); + return (-1); + } + + control_state.fd = fd; + + return (0); +} + +int +control_listen(void) +{ + + if (listen(control_state.fd, CONTROL_BACKLOG) == -1) { + log_warn("control_listen: listen"); + return (-1); + } + + event_set(&control_state.ev, control_state.fd, EV_READ, + control_accept, NULL); + event_add(&control_state.ev, NULL); + evtimer_set(&control_state.evt, control_accept, NULL); + + return (0); +} + +void +control_cleanup(void) +{ + struct ctl_conn *c, *t; + + TAILQ_FOREACH_SAFE(c, &ctl_conns, entry, t) { + TAILQ_REMOVE(&ctl_conns, c, entry); + control_connfree(c); + } + event_del(&control_state.ev); + event_del(&control_state.evt); +} + +/* ARGSUSED */ +void +control_accept(int listenfd, short event, void *bula) +{ + int connfd; + socklen_t len; + struct sockaddr_un sun; + struct ctl_conn *c; + + event_add(&control_state.ev, NULL); + if ((event & EV_TIMEOUT)) + return; + + len = sizeof(sun); + if ((connfd = accept4(listenfd, (struct sockaddr *)&sun, &len, + SOCK_CLOEXEC | SOCK_NONBLOCK)) == -1) { + /* + * Pause accept if we are out of file descriptors, or + * libevent will haunt us here too. + */ + if (errno == ENFILE || errno == EMFILE) { + struct timeval evtpause = { 1, 0 }; + + event_del(&control_state.ev); + evtimer_add(&control_state.evt, &evtpause); + } else if (errno != EWOULDBLOCK && errno != EINTR && + errno != ECONNABORTED) + log_warn("control_accept: accept"); + return; + } + + if ((c = calloc(1, sizeof(struct ctl_conn))) == NULL) { + log_warn("control_accept"); + close(connfd); + return; + } + + if (idseq == 0) /* don't use zero. See radiusd_module_imsg */ + ++idseq; + c->id = idseq++; + imsg_init(&c->iev.ibuf, connfd); + c->iev.handler = control_dispatch_imsg; + c->iev.events = EV_READ; + event_set(&c->iev.ev, c->iev.ibuf.fd, c->iev.events, c->iev.handler, c); + event_add(&c->iev.ev, NULL); + + TAILQ_INSERT_TAIL(&ctl_conns, c, entry); +} + +struct ctl_conn * +control_connbyfd(int fd) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->iev.ibuf.fd == fd) + break; + } + + return (c); +} + +struct ctl_conn * +control_connbyid(uint32_t id) +{ + struct ctl_conn *c; + + TAILQ_FOREACH(c, &ctl_conns, entry) { + if (c->id == id) + break; + } + + return (c); +} + +void +control_close(int fd) +{ + struct ctl_conn *c; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("control_close: fd %d: not found", fd); + return; + } + if (c->modulename[0] != '\0') + radiusd_imsg_compose_module(radiusd_s, c->modulename, + IMSG_RADIUSD_MODULE_CTRL_UNBIND, c->id, -1, -1, NULL, 0); + + control_connfree(c); +} + +void +control_connfree(struct ctl_conn *c) +{ + msgbuf_clear(&c->iev.ibuf.w); + TAILQ_REMOVE(&ctl_conns, c, entry); + + event_del(&c->iev.ev); + close(c->iev.ibuf.fd); + + /* Some file descriptors are available again. */ + if (evtimer_pending(&control_state.evt, NULL)) { + evtimer_del(&control_state.evt); + event_add(&control_state.ev, NULL); + } + + free(c); +} + +/* ARGSUSED */ +void +control_dispatch_imsg(int fd, short event, void *bula) +{ + struct ctl_conn *c; + struct imsg imsg; + ssize_t n, datalen; + char modulename[RADIUSD_MODULE_NAME_LEN + 1], msg[128]; + + if ((c = control_connbyfd(fd)) == NULL) { + log_warn("control_dispatch_imsg: fd %d: not found", fd); + return; + } + + if (event & EV_READ) { + if (((n = imsg_read(&c->iev.ibuf)) == -1 && errno != EAGAIN) || + n == 0) { + control_close(fd); + return; + } + } + if (event & EV_WRITE) { + if (msgbuf_write(&c->iev.ibuf.w) <= 0 && errno != EAGAIN) { + control_close(fd); + return; + } + } + + for (;;) { + if ((n = imsg_get(&c->iev.ibuf, &imsg)) == -1) { + control_close(fd); + return; + } + + if (n == 0) + break; + + datalen = imsg.hdr.len - IMSG_HEADER_SIZE; + switch (imsg.hdr.type) { + default: + if (imsg.hdr.type >= IMSG_RADIUSD_MODULE_MIN) { + if (datalen < RADIUSD_MODULE_NAME_LEN) { + log_warnx( "%s: received an invalid " + "imsg %d: too small", __func__, + imsg.hdr.type); + break; + } + memset(modulename, 0, sizeof(modulename)); + memcpy(modulename, imsg.data, + RADIUSD_MODULE_NAME_LEN); + if (radiusd_imsg_compose_module(radiusd_s, + modulename, imsg.hdr.type, c->id, -1, -1, + (caddr_t)imsg.data + + RADIUSD_MODULE_NAME_LEN, datalen - + RADIUSD_MODULE_NAME_LEN) != 0) { + snprintf(msg, sizeof(msg), + "module `%s' is not loaded or not " + "capable for control command", + modulename); + imsg_compose_event(&c->iev, + IMSG_NG, c->id, -1, -1, msg, + strlen(msg) + 1); + } + } else + log_debug("control_dispatch_imsg: " + "error handling imsg %d", imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(&c->iev); +} + +int +control_imsg_relay(struct imsg *imsg) +{ + struct ctl_conn *c; + + if ((c = control_connbyid(imsg->hdr.peerid)) == NULL) + return (0); + + return (imsg_compose_event(&c->iev, imsg->hdr.type, 0, imsg->hdr.pid, + -1, imsg->data, imsg->hdr.len - IMSG_HEADER_SIZE)); +} + +void +control_conn_bind(uint32_t peerid, const char *modulename) +{ + struct ctl_conn *c; + + if ((c = control_connbyid(peerid)) == NULL) + return; + + if (c->modulename[0] != '\0') + radiusd_imsg_compose_module(radiusd_s, c->modulename, + IMSG_RADIUSD_MODULE_CTRL_UNBIND, c->id, -1, -1, NULL, 0); + strlcpy(c->modulename, modulename, sizeof(c->modulename)); +} diff --git a/usr.sbin/radiusd/control.h b/usr.sbin/radiusd/control.h new file mode 100644 index 00000000000..72f8d3f88fd --- /dev/null +++ b/usr.sbin/radiusd/control.h @@ -0,0 +1,45 @@ +/* $OpenBSD: control.h,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _CONTROL_H_ +#define _CONTROL_H_ + +#include +#include +#include +#include + +#include "radiusd_local.h" + +struct ctl_conn { + uint32_t id; + TAILQ_ENTRY(ctl_conn) entry; + struct imsgev iev; + char modulename[RADIUSD_MODULE_NAME_LEN + 1]; +}; + +int control_check(char *); +int control_init(const char *); +int control_listen(void); +void control_accept(int, short, void *); +void control_dispatch_imsg(int, short, void *); +int control_imsg_relay(struct imsg *); +void control_cleanup(void); +void control_conn_bind(uint32_t, const char *); + +#endif /* _CONTROL_H_ */ diff --git a/usr.sbin/radiusd/parse.y b/usr.sbin/radiusd/parse.y index 6513ca5c4b2..435482ca112 100644 --- a/usr.sbin/radiusd/parse.y +++ b/usr.sbin/radiusd/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.20 2024/07/02 00:33:51 yasuoka Exp $ */ +/* $OpenBSD: parse.y,v 1.21 2024/07/09 17:26:14 yasuoka Exp $ */ /* * Copyright (c) 2002, 2003, 2004 Henning Brauer @@ -1011,6 +1011,7 @@ default_module_path(const char *name) const char *path; } module_paths[] = { { "bsdauth", "/usr/libexec/radiusd/radiusd_bsdauth" }, + { "ipcp", "/usr/libexec/radiusd/radiusd_ipcp" }, { "radius", "/usr/libexec/radiusd/radiusd_radius" }, { "standard", "/usr/libexec/radiusd/radiusd_standard" } }; diff --git a/usr.sbin/radiusd/radiusd.c b/usr.sbin/radiusd/radiusd.c index e9202ba28f0..ec1cf51faa5 100644 --- a/usr.sbin/radiusd/radiusd.c +++ b/usr.sbin/radiusd/radiusd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: radiusd.c,v 1.44 2024/07/02 00:33:51 yasuoka Exp $ */ +/* $OpenBSD: radiusd.c,v 1.45 2024/07/09 17:26:14 yasuoka Exp $ */ /* * Copyright (c) 2013, 2023 Internet Initiative Japan Inc. @@ -50,6 +50,7 @@ #include "log.h" #include "util.h" #include "imsg_subr.h" +#include "control.h" static int radiusd_start(struct radiusd *); static void radiusd_stop(struct radiusd *); @@ -101,6 +102,7 @@ static void close_stdio(void); static u_int radius_query_id_seq = 0; int debug = 0; +struct radiusd *radiusd_s = NULL; static __dead void usage(void) @@ -148,6 +150,7 @@ main(int argc, char *argv[]) if ((radiusd = calloc(1, sizeof(*radiusd))) == NULL) err(1, "calloc"); + radiusd_s = radiusd; TAILQ_INIT(&radiusd->listen); TAILQ_INIT(&radiusd->query); @@ -165,6 +168,9 @@ main(int argc, char *argv[]) if (debug == 0) close_stdio(); /* close stdio files now */ + if (control_init(RADIUSD_SOCK) == -1) + exit(EXIT_FAILURE); + event_init(); if ((pw = getpwnam(RADIUSD_USER)) == NULL) @@ -191,16 +197,22 @@ main(int argc, char *argv[]) if (radiusd_start(radiusd) != 0) errx(EXIT_FAILURE, "start failed"); + if (control_listen() == -1) + exit(EXIT_FAILURE); if (pledge("stdio inet", NULL) == -1) err(EXIT_FAILURE, "pledge"); - if (event_loop(0) < 0) - radiusd_stop(radiusd); + event_loop(0); if (radiusd->error != 0) log_warnx("exiting on error"); + radiusd_stop(radiusd); + control_cleanup(); + + event_loop(0); + radiusd_free(radiusd); event_base_free(NULL); @@ -275,7 +287,7 @@ radiusd_start(struct radiusd *radiusd) return (0); on_error: radiusd->error++; - radiusd_stop(radiusd); + event_loopbreak(); return (-1); } @@ -795,19 +807,15 @@ radiusd_access_request_aborted(struct radius_query *q) static void radiusd_on_sigterm(int fd, short evmask, void *ctx) { - struct radiusd *radiusd = ctx; - log_info("Received SIGTERM"); - radiusd_stop(radiusd); + event_loopbreak(); } static void radiusd_on_sigint(int fd, short evmask, void *ctx) { - struct radiusd *radiusd = ctx; - log_info("Received SIGINT"); - radiusd_stop(radiusd); + event_loopbreak(); } static void @@ -1063,6 +1071,29 @@ radiusd_find_query(struct radiusd *radiusd, u_int q_id) return (NULL); } +int +radiusd_imsg_compose_module(struct radiusd *radiusd, const char *module_name, + uint32_t type, uint32_t id, pid_t pid, int fd, void *data, size_t datalen) +{ + struct radiusd_module *module; + + TAILQ_FOREACH(module, &radiusd_s->module, next) { + if (strcmp(module->name, module_name) == 0) + break; + } + if (module == NULL || + (module->capabilities & RADIUSD_MODULE_CAP_CONTROL) == 0 || + module->fd < 0) + return (-1); + + if (imsg_compose(&module->ibuf, type, id, pid, fd, data, + datalen) == -1) + return (-1); + radiusd_module_reset_ev_handler(module); + + return (0); +} + /*********************************************************************** * radiusd module handling ***********************************************************************/ @@ -1493,9 +1524,15 @@ radiusd_module_imsg(struct radiusd_module *module, struct imsg *imsg) radiusd_access_request_aborted(q); break; } + case IMSG_RADIUSD_MODULE_CTRL_BIND: + control_conn_bind(imsg->hdr.peerid, module->name); + break; default: - RADIUSD_DBG(("Unhandled imsg type=%d from %s", imsg->hdr.type, - module->name)); + if (imsg->hdr.peerid != 0) + control_imsg_relay(imsg); + else + RADIUSD_DBG(("Unhandled imsg type=%d from %s", + imsg->hdr.type, module->name)); } } @@ -1811,3 +1848,44 @@ close_stdio(void) close(fd); } } + +/*********************************************************************** + * imsg_event + ***********************************************************************/ +struct iovec; + +void +imsg_event_add(struct imsgev *iev) +{ + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, uint32_t type, uint32_t peerid, + pid_t pid, int fd, void *data, size_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, + pid, fd, data, datalen)) != -1) + imsg_event_add(iev); + return (ret); +} + +int +imsg_composev_event(struct imsgev *iev, uint32_t type, uint32_t peerid, + pid_t pid, int fd, struct iovec *iov, int niov) +{ + int ret; + + if ((ret = imsg_composev(&iev->ibuf, type, peerid, + pid, fd, iov, niov)) != -1) + imsg_event_add(iev); + return (ret); +} diff --git a/usr.sbin/radiusd/radiusd.conf.5 b/usr.sbin/radiusd/radiusd.conf.5 index 1e0f0e7ef15..b5068fee9a2 100644 --- a/usr.sbin/radiusd/radiusd.conf.5 +++ b/usr.sbin/radiusd/radiusd.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: radiusd.conf.5,v 1.27 2024/07/04 13:14:26 sobrado Exp $ +.\" $OpenBSD: radiusd.conf.5,v 1.28 2024/07/09 17:26:14 yasuoka Exp $ .\" .\" Copyright (c) 2014 Esdenera Networks GmbH .\" Copyright (c) 2014, 2023 Internet Initiative Japan Inc. @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: July 4 2024 $ +.Dd $Mdocdate: July 9 2024 $ .Dt RADIUSD.CONF 5 .Os .Sh NAME @@ -83,6 +83,13 @@ provides authentication from the local system's interface. See .Xr radiusd_bsdauth 8 . +.It Do ipcp Dc module +The +.Dq ipcp +module provides IP configuration and manages IP address pool. +Also provides session-timeout and disconnection feature. +See +.Xr radiusd_ipcp 8 . .It Do radius Dc module The .Dq radius @@ -211,5 +218,6 @@ account * to standard .Sh SEE ALSO .Xr radiusd 8 , .Xr radiusd_bsdauth 8 , +.Xr radiusd_ipcp 8 , .Xr radiusd_radius 8 , .Xr radiusd_standard 8 diff --git a/usr.sbin/radiusd/radiusd.h b/usr.sbin/radiusd/radiusd.h index 3d724176047..047311aa09a 100644 --- a/usr.sbin/radiusd/radiusd.h +++ b/usr.sbin/radiusd/radiusd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: radiusd.h,v 1.7 2024/07/02 00:33:51 yasuoka Exp $ */ +/* $OpenBSD: radiusd.h,v 1.8 2024/07/09 17:26:14 yasuoka Exp $ */ #ifndef RADIUSD_H #define RADIUSD_H 1 @@ -23,6 +23,7 @@ #define RADIUSD_MODULE_NAME_LEN 32 #define RADIUSD_SECRET_MAX 128 +#define RADIUSD_SOCK "/var/run/radiusd.sock" #define RADIUSD_USER "_radiusd" enum imsg_type { @@ -46,7 +47,10 @@ enum imsg_type { IMSG_RADIUSD_MODULE_RESDECO, IMSG_RADIUSD_MODULE_RESDECO_DONE, IMSG_RADIUSD_MODULE_ACCTREQ, + IMSG_RADIUSD_MODULE_CTRL_BIND, /* request by module */ + IMSG_RADIUSD_MODULE_CTRL_UNBIND, /* notice by control */ IMSG_RADIUSD_MODULE_STOP, + IMSG_RADIUSD_MODULE_MIN = 10000 }; /* Module sends LOAD when it becomes ready */ @@ -57,6 +61,7 @@ struct radiusd_module_load_arg { #define RADIUSD_MODULE_CAP_REQDECO 0x04 #define RADIUSD_MODULE_CAP_RESDECO 0x08 #define RADIUSD_MODULE_CAP_ACCTREQ 0x10 +#define RADIUSD_MODULE_CAP_CONTROL 0x20 }; struct radiusd_module_object { diff --git a/usr.sbin/radiusd/radiusd/Makefile b/usr.sbin/radiusd/radiusd/Makefile index bc228c47dc2..1598e9ed314 100644 --- a/usr.sbin/radiusd/radiusd/Makefile +++ b/usr.sbin/radiusd/radiusd/Makefile @@ -1,8 +1,8 @@ -# $OpenBSD: Makefile,v 1.1 2015/07/21 04:06:04 yasuoka Exp $ +# $OpenBSD: Makefile,v 1.2 2024/07/09 17:26:14 yasuoka Exp $ PROG= radiusd BINDIR= /usr/sbin MAN= radiusd.8 radiusd.conf.5 -SRCS= radiusd.c parse.y log.c util.c imsg_subr.c +SRCS= radiusd.c parse.y log.c util.c imsg_subr.c control.c LDADD+= -lradius -lcrypto -levent -lutil DPADD= ${LIBRADIUS} ${LIBCRYPTO} ${LIBEVENT} ${LIBUTIL} diff --git a/usr.sbin/radiusd/radiusd_ipcp.8 b/usr.sbin/radiusd/radiusd_ipcp.8 new file mode 100644 index 00000000000..0049b1d7b2c --- /dev/null +++ b/usr.sbin/radiusd/radiusd_ipcp.8 @@ -0,0 +1,195 @@ +.\" $OpenBSD: radiusd_ipcp.8,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ +.\" +.\" Copyright (c) 2024 Internet Initiative Japan Inc. +.\" +.\" 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. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" The following requests are required for all man pages. +.\" +.Dd $Mdocdate: July 9 2024 $ +.Dt RADIUSD_IPCP 8 +.Os +.Sh NAME +.Nm radiusd_ipcp +.Nd provides IP configuration and manages IP address pool +.Sh SYNOPSIS +.Nm radiusd_ipcp +.Sh DESCRIPTION +The +.Nm +module is executed by +.Xr radiusd 8 +as a module to provide IP configuration through RADIUS Access-Accept messages +and manages IP address pool through RADIUS accounting messages. +The internal sessions can be shown or monitored by +.Xr radiusctl 8 . +Also +.Nm +provides session timeouts and disconnects requested by +.Xr radiusctl 8 +through the Dynamic Authorization Extension +.Po DAE, RFC 5176 Pc . +.Sh CONFIGURATIONS +To use the +.Nm +module, +it should be configure as a decoration module of the authentication +and as an accouting module. +.Bd -literal -offset indent +authenticate * by (any auth module) decorate-by ipcp +account * to ipcp +.Ed +.Pp +The +.Nm +module supports the following configuration key and value: +.Pp +.Bl -tag -width Ds +.It Ic address pool Ar address-space ... +Specify the IP address spaces that is pooled. +The +.Ar address-space +can be specified by a address range +.Pq e.g. 192.168.1.1-192.168.1.199 +or a address mask +.Pq e.g. 192.168.1.0/24 . +The pooled addresses are used for dynamic assignment. +.It Ic address static Ar address-space ... +Specify the IP address spaces that is pooled for static assignment. +The +.Ar address-space +is the same syntax of +.Ic address pool , +see the description for +.Ic address pool +for detail. +.It Ic name-server Ar primary-address Op Ar secondary-address +Specify the DNS servers' IP addresses. +.It Ic netbios-server Ar primary-address Op Ar secondary-address +Specify the NetBIOS name servers' IP addresses. +.It Ic session-timeout Ar seconds | Do radius Dc +Specify the session-timeout in seconds, +or +.Dq radius . +.Nm +disconnects the session through DAE at the specified time after starting. +When +.Dq radius +is specified, +the value of the Session-Timeout attribute in Access-Accepted is used for +the timeout. +Configure +.Ic dae server +to use this option. +.It Ic dae server Ar address Ns Oo Ar :port Oc Ar secret Op Ar nas-id +Configure a DAE server which +.Nm +requests disconnection for sessions. +Specify the +.Ar address , +optionally the +.Ar port +number, +and the +.Ar secret . +If the optional +.Ar nas-id +is specified, +the server is selected only for the session which NAS-Identifier is +matched the spsecified value. +The default port number is 3799. +.It Ic max-sessions Ar number +Specify the maxinum number of sessions. +.Sq 0 +means no limit. +The default value is 0. +.It Ic user-max-sessions Ar number +Specify the maxinum number of sessions per a user. +.Sq 0 +means no limit. +The default value is 0. +.It Ic start-wait Ar seconds +Specify the seconds waiting for the RADIUS Accounting Start for the +session after Access-Accept. +.Nm +preserves the assigned IP address for that period. +The default value is 60 seconds. +.El +.Sh EXAMPLES +An example which +.Nm +works with +.Xr npppd 8 . +.Pp +.Pa /etc/radiusd.conf: +.Bd -literal -offset indent +listen on 127.0.0.1 +listen on 127.0.0.1 accounting + +client 127.0.0.1/32 { + secret "SECRET" +} + +module radius { + set secret "SECRET2" + set server 192.168.0.4:1812 +} + +module ipcp { + set address pool 192.168.1.0/24 + set name-server 192.168.0.4 + set max-sessions 128 + set user-max-sessions 2 + #set dae server 127.0.0.1 "SECRET3" +} + +authenticate * by radius decorate-by ipcp +account * to ipcp +.Ed +.Pp +.Pa /etc/npppd/npppd.conf: +.Bd -literal -offset indent +tunnel L2TP protocol l2tp { + listen on 192.0.2.51 +} +ipcp IPCP { + pool-address 192.168.1.2-192.168.1.255 for dynamic +} +interface pppac0 address 192.168.1.1 ipcp IPCP +authentication RADIUS type radius { + authentication-server { + address 127.0.0.1 secret "SECRET" + } + accounting-server { + address 127.0.0.1 secret "SECRET" + } +} +bind tunnel from L2TP authenticated by RADIUS to pppac0 +.Ed +.Sh FILES +.Bl -tag -width "/usr/libexec/radiusd/radiusd_ipcp" -compact +.It Pa /usr/libexec/radiusd/radiusd_ipcp +.Dq ipcp +module executable. +.El +.Sh SEE ALSO +.Xr radiusctl 8 , +.Xr authenticate 3 , +.Xr radiusd 8 , +.Xr radiusd.conf 5 , +.Xr npppd 8 +.Sh HISTORY +The +.Nm +daemon first appeared in +.Ox 7.6 . diff --git a/usr.sbin/radiusd/radiusd_ipcp.c b/usr.sbin/radiusd/radiusd_ipcp.c new file mode 100644 index 00000000000..bc73ee6522c --- /dev/null +++ b/usr.sbin/radiusd/radiusd_ipcp.c @@ -0,0 +1,1899 @@ +/* $OpenBSD: radiusd_ipcp.c,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */ + +/* + * Copyright (c) 2024 Internet Initiative Japan Inc. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "radiusd.h" +#include "radiusd_module.h" +#include "radiusd_ipcp.h" +#include "log.h" + +#define RADIUSD_IPCP_START_WAIT 60 + +enum ipcp_address_type { + ADDRESS_TYPE_POOL, + ADDRESS_TYPE_STATIC +}; + +struct ipcp_address { + enum ipcp_address_type type; + struct in_addr start; + struct in_addr end; + int naddrs; + TAILQ_ENTRY(ipcp_address) next; +}; + +struct user { + TAILQ_HEAD(, assigned_ipv4) ipv4s; + RB_ENTRY(user) tree; + char name[0]; +}; + +struct module_ipcp_dae; + +struct assigned_ipv4 { + struct in_addr ipv4; + unsigned seq; + char session_id[256]; + char auth_method[16]; + struct user *user; + uint32_t session_timeout; + struct timespec start; + struct timespec timeout; + struct in_addr nas_ipv4; + struct in6_addr nas_ipv6; + char nas_id[256]; + const char *tun_type; + union { + struct sockaddr_in sin4; + struct sockaddr_in6 sin6; + } tun_client; + + struct timespec authtime; + RB_ENTRY(assigned_ipv4) tree; + TAILQ_ENTRY(assigned_ipv4) next; + + /* RFC 5176 Dynamic Authorization Extensions for RADIUS */ + struct module_ipcp_dae *dae; + RADIUS_PACKET *dae_reqpkt; + TAILQ_ENTRY(assigned_ipv4) dae_next; + int dae_ntry; + struct event dae_evtimer; +}; + +struct module_ipcp_ctrlconn { + uint32_t peerid; + TAILQ_ENTRY(module_ipcp_ctrlconn) + next; +}; + +struct module_ipcp_dae { + struct module_ipcp *ipcp; + int sock; + char nas_id[256]; + char secret[80]; + union { + struct sockaddr_in sin4; + struct sockaddr_in6 sin6; + } nas_addr; + struct event ev_sock; + TAILQ_ENTRY(module_ipcp_dae) next; + TAILQ_HEAD(, assigned_ipv4) reqs; +}; + +struct module_ipcp { + struct module_base *base; + int nsessions; + unsigned seq; + int max_sessions; + int user_max_sessions; + int start_wait; + int session_timeout; + bool no_session_timeout; + struct timespec uptime; + struct in_addr name_server[2]; + struct in_addr netbios_server[2]; + RB_HEAD(assigned_ipv4_tree, assigned_ipv4) + ipv4s; + RB_HEAD(user_tree, user) users; + int npools; + TAILQ_HEAD(,ipcp_address) addrs; + TAILQ_HEAD(,module_ipcp_ctrlconn) + ctrls; + TAILQ_HEAD(,module_ipcp_dae) daes; + struct event ev_timer; +}; + +#ifndef nitems +#define nitems(_x) (sizeof((_x)) / sizeof((_x)[0])) +#endif + +#ifndef MAXIMUM +#define MAXIMUM(_a, _b) (((_a) > (_b))? (_a) : (_b)) +#endif + +static void ipcp_init(struct module_ipcp *); +static void ipcp_start(void *); +static void ipcp_stop(void *); +static void ipcp_fini(struct module_ipcp *); +static void ipcp_config_set(void *, const char *, int, char * const *); +static void ipcp_dispatch_control(void *, struct imsg *); +static int ipcp_notice_startstop(struct module_ipcp *, + struct assigned_ipv4 *, int, + struct radiusd_ipcp_statistics *); +static void ipcp_resdeco(void *, u_int, const u_char *, size_t reqlen, + const u_char *, size_t reslen); +static void ipcp_reject(struct module_ipcp *, RADIUS_PACKET *, + unsigned int, RADIUS_PACKET *, int); +static void ipcp_accounting_request(void *, u_int, const u_char *, + size_t); + +struct assigned_ipv4 + *ipcp_ipv4_assign(struct module_ipcp *, struct user *, + struct in_addr); +static struct assigned_ipv4 + *ipcp_ipv4_find(struct module_ipcp *, struct in_addr); +static void ipcp_ipv4_release(struct module_ipcp *, + struct assigned_ipv4 *); +static int assigned_ipv4_compar(struct assigned_ipv4 *, + struct assigned_ipv4 *); +static struct user + *ipcp_user_get(struct module_ipcp *, const char *); +static int user_compar(struct user *, struct user *); +static int ipcp_prepare_db(void); +static int ipcp_restore_from_db(struct module_ipcp *); +static void ipcp_put_db(struct module_ipcp *, struct assigned_ipv4 *); +static void ipcp_del_db(struct module_ipcp *, struct assigned_ipv4 *); +static void ipcp_db_dump_fill_record(struct radiusd_ipcp_db_dump *, int, + struct assigned_ipv4 *); +static void ipcp_on_timer(int, short, void *); +static void ipcp_schedule_timer(struct module_ipcp *); +static void ipcp_dae_send_disconnect_request(struct assigned_ipv4 *); +static void ipcp_dae_request_on_timeout(int, short, void *); +static void ipcp_dae_on_event(int, short, void *); +static struct ipcp_address + *parse_address_range(const char *); +static const char + *radius_tunnel_type_string(unsigned, const char *); +static const char + *radius_terminate_cause_string(unsigned); +static const char + *radius_error_cause_string(unsigned); +static int parse_addr(const char *, int, struct sockaddr *, socklen_t); +static const char + *print_addr(struct sockaddr *, char *, size_t); + +RB_PROTOTYPE_STATIC(assigned_ipv4_tree, assigned_ipv4, tree, + assigned_ipv4_compar); +RB_PROTOTYPE_STATIC(user_tree, user, tree, user_compar); + +int +main(int argc, char *argv[]) +{ + struct module_ipcp module_ipcp; + struct module_handlers handlers = { + .start = ipcp_start, + .stop = ipcp_stop, + .config_set = ipcp_config_set, + .response_decoration = ipcp_resdeco, + .accounting_request = ipcp_accounting_request, + .dispatch_control = ipcp_dispatch_control + }; + + ipcp_init(&module_ipcp); + + if ((module_ipcp.base = module_create(STDIN_FILENO, &module_ipcp, + &handlers)) == NULL) + err(1, "Could not create a module instance"); + + if (ipcp_prepare_db() == -1) + err(1, "ipcp_prepare_db"); + + module_drop_privilege(module_ipcp.base, 1); + if (unveil(_PATH_RADIUSD_IPCP_DB, "rw") == -1) + err(1, "unveil"); + if (pledge("stdio inet rpath wpath flock", NULL) == -1) + err(1, "pledge"); + setproctitle("[main]"); + + module_load(module_ipcp.base); + log_init(0); + event_init(); + + module_start(module_ipcp.base); + event_loop(0); + + ipcp_fini(&module_ipcp); + + event_loop(0); + + exit(EXIT_SUCCESS); +} + +void +ipcp_init(struct module_ipcp *self) +{ + memset(self, 0, sizeof(struct module_ipcp)); + TAILQ_INIT(&self->addrs); + RB_INIT(&self->ipv4s); + RB_INIT(&self->users); + TAILQ_INIT(&self->ctrls); + TAILQ_INIT(&self->daes); + self->seq = 1; + self->no_session_timeout = true; +} + +void +ipcp_start(void *ctx) +{ + struct module_ipcp *self = ctx; + struct ipcp_address *addr; + struct module_ipcp_dae *dae; + int sock; + + if (self->start_wait == 0) + self->start_wait = RADIUSD_IPCP_START_WAIT; + + /* count pool address*/ + TAILQ_FOREACH(addr, &self->addrs, next) { + if (addr->type == ADDRESS_TYPE_POOL) + self->npools += addr->naddrs; + } + log_info("number of pooled IP addresses = %d", self->npools); + + if (ipcp_restore_from_db(self) == -1) { + module_send_message(self->base, IMSG_NG, + "Restoring the database failed: %s", strerror(errno)); + module_stop(self->base); + return; + } + ipcp_schedule_timer(self); + + /* prepare socket for DAE */ + TAILQ_FOREACH(dae, &self->daes, next) { + if ((sock = socket(dae->nas_addr.sin4.sin_family, + SOCK_DGRAM, IPPROTO_UDP)) == -1) { + log_warn("could not start dae: %s", strerror(errno)); + return; + } + if (connect(sock, (struct sockaddr *)&dae->nas_addr, + dae->nas_addr.sin4.sin_len) == -1) { + log_warn("could not start dae: %s", strerror(errno)); + return; + } + dae->sock = sock; + event_set(&dae->ev_sock, sock, EV_READ | EV_PERSIST, + ipcp_dae_on_event, dae); + event_add(&dae->ev_sock, NULL); + } + + module_send_message(self->base, IMSG_OK, NULL); +} + +void +ipcp_stop(void *ctx) +{ + struct module_ipcp *self = ctx; + struct module_ipcp_dae *dae; + + /* stop the sockets for DAE */ + TAILQ_FOREACH(dae, &self->daes, next) { + if (dae->sock >= 0) { + event_del(&dae->ev_sock); + close(dae->sock); + dae->sock = -1; + } + } + if (evtimer_pending(&self->ev_timer, NULL)) + evtimer_del(&self->ev_timer); +} + +void +ipcp_fini(struct module_ipcp *self) +{ + struct assigned_ipv4 *assign, *assignt; + struct user *user, *usert; + struct module_ipcp_ctrlconn *ctrl, *ctrlt; + struct module_ipcp_dae *dae, *daet; + + RB_FOREACH_SAFE(assign, assigned_ipv4_tree, &self->ipv4s, assignt) + ipcp_ipv4_release(self, assign); + RB_FOREACH_SAFE(user, user_tree, &self->users, usert) + free(user); + TAILQ_FOREACH_SAFE(ctrl, &self->ctrls, next, ctrlt) + free(ctrl); + TAILQ_FOREACH_SAFE(dae, &self->daes, next, daet) { + if (dae->sock >= 0) { + event_del(&dae->ev_sock); + close(dae->sock); + } + free(dae); + } + if (evtimer_pending(&self->ev_timer, NULL)) + evtimer_del(&self->ev_timer); + module_destroy(self->base); +} + +void +ipcp_config_set(void *ctx, const char *name, int argc, char * const * argv) +{ + struct module_ipcp *module = ctx; + const char *errmsg = "none"; + int i; + struct ipcp_address *addr; + struct in_addr ina; + struct module_ipcp_dae dae, *dae0; + + if (strcmp(name, "address") == 0) { + SYNTAX_ASSERT(argc >= 1, + "specify one of pool, server, nas-select, or user-select"); + if (strcmp(argv[0], "pool") == 0) { + SYNTAX_ASSERT(argc >= 2, + "`address pool' must have one address range at " + "least"); + addr = TAILQ_FIRST(&module->addrs); + for (i = 0; i < argc - 1; i++) { + if ((addr = parse_address_range(argv[i + 1])) + == NULL) { + module_send_message(module->base, + IMSG_NG, "Invalid address range: " + "%s", argv[i + 1]); + return; + } + addr->type = ADDRESS_TYPE_POOL; + TAILQ_INSERT_TAIL(&module->addrs, addr, next); + } + } else if (strcmp(argv[0], "static") == 0) { + SYNTAX_ASSERT(argc >= 2, + "`address static' must have one address range at " + "least"); + addr = TAILQ_FIRST(&module->addrs); + for (i = 0; i < argc - 1; i++) { + if ((addr = parse_address_range(argv[i + 1])) + == NULL) { + module_send_message(module->base, + IMSG_NG, "Invalid address range: " + "%s", argv[i + 1]); + return; + } + addr->type = ADDRESS_TYPE_STATIC; + TAILQ_INSERT_TAIL(&module->addrs, addr, next); + } + } else + SYNTAX_ASSERT(0, "specify pool or static"); + } else if (strcmp(name, "max-sessions") == 0) { + SYNTAX_ASSERT(argc == 1, + "`max-sessions' must have an argument"); + module->max_sessions = strtonum(argv[0], 0, INT_MAX, &errmsg); + if (errmsg != NULL) { + module_send_message(module->base, IMSG_NG, + "could not parse `max-sessions': %s", errmsg); + return; + } + } else if (strcmp(name, "user-max-sessions") == 0) { + SYNTAX_ASSERT(argc == 1, "`max-session' must have an argument"); + module->user_max_sessions = strtonum(argv[0], 0, INT_MAX, + &errmsg); + if (errmsg != NULL) { + module_send_message(module->base, IMSG_NG, + "could not parse `user-max-session': %s", errmsg); + return; + } + } else if (strcmp(name, "start-wait") == 0) { + SYNTAX_ASSERT(argc == 1, "`start-wait' must have an argument"); + module->start_wait = strtonum(argv[0], 1, INT_MAX, &errmsg); + if (errmsg != NULL) { + module_send_message(module->base, IMSG_NG, + "could not parse `start-wait': %s", errmsg); + return; + } + } else if (strcmp(name, "name-server") == 0) { + SYNTAX_ASSERT(argc == 1 || argc == 2, + "specify 1 or 2 addresses for `name-server'"); + for (i = 0; i < argc; i++) { + if (inet_aton(argv[i], &ina) != 1) { + module_send_message(module->base, IMSG_NG, + "Invalid IP address: %s", argv[i]); + return; + } + if (module->name_server[0].s_addr == 0) + module->name_server[0] = ina; + else if (module->name_server[1].s_addr == 0) + module->name_server[1] = ina; + else + SYNTAX_ASSERT(0, + "too many `name-server' is configured"); + } + } else if (strcmp(name, "netbios-server") == 0) { + SYNTAX_ASSERT(argc == 1 || argc == 2, + "specify 1 or 2 addresses for `name-server'"); + for (i = 0; i < argc; i++) { + if (inet_aton(argv[i], &ina) != 1) { + module_send_message(module->base, IMSG_NG, + "Invalid IP address: %s", argv[i]); + return; + } + if (module->netbios_server[0].s_addr == 0) + module->netbios_server[0] = ina; + else if (module->netbios_server[1].s_addr == 0) + module->netbios_server[1] = ina; + else + SYNTAX_ASSERT(0, + "too many `name-server' is configured"); + } + } else if (strcmp(name, "session-timeout") == 0) { + SYNTAX_ASSERT(argc == 1, + "`session-timeout' must have an argument"); + if (strcmp(argv[0], "radius") == 0) { + module->no_session_timeout = false; + module->session_timeout = 0; + } else { + module->no_session_timeout = false; + module->session_timeout = strtonum(argv[0], 1, INT_MAX, + &errmsg); + if (errmsg != NULL) { + module_send_message(module->base, IMSG_NG, + "could not parse `session-timeout': %s", + errmsg); + return; + } + } + } else if (strcmp(name, "dae") == 0) { + if (!(argc >= 1 || strcmp(argv[1], "server") == 0)) { + module_send_message(module->base, IMSG_NG, + "`%s' is unknown", argv[1]); + return; + } + i = 1; + SYNTAX_ASSERT(i < argc, "no address[:port] for dae server"); + if (i < argc && + parse_addr(argv[i], AF_UNSPEC, (struct sockaddr *) + &dae.nas_addr, sizeof(dae.nas_addr)) == -1) { + module_send_message(module->base, IMSG_NG, + "failed to parse dae server's address, %s", + argv[i]); + return; + } + if (ntohs(dae.nas_addr.sin4.sin_port) == 0) + dae.nas_addr.sin4.sin_port = + htons(RADIUS_DAE_DEFAULT_PORT); + i++; + SYNTAX_ASSERT(i < argc, "no secret for dae server"); + if (strlcpy(dae.secret, argv[i++], sizeof(dae.secret)) >= + sizeof(dae.secret)) { + module_send_message(module->base, IMSG_NG, + "dae server's secret must be < %d bytes", + (int)sizeof(dae.secret) - 1); + return; + } + if (i < argc) + strlcpy(dae.nas_id, argv[i++], sizeof(dae.nas_id)); + if ((dae0 = calloc(1, sizeof(struct module_ipcp_dae))) == NULL) + { + module_send_message(module->base, IMSG_NG, + "%s", strerror(errno)); + return; + } + *dae0 = dae; + TAILQ_INIT(&dae0->reqs); + TAILQ_INSERT_TAIL(&module->daes, dae0, next); + } else if (strcmp(name, "_debug") == 0) + log_init(1); + else if (strncmp(name, "_", 1) == 0) + /* ignore */; + else { + module_send_message(module->base, IMSG_NG, + "Unknown config parameter name `%s'", name); + return; + } + module_send_message(module->base, IMSG_OK, NULL); + + return; + syntax_error: + module_send_message(module->base, IMSG_NG, "%s", errmsg); +} + +void +ipcp_dispatch_control(void *ctx, struct imsg *imsg) +{ + struct module_ipcp *self = ctx; + struct assigned_ipv4 *assign; + struct radiusd_ipcp_db_dump *dump; + struct module_ipcp_ctrlconn *ctrl, *ctrlt; + int i; + size_t dumpsiz; + u_int datalen; + unsigned seq; + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + switch (imsg->hdr.type) { + case IMSG_RADIUSD_MODULE_CTRL_UNBIND: + TAILQ_FOREACH_SAFE(ctrl, &self->ctrls, next, ctrlt) { + if (ctrl->peerid == imsg->hdr.peerid) { + TAILQ_REMOVE(&self->ctrls, ctrl, next); + free(ctrl); + break; + } + } + break; + case IMSG_RADIUSD_MODULE_IPCP_MONITOR: + case IMSG_RADIUSD_MODULE_IPCP_DUMP_AND_MONITOR: + if ((ctrl = calloc(1, sizeof(struct module_ipcp_ctrlconn))) + == NULL) { + log_warn("%s: calloc()", __func__); + goto fail; + } + ctrl->peerid = imsg->hdr.peerid; + TAILQ_INSERT_TAIL(&self->ctrls, ctrl, next); + module_imsg_compose(self->base, IMSG_RADIUSD_MODULE_CTRL_BIND, + imsg->hdr.peerid, 0, -1, NULL, 0); + if (imsg->hdr.type == IMSG_RADIUSD_MODULE_IPCP_MONITOR) + break; + /* FALLTROUGH */ + case IMSG_RADIUSD_MODULE_IPCP_DUMP: + dumpsiz = MAX_IMSGSIZE; + if ((dump = calloc(1, dumpsiz)) == NULL) { + log_warn("%s: calloc()", __func__); + goto fail; + } + i = 0; + RB_FOREACH(assign, assigned_ipv4_tree, &self->ipv4s) { + if (!timespecisset(&assign->start)) + /* not started yet */ + continue; + ipcp_db_dump_fill_record(dump, i++, assign); + if (RB_NEXT(assigned_ipv4_tree, &self->ipv4s, assign) + == NULL) + break; + if (offsetof(struct radiusd_ipcp_db_dump, + records[i + 1]) >= dumpsiz) { + module_imsg_compose(self->base, + IMSG_RADIUSD_MODULE_IPCP_DUMP, + imsg->hdr.peerid, 0, -1, + dump, offsetof(struct radiusd_ipcp_db_dump, + records[i])); + i = 0; + } + } + dump->islast = 1; + module_imsg_compose(self->base, IMSG_RADIUSD_MODULE_IPCP_DUMP, + imsg->hdr.peerid, 0, -1, dump, offsetof( + struct radiusd_ipcp_db_dump, records[i])); + freezero(dump ,dumpsiz); + break; + case IMSG_RADIUSD_MODULE_IPCP_DISCONNECT: + if (datalen < sizeof(unsigned)) { + log_warn("%s: received " + "IMSG_RADIUSD_MODULE_IPCP_DISCONNECT message size " + "is wrong", __func__); + goto fail; + } + seq = *(unsigned *)imsg->data; + RB_FOREACH(assign, assigned_ipv4_tree, &self->ipv4s) { + if (!timespecisset(&assign->start)) + /* not started yet */ + continue; + if (assign->seq == seq) + break; + } + if (assign == NULL) + log_warnx("Disconnect seq=%u requested, but the " + "session is not found", seq); + else { + if (assign->dae == NULL) + log_warnx("Disconnect seq=%u requested, but " + "DAE is not configured", assign->seq); + else { + log_info("Disconnect id=%u requested", + assign->seq); + ipcp_dae_send_disconnect_request(assign); + } + } + break; + } + return; + fail: + module_stop(self->base); +} + +int +ipcp_notice_startstop(struct module_ipcp *self, struct assigned_ipv4 *assign, + int start, struct radiusd_ipcp_statistics *stat) +{ + struct module_ipcp_ctrlconn *ctrl; + struct radiusd_ipcp_db_dump *dump; + size_t dumpsiz; + struct iovec iov[2]; + int niov = 0; + + dumpsiz = offsetof(struct radiusd_ipcp_db_dump, records[1]); + if ((dump = calloc(1, dumpsiz)) == NULL) { + log_warn("%s: calloc()", __func__); + return (-1); + } + dump->islast = 1; + ipcp_db_dump_fill_record(dump, 0, assign); + + iov[niov].iov_base = dump; + iov[niov].iov_len = dumpsiz; + if (start == 0) { + iov[++niov].iov_base = stat; + iov[niov].iov_len = sizeof(struct radiusd_ipcp_statistics); + } + TAILQ_FOREACH(ctrl, &self->ctrls, next) + module_imsg_composev(self->base, + (start)? IMSG_RADIUSD_MODULE_IPCP_START : + IMSG_RADIUSD_MODULE_IPCP_STOP, ctrl->peerid, 0, -1, iov, + niov + 1); + freezero(dump, dumpsiz); + return (0); +} + +void +ipcp_resdeco(void *ctx, u_int q_id, const u_char *req, size_t reqlen, + const u_char *res, size_t reslen) +{ + struct module_ipcp *self = ctx; + RADIUS_PACKET *radres = NULL, *radreq = NULL; + struct in_addr addr4; + const struct in_addr mask4 = { .s_addr = 0xffffffffUL }; + int res_code, msraserr = 935; + struct ipcp_address *addr; + int i, j, n; + bool found = false; + char username[256], buf[128]; + struct user *user = NULL; + struct assigned_ipv4 *assigned = NULL, *assign; + + clock_gettime(CLOCK_BOOTTIME, &self->uptime); + + if ((radres = radius_convert_packet(res, reslen)) == NULL) { + log_warn("%s: radius_convert_packet() failed", __func__); + goto fatal; + } + res_code = radius_get_code(radres); + if (res_code != RADIUS_CODE_ACCESS_ACCEPT) + goto accept; + + if ((radreq = radius_convert_packet(req, reqlen)) == NULL) { + log_warn("%s: radius_convert_packet() failed", __func__); + goto fatal; + } + + /* + * prefer User-Name of the response rather than the request, + * since it must be the authenticated user. + */ + if (radius_get_string_attr(radres, RADIUS_TYPE_USER_NAME, username, + sizeof(username)) != 0 && + radius_get_string_attr(radreq, RADIUS_TYPE_USER_NAME, username, + sizeof(username)) != 0) { + log_warnx("q=%u unexpected request: no user-name", q_id); + goto fatal; + } + + if ((addr = TAILQ_FIRST(&self->addrs)) != NULL) { + /* The address assignment is configured */ + + if ((user = ipcp_user_get(self, username)) == NULL) { + log_warn("%s: ipcp_user_get()", __func__); + goto fatal; + } + + msraserr = 935; + if (self->max_sessions != 0) { + if (self->nsessions >= self->max_sessions) { + log_info("q=%u rejected: number of " + "sessions reached the limit(%d)", q_id, + self->max_sessions); + goto reject; + } + } + if (self->user_max_sessions != 0) { + n = 0; + TAILQ_FOREACH(assign, &user->ipv4s, next) + n++; + if (n >= self->user_max_sessions) { + log_info("q=%u rejected: number of " + "sessions per a user reached the limit(%d)", + q_id, self->user_max_sessions); + goto reject; + } + } + + msraserr = 716; + if (radius_get_ipv4_attr(radres, + RADIUS_TYPE_FRAMED_IP_ADDRESS, &addr4) == 0) { + if (ipcp_ipv4_find(self, addr4) != NULL) + log_info("q=%u rejected: server requested IP " + "address is busy", q_id); + else { + /* compare in host byte order */ + addr4.s_addr = ntohl(addr4.s_addr); + TAILQ_FOREACH(addr, &self->addrs, next) { + if (addr->type != ADDRESS_TYPE_STATIC && + addr->type != ADDRESS_TYPE_POOL) + continue; + if (addr->start.s_addr <= addr4.s_addr + && addr4.s_addr <= addr->end.s_addr) + break; + } + if (addr == NULL) + log_info("q=%u rejected: server " + "requested IP address is out of " + "the range", q_id); + else + found = true; + /* revert the addr to the network byte order */ + addr4.s_addr = htonl(addr4.s_addr); + } + if (!found) + goto reject; + } else { + n = arc4random() % self->npools; + i = 0; + TAILQ_FOREACH(addr, &self->addrs, next) { + if (addr->type == ADDRESS_TYPE_POOL) { + if (i <= n && n < i + addr->naddrs) { + j = n - i; + break; + } + i += addr->naddrs; + } + } + for (i = 0; i < self->npools; i++, j++) { + if (addr == NULL) + break; + if (j >= addr->naddrs) { /* next pool */ + if ((addr = TAILQ_NEXT(addr, next)) + == NULL) + addr = TAILQ_FIRST( + &self->addrs); + j = 0; + } + addr4.s_addr = htonl(addr->start.s_addr + j); + if (ipcp_ipv4_find(self, addr4) == NULL) { + found = true; + break; + } + } + if (!found) { + log_info("q=%u rejected: ran out of the " + "address pool", q_id); + goto reject; + } + } + if ((assigned = ipcp_ipv4_assign(self, user, addr4)) == NULL) { + log_warn("%s: ipcp_ipv4_assign()", __func__); + goto fatal; + } + radius_set_ipv4_attr(radres, RADIUS_TYPE_FRAMED_IP_NETMASK, + mask4); + radius_del_attr_all(radres, RADIUS_TYPE_FRAMED_IP_ADDRESS); + radius_put_ipv4_attr(radres, RADIUS_TYPE_FRAMED_IP_ADDRESS, + addr4); + log_info("q=%u Assign %s for %s", q_id, + inet_ntop(AF_INET, &addr4, buf, sizeof(buf)), username); + if (radius_has_attr(radreq, RADIUS_TYPE_USER_PASSWORD)) + strlcpy(assigned->auth_method, "PAP", + sizeof(assigned->auth_method)); + else if (radius_has_attr(radreq, RADIUS_TYPE_CHAP_PASSWORD)) + strlcpy(assigned->auth_method, "CHAP", + sizeof(assigned->auth_method)); + else if (radius_has_vs_attr(radreq, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP_RESPONSE)) + strlcpy(assigned->auth_method, "MS-CHAP", + sizeof(assigned->auth_method)); + else if (radius_has_vs_attr(radreq, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP2_RESPONSE)) + strlcpy(assigned->auth_method, "MS-CHAP-V2", + sizeof(assigned->auth_method)); + else if (radius_has_attr(radreq, RADIUS_TYPE_EAP_MESSAGE)) + strlcpy(assigned->auth_method, "EAP", + sizeof(assigned->auth_method)); + } + + if (self->name_server[0].s_addr != 0) { + addr4.s_addr = htonl(self->name_server[0].s_addr); + radius_del_vs_attr_all(radres, + RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER); + radius_put_vs_ipv4_attr(radres, + RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER, self->name_server[0]); + } + if (self->name_server[1].s_addr != 0) { + addr4.s_addr = htonl(self->name_server[1].s_addr); + radius_del_vs_attr_all(radres, + RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_SECONDARY_DNS_SERVER); + radius_put_vs_ipv4_attr(radres, + RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_SECONDARY_DNS_SERVER, self->name_server[1]); + } + if (self->netbios_server[0].s_addr != 0) { + addr4.s_addr = htonl(self->netbios_server[0].s_addr); + radius_del_vs_attr_all(radres, + RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER); + radius_put_vs_ipv4_attr(radres, + RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_PRIMARY_DNS_SERVER, + self->netbios_server[0]); + } + if (self->netbios_server[1].s_addr != 0) { + addr4.s_addr = htonl(self->netbios_server[1].s_addr); + radius_del_vs_attr_all(radres, + RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_SECONDARY_NBNS_SERVER); + radius_put_vs_ipv4_attr(radres, + RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_SECONDARY_NBNS_SERVER, + self->netbios_server[1]); + } + if (!self->no_session_timeout && + radius_has_attr(radres, RADIUS_TYPE_SESSION_TIMEOUT)) { + radius_get_uint32_attr(radres, RADIUS_TYPE_SESSION_TIMEOUT, + &assigned->session_timeout); + /* we handle this session-timeout */ + radius_del_attr_all(radres, RADIUS_TYPE_SESSION_TIMEOUT); + } + + accept: + if (module_resdeco_done(self->base, q_id, radius_get_data(radres), + radius_get_length(radres)) == -1) { + log_warn("%s: module_resdeco_done() failed", __func__); + module_stop(self->base); + } + if (radreq != NULL) + radius_delete_packet(radreq); + radius_delete_packet(radres); + return; + reject: + ipcp_reject(self, radreq, q_id, radres, msraserr); + radius_delete_packet(radreq); + radius_delete_packet(radres); + return; + fatal: + if (radreq != NULL) + radius_delete_packet(radreq); + if (radres != NULL) + radius_delete_packet(radres); + module_stop(self->base); +} + +void +ipcp_reject(struct module_ipcp *self, RADIUS_PACKET *reqp, unsigned int q_id, + RADIUS_PACKET *orig_resp, int mserr) +{ + bool is_eap, is_mschap, is_mschap2; + uint8_t attr[256]; + size_t attrlen; + RADIUS_PACKET *resp; + struct { + uint8_t code; + uint8_t id; + uint16_t length; + } __packed eap; + + resp = radius_new_response_packet(RADIUS_CODE_ACCESS_REJECT, reqp); + if (resp == NULL) { + log_warn("%s: radius_new_response_packet() failed", __func__); + module_accsreq_aborted(self->base, q_id); + return; + } + + is_eap = radius_has_attr(reqp, RADIUS_TYPE_EAP_MESSAGE); + if (radius_get_vs_raw_attr(reqp, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP_RESPONSE, attr, &attrlen) == 0) + is_mschap = true; + else if (radius_get_vs_raw_attr(reqp, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP2_RESPONSE, attr, &attrlen) == 0) + is_mschap2 = true; + + if (is_eap) { + memset(&eap, 0, sizeof(eap)); /* just in case */ + eap.code = 1; /* EAP Request */ + attrlen = sizeof(attr); + if (orig_resp != NULL && radius_get_raw_attr(orig_resp, + RADIUS_TYPE_EAP_MESSAGE, &attr, &attrlen) == 0) + eap.id = attr[1]; + else + eap.id = 0; + eap.length = htons(sizeof(eap)); + radius_put_raw_attr(resp, RADIUS_TYPE_EAP_MESSAGE, &eap, + ntohs(eap.length)); + } else if (is_mschap || is_mschap2) { + attr[0] = attr[1]; /* Copy the ident of the request */ + snprintf(attr + 1, sizeof(attr) - 1, "E=%d R=0 V=3", mserr); + radius_put_vs_raw_attr(resp, RADIUS_VENDOR_MICROSOFT, + RADIUS_VTYPE_MS_CHAP_ERROR, attr, strlen(attr + 1) + 1); + } + + module_resdeco_done(self->base, q_id, radius_get_data(resp), + radius_get_length(resp)); + radius_delete_packet(resp); +} + +/*********************************************************************** + * RADIUS Accounting + ***********************************************************************/ +void +ipcp_accounting_request(void *ctx, u_int q_id, const u_char *pkt, + size_t pktlen) +{ + RADIUS_PACKET *radpkt = NULL; + int code, af; + uint32_t type, delay, uval; + struct in_addr addr4, nas_ipv4; + struct in6_addr nas_ipv6, ipv6_zero; + struct module_ipcp *self = ctx; + struct assigned_ipv4 *assign, *assignt; + char username[256], nas_id[256], buf[256], + buf1[80]; + struct timespec dur; + struct radiusd_ipcp_statistics + stat; + struct module_ipcp_dae *dae; + + clock_gettime(CLOCK_BOOTTIME, &self->uptime); + + if ((radpkt = radius_convert_packet(pkt, pktlen)) == NULL) { + log_warn("%s: radius_convert_packet() failed", __func__); + module_stop(self->base); + return; + } + code = radius_get_code(radpkt); + if (code != RADIUS_CODE_ACCOUNTING_REQUEST && + code != RADIUS_CODE_ACCOUNTING_RESPONSE) + goto out; + + if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_STATUS_TYPE, &type) + != 0) + goto out; + + /* identifier for the NAS */ + memset(&ipv6_zero, 0, sizeof(ipv6_zero)); + memset(&nas_ipv4, 0, sizeof(nas_ipv4)); + memset(&nas_ipv6, 0, sizeof(nas_ipv6)); + memset(&nas_id, 0, sizeof(nas_id)); + + radius_get_ipv4_attr(radpkt, RADIUS_TYPE_NAS_IP_ADDRESS, &nas_ipv4); + radius_get_ipv6_attr(radpkt, RADIUS_TYPE_NAS_IPV6_ADDRESS, &nas_ipv6); + radius_get_string_attr(radpkt, RADIUS_TYPE_NAS_IDENTIFIER, nas_id, + sizeof(nas_id)); + + if (nas_ipv4.s_addr == 0 && IN6_ARE_ADDR_EQUAL(&nas_ipv6, &ipv6_zero) && + nas_id[0] == '\0') { + log_warnx("q=%u no NAS-IP-Address, NAS-IPV6-Address, or " + "NAS-Identifier", q_id); + goto out; + } + + if (type == RADIUS_ACCT_STATUS_TYPE_ACCT_ON || + type == RADIUS_ACCT_STATUS_TYPE_ACCT_OFF) { + /* + * NAS or daemon is restarted. Delete all assigned records + * from it + */ + RB_FOREACH_SAFE(assign, assigned_ipv4_tree, &self->ipv4s, + assignt) { + if (assign->nas_ipv4.s_addr != nas_ipv4.s_addr || + !IN6_ARE_ADDR_EQUAL(&assign->nas_ipv6, &nas_ipv6) || + strcmp(assign->nas_id, nas_id) != 0) + continue; + log_info("Delete record for %s", inet_ntop(AF_INET, + &assign->ipv4, buf, sizeof(buf))); + ipcp_del_db(self, assign); + ipcp_ipv4_release(self, assign); + } + return; + } + + if (radius_get_ipv4_attr(radpkt, RADIUS_TYPE_FRAMED_IP_ADDRESS, &addr4) + != 0) + goto out; + if (radius_get_string_attr(radpkt, RADIUS_TYPE_USER_NAME, username, + sizeof(username)) != 0) + goto out; + if ((assign = ipcp_ipv4_find(self, addr4)) == NULL) + /* not assigned by this */ + goto out; + + if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_DELAY_TIME, &delay) + != 0) + delay = 0; + + if (type == RADIUS_ACCT_STATUS_TYPE_START) { + assign->start = self->uptime; + assign->start.tv_sec -= delay; + + if (!self->no_session_timeout && (self->session_timeout > 0 || + assign->session_timeout > 0)) { + assign->timeout = assign->start; + if (self->session_timeout > 0) + assign->timeout.tv_sec += self->session_timeout; + else + assign->timeout.tv_sec += + assign->session_timeout; + } + assign->nas_ipv4 = nas_ipv4; + assign->nas_ipv4 = nas_ipv4; + strlcpy(assign->nas_id, nas_id, sizeof(assign->nas_id)); + + if (radius_get_string_attr(radpkt, RADIUS_TYPE_ACCT_SESSION_ID, + assign->session_id, sizeof(assign->session_id)) != 0) + assign->session_id[0] = '\0'; + if (radius_get_uint32_attr(radpkt, RADIUS_TYPE_TUNNEL_TYPE, + &uval) == 0) + assign->tun_type = radius_tunnel_type_string(uval, + NULL); + if (assign->tun_type == NULL) + assign->tun_type = ""; + + /* + * Get "tunnel from" from Tunnel-Client-Endpoint or Calling- + * Station-Id + */ + af = AF_UNSPEC; + if (radius_get_string_attr(radpkt, + RADIUS_TYPE_TUNNEL_CLIENT_ENDPOINT, buf, sizeof(buf)) == 0) + { + if (radius_get_uint32_attr(radpkt, + RADIUS_TYPE_TUNNEL_MEDIUM_TYPE, &uval) == 0) { + if (uval == RADIUS_TUNNEL_MEDIUM_TYPE_IPV4) + af = AF_INET; + else if (uval == RADIUS_TUNNEL_MEDIUM_TYPE_IPV6) + af = AF_INET6; + } + parse_addr(buf, af, (struct sockaddr *) + &assign->tun_client, sizeof(assign->tun_client)); + } + if (assign->tun_client.sin4.sin_family == 0 && + radius_get_string_attr(radpkt, + RADIUS_TYPE_CALLING_STATION_ID, buf, sizeof(buf)) == 0) + parse_addr(buf, af, (struct sockaddr *) + &assign->tun_client, sizeof(assign->tun_client)); + + TAILQ_FOREACH(dae, &self->daes, next) { + if (dae->nas_id[0] == '\0' || + strcmp(dae->nas_id, assign->nas_id) == 0) + break; + } + assign->dae = dae; + + ipcp_put_db(self, assign); + ipcp_schedule_timer(self); + + if (ipcp_notice_startstop(self, assign, 1, NULL) != 0) + goto fail; + log_info("Start seq=%u user=%s duration=%dsec session=%s " + "tunnel=%s from=%s auth=%s ip=%s", assign->seq, + assign->user->name, delay, assign->session_id, + assign->tun_type, print_addr((struct sockaddr *) + &assign->tun_client, buf1, sizeof(buf1)), + assign->auth_method, inet_ntop(AF_INET, &addr4, buf, + sizeof(buf))); + } else if (type == RADIUS_ACCT_STATUS_TYPE_STOP) { + memset(&stat, 0, sizeof(stat)); + + dur = self->uptime; + dur.tv_sec -= delay; + timespecsub(&dur, &assign->start, &dur); + + if (radius_get_uint32_attr(radpkt, + RADIUS_TYPE_ACCT_INPUT_OCTETS, &uval) == 0) + stat.ibytes = uval; + if (radius_get_uint32_attr(radpkt, + RADIUS_TYPE_ACCT_INPUT_GIGAWORDS, &uval) == 0) + stat.ibytes = ((uint64_t)uval << 32) | stat.ibytes; + if (radius_get_uint32_attr(radpkt, + RADIUS_TYPE_ACCT_OUTPUT_OCTETS, &uval) == 0) + stat.obytes = uval; + if (radius_get_uint32_attr(radpkt, + RADIUS_TYPE_ACCT_OUTPUT_GIGAWORDS, &uval) == 0) + stat.obytes = ((uint64_t)uval << 32) | stat.obytes; + radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_INPUT_PACKETS, + &stat.ipackets); + radius_get_uint32_attr(radpkt, RADIUS_TYPE_ACCT_OUTPUT_PACKETS, + &stat.opackets); + + if (radius_get_uint32_attr(radpkt, + RADIUS_TYPE_ACCT_TERMINATE_CAUSE, &uval) == 0) + strlcpy(stat.cause, radius_terminate_cause_string(uval), + sizeof(stat.cause)); + + log_info("Stop seq=%u user=%s duration=%lldsec session=%s " + "tunnel=%s from=%s auth=%s ip=%s datain=%"PRIu64"bytes,%" + PRIu32"packets dataout=%"PRIu64"bytes,%"PRIu32"packets " + "cause=\"%s\"", + assign->seq, assign->user->name, dur.tv_sec, + assign->session_id, assign->tun_type, print_addr( + (struct sockaddr *)&assign->tun_client, buf1, sizeof(buf1)), + assign->auth_method, inet_ntop(AF_INET, &addr4, buf, + sizeof(buf)), stat.ibytes, stat.ipackets, stat.obytes, + stat.opackets, stat.cause); + + ipcp_del_db(self, assign); + if (ipcp_notice_startstop(self, assign, 0, &stat) != 0) + goto fail; + ipcp_ipv4_release(self, ipcp_ipv4_find(self, addr4)); + } + out: + radius_delete_packet(radpkt); + return; + fail: + module_stop(self->base); + radius_delete_packet(radpkt); + return; +} + +/*********************************************************************** + * On memory database to manage IP address assignment + ***********************************************************************/ +struct assigned_ipv4 * +ipcp_ipv4_assign(struct module_ipcp *self, struct user *user, + struct in_addr ina) +{ + struct assigned_ipv4 *ip; + + ip = calloc(1, sizeof(struct assigned_ipv4)); + if (ip == NULL) { + log_warn("%s: calloc()", __func__); + return (NULL); + } + ip->ipv4 = ina; + ip->user = user; + ip->authtime = self->uptime; + RB_INSERT(assigned_ipv4_tree, &self->ipv4s, ip); + TAILQ_INSERT_TAIL(&user->ipv4s, ip, next); + self->nsessions++; + ip->seq = self->seq++; + + return (ip); +} + +struct assigned_ipv4 * +ipcp_ipv4_find(struct module_ipcp *self, struct in_addr ina) +{ + struct assigned_ipv4 key, *ret; + struct timespec dif; + + key.ipv4 = ina; + ret = RB_FIND(assigned_ipv4_tree, &self->ipv4s, &key); + if (ret != NULL && ret->start.tv_sec == 0) { + /* not yet assigned */ + timespecsub(&self->uptime, &ret->authtime, &dif); + if (dif.tv_sec >= self->start_wait) { + /* assumed NAS finally didn't use the address */ + TAILQ_REMOVE(&ret->user->ipv4s, ret, next); + RB_REMOVE(assigned_ipv4_tree, &self->ipv4s, ret); + free(ret); + ret = NULL; + self->nsessions--; + } + } + return (ret); +} + +void +ipcp_ipv4_release(struct module_ipcp *self, struct assigned_ipv4 *assign) +{ + if (assign != NULL) { + TAILQ_REMOVE(&assign->user->ipv4s, assign, next); + RB_REMOVE(assigned_ipv4_tree, &self->ipv4s, assign); + self->nsessions--; + if (assign->dae != NULL) { + if (assign->dae_ntry > 0) { + TAILQ_REMOVE(&assign->dae->reqs, assign, + dae_next); + if (evtimer_pending(&assign->dae_evtimer, NULL)) + evtimer_del(&assign->dae_evtimer); + } + } + if (assign->dae_reqpkt != NULL) + radius_delete_packet(assign->dae_reqpkt); + if (evtimer_pending(&assign->dae_evtimer, NULL)) + evtimer_del(&assign->dae_evtimer); + free(assign); + } +} + +int +assigned_ipv4_compar(struct assigned_ipv4 *a, struct assigned_ipv4 *b) +{ + return (b->ipv4.s_addr - a->ipv4.s_addr); +} + +struct user * +ipcp_user_get(struct module_ipcp *self, const char *username) +{ + struct { + struct user user; + char name[256]; + } key; + struct user *elm; + + strlcpy(key.user.name, username, 256); + elm = RB_FIND(user_tree, &self->users, &key.user); + if (elm == NULL) { + if ((elm = calloc(1, offsetof(struct user, name[ + strlen(username) + 1]))) == NULL) + return (NULL); + memcpy(elm->name, username, strlen(username)); + RB_INSERT(user_tree, &self->users, elm); + TAILQ_INIT(&elm->ipv4s); + } + + return (elm); +} + +int +user_compar(struct user *a, struct user *b) +{ + return (strcmp(a->name, b->name)); +} + +RB_GENERATE_STATIC(assigned_ipv4_tree, assigned_ipv4, tree, + assigned_ipv4_compar); +RB_GENERATE_STATIC(user_tree, user, tree, user_compar); + +/*********************************************************************** + * DB for the persistent over processes + ***********************************************************************/ +int +ipcp_prepare_db(void) +{ + struct passwd *pw; + DB *db; + + if ((db = dbopen(_PATH_RADIUSD_IPCP_DB, O_CREAT | O_RDWR | O_EXLOCK, + 0600, DB_BTREE, NULL)) == NULL) + return (-1); + if ((pw = getpwnam(RADIUSD_USER)) == NULL) + return (-1); + fchown(db->fd(db), pw->pw_uid, pw->pw_gid); + db->close(db); + + return (0); +} + +int +ipcp_restore_from_db(struct module_ipcp *self) +{ + DB *db; + DBT key, val; + char keybuf[128]; + struct user *user; + struct radiusd_ipcp_db_record + *record; + struct assigned_ipv4 *assigned; + struct in_addr ipv4; + struct module_ipcp_dae *dae; + + if ((db = dbopen(_PATH_RADIUSD_IPCP_DB, O_RDONLY | O_SHLOCK, 0600, + DB_BTREE, NULL)) == NULL) + return (-1); + + key.data = "ipv4/"; + key.size = 5; + if (db->seq(db, &key, &val, R_CURSOR) == 0) { + do { + if (key.size >= sizeof(keybuf)) + break; + memcpy(keybuf, key.data, key.size); + keybuf[key.size] = '\0'; + if (strncmp(keybuf, "ipv4/", 5) != 0) + break; + inet_pton(AF_INET, keybuf + 5, &ipv4); + record = (struct radiusd_ipcp_db_record *)val.data; + if ((user = ipcp_user_get(self, record->username)) + == NULL) + return (-1); + if ((assigned = ipcp_ipv4_assign(self, user, ipv4)) + == NULL) + return (-1); + self->seq = MAXIMUM(assigned->seq + 1, self->seq); + assigned->seq = record->seq; + strlcpy(assigned->auth_method, record->auth_method, + sizeof(assigned->auth_method)); + strlcpy(assigned->session_id, record->session_id, + sizeof(assigned->session_id)); + assigned->start = record->start; + assigned->timeout = record->timeout; + assigned->nas_ipv4 = record->nas_ipv4; + assigned->nas_ipv6 = record->nas_ipv6; + strlcpy(assigned->nas_id, record->nas_id, + sizeof(assigned->nas_id)); + assigned->tun_type = radius_tunnel_type_string(0, + record->tun_type); + memcpy(&assigned->tun_client, &record->tun_client, + sizeof(assigned->tun_client)); + + TAILQ_FOREACH(dae, &self->daes, next) { + if (dae->nas_id[0] == '\0' || + strcmp(dae->nas_id, assigned->nas_id) == 0) + break; + } + assigned->dae = dae; + } while (db->seq(db, &key, &val, R_NEXT) == 0); + } + db->close(db); + + return (0); +} + +void +ipcp_put_db(struct module_ipcp *self, struct assigned_ipv4 *assigned) +{ + DB *db; + DBT key, val; + char keybuf[128]; + struct radiusd_ipcp_db_record + record; + + strlcpy(keybuf, "ipv4/", sizeof(keybuf)); + inet_ntop(AF_INET, &assigned->ipv4, keybuf + 5, sizeof(keybuf) - 5); + key.data = keybuf; + key.size = strlen(keybuf); + strlcpy(record.session_id, assigned->session_id, + sizeof(record.session_id)); + strlcpy(record.auth_method, assigned->auth_method, + sizeof(record.auth_method)); + strlcpy(record.username, assigned->user->name, sizeof(record.username)); + record.seq = assigned->seq; + record.start = assigned->start; + record.timeout = assigned->timeout; + record.nas_ipv4 = assigned->nas_ipv4; + record.nas_ipv6 = assigned->nas_ipv6; + strlcpy(record.nas_id, assigned->nas_id, sizeof(record.nas_id)); + if (assigned->tun_type != NULL) + strlcpy(record.tun_type, assigned->tun_type, + sizeof(record.tun_type)); + memcpy(&record.tun_client, &assigned->tun_client, + sizeof(record.tun_client)); + + val.data = &record; + val.size = sizeof(record); + if ((db = dbopen(_PATH_RADIUSD_IPCP_DB, O_RDWR | O_EXLOCK, 0600, + DB_BTREE, NULL)) == NULL) + return; + db->put(db, &key, &val, 0); + db->close(db); +} + +void +ipcp_del_db(struct module_ipcp *self, struct assigned_ipv4 *assigned) +{ + DB *db; + DBT key; + char keybuf[128]; + + strlcpy(keybuf, "ipv4/", sizeof(keybuf)); + inet_ntop(AF_INET, &assigned->ipv4, keybuf + 5, sizeof(keybuf) - 5); + key.data = keybuf; + key.size = strlen(keybuf); + + if ((db = dbopen(_PATH_RADIUSD_IPCP_DB, O_RDWR | O_EXLOCK, 0600, + DB_BTREE, NULL)) == NULL) + return; + db->del(db, &key, 0); + db->close(db); +} + +void +ipcp_db_dump_fill_record(struct radiusd_ipcp_db_dump *dump, int idx, + struct assigned_ipv4 *assign) +{ + dump->records[idx].af = AF_INET; + dump->records[idx].addr.ipv4 = assign->ipv4; + dump->records[idx].rec.seq = assign->seq; + strlcpy(dump->records[idx].rec.session_id, assign->session_id, + sizeof(dump->records[idx].rec.session_id)); + strlcpy(dump->records[idx].rec.auth_method, assign->auth_method, + sizeof(dump->records[idx].rec.auth_method)); + strlcpy(dump->records[idx].rec.username, assign->user->name, + sizeof(dump->records[idx].rec.username)); + dump->records[idx].rec.start = assign->start; + dump->records[idx].rec.timeout = assign->timeout; + dump->records[idx].rec.nas_ipv4 = assign->nas_ipv4; + dump->records[idx].rec.nas_ipv6 = assign->nas_ipv6; + strlcpy(dump->records[idx].rec.nas_id, assign->nas_id, + sizeof(dump->records[idx].rec.nas_id)); + if (assign->tun_type != NULL) + strlcpy(dump->records[idx].rec.tun_type, assign->tun_type, + sizeof(dump->records[idx].rec.tun_type)); + memcpy(&dump->records[idx].rec.tun_client, &assign->tun_client, + sizeof(dump->records[idx].rec.tun_client)); +} + +/*********************************************************************** + * Timer + ***********************************************************************/ +void +ipcp_on_timer(int fd, short ev, void *ctx) +{ + struct module_ipcp *self = ctx; + + clock_gettime(CLOCK_BOOTTIME, &self->uptime); + ipcp_schedule_timer(self); +} + +void +ipcp_schedule_timer(struct module_ipcp *self) +{ + struct assigned_ipv4 *assign, *min_assign = NULL; + struct timespec tsd; + struct timeval tv; + + /* check session timeout */ + RB_FOREACH(assign, assigned_ipv4_tree, &self->ipv4s) { + if (assign->timeout.tv_sec == 0) + continue; + if (timespeccmp(&assign->timeout, &self->uptime, <=)) { + log_info("Reached session timeout seq=%u", assign->seq); + ipcp_dae_send_disconnect_request(assign); + memset(&assign->timeout, 0, sizeof(assign->timeout)); + ipcp_put_db(self, assign); + } + if (min_assign == NULL || + timespeccmp(&min_assign->timeout, &assign->timeout, >)) + min_assign = assign; + } + if (evtimer_pending(&self->ev_timer, NULL)) + evtimer_del(&self->ev_timer); + + if (min_assign != NULL) { + timespecsub(&min_assign->timeout, &self->uptime, &tsd); + TIMESPEC_TO_TIMEVAL(&tv, &tsd); + evtimer_set(&self->ev_timer, ipcp_on_timer, self); + evtimer_add(&self->ev_timer, &tv); + } +} + +/*********************************************************************** + * Dynamic Authorization Extension for RAIDUS (RFC 5176) + ***********************************************************************/ +static const int dae_request_timeouts[] = { 2, 4, 8, 8 }; + +void +ipcp_dae_send_disconnect_request(struct assigned_ipv4 *assign) +{ + RADIUS_PACKET *reqpkt = NULL; + struct timeval tv; + char buf[80]; + + if (assign->dae == NULL) + return; /* DAE is not configured */ + + if (assign->dae_ntry == 0) + + if (assign->dae_reqpkt != NULL) { + radius_delete_packet(assign->dae_reqpkt); + assign->dae_reqpkt = NULL; + } + + reqpkt = radius_new_request_packet(RADIUS_CODE_DISCONNECT_REQUEST); + + radius_put_string_attr(reqpkt, RADIUS_TYPE_ACCT_SESSION_ID, + assign->session_id); + + radius_set_accounting_request_authenticator(reqpkt, + assign->dae->secret); + + if (radius_send(assign->dae->sock, reqpkt, 0) < 0) + log_warn("%s: sendto: %m", __func__); + + if (assign->dae_ntry == 0) + log_info("Sending Disconnect-Request seq=%u to %s", + assign->seq, print_addr((struct sockaddr *) + &assign->dae->nas_addr, buf, sizeof(buf))); + + assign->dae_reqpkt = reqpkt; + tv.tv_sec = dae_request_timeouts[assign->dae_ntry]; + tv.tv_usec = 0; + evtimer_set(&assign->dae_evtimer, ipcp_dae_request_on_timeout, assign); + evtimer_add(&assign->dae_evtimer, &tv); + + if (assign->dae_ntry++ == 0) + TAILQ_INSERT_TAIL(&assign->dae->reqs, assign, dae_next); +} + +void +ipcp_dae_request_on_timeout(int fd, short ev, void *ctx) +{ + struct assigned_ipv4 *assign = ctx; + char buf[80]; + + if (assign->dae_ntry >= (int)nitems(dae_request_timeouts)) + log_warnx("No answer for Disconnect-Request seq=%u from %s", + assign->seq, print_addr((struct sockaddr *) + &assign->dae->nas_addr, buf, sizeof(buf))); + else + ipcp_dae_send_disconnect_request(assign); +} + +void +ipcp_dae_on_event(int fd, short ev, void *ctx) +{ + struct module_ipcp_dae *dae = ctx; + RADIUS_PACKET *radres = NULL; + int code; + uint32_t u32; + struct assigned_ipv4 *assign; + char buf[80], causestr[80]; + const char *cause; + + if ((ev & EV_READ) == 0) + return; + + if ((radres = radius_recv(dae->sock, 0)) == NULL) { + if (errno == EAGAIN) + return; + log_warn("Failed to receive from %s", print_addr( + (struct sockaddr *)&dae->nas_addr, buf, sizeof(buf))); + return; + } + TAILQ_FOREACH(assign, &dae->reqs, dae_next) { + if (radius_get_id(assign->dae_reqpkt) == radius_get_id(radres)) + break; + } + if (assign == NULL) { + log_warnx("Received RADIUS packet from %s has unknown id=%d", + print_addr((struct sockaddr *)&dae->nas_addr, buf, + sizeof(buf)), radius_get_id(radres)); + return; + } + + radius_set_request_packet(radres, assign->dae_reqpkt); + if ((radius_check_response_authenticator(radres, dae->secret)) != 0) { + log_warnx("Received RADIUS packet for seq=%u from %s has a bad " + "authenticator", assign->seq, print_addr( + (struct sockaddr *)&dae->nas_addr, buf, + sizeof(buf))); + return; + } + causestr[0] = '\0'; + if (radius_get_uint32_attr(radres, RADIUS_TYPE_ERROR_CAUSE, &u32) == 0){ + cause = radius_error_cause_string(u32); + if (cause != NULL) + snprintf(causestr, sizeof(causestr), " cause=%u(%s)", + u32, cause); + else + snprintf(causestr, sizeof(causestr), " cause=%u", u32); + } + + code = radius_get_code(radres); + switch (code) { + case RADIUS_CODE_DISCONNECT_ACK: + log_info("Received Disconnect-ACK for seq=%u from %s%s", + assign->seq, print_addr((struct sockaddr *) + &dae->nas_addr, buf, sizeof(buf)), cause); + evtimer_del(&assign->dae_evtimer); + break; + case RADIUS_CODE_DISCONNECT_NAK: + log_warnx("Received Disconnect-NAK for seq=%u from %s%s", + assign->seq, print_addr((struct sockaddr *) + &dae->nas_addr, buf, sizeof(buf)), cause); + evtimer_del(&assign->dae_evtimer); + break; + default: + log_warn("Received unknown code=%d for id=%u from %s", + code, assign->seq, print_addr((struct sockaddr *) + &dae->nas_addr, buf, sizeof(buf))); + break; + } +} + +/*********************************************************************** + * Miscellaneous functions + ***********************************************************************/ +struct ipcp_address * +parse_address_range(const char *range) +{ + char *buf, *sep; + int masklen; + uint32_t mask; + struct in_addr start, end; + struct ipcp_address *ret; + const char *errstr; + + buf = strdup(range); + if (buf == NULL) + goto error; + if ((sep = strchr(buf, '-')) != NULL) { + *sep = '\0'; + if (inet_aton(buf, &start) != 1) + goto error; + else if (inet_aton(++sep, &end) != 1) + goto error; + start.s_addr = ntohl(start.s_addr); + end.s_addr = ntohl(end.s_addr); + } else { + if ((sep = strchr(buf, '/')) != NULL) { + *sep = '\0'; + if (inet_aton(buf, &start) != 1) + goto error; + masklen = strtonum(++sep, 0, 32, &errstr); + if (errstr != NULL) + goto error; + } else { + if (inet_aton(buf, &start) != 1) + goto error; + masklen = 32; + } + mask = 0xFFFFFFFFUL; + if (masklen < 32) + mask <<= (32 - masklen); + start.s_addr = ntohl(start.s_addr) & mask; + if (masklen == 32) + end = start; + else if (masklen == 31) + end.s_addr = start.s_addr + 1; + else { + end.s_addr = start.s_addr + (1 << (32 - masklen)) - 2; + start.s_addr = start.s_addr + 1; + } + } + free(buf); + if ((ret = calloc(1, sizeof(struct ipcp_address))) == NULL) + return (NULL); + ret->start = start; + ret->end = end; + ret->naddrs = end.s_addr - start.s_addr + 1; + return (ret); + error: + free(buf); + return (NULL); +} + +const char * +radius_tunnel_type_string(unsigned val, const char *label) +{ + unsigned int i; + struct { + const unsigned constval; + const char *label; + } tunnel_types[] = { + { RADIUS_TUNNEL_TYPE_PPTP, "PPTP" }, + { RADIUS_TUNNEL_TYPE_L2F, "L2F" }, + { RADIUS_TUNNEL_TYPE_L2TP, "L2TP" }, + { RADIUS_TUNNEL_TYPE_ATMP, "ATMP" }, + { RADIUS_TUNNEL_TYPE_VTP, "VTP" }, + { RADIUS_TUNNEL_TYPE_AH, "AH" }, + { RADIUS_TUNNEL_TYPE_IP, "IP" }, + { RADIUS_TUNNEL_TYPE_MOBILE, "MIN-IP-IP" }, + { RADIUS_TUNNEL_TYPE_ESP, "ESP" }, + { RADIUS_TUNNEL_TYPE_GRE, "GRE" }, + { RADIUS_TUNNEL_TYPE_VDS, "DVS" }, + /* [MS-RNAS] 3.3.5.1.9 Tunnel-Type */ + { RADIUS_VENDOR_MICROSOFT << 8 | 1, + "SSTP" } + }; + + if (label != NULL) { /* for conversion to the const value */ + for (i = 0; i < nitems(tunnel_types); i++) { + if (strcmp(tunnel_types[i].label, label) == 0) + return (tunnel_types[i].label); + } + } + + for (i = 0; i < nitems(tunnel_types); i++) { + if (tunnel_types[i].constval == val) + return (tunnel_types[i].label); + } + + return (NULL); +} + +const char * +radius_terminate_cause_string(unsigned val) +{ + unsigned int i; + struct { + const unsigned constval; + const char *label; + } terminate_causes[] = { + { RADIUS_TERMNATE_CAUSE_USER_REQUEST, "User Request" }, + { RADIUS_TERMNATE_CAUSE_LOST_CARRIER, "Lost Carrier" }, + { RADIUS_TERMNATE_CAUSE_LOST_SERVICE, "Lost Service" }, + { RADIUS_TERMNATE_CAUSE_IDLE_TIMEOUT, "Idle Timeout" }, + { RADIUS_TERMNATE_CAUSE_SESSION_TIMEOUT, "Session Timeout" }, + { RADIUS_TERMNATE_CAUSE_ADMIN_RESET, "Admin Reset" }, + { RADIUS_TERMNATE_CAUSE_ADMIN_REBOOT, "Admin Reboot" }, + { RADIUS_TERMNATE_CAUSE_PORT_ERROR, "Port Error" }, + { RADIUS_TERMNATE_CAUSE_NAS_ERROR, "NAS Error" }, + { RADIUS_TERMNATE_CAUSE_NAS_RESET, "NAS Request" }, + { RADIUS_TERMNATE_CAUSE_NAS_REBOOT, "NAS Reboot" }, + { RADIUS_TERMNATE_CAUSE_PORT_UNNEEDED, "Port Unneeded" }, + { RADIUS_TERMNATE_CAUSE_PORT_PREEMPTED, "Port Preempted" }, + { RADIUS_TERMNATE_CAUSE_PORT_SUSPENDED, "Port Suspended" }, + { RADIUS_TERMNATE_CAUSE_SERVICE_UNAVAIL, "Service Unavailable" }, + { RADIUS_TERMNATE_CAUSE_CALLBACK, "Callback" }, + { RADIUS_TERMNATE_CAUSE_USER_ERROR, "User Error" }, + { RADIUS_TERMNATE_CAUSE_HOST_REQUEST, "Host Request" }, + }; + + for (i = 0; i < nitems(terminate_causes); i++) { + if (terminate_causes[i].constval == val) + return (terminate_causes[i].label); + } + + return (NULL); +} + +const char * +radius_error_cause_string(unsigned val) +{ + unsigned int i; + struct { + const unsigned constval; + const char *label; + } error_causes[] = { + { RADIUS_ERROR_CAUSE_RESIDUAL_SESSION_REMOVED, + "Residual Session Context Removed" }, + { RADIUS_ERROR_CAUSE_INVALID_EAP_PACKET, + "Invalid EAP Packet (Ignored)" }, + { RADIUS_ERROR_CAUSE_UNSUPPORTED_ATTRIBUTE, + "Unsupported Attribute" }, + { RADIUS_ERROR_CAUSE_MISSING_ATTRIBUTE, + "Missing Attribute" }, + { RADIUS_ERROR_CAUSE_NAS_IDENTIFICATION_MISMATCH, + "NAS Identification Mismatch" }, + { RADIUS_ERROR_CAUSE_INVALID_REQUEST, + "Invalid Request" }, + { RADIUS_ERROR_CAUSE_UNSUPPORTED_SERVICE, + "Unsupported Service" }, + { RADIUS_ERROR_CAUSE_UNSUPPORTED_EXTENSION, + "Unsupported Extension" }, + { RADIUS_ERROR_CAUSE_INVALID_ATTRIBUTE_VALUE, + "Invalid Attribute Valu" }, + { RADIUS_ERROR_CAUSE_ADMINISTRATIVELY_PROHIBITED, + "Administratively Prohibited" }, + { RADIUS_ERROR_CAUSE_REQUEST_NOT_ROUTABLE, + "Request Not Routable (Proxy)" }, + { RADIUS_ERROR_CAUSE_SESSION_NOT_FOUND, + "Session Context Not Found" }, + { RADIUS_ERROR_CAUSE_SESSION_NOT_REMOVABLE, + "Session Context Not Removable" }, + { RADIUS_ERROR_CAUSE_OTHER_PROXY_PROCESSING_ERROR, + "Other Proxy Processing Error" }, + { RADIUS_ERROR_CAUSE_RESOURCES_UNAVAILABLE, + "Resources Unavailable" }, + { RADIUS_ERROR_CAUSE_REQUEST_INITIATED, + "equest Initiated" }, + { RADIUS_ERROR_CAUSE_MULTI_SELECTION_UNSUPPORTED, + "Multiple Session Selection Unsupported" } + }; + + for (i = 0; i < nitems(error_causes); i++) { + if (error_causes[i].constval == val) + return (error_causes[i].label); + } + + return (NULL); +} + +int +parse_addr(const char *str0, int af, struct sockaddr *sa, socklen_t salen) +{ + int error; + char *str, *end, *colon, *colon0, *addr = NULL, *port = NULL; + char *sb, *sb0; + struct addrinfo hints, *ai; + + if ((str = strdup(str0)) == NULL) + return (-1); + if (*str == '[' && (end = strchr(str + 1, ']')) != NULL) { + addr = str + 1; + *end = '\0'; + if (*(end + 1) == ':') + port = end + 2; + else if (*(end + 1) == '[' && (sb = strrchr(end + 2, ']')) + != NULL) { + port = end + 2; + *sb = '\0'; + } + } else if ((sb0 = strchr(str, '[')) != NULL && + (sb = strrchr(sb0 + 1, ']')) != NULL && sb0 < sb) { + addr = str; + *sb0 = '\0'; + port = sb0 + 1; + *sb = '\0'; + } else if ((colon0 = strchr(str, ':')) != NULL && + (colon = strrchr(str, ':')) != NULL && colon0 == colon) { + /* has one : */ + addr = str; + *colon = '\0'; + port = colon + 1; + } else { + addr = str; + port = NULL; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = af; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_NUMERICHOST; + if (port != NULL) + hints.ai_flags |= AI_NUMERICSERV; + if ((error = getaddrinfo(addr, port, &hints, &ai)) != 0) { + free(str); + return (-1); + } + if (salen < ai->ai_addrlen) { + freeaddrinfo(ai); + free(str); + return (-1); + } + memcpy(sa, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); + + return (0); +} + +const char * +print_addr(struct sockaddr *sa, char *buf, size_t bufsiz) +{ + int noport, ret; + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + + if (ntohs(((struct sockaddr_in *)sa)->sin_port) == 0) { + noport = 1; + ret = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST); + } else { + noport = 0; + ret = getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), sbuf, + sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); + } + if (ret != 0) + return ""; + if (noport) + strlcpy(buf, hbuf, bufsiz); + else if (sa->sa_family == AF_INET6) + snprintf(buf, bufsiz, "[%s]:%s", hbuf, sbuf); + else + snprintf(buf, bufsiz, "%s:%s", hbuf, sbuf); + + return (buf); +} diff --git a/usr.sbin/radiusd/radiusd_ipcp.h b/usr.sbin/radiusd/radiusd_ipcp.h new file mode 100644 index 00000000000..e86bbd09e5a --- /dev/null +++ b/usr.sbin/radiusd/radiusd_ipcp.h @@ -0,0 +1,75 @@ +/* $OpenBSD: radiusd_ipcp.h,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */ + +/* + * Copyright (c) 2024 Internet Initiative Japan Inc. + * + * 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. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef RADIUSD_IPCP_H +#define RADIUSD_IPCP_H 1 + +#include +#include + +#include "radiusd.h" + +enum imsg_module_ipcp_type { + IMSG_RADIUSD_MODULE_IPCP_DUMP = IMSG_RADIUSD_MODULE_MIN, + IMSG_RADIUSD_MODULE_IPCP_MONITOR, + IMSG_RADIUSD_MODULE_IPCP_DUMP_AND_MONITOR, + IMSG_RADIUSD_MODULE_IPCP_START, + IMSG_RADIUSD_MODULE_IPCP_STOP, + IMSG_RADIUSD_MODULE_IPCP_DISCONNECT +}; + +#define _PATH_RADIUSD_IPCP_DB "/var/run/radiusd_ipcp.db" + +struct radiusd_ipcp_db_record { + unsigned seq; + char session_id[256]; + char auth_method[16]; + char username[256]; + struct timespec start; /* Start time in boottime */ + struct timespec timeout;/* Timeout time in boottime */ + struct in_addr nas_ipv4; + struct in6_addr nas_ipv6; + char nas_id[256]; + char tun_type[8]; + union { + struct sockaddr_in sin4; + struct sockaddr_in6 sin6; + } tun_client; +}; + +struct radiusd_ipcp_db_dump { + int islast; + struct { + int af; + union { + struct in_addr ipv4; + struct in6_addr ipv6; + } addr; + struct radiusd_ipcp_db_record + rec; + } records[0]; +}; + +struct radiusd_ipcp_statistics { + uint32_t ipackets; + uint32_t opackets; + uint64_t ibytes; + uint64_t obytes; + char cause[80]; +}; +#endif diff --git a/usr.sbin/radiusd/radiusd_ipcp/Makefile b/usr.sbin/radiusd/radiusd_ipcp/Makefile new file mode 100644 index 00000000000..81d90b91534 --- /dev/null +++ b/usr.sbin/radiusd/radiusd_ipcp/Makefile @@ -0,0 +1,11 @@ +# $OpenBSD: Makefile,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ + +PROG= radiusd_ipcp +BINDIR= /usr/libexec/radiusd +SRCS= radiusd_ipcp.c radiusd_module.c log.c +CFLAGS+= -DUSE_LIBEVENT +LDADD+= -lradius -lcrypto -lutil -levent +DPADD+= ${LIBRADIUS} ${LIBCRYPTO} ${LIBUTIL} ${LIBEVENT} +MAN= radiusd_ipcp.8 + +.include diff --git a/usr.sbin/radiusd/radiusd_local.h b/usr.sbin/radiusd/radiusd_local.h index a36373c827c..4fed590c077 100644 --- a/usr.sbin/radiusd/radiusd_local.h +++ b/usr.sbin/radiusd/radiusd_local.h @@ -1,4 +1,4 @@ -/* $OpenBSD: radiusd_local.h,v 1.11 2024/07/02 00:33:51 yasuoka Exp $ */ +/* $OpenBSD: radiusd_local.h,v 1.12 2024/07/09 17:26:14 yasuoka Exp $ */ /* * Copyright (c) 2013 Internet Initiative Japan Inc. @@ -16,6 +16,9 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifndef RADIUSD_LOCAL_H +#define RADIUSD_LOCAL_H 1 + #include /* for struct sockaddr_storage */ #include /* for TAILQ_* */ #include /* for struct sockaddr_in* */ @@ -30,6 +33,7 @@ #define MODULE_IO_TIMEOUT 2000 #define CONFFILE "/etc/radiusd.conf" + struct radius_query; /* forward declaration */ struct radiusd_addr { @@ -136,6 +140,16 @@ struct radius_query { TAILQ_ENTRY(radius_query) next; struct radiusd_module_ref *deco; }; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + short events; +}; + +extern struct radiusd *radiusd_s; + #ifndef nitems #define nitems(_x) (sizeof((_x)) / sizeof((_x)[0])) #endif @@ -182,9 +196,20 @@ void radiusd_module_unload(struct radiusd_module *); void radiusd_access_request_answer(struct radius_query *); void radiusd_access_request_aborted(struct radius_query *); +int radiusd_imsg_compose_module(struct radiusd *, const char *, + uint32_t, uint32_t, pid_t, int, void *, size_t); void radius_attr_hide(const char *, const char *, const u_char *, u_char *, int); void radius_attr_unhide(const char *, const char *, const u_char *, u_char *, int); -int radiusd_module_set(struct radiusd_module *, const char *, int, char * const *); +int radiusd_module_set(struct radiusd_module *, const char *, int, + char * const *); + +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, uint32_t, uint32_t, pid_t, + int, void *, size_t); +int imsg_composev_event (struct imsgev *, uint32_t, uint32_t, + pid_t, int, struct iovec *, int); + +#endif diff --git a/usr.sbin/radiusd/radiusd_module.c b/usr.sbin/radiusd/radiusd_module.c index 9a2b6360632..33fe5476365 100644 --- a/usr.sbin/radiusd/radiusd_module.c +++ b/usr.sbin/radiusd/radiusd_module.c @@ -1,4 +1,4 @@ -/* $OpenBSD: radiusd_module.c,v 1.17 2024/07/02 00:33:51 yasuoka Exp $ */ +/* $OpenBSD: radiusd_module.c,v 1.18 2024/07/09 17:26:14 yasuoka Exp $ */ /* * Copyright (c) 2015 YASUOKA Masahiko @@ -52,6 +52,7 @@ static void (*module_response_decoration) (void *, u_int, const u_char *, size_t, const u_char *, size_t) = NULL; static void (*module_accounting_request) (void *, u_int, const u_char *, size_t) = NULL; +static void (*module_dispatch_control) (void *, struct imsg *) = NULL; struct module_base { void *ctx; @@ -103,6 +104,7 @@ module_create(int sock, void *ctx, struct module_handlers *handler) module_accounting_request = handler->accounting_request; module_start_module = handler->start; module_stop_module = handler->stop; + module_dispatch_control = handler->dispatch_control; return (base); } @@ -161,6 +163,8 @@ module_load(struct module_base *base) load.cap |= RADIUSD_MODULE_CAP_RESDECO; if (module_accounting_request != NULL) load.cap |= RADIUSD_MODULE_CAP_ACCTREQ; + if (module_dispatch_control != NULL) + load.cap |= RADIUSD_MODULE_CAP_CONTROL; imsg_compose(&base->ibuf, IMSG_RADIUSD_MODULE_LOAD, 0, 0, -1, &load, sizeof(load)); imsg_flush(&base->ibuf); @@ -564,6 +568,20 @@ module_imsg_handler(struct module_base *base, struct imsg *imsg) accsreq_out: break; } + case IMSG_RADIUSD_MODULE_CTRL_UNBIND: + goto forward_msg; + break; + default: + if (imsg->hdr.type >= IMSG_RADIUSD_MODULE_MIN) { + forward_msg: + if (module_dispatch_control == NULL) { + const char msg[] = + "the module doesn't handle any controls"; + imsg_compose(&base->ibuf, IMSG_NG, + imsg->hdr.peerid, 0, -1, msg, sizeof(msg)); + } else + module_dispatch_control(base->ctx, imsg); + } } return (0); @@ -638,3 +656,29 @@ module_reset_event(struct module_base *base) syslog(LOG_ERR, "event_add() failed in %s()", __func__); #endif } + +int +module_imsg_compose(struct module_base *base, uint32_t type, uint32_t id, + pid_t pid, int fd, const void *data, size_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&base->ibuf, type, id, pid, fd, data, datalen)) + != -1) + module_reset_event(base); + + return (ret); +} + +int +module_imsg_composev(struct module_base *base, uint32_t type, uint32_t id, + pid_t pid, int fd, const struct iovec *iov, int iovcnt) +{ + int ret; + + if ((ret = imsg_composev(&base->ibuf, type, id, pid, fd, iov, iovcnt)) + != -1) + module_reset_event(base); + + return (ret); +}