##! Implements base functionality for RADIUS analysis. Generates the radius.log file. module RADIUS; @load ./consts.bro @load base/utils/addrs export { redef enum Log::ID += { LOG }; type Info: record { ## Timestamp for when the event happened. ts : time &log; ## Unique ID for the connection. uid : string &log; ## The connection's 4-tuple of endpoint addresses/ports. id : conn_id &log; ## The username, if present. username : string &log &optional; ## MAC address, if present. mac : string &log &optional; ## The address given to the network access server, if ## present. This is only a hint from the RADIUS server ## and the network access server is not required to honor ## the address. framed_addr : addr &log &optional; ## Remote IP address, if present. This is collected ## from the Tunnel-Client-Endpoint attribute. remote_ip : addr &log &optional; ## Connect info, if present. connect_info : string &log &optional; ## Reply message from the server challenge. This is ## frequently shown to the user authenticating. reply_msg : string &log &optional; ## Successful or failed authentication. result : string &log &optional; ## The duration between the first request and ## either the "Access-Accept" message or an error. ## If the field is empty, it means that either ## the request or response was not seen. ttl : interval &log &optional; ## Whether this has already been logged and can be ignored. logged : bool &default=F; }; ## Event that can be handled to access the RADIUS record as it is sent on ## to the logging framework. global log_radius: event(rec: Info); } redef record connection += { radius: Info &optional; }; const ports = { 1812/udp }; redef likely_server_ports += { ports }; event bro_init() &priority=5 { Log::create_stream(RADIUS::LOG, [$columns=Info, $ev=log_radius, $path="radius"]); Analyzer::register_for_ports(Analyzer::ANALYZER_RADIUS, ports); } event radius_message(c: connection, result: RADIUS::Message) &priority=5 { if ( ! c?$radius ) { c$radius = Info($ts = network_time(), $uid = c$uid, $id = c$id); } switch ( RADIUS::msg_types[result$code] ) { case "Access-Request": if ( result?$attributes ) { # User-Name if ( ! c$radius?$username && 1 in result$attributes ) c$radius$username = result$attributes[1][0]; # Calling-Station-Id (we expect this to be a MAC) if ( ! c$radius?$mac && 31 in result$attributes ) c$radius$mac = normalize_mac(result$attributes[31][0]); # Tunnel-Client-EndPoint (useful for VPNs) if ( ! c$radius?$remote_ip && 66 in result$attributes ) c$radius$remote_ip = to_addr(result$attributes[66][0]); # Connect-Info if ( ! c$radius?$connect_info && 77 in result$attributes ) c$radius$connect_info = result$attributes[77][0]; } break; case "Access-Challenge": if ( result?$attributes ) { # Framed-IP-Address if ( ! c$radius?$framed_addr && 8 in result$attributes ) c$radius$framed_addr = raw_bytes_to_v4_addr(result$attributes[8][0]); if ( ! c$radius?$reply_msg && 18 in result$attributes ) c$radius$reply_msg = result$attributes[18][0]; } break; case "Access-Accept": c$radius$result = "success"; break; case "Access-Reject": c$radius$result = "failed"; break; # TODO: Support RADIUS accounting. (add port 1813/udp above too) #case "Accounting-Request": # break; # #case "Accounting-Response": # break; } } event radius_message(c: connection, result: RADIUS::Message) &priority=-5 { if ( c$radius?$result ) { local ttl = network_time() - c$radius$ts; if ( ttl != 0secs ) c$radius$ttl = ttl; Log::write(RADIUS::LOG, c$radius); delete c$radius; } } event connection_state_remove(c: connection) &priority=-5 { if ( c?$radius && ! c$radius$logged ) { c$radius$result = "unknown"; Log::write(RADIUS::LOG, c$radius); } }