src/usr.sbin/radiusd/radiusd_ipcp.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);
}