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