summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-upnp/root/usr/libexec/rpcd/luci.upnp
blob: 37768f972a175c54954c7a3f0b16b06703262796 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#!/usr/bin/env lua

local json = require "luci.jsonc"
local UCI = require "luci.model.uci"
local fs   = require "nixio.fs"
local sys  = require "luci.sys"

local methods = {
	get_status = {
		call = function()
			local uci = UCI.cursor()
			local lease_file = uci:get("upnpd", "config", "upnp_lease_file")

			local ipv4_hints = sys.net.ipv4_hints()
			local rule = { }

			local ipt = io.popen("iptables --line-numbers -t nat -xnvL MINIUPNPD 2>/dev/null")
			if ipt then
				local upnpf = lease_file and io.open(lease_file, "r")
				while true do
					local ln = ipt:read("*l")
					if not ln then
						break
					elseif ln:match("^%d+") then
						local num, proto, extport, intaddr, intport =
							ln:match("^(%d+).-([a-z]+).-dpt:(%d+) to:(%S-):(%d+)")
						local descr = ""

						if num and proto and extport and intaddr and intport then
							extport = tonumber(extport)
							intport = tonumber(intport)

							if upnpf then
								local uln = upnpf:read("*l")
								if uln then descr = uln:match(string.format("^%s:%d:%s:%d:%%d*:(.*)$", proto:upper(), extport, intaddr, intport)) end
								if not descr then descr = "" end
							end

							local host_hint, _, e

							for _,e in pairs(ipv4_hints) do
								if e[1] == intaddr then
									host_hint = e[2]
									break
								end
							end

							rule[#rule+1] = {
								num = num,
								proto   = proto:upper(),
								extport = extport,
								intaddr = intaddr,
								host_hint = host_hint,
								intport = intport,
								descr = descr
							}
						end
					end
				end

				if upnpf then upnpf:close() end
				ipt:close()
			end

			local nft = io.popen("nft --handle list chain inet fw4 upnp_prerouting")
			if nft then
				local num = 1
				local upnpf = lease_file and io.open(lease_file, "r")
				while true do
					local ln = nft:read("*l")
					if not ln then
						break
					elseif ln:match("iif ") then
						local proto, extport, intaddr, intport =
							ln:match('^\t\tiif ".-" @nh,72,8 (0x[0-9a-f]+) th dport ([0-9]+) dnat ip to ([0-9%.]+):([0-9]+)')
						local descr = ""

						if (proto == "0x6" or proto == "0x11") and extport and intaddr and intport then
							proto = (proto == "0x6") and "TCP" or "UDP"
							extport = tonumber(extport)
							intport = tonumber(intport)

							if upnpf then
								local uln = upnpf:read("*l")
								if uln then descr = uln:match(string.format("^%s:%d:%s:%d:%%d*:(.*)$", proto, extport, intaddr, intport)) end
								if not descr then descr = "" end
							end

							local host_hint, _, e

							for _,e in pairs(ipv4_hints) do
								if e[1] == intaddr then
									host_hint = e[2]
									break
								end
							end

							rule[#rule+1] = {
								num = tostring(num),
								proto   = proto,
								extport = extport,
								intaddr = intaddr,
								host_hint = host_hint,
								intport = intport,
								descr = descr
							}

							num = num + 1
						end
					end
				end

				if upnpf then upnpf:close() end
				nft:close()
			end

			return { rules = rule }
		end
	},
	delete_rule = {
		args = { token = "token" },
		call = function(args)
			local util = require "luci.util"
			local idx = args and tonumber(args.token)
			local res = {}

			if idx and idx > 0 then
				local uci = UCI.cursor()

				local lease_file = uci:get("upnpd", "config", "upnp_lease_file")
				if lease_file and fs.access(lease_file) then
					sys.call("sed -i -e '%dd' %s" %{ idx, util.shellquote(lease_file) })
					sys.call("/etc/init.d/miniupnpd restart")
				end

				uci.unload()

				return { result = "OK" }
			end

			return { result = "Bad request" }
		end
	}
}

local function parseInput()
	local parse = json.new()
	local done, err

	while true do
		local chunk = io.read(4096)
		if not chunk then
			break
		elseif not done and not err then
			done, err = parse:parse(chunk)
		end
	end

	if not done then
		print(json.stringify({ error = err or "Incomplete input" }))
		os.exit(1)
	end

	return parse:get()
end

local function validateArgs(func, uargs)
	local method = methods[func]
	if not method then
		print(json.stringify({ error = "Method not found" }))
		os.exit(1)
	end

	if type(uargs) ~= "table" then
		print(json.stringify({ error = "Invalid arguments" }))
		os.exit(1)
	end

	uargs.ubus_rpc_session = nil

	local k, v
	local margs = method.args or {}
	for k, v in pairs(uargs) do
		if margs[k] == nil or
		   (v ~= nil and type(v) ~= type(margs[k]))
		then
			print(json.stringify({ error = "Invalid arguments" }))
			os.exit(1)
		end
	end

	return method
end

if arg[1] == "list" then
	local _, method, rv = nil, nil, {}
	for _, method in pairs(methods) do rv[_] = method.args or {} end
	print((json.stringify(rv):gsub(":%[%]", ":{}")))
elseif arg[1] == "call" then
	local args = parseInput()
	local method = validateArgs(arg[2], args)
	local result, code = method.call(args)
	print((json.stringify(result):gsub("^%[%]$", "{}")))
	os.exit(code or 0)
end