base.py 27.9 KB
Newer Older
1
# Foris - web administration interface for OpenWrt based on NETCONF
2
# Copyright (C) 2013 CZ.NIC, z.s.p.o. <http://www.nic.cz>
3 4 5 6 7 8 9 10 11 12 13 14 15
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
16 17 18
import logging

import bottle
19

20
from foris import ugettext as _
21
from form import File, Password, Textbox, Dropdown, Checkbox, Hidden, Radio
22 23
import fapi
from nuci import client, filters
24
from nuci.modules.uci_raw import Uci, Config, Section, Option, List, Value
25
import validators
26 27 28


logger = logging.getLogger(__name__)
29 30 31 32 33 34 35 36 37 38 39 40 41 42


class BaseConfigHandler(object):
    def __init__(self, data=None):
        self.data = data
        self.__form_cache = None

    @property
    def form(self):
        if self.__form_cache is None:
            self.__form_cache = self.get_form()
        return self.__form_cache

    def call_action(self, action):
43 44 45 46 47 48 49 50
        """Call config page action.

        :param action:
        :return: object that can be passed as HTTP response to Bottle
        """
        raise NotImplementedError()

    def call_ajax_action(self, action):
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
        """Call AJAX action.

        :param action:
        :return: dict of picklable AJAX results
        """
        raise NotImplementedError()

    def get_form(self):
        """Get form for this wizard. MUST be a single-section form.

        :return:
        :rtype: fapi.ForisForm
        """
        raise NotImplementedError()

66 67 68 69 70 71
    def save(self, extra_callbacks=None):
        """

        :param extra_callbacks: list of extra callbacks to call when saved
        :return:
        """
72 73
        form = self.form
        form.validate()
74 75 76
        if extra_callbacks:
            for cb in extra_callbacks:
                form.add_callback(cb)
77 78 79 80 81 82 83 84 85 86 87
        if form.valid:
            form.save()
            return True
        else:
            return False


class PasswordHandler(BaseConfigHandler):
    """
    Setting the password
    """
88

Pavol Otto's avatar
Pavol Otto committed
89
    # {{ _("Password") }} - for translation
90
    userfriendly_title = "Password"
91

92 93 94 95
    def __init__(self, *args, **kwargs):
        self.change = kwargs.pop("change", False)
        super(PasswordHandler, self).__init__(*args, **kwargs)

96 97 98
    def get_form(self):
        # form definitions
        pw_form = fapi.ForisForm("password", self.data)
99
        pw_main = pw_form.add_section(name="set_password", title=_(self.userfriendly_title),
100 101
                                      description=_("Set your password for this administration interface."
                                                    " The password must be at least 6 characters long."))
102
        if self.change:
Bedřich Košata's avatar
Bedřich Košata committed
103 104 105 106 107 108 109 110
            pw_main.add_field(Password, name="old_password", label=_("Current password"))
            label_pass1 = _("New password")
            label_pass2 = _("New password (repeat)")
        else:
            label_pass1 = _("Password")
            label_pass2 = _("Password (repeat)")
        
        pw_main.add_field(Password, name="password", label=label_pass1, required=True,
111
                          validators=validators.LenRange(6, 128))
Bedřich Košata's avatar
Bedřich Košata committed
112
        pw_main.add_field(Password, name="password_validation", label=label_pass2,
113 114
                          required=True, validators=validators.EqualTo("password", "password_validation",
                                                                       _("Passwords are not equal.")))
115
        pw_main.add_field(Checkbox, name="set_system_pw", label=_("Use the same password for advanced configuration"),
116
                          hint=_("Same password would be used for accessing this administration "
117
                                 "interface, for root user in LuCI web interface and for SSH login. "
118 119 120
                                 "Use a strong password! (If you choose not to set the password "
                                 "for advanced configuration here, you will have the option to do "
                                 "so later. Until then, the root account will be blocked.)"))
121 122

        def pw_form_cb(data):
123
            from beaker.crypto import pbkdf2
