ipsectools-model.lua 9.83 KB
Newer Older
1
local mymodule = {}
Natanael Copa's avatar
Natanael Copa committed
2

Mika Havela's avatar
Mika Havela committed
3
-- Load libraries
4 5
posix = require("posix")
modelfunctions = require("modelfunctions")
6 7 8
fs = require("acf.fs")
format = require("acf.format")
validator = require("acf.validator")
Natanael Copa's avatar
Natanael Copa committed
9

Mika Havela's avatar
Mika Havela committed
10
-- Set variables
11
local confdfile = "/etc/conf.d/racoon"
Natanael Copa's avatar
Natanael Copa committed
12
local configfile = "/etc/racoon/racoon.conf"
13
local configfile2 = "/etc/ipsec.conf"
Natanael Copa's avatar
Natanael Copa committed
14
local processname = "racoon"
Mika Havela's avatar
Mika Havela committed
15
local packagename = "ipsec-tools"
Natanael Copa's avatar
Natanael Copa committed
16 17
local baseurl = "/etc/racoon/"

Mika Havela's avatar
Mika Havela committed
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
local descr = {
	state={
	['9']="Established",
	},
	side={
	['R']="We are 'Responder'.",
	['I']="We 'Initiated' this phase1",
	},
	exchange={
	['M']="Main mode",
	['A']="Agressive mode",
	['B']="Basic mode",
	},

}
Natanael Copa's avatar
Natanael Copa committed
33

Mika Havela's avatar
Mika Havela committed
34 35
-- ################################################################################
-- LOCAL FUNCTIONS
36

Mika Havela's avatar
Mika Havela committed
37 38
local function racoonctl_table()
	local output = {}
39
	local value = modelfunctions.run_executable({"ip", "xfrm", "state"})
40 41
	-- Get rid of all lines that don't start with "src"
	local phase2details = string.gsub(value, "\n[^s][^\n]*", "")
Ted Trask's avatar
Ted Trask committed
42

43
	value = modelfunctions.run_executable({"racoonctl", "-lll", "show-sa", "isakmp"})
