module ("xiaoqiang.module.XQDMZModule", package.seeall)

local XQFunction = require("xiaoqiang.common.XQFunction")
local XQConfigs = require("xiaoqiang.common.XQConfigs")

DMZ_NVRAM = {
    ["vlan1hwname"] = "et0",
    ["vlan2hwname"] = "et0",
    ["vlan3hwname"] = "et0",
    ["vlan1ports"] = "2 5*",
    ["vlan2ports"] = "4 5",
    ["vlan3ports"] = "0 5*"
}

DMZ_NETWORK_CONFIGS = {
    -- switch_vlan
    ["eth0_1"] = {
        ["device"] = "eth0",
        ["vlan"] = 1,
        ["ports"] = "2 5*"
    },
    ["eth0_3"] = {
        ["device"] = "eth0",
        ["vlan"] = 3,
        ["ports"] = "0 5*"
    },
    -- interface
    ["dmz"] = {
        ["ifname"] = "eth0.3",
        ["proto"] = "static",
        ["ipaddr"] = "",
        ["netmask"] = "255.255.255.0"
    }
}

DMZ_FIREWALL_CONFIGS = {
    -- zone
    ["zonedmz"] = {
        ["name"] = "dmz",
        ["network"] = "dmz",
        ["input"] = "REJECT",
        ["output"] = "ACCEPT",
        ["forward"] = "REJECT"
    },
    -- rule
    ["dmzdns"] = {
        ["src"] = "dmz",
        ["proto"] = "tcpudp",
        ["dest_port"] = 53,
        ["target"] = "ACCEPT"
    },
    ["dmzdhcp"] = {
        ["src"] = "dmz",
        ["proto"] = "udp",
        ["dest_port"] = 67,
        ["target"] = "ACCEPT"
    },
    -- forwarding
    ["dmztowan"] = {
        ["src"] = "dmz",
        ["dest"] = "wan"
    },
    ["lantodmz"] = {
        ["src"] = "lan",
        ["dest"] = "dmz"
    },
    -- redirect for tcp
    ["dmz"] = {
        ["src"] = "wan",
        ["proto"] = "tcp",
        ["target"] = "DNAT",
        ["dest"] = "lan",
        ["dest_ip"] = ""
     },
    -- redirect for udp except DHCP
    ["dmzudp"] = {
        ["src"] = "wan",
        ["proto"] = "udp",
        ["target"] = "DNAT",
        ["dest"] = "lan",
	["src_port"] = "!67", -- allow dhcp pass
        ["dest_ip"] = ""
    }
}

DMZ_DHCP_CONFIGS = {
    -- dhcp
    ["dmz"] = {
        ["interface"] = "dmz",
        ["start"] = 100,
        ["limit"] = 150,
        ["leasetime"] = "12h",
        ["force"] = 1
    }
}

--
-- Event
--
function hookLanIPChangeEvent(ip)
    if XQFunction.isStrNil(ip) then
        return
    end
    local uci = require("luci.model.uci").cursor()
    local lan = ip:gsub(".%d+$","")
    local destip = uci:get("firewall", "dmz","dest_ip")
    if not XQFunction.isStrNil(destip) then
        destip = lan.."."..destip:match(".(%d+)$")
        uci:set("firewall", "dmz", "dest_ip", destip)
        uci:set("firewall", "dmzudp", "dest_ip", destip)
        uci:commit("firewall")
    end
end

function unsetDMZ(mode)
    local uci = require("luci.model.uci").cursor()
    if mode == 1 then
        uci:delete("firewall", "zonedmz")
        uci:delete("firewall", "dmzdns")
        uci:delete("firewall", "dmzdhcp")
        uci:delete("firewall", "dmztowan")
        uci:delete("firewall", "lantodmz")
        uci:delete("firewall", "dmz")
        uci:commit("firewall")
        uci:delete("dhcp", "dmz")
        uci:commit("dhcp")
        uci:delete("network", "dmz")
        uci:delete("network", "eth0_3")
        uci:commit("network")
        XQFunction.nvramSet("vlan3hwname", nil)
        XQFunction.nvramSet("vlan3ports", nil)
        XQFunction.nvramSet("vlan2ports", "4 5")
        XQFunction.nvramSet("vlan1ports", "0 2 5*")
        XQFunction.setNetMode(nil)
        XQFunction.nvramCommit()
    elseif mode == 0 then
        uci:delete("firewall", "dmz")
	uci:delete("firewall", "dmzudp")
        uci:commit("firewall")
        XQFunction.setNetMode(nil)
        XQFunction.nvramCommit()
    end
end

