Skip to main content

src/sendr_smtp_ffi.erl

-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}}.