diff --git a/.arcconfig b/.arcconfig new file mode 100644 index 0000000..a650463 --- /dev/null +++ b/.arcconfig @@ -0,0 +1,4 @@ +{ + "project_id" : "econnman", + "conduit_uri" : "https://phab.enlightenment.org/" +} diff --git a/AUTHORS b/AUTHORS index 5ab1470..d236e58 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,3 @@ Gustavo Sverzut Barbieri +Matthias Wauer +Leif Middelschulte diff --git a/ChangeLog b/ChangeLog index e69de29..f51649d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -0,0 +1,4 @@ +Since 1.1: +* Added basic support for ieee802.1x wireless networks configuration + The user running econnman needs to be able to write its own configfile at + /var/lib/connman/econnman.config diff --git a/Makefile.am b/Makefile.am index a65ebf2..7867738 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,6 +17,10 @@ econnman-bin: $(top_srcdir)/econnman-bin.in $(top_builddir)/Makefile $(top_srcdir)/econnman-bin.in > $(top_builddir)/econnman-bin chmod +x $(top_builddir)/econnman-bin +confdir = @localstatedir@/lib/connman +dist_conf_DATA = \ + data/config/econnman.config + desktopdir = $(datadir)/applications desktop_DATA = \ data/desktop/econnman-agent.desktop \ diff --git a/README b/README index 89a0ec5..f060206 100644 --- a/README +++ b/README @@ -30,12 +30,14 @@ This package uses automake, so execute: Build:: - ./configure --prefix=/usr + ./configure --prefix=/usr --localstatedir=/var make all Install:: make install + chown root:users /var/lib/connman/econnman.config + chmod g+w /var/lib/connman/econnman.config If you wish to install at alternative locations, then make sure to configure your PYTHONPATH to be able to access this location! diff --git a/TODO b/TODO new file mode 100644 index 0000000..5ace487 --- /dev/null +++ b/TODO @@ -0,0 +1,10 @@ +IEEE802.1x +========== + +(See phab T654) + +Questions that are still there: + + - 1 vs. N config file(s)? The latter would either need changes to connman's .service file or entire write access to /var/lib/connman. Both of which aren't really desireable + - integrate modes guessing into econnman vs. standalone app? + - remember passwords in files or some keyring? diff --git a/data/config/econnman.config b/data/config/econnman.config new file mode 100644 index 0000000..e69de29 diff --git a/data/desktop/econnman-agent.desktop b/data/desktop/econnman-agent.desktop index a693d59..d42c530 100644 --- a/data/desktop/econnman-agent.desktop +++ b/data/desktop/econnman-agent.desktop @@ -1,26 +1,61 @@ [Desktop Entry] +Encoding=UTF-8 Type=Application -Icon=econnman -Name=EConnMan with Agent -Name[eo]=EConnMak kun agento -Name[es]=EConnMan con agente -Name[gl]=EConnMan con axente -Name[it]=EconnMan con agente -Name[pl]=EconnMan z Agentem -Name[pt]=EconnMan com agente +Name=Connection Manager +Name[ca]=Gestor de connexions +Name[cs]=Správce připojení +Name[de]=Verbindungsverwaltung +Name[eo]=Administrilo de konektoj +Name[es]=Administrador de conexiones +Name[fi]=Yhteyksien hallinta +Name[fr]=Gestionnaire de connexion +Name[gl]=Xestor de conexións +Name[hu]=Hálózati kapcsolatok +Name[it]=Gestore connessioni +Name[ja]=接続マネージャー +Name[ko]=연결 관리자 +Name[ms]=Pengurus Sambungan +Name[nl]=EConnMan met Agent +Name[pl]=Menadżer Połączeń +Name[pt]=Gestor de ligações +Name[ru]=Менеджер подключений +Name[sr]=Управник мреже +Name[tr]=Bağlantı Yöneticisi GenericName=Connection Manager +GenericName[ca]=Gestor de connexions +GenericName[cs]=Správce připojení +GenericName[de]=Verbindungsverwaltung +GenericName[eo]=Administrilo de konektoj GenericName[es]=Administrador de conexiones +GenericName[fi]=Yhteyksien hallinta +GenericName[fr]=Gestionnaire de connexion GenericName[gl]=Xestor de conexións +GenericName[hu]=Hálózati kapcsolatok +GenericName[it]=Gestore connessioni +GenericName[ja]=接続マネージャー +GenericName[ko]=연결 관리자 +GenericName[ms]=Pengurus Sambungan +GenericName[nl]=EConnMan met Agent GenericName[pl]=Menadżer Połączeń GenericName[pt]=Gestor de ligações +GenericName[ru]=Менеджер подключений +GenericName[sr]=Управник мреже +GenericName[tr]=Bağlantı Yöneticisi Comment=Manage your internet connections using ConnMan and EFL. Version with agent to get passwords and other input. +Comment[ca]=Gestioni les connexions internet utilitzant ConnMan i EFL. Versió amb agent per obtenir contrasenyes i altres entrades. +Comment[de]=Ihre Internetverbindung verwalten durch Verwendung von ConnMan und EFL. Version mit einem Agenten, um Passwörter und andere Eingaben zu behalten. Comment[eo]=Administri viajn interretajn konektadojn pere de ConnMan kaj EFL. Versio kun agento por akiri pasvortojn aŭ aliajn enigojn. Comment[es]=Administre sus conexiones a internet usando ConnMan y EFL. Versión con agente para obtener contraseñas y otras entradas. +Comment[fi]=Hallinnoi verkkoyhteyksiäsi käyttäen ConnMan-palvelua ja EFL:ää. Tässä versiossa on mukana agenttiohjelma joka tarvittessa kysyy verkkosalauksen salasanasi tai muita tietoja. +Comment[fr]=Gérez vos connexions Internet avec ConnMan et EFL. Version avec agent pour obtenir les mots de passe et autres entrées. Comment[gl]=Xestione as súas conexións a internet empregando ConnMan e EFL. Versión con axente para obter contrasinais e outras entradas. Comment[it]=Gestisce le connessioni internet usando ConnMan e le EFL. Versione con agente per password e altri input. +Comment[ms]=Urus sambungan internet anda menggunakan ConnMan dan EFL. Versi dengan ejen untuk dapatkan kata laluan dan lain-lain input. Comment[pl]=Zarządzaj połączeniami z Internetem z użyciem ConnMan i EFL. Wersja z agentem umożliwia używanie haseł i innych parametrów. Comment[pt]=Gestão das ligações internet com o ConnMan e o EFL. Versão com agente para obter senhas e outros dados -Categories=Network;Settings;Enlightenment; +Comment[tr]=İnternet bağlantınızı Bağlantı Yöneticisi ve EFL kullanarak yönetin. +Icon=econnman Exec=econnman-bin --agent -StartupNotify=true Terminal=false +Categories=Network;Settings;X-Enlightenment; +StartupNotify=true diff --git a/data/desktop/econnman.desktop b/data/desktop/econnman.desktop index 012183e..4edb059 100644 --- a/data/desktop/econnman.desktop +++ b/data/desktop/econnman.desktop @@ -1,21 +1,61 @@ [Desktop Entry] +Encoding=UTF-8 Type=Application -Icon=econnman Name=EConnMan +Name[ca]=Gestor de connexions +Name[cs]=Správce připojení +Name[de]=EConnMan +Name[eo]=Enlightenment Konektado-administrilo +Name[es]=Administrador de conexiones +Name[fi]=EConnMan +Name[fr]=Gestionnaire de connexion +Name[gl]=Xestor de conexións +Name[hu]=Hálózati kapcsolatok +Name[it]=Gestore connessioni +Name[ja]=EConnMan +Name[ko]=연결 관리자 +Name[ms]=EConnMan +Name[nl]=EConnMan met Agent +Name[pl]=Menadżer Połączeń +Name[pt]=Gestor de ligações +Name[ru]=Менеджер подключений +Name[sr]=Управник мреже +Name[tr]=Bağlantı Yöneticisi GenericName=Connection Manager -GenericName[eo]=Administrilo de konektadoj +GenericName[ca]=Gestor de connexions +GenericName[cs]=Správce připojení +GenericName[de]=Verbindungsverwaltung +GenericName[eo]=Administrilo de konektoj GenericName[es]=Administrador de conexiones +GenericName[fi]=Yhteyksien hallinta +GenericName[fr]=Gestionnaire de connexion GenericName[gl]=Xestor de conexións +GenericName[hu]=Hálózati kapcsolatok +GenericName[it]=Gestore connessioni +GenericName[ja]=接続マネージャー +GenericName[ko]=연결 관리자 +GenericName[ms]=Pengurus Sambungan +GenericName[nl]=EConnMan met Agent GenericName[pl]=Menadżer Połączeń GenericName[pt]=Gestor de ligações +GenericName[ru]=Менеджер подключений +GenericName[sr]=Управник мреже +GenericName[tr]=Bağlantı Yöneticisi Comment=Manage your internet connections using ConnMan and EFL +Comment[ca]=Gestiona les conexions a Internet amb ConnMan i EFL +Comment[de]=ConnMan und EFL benutzen, um die Internetverbindungen zu verwalten Comment[eo]=Administri viajn interretajn konektadoj pere de ConnMan kaj EFL. Comment[es]=Administre sus conexiones a internet usando ConnMan y EFL. +Comment[fi]=Hallinnoi verkkoyhteyksiä käyttäen connman-palvelua sekä EFL:ää +Comment[fr]=Gérez vos connexions Internet avec ConnMan et EFL Comment[gl]=Xestione as súas conexións a internet empregando ConnMan e EFL. Comment[it]=Gestisce le connessioni internet usando ConnMan e le EFL. +Comment[ms]=Urus sambungan internet anda menggunakan ConnMan dan EFL Comment[pl]=Zarządzaj połączeniami z Internetem z użyciem ConnMan i EFL. Comment[pt]=Gestão de ligações internet com o ConnMan e o EFL -Categories=Network;Settings;Enlightenment; +Comment[tr]=İnternet bağlantınızı bağlantı Yöneticisi ve EFL kullanarak yönetin +Icon=econnman Exec=econnman-bin -StartupNotify=true Terminal=false +Categories=Network;Settings;X-Enlightenment; +StartupNotify=true diff --git a/econnman-bin.in b/econnman-bin.in index 55d2256..4f23acb 100755 --- a/econnman-bin.in +++ b/econnman-bin.in @@ -8,6 +8,7 @@ # gateway is not updated. +import sys import dbus import dbus.service import logging @@ -17,7 +18,7 @@ import os.path try: import efl.evas as evas import efl.ecore as ecore - import efl.edje as edje + import efl.edje as edje # Class resolve hack for edje_get from efl.dbus_mainloop import DBusEcoreMainLoop import efl.elementary as elm from efl.elementary import ELM_POLICY_QUIT, \ @@ -43,7 +44,9 @@ try: from efl.elementary.theme import Theme except: import elementary as elm - import evas, e_dbus, ecore, edje + import evas + import ecore + import edje # Class resolve hack for edje_get from e_dbus import DBusEcoreMainLoop from elementary import Window, Background, Box, Label, Naviframe, Popup, \ Button, Scroller, Check, Progressbar, Genlist, GenlistItemClass, \ @@ -51,10 +54,15 @@ except: ELM_WIN_DIALOG_BASIC, ELM_POLICY_QUIT, ELM_SCROLLER_POLICY_OFF, \ ELM_SCROLLER_POLICY_AUTO, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED - dbus_ml = DBusEcoreMainLoop() bus = dbus.SystemBus(mainloop=dbus_ml) -log = logging.getLogger() +log = logging.getLogger("econnman-bin") +log_handler = logging.StreamHandler() +log_formatter = logging.Formatter( + "%(created)d %(name)s [%(levelname)s]:: %(message)s (@lineno %(lineno)d)" + ) +log_handler.setFormatter(log_formatter) +log.addHandler(log_handler) manager = None @@ -63,6 +71,126 @@ EXPAND_HORIZ = (evas.EVAS_HINT_EXPAND, 0.0) FILL_BOTH = (evas.EVAS_HINT_FILL, evas.EVAS_HINT_FILL) + +# python2 backwards compatibility +try: + from configparser import SafeConfigParser +except ImportError: + from ConfigParser import SafeConfigParser + + +class PNACConfig(SafeConfigParser): + + """A custom config parser for IEEE802.1x (PNAC) + + Section names are prefixed with service_ + + """ + + CONF_FILE = "/var/lib/connman/econnman.config" + + def __init__(self): + SafeConfigParser.__init__(self) + self.optionxform = str + + def read(self): + args = self.CONF_FILE, 'r' + kwargs = {} + if sys.hexversion >= 0x03000000: + kwargs["encoding"] = 'utf8' + try: + with open(*args, **kwargs) as fd: + self.readfp(fd) + except IOError: + log.error( + "Econnman cannot read the configuration file \"%s\" used by " + "connman to configure your ieee802.1x networks. Make sure the " + "user running econnman is able to read/write it.", + self.CONF_FILE + ) + #raise + + # defaults() + # sections() + + def add_section(self, service_name): + secname = 'service_' + service_name + SafeConfigParser.add_section(self, secname) + self.set(service_name, 'Type', 'wifi') + self.set(service_name, 'Name', service_name) + #self.write() + + def has_section(self, service_name): # config_exists + return bool(self.section_get(service_name)) + + # options() + + def has_option(self, service_name, key): + secname = self.section_get(service_name) + return SafeConfigParser.has_option(secname, key) + + # read() + # readfp() + + def get(self, service_name, key): # config_option_get + secname = self.section_get(service_name) + if self.has_option(service_name, key): + return SafeConfigParser.get(self, secname, key) + return None + + # getint() + # getfloat() + # getboolean() + # items() + + def set(self, service_name, key, value): # config_set + secname = self.section_get(service_name) + if not self.has_section(service_name): + self.add_section(service_name) + if value is not None: + SafeConfigParser.set(self, secname, key, value) + elif self.has_option(secname, key): + self.remove_option(secname, key) + #self.write() + + def write(self): + # TODO: Copy owner and mask from existing config file, write to a temp + # file and request overwriting with sudo or polkit, then set the + # owner and mask like it was before. This is to avoid the + # requirement of running the entire econnman instance with root + # rights. + try: + with open(self.CONF_FILE, 'w', encoding='utf8') as configfile: + SafeConfigParser.write(self, configfile) + except IOError: + log.error( + "Econnman cannot write to the configuration file \"%s\" used " + "by connman to configure your ieee802.1x networks. Make sure " + "the user running econnman is able to read/write it.", + self.CONF_FILE + ) + + def remove_option(self, service_name, key): + secname = self.section_get(service_name) + SafeConfigParser.remove_option(self, secname, key) + #self.write() + + def remove_section(self, service_name): # config_del + secname = self.section_get(service_name) + ret = SafeConfigParser.remove_section(self, secname) + #self.write() + return ret + + def section_get(self, service_name): # config_get + #secname = 'service_' + service_name + for sec in self.sections(): + if self.has_option(sec, 'Name') and \ + self.get(sec, 'Name') == service_name: + return sec + else: + return None + + ######################################################################## # Debug helpers: def dbus_variant_to_str(v): @@ -85,17 +213,24 @@ def dbus_variant_to_str(v): v = repr(v) return v + def dbus_dict_to_str(d): - "Help debug by converting a dbus.Dictionary to a string in a shorter form." + """Help debug by converting a dbus.Dictionary to a string in a shorter + form. + """ s = [] for k, v in d.items(): s.append("%s=%s" % (k, dbus_variant_to_str(v))) return ", ".join(s) + def dbus_array_to_str(a): - "Help debug by converting a complex structure to a string in shorter form." + """Help debug by converting a complex structure to a string in shorter + form. + """ return ", ".join(dbus_variant_to_str(x) for x in a) + def dbus_array_of_dict_to_str(a): """Help debug by converting a complex structure to a string in a shorter form with only the keys, not the value. @@ -212,6 +347,20 @@ class ObjectView(object): sc.callback_changed_add(callback) return sc, items + def add_label_and_segment_control(self, box, options, callback, label): + lb = self.add_label(box, label) + + sc = SegmentControl(box) + sc.size_hint_weight = EXPAND_HORIZ + sc.size_hint_align = FILL_BOTH + items = {} + for o in options: + items[o] = sc.item_add(None, o) + sc.show() + box.pack_end(sc) + sc.callback_changed_add(callback) + return lb, sc, items + def add_frame_and_box(self, box, label): fr = Frame(box) fr.size_hint_weight = EXPAND_HORIZ @@ -284,6 +433,7 @@ class OfflineModeMonitor(object): def _on_user_changed(self, obj): state = obj.state + def on_reply(): log.info("Set OfflineMode=%s", state) @@ -313,21 +463,31 @@ class TechList(object): self.obj.on_del_add(self._deleted) self.on_selected = on_selected self.obj.callback_selected_add(self._tech_selected) - self.sig_added = manager.connect_to_signal("TechnologyAdded", - self._tech_added) - self.sig_removed = manager.connect_to_signal("TechnologyRemoved", - self._tech_removed) - self.sig_propch = bus.add_signal_receiver(self._tech_changed, - "PropertyChanged", - "net.connman.Technology", - "net.connman", - path_keyword='path') - self.itc = GenlistItemClass(item_style="default", - text_get_func=self._item_text_get, - content_get_func=self._item_content_get) - - manager.GetTechnologies(reply_handler=self._get_techs_reply, - error_handler=self._get_techs_error) + self.sig_added = manager.connect_to_signal( + "TechnologyAdded", + self._tech_added + ) + self.sig_removed = manager.connect_to_signal( + "TechnologyRemoved", + self._tech_removed + ) + self.sig_propch = bus.add_signal_receiver( + self._tech_changed, + "PropertyChanged", + "net.connman.Technology", + "net.connman", + path_keyword='path' + ) + self.itc = GenlistItemClass( + item_style="default", + text_get_func=self._item_text_get, + content_get_func=self._item_content_get + ) + + manager.GetTechnologies( + reply_handler=self._get_techs_reply, + error_handler=self._get_techs_error + ) def _deleted(self, lst): self.sig_added.remove() @@ -453,6 +613,7 @@ class TechView(ObjectView): def _on_user_powered(self, obj): state = bool(self.powered.state) + def on_reply(): log.info("Set %s Powered=%s", self.path, state) @@ -462,8 +623,10 @@ class TechView(ObjectView): popup_error(self.obj, "Failed to Apply Powered", exc.get_dbus_message()) - self.bus_obj.SetProperty("Powered", dbus.Boolean(state), - reply_handler=on_reply, error_handler=on_error) + self.bus_obj.SetProperty( + "Powered", dbus.Boolean(state), + reply_handler=on_reply, error_handler=on_error + ) def _scan(self, obj): def on_reply(): @@ -486,10 +649,12 @@ class TechView(ObjectView): self.passphrase.disabled = not state def _tethering_apply(self, obj): - self.to_apply = [("TetheringIdentifier", self.identifier.text), - ("TetheringPassphrase", self.passphrase.text), - ("Tethering", dbus.Boolean(self.tethering.state)), - ] + self.to_apply = [ + ("TetheringIdentifier", self.identifier.text), + ("TetheringPassphrase", self.passphrase.text), + ("Tethering", dbus.Boolean(self.tethering.state)), + ] + def apply_next(): if not self.to_apply: return @@ -548,18 +713,26 @@ class ServicesList(object): self.on_disclosure = on_disclosure self.obj.callback_selected_add(self._item_selected) self.obj.on_del_add(self._deleted) - self.sig_ch = manager.connect_to_signal("ServicesChanged", - self._services_changed) - self.sig_propch = bus.add_signal_receiver(self._service_prop_changed, - "PropertyChanged", - "net.connman.Service", - "net.connman", - path_keyword='path') - manager.GetServices(reply_handler=self._get_services_reply, - error_handler=self._get_services_error) - self.itc = GenlistItemClass(item_style="default", - text_get_func=self._item_text_get, - content_get_func=self._item_content_get) + self.sig_ch = manager.connect_to_signal( + "ServicesChanged", + self._services_changed + ) + self.sig_propch = bus.add_signal_receiver( + self._service_prop_changed, + "PropertyChanged", + "net.connman.Service", + "net.connman", + path_keyword='path' + ) + manager.GetServices( + reply_handler=self._get_services_reply, + error_handler=self._get_services_error + ) + self.itc = GenlistItemClass( + item_style="default", + text_get_func=self._item_text_get, + content_get_func=self._item_content_get + ) def _deleted(self, obj): self.sig_ch.remove() @@ -766,6 +939,28 @@ class ServiceView(ObjectView): """ bus_interface = "net.connman.Service" + ipv4_fields = (#("Method", "ipv4_method"), + ("Address", "ipv4_address"), + ("Netmask", "ipv4_netmask"), + ("Gateway", "ipv4_gateway"), + ) + ipv6_fields = (#("Method", "ipv6_method"), + ("Address", "ipv6_address"), + ("Prefix Length", "ipv6_prefix_length"), + ("Gateway", "ipv6_gateway"), + #("Privacy", "ipv6_privacy"), + ) + proxy_fields = (("Method", "proxy_method"), + ("URL", "proxy_url"), + ("Servers", "proxy_servers"), + ("Excludes", "proxy_excludes"), + ) + vpn_fields = ( # named Provider in spec + ("Host", "vpn_host"), + ("Domain", "vpn_domain"), + ("Name", "vpn_name"), + ("Type", "vpn_type"), + ) eth_fields = (("Method", "eth_method"), ("Interface", "eth_iface"), ("Address", "eth_addr"), @@ -773,15 +968,6 @@ class ServiceView(ObjectView): ("Speed", "eth_speed"), ("Duplex", "eth_duplex"), ) - vpn_fields = (("Host", "vpn_host"), - ("Domain", "vpn_domain"), - ("Name", "vpn_name"), - ("Type", "vpn_type"), - ) - ipv4_fields = (("Address", "ipv4_address"), - ("Netmask", "ipv4_netmask"), - ("Gateway", "ipv4_gateway"), - ) top_widgets = ( "connect", @@ -800,16 +986,20 @@ class ServiceView(ObjectView): "domains_label", "domains_entry", "ipv4_frame", + "ipv6_frame", "proxy_frame", "ethernet_frame", "vpn_frame", + "ieee8021x_frame", ) def create_view(self, properties): self.type = str(properties.get("Type")) + self.security_mode = properties.get("Security") self.immutable = bool(properties.get("Immutable")) self.readwrite_list_properties = {} self.readwrite_list_widget = {} + self.name = str(properties.get("Name")) self.connect = self.add_button(self.box, "Connect", self._connect) self.disconnect = self.add_button(self.box, "Disconnect", @@ -818,6 +1008,12 @@ class ServiceView(ObjectView): if not self.immutable and self.type != "ethernet": self.forget = self.add_button(self.box, "Forget Network", self._forget) + elif self.type == "wifi" and "ieee8021x" in self.security_mode: + pnac_conf.read() + self.forget = self.add_button(self.box, "Forget Network", + self._forget) + if not pnac_conf.has_section(self.name): + self.forget.disabled = True self.error = self.add_label(self.box, "error here") @@ -851,6 +1047,7 @@ class ServiceView(ObjectView): self.domains_label = lb self.domains_entry = en + # section: IPv4 self.ipv4_properties = {"IPv4": {}, "IPv4.Configuration": {}} fr, bx = self.add_frame_and_box(self.box, "IPv4") self.ipv4_frame = fr @@ -864,10 +1061,27 @@ class ServiceView(ObjectView): en.callback_unfocused_add(self._on_ipv4_property_unfocused) setattr(self, attr, en) - if properties.get("IPv6"): - fr, bx = self.add_frame_and_box(self.box, "IPv6") - lb = self.add_label(bx, "TODO") + # section: IPv6 + self.ipv6_properties = {"IPv6": {}, "IPv6.Configuration": {}} + fr, bx = self.add_frame_and_box(self.box, "IPv6") + self.ipv6_frame = fr + self.ipv6_box = bx + options = ("Automatic", "Manual", "Off") + self.ipv6_method, self.ipv6_method_items = self.add_segment_control( + bx, options, self._on_ipv6_method) + for name, attr in self.ipv6_fields: + lb, en = self.add_label_and_entry(bx, name) + en.callback_activated_add(self._on_ipv6_property_changed) + en.callback_unfocused_add(self._on_ipv6_property_unfocused) + setattr(self, attr, en) + options = ("Disabled", "Enabled", "Prefered") + self.ipv6_privacy_lb, self.ipv6_privacy, self.ipv6_privacy_items = self.add_label_and_segment_control( + bx, options, self._on_ipv6_privacy, "Privacy") + # section: Proxy: custom contents for direct, auto and manual + # - direct: nothing + # - auto: url + # - manual: servers, excludes self.proxy_properties = {"Proxy": {}, "Proxy.Configuration": {}} fr, bx = self.add_frame_and_box(self.box, "Proxy") self.proxy_frame = fr @@ -877,12 +1091,7 @@ class ServiceView(ObjectView): bx, options, self._on_proxy_method) self.add_label(bx, "TODO") - # section IPv6: similar to ipv4? refactor ipv4? - # section Proxy: custom contents for direct, auto and manual - # - direct: nothing - # - auto: url - # - manual: servers, excludes - + # section: Ethernet / VPN if self.type in ("wifi", "ethernet", "wimax", "bluetooth", "cellular"): fr, bx = self.add_readonly_section("Ethernet", self.eth_fields) self.ethernet_frame = fr @@ -890,6 +1099,42 @@ class ServiceView(ObjectView): fr, bx = self.add_readonly_section("VPN", self.vpn_fields) self.vpn_frame = fr + # section: Two Phase Authentication + if (self.type == "wifi") and ("ieee8021x" in self.security_mode): + fr, bx = self.add_frame_and_box(self.box, "ieee8021x") + self.ieee8021x_frame = fr + #cfg_sec = pnac_conf.section_get(self.name) + + lb = self.add_label(bx, "EAP:") + options = ("PEAP", "TLS", "TTLS", "None") + self.eap_method, self.eap_method_items = self.add_segment_control( + bx, options, self._on_eap_method + ) + if pnac_conf.has_section(self.name): + conf_val = pnac_conf.get(self.name, 'EAP') + if conf_val == "peap": + self.eap_method_items["PEAP"].selected = True + elif conf_val == "tls": + self.eap_method_items["TLS"].selected = True + elif conf_val == "ttls": + self.eap_method_items["TTLS"].selected = True + elif conf_val is None: + self.eap_method_items["None"].selected = True + + options = ("TLS", "MSCHAPv2", "None") + lb = self.add_label(bx, "Phase2:") + self.phase2, self.phase2_items = self.add_segment_control( + bx, options, self._on_phase2 + ) + if pnac_conf.has_section(self.name): + conf_val = pnac_conf.get(self.name, 'Phase2') + if conf_val == "tls": + self.phase2_items["TLS"].selected = True + elif conf_val == "MSCHAPV2": + self.phase2_items["MSCHAPv2"].selected = True + elif conf_val is None: + self.phase2_items["None"].selected = True + def add_readonly_section(self, title, fields): fr, bx = self.add_frame_and_box(self.box, title) for name, attr in fields: @@ -923,15 +1168,20 @@ class ServiceView(ObjectView): orig_value = self.readwrite_list_properties[name] conf_value = self.readwrite_list_properties[conf] if (conf_value and value != conf_value) or \ - (not conf_value and value != orig_value): - log.debug("User changed %s=%r (%r, %r)", name, value, - orig_value, conf_value) - value = value.strip() - if not value: - value_array = [] - else: - value_array = list(x.strip() for x in value.split(",")) - self._on_readwrite_changed(conf, value_array) + (not conf_value and value != orig_value): + log.debug( + "User changed %s=%r (%r, %r)" % ( + name, value, + orig_value, conf_value + ) + ) + value = value.strip() + if not value: + value_array = [] + else: + value_array = list(x.strip() for x in value.split(",")) + self._on_readwrite_changed(conf, value_array) + def on_unfocused(obj): self.reload_readwrite_list(name) @@ -976,6 +1226,11 @@ class ServiceView(ObjectView): self.ipv4_address.disabled = value self.ipv4_netmask.disabled = value self.ipv4_gateway.disabled = value + self.ipv6_method.disabled = value + self.ipv6_address.disabled = value + self.ipv6_prefix_length.disabled = value + self.ipv6_gateway.disabled = value + self.ipv6_privacy.disabled = value self.proxy_method.disabled = value elif name == "Favorite": value = bool(value) @@ -1016,6 +1271,7 @@ class ServiceView(ObjectView): self.ipv4_properties[name] = value used = self.ipv4_properties["IPv4"] conf = self.ipv4_properties["IPv4.Configuration"] + def get_val(name): v = used.get(name) or conf.get(name) if not v: @@ -1039,11 +1295,53 @@ class ServiceView(ObjectView): self.ipv4_method_items["Off"].selected = True elif method: log.error("Unknown method: %s", method) + elif name in ("IPv6", "IPv6.Configuration"): + self.ipv6_properties[name] = value + used = self.ipv6_properties["IPv6"] + conf = self.ipv6_properties["IPv6.Configuration"] + + def get_val(name): + v = used.get(name) or conf.get(name) + if not v: + return "" + return str(v) + self.ipv6_address.text = get_val("Address") + self.ipv6_prefix_length.text = get_val("PrefixLength") + self.ipv6_gateway.text = get_val("Gateway") + + method = str(conf.get("Method", "")) + editable = (method == "manual") and (not self.immutable) + self.ipv6_address.editable = editable + self.ipv6_prefix_length.editable = editable + self.ipv6_gateway.editable = editable + # privacy has only meaning if Method is set to "auto" + editable = (method == "auto") and (not self.immutable) + self.ipv6_privacy.disabled = not editable + + if method in ("auto", "fixed", "6to4"): + self.ipv6_method_items["Automatic"].selected = True + elif method == "manual": + self.ipv6_method_items["Manual"].selected = True + elif method == "off": + self.ipv6_method_items["Off"].selected = True + elif method: + log.error("Unknown method: %s", method) + + privacy = str(conf.get("Privacy", "")) + if privacy == "disabled": + self.ipv6_privacy_items["Disabled"].selected = True + elif privacy == "enabled": + self.ipv6_privacy_items["Enabled"].selected = True + elif privacy == "prefered": + self.ipv6_privacy_items["Prefered"].selected = True + elif privacy: + log.error("Unknown privacy: %s", privacy) elif name in ("Proxy", "Proxy.Configuration"): self.proxy_properties[name] = value used = self.proxy_properties["Proxy"] conf = self.proxy_properties["Proxy.Configuration"] + def get_val(name): v = used.get(name) or conf.get(name) if not v: @@ -1074,7 +1372,6 @@ class ServiceView(ObjectView): if wid.visible: self.box.pack_end(wid) - def _disconnect(self, obj): def on_reply(): log.debug("Disconnected %s", self.path) @@ -1108,11 +1405,20 @@ class ServiceView(ObjectView): log.error("Could not remove %s", exc) self.forget.disabled = False - self.bus_obj.Remove(reply_handler=on_reply, error_handler=on_error) + if self.type == "wifi" and "ieee8021x" in self.security_mode: + if pnac_conf.has_section(self.name): + pnac_conf.remove_section(self.name) + for it in self.phase2_items: + self.phase2_items[it].selected = False + for it in self.eap_method_items: + self.eap_method_items[it].selected = False + else: + self.bus_obj.Remove(reply_handler=on_reply, error_handler=on_error) self.forget.disabled = True def _on_user_auto_connect(self, obj): state = obj.state + def on_reply(): log.info("Set AutoConnect=%s", state) @@ -1122,8 +1428,10 @@ class ServiceView(ObjectView): popup_error(self.obj, "Failed to Apply Auto Connect", exc.get_dbus_message()) - self.bus_obj.SetProperty("AutoConnect", dbus.Boolean(state), - reply_handler=on_reply, error_handler=on_error) + self.bus_obj.SetProperty( + "AutoConnect", dbus.Boolean(state), + reply_handler=on_reply, error_handler=on_error + ) def _on_readwrite_changed(self, name, value): def on_reply(): @@ -1135,8 +1443,10 @@ class ServiceView(ObjectView): popup_error(self.obj, "Failed to Apply %s" % (key,), exc.get_dbus_message()) self.reload_readwrite_list(key) - self.bus_obj.SetProperty(name, dbus.Array(value, signature="s"), - reply_handler=on_reply, error_handler=on_error) + self.bus_obj.SetProperty( + name, dbus.Array(value, signature="s"), + reply_handler=on_reply, error_handler=on_error + ) def _ipv4_apply(self): value = self.ipv4_method.item_selected.text @@ -1157,7 +1467,7 @@ class ServiceView(ObjectView): new["Netmask"] = make_variant(self.ipv4_netmask.text) if self.ipv4_gateway.text: new["Gateway"] = make_variant(self.ipv4_gateway.text) - if len(new) == 1: # no properties yet + if len(new) == 1: # no properties yet return conf = self.ipv4_properties["IPv4.Configuration"] @@ -1176,8 +1486,10 @@ class ServiceView(ObjectView): log.error("Failed to set IPv4.Configuration=%s: %s", new, exc) popup_error(self.obj, "Failed to Apply IPv4", exc.get_dbus_message()) - self.bus_obj.SetProperty("IPv4.Configuration", new, - reply_handler=on_reply, error_handler=on_error) + self.bus_obj.SetProperty( + "IPv4.Configuration", new, + reply_handler=on_reply, error_handler=on_error + ) def _on_ipv4_method(self, obj, item): if item.text == "Automatic": @@ -1201,6 +1513,7 @@ class ServiceView(ObjectView): def _on_ipv4_property_unfocused(self, obj): used = self.ipv4_properties["IPv4"] conf = self.ipv4_properties["IPv4.Configuration"] + def get_val(name): v = used.get(name) or conf.get(name) if not v: @@ -1210,6 +1523,102 @@ class ServiceView(ObjectView): self.ipv4_netmask.text = get_val("Netmask") self.ipv4_gateway.text = get_val("Gateway") + def _ipv6_apply(self): + value = self.ipv6_method.item_selected.text + if value == "Automatic": + method = "auto" + elif value == "Manual": + method = "manual" + elif value == "Off": + method = "off" + + def make_variant(s): + return dbus.String(s, variant_level=1) + new = {"Method": make_variant(method)} + if method == "manual": + if self.ipv6_address.text: + new["Address"] = make_variant(self.ipv6_address.text) + if self.ipv6_prefix_length.text: + new["PrefixLength"] = make_variant(self.ipv6_prefix_length.text) + if self.ipv6_gateway.text: + new["Gateway"] = make_variant(self.ipv6_gateway.text) + value = self.ipv6_privacy.item_selected.text + if value: + new["Privacy"] = make_variant(value) + if len(new) == 1: # no properties yet + return + + conf = self.ipv6_properties["IPv6.Configuration"] + changed = [] + for k, v in new.items(): + if conf.get(k) != v: + changed.append(k) + log.debug("Changed IPv6: %s", ", ".join(changed)) + if not changed: + return + + def on_reply(): + log.info("Set IPv6=%s", new) + + def on_error(exc): + log.error("Failed to set IPv6.Configuration=%s: %s", new, exc) + popup_error(self.obj, "Failed to Apply IPv6", + exc.get_dbus_message()) + self.bus_obj.SetProperty( + "IPv6.Configuration", new, + reply_handler=on_reply, error_handler=on_error + ) + + def _on_ipv6_method(self, obj, item): + if item.text == "Automatic": + method = "auto" + elif item.text == "Manual": + method = "manual" + elif item.text == "Off": + method = "off" + conf = self.ipv6_properties["IPv6.Configuration"] + + editable = (method == "manual") and (not self.immutable) + self.ipv6_address.editable = editable + self.ipv6_prefix_length.editable = editable + self.ipv6_gateway.editable = editable + # privacy has only meaning if Method is set to "auto" + editable = (method == "auto") and (not self.immutable) + self.ipv6_privacy.disabled = not editable + + if method == conf["Method"]: + return + self._ipv6_apply() + + def _on_ipv6_privacy(self, obj, item): + if item.text == "Disabled": + privacy = "disabled" + elif item.text == "Enabled": + privacy = "enabled" + elif item.text == "Prefered": + privacy = "prefered" + conf = self.ipv6_properties["IPv6.Configuration"] + if privacy == conf["Privacy"]: + return + self._ipv6_apply() + + def _on_ipv6_property_changed(self, obj): + self._ipv6_apply() + + def _on_ipv6_property_unfocused(self, obj): + used = self.ipv6_properties["IPv6"] + conf = self.ipv6_properties["IPv6.Configuration"] + + def get_val(name): + v = used.get(name) or conf.get(name) + if not v: + return "" + return str(v) + self.ipv6_address.text = get_val("Address") + self.ipv6_prefix_length.text = get_val("PrefixLength") + self.ipv6_gateway.text = get_val("Gateway") + #self.ipv6_privacy.text = get_val("Privacy") + def _on_proxy_method(self, obj, item): if item.text == "Direct": method = "direct" @@ -1218,7 +1627,7 @@ class ServiceView(ObjectView): elif item.text == "Automatic": method = "auto" conf = self.proxy_properties["Proxy.Configuration"] - editable = (method == "manual") and (not self.immutable) + #editable = (method == "manual") and (not self.immutable) # use editable... if method == conf["Method"]: return @@ -1232,6 +1641,34 @@ class ServiceView(ObjectView): pass #revert to configured values... + def _on_eap_method(self, obj, item): + eap_val = None + if item.text == "PEAP": + eap_val = "peap" + elif item.text == "TLS": + eap_val = "tls" + elif item.text == "TTLS": + eap_val = "ttls" + elif item.text == "None": + eap_val = None + + pnac_conf.set(self.name, "EAP", eap_val) + pnac_conf.write() + return + + def _on_phase2(self, obj, item): + phase2_val = None + if item.text == "MSCHAPv2": + phase2_val = "MSCHAPV2" + elif item.text == "TLS": + phase2_val = "tls" + elif item.text == "None": + phase2_val = None + + pnac_conf.set(self.name, 'Phase2', phase2_val) + pnac_conf.write() + return + ######################################################################## # Main Actions: @@ -1254,6 +1691,7 @@ def connect_service(path, properties): if type in ("system", "gps", "gadget"): log.error("cannot connect to service with type: %s", type) return + sec = properties.get("Security") name = properties.get("Name") if name: @@ -1261,13 +1699,24 @@ def connect_service(path, properties): log.debug("connect to %s (%s): %s", name, path, dbus_dict_to_str(properties)) + # Connman only supports two phase auth via config files + if ("ieee8021x" in sec) and not pnac_conf.has_section(name): + popup_error( + win, + "This Network needs Configuration", + "This network uses 802.1x authentication. " + "Please configure the options in the section at the bottom." + ) + show_service(path, properties) + return + def on_reply(): log.info("Connected to %s (%s)", name, path) def on_error(exc): exc_name = exc.get_dbus_name() if exc_name == "net.connman.Error.AlreadyConnected" or \ - exc_name == "net.connman.Error.InProgress": + exc_name == "net.connman.Error.InProgress": log.debug("Failed to Connect to %s (%s): %s", name, path, exc) return log.error("Failed to Connect to %s (%s): %s", name, path, exc) @@ -1299,6 +1748,7 @@ def agent_method(in_signature="", out_signature="", **kargs): out_signature=out_signature, **kargs) + class Agent(dbus.service.Object): path = "/org/enlightenment/econnman/agent" @@ -1486,6 +1936,8 @@ if __name__ == "__main__": level -= 10 * args.verbose log.setLevel(level) + pnac_conf = PNACConfig() + elm.init() elm.policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED) @@ -1537,4 +1989,6 @@ if __name__ == "__main__": log.info("Registered agent at %s", agent.path) elm.run() + #pnac_conf.write() + logging.shutdown() elm.shutdown()