function _setSimpleDMZ(destip, destmac)
    local uci = require("luci.model.uci").cursor()
    local config = DMZ_FIREWALL_CONFIGS["dmz"]
    local config_udp = DMZ_FIREWALL_CONFIGS["dmzudp"]
    local lanip = uci:get("network", "lan", "ipaddr")
    local lanpre = lanip:gsub(".%d+$", "")
    local destpre = destip:gsub(".%d+$", "")
    if lanpre ~= destpre or lanip == destip then
        return 2
    end
    config.dest_ip = destip
    config_udp.dest_ip = destip
    uci:section("firewall", "redirect", "dmz", config)
    uci:section("firewall", "redirect", "dmzudp", config_udp)
    uci:commit("firewall")
    if not XQFunction.isStrNil(destmac) then
        local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil")
        local bind = XQLanWanUtil.addBind(destmac, destip)
        if bind == 0 then
            XQLanWanUtil.saveBindInfo()
        else
            return bind
        end
    end
    return 0
end

function _setComplexDMZ(destip, destmac)
    local uci = require("luci.model.uci").cursor()
    local LuciUtil = require("luci.util")
    local lanip = uci:get("network", "lan", "ipaddr")
    local ipv = LuciUtil.split(destip, ".")
    ipv[4] = 1
    ipv = table.concat(ipv, ".")
    local lanpre = lanip:gsub(".%d+$", "")
    local destpre = destip:gsub(".%d+$", "")
    if lanpre == destpre or lanip == destip then
        return 2
    end
    -- nvram
    for key, value in pairs(DMZ_NVRAM) do
        XQFunction.nvramSet(key, value)
    end
    XQFunction.nvramCommit()
    -- network config
    local eth0_1 = DMZ_NETWORK_CONFIGS["eth0_1"]
    local eth0_3 = DMZ_NETWORK_CONFIGS["eth0_3"]
    local dmz = DMZ_NETWORK_CONFIGS["dmz"]
    dmz.ipaddr = ipv
    uci:section("network", "switch_vlan", "eth0_1", eth0_1)
    uci:section("network", "switch_vlan", "eth0_3", eth0_3)
    uci:section("network", "interface", "dmz", dmz)
    uci:commit("network")
    -- firewall config
    local zonedmz = DMZ_FIREWALL_CONFIGS["zonedmz"]
    local dmzdns = DMZ_FIREWALL_CONFIGS["dmzdns"]
    local dmzdhcp = DMZ_FIREWALL_CONFIGS["dmzdhcp"]
    local dmztowan = DMZ_FIREWALL_CONFIGS["dmztowan"]
    local lantodmz = DMZ_FIREWALL_CONFIGS["lantodmz"]
    local fdmz = DMZ_FIREWALL_CONFIGS["dmz"]
    fdmz.dest_ip = destip
    uci:section("firewall", "zone", "zonedmz", zonedmz)
    uci:section("firewall", "rule", "dmzdns", dmzdns)
    uci:section("firewall", "rule", "dmzdhcp", dmzdhcp)
    uci:section("firewall", "forwarding", "dmztowan", dmztowan)
    uci:section("firewall", "forwarding", "lantodmz", lantodmz)
    uci:section("firewall", "redirect", "dmz", fdmz)
    uci:commit("firewall")
    -- dhcp config
    local dhcp = DMZ_DHCP_CONFIGS["dmz"]
    uci:section("dhcp", "dhcp", "dmz", dhcp)
    -- ip mac bind
    if not XQFunction.isStrNil(destmac) then
        local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil")
        local bind = XQLanWanUtil.addBind(destmac, destip)
        if bind == 0 then
            XQLanWanUtil.saveBindInfo()
        else
            return bind
        end
    end
    return 0
end

function moduleOn()
    local uci = require("luci.model.uci").cursor()
    local dmzip = uci:get("firewall", "dmz", "dest_ip")
    if dmzip then
        return true
    else
        return false
    end
end

-- status
-- 0:关闭
-- 1:开启
-- 2:冲突(端口转发被开启，DMZ功能就不能开启)
function getDMZInfo()
    local XQPortForward = require("xiaoqiang.module.XQPortForward")
    local uci = require("luci.model.uci").cursor()
    local info = {}
    if XQPortForward.moduleOn() then
        info["status"] = 2
    else
        info["status"] = moduleOn() and 1 or 0
        if info.status == 1 then
            info["ip"] = uci:get("firewall", "dmz", "dest_ip") or ""
        end
    end
    info["lanip"] = uci:get("network", "lan", "ipaddr") or ""
    return info
end

-- mode 0/1 简单/复杂
-- return 0/1/2/3/4 设置成功/IP冲突/MACIP不合法/工作模式不可设置/开启了端口转发,DMZ不能启用
function setDMZ(mode, destip, destmac)
    if XQFunction.isStrNil(destip) then
        return 2
    end
    local XQPortForward = require("xiaoqiang.module.XQPortForward")
    if XQPortForward.moduleOn() then
        return 4
    end
    if mode == 0 then
        XQFunction.setNetMode("dmzsimple")
        return _setSimpleDMZ(destip, destmac)
    elseif mode == 1 then
        XQFunction.setNetMode("dmzmode")
        return _setComplexDMZ(destip, destmac)
    else
        return 3
    end
end

function dmzReload(mode)
    if mode == 0 then
        -- reload services
        os.execute("/etc/init.d/firewall restart")
        XQFunction.forkRestartDnsmasq()
    elseif mode == 1 then
        -- fork reboot
        XQFunction.forkReboot()
    end
end