Author Topic: Creating BNX clone for IRC  (Read 19328 times)

0 Members and 1 Guest are viewing this topic.

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Creating BNX clone for IRC
« on: September 08, 2012, 05:02:26 pm »
I'm going to attempt to recreate as much of BNX as possible for IRC.

Let me know if you're interested. I've created an SVN repository for it and committed some reference materials (namely BNX 1.03 files and Greetbot files).

I think I'll first create a rudimentary battle.net CHAT emulator (designed for one-on-one CHAT). This way we can use Wine to try BNX out. Though BNX is actually quite well documented.

I think the best choice of implementation language is C++ and we can use libevent for portable event-driven timers and socket multiplexing.

Again, if you're interested, just PM me a SVN username/password and get a dynamic domain name for your development machine (so I can add you to the firewall).

RFC 1459
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #1 on: September 09, 2012, 05:00:00 pm »
I've decided to make a base IrcClient class to take care of all the basic IRC details.
So far, I've implemented all the socket code, the connect/reconnect code, basic event dispatching, and line buffering. Everything is non-blocking and revolves around libevent (this allows several instances of IrcClient to coexist).

Next is to implement more of the get/set functions (only basic ones exist), IRC protocol parsing and high level IRC event dispatching.

One of the most irritating aspects of IRC is the begin, content, and end numerics. It makes the programming a little trickier since you have to keep track of the object of interest currently being constructed. For example, the channel list is sent in multiple messages. You can be in several channels at a time as well as querying channel lists of which you are not in.

In the distant future, I'll create a derived BnxBot class that just implements the BNX behavior.
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #2 on: September 10, 2012, 11:38:40 pm »
I wrote the IRC protocol parsing today ... I tried to stick to the RFC as close as possible. However, I'm not very familiar with the IRC protocol

I'm actually surprised it could be written so concisely! Here's what I wrote:
Code: [Select]
char * PopToken(char *&str) {
        str += strspn(str, " ");

        char *tmp = str;

        str += strcspn(str, " ");
        if (*str != '\0') {
                *str++ = '\0';
                str += strspn(str, " ");
        }

        return tmp;
}

void IrcClient::OnProcessLine(char *line) {
        const char *pPrefix = NULL, *pCommand = NULL, *pParams[16] = { NULL };
        unsigned int numParams = 0;

        puts(line);

        if (*line == ':')
                pPrefix = PopToken(line)+1;

        pCommand = PopToken(line);

        while (*line != '\0' && numParams < 16) {
                if (*line == ':') {
                        pParams[numParams++] = line+1;
                        break;
                }   

                pParams[numParams++] = PopToken(line);
        }   

        if (isdigit(pCommand[0])) {
                // This is probably a numeric

                int numeric = strtol(pCommand,NULL,10);

                OnNumeric(numeric, pParams, numParams);

                return;
        }   

        if (!strcmp(pCommand,"PING")) {
                OnPing(pPrefix, pParams[0]);
        }   

}

Does it look OK? Do you suppose there is any case that I don't handle adequately?
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #3 on: September 14, 2012, 02:03:15 am »
Well, I setup all the callbacks for all word commands and I setup automatic nickname handling (collision and in-use sorts of issues).

I also found RFC 2812 and added more #defines for newer RPL and ERR numerics.

I'm thinking about how to implement the BNX wildcards. Here's a snippit from BNX.TXT
Quote
? * - matches 0 or more characters of any type
? ? - matches exactly one character of any type
? % - matches any number of non-space characters
? ~ - matches at least one space character
? \ - the literal escape character
UNIX-like systems usually provide fnmatch(3) which only covers * and ?.

This sort of wildcard matching will also need to be implemented for the access system anyway (for hostmasks).

I'm also thinking about easy and portable configuration file formats. INI seems to be very simple and sufficiently flexible for this application. There is even a library called iniparser.

Any comments?
An adorable giant isopod!

Offline rabbit

  • x86
  • Hero Member
  • *****
  • Posts: 8092
  • I speak for the entire clan (except Joe)
    • View Profile
Re: Creating BNX clone for IRC
« Reply #4 on: September 16, 2012, 11:12:17 am »
Well, I setup all the callbacks for all word commands and I setup automatic nickname handling (collision and in-use sorts of issues).

