LiquidProxy Lua Edition
1local cn = require "coro-net"
2local ss = require "secure-socket"
3local tp = require "app.tlspeek"
4local uvproxy = require "app.uvproxy"
5
6local tlspeek = tp.peek
7local tpconst = tp.const
8local request_cert = Config.secure.request_cer
9local maxver = Ver2Num((Config.secure.tls.max))
10local tp_max
11if maxver == 0.3 then
12 tp_max = tpconst.tlsVersion10 - 1 -- NOT RECOMMENDED
13else
14 tp_max = tpconst["tlsVersion"..tostring(maxver):gsub("%.", "")]
15end
16
17-- Keeping for later
18--local D_CIPHERS = require "deps.tls.common".DEFAULT_CIPHERS
19
20local errs = {
21 EAI_NONAME = {404, "Cannot resolve host"},
22 ECONNREFUSED = {502, "Connection refused"},
23 ETIMEDOUT = {504, "Timed out"}
24}
25
26errs.EAI_NODATA = errs.EAI_NONAME
27
28for _, t in pairs(errs) do
29 t[3] = tostring(t[2]:len())
30end
31
32local issb = "Internal Server Error\r\n"
33local issh = {
34 code = 500,
35 {"Content-Length", issb:len()}
36}
37
38---@param cSocket uv_tcp_t
39return function(req, cSocket, cread, cwrite)
40 local authpass = HTTPAuth(req, cSocket)
41 local host, port = req.path:match("([^:]+):?(%d*)")
42 port = tonumber(port) or 443
43
44 if Config.log_ip then
45 local ua = req["User-Agent"]
46 l:info("CONNECT to %s:%s by %s (%s)",
47 host, port,
48 ---@diagnostic disable-next-line: undefined-field
49 cSocket:getpeername().ip,
50 ua and ("UA: "..ua) or "No UA")
51 end
52
53 local read, write, sSocket = cn.connect({
54 port = port,
55 host = host,
56 hostname = host,
57 tls = true
58 })
59 if not (read and write and sSocket) then
60 read, write, sSocket = cn.connect({
61 port = port,
62 host = host,
63 hostname = host
64 })
65 if not (read and write and sSocket) then
66 local e = errs[write]
67 l:error("Error connecting to server: "..(e and ("%s (%s)"):format(e[2], write) or write))
68 if e then
69 return {
70 code = e[1],
71 reason = e[2],
72 {"Content-Length", errs[3]}
73 }, e[2]
74 end
75 return issh, issb
76 else
77 cSocket:write("HTTP/1.1 200 Connection Established\r\n\r\n")
78 uvproxy(cSocket, sSocket) return
79 end
80 end
81 cSocket:write("HTTP/1.1 200 Connection Established\r\n\r\n")
82
83 local buf, info, err, nhs = tlspeek(cSocket)
84 if nhs then
85 l:warning "Not a TLS handshake, going with direct proxy"
86 sSocket:close_reset(function()
87 read, write, sSocket = cn.connect({
88 port = port,
89 host = host,
90 hostname = host
91 })
92 if not (read and write and sSocket) then
93 local e = errs[write]
94 l:error("Error connecting to server: "..(e and ("%s (%s)"):format(e[2], write) or write))
95 if e then
96 return {
97 code = e[1],
98 reason = e[2],
99 {"Content-Length", errs[3]}
100 }, e[2]
101 end
102 cSocket:close_reset()
103 else
104 write(buf)
105 uvproxy(cSocket, sSocket)
106 end
107 end)
108 return
109 end
110
111 if err then
112 l:warning("Failed to read handshake ("..err..")")
113 end
114 if not authpass then
115 if info then
116 if Config.secure.mod.http.httpver_auth and not info.supportsHTTP2 then
117 authpass = true
118 elseif Config.secure.tls.pass_auth and not (info.tlsVersion == tp_max or info.tlsVersions[tp_max]) then
119 authpass = true
120 end
121 end
122 end
123 if not authpass then
124 ---@diagnostic disable-next-line: undefined-field
125 l:info("Post-connect auth failed ("..cSocket:getpeername().ip..")")
126 cSocket:close_reset()
127 sSocket:close_reset()
128 end
129
130 local c, k = GenCert((info and next(info.serverNames)) and info.serverNames or host)
131 if not (c and k) then
132 cSocket:close_reset()
133 sSocket:close_reset()
134 end
135
136
137 ---@type uv_tcp_t
138 local tSocket = ss(cSocket, {
139 ca = Cert,
140 cert = c:export(), -- I have zero clue on why this is needed
141 key = k:export(), -- But I do it because I apparently have to
142 server = true,
143
144 buffer = buf,
145
146 hostname = host,
147 host = host,
148 servername = host,
149
150 requestCert = request_cert, -- another reminder to do this
151 ciphers = X_CIPHERS
152 })
153
154 if not tSocket then
155 l:error("Error when upgrading (usually client issue)")
156 print("OpenSSL error: ", require "openssl".error())
157 return
158 end
159
160 uvproxy(tSocket, sSocket)
161end