~certify-web-dev/twisted/certify-trunk

« back to all changes in this revision

Viewing changes to twisted/words/protocols/toc.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2007-01-17 14:52:35 UTC
  • mfrom: (1.1.5 upstream) (2.1.2 etch)
  • Revision ID: james.westby@ubuntu.com-20070117145235-btmig6qfmqfen0om
Tags: 2.5.0-0ubuntu1
New upstream version, compatible with python2.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.words.test -*-
 
2
# Copyright (c) 2001-2005 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
 
 
6
"""
 
7
Implements a AOL Instant Messenger TOC server and client, using the Twisted framework.
 
8
 
 
9
TODO:
 
10
info,dir: see how gaim connects for this...it may never work if it tries to connect to the aim server automatically
 
11
 
 
12
This module is stable, but deprecated.
 
13
 
 
14
Maintainer: U{Paul Swartz<mailto:z3p@twistedmatrix.com>}
 
15
"""
 
16
 
 
17
# twisted imports
 
18
from twisted.internet import reactor, protocol
 
19
from twisted.python import log
 
20
 
 
21
# base imports
 
22
import struct
 
23
import string
 
24
import time
 
25
import base64
 
26
import os
 
27
import StringIO
 
28
 
 
29
SIGNON,DATA,ERROR,SIGNOFF,KEEP_ALIVE=range(1,6)
 
30
PERMITALL,DENYALL,PERMITSOME,DENYSOME=range(1,5)
 
31
 
 
32
DUMMY_CHECKSUM = -559038737 # 0xdeadbeef
 
33
 
 
34
def quote(s):
 
35
    rep=['\\','$','{','}','[',']','(',')','"']
 
36
    for r in rep:
 
37
        s=string.replace(s,r,"\\"+r)
 
38
    return "\""+s+"\""
 
39
 
 
40
def unquote(s):
 
41
    if s=="": return ""
 
42
    if s[0]!='"': return s
 
43
    r=string.replace
 
44
    s=s[1:-1]
 
45
    s=r(s,"\\\\","\\")
 
46
    s=r(s,"\\$","$")
 
47
    s=r(s,"\\{","{")
 
48
    s=r(s,"\\}","}")
 
49
    s=r(s,"\\[","[")
 
50
    s=r(s,"\\]","]")
 
51
    s=r(s,"\\(","(")
 
52
    s=r(s,"\\)",")")
 
53
    s=r(s,"\\\"","\"")
 
54
    return s
 
55
 
 
56
def unquotebeg(s):
 
57
    for i in range(1,len(s)):
 
58
        if s[i]=='"' and s[i-1]!='\\':
 
59
            q=unquote(s[:i+1])
 
60
            return [q,s[i+2:]]
 
61
 
 
62
def unroast(pw):
 
63
    roaststring="Tic/Toc"
 
64
    pw=string.lower(pw[2:])
 
65
    r=""
 
66
    count=0
 
67
    hex=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]
 
68
    while pw:
 
69
        st,pw=pw[:2],pw[2:]
 
70
        value=(16*hex.index(st[0]))+hex.index(st[1])
 
71
        xor=ord(roaststring[count])
 
72
        count=(count+1)%len(roaststring)
 
73
        r=r+chr(value^xor)
 
74
    return r
 
75
 
 
76
def roast(pw):
 
77
    # contributed by jemfinch on #python
 
78
    key="Tic/Toc"
 
79
    ro="0x"
 
80
    i=0
 
81
    ascii=map(ord,pw)
 
82
    for c in ascii:
 
83
        ro=ro+'%02x'%(c^ord(key[i%len(key)]))
 
84
        i=i+1
 
85
    return string.lower(ro)
 
86
 
 
87
def checksum(b):
 
88
    return DUMMY_CHECKSUM # do it like gaim does, since the checksum
 
89
                      # formula doesn't work
 
90
##    # used in file transfers
 
91
##    check0 = check1 = 0x00ff
 
92
##    for i in range(len(b)):
 
93
##        if i%2:
 
94
##            if ord(b[i])>check1:
 
95
##                check1=check1+0x100 # wrap
 
96
##                if check0==0:
 
97
##                    check0=0x00ff
 
98
##                    if check1==0x100:
 
99
##                        check1=check1-1
 
100
##                else:
 
101
##                    check0=check0-1
 
102
##            check1=check1-ord(b[i])
 
103
##        else:
 
104
##            if ord(b[i])>check0: # wrap
 
105
##                check0=check0+0x100
 
106
##                if check1==0:
 
107
##                    check1=0x00ff
 
108
##                    if check0==0x100:
 
109
##                        check0=check0-1
 
110
##                else:
 
111
##                    check1=check1-1
 
112
##            check0=check0-ord(b[i])
 
113
##    check0=check0 & 0xff
 
114
##    check1=check1 & 0xff
 
115
##    checksum=(long(check0)*0x1000000)+(long(check1)*0x10000)
 
116
##    return checksum
 
117
 
 
118
def checksum_file(f):
 
119
    return DUMMY_CHECKSUM # do it like gaim does, since the checksum
 
120
                      # formula doesn't work
 
121
##    check0=check1=0x00ff
 
122
##    i=0
 
123
##    while 1:
 
124
##        b=f.read()
 
125
##        if not b: break
 
126
##        for char in b:
 
127
##            i=not i
 
128
##            if i:
 
129
##                if ord(char)>check1:
 
130
##                    check1=check1+0x100 # wrap
 
131
##                    if check0==0:
 
132
##                        check0=0x00ff
 
133
##                        if check1==0x100:
 
134
##                            check1=check1-1
 
135
##                    else:
 
136
##                        check0=check0-1
 
137
##                check1=check1-ord(char)
 
138
##            else:
 
139
##                if ord(char)>check0: # wrap
 
140
##                    check0=check0+0x100
 
141
##                    if check1==0:
 
142
##                        check1=0x00ff
 
143
##                        if check0==0x100:
 
144
##                            check0=check0-1
 
145
##                    else:
 
146
##                        check1=check1-1
 
147
##                check0=check0-ord(char)
 
148
##    check0=check0 & 0xff
 
149
##    check1=check1 & 0xff
 
150
##    checksum=(long(check0)*0x1000000)+(long(check1)*0x10000)
 
151
##    return checksum
 
152
 
 
153
def normalize(s):
 
154
    s=string.lower(s)
 
155
    s=string.replace(s," ","")
 
156
    return s
 
157
 
 
158
 
 
159
class TOCParseError(ValueError):
 
160
    pass
 
161
 
 
162
 
 
163
class TOC(protocol.Protocol):
 
164
    users={}
 
165
 
 
166
    def connectionMade(self):
 
167
        # initialization of protocol
 
168
        self._buf=""
 
169
        self._ourseqnum=0L
 
170
        self._theirseqnum=0L
 
171
        self._mode="Flapon"
 
172
        self._onlyflaps=0
 
173
        self._laststatus={} # the last status for a user
 
174
        self.username=None
 
175
        self.permitmode=PERMITALL
 
176
        self.permitlist=[]
 
177
        self.denylist=[]
 
178
        self.buddylist=[]
 
179
        self.signontime=0
 
180
        self.idletime=0
 
181
        self.userinfo="<br>"
 
182
        self.userclass=" O"
 
183
        self.away=""
 
184
        self.saved=None
 
185
 
 
186
    def _debug(self,data):
 
187
        log.msg(data)
 
188
 
 
189
    def connectionLost(self, reason):
 
190
        self._debug("dropped connection from %s" % self.username)
 
191
        try:
 
192
            del self.factory.users[self.username]
 
193
        except:
 
194
            pass
 
195
        for k in self.factory.chatroom.keys():
 
196
            try:
 
197
                self.factory.chatroom[k].leave(self)
 
198
            except TOCParseError:
 
199
                pass
 
200
        if self.saved:
 
201
            self.factory.savedusers[self.username]=self.saved
 
202
        self.updateUsers()
 
203
 
 
204
    def sendFlap(self,type,data):
 
205
        """
 
206
        send a FLAP to the client
 
207
        """
 
208
        send="*"
 
209
        self._debug(data)
 
210
        if type==DATA:
 
211
            data=data+"\000"
 
212
        length=len(data)
 
213
        send=send+struct.pack("!BHH",type,self._ourseqnum,length)
 
214
        send=send+data
 
215
        self._ourseqnum=self._ourseqnum+1
 
216
        if self._ourseqnum>(256L**4):
 
217
            self._ourseqnum=0
 
218
        self.transport.write(send)
 
219
 
 
220
    def dataReceived(self,data):
 