I also found RFC 2812 and added more #defines for newer RPL and ERR numerics.

I'm thinking about how to implement the BNX wildcards. Here's a snippit from BNX.TXT
Quote
? * - matches 0 or more characters of any type
? ? - matches exactly one character of any type
? % - matches any number of non-space characters
? ~ - matches at least one space character
? \ - the literal escape character
UNIX-like systems usually provide fnmatch(3) which only covers * and ?.

This sort of wildcard matching will also need to be implemented for the access system anyway (for hostmasks).

I'm also thinking about easy and portable configuration file formats. INI seems to be very simple and sufficiently flexible for this application. There is even a library called iniparser.

Any comments?
Convert them to regular expressions.

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #5 on: September 18, 2012, 12:29:26 am »
With regex in C++11, I guess it wouldn't be an issue of portability.

The usual DOS wildcards still need to be supported for hostmasks (nobody writes hostmasks in regex).
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #6 on: October 06, 2012, 06:57:29 pm »
It's been a while. I've been very busy these past two weeks.
Anyway, I spent some time today to implement the IRC wildcards.

Here's what I wrote:
Code: [Select]
bool IrcMatch(const char *pPattern, const char *pString) {
        if (pPattern == NULL || pString == NULL)
                return false;

        const char *pPatternLast = NULL, *pStringLast = NULL;

        do {
                if (*pPattern == '*') {
                        pPattern += strspn(pPattern,"*");
                        pPatternLast = pPattern;
                        pStringLast = pString;
                }   
     

                if (*pPattern == '?') {
                        if (*pString == '\0') {
                                if (pPatternLast == NULL || *pStringLast == '\0')
                                        return false;

                                pPattern = pPatternLast;
                                pString = ++pStringLast;
                                continue;
                        }   
                }   
                else {
                        if (*pPattern == '\\')
                                ++pPattern;

                        if (*pPattern != *pString) {
                                if (pPatternLast == NULL || *pStringLast == '\0')
                                        return false;

                                pPattern = pPatternLast;
                                pString = ++pStringLast;
                                continue;
                        }   
                }   

                if (*pPattern != '\0')
                        ++pPattern;

                if (*pString != '\0')
                        ++pString;

        } while (*pString != '\0');

        pPattern += strspn(pPattern,"*");

        return *pPattern == '\0' && *pString == '\0';
}

So as an explanation:
  • The beginning is assumed anchored. So consume as many characters as you can, up until '*' or '\0'.
  • Once you do encounter a '*', the algorithm reduces to something like strstr(). You just remember the last matching pattern and string position.
  • A mismatch merely resets the current string and pattern pointers to the last string position + 1 and the last pattern position.
  • Encountering another '*' merely updates the last matching pattern and string position.
This is pretty straightforward. However, when you add the BNX wildcards % and ~, then you have to do something more sophisticated. You need to keep something like a stack of pattern partitions to match. Unlike IRC wildcards, you may have to revisit an earlier partition since the % and ~ require some specific structure.
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #7 on: October 07, 2012, 05:03:50 pm »
I wrote the response engine today. It's quite elegant. Have a look:

Code: [Select]
class BnxResponseEngine {
public:
        BnxResponseEngine() {
                AddDefaultResponse("Please go on.");
                AddDefaultResponse("You must be joking!");
                AddDefaultResponse("Are you talkin' to me?");
                AddDefaultResponse("Get outta town!");
        }   

        bool LoadFromStream(std::istream &is);
        void SaveToStream(std::ostream &os) const;

        void AddDefaultResponse(const std::string &strMessage) {
                m_vDefaultResponses.push_back(strMessage);
        }   

        const std::string & ComputeResponse(const std::string &strMessage) const {
                std::vector<BnxResponseRule>::const_iterator itr;

                itr = std::find(m_vRules.begin(), m_vRules.end(), strMessage);

                if (itr == m_vRules.end())
                        return m_vDefaultResponses[rand() % m_vDefaultResponses.size()];

                return itr->ComputeResponse();
        }   

        void Reset() {
                m_vRules.clear();
        }   

private:
        std::vector<BnxResponseRule> m_vRules;
        std::vector<std::string> m_vDefaultResponses;
};

