diff --git a/protocols/unreal.py b/protocols/unreal.py index b28fb16..cf76d56 100644 --- a/protocols/unreal.py +++ b/protocols/unreal.py @@ -12,6 +12,7 @@ import proto_common from classes import * casemapping = 'ascii' +proto_ver = 2351 hook_map = {} @@ -22,11 +23,17 @@ def joinClient(irc, client, channel): pass def pingServer(irc, source=None, target=None): - pass + source = source or irc.sid + target = target or irc.uplink + if not (target is None or source is None): + _send(irc, source, 'PING %s %s' % (irc.servers[source].name, irc.servers[target].name)) + +def _send(irc, sid, msg): + irc.send(':%s %s' % (sid, msg)) def connect(irc): ts = irc.start_ts - proto_ver = 2351 + irc.caps = [] f = irc.send host = irc.serverdata["hostname"] @@ -39,22 +46,74 @@ def connect(irc): # SID - Use UIDs and SIDs (unreal 3.4) # VL - Sends version string in below SERVER message # UMODE2 - used for users setting modes on themselves (one less argument needed) - # TS - Sends TS in burst to mitigate time-based desyncs. - f('PROTOCTL SJ3 NOQUIT NICKv2 VL UMODE TS EAUTH=%s' % irc.serverdata["hostname"]) - f('PROTOCTL SID=%s TS=%s' % (irc.sid, ts)) - f('SERVER %s 1 U%s-h6e-%s' % (host, proto_ver, irc.sid)) - f('EOS') - # NETINFO maxglobal currenttime protocolversion cloakhash 0 0 0 :networkname + # EAUTH - Early auth? (Unreal 3.4 linking protocol) + f('PROTOCTL SJ3 NOQUIT NICKv2 VL UMODE2 PROTOCTL EAUTH=%s SID=%s' % (irc.serverdata["hostname"], irc.sid)) + sdesc = irc.serverdata.get('serverdesc') or irc.botdata['serverdesc'] + f('SERVER %s 1 U%s-h6e-%s :%s' % (host, proto_ver, irc.sid, sdesc)) + # Now, we wait until remote sends its NETINFO command (handle_netinfo), + # so we can find and use a matching netname, preventing netname mismatch + # errors. + +def handle_netinfo(irc, numeric, command, args): + # <- NETINFO maxglobal currenttime protocolversion cloakhash 0 0 0 :networkname # "maxglobal" is the amount of maximum global users we've seen so far. # We'll just set it to 1 (the PyLink client), since this is completely # arbitrary. - f('NETINFO 1 %s %s * 0 0 0 :networkname' % (ts, proto_ver)) + irc.send('NETINFO 1 %s %s * 0 0 0 :%s' % (irc.start_ts, proto_ver, args[-1])) + _send(irc, irc.sid, 'EOS') def handle_uid(irc, numeric, command, args): # <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname # <- :001 UID GL| 1 1441312224 gl localhost 001J7FZ02 0 +iwx midnight-1C620195 midnight-1C620195 fwAAAQ== :realname pass +def handle_pass(irc, numeric, command, args): + # <- PASS :abcdefg + if args[0] != irc.serverdata['recvpass']: + raise ProtocolError("Error: RECVPASS from uplink does not match configuration!") + +def handle_ping(irc, numeric, command, args): + if numeric == irc.uplink: + irc.send('PONG %s :%s' % (irc.serverdata['hostname'], args[-1])) + +def handle_pong(irc, source, command, args): + log.debug('(%s) Ping received from %s for %s.', irc.name, source, args[-1]) + if source in (irc.uplink, irc.servers[irc.uplink].name) and args[-1] == irc.serverdata['hostname']: + log.debug('(%s) Set irc.lastping.', irc.name) + irc.lastping = time.time() + +def handle_server(irc, numeric, command, args): + # <- SERVER unreal.midnight.vpn 1 :UnrealIRCd test server + sname = args[0] + # TODO: handle server introductions from other servers + if numeric == irc.uplink: + irc.servers[numeric] = IrcServer(None, sname) + else: + raise NotImplementedError + +_unrealCmodes = {'l': 'limit', 'c': 'blockcolor', 'G': 'censor', + 'D': 'delayjoin', 'n': 'noextmsg', 's': 'secret', + 'T': 'nonotice', 'z': 'sslonly', 'b': 'ban', 'V': 'noinvite', + 'Z': 'issecure', 'r': 'registered', 'N': 'nonick', + 'e': 'banexception', 'R': 'regonly', 'M': 'regmoderated', + 'p': 'private', 'Q': 'nokick', 'P': 'permanent', 'k': 'key', + 'C': 'noctcp', 'O': 'operonly', 'S': 'stripcolor', + 'm': 'moderated', 'K': 'noknock', 'o': 'op', 'v': 'voice', + 'I': 'invex', 't': 'topiclock'} +def handle_protoctl(irc, numeric, command, args): + # <- PROTOCTL NOQUIT NICKv2 SJOIN SJOIN2 UMODE2 VL SJ3 TKLEXT TKLEXT2 NICKIP ESVID + # <- PROTOCTL CHANMODES=beI,k,l,psmntirzMQNRTOVKDdGPZSCc NICKCHARS= SID=001 MLOCK TS=1441314501 EXTSWHOIS + irc.caps += args + for cap in args: + if cap.startswith('SID'): + irc.uplink = cap.split('=', 1)[1] + elif cap.startswith('CHANMODES'): + cmodes = cap.split('=', 1)[1] + irc.cmodes['*A'], irc.cmodes['*B'], irc.cmodes['*C'], irc.cmodes['*D'] = cmodes.split(',') + for m in cmodes: + if m in _unrealCmodes: + irc.cmodes[_unrealCmodes[m]] = m + def handle_events(irc, data): # Unreal's protocol has three styles of commands, @servernumeric, :user, and plain commands. # e.g. NICK introduction looks like: @@ -67,7 +126,7 @@ def handle_events(irc, data): # end of the line. args = proto_common.parseArgs(data.split(" ")) # Message starts with a SID/UID prefix. - if args[0] in ':@': + if args[0][0] in ':@': numeric = args[0].lstrip(':@') command = args[1] args = args[2:]