Package x2go :: Module sshproxy
[frames] | no frames]

Source Code for Module x2go.sshproxy

  1  # -*- coding: utf-8 -*- 
  2   
  3  # Copyright (C) 2010-2011 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
  4  # 
  5  # Python X2go is free software; you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation; either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Python X2go is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with this program; if not, write to the 
 17  # Free Software Foundation, Inc., 
 18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
 19   
 20  """\ 
 21  L{X2goSSHProxy} class - providing a forwarding tunnel for connecting to servers behind firewalls. 
 22   
 23  """ 
 24  __NAME__ = 'x2gosshproxy-pylib' 
 25   
 26  # modules 
 27  import gevent 
 28  import os 
 29  import copy 
 30  import paramiko 
 31  import threading 
 32  import socket 
 33   
 34  import string 
 35  import random 
 36   
 37  # Python X2go modules 
 38  import x2go.forward as forward 
 39  import x2go.checkhosts as checkhosts 
 40  import x2go.log as log 
 41  import x2go.utils as utils 
 42  from x2go.x2go_exceptions import * 
 43   
 44  from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER 
 45  from x2go.defaults import LOCAL_HOME as _LOCAL_HOME 
 46  from x2go.defaults import X2GO_SSH_ROOTDIR as _X2GO_SSH_ROOTDIR 
 47   
 48  from monkey_patch_paramiko import monkey_patch_paramiko 
 49  monkey_patch_paramiko() 
 50   
