2 # -*- coding: utf-8 -*-
4 # config.py - support for WiFi Radar configuration
6 # Part of WiFi Radar: A utility for managing WiFi profiles on GNU/Linux.
8 # Copyright (C) 2004-2005 Ahmad Baitalmal <ahmad@baitalmal.com>
9 # Copyright (C) 2005 Nicolas Brouard <nicolas.brouard@mandrake.org>
10 # Copyright (C) 2005-2009 Brian Elliott Finley <brian@thefinleys.com>
11 # Copyright (C) 2006 David Decotigny <com.d2@free.fr>
12 # Copyright (C) 2006 Simon Gerber <gesimu@gmail.com>
13 # Copyright (C) 2006-2007 Joey Hurst <jhurst@lucubrate.org>
14 # Copyright (C) 2006, 2009 Ante Karamatic <ivoks@ubuntu.com>
15 # Copyright (C) 2009-2010,2014 Sean Robinson <robinson@tuxfamily.org>
16 # Copyright (C) 2010 Prokhor Shuchalov <p@shuchalov.ru>
18 # This program is free software; you can redistribute it and/or modify
19 # it under the terms of the GNU General Public License as published by
20 # the Free Software Foundation; version 2 of the License.
22 # This program is distributed in the hope that it will be useful,
23 # but WITHOUT ANY WARRANTY; without even the implied warranty of
24 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 # GNU General Public License in LICENSE.GPL for more details.
27 # You should have received a copy of the GNU General Public License
28 # along with this program; if not, write to:
29 # Free Software Foundation, Inc.
30 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
34 from __future__
import unicode_literals
39 from shutil
import move
40 from subprocess
import Popen
, PIPE
, STDOUT
43 from wifiradar
.misc
import *
46 import ConfigParser
as configparser
51 logger
= logging
.getLogger(__name__
)
54 def copy_configuration(original
, profiles
=False):
55 """ Return a :class:`ConfigManager` copy of :dat:`original`. If
56 :data:`profiles` is False (the default), the copy does not include
57 the known AP profiles.
59 config_copy
= ConfigManager()
60 config_copy
._defaults
= original
._defaults
.copy()
61 config_copy
._sections
= original
._sections
.copy()
62 # If not needed, remove the profiles from the new copy.
64 for section
in config_copy
.profiles():
65 config_copy
.remove_section(section
)
68 def make_section_name(essid
, bssid
):
69 """ Returns the combined 'essid' and 'bssid' to make a config file
70 section name. 'essid' and 'bssid' are strings.
72 return essid
+ ':' + bssid
75 class ConfigManager(object, configparser
.SafeConfigParser
):
76 """ Manage configuration options.
78 def __init__(self
, defaults
=None):
79 """ Create a new configuration manger with DEFAULT options and
80 values in the 'defaults' dictionary.
82 configparser
.SafeConfigParser
.__init
__(self
, defaults
)
83 self
.auto_profile_order
= []
85 def get_network_device(self
):
86 """ Return the network device name.
88 If a device is specified in the configuration file,
89 'get_network_device' returns that value. If the configuration
90 is set to "auto-detect", this method returns the first
91 WiFi device as returned by iwconfig. 'get_network_device'
92 raises 'wifiradar.misc.NoDeviceError' if no wireless device
93 can be found in auto-detect mode.
95 device
= self
.get_opt('DEFAULT', 'interface')
96 if device
== "auto_detect":
97 # auto detect network device
99 self
.get_opt('DEFAULT', 'iwconfig_command'),
102 iwconfig_info
= Popen(iwconfig_command
, stdout
=PIPE
,
103 stderr
=STDOUT
).stdout
104 wireless_devices
= list()
105 for line
in iwconfig_info
:
107 name
= line
[0:line
.find(' ')]
108 wireless_devices
.append(name
)
109 # return the first device in the list
110 return wireless_devices
[0]
112 logger
.critical(_('problem auto-detecting wireless '
113 'device using iwconfig: {EXC}').format(EXC
=e
))
115 logger
.critical(_('No WiFi device found, '
116 'please set this in the preferences.'))
117 raise NoDeviceError('No WiFi device found.')
119 # interface has been manually specified in configuration
122 def set_section(self
, section
, dictionary
):
123 """ Set the options of 'section' to values from 'dictionary'.
125 'section' will be created if it does not exist. The keys of
126 'dictionary' are the options and its values are the option
129 for option
, value
in dictionary
.items():
130 if isinstance(value
, BooleanType
):
131 self
.set_bool_opt(section
, option
, value
)
132 elif isinstance(value
, IntType
):
133 self
.set_int_opt(section
, option
, value
)
134 elif isinstance(value
, FloatType
):
135 self
.set_float_opt(section
, option
, value
)
137 self
.set_opt(section
, option
, value
)
139 def get_profile(self
, section
):
140 """ Return the profile values in 'section' as a dictionary.
142 'get_profile' raises NoSectionError if the profile does not
145 str_types
= ['bssid', 'channel', 'essid', 'protocol',
146 'con_prescript', 'con_postscript', 'dis_prescript',
147 'dis_postscript', 'key', 'mode', 'security',
148 'wpa_driver', 'ip', 'netmask', 'gateway', 'domain',
150 bool_types
= ['known', 'available', 'roaming', 'encrypted',
151 'use_wpa', 'use_dhcp']
152 int_types
= ['signal']
153 profile
= get_new_profile()
154 for option
in bool_types
:
156 profile
[option
] = self
.get_opt_as_bool(section
, option
)
157 except configparser
.NoOptionError
:
158 # A missing option means the default will be used.
160 for option
in int_types
:
162 profile
[option
] = self
.get_opt_as_int(section
, option
)
163 except configparser
.NoOptionError
:
164 # A missing option means the default will be used.
166 for option
in str_types
:
168 profile
[option
] = self
.get_opt(section
, option
)
169 except configparser
.NoOptionError
:
170 # A missing option means the default will be used.
174 def get_opt(self
, section
, option
):
175 """ Return the value of 'option' in 'section', as a string.
177 'section' and 'option' must be strings.
179 'get_opt' raises NoSectionError when 'section' is unknown and
180 NoOptionError when 'option' in unknown.
182 # False means to use interpolation when retrieving the value.
183 return self
.get(section
, option
, False)
185 def get_opt_as_bool(self
, section
, option
):
186 """ Return the value of 'option' in 'section', as a boolean.
188 return self
.getboolean(section
, option
)
190 def get_opt_as_int(self
, section
, option
):
191 """ Return the value of 'option' in 'section', as an integer.
193 return self
.getint(section
, option
)
195 def get_opt_as_float(self
, section
, option
):
196 """ Return the value of 'option' in 'section', as a float.
198 return self
.getfloat(section
, option
)
200 def set_opt(self
, section
, option
, value
):
201 """ Set 'option' to 'value' in 'section'.
203 If 'section', does not exist in the configuration, it is
204 created. If 'section' and 'option' are not strings, each
205 will be turned into one before being added. Raises
206 TypeError if 'value' is not a string.
208 section
, option
= unicode(section
), unicode(option
)
210 self
.set(section
, option
, value
)
211 except configparser
.NoSectionError
:
212 self
.add_section(section
)
213 self
.set_opt(section
, option
, value
)
215 def set_bool_opt(self
, section
, option
, value
):
216 """ Set 'option' to boolean 'value' in 'section'.
218 'set_bool_opt' calls 'set_opt' after converting 'value' to
219 a string. Raises ValueError if 'value' is not a boolean,
220 where a boolean may be True, 'True', or a number greater
221 than 0; or False, 'False', or 0.
223 if isinstance(value
, BooleanType
):
224 # use False = 0 and True = 1 to return index into tuple
225 value
= ('False', 'True')[value
]
226 elif isinstance(value
, IntType
):
228 raise ValueError(_('boolean value must be >= 0'))
229 # use False = 0 and True = 1 to return index into tuple
230 value
= ('False', 'True')[value
> 0]
231 elif isinstance(value
, StringTypes
):
232 # convert to title case (i.e. capital first letter, only)
233 value
= value
.title()
234 if value
not in ('False', 'True'):
235 raise ValueError(_("value must be 'True' or 'False'"))
237 raise ValueError(_('value cannot be converted to string'))
238 self
.set_opt(section
, option
, value
)
240 def set_int_opt(self
, section
, option
, value
):
241 """ Set 'option' to integer 'value' in 'section'.
243 'set_int_opt' calls 'set_opt' after converting 'value' to
244 a string. Raises TypeError if 'value' is not an integer.
246 if not isinstance(value
, IntType
):
247 raise TypeError(_('value is not an integer'))
248 self
.set_opt(section
, option
, unicode(value
))
250 def set_float_opt(self
, section
, option
, value
):
251 """ Set 'option' to float 'value' in 'section'.
253 'set_float_opt' calls 'set_opt' after converting 'value' to
254 a string. Raises TypeError if 'value' is not a float.
256 if not isinstance(value
, (FloatType
, IntType
)):
257 raise TypeError(_('value is not a float or integer'))
258 self
.set_opt(section
, option
, unicode(float(value
)))
261 """ Return a list of the section names which denote AP profiles.
263 'profiles' does not return non-AP sections.
266 for section
in self
.sections():
268 profile_list
.append(section
)
271 def update(self
, config_manager
):
272 """ Update internal configuration information using
273 :data:`config_manager`. This works by replacing the DEFAULT
274 and some non-profile sections in the configuration. All profiles,
275 and any non-profile sections not in :data:`config_manager`, are
276 left untouched during the update.
278 self
._defaults
= config_manager
._defaults
279 for section
in (set(config_manager
.sections()) -
280 set(config_manager
.profiles())):
281 self
.set_section(section
, dict(config_manager
.items(section
)))
284 class ConfigFileManager(ConfigManager
):
285 """ Manage the configuration for the application, including reading
286 from and writing to a file.
288 def __init__(self
, filename
, defaults
):
289 """ Create a new configuration file at 'filename' with DEFAULT
290 options and values in the 'defaults' dictionary.
292 ConfigManager
.__init
__(self
, defaults
)
293 self
.filename
= filename
296 """ Read configuration file from disk into instance variables.
298 fp
= open(self
.filename
, "r")
300 # convert the auto_profile_order to a list for ordering
301 self
.auto_profile_order
= eval(self
.get_opt('DEFAULT', 'auto_profile_order'))
302 for ap
in self
.profiles():
303 self
.set_bool_opt(ap
, 'known', True)
304 if ap
in self
.auto_profile_order
: continue
305 self
.auto_profile_order
.append(ap
)
307 # Remove any auto_profile_order AP without a matching section.
308 auto_profile_order_copy
= self
.auto_profile_order
[:]
309 for ap
in auto_profile_order_copy
:
310 if ap
not in self
.profiles():
311 self
.auto_profile_order
.remove(ap
)
314 """ Write configuration file to disk from instance variables.
316 Copied from configparser and modified to write options in
319 self
.set_opt('DEFAULT', 'auto_profile_order', unicode(self
.auto_profile_order
))
320 self
.set_opt('DEFAULT', 'version', WIFI_RADAR_VERSION
)
321 (fd
, tempfilename
) = tempfile
.mkstemp(prefix
="wifi-radar.conf.")
322 fp
= os
.fdopen(fd
, "w")
323 # write DEFAULT section first
325 fp
.write("[DEFAULT]\n")
326 for key
in sorted(self
._defaults
.keys()):
327 fp
.write("%s = %s\n" % (key
, unicode(self
._defaults
[key
]).replace('\n','\n\t')))
329 # write other non-profile sections next
330 for section
in self
._sections
:
331 if section
not in self
.profiles():
332 fp
.write("[%s]\n" % section
)
333 for key
in sorted(self
._sections
[section
].keys()):
334 if key
!= "__name__":
335 fp
.write("%s = %s\n" %
336 (key
, unicode(self
._sections
[section
][key
]).replace('\n', '\n\t')))
338 # write profile sections
339 for section
in self
._sections
:
340 if section
in self
.profiles():
341 fp
.write("[%s]\n" % section
)
342 for key
in sorted(self
._sections
[section
].keys()):
343 if key
!= "__name__":
344 fp
.write("%s = %s\n" %
345 (key
, unicode(self
._sections
[section
][key
]).replace('\n', '\n\t')))
348 move(tempfilename
, self
.filename
)
351 # Make so we can be imported
352 if __name__
== "__main__":