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 <seankrobinson@gmail.com>
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 the Free Software
29 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
35 import ConfigParser
as configparser
43 from shutil
import move
44 from subprocess
import Popen
, PIPE
, STDOUT
47 import wifiradar
.misc
as misc
50 logger
= logging
.getLogger(__name__
)
53 def copy_configuration(original
, profiles
=False):
54 """ Return a :class:`ConfigManager` copy of :dat:`original`. If
55 :data:`profiles` is False (the default), the copy does not include
56 the known AP profiles.
58 config_copy
= ConfigManager()
59 config_copy
._defaults
= original
._defaults
.copy()
60 config_copy
._sections
= original
._sections
.copy()
61 # If not needed, remove the profiles from the new copy.
63 for section
in config_copy
.profiles():
64 config_copy
.remove_section(section
)
67 def make_section_name(essid
, bssid
):
68 """ Returns the combined 'essid' and 'bssid' to make a config file
69 section name. 'essid' and 'bssid' are strings.
71 return essid
+ ':' + bssid
74 class ConfigManager(object, configparser
.SafeConfigParser
):
75 """ Manage configuration options.
77 def __init__(self
, defaults
=None):
78 """ Create a new configuration manger with DEFAULT options and
79 values in the 'defaults' dictionary.
81 configparser
.SafeConfigParser
.__init
__(self
, defaults
)
82 self
.auto_profile_order
= []
84 def get_network_device(self
):
85 """ Return the network device name.
87 If a device is specified in the configuration file,
88 'get_network_device' returns that value. If the configuration
89 is set to "auto-detect", this method returns the first
90 WiFi device as returned by iwconfig. 'get_network_device'
91 raises 'wifiradar.misc.NoDeviceError' if no wireless device
92 can be found in auto-detect mode.
94 device
= self
.get_opt('DEFAULT', 'interface')
95 if device
== "auto_detect":
96 # auto detect network device
98 self
.get_opt('DEFAULT', 'iwconfig_command'),
101 iwconfig_info
= Popen(iwconfig_command
, stdout
=PIPE
,
102 stderr
=STDOUT
).stdout
103 wireless_devices
= list()
104 for line
in iwconfig_info
:
106 name
= line
[0:line
.find(' ')]
107 wireless_devices
.append(name
)
108 # return the first device in the list
109 return wireless_devices
[0]
111 logger
.critical('problem auto-detecting wireless ' + \
112 'device using iwconfig: {}'.format(e
))
114 logger
.critical('No WiFi device found, ' + \
115 'please set this in the preferences.')
116 raise misc
.NoDeviceError('No WiFi device found.')
118 # interface has been manually specified in configuration
121 def set_section(self
, section
, dictionary
):
122 """ Set the options of 'section' to values from 'dictionary'.
124 'section' will be created if it does not exist. The keys of
125 'dictionary' are the options and its values are the option
128 for option
, value
in dictionary
.items():
129 if isinstance(value
, BooleanType
):
130 self
.set_bool_opt(section
, option
, value
)
131 elif isinstance(value
, IntType
):
132 self
.set_int_opt(section
, option
, value
)
133 elif isinstance(value
, FloatType
):
134 self
.set_float_opt(section
, option
, value
)
136 self
.set_opt(section
, option
, value
)
138 def get_profile(self
, section
):
139 """ Return the profile values in 'section' as a dictionary.
141 'get_profile' raises NoSectionError if the profile does not
144 str_types
= ['bssid', 'channel', 'essid', 'protocol',
145 'con_prescript', 'con_postscript', 'dis_prescript',
146 'dis_postscript', 'key', 'mode', 'security',
147 'wpa_driver', 'ip', 'netmask', 'gateway', 'domain',
149 bool_types
= ['known', 'available', 'roaming', 'encrypted',
150 'use_wpa', 'use_dhcp']
151 int_types
= ['signal']
152 profile
= misc
.get_new_profile()
153 for option
in bool_types
:
155 profile
[option
] = self
.get_opt_as_bool(section
, option
)
156 except configparser
.NoOptionError
:
157 # A missing option means the default will be used.
159 for option
in int_types
:
161 profile
[option
] = self
.get_opt_as_int(section
, option
)
162 except configparser
.NoOptionError
:
163 # A missing option means the default will be used.
165 for option
in str_types
:
167 profile
[option
] = self
.get_opt(section
, option
)
168 except configparser
.NoOptionError
:
169 # A missing option means the default will be used.
173 def get_opt(self
, section
, option
):
174 """ Return the value of 'option' in 'section', as a string.
176 'section' and 'option' must be strings.
178 'get_opt' raises NoSectionError when 'section' is unknown and
179 NoOptionError when 'option' in unknown.
181 # False means to use interpolation when retrieving the value.
182 return self
.get(section
, option
, False)
184 def get_opt_as_bool(self
, section
, option
):
185 """ Return the value of 'option' in 'section', as a boolean.
187 return self
.getboolean(section
, option
)
189 def get_opt_as_int(self
, section
, option
):
190 """ Return the value of 'option' in 'section', as an integer.
192 return self
.getint(section
, option
)
194 def get_opt_as_float(self
, section
, option
):
195 """ Return the value of 'option' in 'section', as a float.
197 return self
.getfloat(section
, option
)
199 def set_opt(self
, section
, option
, value
):
200 """ Set 'option' to 'value' in 'section'.
202 If 'section', does not exist in the configuration, it is
203 created. If 'section' and 'option' are not strings, each
204 will be turned into one before being added. Raises
205 TypeError if 'value' is not a string.
207 section
, option
= str(section
), str(option
)
208 if not isinstance(value
, StringType
):
209 raise TypeError('value must be a string type')
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
, StringType
):
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
):
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', str(self
.auto_profile_order
))
321 self
.set_opt('DEFAULT', 'version', misc
.WIFI_RADAR_VERSION
)
322 (fd
, tempfilename
) = tempfile
.mkstemp(prefix
="wifi-radar.conf.")
323 fp
= os
.fdopen(fd
, "w")
324 # write DEFAULT section first
326 fp
.write("[DEFAULT]\n")
327 for key
in sorted(self
._defaults
.keys()):
328 fp
.write("%s = %s\n" % (key
, str(self
._defaults
[key
]).replace('\n','\n\t')))
330 # write other non-profile sections next
331 for section
in self
._sections
:
332 if section
not in self
.profiles():
333 fp
.write("[%s]\n" % section
)
334 for key
in sorted(self
._sections
[section
].keys()):
335 if key
!= "__name__":
336 fp
.write("%s = %s\n" %
337 (key
, str(self
._sections
[section
][key
]).replace('\n', '\n\t')))
339 # write profile sections
340 for section
in self
._sections
:
341 if section
in self
.profiles():
342 fp
.write("[%s]\n" % section
)
343 for key
in sorted(self
._sections
[section
].keys()):
344 if key
!= "__name__":
345 fp
.write("%s = %s\n" %
346 (key
, str(self
._sections
[section
][key
]).replace('\n', '\n\t')))
349 move(tempfilename
, self
.filename
)
352 # Make so we can be imported
353 if __name__
== "__main__":