Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(301)

Side by Side Diff: third_party/twisted_8_1/twisted/conch/scripts/tkconch.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: twisted.conch.test.test_scripts -*-
2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 #
6 # $Id: tkconch.py,v 1.6 2003/02/22 08:10:15 z3p Exp $
7
8 """ Implementation module for the `tkconch` command.
9 """
10
11 from __future__ import nested_scopes
12
13 import Tkinter, tkFileDialog, tkFont, tkMessageBox, string
14 from twisted.conch.ui import tkvt100
15 from twisted.conch.ssh import transport, userauth, connection, common, keys
16 from twisted.conch.ssh import session, forwarding, channel
17 from twisted.conch.client.default import isInKnownHosts
18 from twisted.internet import reactor, defer, protocol, tksupport
19 from twisted.python import usage, log
20
21 import os, sys, getpass, struct, base64, signal
22
23 class TkConchMenu(Tkinter.Frame):
24 def __init__(self, *args, **params):
25 ## Standard heading: initialization
26 apply(Tkinter.Frame.__init__, (self,) + args, params)
27
28 self.master.title('TkConch')
29 self.localRemoteVar = Tkinter.StringVar()
30 self.localRemoteVar.set('local')
31
32 Tkinter.Label(self, anchor='w', justify='left', text='Hostname').grid(co lumn=1, row=1, sticky='w')
33 self.host = Tkinter.Entry(self)
34 self.host.grid(column=2, columnspan=2, row=1, sticky='nesw')
35
36 Tkinter.Label(self, anchor='w', justify='left', text='Port').grid(column =1, row=2, sticky='w')
37 self.port = Tkinter.Entry(self)
38 self.port.grid(column=2, columnspan=2, row=2, sticky='nesw')
39
40 Tkinter.Label(self, anchor='w', justify='left', text='Username').grid(co lumn=1, row=3, sticky='w')
41 self.user = Tkinter.Entry(self)
42 self.user.grid(column=2, columnspan=2, row=3, sticky='nesw')
43
44 Tkinter.Label(self, anchor='w', justify='left', text='Command').grid(col umn=1, row=4, sticky='w')
45 self.command = Tkinter.Entry(self)
46 self.command.grid(column=2, columnspan=2, row=4, sticky='nesw')
47
48 Tkinter.Label(self, anchor='w', justify='left', text='Identity').grid(co lumn=1, row=5, sticky='w')
49 self.identity = Tkinter.Entry(self)
50 self.identity.grid(column=2, row=5, sticky='nesw')
51 Tkinter.Button(self, command=self.getIdentityFile, text='Browse').grid(c olumn=3, row=5, sticky='nesw')
52
53 Tkinter.Label(self, text='Port Forwarding').grid(column=1, row=6, sticky ='w')
54 self.forwards = Tkinter.Listbox(self, height=0, width=0)
55 self.forwards.grid(column=2, columnspan=2, row=6, sticky='nesw')
56 Tkinter.Button(self, text='Add', command=self.addForward).grid(column=1, row=7)
57 Tkinter.Button(self, text='Remove', command=self.removeForward).grid(col umn=1, row=8)
58 self.forwardPort = Tkinter.Entry(self)
59 self.forwardPort.grid(column=2, row=7, sticky='nesw')
60 Tkinter.Label(self, text='Port').grid(column=3, row=7, sticky='nesw')
61 self.forwardHost = Tkinter.Entry(self)
62 self.forwardHost.grid(column=2, row=8, sticky='nesw')
63 Tkinter.Label(self, text='Host').grid(column=3, row=8, sticky='nesw')
64 self.localForward = Tkinter.Radiobutton(self, text='Local', variable=sel f.localRemoteVar, value='local')
65 self.localForward.grid(column=2, row=9)
66 self.remoteForward = Tkinter.Radiobutton(self, text='Remote', variable=s elf.localRemoteVar, value='remote')
67 self.remoteForward.grid(column=3, row=9)
68
69 Tkinter.Label(self, text='Advanced Options').grid(column=1, columnspan=3 , row=10, sticky='nesw')
70
71 Tkinter.Label(self, anchor='w', justify='left', text='Cipher').grid(colu mn=1, row=11, sticky='w')
72 self.cipher = Tkinter.Entry(self, name='cipher')
73 self.cipher.grid(column=2, columnspan=2, row=11, sticky='nesw')
74
75 Tkinter.Label(self, anchor='w', justify='left', text='MAC').grid(column= 1, row=12, sticky='w')
76 self.mac = Tkinter.Entry(self, name='mac')
77 self.mac.grid(column=2, columnspan=2, row=12, sticky='nesw')
78
79 Tkinter.Label(self, anchor='w', justify='left', text='Escape Char').grid (column=1, row=13, sticky='w')
80 self.escape = Tkinter.Entry(self, name='escape')
81 self.escape.grid(column=2, columnspan=2, row=13, sticky='nesw')
82 Tkinter.Button(self, text='Connect!', command=self.doConnect).grid(colum n=1, columnspan=3, row=14, sticky='nesw')
83
84 # Resize behavior(s)
85 self.grid_rowconfigure(6, weight=1, minsize=64)
86 self.grid_columnconfigure(2, weight=1, minsize=2)
87
88 self.master.protocol("WM_DELETE_WINDOW", sys.exit)
89
90
91 def getIdentityFile(self):
92 r = tkFileDialog.askopenfilename()
93 if r:
94 self.identity.delete(0, Tkinter.END)
95 self.identity.insert(Tkinter.END, r)
96
97 def addForward(self):
98 port = self.forwardPort.get()
99 self.forwardPort.delete(0, Tkinter.END)
100 host = self.forwardHost.get()
101 self.forwardHost.delete(0, Tkinter.END)
102 if self.localRemoteVar.get() == 'local':
103 self.forwards.insert(Tkinter.END, 'L:%s:%s' % (port, host))
104 else:
105 self.forwards.insert(Tkinter.END, 'R:%s:%s' % (port, host))
106
107 def removeForward(self):
108 cur = self.forwards.curselection()
109 if cur:
110 self.forwards.remove(cur[0])
111
112 def doConnect(self):
113 finished = 1
114 options['host'] = self.host.get()
115 options['port'] = self.port.get()
116 options['user'] = self.user.get()
117 options['command'] = self.command.get()
118 cipher = self.cipher.get()
119 mac = self.mac.get()
120 escape = self.escape.get()
121 if cipher:
122 if cipher in SSHClientTransport.supportedCiphers:
123 SSHClientTransport.supportedCiphers = [cipher]
124 else:
125 tkMessageBox.showerror('TkConch', 'Bad cipher.')
126 finished = 0
127
128 if mac:
129 if mac in SSHClientTransport.supportedMACs:
130 SSHClientTransport.supportedMACs = [mac]
131 elif finished:
132 tkMessageBox.showerror('TkConch', 'Bad MAC.')
133 finished = 0
134
135 if escape:
136 if escape == 'none':
137 options['escape'] = None
138 elif escape[0] == '^' and len(escape) == 2:
139 options['escape'] = chr(ord(escape[1])-64)
140 elif len(escape) == 1:
141 options['escape'] = escape
142 elif finished:
143 tkMessageBox.showerror('TkConch', "Bad escape character '%s'." % escape)
144 finished = 0
145
146 if self.identity.get():
147 options.identitys.append(self.identity.get())
148
149 for line in self.forwards.get(0,Tkinter.END):
150 if line[0]=='L':
151 options.opt_localforward(line[2:])
152 else:
153 options.opt_remoteforward(line[2:])
154
155 if '@' in options['host']:
156 options['user'], options['host'] = options['host'].split('@',1)
157
158 if (not options['host'] or not options['user']) and finished:
159 tkMessageBox.showerror('TkConch', 'Missing host or username.')
160 finished = 0
161 if finished:
162 self.master.quit()
163 self.master.destroy()
164 if options['log']:
165 realout = sys.stdout
166 log.startLogging(sys.stderr)
167 sys.stdout = realout
168 else:
169 log.discardLogs()
170 log.deferr = handleError # HACK
171 if not options.identitys:
172 options.identitys = ['~/.ssh/id_rsa', '~/.ssh/id_dsa']
173 host = options['host']
174 port = int(options['port'] or 22)
175 log.msg((host,port))
176 reactor.connectTCP(host, port, SSHClientFactory())
177 frame.master.deiconify()
178 frame.master.title('%s@%s - TkConch' % (options['user'], options['ho st']))
179 else:
180 self.focus()
181
182 class GeneralOptions(usage.Options):
183 synopsis = """Usage: tkconch [options] host [command]
184 """
185
186 optParameters = [['user', 'l', None, 'Log in using this user name.'],
187 ['identity', 'i', '~/.ssh/identity', 'Identity for public ke y authentication'],
188 ['escape', 'e', '~', "Set escape character; ``none'' = disab le"],
189 ['cipher', 'c', None, 'Select encryption algorithm.'],
190 ['macs', 'm', None, 'Specify MAC algorithms for protocol ver sion 2.'],
191 ['port', 'p', None, 'Connect to this port. Server must be o n the same port.'],
192 ['localforward', 'L', None, 'listen-port:host:port Forward local port to remote address'],
193 ['remoteforward', 'R', None, 'listen-port:host:port Forwar d remote port to local address'],
194 ]
195
196 optFlags = [['tty', 't', 'Tty; allocate a tty even if command is given.'],
197 ['notty', 'T', 'Do not allocate a tty.'],
198 ['version', 'V', 'Display version number only.'],
199 ['compress', 'C', 'Enable compression.'],
200 ['noshell', 'N', 'Do not execute a shell or command.'],
201 ['subsystem', 's', 'Invoke command (mandatory) as SSH2 subsystem .'],
202 ['log', 'v', 'Log to stderr'],
203 ['ansilog', 'a', 'Print the receieved data to stdout']]
204
205 #zsh_altArgDescr = {"foo":"use this description for foo instead"}
206 #zsh_multiUse = ["foo", "bar"]
207 zsh_mutuallyExclusive = [("tty", "notty")]
208 zsh_actions = {"cipher":"(%s)" % " ".join(transport.SSHClientTransport.suppo rtedCiphers),
209 "macs":"(%s)" % " ".join(transport.SSHClientTransport.support edMACs)}
210 zsh_actionDescr = {"localforward":"listen-port:host:port",
211 "remoteforward":"listen-port:host:port"}
212 # user, host, or user@host completion similar to zsh's ssh completion
213 zsh_extras = ['1:host | user@host:{_ssh;if compset -P "*@"; then _wanted hos ts expl "remote host name" _ssh_hosts && ret=0 elif compset -S "@*"; then _wante d users expl "login name" _ssh_users -S "" && ret=0 else if (( $+opt_args[-l] )) ; then tmp=() else tmp=( "users:login name:_ssh_users -qS@" ) fi; _alternative " hosts:remote host name:_ssh_hosts" "$tmp[@]" && ret=0 fi}',
214 '*:command: ']
215
216 identitys = []
217 localForwards = []
218 remoteForwards = []
219
220 def opt_identity(self, i):
221 self.identitys.append(i)
222
223 def opt_localforward(self, f):
224 localPort, remoteHost, remotePort = f.split(':') # doesn't do v6 yet
225 localPort = int(localPort)
226 remotePort = int(remotePort)
227 self.localForwards.append((localPort, (remoteHost, remotePort)))
228
229 def opt_remoteforward(self, f):
230 remotePort, connHost, connPort = f.split(':') # doesn't do v6 yet
231 remotePort = int(remotePort)
232 connPort = int(connPort)
233 self.remoteForwards.append((remotePort, (connHost, connPort)))
234
235 def opt_compress(self):
236 SSHClientTransport.supportedCompressions[0:1] = ['zlib']
237
238 def parseArgs(self, *args):
239 if args:
240 self['host'] = args[0]
241 self['command'] = ' '.join(args[1:])
242 else:
243 self['host'] = ''
244 self['command'] = ''
245
246 # Rest of code in "run"
247 options = None
248 menu = None
249 exitStatus = 0
250 frame = None
251
252 def deferredAskFrame(question, echo):
253 if frame.callback:
254 raise ValueError("can't ask 2 questions at once!")
255 d = defer.Deferred()
256 resp = []
257 def gotChar(ch, resp=resp):
258 if not ch: return
259 if ch=='\x03': # C-c
260 reactor.stop()
261 if ch=='\r':
262 frame.write('\r\n')
263 stresp = ''.join(resp)
264 del resp
265 frame.callback = None
266 d.callback(stresp)
267 return
268 elif 32 <= ord(ch) < 127:
269 resp.append(ch)
270 if echo:
271 frame.write(ch)
272 elif ord(ch) == 8 and resp: # BS
273 if echo: frame.write('\x08 \x08')
274 resp.pop()
275 frame.callback = gotChar
276 frame.write(question)
277 frame.canvas.focus_force()
278 return d
279
280 def run():
281 global menu, options, frame
282 args = sys.argv[1:]
283 if '-l' in args: # cvs is an idiot
284 i = args.index('-l')
285 args = args[i:i+2]+args
286 del args[i+2:i+4]
287 for arg in args[:]:
288 try:
289 i = args.index(arg)
290 if arg[:2] == '-o' and args[i+1][0]!='-':
291 args[i:i+2] = [] # suck on it scp
292 except ValueError:
293 pass
294 root = Tkinter.Tk()
295 root.withdraw()
296 top = Tkinter.Toplevel()
297 menu = TkConchMenu(top)
298 menu.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)
299 options = GeneralOptions()
300 try:
301 options.parseOptions(args)
302 except usage.UsageError, u:
303 print 'ERROR: %s' % u
304 options.opt_help()
305 sys.exit(1)
306 for k,v in options.items():
307 if v and hasattr(menu, k):
308 getattr(menu,k).insert(Tkinter.END, v)
309 for (p, (rh, rp)) in options.localForwards:
310 menu.forwards.insert(Tkinter.END, 'L:%s:%s:%s' % (p, rh, rp))
311 options.localForwards = []
312 for (p, (rh, rp)) in options.remoteForwards:
313 menu.forwards.insert(Tkinter.END, 'R:%s:%s:%s' % (p, rh, rp))
314 options.remoteForwards = []
315 frame = tkvt100.VT100Frame(root, callback=None)
316 root.geometry('%dx%d'%(tkvt100.fontWidth*frame.width+3, tkvt100.fontHeight*f rame.height+3))
317 frame.pack(side = Tkinter.TOP)
318 tksupport.install(root)
319 root.withdraw()
320 if (options['host'] and options['user']) or '@' in options['host']:
321 menu.doConnect()
322 else:
323 top.mainloop()
324 reactor.run()
325 sys.exit(exitStatus)
326
327 def handleError():
328 from twisted.python import failure
329 global exitStatus
330 exitStatus = 2
331 log.err(failure.Failure())
332 reactor.stop()
333 raise
334
335 class SSHClientFactory(protocol.ClientFactory):
336 noisy = 1
337
338 def stopFactory(self):
339 reactor.stop()
340
341 def buildProtocol(self, addr):
342 return SSHClientTransport()
343
344 def clientConnectionFailed(self, connector, reason):
345 tkMessageBox.showwarning('TkConch','Connection Failed, Reason:\n %s: %s' % (reason.type, reason.value))
346
347 class SSHClientTransport(transport.SSHClientTransport):
348
349 def receiveError(self, code, desc):
350 global exitStatus
351 exitStatus = 'conch:\tRemote side disconnected with error code %i\nconch :\treason: %s' % (code, desc)
352
353 def sendDisconnect(self, code, reason):
354 global exitStatus
355 exitStatus = 'conch:\tSending disconnect with error code %i\nconch:\trea son: %s' % (code, reason)
356 transport.SSHClientTransport.sendDisconnect(self, code, reason)
357
358 def receiveDebug(self, alwaysDisplay, message, lang):
359 global options
360 if alwaysDisplay or options['log']:
361 log.msg('Received Debug Message: %s' % message)
362
363 def verifyHostKey(self, pubKey, fingerprint):
364 #d = defer.Deferred()
365 #d.addCallback(lambda x:defer.succeed(1))
366 #d.callback(2)
367 #return d
368 goodKey = isInKnownHosts(options['host'], pubKey, {'known-hosts': None})
369 if goodKey == 1: # good key
370 return defer.succeed(1)
371 elif goodKey == 2: # AAHHHHH changed
372 return defer.fail(error.ConchError('bad host key'))
373 else:
374 if options['host'] == self.transport.getPeer()[1]:
375 host = options['host']
376 khHost = options['host']
377 else:
378 host = '%s (%s)' % (options['host'],
379 self.transport.getPeer()[1])
380 khHost = '%s,%s' % (options['host'],
381 self.transport.getPeer()[1])
382 keyType = common.getNS(pubKey)[0]
383 ques = """The authenticity of host '%s' can't be established.\r
384 %s key fingerprint is %s.""" % (host,
385 {'ssh-dss':'DSA', 'ssh-rsa':'RSA'}[keyType],
386 fingerprint)
387 ques+='\r\nAre you sure you want to continue connecting (yes/no)? '
388 return deferredAskFrame(ques, 1).addCallback(self._cbVerifyHostKey, pubKey, khHost, keyType)
389
390 def _cbVerifyHostKey(self, ans, pubKey, khHost, keyType):
391 if ans.lower() not in ('yes', 'no'):
392 return deferredAskFrame("Please type 'yes' or 'no': ",1).addCallbac k(self._cbVerifyHostKey, pubKey, khHost, keyType)
393 if ans.lower() == 'no':
394 frame.write('Host key verification failed.\r\n')
395 raise error.ConchError('bad host key')
396 try:
397 frame.write("Warning: Permanently added '%s' (%s) to the list of kno wn hosts.\r\n" % (khHost, {'ssh-dss':'DSA', 'ssh-rsa':'RSA'}[keyType]))
398 known_hosts = open(os.path.expanduser('~/.ssh/known_hosts'), 'a')
399 encodedKey = base64.encodestring(pubKey).replace('\n', '')
400 known_hosts.write('\n%s %s %s' % (khHost, keyType, encodedKey))
401 known_hosts.close()
402 except:
403 log.deferr()
404 raise error.ConchError
405
406 def connectionSecure(self):
407 if options['user']:
408 user = options['user']
409 else:
410 user = getpass.getuser()
411 self.requestService(SSHUserAuthClient(user, SSHConnection()))
412
413 class SSHUserAuthClient(userauth.SSHUserAuthClient):
414 usedFiles = []
415
416 def getPassword(self, prompt = None):
417 if not prompt:
418 prompt = "%s@%s's password: " % (self.user, options['host'])
419 return deferredAskFrame(prompt,0)
420
421 def getPublicKey(self):
422 files = [x for x in options.identitys if x not in self.usedFiles]
423 if not files:
424 return None
425 file = files[0]
426 log.msg(file)
427 self.usedFiles.append(file)
428 file = os.path.expanduser(file)
429 file += '.pub'
430 if not os.path.exists(file):
431 return
432 try:
433 return keys.getPublicKeyString(file)
434 except:
435 return self.getPublicKey() # try again
436
437 def getPrivateKey(self):
438 file = os.path.expanduser(self.usedFiles[-1])
439 if not os.path.exists(file):
440 return None
441 try:
442 return defer.succeed(keys.getPrivateKeyObject(file))
443 except keys.BadKeyError, e:
444 if e.args[0] == 'encrypted key with no password':
445 prompt = "Enter passphrase for key '%s': " % \
446 self.usedFiles[-1]
447 return deferredAskFrame(prompt, 0).addCallback(self._cbGetPrivat eKey, 0)
448 def _cbGetPrivateKey(self, ans, count):
449 file = os.path.expanduser(self.usedFiles[-1])
450 try:
451 return keys.getPrivateKeyObject(file, password = ans)
452 except keys.BadKeyError:
453 if count == 2:
454 raise
455 prompt = "Enter passphrase for key '%s': " % \
456 self.usedFiles[-1]
457 return deferredAskFrame(prompt, 0).addCallback(self._cbGetPrivateKey , count+1)
458
459 class SSHConnection(connection.SSHConnection):
460 def serviceStarted(self):
461 if not options['noshell']:
462 self.openChannel(SSHSession())
463 if options.localForwards:
464 for localPort, hostport in options.localForwards:
465 reactor.listenTCP(localPort,
466 forwarding.SSHListenForwardingFactory(self,
467 hostport,
468 forwarding.SSHListenClientForwardingChannel))
469 if options.remoteForwards:
470 for remotePort, hostport in options.remoteForwards:
471 log.msg('asking for remote forwarding for %s:%s' %
472 (remotePort, hostport))
473 data = forwarding.packGlobal_tcpip_forward(
474 ('0.0.0.0', remotePort))
475 d = self.sendGlobalRequest('tcpip-forward', data)
476 self.remoteForwards[remotePort] = hostport
477
478 class SSHSession(channel.SSHChannel):
479
480 name = 'session'
481
482 def channelOpen(self, foo):
483 #global globalSession
484 #globalSession = self
485 # turn off local echo
486 self.escapeMode = 1
487 c = session.SSHSessionClient()
488 if options['escape']:
489 c.dataReceived = self.handleInput
490 else:
491 c.dataReceived = self.write
492 c.connectionLost = self.sendEOF
493 frame.callback = c.dataReceived
494 frame.canvas.focus_force()
495 if options['subsystem']:
496 self.conn.sendRequest(self, 'subsystem', \
497 common.NS(options['command']))
498 elif options['command']:
499 if options['tty']:
500 term = os.environ.get('TERM', 'xterm')
501 #winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
502 winSize = (25,80,0,0) #struct.unpack('4H', winsz)
503 ptyReqData = session.packRequest_pty_req(term, winSize, '')
504 self.conn.sendRequest(self, 'pty-req', ptyReqData)
505 self.conn.sendRequest(self, 'exec', \
506 common.NS(options['command']))
507 else:
508 if not options['notty']:
509 term = os.environ.get('TERM', 'xterm')
510 #winsz = fcntl.ioctl(fd, tty.TIOCGWINSZ, '12345678')
511 winSize = (25,80,0,0) #struct.unpack('4H', winsz)
512 ptyReqData = session.packRequest_pty_req(term, winSize, '')
513 self.conn.sendRequest(self, 'pty-req', ptyReqData)
514 self.conn.sendRequest(self, 'shell', '')
515 self.conn.transport.transport.setTcpNoDelay(1)
516
517 def handleInput(self, char):
518 #log.msg('handling %s' % repr(char))
519 if char in ('\n', '\r'):
520 self.escapeMode = 1
521 self.write(char)
522 elif self.escapeMode == 1 and char == options['escape']:
523 self.escapeMode = 2
524 elif self.escapeMode == 2:
525 self.escapeMode = 1 # so we can chain escapes together
526 if char == '.': # disconnect
527 log.msg('disconnecting from escape')
528 reactor.stop()
529 return
530 elif char == '\x1a': # ^Z, suspend
531 # following line courtesy of Erwin@freenode
532 os.kill(os.getpid(), signal.SIGSTOP)
533 return
534 elif char == 'R': # rekey connection
535 log.msg('rekeying connection')
536 self.conn.transport.sendKexInit()
537 return
538 self.write('~' + char)
539 else:
540 self.escapeMode = 0
541 self.write(char)
542
543 def dataReceived(self, data):
544 if options['ansilog']:
545 print repr(data)
546 frame.write(data)
547
548 def extReceived(self, t, data):
549 if t==connection.EXTENDED_DATA_STDERR:
550 log.msg('got %s stderr data' % len(data))
551 sys.stderr.write(data)
552 sys.stderr.flush()
553
554 def eofReceived(self):
555 log.msg('got eof')
556 sys.stdin.close()
557
558 def closed(self):
559 log.msg('closed %s' % self)
560 if len(self.conn.channels) == 1: # just us left
561 reactor.stop()
562
563 def request_exit_status(self, data):
564 global exitStatus
565 exitStatus = int(struct.unpack('>L', data)[0])
566 log.msg('exit status: %s' % exitStatus)
567
568 def sendEOF(self):
569 self.conn.sendEOF(self)
570
571 if __name__=="__main__":
572 run()
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/conch/scripts/conch.py ('k') | third_party/twisted_8_1/twisted/conch/ssh/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698