sippy / b2bua

Sippy B2BUA is a RFC3261-compliant Session Initiation Protocol (SIP) stack and Back-to-back user agent (B2BUA) server software.
http://b2bua.org
BSD 2-Clause "Simplified" License
173 stars 72 forks source link

Get Radius attributes for authentication request from SIP Header #37

Open twmobius opened 4 years ago

twmobius commented 4 years ago

Hi @sobomax,

I wanted to add the sip realm in the authentication radius request (without perform digest authentication), and I've been looking through RadiusAuthorization; there is an extra_attributes parameter in the do_auth method which is not used anywhere and got me thinking.

Similarly to pass_headers parameters it would be nice to have an option to define a sip header from which b2bua will get specific radius attributes and add them to the Authentication request (via the extra_attributes property)

So I've modified the b2bua a bit to support exactly that. The patch adds an -x option, from which the user can add a sip header for b2bua to read radius attributes.

So for example, opensips might do something like:

append_hf('X-Test-Header: h323-ivr-out=SomeProperty=1\r\n');
$du = 'b2bua.ip'

route(RELAY);

sippy would have to run with -x x-test-header parameter and in the authentication request the header contents is added to the radius request. Obviously it's up to the administrator/ matching dictionary to supply valid radius key/ value pairs for this to work.

I am providing the patch in this comment, I don't know if you would be willing to add it to the b2bua source code. If you are, I'd be happy to supply you with a PR

Index: sippy/b2bua_radius.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- sippy/b2bua_radius.py   (revision f1c375b3454445b66b88c4b3cdc365306bb0ef18)
+++ sippy/b2bua_radius.py   (date 1603809097780)
@@ -119,11 +119,12 @@
     rtp_proxy_session = None
     huntstop_scodes = None
     pass_headers = None
+    extra_attributes = None
     auth_proc = None
     proxied = False
     challenge = None

-    def __init__(self, remote_ip, source, global_config, pass_headers):
+    def __init__(self, remote_ip, source, global_config, pass_headers, extra_attributes):
         self.id = CallController.id
         CallController.id += 1
         self.global_config = global_config
@@ -137,6 +138,7 @@
         self.remote_ip = remote_ip
         self.source = source
         self.pass_headers = pass_headers
+        self.extra_attributes = extra_attributes

     def recvEvent(self, event, ua):
         if ua == self.uaA:
@@ -191,11 +193,12 @@
                 elif auth == None or auth.username == None or len(auth.username) == 0:
                     self.username = self.remote_ip
                     self.auth_proc = self.global_config['_radius_client'].do_auth(self.remote_ip, self.cli, self.cld, self.cGUID, \
-                      self.cId, self.remote_ip, self.rDone)
+                      self.cId, self.remote_ip, self.rDone, extra_attributes=self.extra_attributes)
                 else:
                     self.username = auth.username
                     self.auth_proc = self.global_config['_radius_client'].do_auth(auth.username, self.cli, self.cld, self.cGUID, 
-                      self.cId, self.remote_ip, self.rDone, auth.realm, auth.nonce, auth.uri, auth.response)
+                      self.cId, self.remote_ip, self.rDone, auth.realm, auth.nonce, auth.uri, auth.response,
+                      extra_attributes=self.extra_attributes)
                 return
             if self.state not in (CCStateARComplete, CCStateConnected, CCStateDisconnecting) or self.uaO == None:
                 return
@@ -465,7 +468,27 @@
                 hfs = req.getHFs(header)
                 if len(hfs) > 0:
                     pass_headers.extend(hfs)
-            cc = CallController(remote_ip, source, self.global_config, pass_headers)
+
+            extra_attributes = None
+
+            if 'auth_extra_header' in self.global_config:
+                header = self.global_config['auth_extra_header']
+
+                hfs = req.getHFs(header)
+
+                if len(hfs) > 0:
+                    extra_attributes = []
+
+                    for header in hfs:
+                        kvPairs = header.body.body.split(';')
+
+                        for pair in kvPairs:
+                            [key, _, value] = pair.partition("=")
+
+                            if value != '':
+                                extra_attributes.append((key, value))
+
+            cc = CallController(remote_ip, source, self.global_config, pass_headers, extra_attributes)
             cc.challenge = challenge
             rval = cc.uaA.recvRequest(req, sip_t)
             self.ccmap.append(cc)