124 125 126 127 128 129 130 131 132 133
            if self.change:
                # if changing password, check the old pw is right first
                uci_data = client.get(filter=filters.uci)
                password_hash = uci_data.find_child("uci.foris.auth.password")
                # allow changing the password if password_hash is empty
                if password_hash:
                    password_hash = password_hash.value
                    # crypt automatically extracts salt and iterations from formatted pw hash
                    if password_hash != pbkdf2.crypt(data['old_password'], salt=password_hash):
                        return "save_result", {'wrong_old_password': True}
134 135

            uci = Uci()
136 137 138 139
            foris = Config("foris")
            uci.add(foris)
            auth = Section("auth", "config")
            foris.add(auth)
140 141 142
            # use 48bit pseudo-random salt internally generated by pbkdf2
            new_password_hash = pbkdf2.crypt(data['password'], iterations=1000)
            auth.add(Option("password", new_password_hash))
143

144 145 146
            if data['set_system_pw'] is True:
                client.set_password("root", data['password'])

147
            return "edit_config", uci
148

149 150 151 152 153
        pw_form.add_callback(pw_form_cb)
        return pw_form


class WanHandler(BaseConfigHandler):
Pavol Otto's avatar
Pavol Otto committed
154
    # {{ _("WAN") }} - for translation
155
    userfriendly_title = "WAN"
156

157 158 159
    def get_form(self):
        # WAN
        wan_form = fapi.ForisForm("wan", self.data, filter=filters.uci)
160
        wan_main = wan_form.add_section(name="set_wan", title=_(self.userfriendly_title),
161
                                        description=_("Here you specify your WAN port settings. "
Bedřich Košata's avatar
Bedřich Košata committed
162 163 164
                "Usually, you can leave this options untouched unless instructed otherwise by your "
                "internet service provider. Also, in case there is a cable or DSL modem connecting "
                "your router to the network, it is usually not necessary to change this setting."))
165 166 167 168 169

        WAN_DHCP = "dhcp"
        WAN_STATIC = "static"
        WAN_PPPOE = "pppoe"
        WAN_OPTIONS = (
170 171 172
            (WAN_DHCP, _("DHCP (automatic configuration)")),
            (WAN_STATIC, _("Static IP address (manual configuration)")),
            (WAN_PPPOE, _("PPPoE (for DSL bridges, etc.)")),
173
        )
174

175
        # protocol
176
        wan_main.add_field(Dropdown, name="proto", label=_("Protocol"),
177 178
                           nuci_path="uci.network.wan.proto",
                           args=WAN_OPTIONS, default=WAN_DHCP)
179

180
        # static ipv4
181
        wan_main.add_field(Textbox, name="ipaddr", label=_("IP address"),
182 183
                           nuci_path="uci.network.wan.ipaddr",
                           required=True, validators=validators.IPv4())\
184
            .requires("proto", WAN_STATIC)
185
        wan_main.add_field(Textbox, name="netmask", label=_("Network mask"),
186
                           nuci_path="uci.network.wan.netmask",
187
                           required=True, validators=validators.IPv4Netmask())\
188
            .requires("proto", WAN_STATIC)
189
        wan_main.add_field(Textbox, name="gateway", label=_("Gateway"),
190
                           nuci_path="uci.network.wan.gateway",
191 192
                           validators=validators.IPv4(),
                           required=True)\
193
            .requires("proto", WAN_STATIC)
194 195 196 197 198 199 200 201

        def extract_dns_item(dns_string, index, default=None):
            try:
                return dns_string.split(" ")[index]
            except IndexError:
                return default

        wan_main.add_field(Textbox, name="dns1", label=_("DNS server 1"),
202
                           nuci_path="uci.network.wan.dns",
203
                           nuci_preproc=lambda val: extract_dns_item(val.value, 0),
204
                           validators=validators.AnyIP(),
205 206
                           hint=_("DNS server address is not required as the built-in "
                                  "DNS resolver is capable of working without it."))\
