diff options
-rw-r--r-- | lib/eldap/test/eldap_basic_SUITE.erl | 2 | ||||
-rw-r--r-- | lib/inets/src/ftp/ftp.erl | 26 | ||||
-rw-r--r-- | lib/inets/src/ftp/ftp_response.erl | 1 | ||||
-rw-r--r-- | lib/inets/test/ftp_SUITE.erl | 518 | ||||
-rw-r--r-- | lib/inets/test/ftp_format_SUITE.erl | 2 | ||||
-rw-r--r-- | lib/kernel/test/inet_SUITE.erl | 23 | ||||
-rw-r--r-- | lib/kernel/test/inet_res_SUITE.erl | 15 | ||||
-rw-r--r-- | lib/public_key/doc/src/Makefile | 11 | ||||
-rw-r--r-- | lib/public_key/doc/src/public_key.xml | 33 | ||||
-rw-r--r-- | lib/public_key/doc/src/public_key_app.xml | 85 | ||||
-rw-r--r-- | lib/public_key/doc/src/ref_man.xml | 1 | ||||
-rw-r--r-- | lib/ssh/doc/src/ssh.xml | 20 | ||||
-rw-r--r-- | lib/ssh/doc/src/ssh_sftp.xml | 11 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 192 | ||||
-rw-r--r-- | lib/ssh/src/ssh_acceptor.erl | 3 | ||||
-rw-r--r-- | lib/ssh/src/ssh_system_sup.erl | 5 | ||||
-rw-r--r-- | lib/ssh/test/ssh_connection_SUITE.erl | 65 | ||||
-rw-r--r-- | lib/ssh/test/ssh_sftp_SUITE.erl | 1 | ||||
-rw-r--r-- | lib/ssl/src/ssl_connection.erl | 8 | ||||
-rw-r--r-- | lib/ssl/src/ssl_manager.erl | 11 |
20 files changed, 669 insertions, 364 deletions
diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl index d52a7c83f7..ac3447cfe6 100644 --- a/lib/eldap/test/eldap_basic_SUITE.erl +++ b/lib/eldap/test/eldap_basic_SUITE.erl @@ -32,7 +32,7 @@ -define(manageDsaIT, {control,"2.16.840.1.113730.3.4.2",false,asn1_NOVALUE}). suite() -> - [{timetrap,{seconds,40}}]. + [{timetrap,{seconds,360}}]. all() -> [app, diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl index c2ca511795..bbf25f8e90 100644 --- a/lib/inets/src/ftp/ftp.erl +++ b/lib/inets/src/ftp/ftp.erl @@ -106,8 +106,8 @@ -type common_reason() :: 'econn' | 'eclosed' | term(). -type file_write_error_reason() :: term(). % See file:write for more info --define(DBG(F,A), 'n/a'). -%%-define(DBG(F,A), io:format(F,A)). +%%-define(DBG(F,A), 'n/a'). +-define(DBG(F,A), io:format(F,A)). %%%========================================================================= %%% API - CLIENT FUNCTIONS @@ -1383,12 +1383,18 @@ handle_call({_, {transfer_chunk, Bin}}, _, #state{chunk = true} = State) -> send_data_message(State, Bin), {reply, ok, State}; +handle_call({_, {transfer_chunk, _}}, _, #state{chunk = false} = State) -> + {reply, {error, echunk}, State}; + handle_call({_, chunk_end}, From, #state{chunk = true} = State) -> close_data_connection(State), activate_ctrl_connection(State), {noreply, State#state{client = From, dsock = undefined, caller = end_chunk_transfer, chunk = false}}; +handle_call({_, chunk_end}, _, #state{chunk = false} = State) -> + {reply, {error, echunk}, State}; + handle_call({_, {quote, Cmd}}, From, #state{chunk = false} = State) -> send_ctrl_message(State, mk_cmd(Cmd, [])), activate_ctrl_connection(State), @@ -1769,12 +1775,12 @@ handle_ctrl_result({pos_compl, _Lines}, {LSock, Caller}}} = State) -> handle_caller(State#state{caller = Caller, dsock = {lsock, LSock}}); -handle_ctrl_result({Status, Lines}, +handle_ctrl_result({Status, _Lines}, #state{mode = active, caller = {setup_data_connection, {LSock, _}}} = State) -> - close_connection(LSock), - ctrl_result_response(Status, State, {error, Lines}); + close_connection({tcp,LSock}), + ctrl_result_response(Status, State, {error, Status}); %% Data connection setup passive mode handle_ctrl_result({pos_compl, Lines}, @@ -1965,7 +1971,7 @@ handle_ctrl_result(_, #state{caller = {handle_dir_data_third_phase, DirData}, {noreply, State#state{client = undefined, caller = undefined}}; handle_ctrl_result({Status, _}, #state{caller = cd} = State) -> - ctrl_result_response(Status, State, {error, epath}); + ctrl_result_response(Status, State, {error, Status}); handle_ctrl_result(Status={epath, _}, #state{caller = {dir,_}} = State) -> ctrl_result_response(Status, State, {error, epath}); @@ -1980,11 +1986,11 @@ handle_ctrl_result({pos_interm, _}, #state{caller = {rename, NewFile}} handle_ctrl_result({Status, _}, #state{caller = {rename, _}} = State) -> - ctrl_result_response(Status, State, {error, epath}); + ctrl_result_response(Status, State, {error, Status}); handle_ctrl_result({Status, _}, #state{caller = rename_second_phase} = State) -> - ctrl_result_response(Status, State, {error, epath}); + ctrl_result_response(Status, State, {error, Status}); %%-------------------------------------------------------------------------- %% File handling - recv_bin @@ -2095,7 +2101,7 @@ handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}} %% Default handle_ctrl_result({Status, Lines}, #state{client = From} = State) when From =/= undefined -> - ctrl_result_response(Status, State, {error, Lines}). + ctrl_result_response(Status, State, {error, Status}). %%-------------------------------------------------------------------------- %% Help functions to handle_ctrl_result @@ -2113,7 +2119,6 @@ ctrl_result_response(Status, #state{client = From} = State, _) (Status =:= epnospc) orelse (Status =:= efnamena) orelse (Status =:= econn) -> -%Status == etnospc; Status == epnospc; Status == econn -> gen_server:reply(From, {error, Status}), %% {stop, normal, {error, Status}, State#state{client = undefined}}; {stop, normal, State#state{client = undefined}}; @@ -2378,6 +2383,7 @@ close_ctrl_connection(#state{csock = Socket}) -> close_connection(Socket). close_data_connection(#state{dsock = undefined}) -> ok; close_data_connection(#state{dsock = Socket}) -> close_connection(Socket). +close_connection({lsock,Socket}) -> gen_tcp:close(Socket); close_connection({tcp, Socket}) -> gen_tcp:close(Socket); close_connection({ssl, Socket}) -> ssl:close(Socket). diff --git a/lib/inets/src/ftp/ftp_response.erl b/lib/inets/src/ftp/ftp_response.erl index 32db2dfe66..7533bc4550 100644 --- a/lib/inets/src/ftp/ftp_response.erl +++ b/lib/inets/src/ftp/ftp_response.erl @@ -194,5 +194,6 @@ interpret_status(?TRANS_NEG_COMPL,_,_) -> trans_neg_compl; interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,0) -> epath; interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,2) -> epnospc; interpret_status(?PERM_NEG_COMPL,?FILE_SYSTEM,3) -> efnamena; +interpret_status(?PERM_NEG_COMPL,?AUTH_ACC,0) -> elogin; interpret_status(?PERM_NEG_COMPL,_,_) -> perm_neg_compl. diff --git a/lib/inets/test/ftp_SUITE.erl b/lib/inets/test/ftp_SUITE.erl index 08295d4e3c..a8d39e3fe7 100644 --- a/lib/inets/test/ftp_SUITE.erl +++ b/lib/inets/test/ftp_SUITE.erl @@ -50,12 +50,17 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,20}}]. + all() -> [ {group, ftp_passive}, {group, ftp_active}, {group, ftps_passive}, - {group, ftps_active} + {group, ftps_active}, + error_ehost, + clean_shutdown ]. groups() -> @@ -92,14 +97,13 @@ ftp_tests()-> recv_chunk, type, quote, - ip_v6_disabled, + error_elogin, progress_report_send, progress_report_recv, not_owner, unexpected_call, unexpected_cast, - unexpected_bang, - clean_shutdown + unexpected_bang ]. %%-------------------------------------------------------------------- @@ -190,35 +194,31 @@ init_per_group(_Group, Config) -> Config. end_per_group(_Group, Config) -> Config. %%-------------------------------------------------------------------- - -init_per_testcase(Case, Config) when (Case =:= progress_report_send) orelse - (Case =:= progress_report_recv) -> - common_init_per_testcase(Case, [{progress, {?MODULE, progress, #progress{}}} | Config]); - -init_per_testcase(Case, Config) -> - common_init_per_testcase(Case, Config). - -common_init_per_testcase(Case, Config0) -> - Group = proplists:get_value(name,proplists:get_value(tc_group_properties,Config0)), - try ?MODULE:Case(doc) of - Msg -> ct:comment(Msg) - catch - _:_-> ok - end, +init_per_testcase(Case, Config0) -> + Group = proplists:get_value(name, proplists:get_value(tc_group_properties,Config0)), TLS = [{tls,[{reuse_sessions,true}]}], ACTIVE = [{mode,active}], PASSIVE = [{mode,passive}], - ExtraOpts = [verbose], + CaseOpts = case Case of + progress_report_send -> [{progress, {?MODULE,progress,#progress{}}}]; + progress_report_recv -> [{progress, {?MODULE,progress,#progress{}}}]; + _ -> [] + end, + ExtraOpts = [verbose | CaseOpts], Config = case Group of - ftp_active -> ftp__open(Config0, ACTIVE ++ExtraOpts); - ftps_active -> ftp__open(Config0, TLS++ ACTIVE ++ExtraOpts); - ftp_passive -> ftp__open(Config0, PASSIVE ++ExtraOpts); - ftps_passive -> ftp__open(Config0, TLS++PASSIVE ++ExtraOpts) + ftp_active -> ftp__open(Config0, ACTIVE ++ ExtraOpts); + ftps_active -> ftp__open(Config0, TLS++ ACTIVE ++ ExtraOpts); + ftp_passive -> ftp__open(Config0, PASSIVE ++ ExtraOpts); + ftps_passive -> ftp__open(Config0, TLS++PASSIVE ++ ExtraOpts); + undefined -> Config0 end, case Case of - user -> Config; - bad_user -> Config; + user -> Config; + bad_user -> Config; + error_elogin -> Config; + error_ehost -> Config; + clean_shutdown -> Config; _ -> Pid = proplists:get_value(ftp,Config), ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS(atom_to_list(Group)++"-"++atom_to_list(Case)) ), @@ -229,6 +229,9 @@ common_init_per_testcase(Case, Config0) -> end_per_testcase(user, _Config) -> ok; end_per_testcase(bad_user, _Config) -> ok; +end_per_testcase(error_elogin, _Config) -> ok; +end_per_testcase(error_ehost, _Config) -> ok; +end_per_testcase(clean_shutdown, _Config) -> ok; end_per_testcase(_Case, Config) -> case proplists:get_value(tc_status,Config) of ok -> ok; @@ -286,7 +289,8 @@ cd(Config0) -> {ok, PWD} = ftp:pwd(Pid), ExpectedPWD = id2ftp_result(Dir, Config), PWD = ExpectedPWD, - {error, epath} = ftp:cd(Pid, ?BAD_DIR). + {error, epath} = ftp:cd(Pid, ?BAD_DIR), + ok. %%------------------------------------------------------------------------- lcd() -> @@ -359,8 +363,11 @@ rename(Config0) -> id2ftp(NewFile,Config)), true = (chk_file(NewFile,Contents,Config) - and chk_no_file([OldFile],Config)). - + and chk_no_file([OldFile],Config)), + {error,epath} = ftp:rename(Pid, + id2ftp("non_existing_file",Config), + id2ftp(NewFile,Config)), + ok. %%------------------------------------------------------------------------- send() -> @@ -372,14 +379,16 @@ send(Config0) -> Config = set_state([reset,{mkfile,[SrcDir,File],Contents}], Config0), Pid = proplists:get_value(ftp, Config), -chk_no_file([File],Config), -chk_file([SrcDir,File],Contents,Config), + chk_no_file([File],Config), + chk_file([SrcDir,File],Contents,Config), ok = ftp:lcd(Pid, id2ftp(SrcDir,Config)), ok = ftp:cd(Pid, id2ftp("",Config)), ok = ftp:send(Pid, File), + chk_file(File, Contents, Config), - chk_file(File, Contents, Config). + {error,epath} = ftp:send(Pid, "non_existing_file"), + ok. %%------------------------------------------------------------------------- send_3() -> @@ -395,8 +404,10 @@ send_3(Config0) -> ok = ftp:cd(Pid, id2ftp(Dir,Config)), ok = ftp:lcd(Pid, id2ftp("",Config)), ok = ftp:send(Pid, File, RemoteFile), + chk_file([Dir,RemoteFile], Contents, Config), - chk_file([Dir,RemoteFile], Contents, Config). + {error,epath} = ftp:send(Pid, "non_existing_file", RemoteFile), + ok. %%------------------------------------------------------------------------- send_bin() -> @@ -408,24 +419,33 @@ send_bin(Config0) -> Pid = proplists:get_value(ftp, Config), {error, enotbinary} = ftp:send_bin(Pid, "some string", id2ftp(File,Config)), ok = ftp:send_bin(Pid, BinContents, id2ftp(File,Config)), - chk_file(File, BinContents, Config). + chk_file(File, BinContents, Config), + {error, efnamena} = ftp:send_bin(Pid, BinContents, "/nothere"), + ok. %%------------------------------------------------------------------------- send_chunk() -> [{doc, "Send a binary using chunks."}]. send_chunk(Config0) -> - Contents = <<"ftp_SUITE test ...">>, + Contents1 = <<"1: ftp_SUITE test ...">>, + Contents2 = <<"2: ftp_SUITE test ...">>, File = "file.txt", Config = set_state([reset,{mkdir,"incoming"}], Config0), Pid = proplists:get_value(ftp, Config), ok = ftp:send_chunk_start(Pid, id2ftp(File,Config)), + {error, echunk} = ftp:send_chunk_start(Pid, id2ftp(File,Config)), {error, echunk} = ftp:cd(Pid, "incoming"), {error, enotbinary} = ftp:send_chunk(Pid, "some string"), - ok = ftp:send_chunk(Pid, Contents), - ok = ftp:send_chunk(Pid, Contents), + ok = ftp:send_chunk(Pid, Contents1), + ok = ftp:send_chunk(Pid, Contents2), ok = ftp:send_chunk_end(Pid), - chk_file(File, <<Contents/binary,Contents/binary>>, Config). + chk_file(File, <<Contents1/binary,Contents2/binary>>, Config), + + {error, echunk} = ftp:send_chunk(Pid, Contents1), + {error, echunk} = ftp:send_chunk_end(Pid), + {error, efnamena} = ftp:send_chunk_start(Pid, "/"), + ok. %%------------------------------------------------------------------------- delete() -> @@ -436,7 +456,9 @@ delete(Config0) -> Config = set_state([reset,{mkfile,File,Contents}], Config0), Pid = proplists:get_value(ftp, Config), ok = ftp:delete(Pid, id2ftp(File,Config)), - chk_no_file([File], Config). + chk_no_file([File], Config), + {error,epath} = ftp:delete(Pid, id2ftp(File,Config)), + ok. %%------------------------------------------------------------------------- mkdir() -> @@ -446,7 +468,9 @@ mkdir(Config0) -> Config = set_state([reset], Config0), Pid = proplists:get_value(ftp, Config), ok = ftp:mkdir(Pid, id2ftp(NewDir,Config)), - chk_dir([NewDir], Config). + chk_dir([NewDir], Config), + {error,epath} = ftp:mkdir(Pid, id2ftp(NewDir,Config)), + ok. %%------------------------------------------------------------------------- rmdir() -> @@ -456,7 +480,9 @@ rmdir(Config0) -> Config = set_state([reset,{mkdir,Dir}], Config0), Pid = proplists:get_value(ftp, Config), ok = ftp:rmdir(Pid, id2ftp(Dir,Config)), - chk_no_dir([Dir], Config). + chk_no_dir([Dir], Config), + {error,epath} = ftp:rmdir(Pid, id2ftp(Dir,Config)), + ok. %%------------------------------------------------------------------------- append() -> @@ -469,7 +495,9 @@ append(Config0) -> Pid = proplists:get_value(ftp, Config), ok = ftp:append(Pid, id2ftp(SrcFile,Config), id2ftp(DstFile,Config)), ok = ftp:append(Pid, id2ftp(SrcFile,Config), id2ftp(DstFile,Config)), - chk_file(DstFile, <<Contents/binary,Contents/binary>>, Config). + chk_file(DstFile, <<Contents/binary,Contents/binary>>, Config), + {error,epath} = ftp:append(Pid, id2ftp("non_existing_file",Config), id2ftp(DstFile,Config)), + ok. %%------------------------------------------------------------------------- append_bin() -> @@ -511,7 +539,9 @@ recv(Config0) -> ok = ftp:cd(Pid, id2ftp(SrcDir,Config)), ok = ftp:lcd(Pid, id2ftp("",Config)), ok = ftp:recv(Pid, File), - chk_file(File, Contents, Config). + chk_file(File, Contents, Config), + {error,epath} = ftp:recv(Pid, "non_existing_file"), + ok. %%------------------------------------------------------------------------- recv_3() -> @@ -535,7 +565,9 @@ recv_bin(Config0) -> Config = set_state([reset, {mkfile,File,Contents}], Config0), Pid = proplists:get_value(ftp, Config), {ok,Received} = ftp:recv_bin(Pid, id2ftp(File,Config)), - find_diff(Received, Contents). + find_diff(Received, Contents), + {error,epath} = ftp:recv_bin(Pid, id2ftp("non_existing_file",Config)), + ok. %%------------------------------------------------------------------------- recv_chunk() -> @@ -581,6 +613,154 @@ quote(Config) -> %% = ftp:quote(Pid, "list"), ok. +%%------------------------------------------------------------------------- +progress_report_send() -> + [{doc, "Test the option progress for ftp:send/[2,3]"}]. +progress_report_send(Config) when is_list(Config) -> + ReportPid = + spawn_link(?MODULE, progress_report_receiver_init, [self(), 1]), + send(Config), + receive + {ReportPid, ok} -> + ok + end. + +%%------------------------------------------------------------------------- +progress_report_recv() -> + [{doc, "Test the option progress for ftp:recv/[2,3]"}]. +progress_report_recv(Config) when is_list(Config) -> + ReportPid = + spawn_link(?MODULE, progress_report_receiver_init, [self(), 3]), + recv(Config), + receive + {ReportPid, ok} -> + ok + end. + +%%------------------------------------------------------------------------- + +not_owner() -> + [{doc, "Test what happens if a process that not owns the connection tries " + "to use it"}]. +not_owner(Config) when is_list(Config) -> + Pid = proplists:get_value(ftp, Config), + + Parent = self(), + OtherPid = spawn_link( + fun() -> + {error, not_connection_owner} = ftp:pwd(Pid), + ftp:close(Pid), + Parent ! {self(), ok} + end), + receive + {OtherPid, ok} -> + {ok, _} = ftp:pwd(Pid) + end. + + +%%------------------------------------------------------------------------- + + +unexpected_call()-> + [{doc, "Test that behaviour of the ftp process if the api is abused"}]. +unexpected_call(Config) when is_list(Config) -> + Flag = process_flag(trap_exit, true), + Pid = proplists:get_value(ftp, Config), + + %% Serious programming fault, connetion will be shut down + case (catch gen_server:call(Pid, {self(), foobar, 10}, infinity)) of + {error, {connection_terminated, 'API_violation'}} -> + ok; + Unexpected1 -> + exit({unexpected_result, Unexpected1}) + end, + ct:sleep(500), + undefined = process_info(Pid, status), + process_flag(trap_exit, Flag). +%%------------------------------------------------------------------------- + +unexpected_cast()-> + [{doc, "Test that behaviour of the ftp process if the api is abused"}]. +unexpected_cast(Config) when is_list(Config) -> + Flag = process_flag(trap_exit, true), + Pid = proplists:get_value(ftp, Config), + %% Serious programming fault, connetion will be shut down + gen_server:cast(Pid, {self(), foobar, 10}), + ct:sleep(500), + undefined = process_info(Pid, status), + process_flag(trap_exit, Flag). +%%------------------------------------------------------------------------- + +unexpected_bang()-> + [{doc, "Test that connection ignores unexpected bang"}]. +unexpected_bang(Config) when is_list(Config) -> + Flag = process_flag(trap_exit, true), + Pid = proplists:get_value(ftp, Config), + %% Could be an innocent misstake the connection lives. + Pid ! foobar, + ct:sleep(500), + {status, _} = process_info(Pid, status), + process_flag(trap_exit, Flag). + +%%------------------------------------------------------------------------- + +clean_shutdown() -> + [{doc, "Test that owning process that exits with reason " + "'shutdown' does not cause an error message. OTP 6035"}]. + +clean_shutdown(Config) -> + Parent = self(), + HelperPid = spawn( + fun() -> + ftp__open(Config, [verbose]), + Parent ! ok, + receive + nothing -> ok + end + end), + receive + ok -> + PrivDir = proplists:get_value(priv_dir, Config), + LogFile = filename:join([PrivDir,"ticket_6035.log"]), + error_logger:logfile({open, LogFile}), + exit(HelperPid, shutdown), + timer:sleep(2000), + error_logger:logfile(close), + case is_error_report_6035(LogFile) of + true -> ok; + false -> {fail, "Bad logfile"} + end + end. + +%%%---------------------------------------------------------------- +%%% Error codes not tested elsewhere + +error_elogin(Config0) -> + Dir = "test", + OldFile = "old.txt", + NewFile = "new.txt", + SrcDir = "data", + File = "file.txt", + Config = set_state([reset, + {mkdir,Dir}, + {mkfile,OldFile,<<"Contents..">>}, + {mkfile,[SrcDir,File],<<"Contents..">>}], Config0), + + Pid = proplists:get_value(ftp, Config), + ok = ftp:lcd(Pid, id2ftp(SrcDir,Config)), + {error,elogin} = ftp:send(Pid, File), + ok = ftp:lcd(Pid, id2ftp("",Config)), + {error,elogin} = ftp:pwd(Pid), + {error,elogin} = ftp:cd(Pid, id2ftp(Dir,Config)), + {error,elogin} = ftp:rename(Pid, + id2ftp(OldFile,Config), + id2ftp(NewFile,Config)), + ok. + +error_ehost(_Config) -> + {error, ehost} = ftp:open("nohost.nodomain"), + ok. + %%-------------------------------------------------------------------- %% Internal functions ----------------------------------------------- %%-------------------------------------------------------------------- @@ -674,112 +854,7 @@ chk_no_dir(PathList, Config) -> ct:fail("Unexpected error for ~p: ~p",[Path,Error]) end. -%%------------------------------------------------------------------------- -progress_report_send() -> - [{doc, "Test the option progress for ftp:send/[2,3]"}]. -progress_report_send(Config) when is_list(Config) -> - ReportPid = - spawn_link(?MODULE, progress_report_receiver_init, [self(), 1]), - send(Config), - receive - {ReportPid, ok} -> - ok - end. -%%------------------------------------------------------------------------- -progress_report_recv() -> - [{doc, "Test the option progress for ftp:recv/[2,3]"}]. -progress_report_recv(Config) when is_list(Config) -> - ReportPid = - spawn_link(?MODULE, progress_report_receiver_init, [self(), 3]), - recv(Config), - receive - {ReportPid, ok} -> - ok - end. - -%%------------------------------------------------------------------------- - -not_owner() -> - [{doc, "Test what happens if a process that not owns the connection tries " - "to use it"}]. -not_owner(Config) when is_list(Config) -> - Pid = proplists:get_value(ftp, Config), - OtherPid = spawn_link(?MODULE, not_owner, [Pid, self()]), - - receive - {OtherPid, ok} -> - {ok, _} = ftp:pwd(Pid) - end. - - -%%------------------------------------------------------------------------- - - -unexpected_call()-> - [{doc, "Test that behaviour of the ftp process if the api is abused"}]. -unexpected_call(Config) when is_list(Config) -> - Flag = process_flag(trap_exit, true), - Pid = proplists:get_value(ftp, Config), - - %% Serious programming fault, connetion will be shut down - case (catch gen_server:call(Pid, {self(), foobar, 10}, infinity)) of - {error, {connection_terminated, 'API_violation'}} -> - ok; - Unexpected1 -> - exit({unexpected_result, Unexpected1}) - end, - ct:sleep(500), - undefined = process_info(Pid, status), - process_flag(trap_exit, Flag). -%%------------------------------------------------------------------------- - -unexpected_cast()-> - [{doc, "Test that behaviour of the ftp process if the api is abused"}]. -unexpected_cast(Config) when is_list(Config) -> - Flag = process_flag(trap_exit, true), - Pid = proplists:get_value(ftp, Config), - %% Serious programming fault, connetion will be shut down - gen_server:cast(Pid, {self(), foobar, 10}), - ct:sleep(500), - undefined = process_info(Pid, status), - process_flag(trap_exit, Flag). -%%------------------------------------------------------------------------- - -unexpected_bang()-> - [{doc, "Test that connection ignores unexpected bang"}]. -unexpected_bang(Config) when is_list(Config) -> - Flag = process_flag(trap_exit, true), - Pid = proplists:get_value(ftp, Config), - %% Could be an innocent misstake the connection lives. - Pid ! foobar, - ct:sleep(500), - {status, _} = process_info(Pid, status), - process_flag(trap_exit, Flag). - -%%------------------------------------------------------------------------- - -clean_shutdown() -> - [{doc, "Test that owning process that exits with reason " - "'shutdown' does not cause an error message. OTP 6035"}]. - -clean_shutdown(Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - LogFile = filename:join([PrivDir,"ticket_6035.log"]), - Host = proplists:get_value(ftpd_host,Config), - try - Pid = spawn(?MODULE, open_wait_6035, [Host, self()]), - error_logger:logfile({open, LogFile}), - true = kill_ftp_proc_6035(Pid, LogFile), - error_logger:logfile(close) - catch - throw:{error, not_found} -> - {skip, "No available FTP servers"} - end. - %%-------------------------------------------------------------------- -%% Internal functions -%%-------------------------------------------------------------------- - find_executable(Config) -> FTPservers = case proplists:get_value(ftpservers,Config) of undefined -> ?default_ftp_servers; @@ -893,12 +968,6 @@ rm(F, Pfx) -> ok end. -not_owner(FtpPid, Pid) -> - {error, not_connection_owner} = ftp:pwd(FtpPid), - ftp:close(FtpPid), - ct:sleep(100), - Pid ! {self(), ok}. - id2abs(Id, Conf) -> filename:join(proplists:get_value(priv_dir,Conf),ids(Id)). id2ftp(Id, Conf) -> (proplists:get_value(id2ftp,Conf))(ids(Id)). id2ftp_result(Id, Conf) -> (proplists:get_value(id2ftp_result,Conf))(ids(Id)). @@ -912,96 +981,75 @@ is_expected_ftpInName(Id, File, Conf) -> File = (proplists:get_value(id2ftp,Conf is_expected_ftpOutName(Id, File, Conf) -> File = (proplists:get_value(id2ftp_result,Conf))(Id). -progress(#progress{} = Progress , _File, {file_size, Total}) -> +%%%---------------------------------------------------------------- +%%% Help functions for the option '{progress,Progress}' +%%% + +%%%---------------- +%%% Callback: + +progress(#progress{} = P, _File, {file_size, Total} = M) -> + ct:pal("Progress: ~p",[M]), progress_report_receiver ! start, - Progress#progress{total = Total}; + P#progress{total = Total}; -progress(#progress{total = Total, current = Current} - = Progress, _File, {transfer_size, 0}) -> +progress(#progress{current = Current} = P, _File, {transfer_size, 0} = M) -> + ct:pal("Progress: ~p",[M]), progress_report_receiver ! finish, - case Total of - unknown -> - ok; - Current -> - ok; - _ -> - ct:fail({error, {progress, {total, Total}, - {current, Current}}}) - end, - Progress; -progress(#progress{current = Current} = Progress, _File, - {transfer_size, Size}) -> + case P#progress.total of + unknown -> P; + Current -> P; + Total -> ct:fail({error, {progress, {total,Total}, {current,Current}}}), + P + end; + +progress(#progress{current = Current} = P, _File, {transfer_size, Size} = M) -> + ct:pal("Progress: ~p",[M]), progress_report_receiver ! update, - Progress#progress{current = Current + Size}. + P#progress{current = Current + Size}; + +progress(P, _File, M) -> + ct:pal("Progress **** Strange: ~p",[M]), + P. + + +%%%---------------- +%%% Help process that counts the files transferred: -progress_report_receiver_init(Pid, N) -> +progress_report_receiver_init(Parent, N) -> register(progress_report_receiver, self()), + progress_report_receiver_expect_N_files(Parent, N). + +progress_report_receiver_expect_N_files(_Parent, 0) -> + ct:pal("progress_report got all files!", []); +progress_report_receiver_expect_N_files(Parent, N) -> + ct:pal("progress_report expects ~p more files",[N]), receive - start -> - ok + start -> ok end, - progress_report_receiver_loop(Pid, N-1). - -progress_report_receiver_loop(Pid, N) -> - receive - update -> - progress_report_receiver_loop(Pid, N); - finish when N =:= 0 -> - Pid ! {self(), ok}; - finish -> - Pid ! {self(), ok}, - receive - start -> - ok - end, - progress_report_receiver_loop(Pid, N-1) - end. - -kill_ftp_proc_6035(Pid, LogFile) -> + progress_report_receiver_loop(Parent, N-1). + + +progress_report_receiver_loop(Parent, N) -> + ct:pal("progress_report expect update | finish. N = ~p",[N]), receive - open -> - exit(Pid, shutdown), - kill_ftp_proc_6035(Pid, LogFile); - {open_failed, Reason} -> - exit({skip, {failed_openening_server_connection, Reason}}) - after - 5000 -> - is_error_report_6035(LogFile) + update -> + ct:pal("progress_report got update",[]), + progress_report_receiver_loop(Parent, N); + finish -> + ct:pal("progress_report got finish, send ~p to ~p",[{self(),ok}, Parent]), + Parent ! {self(), ok}, + progress_report_receiver_expect_N_files(Parent, N) end. -open_wait_6035({_Tag, FtpServer}, From) -> - case ftp:open(FtpServer, [{timeout, timer:seconds(15)}]) of - {ok, Pid} -> - _LoginResult = ftp:user(Pid,"anonymous","kldjf"), - From ! open, - receive - dummy -> - ok - after - 10000 -> - ok - end, - ok; - {error, Reason} -> - From ! {open_failed, {Reason, FtpServer}}, - ok - end. +%%%---------------------------------------------------------------- +%%% Help functions for bug OTP-6035 is_error_report_6035(LogFile) -> - Res = - case file:read_file(LogFile) of - {ok, Bin} -> - Txt = binary_to_list(Bin), - read_log_6035(Txt); - _ -> - false - end, - %% file:delete(LogFile), - Res. - -read_log_6035("=ERROR REPORT===="++_Rest) -> - true; -read_log_6035([_|T]) -> - read_log_6035(T); -read_log_6035([]) -> - false. + case file:read_file(LogFile) of + {ok, Bin} -> + nomatch =/= binary:match(Bin, <<"=ERROR REPORT====">>); + _ -> + false + end. + diff --git a/lib/inets/test/ftp_format_SUITE.erl b/lib/inets/test/ftp_format_SUITE.erl index 2c17e2657c..a33b31f46f 100644 --- a/lib/inets/test/ftp_format_SUITE.erl +++ b/lib/inets/test/ftp_format_SUITE.erl @@ -253,7 +253,7 @@ ftp_other_status_codes(Config) when is_list(Config) -> {perm_neg_compl, _ } = ftp_response:interpret("501 Foobar\r\n"), {perm_neg_compl, _ } = ftp_response:interpret("503 Foobar\r\n"), {perm_neg_compl, _ } = ftp_response:interpret("504 Foobar\r\n"), - {perm_neg_compl, _ } = ftp_response:interpret("530 Foobar\r\n"), + {elogin, _ } = ftp_response:interpret("530 Foobar\r\n"), {perm_neg_compl, _ } = ftp_response:interpret("532 Foobar\r\n"), {epath, _ } = ftp_response:interpret("550 Foobar\r\n"), {epnospc, _ } = ftp_response:interpret("552 Foobar\r\n"), diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl index 6248d7478c..f60c13d2e3 100644 --- a/lib/kernel/test/inet_SUITE.erl +++ b/lib/kernel/test/inet_SUITE.erl @@ -139,11 +139,13 @@ t_gethostbyaddr(Config) when is_list(Config) -> ok; _ -> io:format("alias list: ~p", [HEnt#hostent.h_aliases]), - io:format("check alias list: ~p", [[Aliases,[Rname]]]), + io:format( + "check alias list: ~p", [[Aliases,tl(Aliases),[Rname]]]), io:format("name: ~p", [HEnt#hostent.h_name]), io:format("check name: ~p", [[Name,FullName]]), - check_elems([{HEnt#hostent.h_name,[Name,FullName]}, - {HEnt#hostent.h_aliases,[[],Aliases,[Rname]]}]) + check_elems( + [{HEnt#hostent.h_name,[Name,FullName]}, + {HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases),[Rname]]}]) end, {_DName, _DFullName, DIPStr, DIP, _, _, _} = ct:get_config(test_dummy_host), @@ -171,8 +173,9 @@ t_gethostbyaddr_v6(Config) when is_list(Config) -> h_length = 16, h_addr_list = [IP6]}, HEnt6_ = HEnt6, - check_elems([{HEnt6#hostent.h_name,[Name6,FullName6]}, - {HEnt6#hostent.h_aliases,[[],Aliases6]}]), + check_elems( + [{HEnt6#hostent.h_name,[Name6,FullName6]}, + {HEnt6#hostent.h_aliases,[[],Aliases6,tl(Aliases6)]}]), {_DName6, _DFullName6, DIPStr6, DIP6, _} = ct:get_config(test_dummy_ipv6_host), @@ -195,14 +198,14 @@ t_gethostbyname(Config) when is_list(Config) -> HEnt_ = HEnt, check_elems([{HEnt#hostent.h_name,[Name,FullName]}, - {HEnt#hostent.h_aliases,[[],Aliases]}]), + {HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]), {ok,HEntF} = inet:gethostbyname(FullName), HEntF_ = HEntF#hostent{h_name = FullName, h_addrtype = inet, h_length = 4, h_addr_list = [IP]}, HEntF_ = HEntF, - check_elems([{HEnt#hostent.h_aliases,[[],Aliases]}]), + check_elems([{HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]), %% FullNameU = toupper(FullName), {ok,HEntU} = inet:gethostbyname(FullNameU), @@ -237,7 +240,7 @@ t_gethostbyname_v6(Config) when is_list(Config) -> h_length = 16} = HEnt, check_elems( [{HEnt#hostent.h_name,[Name,FullName]}, - {HEnt#hostent.h_aliases,[[],Aliases]}]); + {HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]); [IP46] -> % IPv4 compatible address {ok,HEnt4} = inet:gethostbyname(Name, inet), #hostent{h_addrtype = inet, @@ -257,7 +260,7 @@ t_gethostbyname_v6(Config) when is_list(Config) -> h_addrtype = inet6, h_length = 16} = HEntF, check_elems( - [{HEnt#hostent.h_aliases,[[],Aliases]}]); + [{HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]); [IP46F] -> % IPv4 compatible address {ok,HEnt4F} = inet:gethostbyname(FullName, inet), #hostent{h_addrtype = inet, @@ -363,7 +366,7 @@ ipv4_to_ipv6(Config) when is_list(Config) -> h_addr_list = [IP_46]}, HEnt_ = HEnt, check_elems([{HEnt#hostent.h_name,[IP_46_Str,IPStr]}, - {HEnt#hostent.h_aliases,[[],Aliases]}]); + {HEnt#hostent.h_aliases,[[],Aliases,tl(Aliases)]}]); {_,IP4to6Res} -> ok end, ok. diff --git a/lib/kernel/test/inet_res_SUITE.erl b/lib/kernel/test/inet_res_SUITE.erl index 9662d1fef5..6691ad9c06 100644 --- a/lib/kernel/test/inet_res_SUITE.erl +++ b/lib/kernel/test/inet_res_SUITE.erl @@ -42,6 +42,21 @@ -define(RUN_NAMED, "run-named"). +%% This test suite use a script ?RUN_NAMED that tries to start +%% a temporary local nameserver BIND 8 or 9 that must be installed +%% on your machine. +%% +%% For example, on Ubuntu 14.04, as root: +%% apt-get install bind9 +%% Now, that is not enough since Apparmor will not allow +%% the nameserver daemon /usr/sbin/named to read from the test directory. +%% Assuming that you run tests in /ldisk/daily_build, and still on +%% Ubuntu 14.04, make /usr/apparmor.d/local/usr.sbin.named contain: +%% /ldisk/daily_build/** r, +%% And yes; the trailing comma must be there... + + + suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,1}}]. diff --git a/lib/public_key/doc/src/Makefile b/lib/public_key/doc/src/Makefile index f3db24afc9..5bdc5d4159 100644 --- a/lib/public_key/doc/src/Makefile +++ b/lib/public_key/doc/src/Makefile @@ -38,7 +38,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) # ---------------------------------------------------- XML_APPLICATION_FILES = ref_man.xml XML_REF3_FILES = public_key.xml -XML_REF6_FILES = +XML_REF6_FILES = public_key_app.xml XML_PART_FILES = part.xml part_notes.xml XML_CHAPTER_FILES = \ @@ -50,7 +50,7 @@ XML_CHAPTER_FILES = \ BOOK_FILES = book.xml XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) \ - $(XML_PART_FILES) $(XML_CHAPTER_FILES) + $(XML_REF6_FILES) $(XML_PART_FILES) $(XML_CHAPTER_FILES) GIF_FILES = note.gif @@ -67,9 +67,11 @@ EXTRA_FILES = \ $(DEFAULT_GIF_FILES) \ $(DEFAULT_HTML_FILES) \ $(XML_REF3_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_REF6_FILES:%.xml=$(HTMLDIR)/%.html) \ $(XML_CHAPTER_FILES:%.xml=$(HTMLDIR)/%.html) MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) +MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6) HTML_REF_MAN_FILE = $(HTMLDIR)/index.html @@ -98,10 +100,11 @@ html: gifs $(HTML_REF_MAN_FILE) clean clean_docs: rm -rf $(HTMLDIR)/* rm -f $(MAN3DIR)/* + rm -f $(MAN6DIR)/* rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) rm -f errs core *~ -man: $(MAN3_FILES) +man: $(MAN3_FILES) $(MAN6_FILES) gifs: $(GIF_FILES:%=$(HTMLDIR)/%) @@ -122,6 +125,8 @@ release_docs_spec: docs $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3" + $(INSTALL_DIR) "$(RELEASE_PATH)/man/man6" + $(INSTALL_DATA) $(MAN6DIR)/* "$(RELEASE_PATH)/man/man6" release_spec: info: diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index 04daee460f..1aa601dc55 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -34,40 +34,13 @@ <module>public_key</module> <modulesummary>API module for public-key infrastructure.</modulesummary> <description> - <p>This module provides functions to handle public-key infrastructure. It can - encode/decode different file formats (PEM, OpenSSH), sign and verify digital signatures, - and validate certificate paths and certificate revocation lists. + <p>Provides functions to handle public-key infrastructure, + for details see + <seealso marker="public_key_app">public_key(6)</seealso>. </p> </description> <section> - <title>public_key</title> - - <list type="bulleted"> - <item> Public Key requires the Crypto and ASN1 applications, - the latter as OTP R16 (hopefully the runtime dependency on ASN1 will - be removed again in the future).</item> - - <item>Supports <url href="http://www.ietf.org/rfc/rfc5280.txt">RFC 5280 </url> - - Internet X.509 Public-Key Infrastructure Certificate and Certificate Revocation List - (CRL) Profile </item> - <item>Supports <url href="http://www.ietf.org/rfc/rfc3447.txt"> PKCS-1 </url> - - RSA Cryptography Standard </item> - <item>Supports <url href="http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf"> DSS</url> - - Digital Signature Standard (DSA - Digital Signature Algorithm)</item> - <item>Supports - <url href="http://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-3-diffie-hellman-key-agreement-standar.htm"> PKCS-3 </url> - - Diffie-Hellman Key Agreement Standard </item> - <item>Supports <url href="http://www.ietf.org/rfc/rfc2898.txt"> PKCS-5</url> - - Password-Based Cryptography Standard </item> - <item>Supports <url href="http://www.ietf.org/rfc/rfc5208.txt"> PKCS-8</url> - - Private-Key Information Syntax Standard</item> - <item>Supports <url href="http://www.ietf.org/rfc/rfc5967.txt"> PKCS-10</url> - - Certification Request Syntax Standard</item> - </list> - </section> - - <section> <title>DATA TYPES</title> <note><p>All records used in this Reference Manual diff --git a/lib/public_key/doc/src/public_key_app.xml b/lib/public_key/doc/src/public_key_app.xml new file mode 100644 index 0000000000..1f87932b6c --- /dev/null +++ b/lib/public_key/doc/src/public_key_app.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE appref SYSTEM "appref.dtd"> + +<appref> + <header> + <copyright> + <year>2016</year><year>2016</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + </legalnotice> + + <title>public_key</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + <file>public_key_app.sgml</file> + </header> + <app>public_key</app> + <appsummary>Provides functions to handle public-key infrastructure. </appsummary> + <description> + + <p> Provides encode/decode of different file formats (PEM, OpenSSH), + digital signature and verification functions, + validation of certificate paths and certificate revocation lists (CRLs) and + other functions for handling of certificates, keys and CRLs.</p> + + <list type="bulleted"> + <item>Supports <url href="http://www.ietf.org/rfc/rfc5280.txt">RFC 5280 </url> - + Internet X.509 Public-Key Infrastructure Certificate and Certificate Revocation List + (CRL) Profile. Certificate policies are currently not supported. </item> + <item>Supports <url href="http://www.ietf.org/rfc/rfc3447.txt"> PKCS-1 </url> - + RSA Cryptography Standard </item> + <item>Supports <url href="http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf"> DSS</url> - + Digital Signature Standard (DSA - Digital Signature Algorithm)</item> + <item>Supports + <url href="http://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-3-diffie-hellman-key-agreement-standar.htm"> PKCS-3 </url> - + Diffie-Hellman Key Agreement Standard </item> + <item>Supports <url href="http://www.ietf.org/rfc/rfc2898.txt"> PKCS-5</url> - + Password-Based Cryptography Standard </item> + <item>Supports <url href="http://www.ietf.org/rfc/rfc5208.txt"> PKCS-8</url> - + Private-Key Information Syntax Standard</item> + <item>Supports <url href="http://www.ietf.org/rfc/rfc5967.txt"> PKCS-10</url> - + Certification Request Syntax Standard</item> + </list> + </description> + + <section> + <title>DEPENDENCIES</title> + <p>The <c>public_key</c> application uses the + Crypto application to preform cryptographic operations and the + ASN-1 application to handle PKIX-ASN-1 specifications, hence + these applications must be loaded for the <c>public_key</c> application to work. + In an embedded environment this means they must be started with + <c>application:start/[1,2]</c> before the <c>public_key</c> application is + started.</p> + </section> + + <section> + <title>ERROR LOGGER AND EVENT HANDLERS</title> + <p> The <c>public_key</c> application is a library application + and does not use the error logger. The functions will either sucssed + or fail with a runtime error. + </p> + </section> + + <section> + <title>SEE ALSO</title> + <p><seealso marker="kernel:application">application(3)</seealso></p> + </section> + +</appref> diff --git a/lib/public_key/doc/src/ref_man.xml b/lib/public_key/doc/src/ref_man.xml index 75c5374257..2bd1733dbc 100644 --- a/lib/public_key/doc/src/ref_man.xml +++ b/lib/public_key/doc/src/ref_man.xml @@ -36,6 +36,7 @@ from RFC 3280 (X.509 certificates) and parts of the PKCS standard. </p> </description> + <xi:include href="public_key_app.xml"/> <xi:include href="public_key.xml"/> </application> diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml index bd330e479f..e6c54d27bf 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -124,10 +124,10 @@ </func> <func> - <name>connect(TcpSocket, Options) -> </name> - <name>connect(TcpSocket, Options, Timeout) -> </name> <name>connect(Host, Port, Options) -> </name> - <name>connect(Host, Port, Options, Timeout) -> + <name>connect(Host, Port, Options, Timeout) -> </name> + <name>connect(TcpSocket, Options) -> </name> + <name>connect(TcpSocket, Options, Timeout) -> {ok, ssh_connection_ref()} | {error, Reason}</name> <fsummary>Connects to an SSH server.</fsummary> <type> @@ -140,7 +140,7 @@ <d>Negotiation time-out in milli-seconds. The default value is <c>infinity</c>. For connection time-out, use option <c>{connect_timeout, timeout()}</c>.</d> <v>TcpSocket = port()</v> - <d>The socket is supposed to be from <c>gen_tcp:connect</c> with option <c>{active,false}</c></d> + <d>The socket is supposed to be from <seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect</seealso> or <seealso marker="kernel:gen_tcp#accept-1">gen_tcp:accept</seealso> with option <c>{active,false}</c></d> </type> <desc> <p>Connects to an SSH server. No channel is started. This is done @@ -351,8 +351,9 @@ <func> <name>daemon(Port) -> </name> <name>daemon(Port, Options) -> </name> - <name>daemon(HostAddress, Port, Options) -> {ok, - ssh_daemon_ref()} | {error, atom()}</name> + <name>daemon(HostAddress, Port, Options) -> </name> + <name>daemon(TcpSocket) -> </name> + <name>daemon(TcpSocket, Options) -> {ok, ssh_daemon_ref()} | {error, atom()}</name> <fsummary>Starts a server listening for SSH connections on the given port.</fsummary> <type> @@ -361,6 +362,8 @@ <v>Options = [{Option, Value}]</v> <v>Option = atom()</v> <v>Value = term()</v> + <v>TcpSocket = port()</v> + <d>The socket is supposed to be from <seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect</seealso> or <seealso marker="kernel:gen_tcp#accept-1">gen_tcp:accept</seealso> with option <c>{active,false}</c></d> </type> <desc> <p>Starts a server listening for SSH connections on the given @@ -722,12 +725,15 @@ <func> <name>shell(Host) -> </name> <name>shell(Host, Option) -> </name> - <name>shell(Host, Port, Option) -> _</name> + <name>shell(Host, Port, Option) -> </name> + <name>shell(TcpSocket) -> _</name> <fsummary>Starts an interactive shell over an SSH server.</fsummary> <type> <v>Host = string()</v> <v>Port = integer()</v> <v>Options - see ssh:connect/3</v> + <v>TcpSocket = port()</v> + <d>The socket is supposed to be from <seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect</seealso> or <seealso marker="kernel:gen_tcp#accept-1">gen_tcp:accept</seealso> with option <c>{active,false}</c></d> </type> <desc> <p>Starts an interactive shell over an SSH server on the diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml index 67531b7d99..eb6f43d417 100644 --- a/lib/ssh/doc/src/ssh_sftp.xml +++ b/lib/ssh/doc/src/ssh_sftp.xml @@ -526,10 +526,6 @@ </func> <func> - <name>start_channel(TcpSocket) -></name> - <name>start_channel(TcpSocket, Options) -> - {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> - <name>start_channel(ConnectionRef) -></name> <name>start_channel(ConnectionRef, Options) -> {ok, Pid} | {error, reason()|term()}</name> @@ -537,13 +533,18 @@ <name>start_channel(Host, Options) -></name> <name>start_channel(Host, Port, Options) -> {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> + + <name>start_channel(TcpSocket) -></name> + <name>start_channel(TcpSocket, Options) -> + {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> + <fsummary>Starts an SFTP client.</fsummary> <type> <v>Host = string()</v> <v>ConnectionRef = ssh_connection_ref()</v> <v>Port = integer()</v> <v>TcpSocket = port()</v> - <d>The socket is supposed to be from <c>gen_tcp:connect</c> with option <c>{active,false}</c></d> + <d>The socket is supposed to be from <seealso marker="kernel:gen_tcp#connect-3">gen_tcp:connect</seealso> or <seealso marker="kernel:gen_tcp#accept-1">gen_tcp:accept</seealso> with option <c>{active,false}</c></d> <v>Options = [{Option, Value}]</v> </type> <desc> diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 50dfe55798..65f1acc6a6 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -86,29 +86,19 @@ connect(Socket, Options) -> connect(Socket, Options, Timeout) when is_port(Socket) -> case handle_options(Options) of - {error, _Reason} = Error -> - Error; + {error, Error} -> + {error, Error}; {_SocketOptions, SshOptions} -> - case proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}) of - {tcp,_,_} -> - %% Is the socket a valid tcp socket? - case {{ok,[]} =/= inet:getopts(Socket, [delay_send]), - {ok,[{active,false}]} == inet:getopts(Socket, [active]) - } - of - {true, true} -> - {ok, {Host,_Port}} = inet:sockname(Socket), - Opts = [{user_pid,self()}, {host,fmt_host(Host)} | SshOptions], - ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); - {true, false} -> - {error, not_passive_mode}; - _ -> - {error, not_tcp_socket} - end; - {L4,_,_} -> - {error, {unsupported,L4}} + case valid_socket_to_use(Socket, Options) of + ok -> + {ok, {Host,_Port}} = inet:sockname(Socket), + Opts = [{user_pid,self()}, {host,fmt_host(Host)} | SshOptions], + ssh_connection_handler:start_connection(client, Socket, Opts, Timeout); + {error,SockError} -> + {error,SockError} end end; + connect(Host, Port, Options) when is_integer(Port), Port>0 -> connect(Host, Port, Options, infinity). @@ -160,7 +150,7 @@ channel_info(ConnectionRef, ChannelId, Options) -> %%-------------------------------------------------------------------- -spec daemon(integer()) -> {ok, pid()} | {error, term()}. --spec daemon(integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec daemon(integer()|port(), proplists:proplist()) -> {ok, pid()} | {error, term()}. -spec daemon(any | inet:ip_address(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. %% Description: Starts a server listening for SSH connections @@ -169,28 +159,16 @@ channel_info(ConnectionRef, ChannelId, Options) -> daemon(Port) -> daemon(Port, []). -daemon(Port, Options) -> - daemon(any, Port, Options). +daemon(Port, Options) when is_integer(Port) -> + daemon(any, Port, Options); + +daemon(Socket, Options0) when is_port(Socket) -> + Options = daemon_shell_opt(Options0), + start_daemon(Socket, Options). daemon(HostAddr, Port, Options0) -> - Options1 = case proplists:get_value(shell, Options0) of - undefined -> - [{shell, {shell, start, []}} | Options0]; - _ -> - Options0 - end, - - {Host, Inet, Options} = case HostAddr of - any -> - {ok, Host0} = inet:gethostname(), - {Host0, proplists:get_value(inet, Options1, inet), Options1}; - {_,_,_,_} -> - {HostAddr, inet, - [{ip, HostAddr} | Options1]}; - {_,_,_,_,_,_,_,_} -> - {HostAddr, inet6, - [{ip, HostAddr} | Options1]} - end, + Options1 = daemon_shell_opt(Options0), + {Host, Inet, Options} = daemon_host_inet_opt(HostAddr, Options1), start_daemon(Host, Port, Options, Inet). %%-------------------------------------------------------------------- @@ -284,19 +262,128 @@ default_algorithms() -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- +valid_socket_to_use(Socket, Options) -> + case proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}) of + {tcp,_,_} -> + %% Is this tcp-socket a valid socket? + case {is_tcp_socket(Socket), + {ok,[{active,false}]} == inet:getopts(Socket, [active]) + } + of + {true, true} -> + ok; + {true, false} -> + {error, not_passive_mode}; + _ -> + {error, not_tcp_socket} + end; + {L4,_,_} -> + {error, {unsupported,L4}} + end. + +is_tcp_socket(Socket) -> {ok,[]} =/= inet:getopts(Socket, [delay_send]). + + + +daemon_shell_opt(Options) -> + case proplists:get_value(shell, Options) of + undefined -> + [{shell, {shell, start, []}} | Options]; + _ -> + Options + end. + +daemon_host_inet_opt(HostAddr, Options1) -> + case HostAddr of + any -> + {ok, Host0} = inet:gethostname(), + {Host0, proplists:get_value(inet, Options1, inet), Options1}; + {_,_,_,_} -> + {HostAddr, inet, + [{ip, HostAddr} | Options1]}; + {_,_,_,_,_,_,_,_} -> + {HostAddr, inet6, + [{ip, HostAddr} | Options1]} + end. + + +start_daemon(Socket, Options) -> + case handle_options(Options) of + {error, Error} -> + {error, Error}; + {SocketOptions, SshOptions} -> + case valid_socket_to_use(Socket, Options) of + ok -> + try + do_start_daemon(Socket, [{role,server}|SshOptions], SocketOptions) + catch + throw:bad_fd -> {error,bad_fd}; + _C:_E -> {error,{cannot_start_daemon,_C,_E}} + end; + {error,SockError} -> + {error,SockError} + end + end. + start_daemon(Host, Port, Options, Inet) -> case handle_options(Options) of {error, _Reason} = Error -> Error; {SocketOptions, SshOptions}-> try - do_start_daemon(Host, Port,[{role, server} |SshOptions] , [Inet | SocketOptions]) + do_start_daemon(Host, Port, [{role,server}|SshOptions] , [Inet|SocketOptions]) catch throw:bad_fd -> {error,bad_fd}; _C:_E -> {error,{cannot_start_daemon,_C,_E}} end end. +do_start_daemon(Socket, SshOptions, SocketOptions) -> + {ok, {IP,Port}} = + try {ok,_} = inet:sockname(Socket) + catch + _:_ -> throw(bad_socket) + end, + Host = fmt_host(IP), + Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE), + Opts = [{asocket, Socket}, + {asock_owner,self()}, + {address, Host}, + {port, Port}, + {role, server}, + {socket_opts, SocketOptions}, + {ssh_opts, SshOptions}], + {_, Callback, _} = proplists:get_value(transport, SshOptions, {tcp, gen_tcp, tcp_closed}), + case ssh_system_sup:system_supervisor(Host, Port, Profile) of + undefined -> + %% It would proably make more sense to call the + %% address option host but that is a too big change at the + %% monent. The name is a legacy name! + try sshd_sup:start_child(Opts) of + {error, {already_started, _}} -> + {error, eaddrinuse}; + Result = {ok,_} -> + ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket), + Result; + Result = {error, _} -> + Result + catch + exit:{noproc, _} -> + {error, ssh_not_started} + end; + Sup -> + AccPid = ssh_system_sup:acceptor_supervisor(Sup), + case ssh_acceptor_sup:start_child(AccPid, Opts) of + {error, {already_started, _}} -> + {error, eaddrinuse}; + {ok, _} -> + ssh_acceptor:handle_connection(Callback, Host, Port, Opts, Socket), + {ok, Sup}; + Other -> + Other + end + end. + do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> {Host,Port1} = try @@ -312,7 +399,7 @@ do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> _:_ -> throw(bad_fd) end, Profile = proplists:get_value(profile, SshOptions, ?DEFAULT_PROFILE), - {Port, WaitRequestControl, Opts} = + {Port, WaitRequestControl, Opts0} = case Port1 of 0 -> %% Allocate the socket here to get the port number... {_, Callback, _} = @@ -326,17 +413,17 @@ do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> _ -> {Port1, false, []} end, + Opts = [{address, Host}, + {port, Port}, + {role, server}, + {socket_opts, SocketOptions}, + {ssh_opts, SshOptions} | Opts0], case ssh_system_sup:system_supervisor(Host, Port, Profile) of undefined -> %% It would proably make more sense to call the %% address option host but that is a too big change at the %% monent. The name is a legacy name! - try sshd_sup:start_child([{address, Host}, - {port, Port}, - {role, server}, - {socket_opts, SocketOptions}, - {ssh_opts, SshOptions} - | Opts]) of + try sshd_sup:start_child(Opts) of {error, {already_started, _}} -> {error, eaddrinuse}; Result = {ok,_} -> @@ -350,12 +437,7 @@ do_start_daemon(Host0, Port0, SshOptions, SocketOptions) -> end; Sup -> AccPid = ssh_system_sup:acceptor_supervisor(Sup), - case ssh_acceptor_sup:start_child(AccPid, [{address, Host}, - {port, Port}, - {role, server}, - {socket_opts, SocketOptions}, - {ssh_opts, SshOptions} - | Opts]) of + case ssh_acceptor_sup:start_child(AccPid, Opts) of {error, {already_started, _}} -> {error, eaddrinuse}; {ok, _} -> diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl index 90fd951dcd..9f3e60bd62 100644 --- a/lib/ssh/src/ssh_acceptor.erl +++ b/lib/ssh/src/ssh_acceptor.erl @@ -27,7 +27,8 @@ %% Internal application API -export([start_link/5, number_of_connections/1, - callback_listen/3]). + callback_listen/3, + handle_connection/5]). %% spawn export -export([acceptor_init/6, acceptor_loop/6]). diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl index 5035bc8f80..e97ac7b01a 100644 --- a/lib/ssh/src/ssh_system_sup.erl +++ b/lib/ssh/src/ssh_system_sup.erl @@ -131,7 +131,10 @@ init([ServerOpts]) -> RestartStrategy = one_for_one, MaxR = 0, MaxT = 3600, - Children = child_specs(ServerOpts), + Children = case proplists:get_value(asocket,ServerOpts) of + undefined -> child_specs(ServerOpts); + _ -> [] + end, {ok, {{RestartStrategy, MaxR, MaxT}, Children}}. %%%========================================================================= diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl index c9a321fbbd..a52633a269 100644 --- a/lib/ssh/test/ssh_connection_SUITE.erl +++ b/lib/ssh/test/ssh_connection_SUITE.erl @@ -48,6 +48,9 @@ all() -> start_shell_exec, start_shell_exec_fun, start_shell_sock_exec_fun, + start_shell_sock_daemon_exec, + connect_sock_not_tcp, + daemon_sock_not_tcp, gracefull_invalid_version, gracefull_invalid_start, gracefull_invalid_long_start, @@ -57,13 +60,11 @@ all() -> max_channels_option ]. groups() -> - [{openssh, [], payload() ++ ptty()}]. + [{openssh, [], payload() ++ ptty() ++ sock()}]. payload() -> [simple_exec, simple_exec_sock, - connect_sock_not_tcp, - connect_sock_not_passive, small_cat, big_cat, send_after_exit]. @@ -73,6 +74,11 @@ ptty() -> ptty_alloc, ptty_alloc_pixel]. +sock() -> + [connect_sock_not_passive, + daemon_sock_not_passive + ]. + %%-------------------------------------------------------------------- init_per_suite(Config) -> Config. @@ -159,18 +165,30 @@ do_simple_exec(ConnectionRef) -> end. %%-------------------------------------------------------------------- -connect_sock_not_tcp(Config) -> +connect_sock_not_tcp(_Config) -> {ok,Sock} = gen_udp:open(0, []), {error, not_tcp_socket} = ssh:connect(Sock, []), gen_udp:close(Sock). %%-------------------------------------------------------------------- -connect_sock_not_passive(Config) -> +daemon_sock_not_tcp(_Config) -> + {ok,Sock} = gen_udp:open(0, []), + {error, not_tcp_socket} = ssh:daemon(Sock), + gen_udp:close(Sock). + +%%-------------------------------------------------------------------- +connect_sock_not_passive(_Config) -> {ok,Sock} = gen_tcp:connect("localhost", ?SSH_DEFAULT_PORT, []), {error, not_passive_mode} = ssh:connect(Sock, []), gen_tcp:close(Sock). %%-------------------------------------------------------------------- +daemon_sock_not_passive(_Config) -> + {ok,Sock} = gen_tcp:connect("localhost", ?SSH_DEFAULT_PORT, []), + {error, not_passive_mode} = ssh:daemon(Sock), + gen_tcp:close(Sock). + +%%-------------------------------------------------------------------- small_cat() -> [{doc, "Use 'cat' to echo small data block back to us."}]. @@ -520,7 +538,44 @@ start_shell_sock_exec_fun(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- +start_shell_sock_daemon_exec(Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = proplists:get_value(data_dir, Config), + {ok,Sl} = gen_tcp:listen(0, [{active,false}]), + {ok,{_IP,Port}} = inet:sockname(Sl), % _IP is likely to be {0,0,0,0}. Win don't like... + + spawn_link(fun() -> + {ok,Ss} = gen_tcp:connect("localhost", Port, [{active,false}]), + {ok, Pid} = ssh:daemon(Ss, [{system_dir, SysDir}, + {user_dir, UserDir}, + {password, "morot"}, + {exec, fun ssh_exec/1}]) + end), + {ok,Sc} = gen_tcp:accept(Sl), + {ok,ConnectionRef} = ssh:connect(Sc, [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "morot"}, + {user_interaction, true}, + {user_dir, UserDir}]), + + {ok, ChannelId0} = ssh_connection:session_channel(ConnectionRef, infinity), + + success = ssh_connection:exec(ConnectionRef, ChannelId0, + "testing", infinity), + + receive + {ssh_cm, ConnectionRef, {data, _ChannelId, 0, <<"testing\r\n">>}} -> + ok + after 5000 -> + ct:fail("Exec Timeout") + end, + + ssh:close(ConnectionRef). + +%%-------------------------------------------------------------------- gracefull_invalid_version(Config) when is_list(Config) -> PrivDir = proplists:get_value(priv_dir, Config), UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl index 4d40b4647c..19cf6d446e 100644 --- a/lib/ssh/test/ssh_sftp_SUITE.erl +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -673,6 +673,7 @@ start_channel_sock(Config) -> %% Test that the socket is closed when the Connection closes ok = ssh:close(Conn), + timer:sleep(400), %% Until the stop sequence is fixed {error,einval} = inet:getopts(Sock, [active]), ok. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index b45c5c8fc6..90e0810241 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -465,6 +465,14 @@ certify(internal, #certificate{asn1_certificates = []}, Connection:next_record(State0#state{client_certificate_requested = false}), Connection:next_event(certify, Record, State); +certify(internal, #certificate{}, + #state{role = server, + negotiated_version = Version, + ssl_options = #ssl_options{verify = verify_none}} = + State, Connection) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), + Connection:handle_own_alert(Alert, Version, certify, State); + certify(internal, #certificate{} = Cert, #state{negotiated_version = Version, role = Role, diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 60b4fbe995..c7dcbaabe9 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -67,6 +67,7 @@ -define(CLEAN_SESSION_DB, 60000). -define(CLEAN_CERT_DB, 500). -define(DEFAULT_MAX_SESSION_CACHE, 1000). +-define(LOAD_MITIGATION, 10). %%==================================================================== %% API @@ -196,10 +197,12 @@ register_session(Port, Session) -> %%-------------------------------------------------------------------- -spec invalidate_session(host(), inet:port_number(), #session{}) -> ok. invalidate_session(Host, Port, Session) -> + load_mitigation(), cast({invalidate_session, Host, Port, Session}). -spec invalidate_session(inet:port_number(), #session{}) -> ok. invalidate_session(Port, Session) -> + load_mitigation(), cast({invalidate_session, Port, Session}). -spec invalidate_pem(File::binary()) -> ok. @@ -719,3 +722,11 @@ invalidate_session_cache(undefined, CacheCb, Cache) -> start_session_validator(Cache, CacheCb, {invalidate_before, erlang:monotonic_time()}, undefined); invalidate_session_cache(Pid, _CacheCb, _Cache) -> Pid. + +load_mitigation() -> + MSec = rand:uniform(?LOAD_MITIGATION), + receive + after + MSec -> + continue + end. |