--[[
Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]

local M = require('acf2.model')
local object = require('acf2.object')


local Direction = object.class(M.String)
function Direction:init(params)
   if not params then params = {} end
   params.choice = {'in', 'out'}
   object.super(self, Direction):init(params)
end


-- TODO reference types?

local IPSet = M.new()
-- TODO choices
IPSet.type = M.String{required=true}
IPSet.family = M.String{required=true, choice={'inet', 'inet6'}}
-- TODO only for bitmaps
IPSet.range = M.Range{type=M.net.IPv4Address}

local Service = M.new()
Service.proto = M.String{required=true, ui_name='Protocol'}
Service.port = M.Set{type=M.Range{type=M.net.Port}}
Service['icmp-type'] = M.String{ui_name='ICMP type'}
Service['ct-helper'] = M.String{ui_name='Connection tracking helper'}

-- TODO fw zone

local Zone = M.new()
Zone.iface = M.Set{type=M.String, ui_name='Interfaces'}
Zone.addr = M.Set{type=M.String, ui_name='Addresses'}
Zone['route-back'] = M.Boolean{default=false}

local LogClass = M.new()
LogClass.mode = M.String{
   required=true, default='log', choice={'log', 'nflog', 'ulog'}
}
LogClass.every = M.Integer{ui_name='Sampling frequency'}
LogClass.limit = M.Integer
LogClass.prefix = M.String
LogClass.probability = M.Number
LogClass.group = M.Integer
LogClass.range = M.Integer
LogClass.threshold = M.Integer

local IPSetReference = M.new()
IPSetReference.name = M.Reference{scope='/awall/ipset', required=true}
IPSetReference.args = M.List{
   type=Direction, required=true, ui_name='Arguments'
}

local Rule = M.new()
Rule['in'] = M.Set{
   type=M.Reference{scope='/awall/zone'}, ui_name='Ingress zones'
}
Rule.out = M.Set{
   type=M.Reference{scope='/awall/zone'}, ui_name='Egress zones'
}
Rule.src = M.Set{type=M.String, ui_name='Sources'}
Rule.dest = M.Set{type=M.String, ui_name='Destinations'}
Rule.ipset = M.Model{model=IPSetReference, ui_name='IP set'}
Rule.ipsec = Direction{ui_name='Require IPsec'}
Rule.service = M.Set{type=M.Reference{scope='/awall/service'}}
Rule.action = M.String{choice={'accept'}}


local PacketLogRule = M.new(Rule)
PacketLogRule.log = M.Reference{scope='../../log', ui_name='Log class'}

-- TODO no service field
local PolicyRule = M.new(PacketLogRule)
PolicyRule.action = M.String{
   required=true, choice={'accept', 'drop', 'reject', 'tarpit'}
}

local Limit = M.new()
Limit.count = M.Integer
Limit.interval = M.Integer
Limit.log = M.Reference{scope='../../../log'}

local FilterRule = M.new(PolicyRule)
FilterRule['conn-limit'] = M.Model{model=Limit, ui_name='Connection limit'}
FilterRule['flow-limit'] = M.Model{model=Limit, ui_name='Flow limit'}
FilterRule.dnat = M.net.IPv4Address{ui_name='DNAT target'}
FilterRule['no-track'] = M.Boolean{default=false, ui_name='CT bypass'}
FilterRule.related = M.List{type=Rule, ui_name='Related packet rules'}

local DivertRule = M.new(Rule)
DivertRule['to-port'] = M.Range{type=M.net.Port, ui_name='Target port'}

local NATRule = M.new(DivertRule)
NATRule['to-addr'] = M.Range{type=M.net.IPv4Address, ui_name='Target address'}

local MarkRule = M.new(Rule)
MarkRule.mark = M.Integer{required=true}

local ClampMSSRule = M.new(Rule)
ClampMSSRule.mss = M.Integer{ui_name='MSS'}


local AWall = M.new()
-- TODO differentiate lists?
AWall.service = M.Collection{type=M.List{type=Service}}
AWall.zone = M.Collection{type=Zone}
AWall.log = M.Collection{
   type=LogClass, ui_name='Log classes', ui_member='Log class'
}
AWall.policy = M.List{type=PolicyRule, ui_name='Policies', ui_member='Policy'}
AWall['packet-log'] = M.List{
   type=PacketLogRule, ui_name='Logging', ui_member='Logging rule'
}
AWall.filter = M.List{type=FilterRule}
AWall.dnat = M.List{type=NATRule, ui_name='DNAT', ui_member='DNAT rule'}
AWall.snat = M.List{type=NATRule, ui_name='SNAT', ui_member='SNAT rule'}
AWall.mark = M.List{
   type=MarkRule, ui_name='Packet marking', ui_member='Packet marking rule'
}
AWall['route-track'] = M.List{
   type=MarkRule, ui_name='Route tracking', ui_member='Route tracking rule'
}
AWall.tproxy = M.List{
   type=DivertRule,
   ui_name='Transparent proxy',
   ui_member='Transparent proxy rule'
}
AWall['clamp-mss'] = M.List{
   type=ClampMSSRule, ui_name='MSS clamping', ui_member='MSS clamping rule'
}
AWall['no-track'] = M.List{
   type=Rule, ui_name='CT bypass', ui_member='Connection tracking bypass rule'
}
AWall.ipset = M.Collection{type=IPSet, ui_name='IP sets', ui_member='IP set'}

M.register(
   'awall',
   AWall,
   {
      addr='/json'..require('posix').getcwd()..'/config/awall.json',
      ui_name='Alpine Wall'
   }
)

M.permission.defaults('/awall')