207 208 209 210
            .requires("proto", WAN_STATIC)
        wan_main.add_field(Textbox, name="dns2", label=_("DNS server 2"),
                           nuci_path="uci.network.wan.dns",
                           nuci_preproc=lambda val: extract_dns_item(val.value, 1),
211
                           validators=validators.AnyIP(),
212 213
                           hint=_("DNS server address is not required as the built-in "
                                  "DNS resolver is capable of working without it."))\
214 215
            .requires("proto", WAN_STATIC)

216 217 218 219 220
        # static ipv6
        wan_main.add_field(Checkbox, name="static_ipv6", label=_("Use IPv6"),
                           nuci_path="uci.network.wan.ip6addr",
                           nuci_preproc=lambda val: bool(val.value))\
            .requires("proto", WAN_STATIC)
221
        wan_main.add_field(Textbox, name="ip6addr", label=_("IPv6 address"),
222
                           nuci_path="uci.network.wan.ip6addr",
223
                           validators=validators.IPv6Prefix(),
Bedřich Košata's avatar
Bedřich Košata committed
224
                           hint=_("IPv6 address and prefix length for WAN interface, e.g. 2001:db8:be13:37da::1/64"),
225
                           required=True)\
226
            .requires("proto", WAN_STATIC)\
227
            .requires("static_ipv6", True)
228
        wan_main.add_field(Textbox, name="ip6gw", label=_("IPv6 gateway"),
229
                           validators=validators.IPv6(),
230
                           nuci_path="uci.network.wan.ip6gw")\
231
            .requires("proto", WAN_STATIC)\
232
            .requires("static_ipv6", True)
233
        wan_main.add_field(Textbox, name="ip6prefix", label=_("IPv6 prefix"),
234
                           validators=validators.IPv6Prefix(),
235 236
                           nuci_path="uci.network.wan.ip6prefix",
                           hint=_("Address range for local network, e.g. 2001:db8:be13:37da::/64"))\
237
            .requires("proto", WAN_STATIC)\
238
            .requires("static_ipv6", True)
239

240 241 242 243 244 245 246 247 248 249
        wan_main.add_field(Textbox, name="username", label=_("PAP/CHAP username"),
                           nuci_path="uci.network.wan.username")\
            .requires("proto", WAN_PPPOE)
        wan_main.add_field(Textbox, name="password", label=_("PAP/CHAP password"),
                           nuci_path="uci.network.wan.password")\
            .requires("proto", WAN_PPPOE)
        wan_main.add_field(Checkbox, name="ppp_ipv6", label=_("Enable IPv6"),
                           nuci_path="uci.network.wan.ipv6",
                           nuci_preproc=lambda val: bool(int(val.value)))\
            .requires("proto", WAN_PPPOE)
250

251
        wan_main.add_field(Checkbox, name="custom_mac", label=_("Custom MAC address"),
252
                           nuci_path="uci.network.wan.macaddr",
253 254 255
                           nuci_preproc=lambda val: bool(val.value),
                           hint=_("Useful in cases, when a specific MAC address is required by "
                                  "your internet service provider."))
256

257 258
        wan_main.add_field(Textbox, name="macaddr", label=_("MAC address"),
                           nuci_path="uci.network.wan.macaddr",
259
                           validators=validators.MacAddress(),
260
                           hint=_("Separator is a colon, for example 00:11:22:33:44:55"),
261
                           required=True)\
262
            .requires("custom_mac", True)
263

264 265 266 267 268 269 270
        def wan_form_cb(data):
            uci = Uci()
            config = Config("network")
            uci.add(config)

            wan = Section("wan", "interface")
            config.add(wan)
271

272
            wan.add(Option("proto", data['proto']))
273 274 275 276
            if data['custom_mac'] is True:
                wan.add(Option("macaddr", data['macaddr']))
            else:
                wan.add_removal(Option("macaddr", None))
277

278 279
            ucollect_ifname = "eth2"

280 281 282 283
            if data['proto'] == WAN_PPPOE:
                wan.add(Option("username", data['username']))
                wan.add(Option("password", data['password']))
                wan.add(Option("ipv6", data['ppp_ipv6']))
