dm_coa.c 5.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#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"
15
#include "events.h"
16 17
#include "log.h"

18
#include "radius_p.h"
19 20 21

#define PD_COA_PORT 3799

22
struct dm_coa_serv_t
23 24 25 26 27
{
	struct triton_context_t ctx;
	struct triton_md_handler_t hnd;
};

28 29
static struct dm_coa_serv_t serv;

30
static int dm_coa_check_RA(struct rad_packet_t *pack, const char *secret)
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
{
	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);
}

47
static void dm_coa_set_RA(struct rad_packet_t *pack, const char *secret)
48 49 50 51 52 53 54 55 56
{
	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);
}

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
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) {
78 79
		log_ppp_debug("send ");
		rad_packet_print(reply, log_ppp_debug);
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
	}

	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;

102 103
	if (err_code)
		rad_packet_add_int(reply, "Error-Cause", err_code);
104 105 106 107 108 109 110 111 112

	if (rad_packet_build(reply, RA)) {
		rad_packet_free(reply);
		return -1;
	}

	dm_coa_set_RA(reply, conf_dm_coa_secret);

	if (conf_verbose) {
113 114
		log_ppp_debug("send ");
		rad_packet_print(reply, log_ppp_debug);
115 116 117 118 119 120 121 122 123 124
	}

	rad_packet_send(reply, fd, addr);
	
	rad_packet_free(reply);

	return 0;
}


125 126
static void disconnect_request(struct radius_pd_t *rpd)
{
127 128 129 130 131
	if (conf_verbose) {
		log_ppp_debug("recv ");
		rad_packet_print(rpd->dm_coa_req, log_ppp_debug);
	}

132 133
	dm_coa_send_ack(serv.hnd.fd, rpd->dm_coa_req, &rpd->dm_coa_addr);

134 135
	rad_packet_free(rpd->dm_coa_req);
	rpd->dm_coa_req = NULL;
136 137 138 139 140 141

	ppp_terminate(rpd->ppp, 0);
}

static void coa_request(struct radius_pd_t *rpd)
{
142 143 144 145 146
	struct ev_radius_t ev = {
		.ppp = rpd->ppp,
		.request = rpd->dm_coa_req,
	};

147 148 149 150
	if (conf_verbose) {
		log_ppp_debug("recv ");
		rad_packet_print(rpd->dm_coa_req, log_ppp_debug);
	}
151 152 153 154 155 156 157

	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);
158
	
159
	rad_packet_free(rpd->dm_coa_req);
160 161

	pthread_mutex_lock(&rpd->lock);
162
	rpd->dm_coa_req = NULL;
163
	pthread_mutex_unlock(&rpd->lock);
164 165
}

166
static int dm_coa_read(struct triton_md_handler_t *h)
167 168 169 170 171 172 173 174 175 176 177 178
{
	struct rad_packet_t *pack;
	struct radius_pd_t *rpd;
	int err_code;
	struct sockaddr_in addr;


	pack = rad_packet_recv(h->fd, &addr);
	if (!pack)
		return 0;

	if (pack->code != CODE_DISCONNECT_REQUEST	&& pack->code != CODE_COA_REQUEST) {
179
		log_warn("radius:dm_coa: unexpected code (%i) received\n", pack->code);
180 181 182
		goto out_err_no_reply;
	}

183 184
	if (dm_coa_check_RA(pack, conf_dm_coa_secret)) {
		log_warn("radius:dm_coa: RA validation failed\n");
185 186 187 188 189 190 191 192 193
		goto out_err_no_reply;
	}

	if (conf_verbose) {
		log_debug("recv ");
		rad_packet_print(pack, log_debug);
	}

	if (rad_check_nas_pack(pack)) {
194
		log_warn("radius:dm_coa: NAS identification failed\n");
195 196 197 198 199 200
		err_code = 403;
		goto out_err;
	}
	
	rpd = rad_find_session_pack(pack);
	if (!rpd) {
201
		log_warn("radius:dm_coa: session not found\n");
202 203 204 205
		err_code = 503;
		goto out_err;
	}
	
206 207 208 209 210
	if (rpd->dm_coa_req) {
		pthread_mutex_unlock(&rpd->lock);
		goto out_err_no_reply;
	}

211
	rpd->dm_coa_req = pack;
212
	memcpy(&rpd->dm_coa_addr, &addr, sizeof(addr));
213 214 215 216 217 218 219 220 221 222 223

	if (pack->code == CODE_DISCONNECT_REQUEST)
		triton_context_call(rpd->ppp->ctrl->ctx, (void (*)(void *))disconnect_request, rpd);
	else
		triton_context_call(rpd->ppp->ctrl->ctx, (void (*)(void *))coa_request, rpd);

	pthread_mutex_unlock(&rpd->lock);

	return 0;

out_err:
224
	dm_coa_send_nak(h->fd, pack, &addr, err_code);
225 226 227 228 229 230

out_err_no_reply:
	rad_packet_free(pack);
	return 0;
}

231
static void dm_coa_close(struct triton_context_t *ctx)
232
{
233
	struct dm_coa_serv_t *serv = container_of(ctx, typeof(*serv), ctx);
234 235 236 237
	triton_md_unregister_handler(&serv->hnd);
	close(serv->hnd.fd);
}

238 239
static struct dm_coa_serv_t serv = {
	.ctx.close = dm_coa_close,
240
	.ctx.before_switch = log_switch,
241
	.hnd.read = dm_coa_read,
242 243 244 245 246 247 248 249
};

static void __init init(void)
{
  struct sockaddr_in addr;

	serv.hnd.fd = socket (PF_INET, SOCK_DGRAM, 0);
  if (serv.hnd.fd < 0) {
250
    log_error("radius:dm_coa: socket: %s\n", strerror(errno));
251 252 253 254 255 256 257 258 259
    return;
  }
  addr.sin_family = AF_INET;
  addr.sin_port = htons (PD_COA_PORT);
	if (conf_nas_ip_address)
	  addr.sin_addr.s_addr = inet_addr(conf_nas_ip_address);
	else
		addr.sin_addr.s_addr = htonl (INADDR_ANY);
  if (bind (serv.hnd.fd, (struct sockaddr *) &addr, sizeof (addr)) < 0) {
260
    log_error("radius:dm_coa: bind: %s\n", strerror(errno));
261 262 263 264 265
		close(serv.hnd.fd);
    return;
  }

	if (fcntl(serv.hnd.fd, F_SETFL, O_NONBLOCK)) {
266
    log_error("radius:dm_coa: failed to set nonblocking mode: %s\n", strerror(errno));
267 268 269 270
		close(serv.hnd.fd);
    return;
	}
	
271
	triton_context_register(&serv.ctx, NULL);
272 273 274
	triton_md_register_handler(&serv.ctx, &serv.hnd);
	triton_md_enable_handler(&serv.hnd, MD_MODE_READ);
}