%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1999-2010. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% -module(snmp_user_based_sm_mib). %% Avoid warning for local function error/1 clashing with autoimported BIF. -compile({no_auto_import,[error/1]}). -export([configure/1, reconfigure/1, usmUserSpinLock/1, usmUserSpinLock/2, usmUserTable/1, usmUserTable/3, table_next/2, is_engine_id_known/1, get_user/2, get_user_from_security_name/2, mk_key_change/3, mk_key_change/5, extract_new_key/3, mk_random/1]). -export([usmStatsUnsupportedSecLevels/1, usmStatsNotInTimeWindows/1, usmStatsUnknownUserNames/1, usmStatsUnknownEngineIDs/1, usmStatsWrongDigests/1, usmStatsDecryptionErrors/1]). -export([add_user/1, add_user/13, delete_user/1]). %% Internal -export([check_usm/1]). -include("SNMP-USER-BASED-SM-MIB.hrl"). -include("SNMP-USM-AES-MIB.hrl"). -include("SNMPv2-TC.hrl"). -include("snmpa_internal.hrl"). -include("snmp_types.hrl"). -define(VMODULE,"USM_MIB"). -include("snmp_verbosity.hrl"). -ifndef(default_verbosity). -define(default_verbosity,silence). -endif. %% Columns not accessible via SNMP -define(usmUserAuthKey, 14). -define(usmUserPrivKey, 15). %%%----------------------------------------------------------------- %%% Utility functions %%%----------------------------------------------------------------- %%%----------------------------------------------------------------- %%% Implements the instrumentation functions and additional %%% functions for the SNMP-USER-BASED-SM-MIB. %%%----------------------------------------------------------------- %%----------------------------------------------------------------- %% Func: configure/1 %% Args: Dir is the directory where the configuration files are found. %% Purpose: If the tables doesn't exist, this function reads %% the config-files for the USM tables, and %% inserts the data. This means that the data in the tables %% survive a reboot. However, the StorageType column is %% checked for each row. If volatile, the row is deleted. %% Returns: ok %% Fails: exit(configuration_error) %%----------------------------------------------------------------- configure(Dir) -> set_sname(), case db(usmUserTable) of {_, mnesia} -> ?vdebug("usm user table in mnesia: init vars & cleanup",[]), init_vars(), gc_tabs(); TabDb -> case snmpa_local_db:table_exists(TabDb) of true -> ?vdebug("usm user table exists: init vars & cleanup",[]), init_vars(), gc_tabs(); false -> ?vdebug("usm user table does not exist: reconfigure",[]), reconfigure(Dir) end end. %%----------------------------------------------------------------- %% Func: reconfigure/1 %% Args: Dir is the directory where the configuration files are found. %% Purpose: Reads the config-files for the USM tables, and %% inserts the data. Makes sure that all old data in %% the tables are deleted, and the new data inserted. %% This function makes sure that all (and only) %% config-file-data are in the tables. %% Returns: ok %% Fails: exit(configuration_error) | %% exit({unsupported_crypto, Function}) %%----------------------------------------------------------------- reconfigure(Dir) -> set_sname(), case (catch do_reconfigure(Dir)) of ok -> ok; {error, Reason} -> ?vinfo("reconfigure error: ~p", [Reason]), config_err("reconfigure failed: ~p", [Reason]), exit(configuration_error); Error -> ?vinfo("reconfigure failed: ~p", [Error]), config_err("reconfigure failed: ~p", [Error]), exit(configuration_error) end. do_reconfigure(Dir) -> ?vdebug("read usm configuration files",[]), Users = read_usm_config_files(Dir), ?vdebug("check users",[]), check_users(Users), ?vdebug("initiate tables",[]), init_tabs(Users), ?vdebug("initiate vars",[]), init_vars(), ok. read_usm_config_files(Dir) -> ?vdebug("read usm config file",[]), Gen = fun(D) -> generate_usm(D) end, Filter = fun(Usms) -> Usms end, Check = fun(Entry) -> check_usm(Entry) end, [Usms] = snmp_conf:read_files(Dir, [{Gen, Filter, Check, "usm.conf"}]), Usms. generate_usm(Dir) -> info_msg("Incomplete configuration. Generating empty usm.conf.", []), USMFile = filename:join(Dir, "usm.conf"), ok = file:write_file(USMFile, list_to_binary([])). check_usm({EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey}) -> snmp_conf:check_string(EngineID), snmp_conf:check_string(Name), snmp_conf:check_string(SecName), CloneVal = case catch snmp_conf:check_atom(Clone,[{zeroDotZero,?zeroDotZero}]) of {error, _} -> snmp_conf:check_oid(Clone), Clone; {ok, X} -> X end, AuthProtoAlt = [{usmNoAuthProtocol, ?usmNoAuthProtocol}, {usmHMACSHAAuthProtocol, ?usmHMACSHAAuthProtocol}, {usmHMACMD5AuthProtocol, ?usmHMACMD5AuthProtocol}], {ok, AuthProto} = snmp_conf:check_atom(AuthP, AuthProtoAlt), snmp_conf:check_string(AuthKeyC), snmp_conf:check_string(OwnAuthKeyC), PrivProtoAlt = [{usmNoPrivProtocol, ?usmNoPrivProtocol}, {usmDESPrivProtocol, ?usmDESPrivProtocol}, {usmAesCfb128Protocol, ?usmAesCfb128Protocol}], {ok, PrivProto} = snmp_conf:check_atom(PrivP, PrivProtoAlt), snmp_conf:check_string(PrivKeyC), snmp_conf:check_string(OwnPrivKeyC), snmp_conf:check_string(Public), snmp_conf:check_string(AuthKey, alen(AuthP)), snmp_conf:check_string(PrivKey, plen(PrivP)), User = {EngineID, Name, SecName, CloneVal, AuthProto, AuthKeyC, OwnAuthKeyC, PrivProto, PrivKeyC, OwnPrivKeyC, Public, ?'StorageType_nonVolatile', ?'RowStatus_active', AuthKey, PrivKey}, {ok, User}; check_usm(X) -> error({invalid_user, X}). alen(usmNoAuthProtocol) -> any; alen(usmHMACMD5AuthProtocol) -> 16; alen(usmHMACSHAAuthProtocol) -> 20. plen(usmNoPrivProtocol) -> any; plen(usmDESPrivProtocol) -> 16; plen(usmAesCfb128Protocol) -> 16. %%----------------------------------------------------------------- %% This function loops through all users, and check that the %% definition is constistent with the support for crypto on %% the system. Thus, it is not possible to define a user that %% uses authentication if 'crypto' is not started, or a user that %% uses DES if 'crypto' doesn't support DES. %%----------------------------------------------------------------- check_users([User | Users]) -> check_user(User), check_users(Users); check_users([]) -> ok. check_user(User) -> case element(?usmUserAuthProtocol, User) of ?usmNoAuthProtocol -> ok; ?usmHMACMD5AuthProtocol -> case is_crypto_supported(md5_mac_96) of true -> ok; false -> exit({unsupported_crypto, md5_mac_96}) end; ?usmHMACSHAAuthProtocol -> case is_crypto_supported(sha_mac_96) of true -> ok; false -> exit({unsupported_crypto, sha_mac_96}) end end, case element(?usmUserPrivProtocol, User) of ?usmNoPrivProtocol -> ok; ?usmDESPrivProtocol -> case is_crypto_supported(des_cbc_decrypt) of true -> ok; false -> exit({unsupported_crypto, des_cbc_decrypt}) end; ?usmAesCfb128Protocol -> case is_crypto_supported(aes_cfb_128_decrypt) of true -> ok; false -> exit({unsupported_crypto, aes_cfb_128_decrypt}) end end. init_tabs(Users) -> ?vdebug("create usm user table",[]), snmpa_local_db:table_delete(db(usmUserTable)), snmpa_local_db:table_create(db(usmUserTable)), init_user_table(Users). init_user_table([Row | T]) -> Key = mk_user_table_key(Row), snmpa_local_db:table_create_row(db(usmUserTable), Key, Row), init_user_table(T); init_user_table([]) -> true. mk_user_table_key(Row) -> Key1 = element(1, Row), Key2 = element(2, Row), [length(Key1) | Key1] ++ [length(Key2) | Key2]. table_cre_row(Tab, Key, Row) -> snmpa_mib_lib:table_cre_row(db(Tab), Key, Row). table_del_row(Tab, Key) -> snmpa_mib_lib:table_del_row(db(Tab), Key). add_user(EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> User = {EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey}, add_user(User). add_user(User) -> case (catch check_usm(User)) of {ok, Row} -> case (catch check_user(Row)) of {'EXIT', Reason} -> {error, Reason}; _ -> Key = mk_user_table_key(Row), case table_cre_row(usmUserTable, Key, Row) of true -> {ok, Key}; false -> {error, create_failed} end end; {error, Reason} -> {error, Reason}; Error -> {error, Error} end. delete_user(Key) -> case table_del_row(usmUserTable, Key) of true -> ok; false -> {error, delete_failed} end. gc_tabs() -> DB = db(usmUserTable), STC = stc(usmUserTable), FOI = foi(usmUserTable), snmpa_mib_lib:gc_tab(DB, STC, FOI), ok. %%----------------------------------------------------------------- %% Counter functions %%----------------------------------------------------------------- usmStatsUnsupportedSecLevels(print) -> VarAndValue = [{usmStatsUnsupportedSecLevels, usmStatsUnsupportedSecLevels(get)}], snmpa_mib_lib:print_variables(VarAndValue); usmStatsUnsupportedSecLevels(get) -> get_counter(usmStatsUnsupportedSecLevels). usmStatsNotInTimeWindows(print) -> VarAndValue = [{usmStatsNotInTimeWindows, usmStatsNotInTimeWindows(get)}], snmpa_mib_lib:print_variables(VarAndValue); usmStatsNotInTimeWindows(get) -> get_counter(usmStatsNotInTimeWindows). usmStatsUnknownUserNames(print) -> VarAndValue = [{usmStatsUnknownUserNames, usmStatsUnknownUserNames(get)}], snmpa_mib_lib:print_variables(VarAndValue); usmStatsUnknownUserNames(get) -> get_counter(usmStatsUnknownUserNames). usmStatsUnknownEngineIDs(print) -> VarAndValue = [{usmStatsUnknownEngineIDs, usmStatsUnknownEngineIDs(get)}], snmpa_mib_lib:print_variables(VarAndValue); usmStatsUnknownEngineIDs(get) -> get_counter(usmStatsUnknownEngineIDs). usmStatsWrongDigests(print) -> VarAndValue = [{usmStatsWrongDigests, usmStatsWrongDigests(get)}], snmpa_mib_lib:print_variables(VarAndValue); usmStatsWrongDigests(get) -> get_counter(usmStatsWrongDigests). usmStatsDecryptionErrors(print) -> VarAndValue = [{usmStatsDecryptionErrors, usmStatsDecryptionErrors(get)}], snmpa_mib_lib:print_variables(VarAndValue); usmStatsDecryptionErrors(get) -> get_counter(usmStatsDecryptionErrors). get_counter(Name) -> case (catch ets:lookup(snmp_agent_table, Name)) of [{_, Val}] -> {value, Val}; _ -> genErr end. init_vars() -> lists:map(fun maybe_create_var/1, vars()). maybe_create_var(Var) -> case ets:lookup(snmp_agent_table, Var) of [_] -> ok; _ -> init_var(Var) end. init_var(Var) -> ets:insert(snmp_agent_table, {Var, 0}). vars() -> [ usmStatsUnsupportedSecLevels, usmStatsNotInTimeWindows, usmStatsUnknownUserNames, usmStatsUnknownEngineIDs, usmStatsWrongDigests, usmStatsDecryptionErrors ]. %%----------------------------------------------------------------- %% API functions %%----------------------------------------------------------------- is_engine_id_known(EngineID) -> EngineKey = [length(EngineID) | EngineID], ?vtrace("is_engine_id_known -> EngineKey: ~w", [EngineKey]), case table_next(usmUserTable, EngineKey) of endOfTable -> false; Key -> ?vtrace("is_engine_id_known -> Key: ~w", [Key]), lists:prefix(EngineKey, Key) end. get_user(EngineID, UserName) -> Key = [length(EngineID) | EngineID] ++ [length(UserName) | UserName], snmp_generic:table_get_row(db(usmUserTable), Key, foi(usmUserTable)). get_user_from_security_name(EngineID, SecName) -> %% Since the normal mapping between UserName and SecName is the %% identityfunction, we first try to use the SecName as UserName, %% and check the resulting row. If it doesn't match, we'll have to %% loop through the entire table. Key = [length(EngineID) | EngineID] ++ [length(SecName) | SecName], case snmp_generic:table_get_row(db(usmUserTable), Key, foi(usmUserTable)) of Row when is_tuple(Row) -> ?vtrace("get_user_from_security_name -> " "found user using the identity function", []), Row; undefined -> ?vtrace("get_user_from_security_name -> " "user *not* found using the identity function", []), F = fun(_, Row) when ( (element(?usmUserEngineID,Row) =:= EngineID) andalso (element(?usmUserSecurityName,Row) =:= SecName)) -> throw({ok, Row}); (_, _) -> ok end, case catch snmp_generic:table_foreach(db(usmUserTable), F) of {ok, Row} -> Row; _Else -> undefined end end. %%----------------------------------------------------------------- %% Instrumentation Functions %%----------------------------------------------------------------- usmUserSpinLock(print) -> VarAndValue = [{usmUserSpinLock, usmUserSpinLock(get)}], snmpa_mib_lib:print_variables(VarAndValue); usmUserSpinLock(new) -> snmp_generic:variable_func(new, {usmUserSpinLock, volatile}), {A1,A2,A3} = erlang:now(), random:seed(A1,A2,A3), Val = random:uniform(2147483648) - 1, snmp_generic:variable_func(set, Val, {usmUserSpinLock, volatile}); usmUserSpinLock(delete) -> ok; usmUserSpinLock(get) -> snmp_generic:variable_func(get, {usmUserSpinLock, volatile}). usmUserSpinLock(is_set_ok, NewVal) -> case snmp_generic:variable_func(get, {usmUserSpinLock, volatile}) of {value, NewVal} -> noError; _ -> inconsistentValue end; usmUserSpinLock(set, NewVal) -> snmp_generic:variable_func(set, (NewVal + 1) rem 2147483648, {usmUserSpinLock, volatile}). %% Op = print - Used for debugging purposes usmUserTable(print) -> Table = usmUserTable, DB = db(Table), FOI = foi(Table), %% TableInfo = snmpa_mib_lib:get_table(db(Table), foi(Table)), PrintRow = fun(Prefix, Row) -> lists:flatten( io_lib:format("~sEngineID: ~p" "~n~sName: ~p" "~n~sSecurityName: ~p" "~n~sCloneFrom: ~p" "~n~sAuthProtocol: ~p (~w)" "~n~sAuthKeyChange: ~p" "~n~sOwnAuthKeyChange: ~p" "~n~sPrivProtocol: ~p (~w)" "~n~sPrivKeyChange: ~p" "~n~sOwnPrivKeyChange: ~p" "~n~sPublic: ~p" "~n~sStorageType: ~p (~w)" "~n~sStatus: ~p (~w)", [Prefix, element(?usmUserEngineID, Row), Prefix, element(?usmUserName, Row), Prefix, element(?usmUserSecurityName, Row), Prefix, element(?usmUserCloneFrom, Row), Prefix, element(?usmUserAuthProtocol, Row), case element(?usmUserAuthProtocol, Row) of ?usmNoAuthProtocol -> none; ?usmHMACMD5AuthProtocol -> md5; ?usmHMACSHAAuthProtocol -> sha; md5 -> md5; sha -> sha; _ -> undefined end, Prefix, element(?usmUserAuthKeyChange, Row), Prefix, element(?usmUserOwnAuthKeyChange, Row), Prefix, element(?usmUserPrivProtocol, Row), case element(?usmUserPrivProtocol, Row) of ?usmNoPrivProtocol -> none; ?usmDESPrivProtocol -> des; ?usmAesCfb128Protocol -> aes; des -> des; aes -> aes; _ -> undefined end, Prefix, element(?usmUserPrivKeyChange, Row), Prefix, element(?usmUserOwnPrivKeyChange, Row), Prefix, element(?usmUserPublic, Row), Prefix, element(?usmUserStorageType, Row), case element(?usmUserStorageType, Row) of ?'usmUserStorageType_readOnly' -> readOnly; ?'usmUserStorageType_permanent' -> permanent; ?'usmUserStorageType_nonVolatile' -> nonVolatile; ?'usmUserStorageType_volatile' -> volatile; ?'usmUserStorageType_other' -> other; _ -> undefined end, Prefix, element(?usmUserStatus, Row), case element(?usmUserStatus, Row) of ?'usmUserStatus_destroy' -> destroy; ?'usmUserStatus_createAndWait' -> createAndWait; ?'usmUserStatus_createAndGo' -> createAndGo; ?'usmUserStatus_notReady' -> notReady; ?'usmUserStatus_notInService' -> notInService; ?'usmUserStatus_active' -> active; _ -> undefined end])) end, snmpa_mib_lib:print_table(Table, DB, FOI, PrintRow); %% Op == new | delete usmUserTable(Op) -> snmp_generic:table_func(Op, db(usmUserTable)). %% Op == get | is_set_ok | set | get_next usmUserTable(get, RowIndex, Cols) -> get_patch(Cols, get(usmUserTable, RowIndex, Cols)); usmUserTable(get_next, RowIndex, Cols) -> next_patch(next(usmUserTable, RowIndex, Cols)); usmUserTable(is_set_ok, RowIndex, Cols0) -> ?vtrace("usmUserTable(is_set_ok) -> entry with" "~n RowIndex: ~p" "~n Cols0: ~p", [RowIndex, Cols0]), case (catch verify_usmUserTable_cols(Cols0, [])) of {ok, Cols} -> ?vtrace("usmUserTable(is_set_ok) -> verified: " "~n Cols: ~p", [Cols]), %% Add a dummy value for securityName; otherwise snmp_generic will %% think that a value is missing, so the row can't be created. %% Note: this value is only added for is_set_ok, not for set! NCols = [{?usmUserSecurityName, ""} | Cols], IsSetOkRes = snmp_generic:table_func(is_set_ok, RowIndex, NCols, db(usmUserTable)), ?vtrace("usmUserTable(is_set_ok) -> tested: " "~n IsSetOkRes: ~p", [IsSetOkRes]), validate_is_set_ok(IsSetOkRes, RowIndex, Cols); Error -> Error end; usmUserTable(set, RowIndex, Cols0) -> ?vtrace("usmUserTable(set) -> entry with" "~n RowIndex: ~p" "~n Cols0: ~p", [RowIndex, Cols0]), case (catch verify_usmUserTable_cols(Cols0, [])) of {ok, Cols} -> ?vtrace("usmUserTable(set) -> verified" "~n Cols: ~p", [Cols]), NCols = pre_set(RowIndex, Cols), ?vtrace("usmUserTable(set) -> pre-set: " "~n NCols: ~p", [NCols]), %% NOTE: The NCols parameter is sent to snmp_generic, but not to %% validate_set! The reason is that the columns from pre_set are %% set in snmp_generic, but not used by validate_set. validate_set(snmp_generic:table_func(set, RowIndex, NCols, db(usmUserTable)), RowIndex, Cols); Error -> Error end; usmUserTable(Op, Arg1, Arg2) -> snmp_generic:table_func(Op, Arg1, Arg2, db(usmUserTable)). verify_usmUserTable_cols([], Cols) -> ?vtrace("verify_usmUserTable_cols -> entry when done with" "~n Cols: ~p", [Cols]), {ok, lists:reverse(Cols)}; verify_usmUserTable_cols([{Col, Val0}|Cols], Acc) -> ?vtrace("verify_usmUserTable_cols -> entry with" "~n Col: ~p" "~n Val0: ~p", [Col, Val0]), Val = verify_usmUserTable_col(Col, Val0), ?vtrace("verify_usmUserTable_cols -> verified: " "~n Val: ~p", [Val]), verify_usmUserTable_cols(Cols, [{Col, Val}|Acc]). verify_usmUserTable_col(?usmUserEngineID, EngineID) -> case (catch snmp_conf:check_string(EngineID)) of ok -> EngineID; _ -> wrongValue(?usmUserEngineID) end; verify_usmUserTable_col(?usmUserName, Name) -> case (catch snmp_conf:check_string(Name)) of ok -> Name; _ -> wrongValue(?usmUserName) end; verify_usmUserTable_col(?usmUserSecurityName, Name) -> case (catch snmp_conf:check_string(Name)) of ok -> Name; _ -> wrongValue(?usmUserSecurityName) end; verify_usmUserTable_col(?usmUserCloneFrom, Clone) -> case Clone of zeroDotZero -> ?zeroDotZero; ?zeroDotZero -> ?zeroDotZero; _ -> case (catch snmp_conf:check_oid(Clone)) of ok -> Clone; _ -> wrongValue(?usmUserCloneFrom) end end; verify_usmUserTable_col(?usmUserAuthProtocol, AuthP) -> case AuthP of usmNoAuthProtocol -> ?usmNoAuthProtocol; usmHMACSHAAuthProtocol -> ?usmHMACSHAAuthProtocol; usmHMACMD5AuthProtocol -> ?usmHMACMD5AuthProtocol; ?usmNoAuthProtocol -> ?usmNoAuthProtocol; ?usmHMACSHAAuthProtocol -> ?usmHMACSHAAuthProtocol; ?usmHMACMD5AuthProtocol -> ?usmHMACMD5AuthProtocol; _ -> wrongValue(?usmUserAuthProtocol) end; verify_usmUserTable_col(?usmUserAuthKeyChange, AKC) -> case (catch snmp_conf:check_string(AKC)) of ok -> AKC; _ -> wrongValue(?usmUserAuthKeyChange) end; verify_usmUserTable_col(?usmUserOwnAuthKeyChange, OAKC) -> case (catch snmp_conf:check_string(OAKC)) of ok -> OAKC; _ -> wrongValue(?usmUserOwnAuthKeyChange) end; verify_usmUserTable_col(?usmUserPrivProtocol, PrivP) -> case PrivP of usmNoPrivProtocol -> ?usmNoPrivProtocol; usmDESPrivProtocol -> ?usmDESPrivProtocol; usmAesCfb128Protocol -> ?usmAesCfb128Protocol; ?usmNoPrivProtocol -> ?usmNoPrivProtocol; ?usmDESPrivProtocol -> ?usmDESPrivProtocol; ?usmAesCfb128Protocol -> ?usmAesCfb128Protocol; _ -> wrongValue(?usmUserPrivProtocol) end; verify_usmUserTable_col(?usmUserPrivKeyChange, PKC) -> case (catch snmp_conf:check_string(PKC)) of ok -> PKC; _ -> wrongValue(?usmUserPrivKeyChange) end; verify_usmUserTable_col(?usmUserOwnPrivKeyChange, OPKC) -> case (catch snmp_conf:check_string(OPKC)) of ok -> OPKC; _ -> wrongValue(?usmUserOwnPrivKeyChange) end; verify_usmUserTable_col(?usmUserPublic, Public) -> case (catch snmp_conf:check_string(Public)) of ok -> Public; _ -> wrongValue(?usmUserPublic) end; verify_usmUserTable_col(_, Val) -> Val. %% Patch the values stored in the DB with other values for some %% objects. get_patch([?usmUserCloneFrom | Cols], [{value, _Val} | Vals]) -> [{value, ?zeroDotZero} | get_patch(Cols, Vals)]; get_patch([?usmUserAuthKeyChange | Cols], [{value, _Val} | Vals]) -> [{value, ""} | get_patch(Cols, Vals)]; get_patch([?usmUserOwnAuthKeyChange | Cols], [{value, _Val} | Vals]) -> [{value, ""} | get_patch(Cols, Vals)]; get_patch([?usmUserPrivKeyChange | Cols], [{value, _Val} | Vals]) -> [{value, ""} | get_patch(Cols, Vals)]; get_patch([?usmUserOwnPrivKeyChange | Cols], [{value, _Val} | Vals]) -> [{value, ""} | get_patch(Cols, Vals)]; get_patch([_Col | Cols], [Val | Vals]) -> [Val | get_patch(Cols, Vals)]; get_patch(_Cols, Result) -> Result. next_patch([{[?usmUserCloneFrom | Idx], _Val} | Vals]) -> [{[?usmUserCloneFrom | Idx], ?zeroDotZero} | next_patch(Vals)]; next_patch([{[?usmUserAuthKeyChange | Idx], _Val} | Vals]) -> [{[?usmUserAuthKeyChange | Idx], ""} | next_patch(Vals)]; next_patch([{[?usmUserOwnAuthKeyChange | Idx], _Val} | Vals]) -> [{[?usmUserOwnAuthKeyChange | Idx], ""} | next_patch(Vals)]; next_patch([{[?usmUserPrivKeyChange | Idx], _Val} | Vals]) -> [{[?usmUserPrivKeyChange | Idx], ""} | next_patch(Vals)]; next_patch([{[?usmUserOwnPrivKeyChange | Idx], _Val} | Vals]) -> [{[?usmUserOwnPrivKeyChange | Idx], ""} | next_patch(Vals)]; next_patch([Val | Vals]) -> [Val | next_patch(Vals)]; next_patch(Result) -> Result. validate_is_set_ok({noError, 0}, RowIndex, Cols) -> case (catch do_validate_is_set_ok(RowIndex, Cols)) of ok -> {noError, 0}; Error -> Error end; validate_is_set_ok(Error, _RowIndex, _Cols) -> Error. do_validate_is_set_ok(RowIndex, Cols) -> validate_clone_from(RowIndex, Cols), validate_auth_protocol(RowIndex, Cols), validate_auth_key_change(RowIndex, Cols), validate_own_auth_key_change(RowIndex, Cols), validate_priv_protocol(RowIndex, Cols), validate_priv_key_change(RowIndex, Cols), validate_own_priv_key_change(RowIndex, Cols), ok. pre_set(RowIndex, Cols) -> %% Possibly initialize the usmUserSecurityName and privacy keys case snmp_generic:table_row_exists(db(usmUserTable), RowIndex) of true -> Cols; false -> SecName = get_user_name(RowIndex), [{?usmUserSecurityName, SecName} | Cols] ++ [{?usmUserAuthKey, ""}, {?usmUserPrivKey, ""}] end. validate_set({noError, 0}, RowIndex, Cols) -> %% Now, all is_set_ok validation steps have been executed. So %% everything is ready for the set. set_clone_from(RowIndex, Cols), set_auth_key_change(RowIndex, Cols), set_own_auth_key_change(RowIndex, Cols), set_priv_key_change(RowIndex, Cols), set_own_priv_key_change(RowIndex, Cols), {noError, 0}; validate_set(Error, _RowIndex, _Cols) -> Error. %%----------------------------------------------------------------- %% Here's the alg: If this is the first time the CloneFrom is written, %% we must check that the CloneFrom row exists, so we can invoke the %% clone process in the set phase. Otherwise, the set succed, with %% no further checks. %%----------------------------------------------------------------- validate_clone_from(RowIndex, Cols) -> case lists:keysearch(?usmUserCloneFrom, 1, Cols) of {value, {_Col, RowPointer}} -> RowIndex2 = extract_row(RowPointer), OldCloneFrom = snmp_generic:table_get_element(db(usmUserTable), RowIndex, ?usmUserCloneFrom), case OldCloneFrom of {value, Val} when Val /= noinit -> %% This means that the cloning is already done... ok; _ -> %% Otherwise, we must check the CloneFrom value case snmp_generic:table_get_element(db(usmUserTable), RowIndex2, ?usmUserStatus) of {value, ?'RowStatus_active'} -> ok; _ -> inconsistentName(?usmUserCloneFrom) end end; false -> ok end. validate_auth_protocol(RowIndex, Cols) -> case lists:keysearch(?usmUserAuthProtocol, 1, Cols) of {value, {_Col, AuthProtocol}} -> %% Check if the row has been cloned; we can't check the %% old value of authProtocol, because if the row was %% createAndWaited, the default value would have been %% written (usmNoAuthProtocol). OldCloneFrom = snmp_generic:table_get_element(db(usmUserTable), RowIndex, ?usmUserCloneFrom), case OldCloneFrom of {value, Val} when Val /= noinit -> %% This means that the cloning is already done; set is ok %% if new protocol is usmNoAuthProtocol case AuthProtocol of ?usmNoAuthProtocol -> %% Check that the Priv protocl is noPriv case get_priv_proto(RowIndex, Cols) of ?usmNoPrivProtocol -> ok; _ -> inconsistentValue(?usmUserAuthProtocol) end; ?usmHMACMD5AuthProtocol -> inconsistentValue(?usmUserAuthProtocol); ?usmHMACSHAAuthProtocol -> inconsistentValue(?usmUserAuthProtocol); _ -> wrongValue(?usmUserAuthProtocol) end; _ -> %% Otherwise, check that the new protocol is known, %% and that the system we're running supports the %% hash function. case AuthProtocol of ?usmNoAuthProtocol -> %% Check that the Priv protocl is noPriv case get_priv_proto(RowIndex, Cols) of ?usmNoPrivProtocol -> ok; _ -> inconsistentValue(?usmUserAuthProtocol) end; ?usmHMACMD5AuthProtocol -> case is_crypto_supported(md5_mac_96) of true -> ok; false -> wrongValue(?usmUserAuthProtocol) end; ?usmHMACSHAAuthProtocol -> case is_crypto_supported(sha_mac_96) of true -> ok; false -> wrongValue(?usmUserAuthProtocol) end; _ -> wrongValue(?usmUserAuthProtocol) end end; false -> ok end. validate_auth_key_change(RowIndex, Cols) -> validate_key_change(RowIndex, Cols, ?usmUserAuthKeyChange, auth). validate_own_auth_key_change(RowIndex, Cols) -> validate_requester(RowIndex, Cols, ?usmUserOwnAuthKeyChange), validate_key_change(RowIndex, Cols, ?usmUserOwnAuthKeyChange, auth). validate_priv_key_change(RowIndex, Cols) -> validate_key_change(RowIndex, Cols, ?usmUserPrivKeyChange, priv). validate_own_priv_key_change(RowIndex, Cols) -> validate_requester(RowIndex, Cols, ?usmUserOwnPrivKeyChange), validate_key_change(RowIndex, Cols, ?usmUserOwnPrivKeyChange, priv). %% Check that the requesting user is the same as the modified user validate_requester(RowIndex, Cols, KeyChangeCol) -> case lists:keysearch(KeyChangeCol, 1, Cols) of {value, _} -> case get(sec_model) of % Check the securityModel in the request ?SEC_USM -> ok; _ -> noAccess(KeyChangeCol) end, %% The SecurityName may not be set yet. First, check if it is set. SecNameForUser = case snmp_generic:table_get_element(db(usmUserTable), RowIndex, ?usmUserSecurityName) of {value, Val} when Val /= noinit -> Val; _ -> get_user_name(RowIndex) end, case get(sec_name) of % Check the securityName in the request SecNameForUser -> ok; _ -> noAccess(KeyChangeCol) end; false -> ok end. validate_key_change(RowIndex, Cols, KeyChangeCol, Type) -> case lists:keysearch(KeyChangeCol, 1, Cols) of {value, {_Col, KeyC}} -> %% Check if the row has been cloned; or if it is cloned in %% this set-operation. OldCloneFrom = snmp_generic:table_get_element(db(usmUserTable), RowIndex, ?usmUserCloneFrom), IsClonePresent = case lists:keysearch(?usmUserCloneFrom, 1, Cols) of {value, _} -> true; false -> false end, %% Set is ok if 1) the user already is created, 2) this is %% a new user, which has been cloned, or is about to be %% cloned. case {OldCloneFrom, IsClonePresent} of {{value, Val}, _} when Val /= noinit -> %% The user exists, or has been cloned ok; {_, true} -> %% The user is cloned in this operation ok; _ -> %% The user doen't exist, or hasn't been cloned, %% and is not cloned in this operation. inconsistentName(KeyChangeCol) end, %% Check that the length makes sense Len = length(KeyC), case Type of auth -> case get_auth_proto(RowIndex, Cols) of ?usmNoAuthProtocol -> ok; ?usmHMACMD5AuthProtocol when Len =:= 32 -> ok; ?usmHMACSHAAuthProtocol when Len =:= 40 -> ok; _ -> wrongValue(KeyChangeCol) end; priv -> case get_priv_proto(RowIndex, Cols) of ?usmNoPrivProtocol -> ok; ?usmDESPrivProtocol when Len == 32 -> ok; ?usmAesCfb128Protocol when Len == 32 -> ok; _ -> wrongValue(KeyChangeCol) end end; false -> ok end. validate_priv_protocol(RowIndex, Cols) -> case lists:keysearch(?usmUserPrivProtocol, 1, Cols) of {value, {_Col, PrivProtocol}} -> %% Check if the row has been cloned; we can't check the %% old value of privhProtocol, because if the row was %% createAndWaited, the default value would have been %% written (usmNoPrivProtocol). OldCloneFrom = snmp_generic:table_get_element(db(usmUserTable), RowIndex, ?usmUserCloneFrom), case OldCloneFrom of {value, Val} when Val /= noinit -> %% This means that the cloning is already done; set is ok %% if new protocol is usmNoPrivProtocol case PrivProtocol of ?usmNoPrivProtocol -> ok; ?usmDESPrivProtocol -> inconsistentValue(?usmUserPrivProtocol); ?usmAesCfb128Protocol -> inconsistentValue(?usmUserPrivProtocol); _ -> wrongValue(?usmUserPrivProtocol) end; _ -> %% Otherwise, check that the new protocol is known, %% and that the system we're running supports the %% crypto function. case PrivProtocol of ?usmNoPrivProtocol -> ok; ?usmDESPrivProtocol -> %% The 'catch' handles the case when 'crypto' is %% not present in the system. case is_crypto_supported(des_cbc_decrypt) of true -> case get_auth_proto(RowIndex, Cols) of ?usmNoAuthProtocol -> inconsistentValue(?usmUserPrivProtocol); _ -> ok end; false -> wrongValue(?usmUserPrivProtocol) end; ?usmAesCfb128Protocol -> %% The 'catch' handles the case when 'crypto' is %% not present in the system. case is_crypto_supported(aes_cfb_128_decrypt) of true -> case get_auth_proto(RowIndex, Cols) of ?usmNoAuthProtocol -> inconsistentValue(?usmUserPrivProtocol); _ -> ok end; false -> wrongValue(?usmUserPrivProtocol) end; _ -> wrongValue(?usmUserPrivProtocol) end end; false -> ok end. set_clone_from(RowIndex, Cols) -> %% If CloneFrom is modified, do the cloning. case lists:keysearch(?usmUserCloneFrom, 1, Cols) of {value, {_Col, RowPointer}} -> RowIndex2 = extract_row(RowPointer), % won't fail CloneRow = snmp_generic:table_get_row(db(usmUserTable), RowIndex2, foi(usmUserTable)), AuthP = element(?usmUserAuthProtocol, CloneRow), PrivP = element(?usmUserPrivProtocol, CloneRow), AuthK = element(?usmUserAuthKey, CloneRow), PrivK = element(?usmUserPrivKey, CloneRow), SCols = [{?usmUserAuthProtocol, AuthP}, {?usmUserPrivProtocol, PrivP}, {?usmUserAuthKey, AuthK}, {?usmUserPrivKey, PrivK}], case snmp_generic:table_set_elements(db(usmUserTable), RowIndex, SCols) of true -> ok; false -> {commitFailed, ?usmUserCloneFrom} end; false -> ok end. set_auth_key_change(RowIndex, Cols) -> set_key_change(RowIndex, Cols, ?usmUserAuthKeyChange, auth). set_own_auth_key_change(RowIndex, Cols) -> set_key_change(RowIndex, Cols, ?usmUserOwnAuthKeyChange, auth). set_priv_key_change(RowIndex, Cols) -> set_key_change(RowIndex, Cols, ?usmUserPrivKeyChange, priv). set_own_priv_key_change(RowIndex, Cols) -> set_key_change(RowIndex, Cols, ?usmUserOwnPrivKeyChange, priv). set_key_change(RowIndex, Cols, KeyChangeCol, Type) -> case lists:keysearch(KeyChangeCol, 1, Cols) of {value, {_Col, KeyChange}} -> KeyCol = case Type of auth -> ?usmUserAuthKey; priv -> ?usmUserPrivKey end, [AuthP, Key] = snmp_generic:table_get_elements(db(usmUserTable), RowIndex, [?usmUserAuthProtocol, KeyCol]), NewKey = extract_new_key(AuthP, Key, KeyChange), snmp_generic:table_set_element(db(usmUserTable), RowIndex, KeyCol, NewKey); false -> ok end. %% Extract the UserName part from a RowIndex. get_user_name([L1 | Rest]) -> get_user_name(L1, Rest). get_user_name(0, [_L2 | UserName]) -> UserName; get_user_name(N, [_H | T]) -> get_user_name(N-1, T). extract_row(RowPtr) -> extract_row(?usmUserEntry, RowPtr). extract_row([H | T], [H | T2]) -> extract_row(T, T2); extract_row([], [?usmUserSecurityName | T]) -> T; extract_row(_, _) -> wrongValue(?usmUserCloneFrom). %% Pre: the user exixt get_auth_proto(RowIndex, Cols) -> %% The protocol can be chanegd by the request too, otherwise, %% check the stored protocol. case lists:keysearch(?usmUserAuthProtocol, 1, Cols) of {value, {_, Protocol}} -> Protocol; false -> %% OTP-3596 case snmp_generic:table_get_element(db(usmUserTable), RowIndex, ?usmUserAuthProtocol) of {value, Protocol} -> Protocol; _ -> undefined end end. %% Pre: the user exixt get_priv_proto(RowIndex, Cols) -> %% The protocol can be chanegd by the request too, otherwise, %% check the stored protocol. case lists:keysearch(?usmUserPrivProtocol, 1, Cols) of {value, {_, Protocol}} -> Protocol; false -> %% OTP-3596 case snmp_generic:table_get_element(db(usmUserTable), RowIndex, ?usmUserPrivProtocol) of {value, Protocol} -> Protocol; _ -> undefined end end. db(X) -> snmpa_agent:db(X). fa(usmUserTable) -> ?usmUserSecurityName. foi(usmUserTable) -> ?usmUserEngineID. noc(usmUserTable) -> 13. stc(usmUserTable) -> ?usmUserStorageType. next(Name, RowIndex, Cols) -> snmp_generic:handle_table_next(db(Name), RowIndex, Cols, fa(Name), foi(Name), noc(Name)). table_next(Name, RestOid) -> snmp_generic:table_next(db(Name), RestOid). get(Name, RowIndex, Cols) -> snmp_generic:handle_table_get(db(Name), RowIndex, Cols, foi(Name)). %%----------------------------------------------------------------- %% Key change functions. The KeyChange Texual-Convention is %% defined in the SNMP-USER-BASED-SM-MIB. %% Note that this implementation supports md5 and sha, which %% both have fixed length requirements on the length of the key; %% thus the implementation can be (and is) simplified. %%----------------------------------------------------------------- mk_key_change(Hash, OldKey, NewKey) -> KeyLen = length(NewKey), Alg = case Hash of ?usmHMACMD5AuthProtocol -> md5; ?usmHMACSHAAuthProtocol -> sha; md5 -> md5; sha -> sha end, Random = mk_random(KeyLen), mk_key_change(Alg, OldKey, NewKey, KeyLen, Random). %% This function is only exported for test purposes. There is a test %% case in the standard where Random is pre-defined. mk_key_change(Alg, OldKey, NewKey, KeyLen, Random) -> %% OldKey and Random is of length KeyLen... Digest = lists:sublist(binary_to_list(crypto:Alg(OldKey++Random)), KeyLen), %% ... and so is Digest Delta = snmp_misc:str_xor(Digest, NewKey), Random ++ Delta. %% Extracts a new Key from a KeyChange value, sent by a manager. extract_new_key(?usmNoAuthProtocol, OldKey, _KeyChange) -> OldKey; extract_new_key(Hash, OldKey, KeyChange) -> KeyLen = length(OldKey), Alg = case Hash of ?usmHMACMD5AuthProtocol -> md5; ?usmHMACSHAAuthProtocol -> sha; md5 -> md5; sha -> sha end, {Random, Delta} = split(KeyLen, KeyChange, []), Digest = lists:sublist(binary_to_list(crypto:Alg(OldKey++Random)), KeyLen), NewKey = snmp_misc:str_xor(Digest, Delta), NewKey. -define(i16(Int), (Int bsr 8) band 255, Int band 255). -define(i8(Int), Int band 255). mk_random(Len) when Len =< 20 -> %% Use of yield(): %% This will either schedule another process, or fail and invoke %% the error_handler (in old versions). In either case, it is %% safe to assume that now, reductions and garbage_collection have %% changed in a non-deterministically way. {_,_,A} = erlang:now(), catch erlang:yield(), {_,_,B} = erlang:now(), catch erlang:yield(), {_,_,C} = erlang:now(), {D,_} = erlang:statistics(reductions), {E,_} = erlang:statistics(runtime), {F,_} = erlang:statistics(wall_clock), {G,H,_} = erlang:statistics(garbage_collection), catch erlang:yield(), {_,_,C2} = erlang:now(), {D2,_} = erlang:statistics(reductions), {_,H2,_} = erlang:statistics(garbage_collection), %% X(N) means we can use N bits from variable X: %% A(16) B(16) C(16) D(16) E(8) F(16) G(8) H(16) Rnd20 = [?i16(A),?i16(B),?i16(C),?i16(D),?i8(E),?i16(F), ?i8(G),?i16(H),?i16(C2),?i16(D2),?i16(H2)], lists:sublist(Rnd20, Len). split(0, Rest, FirstRev) -> {lists:reverse(FirstRev), Rest}; split(N, [H | T], FirstRev) when N > 0 -> split(N-1, T, [H | FirstRev]). is_crypto_supported(Func) -> %% The 'catch' handles the case when 'crypto' is %% not present in the system (or not started). case catch lists:member(Func, crypto:info()) of true -> true; _ -> false end. inconsistentValue(V) -> throw({inconsistentValue, V}). inconsistentName(N) -> throw({inconsistentName, N}). wrongValue(V) -> throw({wrongValue, V}). noAccess(C) -> throw({noAccess, C}). set_sname() -> set_sname(get(sname)). set_sname(undefined) -> put(sname,conf); set_sname(_) -> %% Keep it, if already set. ok. error(Reason) -> throw({error, Reason}). %%----------------------------------------------------------------- info_msg(F, A) -> ?snmpa_info("USM: " ++ F, A). %% --- config_err(F, A) -> snmpa_error:config_err("[USER-BASED-SM-MIB]: " ++ F, A).