51 -class X2goSSHProxy(paramiko.SSHClient, threading.Thread):
52 """\ 53 X2goSSHProxy can be used to proxy X2go connections through a firewall via SSH. 54 55 """ 56 fw_tunnel = None 57
58 - def __init__(self, hostname=None, port=22, username=None, password=None, key_filename=None, 59 local_host='localhost', local_port=22022, remote_host='localhost', remote_port=22, 60 known_hosts=None, add_to_known_hosts=False, pkey=None, 61 sshproxy_host=None, sshproxy_port=22, sshproxy_user=None, 62 sshproxy_password=None, sshproxy_key_filename=None, sshproxy_pkey=None, 63 sshproxy_tunnel=None, 64 ssh_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SSH_ROOTDIR), 65 session_instance=None, 66 logger=None, loglevel=log.loglevel_DEFAULT, ):
67 """\ 68 Initialize an X2goSSHProxy instance. Use an instance of this class to tunnel X2go requests through 69 a proxying SSH server (i.e. to subLANs that are separated by firewalls or to private IP subLANs that 70 are NATted behind routers). 71 72 @param username: login user name to be used on the SSH proxy host 73 @type username: C{str} 74 @param password: user's password on the SSH proxy host 75 @type password: C{str} 76 @param key_filename: name of a SSH private key file 77 @type key_filename: C{str} 78 @param pkey: a private DSA/RSA key object (as provided by Paramiko/SSH) 79 @type pkey: C{RSA/DSA key instance} 80 @param local_host: bind SSH tunnel to the C{local_host} IP socket address (default: localhost) 81 @type local_host: C{str} 82 @param local_port: IP socket port to bind the SSH tunnel to (default; 22022) 83 @type local_port: C{int} 84 @param remote_host: remote endpoint of the SSH proxying/forwarding tunnel (default: localhost) 85 @type remote_host: C{str} 86 @param remote_port: remote endpoint's IP socket port for listening SSH daemon (default: 22) 87 @type remote_port: C{int} 88 @param known_hosts: full path to a custom C{known_hosts} file 89 @type known_hosts: C{str} 90 @param add_to_known_hosts: automatically add host keys of unknown SSH hosts to the C{known_hosts} file 91 @type add_to_known_hosts: C{bool} 92 @param hostname: alias for C{local_host} 93 @type hostname: C{str} 94 @param port: alias for C{local_port} 95 @type port: C{int} 96 @param sshproxy_host: alias for C{remote_host} 97 @type sshproxy_host: C{str} 98 @param sshproxy_port: alias for C{remote_port} 99 @type sshproxy_port: C{int} 100 @param sshproxy_user: alias for C{username} 101 @type sshproxy_user: C{str} 102 @param sshproxy_password: alias for C{password} 103 @type sshproxy_password: C{str} 104 @param sshproxy_key_filename: alias for C{key_filename} 105 @type sshproxy_key_filename: C{str} 106 @param sshproxy_pkey: alias for C{pkey} 107 @type sshproxy_pkey: C{RSA/DSA key instance} (Paramiko) 108 109 @param sshproxy_tunnel: a string of the format <local_host>:<local_port>:<remote_host>:<remote_port> 110 which will override---if used---the options: C{local_host}, C{local_port}, C{remote_host} and C{remote_port} 111 @type sshproxy_tunnel: C{str} 112 113 @param ssh_rootdir: local user's SSH base directory (default: ~/.ssh) 114 @type ssh_rootdir: C{str} 115 @param session_instance: the L{X2goSession} instance that builds up this SSH proxying tunnel 116 @type session_instance: L{X2goSession} instance 117 @param logger: you can pass an L{X2goLogger} object to the 118 L{X2goSSHProxy} constructor 119 @type logger: L{X2goLogger} instance 120 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be 121 constructed with the given loglevel 122 @type loglevel: int 123 124 """ 125 if logger is None: 126 self.logger = log.X2goLogger(loglevel=loglevel) 127 else: 128 self.logger = copy.deepcopy(logger) 129 self.logger.tag = __NAME__ 130 131 self.hostname, self.port, self.username = hostname, port, username 132 133 # translate between X2goSession options and paramiko.SSHCLient.connect() options 134 if sshproxy_host: 135 if sshproxy_host.find(':'): 136 self.hostname = sshproxy_host.split(':')[0] 137 try: self.port = int(sshproxy_host.split(':')[1]) 138 except IndexError: pass 139 else: 140 self.hostname = sshproxy_host 141 142 if sshproxy_user: self.username = sshproxy_user 143 if sshproxy_password: password = sshproxy_password 144 if sshproxy_key_filename: key_filename = sshproxy_key_filename 145 if sshproxy_pkey: pkey = sshproxy_pkey 146 if sshproxy_tunnel: 147 self.local_host, self.local_port, self.remote_host, self.remote_port = sshproxy_tunnel.split(':') 148 self.local_port = int(self.local_port) 149 self.remote_port = int(self.remote_port) 150 else: 151 self.local_host = local_host 152 self.local_port = int(local_port) 153 self.remote_host = remote_host 154 self.remote_port = int(remote_port) 155 156 # allow more trailing whitespace tolerance in hostnames 157 self.hostname = self.hostname.strip() 158 self.local_host = self.local_host.strip() 159 self.remote_host = self.remote_host.strip() 160 161 # enforce IPv4 for localhost addresses!!! 162 _hostname = self.hostname 163 if _hostname in ('localhost', 'localhost.localdomain'): 164 _hostname = '127.0.0.1' 165 if self.local_host in ('localhost', 'localhost.localdomain'): 166 self.local_host = '127.0.0.1' 167 if self.remote_host in ('localhost', 'localhost.localdomain'): 168 self.remote_host = '127.0.0.1' 169 170 if username is None: 171 username = _CURRENT_LOCAL_USER 172 173 self._keepalive = True 174 175 self.ssh_rootdir = ssh_rootdir 176 paramiko.SSHClient.__init__(self) 177 178 self.known_hosts = known_hosts 179 if self.known_hosts: 180 utils.touch_file(self.known_hosts) 181 self.load_host_keys(self.known_hosts) 182 183 if not add_to_known_hosts and session_instance: 184 self.set_missing_host_key_policy(checkhosts.X2goInteractiveAddPolicy(caller=self, session_instance=session_instance)) 185 186 if add_to_known_hosts: 187 self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 188 189 try: 190 if (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: 191 try: 192 self.connect(_hostname, port=self.port, 193 username=self.username, 194 key_filename=key_filename, 195 pkey=pkey, 196 look_for_keys=False, 197 allow_agent=False, 198 ) 199 except AuthenticationException, e: 200 self.close() 201 raise X2goSSHProxyAuthenticationException('pubkey auth mechanisms both failed') 202 except: 203 self.close() 204 raise 205 206 # if there is not private key, we will use the given password, if any 207 else: 208 # create a random password if password is empty to trigger host key validity check 209 if not password: 210 password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)]) 211 try: 212 self.connect(_hostname, port=self.port, 213 username=self.username, 214 password=password, 215 look_for_keys=False, 216 allow_agent=False, 217 ) 218 except AuthenticationException: 219 self.close() 220 raise X2goSSHProxyAuthenticationException('interactive auth mechanisms failed') 221 except: 222 self.close() 223 raise 224 225 except paramiko.SSHException, e: 226 self.close() 227 raise X2goSSHProxyException(str(e)) 228 except: 229 self.close() 230 raise 231 232 self.set_missing_host_key_policy(paramiko.RejectPolicy()) 233 threading.Thread.__init__(self) 234 self.daemon = True
235
236 - def check_host(self):
237 """\ 238 Wraps around a Paramiko/SSH host key check. 239 240 """ 241 242 # hostname rewrite for localhost, force to IPv4 243 _hostname = self.hostname 244 245 # force into IPv4 for localhost connections 246 if _hostname in ('localhost', 'localhost.localdomain'): 247 _hostname = '127.0.0.1' 248 249 _valid = False 250 (_valid, _hostname, _port, _fingerprint, _fingerprint_type) = checkhosts.check_ssh_host_key(self, _hostname, port=self.port) 251 if not _valid and self.session_instance: 252 _valid = self.session_instance.HOOK_check_host_dialog(_hostname, _port, fingerprint=_fingerprint, fingerprint_type=_fingerprint_type) 253 return _valid
254
255 - def run(self):
256 """\ 257 Start the SSH proxying tunnel... 258 259 """ 260 if self.get_transport() is not None and self.get_transport().is_authenticated(): 261 self.local_port = utils.detect_unused_port(bind_address=self.local_host, preferred_port=self.local_port) 262 self.fw_tunnel = forward.start_forward_tunnel(local_host=self.local_host, 263 local_port=self.local_port, 264 remote_host=self.remote_host, 265 remote_port=self.remote_port, 266 ssh_transport=self.get_transport(), 267 logger=self.logger, ) 268 self.logger('SSH proxy tunnel via [%s]:%s has been set up' % (self.hostname, self.port), loglevel=log.loglevel_NOTICE) 269 self.logger('SSH proxy tunnel startpoint is [%s]:%s, endpoint is [%s]:%s' % (self.local_host, self.local_port, self.remote_host, self.remote_port), loglevel=log.loglevel_NOTICE) 270 271 while self._keepalive: 272 gevent.sleep(.1) 273 274 else: 275 raise X2goSSHProxyException('SSH proxy connection could not retrieve an SSH transport')
276
277 - def get_local_proxy_port(self):
278 """\ 279 Retrieve the local IP socket port this SSH proxying tunnel is (about to) bind/bound to. 280 281 @return: local IP socket port 282 @rtype: C{int} 283 """ 284 return self.local_port
285
286 - def stop_thread(self):
287 """\ 288 Tear down the SSH proxying tunnel. 289 290 """ 291 if self.fw_tunnel is not None and self.fw_tunnel.is_active: 292 self.logger('taking down SSH proxy tunnel via [%s]:%s' % (self.hostname, self.port), loglevel=log.loglevel_NOTICE) 293 try: forward.stop_forward_tunnel(self.fw_tunnel) 294 except: pass 295 self.fw_tunnel = None 296 self._keepalive = False 297 if self.get_transport() is not None: 298 self.logger('closing SSH proxy connection to [%s]:%s' % (self.hostname, self.port), loglevel=log.loglevel_NOTICE) 299 self.close() 300 self.password = self.sshproxy_password = None
301
302 - def __del__(self):
303 """\ 304 Class desctructor. 305 306 """ 307 self.stop_thread()
308