diff options
author | Marco Pesenti Gritti <mpgritti@gmail.com> | 2008-10-22 16:03:04 (GMT) |
---|---|---|
committer | Marco Pesenti Gritti <mpgritti@gmail.com> | 2008-10-22 16:03:04 (GMT) |
commit | 51eb47d2c0f7b7896e3a8c0ea1e2999d754af8cc (patch) | |
tree | 47a31eb2e0fa6e31fdfd431732fc269e06a5cd85 /src/jarabe | |
parent | d5185f7ed4464959beed10ab30a1bc10eb9b0d36 (diff) |
Make connection to WEP work. Lots of cleanups left to
in keydialog, really just an hack to make it work there.
Diffstat (limited to 'src/jarabe')
-rw-r--r-- | src/jarabe/desktop/Makefile.am | 1 | ||||
-rw-r--r-- | src/jarabe/desktop/keydialog.py | 344 | ||||
-rw-r--r-- | src/jarabe/desktop/meshbox.py | 59 | ||||
-rw-r--r-- | src/jarabe/model/network.py | 24 |
4 files changed, 410 insertions, 18 deletions
diff --git a/src/jarabe/desktop/Makefile.am b/src/jarabe/desktop/Makefile.am index 044a943..94d8ab9 100644 --- a/src/jarabe/desktop/Makefile.am +++ b/src/jarabe/desktop/Makefile.am @@ -9,6 +9,7 @@ sugar_PYTHON = \ groupbox.py \ homebox.py \ homewindow.py \ + keydialog.py \ meshbox.py \ myicon.py \ proc_smaps.py \ diff --git a/src/jarabe/desktop/keydialog.py b/src/jarabe/desktop/keydialog.py new file mode 100644 index 0000000..b992db5 --- /dev/null +++ b/src/jarabe/desktop/keydialog.py @@ -0,0 +1,344 @@ +# vi: ts=4 ai noet +# +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# 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 St, Fifth Floor, Boston, MA 02110-1301 USA + +import md5 +from gettext import gettext as _ + +import gtk + +IW_AUTH_ALG_OPEN_SYSTEM = 0x00000001 +IW_AUTH_ALG_SHARED_KEY = 0x00000002 + +IW_AUTH_WPA_VERSION_DISABLED = 0x00000001 +IW_AUTH_WPA_VERSION_WPA = 0x00000002 +IW_AUTH_WPA_VERSION_WPA2 = 0x00000004 + +NM_802_11_CAP_NONE = 0x00000000 +NM_802_11_CAP_PROTO_NONE = 0x00000001 +NM_802_11_CAP_PROTO_WEP = 0x00000002 +NM_802_11_CAP_PROTO_WPA = 0x00000004 +NM_802_11_CAP_PROTO_WPA2 = 0x00000008 +NM_802_11_CAP_KEY_MGMT_PSK = 0x00000040 +NM_802_11_CAP_KEY_MGMT_802_1X = 0x00000080 +NM_802_11_CAP_CIPHER_WEP40 = 0x00001000 +NM_802_11_CAP_CIPHER_WEP104 = 0x00002000 +NM_802_11_CAP_CIPHER_TKIP = 0x00004000 +NM_802_11_CAP_CIPHER_CCMP = 0x00008000 + +NM_AUTH_TYPE_WPA_PSK_AUTO = 0x00000000 +IW_AUTH_CIPHER_NONE = 0x00000001 +IW_AUTH_CIPHER_WEP40 = 0x00000002 +IW_AUTH_CIPHER_TKIP = 0x00000004 +IW_AUTH_CIPHER_CCMP = 0x00000008 +IW_AUTH_CIPHER_WEP104 = 0x00000010 + +IW_AUTH_KEY_MGMT_802_1X = 0x1 +IW_AUTH_KEY_MGMT_PSK = 0x2 + +def string_is_hex(key): + is_hex = True + for c in key: + if not 'a' <= c.lower() <= 'f' and not '0' <= c <= '9': + is_hex = False + return is_hex + +def string_is_ascii(string): + try: + string.encode('ascii') + return True + except UnicodeEncodeError: + return False + +def string_to_hex(passphrase): + key = '' + for c in passphrase: + key += '%02x' % ord(c) + return key + +def hash_passphrase(passphrase): + # passphrase must have a length of 64 + if len(passphrase) > 64: + passphrase = passphrase[:64] + elif len(passphrase) < 64: + while len(passphrase) < 64: + passphrase += passphrase[:64 - len(passphrase)] + passphrase = md5.new(passphrase).digest() + return string_to_hex(passphrase)[:26] + +class KeyDialog(gtk.Dialog): + def __init__(self, ssid, caps, async_cb, async_err_cb): + gtk.Dialog.__init__(self, flags=gtk.DIALOG_MODAL) + self.set_title("Wireless Key Required") + + self._async_cb = async_cb + self._async_err_cb = async_err_cb + self._entry = None + self._ssid = ssid + self._caps = caps + + self.set_has_separator(False) + + label = gtk.Label("A wireless encryption key is required for\n" \ + " the wireless network '%s'." % self._ssid) + self.vbox.pack_start(label) + + self.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OK, gtk.RESPONSE_OK) + self.set_default_response(gtk.RESPONSE_OK) + self.set_has_separator(True) + + def add_key_entry(self): + self._entry = gtk.Entry() + #self._entry.props.visibility = False + self._entry.connect('changed', self._update_response_sensitivity) + self._entry.connect('activate', self._entry_activate_cb) + self.vbox.pack_start(self._entry) + self.vbox.set_spacing(6) + self.vbox.show_all() + + self._update_response_sensitivity() + self._entry.grab_focus() + + def _entry_activate_cb(self, entry): + self.response(gtk.RESPONSE_OK) + + def create_security(self): + raise NotImplementedError + + def get_callbacks(self): + return (self._async_cb, self._async_err_cb) + +WEP_PASSPHRASE = 1 +WEP_HEX = 2 +WEP_ASCII = 3 + +class WEPKeyDialog(KeyDialog): + def __init__(self, ssid, caps, async_cb, async_err_cb): + KeyDialog.__init__(self, ssid, caps, async_cb, async_err_cb) + + # WEP key type + self.key_store = gtk.ListStore(str, int) + self.key_store.append(["Passphrase (128-bit)", WEP_PASSPHRASE]) + self.key_store.append(["Hex (40/128-bit)", WEP_HEX]) + self.key_store.append(["ASCII (40/128-bit)", WEP_ASCII]) + + self.key_combo = gtk.ComboBox(self.key_store) + cell = gtk.CellRendererText() + self.key_combo.pack_start(cell, True) + self.key_combo.add_attribute(cell, 'text', 0) + self.key_combo.set_active(0) + self.key_combo.connect('changed', self._key_combo_changed_cb) + + hbox = gtk.HBox() + hbox.pack_start(gtk.Label(_("Key Type:"))) + hbox.pack_start(self.key_combo) + hbox.show_all() + self.vbox.pack_start(hbox) + + # Key entry field + self.add_key_entry() + + # WEP authentication mode + self.auth_store = gtk.ListStore(str, int) + self.auth_store.append(["Open System", IW_AUTH_ALG_OPEN_SYSTEM]) + self.auth_store.append(["Shared Key", IW_AUTH_ALG_SHARED_KEY]) + + self.auth_combo = gtk.ComboBox(self.auth_store) + cell = gtk.CellRendererText() + self.auth_combo.pack_start(cell, True) + self.auth_combo.add_attribute(cell, 'text', 0) + self.auth_combo.set_active(0) + + hbox = gtk.HBox() + hbox.pack_start(gtk.Label(_("Authentication Type:"))) + hbox.pack_start(self.auth_combo) + hbox.show_all() + + self.vbox.pack_start(hbox) + + def _key_combo_changed_cb(self, widget): + self._update_response_sensitivity() + + def _get_security(self): + key = self._entry.get_text() + + it = self.key_combo.get_active_iter() + (key_type, ) = self.key_store.get(it, 1) + + if key_type == WEP_PASSPHRASE: + key = hash_passphrase(key) + elif key_type == WEP_ASCII: + key = string_to_hex(key) + + it = self.auth_combo.get_active_iter() + (auth_alg, ) = self.auth_store.get(it, 1) + + we_cipher = None + if len(key) == 26: + we_cipher = IW_AUTH_CIPHER_WEP104 + elif len(key) == 10: + we_cipher = IW_AUTH_CIPHER_WEP40 + + return (we_cipher, key, auth_alg) + + def print_security(self): + (we_cipher, key, auth_alg) = self._get_security() + print "Cipher: %d" % we_cipher + print "Key: %s" % key + print "Auth: %d" % auth_alg + + def create_security(self): + (we_cipher, key, auth_alg) = self._get_security() + return { "802-11-wireless-security": { "wep-key0": key } } + + def _update_response_sensitivity(self, ignored=None): + key = self._entry.get_text() + it = self.key_combo.get_active_iter() + (key_type, ) = self.key_store.get(it, 1) + + valid = False + if key_type == WEP_PASSPHRASE: + # As the md5 passphrase can be of any length and has no indicator, + # we cannot check for the validity of the input. + if len(key) > 0: + valid = True + elif key_type == WEP_ASCII: + if len(key) == 5 or len(key) == 13: + valid = string_is_ascii(key) + elif key_type == WEP_HEX: + if len(key) == 10 or len(key) == 26: + valid = string_is_hex(key) + + self.set_response_sensitive(gtk.RESPONSE_OK, valid) + +class WPAKeyDialog(KeyDialog): + def __init__(self, ssid, caps, async_cb, async_err_cb): + KeyDialog.__init__(self, ssid, caps, async_cb, async_err_cb) + self.add_key_entry() + + self.store = gtk.ListStore(str, int) + self.store.append(["Automatic", NM_AUTH_TYPE_WPA_PSK_AUTO]) + if caps & NM_802_11_CAP_CIPHER_CCMP: + self.store.append(["AES-CCMP", IW_AUTH_CIPHER_CCMP]) + if caps & NM_802_11_CAP_CIPHER_TKIP: + self.store.append(["TKIP", IW_AUTH_CIPHER_TKIP]) + + self.combo = gtk.ComboBox(self.store) + cell = gtk.CellRendererText() + self.combo.pack_start(cell, True) + self.combo.add_attribute(cell, 'text', 0) + self.combo.set_active(0) + + self.hbox = gtk.HBox() + self.hbox.pack_start(gtk.Label(_("Encryption Type:"))) + self.hbox.pack_start(self.combo) + self.hbox.show_all() + + self.vbox.pack_start(self.hbox) + + def _get_security(self): + ssid = self._ssid + key = self._entry.get_text() + is_hex = string_is_hex(key) + + real_key = None + if len(key) == 64 and is_hex: + # Hex key + real_key = key + elif len(key) >= 8 and len(key) <= 63: + # passphrase + from subprocess import Popen, PIPE + p = Popen(['/usr/sbin/wpa_passphrase', ssid, key], stdout=PIPE) + for line in p.stdout: + if line.strip().startswith("psk="): + real_key = line.strip()[4:] + if p.wait() != 0: + raise RuntimeError("Error hashing passphrase") + if real_key and len(real_key) != 64: + real_key = None + + if not real_key: + raise RuntimeError("Invalid key") + + it = self.combo.get_active_iter() + (we_cipher, ) = self.store.get(it, 1) + + wpa_ver = IW_AUTH_WPA_VERSION_WPA + if self._caps & NM_802_11_CAP_PROTO_WPA2: + wpa_ver = IW_AUTH_WPA_VERSION_WPA2 + + return (we_cipher, real_key, wpa_ver) + + def print_security(self): + (we_cipher, key, wpa_ver) = self._get_security() + print "Cipher: %d" % we_cipher + print "Key: %s" % key + print "WPA Ver: %d" % wpa_ver + + def create_security(self): + (we_cipher, key, wpa_ver) = self._get_security() + from nminfo import Security + return Security.new_from_args(we_cipher, + (key, wpa_ver, IW_AUTH_KEY_MGMT_PSK)) + + def _update_response_sensitivity(self, ignored=None): + key = self._entry.get_text() + is_hex = string_is_hex(key) + + valid = False + if len(key) == 64 and is_hex: + # hex key + valid = True + elif len(key) >= 8 and len(key) <= 63: + # passphrase + valid = True + self.set_response_sensitive(gtk.RESPONSE_OK, valid) + return False + +def create(ssid, caps, async_cb, async_err_cb): + if (caps & NM_802_11_CAP_CIPHER_TKIP or caps & NM_802_11_CAP_CIPHER_CCMP) \ + and (caps & NM_802_11_CAP_PROTO_WPA or \ + caps & NM_802_11_CAP_PROTO_WPA2): + key_dialog = WPAKeyDialog(ssid, caps, async_cb, async_err_cb) + else: + key_dialog = WEPKeyDialog(ssid, caps, async_cb, async_err_cb) + + key_dialog.connect("response", _key_dialog_response_cb) + key_dialog.connect("destroy", _key_dialog_destroy_cb) + key_dialog.show_all() + +def _key_dialog_destroy_cb(key_dialog, data=None): + _key_dialog_response_cb(key_dialog, gtk.RESPONSE_CANCEL) + +def _key_dialog_response_cb(key_dialog, response_id): + (async_cb, async_err_cb) = key_dialog.get_callbacks() + security = None + if response_id == gtk.RESPONSE_OK: + security = key_dialog.create_security() + key_dialog.destroy() + + if response_id in [gtk.RESPONSE_CANCEL, gtk.RESPONSE_NONE]: + # key dialog dialog was canceled; send the error back to NM + async_err_cb(CanceledKeyRequestError()) + elif response_id == gtk.RESPONSE_OK: + if not security: + raise RuntimeError("Invalid security arguments.") + async_cb(security) + else: + raise RuntimeError("Unhandled key dialog response %d" % response_id) + diff --git a/src/jarabe/desktop/meshbox.py b/src/jarabe/desktop/meshbox.py index 83474e8..ae17091 100644 --- a/src/jarabe/desktop/meshbox.py +++ b/src/jarabe/desktop/meshbox.py @@ -40,6 +40,7 @@ from jarabe.view.buddyicon import BuddyIcon from jarabe.view.pulsingicon import CanvasPulsingIcon from jarabe.desktop.snowflakelayout import SnowflakeLayout from jarabe.desktop.spreadlayout import SpreadLayout +from jarabe.desktop import keydialog from jarabe.model import bundleregistry from jarabe.model import network @@ -49,6 +50,7 @@ _NM_PATH = '/org/freedesktop/NetworkManager' _NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device' _NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless' _NM_ACCESSPOINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint' +_NM_ACTIVE_CONN_IFACE = 'org.freedesktop.NetworkManager.Connection.Active' _ICON_NAME = 'network-wireless' @@ -65,6 +67,7 @@ class AccessPointView(CanvasPulsingIcon): self._name = '' self._strength = 0 self._flags = 0 + self._wpa_flags = 0 self._device_state = None self._active = True @@ -138,12 +141,15 @@ class AccessPointView(CanvasPulsingIcon): self._update_state() def _update_properties(self, props): + logging.debug(props) if 'Ssid' in props: self._name = props['Ssid'] if 'Strength' in props: self._strength = props['Strength'] if 'Flags' in props: self._flags = props['Flags'] + if 'WpaFlags' in props: + self._wpa_flags = props['WpaFlags'] self._update() @@ -239,10 +245,14 @@ class AccessPointView(CanvasPulsingIcon): conn = network.find_connection(self._name) if conn is None: info = { 'connection': { 'id' : 'Auto ' + self._name, - 'uuid' : unique_id(), - 'type' : '802-11-wireless' } , - '802-11-wireless' : { 'ssid': self._name } + 'uuid' : unique_id(), + 'type' : '802-11-wireless' } , + '802-11-wireless' : { 'ssid': self._name } } + + if self._flags == network.AP_FLAGS_802_11_PRIVACY: + info["802-11-wireless-security"] = { "key-mgmt": "none" } + conn = network.add_connection(self._name, info) obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) @@ -263,6 +273,9 @@ class AccessPointView(CanvasPulsingIcon): self._greyed_out = self._name.lower().find(query) == -1 self._update_state() + def create_keydialog(self, reply, error): + keydialog.create(self._name, self._wpa_flags, reply, error) + def disconnect(self): self._bus.remove_signal_receiver(self.__ap_properties_changed_cb, signal_name='PropertiesChanged', @@ -502,17 +515,18 @@ class NetworkManagerObserver(object): self._box = box self._bus = dbus.SystemBus() self._devices = {} + self._netmgr = None def listen(self): try: obj = self._bus.get_object(_NM_SERVICE, _NM_PATH) - netmgr = dbus.Interface(obj, _NM_IFACE) + self._netmgr = dbus.Interface(obj, _NM_IFACE) except dbus.DBusException: logging.debug('%s service not available', _NM_SERVICE) return - netmgr.GetDevices(reply_handler=self._get_devices_reply_cb, - error_handler=self._get_devices_error_cb) + self._netmgr.GetDevices(reply_handler=self.__get_devices_reply_cb, + error_handler=self.__get_devices_error_cb) self._bus.add_signal_receiver(self.__device_added_cb, signal_name='DeviceAdded', @@ -521,11 +535,27 @@ class NetworkManagerObserver(object): signal_name='DeviceRemoved', dbus_interface=_NM_DEVICE_IFACE) - def _get_devices_reply_cb(self, devices_o): + settings = network.get_settings() + settings.secrets_request.connect(self.__secrets_request_cb) + + def __secrets_request_cb(self, **kwargs): + netmgr_props = dbus.Interface( + self._netmgr, 'org.freedesktop.DBus.Properties') + active_connections_o = netmgr_props.Get(_NM_IFACE, 'ActiveConnections') + + for conn_o in active_connections_o: + obj = self._bus.get_object(_NM_IFACE, conn_o) + props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') + ap_o = props.Get(_NM_ACTIVE_CONN_IFACE, 'SpecificObject') + + ap_view = self._box.access_points[ap_o] + ap_view.create_keydialog(kwargs['reply'], kwargs['error']) + + def __get_devices_reply_cb(self, devices_o): for dev_o in devices_o: self._check_device(dev_o) - def _get_devices_error_cb(self, err): + def __get_devices_error_cb(self, err): logging.error('Failed to get devices: %s', err) def _check_device(self, device_o): @@ -556,10 +586,11 @@ class MeshBox(gtk.VBox): gobject.GObject.__init__(self) + self.access_points = {} + self._model = neighborhood.get_model() self._buddies = {} self._activities = {} - self._access_points = {} self._mesh = {} self._buddy_to_activity = {} self._suspended = True @@ -696,24 +727,24 @@ class MeshBox(gtk.VBox): if hasattr(icon, 'set_filter'): icon.set_filter(self._query) - self._access_points[ap.object_path] = icon + self.access_points[ap.object_path] = icon def remove_access_point(self, ap_o): - icon = self._access_points[ap_o] + icon = self.access_points[ap_o] icon.disconnect() self._layout.remove(icon) - del self._access_points[ap_o] + del self.access_points[ap_o] def suspend(self): if not self._suspended: self._suspended = True - for ap in self._access_points.values(): + for ap in self.access_points.values(): ap.props.paused = True def resume(self): if self._suspended: self._suspended = False - for ap in self._access_points.values(): + for ap in self.access_points.values(): ap.props.paused = False def _toolbar_query_changed_cb(self, toolbar, query): diff --git a/src/jarabe/model/network.py b/src/jarabe/model/network.py index 21047c5..a7a6bd1 100644 --- a/src/jarabe/model/network.py +++ b/src/jarabe/model/network.py @@ -14,6 +14,8 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import logging + import dbus from sugar import dispatch @@ -49,7 +51,9 @@ class NMSettings(dbus.service.Object): bus = dbus.SystemBus() bus_name = dbus.service.BusName(SETTINGS_SERVICE, bus=bus) dbus.service.Object.__init__(self, bus_name, NM_SETTINGS_PATH) + self.connections = {} + self.secrets_request = dispatch.Signal() @dbus.service.method(dbus_interface=NM_SETTINGS_IFACE, in_signature='', out_signature='ao') @@ -62,8 +66,14 @@ class NMSettings(dbus.service.Object): def add_connection(self, ssid, conn): self.connections[ssid] = conn + conn.secrets_request.connect(self.__secrets_request_cb) self.NewConnection(conn.path) + def __secrets_request_cb(self, sender, **kwargs): + self.secrets_request.send(self, connection=sender, + reply=kwargs['reply'], + error=kwargs['error']) + class NMSettingsConnection(dbus.service.Object): def __init__(self, path, settings, secrets): bus = dbus.SystemBus() @@ -82,12 +92,18 @@ class NMSettingsConnection(dbus.service.Object): return self._settings @dbus.service.method(dbus_interface=NM_SECRETS_IFACE, + async_callbacks=('reply', 'error'), in_signature='sasb', out_signature='a{sa{sv}}') - def GetSecrets(self, setting_name, hints, request_new): - if request_new or self._secrets is None: - self.secrets_request.send(self) + def GetSecrets(self, setting_name, hints, request_new, reply, error): + logging.debug('Secrets requested for connection %s', self.path) - return self._secrets + if request_new or self._secrets is None: + try: + self.secrets_request.send(self, reply=reply, error=error) + except Exception, e: + logging.error(e) + else: + reply(self._secrets) def get_settings(): global _nm_settings |