%%% BEGIN openflax/handler/email.erl %%%
%%%
%%% openflax - Open Source web server for Erlang/OTP
%%% Copyright (c)2003 Cat's Eye Technologies. All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions
%%% are met:
%%%
%%% Redistributions of source code must retain the above copyright
%%% notice, this list of conditions and the following disclaimer.
%%%
%%% Redistributions in binary form must reproduce the above copyright
%%% notice, this list of conditions and the following disclaimer in
%%% the documentation and/or other materials provided with the
%%% distribution.
%%%
%%% Neither the name of Cat's Eye Technologies nor the names of its
%%% contributors may be used to endorse or promote products derived
%%% from this software without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
%%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
%%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
%%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE.
%% @doc Openflax handler to allow sending e-mail.
%%
%%
No external mail program is required - this handler implements
%% its own simple SMTP client.
%%
%% openflax.handler.template is required to show the email
%% form.
%%
%% @end
-module(openflax.handler.email).
-vsn('$Id: email.erl 31 2004-04-23 07:00:11Z catseye $').
-author('cpressey@catseye.mine.nu').
-copyright('Copyright (c)2003 Cat`s Eye Technologies. All rights reserved.').
-behaviour(openflax.handler).
-export([start/1, stop/1, serve/1]). % behaviour
-export([split/1]).
-import(lists).
-import(string).
-import(inet).
-import(gen_tcp).
%% @spec start(conf()) -> conf()
%% @doc Initializes the email subsystem.
start(Conf) ->
openflax.conf:new().
%% @spec stop(conf()) -> conf()
%% @doc Shuts down the email subsystem.
stop(Conf) ->
Conf.
%% @spec serve(conf()) -> {response(), conf()}
%% @doc Serves an email form, or sends email, as appropriate.
serve(Conf) ->
Subject = openflax.conf:get_string(form_subject, Conf),
Message = lists:flatten(lists:sort(openflax.conf:fold(fun(Key, Value, Acc) ->
case openflax.string:from_term(Key) of
"form_" ++ Tail ->
[Tail ++ ":\n\n" ++ Value ++ "\n\n" | Acc];
_ ->
Acc
end
end, [], Conf))),
Conf0 = case Message of
[] ->
Conf;
_ ->
ConfA = openflax.conf:put_value(cfg_email_subject, Subject, Conf),
ConfB = openflax.conf:put_value(cfg_email_message, Message, ConfA),
% TODO: put result template in template_body
send(ConfB),
ConfB
end,
{openflax.handler.template, Conf0}.
%% @spec send(conf()) -> {ok, Result} | {error, Reason}
%% @doc Sends an e-mail message to an SMTP server. Settings include:
%%
%% -
{cfg_email_server, string()}
%% the name of the host that the SMTP server is running on.
%% -
{cfg_email_port, string()}
%% the port on the host that is running the SMTP service (usually 25.)
%% -
{cfg_email_originator, string()}
%% the name of the host that is originating the transaction (i.e.,
%% this host.)
%% -
{cfg_email_sender, string()}
%% the e-mail address of the message sender.
%% -
{cfg_email_recipients, [string()]}
%% the e-mail addresses of the message recipients.
%% -
{cfg_email_subject, string()}
%% the subject of the message.
%% -
{cfg_email_headers, [string()]}
%% custom e-mail headers in the form "Header: value",
%% one header per string.
%% -
{cfg_email_message, string()}
%% the body of the e-mail message to send, with lines delimited with
%% newline characters.
%%
send(Conf) ->
Server = openflax.conf:get_string(cfg_email_server, Conf),
Port = case openflax.conf:get_value(cfg_email_port, Conf) of
{ok, P} -> P;
_ -> 25
end,
{ok, Socket} = gen_tcp:connect(Server, Port,
[list, {active, false}, {packet, line}]),
Result = send0(Socket, Conf),
send_line(Socket, "QUIT"),
expect(Socket, 221),
gen_tcp:close(Socket),
{ok, Result}.
send0(Socket, Conf) ->
Originator = openflax.conf:get_string(cfg_email_originator, Conf), % HOST
Sender = openflax.conf:get_string(cfg_email_sender, Conf), % USER@HOST
{ok, Recipients} = openflax.conf:get_value(cfg_email_recipients, Conf),
Message = openflax.conf:get_string(cfg_email_message, Conf),
MessageID = openflax.calendar:timestamp() ++ ".openflax@" ++ Originator,
Subject = openflax.conf:get_string(cfg_email_subject, Conf),
Headers = case openflax.conf:get_value(cfg_email_headers, Conf) of
{ok, H} -> H;
_ -> []
end,
expect(Socket, 220),
send_line(Socket, "HELO " ++ Originator),
expect(Socket, 250),
send_line(Socket, "MAIL FROM:<" ++ Sender ++ ">"),
expect(Socket, 250),
lists:foreach(fun(Recipient) ->
send_line(Socket, "RCPT TO:<" ++ Recipient ++ ">"),
expect(Socket, 250)
end, Recipients),
send_line(Socket, "DATA"),
expect(Socket, 354),
send_line(Socket, "From: <" ++ Sender ++ ">"),
send_line(Socket, "Date: " ++ openflax.calendar:rfc_1123_datetime()),
send_line(Socket, "Message-Id: <" ++ MessageID ++ ">"),
RecipientList = lists:foldl(fun(Recipient, A) ->
Recipient0 = "<" ++ Recipient ++ ">",
case A of
"" ->
Recipient0;
_ ->
A ++ ", " ++ Recipient0
end
end, "", Recipients),
send_line(Socket, "To: " ++ RecipientList),
send_line(Socket, "Subject: " ++ Subject),
lists:foreach(fun(Header) ->
send_line(Socket, escape(Header))
end, Headers),
send_line(Socket, ""),
MessageLines = split(Message),
lists:foreach(fun(MessageLine) ->
send_line(Socket, escape(MessageLine))
end, MessageLines),
send_line(Socket, "."),
expect(Socket, 250).
%%% --------- UTILITIES ----------
split(String) ->
split(String, [], []).
split([], Line, Lines) ->
lists:reverse([lists:reverse(Line) | Lines]);
split([$\n | Tail], Line, Lines) ->
split(Tail, [], [lists:reverse(Line) | Lines]);
split([Head | Tail], Line, Lines) ->
split(Tail, [Head | Line], Lines).
%% @spec get_line(socket()) -> string()
%% @doc Gets a line of text from the given socket.
get_line(Socket) ->
{ok, L} = gen_tcp:recv(Socket, 0),
S = openflax.string:chomp(L),
% openflax.log:write("<< ~s", [S]),
S.
%% @spec expect(socket(), integer()) -> ok
%% @doc Expects a given return-code from the SMTP server. If it does not
%% match what is given, an error is thrown.
expect(Socket, Code) ->
CodeString = openflax.string:from_term(Code),
% openflax.log:write("expecting ~p", [Code]),
Line = get_line(Socket),
case string:str(Line, CodeString) of
1 ->
ok;
_ ->
throw({'EXIT', {response, Line}})
end.
%% @spec send_line(socket(), string()) -> ok
%% @doc Sends a line of text to the given socket.
send_line(Socket, Line) ->
% openflax.log:write(">> ~s", [Line]),
gen_tcp:send(Socket, Line ++ eol()), ok.
%% @spec escape(string()) -> string()
%% @doc Escapes a line per RFC 821, prepending a period if need be.
escape("." ++ RestOfString) -> ".." ++ RestOfString;
escape(String) -> String.
%% @spec get_opt(Options::[option()], Key::atom(), Default::term()) -> term()
%% @doc Gets an SMTP option. If the specified option in present
%% in the list, that value is used; if not, the given default is used.
get_opt(Options, Key, Default) ->
case lists:keysearch(Key, 1, Options) of
{value, {Key, P}} ->
P;
_ ->
Default
end.
%% @spec eol() -> string()
%% @doc Returns the internet line terminator sequence.
eol() ->
"\r\n".
%%% END of openflax/handler/email.erl %%%