settings.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  1. #!/usr/bin/python2
  2. #
  3. # The Qubes OS Project, http://www.qubes-os.org
  4. #
  5. # Copyright (C) 2012 Agnieszka Kostrzewa <agnieszka.kostrzewa@gmail.com>
  6. # Copyright (C) 2012 Marek Marczykowski-Górecki
  7. # <marmarek@invisiblethingslab.com>
  8. # Copyright (C) 2017 Wojtek Porczyk <woju@invisiblethingslab.com>
  9. #
  10. # This program is free software; you can redistribute it and/or
  11. # modify it under the terms of the GNU General Public License
  12. # as published by the Free Software Foundation; either version 2
  13. # of the License, or (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with this program; if not, write to the Free Software
  22. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  23. #
  24. #
  25. import collections
  26. import copy
  27. import os
  28. import subprocess
  29. import qubesadmin
  30. import qubesadmin.tools
  31. from . import utils
  32. from .ui_settingsdlg import *
  33. from .appmenu_select import *
  34. from .firewall import *
  35. from .backup_utils import get_path_for_vm
  36. class VMSettingsWindow(Ui_SettingsDialog, QDialog):
  37. tabs_indices = collections.OrderedDict((
  38. ('basic', 0),
  39. ('advanced', 1),
  40. ('firewall', 2),
  41. ('devices', 3),
  42. ('applications', 4),
  43. ('services', 5),
  44. ))
  45. def __init__(self, vm, qapp, init_page="basic", parent=None):
  46. super(VMSettingsWindow, self).__init__(parent)
  47. self.vm = vm
  48. self.qapp = qapp
  49. if self.vm.template:
  50. self.source_vm = self.vm.template
  51. else:
  52. self.source_vm = self.vm
  53. self.setupUi(self)
  54. self.setWindowTitle(self.tr("Settings: {vm}").format(vm=self.vm.name))
  55. if init_page in self.tabs_indices:
  56. idx = self.tabs_indices[init_page]
  57. assert (idx in range(self.tabWidget.count()))
  58. self.tabWidget.setCurrentIndex(idx)
  59. self.connect(self.buttonBox, SIGNAL("accepted()"), self.save_and_apply)
  60. self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject)
  61. self.tabWidget.currentChanged.connect(self.current_tab_changed)
  62. self.tabWidget.setTabEnabled(self.tabs_indices["firewall"], vm.is_networked() and not (vm.is_netvm() and not vm.is_proxyvm()))
  63. ###### basic tab
  64. self.__init_basic_tab__()
  65. ###### advanced tab
  66. self.__init_advanced_tab__()
  67. self.include_in_balancing.stateChanged.connect(self.include_in_balancing_state_changed)
  68. self.connect(self.init_mem, SIGNAL("editingFinished()"), self.check_mem_changes)
  69. self.connect(self.max_mem_size, SIGNAL("editingFinished()"), self.check_mem_changes)
  70. self.drive_path_button.clicked.connect(self.drive_path_button_pressed)
  71. ###### firewall tab
  72. if self.tabWidget.isTabEnabled(self.tabs_indices["firewall"]):
  73. model = QubesFirewallRulesModel()
  74. model.set_vm(vm)
  75. self.set_fw_model(model)
  76. self.newRuleButton.clicked.connect(self.new_rule_button_pressed)
  77. self.editRuleButton.clicked.connect(self.edit_rule_button_pressed)
  78. self.deleteRuleButton.clicked.connect(self.delete_rule_button_pressed)
  79. self.policyDenyRadioButton.clicked.connect(self.policy_changed)
  80. self.policyAllowRadioButton.clicked.connect(self.policy_changed)
  81. ####### devices tab
  82. self.__init_devices_tab__()
  83. self.connect(self.dev_list, SIGNAL("selected_changed()"), self.devices_selection_changed)
  84. ####### services tab
  85. self.__init_services_tab__()
  86. self.add_srv_button.clicked.connect(self.__add_service__)
  87. self.remove_srv_button.clicked.connect(self.__remove_service__)
  88. ####### apps tab
  89. if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]):
  90. self.app_list = MultiSelectWidget(self)
  91. self.apps_layout.addWidget(self.app_list)
  92. self.AppListManager = AppmenuSelectManager(self.vm, self.app_list)
  93. def reject(self):
  94. self.done(0)
  95. #needed not to close the dialog before applying changes
  96. def accept(self):
  97. pass
  98. def save_and_apply(self):
  99. thread_monitor = ThreadMonitor()
  100. thread = threading.Thread (target=self.__save_changes__, args=(thread_monitor,))
  101. thread.daemon = True
  102. thread.start()
  103. progress = QProgressDialog(
  104. self.tr("Applying settings to <b>{0}</b>...").format(self.vm.name), "", 0, 0)
  105. progress.setCancelButton(None)
  106. progress.setModal(True)
  107. progress.show()
  108. while not thread_monitor.is_finished():
  109. self.qapp.processEvents()
  110. time.sleep (0.1)
  111. progress.hide()
  112. if not thread_monitor.success:
  113. QMessageBox.warning(None,
  114. self.tr("Error while changing settings for {0}!").format(self.vm.name),
  115. self.tr("ERROR: {0}").format(thread_monitor.error_msg))
  116. self.done(0)
  117. def __save_changes__(self, thread_monitor):
  118. self.anything_changed = False
  119. ret = []
  120. try:
  121. ret_tmp = self.__apply_basic_tab__()
  122. if len(ret_tmp) > 0:
  123. ret += ["Basic tab:"] + ret_tmp
  124. ret_tmp = self.__apply_advanced_tab__()
  125. if len(ret_tmp) > 0:
  126. ret += ["Advanced tab:"] + ret_tmp
  127. ret_tmp = self.__apply_devices_tab__()
  128. if len(ret_tmp) > 0:
  129. ret += ["Devices tab:"] + ret_tmp
  130. ret_tmp = self.__apply_services_tab__()
  131. if len(ret_tmp) > 0:
  132. ret += ["Sevices tab:"] + ret_tmp
  133. except Exception as ex:
  134. ret.append(self.tr('Error while saving changes: ') + str(ex))
  135. try:
  136. if self.tabWidget.isTabEnabled(self.tabs_indices["firewall"]):
  137. self.fw_model.apply_rules(self.policyAllowRadioButton.isChecked(),
  138. self.dnsCheckBox.isChecked(),
  139. self.icmpCheckBox.isChecked(),
  140. self.yumproxyCheckBox.isChecked(),
  141. self.tempFullAccess.isChecked(),
  142. self.tempFullAccessTime.value())
  143. if self.fw_model.fw_changed:
  144. # might modified vm.services
  145. self.anything_changed = True
  146. except Exception as ex:
  147. ret += [self.tr("Firewall tab:"), str(ex)]
  148. try:
  149. if self.tabWidget.isTabEnabled(self.tabs_indices["applications"]):
  150. self.AppListManager.save_appmenu_select_changes()
  151. except Exception as ex:
  152. ret += [self.tr("Applications tab:"), str(ex)]
  153. if len(ret) > 0 :
  154. thread_monitor.set_error_msg('\n'.join(ret))
  155. utils.debug('\n'.join(ret))
  156. def current_tab_changed(self, idx):
  157. if idx == self.tabs_indices["firewall"]:
  158. if self.vm.netvm is not None and not self.vm.netvm.is_proxyvm():
  159. QMessageBox.warning(None,
  160. self.tr("VM configuration problem!"),
  161. self.tr("The '{vm}' AppVM is not network connected to a "
  162. "FirewallVM!<p>"
  163. "You may edit the '{vm}' VM firewall rules, but these "
  164. "will not take any effect until you connect it to "
  165. "a working Firewall VM.").format(vm=self.vm.name))
  166. ######### basic tab
  167. # TODO LISTENERS
  168. # - vm start/shutdown -> setEnabled on fields: template labels
  169. # - vm create/delete -> choices lists, whole window deactiv (if self.vm)
  170. # - property-set -> individual fields
  171. # TODO INTERACTIONS
  172. # netvm -> networking_groupbox
  173. # hvm -> include_in_balancing
  174. # TODO REMOVE
  175. # other_groupbox
  176. def __init_basic_tab__(self):
  177. self.vmname.setText(self.vm.name)
  178. self.vmname.setValidator(QRegExpValidator(QRegExp("[a-zA-Z0-9-]*", Qt.CaseInsensitive), None))
  179. self.vmname.setEnabled(False)
  180. if self.vm.qid == 0:
  181. self.vmlabel.setVisible(False)
  182. else:
  183. self.label_list, self.label_idx = utils.prepare_label_choice(
  184. self.vmlabel,
  185. self.vm, 'label',
  186. None,
  187. allow_default=False
  188. )
  189. self.vmlabel.setVisible(True)
  190. self.vmlabel.setEnabled(not self.vm.is_running())
  191. if isinstance(self.vm, qubesadmin.vm.AppVM):
  192. self.template_list, self.template_idx = utils.prepare_vm_choice(
  193. self.template_name,
  194. self.vm, 'template',
  195. self.vm.app.default_template,
  196. (lambda vm: isinstance(vm, qubesadmin.vm.TemplateVM)),
  197. allow_default=True, allow_none=False)
  198. else:
  199. self.template_name.setEnabled(False)
  200. self.template_idx = -1
  201. self.netvm_list, self.netvm_idx = utils.prepare_vm_choice(
  202. self.netVM,
  203. self.vm, 'netvm',
  204. self.vm.app.default_netvm,
  205. (lambda vm: vm.provides_network),
  206. allow_default=True, allow_none=True)
  207. self.include_in_backups.setChecked(self.vm.include_in_backups)
  208. try:
  209. self.run_in_debug_mode.setChecked(self.vm.debug)
  210. self.run_in_debug_mode.setVisible(True)
  211. except AttributeError:
  212. self.run_in_debug_mode.setVisible(False)
  213. try:
  214. self.autostart_vm.setChecked(self.vm.autostart)
  215. self.autostart_vm.setVisible(True)
  216. except AttributeError:
  217. self.autostart_vm.setVisible(False)
  218. # XXX - False?
  219. self.seamless_gui.setVisible(True)
  220. self.seamless_gui.setChecked(
  221. self.vm.features.get('seamless-gui', False))
  222. #type
  223. self.type_label.setText(type(self.vm).__name__)
  224. #installed by rpm
  225. self.rpm_label.setText('Yes' if self.vm.installed_by_rpm else 'No')
  226. #networking info
  227. if self.vm.netvm:
  228. self.networking_groupbox.setEnabled(True)
  229. self.ip_label.setText(self.vm.ip if self.vm.ip is not None else "none")
  230. self.netmask_label.setText(self.vm.netmask if self.vm.netmask is not None else "none")
  231. self.gateway_label.setText(self.vm.netvm.gateway if self.vm.netvm is not None else "none")
  232. else:
  233. self.networking_groupbox.setEnabled(False)
  234. #max priv storage
  235. # self.priv_img_size = self.vm.get_private_img_sz()/1024/1024
  236. # self.max_priv_storage.setMinimum(self.priv_img_size)
  237. # self.max_priv_storage.setValue(self.priv_img_size)
  238. # self.root_img_size = self.vm.get_root_img_sz()/1024/1024
  239. # self.root_resize.setValue(self.root_img_size)
  240. # self.root_resize.setMinimum(self.root_img_size)
  241. # self.root_resize.setEnabled(hasattr(self.vm, 'resize_root_img') and
  242. # not self.vm.template)
  243. # self.root_resize_label.setEnabled(self.root_resize.isEnabled())
  244. def __apply_basic_tab__(self):
  245. msg = []
  246. #vm label changed
  247. try:
  248. if self.vmlabel.isVisible():
  249. if self.vmlabel.currentIndex() != self.label_idx:
  250. label = self.label_list[self.vmlabel.currentIndex()]
  251. self.vm.label = label
  252. self.anything_changed = True
  253. except Exception as ex:
  254. msg.append(str(ex))
  255. #vm template changed
  256. try:
  257. if self.template_name.currentIndex() != self.template_idx:
  258. self.vm.template = \
  259. self.template_list[self.template_name.currentIndex()]
  260. self.anything_changed = True
  261. except Exception as ex:
  262. msg.append(str(ex))
  263. #vm netvm changed
  264. try:
  265. if self.netVM.currentIndex() != self.netvm_idx:
  266. self.vm.netvm = self.netvm_list[self.netVM.currentIndex()]
  267. self.anything_changed = True
  268. except Exception as ex:
  269. msg.append(str(ex))
  270. #include in backups
  271. try:
  272. if self.vm.include_in_backups != self.include_in_backups.isChecked():
  273. self.vm.include_in_backups = self.include_in_backups.isChecked()
  274. self.anything_changed = True
  275. except Exception as ex:
  276. msg.append(str(ex))
  277. #run_in_debug_mode
  278. try:
  279. if self.run_in_debug_mode.isVisible():
  280. if self.vm.debug != self.run_in_debug_mode.isChecked():
  281. self.vm.debug = self.run_in_debug_mode.isChecked()
  282. self.anything_changed = True
  283. except Exception as ex:
  284. msg.append(str(ex))
  285. #autostart_vm
  286. try:
  287. if self.autostart_vm.isVisible():
  288. if self.vm.autostart != self.autostart_vm.isChecked():
  289. self.vm.autostart = self.autostart_vm.isChecked()
  290. self.anything_changed = True
  291. except Exception as ex:
  292. msg.append(str(ex))
  293. #seamless_gui
  294. try:
  295. if self.seamless_gui.isVisible():
  296. if self.vm.seamless_gui_mode != self.seamless_gui.isChecked():
  297. self.vm.seamless_gui_mode = self.seamless_gui.isChecked()
  298. self.anything_changed = True
  299. except Exception as ex:
  300. msg.append(str(ex))
  301. #max priv storage
  302. priv_size = self.max_priv_storage.value()
  303. if self.priv_img_size != priv_size:
  304. try:
  305. self.vm.resize_private_img(priv_size*1024*1024)
  306. self.anything_changed = True
  307. except Exception as ex:
  308. msg.append(str(ex))
  309. #max sys storage
  310. sys_size = self.root_resize.value()
  311. if self.root_img_size != sys_size:
  312. try:
  313. self.vm.resize_root_img(sys_size*1024*1024)
  314. self.anything_changed = True
  315. except Exception as ex:
  316. msg.append(str(ex))
  317. return msg
  318. def check_mem_changes(self):
  319. if self.max_mem_size.value() < self.init_mem.value():
  320. QMessageBox.warning(None,
  321. self.tr("Warning!"),
  322. self.tr("Max memory can not be less than initial memory.<br>"
  323. "Setting max memory to equal initial memory."))
  324. self.max_mem_size.setValue(self.init_mem.value())
  325. # Linux specific limit: init memory must not be below max_mem_size/10.79 in order to allow scaling up to max_mem_size (or else "add_memory() failed: -17" problem)
  326. if self.init_mem.value() * 10 < self.max_mem_size.value():
  327. QMessageBox.warning(None,
  328. self.tr("Warning!"),
  329. self.tr("Initial memory can not be less than one tenth "
  330. "Max memory.<br>Setting initial memory to the minimum "
  331. "allowed value."))
  332. self.init_mem.setValue(self.max_mem_size.value() / 10)
  333. ######### advanced tab
  334. def __init_advanced_tab__(self):
  335. #mem/cpu
  336. # qubes_memory = QubesHost().memory_total/1024
  337. self.init_mem.setValue(int(self.vm.memory))
  338. # self.init_mem.setMaximum(qubes_memory)
  339. self.max_mem_size.setValue(int(self.vm.maxmem))
  340. # self.max_mem_size.setMaximum(qubes_memory)
  341. self.vcpus.setMinimum(1)
  342. # self.vcpus.setMaximum(QubesHost().no_cpus)
  343. self.vcpus.setValue(int(self.vm.vcpus))
  344. self.include_in_balancing.setEnabled(not self.vm.hvm)
  345. self.include_in_balancing.setChecked(not self.vm.hvm
  346. and self.vm.features.get('services/meminfo-writer', True))
  347. self.max_mem_size.setEnabled(self.include_in_balancing.isChecked())
  348. #paths
  349. self.dir_path.setText(self.vm.dir_path)
  350. self.config_path.setText(self.vm.conf_file)
  351. try:
  352. self.root_img_path.setText(self.vm.template.root_img)
  353. except AttributeError:
  354. pass
  355. try:
  356. self.root_img_path.setText('{volume.pool}:{volume.vid}'.format(
  357. volume=self.vm.volumes['root']))
  358. except AttributeError:
  359. self.root_img_path.setText("n/a")
  360. try:
  361. self.volatile_img_path.setText('{volume.pool}:{volume.vid}'.format(
  362. volume=self.vm.volumes['volatile']))
  363. except AttributeError:
  364. self.volatile_img_path.setText('n/a')
  365. self.private_img_path.setText('{volume.pool}:{volume.vid}'.format(
  366. volume=self.vm.volumes['private']))
  367. #kernel
  368. #in case VM is HVM
  369. if hasattr(self.vm, "kernel"):
  370. self.kernel_groupbox.setVisible(True)
  371. self.kernel_list, self.kernel_idx = utils.prepare_kernel_choice(
  372. self.kernel, self.vm, 'kernel',
  373. self.vm.app.default_kernel,
  374. allow_default=True, allow_none=True)
  375. else:
  376. self.kernel_groupbox.setVisible(False)
  377. if not hasattr(self.vm, "drive"):
  378. self.drive_groupbox.setVisible(False)
  379. else:
  380. self.drive_groupbox.setVisible(True)
  381. self.drive_groupbox.setChecked(self.vm.drive is not None)
  382. self.drive_running_warning.setVisible(self.vm.is_running())
  383. self.drive_type.addItems(["hd", "cdrom"])
  384. self.drive_type.setCurrentIndex(0)
  385. vm_list = [vm for vm in self.app.values() if not vm
  386. .internal and vm.qid != self.vm.qid]
  387. # default to dom0 (in case of nonexisting vm already set...)
  388. self.drive_domain_idx = 0
  389. for (i, vm) in enumerate(sorted(vm_list, key=lambda v: v.name)):
  390. if vm.qid == 0:
  391. self.drive_domain_idx = i
  392. self.drive_domain.insertItem(i, vm.name)
  393. if self.vm.drive is not None:
  394. (drv_type, drv_domain, drv_path) = self.vm.drive.split(":")
  395. if drv_type == "cdrom":
  396. self.drive_type.setCurrentIndex(1)
  397. else:
  398. self.drive_type.setCurrentIndex(0)
  399. for i in xrange(self.drive_domain.count()):
  400. if drv_domain == self.drive_domain.itemText(i):
  401. self.drive_domain_idx = i
  402. self.drive_path.setText(drv_path)
  403. self.drive_domain.setCurrentIndex(self.drive_domain_idx)
  404. if not hasattr(self.vm, "dispvm_netvm"):
  405. self.other_groupbox.setVisible(False)
  406. else:
  407. self.other_groupbox.setVisible(True)
  408. netvm_list = [vm for vm in self.app.values() if not vm.internal and vm.is_netvm() and vm.qid != 0]
  409. self.dispvm_netvm_idx = -1
  410. text = "default (same as VM own NetVM)"
  411. if self.vm.uses_default_dispvm_netvm:
  412. text += self.tr(" (current)")
  413. self.dispvm_netvm_idx = 0
  414. self.dispvm_netvm.insertItem(0, text)
  415. for (i, vm) in enumerate(netvm_list):
  416. text = vm.name
  417. if self.vm.dispvm_netvm is not None and vm.qid == \
  418. self.vm.dispvm_netvm.qid and not \
  419. self.vm.uses_default_dispvm_netvm:
  420. self.dispvm_netvm_idx = i+1
  421. text += self.tr(" (current)")
  422. self.dispvm_netvm.insertItem(i+1, text)
  423. none_text = "none"
  424. if self.vm.dispvm_netvm is None:
  425. none_text += self.tr(" (current)")
  426. self.dispvm_netvm_idx = len(netvm_list)+1
  427. self.dispvm_netvm.insertItem(len(netvm_list)+1, none_text)
  428. self.dispvm_netvm.setCurrentIndex(self.dispvm_netvm_idx)
  429. def __apply_advanced_tab__(self):
  430. msg = []
  431. #mem/cpu
  432. try:
  433. if self.init_mem.value() != int(self.vm.memory):
  434. self.vm.memory = self.init_mem.value()
  435. self.anything_changed = True
  436. if self.max_mem_size.value() != int(self.vm.maxmem):
  437. self.vm.maxmem = self.max_mem_size.value()
  438. self.anything_changed = True
  439. if self.vcpus.value() != int(self.vm.vcpus):
  440. self.vm.vcpus = self.vcpus.value()
  441. self.anything_changed = True
  442. except Exception as ex:
  443. msg.append(str(ex))
  444. #include_in_memory_balancing applied in services tab
  445. #in case VM is not Linux
  446. if hasattr(self.vm, "kernel") and self.kernel_groupbox.isVisible():
  447. try:
  448. if self.kernel.currentIndex() != self.kernel_idx:
  449. new_kernel = str(self.kernel.currentText())
  450. new_kernel = new_kernel.split(' ')[0]
  451. uses_default_kernel = False
  452. if new_kernel == "default":
  453. kernel = self.app.get_default_kernel()
  454. uses_default_kernel = True
  455. elif new_kernel == "none":
  456. kernel = None
  457. else:
  458. kernel = new_kernel
  459. self.vm.kernel = kernel
  460. # Set self.vm.uses_default_kernel after self.vm.kernel to ensure that
  461. # the correct value persists after QubesVm.kernel resets self.vm.uses_default_kernel
  462. # to False.
  463. self.vm.uses_default_kernel = uses_default_kernel
  464. self.anything_changed = True
  465. except Exception as ex:
  466. msg.append(str(ex))
  467. if hasattr(self.vm, "drive") and self.drive_groupbox.isVisible():
  468. try:
  469. if not self.drive_groupbox.isChecked():
  470. if self.vm.drive != None:
  471. self.vm.drive = None
  472. self.anything_changed = True
  473. else:
  474. new_domain = str(self.drive_domain.currentText())
  475. if self.drive_domain.currentIndex() == self.drive_domain_idx:
  476. # strip "(current)"
  477. new_domain = new_domain.split(' ')[0]
  478. drive = "%s:%s:%s" % (
  479. str(self.drive_type.currentText()),
  480. new_domain,
  481. str(self.drive_path.text())
  482. )
  483. if self.vm.drive != drive:
  484. self.vm.drive = drive
  485. self.anything_changed = True
  486. except Exception as ex:
  487. msg.append(str(ex))
  488. #vm dispvm_netvm changed
  489. try:
  490. if self.dispvm_netvm.currentIndex() != self.dispvm_netvm_idx:
  491. new_dispvm_netvm_name = str(self.dispvm_netvm.currentText())
  492. new_dispvm_netvm_name = new_dispvm_netvm_name.split(' ')[0]
  493. uses_default_dispvm_netvm = False
  494. if new_dispvm_netvm_name == "default":
  495. uses_default_dispvm_netvm = True
  496. if new_dispvm_netvm_name == "none":
  497. dispvm_netvm = None
  498. else:
  499. dispvm_netvm = self.app.get_vm_by_name(
  500. new_dispvm_netvm_name)
  501. assert (dispvm_netvm is None or (dispvm_netvm.qid in
  502. self.app and dispvm_netvm.is_netvm()))
  503. if uses_default_dispvm_netvm:
  504. self.vm.uses_default_dispvm_netvm = True
  505. else:
  506. self.vm.uses_default_dispvm_netvm = False
  507. self.vm.dispvm_netvm = dispvm_netvm
  508. self.anything_changed = True
  509. except Exception as ex:
  510. if utils.is_debug():
  511. traceback.print_exc()
  512. msg.append(str(ex))
  513. return msg
  514. def drive_path_button_pressed(self):
  515. if str(self.drive_domain.currentText()) in ["dom0", "dom0 (current)"]:
  516. file_dialog = QFileDialog()
  517. file_dialog.setReadOnly(True)
  518. new_path = file_dialog.getOpenFileName(self, "Select drive image",
  519. str(self.drive_path.text()))
  520. else:
  521. drv_domain = str(self.drive_domain.currentText())
  522. if drv_domain.count("(current)") > 0:
  523. drv_domain = drv_domain.split(' ')[0]
  524. backend_vm = self.app.get_vm_by_name(drv_domain)
  525. if backend_vm:
  526. new_path = get_path_for_vm(backend_vm, "qubes.SelectFile")
  527. if new_path:
  528. self.drive_path.setText(new_path)
  529. ######## devices tab
  530. def __init_devices_tab__(self):
  531. self.dev_list = MultiSelectWidget(self)
  532. self.dev_list.add_all_button.setVisible(False)
  533. self.devices_layout.addWidget(self.dev_list)
  534. devs = []
  535. lspci = subprocess.Popen(["/usr/sbin/lspci",], stdout = subprocess.PIPE)
  536. for dev in lspci.stdout:
  537. devs.append( (dev.rstrip(), dev.split(' ')[0]) )
  538. class DevListWidgetItem(QListWidgetItem):
  539. def __init__(self, name, slot, parent = None):
  540. super(DevListWidgetItem, self).__init__(name, parent)
  541. self.slot = slot
  542. self.Type
  543. for d in devs:
  544. if d[1] in self.vm.pcidevs:
  545. self.dev_list.selected_list.addItem( DevListWidgetItem(d[0], d[1]))
  546. else:
  547. self.dev_list.available_list.addItem( DevListWidgetItem(d[0], d[1]))
  548. if self.dev_list.selected_list.count() > 0 and self.include_in_balancing.isChecked():
  549. self.dmm_warning_adv.show()
  550. self.dmm_warning_dev.show()
  551. else:
  552. self.dmm_warning_adv.hide()
  553. self.dmm_warning_dev.hide()
  554. if self.vm.is_running():
  555. self.dev_list.setEnabled(False)
  556. self.turn_off_vm_to_modify_devs.setVisible(True)
  557. else:
  558. self.dev_list.setEnabled(True)
  559. self.turn_off_vm_to_modify_devs.setVisible(False)
  560. def __apply_devices_tab__(self):
  561. msg = []
  562. sth_changed = False
  563. added = []
  564. try:
  565. for i in range(self.dev_list.selected_list.count()):
  566. item = self.dev_list.selected_list.item(i)
  567. if item.slot not in self.vm.pcidevs:
  568. added.append(item)
  569. if self.dev_list.selected_list.count() - len(added) < len(self.vm.pcidevs): #sth removed
  570. sth_changed = True
  571. elif len(added) > 0:
  572. sth_changed = True
  573. if sth_changed == True:
  574. pcidevs = []
  575. for i in range(self.dev_list.selected_list.count()):
  576. slot = self.dev_list.selected_list.item(i).slot
  577. pcidevs.append(slot)
  578. self.vm.pcidevs = pcidevs
  579. self.anything_changed = True
  580. except Exception as ex:
  581. if utils.is_debug():
  582. traceback.print_exc()
  583. msg.append(str(ex))
  584. return msg
  585. def include_in_balancing_state_changed(self, state):
  586. for r in range (self.services_list.count()):
  587. item = self.services_list.item(r)
  588. if str(item.text()) == 'meminfo-writer':
  589. item.setCheckState(state)
  590. break
  591. if self.dev_list.selected_list.count() > 0:
  592. if state == QtCore.Qt.Checked:
  593. self.dmm_warning_adv.show()
  594. self.dmm_warning_dev.show()
  595. else:
  596. self.dmm_warning_adv.hide()
  597. self.dmm_warning_dev.hide()
  598. self.max_mem_size.setEnabled(self.include_in_balancing.isChecked())
  599. def devices_selection_changed(self):
  600. if self.include_in_balancing.isChecked():
  601. if self.dev_list.selected_list.count() > 0 :
  602. self.dmm_warning_adv.show()
  603. self.dmm_warning_dev.show()
  604. else:
  605. self.dmm_warning_adv.hide()
  606. self.dmm_warning_dev.hide()
  607. ######## services tab
  608. def __init_services_tab__(self):
  609. for srv in self.vm.services:
  610. item = QListWidgetItem(srv)
  611. if self.vm.services[srv] == True:
  612. item.setCheckState(QtCore.Qt.Checked)
  613. else:
  614. item.setCheckState(QtCore.Qt.Unchecked)
  615. self.services_list.addItem(item)
  616. self.connect(self.services_list, SIGNAL("itemClicked(QListWidgetItem *)"), self.services_item_clicked)
  617. self.new_srv_dict = copy.copy(self.vm.services)
  618. def __add_service__(self):
  619. srv = str(self.service_line_edit.text()).strip()
  620. if srv != "":
  621. if srv in self.new_srv_dict:
  622. QMessageBox.information(None, "",
  623. self.tr("Service already on the list!"))
  624. else:
  625. item = QListWidgetItem(srv)
  626. item.setCheckState(QtCore.Qt.Checked)
  627. self.services_list.addItem(item)
  628. self.new_srv_dict[srv] = True
  629. def __remove_service__(self):
  630. item = self.services_list.currentItem()
  631. if not item:
  632. return
  633. if str(item.text()) == 'meminfo-writer':
  634. QMessageBox.information(None,
  635. self.tr("Service can not be removed"),
  636. self.tr("Service meminfo-writer can not be removed from the list."))
  637. return
  638. row = self.services_list.currentRow()
  639. item = self.services_list.takeItem(row)
  640. del self.new_srv_dict[str(item.text())]
  641. def services_item_clicked(self, item):
  642. if str(item.text()) == 'meminfo-writer':
  643. if item.checkState() == QtCore.Qt.Checked:
  644. if not self.include_in_balancing.isChecked():
  645. self.include_in_balancing.setChecked(True)
  646. elif item.checkState() == QtCore.Qt.Unchecked:
  647. if self.include_in_balancing.isChecked():
  648. self.include_in_balancing.setChecked(False)
  649. def __apply_services_tab__(self):
  650. msg = []
  651. try:
  652. for r in range (self.services_list.count()):
  653. item = self.services_list.item(r)
  654. self.new_srv_dict[str(item.text())] = (item.checkState() == QtCore.Qt.Checked)
  655. balancing_was_checked = (self.vm.services['meminfo-writer']==True)
  656. balancing_is_checked = self.include_in_balancing.isChecked()
  657. meminfo_writer_checked = (self.new_srv_dict['meminfo-writer'] == True)
  658. if balancing_is_checked != meminfo_writer_checked:
  659. if balancing_is_checked != balancing_was_checked:
  660. self.new_srv_dict['meminfo-writer'] = balancing_is_checked
  661. if self.new_srv_dict != self.vm.services:
  662. self.vm.services = self.new_srv_dict
  663. self.anything_changed = True
  664. except Exception as ex:
  665. msg.append(str(ex))
  666. return msg
  667. ######### firewall tab related
  668. def set_fw_model(self, model):
  669. self.fw_model = model
  670. self.rulesTreeView.setModel(model)
  671. self.rulesTreeView.header().setResizeMode(QHeaderView.ResizeToContents)
  672. self.rulesTreeView.header().setResizeMode(0, QHeaderView.Stretch)
  673. self.set_allow(model.allow)
  674. self.dnsCheckBox.setChecked(model.allowDns)
  675. self.icmpCheckBox.setChecked(model.allowIcmp)
  676. self.yumproxyCheckBox.setChecked(model.allowYumProxy)
  677. if model.tempFullAccessExpireTime:
  678. self.tempFullAccess.setChecked(True)
  679. self.tempFullAccessTime.setValue(
  680. (model.tempFullAccessExpireTime -
  681. int(datetime.datetime.now().strftime("%s")))/60)
  682. def set_allow(self, allow):
  683. self.policyAllowRadioButton.setChecked(allow)
  684. self.policyDenyRadioButton.setChecked(not allow)
  685. self.policy_changed(allow)
  686. def policy_changed(self, checked):
  687. self.tempFullAccessWidget.setEnabled(self.policyDenyRadioButton.isChecked())
  688. def new_rule_button_pressed(self):
  689. dialog = NewFwRuleDlg()
  690. self.run_rule_dialog(dialog)
  691. def edit_rule_button_pressed(self):
  692. dialog = NewFwRuleDlg()
  693. dialog.set_ok_enabled(True)
  694. selected = self.rulesTreeView.selectedIndexes()
  695. if len(selected) > 0:
  696. row = self.rulesTreeView.selectedIndexes().pop().row()
  697. address = self.fw_model.get_column_string(0, row).replace(' ', '')
  698. dialog.addressComboBox.setItemText(0, address)
  699. dialog.addressComboBox.setCurrentIndex(0)
  700. service = self.fw_model.get_column_string(1, row)
  701. if service == "any":
  702. service = ""
  703. dialog.serviceComboBox.setItemText(0, service)
  704. dialog.serviceComboBox.setCurrentIndex(0)
  705. protocol = self.fw_model.get_column_string(2, row)
  706. if protocol == "tcp":
  707. dialog.tcp_radio.setChecked(True)
  708. elif protocol == "udp":
  709. dialog.udp_radio.setChecked(True)
  710. else:
  711. dialog.any_radio.setChecked(True)
  712. self.run_rule_dialog(dialog, row)
  713. def delete_rule_button_pressed(self):
  714. for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]):
  715. self.fw_model.removeChild(i)
  716. def run_rule_dialog(self, dialog, row = None):
  717. if dialog.exec_():
  718. address = str(dialog.addressComboBox.currentText())
  719. service = str(dialog.serviceComboBox.currentText())
  720. port = None
  721. port2 = None
  722. unmask = address.split("/", 1)
  723. if len(unmask) == 2:
  724. address = unmask[0]
  725. netmask = int(unmask[1])
  726. else:
  727. netmask = 32
  728. if address == "*":
  729. address = "0.0.0.0"
  730. netmask = 0
  731. if dialog.any_radio.isChecked():
  732. protocol = "any"
  733. port = 0
  734. else:
  735. if dialog.tcp_radio.isChecked():
  736. protocol = "tcp"
  737. elif dialog.udp_radio.isChecked():
  738. protocol = "udp"
  739. else:
  740. protocol = "any"
  741. try:
  742. range = service.split("-", 1)
  743. if len(range) == 2:
  744. port = int(range[0])
  745. port2 = int(range[1])
  746. else:
  747. port = int(service)
  748. except (TypeError, ValueError) as ex:
  749. port = self.fw_model.get_service_port(service)
  750. if port is not None:
  751. if port2 is not None and port2 <= port:
  752. QMessageBox.warning(None, self.tr("Invalid service ports range"),
  753. self.tr("Port {0} is lower than port {1}.").format(
  754. port2, port))
  755. else:
  756. item = {"address": address,
  757. "netmask": netmask,
  758. "portBegin": port,
  759. "portEnd": port2,
  760. "proto": protocol,
  761. }
  762. if row is not None:
  763. self.fw_model.setChild(row, item)
  764. else:
  765. self.fw_model.appendChild(item)
  766. else:
  767. QMessageBox.warning(None, self.tr("Invalid service name"),
  768. self.tr("Service '{0}' is unknown.").format(service))
  769. # Bases on the original code by:
  770. # Copyright (c) 2002-2007 Pascal Varet <p.varet@gmail.com>
  771. def handle_exception(exc_type, exc_value, exc_traceback):
  772. import sys
  773. import os.path
  774. import traceback
  775. filename, line, dummy, dummy = traceback.extract_tb(exc_traceback).pop()
  776. filename = os.path.basename(filename)
  777. error = "%s: %s" % (exc_type.__name__, exc_value)
  778. strace = ""
  779. stacktrace = traceback.extract_tb(exc_traceback)
  780. while len(stacktrace) > 0:
  781. (filename, line, func, txt) = stacktrace.pop()
  782. strace += "----\n"
  783. strace += "line: %s\n" %txt
  784. strace += "func: %s\n" %func
  785. strace += "line no.: %d\n" %line
  786. strace += "file: %s\n" %filename
  787. msg_box = QMessageBox()
  788. msg_box.setDetailedText(strace)
  789. msg_box.setIcon(QMessageBox.Critical)
  790. msg_box.setWindowTitle("Houston, we have a problem...")
  791. msg_box.setText("Whoops. A critical error has occured. This is most likely a bug "
  792. "in Qubes Manager.<br><br>"
  793. "<b><i>%s</i></b>" % error +
  794. "<br/>at line <b>%d</b><br/>of file %s.<br/><br/>"
  795. % ( line, filename ))
  796. msg_box.exec_()
  797. parser = qubesadmin.tools.QubesArgumentParser(vmname_nargs=1)
  798. parser.add_argument('--tab', metavar='TAB',
  799. action='store',
  800. choices=VMSettingsWindow.tabs_indices.keys())
  801. parser.set_defaults(
  802. tab='basic',
  803. )
  804. def main(args=None):
  805. global settings_window
  806. args = parser.parse_args(args)
  807. vm = args.domains.pop()
  808. qapp = QApplication(sys.argv)
  809. qapp.setOrganizationName('Invisible Things Lab')
  810. qapp.setOrganizationDomain("https://www.qubes-os.org/")
  811. qapp.setApplicationName("Qubes VM Settings")
  812. if not utils.is_debug():
  813. sys.excepthook = handle_exception
  814. settings_window = VMSettingsWindow(vm, qapp, args.tab)
  815. settings_window.show()
  816. qapp.exec_()
  817. qapp.exit()
  818. if __name__ == "__main__":
  819. main()
  820. # vim:sw=4:et: