diff options
Diffstat (limited to 'lib/snmp/src/agent/snmpa_acm.erl')
-rw-r--r-- | lib/snmp/src/agent/snmpa_acm.erl | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/lib/snmp/src/agent/snmpa_acm.erl b/lib/snmp/src/agent/snmpa_acm.erl new file mode 100644 index 0000000000..6ad4f0b442 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_acm.erl @@ -0,0 +1,364 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. 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(snmpa_acm). + +-behaviour(snmpa_authentication_service). + +-export([init_check_access/2, get_root_mib_view/0, + error2status/1, + validate_mib_view/2, validate_all_mib_view/2, + is_definitely_not_in_mib_view/2, + invalidate_ca_cache/0]). + +-include("snmp_types.hrl"). +-include("STANDARD-MIB.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). +-include("SNMPv2-TM.hrl"). + +-define(VMODULE,"ACM"). +-include("snmp_verbosity.hrl"). + + +%%%----------------------------------------------------------------- +%%% This module implements the Access Control Model part of the +%%% multi-lingual SNMP agent. It contains generic function not +%%% tied to a specific model, but in this version it uses VACM. +%%% +%%% Note that we don't follow the isAccessAllowed Abstract Service +%%% Interface defined in rfc2271. We implement an optimization +%%% of that ASI. Since the mib view is the same for all variable +%%% bindings in a PDU, there is no need to recalculate the mib +%%% view for each variable. Therefore, one function +%%% (init_check_access/2) is used to find the mib view, and then +%%% each variable is checked against this mib view. +%%% +%%% Access checking is done in several steps. First, the version- +%%% specific MPD (see snmpa_mpd) creates data used by VACM. This +%%% means that the format of this data is known by both the MPD and +%%% the ACM. When the master agent wants to check the access to a +%%% Pdu, it first calls init_check_access/2, which returns a MibView +%%% that can be used to check access of individual variables. +%%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Func: init_check_access(Pdu, ACMData) -> +%% {ok, MibView, ContextName} | +%% {error, Reason} | +%% {discarded, Variable, Reason} +%% Types: Pdu = #pdu +%% ACMData = acm_data() = {community, Community, Address} | +%% {v3, MsgID, SecModel, SecName, SecLevel, +%% ContextEngineID, ContextName, SecData} +%% Community = string() +%% Address = ip() ++ udp() (list) +%% MsgID = integer() <not used> +%% SecModel = ?SEC_* (see snmp_types.hrl) +%% SecName = string() +%% SecLevel = ?'SnmpSecurityLevel_*' (see SNMP-FRAMEWORK-MIB.hrl) +%% ContextEngineID = string() <not used> +%% ContextName = string() +%% SecData = <not used> +%% Variable = snmpInBadCommunityNames | +%% snmpInBadCommunityUses | +%% snmpInASNParseErrs +%% Reason = snmp_message_decoding | +%% {bad_community_name, Address, Community}} | +%% {invalid_access, Access, Op} +%% +%% Purpose: Called once for each Pdu. Returns a MibView +%% which is later used for each variable in the pdu. +%% The authenticationFailure trap is sent (maybe) when the auth. +%% procedure evaluates to unauthentic, +%% +%% NOTE: This function is executed in the Master agents's context +%%----------------------------------------------------------------- +init_check_access(Pdu, ACMData) -> + case init_ca(Pdu, ACMData) of + {ok, MibView, ContextName} -> + {ok, MibView, ContextName}; + {discarded, Reason} -> + {error, Reason}; + {authentication_failure, Variable, Reason} -> + handle_authentication_failure(), + {discarded, Variable, Reason} + end. + +error2status(noSuchView) -> authorizationError; +error2status(noAccessEntry) -> authorizationError; +error2status(noGroupName) -> authorizationError; +error2status(_) -> genErr. + +%%----------------------------------------------------------------- +%% Func: init_ca(Pdu, ACMData) -> +%% {ok, MibView} | +%% {discarded, Reason} | +%% {authentication_failure, Variable, Reason} +%% +%% error: an error response will be sent +%% discarded: no error response is sent +%% authentication_failure: no error response is sent, a trap is generated +%%----------------------------------------------------------------- +init_ca(Pdu, {community, SecModel, Community, TAddr}) -> + %% This is a v1 or v2c request. Use SNMP-COMMUNITY-MIB to + %% map the community to vacm parameters. + ?vtrace("check access for ~n" + " Pdu: ~p~n" + " Security model: ~p~n" + " Community: ~s",[Pdu,SecModel,Community]), + ViewType = case Pdu#pdu.type of + 'set-request' -> write; + _ -> read + end, + ?vtrace("View type: ~p", [ViewType]), + CaCacheKey = {Community, SecModel, TAddr, ViewType}, + case check_ca_cache(CaCacheKey) of + false -> + case snmp_community_mib:community2vacm(Community, + {?snmpUDPDomain,TAddr}) of + {SecName, _ContextEngineId, ContextName} -> + %% Maybe we should check that the contextEngineID + %% matches the local engineID? + %% It better, since we don't impl. proxy. + ?vtrace("get mib view" + "~n Security name: ~p" + "~n Context name: ~p",[SecName,ContextName]), + case snmpa_vacm:get_mib_view(ViewType, SecModel, SecName, + ?'SnmpSecurityLevel_noAuthNoPriv', + ContextName) of + {ok, MibView} -> + Res = {ok, MibView, ContextName}, + upd_ca_cache({CaCacheKey, Res}), + put(sec_model, SecModel), + put(sec_name, SecName), + Res; + {discarded, Reason} -> + snmpa_mpd:inc(snmpInBadCommunityUses), + {discarded, Reason} + end; + undefined -> + {authentication_failure, snmpInBadCommunityNames, + {bad_community_name, TAddr, Community}} + end; + Res -> + Res + end; + +init_ca(Pdu, {v3, _MsgID, SecModel, SecName, SecLevel, + _ContextEngineID, ContextName, _SecData}) -> + ?vtrace("check v3 access for ~n" + " Pdu: ~p~n" + " Security model: ~p~n" + " Security name: ~p~n" + " Security level: ~p",[Pdu,SecModel,SecName,SecLevel]), + ViewType = case Pdu#pdu.type of + 'set-request' -> write; + _ -> read + end, + ?vtrace("View type: ~p",[ViewType]), + %% Convert the msgflag value to a ?'SnmpSecurityLevel*' + SL = case SecLevel of + 0 -> ?'SnmpSecurityLevel_noAuthNoPriv'; + 1 -> ?'SnmpSecurityLevel_authNoPriv'; + 3 -> ?'SnmpSecurityLevel_authPriv' + end, + put(sec_model, SecModel), + put(sec_name, SecName), + CaCacheKey = {ViewType, SecModel, SecName, SL, ContextName}, + case check_ca_cache(CaCacheKey) of + false -> + case snmpa_vacm:get_mib_view(ViewType, SecModel, SecName, + SL, ContextName) of + {ok, MibView} -> + Res = {ok, MibView, ContextName}, + upd_ca_cache({CaCacheKey, Res}), + Res; + Else -> + Else + end; + Res -> + Res + end. + +check_ca_cache(Key) -> + case get(ca_cache) of + undefined -> + put(ca_cache, []), + false; + L -> + check_ca_cache(L, Key) + end. + +check_ca_cache([{Key, Val} | _], Key) -> Val; +check_ca_cache([_ | T], Key) -> check_ca_cache(T, Key); +check_ca_cache([], _) -> false. + +upd_ca_cache(KeyVal) -> + case get(ca_cache) of + [A,B,C,_] -> % cache is full + put(ca_cache, [KeyVal,A,B,C]); + L -> + put(ca_cache, [KeyVal|L]) + end. + +invalidate_ca_cache() -> + erase(ca_cache). + +%%----------------------------------------------------------------- +%% Func: check(Res) -> {ok, MibView} | {discarded, Variable, Reason} +%% Args: Res = {ok, AccessFunc} | +%% {authentication_failure, Variable, Reason} +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% NOTE: The do_get MUST be executed in the Master agents's +%% context. Therefor, force master-agent to do a GET to +%% retrieve the value for snmpEnableAuthenTraps. +%% A user may have another impl. than default for this +%% variable. +%%----------------------------------------------------------------- +handle_authentication_failure() -> + case snmpa_agent:do_get(get_root_mib_view(), + [#varbind{oid = ?snmpEnableAuthenTraps_instance}], + true, true) of + {noError, _, [#varbind{value = ?snmpEnableAuthenTraps_enabled}]} -> + ?vtrace("handle_authentication_failure -> enabled", []), + snmpa:send_notification(snmp_master_agent, + authenticationFailure, + no_receiver); + _ -> + ok + end. + +%%%----------------------------------------------------------------- +%%% MIB View handling +%%%----------------------------------------------------------------- + +get_root_mib_view() -> + [{[1], [], ?view_included}]. + +%%----------------------------------------------------------------- +%% Returns true if Oid is in the MibView, false +%% otherwise. +%% Alg: (defined in SNMP-VIEW-BASED-ACM-MIB) +%% For each family (= {SubTree, Mask, Type}), check if Oid +%% belongs to that family. For each family that Oid belong to, +%% get the longest. If two or more are longest, get the +%% lexicografically greatest. Check the type of this family. If +%% included, then Oid belongs to the MibView, otherwise it +%% does not. +%% Optimisation: Do only one loop, and kepp the largest sofar. +%% When we find a family that Oid belongs to, check if it is +%% larger than the largest. +%%----------------------------------------------------------------- +validate_mib_view(Oid, MibView) -> + case get_largest_family(MibView, Oid, undefined) of + {_, _, ?view_included} -> true; + _ -> false + end. + +get_largest_family([{SubTree, Mask, Type} | T], Oid, Res) -> + case check_mask(Oid, SubTree, Mask) of + true -> get_largest_family(T, Oid, add_res(length(SubTree), SubTree, + Type, Res)); + false -> get_largest_family(T, Oid, Res) + end; +get_largest_family([], _Oid, Res) -> Res. + +%%----------------------------------------------------------------- +%% We keep only the largest (first longest SubTree, and then +%% lexicografically greatest) SubTree. +%%----------------------------------------------------------------- +add_res(Len, SubTree, Type, undefined) -> + {Len, SubTree, Type}; +add_res(Len, SubTree, Type, {MaxLen, _MaxS, _MaxT}) when Len > MaxLen -> + {Len, SubTree, Type}; +add_res(Len, SubTree, Type, {MaxLen, MaxS, MaxT}) when Len == MaxLen -> + if + SubTree > MaxS -> {Len, SubTree, Type}; + true -> {MaxLen, MaxS, MaxT} + end; +add_res(_Len, _SubTree, _Type, MaxRes) -> MaxRes. + + +%% 1 in mask is exact match, 0 is wildcard. +%% If mask is shorter than SubTree, its regarded +%% as being all ones. +check_mask(_Oid, [], _Mask) -> true; +check_mask([X | Xs], [X | Ys], [1 | Ms]) -> + check_mask(Xs, Ys, Ms); +check_mask([X | Xs], [X | Ys], []) -> + check_mask(Xs, Ys, []); +check_mask([_X | Xs], [_Y | Ys], [0 | Ms]) -> + check_mask(Xs, Ys, Ms); +check_mask(_, _, _) -> false. + +%%----------------------------------------------------------------- +%% Validates all oids in the Varbinds list towards the MibView. +%%----------------------------------------------------------------- +validate_all_mib_view([#varbind{oid = Oid, org_index = Index} | Varbinds], + MibView) -> + ?vtrace("validate_all_mib_view -> entry with" + "~n Oid: ~p" + "~n Index: ~p", [Oid, Index]), + case validate_mib_view(Oid, MibView) of + true -> validate_all_mib_view(Varbinds, MibView); + false -> {false, Index} + end; +validate_all_mib_view([], _MibView) -> + ?vtrace("validate_all_mib_view -> done", []), + true. + +%%----------------------------------------------------------------- +%% This function is used to optimize the next operation in +%% snmpa_mib_data. If we get to a node in the tree where we can +%% determine that we are guaranteed to be outside the mibview, +%% we don't have to continue the search in the that tree (Actually +%% we will, because we only check this at leafs. But we won't +%% go into tables or subagents, and that's the important +%% optimization.) For now, this function isn't that sophisticated; +%% it just checks that there is really no family in the mibview +%% that the Oid (or any other oids with Oid as prefix) may be +%% included in. Perhaps this function easily could be more +%% intelligent. +%%----------------------------------------------------------------- +is_definitely_not_in_mib_view(Oid, [{SubTree, Mask,?view_included}|T]) -> + case check_maybe_mask(Oid, SubTree, Mask) of + true -> false; + false -> is_definitely_not_in_mib_view(Oid, T) + end; +is_definitely_not_in_mib_view(Oid, [{_SubTree, _Mask,?view_excluded}|T]) -> + is_definitely_not_in_mib_view(Oid, T); +is_definitely_not_in_mib_view(_Oid, []) -> + true. + +%%----------------------------------------------------------------- +%% As check_mask, BUT if Oid < SubTree and sofar good, we +%% return true. As Oid get larger we may decide. +%%----------------------------------------------------------------- +check_maybe_mask(_Oid, [], _Mask) -> true; +check_maybe_mask([X | Xs], [X | Ys], [1 | Ms]) -> + check_maybe_mask(Xs, Ys, Ms); +check_maybe_mask([X | Xs], [X | Ys], []) -> + check_maybe_mask(Xs, Ys, []); +check_maybe_mask([_X | Xs], [_Y | Ys], [0 | Ms]) -> + check_maybe_mask(Xs, Ys, Ms); +check_maybe_mask([_X | _Xs], [_Y | _Ys], _) -> + false; +check_maybe_mask(_, _, _) -> + true. |