policy.lua 5.73 KB
Newer Older
1
2
--[[
Policy file handling for Alpine Wall
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
3
Copyright (C) 2012-2014 Kaarle Ritvanen
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
4
See LICENSE file for license details
5
6
7
]]--


8
9
local resolve = require('awall.dependency')
local class = require('awall.class')
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
10
local raise = require('awall.uerror').raise
11

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
12
local util = require('awall.util')
13
local contains = util.contains
14
local keys = util.keys
15
local listpairs = util.listpairs
16
local map = util.map
17
18


Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
19
local json = require('cjson')
20
21
22
local lfs = require('lfs')


23
local PolicyConfig = class()
24

25
function PolicyConfig:init(data, source, policies)
26
   self.data = data
27
   self.source = source
28
   self.policies = policies
29
30
end

31
function PolicyConfig:expand()
32

33
   local function expand(value)
34
      if type(value) == 'table' then return map(value, expand) end
35

36
37
38
      local visited = {}
      local pattern = '%$(%a[%w_]*)'
      
39
40
41
      while type(value) == 'string' do
	 local si, ei, name = value:find(pattern)
	 if not si then break end
42
	 
43
	 if contains(visited, name) then
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
44
	    raise('Circular variable definition: '..name)
45
46
47
48
	 end
	 table.insert(visited, name)
	 
	 local var = self.data.variable[name]
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
49
	 if var == nil then raise('Invalid variable reference: '..name) end
50
	 
51
	 if si == 1 and ei == value:len() then value = var
52
	 elseif contains({'number', 'string'}, type(var)) then
53
	    value = value:sub(1, si - 1)..var..value:sub(ei + 1, -1)
54
	 else
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
55
	    raise('Attempted to concatenate complex variable: '..name)
56
57
	 end
      end
58

59
60
      if value == '' then return end
      return value
61
62
   end

63
   return expand(self.data)
64
65
66
end


67
local Policy = class()
68

69
function Policy:init() self.enabled = self.type == 'mandatory' end
70

71
72
73
function Policy:load()
   local file = io.open(self.path)
   if not file then raise('Unable to read policy file '..self.path) end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
74
   local data = file:read('*all')
75
   file:close()
76

77
78
79
80
   local success, res = pcall(json.decode, data)
   if success then return res end
   raise(res..' while parsing '..self.path)
end
81

82
83
84
function Policy:checkoptional()
   if self.type ~= 'optional' then raise('Not an optional policy: '..name) end
end
85

86
87
88
89
90
function Policy:enable()
   self:checkoptional()
   if self.enabled then raise('Policy already enabled: '..self.name) end   
   assert(lfs.link(self.path, self.confdir..'/'..self.fname, true))
end
91

92
93
94
95
function Policy:disable()
   self:checkoptional()
   if not self.enabled then raise('Policy already disabled: '..self.name) end
   assert(os.remove(self.confdir..'/'..self.fname))
96
97
98
end


99
100
101
102
103
local defdirs = {
   mandatory={'/etc/awall', '/usr/share/awall/mandatory'},
   optional={'/etc/awall/optional', '/usr/share/awall/optional'},
   private={'/etc/awall/private', '/usr/share/awall/private'}
}
104

105
local PolicySet = class()
106

107
108
109
function PolicySet:init(dirs)
   local confdir = (dirs.mandatory or defdirs.mandatory)[1]
   self.policies = {}
110

111
112
113
   for i, cls in ipairs{'private', 'optional', 'mandatory'} do
      for i, dir in ipairs(dirs[cls] or defdirs[cls]) do
	 for fname in lfs.dir(dir) do
114
	    local si, ei, name = fname:find('^([%w-]+)%.json$')
115
116
	    if name then
	       local pol = self.policies[name]
117

118
	       local path = dir..'/'..fname
119
	       if path:sub(1, 1) ~= '/' then
120
121
		  path = lfs.currentdir()..'/'..path
	       end
122

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
	       local attrs = lfs.attributes(path)
	       local loc = attrs.dev..':'..attrs.ino

	       if pol then
		  if pol.loc ~= loc then
		     raise('Duplicate policy name: '..name)
		  end

		  if dir == confdir and pol.type == 'optional' then
		     pol.enabled = true
		  else pol.type = cls end

	       else
		  self.policies[name] = Policy.morph{
		     name=name,
		     type=cls,
		     path=path,
		     fname=fname,
		     loc=loc,
		     confdir=confdir
		  }
	       end
	    end
	 end
      end
   end
149
150
151
end


152
function PolicySet:load()
153

154
   local imported = {['%defaults']={}}
155
156
157
   
   local function require(policy)
      if imported[policy.name] then return end
158

159
160
      local data = policy:load()
      imported[policy.name] = data
161

162
163
164
165
166
167
168
169
170
      if not data.after then
	 data.after = {}
	 for _, name in listpairs(data.import) do
	    if not contains(data.before, name) then
	       table.insert(data.after, name)
	    end
	 end
      end

171
      if not contains(data.before, '%defaults') then
172
	 data.after = util.list(data.after)
173
174
175
	 table.insert(data.after, '%defaults')
      end

176
      for i, name in listpairs(data.import) do
177
	 if name:sub(1, 1) ~= '%' then
178
179
180
181
182
	    local pol = self.policies[name]
	    if not pol then
	       raise('Invalid policy reference from '..policy.name..': '..name)
	    end
	    require(pol)
183
	 end
184
      end
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
185
   end
186

187
188
189
   for name, policy in pairs(self.policies) do
      if policy.enabled then require(policy) end
   end
190
191


192
   local order = resolve(imported)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
193
   if type(order) ~= 'table' then
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
194
      raise('Circular ordering directives: '..order)
Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
195
   end
196

197

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
198
199
   local input = {}
   local source = {}
200

Kaarle Ritvanen's avatar
Kaarle Ritvanen committed
201
   for i, name in ipairs(order) do
202
      for cls, objs in pairs(imported[name]) do
203
204
205
206
	 if not contains(
	    {'description', 'import', 'after', 'before'},
	    cls
	 ) then
207
208
209
210
	    if type(objs) ~= 'table' then
	       raise('Invalid top-level attribute: '..cls..' ('..name..')')
	    end

211
	    util.setdefault(source, cls, {})
212
213
214
215
216

	    if not input[cls] then
	       input[cls] = objs
	       for k, v in pairs(objs) do source[cls][k] = name end

217
	    else
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
	       local fk = next(input[cls])
	       map(
		  keys(objs),
		  function(k)
		     if type(k) ~= type(fk) then
			raise(
			   'Type mismatch in '..cls..' definitions ('..
			      name..', '..source[cls][fk]..')'
			)
		     end
		  end
	       )

	       if objs[1] then
		  local last = #input[cls]
		  util.extend(input[cls], objs)
		  for i = 1,#objs do source[cls][last + i] = name end

	       else
		  for k, v in pairs(objs) do
		     input[cls][k] = v
		     source[cls][k] = name
		  end
241
	       end
242
243
244
245
246
	    end
	 end
      end
   end

247
   return PolicyConfig(input, source, keys(imported))
248
end
249
250

return PolicySet