~wiqd/kali/ssl-support

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
import time

from twisted.python import log
from twisted.internet.protocol import ReconnectingClientFactory
from twisted.protocols.basic import LineOnlyReceiver

DEBUG = False


class IRCBadMessage(ValueError):
    """
    Raised when a malformed or invalid IRC message is encountered.
    """


class IRCBadModes(IRCBadMessage):
    """
    Raised when a malformed modes string is encountered.
    """


class EmptyTarget(IRCBadMessage):
    """
    Raised when a target (nick / channel / etc.) is empty.
    """


def parsemsg(s):
    """
    Parses a raw IRC message from the server and returns the components.

    Empty messages are treated as errors and raise L{IRCBadMessage}.

    @param s: Raw IRC message
    @type s: C{str}

    @return: The prefix, command and additional arguments
    @rtype: C{(str, str, list)}
    """
    prefix = ''
    trailing = None
    if not s:
        raise IRCBadMessage('Empty line.')

    if s.startswith(':'):
        prefix, s = s[1:].split(' ', 1)

    if ' :' in s:
        s, trailing = s.split(' :', 1)

    args = s.split()
    if trailing:
        args.append(trailing)

    command = args.pop(0)
    return prefix, command, args


def parseModes(modes, params, paramModes=('', '')):
    """
    Parse an IRC mode string.

    The mode string is parsed into two lists of mode changes (added and
    removed), with each mode change represented as C{(mode, param)} where mode
    is the mode character, and param is the parameter passed for that mode, or
    C{None} if no parameter is required.

    @param modes: The modes string
    @type modes: C{str}
    @param params: The parameters specified along with the modes
    @type params: C{list}

    @returns: (added, removed) modes
    @rtype: C{([(mode, param)], [(mode, param]))}
    """
    if len(modes) == 0:
        raise IRCBadModes('Empty mode string')

    if modes[0] not in '+-':
        raise IRCBadModes('Malformed modes string: %r' % modes)

    changes = ([], [])

    direction = None
    count = -1
    for ch in modes:
        if ch in '+-':
            if count == 0:
                raise IRCBadModes('Empty mode sequence: %r' % modes)
            direction = '+-'.index(ch)
            count = 0
        else:
            param = None
            if ch in paramModes[direction]:
                try:
                    param = params.pop(0)
                except IndexError:
                    raise IRCBadModes('Not enough parameters: %r' % ch)
            changes[direction].append((ch, param))
            count += 1

    if len(params) > 0:
        raise IRCBadModes('Too many parameters: %r %r' % (modes, params))

    if count == 0:
        raise IRCBadModes('Empty mode sequence: %r' % modes)

    return changes


class IRCServices(LineOnlyReceiver):
    serviceModes = ''

    def connectionMade(self):
        self.params = self.services.params
        self.name = self.params['name']

        self.startHandshake()

    def startHandshake(self):
        raise NotImplementedError()

    def sendLine(self, line):
        if DEBUG:
            print '>>>', line
        self.transport.write(line + '\r\n')

    def sendMessage(self, command, *params, **kwargs):
        prefix = kwargs.get('prefix')
        line = ' '.join([command] + [str(param) for param in params])
        if prefix is not None:
            line = ':%s %s' % (prefix, line)

        self.sendLine(line)

        if len(params) > 15:
            log.msg('Message has %d parameters (RFC allows 15):\n%s' % (len(params), line))

    def lineReceived(self, line):
        if DEBUG:
            print '<<<', line
        if line == '':
            return

        try:
            prefix, command, params = parsemsg(line)
        except IRCBadMessage, e:
            log.msg('Invalid protocol line: %r' % (line,))
            log.err()
            self.fatalError(str(e))
        else:
            self.handleCommand(command, prefix, params)

    def handleCommand(self, command, prefix, params):
        method = getattr(self, 'irc_%s' % (command,), None)
        try:
            def handle():
                if method is not None:
                    method(prefix, *params)
                else:
                    self.irc_unknown(prefix, command, params)
            self.services.store.transact(handle)
        except IRCBadMessage, e:
            raise
        except:
            log.msg('%s|%s - %s' % (prefix, command, params))
            log.err()

    ### handlers

    def irc_unknown(self, prefix, command, params):
        log.msg('%s|%s - %s' % (prefix, command, params))

    def irc_PING(self, source, target):
        self.sendMessage('PONG', target, prefix=self.name)

    def irc_QUIT(self, source, msg):
        self.services.event('quit', source, msg)

    def irc_PRIVMSG(self, prefix, nick, msg):
        self.services.event('privmsg', prefix, nick, msg)

    def irc_NOTICE(self, prefix, nick, msg):
        self.services.event('notice', prefix, nick, msg)

    ### commands

    def msg(self, source, nick, msg):
        self.sendMessage('PRIVMSG', nick, ':%s' % (msg,), prefix=source)

    def notice(self, source, nick, msg):
        self.sendMessage('NOTICE', nick, ':%s' % (msg,), prefix=source)

    def kill(self, nick, reason='', source=None):
        if source is None:
            source = self.name

        self.sendMessage('KILL', nick, '%s (%s)' % (source, reason), prefix=source)

    def fatalError(self, reason):
        self.sendMessage('ERROR', ':' + reason)
        self.transport.loseConnection()

    def newServiceNick(self, nick, name, modes):
        return self.newNick(nick, nick.lower(), 'services.', name, ''.join(set(modes + self.serviceModes)))


