diff --git a/shell/hardware/Makefile.am b/shell/hardware/Makefile.am index a3339a6c..320600e5 100644 --- a/shell/hardware/Makefile.am +++ b/shell/hardware/Makefile.am @@ -4,7 +4,7 @@ sugar_PYTHON = \ hardwaremanager.py \ nmclient.py \ nminfo.py \ - wepkeydialog.py + keydialog.py dbusservicedir = $(sysconfdir)/dbus-1/system.d/ dbusservice_DATA = NetworkManagerInfo.conf diff --git a/shell/hardware/keydialog.py b/shell/hardware/keydialog.py new file mode 100644 index 00000000..a19a43a6 --- /dev/null +++ b/shell/hardware/keydialog.py @@ -0,0 +1,252 @@ +# 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 + +from gettext import gettext as _ +import gobject, gtk +import logging + +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 + +class KeyDialog(gtk.Dialog): + def __init__(self, net, async_cb, async_err_cb): + gtk.Dialog.__init__(self, flags=gtk.DIALOG_MODAL) + self.set_title("Wireless Key Required") + + self._net = net + self._async_cb = async_cb + self._async_err_cb = async_err_cb + + self.set_has_separator(False) + + label = gtk.Label("A wireless encryption key is required for\n" \ + " the wireless network '%s'." % net.get_ssid()) + self.vbox.pack_start(label) + + self._entry = gtk.Entry() + #self._entry.props.visibility = False + self._entry.connect('changed', self._update_response_sensitivity) + self.vbox.pack_start(self._entry) + self.vbox.set_spacing(6) + self.vbox.show_all() + + self.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, + gtk.STOCK_OK, gtk.RESPONSE_OK) + self.set_default_response(gtk.RESPONSE_OK) + + self._update_response_sensitivity() + + self._entry.grab_focus() + self.set_has_separator(True) + + def create_security(self): + raise NotImplementedError + + def get_network(self): + return self._net + + def get_callbacks(self): + return (self._async_cb, self._async_err_cb) + +class WEPKeyDialog(KeyDialog): + def __init__(self, net, async_cb, async_err_cb): + KeyDialog.__init__(self, net, async_cb, async_err_cb) + + self.store = gtk.ListStore(str, int) + self.store.append(["Open System", IW_AUTH_ALG_OPEN_SYSTEM]) + self.store.append(["Shared Key", IW_AUTH_ALG_SHARED_KEY]) + + 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(_("Authentication Type:"))) + self.hbox.pack_start(self.combo) + self.hbox.show_all() + + self.vbox.pack_start(self.hbox) + + def create_security(self): + key = self._entry.get_text() + + it = self.combo.get_active_iter() + (auth_alg, ) = self.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 + + from nminfo import Security + return Security.new_from_args(we_cipher, (key, auth_alg)) + + def _update_response_sensitivity(self, ignored=None): + key = self._entry.get_text() + is_hex = string_is_hex(key) + valid_len = (len(key) == 10 or len(key) == 26) + self.set_response_sensitive(gtk.RESPONSE_OK, is_hex and valid_len) + +class WPAKeyDialog(KeyDialog): + def __init__(self, net, async_cb, async_err_cb): + KeyDialog.__init__(self, net, async_cb, async_err_cb) + + self.store = gtk.ListStore(str, int) + self.store.append(["Automatic", NM_AUTH_TYPE_WPA_PSK_AUTO]) + if net.get_caps() & NM_802_11_CAP_CIPHER_CCMP: + self.store.append(["AES-CCMP", IW_AUTH_CIPHER_CCMP]) + if net.get_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 create_security(self): + ssid = self.get_network().get_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 + import commands + (s, o) = commands.getstatusoutput("/usr/sbin/wpa_passphrase '%s' '%s'" % (ssid, key)) + if s != 0: + raise RuntimeError("Error hashing passphrase: %s" % o) + lines = o.split("\n") + for line in lines: + if line.strip().startswith("psk="): + real_key = line.strip()[4:] + 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 + caps = self.get_network().get_caps() + if caps & NM_802_11_CAP_PROTO_WPA2: + logging.debug("Caps WPA2") + wpa_ver = IW_AUTH_WPA_VERSION_WPA2 + + from nminfo import Security + logging.debug("new security with caps 0x%X, wpa %d" % (caps, wpa_ver)) + return Security.new_from_args(we_cipher, (real_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 new_key_dialog(net, async_cb, async_err_cb): + caps = net.get_caps() + 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): + return WPAKeyDialog(net, async_cb, async_err_cb) + elif (caps & NM_802_11_CAP_CIPHER_WEP40 or caps & NM_802_11_CAP_CIPHER_WEP104) and \ + (caps & NM_802_11_CAP_PROTO_WEP): + return WEPKeyDialog(net, async_cb, async_err_cb) + else: + raise RuntimeError("Unhandled network capabilities %x" % caps) + + + +class FakeNet(object): + def get_ssid(self): + return "olpcwpa" + + def get_caps(self): +# return NM_802_11_CAP_CIPHER_WEP104 | NM_802_11_CAP_PROTO_WEP + return NM_802_11_CAP_CIPHER_CCMP | NM_802_11_CAP_CIPHER_TKIP | NM_802_11_CAP_PROTO_WPA + +def response_cb(widget, response_id): + if response_id == gtk.RESPONSE_OK: + print dialog.get_options() + else: + print "canceled" + widget.hide() + widget.destroy() + + +if __name__ == "__main__": + net = FakeNet() + dialog = new_key_dialog(net, None, None) + dialog.connect("response", response_cb) + dialog.run() + diff --git a/shell/hardware/nmclient.py b/shell/hardware/nmclient.py index b699a043..fd8855d2 100644 --- a/shell/hardware/nmclient.py +++ b/shell/hardware/nmclient.py @@ -24,7 +24,6 @@ import dbus.decorators import gobject import gtk -from hardware.wepkeydialog import WEPKeyDialog from hardware import nminfo IW_AUTH_ALG_OPEN_SYSTEM = 0x00000001 @@ -37,7 +36,6 @@ NM_DEVICE_STAGE_STRINGS=("Unknown", "IP Config", "IP Config Get", "IP Config Commit", - "Post-IP Start", "Activated", "Failed", "Canceled" @@ -114,13 +112,13 @@ class Network(gobject.GObject): self._mode = props[6] self._caps = props[7] if self._caps & NM_802_11_CAP_PROTO_WPA or self._caps & NM_802_11_CAP_PROTO_WPA2: - # We do not support WPA at this time, so don't show - # WPA-enabled access points in the menu - logging.debug("Net(%s): ssid '%s' dropping because WPA[2] unsupported" % (self._op, - self._ssid)) - self._valid = False - self.emit('initialized', self._valid) - return + if not (self._caps & NM_802_11_CAP_KEY_MGMT_PSK): + # 802.1x is not supported at this time + logging.debug("Net(%s): ssid '%s' dropping because 802.1x is unsupported" % (self._op, + self._ssid)) + self._valid = False + self.emit('initialized', self._valid) + return if self._mode != IW_MODE_INFRA: # Don't show Ad-Hoc networks; they usually don't DHCP and therefore # won't work well here. This also works around the bug where we show @@ -132,9 +130,7 @@ class Network(gobject.GObject): return self._valid = True -# logging.debug("Net(%s): ssid '%s', mode %d, strength %d" % (self._op, -# self._ssid, self._mode, self._strength)) - + logging.debug("Net(%s): caps 0x%X" % (self._ssid, self._caps)) self.emit('initialized', self._valid) def _update_error_cb(self, err): @@ -560,55 +556,6 @@ class NMClient(gobject.GObject): else: raise dbus.DBusException(e) - def get_key_for_network(self, net, async_cb, async_err_cb): - # Throw up a dialog asking for the key here, and set - # the authentication algorithm to the given one, if any - # - # Key needs to be limited to _either_ 10 or 26 digits long, - # and contain _only_ _hex_ digits, 0-9 or a-f - # - # Auth algorithm should be a dropdown of: [Open System, Shared Key], - # mapping to the values [IW_AUTH_ALG_OPEN_SYSTEM, IW_AUTH_ALG_SHARED_KEY] - # above - - self._key_dialog = WEPKeyDialog(net, async_cb, async_err_cb) - self._key_dialog.connect("response", self._key_dialog_response_cb) - self._key_dialog.connect("destroy", self._key_dialog_destroy_cb) - self._key_dialog.show_all() - - def _key_dialog_destroy_cb(self, widget, foo=None): - if widget != self._key_dialog: - return - self._key_dialog_response_cb(widget, gtk.RESPONSE_CANCEL) - - def _key_dialog_response_cb(self, widget, response_id): - if widget != self._key_dialog: - return - key = self._key_dialog.get_key() - wep_auth_alg = self._key_dialog.get_auth_alg() - net = self._key_dialog.get_network() - (async_cb, async_err_cb) = self._key_dialog.get_callbacks() - - # Clear self._key_dialog before we call destroy(), otherwise - # the destroy will trigger and we'll get called again by - # self._key_dialog_destroy_cb - self._key_dialog = None - widget.destroy() - - if response_id == gtk.RESPONSE_OK: - self.nminfo.get_key_for_network_cb( - net, key, wep_auth_alg, async_cb, async_err_cb, canceled=False) - else: - self.nminfo.get_key_for_network_cb( - net, None, None, async_cb, async_err_cb, canceled=True) - - def cancel_get_key_for_network(self): - # Close the wireless key dialog and just have it return - # with the 'canceled' argument set to true - if not self._key_dialog: - return - self._key_dialog_destroy_cb(self._key_dialog) - def state_changed_sig_handler(self, new_state): logging.debug('NM State Changed to %d' % new_state) diff --git a/shell/hardware/nminfo.py b/shell/hardware/nminfo.py index e74786ff..836a5569 100644 --- a/shell/hardware/nminfo.py +++ b/shell/hardware/nminfo.py @@ -25,10 +25,26 @@ import ConfigParser import logging import nmclient -try: - from sugar import env -except ImportError: - pass +import keydialog +import gtk +from sugar import env + +IW_AUTH_KEY_MGMT_802_1X = 0x1 +IW_AUTH_KEY_MGMT_PSK = 0x2 + +IW_AUTH_WPA_VERSION_DISABLED = 0x00000001 +IW_AUTH_WPA_VERSION_WPA = 0x00000002 +IW_AUTH_WPA_VERSION_WPA2 = 0x00000004 + +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_ALG_OPEN_SYSTEM = 0x00000001 +IW_AUTH_ALG_SHARED_KEY = 0x00000002 NM_INFO_IFACE='org.freedesktop.NetworkManagerInfo' NM_INFO_PATH='/org/freedesktop/NetworkManagerInfo' @@ -87,15 +103,6 @@ class NMConfig(ConfigParser.ConfigParser): raise ValueError("Invalid format for %s/%s. Should be a valid float." % (section, name)) -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_ALG_OPEN_SYSTEM = 0x00000001 -IW_AUTH_ALG_SHARED_KEY = 0x00000002 - NETWORK_TYPE_UNKNOWN = 0 NETWORK_TYPE_ALLOWED = 1 NETWORK_TYPE_INVALID = 2 @@ -119,9 +126,9 @@ class Security(object): security = Security(we_cipher) elif we_cipher == IW_AUTH_CIPHER_WEP40 or we_cipher == IW_AUTH_CIPHER_WEP104: security = WEPSecurity(we_cipher) + elif we_cipher == NM_AUTH_TYPE_WPA_PSK_AUTO or we_cipher == IW_AUTH_CIPHER_CCMP or we_cipher == IW_AUTH_CIPHER_TKIP: + security = WPASecurity(we_cipher) else: - # FIXME: find a way to make WPA config option matrix not - # make you want to throw up raise ValueError("Unsupported security combo") security.read_from_config(cfg, name) except (ConfigParser.NoOptionError, ValueError), e: @@ -136,9 +143,9 @@ class Security(object): security = Security(we_cipher) elif we_cipher == IW_AUTH_CIPHER_WEP40 or we_cipher == IW_AUTH_CIPHER_WEP104: security = WEPSecurity(we_cipher) + elif we_cipher == NM_AUTH_TYPE_WPA_PSK_AUTO or we_cipher == IW_AUTH_CIPHER_CCMP or we_cipher == IW_AUTH_CIPHER_TKIP: + security = WPASecurity(we_cipher) else: - # FIXME: find a way to make WPA config option matrix not - # make you want to throw up raise ValueError("Unsupported security combo") security.read_from_args(args) except ValueError, e: @@ -198,6 +205,62 @@ class WEPSecurity(Security): config.set(section, "key", self._key) config.set(section, "auth_alg", self._auth_alg) +class WPASecurity(Security): + def read_from_args(self, args): + if len(args) != 3: + raise ValueError("not enough arguments") + key = args[0] + if isinstance(key, unicode): + key = key.encode() + if not isinstance(key, str): + raise ValueError("wrong argument type for key") + + wpa_ver = args[1] + if not isinstance(wpa_ver, int): + raise ValueError("wrong argument type for WPA version") + + key_mgmt = args[2] + if not isinstance(key_mgmt, int): + raise ValueError("wrong argument type for WPA key management") + if not key_mgmt & IW_AUTH_KEY_MGMT_PSK: + raise ValueError("Key management types other than PSK are not supported") + + self._key = key + self._wpa_ver = wpa_ver + self._key_mgmt = key_mgmt + + def read_from_config(self, cfg, name): + # Key should be a hex encoded string + self._key = cfg.get(name, "key") + if len(self._key) != 64: + raise ValueError("Key length not right for WPA-PSK") + + try: + a = binascii.a2b_hex(self._key) + except TypeError: + raise ValueError("Key was not a hexadecimal string.") + + self._wpa_ver = cfg.get_int(name, "wpa_ver") + if self._wpa_ver != IW_AUTH_WPA_VERSION_WPA and self._wpa_ver != IW_AUTH_WPA_VERSION_WPA: + raise ValueError("Invalid WPA version %d" % self._wpa_ver) + + self._key_mgmt = cfg.get_int(name, "key_mgmt") + if not self._key_mgmt & IW_AUTH_KEY_MGMT_PSK: + raise ValueError("Invalid WPA key management option %d" % self._key_mgmt) + + def get_properties(self): + args = Security.get_properties(self) + args.append(dbus.String(self._key)) + args.append(dbus.Int32(self._wpa_ver)) + args.append(dbus.Int32(self._key_mgmt)) + return args + + def write_to_config(self, section, config): + Security.write_to_config(self, section, config) + config.set(section, "key", self._key) + config.set(section, "wpa_ver", self._wpa_ver) + config.set(section, "key_mgmt", self._key_mgmt) + class Network: def __init__(self, ssid): @@ -312,15 +375,12 @@ class NMInfoDBusServiceHelper(dbus.service.Object): class NMInfo(object): def __init__(self, client): - try: - profile_path = env.get_profile_path() - except NameError: - home = os.path.expanduser("~") - profile_path = os.path.join(home, ".sugar", "default") + profile_path = env.get_profile_path() self._cfg_file = os.path.join(profile_path, "nm", "networks.cfg") self._nmclient = client self._allowed_networks = self._read_config() self._dbus_helper = NMInfoDBusServiceHelper(self) + self._key_dialog = None def save_config(self): self._write_config(self._allowed_networks) @@ -426,41 +486,44 @@ class NMInfo(object): async_err_cb(NotFoundError("Network was unknown.")) return - self._nmclient.get_key_for_network(net, async_cb, async_err_cb) + self._key_dialog = keydialog.new_key_dialog(net, async_cb, async_err_cb) + self._key_dialog.connect("response", self._key_dialog_response_cb) + self._key_dialog.connect("destroy", self._key_dialog_destroy_cb) + self._key_dialog.show_all() - def get_key_for_network_cb(self, net, key, auth_alg, async_cb, async_err_cb, canceled=False): - """ - Called by the NMClient when the Wireless Network Key dialog - is closed. - """ - if canceled: - # key dialog dialog was canceled; send the error back to NM - async_err_cb(CanceledKeyRequestError()) + def _key_dialog_destroy_cb(self, widget, foo=None): + if widget != self._key_dialog: + return + self._key_dialog_response_cb(widget, gtk.RESPONSE_CANCEL) + + def _key_dialog_response_cb(self, widget, response_id): + if widget != self._key_dialog: return - if not key or not auth_alg: - # no key returned, *** BUG ***; the key dialog - # should always return either a key + auth_alg, or a - #cancel error - raise RuntimeError("No key or auth alg given! Bug!") + (async_cb, async_err_cb) = self._key_dialog.get_callbacks() + net = self._key_dialog.get_network() + security = None + if response_id == gtk.RESPONSE_OK: + security = self._key_dialog.create_security() + self._key_dialog = None + widget.destroy() - we_cipher = None - if len(key) == 26: - we_cipher = IW_AUTH_CIPHER_WEP104 - elif len(key) == 10: - we_cipher = IW_AUTH_CIPHER_WEP40 + if response_id == gtk.RESPONSE_CANCEL: + # 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.") + props = security.get_properties() + a = tuple(props) + async_cb(*a) else: - raise RuntimeError("Invalid key length!") - - # Stuff the returned key and auth algorithm into a security object - # and return it to NetworkManager - sec = Security.new_from_args(we_cipher, (key, auth_alg)) - if not sec: - raise RuntimeError("Invalid security arguments.") - props = sec.get_properties() - a = tuple(props) - async_cb(*a) + raise RuntimeError("Unhandled key dialog response %d" % response_id) def cancel_get_key_for_network(self): - # Tell the NMClient to close the key request dialog - self._nmclient.cancel_get_key_for_network() + # Close the wireless key dialog and just have it return + # with the 'canceled' argument set to true + if not self._key_dialog: + return + self._key_dialog_destroy_cb(self._key_dialog) +