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