Eyepea / aiosip

SIP support for AsyncIO (DEPRECATED)
Apache License 2.0
82 stars 40 forks source link

Always determine the username for authentication from the From: field #143

Open kousu opened 4 years ago

kousu commented 4 years ago

Working off master here (I installed with git clone https://github.com/Eyepea/aiosip/; cd aiosip ;pip install -e .) I can't log in to my SIP server with aiosip, as it stands. Here is my sample program:

sip.py ``` import os import argparse import asyncio import contextlib import logging import random import aiosip sip_config = { 'srv_host': os.environ["USER"].split("@")[-1].strip(), 'srv_port': 5060, 'user': os.environ["USER"].split("@")[0].strip(), 'pwd': os.environ["PASSWORD"], 'local_host': '0.0.0.0', } async def login(peer): await peer.register( from_details=aiosip.Contact.from_header('sip:{}@{}'.format( sip_config['user'], sip_config['srv_host'] )), to_details=aiosip.Contact.from_header('sip:{}'.format( sip_config['srv_host'] )), contact_details=aiosip.Contact.from_header('sip:{}@{}'.format( sip_config['user'], sip_config['srv_host'], )), password=sip_config['pwd'] ) async def run_call(peer, target, duration): call = await peer.invite( from_details=aiosip.Contact.from_header('sip:{}@{}'.format( sip_config['user'], sip_config['local_host'])), #to_details=aiosip.Contact.from_header('sip:666@{}:{}'.format( # sip_config['srv_host'], sip_config['srv_port'])), to_details=aiosip.Contact.from_header(target), password=sip_config['pwd']) async with call: async def reader(): async for msg in call.wait_for_terminate(): print("CALL STATUS:", msg.status_code) if msg.status_code == 200: print("CALL ESTABLISHED") await asyncio.sleep(5) print("GOING AWAY...") with contextlib.suppress(asyncio.TimeoutError): await asyncio.wait_for(reader(), timeout=duration) print("CALL TERMINATED") async def start(app, protocol, duration): peer = await app.connect( (sip_config['srv_host'], sip_config['srv_port']), protocol=protocol, local_addr=(sip_config['local_host'], 0)) session = await login(peer) try: if os.environ['TARGET']: await run_call(peer, os.environ['TARGET'], duration) else: print("No TARGET passed. Did you want to call someone?") finally: await session.close() await app.close() def main(): parser = argparse.ArgumentParser() parser.add_argument('-p', '--protocol', default='udp') parser.add_argument('-d', '--duration', type=int, default=5) args = parser.parse_args() loop = asyncio.get_event_loop() app = aiosip.Application(loop=loop) if args.protocol == 'udp': loop.run_until_complete(start(app, aiosip.UDP, args.duration)) elif args.protocol == 'tcp': loop.run_until_complete(start(app, aiosip.TCP, args.duration)) else: raise RuntimeError("Unsupported protocol: {}".format(args.protocol)) loop.close() if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) main() ```

It dies deep in with a TypeError:

$ TARGET="sip:test.time@sip5060.net" USER="kousu@example.net" PASSWORD="<....>" python3 sip.py 
DEBUG:aiosip.peers:Creating: None
DEBUG:aiosip.peers:Creating: <Dialog call_id=be1afb59-0e68-4ed5-8c3a-b411b2781e81, peer=<Peer 52.0.172.61:5060 UDP, local_addr=192.168.7.191:34987>>
DEBUG:aiosip.transaction:Creating: <UnreliableTransaction cseq=2, method=REGISTER, dialog=<Dialog call_id=be1afb59-0e68-4ed5-8c3a-b411b2781e81, peer=<Peer 52.0.172.61:5060 UDP, local_addr=192.168.7.191:34987>>>
ERROR:asyncio:Task exception was never retrieved
future: <Task finished coro=<Application._dispatch() done, defined at /home/kousu/src/bridges/aiosip/aiosip/application.py:133> exception=TypeError('sequence item 0: expected str instance, NoneType found',)>
Traceback (most recent call last):
  File "/home/kousu/src/bridges/aiosip/aiosip/application.py", line 149, in _dispatch
    await dialog.receive_message(msg)
  File "/home/kousu/src/bridges/aiosip/aiosip/dialog.py", line 280, in receive_message
    return self._receive_response(msg)
  File "/home/kousu/src/bridges/aiosip/aiosip/dialog.py", line 74, in _receive_response
    transaction._incoming(msg)
  File "/home/kousu/src/bridges/aiosip/aiosip/transaction.py", line 124, in _incoming
    self._handle_authenticate(msg)
  File "/home/kousu/src/bridges/aiosip/aiosip/transaction.py", line 82, in _handle_authenticate
    uri=msg.to_details['uri'].short_uri()
  File "/home/kousu/src/bridges/aiosip/aiosip/auth.py", line 153, in generate_authorization
    payload=payload
  File "/home/kousu/src/bridges/aiosip/aiosip/auth.py", line 203, in _calculate_response
    nonce_count=self.get('nc')
  File "/home/kousu/src/bridges/aiosip/aiosip/auth.py", line 103, in _calculate_response
    ha1 = md5digest(username, self['realm'], password)
  File "/home/kousu/src/bridges/aiosip/aiosip/auth.py", line 24, in md5digest
    return md5(':'.join(args).encode()).hexdigest()
TypeError: sequence item 0: expected str instance, NoneType found

With the patch, I can log in (though I still can't make a call, but that's because the server I tested with only takes calls to itself and rejected me):

$ TARGET="sip:test.time@sip5060.net" USER="kousu@example.net" PASSWORD="<....>" python3 sip.py
DEBUG:aiosip.peers:Creating: None
DEBUG:aiosip.peers:Creating: <Dialog call_id=7178df24-cde7-496e-895a-a62b5e274d6c, peer=<Peer 52.0.172.61:5060 UDP, local_addr=192.168.7.191:60018>>
DEBUG:aiosip.transaction:Creating: <UnreliableTransaction cseq=2, method=REGISTER, dialog=<Dialog call_id=7178df24-cde7-496e-895a-a62b5e274d6c, peer=<Peer 52.0.172.61:5060 UDP, local_addr=192.168.7.191:60018>>>
DEBUG:aiosip.transaction:Closing <UnreliableTransaction cseq=3, method=REGISTER, dialog=<Dialog call_id=7178df24-cde7-496e-895a-a62b5e274d6c, peer=<Peer 52.0.172.61:5060 UDP, local_addr=192.168.7.191:60018>>>
DEBUG:aiosip.peers:Creating: <InviteDialog call_id=35b24f8d-8c15-42c2-bcd5-c602f680a647, peer=<Peer 52.0.172.61:5060 UDP, local_addr=192.168.7.191:60018>>
CALL STATUS: 404
DEBUG:aiosip.dialog:Closing: <InviteDialog call_id=35b24f8d-8c15-42c2-bcd5-c602f680a647, peer=<Peer 52.0.172.61:5060 UDP, local_addr=192.168.7.191:60018>>
CALL TERMINATED

I'm trying this on

$ python3 --version
Python 3.6.9

because that's what Ubuntu has for me, but I suspect this is a general problem (though I haven't tested other distros yet).