class InspircdServices(IRCServices):
    serviceModes = 'd'

    userParamModes = ('', '')
    channelParamModes = ('abefghjkloqvIL', 'abehoqvI')
    channelPrefixes = '#'

    def startHandshake(self):
        self.sendMessage('SERVER', self.params['name'], self.params['password'], '0', ':%(desc)s' % self.params)

    def getTargetKind(self, target):
        """
        Determine what kind of entity a target is.

        Currently the only possibilities are channel and user.
        """
        if len(target) == 0:
            raise EmptyTarget(target)
        return dict.fromkeys(self.channelPrefixes, 'channel').get(target[0], 'user')

    def irc_SERVER(self, source, name, password, hopcount, description):
        hopcount = int(hopcount)
        if hopcount == 0:
            # authentication phase
            if source != '':
                raise IRCBadMessage(source)

            # XXX: actually check auth
            self.sendMessage('BURST', '%ld' % time.time())
            self.sendMessage('VERSION', ':Ares X')
            self.services.event('connected', self)
            self.sendMessage('ENDBURST')
        else:
            self.services.event('newserver', name, description)

    def irc_NICK(self, source, *params):
        if len(params) == 1:
            oldnick = source.decode('ascii')
            newnick = params[0].decode('ascii')
            self.services.event('moderemoved', 'INTERNAL', oldnick, 'user', ('r', None))
            self.services.event('nickchange', oldnick, newnick)
        else:
            paramNames = 'timestamp nick hostname fakehost username modes ip realname'.split()
            user = dict(zip(paramNames, params))
            user['nick'] = user['nick'].decode('ascii')
            user['modes'] = user['modes'].lstrip('+')
            user['timestamp'] = int(user['timestamp'])
            user['server'] = source
            user['servicestamp'] = 0
            self.services.createUser(user)

    def irc_OPERTYPE(self, nick, opertype):
        self.services.event('opertype', nick.decode('ascii'), opertype)

    def irc_BURST(self, prefix, stamp):
        pass

    def irc_ENDBURST(self, prefix):
        pass

    def irc_METADATA(self, source, target, key, value):
        pass

    def irc_CAPAB(self, source, *params):
        pass

    def irc_VERSION(self, source, version):
        pass

    def irc_IDLE(self, source, dest):
        self.services.event('idle', source, dest)

    def irc_TIME(self, source, target, nick):
        if target == self.params['name']:
            self.sendMessage('TIME', source, nick, '%ld' % time.time(), prefix=target)

    def irc_FMODE(self, source, target, timestamp, modes, *params):
        self.irc_MODE(source, target, modes, *params)

    def irc_MODE(self, source, target, modes, *params):
        targetKind = self.getTargetKind(target)
        paramModes = {'user': self.userParamModes,
                      'channel': self.channelParamModes}[targetKind]

        if not modes.startswith('+') and not modes.startswith('-'):
            modes = '+' + modes

        added, removed = parseModes(modes, list(params), paramModes)
        for mode in added:
            self.services.event('modeadded', source, target, targetKind, mode)
        for mode in removed:
            self.services.event('moderemoved', source, target, targetKind, mode)

    def irc_FJOIN(self, source, channel, timestamp, users):
        for user in users.split():
            prefixes, nick = user.split(',', 1)
            self.services.event('joined', channel, nick)
            # XXX: translate the prefixes into channel mode changes

    def irc_JOIN(self, source, channels, timestamp):
        for channel in channels.split(','):
            self.services.event('joined', channel, source)

    def irc_PART(self, source, channel, message=''):
        self.services.event('parted', channel, source, message)

    def newNick(self, nick, user, host, name, modes, server=None):
        if server is None:
            server = self.params['name']
        self.sendMessage('NICK', '%ld' % time.time(), nick.encode('ascii'), host, host, user, '+' + modes, '0.0.0.0', ':%s' % name)


class ServicesFactory(ReconnectingClientFactory):
    """
    A simple client factory for services.

    The protocol factory is passed in, to ease selection of the relevant
    ircd-specific protocol. We inherit from ReconnectingClientFactory to ensure
    services tries to stay connected at all times.
    """
    maxDelay = 5 * 60

    def __init__(self, servicesFactory, protocolFactory):
        self.servicesFactory = servicesFactory
        self.protocolFactory = protocolFactory

    def buildProtocol(self, addr):
        """
        Create a protocol instance.

        We give the protocol a services attribute, because the factory doesn't
        have any functionality the protocol needs to care about.
        """
        p = self.protocolFactory()
        p.services = self.servicesFactory()
        return p