221
        self._buf=self._buf+data
 
222
        try:
 
223
            func=getattr(self,"mode%s"%self._mode)
 
224
        except:
 
225
            return
 
226
        self._mode=func()
 
227
        if self._onlyflaps and self.isFlap(): self.dataReceived("")
 
228
 
 
229
    def isFlap(self):
 
230
        """
 
231
        tests to see if a flap is actually on the buffer
 
232
        """
 
233
        if self._buf=='': return 0
 
234
        if self._buf[0]!="*": return 0
 
235
        if len(self._buf)<6: return 0
 
236
        foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6])
 
237
        if type not in range(1,6): return 0
 
238
        if len(self._buf)<6+length: return 0
 
239
        return 1
 
240
 
 
241
    def readFlap(self):
 
242
        """
 
243
        read the first FLAP off self._buf, raising errors if it isn't in the right form.
 
244
        the FLAP is the basic TOC message format, and is logically equivilant to a packet in TCP
 
245
        """
 
246
        if self._buf=='': return None
 
247
        if self._buf[0]!="*":
 
248
            raise TOCParseError
 
249
        if len(self._buf)<6: return None
 
250
        foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6])
 
251
        if len(self._buf)<6+length: return None
 
252
        data=self._buf[6:6+length]
 
253
        self._buf=self._buf[6+length:]
 
254
        if data and data[-1]=="\000":
 
255
            data=data[:-1]
 
256
        self._debug([type,data])
 
257
        return [type,data]
 
258
 
 
259
    #def modeWeb(self):
 
260
    #    try:
 
261
    #        line,rest=string.split(self._buf,"\n",1)
 
262
    #        get,username,http=string.split(line," ",2)
 
263
    #    except:
 
264
    #        return "Web" # not enough data
 
265
    #    foo,type,username=string.split(username,"/")
 
266
    #    if type=="info":
 
267
    #        user=self.factory.users[username]
 
268
    #        text="<HTML><HEAD><TITLE>User Information for %s</TITLE></HEAD><BODY>Username: <B>%s</B><br>\nWarning Level: <B>%s%</B><br>\n Online Since: <B>%s</B><br>\nIdle Minutes: <B>%s</B><br>\n<hr><br>\n%s\n<hr><br>\n"%(user.saved.nick, user.saved.nick, user.saved.evilness, time.asctime(user.signontime), int((time.time()-user.idletime)/60), user.userinfo)
 
269
    #        self.transport.write("HTTP/1.1 200 OK\n")
 
270
    #        self.transport.write("Content-Type: text/html\n")
 
271
    #        self.transport.write("Content-Length: %s\n\n"%len(text))
 
272
    #        self.transport.write(text)
 
273
    #        self.loseConnection()
 
274
 
 
275
    def modeFlapon(self):
 
276
        #if self._buf[:3]=="GET": self.modeWeb() # TODO: get this working
 
277
        if len(self._buf)<10: return "Flapon" # not enough bytes
 
278
        flapon,self._buf=self._buf[:10],self._buf[10:]
 
279
        if flapon!="FLAPON\r\n\r\n":
 
280
            raise TOCParseError
 
281
        self.sendFlap(SIGNON,"\000\000\000\001")
 
282
        self._onlyflaps=1
 
283
        return "Signon"
 
284
 
 
285
    def modeSignon(self):
 
286
        flap=self.readFlap()
 
287
        if flap==None:
 
288
            return "Signon"
 
289
        if flap[0]!=SIGNON: raise TOCParseError
 
290
        version,tlv,unlength=struct.unpack("!LHH",flap[1][:8])
 
291
        if version!=1 or tlv!=1 or unlength+8!=len(flap[1]):
 
292
            raise TOCParseError
 
293
        self.username=normalize(flap[1][8:])
 
294
        if self.username in self.factory.savedusers.keys():
 
295
            self.saved=self.factory.savedusers[self.username]
 
296
        else:
 
297
            self.saved=SavedUser()
 
298
            self.saved.nick=self.username
 
299
        return "TocSignon"
 
300
 
 
301
    def modeTocSignon(self):
 
302
        flap=self.readFlap()
 
303
        if flap==None:
 
304
            return "TocSignon"
 
305
        if flap[0]!=DATA: raise TOCParseError
 
306
        data=string.split(flap[1]," ")
 
307
        if data[0]!="toc_signon": raise TOCParseError
 
308
        for i in data:
 
309
            if not i:data.remove(i)
 
310
        password=unroast(data[4])
 
311
        if not(self.authorize(data[1],int(data[2]),data[3],password)):
 
312
            self.sendError(BAD_NICKNAME)
 
313
            self.transport.loseConnection()
 
314
            return
 
315
        self.sendFlap(DATA,"SIGN_ON:TOC1.0")
 
316
        self.sendFlap(DATA,"NICK:%s"%self.saved.nick)
 
317
        self.sendFlap(DATA,"CONFIG:%s"%self.saved.config)
 
318
        # sending user configuration goes here
 
319
        return "Connected"
 
320
 
 
321
    def authorize(self,server,port,username,password):
 
322
        if self.saved.password=="":
 
323
            self.saved.password=password
 
324
            return 1
 
325
        else:
 
326
            return self.saved.password==password
 
327
 
 
328
    def modeConnected(self):
 
329
        flap=self.readFlap()
 
330
        while flap!=None:
 
331
            if flap[0] not in [DATA,KEEP_ALIVE]: raise TOCParseError
 
332
            flapdata=string.split(flap[1]," ",1)
 
333
            tocname=flapdata[0][4:]
 
334
            if len(flapdata)==2:
 
335
                data=flapdata[1]
 
336
            else:
 
337
                data=""
 
338
            func=getattr(self,"toc_"+tocname,None)
 
339
            if func!=None:
 
340
                func(data)
 
341
            else:
 
342
                self.toc_unknown(tocname,data)
 
343
            flap=self.readFlap()
 
344
        return "Connected"
 
345
 
 
346
    def toc_unknown(self,tocname,data):
 
347
        self._debug("unknown! %s %s" % (tocname,data))
 
348
 
 
349
    def toc_init_done(self,data):
 
350
        """
 
351
        called when all the setup is done.
 
352
 
 
353
        toc_init_done
 
354
        """
 
355
        self.signontime=int(time.time())
 
356
        self.factory.users[self.username]=self
 
357
        self.updateUsers()
 
358
 
 
359
    def toc_add_permit(self,data):
 
360
        """
 
361
        adds users to the permit list.  if the list is null, then set the mode to DENYALL
 
362
        """
 
363
        if data=="":
 
364
            self.permitmode=DENYALL
 
365
            self.permitlist=[]
 
366
            self.denylist=[]
 
367
        else:
 
368
            self.permitmode=PERMITSOME
 
369
            self.denylist=[]
 
370
            users=string.split(data," ")
 
371
            map(self.permitlist.append,users)
 
372
        self.updateUsers()
 
373
 
 
374
    def toc_add_deny(self,data):
 
375
        """
 
376
        adds users to the deny list.  if the list is null, then set the mode to PERMITALL
 
377
        """
 
378
        if data=="":
 
379
            self.permitmode=PERMITALL
 
380
            self.permitlist=[]
 
381
            self.denylist=[]
 
382
        else:
 
383
            self.permitmode=DENYSOME
 
384
            self.permitlist=[]
 
385
            users=string.split(data," ")
 
386
            map(self.denylist.append,users)
 
387
        self.updateUsers()
 
388
 
 
389
    def toc_evil(self,data):
 
390
        """
 
391
        warns a user.
 
392
 
 
393
        toc_evil <username> <anon|norm>
 
394
        """
 
395
        username,nora=string.split(data," ")
 
396
        if nora=="anon":
 
397
            user=""
 
398
        else:
 
399
            user=self.saved.nick
 
400
        if not(self.factory.users.has_key(username)):
 
401
            self.sendError(CANT_WARN,username)
 
402
            return
 
403
        if self.factory.users[username].saved.evilness>=100:
 
404
            self.sendError(CANT_WARN,username)
 
405
            return
 
406
        self.factory.users[username].evilFrom(user)
 
407
 
 
408
    def toc_add_buddy(self,data):
 
409
        """
 
410
        adds users to the buddy list
 
411
 
 
412
        toc_add_buddy <buddyname1> [<buddyname2>] [<buddyname3>]...
 
413
        """
 
414
        buddies=map(normalize,string.split(data," "))
 
415
        for b in buddies:
 
416
            if b not in self.buddylist:
 
417
                self.buddylist.append(b)
 