kousu commented 4 years ago

I don't understand the motivation behind what I got rid of. Hopefully you can take a look and tell me if this will screw something else up. Especially, why would you want to use a different username depending on the case, or why it ignores the one explicitly passed by the user?


Am I misusing aiosip somehow?

If I put my app back closer to how I found the initial example (https://github.com/Eyepea/aiosip/blob/553b7b2b408f1f165a1da68a876f27eaafe3ad1e/examples/back_to_back/client.py#L61-L71)

by re-adding the username@ part:

 async def login(peer):
     await peer.register(
         from_details=aiosip.Contact.from_header('sip:{}@{}'.format(
             sip_config['user'], sip_config['srv_host']
             )),
-        to_details=aiosip.Contact.from_header('sip:{}'.format(
-            sip_config['srv_host']
+        to_details=aiosip.Contact.from_header('sip:{}@{}'.format(
+            sip_config['user'], sip_config['srv_host']
             )),

then it logs in without complaint. But logging in "to" yourself doesn't make sense to me, and it's not what Linphone does either; so I didn't pass a user part for that field, so to_details ends up with an empty ['user'].

A captured REGISTER packet from Linhpone ``` REGISTER sip:jmp.bwapp.bwsip.io SIP/2.0 Via: SIP/2.0/UDP 192.168.1.121:5060;rport;branch=z9hG4bK457671158 From: ;tag=1470882069 To: Call-ID: 1789326028 CSeq: 2 REGISTER Contact: Authorization: Digest username="kousu", realm="example.net", nonce="5ee770f600001559050351431a0d979da84c006ab77ecee4", uri="sip:example.net", response="ebceb21ad44b36cfee62877e760951a7", algorithm=MD5, cnonce="0a4f113b", qop=auth, nc=00000001 Max-Forwards: 70 User-Agent: Linphone/3.6.1 (eXosip2/4.1.0) Expires: 3600 Content-Length: 0 ```