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