Index ¦ Archives  ¦ Atom  ¦ RSS

Django + Twisted + IRC Server = django_ircd

I've been working with Yardbird (Django + Twisted + IRC Bot) and I thought about extending the bot I use to do more than just log the one channel it's in. What if it logged all the channels? What if it was the main authentication route instead of O: lines and the like? I mean, with all the latest stuff coming out in the webapp arena, there should be a way to have an IRC Server more tightly integrate with an existing database (MySQL or otherwise).

Browsing through the existing ircd (IRC server) implementations out there, most are written in C/C++ and none had anything close to a working MySQL connection (InspireIRCd has a 'service provider' module that doesn't actually do anything). None provided a web interface for administration, or really any interface other than text files with an unnecessarily terse syntax. I decided that a server based on Twisted's IRC Server implementation using Django's ORM would make life easier.

I have thought a little about using Yardbird's approach and pulling in Django's 'URL' resolvers to dispatch different server commands to do other processing as necessary, but I don't know what a server would really need to dispatch. I will post this to GitHub sometime tomorrow and see if there is any interest in opening that up (and the use cases attached to that interest).

For the moment, I will instead do a quick walkthrough of creating your own IRC server using Twisted since this took me a day or two to jumble together properly. I used came across this link much later and realized it was exactly what I was looking for. However, in case anybody wants a walkthrough of that code or a simplersetup, here it is:

Basically, Twisted sets up IRC like so: a client connects and a new IRCUser instance is spawned. All interactions between the client and the server are with the IRCUser. Therefore, something needs to spawn those instances, and that's a Factory. The default IRCFactory in twisted (these are all in twisted.words.service) runs the default IRCUser, but there's a caveat. That IRCFactory/IRCUser is setup to demand a password when logging in through a fake NickServ instance. If you want to allow anonymous users or change this behavior, you'll have to create your own IRCFactory and IRCUser subclasses:

class DjangoIRCUser(IRCUser):
    def connectionMade(self):
        self.realm = self.factory.realm
        self.hostname = self.realm.name
class DjangoIRCFactory(IRCFactory):
    protocol = DjangoIRCUser

The connectionMade override is necessary to remove the irc_NICKSERV_PRIVMSG function that implements the fake NickServ. If you want services to be implemented, you can provide a irc_PRIVMSG in DjangoIRCUser that checks if the target (params[0]) is a service and routes accordingly.

The next thing you want to do is create a credentials checker that implements requestAvatarId(credentials). The input argument (credentials) will basically be a class with a username and password attribute that you can use to authenticate with, though it may not be based on which interfaces you allow.

class DjangoCredChecker:
    implements(checkers.ICredentialsChecker)
    credentialInterfaces = (credentials.IUsernamePassword,)
    def requestAvatarId(self, credentials):
        username = credentials.username
        password = credentials.password
        if authenticate(username=username, password=password):
            return defer.succeed(username)
        return defer.fail(failure.Failure(error.UnauthorizedLogin))

I have to admit that the imports caused a lot of headache so if you want it to be easy, just copy-paste the whole import block from the top of the service.py from twisted.

Finally, if you're happy with the InMemoryWordsRealm and portal.Portal, then you can create your server, connect it to a port and go.

realm = InMemoryWordsRealm('fahhem.com')
credcheck = DjangoCredChecker()
djportal = portal.Portal(realm,[credcheck])
factory = DjangoIRCFactory(realm, djportal)
reactor.listenTCP(6667,factory)
reactor.run()

That's all there is to it. You now have a server that authenticates all connecting users with your database (you'll have to setup the settings and such yourself with a setup_environ() call or equivalent). I've tested a slightly bloated version of this (mostly print statements) and successfully got two clients to speak to each other directly and through a channel. I also did a minimal stress test and had a thousand (Twisted) clients connect to the server and the memory usage went to 18MB no matter how many times I had them disconnect and reconnect. There weren't any noticeable memory leaks and the server would connect the thousand instances in about 5-10 seconds total over the loopback interface (127.0.0.1).

I hope this post helps anyone else trying to make an IRC server using Twisted, because the comparative lack of documentation from the IRCClient in Twisted was painful. Also, the oddity of the Twisted IRCUser implementation that talks through NickServ was uncommon since throughout Twisted I've seen a much higher rigor of separating Twisted from someone else's implementation.

© Fahrzin Hemmati. Built using Pelican. Theme by Giulio Fidente on github.