%%
%% %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).