GSoC Commit #1: LuCId + HTTP-Server
[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 nixio = require "nixio"
17 local lucid = require "luci.lucid"
18
19 local ipairs, type, require, setmetatable = ipairs, type, require, setmetatable
20 local pairs, print, tostring, unpack = pairs, print, tostring, unpack
21
22 module "luci.lucid.tcpserver"
23
24 local cursor = lucid.cursor
25 local UCINAME = lucid.UCINAME
26
27 local tcpsockets = {}
28
29
30 function prepare_daemon(config, server)
31         nixio.syslog("info", "Preparing TCP-Daemon " .. config[".name"])
32         if type(config.address) ~= "table" then
33                 config.address = {config.address}
34         end
35         
36         local sockets, socket, code, err = {}
37         local sopts = {reuseaddr = 1}
38         for _, addr in ipairs(config.address) do
39                 local host, port = addr:match("(.-):?([^:]*)")
40                 if not host then
41                         nixio.syslog("err", "Invalid address: " .. addr)
42                         return nil, -5, "invalid address format"
43                 elseif #host == 0 then
44                         host = nil
45                 end
46                 socket, code, err = prepare_socket(config.family, host, port, sopts)
47                 if socket then
48                         sockets[#sockets+1] = socket
49                 end
50         end
51         
52         nixio.syslog("info", "Sockets bound for " .. config[".name"])
53         
54         if #sockets < 1 then
55                 return nil, -6, "no sockets bound"
56         end
57         
58         nixio.syslog("info", "Preparing publishers for " .. config[".name"])
59         
60         local publisher = {}
61         for k, pname in ipairs(config.publisher) do
62                 local pdata = cursor:get_all(UCINAME, pname)
63                 if pdata then
64                         publisher[#publisher+1] = pdata
65                 else
66                         nixio.syslog("err", "Publisher " .. pname .. " not found")
67                 end
68         end
69         
70         nixio.syslog("info", "Preparing TLS for " .. config[".name"])
71         
72         local tls = prepare_tls(config.tls)
73         if not tls and config.encryption == "enable" then
74                 for _, s in ipairs(sockets) do
75                         s:close()
76                 end
77                 return nil, -4, "Encryption requested, but no TLS context given"
78         end
79         
80         nixio.syslog("info", "Invoking daemon factory for " .. config[".name"])
81         local handler, err = config.slave.module.factory(publisher, config)
82         if not handler then
83                 for _, s in ipairs(sockets) do
84                         s:close()
85                 end
86                 return nil, -3, err
87         else
88                 local pollin = nixio.poll_flags("in")
89                 for _, s in ipairs(sockets) do
90                         server.register_pollfd({
91                                 fd = s,
92                                 events = pollin,
93                                 revents = 0,
94                                 handler = accept,
95                                 accept = handler,
96                                 config = config,
97                                 publisher = publisher,
98                                 tls = tls
99                         })
100                 end
101                 return true
102         end
103 end
104
105 function accept(polle)
106         local socket, host, port = polle.fd:accept()
107         if not socket then
108                 return nixio.syslog("warn", "accept() failed: " .. port)
109         end
110         
111         socket:setblocking(true)
112         
113         local function thread()
114                 lucid.close_pollfds()
115                 local inst = setmetatable({
116                         host = host, port = port, interfaces = lucid.get_interfaces() 
117                 }, {__index = polle})
118                 if polle.config.encryption then
119                         socket = polle.tls:create(socket)
120                         if not socket:accept() then
121                                 socket:close()
122                                 return nixio.syslog("warning", "TLS handshake failed: " .. host)
123                         end
124                 end
125                 
126                 return polle.accept(socket, inst)
127         end
128         
129         local stat = {lucid.create_process(thread)}
130         socket:close()
131         return unpack(stat)
132 end
133
134 function prepare_socket(family, host, port, opts, backlog)
135         nixio.syslog("info", "Preparing socket for port " .. port)
136         backlog = backlog or 1024
137         family = family or "inetany"
138         opts = opts or {}
139         
140         local inetany = family == "inetany"
141         family = inetany and "inet6" or family
142
143         local socket, code, err = nixio.socket(family, "stream")
144         if not socket and inetany then
145                 family = "inet"
146                 socket, code, err = nixio.socket(family, "stream")
147         end
148         
149         if not socket then
150                 return nil, code, err
151         end
152
153         for k, v in pairs(opts) do
154                 socket:setsockopt("socket", k, v)
155         end
156         
157         local stat, code, err = socket:bind(host, port)
158         if not stat then
159                 return nil, code, err
160         end
161         
162         stat, code, err = socket:listen(backlog)
163         if not stat then
164                 return nil, code, err
165         end
166         
167         socket:setblocking(false)
168
169         return socket, family
170 end
171
172 function prepare_tls(tlskey)
173         local tls = nixio.tls()
174         if tlskey and cursor:get(UCINAME, tlskey) then
175                 local cert = cursor:get(UCINAME, tlskey, "cert")
176                 if cert then
177                         tls:set_cert(cert)
178                 end
179                 local key = cursor:get(UCINAME, tlskey, "key")
180                 if key then
181                         tls:set_key(key)
182                 end
183                 local ciphers = cursor:get(UCINAME, tlskey, "ciphers")
184                 if ciphers then
185                         if type(ciphers) == "table" then
186                                 ciphers = table.concat(ciphers, ":")
187                         end
188                         tls:set_ciphers(ciphers)
189                 end
190         end
191         return tls
192 end