Guest User

Untitled

a guest
Jul 21st, 2022
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 38.55 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 rerunning 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 debugging 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) # pylint: disable=undefined-variable
  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 >= 7:
  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 distros
  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.decode('utf-8').strip()
  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 distros
  144. that can handle this well.
  145. """
  146. if sys.version_info.major == 3 and sys.version_info.minor >= 7:
  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("Running 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 initial definitions of messages, but they will be
  216. overridden 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_differ = "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 during 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 interaction 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.decode('utf-8').strip()
  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 personal 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_differ)
  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').strip()
  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.decode('utf-8').strip()
  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("realm 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. Prepare and save wpa_supplicant config file
  703. """
  704. def __prepare_network_block(self, ssid, user_data):
  705. out = """network={
  706. ssid=\"""" + ssid + """\"
  707. key_mgmt=WPA-EAP
  708. pairwise=CCMP
  709. group=CCMP TKIP
  710. eap=""" + Config.eap_outer + """
  711. ca_cert=\"""" + os.environ.get('HOME') + """/.cat_installer/ca.pem\"
  712. identity=\"""" + user_data.username + """\"
  713. altsubject_match=\"""" + ";".join(Config.servers) + """\"
  714. phase2=\"auth=""" + Config.eap_inner + """\"
  715. password=\"""" + user_data.password + """\"
  716. anonymous_identity=\"""" + Config.anonymous_identity + """\"
  717. }
  718. """
  719. return out
  720.  
  721. def create_wpa_conf(self, ssids, user_data):
  722. """Create and save the wpa_supplicant config file"""
  723. wpa_conf = os.environ.get('HOME') + \
  724. '/.cat_installer/cat_installer.conf'
  725. with open(wpa_conf, 'w') as conf:
  726. for ssid in ssids:
  727. net = self.__prepare_network_block(ssid, user_data)
  728. conf.write(net)
  729.  
  730.  
  731. class CatNMConfigTool(object):
  732. """
  733. Prepare and save NetworkManager configuration
  734. """
  735. def __init__(self):
  736. self.cacert_file = None
  737. self.settings_service_name = None
  738. self.connection_interface_name = None
  739. self.system_service_name = None
  740. self.nm_version = None
  741. self.pfx_file = None
  742. self.settings = None
  743. self.user_data = None
  744. self.bus = None
  745.  
  746. def connect_to_nm(self):
  747. """
  748. connect to DBus
  749. """
  750. try:
  751. self.bus = dbus.SystemBus()
  752. except dbus.exceptions.DBusException:
  753. print("Can't connect to DBus")
  754. return None
  755. # main service name
  756. self.system_service_name = "org.freedesktop.NetworkManager"
  757. # check NM version
  758. self.__check_nm_version()
  759. debug("NM version: " + self.nm_version)
  760. if self.nm_version == "0.9" or self.nm_version == "1.0":
  761. self.settings_service_name = self.system_service_name
  762. self.connection_interface_name = \
  763. "org.freedesktop.NetworkManager.Settings.Connection"
  764. # settings proxy
  765. sysproxy = self.bus.get_object(
  766. self.settings_service_name,
  767. "/org/freedesktop/NetworkManager/Settings")
  768. # settings interface
  769. self.settings = dbus.Interface(sysproxy, "org.freedesktop."
  770. "NetworkManager.Settings")
  771. elif self.nm_version == "0.8":
  772. self.settings_service_name = "org.freedesktop.NetworkManager"
  773. self.connection_interface_name = "org.freedesktop.NetworkMana" \
  774. "gerSettings.Connection"
  775. # settings proxy
  776. sysproxy = self.bus.get_object(
  777. self.settings_service_name,
  778. "/org/freedesktop/NetworkManagerSettings")
  779. # settings intrface
  780. self.settings = dbus.Interface(
  781. sysproxy, "org.freedesktop.NetworkManagerSettings")
  782. else:
  783. print(Messages.nm_not_supported)
  784. return None
  785. debug("NM connection worked")
  786. return True
  787.  
  788. def __check_opts(self):
  789. """
  790. set certificate files paths and test for existence of the CA cert
  791. """
  792. self.cacert_file = os.environ['HOME'] + '/.cat_installer/ca.pem'
  793. self.pfx_file = os.environ['HOME'] + '/.cat_installer/user.p12'
  794. if not os.path.isfile(self.cacert_file):
  795. print(Messages.cert_error)
  796. sys.exit(2)
  797.  
  798. def __check_nm_version(self):
  799. """
  800. Get the NetworkManager version
  801. """
  802. try:
  803. proxy = self.bus.get_object(
  804. self.system_service_name, "/org/freedesktop/NetworkManager")
  805. props = dbus.Interface(proxy, "org.freedesktop.DBus.Properties")
  806. version = props.Get("org.freedesktop.NetworkManager", "Version")
  807. except dbus.exceptions.DBusException:
  808. version = ""
  809. if re.match(r'^1\.', version):
  810. self.nm_version = "1.0"
  811. return
  812. if re.match(r'^0\.9', version):
  813. self.nm_version = "0.9"
  814. return
  815. if re.match(r'^0\.8', version):
  816. self.nm_version = "0.8"
  817. return
  818. self.nm_version = Messages.unknown_version
  819.  
  820. def __delete_existing_connection(self, ssid):
  821. """
  822. checks and deletes earlier connection
  823. """
  824. try:
  825. conns = self.settings.ListConnections()
  826. except dbus.exceptions.DBusException:
  827. print(Messages.dbus_error)
  828. exit(3)
  829. for each in conns:
  830. con_proxy = self.bus.get_object(self.system_service_name, each)
  831. connection = dbus.Interface(
  832. con_proxy,
  833. "org.freedesktop.NetworkManager.Settings.Connection")
  834. try:
  835. connection_settings = connection.GetSettings()
  836. if connection_settings['connection']['type'] == '802-11-' \
  837. 'wireless':
  838. conn_ssid = byte_to_string(
  839. connection_settings['802-11-wireless']['ssid'])
  840. if conn_ssid == ssid:
  841. debug("deleting connection: " + conn_ssid)
  842. connection.Delete()
  843. except dbus.exceptions.DBusException:
  844. pass
  845.  
  846. def __add_connection(self, ssid):
  847. debug("Adding connection: " + ssid)
  848. server_alt_subject_name_list = dbus.Array(Config.servers)
  849. server_name = Config.server_match
  850. if self.nm_version == "0.9" or self.nm_version == "1.0":
  851. match_key = 'altsubject-matches'
  852. match_value = server_alt_subject_name_list
  853. else:
  854. match_key = 'subject-match'
  855. match_value = server_name
  856. s_8021x_data = {
  857. 'eap': [Config.eap_outer.lower()],
  858. 'identity': self.user_data.username,
  859. 'ca-cert': dbus.ByteArray(
  860. "file://{0}\0".format(self.cacert_file).encode('utf8')),
  861. match_key: match_value}
  862. if Config.eap_outer == 'PEAP' or Config.eap_outer == 'TTLS':
  863. s_8021x_data['password'] = self.user_data.password
  864. s_8021x_data['phase2-auth'] = Config.eap_inner.lower()
  865. if Config.anonymous_identity != '':
  866. s_8021x_data['anonymous-identity'] = Config.anonymous_identity
  867. s_8021x_data['password-flags'] = 0
  868. if Config.eap_outer == 'TLS':
  869. s_8021x_data['client-cert'] = dbus.ByteArray(
  870. "file://{0}\0".format(self.pfx_file).encode('utf8'))
  871. s_8021x_data['private-key'] = dbus.ByteArray(
  872. "file://{0}\0".format(self.pfx_file).encode('utf8'))
  873. s_8021x_data['private-key-password'] = self.user_data.password
  874. s_8021x_data['private-key-password-flags'] = 0
  875. s_con = dbus.Dictionary({
  876. 'type': '802-11-wireless',
  877. 'uuid': str(uuid.uuid4()),
  878. 'permissions': ['user:' +
  879. os.environ.get('USER')],
  880. 'id': ssid
  881. })
  882. s_wifi = dbus.Dictionary({
  883. 'ssid': dbus.ByteArray(ssid.encode('utf8')),
  884. 'security': '802-11-wireless-security'
  885. })
  886. s_wsec = dbus.Dictionary({
  887. 'key-mgmt': 'wpa-eap',
  888. 'proto': ['rsn'],
  889. 'pairwise': ['ccmp'],
  890. 'group': ['ccmp', 'tkip']
  891. })
  892. s_8021x = dbus.Dictionary(s_8021x_data)
  893. s_ip4 = dbus.Dictionary({'method': 'auto'})
  894. s_ip6 = dbus.Dictionary({'method': 'auto'})
  895. con = dbus.Dictionary({
  896. 'connection': s_con,
  897. '802-11-wireless': s_wifi,
  898. '802-11-wireless-security': s_wsec,
  899. '802-1x': s_8021x,
  900. 'ipv4': s_ip4,
  901. 'ipv6': s_ip6
  902. })
  903. self.settings.AddConnection(con)
  904.  
  905. def add_connections(self, user_data):
  906. """Delete and then add connections to the system"""
  907. self.__check_opts()
  908. self.user_data = user_data
  909. for ssid in Config.ssids:
  910. self.__delete_existing_connection(ssid)
  911. self.__add_connection(ssid)
  912. for ssid in Config.del_ssids:
  913. self.__delete_existing_connection(ssid)
  914.  
  915.  
  916. Messages.quit = "Wirklich beenden?"
  917. Messages.username_prompt = "Geben Sie ihre Benutzerkennung ein"
  918. Messages.enter_password = "Geben Sie ihr Passwort ein"
  919. Messages.enter_import_password = "Geben Sie ihr Import-Passwort ein"
  920. Messages.incorrect_password = "falsches Passwort"
  921. Messages.repeat_password = "Wiederholen Sie das Passwort"
  922. Messages.passwords_differ = "Die Passwörter stimmen nicht überein"
  923. Messages.installation_finished = "Installation erfolgreich"
  924. Messages.cat_dir_exisits = "Das Verzeichnis {} existiert bereits; " \
  925. "einige Dateien darin könnten überschrieben werden."
  926. Messages.cont = "Weiter?"
  927. Messages.nm_not_supported = "Diese NetworkManager Version wird nicht " \
  928. "unterstützt."
  929. Messages.cert_error = "Die Zertifikats-Datei konnte nicht gefunden " \
  930. "werden. Dies sieht aus wie ein Fehler des CAT."
  931. Messages.unknown_version = "Unbekannte Version"
  932. Messages.dbus_error = "Ein Problem mit der DBus-Verbindung. Eventuell " \
  933. "hilft es, das Programm mit sudo aufzurufen."
  934. Messages.yes = "J"
  935. Messages.no = "N"
  936. Messages.p12_filter = "Persönliche Zertifikatsdatei (p12 oder pfx)"
  937. Messages.all_filter = "Alle Dateien"
  938. Messages.p12_title = "Persönliche Zertifikatsdatei (p12 oder pfx)"
  939. Messages.save_wpa_conf = "Die Konfiguration von NetworkManager ist " \
  940. "fehlgeschlagen, aber es könnte stattdessen eine Konfigurationsdatei " \
  941. "für das Programm wpa_supplicant erstellt werden wenn Sie das " \
  942. "wünschen. Beachten Sie bitte, dass Ihr Passwort im Klartext in dieser " \
  943. "Datei steht."
  944. Messages.save_wpa_confirm = "Datei schreiben"
  945. Messages.wrongUsernameFormat = "Fehler: Ihr Benutzername muss in der " \
  946. "Form 'xxx@institutsID' sein; zB 'erika.mustermann@example.net'."
  947. Messages.wrong_realm = "Fehler: Ihr Benutzername muss in der Form " \
  948. "'xxx@{}' sein. Bitte verwenden Sie das korrekte Format."
  949. Messages.wrong_realm_suffix = "Fehler: Ihr Benutzername muss in der " \
  950. "Form 'xxx@institutsID' sein und auf '{}' enden. Bitte verwenden Sie " \
  951. "das korrekte Format."
  952. Messages.user_cert_missing = "Persönliches Zertifikat nicht gefunden"
  953. Config.instname = "Leibniz-Rechenzentrum (LRZ) der Bayerischen " \
  954. "Akademie der Wissenschaften"
  955. Config.profilename = "eduroam.mwn.de"
  956. Config.url = "https://servicedesk.lrz.de"
  957. Config.email = "servicedesk@lrz.de"
  958. Config.title = "eduroam CAT"
  959. Config.server_match = "radius.lrz.de"
  960. Config.eap_outer = "PEAP"
  961. Config.eap_inner = "MSCHAPV2"
  962. Config.init_info = "Dieses Installationsprogramm wurde für {0} " \
  963. "hergestellt.\n\nMehr Informationen und Kommentare:\n\nEMAIL: {1}\nWWW: " \
  964. "{2}\n\nDas Installationsprogramm wurde mit Software vom GEANT Projekt " \
  965. "erstellt."
  966. Config.init_confirmation = "Dieses Installationsprogramm funktioniert " \
  967. "nur für Anwender von {0} in der Benutzergruppe: {1}."
  968. Config.user_realm = "eduroam.mwn.de"
  969. Config.ssids = ['eduroam']
  970. Config.del_ssids = []
  971. Config.servers = ['DNS:radius.lrz.de']
  972. Config.use_other_tls_id = False
  973. Config.anonymous_identity = "anonymouscat@eduroam.mwn.de"
  974. Config.tou = ""
  975. Config.CA = """-----BEGIN CERTIFICATE-----
  976. MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
  977. KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
  978. BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
  979. YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
  980. OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
  981. aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
  982. ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
  983. CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
  984. AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
  985. FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
  986. 1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
  987. jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
  988. wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
  989. QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
  990. WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
  991. NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
  992. uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
  993. IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
  994. g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
  995. 9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
  996. BSeOE6Fuwg==
  997. -----END CERTIFICATE-----
  998. """
  999. run_installer()
  1000.  
Add Comment
Please, Sign In to add comment