@@ -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 |