418
        for buddy in buddies:
 
419
            try:
 
420
                buddy=self.factory.users[buddy]
 
421
            except:
 
422
                pass
 
423
            else:
 
424
                self.buddyUpdate(buddy)
 
425
 
 
426
    def toc_remove_buddy(self,data):
 
427
        """
 
428
        removes users from the buddy list
 
429
 
 
430
        toc_remove_buddy <buddyname1> [<buddyname2>] [<buddyname3>]...
 
431
        """
 
432
        buddies=string.split(data," ")
 
433
        for buddy in buddies:
 
434
            try:
 
435
                self.buddylist.remove(normalize(buddy))
 
436
            except: pass
 
437
 
 
438
    def toc_send_im(self,data):
 
439
        """
 
440
        incoming instant message
 
441
 
 
442
        toc_send_im <screenname> <quoted message> [auto]
 
443
        """
 
444
        username,data=string.split(data," ",1)
 
445
        auto=0
 
446
        if data[-4:]=="auto":
 
447
            auto=1
 
448
            data=data[:-5]
 
449
        data=unquote(data)
 
450
        if not(self.factory.users.has_key(username)):
 
451
            self.sendError(NOT_AVAILABLE,username)
 
452
            return
 
453
        user=self.factory.users[username]
 
454
        if not(self.canContact(user)):
 
455
            self.sendError(NOT_AVAILABLE,username)
 
456
            return
 
457
        user.hearWhisper(self,data,auto)
 
458
 
 
459
    def toc_set_info(self,data):
 
460
        """
 
461
        set the users information, retrivable with toc_get_info
 
462
 
 
463
        toc_set_info <user info (quoted)>
 
464
        """
 
465
        info=unquote(data)
 
466
        self._userinfo=info
 
467
 
 
468
    def toc_set_idle(self,data):
 
469
        """
 
470
        set/unset idle
 
471
 
 
472
        toc_set_idle <seconds>
 
473
        """
 
474
        seconds=int(data)
 
475
        self.idletime=time.time()-seconds # time when they started being idle
 
476
        self.updateUsers()
 
477
 
 
478
    def toc_set_away(self,data):
 
479
        """
 
480
        set/unset away message
 
481
 
 
482
        toc_set_away [<away message>]
 
483
        """
 
484
        away=unquote(data)
 
485
        if not self.away and away: # setting an away message
 
486
            self.away=away
 
487
            self.userclass=self.userclass+'U'
 
488
            self.updateUsers()
 
489
        elif self.away and not away: # coming back
 
490
            self.away=""
 
491
            self.userclass=self.userclass[:2]
 
492
            self.updateUsers()
 
493
        else:
 
494
            raise TOCParseError
 
495
 
 
496
    def toc_chat_join(self,data):
 
497
        """
 
498
        joins the chat room.
 
499
 
 
500
        toc_chat_join <exchange> <room name>
 
501
        """
 
502
        exchange,name=string.split(data," ",1)
 
503
        self.factory.getChatroom(int(exchange),unquote(name)).join(self)
 
504
 
 
505
    def toc_chat_invite(self,data):
 
506
        """
 
507
        invite others to the room.
 
508
 
 
509
        toc_chat_invite <room id> <invite message> <buddy 1> [<buddy2>]...
 
510
        """
 
511
        id,data=string.split(data," ",1)
 
512
        id=int(id)
 
513
        message,data=unquotebeg(data)
 
514
        buddies=string.split(data," ")
 
515
        for b in buddies:
 
516
            room=self.factory.chatroom[id]
 
517
            bud=self.factory.users[b]
 
518
            bud.chatInvite(room,self,message)
 
519
 
 
520
    def toc_chat_accept(self,data):
 
521
        """
 
522
        accept an invitation.
 
523
 
 
524
        toc_chat_accept <room id>
 
525
        """
 
526
        id=int(data)
 
527
        self.factory.chatroom[id].join(self)
 
528
 
 
529
    def toc_chat_send(self,data):
 
530
        """
 
531
        send a message to the chat room.
 
532
 
 
533
        toc_chat_send <room id> <message>
 
534
        """
 
535
        id,message=string.split(data," ",1)
 
536
        id=int(id)
 
537
        message=unquote(message)
 
538
        self.factory.chatroom[id].say(self,message)
 
539
 
 
540
    def toc_chat_whisper(self,data):
 
541
        id,user,message=string.split(data," ",2)
 
542
        id=int(id)
 
543
        room=self.factory.chatroom[id]
 
544
        message=unquote(message)
 
545
        self.factory.users[user].chatWhisper(room,self,message)
 
546
 
 
547
    def toc_chat_leave(self,data):
 
548
        """
 
549
        leave the room.
 
550
 
 
551
        toc_chat_leave <room id>
 
552
        """
 
553
        id=int(data)
 
554
        self.factory.chatroom[id].leave(self)
 
555
 
 
556
    def toc_set_config(self,data):
 
557
        """
 
558
        set the saved config.  this gets send when you log in.
 
559
 
 
560
        toc_set_config <config>
 
561
        """
 
562
        self.saved.config=unquote(data)
 
563
 
 
564
    def toc_get_info(self,data):
 
565
        """
 
566
        get the user info for a user
 
567
 
 
568
        toc_get_info <username>
 
569
        """
 
570
        if not self.factory.users.has_key(data):
 
571
            self.sendError(901,data)
 
572
            return
 
573
        self.sendFlap(2,"GOTO_URL:TIC:info/%s"%data)
 
574
 
 
575
    def toc_format_nickname(self,data):
 
576
        """
 
577
        change the format of your nickname.
 
578
 
 
579
        toc_format_nickname <new format>
 
580
        """
 
581
        # XXX may not work
 
582
        nick=unquote(data)
 
583
        if normalize(nick)==self.username:
 
584
            self.saved.nick=nick
 
585
            self.sendFlap(2,"ADMIN_NICK_STATUS:0")
 
586
        else:
 
587
            self.sendError(BAD_INPUT)
 
588
 
 
589
    def toc_change_passwd(self,data):
 
590
        orig,data=unquotebeg(data)
 
591
        new=unquote(data)
 
592
        if orig==self.saved.password:
 
593
            self.saved.password=new
 
594
            self.sendFlap(2,"ADMIN_PASSWD_STATUS:0")
 
595
        else:
 
596
            self.sendError(BAD_INPUT)
 
597
 
 
598
    def sendError(self,code,*varargs):
 
599
        """
 
600
        send an error to the user.  listing of error messages is below.
 
601
        """
 
602
        send="ERROR:%s"%code
 
603
        for v in varargs:
 
604
            send=send+":"+v
 
605
        self.sendFlap(DATA,send)
 
606
 
 
607
    def updateUsers(self):
 
608
        """
 
609
        Update the users who have us on their buddylist.
 
610
        Called when the user changes anything (idle,away) so people can get updates.
 
611
        """
 
612
        for user in self.factory.users.values():
 
613
            if self.username in user.buddylist and self.canContact(user):
 
614
                user.buddyUpdate(self)
 
615
 
 
616
    def getStatus(self,user):
 
617
        if self.canContact(user):
 
618
            if self in self.factory.users.values():ol='T'
 
619
            else: ol='F'
 
620
            idle=0
 
621
            if self.idletime:
 
622
                idle=int((time.time()-self.idletime)/60)
 
623
            return (self.saved.nick,ol,self.saved.evilness,self.signontime,idle,self.userclass)
 
624
        else:
 
625
            return (self.saved.nick,'F',0,0,0,self.userclass)
 
626
 
 
627
    def canContact(self,user):
 
628
        if self.permitmode==PERMITALL: return 1
 
629
        elif self.permitmode==DENYALL: return 0
 
630
        elif self.permitmode==PERMITSOME:
 
631
            if user.username in self.permitlist: return 1
 
632
            else: return 0
 
633
        elif self.permitmode==DENYSOME:
 
634
            if user.username in self.denylist: return 0
 
635
            else: return 1
 
636
        else:
 
637
            assert 0,"bad permitmode %s" % self.permitmode
 
638
 
 
639
    def buddyUpdate(self,user):
 
640
        """
 
641
        Update the buddy.  Called from updateUsers()
 
642
        """
 
643
        if not self.canContact(user): return
 
644
        status=user.getStatus(self)
 
645
        if not self._laststatus.has_key(user):
 
646
            self._laststatus[user]=()
 
647
        if self._laststatus[user]!=status:
 
648
            send="UPDATE_BUDDY:%s:%s:%s:%s:%s:%s"%status
 
