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