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