649
            self.sendFlap(DATA,send)
 
650
            self._laststatus[user]=status
 
651
 
 
652
    def hearWhisper(self,user,data,auto=0):
 
653
        """
 
654
        Called when you get an IM.  If auto=1, it's an autoreply from an away message.
 
655
        """
 
656
        if not self.canContact(user): return
 
657
        if auto: auto='T'
 
658
        else: auto='F'
 
659
        send="IM_IN:%s:%s:%s"%(user.saved.nick,auto,data)
 
660
        self.sendFlap(DATA,send)
 
661
 
 
662
    def evilFrom(self,user):
 
663
        if user=="":
 
664
            percent=0.03
 
665
        else:
 
666
            percent=0.1
 
667
        self.saved.evilness=self.saved.evilness+int((100-self.saved.evilness)*percent)
 
668
        self.sendFlap(2,"EVILED:%s:%s"%(self.saved.evilness,user))
 
669
        self.updateUsers()
 
670
 
 
671
    def chatJoin(self,room):
 
672
        self.sendFlap(2,"CHAT_JOIN:%s:%s"%(room.id,room.name))
 
673
        f="CHAT_UPDATE_BUDDY:%s:T"%room.id
 
674
        for u in room.users:
 
675
            if u!=self:
 
676
                u.chatUserUpdate(room,self)
 
677
            f=f+":"+u.saved.nick
 
678
        self.sendFlap(2,f)
 
679
 
 
680
    def chatInvite(self,room,user,message):
 
681
        if not self.canContact(user): return
 
682
        self.sendFlap(2,"CHAT_INVITE:%s:%s:%s:%s"%(room.name,room.id,user.saved.nick,message))
 
683
 
 
684
    def chatUserUpdate(self,room,user):
 
685
        if user in room.users:
 
686
            inroom='T'
 
687
        else:
 
688
            inroom='F'
 
689
        self.sendFlap(2,"CHAT_UPDATE_BUDDY:%s:%s:%s"%(room.id,inroom,user.saved.nick))
 
690
 
 
691
    def chatMessage(self,room,user,message):
 
692
        if not self.canContact(user): return
 
693
        self.sendFlap(2,"CHAT_IN:%s:%s:F:%s"%(room.id,user.saved.nick,message))
 
694
 
 
695
    def chatWhisper(self,room,user,message):
 
696
        if not self.canContact(user): return
 
697
        self.sendFlap(2,"CHAT_IN:%s:%s:T:%s"%(room.id,user.saved.nick,message))
 
698
 
 
699
    def chatLeave(self,room):
 
700
        self.sendFlap(2,"CHAT_LEFT:%s"%(room.id))
 
701
 
 
702
 
 
703
class Chatroom:
 
704
    def __init__(self,fac,exchange,name,id):
 
705
        self.exchange=exchange
 
706
        self.name=name
 
707
        self.id=id
 
708
        self.factory=fac
 
709
        self.users=[]
 
710
 
 
711
    def join(self,user):
 
712
        if user in self.users:
 
713
            return
 
714
        self.users.append(user)
 
715
        user.chatJoin(self)
 
716
 
 
717
    def leave(self,user):
 
718
        if user not in self.users:
 
719
            raise TOCParseError
 
720
        self.users.remove(user)
 
721
        user.chatLeave(self)
 
722
        for u in self.users:
 
723
            u.chatUserUpdate(self,user)
 
724
        if len(self.users)==0:
 
725
            self.factory.remChatroom(self)
 
726
 
 
727
    def say(self,user,message):
 
728
        for u in self.users:
 
729
            u.chatMessage(self,user,message)
 
730
 
 
731
 
 
732
class SavedUser:
 
733
    def __init__(self):
 
734
        self.config=""
 
735
        self.nick=""
 
736
        self.password=""
 
737
        self.evilness=0
 
738
 
 
739
 
 
740
class TOCFactory(protocol.Factory):
 
741
    def __init__(self):
 
742
        self.users={}
 
743
        self.savedusers={}
 
744
        self.chatroom={}
 
745
        self.chatroomid=0
 
746
 
 
747
    def buildProtocol(self,addr):
 
748
        p=TOC()
 
749
        p.factory=self
 
750
        return p
 
751
 
 
752
    def getChatroom(self,exchange,name):
 
753
        for i in self.chatroom.values():
 
754
            if normalize(i.name)==normalize(name):
 
755
                return i
 
756
        self.chatroom[self.chatroomid]=Chatroom(self,exchange,name,self.chatroomid)
 
757
        self.chatroomid=self.chatroomid+1
 
758
        return self.chatroom[self.chatroomid-1]
 
759
 
 
760
    def remChatroom(self,room):
 
761
        id=room.id
 
762
        del self.chatroom[id]
 
763
 
 
764
MAXARGS={}
 
765
MAXARGS["CONFIG"]=0
 
766
MAXARGS["NICK"]=0
 
767
MAXARGS["IM_IN"]=2
 
768
MAXARGS["UPDATE_BUDDY"]=5
 
769
MAXARGS["ERROR"]=-1
 
770
MAXARGS["EVILED"]=1
 
771
MAXARGS["CHAT_JOIN"]=1
 
772
MAXARGS["CHAT_IN"]=3
 
773
MAXARGS["CHAT_UPDATE_BUDDY"]=-1
 
774
MAXARGS["CHAT_INVITE"]=3
 
775
MAXARGS["CHAT_LEFT"]=0
 
776
MAXARGS["ADMIN_NICK_STATUS"]=0
 
777
MAXARGS["ADMIN_PASSWD_STATUS"]=0
 
778
 
 
779
 
 
780
class TOCClient(protocol.Protocol):
 
781
    def __init__(self,username,password,authhost="login.oscar.aol.com",authport=5190):
 
782
 
 
783
        self.username=normalize(username) # our username
 
784
        self._password=password # our password
 
785
        self._mode="SendNick" # current mode
 
786
        self._ourseqnum=19071 # current sequence number (for sendFlap)
 
787
        self._authhost=authhost # authorization host
 
788
        self._authport=authport # authorization port
 
789
        self._online=0 # are we online?
 
790
        self._buddies=[] # the current buddy list
 
791
        self._privacymode=PERMITALL # current privacy mode
 
792
        self._permitlist=[] # list of users on the permit list
 
793
        self._roomnames={} # the names for each of the rooms we're in
 
794
        self._receivedchatmembers={} # have we gotten who's in our room yet?
 
795
        self._denylist=[]
 
796
        self._cookies={} # for file transfers
 
797
        self._buf='' # current data buffer
 
798
        self._awaymessage=''
 
799
 
 
800
    def _debug(self,data):
 
801
        log.msg(data)
 
802
 
 
803
    def sendFlap(self,type,data):
 
804
        if type==DATA:
 
805
            data=data+"\000"
 
806
        length=len(data)
 
807
        s="*"
 
808
        s=s+struct.pack("!BHH",type,self._ourseqnum,length)
 
809
        s=s+data
 
810
        self._ourseqnum=self._ourseqnum+1
 
811
        if self._ourseqnum>(256*256+256):
 
812
            self._ourseqnum=0
 
813
        self._debug(data)
 
814
        self.transport.write(s)
 
815
 
 
816
    def isFlap(self):
 
817
        """
 
818
        tests to see if a flap is actually on the buffer
 
819
        """
 
820
        if self._buf=='': return 0
 
821
        if self._buf[0]!="*": return 0
 
822
        if len(self._buf)<6: return 0
 
823
        foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6])
 
824
        if type not in range(1,6): return 0
 
825
        if len(self._buf)<6+length: return 0
 
826
        return 1
 
827
 
 
828
    def readFlap(self):
 
829
        if self._buf=='': return None
 
830
        if self._buf[0]!="*":
 
831
            raise TOCParseError
 
832
        if len(self._buf)<6: return None
 
833
        foo,type,seqnum,length=struct.unpack("!BBHH",self._buf[:6])
 
834
        if len(self._buf)<6+length: return None
 
835
        data=self._buf[6:6+length]
 
836
        self._buf=self._buf[6+length:]
 
837
        if data and data[-1]=="\000":
 
838
            data=data[:-1]
 
839
        return [type,data]
 
840
 
 
841
    def connectionMade(self):
 
842
        self._debug("connection made! %s" % self.transport)
 
843
        self.transport.write("FLAPON\r\n\r\n")
 
844
 
 
845
    def connectionLost(self, reason):
 
846
        self._debug("connection lost!")
 
847
        self._online=0
 
848
 
 
849
    def dataReceived(self,data):
 
