2085 lines
60 KiB
C
2085 lines
60 KiB
C
/* $OpenBSD: radiusd_ipcp.c,v 1.23 2025/01/29 10:16:05 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 radiusctl_client {
|
|
int peerid;
|
|
TAILQ_ENTRY(radiusctl_client) entry;
|
|
};
|
|
|
|
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;
|
|
TAILQ_HEAD(, radiusctl_client) dae_clients;
|
|
};
|
|
|
|
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;
|
|
struct event ev_reqs;
|
|
TAILQ_ENTRY(module_ipcp_dae) next;
|
|
TAILQ_HEAD(, assigned_ipv4) reqs;
|
|
int ninflight;
|
|
};
|
|
|
|
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_delete(struct module_ipcp *,
|
|
struct assigned_ipv4 *, const char *);
|
|
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_update_time(struct module_ipcp *);
|
|
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 void ipcp_dae_reset_request(struct assigned_ipv4 *);
|
|
static void ipcp_dae_send_pending_requests(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);
|
|
event_base_free(NULL);
|
|
|
|
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;
|
|
ipcp_update_time(self);
|
|
}
|
|
|
|
void
|
|
ipcp_start(void *ctx)
|
|
{
|
|
struct module_ipcp *self = ctx;
|
|
struct ipcp_address *addr;
|
|
struct module_ipcp_dae *dae;
|
|
int sock;
|
|
|
|
ipcp_update_time(self);
|
|
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("%s: could not start dae: socket()", __func__);
|
|
return;
|
|
}
|
|
if (connect(sock, (struct sockaddr *)&dae->nas_addr,
|
|
dae->nas_addr.sin4.sin_len) == -1) {
|
|
log_warn("%s: could not start dae: connect()",
|
|
__func__);
|
|
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);
|
|
evtimer_set(&dae->ev_reqs, ipcp_dae_send_pending_requests, dae);
|
|
}
|
|
|
|
module_send_message(self->base, IMSG_OK, NULL);
|
|
}
|
|
|
|
void
|
|
ipcp_stop(void *ctx)
|
|
{
|
|
struct module_ipcp *self = ctx;
|
|
struct module_ipcp_dae *dae;
|
|
|
|
ipcp_update_time(self);
|
|
/* 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(&dae->ev_reqs, NULL))
|
|
event_del(&dae->ev_reqs);
|
|
}
|
|
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;
|
|
struct ipcp_address *addr, *addrt;
|
|
|
|
RB_FOREACH_SAFE(assign, assigned_ipv4_tree, &self->ipv4s, assignt)
|
|
ipcp_ipv4_release(self, assign);
|
|
RB_FOREACH_SAFE(user, user_tree, &self->users, usert) {
|
|
RB_REMOVE(user_tree, &self->users, user);
|
|
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);
|
|
}
|
|
TAILQ_FOREACH_SAFE(addr, &self->addrs, next, addrt)
|
|
free(addr);
|
|
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_pton(AF_INET, 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_pton(AF_INET, 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) {
|
|
memset(&dae, 0, sizeof(dae));
|
|
dae.sock = -1;
|
|
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);
|
|
dae0->ipcp = module;
|
|
} 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;
|
|
struct radiusctl_client *client;
|
|
const char *cause;
|
|
|
|
ipcp_update_time(self);
|
|
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;
|
|
/* FALLTHROUGH */
|
|
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:
|
|
case IMSG_RADIUSD_MODULE_IPCP_DELETE:
|
|
if (datalen < sizeof(unsigned)) {
|
|
log_warn("%s: received "
|
|
"%s message size is wrong", __func__,
|
|
(imsg->hdr.type ==
|
|
IMSG_RADIUSD_MODULE_IPCP_DISCONNECT)
|
|
? "IMSG_RADIUSD_MODULE_IPCP_DISCONNECT"
|
|
: "IMSG_RADIUSD_MODULE_IPCP_DELETE");
|
|
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) {
|
|
cause = "session not found";
|
|
log_warnx("%s seq=%u requested, but the "
|
|
"session is not found",
|
|
(imsg->hdr.type ==
|
|
IMSG_RADIUSD_MODULE_IPCP_DISCONNECT)? "Disconnect"
|
|
: "Delete", seq);
|
|
module_imsg_compose(self->base, IMSG_NG,
|
|
imsg->hdr.peerid, 0, -1, cause, strlen(cause) + 1);
|
|
} else if (imsg->hdr.type == IMSG_RADIUSD_MODULE_IPCP_DELETE) {
|
|
log_info("Delete seq=%u by request", assign->seq);
|
|
ipcp_ipv4_delete(self, assign, "By control");
|
|
module_imsg_compose(self->base, IMSG_OK,
|
|
imsg->hdr.peerid, 0, -1, NULL, 0);
|
|
} else {
|
|
if (assign->dae == NULL)
|
|
log_warnx("Disconnect seq=%u requested, but "
|
|
"DAE is not configured", assign->seq);
|
|
else {
|
|
log_info("Disconnect seq=%u requested",
|
|
assign->seq);
|
|
if ((client = calloc(1, sizeof(struct
|
|
radiusctl_client))) == NULL) {
|
|
log_warn("%s: calloc: %m",
|
|
__func__);
|
|
goto fail;
|
|
}
|
|
client->peerid = imsg->hdr.peerid;
|
|
if (assign->dae_ntry == 0)
|
|
ipcp_dae_send_disconnect_request(
|
|
assign);
|
|
TAILQ_INSERT_TAIL(&assign->dae_clients,
|
|
client, entry);
|
|
}
|
|
}
|
|
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, n;
|
|
bool found = false;
|
|
char username[256], buf[128];
|
|
struct user *user = NULL;
|
|
struct assigned_ipv4 *assigned = NULL, *assign;
|
|
|
|
ipcp_update_time(self);
|
|
|
|
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 user=%s rejected: number of "
|
|
"sessions reached the limit(%d)", q_id,
|
|
user->name, 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 user=%s rejected: number of "
|
|
"sessions per a user reached the limit(%d)",
|
|
q_id, user->name, 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 user=%s rejected: server "
|
|
"requested IP address is busy", q_id,
|
|
user->name);
|
|
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 user=%s rejected: "
|
|
"server requested IP address is "
|
|
"out of the range", q_id,
|
|
user->name);
|
|
else
|
|
found = true;
|
|
/* revert the addr to the network byte order */
|
|
addr4.s_addr = htonl(addr4.s_addr);
|
|
}
|
|
if (!found)
|
|
goto reject;
|
|
} else {
|
|
int inpool_idx = 0;
|
|
|
|
/* select a random address */
|
|
n = arc4random_uniform(self->npools);
|
|
i = 0;
|
|
TAILQ_FOREACH(addr, &self->addrs, next) {
|
|
if (addr->type == ADDRESS_TYPE_POOL) {
|
|
if (i <= n && n < i + addr->naddrs) {
|
|
inpool_idx = n - i;
|
|
break;
|
|
}
|
|
i += addr->naddrs;
|
|
}
|
|
}
|
|
/* loop npools times until a free address is found */
|
|
for (i = 0; i < self->npools && addr != NULL; i++) {
|
|
addr4.s_addr = htonl(
|
|
addr->start.s_addr + inpool_idx);
|
|
if (ipcp_ipv4_find(self, addr4) == NULL) {
|
|
found = true;
|
|
break;
|
|
}
|
|
/* try inpool_idx if it's in the range */
|
|
if (++inpool_idx < addr->naddrs)
|
|
continue;
|
|
/* iterate addr to the next pool */
|
|
do {
|
|
addr = TAILQ_NEXT(addr, next);
|
|
if (addr == NULL)
|
|
addr = TAILQ_FIRST(
|
|
&self->addrs);
|
|
} while (addr->type != ADDRESS_TYPE_POOL);
|
|
inpool_idx = 0; /* try the first */
|
|
}
|
|
if (!found) {
|
|
log_info("q=%u user=%s rejected: ran out of "
|
|
"the address pool", q_id, user->name);
|
|
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));
|
|
|
|
radius_get_ipv4_attr(radreq, RADIUS_TYPE_NAS_IP_ADDRESS,
|
|
&assigned->nas_ipv4);
|
|
radius_get_ipv6_attr(radreq, RADIUS_TYPE_NAS_IPV6_ADDRESS,
|
|
&assigned->nas_ipv6);
|
|
radius_get_string_attr(radreq, RADIUS_TYPE_NAS_IDENTIFIER,
|
|
assigned->nas_id, sizeof(assigned->nas_id));
|
|
}
|
|
|
|
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 && assigned != NULL &&
|
|
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[384];
|
|
struct timespec dur;
|
|
struct radiusd_ipcp_statistics
|
|
stat;
|
|
struct module_ipcp_dae *dae;
|
|
|
|
ipcp_update_time(self);
|
|
|
|
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("q=%u Delete record for %s", q_id,
|
|
inet_ntop(AF_INET, &assign->ipv4, buf,
|
|
sizeof(buf)));
|
|
ipcp_ipv4_delete(self, assign,
|
|
(type == RADIUS_ACCT_STATUS_TYPE_ACCT_ON)
|
|
? "Receive Acct-On" : "Receive Acct-Off");
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (radius_get_ipv4_attr(radpkt, RADIUS_TYPE_FRAMED_IP_ADDRESS, &addr4)
|
|
!= 0) {
|
|
log_warnx("q=%u no Framed-IP-Address-Address attribute", q_id);
|
|
goto out;
|
|
}
|
|
if (radius_get_string_attr(radpkt, RADIUS_TYPE_USER_NAME, username,
|
|
sizeof(username)) != 0) {
|
|
log_warnx("q=%u no User-Name attribute", q_id);
|
|
goto out;
|
|
}
|
|
if ((assign = ipcp_ipv4_find(self, addr4)) == NULL) {
|
|
/* not assigned by this */
|
|
log_warnx("q=%u %s is not assigned by us", q_id,
|
|
inet_ntop(AF_INET, &addr4, buf, sizeof(buf)));
|
|
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_ipv6 = nas_ipv6;
|
|
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("q=%u Start seq=%u user=%s duration=%dsec "
|
|
"session=%s tunnel=%s from=%s auth=%s ip=%s", q_id,
|
|
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("q=%u 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\"", q_id,
|
|
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);
|
|
TAILQ_INIT(&ip->dae_clients);
|
|
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_delete(struct module_ipcp *self, struct assigned_ipv4 *assign,
|
|
const char *cause)
|
|
{
|
|
struct radiusd_ipcp_statistics stat;
|
|
|
|
memset(&stat, 0, sizeof(stat));
|
|
strlcpy(stat.cause, cause, sizeof(stat.cause));
|
|
|
|
ipcp_del_db(self, assign);
|
|
ipcp_notice_startstop(self, assign, 0, &stat);
|
|
ipcp_ipv4_release(self, assign);
|
|
}
|
|
|
|
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--;
|
|
ipcp_dae_reset_request(assign);
|
|
free(assign);
|
|
}
|
|
}
|
|
|
|
int
|
|
assigned_ipv4_compar(struct assigned_ipv4 *a, struct assigned_ipv4 *b)
|
|
{
|
|
if (a->ipv4.s_addr > b->ipv4.s_addr)
|
|
return (1);
|
|
else if (a->ipv4.s_addr < b->ipv4.s_addr)
|
|
return (-1);
|
|
return (0);
|
|
}
|
|
|
|
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);
|
|
assigned->seq = record->seq;
|
|
self->seq = MAXIMUM(assigned->seq + 1, self->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_update_time(struct module_ipcp *self)
|
|
{
|
|
clock_gettime(CLOCK_BOOTTIME, &self->uptime);
|
|
}
|
|
|
|
void
|
|
ipcp_on_timer(int fd, short ev, void *ctx)
|
|
{
|
|
struct module_ipcp *self = ctx;
|
|
|
|
ipcp_update_time(self);
|
|
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_reqpkt == NULL) {
|
|
if ((reqpkt = radius_new_request_packet(
|
|
RADIUS_CODE_DISCONNECT_REQUEST)) == NULL) {
|
|
log_warn("%s: radius_new_request_packet(): %m",
|
|
__func__);
|
|
return;
|
|
}
|
|
radius_put_string_attr(reqpkt, RADIUS_TYPE_ACCT_SESSION_ID,
|
|
assign->session_id);
|
|
/*
|
|
* RFC 5176 Section 3, "either the User-Name or
|
|
* Chargeable-User-Identity attribute SHOULD be present in
|
|
* Disconnect-Request and CoA-Request packets."
|
|
*/
|
|
radius_put_string_attr(reqpkt, RADIUS_TYPE_USER_NAME,
|
|
assign->user->name);
|
|
if (assign->nas_id[0] != '\0')
|
|
radius_put_string_attr(reqpkt,
|
|
RADIUS_TYPE_NAS_IDENTIFIER, assign->nas_id);
|
|
if (ntohl(assign->nas_ipv4.s_addr) != 0)
|
|
radius_put_ipv4_attr(reqpkt,
|
|
RADIUS_TYPE_NAS_IP_ADDRESS, assign->nas_ipv4);
|
|
if (!IN6_IS_ADDR_UNSPECIFIED(&assign->nas_ipv6))
|
|
radius_put_ipv6_attr(reqpkt,
|
|
RADIUS_TYPE_NAS_IPV6_ADDRESS, &assign->nas_ipv6);
|
|
radius_set_accounting_request_authenticator(reqpkt,
|
|
assign->dae->secret);
|
|
assign->dae_reqpkt = reqpkt;
|
|
TAILQ_INSERT_TAIL(&assign->dae->reqs, assign, dae_next);
|
|
}
|
|
|
|
if (assign->dae_ntry == 0) {
|
|
if (assign->dae->ninflight >= RADIUSD_IPCP_DAE_MAX_INFLIGHT)
|
|
return;
|
|
log_info("Sending Disconnect-Request seq=%u to %s",
|
|
assign->seq, print_addr((struct sockaddr *)
|
|
&assign->dae->nas_addr, buf, sizeof(buf)));
|
|
}
|
|
|
|
if (radius_send(assign->dae->sock, assign->dae_reqpkt, 0) < 0)
|
|
log_warn("%s: sendto: %m", __func__);
|
|
|
|
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)
|
|
assign->dae->ninflight++;
|
|
assign->dae_ntry++;
|
|
}
|
|
|
|
void
|
|
ipcp_dae_request_on_timeout(int fd, short ev, void *ctx)
|
|
{
|
|
struct assigned_ipv4 *assign = ctx;
|
|
char buf[80];
|
|
struct radiusctl_client *client;
|
|
|
|
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)));
|
|
TAILQ_FOREACH(client, &assign->dae_clients, entry)
|
|
module_imsg_compose(assign->dae->ipcp->base, IMSG_NG,
|
|
client->peerid, 0, -1, NULL, 0);
|
|
ipcp_dae_reset_request(assign);
|
|
} else
|
|
ipcp_dae_send_disconnect_request(assign);
|
|
}
|
|
|
|
void
|
|
ipcp_dae_on_event(int fd, short ev, void *ctx)
|
|
{
|
|
struct module_ipcp_dae *dae = ctx;
|
|
struct module_ipcp *self = dae->ipcp;
|
|
RADIUS_PACKET *radres = NULL;
|
|
int code;
|
|
uint32_t u32;
|
|
struct assigned_ipv4 *assign;
|
|
char buf[80], causestr[80];
|
|
const char *cause = "";
|
|
struct radiusctl_client *client;
|
|
|
|
ipcp_update_time(self);
|
|
|
|
if ((ev & EV_READ) == 0)
|
|
return;
|
|
|
|
if ((radres = radius_recv(dae->sock, 0)) == NULL) {
|
|
if (errno == EAGAIN)
|
|
return;
|
|
log_warn("%s: Failed to receive from %s", __func__, 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("%s: Received RADIUS packet from %s has unknown "
|
|
"id=%d", __func__, print_addr((struct sockaddr *)
|
|
&dae->nas_addr, buf, sizeof(buf)), radius_get_id(radres));
|
|
goto out;
|
|
}
|
|
|
|
radius_set_request_packet(radres, assign->dae_reqpkt);
|
|
if ((radius_check_response_authenticator(radres, dae->secret)) != 0) {
|
|
log_warnx("%s: Received RADIUS packet for seq=%u from %s has "
|
|
"a bad authenticator", __func__, assign->seq, print_addr(
|
|
(struct sockaddr *)&dae->nas_addr, buf,
|
|
sizeof(buf)));
|
|
goto out;
|
|
}
|
|
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);
|
|
cause = causestr;
|
|
}
|
|
|
|
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);
|
|
break;
|
|
case RADIUS_CODE_DISCONNECT_NAK:
|
|
log_info("Received Disconnect-NAK for seq=%u from %s%s",
|
|
assign->seq, print_addr((struct sockaddr *)
|
|
&dae->nas_addr, buf, sizeof(buf)), cause);
|
|
break;
|
|
default:
|
|
log_warn("%s: Received unknown code=%d for id=%u from %s",
|
|
__func__, code, assign->seq, print_addr((struct sockaddr *)
|
|
&dae->nas_addr, buf, sizeof(buf)));
|
|
break;
|
|
}
|
|
|
|
TAILQ_FOREACH(client, &assign->dae_clients, entry) {
|
|
if (*cause != '\0')
|
|
module_imsg_compose(self->base,
|
|
(code == RADIUS_CODE_DISCONNECT_ACK)
|
|
? IMSG_OK : IMSG_NG, client->peerid, 0, -1,
|
|
cause + 1, strlen(cause + 1) + 1);
|
|
else
|
|
module_imsg_compose(self->base,
|
|
(code == RADIUS_CODE_DISCONNECT_ACK)
|
|
? IMSG_OK : IMSG_NG, client->peerid, 0, -1,
|
|
NULL, 0);
|
|
}
|
|
ipcp_dae_reset_request(assign);
|
|
out:
|
|
if (radres != NULL)
|
|
radius_delete_packet(radres);
|
|
}
|
|
|
|
void
|
|
ipcp_dae_reset_request(struct assigned_ipv4 *assign)
|
|
{
|
|
struct radiusctl_client *client, *clientt;
|
|
const struct timeval zero = { 0, 0 };
|
|
|
|
if (assign->dae != NULL) {
|
|
if (assign->dae_reqpkt != NULL)
|
|
TAILQ_REMOVE(&assign->dae->reqs, assign, dae_next);
|
|
if (assign->dae_ntry > 0) {
|
|
assign->dae->ninflight--;
|
|
if (!evtimer_pending(&assign->dae->ev_reqs, NULL))
|
|
evtimer_add(&assign->dae->ev_reqs, &zero);
|
|
}
|
|
}
|
|
if (assign->dae_reqpkt != NULL)
|
|
radius_delete_packet(assign->dae_reqpkt);
|
|
assign->dae_reqpkt = NULL;
|
|
if (evtimer_pending(&assign->dae_evtimer, NULL))
|
|
evtimer_del(&assign->dae_evtimer);
|
|
TAILQ_FOREACH_SAFE(client, &assign->dae_clients, entry, clientt) {
|
|
TAILQ_REMOVE(&assign->dae_clients, client, entry);
|
|
free(client);
|
|
}
|
|
assign->dae_ntry = 0;
|
|
}
|
|
|
|
void
|
|
ipcp_dae_send_pending_requests(int fd, short ev, void *ctx)
|
|
{
|
|
struct module_ipcp_dae *dae = ctx;
|
|
struct module_ipcp *self = dae->ipcp;
|
|
struct assigned_ipv4 *assign, *assignt;
|
|
|
|
ipcp_update_time(self);
|
|
|
|
TAILQ_FOREACH_SAFE(assign, &dae->reqs, dae_next, assignt) {
|
|
if (dae->ninflight >= RADIUSD_IPCP_DAE_MAX_INFLIGHT)
|
|
break;
|
|
if (assign->dae_ntry == 0) /* pending */
|
|
ipcp_dae_send_disconnect_request(assign);
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
* 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_pton(AF_INET, buf, &start) != 1)
|
|
goto error;
|
|
else if (inet_pton(AF_INET, ++sep, &end) != 1)
|
|
goto error;
|
|
start.s_addr = ntohl(start.s_addr);
|
|
end.s_addr = ntohl(end.s_addr);
|
|
if (end.s_addr < start.s_addr)
|
|
goto error;
|
|
} else {
|
|
if ((sep = strchr(buf, '/')) != NULL) {
|
|
*sep = '\0';
|
|
if (inet_pton(AF_INET, buf, &start) != 1)
|
|
goto error;
|
|
masklen = strtonum(++sep, 0, 32, &errstr);
|
|
if (errstr != NULL)
|
|
goto error;
|
|
} else {
|
|
if (inet_pton(AF_INET, 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 Value" },
|
|
{ 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);
|
|
}
|
|
free(str);
|
|
if (salen < ai->ai_addrlen) {
|
|
freeaddrinfo(ai);
|
|
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);
|
|
}
|