/** * yradius.cpp * This file is part of the YATE Project http://YATE.null.ro * * RADIUS Client functionality for YATE * * Yet Another Telephony Engine - a fully featured software PBX and IVR * Copyright (C) 2004-2014 Null Team * * Largely based on the code sent by Faizan Naqvi (Tili) * * This software is distributed under multiple licenses; * see the COPYING file in the main directory for licensing * information for this specific distribution. * * This use of this software may be subject to additional restrictions. * See the LEGAL file in the main directory for details. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ #include #include #include #include #include #include using namespace TelEngine; namespace { // anonymous #define RADIUS_MAXLEN 4096 enum { NoError = 0, AcctSuccess = NoError, AuthSuccess = NoError, AuthFailed, ServerErr, ConfErr, UnknownErr, }; static Configuration s_cfg; static Mutex s_cfgMutex("YRadius::cfg"); static ObjList acctBuilders; static SocketAddr s_localAddr(AF_INET); static Socket s_localSock; static bool s_localTime = false; static bool s_shortnum = false; static bool s_unisocket = false; static bool s_printAttr = false; static bool s_pb_enabled = false; static bool s_pb_parallel = false; static bool s_pb_simplify = false; static bool s_cisco = true; static bool s_quintum = false; static String s_pb_stoperror; static String s_pb_maxcall; // Attribute types, not exactly as in RFC 2865 enum { a_void = 0, // invalid, used in separators a_binary, // rfc2865 string a_string, // rfc2865 text a_ipaddr, // rfc2865 address a_int, // rfc2865 integer a_date, // rfc2865 time a_avpair // special text formatted as name=value }; // Structure for building attribute tables typedef struct { int code; const char* name; const char* confName; int type; } rad_dict; // Structure for building pointers to standard or vendor tables typedef struct { int vendor; const char* name; const rad_dict* dict; } rad_vendor; // Standard or vendor-specific RADIUS values and tables // RADIUS packet codes enum { Access_Request = 1, Access_Accept = 2, Access_Reject = 3, Accounting_Request = 4, Accounting_Response = 5, Access_Challenge = 11, // experimental codes Status_Server = 12, Status_Client = 13, }; // Accounting status types enum { Acct_Start = 1, Acct_Stop = 2, Acct_Alive = 3, Acct_On = 7, Acct_Off = 8, }; // Subattribute type for Digest-Attributes - see draft-sterman-aaa-sip-00.txt enum { Digest_Realm = 1, Digest_Nonce = 2, Digest_Method = 3, Digest_URI = 4, Digest_QOP = 5, Digest_Algo = 6, Digest_Body = 7, Digest_CNonce = 8, Digest_NCount = 9, Digest_UserName = 10 }; // Standard RADIUS attributes static rad_dict radius_dict[] = { { 1, "User-Name", "User-Name", a_string }, { 2, "User-Password", "User-Password", a_binary }, { 3, "CHAP-Password", "CHAP-Password", a_binary }, { 4, "NAS-IP-Address", "NAS-IP-Address", a_ipaddr }, { 5, "NAS-Port", "NAS-Port", a_int }, { 6, "Service-Type", "Service-Type", a_int }, { 18, "Reply-Message", "Reply-Message", a_string }, { 26, "Vendor-Specific", "Vendor-Specific", a_binary }, { 27, "Session-Timeout", "Session-Timeout", a_int }, { 30, "Called-Station-Id", "Called-Station-Id", a_string }, { 31, "Calling-Station-Id", "Calling-Station-Id", a_string }, { 32, "NAS-Identifier", "NAS-Identifier", a_string }, { 40, "Acct-Status-Type", "Acct-Status-Type", a_int }, { 41, "Acct-Delay-Time", "Acct-Delay-Time", a_int }, { 42, "Acct-Input-Octets", "Acct-Input-Octets", a_int }, { 43, "Acct-Output-Octets", "Acct-Output-Octets", a_int }, { 44, "Acct-Session-Id", "Acct-Session-Id", a_string }, { 45, "Acct-Authentic", "Acct-Authentic", a_int }, { 46, "Acct-Session-Time", "Acct-Session-Time", a_int }, { 47, "Acct-Input-Packets", "Acct-Input-Packets", a_int }, { 48, "Acct-Output-Packets", "Acct-Output-Packets", a_int }, { 49, "Acct-Terminate-Cause", "Acct-Terminate-Cause", a_int }, { 50, "Acct-Multi-Session-Id", "Acct-Multi-Session-Id", a_string }, { 51, "Acct-Link-Count", "Acct-Link-Count", a_int }, { 60, "CHAP-Challenge", "CHAP-Challenge", a_binary }, { 61, "NAS-Port-Type", "NAS-Port-Type", a_int }, { 62, "Port-Limit", "Port-Limit", a_int }, { 63, "Login-LAT-Port", "Login-LAT-Port", a_string }, { 68, "Configuration-Token", "Configuration-Token", a_binary }, { 206, "Digest-Response", "Digest-Response", a_string }, { 207, "Digest-Attributes", "Digest-Attributes", a_binary }, { 0, 0, 0, a_void } }; // Cisco vendor attributes static rad_dict cisco_dict[]= { { 1, "Cisco-AVPair", "Cisco-AVPair", a_string }, { 2, "Cisco-NAS-Port", "Cisco-NAS-Port", a_string }, { 2, "NAS-Port-Name", "NAS-Port-Name", a_string }, // alternate name { 23, "h323-remote-address", "h323-remote-address", a_avpair }, { 24, "h323-conf-id", "h323-conf-id", a_avpair }, { 25, "h323-setup-time", "h323-setup-time", a_avpair }, { 26, "h323-call-origin", "h323-call-origin", a_avpair }, { 27, "h323-call-type", "h323-call-type", a_avpair }, { 28, "h323-connect-time", "h323-connect-time", a_avpair }, { 29, "h323-disconnect-time", "h323-disconnect-time", a_avpair }, { 30, "h323-disconnect-cause", "h323-disconnect-cause", a_avpair }, { 31, "h323-voice-quality", "h323-voice-quality", a_avpair }, { 33, "h323-gw-id", "h323-gw-id", a_avpair }, { 34, "h323-call-treatment", "h323-call-treatment", a_string }, { 101, "h323-credit-amount", "h323-credit-amount", a_avpair }, { 102, "h323-credit-time", "h323-credit-time", a_avpair }, { 103, "h323-return-code", "h323-return-code", a_avpair }, { 104, "h323-prompt-id", "h323-prompt-id", a_avpair }, { 105, "h323-time-and-day", "h323-time-and-day", a_avpair }, { 106, "h323-redirect-number", "h323-redirect-number", a_avpair }, { 107, "h323-preferred-lang", "h323-preferred-lang", a_avpair }, { 108, "h323-redirect-ip-address", "h323-redirect-ip-address", a_avpair }, { 109, "h323-billing-model", "h323-billing-model", a_avpair }, { 110, "h323-currency", "h323-currency", a_avpair }, { 187, "Cisco-Multilink-ID", "Cisco-Multilink-ID", a_int }, { 188, "Cisco-Num-In-Multilink", "Cisco-Num-In-Multilink", a_int }, { 190, "Cisco-Pre-Input-Octets", "Cisco-Pre-Input-Octets", a_int }, { 191, "Cisco-Pre-Output-Octets", "Cisco-Pre-Output-Octets", a_int }, { 192, "Cisco-Pre-Input-Packets", "Cisco-Pre-Input-Packets", a_int }, { 193, "Cisco-Pre-Output-Packets", "Cisco-Pre-Output-Packets", a_int }, { 194, "Cisco-Maximum-Time", "Cisco-Maximum-Time", a_int }, { 195, "Cisco-Disconnect-Cause", "Cisco-Disconnect-Cause", a_int }, { 197, "Cisco-Data-Rate", "Cisco-Data-Rate", a_int }, { 198, "Cisco-PreSession-Time", "Cisco-PreSession-Time", a_int }, { 208, "Cisco-PW-Lifetime", "Cisco-PW-Lifetime", a_int }, { 209, "Cisco-IP-Direct", "Cisco-IP-Direct", a_int }, { 210, "Cisco-PPP-VJ-Slot-Comp", "Cisco-PPP-VJ-Slot-Comp", a_int }, { 212, "Cisco-PPP-Async-Map", "Cisco-PPP-Async-Map", a_int }, { 217, "Cisco-IP-Pool-Definition", "Cisco-IP-Pool-Definition", a_int }, { 218, "Cisco-Assign-IP-Pool", "Cisco-Assign-IP-Pool", a_int }, { 228, "Cisco-Route-IP", "Cisco-Route-IP", a_int }, { 233, "Cisco-Link-Compression", "Cisco-Link-Compression", a_int }, { 234, "Cisco-Target-Util", "Cisco-Target-Util", a_int }, { 235, "Cisco-Maximum-Channels", "Cisco-Maximum-Channels", a_int }, { 242, "Cisco-Data-Filter", "Cisco-Data-Filter", a_int }, { 243, "Cisco-Call-Filter", "Cisco-Call-Filter", a_int }, { 244, "Cisco-Idle-Limit", "Cisco-Idle-Limit", a_int }, { 255, "Cisco-Xmit-Rate", "Cisco-Xmit-Rate", a_int }, { 0, 0, 0, a_void } }; static rad_dict quintum_dict[]= { { 1, "Quintum-AVPair", "Quintum-AVPair", a_string }, { 2, "Tenor-NAS-Port", "Tenor-NAS-Port", a_string }, { 23, "h323-remote-address", "Quintum-h323-remote-address", a_avpair }, { 24, "h323-conf-id", "Quintum-h323-conf-id", a_avpair }, { 25, "h323-setup-time", "Quintum-h323-setup-time", a_avpair }, { 26, "h323-call-origin", "Quintum-h323-call-origin", a_avpair }, { 27, "h323-call-type", "Quintum-h323-call-type", a_avpair }, { 28, "h323-connect-time", "Quintum-h323-connect-time", a_avpair }, { 29, "h323-disconnect-time", "Quintum-h323-disconnect-time", a_avpair }, { 30, "h323-disconnect-cause", "Quintum-h323-disconnect-cause", a_avpair }, { 31, "h323-voice-quality", "Quintum-h323-voice-quality", a_avpair }, { 33, "h323-gw-id", "Quintum-h323-gw-id", a_avpair }, { 101, "h323-credit-amount", "Quintum-h323-credit-amount", a_avpair }, { 102, "h323-credit-time", "Quintum-h323-credit-time", a_avpair }, { 103, "h323-return-code", "Quintum-h323-return-code", a_avpair }, { 104, "h323-prompt-id", "Quintum-h323-prompt-id", a_avpair }, { 106, "h323-redirect-number", "Quintum-h323-redirect-number", a_avpair }, { 107, "h323-preferred-lang", "Quintum-h323-preferred-lang", a_avpair }, { 109, "h323-billing-model", "Quintum-h323-billing-model", a_avpair }, { 110, "h323-currency", "Quintum-h323-currency", a_avpair }, { 230, "Trunkid-In", "Quintum-Trunkid-In", a_string }, { 231, "Trunkid-Out", "Quintum-Trunkid-Out", a_string }, { 0, 0, 0, a_void } }; // Microsoft vendor attributes static rad_dict ms_dict[]= { { 1, "MS-CHAP-Response", "MS-CHAP-Response", a_binary }, { 2, "MS-CHAP-Error", "MS-CHAP-Error", a_binary }, { 3, "MS-CHAP-CPW-1", "MS-CHAP-CPW-1", a_binary }, { 4, "MS-CHAP-CPW-2", "MS-CHAP-CPW-2", a_binary }, { 5, "MS-CHAP-LM-Enc-PW", "MS-CHAP-LM-Enc-PW", a_binary }, { 6, "MS-CHAP-NT-Enc-PW", "MS-CHAP-NT-Enc-PW", a_binary }, { 7, "MS-MPPE-Encryption-Policy", "MS-MPPE-Encryption-Policy", a_binary }, { 8, "MS-MPPE-Encryption-Types", "MS-MPPE-Encryption-Types", a_binary }, { 9, "MS-RAS-Vendor", "MS-RAS-Vendor", a_int }, { 10, "MS-CHAP-Domain", "MS-CHAP-Domain", a_binary }, { 11, "MS-CHAP-Challenge", "MS-CHAP-Challenge", a_binary }, { 12, "MS-CHAP-MPPE-Keys", "MS-CHAP-MPPE-Keys", a_binary }, { 13, "MS-BAP-Usage", "MS-BAP-Usage", a_int }, { 14, "MS-Link-Utilization-Threshold", "MS-Link-Utilization-Threshold", a_int }, { 15, "MS-Link-Drop-Time-Limit", "MS-Link-Drop-Time-Limit", a_int }, { 16, "MS-MPPE-Send-Key", "MS-MPPE-Send-Key", a_binary }, { 17, "MS-MPPE-Recv-Key", "MS-MPPE-Recv-Key", a_binary }, { 18, "MS-RAS-Version", "MS-RAS-Version", a_binary }, { 22, "MS-Filter", "MS-Filter", a_binary }, { 23, "MS-Acct-Auth-Type", "MS-Acct-Auth-Type", a_int }, { 24, "MS-Acct-EAP-Type", "MS-Acct-EAP-Type", a_int }, { 25, "MS-CHAP2-Response", "MS-CHAP2-Response", a_binary }, { 26, "MS-CHAP2-Success", "MS-CHAP2-Success", a_binary }, { 27, "MS-CHAP2-PW", "MS-CHAP2-PW", a_binary }, { 30, "MS-Primary-NBNS-Server", "MS-Primary-NBNS-Server", a_ipaddr }, { 31, "MS-Secondary-NBNS-Server", "MS-Secondary-NBNS-Server", a_ipaddr }, { 0, 0, 0, a_void } }; static rad_vendor vendors_dict[] = { { 0, 0, radius_dict }, { 9, "cisco", cisco_dict }, { 311, "microsoft", ms_dict }, { 6618, "quintum", quintum_dict }, { 0, 0, 0 } }; // End of RADIUS values and tables // map termination cause keywords to Acct-Terminate-Cause attribute values static TokenDict dict_errors[] = { { "noanswer", 4 }, // Idle-Timeout { "timeout", 5 }, // Session-Timeout { "drop", 7 }, // Admin-Reset { "reboot", 7 }, // Admin-Reboot { "halt", 7 }, // Admin-Reboot { "offline", 8 }, // Port-Error { "congestion", 8 }, // Port-Error { "failure", 9 }, // NAS-Error { "noconn", 9 }, // NAS-Error { "busy", 13 }, // Port-Preempted { "nocall", 15 }, // Service-Unavailable { "noroute", 15 }, // Service-Unavailable { "forbidden", 17 }, // User-Error { "rejected", 18 }, // Host-Request { 0, 0 }, }; // Class to hold one RADIUS attribute class RadAttrib : public GenObject { public: RadAttrib(const rad_dict* type, int vendor, void* value, unsigned int length); RadAttrib(const rad_dict* type, int vendor, const char* value); RadAttrib(const char* name, const char* value); RadAttrib(const char* name, int value); RadAttrib(const char* name, unsigned char subType, const char* value); virtual ~RadAttrib(); bool packTo(DataBlock& data) const; bool getString(String& retval) const; inline bool isValid() const { return (m_type != 0); } inline bool isVendor() const { return (m_type && (m_type->code == 26)) && (m_vendor == 0); } inline int vendor() const { return m_vendor; } inline const rad_dict* type() const { return m_type; } inline const char* name() const { return m_type ? m_type->name : 0; } inline int code() const { return m_type ? m_type->code : -1; } inline const DataBlock& data() const { return m_value; } static const rad_dict* find(const char* name, int* vendor = 0, const char** vendName = 0); static const rad_dict* find(int code, int vendor = 0); static bool decode(void* buf, unsigned int len, ObjList& list); inline static bool decode(const DataBlock& data, ObjList& list) { return decode(data.data(),data.length(),list); } static RadAttrib* decode(void*& buffer, unsigned int& length, int vendor = 0); private: bool assign(const char* value); bool assign(int value); bool assign(unsigned char subType, const char* value); const rad_dict* m_type; int m_vendor; DataBlock m_value; }; // Class to encapsulate an entire client request operation class RadiusClient : public GenObject { public: RadiusClient() : m_socket(0), m_authPort(0), m_acctPort(0), m_timeout(2000), m_retries(2), m_cisco(s_cisco), m_quintum(s_quintum) { } virtual ~RadiusClient(); inline const String& server() const { return m_server; } inline bool addCisco() const { return m_cisco; } inline bool addQuintum() const { return m_quintum; } bool setRadServer(const char* host, int authport, int acctport, const char* secret, int timeoutms = 4000, int retries = 2); bool setRadServer(const NamedList& sect); bool addSocket(); int doAuthenticate(ObjList* result = 0); int doAccounting(ObjList* result = 0); bool addAttribute(const char* attrib, const char* val, bool emptyOk = false); bool addAttribute(const char* attrib, int val); bool addAttribute(const char* attrib, unsigned char subType, const char* val, bool emptyOk = false); void addAttributes(NamedList& params, NamedList* list); bool prepareAttributes(NamedList& params, bool forAcct = true, String* user = 0); bool returnAttributes(NamedList& params, const ObjList* attributes, bool ok = true); static bool fillRandom(DataBlock& data, int len); private: Socket* socket() const; static unsigned char newSessionId(); int makeRequest(int port, unsigned char request, unsigned char* response = 0, ObjList* result = 0); bool checkAuthenticator(const unsigned char* buffer, int length); inline bool checkAuthenticator(const DataBlock& data) { return checkAuthenticator((const unsigned char*)data.data(),data.length()); } static unsigned char s_sessionId; Socket* m_socket; ObjList m_attribs; String m_server,m_secret,m_section; unsigned int m_authPort,m_acctPort; int m_timeout, m_retries; bool m_cisco, m_quintum; DataBlock m_authdata; }; class RadiusModule : public Module { public: RadiusModule(); virtual ~RadiusModule(); virtual void initialize(); protected: bool m_init; }; INIT_PLUGIN(RadiusModule); static const String s_fmtCisco("cisco_format"); static const String s_fmtQuintum("quintum_format"); class AuthHandler : public MessageHandler { public: inline AuthHandler(int prio) : MessageHandler("user.auth",prio,__plugin.name()) { } virtual bool received(Message &msg); }; class AcctHandler : public MessageHandler { public: inline AcctHandler(int prio) : MessageHandler("call.cdr",prio,__plugin.name()) { } virtual bool received(Message &msg); }; class RadiusHandler : public MessageHandler { public: inline RadiusHandler(int prio) : MessageHandler("radius.generate",prio,__plugin.name()) { } virtual bool received(Message &msg); }; // PortaOne specific routing static void portaBillingRoute(NamedList& params, const ObjList* attributes) { String route; // should we call multiple targets parallel or sequencial? const char* rsep = s_pb_parallel ? " " : " | "; for (; attributes; attributes = attributes->next()) { const RadAttrib* attr = static_cast(attributes->get()); // PortaBilling uses only Cisco-AVPair if (!(attr && (attr->vendor() == 9) && (attr->code() == 1))) continue; String tmp; attr->getString(tmp); if (tmp.startSkip("h323-ivr-in=DURATION:",false)) { int sec = tmp.toInteger(); if (sec > 0) { Debug(&__plugin,DebugCall,"PortaBilling setting timeout %d seconds",sec); tmp = sec * 1000; params.setParam("timeout",tmp); } continue; } if (!tmp.startSkip("h323-ivr-in=PortaBilling_",false)) continue; XDebug(&__plugin,DebugInfo,"PortaBilling '%s'",tmp.c_str()); if (tmp.startSkip("Routing:",false)) { if (s_pb_simplify) { int sep = tmp.find(';'); if (sep >= 0) tmp.assign(tmp,sep); } if (tmp.null()) continue; tmp = "sip/sip:" + tmp; if (route.null()) route = tmp; else { if (!route.startsWith("fork",true)) route = "fork " + route; route << rsep << tmp; } } else if (tmp.startSkip("CLI:",false)) { if (tmp) { Debug(&__plugin,DebugCall,"PortaBilling setting caller '%s'",tmp.c_str()); params.setParam("caller",tmp); } } else if (tmp.startSkip("CompleteNumber:",false)) { if (tmp) { Debug(&__plugin,DebugCall,"PortaBilling setting called '%s'",tmp.c_str()); params.setParam("called",tmp); } } } if (route) { Debug(&__plugin,DebugCall,"PortaBilling returned route '%s'",route.c_str()); params.setParam("callto",route); if (s_pb_maxcall) params.setParam("maxcall",s_pb_maxcall); if (s_pb_stoperror && route.startsWith("fork",true)) params.setParam("stoperror",s_pb_stoperror); } } // find one attribute by name, optionally store vendor ID and name const rad_dict* RadAttrib::find(const char* name, int* vendor, const char** vendName) { for (rad_vendor* v = vendors_dict; v->dict; v++) { for (const rad_dict* dict = v->dict; dict->name; dict++) { if (!strcasecmp(name,dict->confName)) { if (vendor) *vendor = v->vendor; if (vendName) *vendName = v->name; return dict; } } } return 0; } // find one attribute by code and vendor ID const rad_dict* RadAttrib::find(int code, int vendor) { for (rad_vendor* v = vendors_dict; v->dict; v++) { if (v->vendor == vendor) { for (const rad_dict* dict = v->dict; dict->name; dict++) { if (dict->code == code) return dict; } break; } } return 0; } // decode one attribute, adjust buffer and length; on error set buffer to null RadAttrib* RadAttrib::decode(void*& buffer, unsigned int& length, int vendor) { XDebug(&__plugin,DebugAll,"RadAttrib::decode(%p,%u,%d)",buffer,length,vendor); if (!(buffer && length)) return 0; if (length < 3) { buffer = 0; return 0; } unsigned char* ptr = (unsigned char*)buffer; int code = ptr[0]; unsigned int len = ptr[1]; if ((len < 3) || (len > length)) { buffer = 0; return 0; } buffer = ptr + len; length -= len; const rad_dict* type = find(code,vendor); if (!type) return 0; switch (type->type) { case a_ipaddr: case a_int: case a_date: if (len != 6) { buffer = 0; // roll back length to make it easier to debug length += len; return 0; } } return new RadAttrib(type,vendor,ptr+2,len-2); } // decode an entire received set of attributes bool RadAttrib::decode(void* buf, unsigned int len, ObjList& list) { unsigned int len1 = len; while (len) { RadAttrib* attr = decode(buf,len); if (!attr) { if (!buf) { Debug(&__plugin,DebugMild,"Invalid attribute at offset %u",len1 - len + 20); return false; } continue; } if (attr->isVendor()) { unsigned int len2 = attr->data().length(); char* ptr = (char*)attr->data().data(); if ((len2 < 4) || !ptr) { DDebug(&__plugin,DebugMild,"Invalid vendor attribute %u len=%u",attr->code(),len2); attr->destruct(); return false; } void* buf2 = ptr + 4; len2 -= 4; int vendor = ((int)ptr[0] << 24) | ((int)ptr[1] << 16) | ((int)ptr[2] << 8) | ptr[3]; while (len2) { RadAttrib* attr2 = decode(buf2,len2,vendor); if (!attr2) { if (!buf2) { DDebug(&__plugin,DebugMild,"Invalid vendor %u attribute",vendor); attr->destruct(); return false; } continue; } list.append(attr2); } attr->destruct(); } else list.append(attr); } return true; } RadAttrib::RadAttrib(const rad_dict* type, int vendor, void* value, unsigned int length) : m_type(type), m_vendor(vendor), m_value(value,length) { XDebug(&__plugin,DebugAll,"RadAttrib::RadAttrib(%p,%d,%p,%u) [%p]",type,vendor,value,length,this); } RadAttrib::RadAttrib(const rad_dict* type, int vendor, const char* value) : m_type(type), m_vendor(vendor) { XDebug(&__plugin,DebugAll,"RadAttrib::RadAttrib(%p,%d,'%s') [%p]",type,vendor,value,this); if (m_type && value) assign(value); } RadAttrib::RadAttrib(const char* name, const char* value) : m_type(0), m_vendor(0) { XDebug(&__plugin,DebugAll,"RadAttrib::RadAttrib('%s','%s') [%p]",name,value,this); if (null(name) || null(value)) return; m_type = find(name,&m_vendor); if (!m_type) { Debug(&__plugin,DebugWarn,"Failed to find item %s in dictionary",name); return; } assign(value); } RadAttrib::RadAttrib(const char* name, int value) : m_type(0), m_vendor(0) { XDebug(&__plugin,DebugAll,"RadAttrib::RadAttrib('%s',%d) [%p]",name,value,this); if (null(name)) return; m_type = find(name,&m_vendor); if (!m_type) { Debug(&__plugin,DebugWarn,"Failed to find item %s in dictionary",name); return; } assign(value); } RadAttrib::RadAttrib(const char* name, unsigned char subType, const char* value) { XDebug(&__plugin,DebugAll,"RadAttrib::RadAttrib('%s',%u,'%s') [%p]",name,subType,value,this); if (null(name) || null(value)) return; m_type = find(name,&m_vendor); if (!m_type) { Debug(&__plugin,DebugWarn,"Failed to find item %s in dictionary",name); return; } assign(subType,value); } RadAttrib::~RadAttrib() { XDebug(&__plugin,DebugAll,"RadAttrib::~RadAttrib type=%p vendor=%d [%p]",m_type,m_vendor,this); } bool RadAttrib::assign(const char* value) { if (null(value)) return false; switch (m_type->type) { case a_string: m_value.assign((void*)value,strlen(value)); break; case a_avpair: { String val(m_type->name); val << "=" << value; m_value.assign((void*)val.c_str(),val.length()); } break; case a_int: { unsigned int val = htonl((unsigned int)atoi(value)); m_value.assign((void*)&val,sizeof(int32_t)); } break; case a_ipaddr: { uint32_t addr = inet_addr(value); m_value.assign((void*)&addr,sizeof(int32_t)); } break; default: Debug(&__plugin,DebugWarn,"Ignoring unknown attribute of type %d",m_type->type); return false; } return true; } bool RadAttrib::assign(int value) { switch (m_type->type) { case a_string: { String val(value); m_value.assign((void*)val.c_str(),val.length()); } break; case a_avpair: { String val(m_type->name); val << "=" << value; m_value.assign((void*)val.c_str(),val.length()); } break; case a_int: case a_ipaddr: { unsigned int val = htonl(value); m_value.assign((void*)&val,sizeof(int32_t)); } break; default: Debug(&__plugin,DebugWarn,"Ignoring unknown attribute of type %d",m_type->type); return false; } return true; } bool RadAttrib::assign(unsigned char subType, const char* value) { if (null(value) || (m_type->type != a_binary)) return false; // copy at most 253 characters so with header will be under 255 String val(value,253); unsigned char header[2]; header[0] = subType; header[1] = (val.length() + 2) & 0xff; m_value.assign(header,2); m_value.append(val); return true; } bool RadAttrib::packTo(DataBlock& data) const { if (m_value.null() || !m_type) return false; // invalid attribute. // RADIUS cannot support attributes longer than 255 unsigned char buf[256]; unsigned char* ptr = buf; unsigned int len = m_value.length(); if (len > 253) len = 253; unsigned int total = len + 2; if (m_vendor) { if (len > 247) len = 247; total = len + 8; // put generic vendor type, full length, vendor code *ptr++ = 26; *ptr++ = 0xff & total; *ptr++ = 0xff & (m_vendor >> 24); *ptr++ = 0xff & (m_vendor >> 16); *ptr++ = 0xff & (m_vendor >> 8); *ptr++ = 0xff & m_vendor; } // put type, length, data *ptr++ = m_type->code; *ptr++ = 0xff & (len + 2); memcpy(ptr,m_value.data(),len); if (len != m_value.length()) Debug(&__plugin,DebugMild,"Attribute '%s' (%u) truncated from %u to %u bytes", m_type->name,m_type->code,m_value.length(),len); DataBlock tmp(buf,total,false); data += tmp; tmp.clear(false); return true; } bool RadAttrib::getString(String& retval) const { if (!m_type) return false; retval.clear(); if (m_value.null()) return false; switch (m_type->type) { case a_string: retval.assign((const char*)m_value.data(),m_value.length()); break; case a_avpair: retval.assign((const char*)m_value.data(),m_value.length()); { // strip away any "name=" prefix String tmp = m_type->name; tmp << "="; retval.startSkip(tmp,false); } break; case a_ipaddr: { struct in_addr addr; addr.s_addr = *(int32_t*)m_value.data(); // FIXME: this should be globally thread safe s_cfgMutex.lock(); retval = inet_ntoa(addr); s_cfgMutex.unlock(); } break; case a_int: retval = (int)ntohl(*(int32_t*)m_value.data()); break; default: return false; } return true; } unsigned char RadiusClient::s_sessionId = (unsigned char)(Time::now() & 0xff); RadiusClient::~RadiusClient() { delete m_socket; } Socket* RadiusClient::socket() const { return m_socket ? m_socket : &s_localSock; } // Create and add a local UDP socket to the client request bool RadiusClient::addSocket() { if (m_socket) return true; SocketAddr localAddr(AF_INET); localAddr.host(s_localAddr.host()); if (!(localAddr.valid() && localAddr.host())) { Debug(&__plugin,DebugInfo,"Invalid address '%s' - falling back to global socket", localAddr.host().c_str()); return false; } // we only have UDP support Socket* s = new Socket(PF_INET,SOCK_DGRAM,IPPROTO_IP); if (!(s && s->valid())) { Debug(&__plugin,DebugWarn,"Error creating UDP socket - falling back to global socket"); delete s; return false; } if (!s->bind(localAddr)) { Debug(&__plugin,DebugWarn,"Error %d binding to %s - falling back to global socket", s->error(),localAddr.host().c_str()); delete s; return false; } DDebug(&__plugin,DebugInfo,"Created new socket for request"); m_socket = s; return true; } // Build incremental session ID byte unsigned char RadiusClient::newSessionId() { Lock lock(s_cfgMutex); return s_sessionId++; } // Set the server parameters bool RadiusClient::setRadServer(const char* host, int authport, int acctport, const char* secret, int timeoutms, int retries) { // adjust an absolute minimum of 1 try with a 500ms timeout if (retries < 1) retries = 1; if (timeoutms < 500) timeoutms = 500; m_server = host; m_authPort = authport; m_acctPort = acctport; m_secret = secret; m_timeout = timeoutms; m_retries = retries; return (m_server && (m_authPort || m_acctPort)); } // Set the server parameters from a config file section bool RadiusClient::setRadServer(const NamedList& sect) { return setRadServer(sect.getValue("server"), sect.getIntValue("auth_port",1812), sect.getIntValue("acct_port",1813), sect.getValue("secret"), sect.getIntValue("timeout",2000), sect.getIntValue("retries",2)); } // Fill a data block with (pseudo) random data bool RadiusClient::fillRandom(DataBlock& data, int len) { data.assign(0,len); unsigned char *dd = (unsigned char*)data.data(); if (!dd) return false; unsigned int r = 0; while (len--) { while (!r) r = Random::random(); *dd++ = r & 0xff; r = r >> 8; } return true; } // Cryptographically check if the response is properly authenticated bool RadiusClient::checkAuthenticator(const unsigned char* buffer, int length) { if (!buffer) return false; const unsigned char* recauth = buffer+4; const unsigned char* recattr = buffer+20; int attrlen = length - 20; MD5 md5(buffer,4); md5.update(m_authdata); if (attrlen > 0) md5.update(recattr,attrlen); md5.update(m_secret); if (memcmp(md5.rawDigest(),recauth,16)) { Debug(&__plugin,DebugMild,"Authenticators do not match"); return false; } Debug(&__plugin,DebugAll,"Authenticator matched for response"); return true; } // Make one request, wait for answer and optionally decode it int RadiusClient::makeRequest(int port, unsigned char request, unsigned char* response, ObjList* result) { if (!(port && socket() && socket()->valid())) return ServerErr; // create the address to send and receive packets SocketAddr sockAddr(AF_INET); sockAddr.host(m_server); sockAddr.port(port); // build the attribute block DataBlock attrdata; for (ObjList* l = &m_attribs; l; l = l->next()) { const RadAttrib* attr = static_cast(l->get()); if (attr) attr->packTo(attrdata); } int datalen = 20 + attrdata.length(); if (datalen > RADIUS_MAXLEN) { Debug(&__plugin,DebugWarn,"Packet of %u bytes exceeds RADIUS maximum %u",datalen,RADIUS_MAXLEN); return UnknownErr; } // now we create the header for the RADIUS packet unsigned char sessionId = newSessionId(); unsigned char tmp[4]; tmp[0] = request; tmp[1] = sessionId; tmp[2] = (datalen >> 8) & 0xff; tmp[3] = datalen & 0xff; // build the authenticator which is 16 octets long switch (request) { case Access_Request: // random 16 octets used to authenticate the answer if (!fillRandom(m_authdata,16)) return UnknownErr; break; case Accounting_Request: // authenticate our packet to the server (see rfc2866) { DataBlock zeros(0,16); MD5 md5(tmp,4); md5 << zeros << attrdata << m_secret; m_authdata.assign((void*)md5.rawDigest(),16); } break; default: Debug(&__plugin,DebugFail,"Unknown request %u was asked. We only support Access and Accounting",request); return UnknownErr; } // now we build the packet out of header, authenticator and attributes DataBlock radpckt(tmp,sizeof(tmp)); radpckt.append(m_authdata); radpckt.append(attrdata); if (!s_unisocket) addSocket(); // we have the data ready, send it and wait for an answer for (int r = m_retries; r > 0; r--) { if (socket()->sendTo(radpckt.data(),radpckt.length(),sockAddr) == Socket::socketError()) { Alarm(&__plugin,"socket",DebugCrit,"Packet sending error %d to %s:%d", socket()->error(),sockAddr.host().c_str(),sockAddr.port()); return UnknownErr; } // ok, we now must wait for receiving the matching answer u_int64_t tryEnd = Time::now() + 1000 * m_timeout; int64_t tout; while ((tout = (tryEnd - Time::now())) > 0) { bool canRead = false; if (!socket()->select(&canRead,NULL,NULL,tout)) { Debug(&__plugin,DebugWarn,"Error %d in select",socket()->error()); return UnknownErr; } if (!canRead) { if (r > 1) Debug(&__plugin,DebugMild,"Timeout waiting for server %s:%d, there are %d retries left", sockAddr.host().c_str(),sockAddr.port(),r-1); break; } SocketAddr recvAddr; unsigned char recdata[RADIUS_MAXLEN]; int readlen = socket()->recvFrom(recdata,sizeof(recdata),recvAddr); if (readlen == Socket::socketError()) { Debug(&__plugin,DebugWarn,"Packet reading error %d from %s:%d", socket()->error(),sockAddr.host().c_str(),sockAddr.port()); break; } if (readlen < 20) { Debug(&__plugin,DebugInfo,"Ignoring short (%d bytes) response from %s:%d", readlen,recvAddr.host().c_str(),recvAddr.port()); continue; } datalen = ((unsigned int)recdata[2] << 8) | recdata[3]; if ((datalen < 20) || (datalen > readlen)) { Debug(&__plugin,DebugInfo,"Ignoring packet with length %d (%d received) response from %s:%d", datalen,readlen,recvAddr.host().c_str(),recvAddr.port()); continue; } if (recdata[1] != sessionId) { DDebug(&__plugin,DebugAll,"Ignoring mismatched (%u vs %u) response from %s:%d", recdata[1],sessionId,recvAddr.host().c_str(),recvAddr.port()); continue; } if (!checkAuthenticator(recdata,datalen)) { Debug(&__plugin,DebugMild,"Ignoring unauthenticated session %u response from %s:%d", sessionId,recvAddr.host().c_str(),recvAddr.port()); continue; } if (result) { // try to decode answer if (!RadAttrib::decode(recdata+20,datalen-20,*result)) // authenticated but malformed - no reason to try again return ServerErr; } if (response) *response = recdata[0]; DDebug(&__plugin,DebugInfo,"Received valid response %u on session %u from %s:%d", recdata[0],sessionId,recvAddr.host().c_str(),recvAddr.port()); return NoError; } } Debug(&__plugin,DebugWarn,"Timeout receiving session %u from server %s:%d", sessionId,sockAddr.host().c_str(),sockAddr.port()); return ServerErr; } // Make an authentication request, wait for answer int RadiusClient::doAuthenticate(ObjList* result) { unsigned char response = 0; int err = makeRequest(m_authPort,Access_Request,&response,result); if (err != NoError) { Debug(&__plugin,DebugWarn,"Aborting authentication with radius %s:%d", m_server.c_str(),m_authPort); return err; } // we have the response of radius or some other app to which we accidentally sent data because user put wrong port or ip. if (response != Access_Accept) { Debug(&__plugin,DebugMild,"Server returned %u, assuming Access-Reject",response); return AuthFailed; } Debug(&__plugin,DebugInfo,"Server returned Access-Accept"); return AuthSuccess; } // Make an accounting request, wait for answer int RadiusClient::doAccounting(ObjList* result) { unsigned char response = 0; int err = makeRequest(m_acctPort,Accounting_Request,&response,result); if (err != NoError) { Debug(&__plugin,DebugWarn,"Aborting accounting with radius %s:%d", m_server.c_str(),m_acctPort); return err; } // we have the response of radius or some other app to which we accidentally sent data because user put wrong port or ip. if (response != Accounting_Response) { Debug(&__plugin,DebugWarn,"Server %s:%d returned %d but we were expecting Accounting_Response", m_server.c_str(),m_acctPort,response); return ServerErr; } Debug(&__plugin,DebugInfo,"Server returned Accounting-Response"); return AcctSuccess; } // Add one text attribute bool RadiusClient::addAttribute(const char* attrib, const char* val, bool emptyOk) { if (null(attrib)) return false; if (null(val)) return emptyOk; RadAttrib* attr = new RadAttrib(attrib,val); if (attr->isValid()) { m_attribs.append(attr); return true; } attr->destruct(); return false; } // Add one numeric attribute bool RadiusClient::addAttribute(const char* attrib, int val) { if (null(attrib)) return false; RadAttrib* attr = new RadAttrib(attrib,val); if (attr->isValid()) { m_attribs.append(attr); return true; } attr->destruct(); return false; } // Add one text attribute with subtype bool RadiusClient::addAttribute(const char* attrib, unsigned char subType, const char* val, bool emptyOk) { if (null(attrib)) return false; if (null(val)) return emptyOk; RadAttrib* attr = new RadAttrib(attrib,subType,val); if (attr->isValid()) { m_attribs.append(attr); return true; } attr->destruct(); return false; } // Copy from parameter list (usually message) to RADIUS attributes void RadiusClient::addAttributes(NamedList& params, NamedList* list) { if (!list) return; DDebug(&__plugin,DebugInfo,"Adding attributes from section '%s'",list->c_str()); unsigned int n = list->length(); for (unsigned int i = 0; i < n; i++) { NamedString* s = list->getParam(i); if (!s) continue; if ((s->name() == YSTRING("rad_include")) || s->name().startsWith("inc:",false)) { if (*s == *list) Debug(&__plugin,DebugWarn,"Section '%s' includes itself!",s->c_str()); else addAttributes(params,s_cfg.getSection(*s)); continue; } if (s->name().startsWith("set:",false)) { // set parameters in the params itself String key = s->name().substr(4).trimBlanks(); if (key.null()) continue; String val = *s; params.replaceParams(val); params.setParam(key,val); continue; } if (!s->name().startsWith("add:",false)) continue; // set RADIUS attributes String key = s->name().substr(4).trimBlanks(); if (key.null()) continue; String val = *s; params.replaceParams(val); static const Regexp r("^\\([0-9]\\+\\):\\(.*\\)"); if (key.matches(r)) { int subType = key.matchString(1).toInteger(-1); if ((subType >= 0) && (subType <= 255)) { key = key.matchString(2); addAttribute(key,subType,val); } else Debug(&__plugin,DebugWarn,"Invalid subtype in attribute '%s'",key.c_str()); } else addAttribute(key,val); } } // Find matching NAS section and populate attributes accordingly bool RadiusClient::prepareAttributes(NamedList& params, bool forAcct, String* user) { const char* caller = params.getValue("caller"); const char* called = 0; if (s_shortnum) { // prefer short called over calledfull called = params.getValue("called"); if (!called) called = params.getValue("calledfull"); } else { // prefer long calledfull over called called = params.getValue("calledfull"); if (!called) called = params.getValue("called"); } const char* username = params.getValue("username"); if (!username) username = params.getValue("authname"); if (!username) { if (forAcct) username = caller; // we were unable to build an username // don't even send such a request to PortaOne if (s_pb_enabled && !username) return false; } Lock lock(s_cfgMutex); NamedList* nasSect = 0; String nasName; unsigned int n = s_cfg.sections(); for (unsigned int i = 0; i < n; i++) { NamedList* sect = s_cfg.getSection(i); if (!sect) continue; nasName = *sect; // section must be [nas] or [nas SOMETHING] if (!nasName.startSkip("nas")) continue; // found section - see if it's enabled for auth or accounting if (!sect->getBoolValue(forAcct ? "rad_acct" : "rad_auth",true)) continue; unsigned int n2 = sect->length(); for (unsigned int i2 = 0; i2 < n2; i2++) { NamedString* pair = sect->getParam(i2); if (!pair || pair->null()) continue; // ignore format control keys if ((pair->name() == s_fmtCisco) || (pair->name() == s_fmtQuintum)) continue; // ignore keys like rad_SOMETHING or SOMETHING:SOMETHING if (pair->name().startsWith("rad_",false) || (pair->name().find(':') >= 0)) continue; Regexp r(pair->c_str()); const char* val = c_safe(params.getValue(pair->name())); if (!r.matches(val)) { // mismatch - skip this section sect = 0; break; } } if (sect) { nasSect = sect; break; } } if (!nasSect) return false; // find the name of the corresponding server section NamedString* serv = nasSect->getParam("rad_server"); String servName("radius"); if (serv) { // explicit empty name means get out if (serv->null()) return false; servName << " " << *serv; } else if (nasName) servName << " " << nasName; // find the section, exit if empty NamedList* servSect = s_cfg.getSection(servName); if (!servSect) { Debug(&__plugin,DebugWarn,"Section [%s] does not exist",servName.c_str()); return false; } if (!setRadServer(*servSect)) { Debug(&__plugin,DebugWarn,"Section [%s] does not hold a valid server",servName.c_str()); return false; } // remember the name of the NAS section m_section = *nasSect; Debug(&__plugin,DebugInfo,"Using sections [%s] and [%s] for %s", m_section.c_str(),servName.c_str(),forAcct ? "accounting" : "authentication"); m_cisco = nasSect->getBoolValue(s_fmtCisco,servSect->getBoolValue(s_fmtCisco,s_cisco)); m_quintum = nasSect->getBoolValue(s_fmtQuintum,servSect->getBoolValue(s_fmtQuintum,s_quintum)); addAttribute("User-Name",username); addAttribute("Calling-Station-Id",caller); addAttribute("Called-Station-Id",called); addAttributes(params,nasSect); addAttributes(params,servSect); if (user) *user = username; return true; } // Copy some attributes back from RADIUS answer to parameter list (message) bool RadiusClient::returnAttributes(NamedList& params, const ObjList* attributes, bool ok) { Lock lock(s_cfgMutex); NamedList* sect = s_cfg.getSection(m_section); if (!sect) return false; String attrDump; for (; attributes; attributes = attributes->next()) { const RadAttrib* attr = static_cast(attributes->get()); if (!attr) continue; if (s_printAttr && __plugin.debugAt(DebugAll)) { String val; attr->getString(val); attrDump << "\r\n " << attr->name() << "='" << val << "'"; } String tmp(ok ? "ret:" : "ret-fail:"); tmp += attr->name(); String* par = sect->getParam(tmp); if (par && *par) { attr->getString(tmp); if (!params.getParam(*par)) { params.addParam(*par,tmp); continue; } // Handle duplicate params int count = 0; while (++count) { if (params.getParam(*par + "." + String(count))) continue; params.addParam(*par + "." + String(count),tmp); break; } } } if (attrDump) Debug(&__plugin,DebugAll,"Returned attributes:%s",attrDump.c_str()); return true; } bool AuthHandler::received(Message& msg) { if (!msg.getBoolValue("auth_radius",true)) return false; String proto = msg.getValue("protocol",msg.getValue("module")); if (proto.null()) return false; RadiusClient radclient; // preserve the actually authenticated username in case we succeed String user; if (!radclient.prepareAttributes(msg,false,&user)) return false; bool cisco = msg.getBoolValue(s_fmtCisco,radclient.addCisco()); bool quintum = msg.getBoolValue(s_fmtQuintum,radclient.addQuintum()); // TODO: process plaintext password if ((proto == "digest") || (proto == "sip")) { const char* resp = msg.getValue("response"); const char* nonce = msg.getValue("nonce"); const char* method = msg.getValue("method"); const char* uri = msg.getValue("uri"); const char* user = msg.getValue("username"); if (resp && nonce && method && uri && user) { // mandatory auth parameters if (!( radclient.addAttribute("Digest-Response",resp) && radclient.addAttribute("Digest-Attributes",Digest_Nonce,nonce) && radclient.addAttribute("Digest-Attributes",Digest_Method,method) && radclient.addAttribute("Digest-Attributes",Digest_URI,uri) && radclient.addAttribute("Digest-Attributes",Digest_UserName,user) )) return false; // optional auth parameters radclient.addAttribute("Digest-Attributes",Digest_Realm,msg.getValue("realm")); radclient.addAttribute("Digest-Attributes",Digest_Algo,msg.getValue("algorithm","MD5")); radclient.addAttribute("Digest-Attributes",Digest_QOP,msg.getValue("qop")); } } String address = msg.getValue("address"); // suppress any port number - IMHO this is stupid int sep = address.find(':'); if (sep >= 0) address = address.substr(0,sep); if (cisco) radclient.addAttribute("h323-remote-address",address); if (quintum) radclient.addAttribute("Quintum-h323-remote-address",address); if (cisco || quintum) { const String& billid = msg[YSTRING("billid")]; if (billid) { // create a Cisco-compatible conference ID MD5 cid(billid); String confid; confid << cid.hexDigest().substr(0,8) << " "; confid << cid.hexDigest().substr(8,8) << " "; confid << cid.hexDigest().substr(16,8) << " "; confid << cid.hexDigest().substr(24,8); confid.toUpper(); String tmp("call-id="); if (address.null()) address = s_localAddr.host(); tmp << billid << "@" << address; if (cisco) { radclient.addAttribute("h323-conf-id",confid); radclient.addAttribute("Cisco-AVPair",tmp); } if (quintum) { radclient.addAttribute("Quintum-h323-conf-id",confid); radclient.addAttribute("Quintum-AVPair",tmp); } } } ObjList result; if (radclient.doAuthenticate(&result) != AuthSuccess) { radclient.returnAttributes(msg,&result,false); return false; } // copy back the username we actually authenticated if (user) msg.setParam("username",user); // and pick whatever other parameters we want to return radclient.returnAttributes(msg,&result,true); if (s_pb_enabled) portaBillingRoute(msg,&result); // signal we don't return a password msg.retValue().clear(); return true; } // Build a Cisco style (like NTP) date/time string static bool ciscoTime(double t, String& ret) { time_t sec = (time_t)floor(t); unsigned int msec = (unsigned int)(1000.0 * (t - sec)); // we need to protect localtime/gmtime which may not be thread safe static Mutex mutex(false,"YRadius::ciscoTime"); Lock lock(mutex); struct tm* brokenTime = s_localTime ? localtime(&sec) : gmtime(&sec); if (!brokenTime) return false; char buf[64]; ret.clear(); if (!strftime(buf,sizeof(buf),"%H:%M:%S",brokenTime)) return false; ret = buf; sprintf(buf,".%03u ",msec); ret << buf; if (!strftime(buf,sizeof(buf),"%Z %a %b %d %Y",brokenTime)) { ret.clear(); return false; } ret << buf; return false; } bool AcctHandler::received(Message& msg) { if (!msg.getBoolValue(YSTRING("cdrwrite_radius"),true)) return false; int acctStat = 0; const String& op = msg[YSTRING("operation")]; if (op == YSTRING("initialize")) acctStat = Acct_Start; else if (op == YSTRING("finalize")) acctStat = Acct_Stop; else if ((op == YSTRING("status")) || (op == YSTRING("update"))) acctStat = Acct_Alive; else return false; String billid = msg.getValue("billid"); if (billid.null()) return false; String address = msg.getValue("address"); int sep = address.find(':'); if (sep >= 0) address = address.substr(0,sep); String dir = msg.getValue("direction"); if (dir == "incoming") dir = "answer"; else if (dir == "outgoing") dir = "originate"; else return false; RadiusClient radclient; if (!radclient.prepareAttributes(msg)) return false; bool cisco = msg.getBoolValue(s_fmtCisco,radclient.addCisco()); bool quintum = msg.getBoolValue(s_fmtQuintum,radclient.addQuintum()); // create a Cisco-compatible conference ID MD5 cid(billid); String confid; confid << cid.hexDigest().substr(0,8) << " "; confid << cid.hexDigest().substr(8,8) << " "; confid << cid.hexDigest().substr(16,8) << " "; confid << cid.hexDigest().substr(24,8); confid.toUpper(); // cryptographically generate an unique call leg ID MD5 sid(billid); sid << msg.getValue("chan"); radclient.addAttribute("Acct-Session-Id",sid.hexDigest()); radclient.addAttribute("Acct-Status-Type",acctStat); if (cisco) { radclient.addAttribute("h323-call-origin",dir); radclient.addAttribute("h323-conf-id",confid); radclient.addAttribute("h323-remote-address",address); } if (quintum) { radclient.addAttribute("Quintum-h323-call-origin",dir); radclient.addAttribute("Quintum-h323-conf-id",confid); radclient.addAttribute("Quintum-h323-remote-address",address); } String tmp("call-id="); if (address.null()) address = s_localAddr.host(); tmp << billid << "@" << address; if (cisco) radclient.addAttribute("Cisco-AVPair",tmp); if (quintum) radclient.addAttribute("Quintum-AVPair",tmp); double t = msg.getDoubleValue("time"); if (cisco || quintum) { ciscoTime(t,tmp); if (cisco) radclient.addAttribute("h323-setup-time",tmp); if (quintum) radclient.addAttribute("Quintum-h323-setup-time",tmp); } double duration = msg.getDoubleValue("duration",-1); double billtime = msg.getDoubleValue("billtime"); if ((cisco || quintum) && (Acct_Start != acctStat) && (duration >= 0.0)) { if (billtime > 0.0) { ciscoTime(t+duration-billtime,tmp); if (cisco) radclient.addAttribute("h323-connect-time",tmp); if (quintum) radclient.addAttribute("Quintum-h323-connect-time",tmp); } } if (Acct_Stop == acctStat) { if ((cisco || quintum) && (duration >= 0.0)) { ciscoTime(t+duration,tmp); if (cisco) radclient.addAttribute("h323-disconnect-time",tmp); if (quintum) radclient.addAttribute("Quintum-h323-disconnect-time",tmp); } radclient.addAttribute("Acct-Session-Time",(int)billtime); int cause = lookup(msg.getValue("status"),dict_errors,-1,10); if (cause >= 0) radclient.addAttribute("Acct-Terminate-Cause",cause); String tmp = msg.getValue("reason"); if (tmp) { tmp = "disconnect-text=" + tmp; if (cisco) radclient.addAttribute("Cisco-AVPair",tmp); if (quintum) radclient.addAttribute("Quintum-AVPair",tmp); } } radclient.doAccounting(); return false; } bool RadiusHandler::received(Message& msg) { bool auth = msg.getBoolValue(YSTRING("auth"),true); int acctStat = 0; if (!auth) { const String& op = msg[YSTRING("operation")]; if (op == YSTRING("initialize")) acctStat = Acct_Start; else if (op == YSTRING("finalize")) acctStat = Acct_Stop; else if ((op == YSTRING("status")) || (op == YSTRING("update"))) acctStat = Acct_Alive; else return false; } RadiusClient radclient; if (!radclient.prepareAttributes(msg)) return false; if (!auth) radclient.addAttribute("Acct-Status-Type",acctStat); ObjList result; if (auth && radclient.doAuthenticate(&result) != AuthSuccess) { radclient.returnAttributes(msg,&result,false); return false; } else if (!auth && radclient.doAccounting(&result) != AuthSuccess) { radclient.returnAttributes(msg,&result,false); return false; } radclient.returnAttributes(msg,&result,true); return true; } RadiusModule::RadiusModule() : Module("yradius","misc"), m_init(false) { Output("Loaded module Radius client"); } RadiusModule::~RadiusModule() { Output("Unloaded module Radius client"); } void RadiusModule::initialize() { Output("Initializing module Radius client"); s_cfgMutex.lock(); s_cfg = Engine::configFile("yradius"); s_cfg.load(); s_localTime = s_cfg.getBoolValue("general","local_time",false); s_shortnum = s_cfg.getBoolValue("general","short_number",false); s_unisocket = s_cfg.getBoolValue("general","single_socket",false); s_printAttr = s_cfg.getBoolValue("general","print_attributes",false); s_pb_enabled = s_cfg.getBoolValue("portabill","enabled",false); s_pb_parallel = s_cfg.getBoolValue("portabill","parallel",false); s_pb_simplify = s_cfg.getBoolValue("portabill","simplify",false); s_cisco = s_cfg.getBoolValue("general",s_fmtCisco,true); s_quintum = s_cfg.getBoolValue("general",s_fmtQuintum,true); s_pb_stoperror = s_cfg.getValue("portabill","stoperror","busy"); s_pb_maxcall = s_cfg.getValue("portabill","maxcall"); s_cfgMutex.unlock(); if (m_init || !s_cfg.getBoolValue("general","enabled",true)) return; s_localAddr.host(s_cfg.getValue("general","addr")); s_localAddr.port(s_cfg.getIntValue("general","port",1810)); if (s_localAddr.host().null()) { Debug(this,DebugNote,"Local address not set or invalid. Radius functions disabled"); return; } if (!(s_localAddr.valid() && s_localAddr.host() && s_localAddr.port())) { Debug(this,DebugWarn,"Invalid address %s:%d. Radius functions unavailable", s_localAddr.host().c_str(),s_localAddr.port()); return; } // we only have UDP support if (!s_localSock.create(PF_INET,SOCK_DGRAM,IPPROTO_IP)) { Alarm(this,"socket",DebugCrit,"Error creating socket. Radius functions unavailable"); return; } if (!s_localSock.bind(s_localAddr)) { Alarm(this,"socket",DebugCrit,"Error %d binding to %s:%d. Radius functions unavailable", s_localSock.error(),s_localAddr.host().c_str(),s_localAddr.port()); return; } m_init = true; setup(); Engine::install(new AuthHandler(s_cfg.getIntValue("general","auth_priority",70))); Engine::install(new AcctHandler(s_cfg.getIntValue("general","acct_priority",70))); Engine::install(new RadiusHandler(100)); } }; // anonymous namespace /* vi: set ts=8 sw=4 sts=4 noet: */