#!/usr/bin/env lua
local posix = require "Posix"
local json = require "json"
local ubus = require "ubus"
local now_time
local next_time
local status
local conn
local cursor
local cfg = {
        ['stat_file'] = "/tmp/vpn.stat.msg",
        ['stat_file_last'] = "/tmp/vpn.stat.msg.last",
        ['time_step'] = 1,
        ['pptp_mode'] = {
        "refuse-eap\nrefuse-pap\nrefuse-chap\nrefuse-mschap\nmppe required,no40,no56,stateless",
        "",
        "refuse-pap\nrefuse-chap\nrefuse-mschap\nrefuse-mschap-v2",
        "refuse-eap\nrefuse-chap\nrefuse-mschap\nrefuse-mschap-v2",
        "refuse-eap\nrefuse-pap\nrefuse-mschap\nrefuse-mschap-v2",
        "refuse-eap\nrefuse-pap\nrefuse-chap\nrefuse-mschap-v2\nmppe required,no40,no56,stateless"
        },
        ['options_pptp_filename'] = "/etc/ppp/options.pptp",
        ['debug'] = 0,
        ['daemon'] = 1,
        ['max_redail'] = 2
    }
local g = {}


function dlog(fmt, ...)
    if (cfg.debug == 1) then
        posix.syslog(posix.LOG_DEBUG, string.format(fmt, unpack(arg)))
    elseif (cfg.debug == 2) then
        print(string.format(fmt, unpack(arg)))
    end
end

function ilog(fmt, ...)
    if (cfg.debug == 2) then
        print(string.format(fmt, unpack(arg)))
    else
        posix.syslog(posix.LOG_INFO, string.format(fmt, unpack(arg)))
    end
end

function elog(fmt, ...)
    if (cfg.debug == 2) then
        print(string.format(fmt, unpack(arg)))
    else
        posix.syslog(posix.LOG_ERR, string.format(fmt, unpack(arg)))
    end
end

function check_vpn()
    g.status = conn:call("network.interface", "status", {['interface']="vpn"})
    if not g.status.autostart then
        ilog("autostart=false exit")
        os.exit(1)
    end
    if g.action == "up" and g.status.up then
        ilog("action=up up=true exit")
        os.exit(1)
    end
end

function get_last_msg()
    local code = 0
    local msg = ''
    local f = io.open(cfg.stat_file_last)
    if f == nil then
        return nil
    end
    local line = f:read("*line")
    while line do
        _, _, code, msg = string.find(line, "^(%d+) (.*)$")
        line = f:read("*line")
    end
    f:close()
    if code then
        dlog("get_last_msg code[%d] msg[%s]", tonumber(code), msg)
        return {code = tonumber(code) or nil, msg = msg}
    else
        return nil
    end
end

function init()
    g.proc = arg[0]
    g.action = arg[1]

    if not g.action then
        print(string.format("usage: %s <up|down|status>", g.proc))
        os.exit(1)
    end

    if (g.action == "up"  or g.action == "down") and cfg.daemon == 1 then
        posix.daemonize()
    end

    posix.openlog(g.proc, "cp", posix.LOG_LOCAL7)
    conn = ubus.connect()
    cursor = require("luci.model.uci").cursor()
    g.status = conn:call("network.interface", "status", {['interface']="vpn"})

    if g.status == nil then
        conn:call("network", "reload", {})
        dlog("ubus call network reload")
        posix.sleep(1)
        g.status = conn:call("network.interface", "status", {['interface']="vpn"})
    end

    if g.status == nil then
        elog("network.interface.vpn does not exist")
        os.exit(1)
    end

    g.status.proto = cursor:get("network","vpn","proto")
    if g.status.proto ~= "pptp" and g.status.proto ~= "l2tp" then
        elog(string.format("error vpn proto [%s], pptp|l2tp", g.status.proto))
        os.exit(1)
    end
    g.status.auth = cursor:get("network","vpn","auth")
    g.status.auto = cursor:get("network","vpn","auto")
    g.status.server = cursor:get("network","vpn","server")
    g.pptp_index = 1
    g.pptp_redail = 0
    g.l2tp_redail = 0
    g.max_redail = cfg.max_redail
    if g.action == "up" and g.status.proto == "pptp" then
        if g.status.auth == nil or g.status.auth == "auto" then
            g.pptp_mode = 0
            g.max_redail = cfg.max_redail * 6
        elseif g.status.auth == "mschap-v2" then
            g.pptp_mode = 1
        elseif g.status.auth == "all" then
            g.pptp_mode = 2
        elseif g.status.auth == "eap" then
            g.pptp_mode = 3
        elseif g.status.auth == "pap" then
            g.pptp_mode = 4
        elseif g.status.auth == "chap" then
            g.pptp_mode = 5
        elseif g.status.auth == "mschap" then
            g.pptp_mode = 6
        else
            g.pptp_mode = 0
            g.max_redail = cfg.max_redail * 6
        end
    end
