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