284
                ucollect_ifname = "pppoe-wan"
285 286 287 288
            elif data['proto'] == WAN_STATIC:
                wan.add(Option("ipaddr", data['ipaddr']))
                wan.add(Option("netmask", data['netmask']))
                wan.add(Option("gateway", data['gateway']))
289 290
                dns_string = " ".join([data.get("dns1", ""), data.get("dns2", "")]).strip()
                wan.add(Option("dns", dns_string))
291 292 293 294 295 296 297 298
                if data.get("static_ipv6") is True:
                    wan.add(Option("ip6addr", data['ip6addr']))
                    wan.add(Option("ip6gw", data['ip6gw']))
                    wan.add(Option("ip6prefix", data['ip6prefix']))
                else:
                    wan.add_removal(Option("ip6addr", None))
                    wan.add_removal(Option("ip6gw", None))
                    wan.add_removal(Option("ip6prefix", None))
299

300 301 302 303 304 305 306 307
            # set interface for ucollect to listen on
            ucollect = Config("ucollect")
            # FIXME: replacing whole config is... an ugly work-around
            uci.add_replace(ucollect)
            interface = Section(None, "interface", True)
            ucollect.add(interface)
            interface.add(Option("ifname", ucollect_ifname))

308 309 310 311 312 313 314
            return "edit_config", uci

        wan_form.add_callback(wan_form_cb)

        return wan_form


315 316 317 318 319 320 321 322 323 324 325 326
class DNSHandler(BaseConfigHandler):
    """
    DNS-related settings, currently for enabling/disabling upstream forwarding
    """

    # {{ _("DNS") }} - for translation
    userfriendly_title = "DNS"

    def get_form(self):
        dns_form = fapi.ForisForm("dns", self.data)
        dns_main = dns_form.add_section(name="set_dns",
                                        title=_(self.userfriendly_title))
327
        dns_main.add_field(Checkbox, name="forward_upstream", label=_("Use forwarding"),
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
                           nuci_path="uci.unbound.server.forward_upstream",
                           nuci_preproc=lambda val: bool(int(val.value)), default=True)

        def dns_form_cb(data):
            uci = Uci()
            unbound = Config("unbound")
            uci.add(unbound)
            server = Section("server", "unbound")
            unbound.add(server)
            server.add(Option("forward_upstream", data['forward_upstream']))
            return "edit_config", uci

        dns_form.add_callback(dns_form_cb)
        return dns_form


344
class TimeHandler(BaseConfigHandler):
Pavol Otto's avatar
Pavol Otto committed
345
    # {{ _("Time") }} - for translation
346
    userfriendly_title = "Time"
347

348 349 350
    def _action_ntp_update(self):
        return client.ntp_update()

351
    def call_ajax_action(self, action):
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
        """Call AJAX action.

        :param action:
        :return: dict of picklable AJAX results
        """
        if action == "ntp_update":
            ntp_ok = self._action_ntp_update()
            return dict(success=ntp_ok)
        elif action == "time_form":
            if hasattr(self, 'render') and callable(self.render):
                # only if the subclass implements render
                return dict(success=True, form=self.render(is_xhr=True))
        raise ValueError("Unknown Wizard action.")

    def get_form(self):
        time_form = fapi.ForisForm("time", self.data, filter=filters.time)
368
        time_main = time_form.add_section(name="set_time", title=_(self.userfriendly_title),
Pavol Otto's avatar
Pavol Otto committed
369
                                          description=_(
Bedřich Košata's avatar
Bedřich Košata committed
370 371
            "We could not synchronize the time with a timeserver, probably due to a loss of connection. "
            "It is necessary for the router to have correct time in order to function properly. Please, "
Pavol Otto's avatar
Pavol Otto committed
372 373
            "synchronize it with your computer's time, or set it manually."
            ))
374

375
        time_main.add_field(Textbox, name="time", label=_("Time"), nuci_path="time",
376 377 378 379 380 381 382 383 384 385 386 387
                            nuci_preproc=lambda v: v.local)

        def time_form_cb(data):
            client.set_time(data['time'])
            return "none", None

        time_form.add_callback(time_form_cb)

        return time_form


