Environment.lua
local Environment = {}
local log = require("remotelog")
local connection = require("luasql.exasol.Connection")
local ExaError = require("ExaError")
local ConnectionProperties = require("luasql.exasol.ConnectionProperties")
local base64 = require("luasql.exasol.base64")
local function require_udf_module(modname)
local success, result = pcall(require, modname)
if success then
return result
else
local alternative_modname = "_" .. modname
log.warn("Loading module '%s' failed with error '%s', try loading '%s'", modname, result, alternative_modname)
return require(alternative_modname)
end
end
local pkey = require_udf_module("openssl.pkey")
local bignum = require_udf_module("openssl.bignum")
local WEBSOCKET_PROTOCOL = "wss"
local function load_exasol_websocket(args)
if args and args.exasol_websocket then
return args.exasol_websocket
else
return require("luasql.exasol.ExasolWebsocket")
end
end
function Environment:new(args)
local object = {closed = false, connections = {}}
object.exasol_websocket = load_exasol_websocket(args)
self.__index = self
setmetatable(object, self)
return object
end
local function encrypt_password(publicKeyModulus, publicKeyExponent, password)
local rsa = pkey.new({type = "RSA", bits = 1024})
local modulus = bignum.new("0x" .. publicKeyModulus)
local exponent = bignum.new("0x" .. publicKeyExponent)
rsa:setParameters({n = modulus, e = exponent})
return base64.encode(rsa:encrypt(password))
end
local function login(socket, username, password)
local response, err = socket:send_login_command()
if err then
return nil, err
end
local encrypted_password = encrypt_password(response.publicKeyModulus, response.publicKeyExponent, password)
return socket:send_login_credentials(username, encrypted_password)
end
function Environment:connect(sourcename, username, password, properties)
if self.closed then
ExaError:new("E-EDL-21", "Attempt to connect using an environment that is already closed"):raise(3)
end
local connection_properties = ConnectionProperties:create(properties)
local socket = self.exasol_websocket.connect(WEBSOCKET_PROTOCOL .. "://" .. sourcename, connection_properties)
local response, err = login(socket, username, password)
if err then
socket:close()
if err["cause"] == "closed" then
err = ExaError:new("E-EDL-19",
"Login failed because socket is closed. Probably credentials are wrong: {{error}}",
{error = tostring(err)})
else
err = ExaError:new("E-EDL-16", "Login failed: {{error}}", {error = tostring(err)})
end
err:add_mitigations("Check the credentials you provided.")
return nil, err
end
log.trace("Connected to Exasol %s, maximum message size: %s bytes", response.releaseVersion,
response.maxDataMessageSize)
local session_id = response.sessionId
local conn = connection:create(connection_properties, socket, session_id)
self.connections[session_id] = conn
return conn, nil
end
function Environment:close()
if self.closed then
log.warn(tostring(ExaError:new("W-EDL-20", "Attempted to close an already closed environment")))
return false
end
log.trace("Closing environment: check if all %d connections are closed", #self.connections)
for _, conn in pairs(self.connections) do
if not conn.closed then
log.warn(tostring(
ExaError:new("W-EDL-38", "Cannot close environment because not all connections are closed")))
return false
end
end
self.closed = true
return true
end
return Environment