Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

286 рядки
9.4 KiB

  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. import subprocess
  4. import mechanize
  5. import cookielib
  6. import getpass
  7. import sys
  8. import os
  9. import ssl
  10. import argparse
  11. import atexit
  12. import signal
  13. import ConfigParser
  14. import time
  15. import binascii
  16. import hmac
  17. import hashlib
  18. import shlex
  19. import tncc
  20. ssl._create_default_https_context = ssl._create_unverified_context
  21. """
  22. OATH code from https://github.com/bdauvergne/python-oath
  23. Copyright 2010, Benjamin Dauvergne
  24. * All rights reserved.
  25. * Redistribution and use in source and binary forms, with or without
  26. modification, are permitted provided that the following conditions are met:
  27. * Redistributions of source code must retain the above copyright
  28. notice, this list of conditions and the following disclaimer.
  29. * Redistributions in binary form must reproduce the above copyright
  30. notice, this list of conditions and the following disclaimer in the
  31. documentation and/or other materials provided with the distribution.'''
  32. """
  33. def truncated_value(h):
  34. bytes = map(ord, h)
  35. offset = bytes[-1] & 0xf
  36. v = (bytes[offset] & 0x7f) << 24 | (bytes[offset+1] & 0xff) << 16 | \
  37. (bytes[offset+2] & 0xff) << 8 | (bytes[offset+3] & 0xff)
  38. return v
  39. def dec(h,p):
  40. v = truncated_value(h)
  41. v = v % (10**p)
  42. return '%0*d' % (p, v)
  43. def int2beint64(i):
  44. hex_counter = hex(long(i))[2:-1]
  45. hex_counter = '0' * (16 - len(hex_counter)) + hex_counter
  46. bin_counter = binascii.unhexlify(hex_counter)
  47. return bin_counter
  48. def hotp(key):
  49. key = binascii.unhexlify(key)
  50. counter = int2beint64(int(time.time()) / 30)
  51. return dec(hmac.new(key, counter, hashlib.sha256).digest(), 6)
  52. class juniper_vpn(object):
  53. def __init__(self, args):
  54. self.args = args
  55. self.fixed_password = args.password is not None
  56. self.last_connect = 0
  57. self.br = mechanize.Browser()
  58. self.cj = cookielib.LWPCookieJar()
  59. self.br.set_cookiejar(self.cj)
  60. # Browser options
  61. self.br.set_handle_equiv(True)
  62. self.br.set_handle_redirect(True)
  63. self.br.set_handle_referer(True)
  64. self.br.set_handle_robots(False)
  65. # Follows refresh 0 but not hangs on refresh > 0
  66. self.br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(),
  67. max_time=1)
  68. # Want debugging messages?
  69. #self.br.set_debug_http(True)
  70. #self.br.set_debug_redirects(True)
  71. #self.br.set_debug_responses(True)
  72. 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'
  73. self.br.addheaders = [('User-agent', self.user_agent)]
  74. self.last_action = None
  75. self.needs_2factor = False
  76. self.key = None
  77. self.pass_postfix = None
  78. def find_cookie(self, name):
  79. for cookie in self.cj:
  80. if cookie.name == name:
  81. return cookie
  82. return None
  83. def next_action(self):
  84. if self.find_cookie('DSID'):
  85. return 'connect'
  86. for form in self.br.forms():
  87. if form.name == 'frmLogin':
  88. return 'login'
  89. elif form.name == 'frmDefender':
  90. return 'key'
  91. elif form.name == 'frmConfirmation':
  92. return 'continue'
  93. else:
  94. raise Exception('Unknown form type:', form.name)
  95. return 'tncc'
  96. def run(self):
  97. # Open landing page
  98. self.r = self.br.open('https://' + self.args.host)
  99. while True:
  100. action = self.next_action()
  101. if action == 'tncc':
  102. self.action_tncc()
  103. elif action == 'login':
  104. self.action_login()
  105. elif action == 'key':
  106. self.action_key()
  107. elif action == 'continue':
  108. self.action_continue()
  109. elif action == 'connect':
  110. self.action_connect()
  111. self.last_action = action
  112. def action_tncc(self):
  113. # Run tncc host checker
  114. dspreauth_cookie = self.find_cookie('DSPREAUTH')
  115. if dspreauth_cookie is None:
  116. raise Exception('Could not find DSPREAUTH key for host checker')
  117. dssignin_cookie = self.find_cookie('DSSIGNIN')
  118. t = tncc.tncc(self.args.host);
  119. self.cj.set_cookie(t.get_cookie(dspreauth_cookie, dssignin_cookie))
  120. self.r = self.br.open(self.r.geturl())
  121. def action_login(self):
  122. # The token used for two-factor is selected when this form is submitted.
  123. # If we aren't getting a password, then get the key now, otherwise
  124. # we could be sitting on the two factor key prompt later on waiting
  125. # on the user.
  126. if self.args.password is None or self.last_action == 'login':
  127. if self.fixed_password:
  128. print 'Login failed (Invalid username or password?)'
  129. sys.exit(1)
  130. else:
  131. self.args.password = getpass.getpass('Password:')
  132. self.needs_2factor = False
  133. if self.args.pass_prefix:
  134. self.pass_postfix = getpass.getpass("Secondary password postfix:")
  135. if self.needs_2factor:
  136. if self.args.oath:
  137. self.key = hotp(self.args.oath)
  138. else:
  139. self.key = getpass.getpass('Two-factor key:')
  140. else:
  141. self.key = None
  142. # Enter username/password
  143. self.br.select_form(nr=0)
  144. self.br.form['username'] = self.args.username
  145. self.br.form['password'] = self.args.password
  146. if self.args.pass_prefix:
  147. if self.pass_postfix:
  148. secondary_password = "".join([ self.args.pass_prefix,
  149. self.pass_postfix])
  150. else:
  151. print 'Secondary password postfix not provided'
  152. sys.exit(1)
  153. self.br.form['password#2'] = secondary_password
  154. # Untested, a list of availables realms is provided when this
  155. # is necessary.
  156. # self.br.form['realm'] = [realm]
  157. self.r = self.br.submit()
  158. def action_key(self):
  159. # Enter key
  160. self.needs_2factor = True
  161. if self.args.oath:
  162. if self.last_action == 'key':
  163. print 'Login failed (Invalid OATH key)'
  164. sys.exit(1)
  165. self.key = hotp(self.args.oath)
  166. elif self.key is None:
  167. self.key = getpass.getpass('Two-factor key:')
  168. self.br.select_form(nr=0)
  169. self.br.form['password'] = self.key
  170. self.key = None
  171. self.r = self.br.submit()
  172. def action_continue(self):
  173. # Yes, I want to terminate the existing connection
  174. self.br.select_form(nr=0)
  175. self.r = self.br.submit()
  176. def action_connect(self):
  177. now = time.time()
  178. delay = 10.0 - (now - self.last_connect)
  179. if delay > 0:
  180. print 'Waiting %.0f...' % (delay)
  181. time.sleep(delay)
  182. self.last_connect = time.time();
  183. dsid = self.find_cookie('DSID').value
  184. action = []
  185. for arg in self.args.action:
  186. arg = arg.replace('%DSID%', dsid).replace('%HOST%', self.args.host)
  187. action.append(arg)
  188. p = subprocess.Popen(action, stdin=subprocess.PIPE)
  189. if args.stdin is not None:
  190. stdin = args.stdin.replace('%DSID%', dsid)
  191. stdin = stdin.replace('%HOST%', self.args.host)
  192. p.communicate(input = stdin)
  193. else:
  194. ret = p.wait()
  195. ret = p.returncode
  196. # Openconnect specific
  197. if ret == 2:
  198. self.cj.clear(self.args.host, '/', 'DSID')
  199. self.r = self.br.open(self.r.geturl())
  200. def cleanup():
  201. os.killpg(0, signal.SIGTERM)
  202. if __name__ == "__main__":
  203. parser = argparse.ArgumentParser(conflict_handler='resolve')
  204. parser.add_argument('-h', '--host', type=str,
  205. help='VPN host name')
  206. parser.add_argument('-u', '--username', type=str,
  207. help='User name')
  208. parser.add_argument('-p', '--pass_prefix', type=str,
  209. help="Secondary password prefix")
  210. parser.add_argument('-o', '--oath', type=str,
  211. help='OATH key for two factor authentication (hex)')
  212. parser.add_argument('-c', '--config', type=str,
  213. help='Config file')
  214. parser.add_argument('-s', '--stdin', type=str,
  215. help="String to pass to action's stdin")
  216. parser.add_argument('action', nargs=argparse.REMAINDER,
  217. metavar='<action> [<args...>]',
  218. help='External command')
  219. args = parser.parse_args()
  220. args.__dict__['password'] = None
  221. if len(args.action) and args.action[0] == '--':
  222. args.action = args.action[1:]
  223. if not len(args.action):
  224. args.action = None
  225. if args.config is not None:
  226. config = ConfigParser.RawConfigParser()
  227. config.read(args.config)
  228. for arg in ['username', 'host', 'password', 'pass_prefix', 'oath', 'action', 'stdin']:
  229. if args.__dict__[arg] is None:
  230. try:
  231. args.__dict__[arg] = config.get('vpn', arg)
  232. except:
  233. pass
  234. if not isinstance(args.action, list):
  235. args.action = shlex.split(args.action)
  236. if args.username == None or args.host == None or args.action == []:
  237. print "--user, --host, and <action> are required parameters"
  238. sys.exit(1)
  239. atexit.register(cleanup)
  240. jvpn = juniper_vpn(args)
  241. jvpn.run()