class LanHandler(BaseConfigHandler):
Pavol Otto's avatar
Pavol Otto committed
388
    # {{ _("LAN") }} - for translation
389 390
    userfriendly_title = "LAN"
    
391 392
    def get_form(self):
        lan_form = fapi.ForisForm("lan", self.data, filter=filters.uci)
393
        lan_main = lan_form.add_section(name="set_lan", title=_(self.userfriendly_title),
Bedřich Košata's avatar
Bedřich Košata committed
394 395 396 397 398 399 400
                                        description=_("This section contains settings for the local network (LAN). "
            "The provided defaults are suitable for most networks. "
            "<br><strong>Note:</strong> If you change the router IP address, all computers in LAN, probably including the one you "
            "are using now, will need to obtain a <strong>new IP address</strong> which does <strong>not</strong> happen <strong>immediately</strong>. "
            "It is recommended to disconnect and reconnect all LAN cables after submitting your changes "
            "to force the update. The next page will not load until you obtain a new IP from DHCP "
            "(if DHCP enabled) and you might need to <strong>refresh the page</strong> in your browser."))
401

402 403
        lan_main.add_field(Textbox, name="dhcp_subnet", label=_("Router IP address"),
                           nuci_path="uci.network.lan.ipaddr",
404
                           validators=validators.IPv4(),
405 406
                           hint=_("Router's IP address in inner network. Also defines the range of "
                                  "assigned IP addresses."))
407
        lan_main.add_field(Checkbox, name="dhcp_enabled", label=_("Enable DHCP"),
408
                           nuci_path="uci.dhcp.lan.ignore",
409 410 411
                           nuci_preproc=lambda val: not bool(int(val.value)), default=True,
                           hint=_("Enable this option to automatically assign IP addresses to "
                                  "the devices connected to the router."))
Pavol Otto's avatar
Pavol Otto committed
412
        lan_main.add_field(Textbox, name="dhcp_min", label=_("DHCP start"),
413 414
                           nuci_path="uci.dhcp.lan.start")\
            .requires("dhcp_enabled", True)
Pavol Otto's avatar
Pavol Otto committed
415
        lan_main.add_field(Textbox, name="dhcp_max", label=_("DHCP max leases"),
416 417 418 419 420 421 422 423 424 425
                           nuci_path="uci.dhcp.lan.limit")\
            .requires("dhcp_enabled", True)

        def lan_form_cb(data):
            uci = Uci()
            config = Config("dhcp")
            uci.add(config)

            dhcp = Section("lan", "dhcp")
            config.add(dhcp)
426 427 428 429 430 431 432 433 434 435 436
            # FIXME: this would overwrite any unrelated DHCP options the user might have set.
            # Maybe we should get the current values, scan them and remove selectively the ones
            # with 6 in front of them? Or have some support for higher level of stuff in nuci.
            options = List("dhcp_option")
            options.add(Value(0, "6," + data['dhcp_subnet']))
            dhcp.add_replace(options)
            network = Config("network")
            uci.add(network)
            interface = Section("lan", "interface")
            network.add(interface)
            interface.add(Option("ipaddr", data['dhcp_subnet']))
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451
            if data['dhcp_enabled']:
                dhcp.add(Option("ignore", "0"))
                dhcp.add(Option("start", data['dhcp_min']))
                dhcp.add(Option("limit", data['dhcp_max']))
            else:
                dhcp.add(Option("ignore", "1"))

            return "edit_config", uci

        lan_form.add_callback(lan_form_cb)

        return lan_form


class WifiHandler(BaseConfigHandler):
452 453
    # {{ _("Wi-Fi") }} - for translation
    userfriendly_title = "Wi-Fi"
454
    
455
    def get_form(self):
456 457 458 459
        stats = client.get(filter=filters.stats).find_child("stats")
        if len(stats.data['wireless-cards']) < 1:
            return None

