133965Sjdp/*
2218822Sdim * hostapd / RADIUS Accounting
3130561Sobrien * Copyright (c) 2002-2009, 2012, Jouni Malinen <j@w1.fi>
477298Sobrien *
533965Sjdp * This software may be distributed under the terms of the BSD license.
633965Sjdp * See README for more details.
733965Sjdp */
833965Sjdp
933965Sjdp#include "utils/includes.h"
1033965Sjdp
1133965Sjdp#include "utils/common.h"
1233965Sjdp#include "utils/eloop.h"
1333965Sjdp#include "drivers/driver.h"
1433965Sjdp#include "radius/radius.h"
1533965Sjdp#include "radius/radius_client.h"
1633965Sjdp#include "hostapd.h"
1733965Sjdp#include "ieee802_1x.h"
1833965Sjdp#include "ap_config.h"
1933965Sjdp#include "sta_info.h"
20218822Sdim#include "ap_drv_ops.h"
2133965Sjdp#include "accounting.h"
2277298Sobrien
2333965Sjdp
24104834Sobrien/* Default interval in seconds for polling TX/RX octets from the driver if
2533965Sjdp * STA is not using interim accounting. This detects wrap arounds for
2633965Sjdp * input/output octets and updates Acct-{Input,Output}-Gigawords. */
27104834Sobrien#define ACCT_DEFAULT_UPDATE_INTERVAL 300
2833965Sjdp
2933965Sjdpstatic void accounting_sta_interim(struct hostapd_data *hapd,
3033965Sjdp				   struct sta_info *sta);
3133965Sjdp
32218822Sdim
3333965Sjdpstatic struct radius_msg * accounting_msg(struct hostapd_data *hapd,
3433965Sjdp					  struct sta_info *sta,
3533965Sjdp					  int status_type)
3633965Sjdp{
3733965Sjdp	struct radius_msg *msg;
3833965Sjdp	char buf[128];
39218822Sdim	u8 *val;
40130561Sobrien	size_t len;
41130561Sobrien	int i;
42130561Sobrien	struct wpabuf *b;
4333965Sjdp
4433965Sjdp	msg = radius_msg_new(RADIUS_CODE_ACCOUNTING_REQUEST,
4533965Sjdp			     radius_client_get_id(hapd->radius));
4633965Sjdp	if (msg == NULL) {
4733965Sjdp		printf("Could not create net RADIUS packet\n");
4833965Sjdp		return NULL;
4933965Sjdp	}
5033965Sjdp
5133965Sjdp	if (sta) {
5277298Sobrien		radius_msg_make_authenticator(msg, (u8 *) sta, sizeof(*sta));
5333965Sjdp
5433965Sjdp		os_snprintf(buf, sizeof(buf), "%08X-%08X",
55218822Sdim			    sta->acct_session_id_hi, sta->acct_session_id_lo);
56218822Sdim		if (!radius_msg_add_attr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
5733965Sjdp					 (u8 *) buf, os_strlen(buf))) {
5833965Sjdp			printf("Could not add Acct-Session-Id\n");
5933965Sjdp			goto fail;
6033965Sjdp		}
6133965Sjdp	} else {
6233965Sjdp		radius_msg_make_authenticator(msg, (u8 *) hapd, sizeof(*hapd));
63130561Sobrien	}
64130561Sobrien
65130561Sobrien	if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_STATUS_TYPE,
66218822Sdim				       status_type)) {
67218822Sdim		printf("Could not add Acct-Status-Type\n");
6833965Sjdp		goto fail;
6933965Sjdp	}
7033965Sjdp
7133965Sjdp	if (!hostapd_config_get_radius_attr(hapd->conf->radius_acct_req_attr,
72130561Sobrien					    RADIUS_ATTR_ACCT_AUTHENTIC) &&
7333965Sjdp	    !radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_AUTHENTIC,
7489857Sobrien				       hapd->conf->ieee802_1x ?
7589857Sobrien				       RADIUS_ACCT_AUTHENTIC_RADIUS :
7689857Sobrien				       RADIUS_ACCT_AUTHENTIC_LOCAL)) {
7789857Sobrien		printf("Could not add Acct-Authentic\n");
78218822Sdim		goto fail;
7989857Sobrien	}
8089857Sobrien
81218822Sdim	if (sta) {
82218822Sdim		/* Use 802.1X identity if available */
8389857Sobrien		val = ieee802_1x_get_identity(sta->eapol_sm, &len);
8489857Sobrien
8589857Sobrien		/* Use RADIUS ACL identity if 802.1X provides no identity */
8689857Sobrien		if (!val && sta->identity) {
8789857Sobrien			val = (u8 *) sta->identity;
8889857Sobrien			len = os_strlen(sta->identity);
8989857Sobrien		}
9033965Sjdp
91218822Sdim		/* Use STA MAC if neither 802.1X nor RADIUS ACL provided
9260484Sobrien		 * identity */
9333965Sjdp		if (!val) {
9433965Sjdp			os_snprintf(buf, sizeof(buf), RADIUS_ADDR_FORMAT,
9533965Sjdp				    MAC2STR(sta->addr));
9633965Sjdp			val = (u8 *) buf;
9733965Sjdp			len = os_strlen(buf);
9833965Sjdp		}
99130561Sobrien
10033965Sjdp		if (!radius_msg_add_attr(msg, RADIUS_ATTR_USER_NAME, val,
10133965Sjdp					 len)) {
102130561Sobrien			printf("Could not add User-Name\n");
10333965Sjdp			goto fail;
10433965Sjdp		}
10533965Sjdp	}
10633965Sjdp
107130561Sobrien	if (add_common_radius_attr(hapd, hapd->conf->radius_acct_req_attr, sta,
108130561Sobrien				   msg) < 0)
109130561Sobrien		goto fail;
11033965Sjdp
11133965Sjdp	if (sta) {
11233965Sjdp		for (i = 0; ; i++) {
11333965Sjdp			val = ieee802_1x_get_radius_class(sta->eapol_sm, &len,
11433965Sjdp							  i);
11533965Sjdp			if (val == NULL)
11633965Sjdp				break;
11733965Sjdp
11833965Sjdp			if (!radius_msg_add_attr(msg, RADIUS_ATTR_CLASS,
11933965Sjdp						 val, len)) {
12033965Sjdp				printf("Could not add Class\n");
12133965Sjdp				goto fail;
122130561Sobrien			}
12333965Sjdp		}
12433965Sjdp
12533965Sjdp		b = ieee802_1x_get_radius_cui(sta->eapol_sm);
12633965Sjdp		if (b &&
12733965Sjdp		    !radius_msg_add_attr(msg,
128130561Sobrien					 RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
129130561Sobrien					 wpabuf_head(b), wpabuf_len(b))) {
13033965Sjdp			wpa_printf(MSG_ERROR, "Could not add CUI");
13133965Sjdp			goto fail;
13260484Sobrien		}
13333965Sjdp
13433965Sjdp		if (!b && sta->radius_cui &&
13533965Sjdp		    !radius_msg_add_attr(msg,
13633965Sjdp					 RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
13733965Sjdp					 (u8 *) sta->radius_cui,
13833965Sjdp					 os_strlen(sta->radius_cui))) {
13933965Sjdp			wpa_printf(MSG_ERROR, "Could not add CUI from ACL");
14033965Sjdp			goto fail;
14133965Sjdp		}
14233965Sjdp	}
14333965Sjdp
144218822Sdim	return msg;
14560484Sobrien
14660484Sobrien fail:
14760484Sobrien	radius_msg_free(msg);
14833965Sjdp	return NULL;
14933965Sjdp}
15033965Sjdp
15133965Sjdp
152218822Sdimstatic int accounting_sta_update_stats(struct hostapd_data *hapd,
153218822Sdim				       struct sta_info *sta,
154218822Sdim				       struct hostap_sta_driver_data *data)
155218822Sdim{
156218822Sdim	if (hostapd_drv_read_sta_data(hapd, data, sta->addr))
157218822Sdim		return -1;
158218822Sdim
159218822Sdim	if (sta->last_rx_bytes > data->rx_bytes)
160218822Sdim		sta->acct_input_gigawords++;
161218822Sdim	if (sta->last_tx_bytes > data->tx_bytes)
162218822Sdim		sta->acct_output_gigawords++;
163218822Sdim	sta->last_rx_bytes = data->rx_bytes;
164218822Sdim	sta->last_tx_bytes = data->tx_bytes;
165218822Sdim
166218822Sdim	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
167218822Sdim		       HOSTAPD_LEVEL_DEBUG, "updated TX/RX stats: "
168218822Sdim		       "Acct-Input-Octets=%lu Acct-Input-Gigawords=%u "
169218822Sdim		       "Acct-Output-Octets=%lu Acct-Output-Gigawords=%u",
170218822Sdim		       sta->last_rx_bytes, sta->acct_input_gigawords,
171218822Sdim		       sta->last_tx_bytes, sta->acct_output_gigawords);
172218822Sdim
17333965Sjdp	return 0;
17433965Sjdp}
17533965Sjdp
17633965Sjdp
177218822Sdimstatic void accounting_interim_update(void *eloop_ctx, void *timeout_ctx)
17833965Sjdp{
17933965Sjdp	struct hostapd_data *hapd = eloop_ctx;
18033965Sjdp	struct sta_info *sta = timeout_ctx;
18133965Sjdp	int interval;
18233965Sjdp
18333965Sjdp	if (sta->acct_interim_interval) {
18433965Sjdp		accounting_sta_interim(hapd, sta);
18533965Sjdp		interval = sta->acct_interim_interval;
18633965Sjdp	} else {
18733965Sjdp		struct hostap_sta_driver_data data;
18833965Sjdp		accounting_sta_update_stats(hapd, sta, &data);
18938889Sjdp		interval = ACCT_DEFAULT_UPDATE_INTERVAL;
19033965Sjdp	}
19133965Sjdp
19233965Sjdp	eloop_register_timeout(interval, 0, accounting_interim_update,
19333965Sjdp			       hapd, sta);
19433965Sjdp}
19533965Sjdp
19638889Sjdp
19733965Sjdp/**
19833965Sjdp * accounting_sta_start - Start STA accounting
199130561Sobrien * @hapd: hostapd BSS data
200218822Sdim * @sta: The station
201218822Sdim */
202218822Sdimvoid accounting_sta_start(struct hostapd_data *hapd, struct sta_info *sta)
203218822Sdim{
20433965Sjdp	struct radius_msg *msg;
20533965Sjdp	struct os_time t;
20633965Sjdp	int interval;
20733965Sjdp
20833965Sjdp	if (sta->acct_session_started)
20933965Sjdp		return;
21033965Sjdp
21133965Sjdp	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
21233965Sjdp		       HOSTAPD_LEVEL_INFO,
213218822Sdim		       "starting accounting session %08X-%08X",
214218822Sdim		       sta->acct_session_id_hi, sta->acct_session_id_lo);
215218822Sdim
216218822Sdim	os_get_time(&t);
217218822Sdim	sta->acct_session_start = t.sec;
218104834Sobrien	sta->last_rx_bytes = sta->last_tx_bytes = 0;
219218822Sdim	sta->acct_input_gigawords = sta->acct_output_gigawords = 0;
220218822Sdim	hostapd_drv_sta_clear_stats(hapd, sta->addr);
221218822Sdim
222218822Sdim	if (!hapd->conf->radius->acct_server)
223218822Sdim		return;
224218822Sdim
225218822Sdim	if (sta->acct_interim_interval)
226218822Sdim		interval = sta->acct_interim_interval;
227218822Sdim	else
22833965Sjdp		interval = ACCT_DEFAULT_UPDATE_INTERVAL;
229218822Sdim	eloop_register_timeout(interval, 0, accounting_interim_update,
230104834Sobrien			       hapd, sta);
231218822Sdim
232218822Sdim	msg = accounting_msg(hapd, sta, RADIUS_ACCT_STATUS_TYPE_START);
233218822Sdim	if (msg &&
23433965Sjdp	    radius_client_send(hapd->radius, msg, RADIUS_ACCT, sta->addr) < 0)
235218822Sdim		radius_msg_free(msg);
236218822Sdim
237218822Sdim	sta->acct_session_started = 1;
23833965Sjdp}
239218822Sdim
240218822Sdim
241218822Sdimstatic void accounting_sta_report(struct hostapd_data *hapd,
242218822Sdim				  struct sta_info *sta, int stop)
24333965Sjdp{
244218822Sdim	struct radius_msg *msg;
245218822Sdim	int cause = sta->acct_terminate_cause;
246218822Sdim	struct hostap_sta_driver_data data;
247218822Sdim	struct os_time now;
248218822Sdim	u32 gigawords;
249218822Sdim
250218822Sdim	if (!hapd->conf->radius->acct_server)
25133965Sjdp		return;
25233965Sjdp
25333965Sjdp	msg = accounting_msg(hapd, sta,
25433965Sjdp			     stop ? RADIUS_ACCT_STATUS_TYPE_STOP :
25533965Sjdp			     RADIUS_ACCT_STATUS_TYPE_INTERIM_UPDATE);
25633965Sjdp	if (!msg) {
25733965Sjdp		printf("Could not create RADIUS Accounting message\n");
25833965Sjdp		return;
25933965Sjdp	}
26033965Sjdp
261218822Sdim	os_get_time(&now);
26233965Sjdp	if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_SESSION_TIME,
263218822Sdim				       now.sec - sta->acct_session_start)) {
264218822Sdim		printf("Could not add Acct-Session-Time\n");
265218822Sdim		goto fail;
26633965Sjdp	}
26733965Sjdp
268218822Sdim	if (accounting_sta_update_stats(hapd, sta, &data) == 0) {
26933965Sjdp		if (!radius_msg_add_attr_int32(msg,
27033965Sjdp					       RADIUS_ATTR_ACCT_INPUT_PACKETS,
271130561Sobrien					       data.rx_packets)) {
272218822Sdim			printf("Could not add Acct-Input-Packets\n");
273130561Sobrien			goto fail;
274104834Sobrien		}
27533965Sjdp		if (!radius_msg_add_attr_int32(msg,
276104834Sobrien					       RADIUS_ATTR_ACCT_OUTPUT_PACKETS,
27733965Sjdp					       data.tx_packets)) {
27833965Sjdp			printf("Could not add Acct-Output-Packets\n");
279218822Sdim			goto fail;
28033965Sjdp		}
28133965Sjdp		if (!radius_msg_add_attr_int32(msg,
28233965Sjdp					       RADIUS_ATTR_ACCT_INPUT_OCTETS,
28333965Sjdp					       data.rx_bytes)) {
28433965Sjdp			printf("Could not add Acct-Input-Octets\n");
28533965Sjdp			goto fail;
28633965Sjdp		}
28733965Sjdp		gigawords = sta->acct_input_gigawords;
28833965Sjdp#if __WORDSIZE == 64
28933965Sjdp		gigawords += data.rx_bytes >> 32;
29033965Sjdp#endif
29133965Sjdp		if (gigawords &&
292218822Sdim		    !radius_msg_add_attr_int32(
293218822Sdim			    msg, RADIUS_ATTR_ACCT_INPUT_GIGAWORDS,
294218822Sdim			    gigawords)) {
295218822Sdim			printf("Could not add Acct-Input-Gigawords\n");
296218822Sdim			goto fail;
297218822Sdim		}
298218822Sdim		if (!radius_msg_add_attr_int32(msg,
299218822Sdim					       RADIUS_ATTR_ACCT_OUTPUT_OCTETS,
300218822Sdim					       data.tx_bytes)) {
30133965Sjdp			printf("Could not add Acct-Output-Octets\n");
30233965Sjdp			goto fail;
303218822Sdim		}
30433965Sjdp		gigawords = sta->acct_output_gigawords;
30533965Sjdp#if __WORDSIZE == 64
30633965Sjdp		gigawords += data.tx_bytes >> 32;
30733965Sjdp#endif
30833965Sjdp		if (gigawords &&
30933965Sjdp		    !radius_msg_add_attr_int32(
31033965Sjdp			    msg, RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS,
31133965Sjdp			    gigawords)) {
312218822Sdim			printf("Could not add Acct-Output-Gigawords\n");
313218822Sdim			goto fail;
31433965Sjdp		}
31533965Sjdp	}
31633965Sjdp
317130561Sobrien	if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_EVENT_TIMESTAMP,
31833965Sjdp				       now.sec)) {
319104834Sobrien		printf("Could not add Event-Timestamp\n");
320218822Sdim		goto fail;
32133965Sjdp	}
32233965Sjdp
32333965Sjdp	if (eloop_terminated())
32460484Sobrien		cause = RADIUS_ACCT_TERMINATE_CAUSE_ADMIN_REBOOT;
32560484Sobrien
32660484Sobrien	if (stop && cause &&
32789857Sobrien	    !radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_TERMINATE_CAUSE,
32889857Sobrien				       cause)) {
32989857Sobrien		printf("Could not add Acct-Terminate-Cause\n");
33060484Sobrien		goto fail;
33160484Sobrien	}
33260484Sobrien
33333965Sjdp	if (radius_client_send(hapd->radius, msg,
33433965Sjdp			       stop ? RADIUS_ACCT : RADIUS_ACCT_INTERIM,
33533965Sjdp			       sta->addr) < 0)
336218822Sdim		goto fail;
337218822Sdim	return;
33833965Sjdp
33933965Sjdp fail:
34033965Sjdp	radius_msg_free(msg);
341104834Sobrien}
342218822Sdim
34333965Sjdp
344218822Sdim/**
34533965Sjdp * accounting_sta_interim - Send a interim STA accounting report
34633965Sjdp * @hapd: hostapd BSS data
34733965Sjdp * @sta: The station
34833965Sjdp */
34933965Sjdpstatic void accounting_sta_interim(struct hostapd_data *hapd,
35089857Sobrien				   struct sta_info *sta)
35133965Sjdp{
35233965Sjdp	if (sta->acct_session_started)
35333965Sjdp		accounting_sta_report(hapd, sta, 0);
35433965Sjdp}
355130561Sobrien
35677298Sobrien
35777298Sobrien/**
35877298Sobrien * accounting_sta_stop - Stop STA accounting
359104834Sobrien * @hapd: hostapd BSS data
36077298Sobrien * @sta: The station
361104834Sobrien */
36277298Sobrienvoid accounting_sta_stop(struct hostapd_data *hapd, struct sta_info *sta)
36377298Sobrien{
364104834Sobrien	if (sta->acct_session_started) {
36577298Sobrien		accounting_sta_report(hapd, sta, 1);
366104834Sobrien		eloop_cancel_timeout(accounting_interim_update, hapd, sta);
36733965Sjdp		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
36833965Sjdp			       HOSTAPD_LEVEL_INFO,
369104834Sobrien			       "stopped accounting session %08X-%08X",
37033965Sjdp			       sta->acct_session_id_hi,
37133965Sjdp			       sta->acct_session_id_lo);
372130561Sobrien		sta->acct_session_started = 0;
37333965Sjdp	}
37433965Sjdp}
375130561Sobrien
37633965Sjdp
37789857Sobrienvoid accounting_sta_get_id(struct hostapd_data *hapd,
37833965Sjdp				  struct sta_info *sta)
37933965Sjdp{
38033965Sjdp	sta->acct_session_id_lo = hapd->acct_session_id_lo++;
38189857Sobrien	if (hapd->acct_session_id_lo == 0) {
38233965Sjdp		hapd->acct_session_id_hi++;
38333965Sjdp	}
38433965Sjdp	sta->acct_session_id_hi = hapd->acct_session_id_hi;
385218822Sdim}
386218822Sdim
387218822Sdim
388218822Sdim/**
389218822Sdim * accounting_receive - Process the RADIUS frames from Accounting Server
390218822Sdim * @msg: RADIUS response message
39133965Sjdp * @req: RADIUS request message
39233965Sjdp * @shared_secret: RADIUS shared secret
39333965Sjdp * @shared_secret_len: Length of shared_secret in octets
39433965Sjdp * @data: Context data (struct hostapd_data *)
39533965Sjdp * Returns: Processing status
39633965Sjdp */
397104834Sobrienstatic RadiusRxResult
398104834Sobrienaccounting_receive(struct radius_msg *msg, struct radius_msg *req,
39933965Sjdp		   const u8 *shared_secret, size_t shared_secret_len,
40033965Sjdp		   void *data)
40133965Sjdp{
40233965Sjdp	if (radius_msg_get_hdr(msg)->code != RADIUS_CODE_ACCOUNTING_RESPONSE) {
403218822Sdim		printf("Unknown RADIUS message code\n");
40433965Sjdp		return RADIUS_RX_UNKNOWN;
405	}
406
407	if (radius_msg_verify(msg, shared_secret, shared_secret_len, req, 0)) {
408		printf("Incoming RADIUS packet did not have correct "
409		       "Authenticator - dropped\n");
410		return RADIUS_RX_INVALID_AUTHENTICATOR;
411	}
412
413	return RADIUS_RX_PROCESSED;
414}
415
416
417static void accounting_report_state(struct hostapd_data *hapd, int on)
418{
419	struct radius_msg *msg;
420
421	if (!hapd->conf->radius->acct_server || hapd->radius == NULL)
422		return;
423
424	/* Inform RADIUS server that accounting will start/stop so that the
425	 * server can close old accounting sessions. */
426	msg = accounting_msg(hapd, NULL,
427			     on ? RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_ON :
428			     RADIUS_ACCT_STATUS_TYPE_ACCOUNTING_OFF);
429	if (!msg)
430		return;
431
432	if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_ACCT_TERMINATE_CAUSE,
433				       RADIUS_ACCT_TERMINATE_CAUSE_NAS_REBOOT))
434	{
435		printf("Could not add Acct-Terminate-Cause\n");
436		radius_msg_free(msg);
437		return;
438	}
439
440	if (radius_client_send(hapd->radius, msg, RADIUS_ACCT, NULL) < 0)
441		radius_msg_free(msg);
442}
443
444
445/**
446 * accounting_init: Initialize accounting
447 * @hapd: hostapd BSS data
448 * Returns: 0 on success, -1 on failure
449 */
450int accounting_init(struct hostapd_data *hapd)
451{
452	struct os_time now;
453
454	/* Acct-Session-Id should be unique over reboots. If reliable clock is
455	 * not available, this could be replaced with reboot counter, etc. */
456	os_get_time(&now);
457	hapd->acct_session_id_hi = now.sec;
458
459	if (radius_client_register(hapd->radius, RADIUS_ACCT,
460				   accounting_receive, hapd))
461		return -1;
462
463	accounting_report_state(hapd, 1);
464
465	return 0;
466}
467
468
469/**
470 * accounting_deinit: Deinitilize accounting
471 * @hapd: hostapd BSS data
472 */
473void accounting_deinit(struct hostapd_data *hapd)
474{
475	accounting_report_state(hapd, 0);
476}
477