diff options
Diffstat (limited to 'lib/snmp/src/agent/snmpa_vacm.erl')
-rw-r--r-- | lib/snmp/src/agent/snmpa_vacm.erl | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/lib/snmp/src/agent/snmpa_vacm.erl b/lib/snmp/src/agent/snmpa_vacm.erl new file mode 100644 index 0000000000..2eacea4301 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_vacm.erl @@ -0,0 +1,399 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-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_vacm). + +-export([get_mib_view/5]). +-export([init/1, init/2, backup/1]). +-export([delete/1, get_row/1, get_next_row/1, insert/1, insert/2, + dump_table/0]). + +-include("SNMPv2-TC.hrl"). +-include("SNMP-VIEW-BASED-ACM-MIB.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). +-include("snmp_types.hrl"). +-include("snmpa_vacm.hrl"). + +-define(VMODULE,"VACM"). +-include("snmp_verbosity.hrl"). + + +%%%----------------------------------------------------------------- +%%% Access Control Module for VACM (see also snmpa_acm) +%%% This module implements: +%%% 1. access control functions for VACM +%%% 2. vacmAccessTable as an ordered ets table +%%% +%%% This version of VACM handles v1, v2c and v3. +%%%----------------------------------------------------------------- + +%%%----------------------------------------------------------------- +%%% 1. access control functions for VACM +%%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% Func: get_mib_view/5 -> {ok, ViewName} | +%% {discarded, Reason} +%% Types: ViewType = read | write | notify +%% SecModel = ?SEC_* (see snmp_types.hrl) +%% SecName = string() +%% SecLevel = ?'SnmpSecurityLevel_*' (see SNMP-FRAMEWORK-MIB.hrl) +%% ContextName = string() +%% Purpose: This function is used to map VACM parameters to a mib +%% view. +%%----------------------------------------------------------------- +get_mib_view(ViewType, SecModel, SecName, SecLevel, ContextName) -> + check_auth(catch auth(ViewType, SecModel, SecName, SecLevel, ContextName)). + + +%% Follows the procedure in rfc2275 +auth(ViewType, SecModel, SecName, SecLevel, ContextName) -> + % 3.2.1 - Check that the context is known to us + ?vdebug("check that the context (~p) is known to us",[ContextName]), + case snmp_view_based_acm_mib:vacmContextTable(get, ContextName, + [?vacmContextName]) of + [_Found] -> + ok; + _ -> + snmpa_mpd:inc(snmpUnknownContexts), + throw({discarded, noSuchContext}) + end, + % 3.2.2 - Check that the SecModel and SecName is valid + ?vdebug("check that SecModel (~p) and SecName (~p) is valid", + [SecModel,SecName]), + GroupName = + case snmp_view_based_acm_mib:get(vacmSecurityToGroupTable, + [SecModel, length(SecName) | SecName], + [?vacmGroupName, ?vacmSecurityToGroupStatus]) of + [{value, GN}, {value, ?'RowStatus_active'}] -> + GN; + [{value, _GN}, {value, RowStatus}] -> + ?vlog("valid SecModel and SecName but wrong row status:" + "~n RowStatus: ~p", [RowStatus]), + throw({discarded, noGroupName}); + _ -> + throw({discarded, noGroupName}) + end, + % 3.2.3-4 - Find an access entry and its view name + ?vdebug("find an access entry and its view name",[]), + ViewName = + case get_view_name(ViewType, GroupName, ContextName, + SecModel, SecLevel) of + {ok, VN} -> VN; + Error -> throw(Error) + end, + % 3.2.5a - Find the corresponding mib view + ?vdebug("find the corresponding mib view (for ~p)",[ViewName]), + get_mib_view(ViewName). + +check_auth({'EXIT', Error}) -> exit(Error); +check_auth({discarded, Reason}) -> {discarded, Reason}; +check_auth(Res) -> {ok, Res}. + +%%----------------------------------------------------------------- +%% Returns a list of {ViewSubtree, ViewMask, ViewType} +%% The view table is index by ViewIndex, ViewSubtree, +%% so a next on ViewIndex returns the first +%% key in the table >= ViewIndex. +%%----------------------------------------------------------------- +get_mib_view(ViewName) -> + ViewKey = [length(ViewName) | ViewName], + case snmp_view_based_acm_mib:table_next(vacmViewTreeFamilyTable, + ViewKey) of + endOfTable -> + {discarded, noSuchView}; + Indexes -> + case split_prefix(ViewKey, Indexes) of + {ok, Subtree} -> + loop_mib_view(ViewKey, Subtree, Indexes, []); + false -> + {discarded, noSuchView} + end + end. + +split_prefix([H|T], [H|T2]) -> split_prefix(T,T2); +split_prefix([], Rest) -> {ok, Rest}; +split_prefix(_, _) -> false. + + +%% ViewName is including length from now on +loop_mib_view(ViewName, Subtree, Indexes, MibView) -> + [{value, Mask}, {value, Type}, {value, Status}] = + snmp_view_based_acm_mib:vacmViewTreeFamilyTable( + get, Indexes, + [?vacmViewTreeFamilyMask, + ?vacmViewTreeFamilyType, + ?vacmViewTreeFamilyStatus]), + NextMibView = + case Status of + ?'RowStatus_active' -> + [_Length | Tree] = Subtree, + [{Tree, Mask, Type} | MibView]; + _ -> + MibView + end, + case snmp_view_based_acm_mib:table_next(vacmViewTreeFamilyTable, + Indexes) of + endOfTable -> NextMibView; + NextIndexes -> + case split_prefix(ViewName, NextIndexes) of + {ok, NextSubTree} -> + loop_mib_view(ViewName, NextSubTree, NextIndexes, + NextMibView); + false -> + NextMibView + end + end. + +%%%----------------------------------------------------------------- +%%% 1b. The ordered ets table that implements vacmAccessTable +%%%----------------------------------------------------------------- + +init(Dir) -> + init(Dir, terminate). + +init(Dir, InitError) -> + FName = filename:join(Dir, "snmpa_vacm.db"), + case file:read_file_info(FName) of + {ok, _} -> + %% File exists - we must check this, since ets doesn't tell + %% us the reason in case of error... + case ets:file2tab(FName) of + {ok, _Tab} -> + gc_tab([]); + {error, Reason} -> + user_err("Corrupt VACM database ~p", [FName]), + case InitError of + terminate -> + throw({error, {file2tab, FName, Reason}}); + _ -> + %% Rename old file (for later analyzes) + Saved = FName ++ ".saved", + file:rename(FName, Saved), + ets:new(snmpa_vacm, + [public, ordered_set, named_table]) + end + end; + {error, _} -> + ets:new(snmpa_vacm, [public, ordered_set, named_table]) + end, + ets:insert(snmp_agent_table, {snmpa_vacm_file, FName}), + {ok, FName}. + + +backup(BackupDir) -> + BackupFile = filename:join(BackupDir, "snmpa_vacm.db"), + ets:tab2file(snmpa_vacm, BackupFile). + + +%% Ret: {ok, ViewName} | {error, Reason} +get_view_name(ViewType, GroupName, ContextName, SecModel, SecLevel) -> + GroupKey = [length(GroupName) | GroupName], + case get_access_row(GroupKey, ContextName, SecModel, SecLevel) of + undefined -> + {discarded, noAccessEntry}; + Row -> + ?vtrace("get_view_name -> Row: ~n ~p", [Row]), + ViewName = + case ViewType of + read -> element(?vacmAReadViewName, Row); + write -> element(?vacmAWriteViewName, Row); + notify -> element(?vacmANotifyViewName, Row) + end, + case ViewName of + "" -> + ?vtrace("get_view_name -> not found when" + "~n ViewType: ~p" + "~n GroupName: ~p" + "~n ContextName: ~p" + "~n SecModel: ~p" + "~n SecLevel: ~p", [ViewType, GroupName, + ContextName, SecModel, + SecLevel]), + {discarded, noSuchView}; + _ -> {ok, ViewName} + end + end. + + +get_row(Key) -> + case ets:lookup(snmpa_vacm, Key) of + [{_Key, Row}] -> {ok, Row}; + _ -> false + end. + +get_next_row(Key) -> + case ets:next(snmpa_vacm, Key) of + '$end_of_table' -> false; + NextKey -> + case ets:lookup(snmpa_vacm, NextKey) of + [Entry] -> Entry; + _ -> false + end + end. + +insert(Entries) -> insert(Entries, true). + +insert(Entries, Dump) -> + lists:foreach(fun(Entry) -> ets:insert(snmpa_vacm, Entry) end, Entries), + dump_table(Dump). + +delete(Key) -> + ets:delete(snmpa_vacm, Key), + dump_table(). + +dump_table(true) -> + dump_table(); +dump_table(_) -> + ok. + +dump_table() -> + [{_, FName}] = ets:lookup(snmp_agent_table, snmpa_vacm_file), + TmpName = FName ++ ".tmp", + case ets:tab2file(snmpa_vacm, TmpName) of + ok -> + case file:rename(TmpName, FName) of + ok -> + ok; + Else -> % What is this? Undocumented return code... + user_err("Warning: could not move VACM db ~p" + " (~p)", [FName, Else]) + end; + {error, Reason} -> + user_err("Warning: could not save vacm db ~p (~p)", + [FName, Reason]) + end. + + +%%----------------------------------------------------------------- +%% Alg. +%% Procedure is defined in the descr. of vacmAccessTable. +%% +%% for (each entry with matching group name, context, secmodel and seclevel) +%% { +%% rate the entry; if it's score is > prev max score, keep it +%% } +%% +%% Rating: The procedure says to keep entries in order +%% 1. matching secmodel ('any'(0) or same(1) is ok) +%% 2. matching contextprefix (exact(1) or prefix(0) is ok) +%% 3. longest prefix (0..32) +%% 4. highest secLevel (noAuthNoPriv(0) < authNoPriv(1) < authPriv(2)) +%% We give each entry a single rating number according to this order. +%% The number is chosen so that a higher number gives a better +%% entry, according to the order above. +%% The number is: +%% secLevel + (3 * prefix_len) + (99 * match_prefix) + (198 * match_secmodel) +%% +%% Optimisation: Maybe the most common case is that there +%% is just one matching entry, and it matches exact. We could do +%% an exact lookup for this entry; if we find one, use it, otherwise +%% perform this alg. +%%----------------------------------------------------------------- +get_access_row(GroupKey, ContextName, SecModel, SecLevel) -> + %% First, try the optimisation... + ExactKey = + GroupKey ++ [length(ContextName) | ContextName] ++ [SecModel,SecLevel], + case ets:lookup(snmpa_vacm, ExactKey) of + [{_Key, Row}] -> + Row; + _ -> % Otherwise, perform the alg + get_access_row(GroupKey, GroupKey, ContextName, + SecModel, SecLevel, 0, undefined) + end. + +get_access_row(Key, GroupKey, ContextName, SecModel, SecLevel, Score, Found) -> + case get_next_row(Key) of + {NextKey, Row} + when element(?vacmAStatus, Row) == ?'RowStatus_active'-> + case catch score(NextKey, GroupKey, ContextName, + element(?vacmAContextMatch, Row), + SecModel, SecLevel) of + {ok, NScore} when NScore > Score -> + get_access_row(NextKey, GroupKey, ContextName, + SecModel, SecLevel, NScore, Row); + {ok, _} -> % e.g. a throwed {ok, 0} + get_access_row(NextKey, GroupKey, ContextName, + SecModel, SecLevel, Score, Found); + false -> + Found + end; + {NextKey, _InvalidRow} -> + get_access_row(NextKey, GroupKey, ContextName, SecModel, + SecLevel, Score, Found); + false -> + Found + end. + + + +score(Key, GroupKey, ContextName, Match, SecModel, SecLevel) -> + [CtxLen | Rest1] = chop_off_group(GroupKey, Key), + {NPrefix, [VSecModel, VSecLevel]} = + chop_off_context(ContextName, Rest1, 0, CtxLen, Match), + %% Make sure the vacmSecModel is valid (any or matching) + NSecModel = case VSecModel of + SecModel -> 198; + ?SEC_ANY -> 0; + _ -> throw({ok, 0}) + end, + %% Make sure the vacmSecLevel is less than the requested + NSecLevel = if + VSecLevel =< SecLevel -> VSecLevel - 1; + true -> throw({ok, 0}) + end, + {ok, NSecLevel + 3*CtxLen + NPrefix + NSecModel}. + + + +chop_off_group([H|T], [H|T2]) -> chop_off_group(T, T2); +chop_off_group([], Rest) -> Rest; +chop_off_group(_, _) -> throw(false). + +chop_off_context([H|T], [H|T2], Cnt, Len, Match) when Cnt < Len -> + chop_off_context(T, T2, Cnt+1, Len, Match); +chop_off_context([], Rest, _Len, _Len, _Match) -> + %% We have exact match; don't care about Match + {99, Rest}; +chop_off_context(_, Rest, Len, Len, ?vacmAccessContextMatch_prefix) -> + %% We have a prefix match + {0, Rest}; +chop_off_context(_Ctx, _Rest, _Cnt, _Len, _Match) -> + %% Otherwise, it didn't match! + throw({ok, 0}). + + +gc_tab(Oid) -> + case get_next_row(Oid) of + {NextOid, Row} -> + case element(?vacmAStorageType, Row) of + ?'StorageType_volatile' -> + ets:delete(snmpa_vacm, NextOid), + gc_tab(NextOid); + _ -> + gc_tab(NextOid) + end; + false -> + ok + end. + + +user_err(F, A) -> + snmpa_error:user_err(F, A). + +% config_err(F, A) -> +% snmpa_error:config_err(F, A). |