]> code.communitydata.science - nu-vpn-proxy.git/blob - test-globalprotect-login.py
clarify ambiguities in destination, slightly better error messages
[nu-vpn-proxy.git] / test-globalprotect-login.py
1 #!/usr/bin/python3
2
3 from __future__ import print_function
4 from sys import stderr, version_info
5 if (version_info >= (3, 0)):
6     from urllib.parse import urlparse, urlencode
7     raw_input = input
8 else:
9     from urlparse import urlparse
10     from urllib import urlencode
11 import requests
12 import argparse
13 import getpass
14 import os
15 import xml.etree.ElementTree as ET
16 import posixpath
17 from binascii import a2b_base64
18 from tempfile import NamedTemporaryFile
19 from shlex import quote
20
21 p = argparse.ArgumentParser()
22 p.add_argument('-v','--verbose', default=0, action='count')
23 p.add_argument('endpoint', help='GlobalProtect server; can append /ssl-vpn/login.esp (default) or /global-protect/getconfig.esp')
24 p.add_argument('extra', nargs='*', help='Extra field to pass to include in the login query string (e.g. "portal-userauthcookie=deadbeef01234567")')
25 g = p.add_argument_group('Login credentials')
26 g.add_argument('-u','--user', help='Username (will prompt if unspecified)')
27 g.add_argument('-p','--password', help='Password (will prompt if unspecified)')
28 g.add_argument('-c','--cert', help='PEM file containing client certificate (and optionally private key)')
29 g.add_argument('--computer', default=os.uname()[1], help="Computer name (default is `hostname`)")
30 g.add_argument('--key', help='PEM file containing client private key (if not included in same file as certificate)')
31 p.add_argument('-b','--browse', action='store_true', help='Automatically spawn browser for SAML')
32 p.add_argument('--no-verify', dest='verify', action='store_false', default=True, help='Ignore invalid server certificate')
33 args = p.parse_args()
34
35 extra = dict(x.split('=', 1) for x in args.extra)
36 endpoint = urlparse(('https://' if '//' not in args.endpoint else '') + args.endpoint, 'https:')
37 if not endpoint.path:
38     print("Endpoint path unspecified: defaulting to /ssl-vpn/login.esp", file=stderr)
39     endpoint = endpoint._replace(path = '/ssl-vpn/login.esp')
40 prelogin = (posixpath.split(endpoint.path)[-1] == 'prelogin.esp')
41
42 if args.cert and args.key:
43     cert = (args.cert, args.key)
44 elif args.cert:
45     cert = (args.cert, None)
46 elif args.key:
47     p.error('--key specified without --cert')
48 else:
49     cert = None
50
51 s = requests.Session()
52 s.headers['User-Agent'] = 'PAN GlobalProtect'
53 s.cert = cert
54
55 if prelogin:
56     data = extra
57 else:
58     # same request params work for /global-protect/getconfig.esp as for /ssl-vpn/login.esp
59     if args.user == None:
60         args.user = raw_input('Username: ')
61     if args.password == None:
62         args.password = getpass.getpass('Password: ')
63     data=dict(user=args.user, passwd=args.password,
64               # required
65               jnlpReady='jnlpReady', ok='Login', direct='yes',
66               # optional but might affect behavior
67               clientVer=4100, server=endpoint.netloc, prot='https:',
68               computer=args.computer,
69               **extra)
70 res = s.post(endpoint.geturl(), verify=args.verify, data=data)
71
72 if args.verbose:
73     print("Request body:\n", res.request.body, file=stderr)
74
75 res.raise_for_status()
76
77 # build openconnect "cookie" if the result is a <jnlp>
78
79 try:
80     xml = ET.fromstring(res.text)
81 except Exception:
82     xml = None
83
84 if xml is not None and xml.tag == 'jnlp':
85     arguments = [(t.text or '') for t in xml.iter('argument')]
86     arguments += [''] * (16-len(arguments))
87     cookie = urlencode({'authcookie': arguments[1], 'portal': arguments[3], 'user': arguments[4], 'domain': arguments[7],
88                         'computer': args.computer, 'preferred-ip': arguments[15]})
89     if cert:
90         cert_and_key = ' \\\n        ' + ' '.join('%s "%s"' % (opt, quote(fn)) for opt, fn in zip(('-c','-k'), cert) if fn)
91     else:
92         cert_and_key = ''
93
94     print('''
95
96 Extracted connection cookie from <jnlp>. Use this to connect:
97
98     openconnect --protocol=gp --usergroup=gateway %s \\
99         --cookie %s%s
100 ''' % (quote(endpoint.netloc), quote(cookie), cert_and_key), file=stderr)
101
102 # do SAML request if the result is <prelogin-response><saml...>
103
104 elif xml is not None and xml.tag == 'prelogin-response' and None not in (xml.find('saml-auth-method'), xml.find('saml-request')):
105     import webbrowser
106     sam = xml.find('saml-auth-method').text
107     sr = xml.find('saml-request').text
108     if sam == 'POST':
109         with NamedTemporaryFile(delete=False, suffix='.html') as tf:
110             tf.write(a2b_base64(sr))
111         if args.browse:
112             print("Got SAML POST, browsing to %s" % tf.name)
113             webbrowser.open('file://' + tf.name)
114         else:
115             print("Got SAML POST, saved to:\n\t%s" % tf.name)
116     elif sam == 'REDIRECT':
117         if args.browse:
118             print("Got SAML REDIRECT, browsing to %s" % sr)
119             webbrowser.open(sr)
120         else:
121             print("Got SAML REDIRECT to:\n\t%s" % sr)
122
123 # Just print the result
124
125 else:
126     if args.verbose:
127         print(res.headers, file=stderr)
128     print(res.text)

Community Data Science Collective || Want to submit a patch?