Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

kousu
Copy link

@kousu kousu commented Jun 15, 2020

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).

Fixes a later crash:
  File aiosip/aiosip/auth.py, line 24, in md5digest
    return md5(':'.join(args).encode()).hexdigest()
TypeError: sequence item 0: expected str instance, NoneType found
@kousu
Copy link
Author

kousu commented Jun 15, 2020

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 (

registration = Registration(
peer=peer,
from_details=aiosip.Contact.from_header('sip:{}@{}:{}'.format(
sip_config['user'], sip_config['local_host'],
sip_config['local_port'])),
to_details=aiosip.Contact.from_header('sip:{}@{}:{}'.format(
sip_config['user'], sip_config['srv_host'],
sip_config['srv_port'])),
contact_details=aiosip.Contact.from_header('sip:{}@{}:{}'.format(
sip_config['user'], sip_config['local_host'],
sip_config['local_port'])),
)

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: <sip:kousu@example.net>;tag=1470882069
To: <sip:kousu@example.net>
Call-ID: 1789326028
CSeq: 2 REGISTER
Contact: <sip:kousu@example.net@192.168.1.121;line=662bc05c9344c57>
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

@kousu kousu changed the title Always determine the username for login the same way. Always determine the username for login from the From: field Jun 15, 2020
@kousu kousu changed the title Always determine the username for login from the From: field Always determine the username for authentication from the From: field Jun 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant