-module(sendr_smtp_ffi).
-export([get_hostname/0, get_host_by_name/1]).
-export([tcp_close/1, tcp_connect/3, tcp_receive/2, tcp_send/2, ssl_close/1,
ssl_connect/4, ssl_receive/2, ssl_send/2, upgrade/4]).
-include_lib("kernel/include/inet.hrl").
get_hostname() ->
{ok, Hostname} = inet:gethostname(),
unicode:characters_to_binary(Hostname).
get_host_by_name(Hostname) ->
Host = unicode:characters_to_list(Hostname),
case inet:gethostbyname(Host) of
{ok, #hostent{h_name = FQDN}} ->
{ok, unicode:characters_to_binary(FQDN)};
Error ->
Error
end.
tcp_connect(Hostname, Port, Timeout) ->
Host = unicode:characters_to_list(Hostname),
Opts = [{mode, binary}, {active, false}, {packet, line}, {reuseaddr, true}],
Resp = gen_tcp:connect(Host, Port, Opts, Timeout),
normalise(Resp).
tcp_close(Socket) ->
gen_tcp:close(Socket),
nil.
tcp_receive(Socket, Timeout) ->
Resp = gen_tcp:recv(Socket, 0, Timeout),
normalise(Resp).
tcp_send(Socket, Data) ->
Resp = gen_tcp:send(Socket, Data),
normalise(Resp).
upgrade(Socket, Hostname, AllowInvalidCerts, Timeout) ->
application:ensure_all_started(ssl),
Host = unicode:characters_to_list(Hostname),
TlsOptions = ssl_options(Host, AllowInvalidCerts),
Resp = ssl:connect(Socket, TlsOptions, Timeout),
normalise(Resp).
ssl_connect(Hostname, Port, AllowInvalidCerts, Timeout) ->
application:ensure_all_started(ssl),
Host = unicode:characters_to_list(Hostname),
TlsOptions = ssl_options(Host, AllowInvalidCerts),
Resp = ssl:connect(Host, Port, TlsOptions, Timeout),
normalise(Resp).
ssl_options(Host, AllowInvalidCerts) ->
Options = [{mode, binary}, {active, false}, {packet, line}],
case AllowInvalidCerts of
true ->
[{verify, verify_none}, {server_name_indication, Host} | Options];
false ->
[{verify, verify_peer},
{server_name_indication, Host},
{cacerts, public_key:cacerts_get()}
| Options]
end.
ssl_close(Socket) ->
ssl:close(Socket),
nil.
ssl_receive(Socket, Timeout) ->
Resp = ssl:recv(Socket, 0, Timeout),
normalise(Resp).
ssl_send(Socket, Data) ->
Resp = ssl:send(Socket, Data),
normalise(Resp).
normalise(ok) ->
{ok, nil};
normalise({ok, Socket}) ->
{ok, Socket};
normalise({ok, Socket, _Ext}) ->
{ok, Socket};
normalise(closed) ->
{error, closed};
normalise({error, closed} = E) ->
E;
normalise({error, timeout} = E) ->
E;
normalise({error, {timeout, _}}) ->
{error, timeout};
% normalise({error, system_limit} = E) ->
% E;
normalise({error, ssl_not_started}) ->
{error, ssl_not_started};
normalise({error, {tls_alert, {Alert, Description}}}) ->
Desc = unicode:characters_to_binary(Description),
{error, {tls_alert, Alert, Desc}};
normalise({error, Reason}) when is_atom(Reason) ->
{error, {posix_error, Reason}}.
% normalise({ok, {_Address, Port}}) ->
% {ok, {port, Port}};
% normalise({error, Reason}) ->
% Formatted = ssl:format_error(Reason),
% Description = unicode:characters_to_binary(Formatted),
% {error, {ssl_error, Description}}.