%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1999-2014. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(snmp_community_mib). %% Avoid warning for local function error/1 clashing with autoimported BIF. -compile({no_auto_import,[error/1]}). -export([configure/1, reconfigure/1, snmpCommunityTable/1, snmpCommunityTable/3, snmpTargetAddrExtTable/3, community2vacm/2, vacm2community/2, get_target_addr_ext_mms/2]). -export([add_community/5, add_community/6, delete_community/1]). -export([check_community/1]). -include("snmpa_internal.hrl"). -include("SNMP-COMMUNITY-MIB.hrl"). -include("SNMP-TARGET-MIB.hrl"). -include("SNMPv2-TM.hrl"). -include("SNMPv2-TC.hrl"). -include("snmp_types.hrl"). -define(VMODULE,"COMMUNITY-MIB"). -include("snmp_verbosity.hrl"). -ifndef(default_verbosity). -define(default_verbosity,silence). -endif. %%%----------------------------------------------------------------- %%% Implements the instrumentation functions and additional %%% functions for the SNMP-COMMUNITY-MIB. %%% This MIB contains objects that makes it possible to use VACM %%% with SNMPv1 and SNMPv2c. %%%----------------------------------------------------------------- %%----------------------------------------------------------------- %% 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 community mib 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(snmpCommunityTable) of {_, mnesia} -> ?vlog("community table in mnesia: cleanup",[]), gc_tabs(); TabDb -> case snmpa_local_db:table_exists(TabDb) of true -> ?vlog("community table exist: cleanup",[]), gc_tabs(); false -> ?vlog("community 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 community mib 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) %%----------------------------------------------------------------- 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 community config files",[]), Comms = read_community_config_files(Dir), ?vdebug("initiate tables",[]), init_tabs(Comms), ok. init_tabs(Comms) -> ?vdebug("create community table",[]), snmpa_local_db:table_delete(db(snmpCommunityTable)), snmpa_local_db:table_create(db(snmpCommunityTable)), ?vdebug("invalidate cache",[]), invalidate_cache(), ?vdebug("initiate community table",[]), init_comm_table(Comms). read_community_config_files(Dir) -> ?vdebug("read community config file",[]), FileName = "community.conf", Gen = fun(D, Reason) -> warning_msg("failed reading config file ~s" "~n Config Dir: ~s" "~n Reason: ~p", [FileName, D, Reason]), ok end, Order = fun snmp_conf:no_order/2, Filter = fun snmp_conf:no_filter/1, Check = fun(Entry, State) -> {check_community(Entry), State} end, [Comms] = snmp_conf:read_files(Dir, [{FileName, Gen, Order, Check, Filter}]), Comms. check_community({Index, CommunityName, SecName, CtxName, TransportTag}) -> EngineID = get_engine_id(), check_community({Index, CommunityName, SecName, EngineID, CtxName, TransportTag}); check_community({Index, CommunityName, SecName, EngineID, CtxName, TransportTag}) -> snmp_conf:check_string(Index,{gt,0}), snmp_conf:check_string(CommunityName), snmp_conf:check_string(SecName), snmp_conf:check_string(CtxName), snmp_conf:check_string(TransportTag), Comm = {Index, CommunityName, SecName, EngineID, CtxName, TransportTag, ?'StorageType_nonVolatile', ?'RowStatus_active'}, {ok, Comm}; check_community(X) -> error({invalid_community, X}). %% This is for the case when check_community is called from the %% snmp_config module (to generate community config file) and %% the agent is not started. The actual return value is not %% checked, as long as it is '{ok, _}'. get_engine_id() -> case (catch snmp_framework_mib:get_engine_id()) of {'EXIT', _} -> "agentEngine"; EngineID -> EngineID end. init_comm_table([Row | T]) -> ?vtrace("init_comm_table -> entry with" "~n Row: ~p", [Row]), Key = element(1, Row), snmpa_local_db:table_create_row(db(snmpCommunityTable), Key, Row), update_cache(Key), init_comm_table(T); init_comm_table([]) -> ?vtrace("init_comm_table -> entry when done", []), true. 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). %% FIXME: does not work with mnesia add_community(Idx, CommName, SecName, CtxName, TransportTag) -> Community = {Idx, CommName, SecName, CtxName, TransportTag}, do_add_community(Community). add_community(Idx, CommName, SecName, EngineId, CtxName, TransportTag) -> Community = {Idx, CommName, SecName, EngineId, CtxName, TransportTag}, do_add_community(Community). do_add_community(Community) -> try check_community(Community) of {ok, Row} -> Key = element(1, Row), case table_cre_row(snmpCommunityTable, Key, Row) of true -> update_cache(Key), {ok, Key}; false -> {error, create_failed} end catch {error, Reason} -> {error, Reason}; Class:Reason -> {error, {Class, Reason, erlang:get_stacktrace()}} end. %% FIXME: does not work with mnesia delete_community(Key) -> invalidate_cache(Key), case table_del_row(snmpCommunityTable, Key) of true -> ok; false -> {error, delete_failed} end. gc_tabs() -> DB = db(snmpCommunityTable), STC = stc(snmpCommunityTable), FOI = foi(snmpCommunityTable), IR = fun(RowIndex) -> invalidate_cache(RowIndex) end, UR = fun(RowIndex) -> update_cache(RowIndex) end, snmpa_mib_lib:gc_tab(DB, STC, FOI, IR, UR), ok. %%----------------------------------------------------------------- %% API functions %%----------------------------------------------------------------- %%----------------------------------------------------------------- %% We keep two caches for mapping; %% one that maps CommunityName to CommunityIndex, and %% one that maps %% {SecName, ContextEngineId, ContextName} to {CommunityName, Tag} %% Name -> Index instead of Name -> Vacm allows us to save a %% few bytes of memory, although it introduces an extra level of %% indirection. %%----------------------------------------------------------------- community2vacm(Community, Addr) -> Idxs = ets:lookup(snmp_community_cache, Community), ?vtrace("community2vacm ->~n" " Community: ~p~n" " Addr: ~p~n" " Idxs: ~p", [Community, Addr, Idxs]), loop_c2v_rows(lists:keysort(2, Idxs), Addr). loop_c2v_rows([{_, CommunityIndex} | T], Addr) -> ?vtrace("loop_c2v_rows -> entry with" "~n CommunityIndex: ~p", [CommunityIndex]), case get_row(CommunityIndex) of {_Community, VacmParams, Tag} -> ?vtrace("loop_c2v_rows ->~n" " VacmParams: ~p~n" " Tag: ~p", [VacmParams, Tag]), {TDomain, TAddr} = Addr, case snmp_target_mib:is_valid_tag(Tag, TDomain, TAddr) of true -> ?vdebug("loop_c2v_rows -> " "~p valid tag for community index ~p", [Tag, CommunityIndex]), VacmParams; false -> ?vtrace("loop_c2v_rows -> " "~p not valid tag community index ~p", [Tag, CommunityIndex]), loop_c2v_rows(T, Addr) end; undefined -> loop_c2v_rows(T, Addr) end; loop_c2v_rows([], _Addr) -> undefined. %%----------------------------------------------------------------- %% Func: vacm2community(Vacm, {TDomain, TAddr}) -> %% {ok, Community} | undefined %% Types: Vacm = {SecName, ContextEngineId, ContextName} %% Purpose: Follows the steps in RFC 2576 section %% 5.2.3 in order to find a community string to be used %% in a notification. %%----------------------------------------------------------------- vacm2community(Vacm, Addr) -> Names = ets:lookup(snmp_community_cache, Vacm), loop_v2c_rows(lists:keysort(2, Names), Addr). loop_v2c_rows([{_, {CommunityName, Tag}} | T], Addr) -> ?vtrace("loop_v2c_rows -> entry with" "~n CommunityName: ~p" "~n Tag: ~p", [CommunityName, Tag]), {TDomain, TAddr} = Addr, case snmp_target_mib:is_valid_tag(Tag, TDomain, TAddr) of true -> ?vdebug("loop_v2c_rows -> " "~p valid tag for community name ~p", [Tag, CommunityName]), {ok, CommunityName}; false -> loop_v2c_rows(T, Addr) end; loop_v2c_rows([], _Addr) -> undefined. get_row(RowIndex) -> case snmp_generic:table_get_row(db(snmpCommunityTable), RowIndex, foi(snmpCommunityTable)) of {_, CommunityName, SecName, ContextEngineId, ContextName, TransportTag, _StorageType, ?'RowStatus_active'} -> {CommunityName, {SecName, ContextEngineId, ContextName}, TransportTag}; _ -> undefined end. invalidate_cache(RowIndex) -> case get_row(RowIndex) of {CommunityName, VacmParams, TransportTag} -> ets:match_delete(snmp_community_cache, {CommunityName, RowIndex}), ets:match_delete(snmp_community_cache, {VacmParams, {CommunityName, TransportTag}}); undefined -> ok end. update_cache(RowIndex) -> case get_row(RowIndex) of {CommunityName, VacmParams, TransportTag} -> ets:insert(snmp_community_cache, {CommunityName, RowIndex}), % CommunityIndex ets:insert(snmp_community_cache, {VacmParams, {CommunityName, TransportTag}}); undefined -> ok end. invalidate_cache() -> ets:match_delete(snmp_community_cache, {'_', '_'}). get_target_addr_ext_mms(TDomain, TAddress) -> get_target_addr_ext_mms(TDomain, TAddress, []). get_target_addr_ext_mms(TDomain, TAddress, Key) -> case snmp_target_mib:table_next(snmpTargetAddrTable, Key) of endOfTable -> false; NextKey -> case snmp_target_mib:get( snmpTargetAddrTable, NextKey, [?snmpTargetAddrTDomain, ?snmpTargetAddrTAddress, 12]) of [{value, TDomain}, {value, TAddress}, {value, MMS}] -> {ok, MMS}; _ -> get_target_addr_ext_mms(TDomain, TAddress, NextKey) end end. %%----------------------------------------------------------------- %% Instrumentation Functions %%----------------------------------------------------------------- %% Op = print - Used for debugging purposes snmpCommunityTable(print) -> Table = snmpCommunityTable, DB = db(Table), FOI = foi(Table), PrintRow = fun(Prefix, Row) -> lists:flatten( io_lib:format("~sIndex: ~p" "~n~sName: ~p" "~n~sSecurityName: ~p" "~n~sContextEngineID: ~p" "~n~sContextName: ~p" "~n~sTransportTag: ~p" "~n~sStorageType: ~p (~w)" "~n~sStatus: ~p (~w)", [Prefix, element(?snmpCommunityIndex, Row), Prefix, element(?snmpCommunityName, Row), Prefix, element(?snmpCommunitySecurityName, Row), Prefix, element(?snmpCommunityContextEngineID, Row), Prefix, element(?snmpCommunityContextName, Row), Prefix, element(?snmpCommunityTransportTag, Row), Prefix, element(?snmpCommunityStorageType, Row), case element(?snmpCommunityStorageType, Row) of ?'snmpCommunityStorageType_readOnly' -> readOnly; ?'snmpCommunityStorageType_permanent' -> permanent; ?'snmpCommunityStorageType_nonVolatile' -> nonVolatile; ?'snmpCommunityStorageType_volatile' -> volatile; ?'snmpCommunityStorageType_other' -> other; _ -> undefined end, Prefix, element(?snmpCommunityStatus, Row), case element(?snmpCommunityStatus, Row) of ?'snmpCommunityStatus_destroy' -> destroy; ?'snmpCommunityStatus_createAndWait' -> createAndWait; ?'snmpCommunityStatus_createAndGo' -> createAndGo; ?'snmpCommunityStatus_notReady' -> notReady; ?'snmpCommunityStatus_notInService' -> notInService; ?'snmpCommunityStatus_active' -> active; _ -> undefined end])) end, snmpa_mib_lib:print_table(Table, DB, FOI, PrintRow); %% Op == new | delete snmpCommunityTable(Op) -> snmp_generic:table_func(Op, db(snmpCommunityTable)). %% Op == get | is_set_ok | set | get_next snmpCommunityTable(get, RowIndex, Cols) -> get(snmpCommunityTable, RowIndex, Cols); snmpCommunityTable(get_next, RowIndex, Cols) -> next(snmpCommunityTable, RowIndex, Cols); snmpCommunityTable(is_set_ok, RowIndex, Cols0) -> case (catch verify_snmpCommunityTable_is_set_ok(Cols0)) of {ok, Cols1} -> case (catch verify_snmpCommunityTable_cols(Cols1, [])) of {ok, Cols} -> Db = db(snmpCommunityTable), snmp_generic:table_func(is_set_ok, RowIndex, Cols, Db); Error -> Error end; Error -> Error end; snmpCommunityTable(set, RowIndex, Cols0) -> case (catch verify_snmpCommunityTable_cols(Cols0, [])) of {ok, Cols} -> invalidate_cache(RowIndex), Db = db(snmpCommunityTable), Res = snmp_generic:table_func(set, RowIndex, Cols, Db), snmpa_agent:invalidate_ca_cache(), update_cache(RowIndex), Res; Error -> Error end; snmpCommunityTable(Op, Arg1, Arg2) -> snmp_generic:table_func(Op, Arg1, Arg2, db(snmpCommunityTable)). verify_snmpCommunityTable_is_set_ok(Cols) -> LocalEngineID = snmp_framework_mib:get_engine_id(), case lists:keysearch(?snmpCommunityContextEngineID, 1, Cols) of {value, {_, LocalEngineID}} -> {ok, Cols}; {value, _} -> {inconsistentValue, ?snmpCommunityContextEngineID}; false -> {ok, kinsert(Cols, LocalEngineID)} end. verify_snmpCommunityTable_cols([], Cols) -> {ok, lists:reverse(Cols)}; verify_snmpCommunityTable_cols([{Col, Val0}|Cols], Acc) -> Val = verify_snmpCommunityTable_col(Col, Val0), verify_snmpCommunityTable_cols(Cols, [{Col, Val}|Acc]). verify_snmpCommunityTable_col(?snmpCommunityIndex, Index) -> case (catch snmp_conf:check_string(Index,{gt,0})) of ok -> Index; _ -> wrongValue(?snmpCommunityIndex) end; verify_snmpCommunityTable_col(?snmpCommunityName, Name) -> case (catch snmp_conf:check_string(Name)) of ok -> Name; _ -> wrongValue(?snmpCommunityName) end; verify_snmpCommunityTable_col(?snmpCommunitySecurityName, Name) -> case (catch snmp_conf:check_string(Name)) of ok -> Name; _ -> wrongValue(?snmpCommunitySecurityName) end; verify_snmpCommunityTable_col(?snmpCommunityContextName, Name) -> case (catch snmp_conf:check_string(Name)) of ok -> Name; _ -> wrongValue(?snmpCommunityContextName) end; verify_snmpCommunityTable_col(?snmpCommunityTransportTag, Tag) -> case (catch snmp_conf:check_string(Tag)) of ok -> Tag; _ -> wrongValue(?snmpCommunityTransportTag) end; verify_snmpCommunityTable_col(_, Val) -> Val. %% Op == get | is_set_ok | set | get_next snmpTargetAddrExtTable(get, RowIndex, Cols) -> NCols = conv1(Cols), get(snmpTargetAddrExtTable, RowIndex, NCols); snmpTargetAddrExtTable(get_next, RowIndex, Cols) -> NCols = conv1(Cols), conv2(next(snmpTargetAddrExtTable, RowIndex, NCols)); snmpTargetAddrExtTable(set, RowIndex, Cols0) -> case (catch verify_snmpTargetAddrExtTable_cols( Cols0, get_snmpTargetAddrTDomain(RowIndex, Cols0), [])) of {ok, Cols} -> NCols = conv3(Cols), snmp_generic:table_func(set, RowIndex, NCols, db(snmpTargetAddrExtTable)); Error -> Error end; snmpTargetAddrExtTable(is_set_ok, RowIndex, Cols0) -> case (catch verify_snmpTargetAddrExtTable_cols( Cols0, get_snmpTargetAddrTDomain(RowIndex, Cols0), [])) of {ok, Cols} -> NCols = conv3(Cols), snmp_generic:table_func(is_set_ok, RowIndex, NCols, db(snmpTargetAddrExtTable)); Error -> Error end. get_snmpTargetAddrTDomain(RowIndex, Col) -> case get( snmpTargetAddrTable, RowIndex, [?snmpTargetAddrRowStatus,?snmpTargetAddrTDomain]) of [{value,?snmpTargetAddrRowStatus_active},ValueTDomain] -> case ValueTDomain of {value,TDomain} -> TDomain; _ -> ?snmpUDPDomain end; _ -> wrongValue(Col) end. verify_snmpTargetAddrExtTable_cols([], _TDomain, Cols) -> {ok, lists:reverse(Cols)}; verify_snmpTargetAddrExtTable_cols([{Col, Val0}|Cols], TDomain, Acc) -> Val = verify_snmpTargetAddrExtTable_col(Col, TDomain, Val0), verify_snmpTargetAddrExtTable_cols(Cols, TDomain, [{Col, Val}|Acc]). verify_snmpTargetAddrExtTable_col(?snmpTargetAddrTMask, _TDomain, []) -> []; verify_snmpTargetAddrExtTable_col(?snmpTargetAddrTMask, TDomain, TMask) -> case (catch snmp_conf:check_taddress(TDomain, TMask)) of ok -> TMask; _ -> wrongValue(?snmpTargetAddrTMask) end; verify_snmpTargetAddrExtTable_col(?snmpTargetAddrMMS, _TDomain, MMS) -> case (catch snmp_conf:check_packet_size(MMS)) of ok -> MMS; _ -> wrongValue(?snmpTargetAddrMMS) end; verify_snmpTargetAddrExtTable_col(_, _TDomain, Val) -> Val. db(snmpTargetAddrExtTable) -> db(snmpTargetAddrTable); db(X) -> snmpa_agent:db(X). fa(snmpCommunityTable) -> ?snmpCommunityName; fa(snmpTargetAddrExtTable) -> 11. foi(snmpCommunityTable) -> ?snmpCommunityIndex; foi(snmpTargetAddrExtTable) -> 11. noc(snmpCommunityTable) -> 8; noc(snmpTargetAddrExtTable) -> 12. stc(snmpCommunityTable) -> ?snmpCommunityStorageType. next(Name, RowIndex, Cols) -> snmp_generic:handle_table_next(db(Name), RowIndex, Cols, fa(Name), foi(Name), noc(Name)). conv1([Col | T]) -> [Col + 10 | conv1(T)]; conv1([]) -> []. conv2([{[Col | Oid], Val} | T]) -> [{[Col - 10 | Oid], Val} | conv2(T)]; conv2([X | T]) -> [X | conv2(T)]; conv2(X) -> X. conv3([{Idx, Val}|T]) -> [{Idx+10, Val} | conv3(T)]; conv3([]) -> []. get(Name, RowIndex, Cols) -> snmp_generic:handle_table_get(db(Name), RowIndex, Cols, foi(Name)). kinsert([H | T], EngineID) when element(1, H) < ?snmpCommunityContextEngineID -> [H | kinsert(T, EngineID)]; kinsert(Cols, EngineID) -> [{?snmpCommunityContextEngineID, EngineID} | Cols]. wrongValue(V) -> throw({wrongValue, V}). %% ----- 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}). warning_msg(F, A) -> ?snmpa_warning("[COMMUNITY-MIB]: " ++ F, A). config_err(F, A) -> snmpa_error:config_err("[COMMUNITY-MIB]: " ++ F, A).