44 45 46 47 48
 	for i,line in pairs(format.string_to_table(value,"\n")) do
		if not ((string.find(line,"^Source")) or (#line == 0)) then
			entry={}
			local variable=format.string_to_table(line,"%s+")
			entry['Source']=cfe({
Mika Havela's avatar
Mika Havela committed
49 50 51
				label="Source",
				value=variable[1],
				})
52
			entry['Destination']=cfe({
Mika Havela's avatar
Mika Havela committed
53 54 55
				label="Destination",
				value=variable[2],
				})
56
			entry['Cookies']=cfe({
Mika Havela's avatar
Mika Havela committed
57 58 59
				label="Cookies",
				value=variable[3],
				})
60
			entry['St']=cfe({
Mika Havela's avatar
Mika Havela committed
61 62 63 64
				label="State",
				value=variable[4],
				descr=descr.state[variable[4]],
				})
65
			entry['S']=cfe({
Mika Havela's avatar
Mika Havela committed
66 67 68 69
				label="Side",
				value=variable[5],
				descr=descr.side[variable[5]],
				})
70
			entry['V']=cfe({
Mika Havela's avatar
Mika Havela committed
71 72 73
				label="Version",
				value=variable[6],
				})
74
			entry['E']=cfe({
Mika Havela's avatar
Mika Havela committed
75 76 77 78
				label="Exchange",
				value=variable[7],
				descr=descr.exchange[variable[7]],
				})
79
			entry['Created']=cfe({
Mika Havela's avatar
Mika Havela committed
80 81 82 83
				label="Created",
				value=(variable[8] or "") .. " " .. (variable[9] or ""),
				})

84
			local dst = string.match(variable[2],"^(.*)%.")	-- Removes the portnumber
85 86 87
			local incoming = {}
			local outgoing = {}
			for l in string.gmatch(phase2details, "src [^\n]* "..dst.."\n") do
88
				incoming[#incoming+1] = l
89 90
			end
			for l in string.gmatch(phase2details, "src "..dst.." [^\n]*\n") do
91
				outgoing[#outgoing+1] = l
92
			end
93
			local phase2s = {{label="Outgoing", value=table.concat(outgoing)}, {label="Incoming", value=table.concat(incoming)}}
94
			entry['Phase2']=cfe({
Mika Havela's avatar
Mika Havela committed
95 96
				label="Phase2",
				value=variable[10],
97 98 99
				option=phase2s,
				})

100
			entry['Phase2details']=cfe({
101
				label="Phase2details",
102
				value=phase2s[1]['value'] .. phase2s[2]['value']
Mika Havela's avatar
Mika Havela committed
103
				})
104
			output[#output + 1] = entry
Mika Havela's avatar
Mika Havela committed
105 106 107
		end
	end
	return output
108
end
Natanael Copa's avatar
Natanael Copa committed
109 110 111 112

-- ################################################################################
-- PUBLIC FUNCTIONS

113
function mymodule.get_startstop(self, clientdata)
114 115
        return modelfunctions.get_startstop(processname)
end
Ted Trask's avatar
Ted Trask committed
116

117
function mymodule.startstop_service(self, startstop, action)
118
        return modelfunctions.startstop_service(startstop, action)
Mika Havela's avatar
Mika Havela committed
119
end
Natanael Copa's avatar
Natanael Copa committed
120

121
function mymodule.getstatus()
122 123
	return modelfunctions.getstatus(processname, packagename, "Racoon Status")
end
124

125
function mymodule.getstatusdetails()
126
	local status = {}
127
	status.show_isakmp = cfe({ type="structure", value=racoonctl_table(), label="Tunnels" })
128 129
	status.ip_xfrm_policy = cfe({ type="longtext", label="ip xfrm policy" })
	status.ip_xfrm_policy.value, status.ip_xfrm_policy.errtxt = modelfunctions.run_executable({"ip", "xfrm", "policy"})
130

131
	return cfe({ type="group", value=status, label="Racoon Status Details" })
Natanael Copa's avatar
Natanael Copa committed
132 133
end

134
function mymodule.get_racoonfiledetails()
135
	return modelfunctions.getfiledetails(configfile)
136
end
Natanael Copa's avatar
Natanael Copa committed
137

138
function mymodule.update_racoonfiledetails(self, filedetails)
139
	return modelfunctions.setfiledetails(self, filedetails, {configfile})
Mika Havela's avatar
Mika Havela committed
140
end
141

142
function mymodule.get_ipsecfiledetails()
143
	return modelfunctions.getfiledetails(configfile2)
144
end
145

146
function mymodule.update_ipsecfiledetails(self, filedetails)
147
	return modelfunctions.setfiledetails(self, filedetails, {configfile2})
148
end
149

150
function mymodule.list_certs()
151 152
	local list = {}
	for file in fs.find(".*%.pem", baseurl) do
153
		list[#list+1] = posix.basename(file)
154 155 156 157
	end
	return cfe({ type="list", value=list, label="IPSEC Certificates" })
end

158
function mymodule.get_delete_cert(self, clientdata)
159 160 161 162 163
	local retval = {}
	retval.cert = cfe({ value=clientdata.cert or "", label="IPSEC Certificate" })
	return cfe({ type="group", value=retval, label="Delete Certificate" })
end

164 165
function mymodule.delete_cert(self, delcert)
	local list = mymodule.list_certs()
166 167
	delcert.value.cert.errtxt = "Invalid cert name"
	delcert.errtxt = "Failed to delete Certificate"
168
	for i,cert in ipairs(list.value) do
169 170 171 172
		if cert == delcert.value.cert.value then
			os.remove(baseurl..cert)
			delcert.value.cert.errtxt = nil
			delcert.errtxt = nil
173 174 175
			break
		end
	end
176
	return delcert
177 178
end

179
function mymodule.new_upload_cert()
180
	local value = {}
181 182 183
	value.cert = cfe({ type="raw", value=0, label="Certificate", descr='File must be a password protected ".pfx" file', seq=1 })
	value.password = cfe({ type="password", label="Certificate Password", seq=2 })
	value.name = cfe({ label="Certificate Local Name", seq=3 })
184 185 186
	return cfe({ type="group", value=value })
end

187
function mymodule.upload_cert(self, newcert)
188 189 190 191
	local success = true
	-- Trying to upload a cert/key
	-- The way haserl works, cert contains the temporary file name
	-- First, get the cert
192
	local cmd, f, cmdresult, errtxt
193
	if validator.is_valid_filename(newcert.value.cert.value, "/tmp/") and fs.is_file(newcert.value.cert.value) then
194
		cmdresult, errtxt = modelfunctions.run_executable({"openssl", "pkcs12", "-in", newcert.value.cert.value, "-out", newcert.value.cert.value.."cert.pem", "-password", "pass:"..newcert.value.password.value, "-nokeys", "-clcerts"}, true)
195
		local filestats = posix.stat(newcert.value.cert.value.."cert.pem")
196 197
		if errtxt or not filestats or filestats.size == 0 then
			newcert.value.cert.errtxt = "Could not open certificate\n"..(errtxt or cmdresult)
198 199 200 201
			success = false
		end
	else
		newcert.value.cert.errtxt = "Invalid certificate"
202 203 204 205 206
		success = false
	end

	-- Now, get the key and the ca certs
	if success then
207
		cmdresult, errtxt = modelfunctions.run_executable({"openssl", "pkcs12", "-in", newcert.value.cert.value, "-out", newcert.value.cert.value.."key.pem", "-password", "pass:"..newcert.value.password.value, "-nocerts", "-nodes"}, true)
208
		filestats = posix.stat(newcert.value.cert.value.."key.pem")
209 210
		if errtxt or not filestats or filestats.size == 0 then
			newcert.value.cert.errtxt = "Could not find key\n"..(errtxt or cmdresult)
211 212 213
			success = false
		end

214
		cmdresult, errtxt = modelfunctions.run_executable({"openssl", "pkcs12", "-in", newcert.value.cert.value, "-out", newcert.value.cert.value.."ca.pem", "-password", "pass:"..newcert.value.password.value, "-nokeys", "-cacerts"}, true)
215
		filestats = posix.stat(newcert.value.cert.value.."ca.pem")
216 217
		if errtxt or not filestats or filestats.size == 0 then
			newcert.value.cert.errtxt = "Could not find CA certs\n"..(errtxt or cmdresult)
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
			success = false
		end
	end

	if newcert.value.name.value == "" then
		newcert.value.name.errtxt = "Cannot be blank"
		success = false
	elseif posix.stat(baseurl..newcert.value.name.value.."-cert.pem") or posix.stat(baseurl..newcert.value.name.value.."-key.pem") or posix.stat(baseurl..newcert.value.name.value.."-ca.pem") then
		newcert.value.name.errtxt = "Certificate of this name already exists"
		success = false
	end

	if success then
		if not posix.stat(baseurl) then
			posix.mkdir(baseurl)
		end
		-- copy the keys
235 236 237
		fs.move_file(newcert.value.cert.value.."cert.pem", baseurl..newcert.value.name.value.."-cert.pem")
		fs.move_file(newcert.value.cert.value.."key.pem", baseurl..newcert.value.name.value.."-key.pem")
		fs.move_file(newcert.value.cert.value.."ca.pem", baseurl..newcert.value.name.value.."-ca.pem")
238 239 240 241 242 243
		posix.chmod(baseurl..newcert.value.name.value.."-key.pem", "rw-------")
	else
		newcert.errtxt = "Failed to upload certificate"
	end

	-- Delete the temporary files
244
	if validator.is_valid_filename(newcert.value.cert.value, "/tmp/") and fs.is_file(newcert.value.cert.value) then
245 246 247
		os.remove(newcert.value.cert.value.."cert.pem")
		os.remove(newcert.value.cert.value.."key.pem")
		os.remove(newcert.value.cert.value.."ca.pem")
248
	end
249 250 251

	return newcert
end
252

253
function mymodule.get_view_cert(self, clientdata)
254 255 256 257 258
	local retval = {}
	retval.cert = cfe({ value=clientdata.cert or "", label="IPSEC Certificate" })
	return cfe({ type="group", value=retval, label="View Certificate" })
end

259 260
mymodule.view_cert = function(self, viewcert)
	local list = mymodule.list_certs()
261 262
	viewcert.value.cert.errtxt = "Invalid cert name"
	viewcert.errtxt = "Failed to find cert"
263
	for i,cert in ipairs(list.value) do
264
		if cert == viewcert.value.cert.value then
265 266 267
			viewcert.value.result = cfe({ type="longtext", label="Certificate", readonly=true })
			viewcert.value.result.value, viewcert.value.result.errtxt = modelfunctions.run_executable({"openssl", "x509", "-in", baseurl..cert, "-noout", "-text"})
			viewcert.errtxt = viewcert.value.result.errtxt
268 269
			viewcert.value.cert.errtxt = nil
			break
270 271
		end
	end
272
	return viewcert
273
end
274

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
function mymodule.get_logfile(self, clientdata)
	-- Can override syslog with logfile specified in command-line options "-l logfile"
	-- Otherwise, uses syslog with facility LOG_DAEMON
	local retval = cfe({ type="group", value={}, label="Log File Configuration" })
	retval.value.facility = cfe({value="daemon", label="Syslog Facility"})
	retval.value.grep = cfe({ value="racoon", label="Grep" })
	local opts = format.parse_ini_file(fs.read_file(confdfile), "", "RACOON_OPTS")
	if opts then
		-- remove quotes
		local opts2 = string.match(opts, "\"(.*)\"")
		local opts = " "..(opts2 or "")
		local logfile = string.match(opts, "%s%-l%s+(%S+)")
		if logfile then
			retval.value.filename = cfe({value=logfile, label="File name"})
			retval.value.facility = nil
			retval.value.grep = nil
		end
	end
	return retval
end

296
return mymodule