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
= str(section
), str(option
)
209 if not isinstance(value
, StringType
):
210 raise TypeError(_('value must be a string type'))
212 self
.set(section
, option
, value
)
213 except configparser
.NoSectionError
:
214 self
.add_section(section
)
215 self
.set_opt(section
, option
, value
)
217 def set_bool_opt(self
, section
, option
, value
):
218 """ Set 'option' to boolean 'value' in 'section'.
220 'set_bool_opt' calls 'set_opt' after converting 'value' to
221 a string. Raises ValueError if 'value' is not a boolean,
222 where a boolean may be True, 'True', or a number greater
223 than 0; or False, 'False', or 0.
225 if isinstance(value
, BooleanType
):
226 # use False = 0 and True = 1 to return index into tuple
227 value
= ('False', 'True')[value
]
228 elif isinstance(value
, IntType
):
230 raise ValueError(_('boolean value must be >= 0'))
231 # use False = 0 and True = 1 to return index into tuple
232 value
= ('False', 'True')[value
> 0]
233 elif isinstance(value
, StringType
):
234 # convert to title case (i.e. capital first letter, only)
235 value
= value
.title()
236 if value
not in ('False', 'True'):
237 raise ValueError(_("value must be 'True' or 'False'"))
239 raise ValueError(_('value cannot be converted to string'))
240 self
.set_opt(section
, option
, value
)
242 def set_int_opt(self
, section
, option
, value
):
243 """ Set 'option' to integer 'value' in 'section'.
245 'set_int_opt' calls 'set_opt' after converting 'value' to
246 a string. Raises TypeError if 'value' is not an integer.
248 if not isinstance(value
, IntType
):
249 raise TypeError(_('value is not an integer'))
250 self
.set_opt(section
, option
, str(value
))
252 def set_float_opt(self
, section
, option
, value
):
253 """ Set 'option' to float 'value' in 'section'.
255 'set_float_opt' calls 'set_opt' after converting 'value' to
256 a string. Raises TypeError if 'value' is not a float.
258 if not isinstance(value
, (FloatType
, IntType
)):
259 raise TypeError(_('value is not a float or integer'))
260 self
.set_opt(section
, option
, str(float(value
)))
263 """ Return a list of the section names which denote AP profiles.
265 'profiles' does not return non-AP sections.
268 for section
in self
.sections():
270 profile_list
.append(section
)
273 def update(self
, config_manager
):
274 """ Update internal configuration information using
275 :data:`config_manager`. This works by replacing the DEFAULT
276 and some non-profile sections in the configuration. All profiles,
277 and any non-profile sections not in :data:`config_manager`, are
278 left untouched during the update.
280 self
._defaults
= config_manager
._defaults
281 for section
in (set(config_manager
.sections()) -
282 set(config_manager
.profiles())):
283 self
.set_section(section
, dict(config_manager
.items(section
)))
286 class ConfigFileManager(ConfigManager
):
287 """ Manage the configuration for the application, including reading
288 from and writing to a file.
290 def __init__(self
, filename
, defaults
):
291 """ Create a new configuration file at 'filename' with DEFAULT
292 options and values in the 'defaults' dictionary.
294 ConfigManager
.__init
__(self
, defaults
)
295 self
.filename
= filename
298 """ Read configuration file from disk into instance variables.
300 fp
= open(self
.filename
, "r")
302 # convert the auto_profile_order to a list for ordering
303 self
.auto_profile_order
= eval(self
.get_opt('DEFAULT', 'auto_profile_order'))
304 for ap
in self
.profiles():
305 self
.set_bool_opt(ap
, 'known', True)
306 if ap
in self
.auto_profile_order
: continue
307 self
.auto_profile_order
.append(ap
)
309 # Remove any auto_profile_order AP without a matching section.
310 auto_profile_order_copy
= self
.auto_profile_order
[:]
311 for ap
in auto_profile_order_copy
:
312 if ap
not in self
.profiles():
313 self
.auto_profile_order
.remove(ap
)
316 """ Write configuration file to disk from instance variables.
318 Copied from configparser and modified to write options in
321 self
.set_opt('DEFAULT', 'auto_profile_order', str(self
.auto_profile_order
))
322 self
.set_opt('DEFAULT', 'version', WIFI_RADAR_VERSION
)
323 (fd
, tempfilename
) = tempfile
.mkstemp(prefix
="wifi-radar.conf.")
324 fp
= os
.fdopen(fd
, "w")
325 # write DEFAULT section first
327 fp
.write("[DEFAULT]\n")
328 for key
in sorted(self
._defaults
.keys()):
329 fp
.write("%s = %s\n" % (key
, str(self
._defaults
[key
]).replace('\n','\n\t')))
331 # write other non-profile sections next
332 for section
in self
._sections
:
333 if section
not in self
.profiles():
334 fp
.write("[%s]\n" % section
)
335 for key
in sorted(self
._sections
[section
].keys()):
336 if key
!= "__name__":
337 fp
.write("%s = %s\n" %
338 (key
, str(self
._sections
[section
][key
]).replace('\n', '\n\t')))
340 # write profile sections
341 for section
in self
._sections
:
342 if section
in self
.profiles():
343 fp
.write("[%s]\n" % section
)
344 for key
in sorted(self
._sections
[section
].keys()):
345 if key
!= "__name__":
346 fp
.write("%s = %s\n" %
347 (key
, str(self
._sections
[section
][key
]).replace('\n', '\n\t')))
350 move(tempfilename
, self
.filename
)
353 # Make so we can be imported
354 if __name__
== "__main__":