%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1999-2014. 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_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).