850
        self._buf=self._buf+data
 
851
        while self.isFlap():
 
852
            flap=self.readFlap()
 
853
            func=getattr(self,"mode%s"%self._mode)
 
854
            func(flap)
 
855
 
 
856
    def modeSendNick(self,flap):
 
857
        if flap!=[1,"\000\000\000\001"]: raise TOCParseError
 
858
        s="\000\000\000\001\000\001"+struct.pack("!H",len(self.username))+self.username
 
859
        self.sendFlap(1,s)
 
860
        s="toc_signon %s %s  %s %s english \"penguin\""%(self._authhost,\
 
861
            self._authport,self.username,roast(self._password))
 
862
        self.sendFlap(2,s)
 
863
        self._mode="Data"
 
864
 
 
865
    def modeData(self,flap):
 
866
        if not flap[1]:
 
867
            return
 
868
        if not ':' in flap[1]:
 
869
            self._debug("bad SNAC:%s"%(flap[1]))
 
870
            return
 
871
        command,rest=string.split(flap[1],":",1)
 
872
        if MAXARGS.has_key(command):
 
873
            maxsplit=MAXARGS[command]
 
874
        else:
 
875
            maxsplit=-1
 
876
        if maxsplit==-1:
 
877
            l=tuple(string.split(rest,":"))
 
878
        elif maxsplit==0:
 
879
            l=(rest,)
 
880
        else:
 
881
            l=tuple(string.split(rest,":",maxsplit))
 
882
        self._debug("%s %s"%(command,l))
 
883
        try:
 
884
            func=getattr(self,"toc%s"%command)
 
885
            self._debug("calling %s"%func)
 
886
        except:
 
887
            self._debug("calling %s"%self.tocUNKNOWN)
 
888
            self.tocUNKNOWN(command,l)
 
889
            return
 
890
        func(l)
 
891
 
 
892
    def tocUNKNOWN(self,command,data):
 
893
        pass
 
894
 
 
895
    def tocSIGN_ON(self,data):
 
896
        if data!=("TOC1.0",): raise TOCParseError
 
897
        self._debug("Whee, signed on!")
 
898
        if self._buddies: self.add_buddy(self._buddies)
 
899
        self._online=1
 
900
        self.onLine()
 
901
 
 
902
    def tocNICK(self,data):
 
903
        """
 
904
        Handle a message that looks like::
 
905
 
 
906
            NICK:<format of nickname>
 
907
        """
 
908
        self.username=data[0]
 
909
 
 
910
    def tocCONFIG(self,data):
 
911
        """
 
912
        Handle a message that looks like::
 
913
 
 
914
            CONFIG:<config>
 
915
 
 
916
        Format of config data:
 
917
 
 
918
            - g: group.  all users until next g or end of config are in this group
 
919
            - b: buddy
 
920
            - p: person on the permit list
 
921
            - d: person on the deny list
 
922
            - m: permit/deny mode (1: permit all, 2: deny all, 3: permit some, 4: deny some)
 
923
        """
 
924
        data=data[0]
 
925
        if data and data[0]=="{":data=data[1:-1]
 
926
        lines=string.split(data,"\n")
 
927
        buddylist={}
 
928
        currentgroup=""
 
929
        permit=[]
 
930
        deny=[]
 
931
        mode=1
 
932
        for l in lines:
 
933
            if l:
 
934
                code,data=l[0],l[2:]
 
935
                if code=='g': # group
 
936
                    currentgroup=data
 
937
                    buddylist[currentgroup]=[]
 
938
                elif code=='b':
 
939
                    buddylist[currentgroup].append(data)
 
940
                elif code=='p':
 
941
                    permit.append(data)
 
942
                elif code=='d':
 
943
                    deny.append(data)
 
944
                elif code=='m':
 
945
                    mode=int(data)
 
946
        self.gotConfig(mode,buddylist,permit,deny)
 
947
 
 
948
    def tocIM_IN(self,data):
 
949
        """
 
950
        Handle a message that looks like::
 
951
 
 
952
            IM_IN:<user>:<autoreply T|F>:message
 
953
        """
 
954
        user=data[0]
 
955
        autoreply=(data[1]=='T')
 
956
        message=data[2]
 
957
        self.hearMessage(user,message,autoreply)
 
958
 
 
959
    def tocUPDATE_BUDDY(self,data):
 
960
        """
 
961
        Handle a message that looks like::
 
962
 
 
963
            UPDATE_BUDDY:<username>:<online T|F>:<warning level>:<signon time>:<idle time (minutes)>:<user class>
 
964
        """
 
965
        data=list(data)
 
966
        online=(data[1]=='T')
 
967
        if len(data[5])==2:
 
968
            data[5]=data[5]+" "
 
969
        away=(data[5][-1]=='U')
 
970
        if data[5][-1]=='U':
 
971
            data[5]=data[5][:-1]
 
972
        self.updateBuddy(data[0],online,int(data[2]),int(data[3]),int(data[4]),data[5],away)
 
973
 
 
974
    def tocERROR(self,data):
 
975
        """
 
976
        Handle a message that looks like::
 
977
 
 
978
            ERROR:<error code>:<misc. data>
 
979
        """
 
980
        code,args=data[0],data[1:]
 
981
        self.hearError(int(code),args)
 
982
 
 
983
    def tocEVILED(self,data):
 
984
        """
 
985
        Handle a message that looks like::
 
986
 
 
987
            EVILED:<current warning level>:<user who warned us>
 
988
        """
 
989
        self.hearWarning(data[0],data[1])
 
990
 
 
991
    def tocCHAT_JOIN(self,data):
 
992
        """
 
993
        Handle a message that looks like::
 
994
 
 
995
            CHAT_JOIN:<room id>:<room name>
 
996
        """
 
997
        #self.chatJoined(int(data[0]),data[1])
 
998
        self._roomnames[int(data[0])]=data[1]
 
999
        self._receivedchatmembers[int(data[0])]=0
 
1000
 
 
1001
    def tocCHAT_UPDATE_BUDDY(self,data):
 
1002
        """
 
1003
        Handle a message that looks like::
 
1004
 
 
1005
            CHAT_UPDATE_BUDDY:<room id>:<in room? T/F>:<user 1>:<user 2>...
 
1006
        """
 
1007
        roomid=int(data[0])
 
1008
        inroom=(data[1]=='T')
 
1009
        if self._receivedchatmembers[roomid]:
 
1010
            for u in data[2:]:
 
1011
                self.chatUpdate(roomid,u,inroom)
 
1012
        else:
 
1013
            self._receivedchatmembers[roomid]=1
 
1014
            self.chatJoined(roomid,self._roomnames[roomid],list(data[2:]))
 
1015
 
 
1016
    def tocCHAT_IN(self,data):
 
1017
        """
 
1018
        Handle a message that looks like::
 
1019
 
 
1020
            CHAT_IN:<room id>:<username>:<whisper T/F>:<message>
 
1021
 
 
1022
        whisper isn't used
 
1023
        """
 
1024
        whisper=(data[2]=='T')
 
1025
        if whisper:
 
1026
            self.chatHearWhisper(int(data[0]),data[1],data[3])
 
1027
        else:
 
1028
            self.chatHearMessage(int(data[0]),data[1],data[3])
 
1029
 
 
1030
    def tocCHAT_INVITE(self,data):
 
1031
        """
 
1032
        Handle a message that looks like::
 
1033
 
 
1034
            CHAT_INVITE:<room name>:<room id>:<username>:<message>
 
1035
        """
 
1036
        self.chatInvited(int(data[1]),data[0],data[2],data[3])
 
1037
 
 
1038
    def tocCHAT_LEFT(self,data):
 
1039
        """
 
1040
        Handle a message that looks like::
 
1041
 
 
1042
            CHAT_LEFT:<room id>
 
1043
        """
 
1044
        self.chatLeft(int(data[0]))
 
1045
        del self._receivedchatmembers[int(data[0])]
 
1046
        del self._roomnames[int(data[0])]
 
1047
 
 
1048
    def tocRVOUS_PROPOSE(self,data):
 
1049
        """
 
1050
        Handle a message that looks like::
 
1051
 
 
1052
            RVOUS_PROPOSE:<user>:<uuid>:<cookie>:<seq>:<rip>:<pip>:<vip>:<port>
 
1053
                  [:tlv tag1:tlv value1[:tlv tag2:tlv value2[:...]]]
 
1054
        """
 
1055
        user,uid,cookie,seq,rip,pip,vip,port=data[:8]
 
1056
        cookie=base64.decodestring(cookie)
 