460
        wifi_form = fapi.ForisForm("wifi", self.data, filter=filters.uci)
461
        wifi_main = wifi_form.add_section(name="set_wifi", title=_(self.userfriendly_title),
Pavol Otto's avatar
Pavol Otto committed
462
                                          description=_(
463 464 465
            "If you want to use your router as a Wi-Fi access point, enable Wi-Fi here and "
            "fill in an SSID (the name of the access point) and a corresponding password. "
            "To set up a mobile device, you can scan the QR code available next to the form."))
466
        wifi_main.add_field(Hidden, name="iface_section", nuci_path="uci.wireless.@wifi-iface[0]",
467
                            nuci_preproc=lambda val: val.name)
468
        wifi_main.add_field(Checkbox, name="wifi_enabled", label=_("Enable Wi-Fi"), default=True,
469
                            nuci_path="uci.wireless.@wifi-iface[0].disabled",
470
                            nuci_preproc=lambda val: not bool(int(val.value)))
Pavol Otto's avatar
Pavol Otto committed
471
        wifi_main.add_field(Textbox, name="ssid", label=_("SSID"),
472
                            nuci_path="uci.wireless.@wifi-iface[0].ssid",
473
                            required=True, validators=validators.ByteLenRange(1, 32))\
474
            .requires("wifi_enabled", True)
475
        wifi_main.add_field(Checkbox, name="ssid_hidden", label=_("Hide SSID"), default=False,
476
                            nuci_path="uci.wireless.@wifi-iface[0].hidden",
477 478
                            hint=_("If set, network is not visible when scanning for available networks."))\
            .requires("wifi_enabled", True)
479

480 481 482 483 484 485 486 487 488 489 490 491
        channels_2g4 = [("auto", _("auto"))]
        channels_5g = [("auto", _("auto"))]
        for channel in stats.data['wireless-cards'][0]['channels']:
            if channel['disabled']:
                continue
            pretty_channel = "%s (%s MHz)" % (channel['number'], channel['frequency'])
            if channel['frequency'] < 2500:
                channels_2g4.append((str(channel['number']), pretty_channel))
            else:
                channels_5g.append((str(channel['number']), pretty_channel))

        is_dual_band = False
492
        # hwmode choice for dual band devices
493 494
        if len(channels_2g4) > 1 and len(channels_5g) > 1:
            is_dual_band = True
495 496 497
            wifi_main.add_field(Radio, name="hwmode", label=_("Wi-Fi mode"), default="11ng",
                                args=(("11ng", "2.4 GHz (g+n)"), ("11ag", "5 GHz (a+n)")),
                                nuci_path="uci.wireless.radio0.hwmode",
498 499 500 501
                                hint=_("2.4 GHz is more widely supported by clients, but tends to have "
                                       "more interference. 5 GHz is a newer standard and might not be "
                                       "supported by all your devices. It usually has less interference, "
                                       "but does not carry so well indoors."))\
502 503
                .requires("wifi_enabled", True)
        # 2.4 GHz channels
504 505 506 507 508
        if len(channels_2g4) > 1:
            field_2g4 = wifi_main.add_field(Dropdown, name="channel2g4", label=_("Network channel"),
                                            default=channels_2g4[0][0], args=channels_2g4,
                                            nuci_path="uci.wireless.radio0.channel")
            if is_dual_band:
509 510
                field_2g4.requires("hwmode", "11ng")
        # 5 GHz channels
511 512 513 514 515
        if len(channels_5g) > 1:
            field_5g = wifi_main.add_field(Dropdown, name="channel5g", label=_("Network channel"),
                                           default=channels_5g[0][0], args=channels_5g,
                                           nuci_path="uci.wireless.radio0.channel")
            if is_dual_band:
516
                field_5g.requires("hwmode", "11ag")
517
        wifi_main.add_field(Password, name="key", label=_("Network password"),
518
                            nuci_path="uci.wireless.@wifi-iface[0].key",
519
                            required=True,
520
                            validators=validators.ByteLenRange(8, 63),
521
                            hint=_("WPA2 pre-shared key, that is required to connect to the network. "
522
                                   "Minimum length is 8 characters."))\
