luci-0.9: merge r5027 - r5112
[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
32 function prepare_daemon(config, server)
33         nixio.syslog("info", "Preparing TCP-Daemon " .. config[".name"])
34         if type(config.address) ~= "table" then
35                 config.address = {config.address}
36         end
37         
38         local sockets, socket, code, err = {}
39         local sopts = {reuseaddr = 1}
40         for _, addr in ipairs(config.address) do
41                 local host, port = addr:match("(.-):?([^:]*)")
42                 if not host then
43                         nixio.syslog("err", "Invalid address: " .. addr)
44                         return nil, -5, "invalid address format"
45                 elseif #host == 0 then
46                         host = nil
47                 end
48                 socket, code, err = prepare_socket(config.family, host, port, sopts)
49                 if socket then
50                         sockets[#sockets+1] = socket
51                 end
52         end
53         
54         nixio.syslog("info", "Sockets bound for " .. config[".name"])
55         
56         if #sockets < 1 then
57                 return nil, -6, "no sockets bound"
58         end
59         
60         nixio.syslog("info", "Preparing publishers for " .. config[".name"])
61         
62         local publisher = {}
63         for k, pname in ipairs(config.publisher) do
64                 local pdata = cursor:get_all(UCINAME, pname)
65                 if pdata then
66                         publisher[#publisher+1] = pdata
67                 else
68                         nixio.syslog("err", "Publisher " .. pname .. " not found")
69                 end
70         end
71         
72         nixio.syslog("info", "Preparing TLS for " .. config[".name"])
73         
74         local tls = prepare_tls(config.tls)
75         if not tls and config.encryption == "enable" then
76                 for _, s in ipairs(sockets) do
77                         s:close()
78                 end
79                 return nil, -4, "Encryption requested, but no TLS context given"
80         end
81         
82         nixio.syslog("info", "Invoking daemon factory for " .. config[".name"])
83         local handler, err = config.slave.module.factory(publisher, config)
84         if not handler then
85                 for _, s in ipairs(sockets) do
86                         s:close()
87                 end
88                 return nil, -3, err
89         else
90                 local pollin = nixio.poll_flags("in")
91                 for _, s in ipairs(sockets) do
92                         server.register_pollfd({
93                                 fd = s,
94                                 events = pollin,
95                                 revents = 0,
96                                 handler = accept,
97                                 accept = handler,
98                                 config = config,
99                                 publisher = publisher,
100                                 tls = tls
101                         })
102                 end
103                 return true
104         end
105 end
106
107 function accept(polle)
108         if not lucid.try_process() then
109                 return false
110         end
111         local socket, host, port = polle.fd:accept()
112         if not socket then
113                 return nixio.syslog("warn", "accept() failed: " .. port)
114         end
115         
116         socket:setblocking(true)
117         
118         local function thread()
119                 lucid.close_pollfds()
120                 local inst = setmetatable({
121                         host = host, port = port, interfaces = lucid.get_interfaces() 
122                 }, {__index = polle})
123                 if polle.config.encryption then
124                         socket = polle.tls:create(socket)
125                         if not socket:accept() then
126                                 socket:close()
127                                 return nixio.syslog("warning", "TLS handshake failed: " .. host)
128                         end
129                 end
130                 
131                 return polle.accept(socket, inst)
132         end
133         
134         local stat = {lucid.create_process(thread)}
135         socket:close()
136         return unpack(stat)
137 end
138
139 function prepare_socket(family, host, port, opts, backlog)
140         nixio.syslog("info", "Preparing socket for port " .. port)
141         backlog = backlog or 1024
142         family = family or "inetany"
143         opts = opts or {}
144         
145         local inetany = family == "inetany"
146         family = inetany and "inet6" or family
147
148         local socket, code, err = nixio.socket(family, "stream")
149         if not socket and inetany then
150                 family = "inet"
151                 socket, code, err = nixio.socket(family, "stream")
152         end
153         
154         if not socket then
155                 return nil, code, err
156         end
157
158         for k, v in pairs(opts) do
159                 socket:setsockopt("socket", k, v)
160         end
161         
162         local stat, code, err = socket:bind(host, port)
163         if not stat then
164                 return nil, code, err
165         end
166         
167         stat, code, err = socket:listen(backlog)
168         if not stat then
169                 return nil, code, err
170         end
171         
172         socket:setblocking(false)
173
174         return socket, family
175 end
176
177 function prepare_tls(tlskey)
178         local tls
179         if tlskey and cursor:get(UCINAME, tlskey) then
180                 tls = nixio.tls("server")
181                 
182                 local make = cursor:get(UCINAME, tlskey, "generate") == "1"
183                 local key = cursor:get(UCINAME, tlskey, "key")
184                 local xtype = make and "asn1" or cursor:get(UCINAME, tlskey, "type")
185                 local cert = cursor:get(UCINAME, tlskey, "cert")
186                 local ciphers = cursor:get(UCINAME, tlskey, "ciphers")
187                 
188                 if make and (not fs.access(key) or not fs.access(cert)) then
189                         local CN = cursor:get(UCINAME, tlskey, "CN")
190                         local O = cursor:get(UCINAME, tlskey, "O")
191                         local bits = 2048
192                         
193                         local data = {
194                                 CN = CN or nixio.uname().nodename,
195                                 O = not O and "LuCId Keymaster" or #O > 0 and O
196                         }
197                         
198                         local stat, px5g = pcall(require, "px5g")
199                         if not stat then
200                                 return nixio.syslog("err", "Unable to load PX5G Keymaster")
201                         end
202                         
203                         nixio.syslog("warning", "PX5G: Generating private key")
204                         local rk = px5g.genkey(bits)
205                         local keyfile = nixio.open(key, "w", 600)
206                         if not rk or not keyfile or not keyfile:writeall(rk:asn1()) then
207                                 return nixio.syslog("err", "Unable to generate private key")
208                         end
209                         keyfile:close()
210                         
211                         nixio.syslog("warning", "PX5G: Generating self-signed certificate")
212                         if not fs.writefile(cert, rk:create_selfsigned(data,
213                                         os.time(), os.time() + 3600 * 24 * 366 * 15)) then
214                                 return nixio.syslog("err", "Unable to generate certificate")
215                         end
216                 end
217                 
218                 if cert then
219                         if not tls:set_cert(cert, xtype) then
220                                 nixio.syslog("err", "Unable to load certificate: " .. cert)
221                         end
222                 end
223                 if key then
224                         if not tls:set_key(key, xtype) then
225                                 nixio.syslog("err", "Unable to load private key: " .. key)
226                         end
227                 end
228
229                 if ciphers then
230                         if type(ciphers) == "table" then
231                                 ciphers = table.concat(ciphers, ":")
232                         end
233                         tls:set_ciphers(ciphers)
234                 end
235         end
236         return tls
237 end