1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1999-2019. 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-module(snmp_user_based_sm_mib).
21
22%% Avoid warning for local function error/1 clashing with autoimported BIF.
23-compile({no_auto_import,[error/1]}).
24-export([configure/1, reconfigure/1,
25	 usmUserSpinLock/1, usmUserSpinLock/2,
26	 usmUserTable/1, usmUserTable/3,
27	 table_next/2,
28	 is_engine_id_known/1, get_user/2, get_user_from_security_name/2,
29	 mk_key_change/3, mk_key_change/5, extract_new_key/3, mk_random/1]).
30-export([usmStatsUnsupportedSecLevels/1,
31	 usmStatsNotInTimeWindows/1,
32	 usmStatsUnknownUserNames/1,
33	 usmStatsUnknownEngineIDs/1,
34	 usmStatsWrongDigests/1,
35	 usmStatsDecryptionErrors/1]).
36-export([add_user/1, add_user/13, delete_user/1]).
37
38%% Internal
39-export([check_usm/1]).
40
41-include("SNMP-USER-BASED-SM-MIB.hrl").
42-include("SNMP-USM-AES-MIB.hrl").
43-include("SNMPv2-TC.hrl").
44-include("snmpa_internal.hrl").
45-include("snmp_types.hrl").
46
47-define(VMODULE,"USM_MIB").
48-include("snmp_verbosity.hrl").
49
50-ifndef(default_verbosity).
51-define(default_verbosity,silence).
52-endif.
53
54
55%% Columns not accessible via SNMP
56-define(usmUserAuthKey, 14).
57-define(usmUserPrivKey, 15).
58-define(is_cloning, 16).
59
60
61%%%-----------------------------------------------------------------
62%%% Utility functions
63%%%-----------------------------------------------------------------
64
65%%%-----------------------------------------------------------------
66%%% Implements the instrumentation functions and additional
67%%% functions for the SNMP-USER-BASED-SM-MIB.
68%%%-----------------------------------------------------------------
69
70%%-----------------------------------------------------------------
71%% Func: configure/1
72%% Args: Dir is the directory where the configuration files are found.
73%% Purpose: If the tables doesn't exist, this function reads
74%%          the config-files for the USM tables, and
75%%          inserts the data.  This means that the data in the tables
76%%          survive a reboot.  However, the StorageType column is
77%%          checked for each row.  If volatile, the row is deleted.
78%% Returns: ok
79%% Fails: exit(configuration_error)
80%%-----------------------------------------------------------------
81configure(Dir) ->
82    set_sname(),
83    case db(usmUserTable) of
84        {_, mnesia} ->
85            ?vdebug("usm user table in mnesia: init vars & cleanup",[]),
86            init_vars(),
87            gc_tabs();
88	TabDb ->
89	    case snmpa_local_db:table_exists(TabDb) of
90		true ->
91		    ?vdebug("usm user table exists: init vars & cleanup",[]),
92		    init_vars(),
93		    gc_tabs();
94		false ->
95		    ?vdebug("usm user table does not exist: reconfigure",[]),
96		    reconfigure(Dir)
97	    end
98    end.
99
100%%-----------------------------------------------------------------
101%% Func: reconfigure/1
102%% Args: Dir is the directory where the configuration files are found.
103%% Purpose: Reads the config-files for the USM tables, and
104%%          inserts the data.  Makes sure that all old data in
105%%          the tables are deleted, and the new data inserted.
106%%          This function makes sure that all (and only)
107%%          config-file-data are in the tables.
108%% Returns: ok
109%% Fails: exit(configuration_error) |
110%%        exit({unsupported_crypto, Function})
111%%-----------------------------------------------------------------
112reconfigure(Dir) ->
113    set_sname(),
114    case (catch do_reconfigure(Dir)) of
115	ok ->
116	    ok;
117	{error, Reason} ->
118	    ?vinfo("reconfigure error: ~p", [Reason]),
119	    config_err("reconfigure failed: ~p", [Reason]),
120	    exit(configuration_error);
121	Error ->
122	    ?vinfo("reconfigure failed: ~p", [Error]),
123	    config_err("reconfigure failed: ~p", [Error]),
124	    exit(configuration_error)
125    end.
126
127do_reconfigure(Dir) ->
128    ?vdebug("read usm configuration files",[]),
129    Users = read_usm_config_files(Dir),
130    ?vdebug("check users",[]),
131    check_users(Users),
132    ?vdebug("initiate tables",[]),
133    init_tabs(Users),
134    ?vdebug("initiate vars",[]),
135    init_vars(),
136    ok.
137
138
139read_usm_config_files(Dir) ->
140    ?vdebug("read usm config file",[]),
141    Gen    = fun (D, Reason) -> generate_usm(D, Reason) end,
142    Order  = fun snmp_conf:no_order/2,
143    Check  = fun (Entry, State) -> {check_usm(Entry), State} end,
144    Filter = fun snmp_conf:no_filter/1,
145    [Usms] =
146	snmp_conf:read_files(Dir, [{"usm.conf", Gen, Order, Check, Filter}]),
147    Usms.
148
149
150generate_usm(Dir, _Reason) ->
151    info_msg("Incomplete configuration. Generating empty usm.conf.", []),
152    USMFile = filename:join(Dir, "usm.conf"),
153    ok = file:write_file(USMFile, list_to_binary([])),
154    [].
155
156
157check_usm({EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC,
158           PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey}) ->
159    snmp_conf:check_string(EngineID),
160    snmp_conf:check_string(Name),
161    snmp_conf:check_string(SecName),
162    CloneVal =
163        case catch snmp_conf:check_atom(Clone,[{zeroDotZero,?zeroDotZero}]) of
164	    {error, _} ->
165                snmp_conf:check_oid(Clone),
166                Clone;
167	    {ok, X} ->
168                X
169        end,
170    AuthProtoAlt = [{usmNoAuthProtocol,      ?usmNoAuthProtocol},
171		    {usmHMACSHAAuthProtocol, ?usmHMACSHAAuthProtocol},
172		    {usmHMACMD5AuthProtocol, ?usmHMACMD5AuthProtocol}],
173    {ok, AuthProto} = snmp_conf:check_atom(AuthP, AuthProtoAlt),
174    snmp_conf:check_string(AuthKeyC),
175    snmp_conf:check_string(OwnAuthKeyC),
176    PrivProtoAlt = [{usmNoPrivProtocol,    ?usmNoPrivProtocol},
177		    {usmDESPrivProtocol,   ?usmDESPrivProtocol},
178		    {usmAesCfb128Protocol, ?usmAesCfb128Protocol}],
179    {ok, PrivProto} = snmp_conf:check_atom(PrivP, PrivProtoAlt),
180    snmp_conf:check_string(PrivKeyC),
181    snmp_conf:check_string(OwnPrivKeyC),
182    snmp_conf:check_string(Public),
183    snmp_conf:check_string(AuthKey, alen(AuthP)),
184    snmp_conf:check_string(PrivKey, plen(PrivP)),
185
186    User = {EngineID, Name, SecName,
187	    CloneVal, AuthProto, AuthKeyC, OwnAuthKeyC,
188	    PrivProto, PrivKeyC, OwnPrivKeyC, Public,
189	    ?'StorageType_nonVolatile', ?'RowStatus_active', AuthKey, PrivKey},
190    {ok, User};
191check_usm(X) ->
192    error({invalid_user, X}).
193
194alen(usmNoAuthProtocol) -> any;
195alen(usmHMACMD5AuthProtocol) -> 16;
196alen(usmHMACSHAAuthProtocol) -> 20.
197
198plen(usmNoPrivProtocol) -> any;
199plen(usmDESPrivProtocol) -> 16;
200plen(usmAesCfb128Protocol) -> 16.
201
202
203%%-----------------------------------------------------------------
204%% This function loops through all users, and check that the
205%% definition is constistent with the support for crypto on
206%% the system.  Thus, it is not possible to define a user that
207%% uses authentication if 'crypto' is not started, or a user that
208%% uses DES if 'crypto' doesn't support DES.
209%%-----------------------------------------------------------------
210check_users([User | Users]) ->
211    check_user(User),
212    check_users(Users);
213check_users([]) ->
214    ok.
215
216check_user(User) ->
217    case element(?usmUserAuthProtocol, User) of
218	?usmNoAuthProtocol -> ok;
219	?usmHMACMD5AuthProtocol ->
220	    case is_crypto_supported(md5) of
221		true -> ok;
222		false -> exit({unsupported_crypto, md5_mac_96})
223	    end;
224	?usmHMACSHAAuthProtocol ->
225	    case is_crypto_supported(sha) of
226		true -> ok;
227		false -> exit({unsupported_crypto, sha_mac_96})
228	    end
229    end,
230    case element(?usmUserPrivProtocol, User) of
231	?usmNoPrivProtocol -> ok;
232	?usmDESPrivProtocol ->
233	    case is_crypto_supported(des_cbc) of
234		true -> ok;
235		false -> exit({unsupported_crypto, des_cbc})
236	    end;
237	?usmAesCfb128Protocol ->
238	    case is_crypto_supported(aes_cfb128) of
239		true -> ok;
240		false -> exit({unsupported_crypto, aes_cfb128})
241	    end
242    end.
243
244
245init_tabs(Users) ->
246    ?vdebug("create usm user table",[]),
247    snmpa_local_db:table_delete(db(usmUserTable)),
248    snmpa_local_db:table_create(db(usmUserTable)),
249    init_user_table(Users).
250
251init_user_table([Row | T]) ->
252    Key = mk_user_table_key(Row),
253    snmpa_local_db:table_create_row(db(usmUserTable), Key, Row),
254    init_user_table(T);
255init_user_table([]) -> true.
256
257mk_user_table_key(Row) ->
258    Key1 = element(1, Row),
259    Key2 = element(2, Row),
260    [length(Key1) | Key1] ++ [length(Key2) | Key2].
261
262table_cre_row(Tab, Key, Row) ->
263    snmpa_mib_lib:table_cre_row(db(Tab), Key, Row).
264
265table_del_row(Tab, Key) ->
266    snmpa_mib_lib:table_del_row(db(Tab), Key).
267
268
269add_user(EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC,
270	 PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) ->
271    User = {EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC,
272	    PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey},
273    add_user(User).
274
275add_user(User) ->
276    case (catch check_usm(User)) of
277	{ok, Row} ->
278	    case (catch check_user(Row)) of
279		{'EXIT', Reason} ->
280		    {error, Reason};
281		_ ->
282		    Key = mk_user_table_key(Row),
283		    case table_cre_row(usmUserTable, Key, Row) of
284			true ->
285			    {ok, Key};
286			false ->
287			    {error, create_failed}
288		    end
289		end;
290	{error, Reason} ->
291	    {error, Reason};
292	Error ->
293	    {error, Error}
294    end.
295
296delete_user(Key) ->
297    case table_del_row(usmUserTable, Key) of
298	true ->
299	    ok;
300	false ->
301	    {error, delete_failed}
302    end.
303
304
305gc_tabs() ->
306    DB  = db(usmUserTable),
307    STC = stc(usmUserTable),
308    FOI = foi(usmUserTable),
309    snmpa_mib_lib:gc_tab(DB, STC, FOI),
310    ok.
311
312
313%%-----------------------------------------------------------------
314%% Counter functions
315%%-----------------------------------------------------------------
316
317usmStatsUnsupportedSecLevels(print) ->
318    VarAndValue = [{usmStatsUnsupportedSecLevels,
319		    usmStatsUnsupportedSecLevels(get)}],
320    snmpa_mib_lib:print_variables(VarAndValue);
321usmStatsUnsupportedSecLevels(get) ->
322    get_counter(usmStatsUnsupportedSecLevels).
323
324usmStatsNotInTimeWindows(print) ->
325    VarAndValue = [{usmStatsNotInTimeWindows, usmStatsNotInTimeWindows(get)}],
326    snmpa_mib_lib:print_variables(VarAndValue);
327usmStatsNotInTimeWindows(get) ->
328    get_counter(usmStatsNotInTimeWindows).
329
330usmStatsUnknownUserNames(print) ->
331    VarAndValue = [{usmStatsUnknownUserNames, usmStatsUnknownUserNames(get)}],
332    snmpa_mib_lib:print_variables(VarAndValue);
333usmStatsUnknownUserNames(get) ->
334    get_counter(usmStatsUnknownUserNames).
335
336usmStatsUnknownEngineIDs(print) ->
337    VarAndValue = [{usmStatsUnknownEngineIDs, usmStatsUnknownEngineIDs(get)}],
338    snmpa_mib_lib:print_variables(VarAndValue);
339usmStatsUnknownEngineIDs(get) ->
340    get_counter(usmStatsUnknownEngineIDs).
341
342usmStatsWrongDigests(print) ->
343    VarAndValue = [{usmStatsWrongDigests, usmStatsWrongDigests(get)}],
344    snmpa_mib_lib:print_variables(VarAndValue);
345usmStatsWrongDigests(get) ->
346    get_counter(usmStatsWrongDigests).
347
348usmStatsDecryptionErrors(print) ->
349    VarAndValue = [{usmStatsDecryptionErrors, usmStatsDecryptionErrors(get)}],
350    snmpa_mib_lib:print_variables(VarAndValue);
351usmStatsDecryptionErrors(get) ->
352    get_counter(usmStatsDecryptionErrors).
353
354
355get_counter(Name) ->
356    case (catch ets:lookup(snmp_agent_table, Name)) of
357	[{_, Val}] ->
358	    {value, Val};
359	_ ->
360	    genErr
361    end.
362
363
364init_vars() -> lists:map(fun maybe_create_var/1, vars()).
365
366maybe_create_var(Var) ->
367    case ets:lookup(snmp_agent_table, Var) of
368	[_] -> ok;
369	_ -> init_var(Var)
370    end.
371
372init_var(Var) -> ets:insert(snmp_agent_table, {Var, 0}).
373
374vars() ->
375    [
376     usmStatsUnsupportedSecLevels,
377     usmStatsNotInTimeWindows,
378     usmStatsUnknownUserNames,
379     usmStatsUnknownEngineIDs,
380     usmStatsWrongDigests,
381     usmStatsDecryptionErrors
382    ].
383
384
385%%-----------------------------------------------------------------
386%% API functions
387%%-----------------------------------------------------------------
388is_engine_id_known(EngineID) ->
389    EngineKey = [length(EngineID) | EngineID],
390    ?vtrace("is_engine_id_known -> EngineKey: ~w", [EngineKey]),
391    case table_next(usmUserTable, EngineKey) of
392	endOfTable -> false;
393	Key ->
394	    ?vtrace("is_engine_id_known -> Key: ~w", [Key]),
395	    lists:prefix(EngineKey, Key)
396    end.
397
398get_user(EngineID, UserName) ->
399    Key = [length(EngineID) | EngineID] ++ [length(UserName) | UserName],
400    snmp_generic:table_get_row(db(usmUserTable), Key, foi(usmUserTable)).
401
402get_user_from_security_name(EngineID, SecName) ->
403    %% Since the normal mapping between UserName and SecName is the
404    %% identityfunction, we first try to use the SecName as UserName,
405    %% and check the resulting row.  If it doesn't match, we'll have to
406    %% loop through the entire table.
407    Key = [length(EngineID) | EngineID] ++ [length(SecName) | SecName],
408    case snmp_generic:table_get_row(db(usmUserTable), Key,
409				    foi(usmUserTable)) of
410	Row when is_tuple(Row) ->
411	    ?vtrace("get_user_from_security_name -> "
412		    "found user using the identity function", []),
413	    Row;
414	undefined ->
415	    ?vtrace("get_user_from_security_name -> "
416		    "user *not* found using the identity function", []),
417	    F = fun(_, Row) when (
418			      (element(?usmUserEngineID,Row) =:= EngineID) andalso
419			      (element(?usmUserSecurityName,Row) =:= SecName)) ->
420			throw({ok, Row});
421		   (_, _) ->
422			ok
423		end,
424	    case catch snmp_generic:table_foreach(db(usmUserTable), F) of
425		{ok, Row} ->
426		    Row;
427		_Else ->
428		    undefined
429	    end
430    end.
431
432
433%%-----------------------------------------------------------------
434%% Instrumentation Functions
435%%-----------------------------------------------------------------
436
437usmUserSpinLock(print) ->
438    VarAndValue = [{usmUserSpinLock, usmUserSpinLock(get)}],
439    snmpa_mib_lib:print_variables(VarAndValue);
440
441usmUserSpinLock(new) ->
442    snmp_generic:variable_func(new, {usmUserSpinLock, volatile}),
443    ?SNMP_RAND_SEED(),
444    %% rand:seed(exrop,
445    %%           {erlang:phash2([node()]),
446    %%            erlang:monotonic_time(),
447    %%            erlang:unique_integer()}),
448    Val = rand:uniform(2147483648) - 1,
449    snmp_generic:variable_func(set, Val, {usmUserSpinLock, volatile});
450
451usmUserSpinLock(delete) ->
452    ok;
453
454usmUserSpinLock(get) ->
455    snmp_generic:variable_func(get, {usmUserSpinLock, volatile}).
456
457usmUserSpinLock(is_set_ok, NewVal) ->
458    case snmp_generic:variable_func(get, {usmUserSpinLock, volatile}) of
459	{value, NewVal} -> noError;
460	_ -> inconsistentValue
461    end;
462usmUserSpinLock(set, NewVal) ->
463    snmp_generic:variable_func(set, (NewVal + 1) rem 2147483648,
464			       {usmUserSpinLock, volatile}).
465
466
467%% Op = print - Used for debugging purposes
468usmUserTable(print) ->
469    Table = usmUserTable,
470    DB    = db(Table),
471    FOI   = foi(Table),
472    %% TableInfo = snmpa_mib_lib:get_table(db(Table), foi(Table)),
473    PrintRow =
474	fun(Prefix, Row) ->
475		lists:flatten(
476		  io_lib:format("~sEngineID:         ~p"
477				"~n~sName:             ~p"
478				"~n~sSecurityName:     ~p"
479				"~n~sCloneFrom:        ~p"
480				"~n~sAuthProtocol:     ~p (~w)"
481				"~n~sAuthKeyChange:    ~p"
482				"~n~sOwnAuthKeyChange: ~p"
483				"~n~sPrivProtocol:     ~p (~w)"
484				"~n~sPrivKeyChange:    ~p"
485				"~n~sOwnPrivKeyChange: ~p"
486				"~n~sPublic:           ~p"
487				"~n~sStorageType:      ~p (~w)"
488				"~n~sStatus:           ~p (~w)",
489				[Prefix, element(?usmUserEngineID, Row),
490				 Prefix, element(?usmUserName, Row),
491				 Prefix, element(?usmUserSecurityName, Row),
492				 Prefix, element(?usmUserCloneFrom, Row),
493				 Prefix, element(?usmUserAuthProtocol, Row),
494				 case element(?usmUserAuthProtocol, Row) of
495				     ?usmNoAuthProtocol      -> none;
496				     ?usmHMACMD5AuthProtocol -> md5;
497				     ?usmHMACSHAAuthProtocol -> sha;
498				     md5 -> md5;
499				     sha -> sha;
500				     _ -> undefined
501				 end,
502				 Prefix, element(?usmUserAuthKeyChange, Row),
503				 Prefix, element(?usmUserOwnAuthKeyChange, Row),
504				 Prefix, element(?usmUserPrivProtocol, Row),
505				 case element(?usmUserPrivProtocol, Row) of
506				     ?usmNoPrivProtocol    -> none;
507				     ?usmDESPrivProtocol   -> des;
508				     ?usmAesCfb128Protocol -> aes;
509				     des -> des;
510				     aes -> aes;
511				     _ -> undefined
512				 end,
513				 Prefix, element(?usmUserPrivKeyChange, Row),
514				 Prefix, element(?usmUserOwnPrivKeyChange, Row),
515				 Prefix, element(?usmUserPublic, Row),
516				 Prefix, element(?usmUserStorageType, Row),
517				 case element(?usmUserStorageType, Row) of
518				     ?'usmUserStorageType_readOnly' -> readOnly;
519				     ?'usmUserStorageType_permanent' -> permanent;
520				     ?'usmUserStorageType_nonVolatile' -> nonVolatile;
521				     ?'usmUserStorageType_volatile' -> volatile;
522				     ?'usmUserStorageType_other' -> other;
523				     _ -> undefined
524				 end,
525				 Prefix, element(?usmUserStatus, Row),
526				 case element(?usmUserStatus, Row) of
527				     ?'usmUserStatus_destroy' -> destroy;
528				     ?'usmUserStatus_createAndWait' -> createAndWait;
529				     ?'usmUserStatus_createAndGo' -> createAndGo;
530				     ?'usmUserStatus_notReady' -> notReady;
531				     ?'usmUserStatus_notInService' -> notInService;
532				     ?'usmUserStatus_active' -> active;
533				     _ -> undefined
534				 end]))
535	end,
536    snmpa_mib_lib:print_table(Table, DB, FOI, PrintRow);
537%% Op == new | delete
538usmUserTable(Op) ->
539    snmp_generic:table_func(Op, db(usmUserTable)).
540
541%% Op == get | is_set_ok | set | get_next
542usmUserTable(get, RowIndex, Cols) ->
543    get_patch(Cols, get(usmUserTable, RowIndex, Cols));
544usmUserTable(get_next, RowIndex, Cols) ->
545    next_patch(next(usmUserTable, RowIndex, Cols));
546usmUserTable(is_set_ok, RowIndex, Cols0) ->
547    ?vtrace("usmUserTable(is_set_ok) -> entry with"
548	    "~n   RowIndex: ~p"
549	    "~n   Cols0:    ~p", [RowIndex, Cols0]),
550    case (catch verify_usmUserTable_cols(Cols0, [])) of
551	{ok, Cols} ->
552	    ?vtrace("usmUserTable(is_set_ok) -> verified: "
553		    "~n   Cols: ~p", [Cols]),
554	    %% Add a dummy value for securityName; otherwise snmp_generic will
555	    %% think that a value is missing, so the row can't be created.
556	    %% Note: this value is only added for is_set_ok, not for set!
557	    NCols = [{?usmUserSecurityName, ""} | Cols],
558	    IsSetOkRes = snmp_generic:table_func(is_set_ok, RowIndex,
559						 NCols, db(usmUserTable)),
560	    ?vtrace("usmUserTable(is_set_ok) -> tested: "
561		    "~n   IsSetOkRes: ~p", [IsSetOkRes]),
562	    validate_is_set_ok(IsSetOkRes, RowIndex, Cols);
563	Error ->
564	    Error
565    end;
566usmUserTable(set, RowIndex, Cols0) ->
567    ?vtrace("usmUserTable(set) -> entry with"
568	    "~n   RowIndex: ~p"
569	    "~n   Cols0:    ~p", [RowIndex, Cols0]),
570    case (catch verify_usmUserTable_cols(Cols0, [])) of
571	{ok, Cols} ->
572	    ?vtrace("usmUserTable(set) -> verified"
573		    "~n   Cols: ~p", [Cols]),
574        % check whether we're cloning. if so, get cloned params and add a few
575        % defaults that might be needed.
576	    NCols = pre_set(RowIndex, validate_clone_from(RowIndex, Cols)),
577	    ?vtrace("usmUserTable(set) -> pre-set: "
578		    "~n   NCols: ~p", [NCols]),
579	    %% NOTE: The NCols parameter is sent to snmp_generic, but not to
580	    %% validate_set!  The reason is that the columns from pre_set are
581	    %% set in snmp_generic, but not used by validate_set.
582	    validate_set(snmp_generic:table_func(set, RowIndex,
583						 NCols, db(usmUserTable)),
584			 RowIndex, Cols);
585	Error ->
586	    Error
587    end;
588usmUserTable(Op, Arg1, Arg2) ->
589    snmp_generic:table_func(Op, Arg1, Arg2, db(usmUserTable)).
590
591
592verify_usmUserTable_cols([], Cols) ->
593    ?vtrace("verify_usmUserTable_cols -> entry when done with"
594	    "~n   Cols: ~p", [Cols]),
595    {ok, lists:reverse(Cols)};
596verify_usmUserTable_cols([{Col, Val0}|Cols], Acc) ->
597    ?vtrace("verify_usmUserTable_cols -> entry with"
598	    "~n   Col:  ~p"
599	    "~n   Val0: ~p", [Col, Val0]),
600    Val = verify_usmUserTable_col(Col, Val0),
601    ?vtrace("verify_usmUserTable_cols -> verified: "
602	    "~n   Val: ~p", [Val]),
603    verify_usmUserTable_cols(Cols, [{Col, Val}|Acc]).
604
605verify_usmUserTable_col(?usmUserEngineID, EngineID) ->
606    case (catch snmp_conf:check_string(EngineID)) of
607	ok ->
608	    EngineID;
609	_ ->
610	    wrongValue(?usmUserEngineID)
611    end;
612verify_usmUserTable_col(?usmUserName, Name) ->
613    case (catch snmp_conf:check_string(Name)) of
614	ok ->
615	    Name;
616	_ ->
617	    wrongValue(?usmUserName)
618    end;
619verify_usmUserTable_col(?usmUserSecurityName, Name) ->
620    case (catch snmp_conf:check_string(Name)) of
621	ok ->
622	    Name;
623	_ ->
624	    wrongValue(?usmUserSecurityName)
625    end;
626verify_usmUserTable_col(?usmUserCloneFrom, Clone) ->
627    case Clone of
628	zeroDotZero  -> ?zeroDotZero;
629	?zeroDotZero -> ?zeroDotZero;
630	_ ->
631	    case (catch snmp_conf:check_oid(Clone)) of
632		ok ->
633		    Clone;
634		_ ->
635		    wrongValue(?usmUserCloneFrom)
636	    end
637    end;
638verify_usmUserTable_col(?usmUserAuthProtocol, AuthP) ->
639    case AuthP of
640	usmNoAuthProtocol       -> ?usmNoAuthProtocol;
641	usmHMACSHAAuthProtocol  -> ?usmHMACSHAAuthProtocol;
642	usmHMACMD5AuthProtocol  -> ?usmHMACMD5AuthProtocol;
643	?usmNoAuthProtocol      -> ?usmNoAuthProtocol;
644	?usmHMACSHAAuthProtocol -> ?usmHMACSHAAuthProtocol;
645	?usmHMACMD5AuthProtocol -> ?usmHMACMD5AuthProtocol;
646	_ ->
647	    wrongValue(?usmUserAuthProtocol)
648    end;
649verify_usmUserTable_col(?usmUserAuthKeyChange, AKC) ->
650    case (catch snmp_conf:check_string(AKC)) of
651	ok ->
652	    AKC;
653	_ ->
654	    wrongValue(?usmUserAuthKeyChange)
655    end;
656verify_usmUserTable_col(?usmUserOwnAuthKeyChange, OAKC) ->
657    case (catch snmp_conf:check_string(OAKC)) of
658	ok ->
659	    OAKC;
660	_ ->
661	    wrongValue(?usmUserOwnAuthKeyChange)
662    end;
663verify_usmUserTable_col(?usmUserPrivProtocol, PrivP) ->
664    case PrivP of
665	usmNoPrivProtocol     -> ?usmNoPrivProtocol;
666	usmDESPrivProtocol    -> ?usmDESPrivProtocol;
667	usmAesCfb128Protocol  -> ?usmAesCfb128Protocol;
668	?usmNoPrivProtocol    -> ?usmNoPrivProtocol;
669	?usmDESPrivProtocol   -> ?usmDESPrivProtocol;
670	?usmAesCfb128Protocol -> ?usmAesCfb128Protocol;
671	_ ->
672	    wrongValue(?usmUserPrivProtocol)
673    end;
674verify_usmUserTable_col(?usmUserPrivKeyChange, PKC) ->
675    case (catch snmp_conf:check_string(PKC)) of
676	ok ->
677	    PKC;
678	_ ->
679	    wrongValue(?usmUserPrivKeyChange)
680    end;
681verify_usmUserTable_col(?usmUserOwnPrivKeyChange, OPKC) ->
682    case (catch snmp_conf:check_string(OPKC)) of
683	ok ->
684	    OPKC;
685	_ ->
686	    wrongValue(?usmUserOwnPrivKeyChange)
687    end;
688verify_usmUserTable_col(?usmUserPublic, Public) ->
689    case (catch snmp_conf:check_string(Public)) of
690	ok ->
691	    Public;
692	_ ->
693	    wrongValue(?usmUserPublic)
694    end;
695verify_usmUserTable_col(_, Val) ->
696    Val.
697
698
699%% Patch the values stored in the DB with other values for some
700%% objects.
701get_patch([?usmUserCloneFrom | Cols], [{value, _Val} | Vals]) ->
702    [{value, ?zeroDotZero} | get_patch(Cols, Vals)];
703get_patch([?usmUserAuthKeyChange | Cols], [{value, _Val} | Vals]) ->
704    [{value, ""} | get_patch(Cols, Vals)];
705get_patch([?usmUserOwnAuthKeyChange | Cols], [{value, _Val} | Vals]) ->
706    [{value, ""} | get_patch(Cols, Vals)];
707get_patch([?usmUserPrivKeyChange | Cols], [{value, _Val} | Vals]) ->
708    [{value, ""} | get_patch(Cols, Vals)];
709get_patch([?usmUserOwnPrivKeyChange | Cols], [{value, _Val} | Vals]) ->
710    [{value, ""} | get_patch(Cols, Vals)];
711get_patch([_Col | Cols], [Val | Vals]) ->
712    [Val | get_patch(Cols, Vals)];
713get_patch(_Cols, Result) ->
714    Result.
715
716next_patch([{[?usmUserCloneFrom | Idx], _Val} | Vals]) ->
717    [{[?usmUserCloneFrom | Idx], ?zeroDotZero} | next_patch(Vals)];
718next_patch([{[?usmUserAuthKeyChange | Idx], _Val} | Vals]) ->
719    [{[?usmUserAuthKeyChange | Idx], ""} | next_patch(Vals)];
720next_patch([{[?usmUserOwnAuthKeyChange | Idx], _Val} | Vals]) ->
721    [{[?usmUserOwnAuthKeyChange | Idx], ""} | next_patch(Vals)];
722next_patch([{[?usmUserPrivKeyChange | Idx], _Val} | Vals]) ->
723    [{[?usmUserPrivKeyChange | Idx], ""} | next_patch(Vals)];
724next_patch([{[?usmUserOwnPrivKeyChange | Idx], _Val} | Vals]) ->
725    [{[?usmUserOwnPrivKeyChange | Idx], ""} | next_patch(Vals)];
726next_patch([Val | Vals]) ->
727    [Val | next_patch(Vals)];
728next_patch(Result) -> Result.
729
730
731validate_is_set_ok({noError, 0}, RowIndex, Cols) ->
732    case (catch do_validate_is_set_ok(RowIndex, Cols)) of
733	ok ->
734	    {noError, 0};
735	Error ->
736	    Error
737    end;
738validate_is_set_ok(Error, _RowIndex, _Cols) ->
739    Error.
740
741do_validate_is_set_ok(RowIndex, Cols) ->
742    NCols = validate_clone_from(RowIndex, Cols),
743    validate_auth_protocol(RowIndex, NCols),
744    validate_auth_key_change(RowIndex, NCols),
745    validate_own_auth_key_change(RowIndex, NCols),
746    validate_priv_protocol(RowIndex, NCols),
747    validate_priv_key_change(RowIndex, NCols),
748    validate_own_priv_key_change(RowIndex, NCols),
749    ok.
750
751pre_set(RowIndex, Cols) ->
752    %% Remove the ?is_cloning member again; it must no longer be
753    %% present.
754    Cols0 = key1delete(?is_cloning, Cols),
755    %% Possibly initialize the usmUserSecurityName and privacy keys
756    case snmp_generic:table_row_exists(db(usmUserTable), RowIndex) of
757	true -> Cols0;
758	false ->
759	    SecName = get_user_name(RowIndex),
760	    Cols1 = [{?usmUserSecurityName, SecName} | Cols0],
761        case proplists:get_value(?is_cloning, Cols) of
762            true ->
763                % the row is just being cloned. the cloned user's
764                % passwords are already present in Cols and must
765                % not be overwritten.
766                Cols1;
767            _ ->
768                Cols1 ++ [{?usmUserAuthKey, ""},
769                    {?usmUserPrivKey, ""}]
770        end
771    end.
772
773validate_set({noError, 0}, RowIndex, Cols) ->
774    %% Now, all is_set_ok validation steps have been executed.  So
775    %% everything is ready for the set.
776    set_auth_key_change(RowIndex, Cols),
777    set_own_auth_key_change(RowIndex, Cols),
778    set_priv_key_change(RowIndex, Cols),
779    set_own_priv_key_change(RowIndex, Cols),
780    {noError, 0};
781validate_set(Error, _RowIndex, _Cols) ->
782    Error.
783
784%%-----------------------------------------------------------------
785%% Here's the alg: If this is the first time the CloneFrom is written,
786%% we must check that the CloneFrom row exists, so we can invoke the
787%% clone process in the set phase.  Otherwise, the set succed, with
788%% no further checks.
789%%-----------------------------------------------------------------
790validate_clone_from(RowIndex, Cols) ->
791    case key1search(?usmUserCloneFrom, Cols) of
792	{value, {_Col, RowPointer}} ->
793	    RowIndex2 = extract_row(RowPointer),
794	    OldCloneFrom = snmp_generic:table_get_element(db(usmUserTable),
795							  RowIndex,
796							  ?usmUserCloneFrom),
797	    case OldCloneFrom of
798		{value, Val} when Val /= noinit ->
799		    %% This means that the cloning is already done...
800		    no_cloning(Cols);
801		_ ->
802		    %% Otherwise, we must check the CloneFrom value. It
803            %% must relate to a usmUserEntry that exists and is active.
804            case snmp_generic:table_get_row(db(usmUserTable), RowIndex2) of
805                CloneFromRow when is_tuple(CloneFromRow) ->
806                    case element(?usmUserStatus, CloneFromRow) of
807                        ?'RowStatus_active' ->
808                            get_cloned_cols(CloneFromRow, Cols);
809                        _ ->
810                            inconsistentName(?usmUserCloneFrom)
811                    end;
812                undefined ->
813                    inconsistentName(?usmUserCloneFrom)
814            end
815	    end;
816	false ->
817        % no ?usmUserCloneFrom specified, don't modify columns
818        no_cloning(Cols)
819    end.
820
821get_cloned_cols(CloneFromRow, Cols) ->
822    % initialize cloned columns with data from CloneFromRow
823    % and overwrite that again with data found in Cols
824    AuthP = element(?usmUserAuthProtocol, CloneFromRow),
825    PrivP = element(?usmUserPrivProtocol, CloneFromRow),
826    AuthK = element(?usmUserAuthKey, CloneFromRow),
827    PrivK = element(?usmUserPrivKey, CloneFromRow),
828    ClonedCols = [{?usmUserAuthProtocol, AuthP},
829        {?usmUserPrivProtocol, PrivP},
830        {?usmUserAuthKey, AuthK},
831        {?usmUserPrivKey, PrivK},
832        {?is_cloning, true}
833    ],
834    Func = fun({Col, _} = Item, NCols) ->
835            key1store(Col, NCols, Item)
836    end,
837    Cols1 = lists:foldl(Func, ClonedCols, Cols),
838    key1sort(Cols1).
839
840no_cloning(Cols0) ->
841    Cols1 = key1delete(?usmUserCloneFrom, Cols0),
842    key1delete(?is_cloning, Cols1).
843
844
845validate_auth_protocol(RowIndex, Cols) ->
846    case key1search(?usmUserAuthProtocol, Cols) of
847	{value, {_Col, AuthProtocol}} ->
848	    %% Check if the row is being cloned; we can't check the
849	    %% old value of authProtocol, because if the row was
850	    %% createAndWaited, the default value would have been
851	    %% written (usmNoAuthProtocol).
852	    IsCloning = proplists:get_value(?is_cloning, Cols, false),
853	    if
854		not IsCloning ->
855		    %% This means that the row is not being cloned right
856            %% now; set is ok if new protocol is usmNoAuthProtocol
857		    case AuthProtocol of
858			?usmNoAuthProtocol ->
859			    %% Check that the Priv protocl is noPriv
860			    case get_priv_proto(RowIndex, Cols) of
861				?usmNoPrivProtocol -> ok;
862				_ -> inconsistentValue(?usmUserAuthProtocol)
863			    end;
864			?usmHMACMD5AuthProtocol ->
865			    inconsistentValue(?usmUserAuthProtocol);
866			?usmHMACSHAAuthProtocol ->
867			    inconsistentValue(?usmUserAuthProtocol);
868			_ ->
869			    wrongValue(?usmUserAuthProtocol)
870		    end;
871		true ->
872		    %% Otherwise, check that the new protocol is known,
873		    %% and that the system we're running supports the
874		    %% hash function.
875		    case AuthProtocol of
876			?usmNoAuthProtocol ->
877			    %% Check that the Priv protocl is noPriv
878			    case get_priv_proto(RowIndex, Cols) of
879				?usmNoPrivProtocol -> ok;
880				_ -> inconsistentValue(?usmUserAuthProtocol)
881			    end;
882			?usmHMACMD5AuthProtocol ->
883			    case is_crypto_supported(md5) of
884				true -> ok;
885				false ->
886				    wrongValue(?usmUserAuthProtocol)
887			    end;
888			?usmHMACSHAAuthProtocol ->
889			    case is_crypto_supported(sha) of
890				true -> ok;
891				false ->
892				    wrongValue(?usmUserAuthProtocol)
893			    end;
894			_ -> wrongValue(?usmUserAuthProtocol)
895		    end
896	    end;
897	false ->
898	    ok
899    end.
900
901validate_auth_key_change(RowIndex, Cols) ->
902    validate_key_change(RowIndex, Cols, ?usmUserAuthKeyChange, auth).
903
904validate_own_auth_key_change(RowIndex, Cols) ->
905    validate_requester(RowIndex, Cols, ?usmUserOwnAuthKeyChange),
906    validate_key_change(RowIndex, Cols, ?usmUserOwnAuthKeyChange, auth).
907
908validate_priv_key_change(RowIndex, Cols) ->
909    validate_key_change(RowIndex, Cols, ?usmUserPrivKeyChange, priv).
910
911validate_own_priv_key_change(RowIndex, Cols) ->
912    validate_requester(RowIndex, Cols, ?usmUserOwnPrivKeyChange),
913    validate_key_change(RowIndex, Cols, ?usmUserOwnPrivKeyChange, priv).
914
915%% Check that the requesting user is the same as the modified user
916validate_requester(RowIndex, Cols, KeyChangeCol) ->
917    case key1search(KeyChangeCol, Cols) of
918	{value, _} ->
919	    case get(sec_model) of % Check the securityModel in the request
920		?SEC_USM -> ok;
921		_ -> noAccess(KeyChangeCol)
922	    end,
923	    %% The SecurityName may not be set yet.  First, check if it is set.
924	    SecNameForUser =
925		case snmp_generic:table_get_element(db(usmUserTable),
926						    RowIndex,
927						    ?usmUserSecurityName) of
928		    {value, Val} when Val /= noinit -> Val;
929		    _ -> get_user_name(RowIndex)
930		end,
931	    case get(sec_name) of % Check the securityName in the request
932		SecNameForUser -> ok;
933		_ -> noAccess(KeyChangeCol)
934	    end;
935	false ->
936	    ok
937    end.
938
939validate_key_change(RowIndex, Cols, KeyChangeCol, Type) ->
940    case key1search(KeyChangeCol, Cols) of
941	{value, {_Col, KeyC}} ->
942	    %% Check if the row has been cloned; or if it is cloned in
943	    %% this set-operation.
944	    OldCloneFrom = snmp_generic:table_get_element(db(usmUserTable),
945							  RowIndex,
946							  ?usmUserCloneFrom),
947	    IsClonePresent = proplists:get_value(?is_cloning, Cols, false),
948	    %% Set is ok if 1) the user already is created, 2) this is
949	    %% a new user, which has been cloned, or is about to be
950	    %% cloned.
951	    case {OldCloneFrom, IsClonePresent} of
952		{{value, Val}, _} when Val /= noinit ->
953		    %% The user exists, or has been cloned
954		    ok;
955		{_, true} ->
956		    %% The user is cloned in this operation
957		    ok;
958		_ ->
959		    %% The user doesn't exist, or hasn't been cloned,
960		    %% and is not cloned in this operation.
961		    inconsistentName(KeyChangeCol)
962	    end,
963	    %% Check that the length makes sense
964	    Len = length(KeyC),
965	    case Type of
966		auth ->
967		    case get_auth_proto(RowIndex, Cols) of
968			?usmNoAuthProtocol -> ok;
969			?usmHMACMD5AuthProtocol when Len =:= 32 -> ok;
970			?usmHMACSHAAuthProtocol when Len =:= 40 -> ok;
971			_ -> wrongValue(KeyChangeCol)
972		    end;
973		priv ->
974		    case get_priv_proto(RowIndex, Cols) of
975			?usmNoPrivProtocol -> ok;
976			?usmDESPrivProtocol when Len == 32 -> ok;
977			?usmAesCfb128Protocol when Len == 32 -> ok;
978			_ -> wrongValue(KeyChangeCol)
979		    end
980	    end;
981	false ->
982	    ok
983    end.
984
985validate_priv_protocol(RowIndex, Cols) ->
986    case key1search(?usmUserPrivProtocol, Cols) of
987	{value, {_Col, PrivProtocol}} ->
988	    %% Check if the row has been cloned; we can't check the
989	    %% old value of privhProtocol, because if the row was
990	    %% createAndWaited, the default value would have been
991	    %% written (usmNoPrivProtocol).
992        IsCloning = proplists:get_value(?is_cloning, Cols, false),
993	    if
994        not IsCloning ->
995		    %% This means that the cloning is already done; set is ok
996		    %% if new protocol is usmNoPrivProtocol
997		    case PrivProtocol of
998			?usmNoPrivProtocol ->
999			    ok;
1000			?usmDESPrivProtocol ->
1001			    inconsistentValue(?usmUserPrivProtocol);
1002			?usmAesCfb128Protocol ->
1003			    inconsistentValue(?usmUserPrivProtocol);
1004			_ ->
1005			    wrongValue(?usmUserPrivProtocol)
1006		    end;
1007		true ->
1008		    %% Otherwise, check that the new protocol is known,
1009		    %% and that the system we're running supports the
1010		    %% crypto function.
1011		    case PrivProtocol of
1012			?usmNoPrivProtocol ->
1013			    ok;
1014			?usmDESPrivProtocol ->
1015			    %% The 'catch' handles the case when 'crypto' is
1016			    %% not present in the system.
1017			    case is_crypto_supported(des_cbc) of
1018				true ->
1019				    case get_auth_proto(RowIndex, Cols) of
1020					?usmNoAuthProtocol ->
1021					    inconsistentValue(?usmUserPrivProtocol);
1022					_ ->
1023					    ok
1024				    end;
1025				false ->
1026				    wrongValue(?usmUserPrivProtocol)
1027			    end;
1028			?usmAesCfb128Protocol ->
1029			    %% The 'catch' handles the case when 'crypto' is
1030			    %% not present in the system.
1031			    case is_crypto_supported(aes_cfb128) of
1032				true ->
1033				    case get_auth_proto(RowIndex, Cols) of
1034					?usmNoAuthProtocol ->
1035					    inconsistentValue(?usmUserPrivProtocol);
1036					_ ->
1037					    ok
1038				    end;
1039				false ->
1040				    wrongValue(?usmUserPrivProtocol)
1041			    end;
1042			_ -> wrongValue(?usmUserPrivProtocol)
1043		    end
1044	    end;
1045	false ->
1046	    ok
1047    end.
1048
1049
1050set_auth_key_change(RowIndex, Cols) ->
1051    set_key_change(RowIndex, Cols, ?usmUserAuthKeyChange, auth).
1052
1053set_own_auth_key_change(RowIndex, Cols) ->
1054    set_key_change(RowIndex, Cols, ?usmUserOwnAuthKeyChange, auth).
1055
1056set_priv_key_change(RowIndex, Cols) ->
1057    set_key_change(RowIndex, Cols, ?usmUserPrivKeyChange, priv).
1058
1059set_own_priv_key_change(RowIndex, Cols) ->
1060    set_key_change(RowIndex, Cols, ?usmUserOwnPrivKeyChange, priv).
1061
1062set_key_change(RowIndex, Cols, KeyChangeCol, Type) ->
1063    case key1search(KeyChangeCol, Cols) of
1064	{value, {_Col, KeyChange}} ->
1065	    KeyCol = case Type of
1066			 auth -> ?usmUserAuthKey;
1067			 priv -> ?usmUserPrivKey
1068		     end,
1069	    [AuthP, Key] =
1070		snmp_generic:table_get_elements(db(usmUserTable),
1071						RowIndex,
1072						[?usmUserAuthProtocol,
1073						 KeyCol]),
1074	    NewKey = extract_new_key(AuthP, Key, KeyChange),
1075	    snmp_generic:table_set_element(db(usmUserTable), RowIndex,
1076					   KeyCol, NewKey);
1077	false ->
1078	    ok
1079    end.
1080
1081%% Extract the UserName part from a RowIndex.
1082get_user_name([L1 | Rest])         -> get_user_name(L1, Rest).
1083get_user_name(0, [_L2 | UserName]) -> UserName;
1084get_user_name(N, [_H | T])          -> get_user_name(N-1, T).
1085
1086extract_row(RowPtr)                     -> extract_row(?usmUserEntry, RowPtr).
1087extract_row([H | T], [H | T2])          -> extract_row(T, T2);
1088extract_row([], [?usmUserSecurityName | T]) -> T;
1089extract_row(_, _) -> wrongValue(?usmUserCloneFrom).
1090
1091%% Pre: the user exists or is being cloned in this operation
1092get_auth_proto(RowIndex, Cols) ->
1093    %% The protocol can be changed by the request too, otherwise,
1094    %% check the stored protocol.
1095    case key1search(?usmUserAuthProtocol, Cols) of
1096	{value, {_, Protocol}} ->
1097	    Protocol;
1098	false ->
1099	    %% OTP-3596
1100	    case snmp_generic:table_get_element(db(usmUserTable),
1101						RowIndex,
1102						?usmUserAuthProtocol) of
1103		{value, Protocol} ->
1104		    Protocol;
1105		_ ->
1106		    undefined
1107	    end
1108    end.
1109
1110%% Pre: the user exists or is being cloned in this operation
1111get_priv_proto(RowIndex, Cols) ->
1112    %% The protocol can be changed by the request too, otherwise,
1113    %% check the stored protocol.
1114    case key1search(?usmUserPrivProtocol, Cols) of
1115	{value, {_, Protocol}} ->
1116	    Protocol;
1117	false ->
1118	    %% OTP-3596
1119	    case snmp_generic:table_get_element(db(usmUserTable),
1120						RowIndex,
1121						?usmUserPrivProtocol) of
1122		{value, Protocol} ->
1123		    Protocol;
1124		_ ->
1125		    undefined
1126	    end
1127    end.
1128
1129
1130db(X) -> snmpa_agent:db(X).
1131
1132fa(usmUserTable) -> ?usmUserSecurityName.
1133
1134foi(usmUserTable) -> ?usmUserEngineID.
1135
1136noc(usmUserTable) -> 13.
1137
1138stc(usmUserTable) -> ?usmUserStorageType.
1139
1140next(Name, RowIndex, Cols) ->
1141    snmp_generic:handle_table_next(db(Name), RowIndex, Cols,
1142                                   fa(Name), foi(Name), noc(Name)).
1143
1144table_next(Name, RestOid) ->
1145    snmp_generic:table_next(db(Name), RestOid).
1146
1147
1148get(Name, RowIndex, Cols) ->
1149    snmp_generic:handle_table_get(db(Name), RowIndex, Cols, foi(Name)).
1150
1151%%-----------------------------------------------------------------
1152%% Key change functions.  The KeyChange Texual-Convention is
1153%% defined in the SNMP-USER-BASED-SM-MIB.
1154%% Note that this implementation supports md5 and sha, which
1155%% both have fixed length requirements on the length of the key;
1156%% thus the implementation can be (and is) simplified.
1157%%-----------------------------------------------------------------
1158mk_key_change(Hash, OldKey, NewKey) ->
1159    KeyLen = length(NewKey),
1160    Alg = case Hash of
1161	      ?usmHMACMD5AuthProtocol -> md5;
1162	      ?usmHMACSHAAuthProtocol -> sha;
1163	      md5 -> md5;
1164	      sha -> sha
1165	  end,
1166    Random = mk_random(KeyLen),
1167    mk_key_change(Alg, OldKey, NewKey, KeyLen, Random).
1168
1169%% This function is only exported for test purposes.  There is a test
1170%% case in the standard where Random is pre-defined.
1171mk_key_change(Alg, OldKey, NewKey, KeyLen, Random) ->
1172    %% OldKey and Random is of length KeyLen...
1173    Digest = lists:sublist(binary_to_list(crypto:hash(Alg, OldKey++Random)), KeyLen),
1174    %% ... and so is Digest
1175    Delta = snmp_misc:str_xor(Digest, NewKey),
1176    Random ++ Delta.
1177
1178%% Extracts a new Key from a KeyChange value, sent by a manager.
1179extract_new_key(?usmNoAuthProtocol, OldKey, _KeyChange) ->
1180    OldKey;
1181extract_new_key(Hash, OldKey, KeyChange) ->
1182    KeyLen = length(OldKey),
1183    Alg = case Hash of
1184	      ?usmHMACMD5AuthProtocol -> md5;
1185	      ?usmHMACSHAAuthProtocol -> sha;
1186	      md5 -> md5;
1187	      sha -> sha
1188	  end,
1189    {Random, Delta} = split(KeyLen, KeyChange, []),
1190    Digest = lists:sublist(binary_to_list(crypto:hash(Alg, OldKey++Random)), KeyLen),
1191    NewKey = snmp_misc:str_xor(Digest, Delta),
1192    NewKey.
1193
1194-define(i16(Int), (Int bsr 8) band 255, Int band 255).
1195-define(i8(Int), Int band 255).
1196
1197mk_random(Len) when Len =< 20 ->
1198    binary_to_list(crypto:strong_rand_bytes(Len)).
1199
1200split(0, Rest, FirstRev) ->
1201    {lists:reverse(FirstRev), Rest};
1202split(N, [H | T], FirstRev) when N > 0 ->
1203    split(N-1, T, [H | FirstRev]).
1204
1205
1206-compile({inline, [{is_crypto_supported,1}]}).
1207is_crypto_supported(Func) ->
1208    snmp_misc:is_crypto_supported(Func).
1209
1210
1211inconsistentValue(V) -> throw({inconsistentValue, V}).
1212inconsistentName(N)  -> throw({inconsistentName,  N}).
1213wrongValue(V)        -> throw({wrongValue,        V}).
1214noAccess(C)          -> throw({noAccess,          C}).
1215
1216set_sname() ->
1217    set_sname(get(sname)).
1218
1219set_sname(undefined) ->
1220    put(sname,conf);
1221set_sname(_) -> %% Keep it, if already set.
1222    ok.
1223
1224error(Reason) ->
1225    throw({error, Reason}).
1226
1227
1228%%-----------------------------------------------------------------
1229%%     lists key-function(s) wrappers
1230
1231-compile({inline,key1delete/2}).
1232key1delete(Key, List) ->
1233    lists:keydelete(Key, 1, List).
1234
1235-compile({inline,key1search/2}).
1236key1search(Key, List) ->
1237    lists:keysearch(Key, 1, List).
1238
1239-compile({inline,key1store/3}).
1240key1store(Key, List, Elem) ->
1241    lists:keystore(Key, 1, List, Elem).
1242
1243-compile({inline,key1sort/1}).
1244key1sort(List) ->
1245    lists:keysort(1, List).
1246
1247
1248%%-----------------------------------------------------------------
1249
1250info_msg(F, A) ->
1251    ?snmpa_info("USM: " ++ F, A).
1252
1253%% ---
1254
1255config_err(F, A) ->
1256    snmpa_error:config_err("[USER-BASED-SM-MIB]: " ++ F, A).
1257
1258