summaryrefslogtreecommitdiff
path: root/accel-pppd/radius/dm_coa.c
diff options
context:
space:
mode:
Diffstat (limited to 'accel-pppd/radius/dm_coa.c')
-rw-r--r--accel-pppd/radius/dm_coa.c295
1 files changed, 295 insertions, 0 deletions
diff --git a/accel-pppd/radius/dm_coa.c b/accel-pppd/radius/dm_coa.c
new file mode 100644
index 00000000..366bb417
--- /dev/null
+++ b/accel-pppd/radius/dm_coa.c
@@ -0,0 +1,295 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <openssl/md5.h>
+
+#include "triton.h"
+#include "events.h"
+#include "log.h"
+
+#include "radius_p.h"
+
+#include "memdebug.h"
+
+#define PD_COA_PORT 3799
+
+struct dm_coa_serv_t
+{
+ struct triton_context_t ctx;
+ struct triton_md_handler_t hnd;
+};
+
+static struct dm_coa_serv_t serv;
+
+static int dm_coa_check_RA(struct rad_packet_t *pack, const char *secret)
+{
+ uint8_t RA[16];
+ MD5_CTX ctx;
+
+ memset(RA, 0, 16);
+
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, pack->buf, 4);
+ MD5_Update(&ctx, RA, 16);
+ MD5_Update(&ctx, pack->buf + 20, pack->len - 20);
+ MD5_Update(&ctx, secret, strlen(secret));
+ MD5_Final(RA, &ctx);
+
+ return memcmp(RA, pack->buf + 4, 16);
+}
+
+static void dm_coa_set_RA(struct rad_packet_t *pack, const char *secret)
+{
+ MD5_CTX ctx;
+
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, pack->buf, pack->len);
+ MD5_Update(&ctx, secret, strlen(secret));
+ MD5_Final(pack->buf + 4, &ctx);
+}
+
+static int dm_coa_send_ack(int fd, struct rad_packet_t *req, struct sockaddr_in *addr)
+{
+ struct rad_packet_t *reply;
+ uint8_t RA[16];
+
+ memcpy(RA, req->buf + 4, sizeof(RA));
+
+ reply = rad_packet_alloc(req->code == CODE_COA_REQUEST ? CODE_COA_ACK : CODE_DISCONNECT_ACK);
+ if (!reply)
+ return -1;
+
+ reply->id = req->id;
+
+ if (rad_packet_build(reply, RA)) {
+ rad_packet_free(reply);
+ return -1;
+ }
+
+ dm_coa_set_RA(reply, conf_dm_coa_secret);
+
+ if (conf_verbose) {
+ log_ppp_info2("send ");
+ rad_packet_print(reply, log_ppp_info2);
+ }
+
+ rad_packet_send(reply, fd, addr);
+
+ rad_packet_free(reply);
+
+ return 0;
+}
+
+static int dm_coa_send_nak(int fd, struct rad_packet_t *req, struct sockaddr_in *addr, int err_code)
+{
+ struct rad_packet_t *reply;
+ uint8_t RA[16];
+
+ memcpy(RA, req->buf + 4, sizeof(RA));
+
+ reply = rad_packet_alloc(req->code == CODE_COA_REQUEST ? CODE_COA_NAK : CODE_DISCONNECT_NAK);
+ if (!reply)
+ return -1;
+
+ reply->id = req->id;
+
+ if (err_code)
+ rad_packet_add_int(reply, NULL, "Error-Cause", err_code);
+
+ if (rad_packet_build(reply, RA)) {
+ rad_packet_free(reply);
+ return -1;
+ }
+
+ dm_coa_set_RA(reply, conf_dm_coa_secret);
+
+ if (conf_verbose) {
+ log_ppp_info2("send ");
+ rad_packet_print(reply, log_ppp_info2);
+ }
+
+ rad_packet_send(reply, fd, addr);
+
+ rad_packet_free(reply);
+
+ return 0;
+}
+
+
+static void disconnect_request(struct radius_pd_t *rpd)
+{
+ if (conf_verbose) {
+ log_ppp_info2("recv ");
+ rad_packet_print(rpd->dm_coa_req, log_ppp_info2);
+ }
+
+ dm_coa_send_ack(serv.hnd.fd, rpd->dm_coa_req, &rpd->dm_coa_addr);
+
+ rad_packet_free(rpd->dm_coa_req);
+
+ pthread_mutex_lock(&rpd->lock);
+ rpd->dm_coa_req = NULL;
+ pthread_mutex_unlock(&rpd->lock);
+
+ ppp_terminate(rpd->ppp, TERM_ADMIN_RESET, 0);
+}
+
+static void coa_request(struct radius_pd_t *rpd)
+{
+ struct ev_radius_t ev = {
+ .ppp = rpd->ppp,
+ .request = rpd->dm_coa_req,
+ };
+
+ if (conf_verbose) {
+ log_ppp_info2("recv ");
+ rad_packet_print(rpd->dm_coa_req, log_ppp_info2);
+ }
+
+ triton_event_fire(EV_RADIUS_COA, &ev);
+
+ if (ev.res)
+ dm_coa_send_nak(serv.hnd.fd, rpd->dm_coa_req, &rpd->dm_coa_addr, 0);
+ else
+ dm_coa_send_ack(serv.hnd.fd, rpd->dm_coa_req, &rpd->dm_coa_addr);
+
+ rad_packet_free(rpd->dm_coa_req);
+
+ pthread_mutex_lock(&rpd->lock);
+ rpd->dm_coa_req = NULL;
+ pthread_mutex_unlock(&rpd->lock);
+}
+
+void dm_coa_cancel(struct radius_pd_t *rpd)
+{
+ triton_cancel_call(rpd->ppp->ctrl->ctx, (triton_event_func)disconnect_request);
+ triton_cancel_call(rpd->ppp->ctrl->ctx, (triton_event_func)coa_request);
+ rad_packet_free(rpd->dm_coa_req);
+}
+
+static int dm_coa_read(struct triton_md_handler_t *h)
+{
+ struct rad_packet_t *pack;
+ struct radius_pd_t *rpd;
+ int err_code;
+ struct sockaddr_in addr;
+
+ while (1) {
+ if (rad_packet_recv(h->fd, &pack, &addr))
+ return 0;
+
+ if (!pack)
+ continue;
+
+ if (pack->code != CODE_DISCONNECT_REQUEST && pack->code != CODE_COA_REQUEST) {
+ log_warn("radius:dm_coa: unexpected code (%i) received\n", pack->code);
+ goto out_err_no_reply;
+ }
+
+ if (dm_coa_check_RA(pack, conf_dm_coa_secret)) {
+ log_warn("radius:dm_coa: RA validation failed\n");
+ goto out_err_no_reply;
+ }
+
+ if (conf_verbose) {
+ log_debug("recv ");
+ rad_packet_print(pack, log_debug);
+ }
+
+ if (rad_check_nas_pack(pack)) {
+ log_warn("radius:dm_coa: NAS identification failed\n");
+ err_code = 403;
+ goto out_err;
+ }
+
+ rpd = rad_find_session_pack(pack);
+ if (!rpd) {
+ log_warn("radius:dm_coa: session not found\n");
+ err_code = 503;
+ goto out_err;
+ }
+
+ if (rpd->dm_coa_req) {
+ pthread_mutex_unlock(&rpd->lock);
+ goto out_err_no_reply;
+ }
+
+ rpd->dm_coa_req = pack;
+ memcpy(&rpd->dm_coa_addr, &addr, sizeof(addr));
+
+ if (pack->code == CODE_DISCONNECT_REQUEST)
+ triton_context_call(rpd->ppp->ctrl->ctx, (triton_event_func)disconnect_request, rpd);
+ else
+ triton_context_call(rpd->ppp->ctrl->ctx, (triton_event_func)coa_request, rpd);
+
+ pthread_mutex_unlock(&rpd->lock);
+
+ continue;
+
+ out_err:
+ dm_coa_send_nak(h->fd, pack, &addr, err_code);
+
+ out_err_no_reply:
+ rad_packet_free(pack);
+ }
+}
+
+static void dm_coa_close(struct triton_context_t *ctx)
+{
+ struct dm_coa_serv_t *serv = container_of(ctx, typeof(*serv), ctx);
+ triton_md_unregister_handler(&serv->hnd);
+ close(serv->hnd.fd);
+ triton_context_unregister(ctx);
+}
+
+static struct dm_coa_serv_t serv = {
+ .ctx.close = dm_coa_close,
+ .ctx.before_switch = log_switch,
+ .hnd.read = dm_coa_read,
+};
+
+static void __init init(void)
+{
+ struct sockaddr_in addr;
+
+ if (!conf_dm_coa_secret) {
+ log_emerg("radius: no dm_coa_secret specified, DM/CoA disabled...\n");
+ return;
+ }
+
+ serv.hnd.fd = socket (PF_INET, SOCK_DGRAM, 0);
+ if (serv.hnd.fd < 0) {
+ log_emerg("radius:dm_coa: socket: %s\n", strerror(errno));
+ return;
+ }
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons (conf_dm_coa_port);
+ if (conf_dm_coa_server)
+ addr.sin_addr.s_addr = conf_dm_coa_server;
+ else
+ addr.sin_addr.s_addr = htonl (INADDR_ANY);
+ if (bind (serv.hnd.fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
+ log_emerg("radius:dm_coa: bind: %s\n", strerror(errno));
+ close(serv.hnd.fd);
+ return;
+ }
+
+ if (fcntl(serv.hnd.fd, F_SETFL, O_NONBLOCK)) {
+ log_emerg("radius:dm_coa: failed to set nonblocking mode: %s\n", strerror(errno));
+ close(serv.hnd.fd);
+ return;
+ }
+
+ triton_context_register(&serv.ctx, NULL);
+ triton_md_register_handler(&serv.ctx, &serv.hnd);
+ triton_md_enable_handler(&serv.hnd, MD_MODE_READ);
+ triton_context_wakeup(&serv.ctx);
+}