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
52 logger
= logging
.getLogger(__name__
)
55 def copy_configuration(original
, profiles
=False):
56 """ Return a :class:`ConfigManager` copy of :dat:`original`. If
57 :data:`profiles` is False (the default), the copy does not include
58 the known AP profiles.
60 config_copy
= ConfigManager()
61 config_copy
._defaults
= original
._defaults
.copy()
62 config_copy
._sections
= original
._sections
.copy()
63 # If not needed, remove the profiles from the new copy.
65 for section
in config_copy
.profiles():
66 config_copy
.remove_section(section
)
69 def make_section_name(essid
, bssid
):
70 """ Returns the combined 'essid' and 'bssid' to make a config file
71 section name. 'essid' and 'bssid' are strings.
73 return essid
+ ':' + bssid
76 class ConfigManager(object, configparser
.SafeConfigParser
):
77 """ Manage configuration options.
79 def __init__(self
, defaults
=None):
80 """ Create a new configuration manger with DEFAULT options and
81 values in the 'defaults' dictionary.
83 configparser
.SafeConfigParser
.__init
__(self
, defaults
)
84 self
.auto_profile_order
= []
86 def get_network_device(self
):
87 """ Return the network device name.
89 If a device is specified in the configuration file,
90 'get_network_device' returns that value. If the configuration
91 is set to "auto-detect", this method returns the first
92 WiFi device as returned by iwconfig. 'get_network_device'
93 raises 'wifiradar.misc.NoDeviceError' if no wireless device
94 can be found in auto-detect mode.
96 device
= self
.get_opt('DEFAULT', 'interface')
97 if device
== 'auto_detect':
98 # auto detect network device
100 self
.get_opt('DEFAULT', 'iwconfig_command'),
103 iwconfig_info
= Popen(iwconfig_command
, stdout
=PIPE
,
104 stderr
=STDOUT
).stdout
105 wireless_devices
= list()
106 for line
in iwconfig_info
:
108 name
= line
[0:line
.find(' ')]
109 wireless_devices
.append(name
)
110 # return the first device in the list
111 return wireless_devices
[0]
113 logger
.critical(_('problem auto-detecting wireless '
114 'device using iwconfig: {EXC}').format(EXC
=e
))
116 logger
.critical(_('No WiFi device found, '
117 'please set this in the preferences.'))
118 raise NoDeviceError('No WiFi device found.')
120 # interface has been manually specified in configuration
123 def set_section(self
, section
, dictionary
):
124 """ Set the options of 'section' to values from 'dictionary'.
126 'section' will be created if it does not exist. The keys of
127 'dictionary' are the options and its values are the option
130 for option
, value
in dictionary
.items():
131 if isinstance(value
, BooleanType
):
132 self
.set_bool_opt(section
, option
, value
)
133 elif isinstance(value
, IntType
):
134 self
.set_int_opt(section
, option
, value
)
135 elif isinstance(value
, FloatType
):
136 self
.set_float_opt(section
, option
, value
)
138 self
.set_opt(section
, option
, value
)
140 def get_profile(self
, section
):
141 """ Return the profile values in 'section' as a dictionary.
143 'get_profile' raises NoSectionError if the profile does not
146 str_types
= ['bssid', 'channel', 'essid', 'protocol',
147 'con_prescript', 'con_postscript', 'dis_prescript',
148 'dis_postscript', 'key', 'mode', 'security',
149 'wpa_driver', 'ip', 'netmask', 'gateway', 'domain',
151 bool_types
= ['known', 'available', 'roaming', 'encrypted',
152 'use_wpa', 'use_dhcp']
153 int_types
= ['signal']
154 profile
= get_new_profile()
155 for option
in bool_types
:
157 profile
[option
] = self
.get_opt_as_bool(section
, option
)
158 except configparser
.NoOptionError
:
159 # A missing option means the default will be used.
161 for option
in int_types
:
163 profile
[option
] = self
.get_opt_as_int(section
, option
)
164 except configparser
.NoOptionError
:
165 # A missing option means the default will be used.
167 for option
in str_types
:
169 profile
[option
] = self
.get_opt(section
, option
)
170 except configparser
.NoOptionError
:
171 # A missing option means the default will be used.
175 def get_opt(self
, section
, option
):
176 """ Return the value of 'option' in 'section', as a string.
178 'section' and 'option' must be strings.
180 'get_opt' raises NoSectionError when 'section' is unknown and
181 NoOptionError when 'option' in unknown.
183 # False means to use interpolation when retrieving the value.
184 return self
.get(section
, option
, False)
186 def get_opt_as_bool(self
, section
, option
):
187 """ Return the value of 'option' in 'section', as a boolean.
189 return self
.getboolean(section
, option
)
191 def get_opt_as_int(self
, section
, option
):
192 """ Return the value of 'option' in 'section', as an integer.
194 return self
.getint(section
, option
)
196 def get_opt_as_float(self
, section
, option
):
197 """ Return the value of 'option' in 'section', as a float.
199 return self
.getfloat(section
, option
)
201 def set_opt(self
, section
, option
, value
):
202 """ Set 'option' to 'value' in 'section'.
204 If 'section', does not exist in the configuration, it is
205 created. If 'section' and 'option' are not strings, each
206 will be turned into one before being added. Raises
207 TypeError if 'value' is not a string.
209 section
, option
= str(section
), str(option
)
211 self
.set(section
, option
, value
)
212 except configparser
.NoSectionError
:
213 self
.add_section(section
)
214 self
.set_opt(section
, option
, value
)
216 def set_bool_opt(self
, section
, option
, value
):
217 """ Set 'option' to boolean 'value' in 'section'.
219 'set_bool_opt' calls 'set_opt' after converting 'value' to
220 a string. Raises ValueError if 'value' is not a boolean,
221 where a boolean may be True, 'True', or a number greater
222 than 0; or False, 'False', or 0.
224 if isinstance(value
, BooleanType
):
225 # use False = 0 and True = 1 to return index into tuple
226 value
= ('False', 'True')[value
]
227 elif isinstance(value
, IntType
):
229 raise ValueError(_('boolean value must be >= 0'))
230 # use False = 0 and True = 1 to return index into tuple
231 value
= ('False', 'True')[value
> 0]
232 elif isinstance(value
, StringTypes
):
233 # convert to title case (i.e. capital first letter, only)
234 value
= value
.title()
235 if value
not in ('False', 'True'):
236 raise ValueError(_('value must be "True" or "False"'))
238 raise ValueError(_('value cannot be converted to string'))
239 self
.set_opt(section
, option
, value
)
241 def set_int_opt(self
, section
, option
, value
):
242 """ Set 'option' to integer 'value' in 'section'.
244 'set_int_opt' calls 'set_opt' after converting 'value' to
245 a string. Raises TypeError if 'value' is not an integer.
247 if not isinstance(value
, IntType
):
248 raise TypeError(_('value is not an integer'))
249 self
.set_opt(section
, option
, str(value
))
251 def set_float_opt(self
, section
, option
, value
):
252 """ Set 'option' to float 'value' in 'section'.
254 'set_float_opt' calls 'set_opt' after converting 'value' to
255 a string. Raises TypeError if 'value' is not a float.
257 if not isinstance(value
, (FloatType
, IntType
)):
258 raise TypeError(_('value is not a float or integer'))
259 self
.set_opt(section
, option
, str(float(value
)))
262 """ Return a list of the section names which denote AP profiles.
264 'profiles' does not return non-AP sections.
267 for section
in self
.sections():
269 profile_list
.append(section
)
272 def update(self
, config_manager
):
273 """ Update internal configuration information using
274 :data:`config_manager`. This works by replacing the DEFAULT
275 and some non-profile sections in the configuration. All profiles,
276 and any non-profile sections not in :data:`config_manager`, are
277 left untouched during the update.
279 self
._defaults
= config_manager
._defaults
280 for section
in (set(config_manager
.sections()) -
281 set(config_manager
.profiles())):
282 self
.set_section(section
, dict(config_manager
.items(section
)))
285 class ConfigFileManager(ConfigManager
):
286 """ Manage the configuration for the application, including reading
287 from and writing to a file.
289 def __init__(self
, filename
, defaults
=None):
290 """ Create a new configuration file at 'filename' with DEFAULT
291 options and values in the 'defaults' dictionary.
293 ConfigManager
.__init
__(self
, defaults
)
294 self
.filename
= filename
297 """ Read configuration file from disk into instance variables.
299 fp
= open(self
.filename
, 'r')
301 # convert the auto_profile_order to a list for ordering
302 self
.auto_profile_order
= eval(self
.get_opt('DEFAULT', 'auto_profile_order'))
303 for ap
in self
.profiles():
304 self
.set_bool_opt(ap
, 'known', True)
305 if ap
in self
.auto_profile_order
: continue
306 self
.auto_profile_order
.append(ap
)
308 # Remove any auto_profile_order AP without a matching section.
309 auto_profile_order_copy
= self
.auto_profile_order
[:]
310 for ap
in auto_profile_order_copy
:
311 if ap
not in self
.profiles():
312 self
.auto_profile_order
.remove(ap
)
315 """ Write configuration file to disk from instance variables.
317 Copied from configparser and modified to write options in
320 self
.set_opt('DEFAULT', 'auto_profile_order',
321 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('{KEY} = {VALUE}\n'.format(KEY
=key
,
330 VALUE
=str(self
._defaults
[key
]).replace('\n','\n\t')))
332 # write other non-profile sections next
333 for section
in self
._sections
:
334 if section
not in self
.profiles():
335 fp
.write('[{SECT}]\n'.format(SECT
=section
))
336 for key
in sorted(self
._sections
[section
].keys()):
337 if key
!= '__name__':
338 fp
.write('{KEY} = {VALUE}\n'.format(KEY
=key
,
339 VALUE
=str(self
._sections
[section
][key
]
340 ).replace('\n', '\n\t')))
342 # write profile sections
343 for section
in self
._sections
:
344 if section
in self
.profiles():
345 fp
.write('[{SECT}]\n'.format(SECT
=section
))
346 for key
in sorted(self
._sections
[section
].keys()):
347 if key
!= '__name__':
348 fp
.write('{KEY} = {VALUE}\n'.format(KEY
=key
,
349 VALUE
=str(self
._sections
[section
][key
]
350 ).replace('\n', '\n\t')))
353 move(tempfilename
, self
.filename
)
356 # Make so we can be imported
357 if __name__
== '__main__':