Packages
tds
2.3.5
2.3.8
2.3.7
2.3.6
2.3.5
2.3.4
2.3.2
2.3.1
2.3.0
2.3.0-rc.1
2.3.0-rc.0
2.2.0
2.1.3
2.1.2
2.1.1
2.1.0
2.0.4
2.0.3
2.0.2
2.0.1
2.0.1-rc2
2.0.1-rc1
2.0.0
retired
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.0
1.0.19
1.0.18
1.0.17
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Microsoft SQL Server client (Elixir implementation of the MS TDS protocol)
Current section
Files
Jump to
Current section
Files
lib/tds/protocol/prelogin.ex
defmodule Tds.Protocol.Prelogin do
@moduledoc """
Prelogin message definition
See: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/60f56408-0188-4cd5-8b90-25c6f2423868
"""
import Tds.Protocol.Grammar
require Logger
@type state :: Tds.Protocol.t()
@type packet_data :: iodata()
@type response ::
{:ok, state()}
| {:error, Exception.t() | atom(), state()}
defstruct version: nil,
encryption: <<0x00>>,
instance: true,
thread_id: nil,
mars: false,
trace_id: nil,
fed_auth_required: false,
nonce_opt: nil
@type t :: %__MODULE__{
version: tuple(),
encryption: <<_::8>>,
instance: boolean(),
mars: boolean()
}
@packet_header 0x12
# PL Options Tokens
@version_token 0x00
@encryption_token 0x01
@instopt_token 0x02
@thread_id_token 0x03
@mars_token 0x04
# @trace_id_token 0x05
@fed_auth_required_token 0x06
@nonce_opt_token 0x07
@termintator_token 0xFF
# Encryption flags
@encryption_off 0x00
@encryption_on 0x01
@encryption_not_supported 0x02
@encryption_required 0x03
@version Mix.Project.config()[:version]
|> String.split(".")
|> Enum.map(&(Integer.parse(&1, 10) |> elem(0)))
@spec encode(maybe_improper_list()) :: [binary(), ...]
def encode(opts) do
stream = [
{@version_token, get_version()},
encode_encryption(opts),
# when instance id check is sent, encryption is not negotiated
# encode_instance(opts),
encode_thread_id(opts),
encode_mars(opts),
encode_fed_auth_required(opts)
]
start_offset = 5 * Enum.count(stream) + 1
{iodata, _} =
stream
|> Enum.reduce({[[], @termintator_token, []], start_offset}, fn
{token, option_data}, {[options, term, data], offset} ->
data_length = byte_size(option_data)
options = [
options,
<<token, offset::ushort(), data_length::ushort()>>
]
data = [data, option_data]
{[options, term, data], offset + data_length}
end)
data = IO.iodata_to_binary(iodata)
Tds.Messages.encode_packets(@packet_header, data)
end
defp get_version do
@version
|> case do
[major, minor, build] ->
<<build::little-ushort(), minor, major, 0x00, 0x00>>
[major, minor] ->
<<0x00, 0x00, minor, major, 0x00, 0x00>>
_ ->
# probably PRE-release
<<0x01, 0x00, 0, 1, 0x00, 0x00>>
end
end
# TODO: Add support for client certificates
defp encode_encryption(opts) do
data =
case ssl?(opts) do
:on ->
<<@encryption_on::byte()>>
:not_supported ->
<<@encryption_not_supported::byte()>>
:required ->
<<@encryption_required::byte()>>
:off ->
# TODO: Support ssl: :off
# This requires that the LOGIN7 message is send encrypted, but
# the other packages are send unencrypted over the wire.
raise ArgumentError, ~s("ssl: :off" is currently not supported)
# <<@encryption_off::byte>>
value ->
raise ArgumentError, "invalid value for :ssl: #{inspect(value)}"
end
{@encryption_token, data}
end
# defp encode_instance(opts) do
# # not working for some reason
# instance = Keyword.get(opts, :instance)
# if is_nil(instance) do
# {@instopt_token, <<0x00>>}
# else
# {@instopt_token, instance <> <<0x00>>}
# end
# end
defp encode_thread_id(_opts) do
pid_serial =
self()
|> inspect()
|> String.split(".")
|> Enum.at(1)
|> Integer.parse()
|> elem(0)
{@thread_id_token, <<pid_serial::ulong()>>}
end
defp encode_mars(_opts) do
{@mars_token, <<0x00>>}
end
defp encode_fed_auth_required(_opts) do
{@fed_auth_required_token, <<0x01>>}
end
# DECODE
@spec decode(iodata(), state()) ::
{:encrypt, state()}
| {:login, state()}
| {:disconnect, Tds.Error.t(), state()}
def decode(packet_data, %{opts: opts} = s) do
{:ok, %{encryption: encryption, instance: instance}} =
packet_data
|> IO.iodata_to_binary()
|> decode_tokens([], s)
case {ssl?(opts), encryption, instance} do
{_, _, false} ->
msg = "Connection terminated, connected instance is not '#{instance}'!"
disconnect(msg, s)
# Encryption is off. Allowed server response is :off or :not_supported
{:off, enc, _} when enc in [<<@encryption_off>>, <<@encryption_not_supported>>] ->
{:login, s}
# TODO: Encryption is off but server has encryption on. Should upgrade.
{:off, <<@encryption_required>>, _} ->
disconnect("Server does not allow the requested encryption level.", s)
# Encryption is not supported. The server needs to respond with :not_supported
{:not_supported, <<@encryption_not_supported>>, _} ->
{:login, s}
# Encryption is on. The server needs to respond with :on
{:on, <<@encryption_on>>, _} ->
{:encrypt, s}
# Encryption is required. The server needs to respond with :on
{:required, <<@encryption_on>>, _} ->
{:encrypt, s}
{_, _, _} ->
disconnect("Server does not allow the requested encryption level.", s)
end
end
defp decode_tokens(
<<@version_token, offset::ushort(), length::ushort(), tail::binary>>,
tokens,
s
) do
tokens = [{:version, offset, length} | tokens]
decode_tokens(tail, tokens, s)
end
defp decode_tokens(
<<@encryption_token, offset::ushort(), length::ushort(), tail::binary>>,
tokens,
s
) do
tokens = [{:encryption, offset, length} | tokens]
decode_tokens(tail, tokens, s)
end
defp decode_tokens(
<<@instopt_token, offset::ushort(), length::ushort(), tail::binary>>,
tokens,
s
) do
tokens = [{:encryption, offset, length} | tokens]
decode_tokens(tail, tokens, s)
end
defp decode_tokens(
<<@thread_id_token, offset::ushort(), length::ushort(), tail::binary>>,
tokens,
s
) do
tokens = [{:thread_id, offset, length} | tokens]
decode_tokens(tail, tokens, s)
end
defp decode_tokens(
<<@mars_token, offset::ushort(), length::ushort(), tail::binary>>,
tokens,
s
) do
tokens = [{:mars, offset, length} | tokens]
decode_tokens(tail, tokens, s)
end
defp decode_tokens(
<<@fed_auth_required_token, offset::ushort(), length::ushort(), tail::binary>>,
tokens,
s
) do
tokens = [{:fed_auth_required, offset, length} | tokens]
decode_tokens(tail, tokens, s)
end
defp decode_tokens(
<<@nonce_opt_token, offset::ushort(), length::ushort(), tail::binary>>,
tokens,
s
) do
tokens = [{:nonce_opt, offset, length} | tokens]
decode_tokens(tail, tokens, s)
end
defp decode_tokens(
<<@termintator_token, tail::binary>>,
tokens,
_s
) do
{:ok, decode_data(Enum.reverse(tokens), tail, %__MODULE__{})}
end
defp decode_data([], _, result), do: result
defp decode_data([{key, _, length} | tokens], bin, m) do
<<data::binary-size(length), tail::binary>> = bin
case key do
:version ->
<<major, minor, patch, trivial, subbuild, _>> = data
decode_data(
tokens,
tail,
%{m | version: {major, minor, patch, trivial, subbuild}}
)
:encryption ->
decode_data(
tokens,
tail,
%{m | encryption: data}
)
:instance ->
decode_data(
tokens,
tail,
%{m | instance: data == <<0x00>>}
)
# :thread_id ->
# :mars ->
# :fed_auth_required ->
# :nonce_opt ->
_ ->
decode_data(tokens, tail, m)
end
end
defp disconnect(message, s) do
{:disconnect, Tds.Error.exception(message), s}
end
defp ssl?(opts) do
case opts[:ssl] do
nil ->
:not_supported
true ->
:required
false ->
:not_supported
other ->
other
end
end
end