1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 from __future__ import with_statement
17
18
19 import os
20 import types
21 import binascii
22 import base64
23 from twisted.python import log
24 from twisted.application import service, strports
25 from twisted.cred import checkers, portal
26 from twisted.conch import manhole, telnet, manhole_ssh, checkers as conchc
27 from twisted.conch.insults import insults
28 from twisted.internet import protocol
29
30 from buildbot.util import ComparableMixin
31 from zope.interface import implements
32
33
34
36
37
40
42 auth = telnet.AuthenticatingTelnetProtocol
43 return telnet.TelnetTransport(auth, self.portal)
44
46 implements(portal.IRealm)
47
49 self.namespace_maker = namespace_maker
50
52 if telnet.ITelnetProtocol in interfaces:
53 namespace = self.namespace_maker()
54 p = telnet.TelnetBootstrapProtocol(insults.ServerProtocol,
55 manhole.ColoredManhole,
56 namespace)
57 return (telnet.ITelnetProtocol, p, lambda: None)
58 raise NotImplementedError()
59
60
62
63
65 self.namespace = namespace
66
68 return insults.ServerProtocol(manhole.ColoredManhole, self.namespace)
69
71 """Accept connections using SSH keys from a given file.
72
73 SSHPublicKeyDatabase takes the username that the prospective client has
74 requested and attempts to get a ~/.ssh/authorized_keys file for that
75 username. This requires root access, so it isn't as useful as you'd
76 like.
77
78 Instead, this subclass looks for keys in a single file, given as an
79 argument. This file is typically kept in the buildmaster's basedir. The
80 file should have 'ssh-dss ....' lines in it, just like authorized_keys.
81 """
82
83 - def __init__(self, authorized_keys_file):
84 self.authorized_keys_file = os.path.expanduser(authorized_keys_file)
85
87 with open(self.authorized_keys_file) as f:
88 for l in f.readlines():
89 l2 = l.split()
90 if len(l2) < 2:
91 continue
92 try:
93 if base64.decodestring(l2[1]) == credentials.blob:
94 return 1
95 except binascii.Error:
96 continue
97 return 0
98
99
101 """This provides remote access to a python interpreter (a read/exec/print
102 loop) embedded in the buildmaster via an internal SSH server. This allows
103 detailed inspection of the buildmaster state. It is of most use to
104 buildbot developers. Connect to this by running an ssh client.
105 """
106
107 - def __init__(self, port, checker, using_ssh=True):
108 """
109 @type port: string or int
110 @param port: what port should the Manhole listen on? This is a
111 strports specification string, like 'tcp:12345' or
112 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
113 simple tcp port.
114
115 @type checker: an object providing the
116 L{twisted.cred.checkers.ICredentialsChecker} interface
117 @param checker: if provided, this checker is used to authenticate the
118 client instead of using the username/password scheme. You must either
119 provide a username/password or a Checker. Some useful values are::
120 import twisted.cred.checkers as credc
121 import twisted.conch.checkers as conchc
122 c = credc.AllowAnonymousAccess # completely open
123 c = credc.FilePasswordDB(passwd_filename) # file of name:passwd
124 c = conchc.UNIXPasswordDatabase # getpwnam() (probably /etc/passwd)
125
126 @type using_ssh: bool
127 @param using_ssh: If True, accept SSH connections. If False, accept
128 regular unencrypted telnet connections.
129 """
130
131
132
133
134
135
136 service.MultiService.__init__(self)
137 if type(port) is int:
138 port = "tcp:%d" % port
139 self.port = port
140 self.checker = checker
141
142 def makeNamespace():
143 master = self.master
144 namespace = {
145 'master': master,
146 'status': master.getStatus(),
147 'show': show,
148 }
149 return namespace
150
151 def makeProtocol():
152 namespace = makeNamespace()
153 p = insults.ServerProtocol(manhole.ColoredManhole, namespace)
154 return p
155
156 self.using_ssh = using_ssh
157 if using_ssh:
158 r = manhole_ssh.TerminalRealm()
159 r.chainedProtocolFactory = makeProtocol
160 p = portal.Portal(r, [self.checker])
161 f = manhole_ssh.ConchFactory(p)
162 else:
163 r = _TelnetRealm(makeNamespace)
164 p = portal.Portal(r, [self.checker])
165 f = protocol.ServerFactory()
166 f.protocol = makeTelnetProtocol(p)
167 s = strports.service(self.port, f)
168 s.setServiceParent(self)
169
170
172 service.MultiService.startService(self)
173 if self.using_ssh:
174 via = "via SSH"
175 else:
176 via = "via telnet"
177 log.msg("Manhole listening %s on port %s" % (via, self.port))
178
179
181 """This Manhole accepts unencrypted (telnet) connections, and requires a
182 username and password authorize access. You are encouraged to use the
183 encrypted ssh-based manhole classes instead."""
184
185 compare_attrs = ["port", "username", "password"]
186
187 - def __init__(self, port, username, password):
188 """
189 @type port: string or int
190 @param port: what port should the Manhole listen on? This is a
191 strports specification string, like 'tcp:12345' or
192 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
193 simple tcp port.
194
195 @param username:
196 @param password: username= and password= form a pair of strings to
197 use when authenticating the remote user.
198 """
199
200 self.username = username
201 self.password = password
202
203 c = checkers.InMemoryUsernamePasswordDatabaseDontUse()
204 c.addUser(username, password)
205
206 _BaseManhole.__init__(self, port, c, using_ssh=False)
207
209 """This Manhole accepts encrypted (ssh) connections, and requires a
210 username and password to authorize access.
211 """
212
213 compare_attrs = ["port", "username", "password"]
214
215 - def __init__(self, port, username, password):
216 """
217 @type port: string or int
218 @param port: what port should the Manhole listen on? This is a
219 strports specification string, like 'tcp:12345' or
220 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
221 simple tcp port.
222
223 @param username:
224 @param password: username= and password= form a pair of strings to
225 use when authenticating the remote user.
226 """
227
228 self.username = username
229 self.password = password
230
231 c = checkers.InMemoryUsernamePasswordDatabaseDontUse()
232 c.addUser(username, password)
233
234 _BaseManhole.__init__(self, port, c)
235
237 """This Manhole accepts ssh connections, and requires that the
238 prospective client have an ssh private key that matches one of the public
239 keys in our authorized_keys file. It is created with the name of a file
240 that contains the public keys that we will accept."""
241
242 compare_attrs = ["port", "keyfile"]
243
245 """
246 @type port: string or int
247 @param port: what port should the Manhole listen on? This is a
248 strports specification string, like 'tcp:12345' or
249 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
250 simple tcp port.
251
252 @param keyfile: the name of a file (relative to the buildmaster's
253 basedir) that contains SSH public keys of authorized
254 users, one per line. This is the exact same format
255 as used by sshd in ~/.ssh/authorized_keys .
256 """
257
258
259
260 self.keyfile = keyfile
261 c = AuthorizedKeysChecker(keyfile)
262 _BaseManhole.__init__(self, port, c)
263
265 """This Manhole accepts ssh connections, but uses an arbitrary
266 user-supplied 'checker' object to perform authentication."""
267
268 compare_attrs = ["port", "checker"]
269
271 """
272 @type port: string or int
273 @param port: what port should the Manhole listen on? This is a
274 strports specification string, like 'tcp:12345' or
275 'tcp:12345:interface=127.0.0.1'. Bare integers are treated as a
276 simple tcp port.
277
278 @param checker: an instance of a twisted.cred 'checker' which will
279 perform authentication
280 """
281
282 _BaseManhole.__init__(self, port, checker)
283
284
285
287 """Display the data attributes of an object in a readable format"""
288 print "data attributes of %r" % (x,)
289 names = dir(x)
290 maxlen = max([0] + [len(n) for n in names])
291 for k in names:
292 v = getattr(x,k)
293 t = type(v)
294 if t == types.MethodType: continue
295 if k[:2] == '__' and k[-2:] == '__': continue
296 if t is types.StringType or t is types.UnicodeType:
297 if len(v) > 80 - maxlen - 5:
298 v = `v[:80 - maxlen - 5]` + "..."
299 elif t in (types.IntType, types.NoneType):
300 v = str(v)
301 elif v in (types.ListType, types.TupleType, types.DictType):
302 v = "%s (%d elements)" % (v, len(v))
303 else:
304 v = str(t)
305 print "%*s : %s" % (maxlen, k, v)
306 return x
307