523 524 525 526 527 528 529 530 531
            .requires("wifi_enabled", True)

        def wifi_form_cb(data):
            uci = Uci()
            wireless = Config("wireless")
            uci.add(wireless)

            iface = Section(data['iface_section'], "wifi-iface")
            wireless.add(iface)
532
            device = Section("radio0", "wifi-device")
533 534 535 536 537 538 539
            wireless.add(device)
            # we must toggle both wifi-iface and device
            iface.add(Option("disabled", not data['wifi_enabled']))
            device.add(Option("disabled", not data['wifi_enabled']))
            if data['wifi_enabled']:
                iface.add(Option("ssid", data['ssid']))
                iface.add(Option("hidden", data['ssid_hidden']))
540
                iface.add(Option("encryption", "psk2+tkip+aes"))
541
                iface.add(Option("key", data['key']))
542
                if data.get('channel2g4'):
543
                    channel = data['channel2g4']
544
                elif data.get('channel5g'):
545
                    channel = data['channel5g']
546 547 548
                else:
                    logger.critical("Saving form without Wi-Fi channel: %s" % data)
                    channel = "auto"
549 550 551 552
                hwmode = data.get('hwmode')
                if hwmode:
                    # change hwmode only if we had the choice
                    device.add(Option("hwmode", hwmode))
553
                # channel is in wifi-device section
554
                device.add(Option("channel", channel))
555 556 557 558 559 560 561
            else:
                pass  # wifi disabled

            return "edit_config", uci

        wifi_form.add_callback(wifi_form_cb)

562 563 564 565 566 567 568
        return wifi_form


class SystemPasswordHandler(BaseConfigHandler):
    """
    Setting the password of a system user (currently only root's pw).
    """
569
    
Pavol Otto's avatar
Pavol Otto committed
570
    # {{ _("Advanced administration") }} - for translation
571 572
    userfriendly_title = "Advanced administration"
    
573 574
    def get_form(self):
        system_pw_form = fapi.ForisForm("system_password", self.data)
575
        system_pw_main = system_pw_form.add_section(name="set_password",
576
                                                    title=_(self.userfriendly_title),
577
                                                    description=_(
Pavol Otto's avatar
Pavol Otto committed
578
            "In order to access the advanced configuration possibilities which are not present "
579 580 581
            "here, you must set the root user's password. The advanced configuration options can "
            "be managed either through the <a href=\"http://%(ip)s:%(port)d/\">LuCI web interface"
            "</a> or over SSH.") % {'ip': bottle.request.get_header('host'), 'port': 8080})
582
        system_pw_main.add_field(Password, name="password", label=_("Password"), required=True,
583
                                 validators=validators.LenRange(6, 128))
584
        system_pw_main.add_field(Password, name="password_validation", label=_("Password (repeat)"),
585 586
                                 required=True, validators=validators.EqualTo("password", "password_validation",
                                                                              _("Passwords are not equal.")))
587 588 589 590 591 592

        def system_pw_form_cb(data):
            client.set_password("root", data["password"])
            return "none", None

        system_pw_form.add_callback(system_pw_form_cb)
593
        return system_pw_form
594 595 596 597 598 599 600 601 602 603 604 605 606


class MaintenanceHandler(BaseConfigHandler):
    # {{ _("Maintenance") }} - for translation
    userfriendly_title = "Maintenance"

    def get_form(self):
        maintenance_form = fapi.ForisForm("maintenance", self.data)
        maintenance_main = maintenance_form.add_section(name="restore_backup",
                                                        title=_(self.userfriendly_title))
        maintenance_main.add_field(File, name="backup_file", label=_("Backup file"), required=True)

        def maintenance_form_cb(data):
607 608
            result = client.load_config_backup(data['backup_file'].file)
            return "save_result", {'new_ip': result}
609 610 611

        maintenance_form.add_callback(maintenance_form_cb)
        return maintenance_form