By the way, the BNX executable contains no obvious trace of those default responses (at least from strings(1) point of view). The author must have done something clever to prevent hex editing.

Here are some screenshots of some classic responses:

As you can see, "/me" is not properly handled. Actually, there is no "/me" command on IRC. Only the kludge called CTCP. So, I'll have to add some battle.net command to CTCP conversion ...

EDIT: Use thumbnails instead.




« Last Edit: October 07, 2012, 05:14:32 pm by nslay »
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #8 on: October 13, 2012, 05:19:40 pm »
I wrote a CtcpEncoder, CtcpDecoder and added some limited support to BnxBot. It now responds to the CTCP VERSION command (though, it doesn't have any real versioning yet).

Today, I created a sourceforge project for it:
http://sourceforge.net/projects/ircbnx/

While sourceforge doesn't think there are any files, you can check them out with subversion (I recommend TortoiseSVN on Windows).

For Windows:
EDIT: Redundant
Some porting is still necessary despite using libevent2. For one, FreeBSD's libstdc++ doesn't support C++11 std::regex and so I used POSIX regex. BnxResponseRule is the only place where regex is used and it would probably be trivial to port this to C++11 std::regex (e.g. I think VS 2010 and 2012 have this).

  • regex
    FreeBSD's libstdc++ doesn't have C++11 std::regex and so I resorted to POSIX regex. BnxResponseRule is the only place where regex is used and it would be trivial to port it (it's just a header)
  • Winsock
    This project is not yet aware of Winsock and it uses some POSIX functionality (like fcntl) not present on Windows. It even uses signal() to ignore SIGPIPE (which I'm not sure if Windows supports). You'd have to set the socket as non-blocking some other way in Windows.

A list of things that remain to be done:
  • RPL_ISUPPORT
    At present, I assume ISO646 case mappings as per RFC2812 and the support is implemented in IrcString.h. However, these case mappings can vary between servers (many of which just use ascii now). Need a new helper class that parses RPL_ISUPPORT  numerics and provides basic string functionality (e.g. like strcasestr(), strcasecmp()) that respect the case mapping
  • Internal ignore list
    The original BNX would permanently ignore users who told it to "shutup" or who tried to abuse it.
  • Channel accounting
    Need to maintain internal channel lists which is somewhat more challenging than Battle.net. In IRC, channel information is conveyed on JOIN through RPL_NAMEREPLY. More detailed information (such as complete hostmask) is conveyed through RPL_WHOREPLY. XChat, for example, builds a channel list by first populating the nicknames and then occasionally use WHO to collect host and away information.
  • Access system
    Need to implement the BNX access system. This access system would be based on wildcard hostmasks.
  • BNX Commands
    The BNX commands need to be implemented (except for designate). Anyone know the difference between BNX's squelch and ignore?
  • Configuration files
    Lastly, the configuration files need to be implemented. I think INI would be a good choice.

After I get through these, I'll mark the project as Alpha.
« Last Edit: October 13, 2012, 05:35:29 pm by nslay »
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #9 on: October 14, 2012, 02:36:05 am »
Getting close to finishing a baseline.

I implemented the following
  • The access system
  • The following commands
    • login
    • logout
    • say
      Due to the nature of IRC, this now requires a destination argument (e.g. say #nslay hi).
    • chatter
      This also clears the ignore list (the original ignored users indefinitely ... and not by /ignore).
    • shutup
  • The ignore list
    This just ignores users who say "*shut*up*" (case insensitive). The flood protection isn't implemented yet.
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #10 on: October 14, 2012, 05:04:16 pm »
I apparently also need a send queue (which is not obvious). I guess it doesn't surprise me.

See here:
http://forums.unrealircd.com/viewtopic.php?f=9&t=7654

I also prepared a test sequence for the original BNX to see which commands could be accessed at which levels. Here's the script:
Code: [Select]
#!/bin/sh

MakeCommands() {
        echo "2010 NAME nslay"
        echo "1007 CHANNEL \"cheese\""
        echo "1001 USER nslay 0012 [CHAT]"
        echo "1002 JOIN test 0010 [CHAT]"

        #set -x
        wine bnx.exe &

        sleep 5

        for i in `seq 0 20`
        do
                level=`expr 100 - ${i} '*' 5`
                user="enslay${i}"

                echo "*** level = ${level}" 1>&2

                echo "*** login" 1>&2
                echo "1004 WHISPER ${user} 0010 \"login passwd\""
                sleep 3

                echo "*** say" 1>&2
                echo "1004 WHISPER ${user} 0010 \"say test\""
                sleep 3

                echo "*** kick" 1>&2
                echo "1004 WHISPER ${user} 0010 \"kick test\""
                sleep 6

                echo "*** ban" 1>&2
                echo "1004 WHISPER ${user} 0010 \"ban test\""
                sleep 6

                echo "*** unban" 1>&2
                echo "1004 WHISPER ${user} 0010 \"unban test\""
                sleep 6

                echo "*** shitadd" 1>&2
                echo "1004 WHISPER ${user} 0010 \"shitadd test\""
                sleep 3

                echo "*** shitdel" 1>&2
                echo "1004 WHISPER ${user} 0010 \"shitdel test\""
                sleep 3

                echo "*** shitlist" 1>&2
                echo "1004 WHISPER ${user} 0010 \"shitlist\""
                sleep 6

                echo "*** join" 1>&2
                echo "1004 WHISPER ${user} 0010 \"join cheese2\""
                sleep 6

                echo "*** join" 1>&2
                echo "1004 WHISPER ${user} 0010 \"join cheese\""
                sleep 6

                echo "*** shutup" 1>&2
                echo "1004 WHISPER ${user} 0010 \"shutup\""
                sleep 3

                echo "*** chatter" 1>&2
                echo "1004 WHISPER ${user} 0010 \"chatter\""
                sleep 3

                echo "*** squelch" 1>&2
                echo "1004 WHISPER ${user} 0010 \"squelch test\""
                sleep 6

                echo "*** unsquelch" 1>&2
                echo "1004 WHISPER ${user} 0010 \"unsquelch test\""
                sleep 6

               echo "*** splatterkick" 1>&2
                echo "1004 WHISPER ${user} 0010 \"splatterkick test\""
                sleep 15

                echo "*** userlist" 1>&2
                echo "1004 WHISPER ${user} 0010 \"userlist test\""
                sleep 90

                echo "*** voteban" 1>&2
                echo "1004 WHISPER ${user} 0010 \"voteban test\""
                sleep 60
        done

        echo "1004 WHISPER enslay 0010 \"login passwd\""
        echo "1004 WHISPER enslay 0010 \"shutdown\""

        wait
}

MakeCommands | nc -l 6112


Nifty what you can do with netcat!
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #11 on: October 26, 2012, 01:46:26 am »
I'd say it's about 85% done. Maybe in at most a couple weeks it will be alpha-quality. I'll port it to Windows, Linux and OS X (if I can find access to one).

You can see the current status here.

The first release is meant to be an accurate recreation of the original BNX. Maybe after that, I can add some extra features.
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #12 on: November 02, 2012, 12:31:36 am »
Good news, IRCBNX is practically done. I implemented ban, unban and splatterkick today (among many commands in the past week). That leaves one remaining command, voteban as well as some BNX-specific behavior.

If you have the time and interest, do have a look over the source (Look under "Browser SVN" on sourceforge) and let me know if there are any design improvements that could be made.
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #13 on: November 04, 2012, 01:43:39 am »
I added voteban support and configuration file support. I ended up having to write my own INI parser since many existing libraries consider # the beginning of a comment. I also ported it to Windows which actually went surprisingly smoothly. Very little had to be modified and most of that little bit was isolated to the IrcClient base class.

I have a single bot (it's configurable for several bots ... not that it really matters) sitting in #ircbnx on irc.freenode.net for testing purposes.

It's not terribly exciting ... but there you have it.

The project is now promoted to alpha status though it still has some remaining missing details and hopefully I can get around to making some Linux, Windows and OS X binaries.
An adorable giant isopod!

Offline nslay

  • Hero Member
  • *****
  • Posts: 786
  • Giraffe meat, mmm
    • View Profile
Re: Creating BNX clone for IRC
« Reply #14 on: November 08, 2012, 09:55:26 am »
Check this out:
http://forum.valhallalegends.com/index.php/topic,18164.msg185900.html#msg185900

Invert demystified the origins of BNX.
An adorable giant isopod!