diff --git a/README.md b/README.md
index 081a390..d1d6e4f 100644
--- a/README.md
+++ b/README.md
@@ -2,21 +2,20 @@
![test](https://github.com/leafo/pgmoon/workflows/test/badge.svg)
-> **Note!** Are you using the latest version of OpenResty? You must update to
-> pgmoon 1.12 or above, due to a change in Lua pattern compatibility, any query
-> that returns affected number of rows will return the expected value.
+> **Note:** Have you updated from an older version of OpenResty? You must update to
+> pgmoon 1.12 or above, due to a change in Lua pattern compatibility to avoid incorrect
+> results from queries that return affected rows.
-pgmoon is a PostgreSQL client library written in pure Lua (MoonScript).
+**pgmoon** is a PostgreSQL client library written in pure Lua (MoonScript).
-pgmoon was originally designed for use in [OpenResty][5] to take advantage of
-the [cosocket api][4] to provide asynchronous queries but it also works in the
-regular Lua environment as well using [LuaSocket][1] (and optionally
-[luaossl](https://luarocks.org/modules/daurnimator/luaossl) or [LuaCrypto][2] for MD5 authentication and [LuaSec][6] for SSL connections).
-pgmoon can also use [cqueues][]' socket when passed `"cqueues"` as the socket
-type when instantiating.
+**pgmoon** was originally designed for use in [OpenResty][] to take advantage
+of the [cosocket
+api](https://github.com/openresty/lua-nginx-module#ngxsockettcp) to provide
+asynchronous queries but it also works in the regular any Lua environment where
+[LuaSocket][] or [cqueues][] is available.
It's a perfect candidate for running your queries both inside OpenResty's
-environment and on the command line (eg. tests) in web frameworks like [Lapis][3].
+environment and on the command line (eg. tests) in web frameworks like [Lapis][].
## Install
@@ -24,19 +23,32 @@ environment and on the command line (eg. tests) in web frameworks like [Lapis][3
$ luarocks install pgmoon
```
+
+Using OpenResty's OPM
+
+```bash
+$ opm get leafo/pgmoon
+```
+
+
+
+
### Dependencies
pgmoon supports a wide range of environments and libraries, so it may be
necessary to install additional dependencies depending on how you intend to
communicate with the database:
-> Tip: If you're using OpenResty then no additional dependencies are needed
+> **Tip:** If you're using OpenResty then no additional dependencies are needed
+> (generally, a crypto library may be necessary for some authentication
+> methods)
-Some socket library **is required**, depending on the environment you can chose one:
+A socket implementation **is required** to use pgmoon, depending on the
+environment you can chose one:
-* [OpenResty](https://openresty.org/en/) — The built in socket is used, so additional dependencies necessary
-* [LuaSocket](http://w3.impa.br/~diego/software/luasocket/) — Suitable for command line database access, has the highest platform compatibility `luarocks install luasocket`
-* [cqueues](https://github.com/wahern/cqueues) — `luarocks install cqueues`
+* [OpenResty][] — The built in socket is used, no additional dependencies necessary
+* [LuaSocket][] — `luarocks install luasocket`
+* [cqueues][] — `luarocks install cqueues`
If you're on PUC Lua 5.1 or 5.2 then you will need a bit libray (not needed for LuaJIT):
@@ -50,19 +62,21 @@ If you want to use JSON types you will need lua-cjson
$ luarocks install lua-cjson
```
-If you want to use SSL connections with LuaSocket then you will need LuaSec:
-(OpenResty and cqueues come with their own SSL implementations)
+SSL connections may require an additional dependency:
+
+* OpenResty — No additional dependencies required
+* LuaSocket — `luarocks install luasec`
+* cqueues — `luarocks install luaossl`
+Password authentication may require a crypto library, [luaossl][].
```bash
-$ luarocks install luasec
+$ luarocks install luaossl
```
-If you want to use password authentication then you will need a crypto library:
+> **Note:** [LuaCrypto][] can be used as a fallback, but the library is abandoned and not recommended for use
-* [OpenResty](https://openresty.org/en/) — Built in function will be used, no additional dependencies necessary
-* [luaossl](https://github.com/wahern/luaossl) — Recommended `luarocks install luaossl`
-* [luacrypto](https://github.com/starius/luacrypto) — Deprecated library, not recommended
+> **Note:** Use within [OpenResty][] will prioritize built in functions if possible
## Example
@@ -81,8 +95,8 @@ local res = assert(pg:query("select * from users where username = " ..
pg:escape_literal("leafo")))
```
-If you are using OpenResty you should relinquish the socket after you are done
-with it so it can be reused in future requests:
+If you are using OpenResty you can relinquish the socket to the connection pool
+after you are done with it so it can be reused in future requests:
```lua
pg:keepalive()
@@ -94,19 +108,27 @@ Functions in table returned by `require("pgmoon")`:
### `new(options={})`
-Creates a new `Postgres` object. Does not connect automatically. Takes a table
-of options. The table can have the following keys:
+Creates a new `Postgres` object from a configuration object. All fields are
+optional unless otherwise stated. The newly created object will not
+automatically connect, you must call `conect` after creating the object.
+Available options:
+
+* `"database"`: the database name to connect to **required**
* `"host"`: the host to connect to (default: `"127.0.0.1"`)
* `"port"`: the port to connect to (default: `"5432"`)
* `"user"`: the database username to authenticate (default: `"postgres"`)
-* `"database"`: the database name to connect to **required**
-* `"password"`: password for authentication, optional depending on server configuration
+* `"password"`: password for authentication, may be required depending on server configuration
* `"ssl"`: enable ssl (default: `false`)
* `"ssl_verify"`: verify server certificate (default: `nil`)
* `"ssl_required"`: abort the connection if the server does not support SSL connections (default: `nil`)
-* `"pool"`: optional name of pool to use when using OpenResty cosocket (defaults to `"#{host}:#{port}:#{database}"`)
-* `"socket_type"`: optional, the type of socket to use, one of: `"nginx"`, `"luasocket"`, `cqueues` (default: `"nginx"` if in nginx, `"luasocket"` otherwise)
+* `"socket_type"`: the type of socket to use, one of: `"nginx"`, `"luasocket"`, `cqueues` (default: `"nginx"` if in nginx, `"luasocket"` otherwise)
+* `"application_name"`: set the name of the connection as displayed in `pg_stat_activity`. (default: `"pgmoon"`)
+* `"pool"`: (OpenResty only) name of pool to use when using OpenResty cosocket (default: `"#{host}:#{port}:#{database}"`)
+* `"pool_size"`: (OpenResty only) Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
+* `"backlog"`: (OpenResty only) Passed directly to OpenResty cosocket connect function, [see docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
+* `"cqueues_openssl_context"`: Manually created `opensssl.ssl.context` to use when created cqueues SSL connections
+* `"luasec_opts"`: Manually created options object to use when using LuaSec SSL connections
Methods on the `Postgres` object returned by `new`:
@@ -119,9 +141,8 @@ message.
### postgres:settimeout(time)
-Sets the timeout value (in milliseconds) for all socket operations (connect,
-write, receive). This function does not have any return values.
-
+Sets the timeout value (in milliseconds) for all subsequent socket operations
+(connect, write, receive). This function does not have any return values.
### success, err = postgres:disconnect()
@@ -252,9 +273,9 @@ Returns string representation of current state of `Postgres` object.
## SSL connections
pgmoon can establish an SSL connection to a Postgres server. It can also refuse
-to connect to it if the server does not support SSL.
-Just as pgmoon depends on LuaSocket for usage outside of OpenResty, it depends
-on LuaSec for SSL connections in such contexts.
+to connect to it if the server does not support SSL. Just as pgmoon depends on
+LuaSocket for usage outside of OpenResty, it depends on luaossl/LuaSec for SSL
+connections in such contexts.
```lua
local pgmoon = require("pgmoon")
@@ -272,13 +293,13 @@ local pg = pgmoon.new({
assert(pg:connect())
```
-> Note: In Postgres 12 and above, the minium SSL version accepted by client
-> connections is 1.2. When using LuaSec to connect to an SSL server, if you
-> don't specify an `ssl_version` then `tlsv1_2` is used.
+> **Note:** In Postgres 12 and above, the minium SSL version accepted by client
+> connections is 1.2. When using LuaSocket + LuaSec to connect to an SSL
+> server, if you don't specify an `ssl_version` then `tlsv1_2` is used.
-In OpenResty, make sure to configure the [lua_ssl_trusted_certificate][7]
-directive if you wish to verify the server certificate, as the LuaSec-only
-options become irrelevant in that case.
+In OpenResty, make sure to configure the
+[lua_ssl_trusted_certificate](https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate)
+directive if you wish to verify the server certificate.
## Authentication types
@@ -319,6 +340,22 @@ local my_array = {1,2,3,4,5}
pg:query("insert into some_table (some_arr_col) values(" .. encode_array(my_array) .. ")")
```
+### Empty Arrays
+
+When trying to encode an empty array an error will be thrown. Postgres requires
+a type when using an array. When there are values in the array Postgres can
+infer the type, but with no values in the array no type can be inferred. This
+is illustrated in the erorr provided by Postgres:
+
+
+```
+postgres=# select ARRAY[];
+ERROR: cannot determine type of empty array
+LINE 1: select ARRAY[];
+ ^
+HINT: Explicitly cast to the desired type, for example ARRAY[]::integer[].
+```
+
## Handling JSON
`json` and `jsonb` types are automatically decoded when they are returned from
@@ -397,6 +434,7 @@ Homepage:
# Changelog
+* 1.13.0 — 2021-10-13 - Add support for scram_sha_256_auth (@murillopaula), 'backlog' and 'pool_size' options while using ngx.socket (@xiaocang), update LuaSec ssl_protocol default options (@jeremymv2), `application_name` option (@mecampbellsoup)
* 1.12.0 — 2021-01-06 - Lua pattern compatibility fix, Support for Lua 5.1 through 5.4 (@jprjr). Fix bug where SSL vesrion was not being passed. Default to TLS v1.2 when using LuaSec. Luabitop is no longer automatically installed as a dependency. New test suite.
* 1.11.0 — 2020-03-26 - Allow for TLS v1.2 when using LuaSec (Miles Elam)
* 1.10.0 — 2019-04-15 - Support luaossl for crypto functions, added better error when missing crypto library
@@ -434,12 +472,10 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-
- [1]: http://w3.impa.br/~diego/software/luasocket/
- [2]: http://mkottman.github.io/luacrypto/
- [3]: http://leafo.net/lapis
- [4]: http://wiki.nginx.org/HttpLuaModule#ngx.socket.tcp
- [5]: http://openresty.org/
- [6]: https://github.com/brunoos/luasec
- [7]: https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate
+ [luaossl]: https://github.com/wahern/luaossl
+ [LuaCrypto]: https://luarocks.org/modules/starius/luacrypto
+ [LuaSec]: https://github.com/brunoos/luasec
+ [Lapis]: http://leafo.net/lapis
+ [OpenResty]: https://openresty.org/
+ [LuaSocket]: http://w3.impa.br/~diego/software/luasocket/
[cqueues]: http://25thandclement.com/~william/projects/cqueues.html
diff --git a/Tupfile b/Tupfile
deleted file mode 100644
index f0fe651..0000000
--- a/Tupfile
+++ /dev/null
@@ -1 +0,0 @@
-include_rules
diff --git a/Tuprules.tup b/Tuprules.tup
deleted file mode 100644
index 5fa92eb..0000000
--- a/Tuprules.tup
+++ /dev/null
@@ -1 +0,0 @@
-: foreach *.moon |> moonc %f |> %B.lua
diff --git a/pgmoon-dev-1.rockspec b/pgmoon-dev-1.rockspec
index f672764..f6a1aea 100644
--- a/pgmoon-dev-1.rockspec
+++ b/pgmoon-dev-1.rockspec
@@ -2,7 +2,8 @@ package = "pgmoon"
version = "dev-1"
source = {
- url = "git://github.com/leafo/pgmoon.git"
+ url = "git://github.com/kriscode1/pgmoon.git",
+ tag = "luasocket-to-haproxy"
}
description = {
diff --git a/pgmoon/arrays.lua b/pgmoon/arrays.lua
index 15df2e9..882c843 100644
--- a/pgmoon/arrays.lua
+++ b/pgmoon/arrays.lua
@@ -22,6 +22,11 @@ getmetatable(PostgresArray).__call = function(self, t)
return setmetatable(t, self.__base)
end
local default_escape_literal = nil
+local insert, concat
+do
+ local _obj_0 = table
+ insert, concat = _obj_0.insert, _obj_0.concat
+end
local encode_array
do
local append_buffer
@@ -29,13 +34,13 @@ do
for _index_0 = 1, #values do
local item = values[_index_0]
if type(item) == "table" and not getmetatable(item) then
- table.insert(buffer, "[")
+ insert(buffer, "[")
append_buffer(escape_literal, buffer, item)
buffer[#buffer] = "]"
- table.insert(buffer, ",")
+ insert(buffer, ",")
else
- table.insert(buffer, escape_literal(item))
- table.insert(buffer, ",")
+ insert(buffer, escape_literal(item))
+ insert(buffer, ",")
end
end
return buffer
@@ -53,8 +58,12 @@ do
local buffer = append_buffer(escape_literal, {
"ARRAY["
}, tbl)
- buffer[#buffer] = "]"
- return table.concat(buffer)
+ if buffer[#buffer] == "," then
+ buffer[#buffer] = "]"
+ else
+ insert(buffer, "]")
+ end
+ return concat(buffer)
end
end
local convert_values
diff --git a/pgmoon/arrays.moon b/pgmoon/arrays.moon
index 7bb2d5a..100dfe0 100644
--- a/pgmoon/arrays.moon
+++ b/pgmoon/arrays.moon
@@ -6,18 +6,20 @@ getmetatable(PostgresArray).__call = (t) =>
default_escape_literal = nil
+import insert, concat from table
+
encode_array = do
append_buffer = (escape_literal, buffer, values) ->
for item in *values
-- plain array
if type(item) == "table" and not getmetatable(item)
- table.insert buffer, "["
+ insert buffer, "["
append_buffer escape_literal, buffer, item
buffer[#buffer] = "]" -- strips trailing comma
- table.insert buffer, ","
+ insert buffer, ","
else
- table.insert buffer, escape_literal item
- table.insert buffer, ","
+ insert buffer, escape_literal item
+ insert buffer, ","
buffer
@@ -33,8 +35,12 @@ encode_array = do
buffer = append_buffer escape_literal, {"ARRAY["}, tbl
- buffer[#buffer] = "]" -- strips trailing comma
- table.concat buffer
+
+ if buffer[#buffer] == ","
+ buffer[#buffer] = "]"
+ else
+ insert buffer, "]"
+ concat buffer
convert_values = (array, fn) ->
for idx, v in ipairs array
diff --git a/pgmoon/bit.lua b/pgmoon/bit.lua
index 102f944..1b99ceb 100644
--- a/pgmoon/bit.lua
+++ b/pgmoon/bit.lua
@@ -1,16 +1,17 @@
-local rshift, lshift, band, ok, _
-local string_loader
-string_loader = function(str)
+local rshift, lshift, band, bxor
+local load_code
+load_code = function(str)
local sent = false
- return function()
+ return pcall(load(function()
if sent then
return nil
end
sent = true
return str
- end
+ end))
end
-ok, band = pcall(load(string_loader([[ return function(a,b)
+local ok
+ok, band = load_code([[ return function(a,b)
a = a & b
if a > 0x7FFFFFFF then
-- extend the sign bit
@@ -18,9 +19,19 @@ ok, band = pcall(load(string_loader([[ return function(a,b)
end
return a
end
-]])))
+]])
if ok then
- _, lshift = pcall(load(string_loader([[ return function(x,y)
+ local _
+ _, bxor = load_code([[ return function(a,b)
+ a = a ~ b
+ if a > 0x7FFFFFFF then
+ -- extend the sign bit
+ a = ~0xFFFFFFFF | a
+ end
+ return a
+ end
+ ]])
+ _, lshift = load_code([[ return function(x,y)
-- limit to 32-bit shifts
y = y % 32
x = x << y
@@ -30,8 +41,8 @@ if ok then
end
return x
end
- ]])))
- _, rshift = pcall(load(string_loader([[ return function(x,y)
+ ]])
+ _, rshift = load_code([[ return function(x,y)
y = y % 32
-- truncate to 32-bit before applying shift
x = x & 0xFFFFFFFF
@@ -41,15 +52,16 @@ if ok then
end
return x
end
- ]])))
+ ]])
else
do
local _obj_0 = require("bit")
- rshift, lshift, band = _obj_0.rshift, _obj_0.lshift, _obj_0.band
+ rshift, lshift, band, bxor = _obj_0.rshift, _obj_0.lshift, _obj_0.band, _obj_0.bxor
end
end
return {
rshift = rshift,
lshift = lshift,
- band = band
+ band = band,
+ bxor = bxor
}
diff --git a/pgmoon/bit.moon b/pgmoon/bit.moon
index f2a2cc6..df253fd 100644
--- a/pgmoon/bit.moon
+++ b/pgmoon/bit.moon
@@ -1,20 +1,16 @@
-local rshift, lshift, band, ok, _
-local string_loader
-
+local rshift, lshift, band, bxor
-- lua5.1 has separate 'loadstring' and 'load'
-- functions ('load' doesn't accept strings).
-- This provides a function that 'load' can use,
-- and will work on all versions of lua
-string_loader = (str) ->
+load_code = (str) ->
sent = false
- return ->
- if sent then
- return nil
+ pcall load ->
+ return nil if sent
sent = true
- return str
-
+ str
-- use load to treat as a string to prevent
-- parse errors under lua < 5.3
@@ -23,7 +19,7 @@ string_loader = (str) ->
-- uses 32-bit or 64-bit integers, so these wrappers will
-- truncate results and/or extend the sign, as appropriate
-- to match luajit's behavior.
-ok, band = pcall(load(string_loader([[
+ok, band = load_code [[
return function(a,b)
a = a & b
if a > 0x7FFFFFFF then
@@ -32,10 +28,21 @@ ok, band = pcall(load(string_loader([[
end
return a
end
-]])))
+]]
if ok then
- _, lshift = pcall(load(string_loader([[
+ _, bxor = load_code [[
+ return function(a,b)
+ a = a ~ b
+ if a > 0x7FFFFFFF then
+ -- extend the sign bit
+ a = ~0xFFFFFFFF | a
+ end
+ return a
+ end
+ ]]
+
+ _, lshift = load_code [[
return function(x,y)
-- limit to 32-bit shifts
y = y % 32
@@ -46,8 +53,9 @@ if ok then
end
return x
end
- ]])))
- _, rshift = pcall(load(string_loader([[
+ ]]
+
+ _, rshift = load_code [[
return function(x,y)
y = y % 32
-- truncate to 32-bit before applying shift
@@ -58,13 +66,14 @@ if ok then
end
return x
end
- ]])))
+ ]]
else
- import rshift, lshift, band from require "bit"
+ import rshift, lshift, band, bxor from require "bit"
-return {
- rshift: rshift
- lshift: lshift
- band: band
+{
+ :rshift
+ :lshift
+ :band
+ :bxor
}
diff --git a/pgmoon/cqueues.lua b/pgmoon/cqueues.lua
index 9e5b238..7f26b5a 100644
--- a/pgmoon/cqueues.lua
+++ b/pgmoon/cqueues.lua
@@ -21,8 +21,12 @@ do
end
return true
end,
- sslhandshake = function(self)
- return self.sock:starttls()
+ starttls = function(self, ...)
+ return self.sock:starttls(...)
+ end,
+ getpeercertificate = function(self)
+ local ssl = assert(self.sock:checktls())
+ return assert(ssl:getPeerCertificate(), "no peer certificate available")
end,
send = function(self, ...)
return self.sock:write(flatten(...))
diff --git a/pgmoon/cqueues.moon b/pgmoon/cqueues.moon
index 47237dc..977461a 100644
--- a/pgmoon/cqueues.moon
+++ b/pgmoon/cqueues.moon
@@ -21,8 +21,14 @@ class CqueuesSocket
true
- sslhandshake: =>
- @sock\starttls!
+ -- args: [context][, timeout]
+ starttls: (...) =>
+ @sock\starttls ...
+
+ -- returns openssl.x509 object
+ getpeercertificate: =>
+ ssl = assert @sock\checktls!
+ assert ssl\getPeerCertificate!, "no peer certificate available"
send: (...) =>
@sock\write flatten ...
diff --git a/pgmoon/crypto.lua b/pgmoon/crypto.lua
index e8f1572..cd1a938 100644
--- a/pgmoon/crypto.lua
+++ b/pgmoon/crypto.lua
@@ -1,11 +1,10 @@
-if ngx then
- return {
- md5 = ngx.md5
- }
-end
local md5
-pcall(function()
- local digest = require("openssl.digest")
+if ngx then
+ md5 = ngx.md5
+elseif pcall(function()
+ return require("openssl.digest")
+end) then
+ local openssl_digest = require("openssl.digest")
local hex_char
hex_char = function(c)
return string.format("%02x", string.byte(c))
@@ -15,20 +14,55 @@ pcall(function()
return (str:gsub(".", hex_char))
end
md5 = function(str)
- return hex(digest.new("md5"):final(str))
+ return hex(openssl_digest.new("md5"):final(str))
+ end
+elseif pcall(function()
+ return require("crypto")
+end) then
+ local crypto = require("crypto")
+ md5 = function(str)
+ return crypto.digest("md5", str)
end
-end)
-if not (md5) then
- pcall(function()
- local crypto = require("crypto")
- md5 = function(str)
- return crypto.digest("md5", str)
- end
- end)
+else
+ md5 = function()
+ return error("Either luaossl (recommended) or LuaCrypto is required to calculate md5")
+ end
+end
+local hmac_sha256
+hmac_sha256 = function(key, str)
+ local openssl_hmac = require("openssl.hmac")
+ local hmac = assert(openssl_hmac.new(key, "sha256"))
+ hmac:update(str)
+ return assert(hmac:final())
end
-if not (md5) then
- error("Either luaossl (recommended) or LuaCrypto is required to calculate md5")
+local digest_sha256
+digest_sha256 = function(str)
+ local digest = assert(require("openssl.digest").new("sha256"))
+ digest:update(str)
+ return assert(digest:final())
+end
+local kdf_derive_sha256
+kdf_derive_sha256 = function(str, salt, i)
+ local openssl_kdf = require("openssl.kdf")
+ local decode_base64
+ decode_base64 = require("pgmoon.util").decode_base64
+ salt = decode_base64(salt)
+ local key, err = openssl_kdf.derive({
+ type = "PBKDF2",
+ md = "sha256",
+ salt = salt,
+ iter = i,
+ pass = str,
+ outlen = 32
+ })
+ if not (key) then
+ return nil, "failed to derive pbkdf2 key: " .. tostring(err)
+ end
+ return key
end
return {
- md5 = md5
+ md5 = md5,
+ hmac_sha256 = hmac_sha256,
+ digest_sha256 = digest_sha256,
+ kdf_derive_sha256 = kdf_derive_sha256
}
diff --git a/pgmoon/crypto.moon b/pgmoon/crypto.moon
index a810996..89c6836 100644
--- a/pgmoon/crypto.moon
+++ b/pgmoon/crypto.moon
@@ -1,26 +1,49 @@
-if ngx
- return { md5: ngx.md5 }
+md5 = if ngx
+ ngx.md5
+elseif pcall -> require "openssl.digest"
+ openssl_digest = require "openssl.digest"
+ hex_char = (c) -> string.format "%02x", string.byte c
+ hex = (str) -> (str\gsub ".", hex_char)
-local md5
+ (str) -> hex openssl_digest.new("md5")\final str
+elseif pcall -> require "crypto"
+ crypto = require "crypto"
+ (str) -> crypto.digest "md5", str
+else
+ -> error "Either luaossl (recommended) or LuaCrypto is required to calculate md5"
-pcall ->
- digest = require "openssl.digest"
+hmac_sha256 = (key, str) ->
+ openssl_hmac = require("openssl.hmac")
+ hmac = assert openssl_hmac.new(key, "sha256")
- hex_char = (c) -> string.format "%02x", string.byte c
- hex = (str) -> (str\gsub ".", hex_char)
+ hmac\update str
+ assert hmac\final!
+
+digest_sha256 = (str) ->
+ digest = assert require("openssl.digest").new("sha256")
+ digest\update str
+ assert digest\final!
+
+
+kdf_derive_sha256 = (str, salt, i) ->
+ openssl_kdf = require "openssl.kdf"
+ import decode_base64 from require "pgmoon.util"
- md5 = (str) ->
- hex digest.new("md5")\final str
+ salt = decode_base64 salt
-unless md5
- pcall ->
- crypto = require "crypto"
+ key, err = openssl_kdf.derive {
+ type: "PBKDF2"
+ md: "sha256"
+ salt: salt
+ iter: i
+ pass: str
+ outlen: 32 -- our H() produces a 32 byte hash value (SHA-256)
+ }
- md5 = (str) ->
- crypto.digest "md5", str
+ unless key
+ return nil, "failed to derive pbkdf2 key: #{err}"
-unless md5
- error "Either luaossl (recommended) or LuaCrypto is required to calculate md5"
+ key
-{ :md5 }
+{ :md5, :hmac_sha256, :digest_sha256, :kdf_derive_sha256 }
diff --git a/pgmoon/init.lua b/pgmoon/init.lua
index c95df35..04da2c5 100644
--- a/pgmoon/init.lua
+++ b/pgmoon/init.lua
@@ -1,13 +1,13 @@
local socket = require("pgmoon.socket")
local insert
insert = table.insert
-local rshift, lshift, band
+local rshift, lshift, band, bxor
do
local _obj_0 = require("pgmoon.bit")
- rshift, lshift, band = _obj_0.rshift, _obj_0.lshift, _obj_0.band
+ rshift, lshift, band, bxor = _obj_0.rshift, _obj_0.lshift, _obj_0.band, _obj_0.bxor
end
local unpack = table.unpack or unpack
-local VERSION = "1.12.0"
+local VERSION = "1.13.0"
local _len
_len = function(thing, t)
if t == nil then
@@ -126,10 +126,13 @@ do
"NULL"
},
PG_TYPES = PG_TYPES,
- user = "postgres",
- host = "127.0.0.1",
- port = "5432",
- ssl = false,
+ default_config = {
+ application_name = "pgmoon",
+ user = "postgres",
+ host = "127.0.0.1",
+ port = "5432",
+ ssl = false
+ },
type_deserializers = {
json = function(self, val, name)
local decode_json
@@ -185,18 +188,24 @@ do
return self:set_type_oid(tonumber(res.oid), "hstore")
end,
connect = function(self)
- local opts
- if self.sock_type == "nginx" then
- opts = {
- pool = self.pool_name or tostring(self.host) .. ":" .. tostring(self.port) .. ":" .. tostring(self.database) .. ":" .. tostring(self.user)
+ if not (self.sock) then
+ self.sock = socket.new(self.sock_type)
+ end
+ local connect_opts
+ local _exp_0 = self.sock_type
+ if "nginx" == _exp_0 then
+ connect_opts = {
+ pool = self.config.pool_name or tostring(self.config.host) .. ":" .. tostring(self.config.port) .. ":" .. tostring(self.config.database) .. ":" .. tostring(self.config.user),
+ pool_size = self.config.pool_size,
+ backlog = self.config.backlog
}
end
- local ok, err = self.sock:connect(self.host, self.port, opts)
+ local ok, err = self.sock:connect(self.config.host, self.config.port, connect_opts)
if not (ok) then
return nil, err
end
if self.sock:getreusedtimes() == 0 then
- if self.ssl then
+ if self.config.ssl then
local success
success, err = self:send_ssl_message()
if not (success) then
@@ -232,6 +241,35 @@ do
self.sock = nil
return sock:setkeepalive(...)
end,
+ create_cqueues_openssl_context = function(self)
+ if not (self.config.ssl_verify ~= nil or self.config.cert or self.config.key or self.config.ssl_version) then
+ return
+ end
+ local ssl_context = require("openssl.ssl.context")
+ local out = ssl_context.new(self.config.ssl_version)
+ if self.config.ssl_verify == true then
+ out:setVerify(ssl_context.VERIFY_PEER)
+ end
+ if self.config.ssl_verify == false then
+ out:setVerify(ssl_context.VERIFY_NONE)
+ end
+ if self.config.cert then
+ out:setCertificate(self.config.cert)
+ end
+ if self.config.key then
+ out:setPrivateKey(self.config.key)
+ end
+ return out
+ end,
+ create_luasec_opts = function(self)
+ return {
+ key = self.config.key,
+ certificate = self.config.cert,
+ cafile = self.config.cafile,
+ protocol = self.config.ssl_version,
+ verify = self.config.ssl_verify and "peer" or "none"
+ }
+ end,
auth = function(self)
local t, msg = self:receive_message()
if not (t) then
@@ -252,26 +290,194 @@ do
return self:cleartext_auth(msg)
elseif 5 == _exp_0 then
return self:md5_auth(msg)
+ elseif 10 == _exp_0 then
+ return self:scram_sha_256_auth(msg)
else
return error("don't know how to auth: " .. tostring(auth_type))
end
end,
cleartext_auth = function(self, msg)
- assert(self.password, "missing password, required for connect")
+ assert(self.config.password, "missing password, required for connect")
self:send_message(MSG_TYPE.password, {
- self.password,
+ self.config.password,
NULL
})
return self:check_auth()
end,
+ scram_sha_256_auth = function(self, msg)
+ assert(self.config.password, "missing password, required for connect")
+ local openssl_rand = require("openssl.rand")
+ local rand_bytes = assert(openssl_rand.bytes(18))
+ local encode_base64
+ encode_base64 = require("pgmoon.util").encode_base64
+ local c_nonce = encode_base64(rand_bytes)
+ local nonce = "r=" .. c_nonce
+ local saslname = ""
+ local username = "n=" .. saslname
+ local client_first_message_bare = username .. "," .. nonce
+ local plus = false
+ local bare = false
+ if msg:match("SCRAM%-SHA%-256%-PLUS") then
+ plus = true
+ elseif msg:match("SCRAM%-SHA%-256") then
+ bare = true
+ else
+ error("unsupported SCRAM mechanism name: " .. tostring(msg))
+ end
+ local gs2_cbind_flag
+ local gs2_header
+ local cbind_input
+ local mechanism_name
+ if bare then
+ gs2_cbind_flag = "n"
+ gs2_header = gs2_cbind_flag .. ",,"
+ cbind_input = gs2_header
+ mechanism_name = "SCRAM-SHA-256" .. NULL
+ elseif plus then
+ local cb_name = "tls-server-end-point"
+ gs2_cbind_flag = "p=" .. cb_name
+ gs2_header = gs2_cbind_flag .. ",,"
+ mechanism_name = "SCRAM-SHA-256-PLUS" .. NULL
+ local cbind_data
+ do
+ if self.sock_type == "cqueues" then
+ local openssl_x509 = self.sock:getpeercertificate()
+ cbind_data = openssl_x509:digest("sha256", "s")
+ else
+ local pem, signature
+ if self.sock_type == "nginx" then
+ local ssl = require("resty.openssl.ssl").from_socket(self.sock)
+ local server_cert = ssl:get_peer_certificate()
+ pem, signature = server_cert:to_PEM(), server_cert:get_signature_name()
+ else
+ local server_cert = self.sock:getpeercertificate()
+ pem, signature = server_cert:pem(), server_cert:getsignaturename()
+ end
+ signature = signature:lower()
+ if signature:match("^md5") or signature:match("^sha1") then
+ signature = "sha256"
+ end
+ local openssl_x509 = require("openssl.x509").new(pem, "PEM")
+ cbind_data = assert(openssl_x509:digest(signature, "s"))
+ end
+ end
+ cbind_input = gs2_header .. cbind_data
+ end
+ local client_first_message = gs2_header .. client_first_message_bare
+ self:send_message(MSG_TYPE.password, {
+ mechanism_name,
+ self:encode_int(#client_first_message),
+ client_first_message
+ })
+ local t
+ t, msg = self:receive_message()
+ if not (t) then
+ return nil, msg
+ end
+ local server_first_message = msg:sub(5)
+ local int32 = self:decode_int(msg, 4)
+ if int32 == nil or int32 ~= 11 then
+ return nil, "server_first_message error: " .. msg
+ end
+ local channel_binding = "c=" .. encode_base64(cbind_input)
+ nonce = server_first_message:match("([^,]+)")
+ if not (nonce) then
+ return nil, "malformed server message (nonce)"
+ end
+ local client_final_message_without_proof = channel_binding .. "," .. nonce
+ local xor
+ xor = function(a, b)
+ local result
+ do
+ local _accum_0 = { }
+ local _len_0 = 1
+ for i = 1, #a do
+ local x = a:byte(i)
+ local y = b:byte(i)
+ if not (x and y) then
+ return nil
+ end
+ local _value_0 = string.char(bxor(x, y))
+ _accum_0[_len_0] = _value_0
+ _len_0 = _len_0 + 1
+ end
+ result = _accum_0
+ end
+ return table.concat(result)
+ end
+ local salt = server_first_message:match(",s=([^,]+)")
+ if not (salt) then
+ return nil, "malformed server message (salt)"
+ end
+ local i = server_first_message:match(",i=(.+)")
+ if not (i) then
+ return nil, "malformed server message (iteraton count)"
+ end
+ if tonumber(i) < 4096 then
+ return nil, "the iteration-count sent by the server is less than 4096"
+ end
+ local kdf_derive_sha256, hmac_sha256, digest_sha256
+ do
+ local _obj_0 = require("pgmoon.crypto")
+ kdf_derive_sha256, hmac_sha256, digest_sha256 = _obj_0.kdf_derive_sha256, _obj_0.hmac_sha256, _obj_0.digest_sha256
+ end
+ local salted_password, err = kdf_derive_sha256(self.config.password, salt, tonumber(i))
+ if not (salted_password) then
+ return nil, err
+ end
+ local client_key
+ client_key, err = hmac_sha256(salted_password, "Client Key")
+ if not (client_key) then
+ return nil, err
+ end
+ local stored_key
+ stored_key, err = digest_sha256(client_key)
+ if not (stored_key) then
+ return nil, err
+ end
+ local auth_message = tostring(client_first_message_bare) .. "," .. tostring(server_first_message) .. "," .. tostring(client_final_message_without_proof)
+ local client_signature
+ client_signature, err = hmac_sha256(stored_key, auth_message)
+ if not (client_signature) then
+ return nil, err
+ end
+ local proof = xor(client_key, client_signature)
+ if not (proof) then
+ return nil, "failed to generate the client proof"
+ end
+ local client_final_message = tostring(client_final_message_without_proof) .. ",p=" .. tostring(encode_base64(proof))
+ self:send_message(MSG_TYPE.password, {
+ client_final_message
+ })
+ t, msg = self:receive_message()
+ if not (t) then
+ return nil, msg
+ end
+ local server_key
+ server_key, err = hmac_sha256(salted_password, "Server Key")
+ if not (server_key) then
+ return nil, err
+ end
+ local server_signature
+ server_signature, err = hmac_sha256(server_key, auth_message)
+ if not (server_signature) then
+ return nil, err
+ end
+ server_signature = encode_base64(server_signature)
+ local sent_server_signature = msg:match("v=([^,]+)")
+ if server_signature ~= sent_server_signature then
+ return nil, "authentication exchange unsuccessful"
+ end
+ return self:check_auth()
+ end,
md5_auth = function(self, msg)
local md5
md5 = require("pgmoon.crypto").md5
local salt = msg:sub(5, 8)
- assert(self.password, "missing password, required for connect")
+ assert(self.config.password, "missing password, required for connect")
self:send_message(MSG_TYPE.password, {
"md5",
- md5(md5(self.password .. self.user) .. salt),
+ md5(md5(self.config.password .. self.config.user) .. salt),
NULL
})
return self:check_auth()
@@ -546,21 +752,21 @@ do
return t, msg
end,
send_startup_message = function(self)
- assert(self.user, "missing user for connect")
- assert(self.database, "missing database for connect")
+ assert(self.config.user, "missing user for connect")
+ assert(self.config.database, "missing database for connect")
local data = {
self:encode_int(196608),
"user",
NULL,
- self.user,
+ self.config.user,
NULL,
"database",
NULL,
- self.database,
+ self.config.database,
NULL,
"application_name",
NULL,
- "pgmoon",
+ self.config.application_name,
NULL,
NULL
}
@@ -583,12 +789,17 @@ do
return nil, err
end
if t == MSG_TYPE.status then
- if self.sock_type == "nginx" then
- return self.sock:sslhandshake(false, nil, self.ssl_verify)
+ local _exp_0 = self.sock_type
+ if "nginx" == _exp_0 then
+ return self.sock:sslhandshake(false, nil, self.config.ssl_verify)
+ elseif "luasocket" == _exp_0 then
+ return self.sock:sslhandshake(self.config.luasec_opts or self:create_luasec_opts())
+ elseif "cqueues" == _exp_0 then
+ return self.sock:starttls(self.config.cqueues_openssl_context or self:create_cqueues_openssl_context())
else
- return self.sock:sslhandshake(self.ssl_verify, self.luasec_opts)
+ return error("don't know how to do ssl handshake for socket type: " .. tostring(self.sock_type))
end
- elseif t == MSG_TYPE.error or self.ssl_required then
+ elseif t == MSG_TYPE.error or self.config.ssl_required then
self:disconnect()
return nil, "the server does not support SSL connections"
else
@@ -672,31 +883,22 @@ do
}
_base_0.__index = _base_0
_class_0 = setmetatable({
- __init = function(self, opts)
- self.sock, self.sock_type = socket.new(opts and opts.socket_type)
- if opts then
- self.user = opts.user
- self.host = opts.host
- self.database = opts.database
- self.port = opts.port
- self.password = opts.password
- self.ssl = opts.ssl
- self.ssl_verify = opts.ssl_verify
- self.ssl_required = opts.ssl_required
- self.pool_name = opts.pool
- self.luasec_opts = {
- key = opts.key,
- cert = opts.cert,
- cafile = opts.cafile,
- ssl_version = opts.ssl_version or "any",
- options = {
- "all",
- "no_sslv2",
- "no_sslv3",
- "no_tlsv1"
- }
- }
- end
+ __init = function(self, _config)
+ if _config == nil then
+ _config = { }
+ end
+ self._config = _config
+ self.config = setmetatable({ }, {
+ __index = function(t, key)
+ local value = self._config[key]
+ if value == nil then
+ return self.default_config[key]
+ else
+ return value
+ end
+ end
+ })
+ self.sock, self.sock_type = socket.new(self.config.socket_type)
end,
__base = _base_0,
__name = "Postgres"
diff --git a/pgmoon/init.moon b/pgmoon/init.moon
index 4f9fa61..cea6660 100644
--- a/pgmoon/init.moon
+++ b/pgmoon/init.moon
@@ -1,11 +1,14 @@
socket = require "pgmoon.socket"
import insert from table
-import rshift, lshift, band from require "pgmoon.bit"
+import rshift, lshift, band, bxor from require "pgmoon.bit"
unpack = table.unpack or unpack
-VERSION = "1.12.0"
+-- Protocol documentation:
+-- https://www.postgresql.org/docs/current/protocol-message-formats.html
+
+VERSION = "1.13.0"
_len = (thing, t=type(thing)) ->
switch t
@@ -106,10 +109,13 @@ class Postgres
NULL: {"NULL"}
:PG_TYPES
- user: "postgres"
- host: "127.0.0.1"
- port: "5432"
- ssl: false
+ default_config: {
+ application_name: "pgmoon"
+ user: "postgres"
+ host: "127.0.0.1"
+ port: "5432"
+ ssl: false
+ }
-- custom types supplementing PG_TYPES
type_deserializers: {
@@ -153,38 +159,47 @@ class Postgres
assert res, "hstore oid not found"
@set_type_oid tonumber(res.oid), "hstore"
- new: (opts) =>
- @sock, @sock_type = socket.new opts and opts.socket_type
-
- if opts
- @user = opts.user
- @host = opts.host
- @database = opts.database
- @port = opts.port
- @password = opts.password
- @ssl = opts.ssl
- @ssl_verify = opts.ssl_verify
- @ssl_required = opts.ssl_required
- @pool_name = opts.pool
- @luasec_opts = {
- key: opts.key
- cert: opts.cert
- cafile: opts.cafile
- ssl_version: opts.ssl_version or "any"
- options: { "all", "no_sslv2", "no_sslv3", "no_tlsv1" }
- }
+ -- config={}
+ -- host: server hostname
+ -- port: server port
+ -- user: the username to authenticate with
+ -- password: the username to authenticate with
+ -- database: database to connect to
+ -- application_name: name assigned to connection to server
+ -- socket_type: type of socket to use (nginx, luasocket, cqueues)
+ -- ssl: enable ssl connections
+ -- ssl_verify: verify the certificate
+ -- cqueues_openssl_context: manually created openssl.ssl.context for cqueues sockets
+ -- luasec_opts: manually created options for LuaSocket ssl connections
+ new: (@_config={}) =>
+ @config = setmetatable {}, {
+ __index: (t, key) ->
+ value = @_config[key]
+ if value == nil
+ @default_config[key]
+ else
+ value
+ }
+
+ @sock, @sock_type = socket.new @config.socket_type
connect: =>
- opts = if @sock_type == "nginx"
- {
- pool: @pool_name or "#{@host}:#{@port}:#{@database}:#{@user}"
- }
-
- ok, err = @sock\connect @host, @port, opts
+ unless @sock
+ @sock = socket.new @sock_type
+
+ connect_opts = switch @sock_type
+ when "nginx"
+ {
+ pool: @config.pool_name or "#{@config.host}:#{@config.port}:#{@config.database}:#{@config.user}"
+ pool_size: @config.pool_size
+ backlog: @config.backlog
+ }
+
+ ok, err = @sock\connect @config.host, @config.port, connect_opts
return nil, err unless ok
if @sock\getreusedtimes! == 0
- if @ssl
+ if @config.ssl
success, err = @send_ssl_message!
return nil, err unless success
@@ -212,6 +227,38 @@ class Postgres
@sock = nil
sock\setkeepalive ...
+ -- see: http://25thandclement.com/~william/projects/luaossl.pdf
+ create_cqueues_openssl_context: =>
+ return unless @config.ssl_verify != nil or @config.cert or @config.key or @config.ssl_version
+
+ ssl_context = require("openssl.ssl.context")
+
+ out = ssl_context.new @config.ssl_version
+
+ if @config.ssl_verify == true
+ out\setVerify ssl_context.VERIFY_PEER
+
+ if @config.ssl_verify == false
+ out\setVerify ssl_context.VERIFY_NONE
+
+ if @config.cert
+ out\setCertificate @config.cert
+
+ if @config.key
+ out\setPrivateKey @config.key
+
+ out
+
+ create_luasec_opts: =>
+ {
+ key: @config.key
+ certificate: @config.cert
+ cafile: @config.cafile
+ protocol: @config.ssl_version
+ verify: @config.ssl_verify and "peer" or "none"
+ }
+
+
auth: =>
t, msg = @receive_message!
return nil, msg unless t
@@ -232,27 +279,206 @@ class Postgres
@cleartext_auth msg
when 5 -- md5 password
@md5_auth msg
+ when 10 -- AuthenticationSASL
+ @scram_sha_256_auth msg
else
error "don't know how to auth: #{auth_type}"
cleartext_auth: (msg) =>
- assert @password, "missing password, required for connect"
+ assert @config.password, "missing password, required for connect"
@send_message MSG_TYPE.password, {
- @password
+ @config.password
NULL
}
@check_auth!
+ -- https://www.postgresql.org/docs/current/sasl-authentication.html#SASL-SCRAM-SHA-256
+ scram_sha_256_auth: (msg) =>
+ assert @config.password, "missing password, required for connect"
+
+ openssl_rand = require "openssl.rand"
+
+ -- '18' is the number set by postgres on the server side
+ rand_bytes = assert openssl_rand.bytes 18
+
+ import encode_base64 from require "pgmoon.util"
+
+ c_nonce = encode_base64 rand_bytes
+ nonce = "r=" .. c_nonce
+ saslname = ""
+ username = "n=" .. saslname
+ client_first_message_bare = username .. "," .. nonce
+
+ plus = false
+ bare = false
+
+ if msg\match "SCRAM%-SHA%-256%-PLUS"
+ plus = true
+ elseif msg\match "SCRAM%-SHA%-256"
+ bare = true
+ else
+ error "unsupported SCRAM mechanism name: " .. tostring(msg)
+
+ local gs2_cbind_flag
+ local gs2_header
+ local cbind_input
+ local mechanism_name
+
+ if bare
+ gs2_cbind_flag = "n"
+ gs2_header = gs2_cbind_flag .. ",,"
+ cbind_input = gs2_header
+ mechanism_name = "SCRAM-SHA-256" .. NULL
+ elseif plus
+ cb_name = "tls-server-end-point"
+ gs2_cbind_flag = "p=" .. cb_name
+ gs2_header = gs2_cbind_flag .. ",,"
+ mechanism_name = "SCRAM-SHA-256-PLUS" .. NULL
+
+ cbind_data = do
+ if @sock_type == "cqueues"
+ openssl_x509 = @sock\getpeercertificate!
+ openssl_x509\digest "sha256", "s"
+ else
+ pem, signature = if @sock_type == "nginx"
+ ssl = require("resty.openssl.ssl").from_socket(@sock)
+ server_cert = ssl\get_peer_certificate()
+ server_cert\to_PEM!, server_cert\get_signature_name!
+ else
+ server_cert = @sock\getpeercertificate()
+ server_cert\pem!, server_cert\getsignaturename!
+
+ signature = signature\lower!
+
+ -- upgrade the signature if necessary
+ if signature\match("^md5") or signature\match("^sha1")
+ signature = "sha256"
+
+ openssl_x509 = require("openssl.x509").new(pem, "PEM")
+ assert openssl_x509\digest(signature, "s")
+
+ cbind_input = gs2_header .. cbind_data
+
+ client_first_message = gs2_header .. client_first_message_bare
+
+ @send_message MSG_TYPE.password, {
+ mechanism_name
+ @encode_int #client_first_message
+ client_first_message
+ }
+
+ t, msg = @receive_message()
+
+ unless t
+ return nil, msg
+
+ server_first_message = msg\sub 5
+ int32 = @decode_int msg, 4
+
+ if int32 == nil or int32 != 11
+ return nil, "server_first_message error: " .. msg
+
+ channel_binding = "c=" .. encode_base64 cbind_input
+ nonce = server_first_message\match "([^,]+)"
+
+ unless nonce
+ return nil, "malformed server message (nonce)"
+
+ client_final_message_without_proof = channel_binding .. "," .. nonce
+
+ xor = (a, b) ->
+ result = for i=1,#a
+ x = a\byte i
+ y = b\byte i
+
+ unless x and y
+ return nil
+
+ string.char bxor x, y
+
+ table.concat result
+
+ salt = server_first_message\match ",s=([^,]+)"
+
+ unless salt
+ return nil, "malformed server message (salt)"
+
+ i = server_first_message\match ",i=(.+)"
+
+ unless i
+ return nil, "malformed server message (iteraton count)"
+
+ if tonumber(i) < 4096
+ return nil, "the iteration-count sent by the server is less than 4096"
+
+ import kdf_derive_sha256, hmac_sha256, digest_sha256 from require "pgmoon.crypto"
+ salted_password, err = kdf_derive_sha256 @config.password, salt, tonumber i
+
+ unless salted_password
+ return nil, err
+
+ client_key, err = hmac_sha256 salted_password, "Client Key"
+
+ unless client_key
+ return nil, err
+
+ stored_key, err = digest_sha256 client_key
+
+ unless stored_key
+ return nil, err
+
+ auth_message = "#{client_first_message_bare },#{server_first_message },#{client_final_message_without_proof}"
+
+ client_signature, err = hmac_sha256 stored_key, auth_message
+
+ unless client_signature
+ return nil, err
+
+ proof = xor client_key, client_signature
+
+ unless proof
+ return nil, "failed to generate the client proof"
+
+ client_final_message = "#{client_final_message_without_proof },p=#{encode_base64 proof}"
+
+ @send_message MSG_TYPE.password, {
+ client_final_message
+ }
+
+ t, msg = @receive_message()
+
+ unless t
+ return nil, msg
+
+ server_key, err = hmac_sha256 salted_password, "Server Key"
+
+ unless server_key
+ return nil, err
+
+ server_signature, err = hmac_sha256 server_key, auth_message
+
+ unless server_signature
+ return nil, err
+
+
+ server_signature = encode_base64 server_signature
+ sent_server_signature = msg\match "v=([^,]+)"
+
+ if server_signature != sent_server_signature then
+ return nil, "authentication exchange unsuccessful"
+
+ @check_auth!
+
md5_auth: (msg) =>
import md5 from require "pgmoon.crypto"
salt = msg\sub 5, 8
- assert @password, "missing password, required for connect"
+ assert @config.password, "missing password, required for connect"
@send_message MSG_TYPE.password, {
"md5"
- md5 md5(@password .. @user) .. salt
+ md5 md5(@config.password .. @config.user) .. salt
NULL
}
@@ -500,17 +726,17 @@ class Postgres
t, msg
send_startup_message: =>
- assert @user, "missing user for connect"
- assert @database, "missing database for connect"
+ assert @config.user, "missing user for connect"
+ assert @config.database, "missing database for connect"
data = {
@encode_int 196608
"user", NULL
- @user, NULL
+ @config.user, NULL
"database", NULL
- @database, NULL
+ @config.database, NULL
"application_name", NULL
- "pgmoon", NULL
+ @config.application_name, NULL
NULL
}
@@ -530,11 +756,16 @@ class Postgres
return nil, err unless t
if t == MSG_TYPE.status
- if @sock_type == "nginx"
- @sock\sslhandshake false, nil, @ssl_verify
- else
- @sock\sslhandshake @ssl_verify, @luasec_opts
- elseif t == MSG_TYPE.error or @ssl_required
+ switch @sock_type
+ when "nginx"
+ @sock\sslhandshake false, nil, @config.ssl_verify
+ when "luasocket"
+ @sock\sslhandshake @config.luasec_opts or @create_luasec_opts!
+ when "cqueues"
+ @sock\starttls @config.cqueues_openssl_context or @create_cqueues_openssl_context!
+ else
+ error "don't know how to do ssl handshake for socket type: #{@sock_type}"
+ elseif t == MSG_TYPE.error or @config.ssl_required
@disconnect!
nil, "the server does not support SSL connections"
else
diff --git a/pgmoon/socket.lua b/pgmoon/socket.lua
index d823baa..43db8f5 100644
--- a/pgmoon/socket.lua
+++ b/pgmoon/socket.lua
@@ -26,8 +26,7 @@ do
}
luasocket = {
tcp = function(...)
- local socket = require("socket")
- local sock = socket.tcp(...)
+ local sock = core.tcp(...)
local proxy = setmetatable({
sock = sock,
send = function(self, ...)
@@ -42,18 +41,15 @@ do
end
return self.sock:settimeout(t)
end,
- sslhandshake = function(self, verify, opts)
+ sslhandshake = function(self, opts)
if opts == nil then
opts = { }
end
local ssl = require("ssl")
local params = {
mode = "client",
- protocol = opts.ssl_version or "any",
- key = opts.key,
- certificate = opts.cert,
- cafile = opts.cafile,
- verify = verify and "peer" or "none",
+ protocol = "any",
+ verify = "none",
options = {
"all",
"no_sslv2",
@@ -61,6 +57,9 @@ do
"no_tlsv1"
}
}
+ for k, v in pairs(opts) do
+ params[k] = v
+ end
local sec_sock, err = ssl.wrap(self.sock, params)
if not (sec_sock) then
return false, err
@@ -89,14 +88,14 @@ return {
if ngx and ngx.get_phase() ~= "init" then
socket_type = "nginx"
else
- socket_type = "luasocket"
+ socket_type = "haproxy"
end
end
local socket
local _exp_0 = socket_type
if "nginx" == _exp_0 then
socket = ngx.socket.tcp()
- elseif "luasocket" == _exp_0 then
+ elseif "haproxy" == _exp_0 then
socket = luasocket.tcp()
elseif "cqueues" == _exp_0 then
socket = require("pgmoon.cqueues").CqueuesSocket()
diff --git a/pgmoon/socket.moon b/pgmoon/socket.moon
index e206f61..2b9ca44 100644
--- a/pgmoon/socket.moon
+++ b/pgmoon/socket.moon
@@ -34,18 +34,19 @@ luasocket = do
if t
t = t/1000
@sock\settimeout t
- sslhandshake: (verify, opts={}) =>
+
+ sslhandshake: (opts={}) =>
ssl = require "ssl"
params = {
mode: "client"
- protocol: opts.ssl_version or "any"
- key: opts.key
- certificate: opts.cert
- cafile: opts.cafile
- verify: verify and "peer" or "none"
+ protocol: "any"
+ verify: "none"
options: { "all", "no_sslv2", "no_sslv3", "no_tlsv1" }
}
+ for k,v in pairs opts
+ params[k] = v
+
sec_sock, err = ssl.wrap @sock, params
return false, err unless sec_sock
diff --git a/pgmoon/util.lua b/pgmoon/util.lua
index 0d40e35..9752404 100644
--- a/pgmoon/util.lua
+++ b/pgmoon/util.lua
@@ -20,6 +20,27 @@ do
return table.concat(buffer)
end
end
+local encode_base64, decode_base64
+if ngx then
+ do
+ local _obj_0 = ngx
+ encode_base64, decode_base64 = _obj_0.encode_base64, _obj_0.decode_base64
+ end
+else
+ local b64, unb64
+ do
+ local _obj_0 = require("mime")
+ b64, unb64 = _obj_0.b64, _obj_0.unb64
+ end
+ encode_base64 = function(...)
+ return (b64(...))
+ end
+ decode_base64 = function(...)
+ return (unb64(...))
+ end
+end
return {
- flatten = flatten
+ flatten = flatten,
+ encode_base64 = encode_base64,
+ decode_base64 = decode_base64
}
diff --git a/pgmoon/util.moon b/pgmoon/util.moon
index 6ac64f5..9a4d937 100644
--- a/pgmoon/util.moon
+++ b/pgmoon/util.moon
@@ -16,4 +16,13 @@ flatten = do
__flatten t, buffer
table.concat buffer
-{:flatten}
+local encode_base64, decode_base64
+
+if ngx
+ {:encode_base64, :decode_base64} = ngx
+else
+ { :b64, :unb64 } = require "mime" -- provided by luasocket
+ encode_base64 = (...) -> (b64 ...)
+ decode_base64 = (...) -> (unb64 ...)
+
+{:flatten, :encode_base64, :decode_base64}
diff --git a/spec/pgmoon_spec.moon b/spec/pgmoon_spec.moon
index 8eeb748..4ce57d3 100644
--- a/spec/pgmoon_spec.moon
+++ b/spec/pgmoon_spec.moon
@@ -580,6 +580,7 @@ describe "pgmoon with server", ->
assert PostgresArray.__base == getmetatable array
it "encodes array value", ->
+ assert.same "ARRAY[]", encode_array {}
assert.same "ARRAY[1,2,3]", encode_array {1,2,3}
assert.same "ARRAY['hello','world']", encode_array {"hello", "world"}
assert.same "ARRAY[[4,5],[6,7]]", encode_array {{4,5}, {6,7}}
diff --git a/spec/pgmoon_ssl_spec.moon b/spec/pgmoon_ssl_spec.moon
index c29c463..d3ce227 100644
--- a/spec/pgmoon_ssl_spec.moon
+++ b/spec/pgmoon_ssl_spec.moon
@@ -79,7 +79,6 @@ describe "pgmoon with server", ->
assert.same 'TLSv1.3', res[1].version
pg\disconnect!
-
it "connects with ssl using cqueues", ->
pg = Postgres {
database: DB
@@ -93,5 +92,27 @@ describe "pgmoon with server", ->
assert pg\connect!
assert pg\query "select * from information_schema.tables"
+
+ pg\disconnect!
+
+
+ it "connects with ssl using cqueues with context options", ->
+ pg = Postgres {
+ database: DB
+ port: PORT
+ user: USER
+ password: PASSWORD
+ host: HOST
+ ssl: true
+ socket_type: "cqueues"
+ ssl_version: "TLSv1_2"
+ }
+
+ assert pg\connect!
+ assert pg\query "select * from information_schema.tables"
+
+ res = assert pg\query [[SELECT version FROM pg_stat_ssl WHERE pid=pg_backend_pid()]]
+ assert.same 'TLSv1.2', res[1].version
+
pg\disconnect!