-
Notifications
You must be signed in to change notification settings - Fork 272
/
Copy pathgen_smtp_server.erl
122 lines (108 loc) · 5.11 KB
/
gen_smtp_server.erl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
%%% vim:ts=2:sw=2:et
%%% Copyright 2009 Andrew Thompson <andrew@hijacked.us>. All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions are met:
%%%
%%% 1. Redistributions of source code must retain the above copyright notice,
%%% this list of conditions and the following disclaimer.
%%% 2. 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.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``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 FREEBSD PROJECT 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 Setup ranch socket acceptor for gen_smtp_server_session
-module(gen_smtp_server).
-define(PORT, 2525).
-include_lib("hut/include/hut.hrl").
%% External API
-export([
start/3, start/2, start/1,
stop/1, child_spec/3, sessions/1]).
-export_type([options/0]).
-type server_name() :: any().
-type options() ::
[{domain, string()}
| {address, inet:ip4_address()}
| {family, inet | inet6}
| {port, inet:port_number()}
| {protocol, 'tcp' | 'ssl'}
| {ranch_opts, ranch:opts()}
| {sessionoptions, gen_smtp_server_session:options()}].
%% @doc Start the listener as a registered process with callback module `Module' with options `Options' linked to no process.
-spec start(ServerName :: server_name(),
CallbackModule :: module(),
Options :: options()) -> {'ok', pid()} | {'error', any()}.
start(ServerName, CallbackModule, Options) when is_list(Options) ->
case convert_options(CallbackModule, Options) of
{ok, Transport, TransportOpts, ProtocolOpts} ->
ranch:start_listener(
ServerName, Transport, TransportOpts, gen_smtp_server_session, ProtocolOpts);
{error, Reason} -> {error, Reason}
end.
child_spec(ServerName, CallbackModule, Options) ->
case convert_options(CallbackModule, Options) of
{ok, Transport, TransportOpts, ProtocolOpts} ->
ranch:child_spec(
ServerName, Transport, TransportOpts, gen_smtp_server_session, ProtocolOpts);
{error, Reason} ->
% `supervisor:child_spec' is not compatible with ok/error tuples.
% This error is likely to occur when starting the application,
% so the user can sort out the configuration parameters and try again.
erlang:error(Reason)
end.
convert_options(CallbackModule, Options) ->
Transport = case proplists:get_value(protocol, Options, tcp) of
tcp -> ranch_tcp;
ssl -> ranch_ssl
end,
Family = proplists:get_value(family, Options, inet),
Address = proplists:get_value(address, Options, {0, 0, 0, 0}),
Port = proplists:get_value(port, Options, ?PORT),
Hostname = proplists:get_value(domain, Options, smtp_util:guess_FQDN()),
ProtocolOpts = proplists:get_value(sessionoptions, Options, []),
EmailTransferProtocol = proplists:get_value(protocol, ProtocolOpts, smtp),
case {EmailTransferProtocol, Port} of
{lmtp, 25} ->
?log(error, "LMTP is different from SMTP, it MUST NOT be used on the TCP port 25"),
% Error defined in section 5 of https://tools.ietf.org/html/rfc2033
{error, invalid_lmtp_port};
_ ->
ProtocolOpts1 = {CallbackModule, [{hostname, Hostname} | ProtocolOpts]},
RanchOpts = proplists:get_value(ranch_opts, Options, #{}),
SocketOpts = maps:get(socket_opts, RanchOpts, []),
TransportOpts = RanchOpts#{
socket_opts =>
[{port, Port},
{ip, Address},
{keepalive, true},
%% binary, {active, false}, {reuseaddr, true} - ranch defaults
Family
| SocketOpts]},
{ok, Transport, TransportOpts, ProtocolOpts1}
end.
%% @doc Start the listener with callback module `Module' with options `Options' linked to no process.
-spec start(CallbackModule :: module(), Options :: options()) -> {'ok', pid()} | 'ignore' | {'error', any()}.
start(CallbackModule, Options) when is_list(Options) ->
start(?MODULE, CallbackModule, Options).
%% @doc Start the listener with callback module `Module' with default options linked to no process.
-spec start(CallbackModule :: atom()) -> {'ok', pid()} | 'ignore' | {'error', any()}.
start(CallbackModule) ->
start(CallbackModule, []).
%% @doc Stop the listener pid() `Pid' with reason `normal'.
-spec stop(Name :: server_name()) -> 'ok'.
stop(Name) ->
ranch:stop_listener(Name).
%% @doc Return the list of active SMTP session pids.
-spec sessions(Name :: server_name()) -> [pid()].
sessions(Name) ->
ranch:procs(Name, connections).