Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Split wader into core and common parts

  • Loading branch information...
commit a8da5f103df68c58a4d9c4f98d867036ec8820c8 1 parent 625c1ca
Vicente Hernando Ara authored committed
Showing with 2,727 additions and 1,166 deletions.
  1. +1 −0  bin/wader-core-ctl
  2. +7 −3 core-tap.py
  3. +1 −8 {wader/plugins → core}/__init__.py
  4. +16 −0 core/backends/__init__.py
  5. +1,348 −0 core/backends/nm.py
  6. +881 −0 core/backends/plain.py
  7. 0  {wader/common → core}/command.py
  8. 0  {wader/common → core}/contact.py
  9. 0  {wader/common → core}/daemon.py
  10. +2 −2 {wader/common → core}/dialer.py
  11. +2 −2 {wader/common → core}/exported.py
  12. 0  {wader/common → core}/hardware/__init__.py
  13. +8 −8 {wader/common → core}/hardware/base.py
  14. +8 −8 {wader/common → core}/hardware/ericsson.py
  15. +6 −6 {wader/common → core}/hardware/huawei.py
  16. +6 −6 {wader/common → core}/hardware/icera.py
  17. +5 −5 {wader/common → core}/hardware/novatel.py
  18. +6 −6 {wader/common → core}/hardware/option.py
  19. +5 −5 {wader/common → core}/hardware/qualcomm.py
  20. +4 −4 {wader/common → core}/hardware/sierra.py
  21. +1 −1  {wader/common → core}/hardware/sonyericsson.py
  22. +5 −5 {wader/common → core}/hardware/zte.py
  23. +1 −1  {wader/common → core}/mal.py
  24. +10 −10 {wader/common → core}/middleware.py
  25. 0  {wader/common → core}/mms.py
  26. 0  {wader/common → core}/modemmanager.py
  27. +1 −1  {wader/common → core}/oal.py
  28. 0  {wader/common → core}/oses/__init__.py
  29. +1 −1  {wader/common → core}/oses/bsd.py
  30. +5 −5 {wader/common → core}/oses/linux.py
  31. +6 −6 {wader/common → core}/oses/osx.py
  32. +2 −2 {wader/common → core}/oses/unix.py
  33. +7 −6 {wader/common → core}/plugin.py
  34. +8 −8 {wader/common → core}/protocol.py
  35. 0  {wader/common → core}/resolvconf.py
  36. +1 −1  {wader/common → core}/serialport.py
  37. 0  {wader/common → core}/shell.py
  38. 0  {wader/common → core}/sim.py
  39. +5 −5 {wader/common → core}/startup.py
  40. 0  {wader/common → core}/statem/__init__.py
  41. 0  {wader/common → core}/statem/auth.py
  42. 0  {wader/common → core}/statem/networkreg.py
  43. 0  {wader/common → core}/statem/simple.py
  44. +4 −4 plugins/devices/dell_d5520.py
  45. +2 −2 plugins/devices/dell_d5530.py
  46. +2 −2 plugins/devices/dell_d5540.py
  47. +2 −2 plugins/devices/dell_gobi2000.py
  48. +2 −2 plugins/devices/ericsson_f3307.py
  49. +2 −2 plugins/devices/ericsson_f3507g.py
  50. +2 −2 plugins/devices/ericsson_f3607gw.py
  51. +2 −2 plugins/devices/ericsson_md300.py
  52. +2 −2 plugins/devices/hp_gobi2000.py
  53. +5 −5 plugins/devices/huawei_b970.py
  54. +5 −5 plugins/devices/huawei_e1550.py
  55. +5 −5 plugins/devices/huawei_e160.py
  56. +2 −2 plugins/devices/huawei_e160b.py
  57. +2 −2 plugins/devices/huawei_e169.py
  58. +2 −2 plugins/devices/huawei_e1692.py
  59. +5 −5 plugins/devices/huawei_e173.py
  60. +5 −5 plugins/devices/huawei_e1750.py
  61. +2 −2 plugins/devices/huawei_e1752.py
  62. +4 −4 plugins/devices/huawei_e17X.py
  63. +2 −2 plugins/devices/huawei_e180.py
  64. +5 −5 plugins/devices/huawei_e1820.py
  65. +6 −6 plugins/devices/huawei_e220.py
  66. +3 −3 plugins/devices/huawei_e270.py
  67. +3 −3 plugins/devices/huawei_e272.py
  68. +5 −5 plugins/devices/huawei_e3735.py
  69. +4 −4 plugins/devices/huawei_e510.py
  70. +3 −3 plugins/devices/huawei_e620.py
  71. +2 −2 plugins/devices/huawei_e660.py
  72. +5 −5 plugins/devices/huawei_e660a.py
  73. +5 −5 plugins/devices/huawei_e870.py
  74. +5 −5 plugins/devices/huawei_em730v.py
  75. +2 −2 plugins/devices/huawei_em770.py
  76. +34 −34 plugins/devices/huawei_exxx.py
  77. +6 −6 plugins/devices/huawei_k2540.py
  78. +4 −4 plugins/devices/huawei_k3520.py
  79. +4 −4 plugins/devices/huawei_k3565.py
  80. +5 −5 plugins/devices/huawei_k3715.py
  81. +4 −4 plugins/devices/huawei_k3765.py
  82. +4 −4 plugins/devices/huawei_k3770.py
  83. +4 −4 plugins/devices/huawei_k3771.py
  84. +5 −5 plugins/devices/huawei_k3806.py
  85. +5 −5 plugins/devices/huawei_k4505.py
  86. +4 −4 plugins/devices/huawei_k4510.py
  87. +4 −4 plugins/devices/huawei_k4511.py
  88. +4 −4 plugins/devices/huawei_k4605.py
  89. +2 −2 plugins/devices/novatel_eu740.py
  90. +2 −2 plugins/devices/novatel_eu870d.py
  91. +4 −4 plugins/devices/novatel_mc950d.py
  92. +2 −2 plugins/devices/novatel_mc990d.py
  93. +2 −2 plugins/devices/novatel_mifi2352.py
  94. +4 −4 plugins/devices/novatel_mother.py
  95. +1 −1  plugins/devices/novatel_s720.py
  96. +2 −2 plugins/devices/novatel_u630.py
  97. +2 −2 plugins/devices/novatel_u740.py
  98. +4 −4 plugins/devices/novatel_x950d.py
  99. +4 −4 plugins/devices/novatel_xu870.py
  100. +2 −2 plugins/devices/onda_msa405hs.py
  101. +2 −2 plugins/devices/onda_mt503hs.py
  102. +5 −5 plugins/devices/option_colt.py
  103. +2 −2 plugins/devices/option_e3730.py
  104. +2 −2 plugins/devices/option_etna.py
  105. +2 −2 plugins/devices/option_etna_ndis.py
  106. +2 −2 plugins/devices/option_gi0335.py
  107. +2 −2 plugins/devices/option_globesurfericon.py
  108. +2 −2 plugins/devices/option_gtfusion.py
  109. +2 −2 plugins/devices/option_gtfusionquadlite.py
  110. +2 −2 plugins/devices/option_gtm378.py
  111. +2 −2 plugins/devices/option_gtmax36.py
  112. +2 −2 plugins/devices/option_icon225.py
  113. +2 −2 plugins/devices/option_icon401.py
  114. +2 −2 plugins/devices/option_k3760.py
  115. +2 −2 plugins/devices/option_nozomi.py
  116. +2 −2 plugins/devices/qualcomm_gobi2000.py
  117. +2 −2 plugins/devices/sierrawireless_850.py
  118. +2 −2 plugins/devices/sierrawireless_875.py
  119. +2 −2 plugins/devices/sierrawireless_mc8755.py
  120. +4 −4 plugins/devices/sonyericsson_k610i.py
  121. +3 −3 plugins/devices/sonyericsson_k618i.py
  122. +2 −2 plugins/devices/toshiba_f3607gw.py
  123. +4 −4 plugins/devices/zte_k2525.py
  124. +2 −2 plugins/devices/zte_k3520.py
  125. +2 −2 plugins/devices/zte_k3565.py
  126. +2 −2 plugins/devices/zte_k3570.py
  127. +2 −2 plugins/devices/zte_k3571.py
  128. +2 −2 plugins/devices/zte_k3765.py
  129. +2 −2 plugins/devices/zte_k3770.py
  130. +2 −2 plugins/devices/zte_k3772.py
  131. +2 −2 plugins/devices/zte_k3805.py
  132. +2 −2 plugins/devices/zte_k3806.py
  133. +2 −2 plugins/devices/zte_k4505.py
  134. +2 −2 plugins/devices/zte_k4510.py
  135. +2 −2 plugins/devices/zte_mf620.py
  136. +2 −2 plugins/devices/zte_mf628.py
  137. +2 −2 plugins/devices/zte_mf632.py
  138. +2 −2 plugins/devices/zte_mf637u.py
  139. +2 −2 plugins/devices/zte_mf651.py
  140. +7 −7 plugins/devices/zte_mf6xx.py
  141. +3 −3 plugins/devices/zte_mother_0031.py
  142. +1 −1  plugins/oses/debian.py
  143. +1 −1  plugins/oses/fedora.py
  144. +1 −1  plugins/oses/freebsd.py
  145. +1 −1  plugins/oses/mint.py
  146. +1 −1  plugins/oses/osx.py
  147. +1 −1  plugins/oses/suse.py
  148. +1 −1  plugins/oses/ubuntu.py
  149. +13 −13 resources/rpm/wader.spec
  150. +9 −3 setup.py
  151. +3 −1 test/test_command.py
  152. +2 −2 test/test_contact.py
  153. +3 −1 test/test_resolvconf.py
  154. +5 −185 wader/common/backends/nm.py
  155. +5 −518 wader/common/backends/plain.py