1057
        port=int(port)
 
1058
        tlvs={}
 
1059
        for i in range(8,len(data),2):
 
1060
            key=data[i]
 
1061
            value=base64.decodestring(data[i+1])
 
1062
            tlvs[key]=value
 
1063
        name=UUIDS[uid]
 
1064
        try:
 
1065
            func=getattr(self,"toc%s"%name)
 
1066
        except:
 
1067
            self._debug("no function for UID %s" % uid)
 
1068
            return
 
1069
        func(user,cookie,seq,pip,vip,port,tlvs)
 
1070
 
 
1071
    def tocSEND_FILE(self,user,cookie,seq,pip,vip,port,tlvs):
 
1072
        if tlvs.has_key('12'):
 
1073
            description=tlvs['12']
 
1074
        else:
 
1075
            description=""
 
1076
        subtype,numfiles,size=struct.unpack("!HHI",tlvs['10001'][:8])
 
1077
        name=tlvs['10001'][8:-4]
 
1078
        while name[-1]=='\000':
 
1079
            name=name[:-1]
 
1080
        self._cookies[cookie]=[user,SEND_FILE_UID,pip,port,{'name':name}]
 
1081
        self.rvousProposal("send",cookie,user,vip,port,description=description,
 
1082
                           name=name,files=numfiles,size=size)
 
1083
 
 
1084
    def tocGET_FILE(self,user,cookie,seq,pip,vip,port,tlvs):
 
1085
        return
 
1086
        # XXX add this back in
 
1087
        #reactor.clientTCP(pip,port,GetFileTransfer(self,cookie,os.path.expanduser("~")))
 
1088
        #self.rvous_accept(user,cookie,GET_FILE_UID)
 
1089
 
 
1090
    def onLine(self):
 
1091
        """
 
1092
        called when we are first online
 
1093
        """
 
1094
        pass
 
1095
 
 
1096
    def gotConfig(self,mode,buddylist,permit,deny):
 
1097
        """
 
1098
        called when we get a configuration from the server
 
1099
        mode := permit/deny mode
 
1100
        buddylist := current buddylist
 
1101
        permit := permit list
 
1102
        deny := deny list
 
1103
        """
 
1104
        pass
 
1105
 
 
1106
    def hearError(self,code,args):
 
1107
        """
 
1108
        called when an error is received
 
1109
        code := error code
 
1110
        args := misc. arguments (username, etc.)
 
1111
        """
 
1112
        pass
 
1113
 
 
1114
    def hearWarning(self,newamount,username):
 
1115
        """
 
1116
        called when we get warned
 
1117
        newamount := the current warning level
 
1118
        username := the user who warned us, or '' if it's anonymous
 
1119
        """
 
1120
        pass
 
1121
 
 
1122
    def hearMessage(self,username,message,autoreply):
 
1123
        """
 
1124
        called when you receive an IM
 
1125
        username := the user who the IM is from
 
1126
        message := the message
 
1127
        autoreply := true if the message is an autoreply from an away message
 
1128
        """
 
1129
        pass
 
1130
 
 
1131
    def updateBuddy(self,username,online,evilness,signontime,idletime,userclass,away):
 
1132
        """
 
1133
        called when a buddy changes state
 
1134
        username := the user whos state changed
 
1135
        online := true if the user is online
 
1136
        evilness := the users current warning level
 
1137
        signontime := the time the user signed on (UNIX epoch)
 
1138
        idletime := the time the user has been idle (minutes)
 
1139
        away := true if the user is away
 
1140
        userclass := the class of the user (generally " O")
 
1141
        """
 
1142
        pass
 
1143
 
 
1144
    def chatJoined(self,roomid,roomname,users):
 
1145
        """
 
1146
        we just joined a chat room
 
1147
        roomid := the AIM id for the room
 
1148
        roomname := the name for the room
 
1149
        users := a list of the users already in the room
 
1150
        """
 
1151
        pass
 
1152
 
 
1153
    def chatUpdate(self,roomid,username,inroom):
 
1154
        """
 
1155
        a user has joined the room
 
1156
        roomid := the AIM id for the room
 
1157
        username := the username
 
1158
        inroom := true if the user is in the room
 
1159
        """
 
1160
        pass
 
1161
 
 
1162
    def chatHearMessage(self,roomid,username,message):
 
1163
        """
 
1164
        a message was sent to the room
 
1165
        roomid := the AIM id for the room
 
1166
        username := the user who sent the message
 
1167
        message := the message
 
1168
        """
 
1169
        pass
 
1170
 
 
1171
    def chatHearWhisper(self,roomid,username,message):
 
1172
        """
 
1173
        someone whispered to us in a chatroom
 
1174
        roomid := the AIM for the room
 
1175
        username := the user who whispered to us
 
1176
        message := the message
 
1177
        """
 
1178
        pass
 
1179
 
 
1180
    def chatInvited(self,roomid,roomname,username,message):
 
1181
        """
 
1182
        we were invited to a chat room
 
1183
        roomid := the AIM id for the room
 
1184
        roomname := the name of the room
 
1185
        username := the user who invited us
 
1186
        message := the invite message
 
1187
        """
 
1188
        pass
 
1189
 
 
1190
    def chatLeft(self,roomid):
 
1191
        """
 
1192
        we left the room
 
1193
        roomid := the AIM id for the room
 
1194
        """
 
1195
        pass
 
1196
 
 
1197
    def rvousProposal(self,type,cookie,user,vip,port,**kw):
 
1198
        """
 
1199
        we were asked for a rondevouz
 
1200
        type := the type of rondevous.  currently, one of ["send"]
 
1201
        cookie := the cookie. pass this to rvous_accept()
 
1202
        user := the user who asked us
 
1203
        vip := their verified_ip
 
1204
        port := the port they want us to conenct to
 
1205
        kw := misc. args
 
1206
        """
 
1207
        pass #self.rvous_accept(cookie)
 
1208
 
 
1209
    def receiveBytes(self,user,file,chunk,sofar,total):
 
1210
        """
 
1211
        we received part of a file from a file transfer
 
1212
        file := the name of the file
 
1213
        chunk := the chunk of data
 
1214
        sofar := how much data we've gotten so far
 
1215
        total := the total amount of data
 
1216
        """
 
1217
        pass #print user,file,sofar,total
 
1218
 
 
1219
    def isaway(self):
 
1220
        """
 
1221
        return our away status
 
1222
        """
 
1223
        return len(self._awaymessage)>0
 
1224
 
 
1225
    def set_config(self,mode,buddylist,permit,deny):
 
1226
        """
 
1227
        set the server configuration
 
1228
        mode := permit mode
 
1229
        buddylist := buddy list
 
1230
        permit := permit list
 
1231
        deny := deny list
 
1232
        """
 
1233
        s="m %s\n"%mode
 
1234
        for g in buddylist.keys():
 
1235
            s=s+"g %s\n"%g
 
1236
            for u in buddylist[g]:
 
1237
                s=s+"b %s\n"%u
 
1238
        for p in permit:
 
1239
            s=s+"p %s\n"%p
 
1240
        for d in deny:
 
1241
            s=s+"d %s\n"%d
 
1242
        #s="{\n"+s+"\n}"
 
1243
        self.sendFlap(2,"toc_set_config %s"%quote(s))
 
1244
 
 
1245
    def add_buddy(self,buddies):
 
1246
        s=""
 
1247
        if type(buddies)==type(""): buddies=[buddies]
 
1248
        for b in buddies:
 
1249
            s=s+" "+normalize(b)
 
1250
        self.sendFlap(2,"toc_add_buddy%s"%s)
 
1251
 
 
1252
    def del_buddy(self,buddies):
 
1253
        s=""
 
1254
        if type(buddies)==type(""): buddies=[buddies]
 
1255
        for b in buddies:
 
1256
            s=s+" "+b
 
1257
        self.sendFlap(2,"toc_remove_buddy%s"%s)
 
1258
 
 
1259
    def add_permit(self,users):
 
1260
        if type(users)==type(""): users=[users]
 
1261
        s=""
 
1262
        if self._privacymode!=PERMITSOME:
 
1263
            self._privacymode=PERMITSOME
 
1264
            self._permitlist=[]
 
1265
        for u in users:
 
1266
            u=normalize(u)
 
1267
            if u not in self._permitlist:self._permitlist.append(u)
 
1268
            s=s+" "+u
 
1269
        if not s:
 
1270
            self._privacymode=DENYALL
 
1271
            self._permitlist=[]
 
1272
            self._denylist=[]
 
