diff --git a/Makefile b/Makefile index 654de11..1d1f4d9 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,8 @@ rpms: res: pyrcc4 -o qubesmanager/qrc_resources.py resources.qrc pyuic4 -o qubesmanager/ui_newappvmdlg.py newappvmdlg.ui + pyuic4 -o qubesmanager/ui_editfwrulesdlg.py editfwrulesdlg.ui + pyuic4 -o qubesmanager/ui_newfwruledlg.py newfwruledlg.ui update-repo: ln -f $(RPMS_DIR)/x86_64/qubes-manager-*.rpm ../yum/r1/dom0/rpm/ diff --git a/editfwrulesdlg.ui b/editfwrulesdlg.ui new file mode 100644 index 0000000..2d05eb0 --- /dev/null +++ b/editfwrulesdlg.ui @@ -0,0 +1,147 @@ + + + EditFwRulesDlg + + + + 0 + 0 + 500 + 280 + + + + Edit Firewall Rules + + + + + + QLayout::SetMaximumSize + + + + + false + + + false + + + false + + + true + + + true + + + 40 + + + false + + + 40 + + + false + + + + + + + + + &New + + + + + + + &Edit + + + + + + + &Delete + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + newRuleButton + editRuleButton + deleteRuleButton + rulesTreeView + buttonBox + + + + + buttonBox + rejected() + EditFwRulesDlg + reject() + + + 316 + 260 + + + 286 + 274 + + + + + buttonBox + accepted() + EditFwRulesDlg + accept() + + + 248 + 254 + + + 157 + 274 + + + + + diff --git a/newfwruledlg.ui b/newfwruledlg.ui new file mode 100644 index 0000000..8241f3f --- /dev/null +++ b/newfwruledlg.ui @@ -0,0 +1,239 @@ + + + NewFwRuleDlg + + + Qt::NonModal + + + + 0 + 0 + 311 + 202 + + + + New Firewall Rule + + + true + + + + + 30 + 160 + 271 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 10 + 62 + 17 + + + + Name + + + + + + 10 + 40 + 291 + 121 + + + + + + + + + 10 + 10 + 62 + 17 + + + + Address + + + + + + 190 + 10 + 62 + 17 + + + + Netmask + + + + + + 200 + 80 + 71 + 23 + + + + + 0 + 0 + + + + Allow + + + + + + 10 + 30 + 171 + 27 + + + + + + + 10 + 62 + 31 + 21 + + + + Port + + + + + + 123 + 62 + 16 + 21 + + + + - + + + + + + 190 + 30 + 84 + 27 + + + + + + + 50 + 60 + 71 + 27 + + + + 65535 + + + 0 + + + + + + 130 + 60 + 71 + 27 + + + + 65535 + + + + + + + 60 + 4 + 241 + 27 + + + + + + nameEdit + addressEdit + netmaskComboBox + portBeginSpinBox + portEndSpinBox + allowCheckBox + buttonBox + + + + + buttonBox + accepted() + NewFwRuleDlg + accept() + + + 248 + 174 + + + 157 + 201 + + + + + buttonBox + rejected() + NewFwRuleDlg + reject() + + + 300 + 180 + + + 286 + 201 + + + + + diff --git a/qubesmanager/firewall.py b/qubesmanager/firewall.py new file mode 100644 index 0000000..abc65e4 --- /dev/null +++ b/qubesmanager/firewall.py @@ -0,0 +1,269 @@ +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2011 Tomasz Sterna +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# + +import sys +import os +import xml.etree.ElementTree + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from qubes.qubes import QubesVmCollection +from qubes.qubes import QubesException +from qubes.qubes import qubes_store_filename +from qubes.qubes import QubesVmLabels +from qubes.qubes import dry_run +from qubes.qubes import qubes_guid_path + +import ui_editfwrulesdlg +import ui_newfwruledlg + +class EditFwRulesDlg (QDialog, ui_editfwrulesdlg.Ui_EditFwRulesDlg): + def __init__(self, parent = None): + super (EditFwRulesDlg, self).__init__(parent) + self.setupUi(self) + self.newRuleButton.clicked.connect(self.new_rule_button_pressed) + self.deleteRuleButton.clicked.connect(self.delete_rule_button_pressed) + + def set_model(self, model): + self.__model = model + self.rulesTreeView.setModel(model) + self.rulesTreeView.header().setResizeMode(QHeaderView.ResizeToContents) + self.rulesTreeView.header().setResizeMode(0, QHeaderView.Stretch) + + def new_rule_button_pressed(self): + dialog = NewFwRuleDlg() + + if dialog.exec_(): + name = dialog.nameEdit.text() + allow = dialog.allowCheckBox.isChecked() + address = dialog.addressEdit.text() + netmask = dialog.netmasks[dialog.netmaskComboBox.currentIndex()] + portBegin = dialog.portBeginSpinBox.value() + portEnd = dialog.portEndSpinBox.value() + if portEnd <= portBegin: + portEnd = None + + if portBegin == 0 and portEnd is None: + return + + if name == "": + QMessageBox.warning(None, "Incorrect name", "You need to name the rule.") + return + + if address == "": + QMessageBox.warning(None, "Incorrect address", "Pleas give an address for the rule.") + return + + self.__model.appendChild(QubesFirewallRuleItem(name, allow, address, netmask, portBegin, portEnd)) + + def delete_rule_button_pressed(self): + for i in set([index.row() for index in self.rulesTreeView.selectedIndexes()]): + self.__model.removeChild(i) + +class NewFwRuleDlg (QDialog, ui_newfwruledlg.Ui_NewFwRuleDlg): + def __init__(self, parent = None): + super (NewFwRuleDlg, self).__init__(parent) + self.setupUi(self) + + self.netmasks = [ 32, 24, 16, 0 ] + for mask in self.netmasks: + self.netmaskComboBox.addItem(str(mask)) + +class QubesFirewallRuleItem(object): + def __init__(self, name = str(), allow = bool(), address = str(), netmask = 32, portBegin = 0, portEnd = None): + self.__name = name + self.__allow = allow + self.__address = address + self.__netmask = netmask + self.__portBegin = portBegin + self.__portEnd = portEnd + + @property + def name(self): + return self.__name + + @property + def allow(self): + return self.__allow + + @property + def address(self): + return self.__address + + @property + def netmask(self): + return self.__netmask + + @property + def portBegin(self): + return self.__portBegin + + @property + def portEnd(self): + return self.__portEnd + + def hasChildren(self): + return False + +class QubesFirewallRulesModel(QAbstractItemModel): + def __init__(self, parent=None): + QAbstractItemModel.__init__(self, parent) + + self.__columnValues = { + 0: lambda x: self.children[x].name, + 1: lambda x: self.children[x].address, + 2: lambda x: "/{0}".format(self.children[x].netmask), + 3: lambda x: "{0}-{1}".format(self.children[x].portBegin, self.children[x].portEnd) if self.children[x].portEnd is not None \ + else self.children[x].portBegin, + 4: lambda x: "ALLOW" if self.children[x].allow else "DENY", + } + self.__columnNames = { + 0: "Name", + 1: "Address", + 2: "Mask", + 3: "Port(s)", + 4: "Allow", + } + + def set_vm(self, vm): + self.__vm = vm + + self.clearChildren() + + root = vm.get_firewall_conf() + for element in root: + try: + kwargs = { "allow": element.tag=="allow" } + attr_list = ("name", "address", "netmask", "port", "toport") + + for attribute in attr_list: + kwargs[attribute] = element.get(attribute) + + kwargs["netmask"] = int(kwargs["netmask"]) + kwargs["portBegin"] = int(kwargs["port"]) + if kwargs["toport"] is not None: + kwargs["portEnd"] = int(kwargs["toport"]) + del(kwargs["port"]) + del(kwargs["toport"]) + + self.appendChild(QubesFirewallRuleItem(**kwargs)) + + except (ValueError, LookupError) as err: + print "{0}: load error: {1}".format( + os.path.basename(sys.argv[0]), err) + return False + + return True + + def apply_rules(self): + assert self.__vm is not None + + root = xml.etree.ElementTree.Element( + "QubesFirwallRules", + policy="allow" + ) + + for rule in self.children: + element = xml.etree.ElementTree.Element( + "allow" if rule.allow else "deny", + name=rule.name, + address=rule.address, + netmask=str(rule.netmask), + port=str(rule.portBegin), + ) + if rule.portEnd is not None: + element.set("toport", str(rule.portEnd)) + root.append(element) + + tree = xml.etree.ElementTree.ElementTree(root) + + try: + self.__vm.write_firewall_conf(tree) + except EnvironmentError as err: + print "{0}: save error: {1}".format( + os.path.basename(sys.argv[0]), err) + return False + + return True + + def index(self, row, column, parent=QModelIndex()): + if not self.hasIndex(row, column, parent): + return QModelIndex() + + return self.createIndex(row, column, self.children[row]) + + def parent(self, child): + return QModelIndex() + + def rowCount(self, parent=QModelIndex()): + return len(self) + + def columnCount(self, parent=QModelIndex()): + return len(self.__columnValues) + + def hasChildren(self, index=QModelIndex()): + parentItem = index.internalPointer() + if parentItem is not None: + return parentItem.hasChildren() + else: + return True + + def data(self, index, role=Qt.DisplayRole): + if index.isValid() and role == Qt.DisplayRole: + return self.__columnValues[index.column()](index.row()) + + return QVariant() + + def headerData(self, section, orientation, role=Qt.DisplayRole): + if section < len(self.__columnNames) \ + and orientation == Qt.Horizontal and role == Qt.DisplayRole: + return self.__columnNames[section] + + return QVariant() + + @property + def children(self): + return self.__children + + def appendChild(self, child): + row = len(self) + self.beginInsertRows(QModelIndex(), row, row) + self.children.append(child) + self.endInsertRows() + index = self.createIndex(row, 0, child) + self.dataChanged.emit(index, index) + + def removeChild(self, i): + if i >= len(self): + return + + self.beginRemoveRows(QModelIndex(), i, i) + del self.children[i] + self.endRemoveRows() + index = self.createIndex(i, 0) + self.dataChanged.emit(index, index) + + def clearChildren(self): + self.__children = list() + + def __len__(self): + return len(self.children) diff --git a/qubesmanager/main.py b/qubesmanager/main.py index 58cf124..ea0fcff 100755 --- a/qubesmanager/main.py +++ b/qubesmanager/main.py @@ -36,6 +36,8 @@ from qubes.qubes import QubesHost import qubesmanager.qrc_resources import ui_newappvmdlg +from firewall import EditFwRulesDlg, QubesFirewallRulesModel + from pyinotify import WatchManager, Notifier, ThreadedNotifier, EventsCodes, ProcessEvent import subprocess @@ -328,6 +330,9 @@ class VmManagerWindow(QMainWindow): self.action_showcpuload = self.createAction ("Show/Hide CPU Load chart", slot=self.showcpuload, checkable=True, icon="showcpuload", tip="Show/Hide CPU Load chart") + self.action_editfwrules = self.createAction ("Edit VM Firewall rules", slot=self.edit_fw_rules, + icon="showcpuload", tip="Edit VM Firewall rules") + self.action_removevm.setDisabled(True) self.action_resumevm.setDisabled(True) @@ -341,7 +346,8 @@ class VmManagerWindow(QMainWindow): self.toolbar.setFloatable(False) self.addActions (self.toolbar, (self.action_createvm, self.action_removevm, None, - self.action_resumevm, self.action_pausevm, self.action_shutdownvm, self.action_updatevm, + self.action_resumevm, self.action_pausevm, self.action_shutdownvm, + self.action_updatevm, self.action_editfwrules, None, self.action_showcpuload, )) @@ -491,6 +497,7 @@ class VmManagerWindow(QMainWindow): #self.action_resumevm.setEnabled(not vm.is_running()) #self.action_pausevm.setEnabled(vm.is_running() and vm.qid != 0) self.action_shutdownvm.setEnabled(vm.is_running() and vm.qid != 0) + self.action_editfwrules.setEnabled(vm.is_networked() and (vm.is_appvm() or vm.is_disposablevm())) def closeEvent (self, event): self.hide() @@ -684,6 +691,15 @@ class VmManagerWindow(QMainWindow): def showcpuload(self): pass + def edit_fw_rules(self): + vm = self.get_selected_vm() + dialog = EditFwRulesDlg() + model = QubesFirewallRulesModel() + model.set_vm(vm) + dialog.set_model(model) + + if dialog.exec_(): + model.apply_rules() class QubesTrayIcon(QSystemTrayIcon): def __init__(self, icon): diff --git a/qubesmanager/ui_editfwrulesdlg.py b/qubesmanager/ui_editfwrulesdlg.py new file mode 100644 index 0000000..0d179b4 --- /dev/null +++ b/qubesmanager/ui_editfwrulesdlg.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'editfwrulesdlg.ui' +# +# Created: Wed Feb 16 20:55:59 2011 +# by: PyQt4 UI code generator 4.7.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +class Ui_EditFwRulesDlg(object): + def setupUi(self, EditFwRulesDlg): + EditFwRulesDlg.setObjectName("EditFwRulesDlg") + EditFwRulesDlg.resize(500, 280) + self.verticalLayout_3 = QtGui.QVBoxLayout(EditFwRulesDlg) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setSizeConstraint(QtGui.QLayout.SetMaximumSize) + self.horizontalLayout.setObjectName("horizontalLayout") + self.rulesTreeView = QtGui.QTreeView(EditFwRulesDlg) + self.rulesTreeView.setRootIsDecorated(False) + self.rulesTreeView.setUniformRowHeights(False) + self.rulesTreeView.setItemsExpandable(False) + self.rulesTreeView.setAllColumnsShowFocus(True) + self.rulesTreeView.setExpandsOnDoubleClick(True) + self.rulesTreeView.setObjectName("rulesTreeView") + self.rulesTreeView.header().setDefaultSectionSize(40) + self.rulesTreeView.header().setStretchLastSection(False) + self.horizontalLayout.addWidget(self.rulesTreeView) + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.newRuleButton = QtGui.QPushButton(EditFwRulesDlg) + self.newRuleButton.setObjectName("newRuleButton") + self.verticalLayout.addWidget(self.newRuleButton) + self.editRuleButton = QtGui.QPushButton(EditFwRulesDlg) + self.editRuleButton.setObjectName("editRuleButton") + self.verticalLayout.addWidget(self.editRuleButton) + self.deleteRuleButton = QtGui.QPushButton(EditFwRulesDlg) + self.deleteRuleButton.setObjectName("deleteRuleButton") + self.verticalLayout.addWidget(self.deleteRuleButton) + spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) + self.verticalLayout.addItem(spacerItem) + self.horizontalLayout.addLayout(self.verticalLayout) + self.verticalLayout_3.addLayout(self.horizontalLayout) + self.buttonBox = QtGui.QDialogButtonBox(EditFwRulesDlg) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout_3.addWidget(self.buttonBox) + + self.retranslateUi(EditFwRulesDlg) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), EditFwRulesDlg.reject) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), EditFwRulesDlg.accept) + QtCore.QMetaObject.connectSlotsByName(EditFwRulesDlg) + EditFwRulesDlg.setTabOrder(self.newRuleButton, self.editRuleButton) + EditFwRulesDlg.setTabOrder(self.editRuleButton, self.deleteRuleButton) + EditFwRulesDlg.setTabOrder(self.deleteRuleButton, self.rulesTreeView) + EditFwRulesDlg.setTabOrder(self.rulesTreeView, self.buttonBox) + + def retranslateUi(self, EditFwRulesDlg): + EditFwRulesDlg.setWindowTitle(QtGui.QApplication.translate("EditFwRulesDlg", "Edit Firewall Rules", None, QtGui.QApplication.UnicodeUTF8)) + self.newRuleButton.setText(QtGui.QApplication.translate("EditFwRulesDlg", "&New", None, QtGui.QApplication.UnicodeUTF8)) + self.editRuleButton.setText(QtGui.QApplication.translate("EditFwRulesDlg", "&Edit", None, QtGui.QApplication.UnicodeUTF8)) + self.deleteRuleButton.setText(QtGui.QApplication.translate("EditFwRulesDlg", "&Delete", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/qubesmanager/ui_newfwruledlg.py b/qubesmanager/ui_newfwruledlg.py new file mode 100644 index 0000000..5081e55 --- /dev/null +++ b/qubesmanager/ui_newfwruledlg.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'newfwruledlg.ui' +# +# Created: Wed Feb 16 20:55:59 2011 +# by: PyQt4 UI code generator 4.7.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +class Ui_NewFwRuleDlg(object): + def setupUi(self, NewFwRuleDlg): + NewFwRuleDlg.setObjectName("NewFwRuleDlg") + NewFwRuleDlg.setWindowModality(QtCore.Qt.NonModal) + NewFwRuleDlg.resize(311, 202) + NewFwRuleDlg.setModal(True) + self.buttonBox = QtGui.QDialogButtonBox(NewFwRuleDlg) + self.buttonBox.setGeometry(QtCore.QRect(30, 160, 271, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.label = QtGui.QLabel(NewFwRuleDlg) + self.label.setGeometry(QtCore.QRect(10, 10, 62, 17)) + self.label.setObjectName("label") + self.groupBox = QtGui.QGroupBox(NewFwRuleDlg) + self.groupBox.setGeometry(QtCore.QRect(10, 40, 291, 121)) + self.groupBox.setTitle("") + self.groupBox.setObjectName("groupBox") + self.label_2 = QtGui.QLabel(self.groupBox) + self.label_2.setGeometry(QtCore.QRect(10, 10, 62, 17)) + self.label_2.setObjectName("label_2") + self.label_3 = QtGui.QLabel(self.groupBox) + self.label_3.setGeometry(QtCore.QRect(190, 10, 62, 17)) + self.label_3.setObjectName("label_3") + self.allowCheckBox = QtGui.QCheckBox(self.groupBox) + self.allowCheckBox.setGeometry(QtCore.QRect(200, 80, 71, 23)) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.allowCheckBox.sizePolicy().hasHeightForWidth()) + self.allowCheckBox.setSizePolicy(sizePolicy) + self.allowCheckBox.setObjectName("allowCheckBox") + self.addressEdit = QtGui.QLineEdit(self.groupBox) + self.addressEdit.setGeometry(QtCore.QRect(10, 30, 171, 27)) + self.addressEdit.setObjectName("addressEdit") + self.label_4 = QtGui.QLabel(self.groupBox) + self.label_4.setGeometry(QtCore.QRect(10, 62, 31, 21)) + self.label_4.setObjectName("label_4") + self.label_5 = QtGui.QLabel(self.groupBox) + self.label_5.setGeometry(QtCore.QRect(123, 62, 16, 21)) + self.label_5.setObjectName("label_5") + self.netmaskComboBox = QtGui.QComboBox(self.groupBox) + self.netmaskComboBox.setGeometry(QtCore.QRect(190, 30, 84, 27)) + self.netmaskComboBox.setObjectName("netmaskComboBox") + self.portBeginSpinBox = QtGui.QSpinBox(self.groupBox) + self.portBeginSpinBox.setGeometry(QtCore.QRect(50, 60, 71, 27)) + self.portBeginSpinBox.setMaximum(65535) + self.portBeginSpinBox.setProperty("value", 0) + self.portBeginSpinBox.setObjectName("portBeginSpinBox") + self.portEndSpinBox = QtGui.QSpinBox(self.groupBox) + self.portEndSpinBox.setGeometry(QtCore.QRect(130, 60, 71, 27)) + self.portEndSpinBox.setMaximum(65535) + self.portEndSpinBox.setObjectName("portEndSpinBox") + self.nameEdit = QtGui.QLineEdit(NewFwRuleDlg) + self.nameEdit.setGeometry(QtCore.QRect(60, 4, 241, 27)) + self.nameEdit.setObjectName("nameEdit") + + self.retranslateUi(NewFwRuleDlg) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), NewFwRuleDlg.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), NewFwRuleDlg.reject) + QtCore.QMetaObject.connectSlotsByName(NewFwRuleDlg) + NewFwRuleDlg.setTabOrder(self.nameEdit, self.addressEdit) + NewFwRuleDlg.setTabOrder(self.addressEdit, self.netmaskComboBox) + NewFwRuleDlg.setTabOrder(self.netmaskComboBox, self.portBeginSpinBox) + NewFwRuleDlg.setTabOrder(self.portBeginSpinBox, self.portEndSpinBox) + NewFwRuleDlg.setTabOrder(self.portEndSpinBox, self.allowCheckBox) + NewFwRuleDlg.setTabOrder(self.allowCheckBox, self.buttonBox) + + def retranslateUi(self, NewFwRuleDlg): + NewFwRuleDlg.setWindowTitle(QtGui.QApplication.translate("NewFwRuleDlg", "New Firewall Rule", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("NewFwRuleDlg", "Name", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("NewFwRuleDlg", "Address", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("NewFwRuleDlg", "Netmask", None, QtGui.QApplication.UnicodeUTF8)) + self.allowCheckBox.setText(QtGui.QApplication.translate("NewFwRuleDlg", "Allow", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.translate("NewFwRuleDlg", "Port", None, QtGui.QApplication.UnicodeUTF8)) + self.label_5.setText(QtGui.QApplication.translate("NewFwRuleDlg", "-", None, QtGui.QApplication.UnicodeUTF8)) +