View
1  bin/wader-core-ctl
@@ -6,6 +6,7 @@ import sys
from time import sleep
from wader.common.consts import (APP_VERSION, DATA_DIR, PID_PATH)
+sys.path.insert(0, DATA_DIR)
from twisted.python.release import sh
View
10 core-tap.py
@@ -23,13 +23,17 @@
locale.setlocale(locale.LC_ALL, '')
from wader.common.consts import APP_NAME
-from wader.common.startup import (create_skeleton_and_do_initial_setup,
- get_wader_application)
+
+import sys
+sys.path.insert(0, '/usr/share/wader-core')
+
+from core.startup import (create_skeleton_and_do_initial_setup,
+ get_wader_application)
# it will just return if its not necessary
create_skeleton_and_do_initial_setup()
# check if we have an OSPlugin for this OS/Distro
-from wader.common.oal import get_os_object
+from core.oal import get_os_object
if get_os_object() is None:
message = 'OS/Distro not registered'
details = """
View
9 wader/plugins/__init__.py → core/__init__.py
@@ -16,11 +16,4 @@
# 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.
-"""
-I just point to /usr/share/wader-core/plugins
-"""
-
-from wader.common.consts import PLUGINS_DIR
-
-__path__ = PLUGINS_DIR
-__all__ = []
+"""Wader's core"""
View
16 core/backends/__init__.py
@@ -0,0 +1,16 @@
+from core.backends.nm import nm_backend
+from core.backends.plain import plain_backend
+
+
+__backend = None
+
+
+def get_backend():
+ global __backend
+ if __backend is not None:
+ return __backend
+
+ for backend in [nm_backend, plain_backend]:
+ if backend.should_be_used():
+ __backend = backend
+ return __backend
View
1,348 core/backends/nm.py
@@ -0,0 +1,1348 @@
+from collections import defaultdict
+import os
+
+import copy
+import dbus
+from dbus.service import method, signal, Object, BusName
+import gobject
+from twisted.internet.defer import Deferred
+from twisted.python import log
+from zope.interface import implements
+
+from wader.common._gconf import GConfHelper
+from wader.common.consts import (WADER_PROFILES_SERVICE,
+ WADER_PROFILES_INTFACE,
+ WADER_PROFILES_OBJPATH,
+ MDM_INTFACE, MM_IP_METHOD_PPP,
+ MM_SYSTEM_SETTINGS_PATH,
+ MM_ALLOWED_MODE_ANY,
+ MM_ALLOWED_MODE_2G_PREFERRED,
+ MM_ALLOWED_MODE_3G_PREFERRED,
+ MM_ALLOWED_MODE_2G_ONLY,
+ MM_ALLOWED_MODE_3G_ONLY)
+
+from core.dialer import Dialer
+import wader.common.exceptions as ex
+from wader.common.interfaces import IBackend, IProfileManagerBackend
+from wader.common.keyring import (KeyringManager, KeyringInvalidPassword,
+ KeyringIsClosed, KeyringNoMatchError)
+from wader.common.profile import Profile
+from wader.common.secrets import ProfileSecrets
+from wader.common.utils import (convert_int_to_uint32, convert_uint32_to_int,
+ patch_list_signature, revert_dict)
+
+# XXX: mustn't set the application name here else BCM gets called 'wader-core'
+# > this line is required, otherwise gnomekeyring will complain about
+# > the application name not being set
+# gobject.set_application_name(APP_SLUG_NAME)
+
+NM_SERVICE = 'org.freedesktop.NetworkManager'
+NM_OBJPATH = '/org/freedesktop/NetworkManager'
+NM_INTFACE = 'org.freedesktop.NetworkManager'
+NM_DEVICE = '%s.Device' % NM_INTFACE
+
+NM08_GSM_INTFACE = '%s.Gsm' % NM_DEVICE
+NM08_USER_SETTINGS = 'org.freedesktop.NetworkManagerUserSettings'
+NM08_SYSTEM_SETTINGS = 'org.freedesktop.NetworkManagerSettings'
+NM08_SYSTEM_SETTINGS_OBJ = '/org/freedesktop/NetworkManagerSettings'
+NM08_SYSTEM_SETTINGS_CONNECTION = '%s.Connection' % NM08_SYSTEM_SETTINGS
+NM08_SYSTEM_SETTINGS_SECRETS = '%s.Secrets' % NM08_SYSTEM_SETTINGS_CONNECTION
+
+NM084_SETTINGS = '%sUserSettings' % NM_SERVICE
+NM084_SETTINGS_INT = '%sSettings' % NM_INTFACE
+NM084_SETTINGS_OBJ = '%sSettings' % NM_OBJPATH
+NM084_SETTINGS_CONNECTION = '%s.Connection' % NM084_SETTINGS_INT
+NM084_SETTINGS_CONNECTION_SECRETS = '%s.Secrets' % NM084_SETTINGS_CONNECTION
+
+NM09_MODEM_INTFACE = '%s.Modem' % NM_DEVICE
+NM09_SETTINGS = NM_SERVICE
+NM09_SETTINGS_INT = '%s.Settings' % NM_INTFACE
+NM09_SETTINGS_OBJ = '%s/Settings' % NM_OBJPATH
+NM09_SETTINGS_CONNECTION = '%s.Connection' % NM09_SETTINGS_INT
+
+
+NM08_STATE = {
+ 'UNKNOWN': 0,
+ 'ASLEEP': 1,
+ 'CONNECTING': 2,
+ 'CONNECTED': 3,
+ 'DISCONNECTED': 4,
+}
+
+NM09_STATE = {
+ 'UNKNOWN': 0,
+ 'ASLEEP': 10,
+ 'DISCONNECTED': 20,
+ 'DISCONNECTING': 30,
+ 'CONNECTING': 40,
+ 'CONNECTED_LOCAL': 50,
+ 'CONNECTED_SITE': 60,
+ 'CONNECTED_GLOBAL': 70,
+ 'IPCONFIG': 100
+}
+
+NM08_DEVICE_STATE_UNKNOWN = 0
+NM08_DEVICE_STATE_UNMANAGED = 1
+NM08_DEVICE_STATE_UNAVAILABLE = 2
+NM08_DEVICE_STATE_DISCONNECTED = 3
+NM08_DEVICE_STATE_PREPARE = 4
+NM08_DEVICE_STATE_CONFIG = 5
+NM08_DEVICE_STATE_NEED_AUTH = 6
+NM08_DEVICE_STATE_IP_CONFIG = 7
+NM08_DEVICE_STATE_ACTIVATED = 8
+NM08_DEVICE_STATE_FAILED = 9
+
+NM08_DEVICE_STATE = {
+ 'UNKNOWN': NM08_DEVICE_STATE_UNKNOWN,
+ 'UNMANAGED': NM08_DEVICE_STATE_UNMANAGED,
+ 'UNAVAILABLE': NM08_DEVICE_STATE_UNAVAILABLE,
+ 'DISCONNECTED': NM08_DEVICE_STATE_DISCONNECTED,
+ 'PREPARE': NM08_DEVICE_STATE_PREPARE,
+ 'CONFIG': NM08_DEVICE_STATE_CONFIG,
+ 'NEED_AUTH': NM08_DEVICE_STATE_NEED_AUTH,
+ 'IP_CONFIG': NM08_DEVICE_STATE_IP_CONFIG,
+ 'ACTIVATED': NM08_DEVICE_STATE_ACTIVATED,
+ 'FAILED': NM08_DEVICE_STATE_FAILED
+}
+
+NM09_DEVICE_STATE_UNKNOWN = 0
+NM09_DEVICE_STATE_UNMANAGED = 10
+NM09_DEVICE_STATE_UNAVAILABLE = 20
+NM09_DEVICE_STATE_DISCONNECTED = 30
+NM09_DEVICE_STATE_PREPARE = 40
+NM09_DEVICE_STATE_CONFIG = 50
+NM09_DEVICE_STATE_NEED_AUTH = 60
+NM09_DEVICE_STATE_IP_CONFIG = 70
+NM09_DEVICE_STATE_IP_CHECK = 80
+NM09_DEVICE_STATE_SECONDARIES = 90
+NM09_DEVICE_STATE_ACTIVATED = 100
+
+NM09_DEVICE_STATE = {
+ 'UNKNOWN': NM09_DEVICE_STATE_UNKNOWN,
+ 'UNMANAGED': NM09_DEVICE_STATE_UNMANAGED,
+ 'UNAVAILABLE': NM09_DEVICE_STATE_UNAVAILABLE,
+ 'DISCONNECTED': NM09_DEVICE_STATE_DISCONNECTED,
+ 'PREPARE': NM09_DEVICE_STATE_PREPARE,
+ 'CONFIG': NM09_DEVICE_STATE_CONFIG,
+ 'NEED_AUTH': NM09_DEVICE_STATE_NEED_AUTH,
+ 'IP_CONFIG': NM09_DEVICE_STATE_IP_CONFIG,
+ 'IP_CHECK': NM09_DEVICE_STATE_IP_CHECK,
+ 'SECONDARIES': NM09_DEVICE_STATE_SECONDARIES,
+ 'ACTIVATED': NM09_DEVICE_STATE_ACTIVATED
+}
+
+GCONF_PROFILES_BASE = '/system/networking/connections'
+
+NM_NETWORK_TYPE_MAP = {
+ MM_ALLOWED_MODE_ANY: -1,
+ MM_ALLOWED_MODE_2G_PREFERRED: 3,
+ MM_ALLOWED_MODE_3G_PREFERRED: 2,
+ MM_ALLOWED_MODE_2G_ONLY: 1,
+ MM_ALLOWED_MODE_3G_ONLY: 0,
+}
+NM_NETWORK_TYPE_MAP_REV = revert_dict(NM_NETWORK_TYPE_MAP)
+
+
+def transpose_from_NM(oldprops):
+ # call on read
+ props = copy.deepcopy(oldprops)
+
+ if 'gsm' in props:
+ # map to Modem manager constants, default to ANY
+ if not 'network-type' in props['gsm']:
+ props['gsm']['network-type'] = MM_ALLOWED_MODE_ANY
+ else:
+ nm_val = props['gsm'].get('network-type')
+ props['gsm']['network-type'] = NM_NETWORK_TYPE_MAP_REV[nm_val]
+
+ # Note: password is never retrieved via plain props but we map it
+ # anyway to be symmetric
+ if 'password' in props['gsm']:
+ props['gsm']['passwd'] = props['gsm']['password']
+ del props['gsm']['password']
+
+ if 'ipv4' in props:
+ if 'dns' in props['ipv4']:
+ props['ipv4']['ignore-auto-dns'] = (len(props['ipv4']['dns']) > 0)
+ else:
+ props['ipv4']['ignore-auto-dns'] = False
+
+ # convert the integer format
+ for key in ['addresses', 'dns', 'routes']:
+ if key in props['ipv4']:
+ vals = map(convert_uint32_to_int, props['ipv4'][key])
+ props['ipv4'][key] = vals
+
+ return dict(props)
+
+
+def transpose_to_NM(oldprops, new=True):
+ # call on write
+ props = copy.deepcopy(oldprops)
+
+ if 'gsm' in props:
+ mm_val = props['gsm'].get('network-type', MM_ALLOWED_MODE_ANY)
+ props['gsm']['network-type'] = NM_NETWORK_TYPE_MAP[mm_val]
+
+ # filter out old single band settings, NM now uses a mask
+ if 'band' in props['gsm']:
+ del props['gsm']['band']
+
+ # Note: password is set via plain props
+ if 'passwd' in props['gsm']:
+ props['gsm']['password'] = props['gsm']['passwd']
+ del props['gsm']['passwd']
+
+ # NM doesn't like us setting these on update
+ if not new:
+ for key in ['connection', 'gsm', 'ppp', 'serial', 'ipv4']:
+ if 'name' in props[key]:
+ del props[key]['name']
+
+ if 'ipv4' in props:
+ if not props['ipv4'].get('ignore-auto-dns'):
+ props['ipv4']['dns'] = []
+
+ # convert the integer format
+ for key in ['addresses', 'dns', 'routes']:
+ if key in props['ipv4']:
+ value = map(convert_int_to_uint32, props['ipv4'][key])
+ if key in ['dns']:
+ props['ipv4'][key] = dbus.Array(value, signature='u')
+ else:
+ props['ipv4'][key] = dbus.Array(value, signature='au')
+
+ return props
+
+
+class NMDialer(Dialer):
+ """I wrap NetworkManager's dialer"""
+
+ def __init__(self, device, opath, **kwds):
+ super(NMDialer, self).__init__(device, opath, **kwds)
+
+ self.int = None
+ self.conn_obj = None
+ self.iface = self._get_stats_iface()
+ self.state = self.NM_DISCONNECTED
+
+ self.nm_opath = None
+ self.connect_deferred = None
+ self.disconnect_deferred = None
+ self.sm = None
+
+ def _cleanup(self):
+ # enable +CREG notifications afterwards
+ self.device.sconn.set_netreg_notification(1)
+ self.sm.remove()
+ self.sm = None
+
+ def _get_device_opath(self):
+ """
+ Returns the object path to use in the connection / signal
+ """
+ obj = self.bus.get_object(NM_SERVICE, NM_OBJPATH)
+ interface = dbus.Interface(obj, NM_INTFACE)
+ for opath in interface.GetDevices():
+ dev = self.bus.get_object(NM_SERVICE, opath)
+ udi = dev.Get('org.freedesktop.NetworkManager.Device', 'Udi')
+ if self.device.opath == udi:
+ return opath
+
+ def _get_stats_iface(self):
+ if self.device.get_property(MDM_INTFACE,
+ 'IpMethod') == MM_IP_METHOD_PPP:
+ iface = 'ppp0' # XXX: shouldn't hardcode to first PPP instance
+ else:
+ iface = self.device.get_property(MDM_INTFACE, 'Device')
+
+ return iface
+
+ def _on_properties_changed(self, changed):
+ if 'State' not in changed:
+ return
+
+ if changed['State'] == self.NM_CONNECTED and \
+ self.state == self.NM_DISCONNECTED:
+ # emit the connected signal and send back the opath
+ # if the deferred is present
+ self.state = self.NM_CONNECTED
+ self.Connected()
+ self.connect_deferred.callback(self.opath)
+ return
+
+ if changed['State'] == self.NM_DISCONNECTED:
+
+ if self.state == self.NM_CONNECTED:
+ self.Disconnected()
+ if self.disconnect_deferred is not None:
+ # could happen if we are connected and a NM_DISCONNECTED
+ # signal arrives without having explicitly disconnected
+ self.disconnect_deferred.callback(self.conn_obj)
+
+ if self.state == self.NM_DISCONNECTED:
+ # Occurs if the connection attempt failed
+ self.Disconnected()
+ if self.connect_deferred is not None:
+ msg = 'Network Manager failed to connect'
+ self.connect_deferred.errback(RuntimeError(msg))
+
+ self.state = self.NM_DISCONNECTED
+ self._cleanup()
+
+ def _setup_signals(self):
+ self.sm = self.bus.add_signal_receiver(self._on_properties_changed,
+ "PropertiesChanged",
+ path=self._get_device_opath(),
+ dbus_interface=self.NM_MODEM_INT)
+
+ def configure(self, config):
+ self._setup_signals()
+ # get the profile object and obtains its uuid
+ # get ProfileManager and translate the uuid to a NM object path
+ profiles = self.bus.get_object(WADER_PROFILES_SERVICE,
+ WADER_PROFILES_OBJPATH)
+ # get the object path of the profile being used
+ self.nm_opath = profiles.GetNMObjectPath(str(config.uuid),
+ dbus_interface=WADER_PROFILES_INTFACE)
+ # Disable +CREG notifications, otherwise NMDialer won't work
+ return self.device.sconn.set_netreg_notification(0)
+
+ def connect(self):
+ raise NotImplementedError("Implement in subclass")
+
+ def stop(self):
+ self._cleanup()
+ return self.disconnect()
+
+ def disconnect(self):
+ self.disconnect_deferred = Deferred()
+ self.int.DeactivateConnection(self.conn_obj)
+ # the deferred will be callbacked as soon as we get a
+ # connectivity status change
+ return self.disconnect_deferred
+
+
+class NM08Dialer(NMDialer):
+
+ def __init__(self, device, opath, **kwds):
+ self.NM_CONNECTED = NM08_DEVICE_STATE_ACTIVATED
+ self.NM_DISCONNECTED = NM08_DEVICE_STATE_DISCONNECTED
+ self.NM_MODEM_INT = NM08_GSM_INTFACE
+
+ super(NM08Dialer, self).__init__(device, opath, **kwds)
+
+ def connect(self):
+ self.connect_deferred = Deferred()
+ obj = self.bus.get_object(NM_SERVICE, NM_OBJPATH)
+ self.int = dbus.Interface(obj, NM_INTFACE)
+
+ # NM 0.8 - applet (USER)
+ # <arg name="service_name" type="s" direction="in"/>
+ # <arg name="connection" type="o" direction="in"/>
+ # <arg name="device" type="o" direction="in"/>
+ # <arg name="specific_object" type="o" direction="in"/>
+ args = (NM08_USER_SETTINGS,
+ self.nm_opath, self._get_device_opath(), '/')
+ log.msg("Connecting with:\n%s\n%s\n%s\n%s" % args)
+
+ try:
+ self.conn_obj = self.int.ActivateConnection(*args)
+ # the deferred will be callbacked as soon as we get a
+ # connectivity status change
+ return self.connect_deferred
+ except dbus.DBusException, e:
+ log.err(e)
+ self._cleanup()
+
+
+class NM09Dialer(NMDialer):
+
+ def __init__(self, device, opath, **kwds):
+ self.NM_CONNECTED = NM09_DEVICE_STATE_ACTIVATED
+ self.NM_DISCONNECTED = NM09_DEVICE_STATE_DISCONNECTED
+ self.NM_MODEM_INT = NM09_MODEM_INTFACE
+
+ super(NM09Dialer, self).__init__(device, opath, **kwds)
+
+ def connect(self):
+ self.connect_deferred = Deferred()
+ obj = self.bus.get_object(NM_SERVICE, NM_OBJPATH)
+ self.int = dbus.Interface(obj, NM_INTFACE)
+
+ # NM 0.9
+ # <arg name="connection" type="o" direction="in">
+ # <arg name="device" type="o" direction="in">
+ # <arg name="specific_object" type="o" direction="in">
+ args = (self.nm_opath, self._get_device_opath(), '/')
+ log.msg("Connecting with:\n%s\n%s\n%s" % args)
+
+ try:
+ self.conn_obj = self.int.ActivateConnection(*args)
+ # the deferred will be callbacked as soon as we get a
+ # connectivity status change
+ return self.connect_deferred
+ except dbus.DBusException, e:
+ log.err(e)
+ self._cleanup()
+
+
+class DummyKeyringManager(object):
+
+ def __init__(self, get_cb, set_cb):
+ self.get_cb = get_cb
+ self.set_cb = set_cb
+
+ def get_secrets(self, uuid):
+ """Returns the secrets associated with ``uuid``"""
+ return self.get_cb(uuid)
+
+ def is_open(self):
+ """Always open"""
+ return True
+
+ def update_secret(self, uuid, secrets, update=True):
+ return self.set_cb(uuid, secrets)
+
+
+class DummySecrets(object):
+
+ def __init__(self, connection, manager):
+ self.uuid = connection.get_settings()['connection']['uuid']
+ self.manager = manager
+
+ def get(self, ask=True):
+ """Returns the secrets associated with the profile"""
+ return self.manager.get_secrets(self.uuid)
+
+ def is_open(self):
+ return self.manager.is_open()
+
+ def update(self, secrets, ask=True):
+ """Updates the secrets associated with the profile"""
+ return self.manager.update_secret(self.uuid, secrets)
+
+
+class GnomeKeyring(object):
+ """I just wrap gnome-keyring"""
+
+ def __init__(self):
+ super(GnomeKeyring, self).__init__()
+ self._is_new = False
+ self.gk = None
+ self.name = None
+ self._setup_keyring()
+
+ def _setup_keyring(self):
+ # import it here so importing this backend on a non GNOME
+ # system doesn't fails
+ import gnomekeyring as gk
+ self.gk = gk
+ self.name = self.gk.get_default_keyring_sync()
+
+ if not self.name:
+ self._is_new = True
+ self.name = 'login'
+
+ # if keyring does not exist, create it
+ try:
+ self.gk.create_sync(self.name, None)
+ except self.gk.AlreadyExistsError:
+ pass
+
+ self.gk.set_default_keyring_sync(self.name)
+
+ def is_open(self):
+ info = self.gk.get_info_sync(self.name)
+ return not info.get_is_locked()
+
+ def is_new(self):
+ return self._is_new
+
+ def open(self, password):
+ """See :meth:`KeyringManager.open`"""
+ if not self.is_open():
+ try:
+ self.gk.unlock_sync(self.name, password)
+ except (IOError, self.gk.DeniedError):
+ raise KeyringInvalidPassword()
+
+ def close(self):
+ """See :meth:`KeyringManager.close`"""
+ if self.is_open():
+ self.gk.lock_sync(self.name)
+ else:
+ raise KeyringIsClosed()
+
+ def get(self, uuid):
+ """See :meth:`KeyringManager.get_secrets`"""
+ attrs = {'connection-uuid': str(uuid)}
+ try:
+ secrets = self.gk.find_items_sync(
+ self.gk.ITEM_GENERIC_SECRET, attrs)
+ return {'gsm': {'passwd': secrets[0].secret}}
+ except self.gk.NoMatchError:
+ msg = "No secrets for connection '%s'"
+ raise KeyringNoMatchError(msg % str(uuid))
+
+ def update(self, uuid, conn_id, secrets, update=True):
+ """See :meth:`KeyringManager.update_secret`"""
+ attrs = {'connection-uuid': str(uuid), 'setting-name': 'gsm',
+ 'setting-key': 'password'}
+
+ password = secrets['gsm']['passwd']
+ text = 'Network secret for %s/%s/%s' % (conn_id, 'gsm', 'password')
+ return self.gk.item_create_sync(self.name, self.gk.ITEM_GENERIC_SECRET,
+ text, attrs, password, update)
+
+ def delete(self, uuid):
+ """See :meth:`KeyringManager.delete_secret`"""
+ attrs = {'connection-uuid': str(uuid)}
+ secrets = self.gk.find_items_sync(self.gk.ITEM_GENERIC_SECRET, attrs)
+ # we find the secret, and we delete it
+ return self.gk.item_delete_sync(self.name, secrets[0].item_id)
+
+
+class NMProfile(Profile):
+ """I am a group of settings required to dial up"""
+
+ def __init__(self, opath, nm_obj, props, manager):
+ super(NMProfile, self).__init__(opath)
+
+ self.nm_obj = nm_obj
+ self.props = props
+ self.manager = manager
+
+ def _connect_to_signals(self):
+ self.nm_obj.connect_to_signal("Removed", self._on_removed)
+ self.nm_obj.connect_to_signal("Updated", self._on_updated)
+
+ def get_secrets(self, tag, hints=None, ask=True):
+ """
+ Returns the secrets associated with the profile
+
+ :param tag: The section to use
+ :param hints: what specific setting are we interested in
+ :param ask: Should we ask the user if there is no secret?
+ """
+ return self.secrets.get(ask)
+
+ def get_settings(self):
+ """Returns the profile settings"""
+ return patch_list_signature(self.props)
+
+ def get_timestamp(self):
+ """Returns the last time this profile was used"""
+ try:
+ return self.get_settings()['connection']['timestamp']
+ except KeyError:
+ return None
+
+ def is_good(self):
+ """Has this profile been successfully used?"""
+ return bool(self.get_timestamp())
+
+ def on_open_keyring(self, tag):
+ """Callback to be executed when the keyring has been opened"""
+ secrets = self.secrets.get()
+ if secrets:
+ self.GetSecrets.reply(self, result=(secrets,))
+ else:
+ self.KeyNeeded(self, tag)
+
+ def set_secrets(self, tag, secrets):
+ """
+ Sets or updates the secrets associated with the profile
+
+ :param tag: The section to use
+ :param secrets: The new secret to store
+ """
+ self.secrets.update(secrets)
+ self.GetSecrets.reply(self, result=(secrets,))
+
+
+class NM08Profile(NMProfile):
+
+ def __init__(self, opath, nm_obj, gpath, props, manager):
+ super(NM08Profile, self).__init__(opath, nm_obj, props, manager)
+
+ self.helper = GConfHelper()
+ self.gpath = gpath
+
+ from core.backends import get_backend
+ keyring = get_backend().get_keyring()
+ self.secrets = ProfileSecrets(self, keyring)
+
+ self._connect_to_signals()
+
+ def _on_removed(self):
+ log.msg("Profile %s has been removed externally" % self.opath)
+ self.manager.remove_profile(self)
+
+ def _on_updated(self, props):
+ log.msg("Profile %s has been updated" % self.opath)
+ self.update(props)
+
+ def _write(self, props):
+ self.props = props
+
+ props = transpose_to_NM(props)
+
+ for key, value in props.iteritems():
+ new_path = os.path.join(self.gpath, key)
+ self.helper.set_value(new_path, value)
+
+ self.helper.client.notify(self.gpath)
+ self.helper.client.suggest_sync()
+
+ def _load_info(self):
+ props = {}
+
+ if self.helper.client.dir_exists(self.gpath):
+ self._load_dir(self.gpath, props)
+
+ self.props = transpose_from_NM(props)
+
+ def _load_dir(self, directory, info):
+ for entry in self.helper.client.all_entries(directory):
+ key = os.path.basename(entry.key)
+ info[key] = self.helper.get_value(entry.value)
+
+ for _dir in self.helper.client.all_dirs(directory):
+ dirname = os.path.basename(_dir)
+ info[dirname] = {}
+ self._load_dir(_dir, info[dirname])
+
+ def update(self, props):
+ """Updates the profile with settings ``props``"""
+ self._write(props)
+ self._load_info()
+ self.Updated(patch_list_signature(self.props))
+
+ def remove(self):
+ """Removes the profile"""
+ from gconf import UNSET_INCLUDING_SCHEMA_NAMES
+ self.helper.client.recursive_unset(self.gpath,
+ UNSET_INCLUDING_SCHEMA_NAMES)
+ # emit Removed and unexport from DBus
+ self.Removed()
+ self.remove_from_connection()
+
+
+class NM084Profile(NMProfile):
+
+ def __init__(self, opath, nm_obj, props, manager):
+ super(NM084Profile, self).__init__(opath, nm_obj, props, manager)
+
+ self.secrets = DummySecrets(self, self.manager.keyring_manager)
+
+ self._connect_to_signals()
+
+ def _on_removed(self):
+ log.msg("NM Connection profile %s has been removed" % self.opath)
+ self.manager.remove_profile_cb(self)
+
+ def _on_updated(self, props):
+ log.msg("NM Connection profile %s has been updated" % self.opath)
+ self.manager.update_profile_cb(self, props)
+
+ def update(self, props):
+ """Updates the profile with settings ``props``"""
+ self.props = props
+ # emit Updated
+ self.Updated(patch_list_signature(self.props))
+
+ def remove(self):
+ """Removes the profile"""
+ # emit Removed and unexport from DBus
+ self.Removed()
+ self.remove_from_connection()
+
+
+class NM09Profile(NM084Profile):
+
+ def _on_updated(self):
+ log.msg("NM Connection profile %s has been updated" % self.opath)
+ self.manager.update_profile_cb(self)
+
+
+class NMProfileManager(Object):
+ """I manage profiles in the system"""
+
+ implements(IProfileManagerBackend)
+
+ def __init__(self):
+ self.bus = dbus.SystemBus()
+ bus_name = BusName(WADER_PROFILES_SERVICE, bus=self.bus)
+ super(NMProfileManager, self).__init__(bus_name,
+ WADER_PROFILES_OBJPATH)
+ self.profiles = {}
+ self.nm_profiles = {}
+ self.nm_manager = None
+ self.index = -1
+
+ self._init_nm_manager()
+
+ def get_next_dbus_opath(self):
+ self.index += 1
+ return os.path.join(MM_SYSTEM_SETTINGS_PATH, str(self.index))
+
+ def get_profile_by_object_path(self, opath):
+ """Returns a :class:`Profile` out of its object path ``opath``"""
+ for profile in self.profiles.values():
+ if profile.opath == opath:
+ return profile
+
+ raise ex.ProfileNotFoundError("No profile with object path %s" % opath)
+
+ def get_profile_by_uuid(self, uuid):
+ """
+ Returns the :class:`Profile` identified by ``uuid``
+
+ :param uuid: The uuid of the profile
+ :raise ProfileNotFoundError: If no profile was found
+ """
+ if not self.profiles:
+ # initialise just in case
+ self.get_profiles()
+
+ try:
+ return self.profiles[uuid]
+ except KeyError:
+ raise ex.ProfileNotFoundError("No profile with uuid %s" % uuid)
+
+ @signal(dbus_interface=WADER_PROFILES_INTFACE, signature='o')
+ def NewConnection(self, opath):
+ pass
+
+ @method(dbus_interface=WADER_PROFILES_INTFACE,
+ in_signature='s', out_signature='o')
+ def GetNMObjectPath(self, uuid):
+ """Returns the object path of the connection referred by ``uuid``"""
+ if uuid not in self.nm_profiles:
+ msg = "Could not find profile %s in %s"
+ raise KeyError(msg % (uuid, self.nm_profiles))
+
+ profile = self.nm_profiles[uuid]
+ return profile.__dbus_object_path__
+
+
+class NM08ProfileManager(NMProfileManager):
+ """I manage profiles in the system"""
+
+ def __init__(self):
+ super(NM08ProfileManager, self).__init__()
+
+ self.helper = GConfHelper()
+ self.gpath = GCONF_PROFILES_BASE
+
+ # connect to signals
+ self._connect_to_signals()
+
+ def _init_nm_manager(self):
+ obj = self.bus.get_object(NM08_USER_SETTINGS, NM08_SYSTEM_SETTINGS_OBJ)
+ self.nm_manager = dbus.Interface(obj, NM08_SYSTEM_SETTINGS)
+
+ def _connect_to_signals(self):
+ self.nm_manager.connect_to_signal("NewConnection",
+ self._on_new_nm_profile, NM08_SYSTEM_SETTINGS)
+
+ def _on_new_nm_profile(self, opath):
+ obj = self.bus.get_object(NM08_USER_SETTINGS, opath)
+ props = obj.GetSettings(dbus_interface=NM08_SYSTEM_SETTINGS_CONNECTION)
+ # filter out non GSM profiles
+ if props['connection']['type'] == 'gsm':
+ self._add_nm_profile(obj, props)
+
+ def _add_nm_profile(self, obj, props):
+ uuid = props['connection']['uuid']
+ assert uuid not in self.nm_profiles, "Adding twice the same profile?"
+ self.nm_profiles[uuid] = obj
+
+ # handle when a NM profile has been externally added
+ if uuid not in self.profiles:
+ try:
+ profile = self._get_profile_from_nm_connection(uuid)
+ except ex.ProfileNotFoundError:
+ log.msg("Removing non existing NM profile %s" % uuid)
+ del self.nm_profiles[uuid]
+ else:
+ self.profiles[uuid] = profile
+ self.NewConnection(profile.opath)
+
+ def _get_next_free_gpath(self):
+ """Returns the next unused slot of /system/networking/connections"""
+ all_dirs = list(self.helper.client.all_dirs(self.gpath))
+ try:
+ max_index = max(map(int, [d.split('/')[-1] for d in all_dirs]))
+ except ValueError:
+ # /system/networking/connections is empty
+ max_index = -1
+
+ index = 0 if not all_dirs else max_index + 1
+ return os.path.join(self.gpath, str(index))
+
+ def _get_profile_from_nm_connection(self, uuid):
+ for gpath in self.helper.client.all_dirs(self.gpath):
+ # filter out wlan connections
+ if self.helper.client.dir_exists(os.path.join(gpath, 'gsm')):
+ path = os.path.join(gpath, 'connection', 'uuid')
+ value = self.helper.client.get(path)
+ if value and uuid == self.helper.get_value(value):
+ return self._get_profile_from_gconf_path(gpath)
+
+ msg = "NM profile identified by uuid %s could not be found"
+ raise ex.ProfileNotFoundError(msg % uuid)
+
+ def _get_profile_from_gconf_path(self, gconf_path):
+ props = defaultdict(dict)
+ for path in self.helper.client.all_dirs(gconf_path):
+ for entry in self.helper.client.all_entries(path):
+ section, key = entry.get_key().split('/')[-2:]
+ value = entry.get_value()
+ if value is not None:
+ props[section][key] = self.helper.get_value(value)
+
+ props = transpose_from_NM(props)
+ uuid = props['connection']['uuid']
+ try:
+ return NM08Profile(self.get_next_dbus_opath(),
+ self.nm_profiles[uuid],
+ gconf_path, props, self)
+ except KeyError:
+ raise ex.ProfileNotFoundError("Profile '%s' could not "
+ "be found" % uuid)
+
+ def _do_set_profile(self, path, props):
+ props = transpose_to_NM(props)
+
+ for key in props:
+ for name in props[key]:
+ value = props[key][name]
+ _path = os.path.join(path, key, name)
+
+ self.helper.set_value(_path, value)
+
+ self.helper.client.notify(path)
+ self.helper.client.suggest_sync()
+
+ def add_profile(self, props):
+ """Adds a profile with settings ``props``"""
+ gconf_path = self._get_next_free_gpath()
+ self._do_set_profile(gconf_path, props)
+ # the rest will be handled by _on_new_nm_profile
+
+ def get_profiles(self):
+ """Returns all the profiles in the system"""
+ if not self.nm_profiles:
+ # cache existing profiles
+ map(self._on_new_nm_profile, self.nm_manager.ListConnections())
+
+ if not self.profiles:
+ for path in self.helper.client.all_dirs(self.gpath):
+ # filter out wlan connections
+ if self.helper.client.dir_exists(os.path.join(path, 'gsm')):
+ # profile = self._get_profile_from_gconf_path(path)
+ # uuid = profile.get_settings()['connection']['uuid']
+ # self.profiles[uuid] = profile
+ try:
+ profile = self._get_profile_from_gconf_path(path)
+ uuid = profile.get_settings()['connection']['uuid']
+ self.profiles[uuid] = profile
+ except ex.ProfileNotFoundError:
+ pass
+
+ return self.profiles.values()
+
+ def remove_profile(self, profile):
+ """Removes profile ``profile``"""
+ uuid = profile.get_settings()['connection']['uuid']
+ assert uuid in self.profiles, "Removing a non-existent profile?"
+
+ self.profiles[uuid].remove()
+ del self.profiles[uuid]
+
+ # as NetworkManager listens for GConf-DBus signals, we don't need
+ # to manually sync it
+ if uuid in self.nm_profiles:
+ del self.nm_profiles[uuid]
+
+ def update_profile(self, profile, props):
+ """Updates ``profile`` with settings ``props``"""
+ uuid = profile.get_settings()['connection']['uuid']
+ assert uuid in self.profiles, "Updating a non-existent profile?"
+
+ _profile = self.profiles[uuid]
+ _profile.update(props)
+
+ props = transpose_to_NM(props, new=False)
+
+ if uuid in self.nm_profiles:
+ obj = self.nm_profiles[uuid]
+ obj.Update(props,
+ dbus_interface=NM08_SYSTEM_SETTINGS_CONNECTION)
+
+
+class NMLaterProfileManager(NMProfileManager):
+ # XXX: Poor name but is intended to provide common stuff in 0.8.4 and 0.9,
+ # to be subclassed only
+
+ def __init__(self):
+ super(NMLaterProfileManager, self).__init__()
+
+ def _init_nm_manager(self):
+ obj = self.bus.get_object(self.NM_SETTINGS, self.NM_SETTINGS_OBJ)
+ self.nm_manager = dbus.Interface(obj, self.NM_SETTINGS_INT)
+
+ def _connect_to_signals(self):
+ self.nm_manager.connect_to_signal("NewConnection",
+ self._on_new_nm_profile, self.NM_SETTINGS_INT)
+
+ def _on_new_nm_profile(self, opath):
+ log.msg("NM notified us of a new connection profile %s" % opath)
+ self._store_new_profile(opath)
+
+ def _get_profile_from_nm_connection(self, props):
+ print "NMLaterProfileManager::_get_profile_from_nm_connection()"
+
+ props = transpose_from_NM(props)
+ uuid = props['connection']['uuid']
+ try:
+ return self.NM_PROFILE_KLASS(self.get_next_dbus_opath(),
+ self.nm_profiles[uuid], props, self)
+ except KeyError:
+ raise ex.ProfileNotFoundError("Profile '%s' could not "
+ "be found" % uuid)
+
+ def _keyring_set_callback(self, uuid, secrets):
+ # Our UI clients expect that setting password secrets is a separate
+ # operation to other profile activities when in fact we do it via the
+ # same mechanism again, this means that two NM connection updates are
+ # generated for every client profile save.
+ print "NMLaterProfileManager::_keyring_set_callback()"
+
+ passwd = secrets['gsm']['passwd']
+
+ if uuid in self.nm_profiles:
+ obj = self.nm_profiles[uuid]
+ # Need to merge in the password
+ nm_props = obj.GetSettings(dbus_interface=self.NM_SETTINGS_CONNECTION)
+ nm_props['gsm']['password'] = passwd
+
+ obj.Update(nm_props, dbus_interface=self.NM_SETTINGS_CONNECTION)
+ else:
+ log.msg("NM connection profile does not exist for %s" % uuid)
+
+ def _store_new_profile(self, opath):
+ """
+ called:
+ 1/ from signal handler when NM has a new connection
+ 2/ by get_profiles to populate the profiles cache
+ """
+ print "NMLaterProfileManager::_store_new_profile()"
+ obj = self.bus.get_object(self.NM_SETTINGS, opath)
+
+ props = obj.GetSettings(dbus_interface=self.NM_SETTINGS_CONNECTION)
+
+ # filter out non GSM profiles
+ if props['connection']['type'] != 'gsm':
+ return
+
+ uuid = props['connection']['uuid']
+ assert uuid not in self.nm_profiles, "Adding twice the same profile?"
+ self.nm_profiles[uuid] = obj
+
+ # handle when a NM profile has been externally added
+ if uuid not in self.profiles:
+ try:
+ profile = self._get_profile_from_nm_connection(props)
+ except ex.ProfileNotFoundError:
+ log.msg("Adding non existing NM profile %s" % uuid)
+ del self.nm_profiles[uuid]
+ else:
+ self.profiles[uuid] = profile
+ self.NewConnection(profile.opath)
+
+ def get_profiles(self):
+ """Returns all the profiles in the system"""
+ if not self.nm_profiles:
+ # cache existing profiles
+ map(self._store_new_profile, self.nm_manager.ListConnections())
+
+ if self.profiles is None:
+ return []
+
+ return self.profiles.values()
+
+ def remove_profile(self, profile):
+ print "NMLaterProfileManager::remove_profile()"
+ """
+ Removes profile ``profile``
+
+ Should initiate the NM connection removal, but the removal of our
+ profile should be done by the signal handler
+ """
+ uuid = profile.get_settings()['connection']['uuid']
+
+ if uuid in self.nm_profiles:
+ obj = self.nm_profiles[uuid]
+ obj.Delete(dbus_interface=self.NM_SETTINGS_CONNECTION)
+ else:
+ log.msg("NM connection profile does not exist for %s" % uuid)
+
+ def remove_profile_cb(self, profile):
+ """
+ Called by NMxxxProfile's Remove signal handler
+ """
+ log.msg("NM notified us of a connection profile removal")
+
+ uuid = profile.props['connection']['uuid']
+
+ if uuid in self.profiles:
+ self.profiles[uuid].remove()
+ del self.profiles[uuid]
+
+ if uuid in self.nm_profiles:
+ del self.nm_profiles[uuid]
+
+ def update_profile(self, profile, props):
+ """
+ Updates ``profile`` with settings ``props``
+
+ Should initiate the NM connection update, but the update of our
+ profile should be done by the signal handler
+ """
+ print "NMLaterProfileManager::update_profile()"
+ uuid = profile.get_settings()['connection']['uuid']
+ nm_props = transpose_to_NM(props, new=False)
+
+ if uuid in self.nm_profiles:
+ obj = self.nm_profiles[uuid]
+ obj.Update(nm_props, dbus_interface=self.NM_SETTINGS_CONNECTION)
+ else:
+ log.msg("NM connection profile does not exist for %s" % uuid)
+
+ def update_profile_cb(self, profile, nm_props=None):
+ """
+ Called by NMxxxProfile's Updated signal handler
+ Called twice per BCM profile change as passwd is updated separately
+ via the keyring
+ """
+ log.msg("NM notified us of a connection profile update")
+
+ obj = profile.nm_obj
+
+ if nm_props is None:
+ # NM 0.9 doesn't signal the changed props so we have to retrieve them
+ nm_props = obj.GetSettings(dbus_interface=self.NM_SETTINGS_CONNECTION)
+
+ # NM 0.8.4 does signal the changed props but we still have to retrieve the
+ # password and merge
+ try:
+ secrets = obj.GetSecrets('gsm',
+ dbus_interface=self.NM_SETTINGS_CONNECTION)
+ password = secrets['gsm']['password']
+ except (KeyError, dbus.exceptions.DBusException):
+ pass
+ else:
+ nm_props['gsm']['password'] = password
+
+ props = transpose_from_NM(nm_props)
+
+ uuid = props['connection']['uuid']
+ if uuid in self.profiles:
+ self.profiles[uuid].update(props)
+
+
+class NM084ProfileManager(NMLaterProfileManager):
+
+ def __init__(self):
+ super(NM084ProfileManager, self).__init__()
+
+ self.keyring_manager = DummyKeyringManager(self._keyring_get_callback,
+ self._keyring_set_callback)
+ self.helper = GConfHelper()
+
+ # profile class to create
+ self.NM_PROFILE_KLASS = NM084Profile
+
+ # connect to signals
+ self._connect_to_signals()
+
+ def _init_nm_manager(self):
+ # define DBus details
+ self.NM_SETTINGS = NM084_SETTINGS
+ self.NM_SETTINGS_OBJ = NM084_SETTINGS_OBJ
+ self.NM_SETTINGS_INT = NM084_SETTINGS_INT
+ self.NM_SETTINGS_CONNECTION = NM084_SETTINGS_CONNECTION
+
+ super(NM084ProfileManager, self)._init_nm_manager()
+
+ def _get_secrets_gnome(self, uuid):
+ # Absolutely the bare minimum wanted here, if the keyring doesn't exist
+ # don't create it, and if we fail because we aren't on a gnome system or
+ # the value doesn't exist then just return None
+ print "NM084ProfileManager::_keyring_get_callback() trying GnomeKeyring"
+
+ attr = {
+ 'setting-name': 'gsm',
+ 'setting-key': 'password',
+ 'connection-uuid': str(uuid)
+ }
+
+ try:
+ import gnomekeyring as gk
+
+ # Search all the keyrings for it, not just default
+ items = gk.find_items_sync(gk.ITEM_GENERIC_SECRET, attr)
+ return items[0].secret
+
+ except (ImportError, IndexError, gk.IOError, gk.NoMatchError):
+ return None
+
+ def _keyring_get_callback(self, uuid):
+ # Getting the secrets probably will need to be obtained from Gnome Keyring
+ # directly on NM 0.8.4 as the DBus config usually restricts the secrets
+ # access to root only
+ print "NM084ProfileManager::_keyring_get_callback()"
+
+ try:
+ secrets = self.nm_profiles[uuid].GetSecrets('gsm', ['password',], True,
+ dbus_interface=NM084_SETTINGS_CONNECTION_SECRETS)
+ print "NM084ProfileManager::_keyring_get_callback() got secrets from NM"
+ return transpose_from_NM(secrets)
+
+ except KeyError:
+ # XXX: ideally we'd return None, but callers may expect
+ return {u'gsm': {u'passwd': u'not found'}}
+
+ except dbus.exceptions.DBusException, e:
+ if 'AccessDenied' in str(e):
+ print "NM084ProfileManager::_keyring_get_callback() DBus config prevents access to secrets"
+ else:
+ print str(e)
+
+ passwd = self._get_secrets_gnome(uuid)
+ if passwd is not None:
+ return {u'gsm': {u'passwd': unicode(passwd)}}
+
+ # XXX: TODO, try KDE's keyring, kwallet
+
+ # XXX: ideally we'd return None, but callers may expect
+ return {u'gsm': {u'passwd': u'not found'}}
+
+ def _get_next_free_gpath(self):
+ """Returns the next unused slot of /system/networking/connections"""
+ all_dirs = list(self.helper.client.all_dirs(GCONF_PROFILES_BASE))
+ try:
+ max_index = max(map(int, [d.split('/')[-1] for d in all_dirs]))
+ except ValueError:
+ # /system/networking/connections is empty
+ max_index = -1
+
+ index = 0 if not all_dirs else max_index + 1
+ return os.path.join(GCONF_PROFILES_BASE, str(index))
+
+ def _write_NM_connection_to_gconf(self, path, props):
+ for key in props:
+ for name in props[key]:
+ value = props[key][name]
+ _path = os.path.join(path, key, name)
+
+ self.helper.set_value(_path, value)
+
+ self.helper.client.notify(path)
+ self.helper.client.suggest_sync()
+
+ def add_profile(self, props):
+ """Adds a profile with settings ``props``"""
+ log.msg("Creating a new NM connection profile in GConf")
+
+ props = transpose_to_NM(props)
+
+ # AddConnection(props) never got implemented in NM 0.8x
+ # So we need to write to gconf and wait for the applet to notice
+ next_gpath = self._get_next_free_gpath()
+ self._write_NM_connection_to_gconf(next_gpath, props)
+
+ # The rest will be handled by _on_new_nm_profile when the signal
+ # arrives
+
+
+class NM09ProfileManager(NMLaterProfileManager):
+
+ def __init__(self):
+ super(NM09ProfileManager, self).__init__()
+
+ self.keyring_manager = DummyKeyringManager(self._keyring_get_callback,
+ self._keyring_set_callback)
+ # profile class to create
+ self.NM_PROFILE_KLASS = NM09Profile
+
+ # connect to signals
+ self._connect_to_signals()
+
+ def _init_nm_manager(self):
+ # define DBus details
+ self.NM_SETTINGS = NM09_SETTINGS
+ self.NM_SETTINGS_OBJ = NM09_SETTINGS_OBJ
+ self.NM_SETTINGS_INT = NM09_SETTINGS_INT
+ self.NM_SETTINGS_CONNECTION = NM09_SETTINGS_CONNECTION
+
+ super(NM09ProfileManager, self)._init_nm_manager()
+
+ def _keyring_get_callback(self, uuid):
+ # On NM0.9 we can get the secrets directly from the network manager
+ # service
+ try:
+ secrets = self.nm_profiles[uuid].GetSecrets('gsm',
+ dbus_interface=self.NM_SETTINGS_CONNECTION)
+ except (KeyError, dbus.exceptions.DBusException):
+ # XXX: ideally we'd return None, but callers may expect
+ return {u'gsm': {u'passwd': u'not found'}}
+ else:
+ return transpose_from_NM(secrets)
+
+ def add_profile(self, props):
+ """Adds a profile with settings ``props``"""
+ log.msg("Asking NM to create new connection profile")
+
+ props = transpose_to_NM(props)
+
+ # In NM 0.9 this finally got implemented
+ self.nm_manager.AddConnection(props)
+
+ # The rest will be handled by _on_new_nm_profile when the signal
+ # arrives
+
+
+class NetworkManagerBackend(object):
+
+ implements(IBackend)
+
+ def __init__(self):
+ self.bus = dbus.SystemBus()
+ self._nm08_core_present = None
+ self._nm08_applet_present = None
+ self._nm084_present = None
+ self._nm09_present = None
+ self._profile_manager = None
+ self._keyring_manager = None
+
+ def _is_nm08_core_present(self):
+ if self._nm08_core_present is None:
+ try:
+ obj = self.bus.get_object(NM_SERVICE, NM_OBJPATH)
+ devices = obj.GetDevices()
+ if len(devices):
+ self._nm08_core_present = 'NetworkManager' in devices[0]
+ else:
+ self._nm08_core_present = False
+ except dbus.DBusException:
+ self._nm08_core_present = False
+
+ return self._nm08_core_present
+
+ def _is_nm08_applet_present(self):
+ if self._nm08_applet_present is None:
+ try:
+ self.bus.get_object(NM08_USER_SETTINGS,
+ NM08_SYSTEM_SETTINGS_OBJ)
+ self._nm08_applet_present = True
+ except dbus.DBusException:
+ self._nm08_applet_present = False
+
+ return self._nm08_applet_present
+
+ def _is_nm08_present(self):
+ return all([self._is_nm08_core_present(),
+ self._is_nm08_applet_present()])
+
+ def _is_nm084_present(self):
+ if self._nm084_present is None:
+ try:
+ # NM 0.8.4 core now provides a version property
+ obj = self.bus.get_object(NM_SERVICE, NM_OBJPATH)
+ iface = dbus.Interface(obj, "org.freedesktop.DBus.Properties")
+ ver = iface.Get(NM_INTFACE, "Version")
+ if ver.startswith('0.8.4') or ver.startswith('0.8.3.99'):
+ self._nm084_present = True
+ else:
+ self._nm084_present = False
+ except dbus.DBusException:
+ self._nm084_present = False
+
+ return self._nm084_present
+
+ def _is_nm09_present(self):
+ if self._nm09_present is None:
+ try:
+ # NM 0.9 core now provides a version property
+ obj = self.bus.get_object(NM_SERVICE, NM_OBJPATH)
+ iface = dbus.Interface(obj, "org.freedesktop.DBus.Properties")
+ ver = iface.Get(NM_INTFACE, "Version")
+ if ver.startswith('0.9') or ver.startswith('0.8.99'):
+ self._nm09_present = True
+ else:
+ self._nm09_present = False
+ except dbus.DBusException:
+ self._nm09_present = False
+
+ return self._nm09_present
+
+ def _get_version(self):
+ """
+ There may be some crossover between the _is_nmXX_present()
+ functions. Using this function evaluates version in the
+ correct order.
+ """
+ if self._is_nm09_present():
+ return '09'
+ if self._is_nm084_present():
+ return '084'
+ if self._is_nm08_present():
+ return '08'
+ return None
+
+ def should_be_used(self):
+ """
+ Returns True if:
+ NM084 or NM09 is present
+ or
+ Both NM08 core and UI applet are present
+ """
+ return self._get_version() is not None
+
+ def get_dialer_klass(self, device):
+ if self._get_version() == '08':
+ return NM08Dialer
+ elif self._get_version() == '084':
+ return NM08Dialer
+ else:
+ return NM09Dialer
+
+ def get_keyring(self):
+ # XXX: should be called get_keyring_manager
+
+ if self._keyring_manager is None:
+ if self._get_version() == '08':
+ self._keyring_manager = KeyringManager(GnomeKeyring())
+ else:
+ pm = self.get_profile_manager()
+ self._keyring_manager = pm.keyring_manager
+
+ return self._keyring_manager
+
+ def get_profile_manager(self, arg=None):
+ if self._profile_manager is None:
+ if self._get_version() == '08':
+ self._profile_manager = NM08ProfileManager()
+ elif self._get_version() == '084':
+ self._profile_manager = NM084ProfileManager()
+ else:
+ self._profile_manager = NM09ProfileManager()
+
+ return self._profile_manager
+
+
+nm_backend = NetworkManagerBackend()
View
881 core/backends/plain.py
@@ -0,0 +1,881 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2006-2008 Vodafone España, S.A.
+# Copyright (C) 2008-2009 Warp Networks, S.L.
+# Author: Pablo Martí
+#
+# 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.
+from __future__ import with_statement
+
+import errno
+import pickle
+from cStringIO import StringIO
+import os
+import tempfile
+import re
+import shutil
+from signal import SIGTERM, SIGKILL
+from string import Template
+
+import dbus
+from dbus.service import signal, Object, BusName
+from twisted.internet import reactor, defer, protocol, error, task
+from twisted.python import log, procutils
+from zope.interface import implements
+
+from wader.common.aes import decryptData, encryptData
+from wader.common.consts import (WADER_PROFILES_SERVICE,
+ WADER_PROFILES_INTFACE,
+ WADER_PROFILES_OBJPATH,
+ APP_NAME, FALLBACK_DNS, MDM_INTFACE,
+ HSO_CHAP_AUTH, HSO_NO_AUTH, HSO_PAP_AUTH,
+ MM_MODEM_STATE_REGISTERED,
+ MM_MODEM_STATE_CONNECTING,
+ MM_MODEM_STATE_DISCONNECTING,
+ MM_MODEM_STATE_CONNECTED,
+ MM_SYSTEM_SETTINGS_PATH)
+from core.dialer import Dialer
+import wader.common.exceptions as ex
+from wader.common.interfaces import IBackend, IProfileManagerBackend
+from wader.common.keyring import (KeyringManager, KeyringInvalidPassword,
+ KeyringIsClosed, KeyringNoMatchError)
+from core.oal import get_os_object
+from wader.common.profile import Profile
+from wader.common.secrets import ProfileSecrets
+from wader.common.utils import save_file, is_bogus_ip, patch_list_signature
+
+
+def proc_running(pid):
+ try:
+ pid = int(pid)
+ except (TypeError, ValueError):
+ return None
+
+ if pid <= 1:
+ return False # No killing of process group members or all of init's
+ # children
+ try:
+ os.kill(pid, 0)
+ except OSError, err:
+ if err.errno == errno.ESRCH:
+ return False
+ elif err.errno == errno.EPERM:
+ return pid
+ else:
+ return None # Unknown error
+ else:
+ return pid
+
+
+def signal_process(name, pid, signal):
+ pid = proc_running(pid)
+ if not pid:
+ log.msg('wvdial: "%s" process (%s) already exited' %
+ (name, str(pid)))
+ return False
+
+ log.msg('wvdial: "%s" process (%s) will be sent %s' %
+ (name, str(pid), signal))
+ try:
+ os.kill(pid, SIGKILL)
+ except OSError, err:
+ if err.errno == errno.ESRCH:
+ log.msg('wvdial: "%s" process (%s) not found' %
+ (name, str(pid)))
+ elif err.errno == errno.EPERM:
+ log.msg('wvdial: "%s" process (%s) permission denied' %
+ (name, str(pid)))
+ else:
+ log.msg('wvdial: "%s" process exit "%s"' % (name, str(err)))
+
+ return True # signal was sent
+
+
+def validate_dns(dynamic, use_static, static):
+
+ if use_static:
+ valid_dns = [addr for addr in static if addr]
+ else:
+ # If static DNS is not set, then we should use the DNS returned by the
+ # network, but let's check if they're valid DNS IPs
+ valid_dns = [addr for addr in dynamic if not is_bogus_ip(addr)]
+
+ if len(valid_dns):
+ return True, valid_dns
+
+ # The DNS assigned by the network is invalid or missing, or the static
+ # addresses are missing, so notify the user and fallback to Google etc
+ return False, FALLBACK_DNS
+
+WVDIAL_PPPD_OPTIONS = os.path.join('/etc', 'ppp', 'peers', 'wvdial')
+WVDIAL_RETRIES = 3
+WVTEMPLATE = """
+[Dialer Defaults]
+
+Phone = $phone
+Username = $username
+Password = $password
+Stupid Mode = 1
+Dial Command = ATDT
+New PPPD = yes
+Check Def Route = on
+Dial Attempts = 3
+Dial Timeout = 30
+Auto Reconnect = off
+Auto DNS = on
+
+[Dialer connect]
+
+Modem = $serialport
+Baud = 460800
+Init1 = AT
+Init2 = AT
+Init3 = ATQ0 V1 E0 S0=0 &C1 &D2
+Init4 = AT+CGDCONT=$context,"IP","$apn"
+ISDN = 0
+Modem Type = Analog Modem
+"""
+
+CONNECTED_REGEXP = re.compile('Connected\.\.\.')
+PPPD_PID_REGEXP = re.compile('Pid of pppd: (?P<pid>\d+)')
+PPPD_IFACE_REGEXP = re.compile('Using interface (?P<iface>ppp\d+)')
+MAX_ATTEMPTS_REGEXP = re.compile('Maximum Attempts Exceeded')
+PPPD_DIED_REGEXP = re.compile('The PPP daemon has died')
+DNS_REGEXP = re.compile(r"""
+ DNS\saddress
+ \s # beginning of the string
+ (?P<ip> # group named ip
+ (25[0-5]| # integer range 250-255 OR
+ 2[0-4][0-9]| # integer range 200-249 OR
+ [01]?[0-9][0-9]?) # any number < 200
+ \. # matches '.'
+ (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # repeat
+ \. # matches '.'
+ (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # repeat
+ \. # matches '.'
+ (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?) # repeat
+ ) # end of group
+ \b # end of the string
+ """, re.VERBOSE)
+
+
+DEFAULT_TEMPLATE = """
+ipparam wader
+debug
+noauth
+name wvdial
+noipdefault
+nomagic
+ipcp-accept-local
+ipcp-accept-remote
+nomp
+noccp
+nopredictor1
+novj
+novjccomp
+nobsdcomp"""
+
+PAP_TEMPLATE = DEFAULT_TEMPLATE + """
+refuse-chap
+refuse-mschap
+refuse-mschap-v2
+refuse-eap
+"""
+
+CHAP_TEMPLATE = DEFAULT_TEMPLATE + """
+refuse-pap
+"""
+
+
+def get_wvdial_conf_file(conf, context, serial_port):
+ """
+ Returns the path of the generated wvdial.conf
+
+ :param conf: `DialerConf` instance
+ :param serial_port: The port to use
+ :rtype: str
+ """
+ text = _generate_wvdial_conf(conf, context, serial_port)
+ dirpath = tempfile.mkdtemp('', APP_NAME, '/tmp')
+ path = tempfile.mkstemp('wvdial.conf', APP_NAME, dirpath, True)[1]
+ save_file(path, text)
+ return path
+
+
+def _generate_wvdial_conf(conf, context, sport):
+ """
+ Generates a specially crafted wvdial.conf with `conf` and `sport`
+
+ :param conf: `DialerConf` instance
+ :param sport: The port to use
+ :rtype: str
+ """
+ user = conf.username if conf.username else '*'
+ passwd = conf.password if conf.password else '*'
+ theapn = conf.apn
+
+ # build template
+ data = StringIO(WVTEMPLATE)
+ template = Template(data.getvalue())
+ data.close()
+ # construct number
+ number = '*99***%d#' % context
+ # return template
+ props = dict(serialport=sport, username=user, password=passwd,
+ context=context, apn=theapn, phone=number)
+ return template.substitute(props)
+
+
+class WVDialDialer(Dialer):
+ """Dialer for WvDial"""
+
+ binary = 'wvdial'
+
+ def __init__(self, device, opath, **kwds):
+ super(WVDialDialer, self).__init__(device, opath, **kwds)
+ try:
+ self.bin_path = procutils.which(self.binary)[0]
+ except IndexError:
+ self.bin_path = '/usr/bin/wvdial'
+
+ self.backup_path = ""
+ self.conf = None
+ self.conf_path = ""
+ self.dirty = False
+ self.proto = None
+ self.iconn = None
+ self.iface = 'ppp0'
+ self.should_stop = False
+ self.attempting_connect = False
+
+ def Connected(self):
+ self.device.set_status(MM_MODEM_STATE_CONNECTED)
+ self.attempting_connect = False
+ super(WVDialDialer, self).Connected()
+
+ def Disconnected(self):
+ if self.device.status >= MM_MODEM_STATE_REGISTERED:
+ self.device.set_status(MM_MODEM_STATE_REGISTERED)
+ self.attempting_connect = False
+ super(WVDialDialer, self).Disconnected()
+
+ def configure(self, config):
+ self.dirty = True
+
+ def get_context_id(ign):
+ conn_id = self.device.sconn.state_dict.get('conn_id')
+ try:
+ context = int(conn_id)
+ except ValueError:
+ raise Exception('WVDialDialer context id is "%s"' %
+ str(conn_id))
+ return context
+
+ d = self.device.sconn.set_apn(config.apn)
+ d.addCallback(get_context_id)
+ d.addCallback(lambda context: self._generate_config(config, context))
+ return d
+
+ def connect(self):
+ if self.should_stop:
+ self.should_stop = False
+ return
+
+ self.device.set_status(MM_MODEM_STATE_CONNECTING)
+ self.attempting_connect = True
+
+ self.proto = WVDialProtocol(self)
+ args = [self.binary, '-C', self.conf_path, 'connect']
+ self.iconn = reactor.spawnProcess(self.proto, args[0], args, env=None)
+ return self.proto.deferred
+
+ def stop(self):
+ self.should_stop = True
+ self.attempting_connect = False
+ return self.disconnect()
+
+ def disconnect(self):
+ if self.proto is None:
+ return defer.succeed(self.opath)
+
+ self.device.set_status(MM_MODEM_STATE_DISCONNECTING)
+
+ msg = 'WVdial failed to connect'
+
+ def get_pppd_pid():
+ pid_file = "/var/run/%s.pid" % self.iface
+
+ if not os.path.exists(pid_file):
+ return False
+
+ pid = None
+ with open(pid_file) as f:
+ pid = f.read()
+ return pid
+
+ def cleanup_pppd():
+ if self.attempting_connect:
+ self.proto.deferred.errback(RuntimeError(msg))
+
+ pppd_pid = get_pppd_pid()
+ if not signal_process('pppd', pppd_pid, SIGTERM):
+ # process was already gone
+ d = defer.succeed(self._cleanup())
+ else:
+ d = task.deferLater(reactor, 5,
+ signal_process, 'pppd', pppd_pid, SIGKILL)
+ d.addCallback(lambda _: self._cleanup())
+ return d
+
+ # tell wvdial to quit
+ try:
+ self.proto.transport.signalProcess('TERM')
+ except error.ProcessExitedAlready:
+ log.msg("wvdial: wvdial exited")
+
+ # just be damn sure that we're killing everything
+ wvdial_pid = proc_running(self.proto.pid)
+ if not wvdial_pid:
+ # process was already gone
+ d = cleanup_pppd()
+ else:
+ d = task.deferLater(reactor, 5,
+ signal_process, 'wvdial', wvdial_pid, SIGKILL)
+ d.addCallback(lambda _: cleanup_pppd())
+
+ d.addCallback(lambda _: self.opath)
+ return d
+
+ def _generate_config(self, conf, context):
+ # backup wvdial configuration
+ self.backup_path = self._backup_conf()
+ self.conf = conf
+ # generate auth configuration
+ self._generate_wvdial_ppp_options()
+ # generate wvdial.conf from template
+ port = self.device.ports.dport
+ self.conf_path = get_wvdial_conf_file(self.conf, context, port.path)
+
+ def _cleanup(self, ignored=None):
+ """cleanup our traces"""
+ if not self.dirty:
+ return
+
+ try:
+ path = os.path.dirname(self.conf_path)
+ os.unlink(self.conf_path)
+ os.rmdir(path)
+ except (IOError, OSError):
+ pass
+
+ self._restore_conf()
+
+ def _generate_wvdial_ppp_options(self):
+ if not self.conf.refuse_chap:
+ wvdial_ppp_options = CHAP_TEMPLATE
+ elif not self.conf.refuse_pap:
+ wvdial_ppp_options = PAP_TEMPLATE
+ else:
+ # this could be a NOOP, but the user might have modified
+ # the stock /etc/ppp/peers/wvdial file, so the safest option
+ # is to overwrite with our known good options.
+ wvdial_ppp_options = DEFAULT_TEMPLATE
+
+ # There are some patched pppd implementations
+ # Most systems offer 'replacedefaultroute', but not Fedora
+ osobj = get_os_object()
+ if hasattr(osobj, 'get_additional_wvdial_ppp_options'):
+ wvdial_ppp_options += osobj.get_additional_wvdial_ppp_options()
+
+ save_file(WVDIAL_PPPD_OPTIONS, wvdial_ppp_options)
+
+ def _backup_conf(self):
+ path = tempfile.mkstemp('wvdial', APP_NAME)[1]
+ try:
+ shutil.copy(WVDIAL_PPPD_OPTIONS, path)
+ return path
+ except IOError:
+ return None
+
+ def _restore_conf(self):
+ if self.backup_path:
+ shutil.copy(self.backup_path, WVDIAL_PPPD_OPTIONS)
+ os.unlink(self.backup_path)
+ self.backup_path = None
+
+ def _set_iface(self, iface):
+ self.iface = iface
+
+
+class WVDialProtocol(protocol.ProcessProtocol):
+ """ProcessProtocol for wvdial"""
+
+ def __init__(self, dialer):
+ self.dialer = dialer
+ self.__connected = False
+ self.pid = None
+ self.retries = 0
+ self.deferred = defer.Deferred()
+ self.dns = []
+
+ def connectionMade(self):
+ self.transport.closeStdin()
+
+ def outReceived(self, data):
+ log.msg("wvdial: sysout %s" % data)
+
+ def errReceived(self, data):
+ """wvdial has this bad habit of using stderr for debug"""
+ log.msg("wvdial: %r" % data)
+ self._parse_output(data)
+
+ def outConnectionLost(self):
+ log.msg('wvdial: wvdial closed its stdout!')
+
+ def errConnectionLost(self):
+ log.msg('wvdial: wvdial closed its stderr.')
+
+ def processEnded(self, status_object):
+ log.msg('wvdial: quitting')
+
+ if not self.__connected:
+ self.dialer.disconnect()
+
+ self._set_disconnected(force=True)
+
+ def processExited(self, status):
+ log.msg('wvdial: wvdial processExited')
+
+ def _set_connected(self):
+ if self.__connected:
+ return
+
+ valid, dns = validate_dns(self.dns, self.dialer.conf.staticdns,
+ [self.dialer.conf.dns1, self.dialer.conf.dns2])
+ if not valid:
+ if self.dialer.conf.staticdns:
+ self.dialer.InvalidDNS([])
+ else:
+ self.dialer.InvalidDNS(self.dns)
+
+ osobj = get_os_object()
+ osobj.add_dns_info(dns, self.dialer.iface)
+
+ self.__connected = True
+ self.dialer.Connected()
+ self.deferred.callback(self.dialer.opath)
+
+ def _set_disconnected(self, force=False):
+ if not self.__connected and not force:
+ return
+
+ osobj = get_os_object()
+ osobj.delete_dns_info(self.dialer.iface)
+
+ self.__connected = False
+ self.dialer.Disconnected()
+
+ def _extract_iface(self, data):
+ match = PPPD_IFACE_REGEXP.search(data)
+ if match:
+ self.dialer._set_iface(match.group('iface'))
+ log.msg("wvdial: dialer interface %s" % self.dialer.iface)
+
+ def _extract_dns_strings(self, data):
+ for match in re.finditer(DNS_REGEXP, data):
+ self.dns.append(match.group('ip'))
+
+ def _extract_connected(self, data):
+ if CONNECTED_REGEXP.search(data):
+ self._set_connected()
+
+ def _extract_disconnected(self, data):
+ # more than three attempts
+ max_attempts = MAX_ATTEMPTS_REGEXP.search(data)
+ # pppd died
+ pppd_died = PPPD_DIED_REGEXP.search(data)
+
+ if max_attempts or pppd_died:
+ self._set_disconnected()
+
+ def _extract_tries(self, data):
+ # force wvdial to stop after three attempts
+ if self.retries >= WVDIAL_RETRIES:
+ self.dialer.disconnect()
+ self._set_disconnected()
+ return
+
+ # extract pppd pid
+ match = PPPD_PID_REGEXP.search(data)
+ if match:
+ self.pid = int(match.group('pid'))
+ self.retries += 1
+ log.msg("wvdial: dialer tries %d" % self.retries)
+
+ def _parse_output(self, data):
+ self._extract_iface(data)
+ self._extract_dns_strings(data)
+
+ if not self.__connected:
+ self._extract_connected(data)
+ else:
+ self._extract_disconnected(data)
+
+ self._extract_tries(data)
+
+
+class HSODialer(Dialer):
+ """Dialer for HSO type devices"""
+ # Note: The interface is called HSO for historical reasons but actually
+ # it can be used by devices other than Option's e.g. ZTE's Icera
+
+ def __init__(self, device, opath, **kwds):
+ super(HSODialer, self).__init__(device, opath, **kwds)
+ self.iface = self.device.get_property(MDM_INTFACE, 'Device')
+ self.conf = None
+
+ def configure(self, config):
+ self.conf = config
+
+ if not config.refuse_chap:
+ auth = HSO_CHAP_AUTH
+ elif not config.refuse_pap:
+ auth = HSO_PAP_AUTH
+ else:
+ auth = HSO_NO_AUTH
+
+ d = self.device.sconn.set_apn(config.apn)
+ d.addCallback(lambda _: self.device.sconn.hso_authenticate(
+ config.username, config.password, auth))
+ return d
+
+ def connect(self):
+ # start the connection
+ d = self.device.sconn.hso_connect()
+ # now get the IP4Config and set up device and routes
+ d.addCallback(lambda _: self.device.sconn.get_ip4_config())
+ d.addCallback(self._get_ip4_config_cb)
+ d.addCallback(lambda _: self.Connected())
+ d.addCallback(lambda _: self.opath)
+ return d
+
+ def _get_ip4_config_cb(self, (ip, dns1, dns2, dns3)):
+ valid, dns = validate_dns([dns1, dns2, dns3], self.conf.staticdns,
+ [self.conf.dns1, self.conf.dns2])
+ if not valid:
+ if self.conf.staticdns:
+ self.InvalidDNS([])
+ else:
+ self.InvalidDNS(self.dns)
+
+ osobj = get_os_object()
+ d = osobj.configure_iface(self.iface, ip, 'up')
+ d.addCallback(lambda _: osobj.add_default_route(self.iface))
+ d.addCallback(lambda _: osobj.add_dns_info(dns, self.iface))
+ return d
+
+ def disconnect(self):
+ d = self.device.sconn.disconnect_from_internet()
+ osobj = get_os_object()
+ osobj.delete_default_route(self.iface)
+ osobj.delete_dns_info(None, self.iface)
+ osobj.configure_iface(self.iface, '', 'down')
+ d.addCallback(lambda _: self.Disconnected())
+ return d
+
+ def stop(self):
+ # set internal flag in device for disconnection
+ self.device.sconn.state_dict['should_stop'] = True
+ return self.disconnect()
+
+
+class PlainProfile(Profile):
+ """I am a group of settings required to dial up"""
+
+ def __init__(self, opath, path, secrets_path, props=None):
+ super(PlainProfile, self).__init__(opath)
+ self.path = path
+ self.secrets_path = secrets_path
+ self.props = props
+ self.secrets = None
+ self._init()
+
+ def _init(self):
+ if self.props is None:
+ # created with "from_path"
+ self.props = pickle.load(open(self.path))
+ else:
+ # regular constructor with properties
+ self._write()
+
+ from core.backends import get_backend
+ keyring = get_backend().get_keyring(self.secrets_path)
+ self.secrets = ProfileSecrets(self, keyring)
+
+ def _write(self):
+ with open(self.path, 'w') as configfile:
+ pickle.dump(self.props, configfile, pickle.HIGHEST_PROTOCOL)
+
+ @classmethod
+ def from_path(cls, opath, path, secrets_path):
+ return cls(opath, path, secrets_path)
+
+ def get_settings(self):
+ """Returns the profile settings"""
+ return patch_list_signature(self.props)
+
+ def get_secrets(self, tag, hints=None, ask=True):
+ """
+ Returns the secrets associated with the profile
+
+ :param tag: The section to use
+ :param hints: what specific setting are we interested in
+ :param ask: Should we ask the user if there is no secret?
+ """
+ return self.secrets.get()
+
+ def get_timestamp(self):
+ """Returns the last time this profile was used"""
+ return self.props['connection'].get('timestamp', 0)
+
+ def is_good(self):
+ """Has this profile been successfully used?"""
+ return bool(self.get_timestamp())
+
+ def on_open_keyring(self, tag):
+ """Callback to be executed when the keyring has been opened"""
+ secrets = self.secrets.get(tag)
+ if secrets:
+ self.GetSecrets.reply(self, result=(secrets,))
+ else:
+ self.KeyNeeded(self, tag)
+
+ def set_secrets(self, tag, secrets):
+ """
+ Sets or updates the secrets associated with the profile
+
+ :param tag: The section to use
+ :param secrets: The new secret to store
+ """
+ self.secrets.update(secrets)
+ self.GetSecrets.reply(self, result=(secrets,))
+
+ def update(self, props):
+ """Updates the profile with settings ``props``"""
+ self.props = props
+ self._write()
+ # emit the signal
+ self.Updated(patch_list_signature(props))
+
+ def remove(self):
+ """Removes the profile"""
+ os.unlink(self.path)
+
+ # emit Removed and unexport from DBus
+ self.Removed()
+ self.remove_from_connection()
+
+
+class PlainProfileManager(Object):
+ """I manage profiles in the system"""
+
+ implements(IProfileManagerBackend)
+
+ def __init__(self, base_path):
+ self.bus = dbus.SystemBus()
+ bus_name = BusName(WADER_PROFILES_SERVICE, bus=self.bus)
+ super(PlainProfileManager, self).__init__(bus_name,
+ WADER_PROFILES_OBJPATH)
+ self.profiles = {}
+ self.index = -1
+ self.base_path = base_path
+ self.profiles_path = os.path.join(base_path, 'profiles')
+ self.secrets_path = os.path.join(base_path, 'secrets')
+ self._init()
+
+ def _init(self):
+ # check that the profiles path exists and create it otherwise
+ if not os.path.exists(self.profiles_path):
+ os.makedirs(self.profiles_path, mode=0700)
+
+ # now load the profiles
+ for uuid in os.listdir(self.profiles_path):
+ path = os.path.join(self.profiles_path, uuid)
+ profile = PlainProfile.from_path(self.get_next_dbus_opath(),
+ path, self.secrets_path)
+ self.profiles[uuid] = profile
+
+ def get_next_dbus_opath(self):
+ self.index += 1
+ return os.path.join(MM_SYSTEM_SETTINGS_PATH, str(self.index))