Skip to content

Commit

Permalink
Clean up travis CI config.
Browse files Browse the repository at this point in the history
Rename pyproject.toml due to a pip bug.
Fix `redssh.RedSSH().close_tunnels()` to properly reset the tunnels dict.
Fix local tunnels error handling.
Fix SOCKS server for looking up domain names.
Properly block for checking eof of a local tunnel's channel.
Add and update tests.
  • Loading branch information
Red-M committed Jan 4, 2020
1 parent a9b198f commit 1a2bd0f
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 48 deletions.
3 changes: 0 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ addons:
- curl
- wget
- python3
#~ - python3-distutils

install:
#~ - sudo -E apt update
#~ - sudo -E apt -yq --no-install-suggests --no-install-recommends install \-y make curl wget python3
- sudo -E ./tests/scripts/install_root.sh TRAVIS 3
- ./tests/scripts/install.sh TRAVIS 3

Expand Down
File renamed without changes.
8 changes: 6 additions & 2 deletions redssh/redssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,12 @@ def close_tunnels(self):
(thread,queue,server,server_port) = self.tunnels[thread_type][option_string]
self.__shutdown_thread__(thread,queue,server)
del self.tunnels
self.tunnels = {
enums.TunnelType.local.value:{},
enums.TunnelType.remote.value:{},
enums.TunnelType.dynamic.value:{},
enums.TunnelType.x11.value:{}
}

