/* ==================================================================== * The Kannel Software License, Version 1.0 * * Copyright (c) 2001-2005 Kannel Group * Copyright (c) 1998-2001 WapIT Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Kannel Group (http://www.kannel.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Kannel" and "Kannel Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please * contact org@kannel.org. * * 5. Products derived from this software may not be called "Kannel", * nor may "Kannel" appear in their name, without prior written * permission of the Kannel Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Kannel Group. For more information on * the Kannel Group, please see . * * Portions of this software are based upon software originally written at * WapIT Ltd., Helsinki, Finland for the Kannel project. */ /* * radius_acct.c - RADIUS accounting proxy thread * * Stipe Tolj */ #include #include #include #include "gwlib/gwlib.h" #include "radius/radius_acct.h" #include "radius/radius_pdu.h" static Dict *radius_table = NULL; /* maps client ip -> msisdn */ static Dict *session_table = NULL; /* maps session id -> client ip */ static Dict *client_table = NULL; /* maps client ip -> session id */ /* we will initialize hash tables in the size of our NAS ports */ #define RADIUS_NAS_PORTS 30 static Mutex *radius_mutex = NULL; static int run_thread = 0; /* * Beware that the official UDP port for RADIUS accounting packets * is 1813 (according to RFC2866). The previously used port 1646 has * been conflicting with an other protocol and "should" not be used. */ static Octstr *our_host = NULL; static long our_port = 1813; static Octstr *remote_host = NULL; static long remote_port = 1813; /* the shared secrets for NAS and remote RADIUS communication */ static Octstr *secret_nas = NULL; static Octstr *secret_radius = NULL; /* the global unified-prefix list */ static Octstr *unified_prefix = NULL; /* timeout in msec for the remote RADIUS responses */ static long remote_timeout = 40000; /************************************************************************* * */ /* * Updates the internal RADIUS mapping tables. Returns 1 if the * mapping has been processes and the PDU should be proxied to the * remote RADIUS server, otherwise if it is a duplicate returns 0. */ static int update_tables(RADIUS_PDU *pdu) { Octstr *client_ip, *msisdn; Octstr *type, *session_id; int ret = 0; Octstr *rm_item; client_ip = msisdn = type = session_id = NULL; /* only add if we have a Accounting-Request PDU */ if (pdu->type == 0x04) { /* check if we have a START or STOP event */ type = dict_get(pdu->attr, octstr_imm("Acct-Status-Type")); /* get the sesion id */ session_id = dict_get(pdu->attr, octstr_imm("Acct-Session-Id")); /* grep the needed data */ client_ip = dict_get(pdu->attr, octstr_imm("Framed-IP-Address")); msisdn = dict_get(pdu->attr, octstr_imm("Calling-Station-Id")); /* we can't add mapping without both components */ if (client_ip == NULL || msisdn == NULL) { warning(0, "RADIUS: NAS did either not send 'Framed-IP-Address' or/and " "'Calling-Station-Id', dropping mapping but will forward."); /* anyway forward the packet to remote RADIUS server */ return 1; } if (octstr_compare(type, octstr_imm("1")) == 0 && session_id && msisdn) { /* session START */ if (dict_get(radius_table, client_ip) == NULL && dict_get(session_table, session_id) == NULL) { Octstr *put_msisdn = octstr_duplicate(msisdn); Octstr *put_client_ip = octstr_duplicate(client_ip); Octstr *put_session_id = octstr_duplicate(session_id); Octstr *old_session_id, *old_client_ip; /* ok, this is a new session. If it contains an IP that is still * in the session/client tables then remove the old session from the * two tables session/client */ if ((old_session_id = dict_get(client_table, client_ip)) != NULL && (old_client_ip = dict_get(session_table, old_session_id)) != NULL && octstr_compare(old_session_id, session_id) != 0) { rm_item = dict_remove(client_table, client_ip); octstr_destroy(rm_item); rm_item = dict_remove(session_table, old_session_id); octstr_destroy(rm_item); octstr_destroy(old_session_id); octstr_destroy(old_client_ip); } /* insert both, new client IP and session to mapping tables */ dict_put(radius_table, client_ip, put_msisdn); dict_put(session_table, session_id, put_client_ip); dict_put(client_table, client_ip, put_session_id); info(0, "RADIUS: Mapping `%s <-> %s' for session id <%s> added.", octstr_get_cstr(client_ip), octstr_get_cstr(msisdn), octstr_get_cstr(session_id)); ret = 1; } else { warning(0, "RADIUS: Duplicate mapping `%s <-> %s' for session " "id <%s> received, ignoring.", octstr_get_cstr(client_ip), octstr_get_cstr(msisdn), octstr_get_cstr(session_id)); } } else if (octstr_compare(type, octstr_imm("2")) == 0) { /* session STOP */ Octstr *comp_client_ip; if ((msisdn = dict_get(radius_table, client_ip)) != NULL && (comp_client_ip = dict_get(session_table, session_id)) != NULL && octstr_compare(client_ip, comp_client_ip) == 0) { dict_remove(radius_table, client_ip); rm_item = dict_remove(client_table, client_ip); octstr_destroy(rm_item); dict_remove(session_table, session_id); info(0, "RADIUS: Mapping `%s <-> %s' for session id <%s> removed.", octstr_get_cstr(client_ip), octstr_get_cstr(msisdn), octstr_get_cstr(session_id)); octstr_destroy(msisdn); octstr_destroy(comp_client_ip); ret = 1; } else { warning(0, "RADIUS: Could not find mapping for `%s' session " "id <%s>, ignoring.", octstr_get_cstr(client_ip), octstr_get_cstr(session_id)); } } else { error(0, "RADIUS: unknown Acct-Status-Type `%s' received, ignoring.", octstr_get_cstr(type)); } } return ret; } /************************************************************************* * The main proxy thread. */ static void proxy_thread(void *arg) { int ss, cs; /* server and client sockets */ int fl; /* socket flags */ Octstr *addr = NULL; int forward; Octstr *tmp; run_thread = 1; ss = cs = -1; /* create client binding, only if we have a remote server * and make the client socet non-blocking */ if (remote_host != NULL) { cs = udp_client_socket(); fl = fcntl(cs, F_GETFL); fcntl(cs, F_SETFL, fl | O_NONBLOCK); addr = udp_create_address(remote_host, remote_port); } /* create server binding */ ss = udp_bind(our_port, octstr_get_cstr(our_host)); /* make the server socket non-blocking */ fl = fcntl(ss, F_GETFL); fcntl(ss, F_SETFL, fl | O_NONBLOCK); if (ss == -1) panic(0, "RADIUS: Couldn't set up server socket for port %ld.", our_port); while (run_thread) { RADIUS_PDU *pdu, *r; Octstr *data, *rdata; Octstr *from_nas, *from_radius; pdu = r = NULL; data = rdata = from_nas = from_radius = NULL; if (read_available(ss, 100000) < 1) continue; /* get request from NAS */ if (udp_recvfrom(ss, &data, &from_nas) == -1) { if (errno == EAGAIN) /* No datagram available, don't block. */ continue; error(0, "RADIUS: Couldn't receive request data from NAS"); continue; } tmp = udp_get_ip(from_nas); info(0, "RADIUS: Got data from NAS <%s:%d>", octstr_get_cstr(tmp), udp_get_port(from_nas)); octstr_destroy(tmp); octstr_dump(data, 0); /* unpacking the RADIUS PDU */ if ((pdu = radius_pdu_unpack(data)) == NULL) { warning(0, "RADIUS: Couldn't unpack PDU from NAS, ignoring."); goto error; } info(0, "RADIUS: from NAS: PDU type: %s", pdu->type_name); /* authenticate the Accounting-Request packet */ if (radius_authenticate_pdu(pdu, &data, secret_nas) == 0) { warning(0, "RADIUS: Authentication failed for PDU from NAS, ignoring."); goto error; } /* store to hash table if not present yet */ mutex_lock(radius_mutex); forward = update_tables(pdu); mutex_unlock(radius_mutex); /* create response PDU for NAS */ r = radius_pdu_create(0x05, pdu); /* * create response authenticator * code+identifier(req)+length+authenticator(req)+(attributes)+secret */ r->u.Accounting_Response.identifier = pdu->u.Accounting_Request.identifier; r->u.Accounting_Response.authenticator = octstr_duplicate(pdu->u.Accounting_Request.authenticator); /* pack response for NAS */ rdata = radius_pdu_pack(r); /* creates response autenticator in encoded PDU */ radius_authenticate_pdu(r, &rdata, secret_nas); /* * forward request to remote RADIUS server only if updated * and if we have a configured remote RADIUS server */ if ((remote_host != NULL) && forward) { if (udp_sendto(cs, data, addr) == -1) { error(0, "RADIUS: Couldn't send to remote RADIUS <%s:%ld>.", octstr_get_cstr(remote_host), remote_port); } else if (read_available(cs, remote_timeout) < 1) { error(0, "RADIUS: Timeout for response from remote RADIUS <%s:%ld>.", octstr_get_cstr(remote_host), remote_port); } else if (udp_recvfrom(cs, &data, &from_radius) == -1) { error(0, "RADIUS: Couldn't receive from remote RADIUS <%s:%ld>.", octstr_get_cstr(remote_host), remote_port); } else { info(0, "RADIUS: Got data from remote RADIUS <%s:%d>.", octstr_get_cstr(udp_get_ip(from_radius)), udp_get_port(from_radius)); octstr_dump(data, 0); /* XXX unpack the response PDU and check if the response * authenticator is valid */ } } /* send response to NAS */ if (udp_sendto(ss, rdata, from_nas) == -1) error(0, "RADIUS: Couldn't send response data to NAS <%s:%d>.", octstr_get_cstr(udp_get_ip(from_nas)), udp_get_port(from_nas)); error: radius_pdu_destroy(pdu); radius_pdu_destroy(r); octstr_destroy(rdata); octstr_destroy(data); octstr_destroy(from_nas); debug("radius.proxy", 0, "RADIUS: Mapping table contains %ld elements", dict_key_count(radius_table)); debug("radius.proxy", 0, "RADIUS: Session table contains %ld elements", dict_key_count(session_table)); debug("radius.proxy", 0, "RADIUS: Client table contains %ld elements", dict_key_count(client_table)); } octstr_destroy(addr); } /************************************************************************* * Public functions: init, shutdown, mapping. */ Octstr *radius_acct_get_msisdn(Octstr *client_ip) { Octstr *m, *r; char *uf; /* if no proxy thread is running, then pass NULL as result */ if (radius_table == NULL || client_ip == NULL) return NULL; mutex_lock(radius_mutex); m = dict_get(radius_table, client_ip); mutex_unlock(radius_mutex); r = m ? octstr_duplicate(m) : NULL; /* apply number normalization */ uf = unified_prefix ? octstr_get_cstr(unified_prefix) : NULL; normalize_number(uf, &r); return r; } void radius_acct_init(CfgGroup *grp) { unsigned long nas_ports = 0; /* get configured parameters */ if ((our_host = cfg_get(grp, octstr_imm("our-host"))) == NULL) { our_host = octstr_create("0.0.0.0"); } if ((remote_host = cfg_get(grp, octstr_imm("remote-host"))) != NULL) { cfg_get_integer(&remote_port, grp, octstr_imm("remote-port")); if ((secret_radius = cfg_get(grp, octstr_imm("secret-radius"))) == NULL) { panic(0, "RADIUS: No shared secret `secret-radius' for remote RADIUS in `radius-acct' provided."); } } cfg_get_integer(&our_port, grp, octstr_imm("our-port")); cfg_get_integer(&remote_timeout, grp, octstr_imm("remote-timeout")); if ((cfg_get_integer(&nas_ports, grp, octstr_imm("nas-ports"))) == -1) { nas_ports = RADIUS_NAS_PORTS; } if ((secret_nas = cfg_get(grp, octstr_imm("secret-nas"))) == NULL) { panic(0, "RADIUS: No shared secret `secret-nas' for NAS in `radius-acct' provided."); } unified_prefix = cfg_get(grp, octstr_imm("unified-prefix")); info(0, "RADIUS: local RADIUS accounting proxy at <%s:%ld>", octstr_get_cstr(our_host), our_port); if (remote_host == NULL) { info(0, "RADIUS: remote RADIUS accounting server is absent"); } else { info(0, "RADIUS: remote RADIUS accounting server at <%s:%ld>", octstr_get_cstr(remote_host), remote_port); } info(0, "RADIUS: initializing internal hash tables with %ld buckets.", nas_ports); radius_mutex = mutex_create(); /* init hash tables */ radius_table = dict_create(nas_ports, (void (*)(void *))octstr_destroy); session_table = dict_create(nas_ports, (void (*)(void *))octstr_destroy); client_table = dict_create(nas_ports, (void (*)(void *))octstr_destroy); gwthread_create(proxy_thread, NULL); } void radius_acct_shutdown(void) { if (radius_mutex == NULL) /* haven't init'ed at all */ return ; mutex_lock(radius_mutex); run_thread = 0; mutex_unlock(radius_mutex); gwthread_join_every(proxy_thread); dict_destroy(radius_table); dict_destroy(session_table); dict_destroy(client_table); mutex_destroy(radius_mutex); octstr_destroy(our_host); octstr_destroy(remote_host); octstr_destroy(secret_nas); octstr_destroy(secret_radius); octstr_destroy(unified_prefix); info(0, "RADIUS: accounting proxy stopped."); }