1273
        self.sendFlap(2,"toc_add_permit"+s)
 
1274
 
 
1275
    def del_permit(self,users):
 
1276
        if type(users)==type(""): users=[users]
 
1277
        p=self._permitlist[:]
 
1278
        for u in users:
 
1279
            u=normalize(u)
 
1280
            if u in p:
 
1281
                p.remove(u)
 
1282
        self.add_permit([])
 
1283
        self.add_permit(p)
 
1284
 
 
1285
    def add_deny(self,users):
 
1286
        if type(users)==type(""): users=[users]
 
1287
        s=""
 
1288
        if self._privacymode!=DENYSOME:
 
1289
            self._privacymode=DENYSOME
 
1290
            self._denylist=[]
 
1291
        for u in users:
 
1292
            u=normalize(u)
 
1293
            if u not in self._denylist:self._denylist.append(u)
 
1294
            s=s+" "+u
 
1295
        if not s:
 
1296
            self._privacymode=PERMITALL
 
1297
            self._permitlist=[]
 
1298
            self._denylist=[]
 
1299
        self.sendFlap(2,"toc_add_deny"+s)
 
1300
 
 
1301
    def del_deny(self,users):
 
1302
        if type(users)==type(""): users=[users]
 
1303
        d=self._denylist[:]
 
1304
        for u in users:
 
1305
            u=normalize(u)
 
1306
            if u in d:
 
1307
                d.remove(u)
 
1308
        self.add_deny([])
 
1309
        if d:
 
1310
            self.add_deny(d)
 
1311
 
 
1312
    def signon(self):
 
1313
        """
 
1314
        called to finish the setup, and signon to the network
 
1315
        """
 
1316
        self.sendFlap(2,"toc_init_done")
 
1317
        self.sendFlap(2,"toc_set_caps %s" % (SEND_FILE_UID,)) # GET_FILE_UID)
 
1318
 
 
1319
    def say(self,user,message,autoreply=0):
 
1320
        """
 
1321
        send a message
 
1322
        user := the user to send to
 
1323
        message := the message
 
1324
        autoreply := true if the message is an autoreply (good for away messages)
 
1325
        """
 
1326
        if autoreply: a=" auto"
 
1327
        else: a=''
 
1328
        self.sendFlap(2,"toc_send_im %s %s%s"%(normalize(user),quote(message),a))
 
1329
 
 
1330
    def idle(self,idletime=0):
 
1331
        """
 
1332
        change idle state
 
1333
        idletime := the seconds that the user has been away, or 0 if they're back
 
1334
        """
 
1335
        self.sendFlap(2,"toc_set_idle %s" % int(idletime))
 
1336
 
 
1337
    def evil(self,user,anon=0):
 
1338
        """
 
1339
        warn a user
 
1340
        user := the user to warn
 
1341
        anon := if true, an anonymous warning
 
1342
        """
 
1343
        self.sendFlap(2,"toc_evil %s %s"%(normalize(user), (not anon and "anon") or "norm"))
 
1344
 
 
1345
    def away(self,message=''):
 
1346
        """
 
1347
        change away state
 
1348
        message := the message, or '' to come back from awayness
 
1349
        """
 
1350
        self._awaymessage=message
 
1351
        if message:
 
1352
            message=' '+quote(message)
 
1353
        self.sendFlap(2,"toc_set_away%s"%message)
 
1354
 
 
1355
    def chat_join(self,exchange,roomname):
 
1356
        """
 
1357
        join a chat room
 
1358
        exchange := should almost always be 4
 
1359
        roomname := room name
 
1360
        """
 
1361
        roomname=string.replace(roomname," ","")
 
1362
        self.sendFlap(2,"toc_chat_join %s %s"%(int(exchange),roomname))
 
1363
 
 
1364
    def chat_say(self,roomid,message):
 
1365
        """
 
1366
        send a message to a chatroom
 
1367
        roomid := the AIM id for the room
 
1368
        message := the message to send
 
1369
        """
 
1370
        self.sendFlap(2,"toc_chat_send %s %s"%(int(roomid),quote(message)))
 
1371
 
 
1372
    def chat_whisper(self,roomid,user,message):
 
1373
        """
 
1374
        whisper to another user in a chatroom
 
1375
        roomid := the AIM id for the room
 
1376
        user := the user to whisper to
 
1377
        message := the message to send
 
1378
        """
 
1379
        self.sendFlap(2,"toc_chat_whisper %s %s %s"%(int(roomid),normalize(user),quote(message)))
 
1380
 
 
1381
    def chat_leave(self,roomid):
 
1382
        """
 
1383
        leave a chat room.
 
1384
        roomid := the AIM id for the room
 
1385
        """
 
1386
        self.sendFlap(2,"toc_chat_leave %s" % int(roomid))
 
1387
 
 
1388
    def chat_invite(self,roomid,usernames,message):
 
1389
        """
 
1390
        invite a user[s] to the chat room
 
1391
        roomid := the AIM id for the room
 
1392
        usernames := either a string (one username) or a list (more than one)
 
1393
        message := the message to invite them with
 
1394
        """
 
1395
        if type(usernames)==type(""): # a string, one username
 
1396
            users=usernames
 
1397
        else:
 
1398
            users=""
 
1399
            for u in usernames:
 
1400
                users=users+u+" "
 
1401
            users=users[:-1]
 
1402
        self.sendFlap(2,"toc_chat_invite %s %s %s" % (int(roomid),quote(message),users))
 
1403
 
 
1404
    def chat_accept(self,roomid):
 
1405
        """
 
1406
        accept an invite to a chat room
 
1407
        roomid := the AIM id for the room
 
1408
        """
 
1409
        self.sendFlap(2,"toc_chat_accept %s"%int(roomid))
 
1410
 
 
1411
    def rvous_accept(self,cookie):
 
1412
        user,uuid,pip,port,d=self._cookies[cookie]
 
1413
        self.sendFlap(2,"toc_rvous_accept %s %s %s" % (normalize(user),
 
1414
                                                     cookie,uuid))
 
1415
        if uuid==SEND_FILE_UID:
 
1416
            protocol.ClientCreator(reactor, SendFileTransfer,self,cookie,user,d["name"]).connectTCP(pip,port)
 
1417
 
 
1418
    def rvous_cancel(self,cookie):
 
1419
        user,uuid,pip,port,d=self._cookies[cookie]
 
1420
        self.sendFlap(2,"toc_rvous_accept %s %s %s" % (normalize(user),
 
1421
                                                       cookie,uuid))
 
1422
        del self._cookies[cookie]
 
1423
 
 
1424
 
 
1425
class SendFileTransfer(protocol.Protocol):
 
1426
    header_fmt="!4s2H8s6H10I32s3c69s16s2H64s"
 
1427
 
 
1428
    def __init__(self,client,cookie,user,filename):
 
1429
        self.client=client
 
1430
        self.cookie=cookie
 
1431
        self.user=user
 
1432
        self.filename=filename
 
1433
        self.hdr=[0,0,0]
 
1434
        self.sofar=0
 
1435
 
 
1436
    def dataReceived(self,data):
 
1437
        if not self.hdr[2]==0x202:
 
1438
            self.hdr=list(struct.unpack(self.header_fmt,data[:256]))
 
1439
            self.hdr[2]=0x202
 
1440
            self.hdr[3]=self.cookie
 
1441
            self.hdr[4]=0
 
1442
            self.hdr[5]=0
 
1443
            self.transport.write(apply(struct.pack,[self.header_fmt]+self.hdr))
 
1444
            data=data[256:]
 
1445
            if self.hdr[6]==1:
 
1446
                self.name=self.filename
 
1447
            else:
 
1448
                self.name=self.filename+self.hdr[-1]
 
1449
                while self.name[-1]=="\000":
 
1450
                    self.name=self.name[:-1]
 
1451
        if not data: return
 
1452
        self.sofar=self.sofar+len(data)
 
1453
        self.client.receiveBytes(self.user,self.name,data,self.sofar,self.hdr[11])
 
1454
        if self.sofar==self.hdr[11]: # end of this file
 
1455
            self.hdr[2]=0x204
 
1456
            self.hdr[7]=self.hdr[7]-1
 
1457
            self.hdr[9]=self.hdr[9]-1
 
1458
            self.hdr[19]=DUMMY_CHECKSUM # XXX really calculate this
 
1459
            self.hdr[18]=self.hdr[18]+1
 
1460
            self.hdr[21]="\000"
 
1461
            self.transport.write(apply(struct.pack,[self.header_fmt]+self.hdr))
 
1462
            self.sofar=0
 
