266ea29e490de3d9d81fe00d14a7110eceaa3d18
[project/luci.git] / libs / lucid / luasrc / lucid / tcpserver.lua
1 --[[
2 LuCI - Lua Development Framework
3
4 Copyright 2009 Steven Barth <steven@midlink.org>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 $Id$
13 ]]
14
15 local os = require "os"
16 local fs = require "nixio.fs"
17 local nixio = require "nixio"
18 local lucid = require "luci.lucid"
19
20 local ipairs, type, require, setmetatable = ipairs, type, require, setmetatable
21 local pairs, print, tostring, unpack = pairs, print, tostring, unpack
22 local pcall = pcall
23
24 module "luci.lucid.tcpserver"
25
26 local cursor = lucid.cursor
27 local UCINAME = lucid.UCINAME
28
29 local tcpsockets = {}
30
31 --- Prepare a daemon and allocate its resources. (superserver callback)
32 -- @param config configuration table
33 -- @param server LuCId basemodule
34 -- @return binary data
35 function prepare_daemon(config, server)
36         nixio.syslog("info", "Preparing TCP-Daemon " .. config[".name"])
37         if type(config.address) ~= "table" then
38                 config.address = {config.address}
39         end
40         
41         local sockets, socket, code, err = {}
42         local sopts = {reuseaddr = 1}
43         for _, addr in ipairs(config.address) do
44                 local host, port = addr:match("(.-):?([^:]*)")
45                 if not host then
46                         nixio.syslog("err", "Invalid address: " .. addr)
47                         return nil, -5, "invalid address format"
48                 elseif #host == 0 then
49                         host = nil
50                 end
51                 socket, code, err = prepare_socket(config.family, host, port, sopts)
52                 if socket then
53                         sockets[#sockets+1] = socket
54                 end
55         end
56         
57         nixio.syslog("info", "Sockets bound for " .. config[".name"])
58         
59         if #sockets < 1 then
60                 return nil, -6, "no sockets bound"
61         end
62         
63         nixio.syslog("info", "Preparing publishers for " .. config[".name"])
64         
65         local publisher = {}
66         for k, pname in ipairs(config.publisher) do
67                 local pdata = cursor:get_all(UCINAME, pname)
68                 if pdata then
69                         publisher[#publisher+1] = pdata
70                 else
71                         nixio.syslog("err", "Publisher " .. pname .. " not found")
72                 end
73         end
74         
75         nixio.syslog("info", "Preparing TLS for " .. config[".name"])
76         
77         local tls = prepare_tls(config.tls)
78         if not tls and config.encryption == "enable" then
79                 for _, s in ipairs(sockets) do
80                         s:close()
81                 end
82                 return nil, -4, "Encryption requested, but no TLS context given"
83         end
84         
85         nixio.syslog("info", "Invoking daemon factory for " .. config[".name"])
86         local handler, err = config.slave.module.factory(publisher, config)
87         if not handler then
88                 for _, s in ipairs(sockets) do
89                         s:close()
90                 end
91                 return nil, -3, err
92         else
93                 local pollin = nixio.poll_flags("in")
94                 for _, s in ipairs(sockets) do
95                         server.register_pollfd({
96                                 fd = s,
97                                 events = pollin,
98                                 revents = 0,
99                                 handler = accept,
100                                 accept = handler,
101                                 config = config,
102                                 publisher = publisher,
103                                 tls = tls
104                         })
105                 end
106                 return true
107         end
108 end
109
110 --- Accept a new TCP connection. (server callback)
111 -- @param polle Poll descriptor
112 -- @return handler process id or nil, error code, error message 
113 function accept(polle)
114         if not lucid.try_process() then
115                 return false
116         end
117         local socket, host, port = polle.fd:accept()
118         if not socket then
119                 return nixio.syslog("warning", "accept() failed: " .. port)
120         end
121         
122         socket:setblocking(true)
123         
124         local function thread()
125                 lucid.close_pollfds()
126                 local inst = setmetatable({
127                         host = host, port = port, interfaces = lucid.get_interfaces() 
128                 }, {__index = polle})
129                 if polle.config.encryption then
130                         socket = polle.tls:create(socket)
131                         if not socket:accept() then
132                                 socket:close()
133                                 return nixio.syslog("warning", "TLS handshake failed: " .. host)
134                         end
135                 end
136                 
137                 return polle.accept(socket, inst)
138         end
139         
140         local stat = {lucid.create_process(thread)}
141         socket:close()
142         return unpack(stat)
143 end
144
145 --- Prepare a TCP server socket.
146 -- @param family protocol family ["inetany", "inet6", "inet"]
147 -- @param host host
148 -- @param port port
149 -- @param opts table of socket options
150 -- @param backlog socket backlog
151 -- @return socket, final socket family
152 function prepare_socket(family, host, port, opts, backlog)
153         nixio.syslog("info", "Preparing socket for port " .. port)
154         backlog = backlog or 1024
155         family = family or "inetany"
156         opts = opts or {}
157         
158         local inetany = family == "inetany"
159         family = inetany and "inet6" or family
160
161         local socket, code, err = nixio.socket(family, "stream")
162         if not socket and inetany then
163                 family = "inet"
164                 socket, code, err = nixio.socket(family, "stream")
165         end
166         
167         if not socket then
168                 return nil, code, err
169         end
170
171         for k, v in pairs(opts) do
172                 socket:setsockopt("socket", k, v)
173         end
174         
175         local stat, code, err = socket:bind(host, port)
176         if not stat then
177                 return nil, code, err
178         end
179         
180         stat, code, err = socket:listen(backlog)
181         if not stat then
182                 return nil, code, err
183         end
184         
185         socket:setblocking(false)
186
187         return socket, family
188 end
189
190 --- Prepare a TLS server context and load keys and certificates.
191 -- May invoke px5g to create keys and certificate on demand if available.
192 -- @param tlskey TLS configuration identifier
193 -- @return TLS server conext or nil
194 function prepare_tls(tlskey)
195         local tls
196         if nixio.tls and tlskey and cursor:get(UCINAME, tlskey) then
197                 tls = nixio.tls("server")
198                 
199                 local make = cursor:get(UCINAME, tlskey, "generate") == "1"
200                 local key = cursor:get(UCINAME, tlskey, "key")
201                 local xtype = make and "asn1" or cursor:get(UCINAME, tlskey, "type")
202                 local cert = cursor:get(UCINAME, tlskey, "cert")
203                 local ciphers = cursor:get(UCINAME, tlskey, "ciphers")
204                 
205                 if make and (not fs.access(key) or not fs.access(cert)) then
206                         local CN = cursor:get(UCINAME, tlskey, "CN")
207                         local O = cursor:get(UCINAME, tlskey, "O")
208                         local bits = 2048
209                         
210                         local data = {
211                                 CN = CN or nixio.uname().nodename,
212                                 O = not O and "LuCId Keymaster" or #O > 0 and O
213                         }
214                         
215                         local stat, px5g = pcall(require, "px5g")
216                         if not stat then
217                                 return nixio.syslog("err", "Unable to load PX5G Keymaster")
218                         end
219                         
220                         nixio.syslog("warning", "PX5G: Generating private key")
221                         local rk = px5g.genkey(bits)
222                         local keyfile = nixio.open(key, "w", 600)
223                         if not rk or not keyfile or not keyfile:writeall(rk:asn1()) then
224                                 return nixio.syslog("err", "Unable to generate private key")
225                         end
226                         keyfile:close()
227                         
228                         nixio.syslog("warning", "PX5G: Generating self-signed certificate")
229                         if not fs.writefile(cert, rk:create_selfsigned(data,
230                                         os.time(), os.time() + 3600 * 24 * 366 * 15)) then
231                                 return nixio.syslog("err", "Unable to generate certificate")
232                         end
233                 end
234                 
235                 if cert then
236                         if not tls:set_cert(cert, xtype) then
237                                 nixio.syslog("err", "Unable to load certificate: " .. cert)
238                         end
239                 end
240                 if key then
241                         if not tls:set_key(key, xtype) then
242                                 nixio.syslog("err", "Unable to load private key: " .. key)
243                         end
244                 end
245
246                 if ciphers then
247                         if type(ciphers) == "table" then
248                                 ciphers = table.concat(ciphers, ":")
249                         end
250                         tls:set_ciphers(ciphers)
251                 end
252         end
253         return tls
254 end