PageRenderTime 59ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/src/gen_smtp_server_session.erl

https://github.com/pedroaxl/gen_smtp
Erlang | 2251 lines | 1983 code | 59 blank | 209 comment | 9 complexity | afab91831c9db5e9e76df0b75a6885e7 MD5 | raw file
  1. %%% Copyright 2009 Andrew Thompson <andrew@hijacked.us>. All rights reserved.
  2. %%%
  3. %%% Redistribution and use in source and binary forms, with or without
  4. %%% modification, are permitted provided that the following conditions are met:
  5. %%%
  6. %%% 1. Redistributions of source code must retain the above copyright notice,
  7. %%% this list of conditions and the following disclaimer.
  8. %%% 2. Redistributions in binary form must reproduce the above copyright
  9. %%% notice, this list of conditions and the following disclaimer in the
  10. %%% documentation and/or other materials provided with the distribution.
  11. %%%
  12. %%% THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY EXPRESS OR
  13. %%% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  14. %%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  15. %%% EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  16. %%% INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  17. %%% (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  18. %%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  19. %%% ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  20. %%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  21. %%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22. %% @doc A Per-connection SMTP server, extensible via a callback module.
  23. -module(gen_smtp_server_session).
  24. -behaviour(gen_server).
  25. -ifdef(TEST).
  26. -import(smtp_util, [compute_cram_digest/2]).
  27. -include_lib("eunit/include/eunit.hrl").
  28. -endif.
  29. -define(MAXIMUMSIZE, 10485760). %10mb
  30. -define(BUILTIN_EXTENSIONS, [{"SIZE", "10485670"}, {"8BITMIME", true}, {"PIPELINING", true}]).
  31. -define(TIMEOUT, 180000). % 3 minutes
  32. %% External API
  33. -export([start_link/3, start/3]).
  34. %% gen_server callbacks
  35. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
  36. code_change/3]).
  37. -export([behaviour_info/1]).
  38. -record(envelope,
  39. {
  40. from :: string() | 'undefined',
  41. to = [] :: [string()],
  42. data = <<>> :: binary(),
  43. expectedsize = 0 :: pos_integer() | 0,
  44. auth = {[], []} :: {string() | [], string() | []} % {"username", "password"}
  45. }
  46. ).
  47. -record(state,
  48. {
  49. socket = erlang:error({undefined, socket}) :: port() | tuple(),
  50. module = erlang:error({undefined, module}) :: atom(),
  51. envelope = undefined :: 'undefined' | #envelope{},
  52. extensions = [] :: [{string(), string()}],
  53. waitingauth = false :: 'false' | 'plain' | 'login' | 'cram-md5',
  54. authdata :: 'undefined' | string(),
  55. readmessage = false :: boolean(),
  56. tls = false :: boolean(),
  57. callbackstate :: any(),
  58. options = [] :: [tuple()]
  59. }
  60. ).
  61. behaviour_info(callbacks) ->
  62. [{init,4},
  63. {terminate,2},
  64. {code_change,3},
  65. {handle_HELO,2},
  66. {handle_EHLO,3},
  67. {handle_MAIL,2},
  68. {handle_MAIL_extension,2},
  69. {handle_RCPT,2},
  70. {handle_RCPT_extension,2},
  71. {handle_DATA,4},
  72. {handle_RSET,1},
  73. {handle_VRFY,2},
  74. {handle_other,3}];
  75. behaviour_info(_Other) ->
  76. undefined.
  77. -spec(start_link/3 :: (Socket :: port(), Module :: atom(), Options :: [tuple()]) -> {'ok', pid()} | 'ignore' | {'error', any()}).
  78. start_link(Socket, Module, Options) ->
  79. gen_server:start_link(?MODULE, [Socket, Module, Options], []).
  80. -spec(start/3 :: (Socket :: port(), Module :: atom(), Options :: [tuple()]) -> {'ok', pid()} | 'ignore' | {'error', any()}).
  81. start(Socket, Module, Options) ->
  82. gen_server:start(?MODULE, [Socket, Module, Options], []).
  83. -spec(init/1 :: (Args :: list()) -> {'ok', #state{}} | {'stop', any()} | 'ignore').
  84. init([Socket, Module, Options]) ->
  85. {ok, {PeerName, _Port}} = socket:peername(Socket),
  86. case Module:init(proplists:get_value(hostname, Options, smtp_util:guess_FQDN()), proplists:get_value(sessioncount, Options, 0), PeerName, proplists:get_value(callbackoptions, Options, [])) of
  87. {ok, Banner, CallbackState} ->
  88. socket:send(Socket, io_lib:format("220 ~s\r\n", [Banner])),
  89. socket:active_once(Socket),
  90. {ok, #state{socket = Socket, module = Module, options = Options, callbackstate = CallbackState}, ?TIMEOUT};
  91. {stop, Reason, Message} ->
  92. socket:send(Socket, Message ++ "\r\n"),
  93. socket:close(Socket),
  94. {stop, Reason};
  95. ignore ->
  96. socket:close(Socket),
  97. ignore
  98. end.
  99. %% @hidden
  100. handle_call(stop, _From, State) ->
  101. {stop, normal, ok, State};
  102. handle_call(Request, _From, State) ->
  103. {reply, {unknown_call, Request}, State}.
  104. %% @hidden
  105. handle_cast(_Msg, State) ->
  106. {noreply, State}.
  107. handle_info({receive_data, {error, size_exceeded}}, #state{socket = Socket, readmessage = true} = State) ->
  108. socket:send(Socket, "552 Message too large\r\n"),
  109. socket:active_once(Socket),
  110. {noreply, State#state{readmessage = false, envelope = #envelope{}}, ?TIMEOUT};
  111. handle_info({receive_data, {error, bare_newline}}, #state{socket = Socket, readmessage = true} = State) ->
  112. socket:send(Socket, "451 Bare newline detected\r\n"),
  113. io:format("bare newline detected: ~p~n", [self()]),
  114. socket:active_once(Socket),
  115. {noreply, State#state{readmessage = false, envelope = #envelope{}}, ?TIMEOUT};
  116. handle_info({receive_data, Body, Rest}, #state{socket = Socket, readmessage = true, envelope = Env, module=Module,
  117. callbackstate = OldCallbackState, extensions = Extensions} = State) ->
  118. % send the remainder of the data...
  119. case Rest of
  120. <<>> -> ok; % no remaining data
  121. _ -> self() ! {socket:get_proto(Socket), Socket, Rest}
  122. end,
  123. socket:setopts(Socket, [{packet, line}]),
  124. Envelope = Env#envelope{data = Body},% size = length(Body)},
  125. %io:format("received body from child process, remainder was ~p (~p)~n", [Rest, self()]),
  126. %handle_info({_Proto, Socket, <<".\r\n">>}, #state{readmessage = true, envelope = Env, module = Module} = State) ->
  127. %io:format("done reading message~n"),
  128. %io:format("entire message~n~s~n", [Envelope#envelope.data]),
  129. %Envelope = Env#envelope{data = list_to_binary(lists:reverse(Env#envelope.data))},
  130. Valid = case has_extension(Extensions, "SIZE") of
  131. {true, Value} ->
  132. case byte_size(Envelope#envelope.data) > list_to_integer(Value) of
  133. true ->
  134. socket:send(Socket, "552 Message too large\r\n"),
  135. socket:active_once(Socket),
  136. false;
  137. false ->
  138. true
  139. end;
  140. false ->
  141. true
  142. end,
  143. case Valid of
  144. true ->
  145. case Module:handle_DATA(Envelope#envelope.from, Envelope#envelope.to, Envelope#envelope.data, OldCallbackState) of
  146. {ok, Reference, CallbackState} ->
  147. socket:send(Socket, io_lib:format("250 queued as ~s\r\n", [Reference])),
  148. socket:active_once(Socket),
  149. {noreply, State#state{readmessage = false, envelope = #envelope{}, callbackstate = CallbackState}, ?TIMEOUT};
  150. {error, Message, CallbackState} ->
  151. socket:send(Socket, Message++"\r\n"),
  152. socket:active_once(Socket),
  153. {noreply, State#state{readmessage = false, envelope = #envelope{}, callbackstate = CallbackState}, ?TIMEOUT}
  154. end;
  155. false ->
  156. % might not even be able to get here anymore...
  157. {noreply, State#state{readmessage = false, envelope = #envelope{}}, ?TIMEOUT}
  158. end;
  159. handle_info({_SocketType, Socket, Packet}, State) ->
  160. case handle_request(parse_request(Packet), State) of
  161. {ok, #state{envelope = Envelope, extensions = Extensions, options = Options, readmessage = true} = NewState} ->
  162. MaxSize = case has_extension(Extensions, "SIZE") of
  163. {true, Value} ->
  164. list_to_integer(Value);
  165. false ->
  166. ?MAXIMUMSIZE
  167. end,
  168. Session = self(),
  169. Size = 0,
  170. socket:setopts(Socket, [{packet, raw}]),
  171. spawn_opt(fun() -> receive_data([],
  172. Socket, {0, Envelope#envelope.expectedsize div 2}, Size, MaxSize, Session, Options) end,
  173. [link, {fullsweep_after, 0}]),
  174. {noreply, NewState, ?TIMEOUT};
  175. {ok, NewState} ->
  176. socket:active_once(NewState#state.socket),
  177. {noreply, NewState, ?TIMEOUT};
  178. {stop, Reason, NewState} ->
  179. {stop, Reason, NewState}
  180. end;
  181. handle_info({tcp_closed, _Socket}, State) ->
  182. {stop, normal, State};
  183. handle_info({ssl_closed, _Socket}, State) ->
  184. {stop, normal, State};
  185. handle_info(timeout, #state{socket = Socket} = State) ->
  186. socket:send(Socket, "421 Error: timeout exceeded\r\n"),
  187. socket:close(Socket),
  188. {stop, normal, State};
  189. handle_info(Info, State) ->
  190. io:format("unhandled info message ~p~n", [Info]),
  191. {noreply, State}.
  192. %% @hidden
  193. -spec(terminate/2 :: (Reason :: any(), State :: #state{}) -> 'ok').
  194. terminate(Reason, State) ->
  195. socket:close(State#state.socket),
  196. (State#state.module):terminate(Reason, State#state.callbackstate).
  197. %% @hidden
  198. code_change(OldVsn, #state{module = Module} = State, Extra) ->
  199. % TODO - this should probably be the callback module's version or its checksum
  200. CallbackState =
  201. case catch Module:code_change(OldVsn, State#state.callbackstate, Extra) of
  202. {ok, NewCallbackState} -> NewCallbackState;
  203. _ -> State#state.callbackstate
  204. end,
  205. {ok, State#state{callbackstate = CallbackState}}.
  206. -spec(parse_request/1 :: (Packet :: binary()) -> {string(), list()}).
  207. parse_request(Packet) ->
  208. Request = binstr:strip(binstr:strip(binstr:strip(binstr:strip(Packet, right, $\n), right, $\r), right, $\s), left, $\s),
  209. case binstr:strchr(Request, $\s) of
  210. 0 ->
  211. % io:format("got a ~s request~n", [Request]),
  212. case binstr:to_upper(Request) of
  213. <<"QUIT">> = Res -> {Res, <<>>};
  214. <<"DATA">> = Res -> {Res, <<>>};
  215. % likely a base64-encoded client reply
  216. _ -> {Request, <<>>}
  217. end;
  218. Index ->
  219. Verb = binstr:substr(Request, 1, Index - 1),
  220. Parameters = binstr:strip(binstr:substr(Request, Index + 1), left, $\s),
  221. %io:format("got a ~s request with parameters ~s~n", [Verb, Parameters]),
  222. {binstr:to_upper(Verb), Parameters}
  223. end.
  224. -spec(handle_request/2 :: ({Verb :: string(), Args :: string()}, State :: #state{}) -> {'ok', #state{}} | {'stop', any(), #state{}}).
  225. handle_request({<<>>, _Any}, #state{socket = Socket} = State) ->
  226. socket:send(Socket, "500 Error: bad syntax\r\n"),
  227. {ok, State};
  228. handle_request({<<"HELO">>, <<>>}, #state{socket = Socket} = State) ->
  229. socket:send(Socket, "501 Syntax: HELO hostname\r\n"),
  230. {ok, State};
  231. handle_request({<<"HELO">>, Hostname}, #state{socket = Socket, options = Options, module = Module, callbackstate = OldCallbackState} = State) ->
  232. case Module:handle_HELO(Hostname, OldCallbackState) of
  233. {ok, CallbackState} ->
  234. socket:send(Socket, io_lib:format("250 ~s\r\n", [proplists:get_value(hostname, Options, smtp_util:guess_FQDN())])),
  235. {ok, State#state{envelope = #envelope{}, callbackstate = CallbackState}};
  236. {error, Message, CallbackState} ->
  237. socket:send(Socket, Message ++ "\r\n"),
  238. {ok, State#state{callbackstate = CallbackState}}
  239. end;
  240. handle_request({<<"EHLO">>, <<>>}, #state{socket = Socket} = State) ->
  241. socket:send(Socket, "501 Syntax: EHLO hostname\r\n"),
  242. {ok, State};
  243. handle_request({<<"EHLO">>, Hostname}, #state{socket = Socket, options = Options, module = Module, callbackstate = OldCallbackState, tls = Tls} = State) ->
  244. case Module:handle_EHLO(Hostname, ?BUILTIN_EXTENSIONS, OldCallbackState) of
  245. {ok, Extensions, CallbackState} ->
  246. case Extensions of
  247. [] ->
  248. socket:send(Socket, io_lib:format("250 ~s\r\n", [proplists:get_value(hostname, Options, smtp_util:guess_FQDN())])),
  249. {ok, State#state{extensions = Extensions, callbackstate = CallbackState}};
  250. _Else ->
  251. F =
  252. fun({E, true}, {Pos, Len, Acc}) when Pos =:= Len ->
  253. {Pos, Len, string:concat(string:concat(string:concat(Acc, "250 "), E), "\r\n")};
  254. ({E, Value}, {Pos, Len, Acc}) when Pos =:= Len ->
  255. {Pos, Len, string:concat(Acc, io_lib:format("250 ~s ~s\r\n", [E, Value]))};
  256. ({E, true}, {Pos, Len, Acc}) ->
  257. {Pos+1, Len, string:concat(string:concat(string:concat(Acc, "250-"), E), "\r\n")};
  258. ({E, Value}, {Pos, Len, Acc}) ->
  259. {Pos+1, Len, string:concat(Acc, io_lib:format("250-~s ~s\r\n", [E, Value]))}
  260. end,
  261. Extensions2 = case Tls of
  262. true ->
  263. Extensions -- [{"STARTTLS", true}];
  264. false ->
  265. Extensions
  266. end,
  267. {_, _, Response} = lists:foldl(F, {1, length(Extensions2), string:concat(string:concat("250-", proplists:get_value(hostname, Options, smtp_util:guess_FQDN())), "\r\n")}, Extensions2),
  268. socket:send(Socket, Response),
  269. {ok, State#state{extensions = Extensions2, envelope = #envelope{}, callbackstate = CallbackState}}
  270. end;
  271. {error, Message, CallbackState} ->
  272. socket:send(Socket, Message++"\r\n"),
  273. {ok, State#state{callbackstate = CallbackState}}
  274. end;
  275. handle_request({<<"AUTH">>, _Args}, #state{envelope = undefined, socket = Socket} = State) ->
  276. socket:send(Socket, "503 Error: send EHLO first\r\n"),
  277. {ok, State};
  278. handle_request({<<"AUTH">>, Args}, #state{socket = Socket, extensions = Extensions, envelope = Envelope, options = Options} = State) ->
  279. case binstr:strchr(Args, $\s) of
  280. 0 ->
  281. AuthType = Args,
  282. Parameters = false;
  283. Index ->
  284. AuthType = binstr:substr(Args, 1, Index - 1),
  285. Parameters = binstr:strip(binstr:substr(Args, Index + 1), left, $\s)
  286. end,
  287. case has_extension(Extensions, "AUTH") of
  288. false ->
  289. socket:send(Socket, "502 Error: AUTH not implemented\r\n"),
  290. {ok, State};
  291. {true, AvailableTypes} ->
  292. case lists:member(string:to_upper(binary_to_list(AuthType)), string:tokens(AvailableTypes, " ")) of
  293. false ->
  294. socket:send(Socket, "504 Unrecognized authentication type\r\n"),
  295. {ok, State};
  296. true ->
  297. case binstr:to_upper(AuthType) of
  298. <<"LOGIN">> ->
  299. % socket:send(Socket, "334 " ++ base64:encode_to_string("Username:")),
  300. socket:send(Socket, "334 VXNlcm5hbWU6\r\n"),
  301. {ok, State#state{waitingauth = 'login', envelope = Envelope#envelope{auth = {[], []}}}};
  302. <<"PLAIN">> when Parameters =/= false ->
  303. % TODO - duplicated below in handle_request waitingauth PLAIN
  304. case string:tokens(base64:decode_to_string(Parameters), [0]) of
  305. [_Identity, Username, Password] ->
  306. try_auth('plain', Username, Password, State);
  307. [Username, Password] ->
  308. try_auth('plain', Username, Password, State);
  309. _ ->
  310. % TODO error
  311. {ok, State}
  312. end;
  313. <<"PLAIN">> ->
  314. socket:send(Socket, "334\r\n"),
  315. {ok, State#state{waitingauth = 'plain', envelope = Envelope#envelope{auth = {[], []}}}};
  316. <<"CRAM-MD5">> ->
  317. crypto:start(), % ensure crypto is started, we're gonna need it
  318. String = smtp_util:get_cram_string(proplists:get_value(hostname, Options, smtp_util:guess_FQDN())),
  319. socket:send(Socket, "334 "++String++"\r\n"),
  320. {ok, State#state{waitingauth = 'cram-md5', authdata=base64:decode_to_string(String), envelope = Envelope#envelope{auth = {[], []}}}}
  321. %"DIGEST-MD5" -> % TODO finish this? (see rfc 2831)
  322. %crypto:start(), % ensure crypto is started, we're gonna need it
  323. %Nonce = get_digest_nonce(),
  324. %Response = io_lib:format("nonce=\"~s\",realm=\"~s\",qop=\"auth\",algorithm=md5-sess,charset=utf-8", Nonce, State#state.hostname),
  325. %socket:send(Socket, "334 "++Response++"\r\n"),
  326. %{ok, State#state{waitingauth = "DIGEST-MD5", authdata=base64:decode_to_string(Nonce), envelope = Envelope#envelope{auth = {[], []}}}}
  327. end
  328. end
  329. end;
  330. % the client sends a response to auth-cram-md5
  331. handle_request({Username64, <<>>}, #state{waitingauth = 'cram-md5', envelope = #envelope{auth = {[],[]}}, authdata = AuthData} = State) ->
  332. case string:tokens(base64:decode_to_string(Username64), " ") of
  333. [Username, Digest] ->
  334. try_auth('cram-md5', Username, {Digest, AuthData}, State#state{authdata=undefined});
  335. _ ->
  336. % TODO error
  337. {ok, State#state{waitingauth=false, authdata=undefined}}
  338. end;
  339. % the client sends a \0username\0password response to auth-plain
  340. handle_request({Username64, <<>>}, #state{waitingauth = 'plain', envelope = #envelope{auth = {[],[]}}} = State) ->
  341. case string:tokens(base64:decode_to_string(Username64), [0]) of
  342. [_Identity, Username, Password] ->
  343. try_auth('plain', Username, Password, State);
  344. [Username, Password] ->
  345. try_auth('plain', Username, Password, State);
  346. _ ->
  347. % TODO error
  348. {ok, State#state{waitingauth=false}}
  349. end;
  350. % the client sends a username response to auth-login
  351. handle_request({Username64, <<>>}, #state{socket = Socket, waitingauth = 'login', envelope = #envelope{auth = {[],[]}}} = State) ->
  352. Envelope = State#state.envelope,
  353. Username = base64:decode_to_string(Username64),
  354. % socket:send(Socket, "334 " ++ base64:encode_to_string("Password:")),
  355. socket:send(Socket, "334 UGFzc3dvcmQ6\r\n"),
  356. % store the provided username in envelope.auth
  357. NewState = State#state{envelope = Envelope#envelope{auth = {Username, []}}},
  358. {ok, NewState};
  359. % the client sends a password response to auth-login
  360. handle_request({Password64, <<>>}, #state{waitingauth = 'login', envelope = #envelope{auth = {Username,[]}}} = State) ->
  361. Password = base64:decode_to_string(Password64),
  362. try_auth('login', Username, Password, State);
  363. handle_request({<<"MAIL">>, _Args}, #state{envelope = undefined, socket = Socket} = State) ->
  364. socket:send(Socket, "503 Error: send HELO/EHLO first\r\n"),
  365. {ok, State};
  366. handle_request({<<"MAIL">>, Args}, #state{socket = Socket, module = Module, envelope = Envelope, callbackstate = OldCallbackState, extensions = Extensions} = State) ->
  367. case Envelope#envelope.from of
  368. undefined ->
  369. case binstr:strpos(binstr:to_upper(Args), "FROM:") of
  370. 1 ->
  371. Address = binstr:strip(binstr:substr(Args, 6), left, $\s),
  372. case parse_encoded_address(binary_to_list(Address)) of
  373. error ->
  374. socket:send(Socket, "501 Bad sender address syntax\r\n"),
  375. {ok, State};
  376. {ParsedAddress, []} ->
  377. %io:format("From address ~s (parsed as ~s)~n", [Address, ParsedAddress]),
  378. case Module:handle_MAIL(ParsedAddress, OldCallbackState) of
  379. {ok, CallbackState} ->
  380. socket:send(Socket, "250 sender Ok\r\n"),
  381. {ok, State#state{envelope = Envelope#envelope{from = ParsedAddress}, callbackstate = CallbackState}};
  382. {error, Message, CallbackState} ->
  383. socket:send(Socket, Message ++ "\r\n"),
  384. {ok, State#state{callbackstate = CallbackState}}
  385. end;
  386. {ParsedAddress, ExtraInfo} ->
  387. %io:format("From address ~s (parsed as ~s) with extra info ~s~n", [Address, ParsedAddress, ExtraInfo]),
  388. Options = [binstr:to_upper(X) || X <- binstr:split(list_to_binary(ExtraInfo), <<" ">>)],
  389. %io:format("options are ~p~n", [Options]),
  390. F = fun(_, {error, Message}) ->
  391. {error, Message};
  392. (<<"SIZE=", Size/binary>>, InnerState) ->
  393. case has_extension(Extensions, "SIZE") of
  394. {true, Value} ->
  395. case list_to_integer(binary_to_list(Size)) > list_to_integer(Value) of
  396. true ->
  397. {error, io_lib:format("552 Estimated message length ~s exceeds limit of ~s\r\n", [Size, Value])};
  398. false ->
  399. InnerState#state{envelope = Envelope#envelope{expectedsize = list_to_integer(binary_to_list(Size))}}
  400. end;
  401. false ->
  402. {error, "555 Unsupported option SIZE\r\n"}
  403. end;
  404. (<<"BODY=", _BodyType/binary>>, InnerState) ->
  405. case has_extension(Extensions, "8BITMIME") of
  406. {true, _} ->
  407. InnerState;
  408. false ->
  409. {error, "555 Unsupported option BODY\r\n"}
  410. end;
  411. (X, InnerState) ->
  412. case Module:handle_MAIL_extension(X, OldCallbackState) of
  413. {ok, CallbackState} ->
  414. InnerState#state{callbackstate = CallbackState};
  415. error ->
  416. {error, io_lib:format("555 Unsupported option: ~s\r\n", [ExtraInfo])}
  417. end
  418. end,
  419. case lists:foldl(F, State, Options) of
  420. {error, Message} ->
  421. %io:format("error: ~s~n", [Message]),
  422. socket:send(Socket, Message),
  423. {ok, State};
  424. NewState ->
  425. %io:format("OK~n"),
  426. case Module:handle_MAIL(ParsedAddress, State#state.callbackstate) of
  427. {ok, CallbackState} ->
  428. socket:send(Socket, "250 sender Ok\r\n"),
  429. {ok, State#state{envelope = Envelope#envelope{from = ParsedAddress}, callbackstate = CallbackState}};
  430. {error, Message, CallbackState} ->
  431. socket:send(Socket, Message ++ "\r\n"),
  432. {ok, NewState#state{callbackstate = CallbackState}}
  433. end
  434. end
  435. end;
  436. _Else ->
  437. socket:send(Socket, "501 Syntax: MAIL FROM:<address>\r\n"),
  438. {ok, State}
  439. end;
  440. _Other ->
  441. socket:send(Socket, "503 Error: Nested MAIL command\r\n"),
  442. {ok, State}
  443. end;
  444. handle_request({<<"RCPT">>, _Args}, #state{envelope = undefined, socket = Socket} = State) ->
  445. socket:send(Socket, "503 Error: need MAIL command\r\n"),
  446. {ok, State};
  447. handle_request({<<"RCPT">>, Args}, #state{socket = Socket, envelope = Envelope, module = Module, callbackstate = OldCallbackState} = State) ->
  448. case binstr:strpos(binstr:to_upper(Args), "TO:") of
  449. 1 ->
  450. Address = binstr:strip(binstr:substr(Args, 4), left, $\s),
  451. case parse_encoded_address(binary_to_list(Address)) of
  452. error ->
  453. socket:send(Socket, "501 Bad recipient address syntax\r\n"),
  454. {ok, State};
  455. {[], _} ->
  456. % empty rcpt to addresses aren't cool
  457. socket:send(Socket, "501 Bad recipient address syntax\r\n"),
  458. {ok, State};
  459. {ParsedAddress, []} ->
  460. %io:format("To address ~s (parsed as ~s)~n", [Address, ParsedAddress]),
  461. case Module:handle_RCPT(ParsedAddress, OldCallbackState) of
  462. {ok, CallbackState} ->
  463. socket:send(Socket, "250 recipient Ok\r\n"),
  464. {ok, State#state{envelope = Envelope#envelope{to = Envelope#envelope.to ++ [ParsedAddress]}, callbackstate = CallbackState}};
  465. {error, Message, CallbackState} ->
  466. socket:send(Socket, Message++"\r\n"),
  467. {ok, State#state{callbackstate = CallbackState}}
  468. end;
  469. {ParsedAddress, ExtraInfo} ->
  470. % TODO - are there even any RCPT extensions?
  471. io:format("To address ~s (parsed as ~s) with extra info ~s~n", [Address, ParsedAddress, ExtraInfo]),
  472. socket:send(Socket, io_lib:format("555 Unsupported option: ~s\r\n", [ExtraInfo])),
  473. {ok, State}
  474. end;
  475. _Else ->
  476. socket:send(Socket, "501 Syntax: RCPT TO:<address>\r\n"),
  477. {ok, State}
  478. end;
  479. handle_request({<<"DATA">>, <<>>}, #state{socket = Socket, envelope = undefined} = State) ->
  480. socket:send(Socket, "503 Error: send HELO/EHLO first\r\n"),
  481. {ok, State};
  482. handle_request({<<"DATA">>, <<>>}, #state{socket = Socket, envelope = Envelope} = State) ->
  483. case {Envelope#envelope.from, Envelope#envelope.to} of
  484. {undefined, _} ->
  485. socket:send(Socket, "503 Error: need MAIL command\r\n"),
  486. {ok, State};
  487. {_, []} ->
  488. socket:send(Socket, "503 Error: need RCPT command\r\n"),
  489. {ok, State};
  490. _Else ->
  491. socket:send(Socket, "354 enter mail, end with line containing only '.'\r\n"),
  492. %io:format("switching to data read mode~n", []),
  493. {ok, State#state{readmessage = true}}
  494. end;
  495. handle_request({<<"RSET">>, _Any}, #state{socket = Socket, envelope = Envelope, module = Module, callbackstate = OldCallbackState} = State) ->
  496. socket:send(Socket, "250 Ok\r\n"),
  497. % if the client sends a RSET before a HELO/EHLO don't give them a valid envelope
  498. NewEnvelope = case Envelope of
  499. undefined -> undefined;
  500. _Something -> #envelope{}
  501. end,
  502. {ok, State#state{envelope = NewEnvelope, callbackstate = Module:handle_RSET(OldCallbackState)}};
  503. handle_request({<<"NOOP">>, _Any}, #state{socket = Socket} = State) ->
  504. socket:send(Socket, "250 Ok\r\n"),
  505. {ok, State};
  506. handle_request({<<"QUIT">>, _Any}, #state{socket = Socket} = State) ->
  507. socket:send(Socket, "221 Bye\r\n"),
  508. {stop, normal, State};
  509. handle_request({<<"VRFY">>, Address}, #state{module= Module, socket = Socket, callbackstate = OldCallbackState} = State) ->
  510. case parse_encoded_address(binary_to_list(Address)) of
  511. {ParsedAddress, []} ->
  512. case Module:handle_VRFY(ParsedAddress, OldCallbackState) of
  513. {ok, Reply, CallbackState} ->
  514. socket:send(Socket, io_lib:format("250 ~s\r\n", [Reply])),
  515. {ok, State#state{callbackstate = CallbackState}};
  516. {error, Message, CallbackState} ->
  517. socket:send(Socket, Message++"\r\n"),
  518. {ok, State#state{callbackstate = CallbackState}}
  519. end;
  520. _Other ->
  521. socket:send(Socket, "501 Syntax: VRFY username/address\r\n"),
  522. {ok, State}
  523. end;
  524. handle_request({<<"STARTTLS">>, <<>>}, #state{socket = Socket, tls=false, extensions = Extensions} = State) ->
  525. case has_extension(Extensions, "STARTTLS") of
  526. {true, _} ->
  527. socket:send(Socket, "220 OK\r\n"),
  528. crypto:start(),
  529. application:start(public_key),
  530. application:start(ssl),
  531. % TODO: certfile and keyfile should be at configurable locations
  532. case socket:to_ssl_server(Socket, [], 5000) of
  533. {ok, NewSocket} ->
  534. %io:format("SSL negotiation sucessful~n"),
  535. {ok, State#state{socket = NewSocket, envelope=undefined,
  536. authdata=undefined, waitingauth=false, readmessage=false,
  537. tls=true}};
  538. {error, Reason} ->
  539. io:format("SSL handshake failed : ~p~n", [Reason]),
  540. socket:send(Socket, "454 TLS negotiation failed\r\n"),
  541. {ok, State}
  542. end;
  543. false ->
  544. socket:send(Socket, "500 Command unrecognized\r\n"),
  545. {ok, State}
  546. end;
  547. handle_request({<<"STARTTLS">>, <<>>}, #state{socket = Socket} = State) ->
  548. socket:send(Socket, "500 TLS already negotiated\r\n"),
  549. {ok, State};
  550. handle_request({<<"STARTTLS">>, _Args}, #state{socket = Socket} = State) ->
  551. socket:send(Socket, "501 Syntax error (no parameters allowed)\r\n"),
  552. {ok, State};
  553. handle_request({Verb, Args}, #state{socket = Socket, module = Module, callbackstate = OldCallbackState} = State) ->
  554. {Message, CallbackState} = Module:handle_other(Verb, Args, OldCallbackState),
  555. socket:send(Socket, Message++"\r\n"),
  556. {ok, State#state{callbackstate = CallbackState}}.
  557. -spec(parse_encoded_address/1 :: (Address :: string()) -> {string(), string()} | 'error').
  558. parse_encoded_address([]) ->
  559. error; % empty
  560. parse_encoded_address("<@" ++ Address) ->
  561. case string:str(Address, ":") of
  562. 0 ->
  563. error; % invalid address
  564. Index ->
  565. parse_encoded_address(string:substr(Address, Index + 1), "", {false, true})
  566. end;
  567. parse_encoded_address("<" ++ Address) ->
  568. parse_encoded_address(Address, "", {false, true});
  569. parse_encoded_address(" " ++ Address) ->
  570. parse_encoded_address(Address);
  571. parse_encoded_address(Address) ->
  572. parse_encoded_address(Address, "", {false, false}).
  573. -spec(parse_encoded_address/3 :: (Address :: string(), Acc :: string(), Flags :: {boolean(), boolean()}) -> {string(), string()}).
  574. parse_encoded_address([], Acc, {_Quotes, false}) ->
  575. {lists:reverse(Acc), []};
  576. parse_encoded_address([], _Acc, {_Quotes, true}) ->
  577. error; % began with angle brackets but didn't end with them
  578. parse_encoded_address(_, Acc, _) when length(Acc) > 129 ->
  579. error; % too long
  580. parse_encoded_address("\\" ++ Tail, Acc, Flags) ->
  581. [H | NewTail] = Tail,
  582. parse_encoded_address(NewTail, [H | Acc], Flags);
  583. parse_encoded_address("\"" ++ Tail, Acc, {false, AB}) ->
  584. parse_encoded_address(Tail, Acc, {true, AB});
  585. parse_encoded_address("\"" ++ Tail, Acc, {true, AB}) ->
  586. parse_encoded_address(Tail, Acc, {false, AB});
  587. parse_encoded_address(">" ++ Tail, Acc, {false, true}) ->
  588. {lists:reverse(Acc), string:strip(Tail, left, $\s)};
  589. parse_encoded_address(">" ++ _Tail, _Acc, {false, false}) ->
  590. error; % ended with angle brackets but didn't begin with them
  591. parse_encoded_address(" " ++ Tail, Acc, {false, false}) ->
  592. {lists:reverse(Acc), string:strip(Tail, left, $\s)};
  593. parse_encoded_address(" " ++ _Tail, _Acc, {false, true}) ->
  594. error; % began with angle brackets but didn't end with them
  595. parse_encoded_address([H | Tail], Acc, {false, AB}) when H >= $0, H =< $9 ->
  596. parse_encoded_address(Tail, [H | Acc], {false, AB}); % digits
  597. parse_encoded_address([H | Tail], Acc, {false, AB}) when H >= $@, H =< $Z ->
  598. parse_encoded_address(Tail, [H | Acc], {false, AB}); % @ symbol and uppercase letters
  599. parse_encoded_address([H | Tail], Acc, {false, AB}) when H >= $a, H =< $z ->
  600. parse_encoded_address(Tail, [H | Acc], {false, AB}); % lowercase letters
  601. parse_encoded_address([H | Tail], Acc, {false, AB}) when H =:= $-; H =:= $.; H =:= $_ ->
  602. parse_encoded_address(Tail, [H | Acc], {false, AB}); % dash, dot, underscore
  603. parse_encoded_address([_H | _Tail], _Acc, {false, _AB}) ->
  604. error;
  605. parse_encoded_address([H | Tail], Acc, Quotes) ->
  606. parse_encoded_address(Tail, [H | Acc], Quotes).
  607. -spec(has_extension/2 :: (Extensions :: [{string(), string()}], Extension :: string()) -> {'true', string()} | 'false').
  608. has_extension(Exts, Ext) ->
  609. Extension = string:to_upper(Ext),
  610. Extensions = [{string:to_upper(X), Y} || {X, Y} <- Exts],
  611. %io:format("extensions ~p~n", [Extensions]),
  612. case proplists:get_value(Extension, Extensions) of
  613. undefined ->
  614. false;
  615. Value ->
  616. {true, Value}
  617. end.
  618. -spec(try_auth/4 :: (AuthType :: 'login' | 'plain' | 'cram-md5', Username :: string(), Credential :: string() | {string(), string()}, State :: #state{}) -> {'ok', #state{}}).
  619. try_auth(AuthType, Username, Credential, #state{module = Module, socket = Socket, envelope = Envelope, callbackstate = OldCallbackState} = State) ->
  620. % clear out waiting auth
  621. NewState = State#state{waitingauth = false, envelope = Envelope#envelope{auth = {[], []}}},
  622. case erlang:function_exported(Module, handle_AUTH, 4) of
  623. true ->
  624. case Module:handle_AUTH(AuthType, Username, Credential, OldCallbackState) of
  625. {ok, CallbackState} ->
  626. socket:send(Socket, "235 Authentication successful.\r\n"),
  627. {ok, NewState#state{callbackstate = CallbackState,
  628. envelope = Envelope#envelope{auth = {Username, Credential}}}};
  629. _Other ->
  630. socket:send(Socket, "535 Authentication failed.\r\n"),
  631. {ok, NewState}
  632. end;
  633. false ->
  634. io:format("Please define handle_AUTH/4 in your server module or remove AUTH from your module extensions~n"),
  635. socket:send(Socket, "535 authentication failed (#5.7.1)\r\n"),
  636. {ok, NewState}
  637. end.
  638. %get_digest_nonce() ->
  639. %A = [io_lib:format("~2.16.0b", [X]) || <<X>> <= erlang:md5(integer_to_list(crypto:rand_uniform(0, 4294967295)))],
  640. %B = [io_lib:format("~2.16.0b", [X]) || <<X>> <= erlang:md5(integer_to_list(crypto:rand_uniform(0, 4294967295)))],
  641. %binary_to_list(base64:encode(lists:flatten(A ++ B))).
  642. %% @doc a tight loop to receive the message body
  643. receive_data(_Acc, _Socket, _, Size, MaxSize, Session, _Options) when Size > MaxSize ->
  644. io:format("message body size ~B exceeded maximum allowed ~B~n", [Size, MaxSize]),
  645. Session ! {receive_data, {error, size_exceeded}};
  646. receive_data(Acc, Socket, {OldCount, OldRecvSize}, Size, MaxSize, Session, Options) ->
  647. {Count, RecvSize} = case Size of
  648. Size when OldCount > 2, OldRecvSize =:= 262144 ->
  649. %io:format("increasing receive size to ~B~n", [1048576]),
  650. {0, 1048576};% 1m
  651. Size when OldCount > 5, OldRecvSize =:= 65536 ->
  652. %io:format("increasing receive size to ~B~n", [262144]),
  653. {0, 262144};% 256k
  654. Size when OldCount > 5, OldRecvSize =:= 8192 ->
  655. %io:format("increasing receive size to ~B~n", [65536]),
  656. {0, 65536};% 64k
  657. Size when OldCount > 2, Size > 8192, OldRecvSize =:= 0 ->
  658. %io:format("increasing receive size to ~B~n", [8192]),
  659. {0, 8192}; % 8k
  660. _ ->
  661. {OldCount + 1, OldRecvSize} % don't change anything
  662. end,
  663. %socket:setopts(Socket, [{packet, raw}]),
  664. case socket:recv(Socket, RecvSize, 1000) of
  665. {ok, Packet} when Acc == [] ->
  666. case check_bare_crlf(Packet, <<>>, proplists:get_value(allow_bare_newlines, Options, false), 0) of
  667. error ->
  668. Session ! {receive_data, {error, bare_newline}};
  669. FixedPacket ->
  670. case binstr:strpos(FixedPacket, "\r\n.\r\n") of
  671. 0 ->
  672. %io:format("received ~B bytes; size is now ~p~n", [RecvSize, Size + size(Packet)]),
  673. %io:format("memory usage: ~p~n", [erlang:process_info(self(), memory)]),
  674. receive_data([FixedPacket | Acc], Socket, {Count, RecvSize}, Size + byte_size(FixedPacket), MaxSize, Session, Options);
  675. Index ->
  676. String = binstr:substr(FixedPacket, 1, Index - 1),
  677. Rest = binstr:substr(FixedPacket, Index+5),
  678. %io:format("memory usage before flattening: ~p~n", [erlang:process_info(self(), memory)]),
  679. Result = list_to_binary(lists:reverse([String | Acc])),
  680. %io:format("memory usage after flattening: ~p~n", [erlang:process_info(self(), memory)]),
  681. Session ! {receive_data, Result, Rest}
  682. end
  683. end;
  684. {ok, Packet} ->
  685. [Last | _] = Acc,
  686. case check_bare_crlf(Packet, Last, proplists:get_value(allow_bare_newlines, Options, false), 0) of
  687. error ->
  688. Session ! {receive_data, {error, bare_newline}};
  689. FixedPacket ->
  690. case binstr:strpos(FixedPacket, "\r\n.\r\n") of
  691. 0 ->
  692. %io:format("received ~B bytes; size is now ~p~n", [RecvSize, Size + size(Packet)]),
  693. %io:format("memory usage: ~p~n", [erlang:process_info(self(), memory)]),
  694. receive_data([FixedPacket | Acc], Socket, {Count, RecvSize}, Size + byte_size(FixedPacket), MaxSize, Session, Options);
  695. Index ->
  696. String = binstr:substr(FixedPacket, 1, Index - 1),
  697. Rest = binstr:substr(FixedPacket, Index+5),
  698. %io:format("memory usage before flattening: ~p~n", [erlang:process_info(self(), memory)]),
  699. Result = list_to_binary(lists:reverse([String | Acc])),
  700. %io:format("memory usage after flattening: ~p~n", [erlang:process_info(self(), memory)]),
  701. Session ! {receive_data, Result, Rest}
  702. end
  703. end;
  704. {error, timeout} when RecvSize =:= 0, length(Acc) > 1 ->
  705. % check that we didn't accidentally receive a \r\n.\r\n split across 2 receives
  706. [A, B | Acc2] = Acc,
  707. Packet = list_to_binary([B, A]),
  708. case binstr:strpos(Packet, "\r\n.\r\n") of
  709. 0 ->
  710. % uh-oh
  711. %io:format("no data on socket, and no DATA terminator, retrying ~p~n", [Session]),
  712. % eventually we'll either get data or a different error, just keep retrying
  713. receive_data(Acc, Socket, {Count - 1, RecvSize}, Size, MaxSize, Session, Options);
  714. Index ->
  715. String = binstr:substr(Packet, 1, Index - 1),
  716. Rest = binstr:substr(Packet, Index+5),
  717. %io:format("memory usage before flattening: ~p~n", [erlang:process_info(self(), memory)]),
  718. Result = list_to_binary(lists:reverse([String | Acc2])),
  719. %io:format("memory usage after flattening: ~p~n", [erlang:process_info(self(), memory)]),
  720. Session ! {receive_data, Result, Rest}
  721. end;
  722. {error, timeout} ->
  723. NewRecvSize = adjust_receive_size_down(Size, RecvSize),
  724. %io:format("timeout when trying to read ~B bytes, lowering receive size to ~B~n", [RecvSize, NewRecvSize]),
  725. receive_data(Acc, Socket, {-5, NewRecvSize}, Size, MaxSize, Session, Options);
  726. {error, Reason} ->
  727. io:format("receive error: ~p~n", [Reason]),
  728. exit(receive_error)
  729. end.
  730. adjust_receive_size_down(_Size, RecvSize) when RecvSize > 262144 ->
  731. 262144;
  732. adjust_receive_size_down(_Size, RecvSize) when RecvSize > 65536 ->
  733. 65536;
  734. adjust_receive_size_down(_Size, RecvSize) when RecvSize > 8192 ->
  735. 8192;
  736. adjust_receive_size_down(_Size, _RecvSize) ->
  737. 0.
  738. check_for_bare_crlf(Bin, Offset) ->
  739. case {re:run(Bin, "(?<!\r)\n", [{capture, none}, {offset, Offset}]), re:run(Bin, "\r(?!\n)", [{capture, none}, {offset, Offset}])} of
  740. {match, _} -> true;
  741. {_, match} -> true;
  742. _ -> false
  743. end.
  744. fix_bare_crlf(Bin, Offset) ->
  745. Options = [{offset, Offset}, {return, binary}, global],
  746. re:replace(re:replace(Bin, "(?<!\r)\n", "\r\n", Options), "\r(?!\n)", "\r\n", Options).
  747. strip_bare_crlf(Bin, Offset) ->
  748. Options = [{offset, Offset}, {return, binary}, global],
  749. re:replace(re:replace(Bin, "(?<!\r)\n", "", Options), "\r(?!\n)", "", Options).
  750. check_bare_crlf(Binary, _, ignore, _) ->
  751. Binary;
  752. check_bare_crlf(<<$\n, _Rest/binary>> = Bin, Prev, Op, Offset) when byte_size(Prev) > 0, Offset == 0 ->
  753. % check if last character of previous was a CR
  754. Lastchar = binstr:substr(Prev, -1),
  755. case Lastchar of
  756. <<"\r">> ->
  757. % okay, check again for the rest
  758. check_bare_crlf(Bin, <<>>, Op, 1);
  759. _ when Op == false -> % not fixing or ignoring them
  760. error;
  761. _ ->
  762. % no dice
  763. check_bare_crlf(Bin, <<>>, Op, 0)
  764. end;
  765. check_bare_crlf(Binary, _Prev, Op, Offset) ->
  766. Last = binstr:substr(Binary, -1),
  767. % is the last character a CR?
  768. case Last of
  769. <<"\r">> ->
  770. % okay, the last character is a CR, we have to assume the next packet contains the corresponding LF
  771. NewBin = binstr:substr(Binary, 1, byte_size(Binary) -1),
  772. case check_for_bare_crlf(NewBin, Offset) of
  773. true when Op == fix ->
  774. list_to_binary([fix_bare_crlf(NewBin, Offset), "\r"]);
  775. true when Op == strip ->
  776. list_to_binary([strip_bare_crlf(NewBin, Offset), "\r"]);
  777. true ->
  778. error;
  779. false ->
  780. Binary
  781. end;
  782. _ ->
  783. case check_for_bare_crlf(Binary, Offset) of
  784. true when Op == fix ->
  785. fix_bare_crlf(Binary, Offset);
  786. true when Op == strip ->
  787. strip_bare_crlf(Binary, Offset);
  788. true ->
  789. error;
  790. false ->
  791. Binary
  792. end
  793. end.
  794. -ifdef(TEST).
  795. parse_encoded_address_test_() ->
  796. [
  797. {"Valid addresses should parse",
  798. fun() ->
  799. ?assertEqual({"God@heaven.af.mil", []}, parse_encoded_address("<God@heaven.af.mil>")),
  800. ?assertEqual({"God@heaven.af.mil", []}, parse_encoded_address("<\\God@heaven.af.mil>")),
  801. ?assertEqual({"God@heaven.af.mil", []}, parse_encoded_address("<\"God\"@heaven.af.mil>")),
  802. ?assertEqual({"God@heaven.af.mil", []}, parse_encoded_address("<@gateway.af.mil,@uucp.local:\"\\G\\o\\d\"@heaven.af.mil>")),
  803. ?assertEqual({"God2@heaven.af.mil", []}, parse_encoded_address("<God2@heaven.af.mil>"))
  804. end
  805. },
  806. {"Addresses that are sorta valid should parse",
  807. fun() ->
  808. ?assertEqual({"God@heaven.af.mil", []}, parse_encoded_address("God@heaven.af.mil")),
  809. ?assertEqual({"God@heaven.af.mil", []}, parse_encoded_address("God@heaven.af.mil ")),
  810. ?assertEqual({"God@heaven.af.mil", []}, parse_encoded_address(" God@heaven.af.mil ")),
  811. ?assertEqual({"God@heaven.af.mil", []}, parse_encoded_address(" <God@heaven.af.mil> "))
  812. end
  813. },
  814. {"Addresses containing unescaped <> that aren't at start/end should fail",
  815. fun() ->
  816. ?assertEqual(error, parse_encoded_address("<<")),
  817. ?assertEqual(error, parse_encoded_address("<God<@heaven.af.mil>"))
  818. end
  819. },
  820. {"Address that begins with < but doesn't end with a > should fail",
  821. fun() ->
  822. ?assertEqual(error, parse_encoded_address("<God@heaven.af.mil")),
  823. ?assertEqual(error, parse_encoded_address("<God@heaven.af.mil "))
  824. end
  825. },
  826. {"Address that begins without < but ends with a > should fail",
  827. fun() ->
  828. ?assertEqual(error, parse_encoded_address("God@heaven.af.mil>"))
  829. end
  830. },
  831. {"Address longer than 129 character should fail",
  832. fun() ->
  833. MegaAddress = lists:seq(97, 122) ++ lists:seq(97, 122) ++ lists:seq(97, 122) ++ "@" ++ lists:seq(97, 122) ++ lists:seq(97, 122),
  834. ?assertEqual(error, parse_encoded_address(MegaAddress))
  835. end
  836. },
  837. {"Address with an invalid route should fail",
  838. fun() ->
  839. ?assertEqual(error, parse_encoded_address("<@gateway.af.mil God@heaven.af.mil>"))
  840. end
  841. },
  842. {"Empty addresses should parse OK",
  843. fun() ->
  844. ?assertEqual({[], []}, parse_encoded_address("<>")),
  845. ?assertEqual({[], []}, parse_encoded_address(" <> "))
  846. end
  847. },
  848. {"Completely empty addresses are an error",
  849. fun() ->
  850. ?assertEqual(error, parse_encoded_address("")),
  851. ?assertEqual(error, parse_encoded_address(" "))
  852. end
  853. },
  854. {"addresses with trailing parameters should return the trailing parameters",
  855. fun() ->
  856. ?assertEqual({"God@heaven.af.mil", "SIZE=100 BODY=8BITMIME"}, parse_encoded_address("<God@heaven.af.mil> SIZE=100 BODY=8BITMIME"))
  857. end
  858. }
  859. ].
  860. parse_request_test_() ->
  861. [
  862. {"Parsing normal SMTP requests",
  863. fun() ->
  864. ?assertEqual({<<"HELO">>, <<>>}, parse_request(<<"HELO\r\n">>)),
  865. ?assertEqual({<<"EHLO">>, <<"hell.af.mil">>}, parse_request(<<"EHLO hell.af.mil\r\n">>)),
  866. ?assertEqual({<<"MAIL">>, <<"FROM:God@heaven.af.mil">>}, parse_request(<<"MAIL FROM:God@heaven.af.mil">>))
  867. end
  868. },
  869. {"Verbs should be uppercased",
  870. fun() ->
  871. ?assertEqual({<<"HELO">>, <<"hell.af.mil">>}, parse_request(<<"helo hell.af.mil">>))
  872. end
  873. },
  874. {"Leading and trailing spaces are removed",
  875. fun() ->
  876. ?assertEqual({<<"HELO">>, <<"hell.af.mil">>}, parse_request(<<" helo hell.af.mil ">>))
  877. end
  878. },
  879. {"Blank lines are blank",
  880. fun() ->
  881. ?assertEqual({<<>>, <<>>}, parse_request(<<"">>))
  882. end
  883. }
  884. ].
  885. smtp_session_test_() ->
  886. {foreach,
  887. local,
  888. fun() ->
  889. Self = self(),
  890. spawn(fun() ->
  891. {ok, ListenSock} = socket:listen(tcp, 9876, [binary]),
  892. {ok, X} = socket:accept(ListenSock),
  893. socket:controlling_process(X, Self),
  894. Self ! X
  895. end),
  896. {ok, CSock} = socket:connect(tcp, "localhost", 9876),
  897. receive
  898. SSock when is_port(SSock) ->
  899. ?debugFmt("Got server side of the socket ~p, client is ~p~n", [SSock, CSock])
  900. end,
  901. {ok, Pid} = gen_smtp_server_session:start(SSock, smtp_server_example, [{hostname, "localhost"}, {sessioncount, 1}]),
  902. socket:controlling_process(SSock, Pid),
  903. {CSock, Pid}
  904. end,
  905. fun({CSock, _Pid}) ->
  906. socket:close(CSock)
  907. end,
  908. [fun({CSock, _Pid}) ->
  909. {"A new connection should get a banner",
  910. fun() ->
  911. socket:active_once(CSock),
  912. receive {tcp, CSock, Packet} -> ok end,
  913. ?assertMatch("220 localhost"++_Stuff, Packet)
  914. end
  915. }
  916. end,
  917. fun({CSock, _Pid}) ->
  918. {"A correct response to HELO",
  919. fun() ->
  920. socket:active_once(CSock),
  921. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  922. ?assertMatch("220 localhost"++_Stuff, Packet),
  923. socket:send(CSock, "HELO somehost.com\r\n"),
  924. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  925. ?debugFmt("~nHere 5", []),
  926. ?assertMatch("250 localhost\r\n", Packet2)
  927. end
  928. }
  929. end,
  930. fun({CSock, _Pid}) ->
  931. {"An error in response to an invalid HELO",
  932. fun() ->
  933. socket:active_once(CSock),
  934. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  935. ?assertMatch("220 localhost"++_Stuff, Packet),
  936. socket:send(CSock, "HELO\r\n"),
  937. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  938. ?assertMatch("501 Syntax: HELO hostname\r\n", Packet2)
  939. end
  940. }
  941. end,
  942. fun({CSock, _Pid}) ->
  943. {"A rejected HELO",
  944. fun() ->
  945. socket:active_once(CSock),
  946. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  947. ?assertMatch("220 localhost"++_Stuff, Packet),
  948. socket:send(CSock, "HELO invalid\r\n"),
  949. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  950. ?assertMatch("554 invalid hostname\r\n", Packet2)
  951. end
  952. }
  953. end,
  954. fun({CSock, _Pid}) ->
  955. {"A rejected EHLO",
  956. fun() ->
  957. socket:active_once(CSock),
  958. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  959. ?assertMatch("220 localhost"++_Stuff, Packet),
  960. socket:send(CSock, "EHLO invalid\r\n"),
  961. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  962. ?assertMatch("554 invalid hostname\r\n", Packet2)
  963. end
  964. }
  965. end,
  966. fun({CSock, _Pid}) ->
  967. {"EHLO response",
  968. fun() ->
  969. socket:active_once(CSock),
  970. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  971. ?assertMatch("220 localhost"++_Stuff, Packet),
  972. socket:send(CSock, "EHLO somehost.com\r\n"),
  973. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  974. ?assertMatch("250-localhost\r\n", Packet2),
  975. Foo = fun(F) ->
  976. receive
  977. {tcp, CSock, "250-"++Packet3} ->
  978. socket:active_once(CSock),
  979. F(F);
  980. {tcp, CSock, "250 "++Packet3} ->
  981. socket:active_once(CSock),
  982. ok;
  983. R ->
  984. socket:active_once(CSock),
  985. error
  986. end
  987. end,
  988. ?assertEqual(ok, Foo(Foo))
  989. end
  990. }
  991. end,
  992. fun({CSock, _Pid}) ->
  993. {"Unsupported AUTH PLAIN",
  994. fun() ->
  995. socket:active_once(CSock),
  996. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  997. ?assertMatch("220 localhost"++_Stuff, Packet),
  998. socket:send(CSock, "EHLO somehost.com\r\n"),
  999. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1000. ?assertMatch("250-localhost\r\n", Packet2),
  1001. Foo = fun(F) ->
  1002. receive
  1003. {tcp, CSock, "250-"++Packet3} ->
  1004. socket:active_once(CSock),
  1005. F(F);
  1006. {tcp, CSock, "250"++Packet3} ->
  1007. socket:active_once(CSock),
  1008. ok;
  1009. R ->
  1010. socket:active_once(CSock),
  1011. error
  1012. end
  1013. end,
  1014. ?assertEqual(ok, Foo(Foo)),
  1015. socket:send(CSock, "AUTH PLAIN\r\n"),
  1016. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1017. ?assertMatch("502 Error: AUTH not implemented\r\n", Packet4)
  1018. end
  1019. }
  1020. end,
  1021. fun({CSock, _Pid}) ->
  1022. {"Sending DATA",
  1023. fun() ->
  1024. socket:active_once(CSock),
  1025. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1026. ?assertMatch("220 localhost"++_Stuff, Packet),
  1027. socket:send(CSock, "HELO somehost.com\r\n"),
  1028. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1029. ?assertMatch("250 localhost\r\n", Packet2),
  1030. socket:send(CSock, "MAIL FROM: <user@somehost.com>\r\n"),
  1031. receive {tcp, CSock, Packet3} -> socket:active_once(CSock) end,
  1032. ?assertMatch("250 "++_, Packet3),
  1033. socket:send(CSock, "RCPT TO: <user@otherhost.com>\r\n"),
  1034. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1035. ?assertMatch("250 "++_, Packet4),
  1036. socket:send(CSock, "DATA\r\n"),
  1037. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1038. ?assertMatch("354 "++_, Packet5),
  1039. socket:send(CSock, "Subject: tls message\r\n"),
  1040. socket:send(CSock, "To: <user@otherhost>\r\n"),
  1041. socket:send(CSock, "From: <user@somehost.com>\r\n"),
  1042. socket:send(CSock, "\r\n"),
  1043. socket:send(CSock, "message body"),
  1044. socket:send(CSock, "\r\n.\r\n"),
  1045. receive {tcp, CSock, Packet6} -> socket:active_once(CSock) end,
  1046. ?assertMatch("250 queued as"++_, Packet6),
  1047. ?debugFmt("Message send, received: ~p~n", [Packet6])
  1048. end
  1049. }
  1050. end,
  1051. % fun({CSock, _Pid}) ->
  1052. % {"Sending DATA with a bare newline",
  1053. % fun() ->
  1054. % socket:active_once(CSock),
  1055. % receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1056. % ?assertMatch("220 localhost"++_Stuff, Packet),
  1057. % socket:send(CSock, "HELO somehost.com\r\n"),
  1058. % receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1059. % ?assertMatch("250 localhost\r\n", Packet2),
  1060. % socket:send(CSock, "MAIL FROM: <user@somehost.com>\r\n"),
  1061. % receive {tcp, CSock, Packet3} -> socket:active_once(CSock) end,
  1062. % ?assertMatch("250 "++_, Packet3),
  1063. % socket:send(CSock, "RCPT TO: <user@otherhost.com>\r\n"),
  1064. % receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1065. % ?assertMatch("250 "++_, Packet4),
  1066. % socket:send(CSock, "DATA\r\n"),
  1067. % receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1068. % ?assertMatch("354 "++_, Packet5),
  1069. % socket:send(CSock, "Subject: tls message\r\n"),
  1070. % socket:send(CSock, "To: <user@otherhost>\r\n"),
  1071. % socket:send(CSock, "From: <user@somehost.com>\r\n"),
  1072. % socket:send(CSock, "\r\n"),
  1073. % socket:send(CSock, "this\r\n"),
  1074. % socket:send(CSock, "body\r\n"),
  1075. % socket:send(CSock, "has\r\n"),
  1076. % socket:send(CSock, "a\r\n"),
  1077. % socket:send(CSock, "bare\n"),
  1078. % socket:send(CSock, "newline\r\n"),
  1079. % socket:send(CSock, "\r\n.\r\n"),
  1080. % receive {tcp, CSock, Packet6} -> socket:active_once(CSock) end,
  1081. % ?assertMatch("451 "++_, Packet6),
  1082. % ?debugFmt("Message send, received: ~p~n", [Packet6])
  1083. % end
  1084. % }
  1085. % end,
  1086. %fun({CSock, _Pid}) ->
  1087. % {"Sending DATA with a bare CR",
  1088. % fun() ->
  1089. % socket:active_once(CSock),
  1090. % receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1091. % ?assertMatch("220 localhost"++_Stuff, Packet),
  1092. % socket:send(CSock, "HELO somehost.com\r\n"),
  1093. % receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1094. % ?assertMatch("250 localhost\r\n", Packet2),
  1095. % socket:send(CSock, "MAIL FROM: <user@somehost.com>\r\n"),
  1096. % receive {tcp, CSock, Packet3} -> socket:active_once(CSock) end,
  1097. % ?assertMatch("250 "++_, Packet3),
  1098. % socket:send(CSock, "RCPT TO: <user@otherhost.com>\r\n"),
  1099. % receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1100. % ?assertMatch("250 "++_, Packet4),
  1101. % socket:send(CSock, "DATA\r\n"),
  1102. % receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1103. % ?assertMatch("354 "++_, Packet5),
  1104. % socket:send(CSock, "Subject: tls message\r\n"),
  1105. % socket:send(CSock, "To: <user@otherhost>\r\n"),
  1106. % socket:send(CSock, "From: <user@somehost.com>\r\n"),
  1107. % socket:send(CSock, "\r\n"),
  1108. % socket:send(CSock, "this\r\n"),
  1109. % socket:send(CSock, "\rbody\r\n"),
  1110. % socket:send(CSock, "has\r\n"),
  1111. % socket:send(CSock, "a\r\n"),
  1112. % socket:send(CSock, "bare\r"),
  1113. % socket:send(CSock, "CR\r\n"),
  1114. % socket:send(CSock, "\r\n.\r\n"),
  1115. % receive {tcp, CSock, Packet6} -> socket:active_once(CSock) end,
  1116. % ?assertMatch("451 "++_, Packet6),
  1117. % ?debugFmt("Message send, received: ~p~n", [Packet6])
  1118. % end
  1119. % }
  1120. % end,
  1121. % fun({CSock, _Pid}) ->
  1122. % {"Sending DATA with a bare newline in the headers",
  1123. % fun() ->
  1124. % socket:active_once(CSock),
  1125. % receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1126. % ?assertMatch("220 localhost"++_Stuff, Packet),
  1127. % socket:send(CSock, "HELO somehost.com\r\n"),
  1128. % receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1129. % ?assertMatch("250 localhost\r\n", Packet2),
  1130. % socket:send(CSock, "MAIL FROM: <user@somehost.com>\r\n"),
  1131. % receive {tcp, CSock, Packet3} -> socket:active_once(CSock) end,
  1132. % ?assertMatch("250 "++_, Packet3),
  1133. % socket:send(CSock, "RCPT TO: <user@otherhost.com>\r\n"),
  1134. % receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1135. % ?assertMatch("250 "++_, Packet4),
  1136. % socket:send(CSock, "DATA\r\n"),
  1137. % receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1138. % ?assertMatch("354 "++_, Packet5),
  1139. % socket:send(CSock, "Subject: tls message\r\n"),
  1140. % socket:send(CSock, "To: <user@otherhost>\n"),
  1141. % socket:send(CSock, "From: <user@somehost.com>\r\n"),
  1142. % socket:send(CSock, "\r\n"),
  1143. % socket:send(CSock, "this\r\n"),
  1144. % socket:send(CSock, "body\r\n"),
  1145. % socket:send(CSock, "has\r\n"),
  1146. % socket:send(CSock, "no\r\n"),
  1147. % socket:send(CSock, "bare\r\n"),
  1148. % socket:send(CSock, "newlines\r\n"),
  1149. % socket:send(CSock, "\r\n.\r\n"),
  1150. % receive {tcp, CSock, Packet6} -> socket:active_once(CSock) end,
  1151. % ?assertMatch("451 "++_, Packet6),
  1152. % ?debugFmt("Message send, received: ~p~n", [Packet6])
  1153. % end
  1154. % }
  1155. % end,
  1156. fun({CSock, _Pid}) ->
  1157. {"Sending DATA with bare newline on first line of body",
  1158. fun() ->
  1159. socket:active_once(CSock),
  1160. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1161. ?assertMatch("220 localhost"++_Stuff, Packet),
  1162. socket:send(CSock, "HELO somehost.com\r\n"),
  1163. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1164. ?assertMatch("250 localhost\r\n", Packet2),
  1165. socket:send(CSock, "MAIL FROM: <user@somehost.com>\r\n"),
  1166. receive {tcp, CSock, Packet3} -> socket:active_once(CSock) end,
  1167. ?assertMatch("250 "++_, Packet3),
  1168. socket:send(CSock, "RCPT TO: <user@otherhost.com>\r\n"),
  1169. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1170. ?assertMatch("250 "++_, Packet4),
  1171. socket:send(CSock, "DATA\r\n"),
  1172. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1173. ?assertMatch("354 "++_, Packet5),
  1174. socket:send(CSock, "Subject: tls message\r\n"),
  1175. socket:send(CSock, "To: <user@otherhost>\n"),
  1176. socket:send(CSock, "From: <user@somehost.com>\r\n"),
  1177. socket:send(CSock, "\r\n"),
  1178. socket:send(CSock, "this\n"),
  1179. socket:send(CSock, "body\r\n"),
  1180. socket:send(CSock, "has\r\n"),
  1181. socket:send(CSock, "no\r\n"),
  1182. socket:send(CSock, "bare\r\n"),
  1183. socket:send(CSock, "newlines\r\n"),
  1184. socket:send(CSock, "\r\n.\r\n"),
  1185. receive {tcp, CSock, Packet6} -> socket:active_once(CSock) end,
  1186. ?assertMatch("451 "++_, Packet6),
  1187. ?debugFmt("Message send, received: ~p~n", [Packet6])
  1188. end
  1189. }
  1190. end
  1191. ]
  1192. }.
  1193. smtp_session_auth_test_() ->
  1194. {foreach,
  1195. local,
  1196. fun() ->
  1197. Self = self(),
  1198. spawn(fun() ->
  1199. {ok, ListenSock} = socket:listen(tcp, 9876, [binary]),
  1200. {ok, X} = socket:accept(ListenSock),
  1201. socket:controlling_process(X, Self),
  1202. Self ! X
  1203. end),
  1204. {ok, CSock} = socket:connect(tcp, "localhost", 9876),
  1205. receive
  1206. SSock when is_port(SSock) ->
  1207. ?debugFmt("Got server side of the socket ~p, client is ~p~n", [SSock, CSock])
  1208. end,
  1209. {ok, Pid} = gen_smtp_server_session:start(SSock, smtp_server_example, [{hostname, "localhost"}, {sessioncount, 1}, {callbackoptions, [{auth, true}]}]),
  1210. socket:controlling_process(SSock, Pid),
  1211. {CSock, Pid}
  1212. end,
  1213. fun({CSock, _Pid}) ->
  1214. socket:close(CSock)
  1215. end,
  1216. [fun({CSock, _Pid}) ->
  1217. {"EHLO response includes AUTH",
  1218. fun() ->
  1219. socket:active_once(CSock),
  1220. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1221. ?assertMatch("220 localhost"++_Stuff, Packet),
  1222. socket:send(CSock, "EHLO somehost.com\r\n"),
  1223. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1224. ?assertMatch("250-localhost\r\n", Packet2),
  1225. Foo = fun(F, Acc) ->
  1226. receive
  1227. {tcp, CSock, "250-AUTH"++Packet3} ->
  1228. socket:active_once(CSock),
  1229. F(F, true);
  1230. {tcp, CSock, "250-"++Packet3} ->
  1231. socket:active_once(CSock),
  1232. F(F, Acc);
  1233. {tcp, CSock, "250 AUTH"++Packet3} ->
  1234. socket:active_once(CSock),
  1235. true;
  1236. {tcp, CSock, "250 "++Packet3} ->
  1237. socket:active_once(CSock),
  1238. Acc;
  1239. {tcp, CSock, _} ->
  1240. socket:active_once(CSock),
  1241. error
  1242. end
  1243. end,
  1244. ?assertEqual(true, Foo(Foo, false))
  1245. end
  1246. }
  1247. end,
  1248. fun({CSock, _Pid}) ->
  1249. {"AUTH before EHLO is error",
  1250. fun() ->
  1251. socket:active_once(CSock),
  1252. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1253. ?assertMatch("220 localhost"++_Stuff, Packet),
  1254. socket:send(CSock, "AUTH CRAZY\r\n"),
  1255. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1256. ?assertMatch("503 "++_, Packet4)
  1257. end
  1258. }
  1259. end,
  1260. fun({CSock, _Pid}) ->
  1261. {"Unknown authentication type",
  1262. fun() ->
  1263. socket:active_once(CSock),
  1264. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1265. ?assertMatch("220 localhost"++_Stuff, Packet),
  1266. socket:send(CSock, "EHLO somehost.com\r\n"),
  1267. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1268. ?assertMatch("250-localhost\r\n", Packet2),
  1269. Foo = fun(F, Acc) ->
  1270. receive
  1271. {tcp, CSock, "250-AUTH"++_} ->
  1272. socket:active_once(CSock),
  1273. F(F, true);
  1274. {tcp, CSock, "250-"++_} ->
  1275. socket:active_once(CSock),
  1276. F(F, Acc);
  1277. {tcp, CSock, "250 AUTH"++_} ->
  1278. socket:active_once(CSock),
  1279. true;
  1280. {tcp, CSock, "250 "++_} ->
  1281. socket:active_once(CSock),
  1282. Acc;
  1283. {tcp, CSock, _} ->
  1284. socket:active_once(CSock),
  1285. error
  1286. end
  1287. end,
  1288. ?assertEqual(true, Foo(Foo, false)),
  1289. socket:send(CSock, "AUTH CRAZY\r\n"),
  1290. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1291. ?assertMatch("504 Unrecognized authentication type\r\n", Packet4)
  1292. end
  1293. }
  1294. end,
  1295. fun({CSock, _Pid}) ->
  1296. {"A successful AUTH PLAIN",
  1297. fun() ->
  1298. socket:active_once(CSock),
  1299. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1300. ?assertMatch("220 localhost"++_Stuff, Packet),
  1301. socket:send(CSock, "EHLO somehost.com\r\n"),
  1302. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1303. ?assertMatch("250-localhost\r\n", Packet2),
  1304. Foo = fun(F, Acc) ->
  1305. receive
  1306. {tcp, CSock, "250-AUTH"++Packet3} ->
  1307. socket:active_once(CSock),
  1308. F(F, true);
  1309. {tcp, CSock, "250-"++Packet3} ->
  1310. socket:active_once(CSock),
  1311. F(F, Acc);
  1312. {tcp, CSock, "250 AUTH"++Packet3} ->
  1313. socket:active_once(CSock),
  1314. true;
  1315. {tcp, CSock, "250 "++Packet3} ->
  1316. socket:active_once(CSock),
  1317. Acc;
  1318. {tcp, CSock, _} ->
  1319. socket:active_once(CSock),
  1320. error
  1321. end
  1322. end,
  1323. ?assertEqual(true, Foo(Foo, false)),
  1324. socket:send(CSock, "AUTH PLAIN\r\n"),
  1325. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1326. ?assertMatch("334\r\n", Packet4),
  1327. String = binary_to_list(base64:encode("\0username\0PaSSw0rd")),
  1328. socket:send(CSock, String++"\r\n"),
  1329. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1330. ?assertMatch("235 Authentication successful.\r\n", Packet5)
  1331. end
  1332. }
  1333. end,
  1334. fun({CSock, _Pid}) ->
  1335. {"A successful AUTH PLAIN with an identity",
  1336. fun() ->
  1337. socket:active_once(CSock),
  1338. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1339. ?assertMatch("220 localhost"++_Stuff, Packet),
  1340. socket:send(CSock, "EHLO somehost.com\r\n"),
  1341. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1342. ?assertMatch("250-localhost\r\n", Packet2),
  1343. Foo = fun(F, Acc) ->
  1344. receive
  1345. {tcp, CSock, "250-AUTH"++Packet3} ->
  1346. socket:active_once(CSock),
  1347. F(F, true);
  1348. {tcp, CSock, "250-"++Packet3} ->
  1349. socket:active_once(CSock),
  1350. F(F, Acc);
  1351. {tcp, CSock, "250 AUTH"++Packet3} ->
  1352. socket:active_once(CSock),
  1353. true;
  1354. {tcp, CSock, "250 "++Packet3} ->
  1355. socket:active_once(CSock),
  1356. Acc;
  1357. {tcp, CSock, _} ->
  1358. socket:active_once(CSock),
  1359. error
  1360. end
  1361. end,
  1362. ?assertEqual(true, Foo(Foo, false)),
  1363. socket:send(CSock, "AUTH PLAIN\r\n"),
  1364. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1365. ?assertMatch("334\r\n", Packet4),
  1366. String = binary_to_list(base64:encode("username\0username\0PaSSw0rd")),
  1367. socket:send(CSock, String++"\r\n"),
  1368. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1369. ?assertMatch("235 Authentication successful.\r\n", Packet5)
  1370. end
  1371. }
  1372. end,
  1373. fun({CSock, _Pid}) ->
  1374. {"A successful immediate AUTH PLAIN",
  1375. fun() ->
  1376. socket:active_once(CSock),
  1377. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1378. ?assertMatch("220 localhost"++_Stuff, Packet),
  1379. socket:send(CSock, "EHLO somehost.com\r\n"),
  1380. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1381. ?assertMatch("250-localhost\r\n", Packet2),
  1382. Foo = fun(F, Acc) ->
  1383. receive
  1384. {tcp, CSock, "250-AUTH"++Packet3} ->
  1385. socket:active_once(CSock),
  1386. F(F, true);
  1387. {tcp, CSock, "250-"++Packet3} ->
  1388. socket:active_once(CSock),
  1389. F(F, Acc);
  1390. {tcp, CSock, "250 AUTH"++Packet3} ->
  1391. socket:active_once(CSock),
  1392. true;
  1393. {tcp, CSock, "250 "++Packet3} ->
  1394. socket:active_once(CSock),
  1395. Acc;
  1396. {tcp, CSock, _} ->
  1397. socket:active_once(CSock),
  1398. error
  1399. end
  1400. end,
  1401. ?assertEqual(true, Foo(Foo, false)),
  1402. String = binary_to_list(base64:encode("\0username\0PaSSw0rd")),
  1403. socket:send(CSock, "AUTH PLAIN "++String++"\r\n"),
  1404. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1405. ?assertMatch("235 Authentication successful.\r\n", Packet5)
  1406. end
  1407. }
  1408. end,
  1409. fun({CSock, _Pid}) ->
  1410. {"A successful immediate AUTH PLAIN with an identity",
  1411. fun() ->
  1412. socket:active_once(CSock),
  1413. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1414. ?assertMatch("220 localhost"++_Stuff, Packet),
  1415. socket:send(CSock, "EHLO somehost.com\r\n"),
  1416. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1417. ?assertMatch("250-localhost\r\n", Packet2),
  1418. ?assertMatch("250-localhost\r\n", Packet2),
  1419. Foo = fun(F, Acc) ->
  1420. receive
  1421. {tcp, CSock, "250-AUTH"++Packet3} ->
  1422. socket:active_once(CSock),
  1423. F(F, true);
  1424. {tcp, CSock, "250-"++Packet3} ->
  1425. socket:active_once(CSock),
  1426. F(F, Acc);
  1427. {tcp, CSock, "250 AUTH"++Packet3} ->
  1428. socket:active_once(CSock),
  1429. true;
  1430. {tcp, CSock, "250 "++Packet3} ->
  1431. socket:active_once(CSock),
  1432. Acc;
  1433. {tcp, CSock, R} ->
  1434. socket:active_once(CSock),
  1435. error
  1436. end
  1437. end,
  1438. ?assertEqual(true, Foo(Foo, false)),
  1439. String = binary_to_list(base64:encode("username\0username\0PaSSw0rd")),
  1440. socket:send(CSock, "AUTH PLAIN "++String++"\r\n"),
  1441. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1442. ?assertMatch("235 Authentication successful.\r\n", Packet5)
  1443. end
  1444. }
  1445. end,
  1446. fun({CSock, _Pid}) ->
  1447. {"An unsuccessful immediate AUTH PLAIN",
  1448. fun() ->
  1449. socket:active_once(CSock),
  1450. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1451. ?assertMatch("220 localhost"++_Stuff, Packet),
  1452. socket:send(CSock, "EHLO somehost.com\r\n"),
  1453. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1454. ?assertMatch("250-localhost\r\n", Packet2),
  1455. ?assertMatch("250-localhost\r\n", Packet2),
  1456. Foo = fun(F, Acc) ->
  1457. receive
  1458. {tcp, CSock, "250-AUTH"++Packet3} ->
  1459. socket:active_once(CSock),
  1460. F(F, true);
  1461. {tcp, CSock, "250-"++Packet3} ->
  1462. socket:active_once(CSock),
  1463. F(F, Acc);
  1464. {tcp, CSock, "250 AUTH"++Packet3} ->
  1465. socket:active_once(CSock),
  1466. true;
  1467. {tcp, CSock, "250 "++Packet3} ->
  1468. socket:active_once(CSock),
  1469. Acc;
  1470. {tcp, CSock, _} ->
  1471. socket:active_once(CSock),
  1472. error
  1473. end
  1474. end,
  1475. ?assertEqual(true, Foo(Foo, false)),
  1476. String = binary_to_list(base64:encode("username\0username\0PaSSw0rd2")),
  1477. socket:send(CSock, "AUTH PLAIN "++String++"\r\n"),
  1478. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1479. ?assertMatch("535 Authentication failed.\r\n", Packet5)
  1480. end
  1481. }
  1482. end,
  1483. fun({CSock, _Pid}) ->
  1484. {"An unsuccessful AUTH PLAIN",
  1485. fun() ->
  1486. socket:active_once(CSock),
  1487. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1488. ?assertMatch("220 localhost"++_Stuff, Packet),
  1489. socket:send(CSock, "EHLO somehost.com\r\n"),
  1490. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1491. ?assertMatch("250-localhost\r\n", Packet2),
  1492. ?assertMatch("250-localhost\r\n", Packet2),
  1493. Foo = fun(F, Acc) ->
  1494. receive
  1495. {tcp, CSock, "250-AUTH"++Packet3} ->
  1496. socket:active_once(CSock),
  1497. F(F, true);
  1498. {tcp, CSock, "250-"++Packet3} ->
  1499. socket:active_once(CSock),
  1500. F(F, Acc);
  1501. {tcp, CSock, "250 AUTH"++Packet3} ->
  1502. socket:active_once(CSock),
  1503. true;
  1504. {tcp, CSock, "250 "++Packet3} ->
  1505. socket:active_once(CSock),
  1506. Acc;
  1507. {tcp, CSock, _} ->
  1508. socket:active_once(CSock),
  1509. error
  1510. end
  1511. end,
  1512. ?assertEqual(true, Foo(Foo, false)),
  1513. socket:send(CSock, "AUTH PLAIN\r\n"),
  1514. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1515. ?assertMatch("334\r\n", Packet4),
  1516. String = binary_to_list(base64:encode("\0username\0NotThePassword")),
  1517. socket:send(CSock, String++"\r\n"),
  1518. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1519. ?assertMatch("535 Authentication failed.\r\n", Packet5)
  1520. end
  1521. }
  1522. end,
  1523. fun({CSock, _Pid}) ->
  1524. {"A successful AUTH LOGIN",
  1525. fun() ->
  1526. socket:active_once(CSock),
  1527. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1528. ?assertMatch("220 localhost"++_Stuff, Packet),
  1529. socket:send(CSock, "EHLO somehost.com\r\n"),
  1530. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1531. ?assertMatch("250-localhost\r\n", Packet2),
  1532. Foo = fun(F, Acc) ->
  1533. receive
  1534. {tcp, CSock, "250-AUTH"++Packet3} ->
  1535. socket:active_once(CSock),
  1536. F(F, true);
  1537. {tcp, CSock, "250-"++Packet3} ->
  1538. socket:active_once(CSock),
  1539. F(F, Acc);
  1540. {tcp, CSock, "250 AUTH"++Packet3} ->
  1541. socket:active_once(CSock),
  1542. true;
  1543. {tcp, CSock, "250 "++Packet3} ->
  1544. socket:active_once(CSock),
  1545. Acc;
  1546. {tcp, CSock, _} ->
  1547. socket:active_once(CSock),
  1548. error
  1549. end
  1550. end,
  1551. ?assertEqual(true, Foo(Foo, false)),
  1552. socket:send(CSock, "AUTH LOGIN\r\n"),
  1553. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1554. ?assertMatch("334 VXNlcm5hbWU6\r\n", Packet4),
  1555. String = binary_to_list(base64:encode("username")),
  1556. socket:send(CSock, String++"\r\n"),
  1557. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1558. ?assertMatch("334 UGFzc3dvcmQ6\r\n", Packet5),
  1559. PString = binary_to_list(base64:encode("PaSSw0rd")),
  1560. socket:send(CSock, PString++"\r\n"),
  1561. receive {tcp, CSock, Packet6} -> socket:active_once(CSock) end,
  1562. ?assertMatch("235 Authentication successful.\r\n", Packet6)
  1563. end
  1564. }
  1565. end,
  1566. fun({CSock, _Pid}) ->
  1567. {"An unsuccessful AUTH LOGIN",
  1568. fun() ->
  1569. socket:active_once(CSock),
  1570. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1571. ?assertMatch("220 localhost"++_Stuff, Packet),
  1572. socket:send(CSock, "EHLO somehost.com\r\n"),
  1573. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1574. ?assertMatch("250-localhost\r\n", Packet2),
  1575. Foo = fun(F, Acc) ->
  1576. receive
  1577. {tcp, CSock, "250-AUTH"++Packet3} ->
  1578. socket:active_once(CSock),
  1579. F(F, true);
  1580. {tcp, CSock, "250-"++Packet3} ->
  1581. socket:active_once(CSock),
  1582. F(F, Acc);
  1583. {tcp, CSock, "250 AUTH"++Packet3} ->
  1584. socket:active_once(CSock),
  1585. true;
  1586. {tcp, CSock, "250 "++Packet3} ->
  1587. socket:active_once(CSock),
  1588. Acc;
  1589. {tcp, CSock, _} ->
  1590. socket:active_once(CSock),
  1591. error
  1592. end
  1593. end,
  1594. ?assertEqual(true, Foo(Foo, false)),
  1595. socket:send(CSock, "AUTH LOGIN\r\n"),
  1596. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1597. ?assertMatch("334 VXNlcm5hbWU6\r\n", Packet4),
  1598. String = binary_to_list(base64:encode("username2")),
  1599. socket:send(CSock, String++"\r\n"),
  1600. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1601. ?assertMatch("334 UGFzc3dvcmQ6\r\n", Packet5),
  1602. PString = binary_to_list(base64:encode("PaSSw0rd")),
  1603. socket:send(CSock, PString++"\r\n"),
  1604. receive {tcp, CSock, Packet6} -> socket:active_once(CSock) end,
  1605. ?assertMatch("535 Authentication failed.\r\n", Packet6)
  1606. end
  1607. }
  1608. end,
  1609. fun({CSock, _Pid}) ->
  1610. {"A successful AUTH CRAM-MD5",
  1611. fun() ->
  1612. socket:active_once(CSock),
  1613. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1614. ?assertMatch("220 localhost"++_Stuff, Packet),
  1615. socket:send(CSock, "EHLO somehost.com\r\n"),
  1616. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1617. ?assertMatch("250-localhost\r\n", Packet2),
  1618. Foo = fun(F, Acc) ->
  1619. receive
  1620. {tcp, CSock, "250-AUTH"++Packet3} ->
  1621. socket:active_once(CSock),
  1622. F(F, true);
  1623. {tcp, CSock, "250-"++Packet3} ->
  1624. socket:active_once(CSock),
  1625. F(F, Acc);
  1626. {tcp, CSock, "250 AUTH"++Packet3} ->
  1627. socket:active_once(CSock),
  1628. true;
  1629. {tcp, CSock, "250 "++Packet3} ->
  1630. socket:active_once(CSock),
  1631. Acc;
  1632. {tcp, CSock, _} ->
  1633. socket:active_once(CSock),
  1634. error
  1635. end
  1636. end,
  1637. ?assertEqual(true, Foo(Foo, false)),
  1638. socket:send(CSock, "AUTH CRAM-MD5\r\n"),
  1639. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1640. ?assertMatch("334 "++_, Packet4),
  1641. ["334", Seed64] = string:tokens(smtp_util:trim_crlf(Packet4), " "),
  1642. Seed = base64:decode_to_string(Seed64),
  1643. Digest = compute_cram_digest("PaSSw0rd", Seed),
  1644. String = binary_to_list(base64:encode("username "++Digest)),
  1645. socket:send(CSock, String++"\r\n"),
  1646. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1647. ?assertMatch("235 Authentication successful.\r\n", Packet5)
  1648. end
  1649. }
  1650. end,
  1651. fun({CSock, _Pid}) ->
  1652. {"An unsuccessful AUTH CRAM-MD5",
  1653. fun() ->
  1654. socket:active_once(CSock),
  1655. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1656. ?assertMatch("220 localhost"++_Stuff, Packet),
  1657. socket:send(CSock, "EHLO somehost.com\r\n"),
  1658. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1659. ?assertMatch("250-localhost\r\n", Packet2),
  1660. Foo = fun(F, Acc) ->
  1661. receive
  1662. {tcp, CSock, "250-AUTH"++Packet3} ->
  1663. socket:active_once(CSock),
  1664. F(F, true);
  1665. {tcp, CSock, "250-"++Packet3} ->
  1666. socket:active_once(CSock),
  1667. F(F, Acc);
  1668. {tcp, CSock, "250 AUTH"++Packet3} ->
  1669. socket:active_once(CSock),
  1670. true;
  1671. {tcp, CSock, "250 "++Packet3} ->
  1672. socket:active_once(CSock),
  1673. Acc;
  1674. {tcp, CSock, _} ->
  1675. socket:active_once(CSock),
  1676. error
  1677. end
  1678. end,
  1679. ?assertEqual(true, Foo(Foo, false)),
  1680. socket:send(CSock, "AUTH CRAM-MD5\r\n"),
  1681. receive {tcp, CSock, Packet4} -> socket:active_once(CSock) end,
  1682. ?assertMatch("334 "++_, Packet4),
  1683. ["334", Seed64] = string:tokens(smtp_util:trim_crlf(Packet4), " "),
  1684. Seed = base64:decode_to_string(Seed64),
  1685. Digest = compute_cram_digest("Passw0rd", Seed),
  1686. String = binary_to_list(base64:encode("username "++Digest)),
  1687. socket:send(CSock, String++"\r\n"),
  1688. receive {tcp, CSock, Packet5} -> socket:active_once(CSock) end,
  1689. ?assertMatch("535 Authentication failed.\r\n", Packet5)
  1690. end
  1691. }
  1692. end
  1693. ]
  1694. }.
  1695. smtp_session_tls_test_() ->
  1696. case filelib:is_regular("server.crt") of
  1697. true ->
  1698. {foreach,
  1699. local,
  1700. fun() ->
  1701. crypto:start(),
  1702. application:start(public_key),
  1703. application:start(ssl),
  1704. Self = self(),
  1705. spawn(fun() ->
  1706. {ok, ListenSock} = socket:listen(tcp, 9876, [binary]),
  1707. {ok, X} = socket:accept(ListenSock),
  1708. socket:controlling_process(X, Self),
  1709. Self ! X
  1710. end),
  1711. {ok, CSock} = socket:connect(tcp, "localhost", 9876),
  1712. receive
  1713. SSock when is_port(SSock) ->
  1714. ?debugFmt("Got server side of the socket ~p, client is ~p~n", [SSock, CSock])
  1715. end,
  1716. {ok, Pid} = gen_smtp_server_session:start(SSock, smtp_server_example, [{hostname, "localhost"}, {sessioncount, 1}, {callbackoptions, [{auth, true}]}]),
  1717. socket:controlling_process(SSock, Pid),
  1718. {CSock, Pid}
  1719. end,
  1720. fun({CSock, _Pid}) ->
  1721. socket:close(CSock)
  1722. end,
  1723. [fun({CSock, _Pid}) ->
  1724. {"EHLO response includes STARTTLS",
  1725. fun() ->
  1726. socket:active_once(CSock),
  1727. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1728. ?assertMatch("220 localhost"++_Stuff, Packet),
  1729. socket:send(CSock, "EHLO somehost.com\r\n"),
  1730. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1731. ?assertMatch("250-localhost\r\n", Packet2),
  1732. Foo = fun(F, Acc) ->
  1733. receive
  1734. {tcp, CSock, "250-STARTTLS"++_} ->
  1735. socket:active_once(CSock),
  1736. F(F, true);
  1737. {tcp, CSock, "250-"++Packet3} ->
  1738. ?debugFmt("XX~sXX", [Packet3]),
  1739. socket:active_once(CSock),
  1740. F(F, Acc);
  1741. {tcp, CSock, "250 STARTTLS"++_} ->
  1742. socket:active_once(CSock),
  1743. true;
  1744. {tcp, CSock, "250 "++Packet3} ->
  1745. socket:active_once(CSock),
  1746. Acc;
  1747. {tcp, CSock, _} ->
  1748. socket:active_once(CSock),
  1749. error
  1750. end
  1751. end,
  1752. ?assertEqual(true, Foo(Foo, false))
  1753. end
  1754. }
  1755. end,
  1756. fun({CSock, _Pid}) ->
  1757. {"STARTTLS does a SSL handshake",
  1758. fun() ->
  1759. socket:active_once(CSock),
  1760. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1761. ?assertMatch("220 localhost"++_Stuff, Packet),
  1762. socket:send(CSock, "EHLO somehost.com\r\n"),
  1763. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1764. ?assertMatch("250-localhost\r\n", Packet2),
  1765. Foo = fun(F, Acc) ->
  1766. receive
  1767. {tcp, CSock, "250-STARTTLS"++_} ->
  1768. socket:active_once(CSock),
  1769. F(F, true);
  1770. {tcp, CSock, "250-"++Packet3} ->
  1771. ?debugFmt("XX~sXX", [Packet3]),
  1772. socket:active_once(CSock),
  1773. F(F, Acc);
  1774. {tcp, CSock, "250 STARTTLS"++_} ->
  1775. socket:active_once(CSock),
  1776. true;
  1777. {tcp, CSock, "250 "++Packet3} ->
  1778. socket:active_once(CSock),
  1779. Acc;
  1780. {tcp, CSock, _} ->
  1781. socket:active_once(CSock),
  1782. error
  1783. end
  1784. end,
  1785. ?assertEqual(true, Foo(Foo, false)),
  1786. socket:send(CSock, "STARTTLS\r\n"),
  1787. receive {tcp, CSock, Packet4} -> ok end,
  1788. ?assertMatch("220 "++_, Packet4),
  1789. Result = socket:to_ssl_client(CSock),
  1790. ?assertMatch({ok, Socket}, Result),
  1791. {ok, Socket} = Result
  1792. %socket:active_once(Socket),
  1793. %ssl:send(Socket, "EHLO somehost.com\r\n"),
  1794. %receive {ssl, Socket, Packet5} -> socket:active_once(Socket) end,
  1795. %?assertEqual("Foo", Packet5),
  1796. end
  1797. }
  1798. end,
  1799. fun({CSock, _Pid}) ->
  1800. {"After STARTTLS, EHLO doesn't report STARTTLS",
  1801. fun() ->
  1802. socket:active_once(CSock),
  1803. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1804. ?assertMatch("220 localhost"++_Stuff, Packet),
  1805. socket:send(CSock, "EHLO somehost.com\r\n"),
  1806. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1807. ?assertMatch("250-localhost\r\n", Packet2),
  1808. Foo = fun(F, Acc) ->
  1809. receive
  1810. {tcp, CSock, "250-STARTTLS"++_} ->
  1811. socket:active_once(CSock),
  1812. F(F, true);
  1813. {tcp, CSock, "250-"++Packet3} ->
  1814. ?debugFmt("XX~sXX", [Packet3]),
  1815. socket:active_once(CSock),
  1816. F(F, Acc);
  1817. {tcp, CSock, "250 STARTTLS"++_} ->
  1818. socket:active_once(CSock),
  1819. true;
  1820. {tcp, CSock, "250 "++Packet3} ->
  1821. socket:active_once(CSock),
  1822. Acc;
  1823. {tcp, CSock, _} ->
  1824. socket:active_once(CSock),
  1825. error
  1826. end
  1827. end,
  1828. ?assertEqual(true, Foo(Foo, false)),
  1829. socket:send(CSock, "STARTTLS\r\n"),
  1830. receive {tcp, CSock, Packet4} -> ok end,
  1831. ?assertMatch("220 "++_, Packet4),
  1832. Result = socket:to_ssl_client(CSock),
  1833. ?assertMatch({ok, Socket}, Result),
  1834. {ok, Socket} = Result,
  1835. socket:active_once(Socket),
  1836. socket:send(Socket, "EHLO somehost.com\r\n"),
  1837. receive {ssl, Socket, Packet5} -> socket:active_once(Socket) end,
  1838. ?assertMatch("250-localhost\r\n", Packet5),
  1839. Bar = fun(F, Acc) ->
  1840. receive
  1841. {ssl, Socket, "250-STARTTLS"++_} ->
  1842. socket:active_once(Socket),
  1843. F(F, true);
  1844. {ssl, Socket, "250-"++_} ->
  1845. socket:active_once(Socket),
  1846. F(F, Acc);
  1847. {ssl, Socket, "250 STARTTLS"++_} ->
  1848. socket:active_once(Socket),
  1849. true;
  1850. {ssl, Socket, "250 "++_} ->
  1851. socket:active_once(Socket),
  1852. Acc;
  1853. {ssl, Socket, _} ->
  1854. socket:active_once(Socket),
  1855. error
  1856. end
  1857. end,
  1858. ?assertEqual(false, Bar(Bar, false))
  1859. end
  1860. }
  1861. end,
  1862. fun({CSock, _Pid}) ->
  1863. {"After STARTTLS, re-negotiating STARTTLS is an error",
  1864. fun() ->
  1865. socket:active_once(CSock),
  1866. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1867. ?assertMatch("220 localhost"++_Stuff, Packet),
  1868. socket:send(CSock, "EHLO somehost.com\r\n"),
  1869. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1870. ?assertMatch("250-localhost\r\n", Packet2),
  1871. Foo = fun(F, Acc) ->
  1872. receive
  1873. {tcp, CSock, "250-STARTTLS"++_} ->
  1874. socket:active_once(CSock),
  1875. F(F, true);
  1876. {tcp, CSock, "250-"++Packet3} ->
  1877. ?debugFmt("XX~sXX", [Packet3]),
  1878. socket:active_once(CSock),
  1879. F(F, Acc);
  1880. {tcp, CSock, "250 STARTTLS"++_} ->
  1881. socket:active_once(CSock),
  1882. true;
  1883. {tcp, CSock, "250 "++Packet3} ->
  1884. socket:active_once(CSock),
  1885. Acc;
  1886. {tcp, CSock, _} ->
  1887. socket:active_once(CSock),
  1888. error
  1889. end
  1890. end,
  1891. ?assertEqual(true, Foo(Foo, false)),
  1892. socket:send(CSock, "STARTTLS\r\n"),
  1893. receive {tcp, CSock, Packet4} -> ok end,
  1894. ?assertMatch("220 "++_, Packet4),
  1895. Result = socket:to_ssl_client(CSock),
  1896. ?assertMatch({ok, Socket}, Result),
  1897. {ok, Socket} = Result,
  1898. socket:active_once(Socket),
  1899. socket:send(Socket, "EHLO somehost.com\r\n"),
  1900. receive {ssl, Socket, Packet5} -> socket:active_once(Socket) end,
  1901. ?assertMatch("250-localhost\r\n", Packet5),
  1902. Bar = fun(F, Acc) ->
  1903. receive
  1904. {ssl, Socket, "250-STARTTLS"++_} ->
  1905. socket:active_once(Socket),
  1906. F(F, true);
  1907. {ssl, Socket, "250-"++_} ->
  1908. socket:active_once(Socket),
  1909. F(F, Acc);
  1910. {ssl, Socket, "250 STARTTLS"++_} ->
  1911. socket:active_once(Socket),
  1912. true;
  1913. {ssl, Socket, "250 "++_} ->
  1914. socket:active_once(Socket),
  1915. Acc;
  1916. {ssl, Socket, _} ->
  1917. socket:active_once(Socket),
  1918. error
  1919. end
  1920. end,
  1921. ?assertEqual(false, Bar(Bar, false)),
  1922. socket:send(Socket, "STARTTLS\r\n"),
  1923. receive {ssl, Socket, Packet6} -> socket:active_once(Socket) end,
  1924. ?assertMatch("500 "++_, Packet6)
  1925. end
  1926. }
  1927. end,
  1928. fun({CSock, _Pid}) ->
  1929. {"STARTTLS can't take any parameters",
  1930. fun() ->
  1931. socket:active_once(CSock),
  1932. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1933. ?assertMatch("220 localhost"++_Stuff, Packet),
  1934. socket:send(CSock, "EHLO somehost.com\r\n"),
  1935. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1936. ?assertMatch("250-localhost\r\n", Packet2),
  1937. Foo = fun(F, Acc) ->
  1938. receive
  1939. {tcp, CSock, "250-STARTTLS"++_} ->
  1940. socket:active_once(CSock),
  1941. F(F, true);
  1942. {tcp, CSock, "250-"++Packet3} ->
  1943. ?debugFmt("XX~sXX", [Packet3]),
  1944. socket:active_once(CSock),
  1945. F(F, Acc);
  1946. {tcp, CSock, "250 STARTTLS"++_} ->
  1947. socket:active_once(CSock),
  1948. true;
  1949. {tcp, CSock, "250 "++Packet3} ->
  1950. socket:active_once(CSock),
  1951. Acc;
  1952. {tcp, CSock, _} ->
  1953. socket:active_once(CSock),
  1954. error
  1955. end
  1956. end,
  1957. ?assertEqual(true, Foo(Foo, false)),
  1958. socket:send(CSock, "STARTTLS foo\r\n"),
  1959. receive {tcp, CSock, Packet4} -> ok end,
  1960. ?assertMatch("501 "++_, Packet4)
  1961. end
  1962. }
  1963. end,
  1964. fun({CSock, _Pid}) ->
  1965. {"Negotiating STARTTLS twice is an error",
  1966. fun() ->
  1967. socket:active_once(CSock),
  1968. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  1969. socket:send(CSock, "EHLO somehost.com\r\n"),
  1970. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  1971. ReadExtensions = fun(F, Acc) ->
  1972. receive
  1973. {tcp, CSock, "250-STARTTLS"++_} ->
  1974. socket:active_once(CSock),
  1975. F(F, true);
  1976. {tcp, CSock, "250-"++Packet3} ->
  1977. socket:active_once(CSock),
  1978. F(F, Acc);
  1979. {tcp, CSock, "250 STARTTLS"++_} ->
  1980. socket:active_once(CSock),
  1981. true;
  1982. {tcp, CSock, "250 "++Packet3} ->
  1983. socket:active_once(CSock),
  1984. Acc;
  1985. {tcp, CSock, _} ->
  1986. socket:active_once(CSock),
  1987. error
  1988. end
  1989. end,
  1990. ?assertEqual(true, ReadExtensions(ReadExtensions, false)),
  1991. socket:send(CSock, "STARTTLS\r\n"),
  1992. receive {tcp, CSock, _} -> ok end,
  1993. {ok, Socket} = socket:to_ssl_client(CSock),
  1994. socket:active_once(Socket),
  1995. socket:send(Socket, "EHLO somehost.com\r\n"),
  1996. receive {ssl, Socket, PacketN} -> socket:active_once(Socket) end,
  1997. ?assertMatch("250-localhost\r\n", PacketN),
  1998. Bar = fun(F, Acc) ->
  1999. receive
  2000. {ssl, Socket, "250-STARTTLS"++_} ->
  2001. socket:active_once(Socket),
  2002. F(F, true);
  2003. {ssl, Socket, "250-"++_} ->
  2004. socket:active_once(Socket),
  2005. F(F, Acc);
  2006. {ssl, Socket, "250 STARTTLS"++_} ->
  2007. socket:active_once(Socket),
  2008. true;
  2009. {ssl, Socket, "250 "++_} ->
  2010. socket:active_once(Socket),
  2011. Acc;
  2012. {tcp, Socket, _} ->
  2013. socket:active_once(Socket),
  2014. error
  2015. end
  2016. end,
  2017. ?assertEqual(false, Bar(Bar, false)),
  2018. socket:send(Socket, "STARTTLS\r\n"),
  2019. receive {ssl, Socket, Packet6} -> socket:active_once(Socket) end,
  2020. ?assertMatch("500 "++_, Packet6)
  2021. end
  2022. }
  2023. end,
  2024. fun({CSock, _Pid}) ->
  2025. {"STARTTLS can't take any parameters",
  2026. fun() ->
  2027. socket:active_once(CSock),
  2028. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  2029. ?assertMatch("220 localhost"++_Stuff, Packet),
  2030. socket:send(CSock, "EHLO somehost.com\r\n"),
  2031. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  2032. ?assertMatch("250-localhost\r\n", Packet2),
  2033. Foo = fun(F, Acc) ->
  2034. receive
  2035. {tcp, CSock, "250-STARTTLS"++_} ->
  2036. socket:active_once(CSock),
  2037. F(F, true);
  2038. {tcp, CSock, "250-"++Packet3} ->
  2039. ?debugFmt("XX~sXX", [Packet3]),
  2040. socket:active_once(CSock),
  2041. F(F, Acc);
  2042. {tcp, CSock, "250 STARTTLS"++_} ->
  2043. socket:active_once(CSock),
  2044. true;
  2045. {tcp, CSock, "250 "++Packet3} ->
  2046. socket:active_once(CSock),
  2047. Acc;
  2048. {tcp, CSock, _} ->
  2049. socket:active_once(CSock),
  2050. error
  2051. end
  2052. end,
  2053. ?assertEqual(true, Foo(Foo, false)),
  2054. socket:send(CSock, "STARTTLS foo\r\n"),
  2055. receive {tcp, CSock, Packet4} -> ok end,
  2056. ?assertMatch("501 "++_, Packet4)
  2057. end
  2058. }
  2059. end,
  2060. fun({CSock, _Pid}) ->
  2061. {"After STARTTLS, message is received by server",
  2062. fun() ->
  2063. socket:active_once(CSock),
  2064. receive {tcp, CSock, Packet} -> socket:active_once(CSock) end,
  2065. socket:send(CSock, "EHLO somehost.com\r\n"),
  2066. receive {tcp, CSock, Packet2} -> socket:active_once(CSock) end,
  2067. ReadExtensions = fun(F, Acc) ->
  2068. receive
  2069. {tcp, CSock, "250-STARTTLS"++_} ->
  2070. socket:active_once(CSock),
  2071. F(F, true);
  2072. {tcp, CSock, "250-"++Packet3} ->
  2073. socket:active_once(CSock),
  2074. F(F, Acc);
  2075. {tcp, CSock, "250 STARTTLS"++_} ->
  2076. socket:active_once(CSock),
  2077. true;
  2078. {tcp, CSock, "250 "++Packet3} ->
  2079. socket:active_once(CSock),
  2080. Acc;
  2081. {tcp, CSock, _} ->
  2082. socket:active_once(CSock),
  2083. error
  2084. end
  2085. end,
  2086. ?assertEqual(true, ReadExtensions(ReadExtensions, false)),
  2087. socket:send(CSock, "STARTTLS\r\n"),
  2088. receive {tcp, CSock, _} -> ok end,
  2089. {ok, Socket} = socket:to_ssl_client(CSock),
  2090. socket:active_once(Socket),
  2091. socket:send(Socket, "EHLO somehost.com\r\n"),
  2092. ReadSSLExtensions = fun(F, Acc) ->
  2093. receive
  2094. {ssl, Socket, "250-"++_Rest} ->
  2095. ?debugFmt("2~n", []),
  2096. socket:active_once(Socket),
  2097. F(F, Acc);
  2098. {ssl, Socket, "250 "++_} ->
  2099. ?debugFmt("4~n", []),
  2100. socket:active_once(Socket),
  2101. true;
  2102. {ssl, Socket, R} ->
  2103. ?debugFmt("ReadSSLExtensions error: ~p~n", [R]),
  2104. socket:active_once(Socket),
  2105. error
  2106. end
  2107. end,
  2108. ?assertEqual(true, ReadSSLExtensions(ReadSSLExtensions, false)),
  2109. socket:send(Socket, "MAIL FROM: <user@somehost.com>\r\n"),
  2110. receive {ssl, Socket, Packet4} -> socket:active_once(Socket) end,
  2111. ?assertMatch("250 "++_, Packet4),
  2112. socket:send(Socket, "RCPT TO: <user@otherhost.com>\r\n"),
  2113. receive {ssl, Socket, Packet5} -> socket:active_once(Socket) end,
  2114. ?assertMatch("250 "++_, Packet5),
  2115. socket:send(Socket, "DATA\r\n"),
  2116. receive {ssl, Socket, Packet6} -> socket:active_once(Socket) end,
  2117. ?assertMatch("354 "++_, Packet6),
  2118. socket:send(Socket, "Subject: tls message\r\n"),
  2119. socket:send(Socket, "To: <user@otherhost>\r\n"),
  2120. socket:send(Socket, "From: <user@somehost.com>\r\n"),
  2121. socket:send(Socket, "\r\n"),
  2122. socket:send(Socket, "message body"),
  2123. socket:send(Socket, "\r\n.\r\n"),
  2124. receive {ssl, Socket, Packet7} -> socket:active_once(Socket) end,
  2125. ?assertMatch("250 "++_, Packet7),
  2126. ?debugFmt("Message send, received: ~p~n", [Packet7])
  2127. end
  2128. }
  2129. end
  2130. ]
  2131. };
  2132. false ->
  2133. [
  2134. {"SSL certificate exists",
  2135. fun() ->
  2136. ?debugFmt("~n********************************************~nPLEASE run rake generate_self_signed_certificate to run the SSL tests!~n********************************************~n", []),
  2137. ?assert(false)
  2138. end
  2139. }
  2140. ]
  2141. end.
  2142. stray_newline_test_() ->
  2143. [
  2144. {"Error out by default",
  2145. fun() ->
  2146. ?assertEqual(<<"foo">>, check_bare_crlf(<<"foo">>, <<>>, false, 0)),
  2147. ?assertEqual(error, check_bare_crlf(<<"foo\n">>, <<>>, false, 0)),
  2148. ?assertEqual(error, check_bare_crlf(<<"fo\ro\n">>, <<>>, false, 0)),
  2149. ?assertEqual(error, check_bare_crlf(<<"fo\ro\n\r">>, <<>>, false, 0)),
  2150. ?assertEqual(<<"foo\r\n">>, check_bare_crlf(<<"foo\r\n">>, <<>>, false, 0)),
  2151. ?assertEqual(<<"foo\r">>, check_bare_crlf(<<"foo\r">>, <<>>, false, 0))
  2152. end
  2153. },
  2154. {"Fixing them should work",
  2155. fun() ->
  2156. ?assertEqual(<<"foo">>, check_bare_crlf(<<"foo">>, <<>>, fix, 0)),
  2157. ?assertEqual(<<"foo\r\n">>, check_bare_crlf(<<"foo\n">>, <<>>, fix, 0)),
  2158. ?assertEqual(<<"fo\r\no\r\n">>, check_bare_crlf(<<"fo\ro\n">>, <<>>, fix, 0)),
  2159. ?assertEqual(<<"fo\r\no\r\n\r">>, check_bare_crlf(<<"fo\ro\n\r">>, <<>>, fix, 0)),
  2160. ?assertEqual(<<"foo\r\n">>, check_bare_crlf(<<"foo\r\n">>, <<>>, fix, 0))
  2161. end
  2162. },
  2163. {"Stripping them should work",
  2164. fun() ->
  2165. ?assertEqual(<<"foo">>, check_bare_crlf(<<"foo">>, <<>>, strip, 0)),
  2166. ?assertEqual(<<"foo">>, check_bare_crlf(<<"fo\ro\n">>, <<>>, strip, 0)),
  2167. ?assertEqual(<<"foo\r">>, check_bare_crlf(<<"fo\ro\n\r">>, <<>>, strip, 0)),
  2168. ?assertEqual(<<"foo\r\n">>, check_bare_crlf(<<"foo\r\n">>, <<>>, strip, 0))
  2169. end
  2170. },
  2171. {"Ignoring them should work",
  2172. fun() ->
  2173. ?assertEqual(<<"foo">>, check_bare_crlf(<<"foo">>, <<>>, ignore, 0)),
  2174. ?assertEqual(<<"fo\ro\n">>, check_bare_crlf(<<"fo\ro\n">>, <<>>, ignore, 0)),
  2175. ?assertEqual(<<"fo\ro\n\r">>, check_bare_crlf(<<"fo\ro\n\r">>, <<>>, ignore, 0)),
  2176. ?assertEqual(<<"foo\r\n">>, check_bare_crlf(<<"foo\r\n">>, <<>>, ignore, 0))
  2177. end
  2178. },
  2179. {"Leading bare LFs should check the previous line",
  2180. fun() ->
  2181. ?assertEqual(<<"\nfoo\r\n">>, check_bare_crlf(<<"\nfoo\r\n">>, <<"bar\r">>, false, 0)),
  2182. ?assertEqual(<<"\r\nfoo\r\n">>, check_bare_crlf(<<"\nfoo\r\n">>, <<"bar\r\n">>, fix, 0)),
  2183. ?assertEqual(<<"\nfoo\r\n">>, check_bare_crlf(<<"\nfoo\r\n">>, <<"bar\r">>, fix, 0)),
  2184. ?assertEqual(<<"foo\r\n">>, check_bare_crlf(<<"\nfoo\r\n">>, <<"bar\r\n">>, strip, 0)),
  2185. ?assertEqual(<<"\nfoo\r\n">>, check_bare_crlf(<<"\nfoo\r\n">>, <<"bar\r">>, strip, 0)),
  2186. ?assertEqual(<<"\nfoo\r\n">>, check_bare_crlf(<<"\nfoo\r\n">>, <<"bar\r\n">>, ignore, 0)),
  2187. ?assertEqual(error, check_bare_crlf(<<"\nfoo\r\n">>, <<"bar\r\n">>, false, 0)),
  2188. ?assertEqual(<<"\nfoo\r\n">>, check_bare_crlf(<<"\nfoo\r\n">>, <<"bar\r">>, false, 0))
  2189. end
  2190. }
  2191. ].
  2192. -endif.