Add radiusd_ipcp(8). A module which provides IP configuration through
authoryasuoka <yasuoka@openbsd.org>
Tue, 9 Jul 2024 17:26:14 +0000 (17:26 +0000)
committeryasuoka <yasuoka@openbsd.org>
Tue, 9 Jul 2024 17:26:14 +0000 (17:26 +0000)
RADIUS Access-Accept messages and manages IP address pool through
RADIUS accounting messages.

21 files changed:
usr.sbin/radiusctl/Makefile
usr.sbin/radiusctl/json.c [new file with mode: 0644]
usr.sbin/radiusctl/json.h [new file with mode: 0644]
usr.sbin/radiusctl/parser.c
usr.sbin/radiusctl/parser.h
usr.sbin/radiusctl/radiusctl.8
usr.sbin/radiusctl/radiusctl.c
usr.sbin/radiusd/Makefile
usr.sbin/radiusd/control.c [new file with mode: 0644]
usr.sbin/radiusd/control.h [new file with mode: 0644]
usr.sbin/radiusd/parse.y
usr.sbin/radiusd/radiusd.c
usr.sbin/radiusd/radiusd.conf.5
usr.sbin/radiusd/radiusd.h
usr.sbin/radiusd/radiusd/Makefile
usr.sbin/radiusd/radiusd_ipcp.8 [new file with mode: 0644]
usr.sbin/radiusd/radiusd_ipcp.c [new file with mode: 0644]
usr.sbin/radiusd/radiusd_ipcp.h [new file with mode: 0644]
usr.sbin/radiusd/radiusd_ipcp/Makefile [new file with mode: 0644]
usr.sbin/radiusd/radiusd_local.h
usr.sbin/radiusd/radiusd_module.c

index 48cec71..c83db48 100644 (file)
@@ -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 <bsd.prog.mk>
diff --git a/usr.sbin/radiusctl/json.c b/usr.sbin/radiusctl/json.c
new file mode 100644 (file)
index 0000000..1a653ac
--- /dev/null
@@ -0,0 +1,324 @@
+/*     $OpenBSD: json.c,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */
+
+/*
+ * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
+ *
+ * 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 <ctype.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 (file)
index 0000000..2c13431
--- /dev/null
@@ -0,0 +1,34 @@
+/*     $OpenBSD: json.h,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */
+
+/*
+ * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
+ *
+ * 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 <stdarg.h>
+#include <stdio.h>
+
+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);
index 3b97790..c43d7e4 100644 (file)
@@ -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 <reyk@vantronix.net>
@@ -24,6 +24,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <strings.h>
+#include <limits.h>
 
 #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, "  <maxwait (%u-%u)>\n",
                            TEST_MAXWAIT_MIN, TEST_MAXWAIT_MAX);
                        break;
+               case FLAGS:
+                       fprintf(stderr, "  %s\n", table[i].keyword);
+                       break;
+               case SESSION_SEQ:
+                       fprintf(stderr, "  <sequence number>\n");
+                       break;
                case ENDTOKEN:
                        break;
                }
index 6fd0a6c..3f5e271 100644 (file)
@@ -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 */
 /*
 #ifndef _RADIUSCTL_PARSER_H
 #define _RADIUSCTL_PARSER_H
 
+#include <sys/types.h>
+#include <sys/time.h>
+
 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 *[]);
index 9bebe4c..c9970aa 100644 (file)
@@ -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 <yasuoka@yasuoka.net>
 .\"
@@ -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
index c374884..8c46815 100644 (file)
@@ -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 <yasuoka@yasuoka.net>
  *
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 #include <sys/types.h>
+#include <sys/cdefs.h>
 #include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+#include <sys/un.h>
 #include <netinet/in.h>
-
 #include <arpa/inet.h>
-#include <errno.h>
+
 #include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <inttypes.h>
 #include <md5.h>
 #include <netdb.h>
+#include <radius.h>
 #include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sysexits.h>
+#include <time.h>
 #include <unistd.h>
 
-#include <radius.h>
-
-#include <event.h>
-
 #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);
+}
index 437b7e2..61ba318 100644 (file)
@@ -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 (file)
index 0000000..45c243a
--- /dev/null
@@ -0,0 +1,337 @@
+/*     $OpenBSD: control.c,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/un.h>
+
+#include <errno.h>
+#include <event.h>
+#include <imsg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 (file)
index 0000000..72f8d3f
--- /dev/null
@@ -0,0 +1,45 @@
+/*     $OpenBSD: control.h,v 1.1 2024/07/09 17:26:14 yasuoka Exp $ */
+
+/*
+ * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
+ *
+ * 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 <sys/queue.h>
+#include <sys/time.h>
+#include <event.h>
+#include <imsg.h>
+
+#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_ */
index 6513ca5..435482c 100644 (file)
@@ -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 <henning@openbsd.org>
@@ -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" }
        };
index e9202ba..ec1cf51 100644 (file)
@@ -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);
+}
index 1e0f0e7..b5068fe 100644 (file)
@@ -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
index 3d72417..047311a 100644 (file)
@@ -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 {
index bc228c4..1598e9e 100644 (file)
@@ -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 (file)
index 0000000..0049b1d
--- /dev/null
@@ -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 (file)
index 0000000..bc73ee6
--- /dev/null
@@ -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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/tree.h>
+#include <arpa/inet.h>
+
+#include <inttypes.h>
+#include <netdb.h>
+#include <db.h>
+#include <err.h>
+#include <errno.h>
+#include <event.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <radius.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <imsg.h>
+
+#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 (file)
index 0000000..e86bbd0
--- /dev/null
@@ -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 <netinet/in.h>
+#include <stdint.h>
+
+#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 (file)
index 0000000..81d90b9
--- /dev/null
@@ -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 <bsd.prog.mk>
index a36373c..4fed590 100644 (file)
@@ -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 <sys/socket.h>                /* for struct sockaddr_storage */
 #include <sys/queue.h>         /* for TAILQ_* */
 #include <netinet/in.h>                /* 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
index 9a2b636..33fe547 100644 (file)
@@ -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 <yasuoka@yasuoka.net>
@@ -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);
+}