Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

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