1463
            if self.hdr[7]==0:
 
1464
                self.transport.loseConnection()
 
1465
 
 
1466
 
 
1467
class GetFileTransfer(protocol.Protocol):
 
1468
    header_fmt="!4s 2H 8s 6H 10I 32s 3c 69s 16s 2H 64s"
 
1469
    def __init__(self,client,cookie,dir):
 
1470
        self.client=client
 
1471
        self.cookie=cookie
 
1472
        self.dir=dir
 
1473
        self.buf=""
 
1474
 
 
1475
    def connectionMade(self):
 
1476
        def func(f,path,names):
 
1477
            names.sort(lambda x,y:cmp(string.lower(x),string.lower(y)))
 
1478
            for n in names:
 
1479
                name=os.path.join(path,n)
 
1480
                lt=time.localtime(os.path.getmtime(name))
 
1481
                size=os.path.getsize(name)
 
1482
                f[1]=f[1]+size
 
1483
                f.append("%02d/%02d/%4d %02d:%02d %8d %s" %
 
1484
                             (lt[1],lt[2],lt[0],lt[3],lt[4],size,name[f[0]:]))
 
1485
        f=[len(self.dir)+1,0]
 
1486
        os.path.walk(self.dir,func,f)
 
1487
        size=f[1]
 
1488
        self.listing=string.join(f[2:],"\r\n")+"\r\n"
 
1489
        open("\\listing.txt","w").write(self.listing)
 
1490
        hdr=["OFT2",256,0x1108,self.cookie,0,0,len(f)-2,len(f)-2,1,1,size,
 
1491
             len(self.listing),os.path.getmtime(self.dir),
 
1492
             checksum(self.listing),0,0,0,0,0,0,"OFT_Windows ICBMFT V1.1 32",
 
1493
             "\002",chr(0x1a),chr(0x10),"","",0,0,""]
 
1494
        self.transport.write(apply(struct.pack,[self.header_fmt]+hdr))
 
1495
 
 
1496
    def dataReceived(self,data):
 
1497
        self.buf=self.buf+data
 
1498
        while len(self.buf)>=256:
 
1499
            hdr=list(struct.unpack(self.header_fmt,self.buf[:256]))
 
1500
            self.buf=self.buf[256:]
 
1501
            if hdr[2]==0x1209:
 
1502
                self.file=StringIO.StringIO(self.listing)
 
1503
                self.transport.registerProducer(self,0)
 
1504
            elif hdr[2]==0x120b: pass
 
1505
            elif hdr[2]==0x120c: # file request
 
1506
                file=hdr[-1]
 
1507
                for k,v in [["\000",""],["\001",os.sep]]:
 
1508
                    file=string.replace(file,k,v)
 
1509
                self.name=os.path.join(self.dir,file)
 
1510
                self.file=open(self.name,'rb')
 
1511
                hdr[2]=0x0101
 
1512
                hdr[6]=hdr[7]=1
 
1513
                hdr[10]=hdr[11]=os.path.getsize(self.name)
 
1514
                hdr[12]=os.path.getmtime(self.name)
 
1515
                hdr[13]=checksum_file(self.file)
 
1516
                self.file.seek(0)
 
1517
                hdr[18]=hdr[19]=0
 
1518
                hdr[21]=chr(0x20)
 
1519
                self.transport.write(apply(struct.pack,[self.header_fmt]+hdr))
 
1520
                log.msg("got file request for %s"%file,hex(hdr[13]))
 
1521
            elif hdr[2]==0x0202:
 
1522
                log.msg("sending file")
 
1523
                self.transport.registerProducer(self,0)
 
1524
            elif hdr[2]==0x0204:
 
1525
                log.msg("real checksum: %s"%hex(hdr[19]))
 
1526
                del self.file
 
1527
            elif hdr[2]==0x0205: # resume
 
1528
                already=hdr[18]
 
1529
                if already:
 
1530
                    data=self.file.read(already)
 
1531
                else:
 
1532
                    data=""
 
1533
                log.msg("restarting at %s"%already)
 
1534
                hdr[2]=0x0106
 
1535
                hdr[19]=checksum(data)
 
1536
                self.transport.write(apply(struct.pack,[self.header_fmt]+hdr))
 
1537
            elif hdr[2]==0x0207:
 
1538
                self.transport.registerProducer(self,0)
 
1539
            else:
 
1540
                log.msg("don't understand 0x%04x"%hdr[2])
 
1541
                log.msg(hdr)
 
1542
 
 
1543
    def resumeProducing(self):
 
1544
        data=self.file.read(4096)
 
1545
        log.msg(len(data))
 
1546
        if not data:
 
1547
            self.transport.unregisterProducer()
 
1548
        self.transport.write(data)
 
1549
 
 
1550
    def pauseProducing(self): pass
 
1551
 
 
1552
    def stopProducing(self): del self.file
 
1553
 
 
1554
# UUIDs
 
1555
SEND_FILE_UID = "09461343-4C7F-11D1-8222-444553540000"
 
1556
GET_FILE_UID  = "09461348-4C7F-11D1-8222-444553540000"
 
1557
UUIDS={
 
1558
    SEND_FILE_UID:"SEND_FILE",
 
1559
    GET_FILE_UID:"GET_FILE"
 
1560
}
 
1561
 
 
1562
# ERRORS
 
1563
# general
 
1564
NOT_AVAILABLE=901
 
1565
CANT_WARN=902
 
1566
MESSAGES_TOO_FAST=903
 
1567
# admin
 
1568
BAD_INPUT=911
 
1569
BAD_ACCOUNT=912
 
1570
REQUEST_ERROR=913
 
1571
SERVICE_UNAVAILABLE=914
 
1572
# chat
 
1573
NO_CHAT_IN=950
 
1574
# im and info
 
1575
SEND_TOO_FAST=960
 
1576
MISSED_BIG_IM=961
 
1577
MISSED_FAST_IM=962
 
1578
# directory
 
1579
DIR_FAILURE=970
 
1580
TOO_MANY_MATCHES=971
 
1581
NEED_MORE_QUALIFIERS=972
 
1582
DIR_UNAVAILABLE=973
 
1583
NO_EMAIL_LOOKUP=974
 
1584
KEYWORD_IGNORED=975
 
1585
NO_KEYWORDS=976
 
1586
BAD_LANGUAGE=977
 
1587
BAD_COUNTRY=978
 
1588
DIR_FAIL_UNKNOWN=979
 
1589
# authorization
 
1590
BAD_NICKNAME=980
 
1591
SERVICE_TEMP_UNAVAILABLE=981
 
1592
WARNING_TOO_HIGH=982
 
1593
CONNECTING_TOO_QUICK=983
 
1594
UNKNOWN_SIGNON=989
 
1595
 
 
1596
STD_MESSAGE={}
 
1597
STD_MESSAGE[NOT_AVAILABLE]="%s not currently available"
 
1598
STD_MESSAGE[CANT_WARN]="Warning of %s not currently available"
 
1599
STD_MESSAGE[MESSAGES_TOO_FAST]="A message has been dropped, you are exceeding the server speed limit"
 
1600
STD_MESSAGE[BAD_INPUT]="Error validating input"
 
1601
STD_MESSAGE[BAD_ACCOUNT]="Invalid account"
 
1602
STD_MESSAGE[REQUEST_ERROR]="Error encountered while processing request"
 
1603
STD_MESSAGE[SERVICE_UNAVAILABLE]="Service unavailable"
 
1604
STD_MESSAGE[NO_CHAT_IN]="Chat in %s is unavailable"
 
1605
STD_MESSAGE[SEND_TOO_FAST]="You are sending messages too fast to %s"
 
1606
STD_MESSAGE[MISSED_BIG_IM]="You missed an IM from %s because it was too big"
 
1607
STD_MESSAGE[MISSED_FAST_IM]="You missed an IM from %s because it was sent too fast"
 
1608
# skipping directory for now
 
1609
STD_MESSAGE[BAD_NICKNAME]="Incorrect nickname or password"
 
1610
STD_MESSAGE[SERVICE_TEMP_UNAVAILABLE]="The service is temporarily unavailable"
 
1611
STD_MESSAGE[WARNING_TOO_HIGH]="Your warning level is currently too high to sign on"
 
1612
STD_MESSAGE[CONNECTING_TOO_QUICK]="You have been connecting and disconnecting too frequently.  Wait 10 minutes and try again.  If you continue to try, you will need to wait even longer."
 
1613
STD_MESSAGE[UNKNOWN_SIGNON]="An unknown signon error has occurred %s"