@@ -668,7 +691,7 @@
     global_config['_orig_argv'] = sys.argv[:]
     global_config['_orig_cwd'] = os.getcwd()
     try:
-        opts, args = getopt.getopt(sys.argv[1:], 'fDl:p:d:P:L:s:a:t:T:k:m:A:ur:F:R:h:c:M:HC:W:',
+        opts, args = getopt.getopt(sys.argv[1:], 'fDl:p:d:P:L:s:a:t:T:k:m:A:ur:F:R:h:c:M:HC:W:x:',
           global_config.get_longopts())
     except getopt.GetoptError:
         usage(global_config)
@@ -759,6 +782,9 @@
         if o == '-h':
             for a in a.split(','):
                 global_config.check_and_set('pass_header', a)
+            continue
+        if o == '-x':
+            global_config.check_and_set('auth_extra_header', a)
             continue
         if o == '-c':
             global_config.check_and_set('b2bua_socket', a)
Index: sippy/MyConfigParser.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- sippy/MyConfigParser.py (revision f1c375b3454445b66b88c4b3cdc365306bb0ef18)
+++ sippy/MyConfigParser.py (date 1603808883519)
@@ -98,6 +98,8 @@
                              'and "SUBSCRIBE" messages. Address in the format ' \
                              '"host[:port]"'),
  'nat_traversal':     ('B', 'enable NAT traversal for signalling'), \
+ 'auth_extra_header': ('S', 'sip header containing radius parameters to pass ' \
+                            'to authentication request'), \
  'xmpp_b2bua_id':     ('I', 'ID passed to the XMPP socket server')}

 class MyConfigParser(RawConfigParser):
twmobius commented 4 years ago

I went a step further and added another parameter named parameter on the Routing h323-ivr-in response in the authentication that a user could supply a set of radius attribute-value pairs that b2bua later adds in the accounting messages.

So for example if the authorization response if of this sort:

h323-ivr-in = 'Routing:123@192.168.5.104:5060;credit-time=7200;expires=15;np_expires=5;bill-to=123@realm;parameters=h323-ivr-out=Test-Data:d=2,vr=1,ar=8'

both originate and answer, start, stop accounting will containing the header:

h323-ivr-out = 'Test-Data:d=2,vr=1,ar=8'

The idea is that when the billing engine performs authorisation for a call, it could provide information to the accounting events so that the engine could easily charge the call without having to recalculate everything from scratch.

Please let me know if you are interested in such feature additions on b2bua. :)

sobomax commented 4 years ago

Looks promising. I don't mind having this as a feature, would you please convert this to a PR?

Thanks!

-Max

On Thu., Oct. 29, 2020, 7:38 a.m. Paris, notifications@github.com wrote:

I went a step further and added another parameter named parameter on the Routing h323-ivr-in response in the authentication that a user could supply a set of radius attribute-value pairs that b2bua later adds in the accounting messages.

So for example if the authorization response if of this sort:

h323-ivr-in = 'Routing:123@192.168.5.104:5060 ;credit-time=7200;expires=15;np_expires=5;bill-to=123@realm ;parameters=h323-ivr-out=Test-Data:d=2,vr=1,ar=8'

both originate and answer, start, stop accounting will containing the header:

h323-ivr-out = 'Test-Data:d=2,vr=1,ar=8'

The idea is that when the billing engine performs authorisation for a call, it could provide information to the accounting events so that the engine could easily charge the call without having to recalculate everything from scratch.

Please let me know if you are interested in such feature additions on b2bua. :)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sippy/b2bua/issues/37#issuecomment-718796698, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABVMJTZKUTBGWG2UPC3PCLSNF47ZANCNFSM4TA2F7HQ .

twmobius commented 4 years ago

@sobomax there you go, I've split them up into two PRs one for each new feature.