%%% 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: %% 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 %%%