1#!/usr/bin/env python
2
3'''This starts an SSH tunnel to a given host. If the SSH process ever dies then
4this script will detect that and restart it. I use this under Cygwin to keep
5open encrypted tunnels to port 25 (SMTP), port 143 (IMAP4), and port 110
6(POP3). I set my mail client to talk to localhost and I keep this script
7running in the background.
8
9Note that this is a rather stupid script at the moment because it just looks to
10see if any ssh process is running. It should really make sure that our specific
11ssh process is running. The problem is that ssh is missing a very useful
12feature. It has no way to report the process id of the background daemon that
13it creates with the -f command. This would be a really useful script if I could
14figure a way around this problem.
15
16PEXPECT LICENSE
17
18    This license is approved by the OSI and FSF as GPL-compatible.
19        http://opensource.org/licenses/isc-license.txt
20
21    Copyright (c) 2012, Noah Spurrier <noah@noah.org>
22    PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
23    PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
24    COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
25    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
26    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
27    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
28    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
29    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
30    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
31    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
32
33'''
34
35from __future__ import print_function
36
37from __future__ import absolute_import
38
39import pexpect
40import getpass
41import time
42
43
44try:
45    raw_input
46except NameError:
47    raw_input = input
48
49
50# SMTP:25 IMAP4:143 POP3:110
51tunnel_command = 'ssh -C -N -f -L 25:127.0.0.1:25 -L 143:127.0.0.1:143 -L 110:127.0.0.1:110 %(user)@%(host)'
52host = raw_input('Hostname: ')
53user = raw_input('Username: ')
54X = getpass.getpass('Password: ')
55
56def get_process_info ():
57
58    # This seems to work on both Linux and BSD, but should otherwise be considered highly UNportable.
59
60    ps = pexpect.run ('ps ax -O ppid')
61    pass
62
63def start_tunnel ():
64
65    try:
66        ssh_tunnel = pexpect.spawn (tunnel_command % globals())
67        ssh_tunnel.expect ('password:')
68        time.sleep (0.1)
69        ssh_tunnel.sendline (X)
70        time.sleep (60) # Cygwin is slow to update process status.
71        ssh_tunnel.expect (pexpect.EOF)
72
73    except Exception as e:
74        print(str(e))
75
76def main ():
77
78    while True:
79        ps = pexpect.spawn ('ps')
80        time.sleep (1)
81        index = ps.expect (['/usr/bin/ssh', pexpect.EOF, pexpect.TIMEOUT])
82        if index == 2:
83            print('TIMEOUT in ps command...')
84            print(str(ps))
85            time.sleep (13)
86        if index == 1:
87            print(time.asctime(), end=' ')
88            print('restarting tunnel')
89            start_tunnel ()
90            time.sleep (11)
91            print('tunnel OK')
92        else:
93            # print 'tunnel OK'
94            time.sleep (7)
95
96if __name__ == '__main__':
97
98    main ()
99
100# This was for older SSH versions that didn't have -f option
101#tunnel_command = 'ssh -C -n -L 25:%(host)s:25 -L 110:%(host)s:110 %(user)s@%(host)s -f nothing.sh'
102#nothing_script = '''#!/bin/sh
103#while true; do sleep 53; done
104#'''
105
106