@@ -0,0 +1,340 @@ | |||
GNU GENERAL PUBLIC LICENSE | |||
Version 2, June 1991 | |||
Copyright (C) 1989, 1991 Free Software Foundation, Inc. | |||
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |||
Everyone is permitted to copy and distribute verbatim copies | |||
of this license document, but changing it is not allowed. | |||
Preamble | |||
The licenses for most software are designed to take away your | |||
freedom to share and change it. By contrast, the GNU General Public | |||
License is intended to guarantee your freedom to share and change free | |||
software--to make sure the software is free for all its users. This | |||
General Public License applies to most of the Free Software | |||
Foundation's software and to any other program whose authors commit to | |||
using it. (Some other Free Software Foundation software is covered by | |||
the GNU Library General Public License instead.) You can apply it to | |||
your programs, too. | |||
When we speak of free software, we are referring to freedom, not | |||
price. Our General Public Licenses are designed to make sure that you | |||
have the freedom to distribute copies of free software (and charge for | |||
this service if you wish), that you receive source code or can get it | |||
if you want it, that you can change the software or use pieces of it | |||
in new free programs; and that you know you can do these things. | |||
To protect your rights, we need to make restrictions that forbid | |||
anyone to deny you these rights or to ask you to surrender the rights. | |||
These restrictions translate to certain responsibilities for you if you | |||
distribute copies of the software, or if you modify it. | |||
For example, if you distribute copies of such a program, whether | |||
gratis or for a fee, you must give the recipients all the rights that | |||
you have. You must make sure that they, too, receive or can get the | |||
source code. And you must show them these terms so they know their | |||
rights. | |||
We protect your rights with two steps: (1) copyright the software, and | |||
(2) offer you this license which gives you legal permission to copy, | |||
distribute and/or modify the software. | |||
Also, for each author's protection and ours, we want to make certain | |||
that everyone understands that there is no warranty for this free | |||
software. If the software is modified by someone else and passed on, we | |||
want its recipients to know that what they have is not the original, so | |||
that any problems introduced by others will not reflect on the original | |||
authors' reputations. | |||
Finally, any free program is threatened constantly by software | |||
patents. We wish to avoid the danger that redistributors of a free | |||
program will individually obtain patent licenses, in effect making the | |||
program proprietary. To prevent this, we have made it clear that any | |||
patent must be licensed for everyone's free use or not licensed at all. | |||
The precise terms and conditions for copying, distribution and | |||
modification follow. | |||
GNU GENERAL PUBLIC LICENSE | |||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |||
0. This License applies to any program or other work which contains | |||
a notice placed by the copyright holder saying it may be distributed | |||
under the terms of this General Public License. The "Program", below, | |||
refers to any such program or work, and a "work based on the Program" | |||
means either the Program or any derivative work under copyright law: | |||
that is to say, a work containing the Program or a portion of it, | |||
either verbatim or with modifications and/or translated into another | |||
language. (Hereinafter, translation is included without limitation in | |||
the term "modification".) Each licensee is addressed as "you". | |||
Activities other than copying, distribution and modification are not | |||
covered by this License; they are outside its scope. The act of | |||
running the Program is not restricted, and the output from the Program | |||
is covered only if its contents constitute a work based on the | |||
Program (independent of having been made by running the Program). | |||
Whether that is true depends on what the Program does. | |||
1. You may copy and distribute verbatim copies of the Program's | |||
source code as you receive it, in any medium, provided that you | |||
conspicuously and appropriately publish on each copy an appropriate | |||
copyright notice and disclaimer of warranty; keep intact all the | |||
notices that refer to this License and to the absence of any warranty; | |||
and give any other recipients of the Program a copy of this License | |||
along with the Program. | |||
You may charge a fee for the physical act of transferring a copy, and | |||
you may at your option offer warranty protection in exchange for a fee. | |||
2. You may modify your copy or copies of the Program or any portion | |||
of it, thus forming a work based on the Program, and copy and | |||
distribute such modifications or work under the terms of Section 1 | |||
above, provided that you also meet all of these conditions: | |||
a) You must cause the modified files to carry prominent notices | |||
stating that you changed the files and the date of any change. | |||
b) You must cause any work that you distribute or publish, that in | |||
whole or in part contains or is derived from the Program or any | |||
part thereof, to be licensed as a whole at no charge to all third | |||
parties under the terms of this License. | |||
c) If the modified program normally reads commands interactively | |||
when run, you must cause it, when started running for such | |||
interactive use in the most ordinary way, to print or display an | |||
announcement including an appropriate copyright notice and a | |||
notice that there is no warranty (or else, saying that you provide | |||
a warranty) and that users may redistribute the program under | |||
these conditions, and telling the user how to view a copy of this | |||
License. (Exception: if the Program itself is interactive but | |||
does not normally print such an announcement, your work based on | |||
the Program is not required to print an announcement.) | |||
These requirements apply to the modified work as a whole. If | |||
identifiable sections of that work are not derived from the Program, | |||
and can be reasonably considered independent and separate works in | |||
themselves, then this License, and its terms, do not apply to those | |||
sections when you distribute them as separate works. But when you | |||
distribute the same sections as part of a whole which is a work based | |||
on the Program, the distribution of the whole must be on the terms of | |||
this License, whose permissions for other licensees extend to the | |||
entire whole, and thus to each and every part regardless of who wrote it. | |||
Thus, it is not the intent of this section to claim rights or contest | |||
your rights to work written entirely by you; rather, the intent is to | |||
exercise the right to control the distribution of derivative or | |||
collective works based on the Program. | |||
In addition, mere aggregation of another work not based on the Program | |||
with the Program (or with a work based on the Program) on a volume of | |||
a storage or distribution medium does not bring the other work under | |||
the scope of this License. | |||
3. You may copy and distribute the Program (or a work based on it, | |||
under Section 2) in object code or executable form under the terms of | |||
Sections 1 and 2 above provided that you also do one of the following: | |||
a) Accompany it with the complete corresponding machine-readable | |||
source code, which must be distributed under the terms of Sections | |||
1 and 2 above on a medium customarily used for software interchange; or, | |||
b) Accompany it with a written offer, valid for at least three | |||
years, to give any third party, for a charge no more than your | |||
cost of physically performing source distribution, a complete | |||
machine-readable copy of the corresponding source code, to be | |||
distributed under the terms of Sections 1 and 2 above on a medium | |||
customarily used for software interchange; or, | |||
c) Accompany it with the information you received as to the offer | |||
to distribute corresponding source code. (This alternative is | |||
allowed only for noncommercial distribution and only if you | |||
received the program in object code or executable form with such | |||
an offer, in accord with Subsection b above.) | |||
The source code for a work means the preferred form of the work for | |||
making modifications to it. For an executable work, complete source | |||
code means all the source code for all modules it contains, plus any | |||
associated interface definition files, plus the scripts used to | |||
control compilation and installation of the executable. However, as a | |||
special exception, the source code distributed need not include | |||
anything that is normally distributed (in either source or binary | |||
form) with the major components (compiler, kernel, and so on) of the | |||
operating system on which the executable runs, unless that component | |||
itself accompanies the executable. | |||
If distribution of executable or object code is made by offering | |||
access to copy from a designated place, then offering equivalent | |||
access to copy the source code from the same place counts as | |||
distribution of the source code, even though third parties are not | |||
compelled to copy the source along with the object code. | |||
4. You may not copy, modify, sublicense, or distribute the Program | |||
except as expressly provided under this License. Any attempt | |||
otherwise to copy, modify, sublicense or distribute the Program is | |||
void, and will automatically terminate your rights under this License. | |||
However, parties who have received copies, or rights, from you under | |||
this License will not have their licenses terminated so long as such | |||
parties remain in full compliance. | |||
5. You are not required to accept this License, since you have not | |||
signed it. However, nothing else grants you permission to modify or | |||
distribute the Program or its derivative works. These actions are | |||
prohibited by law if you do not accept this License. Therefore, by | |||
modifying or distributing the Program (or any work based on the | |||
Program), you indicate your acceptance of this License to do so, and | |||
all its terms and conditions for copying, distributing or modifying | |||
the Program or works based on it. | |||
6. Each time you redistribute the Program (or any work based on the | |||
Program), the recipient automatically receives a license from the | |||
original licensor to copy, distribute or modify the Program subject to | |||
these terms and conditions. You may not impose any further | |||
restrictions on the recipients' exercise of the rights granted herein. | |||
You are not responsible for enforcing compliance by third parties to | |||
this License. | |||
7. If, as a consequence of a court judgment or allegation of patent | |||
infringement or for any other reason (not limited to patent issues), | |||
conditions are imposed on you (whether by court order, agreement or | |||
otherwise) that contradict the conditions of this License, they do not | |||
excuse you from the conditions of this License. If you cannot | |||
distribute so as to satisfy simultaneously your obligations under this | |||
License and any other pertinent obligations, then as a consequence you | |||
may not distribute the Program at all. For example, if a patent | |||
license would not permit royalty-free redistribution of the Program by | |||
all those who receive copies directly or indirectly through you, then | |||
the only way you could satisfy both it and this License would be to | |||
refrain entirely from distribution of the Program. | |||
If any portion of this section is held invalid or unenforceable under | |||
any particular circumstance, the balance of the section is intended to | |||
apply and the section as a whole is intended to apply in other | |||
circumstances. | |||
It is not the purpose of this section to induce you to infringe any | |||
patents or other property right claims or to contest validity of any | |||
such claims; this section has the sole purpose of protecting the | |||
integrity of the free software distribution system, which is | |||
implemented by public license practices. Many people have made | |||
generous contributions to the wide range of software distributed | |||
through that system in reliance on consistent application of that | |||
system; it is up to the author/donor to decide if he or she is willing | |||
to distribute software through any other system and a licensee cannot | |||
impose that choice. | |||
This section is intended to make thoroughly clear what is believed to | |||
be a consequence of the rest of this License. | |||
8. If the distribution and/or use of the Program is restricted in | |||
certain countries either by patents or by copyrighted interfaces, the | |||
original copyright holder who places the Program under this License | |||
may add an explicit geographical distribution limitation excluding | |||
those countries, so that distribution is permitted only in or among | |||
countries not thus excluded. In such case, this License incorporates | |||
the limitation as if written in the body of this License. | |||
9. The Free Software Foundation may publish revised and/or new versions | |||
of the General Public License from time to time. Such new versions will | |||
be similar in spirit to the present version, but may differ in detail to | |||
address new problems or concerns. | |||
Each version is given a distinguishing version number. If the Program | |||
specifies a version number of this License which applies to it and "any | |||
later version", you have the option of following the terms and conditions | |||
either of that version or of any later version published by the Free | |||
Software Foundation. If the Program does not specify a version number of | |||
this License, you may choose any version ever published by the Free Software | |||
Foundation. | |||
10. If you wish to incorporate parts of the Program into other free | |||
programs whose distribution conditions are different, write to the author | |||
to ask for permission. For software which is copyrighted by the Free | |||
Software Foundation, write to the Free Software Foundation; we sometimes | |||
make exceptions for this. Our decision will be guided by the two goals | |||
of preserving the free status of all derivatives of our free software and | |||
of promoting the sharing and reuse of software generally. | |||
NO WARRANTY | |||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY | |||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN | |||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES | |||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED | |||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS | |||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE | |||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, | |||
REPAIR OR CORRECTION. | |||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | |||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR | |||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, | |||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING | |||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED | |||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY | |||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER | |||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE | |||
POSSIBILITY OF SUCH DAMAGES. | |||
END OF TERMS AND CONDITIONS | |||
How to Apply These Terms to Your New Programs | |||
If you develop a new program, and you want it to be of the greatest | |||
possible use to the public, the best way to achieve this is to make it | |||
free software which everyone can redistribute and change under these terms. | |||
To do so, attach the following notices to the program. It is safest | |||
to attach them to the start of each source file to most effectively | |||
convey the exclusion of warranty; and each file should have at least | |||
the "copyright" line and a pointer to where the full notice is found. | |||
<one line to give the program's name and a brief idea of what it does.> | |||
Copyright (C) <year> <name of author> | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation; either version 2 of the License, or | |||
(at your option) any later version. | |||
This program is distributed in the hope that it will be useful, | |||
but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
GNU General Public License for more details. | |||
You should have received a copy of the GNU General Public License | |||
along with this program; if not, write to the Free Software | |||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |||
Also add information on how to contact you by electronic and paper mail. | |||
If the program is interactive, make it output a short notice like this | |||
when it starts in an interactive mode: | |||
Gnomovision version 69, Copyright (C) year name of author | |||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | |||
This is free software, and you are welcome to redistribute it | |||
under certain conditions; type `show c' for details. | |||
The hypothetical commands `show w' and `show c' should show the appropriate | |||
parts of the General Public License. Of course, the commands you use may | |||
be called something other than `show w' and `show c'; they could even be | |||
mouse-clicks or menu items--whatever suits your program. | |||
You should also get your employer (if you work as a programmer) or your | |||
school, if any, to sign a "copyright disclaimer" for the program, if | |||
necessary. Here is a sample; alter the names: | |||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program | |||
`Gnomovision' (which makes passes at compilers) written by James Hacker. | |||
<signature of Ty Coon>, 1 April 1989 | |||
Ty Coon, President of Vice | |||
This General Public License does not permit incorporating your program into | |||
proprietary programs. If your program is a subroutine library, you may | |||
consider it more useful to permit linking proprietary applications with the | |||
library. If this is what you want to do, use the GNU Library General | |||
Public License instead of this License. |
@@ -0,0 +1,78 @@ | |||
Juniper VPN Authenticator: | |||
This script authenticates with a Juniper VPN server to generate a session | |||
cookie (DSID), and then passes that cookie to a VPN client. | |||
Example usage with openconnect: | |||
./juniper-vpn.py --host vpn.example.com --user joeuser --stdin DSID=%DSID% \ | |||
openconnect --juniper %HOST% --cookie-on-stdin | |||
This will connect to vpn.example.com and prompt the user for a authentication | |||
password. Once authenticated, the session cookie will be passed to openconnect | |||
which will connect to the VPN. Note that because the DSID provides full access | |||
to the VPN, it can be easily passed via stdin to avoid having it show up | |||
in the process list. | |||
Juniper Networks Host Checker: | |||
A python module, tncc.py, is integrated with juniper-vpn.py and provides | |||
support for VPN sites which require a host checker step. It is currently only | |||
tested on a subset of sites and does not yet support sites that require | |||
periodic host checker updates. | |||
Command line options: | |||
juniper-vpn.py [-h HOST] [-u USERNAME] [-o OATH] [-c CONFIG] [-s STDIN] \ | |||
<external program> <external program arguments...> | |||
-h --host | |||
VPN server to access. This option is required. | |||
-u --username | |||
Username to authenticate with. This option is required. | |||
-o --oath | |||
OATH key to use for OTP generation if required for authentication. | |||
Key should be in hex format. | |||
-c --config | |||
Config file. Rather than passing arguments on the command line, | |||
they can be contained within a config file. Command line arguments | |||
override config file options. See sample.cfg for documentation. | |||
-s --stdin | |||
Provide input to external program. This allows the cookie to be passed | |||
on stdin, avoiding having it appear in the process list. The string | |||
%DSID% will be replaced with the DSID cookie value. The string %HOST% | |||
will be replaced with the server hostname. | |||
<external program> <external program arguments...> | |||
Runs the external program with the supplied arguments when a cookie | |||
is generated. %DSID% in any argument is replaced with the DSID cookie | |||
value. %HOST% in any argument is replaced with the server hostname. | |||
If the external program returns a positive return code, it is assumed | |||
that a fatal error has occurred (such as bad command line arguments) | |||
and the script exits. If the external program returns -EPERM, it is | |||
assumed that the DSID is no longer valid and a new one is generated. | |||
For all other return codes, the external program is simply called again. | |||
An external program is required. | |||
Running without root or tun access: | |||
openconnect provides two options for running without any special permissions. | |||
The first option is to create a tun device in advance and configure permissions | |||
for user access. | |||
The second is to redirect the traffic for the tun device to an external program. | |||
This external program can then configure a user-level SOCKS proxy: | |||
./juniper-vpn.py -c example.cfg -s DSID=%DSID% \ | |||
openconnect --juniper %HOST% --cookie-on-stdin --script-tun \ | |||
--script "tunsocks -D 1080" | |||
Both tunsocks and ocproxy can perform this role: | |||
http://github.com/russdill/tunsocks | |||
http://repo.or.cz/w/ocproxy.git | |||
@@ -0,0 +1,276 @@ | |||
#!/usr/bin/python | |||
# -*- coding: utf-8 -*- | |||
import subprocess | |||
import mechanize | |||
import cookielib | |||
import getpass | |||
import sys | |||
import os | |||
import ssl | |||
import errno | |||
import argparse | |||
import atexit | |||
import signal | |||
import ConfigParser | |||
import time | |||
import binascii | |||
import hmac | |||
import hashlib | |||
import shlex | |||
import tncc | |||
ssl._create_default_https_context = ssl._create_unverified_context | |||
""" | |||
OATH code from https://github.com/bdauvergne/python-oath | |||
Copyright 2010, Benjamin Dauvergne | |||
* All rights reserved. | |||
* Redistribution and use in source and binary forms, with or without | |||
modification, are permitted provided that the following conditions are met: | |||
* Redistributions of source code must retain the above copyright | |||
notice, this list of conditions and the following disclaimer. | |||
* Redistributions in binary form must reproduce the above copyright | |||
notice, this list of conditions and the following disclaimer in the | |||
documentation and/or other materials provided with the distribution.''' | |||
""" | |||
def truncated_value(h): | |||
bytes = map(ord, h) | |||
offset = bytes[-1] & 0xf | |||
v = (bytes[offset] & 0x7f) << 24 | (bytes[offset+1] & 0xff) << 16 | \ | |||
(bytes[offset+2] & 0xff) << 8 | (bytes[offset+3] & 0xff) | |||
return v | |||
def dec(h,p): | |||
v = truncated_value(h) | |||
v = v % (10**p) | |||
return '%0*d' % (p, v) | |||
def int2beint64(i): | |||
hex_counter = hex(long(i))[2:-1] | |||
hex_counter = '0' * (16 - len(hex_counter)) + hex_counter | |||
bin_counter = binascii.unhexlify(hex_counter) | |||
return bin_counter | |||
def hotp(key): | |||
key = binascii.unhexlify(key) | |||
counter = int2beint64(int(time.time()) / 30) | |||
return dec(hmac.new(key, counter, hashlib.sha256).digest(), 6) | |||
class juniper_vpn(object): | |||
def __init__(self, args): | |||
self.args = args | |||
self.fixed_password = args.password is not None | |||
self.last_connect = 0 | |||
self.br = mechanize.Browser() | |||
self.cj = cookielib.LWPCookieJar() | |||
self.br.set_cookiejar(self.cj) | |||
# Browser options | |||
self.br.set_handle_equiv(True) | |||
self.br.set_handle_redirect(True) | |||
self.br.set_handle_referer(True) | |||
self.br.set_handle_robots(False) | |||
# Follows refresh 0 but not hangs on refresh > 0 | |||
self.br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), | |||
max_time=1) | |||
# Want debugging messages? | |||
#self.br.set_debug_http(True) | |||
#self.br.set_debug_redirects(True) | |||
#self.br.set_debug_responses(True) | |||
self.user_agent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1' | |||
self.br.addheaders = [('User-agent', self.user_agent)] | |||
self.last_action = None | |||
self.needs_2factor = False | |||
self.key = None | |||
def find_cookie(self, name): | |||
for cookie in self.cj: | |||
if cookie.name == name: | |||
return cookie | |||
return None | |||
def next_action(self): | |||
if self.find_cookie('DSID'): | |||
return 'connect' | |||
for form in self.br.forms(): | |||
if form.name == 'frmLogin': | |||
return 'login' | |||
elif form.name == 'frmDefender': | |||
return 'key' | |||
elif form.name == 'frmConfirmation': | |||
return 'continue' | |||
else: | |||
raise Exception('Unknown form type:', form.name) | |||
return 'tncc' | |||
def run(self): | |||
# Open landing page | |||
self.r = self.br.open('https://' + self.args.host) | |||
while True: | |||
action = self.next_action() | |||
if action == 'tncc': | |||
self.action_tncc() | |||
elif action == 'login': | |||
self.action_login() | |||
elif action == 'key': | |||
self.action_key() | |||
elif action == 'continue': | |||
self.action_continue() | |||
elif action == 'connect': | |||
self.action_connect() | |||
self.last_action = action | |||
def action_tncc(self): | |||
# Run tncc host checker | |||
dspreauth_cookie = self.find_cookie('DSPREAUTH') | |||
if dspreauth_cookie is None: | |||
raise Exception('Could not find DSPREAUTH key for host checker') | |||
dssignin_cookie = self.find_cookie('DSSIGNIN') | |||
t = tncc.tncc(self.args.host); | |||
self.cj.set_cookie(t.get_cookie(dspreauth_cookie, dssignin_cookie)) | |||
self.r = self.br.open(self.r.geturl()) | |||
def action_login(self): | |||
# The token used for two-factor is selected when this form is submitted. | |||
# If we aren't getting a password, then get the key now, otherwise | |||
# we could be sitting on the two factor key prompt later on waiting | |||
# on the user. | |||
if self.args.password is None or self.last_action == 'login': | |||
if self.fixed_password: | |||
print 'Login failed (Invalid username or password?)' | |||
sys.exit(1) | |||
else: | |||
self.args.password = getpass.getpass('Password:') | |||
self.needs_2factor = False | |||
if self.needs_2factor: | |||
if self.args.oath: | |||
self.key = hotp(self.args.oath) | |||
else: | |||
self.key = getpass.getpass('Two-factor key:') | |||
else: | |||
self.key = None | |||
# Enter username/password | |||
self.br.select_form(nr=0) | |||
self.br.form['username'] = self.args.username | |||
self.br.form['password'] = self.args.password | |||
# Untested, a list of availables realms is provided when this | |||
# is necessary. | |||
# self.br.form['realm'] = [realm] | |||
self.r = self.br.submit() | |||
def action_key(self): | |||
# Enter key | |||
self.needs_2factor = True | |||
if self.args.oath: | |||
if self.last_action == 'key': | |||
print 'Login failed (Invalid OATH key)' | |||
sys.exit(1) | |||
self.key = hotp(self.args.oath) | |||
elif self.key is None: | |||
self.key = getpass.getpass('Two-factor key:') | |||
self.br.select_form(nr=0) | |||
self.br.form['password'] = self.key | |||
self.key = None | |||
self.r = self.br.submit() | |||
def action_continue(self): | |||
# Yes, I want to terminate the existing connection | |||
self.br.select_form(nr=0) | |||
self.r = self.br.submit() | |||
def action_connect(self): | |||
now = time.time() | |||
delay = 10.0 - (now - self.last_connect) | |||
if delay > 0: | |||
print 'Waiting %.0f...' % (delay) | |||
time.sleep(delay) | |||
self.last_connect = time.time(); | |||
dsid = self.find_cookie('DSID').value | |||
action = [] | |||
for arg in self.args.action: | |||
arg = arg.replace('%DSID%', dsid).replace('%HOST%', self.args.host) | |||
action.append(arg) | |||
p = subprocess.Popen(action, stdin=subprocess.PIPE) | |||
if args.stdin is not None: | |||
stdin = args.stdin.replace('%DSID%', dsid) | |||
stdin = stdin.replace('%HOST%', self.args.host) | |||
p.communicate(input = stdin) | |||
else: | |||
ret = p.wait() | |||
ret = p.returncode | |||
# Openconnect specific | |||
if ret == -errno.EPERM: | |||
self.cj.clear(self.args.host, '/', 'DSID') | |||
self.r = self.br.open(self.r.geturl()) | |||
elif ret > 0: | |||
sys.exit(ret) | |||
def cleanup(): | |||
os.killpg(0, signal.SIGTERM) | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser(conflict_handler='resolve') | |||
parser.add_argument('-h', '--host', type=str, | |||
help='VPN host name') | |||
parser.add_argument('-u', '--username', type=str, | |||
help='User name') | |||
parser.add_argument('-o', '--oath', type=str, | |||
help='OATH key for two factor authentication (hex)') | |||
parser.add_argument('-c', '--config', type=str, | |||
help='Config file') | |||
parser.add_argument('-s', '--stdin', type=str, | |||
help="String to pass to action's stdin") | |||
parser.add_argument('action', nargs=argparse.REMAINDER, | |||
metavar='<action> [<args...>]', | |||
help='External command') | |||
args = parser.parse_args() | |||
args.__dict__['password'] = None | |||
if len(args.action) and args.action[0] == '--': | |||
args.action = args.action[1:] | |||
if not len(args.action): | |||
args.action = None | |||
if args.config is not None: | |||
config = ConfigParser.RawConfigParser() | |||
config.read(args.config) | |||
for arg in ['username', 'host', 'password', 'oath', 'action', 'stdin']: | |||
if args.__dict__[arg] is None: | |||
try: | |||
args.__dict__[arg] = config.get('vpn', arg) | |||
except: | |||
pass | |||
if not isinstance(args.action, list): | |||
args.action = shlex.split(args.action) | |||
if args.username == None or args.host == None or args.action == []: | |||
print "--user, --host, and <action> are required parameters" | |||
sys.exit(1) | |||
atexit.register(cleanup) | |||
jvpn = juniper_vpn(args) | |||
jvpn.run() | |||
@@ -0,0 +1,11 @@ | |||
[vpn] | |||
username = joeUser | |||
host = juniper.vpn.host.somewhere | |||
password = nobodyknows | |||
oath = d41d8cd98f00b204e9800998ecf8427e | |||
stdin = DSID=%DSID% | |||
action = openconnect --juniper %HOST% --cookie-on-stdin --script-tun | |||
--script "tunproxy -D 8080" | |||
@@ -0,0 +1,291 @@ | |||
#!/usr/bin/python | |||
# -*- coding: utf-8 -*- | |||
import sys | |||
import mechanize | |||
import cookielib | |||
import struct | |||
import ssl | |||
import base64 | |||
import collections | |||
import zlib | |||
import HTMLParser | |||
ssl._create_default_https_context = ssl._create_unverified_context | |||
# 0013 - Message | |||
def decode_0013(buf): | |||
ret = collections.defaultdict(list) | |||
while (len(buf) >= 12): | |||
length, cmd, out = decode_packet(buf) | |||
buf = buf[length:] | |||
ret[cmd].append(out) | |||
return ret | |||
# 0012 - u32 | |||
def decode_0012(buf): | |||
return struct.unpack(">I", buf) | |||
# 0ce4 - encapsulation | |||
def decode_0ce4(buf): | |||
ret = collections.defaultdict(list) | |||
while (len(buf) >= 12): | |||
length, cmd, out = decode_packet(buf) | |||
buf = buf[length:] | |||
ret[cmd].append(out) | |||
return ret | |||
# 0ce5 - string without hex prefixer | |||
def decode_0ce5(buf): | |||
return struct.unpack(str(len(buf)) + "s", buf) | |||
# 0ce7 - string with hex prefixer | |||
def decode_0ce7(buf): | |||
_, s = struct.unpack(">I" + str(len(buf) - 4) + "s", buf) | |||
return s | |||
# 0cf0 - encapsulation | |||
def decode_0cf0(buf): | |||
ret = dict() | |||
cmd, _, out = decode_packet(buf) | |||
ret[cmd] = out | |||
return ret | |||
# 0cf1 - string without hex prefixer | |||
def decode_0cf1(buf): | |||
return struct.unpack(str(len(buf)) + "s", buf) | |||
# 0cf3 - u32 | |||
def decode_0cf3(buf): | |||
return struct.unpack(">I", buf) | |||
def decode_packet(buf): | |||
cmd, _1, _2, length, _3 = struct.unpack(">IBBHI", buf[:12]) | |||
if (length < 12): | |||
raise Exception("Invalid packet") | |||
data = buf[12:length] | |||
if cmd == 0x0013: | |||
data = decode_0013(data) | |||
elif cmd == 0x0012: | |||
data = decode_0012(data) | |||
elif cmd == 0x0ce4: | |||
data = decode_0ce4(data) | |||
elif cmd == 0x0ce5: | |||
data = decode_0ce5(data) | |||
elif cmd == 0x0ce7: | |||
data = decode_0ce7(data) | |||
elif cmd == 0x0cf0: | |||
data = decode_0cf0(data) | |||
elif cmd == 0x0cf1: | |||
data = decode_0cf1(data) | |||
elif cmd == 0x0cf3: | |||
data = decode_0cf3(data) | |||
else: | |||
data = None | |||
return length, cmd, data | |||
def encode_packet(cmd, align, buf): | |||
if (align > 1 and (len(buf) + 12) % align): | |||
buf += struct.pack(str(align - len(buf) % align) + "x") | |||
return struct.pack(">IBBHI", cmd, 0xc0, 0x00, len(buf) + 12, 0x0000583) + buf | |||
# 0013 - Message | |||
def encode_0013(buf): | |||
return encode_packet(0x0013, 4, buf) | |||
# 0012 - u32 | |||
def encode_0012(i): | |||
return encode_packet(0x0012, 1, struct.pack("<I", i)) | |||
# 0ce4 - encapsulation | |||
def encode_0ce4(buf): | |||
return encode_packet(0x0ce4, 4, buf) | |||
# 0ce5 - string without hex prefixer | |||
def encode_0ce5(s): | |||
return encode_packet(0x0ce5, 1, struct.pack(str(len(s)) + "s", s)) | |||
# 0ce7 - string with hex prefixer | |||
def encode_0ce7(s): | |||
return encode_packet(0x0ce7, 1, struct.pack(">I" + str(len(s)) + "sx", | |||
0x00058316, s)) | |||
# 0cf0 - encapsulation | |||
def encode_0cf0(buf): | |||
return encode_packet(0x0cf0, 4, buf) | |||
# 0cf1 - string without hex prefixer | |||
def encode_0cf1(s): | |||
return encode_packet(0x0ce5, 1, struct.pack(str(len(s)) + "s", s)) | |||
# 0cf3 - u32 | |||
def encode_0cf3(i): | |||
return encode_packet(0x0013, 1, struct.pack("<I", i)) | |||
class tncc(object): | |||
def __init__(self, vpn_host): | |||
self.vpn_host = vpn_host | |||
self.br = mechanize.Browser() | |||
self.cj = cookielib.LWPCookieJar() | |||
self.br.set_cookiejar(self.cj) | |||
# Browser options | |||
self.br.set_handle_equiv(True) | |||
self.br.set_handle_redirect(True) | |||
self.br.set_handle_referer(True) | |||
self.br.set_handle_robots(False) | |||
# Follows refresh 0 but not hangs on refresh > 0 | |||
self.br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), | |||
max_time=1) | |||
# Want debugging messages? | |||
#self.br.set_debug_http(True) | |||
#self.br.set_debug_redirects(True) | |||
#self.br.set_debug_responses(True) | |||
self.user_agent = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008071615 Fedora/3.0.1-1.fc9 Firefox/3.0.1' | |||
self.br.addheaders = [('User-agent', self.user_agent)] | |||
def find_cookie(self, name): | |||
for cookie in self.cj: | |||
if cookie.name == name: | |||
return cookie | |||
return None | |||
def parse_response(self): | |||
# Read in key/token fields in HTTP response | |||
response = dict() | |||
last_key = '' | |||
for line in self.r.readlines(): | |||
line = line.strip() | |||
# Note that msg is too long and gets wrapped, handle it special | |||
if last_key == 'msg' and len(line): | |||
response['msg'] += line | |||
else: | |||
key = '' | |||
try: | |||
key, val = line.split('=', 1) | |||
response[key] = val | |||
except: | |||
pass | |||
last_key = key | |||
return response | |||
def get_msg_contents(self, msg_value): | |||
# msg has the stuff we want, it's base64 encoded | |||
msg_raw = base64.b64decode(msg_value) | |||
_1, _2, msg_decoded = decode_packet(msg_raw) | |||
# Within msg, there is a field of data | |||
compressed = msg_decoded[0x0ce4][0][0x0ce7][0] | |||
# That field has a field that is compressed, decompress it | |||
typ, length, data = compressed.split(':', 2) | |||
if typ == 'COMPRESSED': | |||
data = zlib.decompress(data) | |||
else: | |||
raise Exception("Unknown storage type", typ) | |||
return data | |||
def parse_msg(self, msg_data): | |||
# The decompressed data is HTMLish, decode it. The value="" of each | |||
# tag is the data we want. | |||
objs = [] | |||
class ParamHTMLParser(HTMLParser.HTMLParser): | |||
def handle_starttag(self, tag, attrs): | |||
for key, value in attrs: | |||
if key == 'value': | |||
# It's made up of a bunch of key=value pairs separated | |||
# by semicolons | |||
d = dict() | |||
for field in value.split(';'): | |||
field = field.strip() | |||
try: | |||
key, value = field.split('=', 1) | |||
d[key] = value | |||
except: | |||
pass | |||
objs.append(d) | |||
p = ParamHTMLParser() | |||
p.feed(msg_data) | |||
p.close() | |||
return objs | |||
def get_cookie(self, dspreauth=None, dssignin=None): | |||
if (dspreauth is None or dssignin is None): | |||
self.r = self.br.open('https://' + self.vpn_host) | |||
else: | |||
self.cj.set_cookie(dspreauth) | |||
self.cj.set_cookie(dssignin) | |||
msg_raw = encode_0013(encode_0ce4(encode_0ce7('policy request')) + | |||
encode_0ce5('Accept-Language: en')) | |||
msg = base64.b64encode(msg_raw) | |||
post_data = 'connId=0;msg=' + msg + ';firsttime=1;' | |||
self.r = self.br.open('https://' + self.vpn_host + '/dana-na/hc/tnchcupdate.cgi', post_data) | |||
# Parse the data returned into a key/value dict | |||
response = self.parse_response() | |||
# Pull the compressed data block out of msg | |||
data = self.get_msg_contents(response['msg']) | |||
# Pull the data out of the 'value' key in the htmlish stuff returned | |||
objs = self.parse_msg(data) | |||
# Make a set of policies | |||
policies = set() | |||
for entry in objs: | |||
if 'policy' in entry: | |||
policies.add(entry['policy']) | |||
# Everything is OK, this may need updating if OK isn't the right answer | |||
policy_report = "" | |||
for policy in policies: | |||
policy_report += '\npolicy:' + policy + '\nstatus:OK\n' | |||
msg_raw = encode_0013(encode_0ce4(encode_0ce7(policy_report)) + | |||
encode_0ce5('Accept-Language: en')) | |||
msg = base64.b64encode(msg_raw) | |||
post_data = 'connId=1;msg=' + msg + ';firsttime=1;' | |||
self.r = self.br.open('https://' + self.vpn_host + '/dana-na/hc/tnchcupdate.cgi', post_data) | |||
# We have a new DSPREAUTH cookie | |||
return self.find_cookie('DSPREAUTH') | |||
if __name__ == "__main__": | |||
vpn_host = sys.argv[1] | |||
if len(sys.argv) == 4: | |||
dspreauth = sys.argv[2] | |||
dssignin = sys.argv[3] | |||
dspreauth_cookie = cookielib.Cookie(version=0, name='DSPREAUTH', value=dspreauth, | |||
port=None, port_specified=False, domain='', | |||
domain_specified=False, domain_initial_dot=False, path='/', | |||
path_specified=False, secure=False, expires=None, discard=True, | |||
comment=None, comment_url=None, rest=None, rfc2109=False) | |||
dssignin_cookie = cookielib.Cookie(version=0, name='DSSIGNIN', value=dssignin, | |||
port=None, port_specified=False, domain='', | |||
domain_specified=False, domain_initial_dot=False, path='/', | |||
path_specified=False, secure=False, expires=None, discard=True, | |||
comment=None, comment_url=None, rest=None, rfc2109=False) | |||
else: | |||
dspreauth_cookie = None | |||
dssignin_cookie = None | |||
t = tncc(vpn_host) | |||
print t.get_cookie(dspreauth_cookie, dssignin_cookie).value |