cjdns: support the supernodes feature (#884)
[feed/routing.git] / cjdns / lua / cjdns / uci.lua
1 common = require("cjdns/common")
2 uci = require("uci")
3
4 UCI = {}
5 common.uci = UCI
6
7 --- Return the configuration defaults as a table suitable for JSON output
8 --
9 -- Mostly taken from cjdroute --genconf
10 -- @return table with configuration defaults
11 function UCI.defaults()
12 return {
13 security = {
14 { setuser = "nobody", keepNetAdmin = 1 },
15 { chroot = "/var/run/" },
16 { nofiles = 0 },
17 { noforks = 1 },
18 { seccomp = 0 },
19 { setupComplete = 1 }
20 },
21 router = {
22 supernodes = {},
23 ipTunnel = { outgoingConnections = {}, allowedConnections = {} },
24 interface = { type = "TUNInterface" }
25 },
26 interfaces = { UDPInterface = {}, ETHInterface = {} },
27 authorizedPasswords = {},
28 logging = { logTo = "stdout" }
29 }
30 end
31
32 --- Return the cjdns configuration as a table suitable for JSON output
33 --
34 -- Iterates over cjdns, eth_interface, udp_interface, eth_peer, udp_peer,
35 -- and password sections. Doesn't include IPTunnel related options yet.
36 -- @return table with cjdns configuration
37 function UCI.get()
38 local obj = UCI.defaults()
39
40 local cursor = uci.cursor()
41
42 local config = cursor:get_all("cjdns", "cjdns")
43 if not config then return obj end
44
45 obj.ipv6 = config.ipv6
46 obj.publicKey = config.public_key
47 obj.privateKey = config.private_key
48 obj.admin = {
49 bind = config.admin_address .. ":" .. config.admin_port,
50 password = config.admin_password }
51
52 if config.tun_device and string.len(config.tun_device) > 0 then
53 obj.router.interface.tunDevice = config.tun_device
54 end
55
56 cursor:foreach("cjdns", "supernodes", function(supernodes)
57 table.insert(obj.router.supernodes, supernodes.public_key)
58 end)
59
60 for i,section in pairs(obj.security) do
61 if type(section.seccomp) == "number" then
62 obj.security[i].seccomp = tonumber(config.seccomp)
63 end
64 end
65
66 cursor:foreach("cjdns", "iptunnel_outgoing", function(outgoing)
67 table.insert(obj.router.ipTunnel.outgoingConnections, outgoing.public_key)
68 end)
69
70 cursor:foreach("cjdns", "iptunnel_allowed", function(allowed)
71 entry = { publicKey = allowed.public_key }
72 if allowed.ipv4 then
73 entry["ip4Address"] = allowed.ipv4
74 end
75 if allowed.ipv6 then
76 entry["ip6Address"] = allowed.ipv6
77 end
78 table.insert(obj.router.ipTunnel.allowedConnections, entry)
79 end)
80
81 cursor:foreach("cjdns", "eth_interface", function(eth_interface)
82 table.insert(obj.interfaces.ETHInterface, {
83 bind = eth_interface.bind,
84 beacon = tonumber(eth_interface.beacon),
85 connectTo = {}
86 })
87 end)
88
89 cursor:foreach("cjdns", "udp_interface", function(udp_interface)
90 table.insert(obj.interfaces.UDPInterface, {
91 bind = udp_interface.address .. ":" .. udp_interface.port,
92 connectTo = {}
93 })
94 end)
95
96 cursor:foreach("cjdns", "eth_peer", function(eth_peer)
97 if not eth_peer.address == "" then
98 local i = tonumber(eth_peer.interface)
99 obj.interfaces.ETHInterface[i].connectTo[eth_peer.address] = {
100 publicKey = eth_peer.public_key,
101 password = eth_peer.password
102 }
103 end
104 end)
105
106 cursor:foreach("cjdns", "udp_peer", function(udp_peer)
107 local bind = udp_peer.address .. ":" .. udp_peer.port
108 local i = tonumber(udp_peer.interface)
109 obj.interfaces.UDPInterface[i].connectTo[bind] = {
110 user = udp_peer.user,
111 publicKey = udp_peer.public_key,
112 password = udp_peer.password
113 }
114 end)
115
116 cursor:foreach("cjdns", "password", function(password)
117 table.insert(obj.authorizedPasswords, {
118 password = password.password,
119 user = password.user,
120 contact = password.contact
121 })
122 end)
123
124 return obj
125 end
126
127 --- Parse and save updated configuration from JSON input
128 --
129 -- Transforms general settings, ETHInterface, UDPInterface, connectTo, and
130 -- authorizedPasswords fields into UCI sections, and replaces the UCI config's
131 -- contents with them.
132 -- @param table JSON input
133 -- @return Boolean whether saving succeeded
134 function UCI.set(obj)
135 local cursor = uci.cursor()
136
137 for i, section in pairs(cursor:get_all("cjdns")) do
138 cursor:delete("cjdns", section[".name"])
139 end
140
141 local admin_address, admin_port = string.match(obj.admin.bind, "^(.*):(.*)$")
142 UCI.cursor_section(cursor, "cjdns", "cjdns", "cjdns", {
143 ipv6 = obj.ipv6,
144 public_key = obj.publicKey,
145 private_key = obj.privateKey,
146 admin_password = obj.admin.password,
147 admin_address = admin_address,
148 admin_port = admin_port
149 })
150
151 if obj.router.interface.tunDevice then
152 UCI.cursor_section(cursor, "cjdns", "cjdns", "cjdns", {
153 tun_device = tostring(obj.router.interface.tunDevice)
154 })
155 end
156
157 if obj.security then
158 for i,section in pairs(obj.security) do
159 for key,value in pairs(section) do
160 if key == "seccomp" then
161 UCI.cursor_section(cursor, "cjdns", "cjdns", "cjdns", {
162 seccomp = tonumber(value)
163 })
164 end
165 end
166 end
167 end
168
169 if obj.router.ipTunnel.outgoingConnections then
170 for i,public_key in pairs(obj.router.ipTunnel.outgoingConnections) do
171 UCI.cursor_section(cursor, "cjdns", "iptunnel_outgoing", nil, {
172 public_key = public_key
173 })
174 end
175 end
176
177 if obj.router.ipTunnel.allowedConnections then
178 for i,allowed in pairs(obj.router.ipTunnel.allowedConnections) do
179 entry = { public_key = allowed.publicKey }
180 if allowed.ip4Address then
181 entry["ipv4"] = allowed.ip4Address
182 end
183 if allowed.ip6Address then
184 entry["ipv6"] = allowed.ip6Address
185 end
186
187 UCI.cursor_section(cursor, "cjdns", "iptunnel_allowed", nil, entry)
188 end
189 end
190
191 if obj.interfaces.ETHInterface then
192 for i,interface in pairs(obj.interfaces.ETHInterface) do
193 UCI.cursor_section(cursor, "cjdns", "eth_interface", nil, {
194 bind = interface.bind,
195 beacon = tostring(interface.beacon)
196 })
197
198 if interface.connectTo then
199 for peer_address,peer in pairs(interface.connectTo) do
200 UCI.cursor_section(cursor, "cjdns", "eth_peer", nil, {
201 interface = i,
202 address = peer_address,
203 public_key = peer.publicKey,
204 password = peer.password
205 })
206 end
207 end
208 end
209 end
210
211 if obj.interfaces.UDPInterface then
212 for i,interface in pairs(obj.interfaces.UDPInterface) do
213 local address, port = string.match(interface.bind, "^(.*):(.*)$")
214 UCI.cursor_section(cursor, "cjdns", "udp_interface", nil, {
215 address = address,
216 port = port
217 })
218
219 if interface.connectTo then
220 for peer_bind,peer in pairs(interface.connectTo) do
221 local peer_address, peer_port = string.match(peer_bind, "^(.*):(.*)$")
222 UCI.cursor_section(cursor, "cjdns", "udp_peer", nil, {
223 interface = i,
224 address = peer_address,
225 port = peer_port,
226 user = peer.user,
227 public_key = peer.publicKey,
228 password = peer.password
229 })
230 end
231 end
232 end
233 end
234
235 if obj.authorizedPasswords then
236 for i,password in pairs(obj.authorizedPasswords) do
237 local user = password.user
238 if not user or string.len(user) == 0 then
239 user = "user-" .. UCI.random_string(6)
240 end
241
242 UCI.cursor_section(cursor, "cjdns", "password", nil, {
243 password = password.password,
244 user = user,
245 contact = password.contact
246 })
247 end
248 end
249
250 return cursor:save("cjdns")
251 end
252
253 --- Simple backport of Cursor:section from luci.model.uci
254 --
255 -- Backport reason: we don't wanna depend on LuCI.
256 -- @param Cursor the UCI cursor to operate on
257 -- @param string name of the config
258 -- @param string type of the section
259 -- @param string name of the section (optional)
260 -- @param table config values
261 function UCI.cursor_section(cursor, config, type, section, values)
262 if section then
263 cursor:set(config, section, type)
264 else
265 section = cursor:add("cjdns", type)
266 end
267
268 for k,v in pairs(values) do
269 cursor:set(config, section, k, v)
270 end
271 end
272
273 function UCI.makeInterface()
274 local cursor = uci.cursor()
275
276 local config = cursor:get_all("cjdns", "cjdns")
277 if not config then return nil end
278
279 return common.AdminInterface.new({
280 host = config.admin_address,
281 port = config.admin_port,
282 password = config.admin_password,
283 config = UCI.get(),
284 timeout = 2
285 })
286 end
287
288 function UCI.random_string(length)
289 -- tr -cd 'A-Za-z0-9' < /dev/urandom
290 local urandom = io.popen("tr -cd 'A-Za-z0-9' 2> /dev/null < /dev/urandom", "r")
291 local string = urandom:read(length)
292 urandom:close()
293 return string
294 end