1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2004-2020. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21%% 22 23-module(ssh_options). 24 25-include("ssh.hrl"). 26-include_lib("kernel/include/file.hrl"). 27 28-export([default/1, 29 get_value/5, get_value/6, 30 put_value/5, 31 delete_key/5, 32 handle_options/2, 33 keep_user_options/2, 34 keep_set_options/2, 35 no_sensitive/2, 36 initial_default_algorithms/2, 37 check_preferred_algorithms/1 38 ]). 39 40-export_type([private_options/0 41 ]). 42 43%%%================================================================ 44%%% Types 45 46-type option_in() :: proplists:property() | proplists:proplist() . 47 48-type option_class() :: internal_options | socket_options | user_options . 49 50-type option_declaration() :: #{class := user_option | undoc_user_option, 51 chk := fun((any()) -> boolean() | {true,any()}), 52 default => any() 53 }. 54 55-type option_key() :: atom(). 56 57-type option_declarations() :: #{ option_key() := option_declaration() }. 58 59-type error() :: {error,{eoptions,any()}} . 60 61-type private_options() :: #{socket_options := socket_options(), 62 internal_options := internal_options(), 63 option_key() => any() 64 }. 65 66%%%================================================================ 67%%% 68%%% Get an option 69%%% 70 71-spec get_value(option_class(), option_key(), private_options(), 72 atom(), non_neg_integer()) -> any() | no_return(). 73 74get_value(Class, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> 75 case Class of 76 internal_options -> maps:get(Key, maps:get(internal_options,Opts)); 77 socket_options -> proplists:get_value(Key, maps:get(socket_options,Opts)); 78 user_options -> maps:get(Key, Opts) 79 end; 80get_value(Class, Key, Opts, _CallerMod, _CallerLine) -> 81 error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). 82 83 84-spec get_value(option_class(), option_key(), private_options(), fun(() -> any()), 85 atom(), non_neg_integer()) -> any() | no_return(). 86 87get_value(socket_options, Key, Opts, DefFun, _CallerMod, _CallerLine) when is_map(Opts) -> 88 proplists:get_value(Key, maps:get(socket_options,Opts), DefFun); 89get_value(Class, Key, Opts, DefFun, CallerMod, CallerLine) when is_map(Opts) -> 90 try get_value(Class, Key, Opts, CallerMod, CallerLine) 91 of 92 undefined -> DefFun(); 93 Value -> Value 94 catch 95 error:{badkey,Key} -> DefFun() 96 end; 97get_value(Class, Key, Opts, _DefFun, _CallerMod, _CallerLine) -> 98 error({bad_options,Class, Key, Opts, _CallerMod, _CallerLine}). 99 100 101%%%================================================================ 102%%% 103%%% Put an option 104%%% 105 106-spec put_value(option_class(), option_in(), private_options(), 107 atom(), non_neg_integer()) -> private_options(). 108 109put_value(user_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> 110 put_user_value(KeyVal, Opts); 111 112put_value(internal_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> 113 InternalOpts = maps:get(internal_options,Opts), 114 Opts#{internal_options := put_internal_value(KeyVal, InternalOpts)}; 115 116put_value(socket_options, KeyVal, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> 117 SocketOpts = maps:get(socket_options,Opts), 118 Opts#{socket_options := put_socket_value(KeyVal, SocketOpts)}. 119 120 121%%%---------------- 122put_user_value(L, Opts) when is_list(L) -> 123 lists:foldl(fun put_user_value/2, Opts, L); 124put_user_value({Key,Value}, Opts) -> 125 Opts#{Key := Value}. 126 127%%%---------------- 128put_internal_value(L, IntOpts) when is_list(L) -> 129 lists:foldl(fun put_internal_value/2, IntOpts, L); 130put_internal_value({Key,Value}, IntOpts) -> 131 IntOpts#{Key => Value}. 132 133%%%---------------- 134put_socket_value(L, SockOpts) when is_list(L) -> 135 L ++ SockOpts; 136put_socket_value({Key,Value}, SockOpts) -> 137 [{Key,Value} | SockOpts]; 138put_socket_value(A, SockOpts) when is_atom(A) -> 139 [A | SockOpts]. 140 141%%%================================================================ 142%%% 143%%% Delete an option 144%%% 145 146-spec delete_key(option_class(), option_key(), private_options(), 147 atom(), non_neg_integer()) -> private_options(). 148 149delete_key(internal_options, Key, Opts, _CallerMod, _CallerLine) when is_map(Opts) -> 150 InternalOpts = maps:get(internal_options,Opts), 151 Opts#{internal_options := maps:remove(Key, InternalOpts)}. 152 153 154%%%================================================================ 155%%% 156%%% Initialize the options 157%%% 158 159-spec handle_options(role(), client_options()|daemon_options()) -> private_options() | error() . 160 161handle_options(Role, PropList0) -> 162 handle_options(Role, PropList0, #{socket_options => [], 163 internal_options => #{}, 164 key_cb_options => [] 165 }). 166 167handle_options(Role, OptsList0, Opts0) when is_map(Opts0), 168 is_list(OptsList0) -> 169 OptsList1 = proplists:unfold( 170 lists:foldr(fun(T,Acc) when is_tuple(T), 171 size(T) =/= 2-> [{special_trpt_args,T} | Acc]; 172 (X,Acc) -> [X|Acc] 173 end, 174 [], OptsList0)), 175 try 176 OptionDefinitions = default(Role), 177 RoleCnfs = application:get_env(ssh, cnf_key(Role), []), 178 {InitialMap,OptsList2} = 179 maps:fold( 180 fun(K, #{default:=Vd}, {M,PL}) -> 181 %% Now set as the default value: 182 %% 1: from erl command list: erl -ssh opt val 183 %% 2: from config file: {options, [..., {opt,val}, ....]} 184 %% 3: from the hard-coded option values in default/1 185 %% The value in the option list will be handled later in save/3 later 186 case config_val(K, RoleCnfs, OptsList1) of 187 {ok,V1} -> 188 %% A value set in config or options. Replace the current. 189 {M#{K => V1, 190 key_cb_options => [{K,V1} | maps:get(key_cb_options,M)]}, 191 [{K,V1} | PL] 192 }; 193 194 {append,V1} -> 195 %% A value set in config or options, but should be 196 %% appended to the existing value 197 NewVal = maps:get(K,M,[]) ++ V1, 198 {M#{K => NewVal, 199 key_cb_options => [{K,NewVal} | 200 lists:keydelete(K,1,maps:get(key_cb_options,M))]}, 201 [{K,NewVal} | lists:keydelete(K,1,PL)] 202 }; 203 204 undefined -> 205 %% Use the default value 206 {M#{K => Vd}, PL} 207 end 208 %% ; 209 %% (_,_,Acc) -> 210 %% Acc 211 end, 212 {Opts0#{key_cb_options => maps:get(key_cb_options,Opts0)}, 213 [{K,V} || {K,V} <- OptsList1, 214 not maps:is_key(K,Opts0) % Keep socket opts 215 ] 216 }, 217 OptionDefinitions), 218 219 220 %% Enter the user's values into the map; unknown keys are 221 %% treated as socket options 222 final_preferred_algorithms( 223 lists:foldl(fun(KV, Vals) -> 224 save(KV, OptionDefinitions, Vals) 225 end, InitialMap, OptsList2)) 226 catch 227 error:{EO, KV, Reason} when EO == eoptions ; EO == eerl_env -> 228 if 229 Reason == undefined -> 230 {error, {EO,KV}}; 231 is_list(Reason) -> 232 {error, {EO,{KV,lists:flatten(Reason)}}}; 233 true -> 234 {error, {EO,{KV,Reason}}} 235 end 236 end. 237 238cnf_key(server) -> server_options; 239cnf_key(client) -> client_options. 240 241 242config_val(modify_algorithms=Key, RoleCnfs, Opts) -> 243 V = case application:get_env(ssh, Key) of 244 {ok,V0} -> V0; 245 _ -> [] 246 end 247 ++ proplists:get_value(Key, RoleCnfs, []) 248 ++ proplists:get_value(Key, Opts, []), 249 case V of 250 [] -> undefined; 251 _ -> {append,V} 252 end; 253 254config_val(Key, RoleCnfs, Opts) -> 255 case lists:keysearch(Key, 1, Opts) of 256 {value, {_,V}} -> 257 {ok,V}; 258 false -> 259 case lists:keysearch(Key, 1, RoleCnfs) of 260 {value, {_,V}} -> 261 {ok,V}; 262 false -> 263 application:get_env(ssh, Key) % returns {ok,V} | undefined 264 end 265 end. 266 267 268check_fun(Key, Defs) -> 269 case ssh_connection_handler:prohibited_sock_option(Key) of 270 false -> 271 #{chk := Fun} = maps:get(Key, Defs), 272 Fun; 273 true -> 274 fun(_,_) -> forbidden end 275 end. 276 277%%%================================================================ 278%%% 279%%% Check and save one option 280%%% 281 282%%% First compatibility conversions: 283save({allow_user_interaction,V}, Opts, Vals) -> 284 save({user_interaction,V}, Opts, Vals); 285 286%% Special case for socket options 'inet' and 'inet6' 287save(Inet, Defs, OptMap) when Inet==inet ; Inet==inet6 -> 288 save({inet,Inet}, Defs, OptMap); 289 290%% Two clauses to prepare for a proplists:unfold 291save({Inet,true}, Defs, OptMap) when Inet==inet ; Inet==inet6 -> save({inet,Inet}, Defs, OptMap); 292save({Inet,false}, _Defs, OptMap) when Inet==inet ; Inet==inet6 -> OptMap; 293 294%% There are inet-options that are not a tuple sized 2. They where marked earlier 295save({special_trpt_args,T}, _Defs, OptMap) when is_map(OptMap) -> 296 OptMap#{socket_options := [T | maps:get(socket_options,OptMap)]}; 297 298%% and finaly the 'real stuff': 299save({Key,Value}, Defs, OptMap) when is_map(OptMap) -> 300 try (check_fun(Key,Defs))(Value) 301 of 302 true -> 303 OptMap#{Key := Value}; 304 {true, ModifiedValue} -> 305 OptMap#{Key := ModifiedValue}; 306 false -> 307 error({eoptions, {Key,Value}, "Bad value"}); 308 forbidden -> 309 error({eoptions, {Key,Value}, 310 io_lib:format("The option '~s' is used internally. The " 311 "user is not allowed to specify this option.", 312 [Key])}) 313 catch 314 %% An unknown Key (= not in the definition map) is 315 %% regarded as an inet option: 316 error:{badkey,inet} -> 317 %% atomic (= non-tuple) options 'inet' and 'inet6': 318 OptMap#{socket_options := [Value | maps:get(socket_options,OptMap)]}; 319 error:{badkey,Key} -> 320 OptMap#{socket_options := [{Key,Value} | maps:get(socket_options,OptMap)]}; 321 322 %% But a Key that is known but the value does not validate 323 %% by the check fun will give an error exception: 324 error:{check,{BadValue,Extra}} -> 325 error({eoptions, {Key,BadValue}, Extra}) 326 end; 327save(Opt, _Defs, OptMap) when is_map(OptMap) -> 328 OptMap#{socket_options := [Opt | maps:get(socket_options,OptMap)]}. 329 330 331%%%================================================================ 332no_sensitive(rm, #{id_string := _, 333 tstflg := _}) -> '*** removed ***'; 334no_sensitive(filter, Opts = #{id_string := _, 335 tstflg := _}) -> 336 Sensitive = [password, user_passwords, 337 dsa_pass_phrase, rsa_pass_phrase, ecdsa_pass_phrase, 338 ed25519_pass_phrase, ed448_pass_phrase], 339 maps:fold( 340 fun(K, _V, Acc) -> 341 case lists:member(K, Sensitive) of 342 true -> Acc#{K := '***'}; 343 false -> Acc 344 end 345 end, Opts, Opts); 346no_sensitive(Type, L) when is_list(L) -> 347 [no_sensitive(Type,E) || E <- L]; 348no_sensitive(Type, T) when is_tuple(T) -> 349 list_to_tuple( no_sensitive(Type, tuple_to_list(T)) ); 350no_sensitive(_, X) -> 351 X. 352 353%%%================================================================ 354%%% 355-spec keep_user_options(client|server, #{}) -> #{}. 356 357keep_user_options(Type, Opts) -> 358 Defs = default(Type), 359 maps:filter(fun(Key, _Value) -> 360 try 361 #{class := Class} = maps:get(Key,Defs), 362 Class == user_option 363 catch 364 _:_ -> false 365 end 366 end, Opts). 367 368 369-spec keep_set_options(client|server, #{}) -> #{}. 370 371keep_set_options(Type, Opts) -> 372 Defs = default(Type), 373 maps:filter(fun(Key, Value) -> 374 try 375 #{default := DefVal} = maps:get(Key,Defs), 376 DefVal =/= Value 377 catch 378 _:_ -> false 379 end 380 end, Opts). 381 382%%%================================================================ 383%%% 384%%% Default options 385%%% 386 387-spec default(role() | common) -> option_declarations() . 388 389default(server) -> 390 (default(common)) 391 #{ 392 subsystems => 393 #{default => [ssh_sftpd:subsystem_spec([])], 394 chk => fun(L) -> 395 is_list(L) andalso 396 lists:all(fun({Name,{CB,Args}}) -> 397 check_string(Name) andalso 398 is_atom(CB) andalso 399 is_list(Args); 400 (_) -> 401 false 402 end, L) 403 end, 404 class => user_option 405 }, 406 407 shell => 408 #{default => ?DEFAULT_SHELL, 409 chk => fun({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A); 410 (disabled) -> true; 411 (V) -> check_function1(V) orelse 412 check_function2(V) 413 end, 414 class => user_option 415 }, 416 417 exec => 418 #{default => undefined, 419 chk => fun({direct, V}) -> check_function1(V) orelse check_function2(V) orelse check_function3(V); 420 (disabled) -> true; 421 %% Compatibility (undocumented): 422 ({M,F,A}) -> is_atom(M) andalso is_atom(F) andalso is_list(A); 423 (V) -> check_function1(V) orelse check_function2(V) orelse check_function3(V) 424 end, 425 class => user_option 426 }, 427 428 ssh_cli => 429 #{default => undefined, 430 chk => fun({Cb, As}) -> is_atom(Cb) andalso is_list(As); 431 (V) -> V == no_cli 432 end, 433 class => user_option 434 }, 435 436 tcpip_tunnel_out => 437 #{default => false, 438 chk => fun(V) -> erlang:is_boolean(V) end, 439 class => user_option 440 }, 441 442 tcpip_tunnel_in => 443 #{default => false, 444 chk => fun(V) -> erlang:is_boolean(V) end, 445 class => user_option 446 }, 447 448 system_dir => 449 #{default => "/etc/ssh", 450 chk => fun(V) -> check_string(V) andalso check_dir(V) end, 451 class => user_option 452 }, 453 454 auth_method_kb_interactive_data => 455 #{default => undefined, % Default value can be constructed when User is known 456 chk => fun({S1,S2,S3,B}) -> 457 check_string(S1) andalso 458 check_string(S2) andalso 459 check_string(S3) andalso 460 is_boolean(B); 461 (F) -> 462 check_function3(F) orelse 463 check_function4(F) 464 end, 465 class => user_option 466 }, 467 468 user_passwords => 469 #{default => [], 470 chk => fun(V) -> 471 is_list(V) andalso 472 lists:all(fun({S1,S2}) -> 473 check_string(S1) andalso 474 check_string(S2) 475 end, V) 476 end, 477 class => user_option 478 }, 479 480 pk_check_user => 481 #{default => false, 482 chk => fun(V) -> erlang:is_boolean(V) end, 483 class => user_option 484 }, 485 486 password => 487 #{default => undefined, 488 chk => fun(V) -> check_string(V) end, 489 class => user_option 490 }, 491 492 dh_gex_groups => 493 #{default => undefined, 494 chk => fun(V) -> check_dh_gex_groups(V) end, 495 class => user_option 496 }, 497 498 dh_gex_limits => 499 #{default => {0, infinity}, 500 chk => fun({I1,I2}) -> 501 check_pos_integer(I1) andalso 502 check_pos_integer(I2) andalso 503 I1 < I2; 504 (_) -> 505 false 506 end, 507 class => user_option 508 }, 509 510 pwdfun => 511 #{default => undefined, 512 chk => fun(V) -> check_function4(V) orelse check_function2(V) end, 513 class => user_option 514 }, 515 516 negotiation_timeout => 517 #{default => 2*60*1000, 518 chk => fun(V) -> check_timeout(V) end, 519 class => user_option 520 }, 521 522 hello_timeout => 523 #{default => 30*1000, 524 chk => fun check_timeout/1, 525 class => user_option 526 }, 527 528 max_sessions => 529 #{default => infinity, 530 chk => fun(V) -> check_pos_integer(V) end, 531 class => user_option 532 }, 533 534 max_channels => 535 #{default => infinity, 536 chk => fun(V) -> check_pos_integer(V) end, 537 class => user_option 538 }, 539 540 parallel_login => 541 #{default => false, 542 chk => fun(V) -> erlang:is_boolean(V) end, 543 class => user_option 544 }, 545 546 minimal_remote_max_packet_size => 547 #{default => 0, 548 chk => fun(V) -> check_pos_integer(V) end, 549 class => user_option 550 }, 551 552 failfun => 553 #{default => fun(_,_,_) -> void end, 554 chk => fun(V) -> check_function3(V) orelse 555 check_function2(V) % Backwards compatibility 556 end, 557 class => user_option 558 }, 559 560 connectfun => 561 #{default => fun(_,_,_) -> void end, 562 chk => fun(V) -> check_function3(V) end, 563 class => user_option 564 }, 565 566%%%%% Undocumented 567 infofun => 568 #{default => fun(_,_,_) -> void end, 569 chk => fun(V) -> check_function3(V) orelse 570 check_function2(V) % Backwards compatibility 571 end, 572 class => undoc_user_option 573 } 574 }; 575 576default(client) -> 577 (default(common)) 578 #{ 579 dsa_pass_phrase => 580 #{default => undefined, 581 chk => fun(V) -> check_string(V) end, 582 class => user_option 583 }, 584 585 rsa_pass_phrase => 586 #{default => undefined, 587 chk => fun(V) -> check_string(V) end, 588 class => user_option 589 }, 590 591 ecdsa_pass_phrase => 592 #{default => undefined, 593 chk => fun(V) -> check_string(V) end, 594 class => user_option 595 }, 596 597%%% Not yet implemented ed25519_pass_phrase => 598%%% Not yet implemented #{default => undefined, 599%%% Not yet implemented chk => fun(V) -> check_string(V) end, 600%%% Not yet implemented class => user_option 601%%% Not yet implemented }, 602%%% Not yet implemented 603%%% Not yet implemented ed448_pass_phrase => 604%%% Not yet implemented #{default => undefined, 605%%% Not yet implemented chk => fun(V) -> check_string(V) end, 606%%% Not yet implemented class => user_option 607%%% Not yet implemented }, 608%%% Not yet implemented 609 silently_accept_hosts => 610 #{default => false, 611 chk => fun(V) -> check_silently_accept_hosts(V) end, 612 class => user_option 613 }, 614 615 user_interaction => 616 #{default => true, 617 chk => fun(V) -> erlang:is_boolean(V) end, 618 class => user_option 619 }, 620 621 save_accepted_host => 622 #{default => true, 623 chk => fun(V) -> erlang:is_boolean(V) end, 624 class => user_option 625 }, 626 627 dh_gex_limits => 628 #{default => {1024, 6144, 8192}, % FIXME: Is this true nowadays? 629 chk => fun({Min,I,Max}) -> 630 lists:all(fun check_pos_integer/1, 631 [Min,I,Max]); 632 (_) -> false 633 end, 634 class => user_option 635 }, 636 637 connect_timeout => 638 #{default => infinity, 639 chk => fun(V) -> check_timeout(V) end, 640 class => user_option 641 }, 642 643 user => 644 #{default => 645 begin 646 Env = case os:type() of 647 {win32, _} -> "USERNAME"; 648 {unix, _} -> "LOGNAME" 649 end, 650 case os:getenv(Env) of 651 false -> 652 case os:getenv("USER") of 653 false -> undefined; 654 User -> User 655 end; 656 User -> 657 User 658 end 659 end, 660 chk => fun(V) -> check_string(V) end, 661 class => user_option 662 }, 663 664 password => 665 #{default => undefined, 666 chk => fun(V) -> check_string(V) end, 667 class => user_option 668 }, 669 670 quiet_mode => 671 #{default => false, 672 chk => fun(V) -> erlang:is_boolean(V) end, 673 class => user_option 674 }, 675 676%%%%% Undocumented 677 keyboard_interact_fun => 678 #{default => undefined, 679 chk => fun(V) -> check_function3(V) end, 680 class => undoc_user_option 681 } 682 }; 683 684default(common) -> 685 #{ 686 user_dir => 687 #{default => false, % FIXME: TBD ~/.ssh at time of call when user is known 688 chk => fun(V) -> check_string(V) andalso check_dir(V) end, 689 class => user_option 690 }, 691 692 %% NOTE: This option's default value must be undefined. 693 %% In the final stage that "merges" the modify_algorithms and preferred_algorithms, 694 %% this option's default values is set. 695 pref_public_key_algs => 696 #{default => undefined, 697 chk => fun(V) -> check_pref_public_key_algs(V) end, 698 class => user_option 699 }, 700 701 preferred_algorithms => 702 #{default => ssh:default_algorithms(), 703 chk => fun(V) -> check_preferred_algorithms(V) end, 704 class => user_option 705 }, 706 707 %% NOTE: This option is supposed to be used only in this very module (?MODULE). There is 708 %% a final stage in handle_options that "merges" the preferred_algorithms option and this one. 709 %% The preferred_algorithms is the one to use in the rest of the ssh application! 710 modify_algorithms => 711 #{default => undefined, % signals error if unsupported algo in preferred_algorithms :( 712 chk => fun(V) -> check_modify_algorithms(V) end, 713 class => user_option 714 }, 715 716 id_string => 717 #{default => try {ok, [_|_] = VSN} = application:get_key(ssh, vsn), 718 "Erlang/" ++ VSN 719 catch 720 _:_ -> "" 721 end, 722 chk => fun(random) -> 723 {true, {random,2,5}}; % 2 - 5 random characters 724 ({random,I1,I2}) -> 725 %% Undocumented 726 check_pos_integer(I1) andalso 727 check_pos_integer(I2) andalso 728 I1=<I2; 729 (V) -> 730 check_string(V) 731 end, 732 class => user_option 733 }, 734 735 key_cb => 736 #{default => {ssh_file, []}, 737 chk => fun({Mod,Opts}) -> is_atom(Mod) andalso is_list(Opts); 738 (Mod) when is_atom(Mod) -> {true, {Mod,[]}}; 739 (_) -> false 740 end, 741 class => user_option 742 }, 743 744 profile => 745 #{default => ?DEFAULT_PROFILE, 746 chk => fun(V) -> erlang:is_atom(V) end, 747 class => user_option 748 }, 749 750 idle_time => 751 #{default => infinity, 752 chk => fun(V) -> check_timeout(V) end, 753 class => user_option 754 }, 755 756 disconnectfun => 757 #{default => fun(_) -> void end, 758 chk => fun(V) -> check_function1(V) end, 759 class => user_option 760 }, 761 762 unexpectedfun => 763 #{default => fun(_,_) -> report end, 764 chk => fun(V) -> check_function2(V) end, 765 class => user_option 766 }, 767 768 ssh_msg_debug_fun => 769 #{default => fun(_,_,_,_) -> void end, 770 chk => fun(V) -> check_function4(V) end, 771 class => user_option 772 }, 773 774 rekey_limit => 775 #{default => {3600000, 1024000000}, % {1 hour, 1 GB} 776 chk => fun({infinity, infinity}) -> 777 true; 778 ({Mins, infinity}) when is_integer(Mins), Mins>0 -> 779 {true, {Mins*60*1000, infinity}}; 780 ({infinity, Bytes}) when is_integer(Bytes), Bytes>=0 -> 781 true; 782 ({Mins, Bytes}) when is_integer(Mins), Mins>0, 783 is_integer(Bytes), Bytes>=0 -> 784 {true, {Mins*60*1000, Bytes}}; 785 (infinity) -> 786 {true, {3600000, infinity}}; 787 (Bytes) when is_integer(Bytes), Bytes>=0 -> 788 {true, {3600000, Bytes}}; 789 (_) -> 790 false 791 end, 792 class => user_option 793 }, 794 795 auth_methods => 796 #{default => ?SUPPORTED_AUTH_METHODS, 797 chk => fun(As) -> 798 try 799 Sup = string:tokens(?SUPPORTED_AUTH_METHODS, ","), 800 New = string:tokens(As, ","), 801 [] == [X || X <- New, 802 not lists:member(X,Sup)] 803 catch 804 _:_ -> false 805 end 806 end, 807 class => user_option 808 }, 809 810 send_ext_info => 811 #{default => true, 812 chk => fun erlang:is_boolean/1, 813 class => user_option 814 }, 815 816 recv_ext_info => 817 #{default => true, 818 chk => fun erlang:is_boolean/1, 819 class => user_option 820 }, 821 822%%%%% Undocumented 823 transport => 824 #{default => ?DEFAULT_TRANSPORT, 825 chk => fun({A,B,C}) -> 826 is_atom(A) andalso is_atom(B) andalso is_atom(C) 827 end, 828 class => undoc_user_option 829 }, 830 831 vsn => 832 #{default => {2,0}, 833 chk => fun({Maj,Min}) -> check_non_neg_integer(Maj) andalso check_non_neg_integer(Min); 834 (_) -> false 835 end, 836 class => undoc_user_option 837 }, 838 839 tstflg => 840 #{default => [], 841 chk => fun(V) -> erlang:is_list(V) end, 842 class => undoc_user_option 843 }, 844 845 user_dir_fun => 846 #{default => undefined, 847 chk => fun(V) -> check_function1(V) end, 848 class => undoc_user_option 849 }, 850 851 max_random_length_padding => 852 #{default => ?MAX_RND_PADDING_LEN, 853 chk => fun(V) -> check_non_neg_integer(V) end, 854 class => undoc_user_option 855 } 856 }. 857 858 859%%%================================================================ 860%%%================================================================ 861%%%================================================================ 862 863%%% 864%%% check_*/1 -> true | false | error({check,Spec}) 865%%% See error_in_check/2,3 866%%% 867 868%%% error_in_check(BadValue) -> error_in_check(BadValue, undefined). 869 870error_in_check(BadValue, Extra) -> error({check,{BadValue,Extra}}). 871 872 873%%%---------------------------------------------------------------- 874check_timeout(infinity) -> true; 875check_timeout(I) -> check_pos_integer(I). 876 877%%%---------------------------------------------------------------- 878check_pos_integer(I) -> is_integer(I) andalso I>0. 879 880%%%---------------------------------------------------------------- 881check_non_neg_integer(I) -> is_integer(I) andalso I>=0. 882 883%%%---------------------------------------------------------------- 884check_function1(F) -> is_function(F,1). 885check_function2(F) -> is_function(F,2). 886check_function3(F) -> is_function(F,3). 887check_function4(F) -> is_function(F,4). 888 889%%%---------------------------------------------------------------- 890check_pref_public_key_algs(V) -> 891 %% Get the dynamically supported keys, that is, thoose 892 %% that are stored 893 PKs = ssh_transport:supported_algorithms(public_key), 894 CHK = fun(A, Ack) -> 895 case lists:member(A, PKs) of 896 true -> 897 case lists:member(A,Ack) of 898 false -> [A|Ack]; 899 true -> Ack % Remove duplicates 900 end; 901 false -> error_in_check(A, "Not supported public key") 902 end 903 end, 904 case lists:foldr( 905 fun(ssh_dsa, Ack) -> CHK('ssh-dss', Ack); % compatibility 906 (ssh_rsa, Ack) -> CHK('ssh-rsa', Ack); % compatibility 907 (X, Ack) -> CHK(X, Ack) 908 end, [], V) 909 of 910 V -> true; 911 [] -> false; 912 V1 -> {true,V1} 913 end. 914 915 916%%%---------------------------------------------------------------- 917%% Check that it is a directory and is readable 918check_dir(Dir) -> 919 case file:read_file_info(Dir) of 920 {ok, #file_info{type = directory, 921 access = Access}} -> 922 case Access of 923 read -> true; 924 read_write -> true; 925 _ -> error_in_check(Dir, eacces) 926 end; 927 928 {ok, #file_info{}}-> 929 error_in_check(Dir, enotdir); 930 931 {error, Error} -> 932 error_in_check(Dir, Error) 933 end. 934 935%%%---------------------------------------------------------------- 936check_string(S) -> is_list(S). % FIXME: stub 937 938%%%---------------------------------------------------------------- 939check_dh_gex_groups({file,File}) when is_list(File) -> 940 case file:consult(File) of 941 {ok, GroupDefs} -> 942 check_dh_gex_groups(GroupDefs); 943 {error, Error} -> 944 error_in_check({file,File},Error) 945 end; 946 947check_dh_gex_groups({ssh_moduli_file,File}) when is_list(File) -> 948 case file:open(File,[read]) of 949 {ok,D} -> 950 try 951 read_moduli_file(D, 1, []) 952 of 953 {ok,Moduli} -> 954 check_dh_gex_groups(Moduli); 955 {error,Error} -> 956 error_in_check({ssh_moduli_file,File}, Error) 957 catch 958 _:_ -> 959 error_in_check({ssh_moduli_file,File}, "Bad format in file "++File) 960 after 961 file:close(D) 962 end; 963 964 {error, Error} -> 965 error_in_check({ssh_moduli_file,File}, Error) 966 end; 967 968check_dh_gex_groups(L0) when is_list(L0), is_tuple(hd(L0)) -> 969 {true, 970 collect_per_size( 971 lists:foldl( 972 fun({N,G,P}, Acc) when is_integer(N),N>0, 973 is_integer(G),G>0, 974 is_integer(P),P>0 -> 975 [{N,{G,P}} | Acc]; 976 ({N,{G,P}}, Acc) when is_integer(N),N>0, 977 is_integer(G),G>0, 978 is_integer(P),P>0 -> 979 [{N,{G,P}} | Acc]; 980 ({N,GPs}, Acc) when is_list(GPs) -> 981 lists:foldr(fun({Gi,Pi}, Acci) when is_integer(Gi),Gi>0, 982 is_integer(Pi),Pi>0 -> 983 [{N,{Gi,Pi}} | Acci] 984 end, Acc, GPs) 985 end, [], L0))}; 986 987check_dh_gex_groups(_) -> 988 false. 989 990 991 992collect_per_size(L) -> 993 lists:foldr( 994 fun({Sz,GP}, [{Sz,GPs}|Acc]) -> [{Sz,[GP|GPs]}|Acc]; 995 ({Sz,GP}, Acc) -> [{Sz,[GP]}|Acc] 996 end, [], lists:sort(L)). 997 998read_moduli_file(D, I, Acc) -> 999 case io:get_line(D,"") of 1000 {error,Error} -> 1001 {error,Error}; 1002 eof -> 1003 {ok, Acc}; 1004 "#" ++ _ -> read_moduli_file(D, I+1, Acc); 1005 <<"#",_/binary>> -> read_moduli_file(D, I+1, Acc); 1006 Data -> 1007 Line = if is_binary(Data) -> binary_to_list(Data); 1008 is_list(Data) -> Data 1009 end, 1010 try 1011 [_Time,_Class,_Tests,_Tries,Size,G,P] = string:tokens(Line," \r\n"), 1012 M = {list_to_integer(Size), 1013 {list_to_integer(G), list_to_integer(P,16)} 1014 }, 1015 read_moduli_file(D, I+1, [M|Acc]) 1016 catch 1017 _:_ -> 1018 read_moduli_file(D, I+1, Acc) 1019 end 1020 end. 1021 1022%%%---------------------------------------------------------------- 1023-define(SHAs, [md5, sha, sha224, sha256, sha384, sha512]). 1024 1025check_silently_accept_hosts(B) when is_boolean(B) -> true; 1026check_silently_accept_hosts(F) when is_function(F,2) -> true; 1027check_silently_accept_hosts({false,S}) when is_atom(S) -> valid_hash(S); 1028check_silently_accept_hosts({S,F}) when is_function(F,2) -> valid_hash(S); 1029check_silently_accept_hosts(_) -> false. 1030 1031 1032valid_hash(S) -> valid_hash(S, proplists:get_value(hashs,crypto:supports())). 1033 1034valid_hash(S, Ss) when is_atom(S) -> lists:member(S, ?SHAs) andalso lists:member(S, Ss); 1035valid_hash(L, Ss) when is_list(L) -> lists:all(fun(S) -> valid_hash(S,Ss) end, L); 1036valid_hash(X, _) -> error_in_check(X, "Expect atom or list in fingerprint spec"). 1037 1038%%%---------------------------------------------------------------- 1039initial_default_algorithms(DefList, ModList) -> 1040 {true, L0} = check_modify_algorithms(ModList), 1041 rm_non_supported(false, eval_ops(DefList,L0)). 1042 1043%%%---------------------------------------------------------------- 1044check_modify_algorithms(M) when is_list(M) -> 1045 [error_in_check(Op_KVs, "Bad modify_algorithms") 1046 || Op_KVs <- M, 1047 not is_tuple(Op_KVs) 1048 orelse (size(Op_KVs) =/= 2) 1049 orelse (not lists:member(element(1,Op_KVs), [append,prepend,rm]))], 1050 {true, [{Op,normalize_mod_algs(KVs,false)} || {Op,KVs} <- M]}; 1051check_modify_algorithms(_) -> 1052 error_in_check(modify_algorithms, "Bad option value. List expected."). 1053 1054 1055 1056 1057normalize_mod_algs(KVs, UseDefaultAlgs) -> 1058 normalize_mod_algs(ssh_transport:algo_classes(), KVs, [], UseDefaultAlgs). 1059 1060normalize_mod_algs([K|Ks], KVs0, Acc, UseDefaultAlgs) -> 1061 %% Pick the expected keys in order and check if they are in the user's list 1062 {Vs1, KVs} = 1063 case lists:keytake(K, 1, KVs0) of 1064 {value, {K,Vs0}, KVs1} -> 1065 {Vs0, KVs1}; 1066 false -> 1067 {[], KVs0} 1068 end, 1069 Vs = normalize_mod_alg_list(K, Vs1, UseDefaultAlgs), 1070 normalize_mod_algs(Ks, KVs, [{K,Vs} | Acc], UseDefaultAlgs); 1071normalize_mod_algs([], [], Acc, _) -> 1072 %% No values left in the key-value list after removing the expected entries 1073 %% (thats good) 1074 lists:reverse(Acc); 1075normalize_mod_algs([], [{K,_}|_], _, _) -> 1076 %% Some values left in the key-value list after removing the expected entries 1077 %% (thats bad) 1078 case ssh_transport:algo_class(K) of 1079 true -> error_in_check(K, "Duplicate key"); 1080 false -> error_in_check(K, "Unknown key") 1081 end; 1082normalize_mod_algs([], [X|_], _, _) -> 1083 error_in_check(X, "Bad list element"). 1084 1085 1086 1087%%% Handle the algorithms list 1088normalize_mod_alg_list(K, Vs, UseDefaultAlgs) -> 1089 normalize_mod_alg_list(K, 1090 ssh_transport:algo_two_spec_class(K), 1091 Vs, 1092 def_alg(K,UseDefaultAlgs)). 1093 1094 1095normalize_mod_alg_list(_K, _, [], Default) -> 1096 Default; 1097 1098normalize_mod_alg_list(K, true, [{client2server,L1}], [_,{server2client,L2}]) -> 1099 [nml1(K,{client2server,L1}), 1100 {server2client,L2}]; 1101 1102normalize_mod_alg_list(K, true, [{server2client,L2}], [{client2server,L1},_]) -> 1103 [{client2server,L1}, 1104 nml1(K,{server2client,L2})]; 1105 1106normalize_mod_alg_list(K, true, [{server2client,L2},{client2server,L1}], _) -> 1107 [nml1(K,{client2server,L1}), 1108 nml1(K,{server2client,L2})]; 1109 1110normalize_mod_alg_list(K, true, [{client2server,L1},{server2client,L2}], _) -> 1111 [nml1(K,{client2server,L1}), 1112 nml1(K,{server2client,L2})]; 1113 1114normalize_mod_alg_list(K, true, L0, _) -> 1115 L = nml(K,L0), % Throws errors 1116 [{client2server,L}, 1117 {server2client,L}]; 1118 1119normalize_mod_alg_list(K, false, L, _) -> 1120 nml(K,L). 1121 1122 1123nml1(K, {T,V}) when T==client2server ; T==server2client -> 1124 {T, nml({K,T}, V)}. 1125 1126nml(K, L) -> 1127 [error_in_check(K, "Bad value for this key") % This is a throw 1128 || V <- L, 1129 not is_atom(V) 1130 ], 1131 case L -- lists:usort(L) of 1132 [] -> ok; 1133 Dups -> error_in_check({K,Dups}, "Duplicates") % This is a throw 1134 end, 1135 L. 1136 1137 1138def_alg(K, false) -> 1139 case ssh_transport:algo_two_spec_class(K) of 1140 false -> []; 1141 true -> [{client2server,[]}, {server2client,[]}] 1142 end; 1143def_alg(K, true) -> 1144 ssh_transport:default_algorithms(K). 1145 1146 1147 1148check_preferred_algorithms(Algs) when is_list(Algs) -> 1149 check_input_ok(Algs), 1150 {true, normalize_mod_algs(Algs, true)}; 1151 1152check_preferred_algorithms(_) -> 1153 error_in_check(modify_algorithms, "Bad option value. List expected."). 1154 1155 1156check_input_ok(Algs) -> 1157 [error_in_check(KVs, "Bad preferred_algorithms") 1158 || KVs <- Algs, 1159 not is_tuple(KVs) 1160 orelse (size(KVs) =/= 2)]. 1161 1162%%%---------------------------------------------------------------- 1163final_preferred_algorithms(Options0) -> 1164 Result = 1165 case ?GET_OPT(modify_algorithms, Options0) of 1166 undefined -> 1167 rm_non_supported(true, 1168 ?GET_OPT(preferred_algorithms, Options0)); 1169 ModAlgs -> 1170 rm_non_supported(false, 1171 eval_ops(?GET_OPT(preferred_algorithms, Options0), 1172 ModAlgs)) 1173 end, 1174 error_if_empty(Result), % Throws errors if any value list is empty 1175 Options1 = ?PUT_OPT({preferred_algorithms,Result}, Options0), 1176 case ?GET_OPT(pref_public_key_algs, Options1) of 1177 undefined -> 1178 ?PUT_OPT({pref_public_key_algs, proplists:get_value(public_key,Result)}, Options1); 1179 _ -> 1180 Options1 1181 end. 1182 1183eval_ops(PrefAlgs, ModAlgs) -> 1184 lists:foldl(fun eval_op/2, PrefAlgs, ModAlgs). 1185 1186eval_op({Op,AlgKVs}, PrefAlgs) -> 1187 eval_op(Op, AlgKVs, PrefAlgs, []). 1188 1189eval_op(Op, [{C,L1}|T1], [{C,L2}|T2], Acc) -> 1190 eval_op(Op, T1, T2, [{C,eval_op(Op,L1,L2,[])} | Acc]); 1191 1192eval_op(_, [], [], Acc) -> lists:reverse(Acc); 1193eval_op(rm, Opt, Pref, []) when is_list(Opt), is_list(Pref) -> Pref -- Opt; 1194eval_op(append, Opt, Pref, []) when is_list(Opt), is_list(Pref) -> (Pref--Opt) ++ Opt; 1195eval_op(prepend, Opt, Pref, []) when is_list(Opt), is_list(Pref) -> Opt ++ (Pref--Opt). 1196 1197 1198rm_non_supported(UnsupIsErrorFlg, KVs) -> 1199 [{K,rmns(K,Vs, UnsupIsErrorFlg)} || {K,Vs} <- KVs]. 1200 1201rmns(K, Vs, UnsupIsErrorFlg) -> 1202 case ssh_transport:algo_two_spec_class(K) of 1203 false -> 1204 rm_unsup(Vs, ssh_transport:supported_algorithms(K), UnsupIsErrorFlg, K); 1205 true -> 1206 [{C, rm_unsup(Vsx, Sup, UnsupIsErrorFlg, {K,C})} 1207 || {{C,Vsx},{C,Sup}} <- lists:zip(Vs,ssh_transport:supported_algorithms(K)) 1208 ] 1209 end. 1210 1211rm_unsup(A, B, Flg, ErrInf) -> 1212 case A--B of 1213 Unsup=[_|_] when Flg==true -> error({eoptions, 1214 {preferred_algorithms,{ErrInf,Unsup}}, 1215 "Unsupported value(s) found" 1216 }); 1217 Unsup -> A -- Unsup 1218 end. 1219 1220 1221error_if_empty([{K,[]}|_]) -> 1222 error({eoptions, K, "Empty resulting algorithm list"}); 1223error_if_empty([{K,[{client2server,[]}, {server2client,[]}]}]) -> 1224 error({eoptions, K, "Empty resulting algorithm list"}); 1225error_if_empty([{K,[{client2server,[]}|_]} | _]) -> 1226 error({eoptions, {K,client2server}, "Empty resulting algorithm list"}); 1227error_if_empty([{K,[_,{server2client,[]}|_]} | _]) -> 1228 error({eoptions, {K,server2client}, "Empty resulting algorithm list"}); 1229error_if_empty([_|T]) -> 1230 error_if_empty(T); 1231error_if_empty([]) -> 1232 ok. 1233 1234%%%---------------------------------------------------------------- 1235