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