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