~0x44/nova/bug838466

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/words/protocols/toc.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

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