Guest User

Untitled

a guest
Mar 29th, 2019
149
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 40.14 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. * **************************************************************************
  5. * Contributions to this work were made on behalf of the GÉANT project,
  6. * a project that has received funding from the European Union’s Framework
  7. * Programme 7 under Grant Agreements No. 238875 (GN3)
  8. * and No. 605243 (GN3plus), Horizon 2020 research and innovation programme
  9. * under Grant Agreements No. 691567 (GN4-1) and No. 731122 (GN4-2).
  10. * On behalf of the aforementioned projects, GEANT Association is
  11. * the sole owner of the copyright in all material which was developed
  12. * by a member of the GÉANT project.
  13. * GÉANT Vereniging (Association) is registered with the Chamber of
  14. * Commerce in Amsterdam with registration number 40535155 and operates
  15. * in the UK as a branch of GÉANT Vereniging.
  16. *
  17. * Registered office: Hoekenrode 3, 1102BR Amsterdam, The Netherlands.
  18. * UK branch address: City House, 126-130 Hills Road, Cambridge CB2 1PQ, UK
  19. *
  20. * License: see the web/copyright.inc.php file in the file structure or
  21. * <base_url>/copyright.php after deploying the software
  22.  
  23. Authors:
  24. Tomasz Wolniewicz <twoln@umk.pl>
  25. Michał Gasewicz <genn@umk.pl> (Network Manager support)
  26.  
  27. Contributors:
  28. Steffen Klemer https://github.com/sklemer1
  29. ikerb7 https://github.com/ikreb7
  30. Many thanks for multiple code fixes, feature ideas, styling remarks
  31. much of the code provided by them in the form of pull requests
  32. has been incorporated into the final form of this script.
  33.  
  34. This script is the main body of the CAT Linux installer.
  35. In the generation process configuration settings are added
  36. as well as messages which are getting translated into the language
  37. selected by the user.
  38.  
  39. The script is meant to run both under python 2.7 and python3. It tests
  40. for the crucial dbus module and if it does not find it and if it is not
  41. running python3 it will try reruning iself again with python3.
  42. """
  43. import argparse
  44. import base64
  45. import getpass
  46. import os
  47. import re
  48. import subprocess
  49. import sys
  50. import uuid
  51. from shutil import copyfile
  52.  
  53. NM_AVAILABLE = True
  54. CRYPTO_AVAILABLE = True
  55. DEBUG_ON = False
  56. DEV_NULL = open("/dev/null", "w")
  57. STDERR_REDIR = DEV_NULL
  58.  
  59.  
  60. def debug(msg):
  61. """Print debbuging messages to stdout"""
  62. if not DEBUG_ON:
  63. return
  64. print("DEBUG:" + str(msg))
  65.  
  66.  
  67. def missing_dbus():
  68. """Handle missing dbus module"""
  69. global NM_AVAILABLE
  70. debug("Cannot import the dbus module")
  71. NM_AVAILABLE = False
  72.  
  73.  
  74. def byte_to_string(barray):
  75. """conversion utility"""
  76. return "".join([chr(x) for x in barray])
  77.  
  78.  
  79. def get_input(prompt):
  80. if sys.version_info.major < 3:
  81. return raw_input(prompt)
  82. return input(prompt)
  83.  
  84.  
  85. debug(sys.version_info.major)
  86.  
  87.  
  88. try:
  89. import dbus
  90. except ImportError:
  91. if sys.version_info.major == 3:
  92. missing_dbus()
  93. if sys.version_info.major < 3:
  94. try:
  95. subprocess.call(['python3'] + sys.argv)
  96. except:
  97. missing_dbus()
  98. sys.exit(0)
  99.  
  100. try:
  101. from OpenSSL import crypto
  102. except ImportError:
  103. CRYPTO_AVAILABLE = False
  104.  
  105.  
  106. if sys.version_info.major == 3 and sys.version_info.minor >= 8:
  107. import distro
  108. else:
  109. import platform
  110.  
  111.  
  112. # the function below was partially copied
  113. # from https://ubuntuforums.org/showthread.php?t=1139057
  114. def detect_desktop_environment():
  115. """
  116. Detect what desktop type is used. This method is prepared for
  117. possible future use with password encryption on supported distrs
  118. """
  119. desktop_environment = 'generic'
  120. if os.environ.get('KDE_FULL_SESSION') == 'true':
  121. desktop_environment = 'kde'
  122. elif os.environ.get('GNOME_DESKTOP_SESSION_ID'):
  123. desktop_environment = 'gnome'
  124. else:
  125. try:
  126. shell_command = subprocess.Popen(['xprop', '-root',
  127. '_DT_SAVE_MODE'],
  128. stdout=subprocess.PIPE,
  129. stderr=subprocess.PIPE)
  130. out, err = shell_command.communicate()
  131. info = out.strip().decode('utf-8')
  132. except (OSError, RuntimeError):
  133. pass
  134. else:
  135. if ' = "xfce4"' in info:
  136. desktop_environment = 'xfce'
  137. return desktop_environment
  138.  
  139.  
  140. def get_system():
  141. """
  142. Detect Linux platform. Not used at this stage.
  143. It is meant to enable password encryption in distos
  144. that can handle this well.
  145. """
  146. if sys.version_info.major == 3 and sys.version_info.minor >= 8:
  147. system = distro.linux_distribution()
  148. else:
  149. system = platform.linux_distribution()
  150. desktop = detect_desktop_environment()
  151. return [system[0], system[1], desktop]
  152.  
  153.  
  154. def run_installer():
  155. """
  156. This is the main installer part. It tests for MN availability
  157. gets user credentials and starts a proper installer.
  158. """
  159. global DEBUG_ON
  160. global NM_AVAILABLE
  161. username = ''
  162. password = ''
  163. silent = False
  164. pfx_file = ''
  165. parser = argparse.ArgumentParser(description='eduroam linux installer.')
  166. parser.add_argument('--debug', '-d', action='store_true', dest='debug',
  167. default=False, help='set debug flag')
  168. parser.add_argument('--username', '-u', action='store', dest='username',
  169. help='set username')
  170. parser.add_argument('--password', '-p', action='store', dest='password',
  171. help='set text_mode flag')
  172. parser.add_argument('--silent', '-s', action='store_true', dest='silent',
  173. help='set silent flag')
  174. parser.add_argument('--pfxfile', action='store', dest='pfx_file',
  175. help='set path to user certificate file')
  176. args = parser.parse_args()
  177. if args.debug:
  178. DEBUG_ON = True
  179. print("Runnng debug mode")
  180.  
  181. if args.username:
  182. username = args.username
  183. if args.password:
  184. password = args.password
  185. if args.silent:
  186. silent = args.silent
  187. if args.pfx_file:
  188. pfx_file = args.pfx_file
  189. debug(get_system())
  190. debug("Calling InstallerData")
  191. installer_data = InstallerData(silent=silent, username=username,
  192. password=password, pfx_file=pfx_file)
  193.  
  194. # test dbus connection
  195. if NM_AVAILABLE:
  196. config_tool = CatNMConfigTool()
  197. if config_tool.connect_to_nm() is None:
  198. NM_AVAILABLE = False
  199. if not NM_AVAILABLE:
  200. # no dbus so ask if the user will want wpa_supplicant config
  201. if installer_data.ask(Messages.save_wpa_conf, Messages.cont, 1):
  202. sys.exit(1)
  203. installer_data.get_user_cred()
  204. installer_data.save_ca()
  205. if NM_AVAILABLE:
  206. config_tool.add_connections(installer_data)
  207. else:
  208. wpa_config = WpaConf()
  209. wpa_config.create_wpa_conf(Config.ssids, installer_data)
  210. installer_data.show_info(Messages.installation_finished)
  211.  
  212.  
  213. class Messages(object):
  214. """
  215. These are innitial definitions of messages, but they will be
  216. overriden with translated strings.
  217. """
  218. quit = "Really quit?"
  219. username_prompt = "enter your userid"
  220. enter_password = "enter password"
  221. enter_import_password = "enter your import password"
  222. incorrect_password = "incorrect password"
  223. repeat_password = "repeat your password"
  224. passwords_difffer = "passwords do not match"
  225. installation_finished = "Installation successful"
  226. cat_dir_exists = "Directory {} exists; some of its files may be " \
  227. "overwritten."
  228. cont = "Continue?"
  229. nm_not_supported = "This NetworkManager version is not supported"
  230. cert_error = "Certificate file not found, looks like a CAT error"
  231. unknown_version = "Unknown version"
  232. dbus_error = "DBus connection problem, a sudo might help"
  233. yes = "Y"
  234. nay = "N"
  235. p12_filter = "personal certificate file (p12 or pfx)"
  236. all_filter = "All files"
  237. p12_title = "personal certificate file (p12 or pfx)"
  238. save_wpa_conf = "NetworkManager configuration failed, " \
  239. "but we may generate a wpa_supplicant configuration file " \
  240. "if you wish. Be warned that your connection password will be saved " \
  241. "in this file as clear text."
  242. save_wpa_confirm = "Write the file"
  243. wrongUsernameFormat = "Error: Your username must be of the form " \
  244. "'xxx@institutionID' e.g. 'john@example.net'!"
  245. wrong_realm = "Error: your username must be in the form of 'xxx@{}'. " \
  246. "Please enter the username in the correct format."
  247. wrong_realm_suffix = "Error: your username must be in the form of " \
  248. "'xxx@institutionID' and end with '{}'. Please enter the username " \
  249. "in the correct format."
  250. user_cert_missing = "personal certificate file not found"
  251. # "File %s exists; it will be overwritten."
  252. # "Output written to %s"
  253.  
  254.  
  255. class Config(object):
  256. """
  257. This is used to prepare settings durig installer generation.
  258. """
  259. instname = ""
  260. profilename = ""
  261. url = ""
  262. email = ""
  263. title = "eduroam CAT"
  264. servers = []
  265. ssids = []
  266. del_ssids = []
  267. eap_outer = ''
  268. eap_inner = ''
  269. use_other_tls_id = False
  270. server_match = ''
  271. anonymous_identity = ''
  272. CA = ""
  273. init_info = ""
  274. init_confirmation = ""
  275. tou = ""
  276. sb_user_file = ""
  277. verify_user_realm_input = False
  278. user_realm = ""
  279. hint_user_input = False
  280.  
  281.  
  282. class InstallerData(object):
  283. """
  284. General user intercation handling, supports zenity, kdialog and
  285. standard command-line interface
  286. """
  287.  
  288. def __init__(self, silent=False, username='', password='', pfx_file=''):
  289. self.graphics = ''
  290. self.username = username
  291. self.password = password
  292. self.silent = silent
  293. self.pfx_file = pfx_file
  294. debug("starting constructor")
  295. if silent:
  296. self.graphics = 'tty'
  297. else:
  298. self.__get_graphics_support()
  299. self.show_info(Config.init_info.format(Config.instname,
  300. Config.email, Config.url))
  301. if self.ask(Config.init_confirmation.format(Config.instname,
  302. Config.profilename),
  303. Messages.cont, 1):
  304. sys.exit(1)
  305. if Config.tou != '':
  306. if self.ask(Config.tou, Messages.cont, 1):
  307. sys.exit(1)
  308. if os.path.exists(os.environ.get('HOME') + '/.cat_installer'):
  309. if self.ask(Messages.cat_dir_exists.format(
  310. os.environ.get('HOME') + '/.cat_installer'),
  311. Messages.cont, 1):
  312. sys.exit(1)
  313. else:
  314. os.mkdir(os.environ.get('HOME') + '/.cat_installer', 0o700)
  315.  
  316. def save_ca(self):
  317. """
  318. Save CA certificate to .cat_installer directory
  319. (create directory if needed)
  320. """
  321. certfile = os.environ.get('HOME') + '/.cat_installer/ca.pem'
  322. debug("saving cert")
  323. with open(certfile, 'w') as cert:
  324. cert.write(Config.CA + "\n")
  325.  
  326. def ask(self, question, prompt='', default=None):
  327. """
  328. Propmpt user for a Y/N reply, possibly supplying a default answer
  329. """
  330. if self.silent:
  331. return 0
  332. if self.graphics == 'tty':
  333. yes = Messages.yes[:1].upper()
  334. nay = Messages.nay[:1].upper()
  335. print("\n-------\n" + question + "\n")
  336. while True:
  337. tmp = prompt + " (" + Messages.yes + "/" + Messages.nay + ") "
  338. if default == 1:
  339. tmp += "[" + yes + "]"
  340. elif default == 0:
  341. tmp += "[" + nay + "]"
  342. inp = get_input(tmp)
  343. if inp == '':
  344. if default == 1:
  345. return 0
  346. if default == 0:
  347. return 1
  348. i = inp[:1].upper()
  349. if i == yes:
  350. return 0
  351. if i == nay:
  352. return 1
  353. if self.graphics == "zenity":
  354. command = ['zenity', '--title=' + Config.title, '--width=500',
  355. '--question', '--text=' + question + "\n\n" + prompt]
  356. elif self.graphics == 'kdialog':
  357. command = ['kdialog', '--yesno', question + "\n\n" + prompt,
  358. '--title=', Config.title]
  359. returncode = subprocess.call(command, stderr=STDERR_REDIR)
  360. return returncode
  361.  
  362. def show_info(self, data):
  363. """
  364. Show a piece of information
  365. """
  366. if self.silent:
  367. return
  368. if self.graphics == 'tty':
  369. print(data)
  370. return
  371. if self.graphics == "zenity":
  372. command = ['zenity', '--info', '--width=500', '--text=' + data]
  373. elif self.graphics == "kdialog":
  374. command = ['kdialog', '--msgbox', data]
  375. else:
  376. sys.exit(1)
  377. subprocess.call(command, stderr=STDERR_REDIR)
  378.  
  379. def confirm_exit(self):
  380. """
  381. Confirm exit from installer
  382. """
  383. ret = self.ask(Messages.quit)
  384. if ret == 0:
  385. sys.exit(1)
  386.  
  387. def alert(self, text):
  388. """Generate alert message"""
  389. if self.silent:
  390. return
  391. if self.graphics == 'tty':
  392. print(text)
  393. return
  394. if self.graphics == 'zenity':
  395. command = ['zenity', '--warning', '--text=' + text]
  396. elif self.graphics == "kdialog":
  397. command = ['kdialog', '--sorry', text]
  398. else:
  399. sys.exit(1)
  400. subprocess.call(command, stderr=STDERR_REDIR)
  401.  
  402. def prompt_nonempty_string(self, show, prompt, val=''):
  403. """
  404. Prompt user for input
  405. """
  406. if self.graphics == 'tty':
  407. if show == 0:
  408. while True:
  409. inp = str(getpass.getpass(prompt + ": "))
  410. output = inp.strip()
  411. if output != '':
  412. return output
  413. while True:
  414. inp = str(get_input(prompt + ": "))
  415. output = inp.strip()
  416. if output != '':
  417. return output
  418.  
  419. if self.graphics == 'zenity':
  420. if val == '':
  421. default_val = ''
  422. else:
  423. default_val = '--entry-text=' + val
  424. if show == 0:
  425. hide_text = '--hide-text'
  426. else:
  427. hide_text = ''
  428. command = ['zenity', '--entry', hide_text, default_val,
  429. '--width=500', '--text=' + prompt]
  430. elif self.graphics == 'kdialog':
  431. if show == 0:
  432. hide_text = '--password'
  433. else:
  434. hide_text = '--inputbox'
  435. command = ['kdialog', hide_text, prompt]
  436.  
  437. output = ''
  438. while not output:
  439. shell_command = subprocess.Popen(command, stdout=subprocess.PIPE,
  440. stderr=subprocess.PIPE)
  441. out, err = shell_command.communicate()
  442. output = out.strip().decode('utf-8')
  443. if shell_command.returncode == 1:
  444. self.confirm_exit()
  445. return output
  446.  
  447. def get_user_cred(self):
  448. """
  449. Get user credentials both username/password and personame certificate
  450. based
  451. """
  452. if Config.eap_outer == 'PEAP' or Config.eap_outer == 'TTLS':
  453. self.__get_username_password()
  454. if Config.eap_outer == 'TLS':
  455. self.__get_p12_cred()
  456.  
  457. def __get_username_password(self):
  458. """
  459. read user password and set the password property
  460. do nothing if silent mode is set
  461. """
  462. password = "a"
  463. password1 = "b"
  464. if self.silent:
  465. return
  466. if self.username:
  467. user_prompt = self.username
  468. elif Config.hint_user_input:
  469. user_prompt = '@' + Config.user_realm
  470. else:
  471. user_prompt = ''
  472. while True:
  473. self.username = self.prompt_nonempty_string(
  474. 1, Messages.username_prompt, user_prompt)
  475. if self.__validate_user_name():
  476. break
  477. while password != password1:
  478. password = self.prompt_nonempty_string(
  479. 0, Messages.enter_password)
  480. password1 = self.prompt_nonempty_string(
  481. 0, Messages.repeat_password)
  482. if password != password1:
  483. self.alert(Messages.passwords_difffer)
  484. self.password = password
  485.  
  486. def __get_graphics_support(self):
  487. if os.environ.get('DISPLAY') is not None:
  488. shell_command = subprocess.Popen(['which', 'zenity'],
  489. stdout=subprocess.PIPE,
  490. stderr=subprocess.PIPE)
  491. shell_command.wait()
  492. if shell_command.returncode == 0:
  493. self.graphics = 'zenity'
  494. else:
  495. shell_command = subprocess.Popen(['which', 'kdialog'],
  496. stdout=subprocess.PIPE,
  497. stderr=subprocess.PIPE)
  498. shell_command.wait()
  499. # out, err = shell_command.communicate()
  500. if shell_command.returncode == 0:
  501. self.graphics = 'kdialog'
  502. else:
  503. self.graphics = 'tty'
  504. else:
  505. self.graphics = 'tty'
  506.  
  507. def __process_p12(self):
  508. debug('process_p12')
  509. pfx_file = os.environ['HOME'] + '/.cat_installer/user.p12'
  510. if CRYPTO_AVAILABLE:
  511. debug("using crypto")
  512. try:
  513. p12 = crypto.load_pkcs12(open(pfx_file, 'rb').read(),
  514. self.password)
  515. except:
  516. debug("incorrect password")
  517. return False
  518. else:
  519. if Config.use_other_tls_id:
  520. return True
  521. try:
  522. self.username = p12.get_certificate().\
  523. get_subject().commonName
  524. except:
  525. self.username = p12.get_certificate().\
  526. get_subject().emailAddress
  527. return True
  528. else:
  529. debug("using openssl")
  530. command = ['openssl', 'pkcs12', '-in', pfx_file, '-passin',
  531. 'pass:' + self.password, '-nokeys', '-clcerts']
  532. shell_command = subprocess.Popen(command, stdout=subprocess.PIPE,
  533. stderr=subprocess.PIPE)
  534. out, err = shell_command.communicate()
  535. if shell_command.returncode != 0:
  536. return False
  537. if Config.use_other_tls_id:
  538. return True
  539. out_str = out.decode('utf-8')
  540. subject = re.split(r'\s*[/,]\s*',
  541. re.findall(r'subject=/?(.*)$',
  542. out_str, re.MULTILINE)[0])
  543. cert_prop = {}
  544. for field in subject:
  545. if field:
  546. cert_field = re.split(r'\s*=\s*', field)
  547. cert_prop[cert_field[0].lower()] = cert_field[1]
  548. if cert_prop['cn'] and re.search(r'@', cert_prop['cn']):
  549. debug('Using cn: ' + cert_prop['cn'])
  550. self.username = cert_prop['cn']
  551. elif cert_prop['emailaddress'] and \
  552. re.search(r'@', cert_prop['emailaddress']):
  553. debug('Using email: ' + cert_prop['emailaddress'])
  554. self.username = cert_prop['emailaddress']
  555. else:
  556. self.username = ''
  557. self.alert("Unable to extract username "
  558. "from the certificate")
  559. return True
  560.  
  561. def __select_p12_file(self):
  562. """
  563. prompt user for the PFX file selection
  564. this method is not being called in the silent mode
  565. therefore there is no code for this case
  566. """
  567. if self.graphics == 'tty':
  568. my_dir = os.listdir(".")
  569. p_count = 0
  570. pfx_file = ''
  571. for my_file in my_dir:
  572. if my_file.endswith('.p12') or my_file.endswith('*.pfx') or \
  573. my_file.endswith('.P12') or my_file.endswith('*.PFX'):
  574. p_count += 1
  575. pfx_file = my_file
  576. prompt = "personal certificate file (p12 or pfx)"
  577. default = ''
  578. if p_count == 1:
  579. default = '[' + pfx_file + ']'
  580.  
  581. while True:
  582. inp = get_input(prompt + default + ": ")
  583. output = inp.strip()
  584.  
  585. if default != '' and output == '':
  586. return pfx_file
  587. default = ''
  588. if os.path.isfile(output):
  589. return output
  590. print("file not found")
  591.  
  592. if self.graphics == 'zenity':
  593. command = ['zenity', '--file-selection',
  594. '--file-filter=' + Messages.p12_filter +
  595. ' | *.p12 *.P12 *.pfx *.PFX', '--file-filter=' +
  596. Messages.all_filter + ' | *',
  597. '--title=' + Messages.p12_title]
  598. shell_command = subprocess.Popen(command, stdout=subprocess.PIPE,
  599. stderr=subprocess.PIPE)
  600. cert, err = shell_command.communicate()
  601. if self.graphics == 'kdialog':
  602. command = ['kdialog', '--getopenfilename',
  603. '.', '*.p12 *.P12 *.pfx *.PFX | ' +
  604. Messages.p12_filter, '--title', Messages.p12_title]
  605. shell_command = subprocess.Popen(command, stdout=subprocess.PIPE,
  606. stderr=STDERR_REDIR)
  607. cert, err = shell_command.communicate()
  608. return cert.strip().decode('utf-8')
  609.  
  610. def __save_sb_pfx(self):
  611. """write the user PFX file"""
  612. certfile = os.environ.get('HOME') + '/.cat_installer/user.p12'
  613. with open(certfile, 'wb') as cert:
  614. cert.write(base64.b64decode(Config.sb_user_file))
  615.  
  616. def __get_p12_cred(self):
  617. """get the password for the PFX file"""
  618. if Config.eap_inner == 'SILVERBULLET':
  619. self.__save_sb_pfx()
  620. else:
  621. if self.silent:
  622. pfx_file = self.pfx_file
  623. else:
  624. pfx_file = self.__select_p12_file()
  625. try:
  626. copyfile(pfx_file, os.environ['HOME'] +
  627. '/.cat_installer/user.p12')
  628. except (OSError, RuntimeError):
  629. print(Messages.user_cert_missing)
  630. sys.exit(1)
  631. if self.silent:
  632. username = self.username
  633. if not self.__process_p12():
  634. sys.exit(1)
  635. if username:
  636. self.username = username
  637. else:
  638. while not self.password:
  639. self.password = self.prompt_nonempty_string(
  640. 0, Messages.enter_import_password)
  641. if not self.__process_p12():
  642. self.alert(Messages.incorrect_password)
  643. self.password = ''
  644. if not self.username:
  645. self.username = self.prompt_nonempty_string(
  646. 1, Messages.username_prompt)
  647.  
  648. def __validate_user_name(self):
  649. # locate the @ character in username
  650. pos = self.username.find('@')
  651. debug("@ position: " + str(pos))
  652. # trailing @
  653. if pos == len(self.username) - 1:
  654. debug("username ending with @")
  655. self.alert(Messages.wrongUsernameFormat)
  656. return False
  657. # no @ at all
  658. if pos == -1:
  659. if Config.verify_user_realm_input:
  660. debug("missing realm")
  661. self.alert(Messages.wrongUsernameFormat)
  662. return False
  663. debug("No realm, but possibly correct")
  664. return True
  665. # @ at the beginning
  666. if pos == 0:
  667. debug("missing user part")
  668. self.alert(Messages.wrongUsernameFormat)
  669. return False
  670. pos += 1
  671. if Config.verify_user_realm_input:
  672. if Config.hint_user_input:
  673. if self.username.endswith('@' + Config.user_realm, pos-1):
  674. debug("realm equal to the expected value")
  675. return True
  676. debug("incorrect realm; expected:" + Config.user_realm)
  677. self.alert(Messages.wrong_realm.format(Config.user_realm))
  678. return False
  679. if self.username.endswith(Config.user_realm, pos):
  680. debug("real ends with expected suffix")
  681. return True
  682. debug("realm suffix error; expected: " + Config.user_realm)
  683. self.alert(Messages.wrong_realm_suffix.format(
  684. Config.user_realm))
  685. return False
  686. pos1 = self.username.find('@', pos)
  687. if pos1 > -1:
  688. debug("second @ character found")
  689. self.alert(Messages.wrongUsernameFormat)
  690. return False
  691. pos1 = self.username.find('.', pos)
  692. if pos1 == pos:
  693. debug("a dot immediately after the @ character")
  694. self.alert(Messages.wrongUsernameFormat)
  695. return False
  696. debug("all passed")
  697. return True
  698.  
  699.  
  700. class WpaConf(object):
  701. """
  702. Preapre and save wpa_supplicant config file
  703. """
  704. def __prepare_network_block(self, ssid, user_data):
  705. altsubj_match = "altsubject_match=\"%s\"" % ";".join(Config.servers)
  706. out = """network={
  707. ssid=""" + ssid + """
  708. key_mgmt=WPA-EAP
  709. pairwise=CCMP
  710. group=CCMP TKIP
  711. eap=""" + Config.eap_outer + """
  712. ca_cert=\"""" + os.environ.get('HOME') + """/.cat_installer/ca.pem\"
  713. identity=\"""" + user_data.username + """\"
  714. altsubject_match=\"""" + altsubj_match + """\"
  715. phase2=\"auth=""" + Config.eap_inner + """\"
  716. password=\"""" + user_data.password + """\"
  717. anonymous_identity=\"""" + Config.anonymous_identity + """\"
  718. }
  719. """
  720. return out
  721.  
  722. def create_wpa_conf(self, ssids, user_data):
  723. """Create and save the wpa_supplicant config file"""
  724. wpa_conf = os.environ.get('HOME') + \
  725. '/.cat_installer/cat_installer.conf'
  726. with open(wpa_conf, 'w') as conf:
  727. for ssid in ssids:
  728. net = self.__prepare_network_block(ssid, user_data)
  729. conf.write(net)
  730.  
  731.  
  732. class CatNMConfigTool(object):
  733. """
  734. Prepare and save NetworkManager configuration
  735. """
  736. def __init__(self):
  737. self.cacert_file = None
  738. self.settings_service_name = None
  739. self.connection_interface_name = None
  740. self.system_service_name = None
  741. self.nm_version = None
  742. self.pfx_file = None
  743. self.settings = None
  744. self.user_data = None
  745. self.bus = None
  746.  
  747. def connect_to_nm(self):
  748. """
  749. connect to DBus
  750. """
  751. try:
  752. self.bus = dbus.SystemBus()
  753. except dbus.exceptions.DBusException:
  754. print("Can't connect to DBus")
  755. return None
  756. # main service name
  757. self.system_service_name = "org.freedesktop.NetworkManager"
  758. # check NM version
  759. self.__check_nm_version()
  760. debug("NM version: " + self.nm_version)
  761. if self.nm_version == "0.9" or self.nm_version == "1.0":
  762. self.settings_service_name = self.system_service_name
  763. self.connection_interface_name = \
  764. "org.freedesktop.NetworkManager.Settings.Connection"
  765. # settings proxy
  766. sysproxy = self.bus.get_object(
  767. self.settings_service_name,
  768. "/org/freedesktop/NetworkManager/Settings")
  769. # settings intrface
  770. self.settings = dbus.Interface(sysproxy, "org.freedesktop."
  771. "NetworkManager.Settings")
  772. elif self.nm_version == "0.8":
  773. self.settings_service_name = "org.freedesktop.NetworkManager"
  774. self.connection_interface_name = "org.freedesktop.NetworkMana" \
  775. "gerSettings.Connection"
  776. # settings proxy
  777. sysproxy = self.bus.get_object(
  778. self.settings_service_name,
  779. "/org/freedesktop/NetworkManagerSettings")
  780. # settings intrface
  781. self.settings = dbus.Interface(
  782. sysproxy, "org.freedesktop.NetworkManagerSettings")
  783. else:
  784. print(Messages.nm_not_supported)
  785. return None
  786. debug("NM connection worked")
  787. return True
  788.  
  789. def __check_opts(self):
  790. """
  791. set certificate files paths and test for existence of the CA cert
  792. """
  793. self.cacert_file = os.environ['HOME'] + '/.cat_installer/ca.pem'
  794. self.pfx_file = os.environ['HOME'] + '/.cat_installer/user.p12'
  795. if not os.path.isfile(self.cacert_file):
  796. print(Messages.cert_error)
  797. sys.exit(2)
  798.  
  799. def __check_nm_version(self):
  800. """
  801. Get the NetworkManager version
  802. """
  803. try:
  804. proxy = self.bus.get_object(
  805. self.system_service_name, "/org/freedesktop/NetworkManager")
  806. props = dbus.Interface(proxy, "org.freedesktop.DBus.Properties")
  807. version = props.Get("org.freedesktop.NetworkManager", "Version")
  808. except dbus.exceptions.DBusException:
  809. version = "0.8"
  810. if re.match(r'^1\.', version):
  811. self.nm_version = "1.0"
  812. return
  813. if re.match(r'^0\.9', version):
  814. self.nm_version = "0.9"
  815. return
  816. if re.match(r'^0\.8', version):
  817. self.nm_version = "0.8"
  818. return
  819. self.nm_version = Messages.unknown_version
  820.  
  821. def __delete_existing_connection(self, ssid):
  822. """
  823. checks and deletes earlier connection
  824. """
  825. try:
  826. conns = self.settings.ListConnections()
  827. except dbus.exceptions.DBusException:
  828. print(Messages.dbus_error)
  829. exit(3)
  830. for each in conns:
  831. con_proxy = self.bus.get_object(self.system_service_name, each)
  832. connection = dbus.Interface(
  833. con_proxy,
  834. "org.freedesktop.NetworkManager.Settings.Connection")
  835. try:
  836. connection_settings = connection.GetSettings()
  837. if connection_settings['connection']['type'] == '802-11-' \
  838. 'wireless':
  839. conn_ssid = byte_to_string(
  840. connection_settings['802-11-wireless']['ssid'])
  841. if conn_ssid == ssid:
  842. debug("deleting connection: " + conn_ssid)
  843. connection.Delete()
  844. except dbus.exceptions.DBusException:
  845. pass
  846.  
  847. def __add_connection(self, ssid):
  848. debug("Adding connection: " + ssid)
  849. server_alt_subject_name_list = dbus.Array(Config.servers)
  850. server_name = Config.server_match
  851. if self.nm_version == "0.9" or self.nm_version == "1.0":
  852. match_key = 'altsubject-matches'
  853. match_value = server_alt_subject_name_list
  854. else:
  855. match_key = 'subject-match'
  856. match_value = server_name
  857. s_8021x_data = {
  858. 'eap': [Config.eap_outer.lower()],
  859. 'identity': self.user_data.username,
  860. 'ca-cert': dbus.ByteArray(
  861. "file://{0}\0".format(self.cacert_file).encode('utf8')),
  862. match_key: match_value}
  863. if Config.eap_outer == 'PEAP' or Config.eap_outer == 'TTLS':
  864. s_8021x_data['password'] = self.user_data.password
  865. s_8021x_data['phase2-auth'] = Config.eap_inner.lower()
  866. if Config.anonymous_identity != '':
  867. s_8021x_data['anonymous-identity'] = Config.anonymous_identity
  868. s_8021x_data['password-flags'] = 0
  869. if Config.eap_outer == 'TLS':
  870. s_8021x_data['client-cert'] = dbus.ByteArray(
  871. "file://{0}\0".format(self.pfx_file).encode('utf8'))
  872. s_8021x_data['private-key'] = dbus.ByteArray(
  873. "file://{0}\0".format(self.pfx_file).encode('utf8'))
  874. s_8021x_data['private-key-password'] = self.user_data.password
  875. s_8021x_data['private-key-password-flags'] = 0
  876. s_con = dbus.Dictionary({
  877. 'type': '802-11-wireless',
  878. 'uuid': str(uuid.uuid4()),
  879. 'permissions': ['user:' +
  880. os.environ.get('USER')],
  881. 'id': ssid
  882. })
  883. s_wifi = dbus.Dictionary({
  884. 'ssid': dbus.ByteArray(ssid.encode('utf8')),
  885. 'security': '802-11-wireless-security'
  886. })
  887. s_wsec = dbus.Dictionary({
  888. 'key-mgmt': 'wpa-eap',
  889. 'proto': ['rsn'],
  890. 'pairwise': ['ccmp'],
  891. 'group': ['ccmp', 'tkip']
  892. })
  893. s_8021x = dbus.Dictionary(s_8021x_data)
  894. s_ip4 = dbus.Dictionary({'method': 'auto'})
  895. s_ip6 = dbus.Dictionary({'method': 'auto'})
  896. con = dbus.Dictionary({
  897. 'connection': s_con,
  898. '802-11-wireless': s_wifi,
  899. '802-11-wireless-security': s_wsec,
  900. '802-1x': s_8021x,
  901. 'ipv4': s_ip4,
  902. 'ipv6': s_ip6
  903. })
  904. self.settings.AddConnection(con)
  905.  
  906. def add_connections(self, user_data):
  907. """Deleta and then add connections to the system"""
  908. self.__check_opts()
  909. self.user_data = user_data
  910. for ssid in Config.ssids:
  911. self.__delete_existing_connection(ssid)
  912. self.__add_connection(ssid)
  913. for ssid in Config.del_ssids:
  914. self.__delete_existing_connection(ssid)
  915.  
  916.  
  917. Messages.quit = "Naprawdę zakończyć?"
  918. Messages.username_prompt = "wprowadź identyfikator użytkownika"
  919. Messages.enter_password = "wprowadź hasło"
  920. Messages.enter_import_password = "wprowadź swoje hasło importu"
  921. Messages.incorrect_password = "błędne hasło"
  922. Messages.repeat_password = "powtórz hasło"
  923. Messages.passwords_difffer = "niezgodność haseł"
  924. Messages.installation_finished = "Instalacja zakończona"
  925. Messages.cat_dir_exisits = "Katalog {} już istnieje; niektóre pliki " \
  926. "mogą ulec zmianie."
  927. Messages.cont = "Czy kontynuować?"
  928. Messages.nm_not_supported = "Ta wersja Network Managera nie jest " \
  929. "wspierana"
  930. Messages.cert_error = "Nie znaleziono pliku z certyfikatem, " \
  931. "prawdopodobnie błąd CAT-a"
  932. Messages.unknown_version = "Nieznana wersja"
  933. Messages.dbus_error = "Błąd połączenia DBus, prawdopodobnie " \
  934. "pomocne byłoby wykonanie sudo"
  935. Messages.yes = "T"
  936. Messages.no = "N"
  937. Messages.p12_filter = "plik certyfikatu osobistego (p12 lub pfx)"
  938. Messages.all_filter = "Wszystkie pliki"
  939. Messages.p12_title = "plik certyfikatu osobistego (p12 lub pfx)"
  940. Messages.save_wpa_conf = "Konfiguracja Network Managera nie powiodła " \
  941. "się, ale możemy wygenerować Twój plik konfiguracyjny dla programu " \
  942. "wpa_supplicant. Uprzedzamy, że hasło połączenia zostanie " \
  943. "zapamiętane w tym pliku otwartym tekstem."
  944. Messages.save_wpa_confirm = "Zapisz plik"
  945. Messages.wrongUsernameFormat = "Błąd: Twoja nazwa użytkownika musi " \
  946. "mieć postać 'xxx@identyfikatorInstytucji', np. 'john@example.net'!"
  947. Messages.wrong_realm = "Błąd: Twoja nazwa użytkownika musi mieć " \
  948. "postać 'xxx@{}'. Proszę wprowadzić poprawną nazwę użytkownika."
  949. Messages.wrong_realm_suffix = "Błąd: Twoja nazwa użytkownika musi " \
  950. "mieć postać 'xxx@identyfikatorInstytucji' i kończyć się ciągiem " \
  951. "'{}'. Proszę wprowadzić poprawną nazwę użytkownika."
  952. Messages.user_cert_missing = "nie znaleziono pliku z osobistym " \
  953. "certyfikatem"
  954. Config.instname = "Uniwersytet Warszawski"
  955. Config.profilename = "Studenci i Pracownicy Uniwersytetu " \
  956. "Warszawskiego"
  957. Config.url = "https://it.uw.edu.pl/eduroam/"
  958. Config.email = "eduroam@adm.uw.edu.pl"
  959. Config.title = "eduroam CAT"
  960. Config.server_match = "eduroam.uw.edu.pl"
  961. Config.eap_outer = "PEAP"
  962. Config.eap_inner = "MSCHAPV2"
  963. Config.init_info = "Przygotowano instalator dla {0}\n\nWięcej " \
  964. "informacji i uwagi:\n\nEMAIL: {1}\nWWW: {2}\n\nInstalator został " \
  965. "stworzony przy pomocy oprogramowania przygotowanego przez projekt " \
  966. "GEANT."
  967. Config.init_confirmation = "Ten instalator będzie działał poprawnie " \
  968. "tylko jeśli jesteś członkiem {0}."
  969. Config.user_realm = "uw.edu.pl"
  970. Config.ssids = ['eduroam']
  971. Config.del_ssids = []
  972. Config.servers = ['DNS:eduroam.uw.edu.pl']
  973. Config.use_other_tls_id = False
  974. Config.tou = ""
  975. Config.CA = """-----BEGIN CERTIFICATE-----
  976. MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
  977. MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
  978. d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
  979. b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
  980. EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
  981. cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
  982. MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
  983. JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
  984. mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
  985. wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
  986. VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
  987. AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
  988. AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
  989. BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
  990. pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
  991. dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
  992. fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
  993. NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
  994. H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
  995. +o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
  996. -----END CERTIFICATE-----
  997. -----BEGIN CERTIFICATE-----
  998. MIIE+zCCA+OgAwIBAgIQCHC8xa8/25Wakctq7u/kZTANBgkqhkiG9w0BAQsFADBl
  999. MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
  1000. d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
  1001. b3QgQ0EwHhcNMTQxMTE4MTIwMDAwWhcNMjQxMTE4MTIwMDAwWjBkMQswCQYDVQQG
  1002. EwJOTDEWMBQGA1UECBMNTm9vcmQtSG9sbGFuZDESMBAGA1UEBxMJQW1zdGVyZGFt
  1003. MQ8wDQYDVQQKEwZURVJFTkExGDAWBgNVBAMTD1RFUkVOQSBTU0wgQ0EgMzCCASIw
  1004. DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMV2Dw/ZQyk7bG3RR63eEL8jwnio
  1005. Snc18SNb4EweQefCMQC9iDdFdd25AhCAHo/tZCMERaegOTuBTc9jP8JJ/yKeiLDS
  1006. lrlcinQfkioq8hLIt2hUtVhBgUBoBhpPhSn7tU08D08/QJYbzqjMXjX/ZJj1dd10
  1007. VAWgNhEEEiRVY++Udy538RV27tOkWUUhn6i+0SftCuirOMo/h9Ha8Y+5Cx9E5+Ct
  1008. 85XCFk3shKM6ktTPxn3mvcsaQE+zVLHzj28NHuO+SaNW5Ae8jafOHbBbV1bRxBz8
  1009. mGXRzUYvkZS/RYVJ+G1ShxwCVgEnFqtyLvRx5GG1IKD6JmlqCvGrn223zyUCAwEA
  1010. AaOCAaYwggGiMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMHkG
  1011. CCsGAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu
  1012. Y29tMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln
  1013. aUNlcnRBc3N1cmVkSURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRw
  1014. Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3Js
  1015. MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk
  1016. SURSb290Q0EuY3JsMD0GA1UdIAQ2MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxo
  1017. dHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMB0GA1UdDgQWBBRn/YggFCeYxwnS
  1018. JRm76VERY3VQYjAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
  1019. hkiG9w0BAQsFAAOCAQEAqSg1esR71tonHqyYzyc2TxEydHTmQN0dzfJodzWvs4xd
  1020. xgS/FfQjZ4u5b5cE60adws3J0aSugS7JurHogNAcyTnBVnZZbJx946nw09E02DxJ
  1021. WYsamM6/xvLYMDX/6W9doK867mZTrqqMaci+mqege9iCSzMTyAfzd9fzZM2eY/lC
  1022. J1OuEDOJcjcV8b73HjWizsMt8tey5gvHacDlH198aZt+ziYaM0TDuncFO7pdP0GJ
  1023. +hY77gRuW6xWS++McPJKe1e9GW6LNgdUJi2GCZQfXzer8CM/jyxflp5HcahE3qm5
  1024. hS+1NGClXwmgmkMd1L8tRNaN2v11y18WoA5hwnA9Ng==
  1025. -----END CERTIFICATE-----
  1026. """
  1027. run_installer()
Add Comment
Please, Sign In to add comment