end


function check_redial()
    while posix.stat(cfg.stat_file) == nil do
        check_vpn()
        dlog("wait %s", cfg.stat_file)
        posix.sleep(cfg.time_step)
    end
    dlog("rename %s -> %s", cfg.stat_file, cfg.stat_file_last)
    os.rename(cfg.stat_file, cfg.stat_file_last)
    local m = get_last_msg()
    if m ~= nil and m.code == 0 then
        ilog("succeeded")
        return false
    end

    check_vpn()
    if g.status.proto == "pptp" then
        g.pptp_redail = g.pptp_redail + 1
        if g.pptp_redail <= g.max_redail then
            dlog("vpn pptp redial[%d]", g.pptp_redail)
            return true
        else
            os.execute("ifdown vpn")
            elog("vpn pptp max redial[%d] ifdown vpn and exit", g.pptp_redail)
            return false
        end
    elseif g.status.proto == "l2tp" then
        g.l2tp_redail = g.l2tp_redail + 1
        if g.l2tp_redail <= g.max_redail  then
            dlog("vpn l2tp redial[%d]", g.l2tp_redail)
            return true
        else
            os.execute("ifdown vpn")
            elog("vpn l2tp max redial[%d] ifdown vpn and exit", g.l2tp_redail)
            return false
        end
    else
        elog("error vpn proto")
        os.execute("ifdown vpn")
        return false
    end
end

function update_options_pptp(index)
    if cfg.pptp_mode[index] then
        local f = assert(io.open(cfg.options_pptp_filename,"w"))
        f:write(string.format("noipdefault\nnoauth\nnobsdcomp\nnodeflate\n"..
            "idle 0\n%s\nmaxfail 0\n",
            cfg.pptp_mode[index]))
        f:close()
    end

end

function check_hostname()

    if posix.gethostbyname(g.status.server) == nil then

        local file,err = io.open( cfg.stat_file_last, "wb" )
        if err then
            elog(string.format("open %s error"), cfg.stat_file_last)
            return -1
        end
        elog(string.format("701 Host %s not found\n", g.status.server))
        file:write(string.format("701 Host %s not found\n", g.status.server))
        file:close()
        return -1
    end
    return 0
end

---------------------------------------------------------------------

local status, err = pcall(
    function ()

        init()

        if g.action == "up" then

            if g.status.autostart then
                elog("already start, %s down first", g.proc)
                os.exit(1)
            end

            if check_hostname() ~= 0 then
                os.exit(1)
            end

            if g.status.proto == "pptp" then
                update_options_pptp(g.pptp_mode == 0 and 1 or g.pptp_mode)
            end
            dlog("rm %s", cfg.stat_file)
            os.remove(cfg.stat_file)
            dlog("ifup vpn")
            os.execute("ifup vpn")


            while check_redial() do
                if g.status.proto == "pptp" and g.pptp_mode == 0 then
                    if g.pptp_redail % cfg.max_redail == 0 then
                        update_options_pptp(g.pptp_redail / cfg.max_redail + 1)
                        os.execute("ifup vpn")
                        dlog("options.pptp -> %d", g.pptp_index)
                    end
                end
            end
            os.exit(0)

        elseif g.action == "down" then
            --[[
            local wan = conn:call("network.interface", "status", {['interface']="wan"})
            for k,v in pairs(wan.route) do
                if v.target == "0.0.0.0" then
                    local command = string.format("route -n | awk '{print $1\" \"$2}' | grep '0.0.0.0 %s' | wc -l", v.nexthop):q
                    local pp   = io.popen(command)
                    local data = tonumber(pp:read("*a"))
                    pp:close()
                    if data == 0 then
                        os.execute(string.format("route add -net 0.0.0.0 netmask 0.0.0.0 gw %s", v.nexthop))
                    end
                    ilog("ifdown vpn and default gw: %s", v.nexthop)
                end
            end
            ]]
            os.execute("ifdown vpn")
            os.exit(0)

        elseif g.action == "status" then
            status = conn:call("network.interface", "status", {['interface']="vpn"})
            status.stat = get_last_msg()
            status.auto = cursor:get("network","vpn","auto")
            print(json.encode(status))
            os.exit(0)

        else
            print(string.format("usage: %s <up|down|status>", arg[0]))
            os.exit(1)

        end
    end
)
if not status then
    elog(err)
end