def exit(self):
'''
Expand All @@ -626,8 +632,6 @@ def exit(self):
except:
pass
self.sock.close()
# if self.__check_for_attr__('x11_channels')==True:
# del self.x11_channels
del self.channel,self.past_login,self._ssh_keepalive_thread
del self.session
del self.sock
Expand Down
25 changes: 14 additions & 11 deletions redssh/tunnelling.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import sys
import time
import select
import traceback
import struct
import socket
import threading
Expand Down Expand Up @@ -56,7 +57,7 @@ def handle_error(self,request,client_address):
print('Exception happened during processing of request from',client_address,file=sys.stderr)
if error_level==enums.TunnelErrorLevel.debug:
print('Exception happened during processing of request from',client_address,file=sys.stderr)
print(sys.last_value)
print(traceback.print_exc())
elif error_level==enums.TunnelErrorLevel.error:
super().handle_error(self,request,client_address)

Expand All @@ -79,7 +80,8 @@ def handle(self):
if address_type == 1: # IPv4
address = socket.inet_ntoa(self.request.recv(4))
elif address_type == 3: # Domain name
domain_length = ord(self.request.recv(1)[0])
# domain_length = ord(self.request.recv(1)[0])
domain_length = self.request.recv(1)[0]
address = self.request.recv(domain_length)
port = struct.unpack('!H', self.request.recv(2))[0]
try:
Expand All @@ -93,7 +95,7 @@ def handle(self):
c_port = bind_address[1]
reply = struct.pack("!BBBBIH", self.server.socks_version, 0, 0, address_type, c_addr, c_port)
except Exception as err:
self.handle_error(self.request,)
self.handle_error(self.request,self.request.getpeername())
reply = self.generate_failed_reply(address_type, 5)
self.request.sendall(reply)
if reply[1] == 0 and cmd == 1:
Expand All @@ -116,24 +118,24 @@ def generate_failed_reply(self, address_type, error_number):

def local_handler(self,terminate,request,remote_host,remote_port):
chan = self._block(self.session.direct_tcpip_ex,remote_host,remote_port,*request.getpeername())
chan_eof = False
while terminate.is_set()==False and chan_eof!=True:
# chan_eof = False
while terminate.is_set()==False and self._block(chan.eof)!=True:
(r,w,x) = select.select([request,self.sock],[],[],self._select_tun_timeout)
no_data = False
if terminate.is_set()==True:
no_data = True
break
for buf in self._read_iter(chan.read):
if request.send(buf)<=0 or chan_eof==True or terminate.is_set()==True:
if request.send(buf)<=0 or self._block(chan.eof)==True or terminate.is_set()==True:
no_data = True
break
if no_data==True:
break
if request in r and terminate.is_set()==False and chan_eof!=True:
if request in r and terminate.is_set()==False and self._block(chan.eof)!=True:
if self._block_write(chan.write,request.recv(1024))<=0 or terminate.is_set()==True:
break
chan_eof = self._block(chan.eof)
if terminate.is_set()==True or chan_eof==True:
# chan_eof = self._block(chan.eof)
if terminate.is_set()==True or self._block(chan.eof)==True:
break

if terminate.is_set()==True and chan.eof()==False:
Expand Down Expand Up @@ -185,6 +187,7 @@ def remote_handle(self,chan,host,port,terminate,error_level):
if self.sock in r:
for buf in self._read_iter(chan.read):
if request.send(buf)<=0:
request.close()
return()
while terminate.is_set()==False and chan_eof!=True:
(r,w,x) = select.select([self.sock,request],[],[],self._select_tun_timeout)
Expand All @@ -197,6 +200,7 @@ def remote_handle(self,chan,host,port,terminate,error_level):
for buf in self._read_iter(chan.read):
if request.send(buf)<=0:
no_data = True
request.close()
break
if no_data==True:
break
Expand All @@ -205,7 +209,6 @@ def remote_handle(self,chan,host,port,terminate,error_level):
break
chan_eof = self._block(chan.eof)
request.close()
if terminate.is_set()==True:
self._block(chan.close)
self._block(chan.close)


14 changes: 10 additions & 4 deletions tests/base_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@

class SSHSession(object):
def __init__(self,hostname='127.0.0.1',port=2200,class_init={},connect_args={}):
self.connect_args = connect_args
self.connected_hostname = hostname
self.connected_port = port
self.rs = redssh.RedSSH(**class_init)
self.rs.connect(hostname, port, **connect_args)
self.rs.connect(self.connected_hostname, self.connected_port, **self.connect_args)

def wait_for(self, wait_string):
if isinstance(wait_string,type('')):
Expand All @@ -42,9 +45,12 @@ def setUp(self):
self.bad_key_path = os.path.join(os.path.join(os.getcwd(),'tests'),'ssh_host_does_not_exist')
self.ssh_servers = []
self.ssh_sessions = []
self.server_hostname = '127.0.0.1'
self.server_bind_host = '127.0.0.1'
_mask = int('0600') if sys.version_info <= (2,) else 0o600
os.chmod(self.key_path, _mask)
self.remote_tunnel_hostname = 'google.com'
self.remote_tunnel_port = 80
self.remote_tunnel_bad_port = 90
self.response_text = '<title>Error 404 (Not Found)!!1</title>'
self.cur_dir = os.path.expanduser(os.path.dirname(__file__))
# self.test_dir = os.path.join(self.cur_dir,'file_tests')
Expand All @@ -61,7 +67,7 @@ def setUp(self):
def start_ssh_server(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('127.0.0.1', 0))
sock.bind((self.server_bind_host, 0))
server_port = sock.getsockname()[1]
sock.close()
server = OpenSSHServer(port=server_port,server_key=self.key_path)
Expand All @@ -86,7 +92,7 @@ def start_ssh_session(self,test_name=None,server_port=None,class_init={},connect
for arg in conn_args:
if not arg in connect_args:
connect_args.update({arg:conn_args[arg]})
sshs = SSHSession(self.server_hostname,server_port,class_init,connect_args)
sshs = SSHSession(self.server_bind_host,server_port,class_init,connect_args)
self.ssh_sessions.append(sshs)
return(sshs)

Expand Down
2 changes: 1 addition & 1 deletion tests/embedded_server/openssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def stop(self):
def __del__(self):
self.stop()
try:
os.unlink(self.sshd_config)
os.remove(self.sshd_config)
os.unlink(self.sshd_config)
except:
pass
13 changes: 7 additions & 6 deletions tests/test_basics.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,21 @@ def test_basic_set_session_options(self):

def test_basic_setenv(self):
sshs = self.start_ssh_session()
sshs.wait_for(self.prompt)
sshs.sendline('echo')
try:
sshs.rs.setenv('TEST','test') # I need to rewrite the test server at this point, this is crap.
sshs.rs.setenv('TEST','test') # There is something else at play here,
# this needs to be ran at a certain point in the session's lifetime.
except:
pass
sshs.wait_for(self.prompt)
sshs.sendline('echo')

# def test_basic_reconnect(self): # This is broken but should be working, I blame the test ssh server.
# def test_basic_reconnect(self):
# sshs = self.start_ssh_session()
# sshs.wait_for(self.prompt)
# sshs.sendline('echo')
# sshs.wait_for(self.prompt)
# sshs.rs.exit()
# sshs.conn_port = self.start_ssh_server()
# sshs._tests_connect()
# sshs.rs.connect(sshs.connected_hostname, sshs.connected_port,**sshs.connect_args)
# sshs.wait_for(self.prompt)
# sshs.sendline('echo')

Expand Down
76 changes: 55 additions & 21 deletions tests/test_tunnels.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,67 @@
from .base_test import base_test as unittest_base


def get_local(url,headers={},proxies={}):
try:
out = requests.get(url,timeout=(3,3),headers=headers,proxies=proxies).text
return(out)
except Exception as e:
print(e)
def get_local(url,headers={},proxies={},timeout=(3,3)):
# try:
out = requests.get(url,timeout=timeout,headers=headers,proxies=proxies).text
return(out)
# except Exception as e:
# print(e)


class RedSSHUnitTest(unittest_base):

def test_local_tunnel_bad_host(self):
sshs = self.start_ssh_session()
sshs.wait_for(self.prompt)
sshs.sendline('echo')
sshs.wait_for(self.prompt)
port = sshs.rs.dynamic_tunnel(0)
failed = False
try:
sshs = self.start_ssh_session()
sshs.wait_for(self.prompt)
sshs.sendline('echo')
sshs.wait_for(self.prompt)
port = sshs.rs.dynamic_tunnel(0)
# out = get_local('http://ksmjdlfngkdsfg.com',headers={'host':'localhost'},proxies={'http':'socks5h://localhost:'+str(port),'https':'socks5h://localhost:'+str(port)})
out = get_local('http://ksmjdlfngkdsfg.com',headers={'host':'localhost'},proxies={'http':'socks5h://localhost:'+str(port),'https':'socks5h://localhost:'+str(port)})
except:
pass
failed = True
assert failed==True

def test_local_tunnel_error_levels(self):
sshs = self.start_ssh_session()
sshs.wait_for(self.prompt)
sshs.sendline('echo')
sshs.wait_for(self.prompt)
for error_level in redssh.enums.TunnelErrorLevel:
port = sshs.rs.local_tunnel(0,self.remote_tunnel_hostname,self.remote_tunnel_bad_port,error_level=error_level)
try:
get_local('http://localhost:'+str(port),timeout=(0.5,0.5))
except:
pass
sshs.rs.close_tunnels()

# def test_remote_tunnel_error_levels(self):
# sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# sock.bind(('localhost', 0))
# rem_port = int(sock.getsockname()[1])
# sock.close()

# sshs = self.start_ssh_session()
# sshs.wait_for(self.prompt)
# sshs.sendline('echo')
# sshs.wait_for(self.prompt)
# for error_level in redssh.enums.TunnelErrorLevel:
# sshs.rs.remote_tunnel(rem_port,self.remote_tunnel_hostname,self.remote_tunnel_bad_port,error_level=error_level)
# try:
# get_local('http://localhost:'+str(rem_port),timeout=(0.5,0.5))
# except:
# pass
# sshs.rs.close_tunnels()

def test_local_tunnel_read_write(self):
sshs = self.start_ssh_session()
sshs.wait_for(self.prompt)
sshs.sendline('echo')
sshs.wait_for(self.prompt)
port = sshs.rs.local_tunnel(0,'google.com',80)
port = sshs.rs.local_tunnel(0,self.remote_tunnel_hostname,self.remote_tunnel_port)
out = get_local('http://localhost:'+str(port))
assert self.response_text in out

Expand All @@ -49,7 +83,7 @@ def test_dynamic_tunnel_read_write(self):
out = get_local('http://google.com',headers={'host':'localhost'},proxies={'http':'socks5://localhost:'+str(port),'https':'socks5://localhost:'+str(port)})
assert self.response_text in out

@pytest.mark.xfail
# @pytest.mark.xfail
def test_remote_tunnel_read_write(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
Expand All @@ -61,11 +95,11 @@ def test_remote_tunnel_read_write(self):
sshs.wait_for(self.prompt)
sshs.sendline('echo')
sshs.wait_for(self.prompt)
sshs.rs.remote_tunnel(rem_port,'google.com',80)
sshs.rs.remote_tunnel(rem_port,self.remote_tunnel_hostname,self.remote_tunnel_port)
out = get_local('http://localhost:'+str(rem_port))
assert self.response_text in out

@pytest.mark.xfail
# @pytest.mark.xfail
def test_local_remote_dynamic_tunnels(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
Expand All @@ -77,20 +111,20 @@ def test_local_remote_dynamic_tunnels(self):
sshs.wait_for(self.prompt)
sshs.sendline('echo')
sshs.wait_for(self.prompt)
sshs.rs.remote_tunnel(rem_port,'google.com',80)
sshs.rs.remote_tunnel(rem_port,self.remote_tunnel_hostname,self.remote_tunnel_port)
out = get_local('http://localhost:'+str(rem_port))
assert self.response_text in out

local_port = sshs.rs.local_tunnel(0,'google.com',80)
local_port = sshs.rs.local_tunnel(0,self.remote_tunnel_hostname,self.remote_tunnel_port)
out = get_local('http://localhost:'+str(local_port))
assert self.response_text in out

dyn_port = sshs.rs.dynamic_tunnel(0)
out = get_local('http://google.com',headers={'host':'localhost'},proxies={'http':'socks5://localhost:'+str(dyn_port),'https':'socks5://localhost:'+str(dyn_port)})
assert self.response_text in out

sshs.rs.shutdown_tunnel(redssh.enums.TunnelType.local,local_port,'google.com',80)
sshs.rs.shutdown_tunnel(redssh.enums.TunnelType.remote,rem_port,'google.com',80)
sshs.rs.shutdown_tunnel(redssh.enums.TunnelType.local,local_port,self.remote_tunnel_hostname,self.remote_tunnel_port)
sshs.rs.shutdown_tunnel(redssh.enums.TunnelType.remote,rem_port,self.remote_tunnel_hostname,self.remote_tunnel_port)
sshs.rs.shutdown_tunnel(redssh.enums.TunnelType.dynamic,dyn_port)
sshs.rs.shutdown_tunnel(redssh.enums.TunnelType.local,dyn_port)

Expand Down

0 comments on commit 1a2bd0f

Please sign in to comment.