]> code.delx.au - pymsnt/blob - src/misciq.py
Recursively ignore *.pyc
[pymsnt] / src / misciq.py
1 # Copyright 2004-2005 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
3
4 import utils
5 from twisted.internet import reactor, task, protocol, error
6 from twisted.words.xish.domish import Element
7 from twisted.words.protocols.jabber.jid import internJID
8 from debug import LogEvent, INFO, WARN, ERROR
9 import svninfo
10 import jabw
11 import legacy
12 import disco
13 import config
14 import lang
15 import ft
16 import base64
17 import sys, urllib
18
19
20 class ConnectUsers:
21 def __init__(self, pytrans):
22 self.pytrans = pytrans
23 self.pytrans.adHocCommands.addCommand("connectusers", self.incomingIq, "command_ConnectUsers")
24
25 def sendProbes(self):
26 for jid in self.pytrans.xdb.files():
27 jabw.sendPresence(self.pytrans, jid, config.jid, ptype="probe")
28
29 def incomingIq(self, el):
30 to = el.getAttribute("from")
31 ID = el.getAttribute("id")
32 ulang = utils.getLang(el)
33
34 if config.admins.count(internJID(to).userhost()) == 0:
35 self.pytrans.discovery.sendIqError(to=to, fro=config.jid, ID=ID, xmlns=disco.COMMANDS, etype="cancel", condition="not-authorized")
36 return
37
38
39 self.sendProbes()
40
41 iq = Element((None, "iq"))
42 iq.attributes["to"] = to
43 iq.attributes["from"] = config.jid
44 if(ID):
45 iq.attributes["id"] = ID
46 iq.attributes["type"] = "result"
47
48 command = iq.addElement("command")
49 command.attributes["sessionid"] = self.pytrans.makeMessageID()
50 command.attributes["xmlns"] = disco.COMMANDS
51 command.attributes["status"] = "completed"
52
53 x = command.addElement("x")
54 x.attributes["xmlns"] = disco.XDATA
55 x.attributes["type"] = "result"
56
57 title = x.addElement("title")
58 title.addContent(lang.get(ulang).command_ConnectUsers)
59
60 field = x.addElement("field")
61 field.attributes["type"] = "fixed"
62 field.addElement("value").addContent(lang.get(ulang).command_Done)
63
64 self.pytrans.send(iq)
65
66
67 class Statistics:
68 def __init__(self, pytrans):
69 self.pytrans = pytrans
70 self.pytrans.adHocCommands.addCommand("stats", self.incomingIq, "command_Statistics")
71
72 # self.stats is indexed by a unique ID, with value being the value for that statistic
73 self.stats = {}
74 self.stats["Uptime"] = 0
75 self.stats["OnlineUsers"] = 0
76 self.stats["TotalUsers"] = 0
77
78 legacy.startStats(self)
79
80 def incomingIq(self, el):
81 to = el.getAttribute("from")
82 ID = el.getAttribute("id")
83 ulang = utils.getLang(el)
84
85 iq = Element((None, "iq"))
86 iq.attributes["to"] = to
87 iq.attributes["from"] = config.jid
88 if(ID):
89 iq.attributes["id"] = ID
90 iq.attributes["type"] = "result"
91
92 command = iq.addElement("command")
93 command.attributes["sessionid"] = self.pytrans.makeMessageID()
94 command.attributes["xmlns"] = disco.COMMANDS
95 command.attributes["status"] = "completed"
96
97 x = command.addElement("x")
98 x.attributes["xmlns"] = disco.XDATA
99 x.attributes["type"] = "result"
100
101 title = x.addElement("title")
102 title.addContent(lang.get(ulang).command_Statistics)
103
104 for key in self.stats:
105 label = getattr(lang.get(ulang), "command_%s" % key)
106 description = getattr(lang.get(ulang), "command_%s_Desc" % key)
107 field = x.addElement("field")
108 field.attributes["var"] = key
109 field.attributes["label"] = label
110 field.attributes["type"] = "text-single"
111 field.addElement("value").addContent(str(self.stats[key]))
112 field.addElement("desc").addContent(description)
113
114 self.pytrans.send(iq)
115
116
117
118 class AdHocCommands:
119 def __init__(self, pytrans):
120 self.pytrans = pytrans
121 self.pytrans.discovery.addFeature(disco.COMMANDS, self.incomingIq, config.jid)
122 self.pytrans.discovery.addNode(disco.COMMANDS, self.sendCommandList, "command_CommandList", config.jid, True)
123
124 self.commands = {} # Dict of handlers indexed by node
125 self.commandNames = {} # Dict of names indexed by node
126
127 def addCommand(self, command, handler, name):
128 self.commands[command] = handler
129 self.commandNames[command] = name
130 self.pytrans.discovery.addNode(command, self.incomingIq, name, config.jid, False)
131
132 def incomingIq(self, el):
133 itype = el.getAttribute("type")
134 fro = el.getAttribute("from")
135 froj = internJID(fro)
136 to = el.getAttribute("to")
137 ID = el.getAttribute("id")
138
139 LogEvent(INFO, "", "Looking for handler")
140
141 node = None
142 for child in el.elements():
143 xmlns = child.uri
144 node = child.getAttribute("node")
145
146 handled = False
147 if(child.name == "query" and xmlns == disco.DISCO_INFO):
148 if(node and self.commands.has_key(node) and (itype == "get")):
149 self.sendCommandInfoResponse(to=fro, ID=ID)
150 handled = True
151 elif(child.name == "query" and xmlns == disco.DISCO_ITEMS):
152 if(node and self.commands.has_key(node) and (itype == "get")):
153 self.sendCommandItemsResponse(to=fro, ID=ID)
154 handled = True
155 elif(child.name == "command" and xmlns == disco.COMMANDS):
156 if((node and self.commands.has_key(node)) and (itype == "set" or itype == "error")):
157 self.commands[node](el)
158 handled = True
159 if(not handled):
160 LogEvent(WARN, "", "Unknown Ad-Hoc command received.")
161 self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns=xmlns, etype="cancel", condition="feature-not-implemented")
162
163
164 def sendCommandList(self, el):
165 to = el.getAttribute("from")
166 ID = el.getAttribute("id")
167 ulang = utils.getLang(el)
168
169 iq = Element((None, "iq"))
170 iq.attributes["to"] = to
171 iq.attributes["from"] = config.jid
172 if ID:
173 iq.attributes["id"] = ID
174 iq.attributes["type"] = "result"
175
176 query = iq.addElement("query")
177 query.attributes["xmlns"] = disco.DISCO_ITEMS
178 query.attributes["node"] = disco.COMMANDS
179
180 for command in self.commands:
181 item = query.addElement("item")
182 item.attributes["jid"] = config.jid
183 item.attributes["node"] = command
184 item.attributes["name"] = getattr(lang.get(ulang), self.commandNames[command])
185
186 self.pytrans.send(iq)
187
188 def sendCommandInfoResponse(self, to, ID):
189 LogEvent(INFO, "", "Replying to disco#info")
190 iq = Element((None, "iq"))
191 iq.attributes["type"] = "result"
192 iq.attributes["from"] = config.jid
193 iq.attributes["to"] = to
194 if(ID): iq.attributes["id"] = ID
195 query = iq.addElement("query")
196 query.attributes["xmlns"] = disco.DISCO_INFO
197
198 feature = query.addElement("feature")
199 feature.attributes["var"] = disco.COMMANDS
200 self.pytrans.send(iq)
201
202 def sendCommandItemsResponse(self, to, ID):
203 LogEvent(INFO, "", "Replying to disco#items")
204 iq = Element((None, "iq"))
205 iq.attributes["type"] = "result"
206 iq.attributes["from"] = config.jid
207 iq.attributes["to"] = to
208 if(ID): iq.attributes["id"] = ID
209 query = iq.addElement("query")
210 query.attributes["xmlns"] = disco.DISCO_ITEMS
211 self.pytrans.send(iq)
212
213
214 class VCardFactory:
215 def __init__(self, pytrans):
216 self.pytrans = pytrans
217 self.pytrans.discovery.addFeature("vcard-temp", self.incomingIq, "USER")
218 self.pytrans.discovery.addFeature("vcard-temp", self.incomingIq, config.jid)
219
220 def incomingIq(self, el):
221 itype = el.getAttribute("type")
222 fro = el.getAttribute("from")
223 froj = internJID(fro)
224 to = el.getAttribute("to")
225 toj = internJID(to)
226 ID = el.getAttribute("id")
227 if itype != "get" and itype != "error":
228 self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns="vcard-temp", etype="cancel", condition="feature-not-implemented")
229 return
230
231 LogEvent(INFO, "", "Sending vCard")
232
233 toGateway = not (to.find('@') > 0)
234
235 if not toGateway:
236 if not self.pytrans.sessions.has_key(froj.userhost()):
237 self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns="vcard-temp", etype="auth", condition="not-authorized")
238 return
239 s = self.pytrans.sessions[froj.userhost()]
240 if not s.ready:
241 self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns="vcard-temp", etype="auth", condition="not-authorized")
242 return
243
244 c = s.contactList.findContact(toj.userhost())
245 if not c:
246 self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns="vcard-temp", etype="cancel", condition="recipient-unavailable")
247 return
248
249
250 iq = Element((None, "iq"))
251 iq.attributes["to"] = fro
252 iq.attributes["from"] = to
253 if ID:
254 iq.attributes["id"] = ID
255 iq.attributes["type"] = "result"
256 vCard = iq.addElement("vCard")
257 vCard.attributes["xmlns"] = "vcard-temp"
258 if toGateway:
259 FN = vCard.addElement("FN")
260 FN.addContent(config.discoName)
261 DESC = vCard.addElement("DESC")
262 DESC.addContent(config.discoName)
263 URL = vCard.addElement("URL")
264 URL.addContent(legacy.url)
265 else:
266 if c.nickname:
267 NICKNAME = vCard.addElement("NICKNAME")
268 NICKNAME.addContent(c.nickname)
269 if c.avatar:
270 PHOTO = c.avatar.makePhotoElement()
271 vCard.addChild(PHOTO)
272
273 self.pytrans.send(iq)
274
275 class IqAvatarFactory:
276 def __init__(self, pytrans):
277 self.pytrans = pytrans
278 self.pytrans.discovery.addFeature(disco.IQAVATAR, self.incomingIq, "USER")
279 self.pytrans.discovery.addFeature(disco.STORAGEAVATAR, self.incomingIq, "USER")
280
281 def incomingIq(self, el):
282 itype = el.getAttribute("type")
283 fro = el.getAttribute("from")
284 froj = internJID(fro)
285 to = el.getAttribute("to")
286 toj = internJID(to)
287 ID = el.getAttribute("id")
288
289 if(itype != "get" and itype != "error"):
290 self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns=disco.IQAVATAR, etype="cancel", condition="feature-not-implemented")
291 return
292
293 LogEvent(INFO, "", "Retrieving avatar")
294
295 if(not self.pytrans.sessions.has_key(froj.userhost())):
296 self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns=disco.IQAVATAR, etype="auth", condition="not-authorized")
297 return
298 s = self.pytrans.sessions[froj.userhost()]
299 if(not s.ready):
300 self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns=disco.IQAVATAR, etype="auth", condition="not-authorized")
301 return
302
303 c = s.contactList.findContact(toj.userhost())
304 if(not c):
305 self.pytrans.discovery.sendIqError(to=fro, fro=config.jid, ID=ID, xmlns=disco.IQAVATAR, etype="cancel", condition="recipient-unavailable")
306 return
307
308 iq = Element((None, "iq"))
309 iq.attributes["to"] = fro
310 iq.attributes["from"] = to
311 if ID:
312 iq.attributes["id"] = ID
313 iq.attributes["type"] = "result"
314 query = iq.addElement("query")
315 query.attributes["xmlns"] = disco.IQAVATAR
316 if(c.avatar):
317 DATA = c.avatar.makeDataElement()
318 query.addChild(DATA)
319
320 self.pytrans.send(iq)
321
322
323
324 class PingService:
325 def __init__(self, pytrans):
326 self.pytrans = pytrans
327 # self.pingCounter = 0
328 # self.pingTask = task.LoopingCall(self.pingCheck)
329 self.pingTask = task.LoopingCall(self.whitespace)
330 # reactor.callLater(10.0, self.start)
331
332 # def start(self):
333 # self.pingTask.start(120.0)
334
335 def whitespace(self):
336 self.pytrans.send(" ")
337
338 # def pingCheck(self):
339 # if(self.pingCounter >= 2 and self.pytrans.xmlstream): # Two minutes of no response from the main server
340 # LogEvent(WARN, "", "Disconnecting because the main server has ignored our pings for too long.")
341 # self.pytrans.xmlstream.transport.loseConnection()
342 # elif(config.mainServerJID):
343 # d = self.pytrans.discovery.sendIq(self.makePingPacket())
344 # d.addCallback(self.pongReceived)
345 # d.addErrback(self.pongFailed)
346 # self.pingCounter += 1
347
348 # def pongReceived(self, el):
349 # self.pingCounter = 0
350
351 # def pongFailed(self, el):
352 # pass
353
354 # def makePingPacket(self):
355 # iq = Element((None, "iq"))
356 # iq.attributes["from"] = config.jid
357 # iq.attributes["to"] = config.mainServerJID
358 # iq.attributes["type"] = "get"
359 # query = iq.addElement("query")
360 # query.attributes["xmlns"] = disco.IQVERSION
361 # return iq
362
363 class GatewayTranslator:
364 def __init__(self, pytrans):
365 self.pytrans = pytrans
366 self.pytrans.discovery.addFeature(disco.IQGATEWAY, self.incomingIq, config.jid)
367
368 def incomingIq(self, el):
369 fro = el.getAttribute("from")
370 ID = el.getAttribute("id")
371 itype = el.getAttribute("type")
372 if(itype == "get"):
373 self.sendPrompt(fro, ID, utils.getLang(el))
374 elif(itype == "set"):
375 self.sendTranslation(fro, ID, el)
376
377
378 def sendPrompt(self, to, ID, ulang):
379 LogEvent(INFO)
380
381 iq = Element((None, "iq"))
382
383 iq.attributes["type"] = "result"
384 iq.attributes["from"] = config.jid
385 iq.attributes["to"] = to
386 if ID:
387 iq.attributes["id"] = ID
388 query = iq.addElement("query")
389 query.attributes["xmlns"] = disco.IQGATEWAY
390 desc = query.addElement("desc")
391 desc.addContent(lang.get(ulang).gatewayTranslator)
392 prompt = query.addElement("prompt")
393
394 self.pytrans.send(iq)
395
396 def sendTranslation(self, to, ID, el):
397 LogEvent(INFO)
398
399 # Find the user's legacy account
400 legacyaccount = None
401 for query in el.elements():
402 if(query.name == "query"):
403 for child in query.elements():
404 if(child.name == "prompt"):
405 legacyaccount = str(child)
406 break
407 break
408
409
410 if(legacyaccount and len(legacyaccount) > 0):
411 LogEvent(INFO, "", "Sending translated account.")
412 iq = Element((None, "iq"))
413 iq.attributes["type"] = "result"
414 iq.attributes["from"] = config.jid
415 iq.attributes["to"] = to
416 if ID:
417 iq.attributes["id"] = ID
418 query = iq.addElement("query")
419 query.attributes["xmlns"] = disco.IQGATEWAY
420 prompt = query.addElement("prompt")
421 prompt.addContent(legacy.translateAccount(legacyaccount))
422
423 self.pytrans.send(iq)
424
425 else:
426 self.pytrans.discovery.sendIqError(to, ID, disco.IQGATEWAY)
427 self.pytrans.discovery.sendIqError(to=to, fro=config.jid, ID=ID, xmlns=disco.IQGATEWAY, etype="retry", condition="bad-request")
428
429
430
431 class VersionTeller:
432 def __init__(self, pytrans):
433 self.pytrans = pytrans
434 self.pytrans.discovery.addFeature(disco.IQVERSION, self.incomingIq, config.jid)
435 self.pytrans.discovery.addFeature(disco.IQVERSION, self.incomingIq, "USER")
436 try:
437 self.version = "%s - SVN r%s" % (legacy.version, svninfo.getSVNVersion())
438 except:
439 self.version = legacy.version
440 self.os = "Python" + ".".join([str(x) for x in sys.version_info[0:3]]) + " - " + sys.platform
441
442 def incomingIq(self, el):
443 eltype = el.getAttribute("type")
444 if(eltype != "get"): return # Only answer "get" stanzas
445
446 self.sendVersion(el)
447
448 def sendVersion(self, el):
449 LogEvent(INFO)
450 iq = Element((None, "iq"))
451 iq.attributes["type"] = "result"
452 iq.attributes["from"] = el.getAttribute("to")
453 iq.attributes["to"] = el.getAttribute("from")
454 if(el.getAttribute("id")):
455 iq.attributes["id"] = el.getAttribute("id")
456 query = iq.addElement("query")
457 query.attributes["xmlns"] = disco.IQVERSION
458 name = query.addElement("name")
459 name.addContent(config.discoName)
460 version = query.addElement("version")
461 version.addContent(self.version)
462 os = query.addElement("os")
463 os.addContent(self.os)
464
465 self.pytrans.send(iq)
466
467
468 class FileTransferOOBSend:
469 def __init__(self, pytrans):
470 self.pytrans = pytrans
471 self.pytrans.discovery.addFeature(disco.IQOOB, self.incomingOOB, "USER")
472
473 def incomingOOB(self, el):
474 ID = el.getAttribute("id")
475 def errOut():
476 self.pytrans.discovery.sendIqError(to=el.getAttribute("from"), fro=el.getAttribute("to"), ID=ID, xmlns=disco.IQOOB, etype="cancel", condition="feature-not-implemented")
477
478 if el.attributes["type"] != "set":
479 return errOut()
480 for child in el.elements():
481 if child.name == "query":
482 query = child
483 break
484 else:
485 return errOut()
486 for child in query.elements():
487 if child.name == "url":
488 url = child.__str__()
489 break
490 else:
491 return errOut()
492
493 froj = internJID(el.getAttribute("from"))
494 toj = internJID(el.getAttribute("to"))
495 session = self.pytrans.sessions.get(froj.userhost(), None)
496 if not session:
497 return errOut()
498
499 res = utils.getURLBits(url, "http")
500 if not res:
501 return errOut()
502 host, port, path, filename = res
503
504
505 def sendResult():
506 iq = Element((None, "iq"))
507 iq.attributes["to"] = froj.full()
508 iq.attributes["from"] = toj.full()
509 iq.attributes["type"] = "result"
510 if ID:
511 iq.attributes["id"] = ID
512 iq.addElement("query").attributes["xmlns"] = "jabber:iq:oob"
513 self.pytrans.send(iq)
514
515 def startTransfer(consumer):
516 factory = protocol.ClientFactory()
517 factory.protocol = ft.OOBSendConnector
518 factory.path = path
519 factory.host = host
520 factory.port = port
521 factory.consumer = consumer
522 factory.finished = sendResult
523 reactor.connectTCP(host, port, factory)
524
525 def doSendFile(length):
526 ft.FTSend(session, toj.userhost(), startTransfer, errOut, filename, length)
527
528 # Make a HEAD request to grab the length of data first
529 factory = protocol.ClientFactory()
530 factory.protocol = ft.OOBHeaderHelper
531 factory.path = path
532 factory.host = host
533 factory.port = port
534 factory.gotLength = doSendFile
535 reactor.connectTCP(host, port, factory)
536
537
538
539 class Socks5FileTransfer:
540 def __init__(self, pytrans):
541 self.pytrans = pytrans
542 self.pytrans.discovery.addFeature(disco.SI, self.incomingSI, "USER")
543 self.pytrans.discovery.addFeature(disco.FT, lambda: None, "USER")
544 self.pytrans.discovery.addFeature(disco.S5B, self.incomingS5B, "USER")
545 self.sessions = {}
546
547 def incomingSI(self, el):
548 ID = el.getAttribute("id")
549 def errOut():
550 self.pytrans.discovery.sendIqError(to=el.getAttribute("from"), fro=el.getAttribute("to"), ID=ID, xmlns=disco.SI, etype="cancel", condition="bad-request")
551
552 toj = internJID(el.getAttribute("to"))
553 froj = internJID(el.getAttribute("from"))
554 session = self.pytrans.sessions.get(froj.userhost(), None)
555 if not session:
556 return errOut()
557
558 si = el.si
559 if not (si and si.getAttribute("profile") == disco.FT):
560 return errOut()
561 file = si.file
562 if not (file and file.uri == disco.FT):
563 return errOut()
564 try:
565 sid = si["id"]
566 filename = file["name"]
567 filesize = int(file["size"])
568 except KeyError:
569 return errOut()
570 except ValueError:
571 return errOut()
572
573 # Check that we can use socks5 bytestreams
574 feature = si.feature
575 if not (feature and feature.uri == disco.FEATURE_NEG):
576 return errOut()
577 x = feature.x
578 if not (x and x.uri == disco.XDATA):
579 return errOut()
580 field = x.field
581 if not (field and field.getAttribute("var") == "stream-method"):
582 return errOut()
583 for option in field.elements():
584 value = option.value
585 if not value:
586 continue
587 value = value.__str__()
588 if value == disco.S5B:
589 break
590 else:
591 return errOut() # Socks5 bytestreams not supported :(
592
593
594 def startTransfer(consumer):
595 iq = Element((None, "iq"))
596 iq["type"] = "result"
597 iq["to"] = froj.full()
598 iq["from"] = toj.full()
599 iq["id"] = ID
600 si = iq.addElement("si")
601 si["xmlns"] = disco.SI
602 feature = si.addElement("feature")
603 feature["xmlns"] = disco.FEATURE_NEG
604 x = feature.addElement("x")
605 x["xmlns"] = disco.XDATA
606 x["type"] = "submit"
607 field = x.addElement("field")
608 field["var"] = "stream-method"
609 value = field.addElement("value")
610 value.addContent(disco.S5B)
611 self.pytrans.send(iq)
612 self.sessions[(froj.full(), sid)] = consumer
613
614 ft.FTSend(session, toj.userhost(), startTransfer, errOut, filename, filesize)
615
616 def incomingS5B(self, el):
617 ID = el.getAttribute("id")
618 def errOut():
619 self.pytrans.discovery.sendIqError(to=el.getAttribute("from"), fro=el.getAttribute("to"), ID=ID, xmlns=disco.S5B, etype="cancel", condition="item-not-found")
620
621 if el.getAttribute("type") != "set":
622 return errOut()
623
624 toj = internJID(el.getAttribute("to"))
625 froj = internJID(el.getAttribute("from"))
626
627 query = el.query
628 if not (query and query.getAttribute("mode", "tcp") == "tcp"):
629 return errOut()
630 sid = query.getAttribute("sid")
631 consumer = self.sessions.pop((froj.full(), sid), None)
632 if not consumer:
633 return errOut()
634 streamhosts = []
635 for streamhost in query.elements():
636 if streamhost.name == "streamhost":
637 try:
638 JID = streamhost["jid"]
639 host = streamhost["host"]
640 port = int(streamhost["port"])
641 except ValueError:
642 return errOut()
643 except KeyError:
644 continue
645 streamhosts.append((JID, host, port))
646
647
648 def gotStreamhost(host):
649 for streamhost in streamhosts:
650 if streamhost[1] == host:
651 jid = streamhost[0]
652 break
653 else:
654 LogEvent(WARN)
655 return errOut()
656
657 for connector in factory.connectors:
658 # Stop any other connections
659 try:
660 connector.stopConnecting()
661 except error.NotConnectingError:
662 pass
663
664 if factory.streamHostTimeout:
665 factory.streamHostTimeout.cancel()
666 factory.streamHostTimeout = None
667
668 iq = Element((None, "iq"))
669 iq["type"] = "result"
670 iq["from"] = toj.full()
671 iq["to"] = froj.full()
672 iq["id"] = ID
673 query = iq.addElement("query")
674 query["xmlns"] = disco.S5B
675 streamhost = query.addElement("streamhost-used")
676 streamhost["jid"] = jid
677 self.pytrans.send(iq)
678
679
680 # Try the streamhosts
681 LogEvent(INFO)
682 factory = protocol.ClientFactory()
683 factory.protocol = ft.JEP65ConnectionSend
684 factory.consumer = consumer
685 factory.hash = utils.socks5Hash(sid, froj.full(), toj.full())
686 factory.madeConnection = gotStreamhost
687 factory.connectors = []
688 factory.streamHostTimeout = reactor.callLater(120, consumer.error)
689
690 for streamhost in streamhosts:
691 factory.connectors.append(reactor.connectTCP(streamhost[1], streamhost[2], factory))
692
693