Skip to content

Commit b56b1fd

Browse files
committed
Added Erlang version
1 parent f0c5e22 commit b56b1fd

9 files changed

+271
-0
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ All implementations have roughly the same functionality, which is at the "hello
88

99
## Running
1010

11+
### Erlang
12+
13+
cd erlang
14+
./compile
15+
./start
16+
1> application:start(smtpd).
17+
1118
### Go
1219

1320
cd go

erlang/compile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/sh
2+
erlc -o ebin src/*.erl
3+

erlang/ebin/smtpd.app

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{application, smtpd,
2+
[{description, "A simple SMTP server"},
3+
{vsn, "0.1.0"},
4+
{modules, [smtpd_app,
5+
smtpd_sup,
6+
smtp_server, smtp_session]},
7+
{registered, [smtpd_sup]},
8+
{applications, [kernel, stdlib]},
9+
{mod, {smtpd_app, []}}
10+
]}.

erlang/src/smtp_server.erl

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
%%%
2+
%%% Simple SMTP server in Erlang
3+
%%%
4+
%%% Author: Maarten Oelering
5+
%%%
6+
7+
-module(smtp_server).
8+
9+
-behaviour(gen_server).
10+
11+
%% API
12+
-export([start_link/1]).
13+
14+
%% callbacks
15+
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
16+
terminate/2, code_change/3]).
17+
18+
-record(state, {lsock, hostname, session}).
19+
20+
%%%===================================================================
21+
%%% API
22+
%%%===================================================================
23+
24+
start_link({LSock, Hostname}) ->
25+
gen_server:start_link(?MODULE, [{LSock, Hostname}], []).
26+
27+
%%%===================================================================
28+
%%% callbacks
29+
%%%===================================================================
30+
31+
init([{LSock, Hostname}]) ->
32+
% force timeout and wait for connect in handle_info/2
33+
{ok, #state{lsock = LSock, hostname = Hostname}, 0}.
34+
35+
handle_call(Request, _From, State) ->
36+
Reply = {ok, Request},
37+
{reply, Reply, State}.
38+
39+
handle_cast(stop, State) ->
40+
{stop, normal, State}.
41+
42+
%% data received on socket
43+
handle_info({tcp, _Socket, Data}, #state{session = Session} = State) ->
44+
case smtp_session:data_line(Session, Data) of
45+
{ok, NewSession} ->
46+
{noreply, State#state{session = NewSession}};
47+
{stop, NewSession} ->
48+
{stop, normal, State#state{session = NewSession}}
49+
end;
50+
51+
%% socket closed by peer
52+
handle_info({tcp_closed, _Socket}, State) ->
53+
{stop, normal, State};
54+
55+
handle_info(timeout, #state{lsock = LSock, hostname = Hostname} = State) ->
56+
% wait for connection
57+
{ok, Socket} = gen_tcp:accept(LSock),
58+
{ok, Session} = smtp_session:connect(Socket, Hostname),
59+
% start new acceptor process
60+
smtpd_sup:start_child(),
61+
{noreply, State#state{session = Session}};
62+
63+
handle_info(_Info, State) ->
64+
{noreply, State}.
65+
66+
terminate(_Reason, #state{session = Session} = _State) ->
67+
smtp_session:disconnect(Session),
68+
% ?? gen_tcp:close(Socket),
69+
ok.
70+
71+
code_change(_OldVsn, State, _Extra) ->
72+
{ok, State}.
73+
74+
%%%===================================================================
75+
%%% helpers
76+
%%%===================================================================
77+

erlang/src/smtp_session.erl

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
%%%
2+
%%% Simple SMTP server in Erlang
3+
%%%
4+
%%% Author: Maarten Oelering
5+
%%%
6+
7+
-module(smtp_session).
8+
9+
%% API
10+
-export([connect/2, disconnect/1, data_line/2]).
11+
12+
-record(session, {socket, mode, hostname}).
13+
14+
%%%===================================================================
15+
%%% API
16+
%%%===================================================================
17+
18+
connect(Socket, Hostname) ->
19+
gen_tcp:send(Socket, reply(220, lists:concat([Hostname, " ESMTP"]))),
20+
{ok, #session{socket = Socket, mode = command, hostname = Hostname}}.
21+
22+
disconnect(_Session) ->
23+
{ok}.
24+
25+
data_line(#session{mode = command} = Session, Data) ->
26+
Command = string:to_upper(string:substr(Data, 1, 4)),
27+
case Command of
28+
"HELO" ->
29+
send(Session, reply(250, Session#session.hostname)),
30+
{ok, Session};
31+
"EHLO" ->
32+
send(Session, reply(250, Session#session.hostname)),
33+
{ok, Session};
34+
"MAIL" ->
35+
send(Session, reply(250, "OK")),
36+
{ok, Session};
37+
"RCPT" ->
38+
send(Session, reply(250, "OK")),
39+
{ok, Session};
40+
"DATA" ->
41+
send(Session, reply(354, "End data with <CR><LF>.<CR><LF>")),
42+
{ok, Session#session{mode = data}};
43+
"QUIT" ->
44+
send(Session, reply(221, lists:concat([Session#session.hostname, " closing connection"]))),
45+
{stop, Session};
46+
_ ->
47+
send(Session, reply(500, "unrecognized command")),
48+
{ok, Session}
49+
end;
50+
51+
data_line(#session{mode = data} = Session, Data) ->
52+
case Data of
53+
".\r\n" ->
54+
send(Session, reply(250, "OK")),
55+
{ok, Session#session{mode = command}};
56+
_ ->
57+
{ok, Session}
58+
end.
59+
60+
%%%===================================================================
61+
%%% Implementation
62+
%%%===================================================================
63+
64+
send(#session{socket = Socket}, Reply) ->
65+
gen_tcp:send(Socket, Reply).
66+
67+
%% construct multi or singleline reply
68+
69+
reply(_, []) -> [];
70+
71+
reply(Code, [Tail]) when is_list(Tail) ->
72+
reply(Code, Tail);
73+
74+
reply(Code, [Head | Tail]) when is_list(Head) ->
75+
[lists:concat([Code, "-", Head, "\r\n"]) | reply(Code, Tail)];
76+
77+
reply(Code, Text) ->
78+
[lists:concat([Code, " ", Text, "\r\n"])].
79+
80+

erlang/src/smtpd_app.erl

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
%%%
2+
%%% Simple SMTP server in Erlang
3+
%%%
4+
%%% Author: Maarten Oelering
5+
%%%
6+
7+
-module(smtpd_app).
8+
9+
-behaviour(application).
10+
11+
-export([start/2, stop/1]).
12+
13+
-define(DEFAULT_PORT, 2525).
14+
15+
start(_Type, _StartArgs) ->
16+
% open listen socket here, avoiding adding code to supervisors
17+
Port = case application:get_env(tcp_interface, port) of
18+
{ok, P} -> P;
19+
undefined -> ?DEFAULT_PORT
20+
end,
21+
% TODO: binary, {reuseaddr, true}, {keepalive, true}, {backlog, 30}
22+
{ok, LSock} = gen_tcp:listen(Port, [{active, true}, {packet, line}, {reuseaddr, true}]),
23+
% TODO: check for {error, Reason} return
24+
25+
{ok, Hostname} = inet:gethostname(),
26+
27+
% start supervisor
28+
case smtpd_sup:start_link({LSock, Hostname}) of
29+
{ok, Pid} ->
30+
io:format("server started~n"),
31+
% start first accept server
32+
smtpd_sup:start_child(),
33+
{ok, Pid};
34+
Other ->
35+
{error, Other}
36+
end.
37+
38+
stop(_State) ->
39+
ok.
40+

erlang/src/smtpd_sup.erl

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
%%%
2+
%%% Simple SMTP server in Erlang
3+
%%%
4+
%%% Author: Maarten Oelering
5+
%%%
6+
7+
-module(smtpd_sup).
8+
9+
-behaviour(supervisor).
10+
11+
%% API
12+
-export([start_link/1, start_child/0]).
13+
14+
%% callbacks
15+
-export([init/1]).
16+
17+
%%%===================================================================
18+
%%% API
19+
%%%===================================================================
20+
21+
start_link({LSock, Hostname}) ->
22+
supervisor:start_link({local, ?MODULE}, ?MODULE, [{LSock, Hostname}]).
23+
24+
start_child() ->
25+
supervisor:start_child(?MODULE, []).
26+
27+
%%%===================================================================
28+
%%% callbacks
29+
%%%===================================================================
30+
31+
init([{LSock, Hostname}]) ->
32+
SupervisorSpec = {simple_one_for_one, 0, 1},
33+
ChildSpec = {smtp_server, {smtp_server, start_link, [{LSock, Hostname}]},
34+
temporary, brutal_kill, worker, [smtp_server]},
35+
{ok, {SupervisorSpec, [ChildSpec]}}.
36+

erlang/start

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/sh
2+
echo ""
3+
echo "At the prompt, to start enter:"
4+
echo "1> application:start(smtpd)."
5+
echo "and to stop:"
6+
echo "2> q()."
7+
echo ""
8+
erl -pa ebin
9+

results.md

+9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
### Erlang
2+
3+
$ time smtp-source -c -d -l 32000 -m 10000 -N -s 30 127.0.0.1:2525
4+
10000
5+
6+
real 0m2.693s
7+
user 0m0.291s
8+
sys 0m1.239s
9+
110
### Go
211

312
$ time smtp-source -c -d -l 32000 -m 10000 -N -s 30 127.0.0.1:2525

0 commit comments

Comments
 (0)