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