-/* $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)
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) {
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;
test.res = res;
test.sock = sock;
test.reqpkt = reqpkt;
-
+
event_set(&test.ev_recv, sock, EV_READ|EV_PERSIST,
radius_test_recv, &test);
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,
}
-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;
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);
+}
--- /dev/null
+/* $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);
+}