-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathopenconnect-pulse-launcher.py
executable file
·166 lines (142 loc) · 6.52 KB
/
openconnect-pulse-launcher.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python
import getopt
import inspect
import logging
import os
import psutil
import signal
import shutil
import subprocess
import sys
import time
import urllib
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.support.ui import WebDriverWait
from xdg_base_dirs import xdg_config_home
script_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
class OpenconnectPulseLauncher:
def signal_handler(self, _sig, _frame):
subprocess.run(['sudo', 'route', 'del', 'default', 'gw', self.vpn_gateway_ip])
while 'openconnect' in (i.name() for i in psutil.process_iter()):
subprocess.run(['sudo', 'pkill', '-SIGINT', 'openconnect'])
ps = subprocess.Popen(
['getent', 'hosts', self.hostname],
stdout=subprocess.PIPE,
)
output = subprocess.check_output(
['awk', '{print $1}'],
stdin=ps.stdout
)
ps.wait()
vpn_ip = output.decode().rstrip()
# This is normally deleted when the VPN is killed, but sometimes is left behind as there are two entries
subprocess.run(['sudo', 'route', 'del', vpn_ip])
sys.exit(0)
def __init__(self):
self.is_root = os.geteuid() == 0
self.chrome_profile_dir = os.path.join(xdg_config_home(), 'chromedriver', 'pulsevpn')
if not os.path.exists(self.chrome_profile_dir):
os.makedirs(self.chrome_profile_dir)
self.vpn_gateway_ip = None
signal.signal(signal.SIGINT, self.signal_handler)
def is_dsid_valid(self, dsid):
# Expiry is set to Session
return dsid is not None and 'value' in dsid
def connect(self, vpn_url, chromedriver_path, chromium_path, debug=False, script=None):
self.hostname = urllib.parse.urlparse(vpn_url).hostname
dsid = None
returncode = 0
while True:
if self.is_dsid_valid(dsid) and returncode != 2:
logging.info('Launching openconnect.')
## Run in background
## openconnect is built to already point to a pre-packaged vpnc-script, so no need to specify
# p = subprocess.run(['sudo', 'openconnect', '-b', '-C', dsid['value'], '--protocol=pulse', vpn_url, '-s', '${pkgs.unstable.vpnc-scripts}/bin/vpnc-script'])
## --no-dtls addresses VPN dying with "ESP detected dead peer", and also "ESP receive error: Message too long" error
## See: https://gitlab.com/openconnect/openconnect/-/issues/647
## Downside: lots of console spam
## Also, seems to die often with this error:
## Short packet received (2 bytes)
## Unrecoverable I/O error; exiting.
# p = subprocess.run(['sudo', 'openconnect', '--no-dtls', '-b', '-C', dsid['value'], '--protocol=pulse', vpn_url])
command_line = ['sudo', 'openconnect']
if debug:
command_line.extend(['-vvvv'])
if script is not None:
command_line.extend(['-s', script])
command_line.extend(['-b', '-C', dsid['value'], '--protocol=pulse', vpn_url])
if debug:
print('Command line:')
print(' {}'.format(' '.join(command_line)))
print('')
p = subprocess.run(command_line)
returncode = p.returncode
## Get tun0 IP and set as default GW (vpnc-script doesn't do this for some reason)
## Probably due to something like this:
## https://github.com/dlenski/openconnect/issues/125#issuecomment-426032102
## There is an error on the command line when openconnect is run:
## Error: argument "via" is wrong: use nexthop syntax to specify multiple via
## sleep to make sure tun0 is available
time.sleep(3)
ps = subprocess.Popen(
['ifconfig', 'tun0'],
stdout=subprocess.PIPE
)
output = subprocess.check_output(
['awk', '-F', ' *|:', '/inet /{print $3}'],
stdin=ps.stdout
)
ps.wait()
self.vpn_gateway_ip = output.decode().rstrip()
print('VPN IP: '+self.vpn_gateway_ip)
p = subprocess.run(['sudo', 'route', 'add', 'default', 'gw', self.vpn_gateway_ip])
# Wait for ctrl-c
signal.pause()
else:
returncode = 0
service = Service(executable_path=chromedriver_path)
options = webdriver.ChromeOptions()
options.binary_location = chromium_path
options.add_argument('--window-size=800,900')
# options.add_argument('--remote-debugging-pipe')
# options.add_argument('--remote-debugging-port=9222')
options.add_argument('user-data-dir=' + self.chrome_profile_dir)
logging.info('Starting browser.')
driver = webdriver.Chrome(service=service, options=options)
driver.get(vpn_url)
dsid = WebDriverWait(driver, float('inf')).until(lambda driver: driver.get_cookie('DSID'))
driver.quit()
logging.info('DSID cookie: %s', dsid)
def main(argv):
script_name = os.path.basename(__file__)
chromedriver_path = shutil.which('chromedriver')
chromium_path = shutil.which('chromium') or shutil.which('google-chrome')
help_message = '{} <vpn_url>'.format(script_name)
try:
opts, args = getopt.getopt(argv, 'hds:c:', ['help', 'debug', 'script=', 'chromedriver-path'])
except getopt.GetoptError:
print(help_message)
sys.exit(2)
if len(args) != 1:
print(help_message)
sys.exit(2)
debug = False
script = None
for o, a in opts:
if o in ('-h', '--help'):
print(help_message)
sys.exit()
elif o in ('-d', '--debug'):
debug = True
elif o in ('-s', '--script'):
if len(a):
script = a
elif o in ('-c', '--chromedriver-path'):
if len(a):
chromedriver_path = a
vpn_url = args[0]
launcher = OpenconnectPulseLauncher()
launcher.connect(vpn_url, chromedriver_path=chromedriver_path, chromium_path=chromium_path, debug=debug, script=script)
if __name__ == "__main__":
main(sys.argv[1:])