diff options
Diffstat (limited to 'lib/snmp/src/agent')
53 files changed, 26015 insertions, 0 deletions
diff --git a/lib/snmp/src/agent/Makefile b/lib/snmp/src/agent/Makefile new file mode 100644 index 0000000000..a67fe4d17c --- /dev/null +++ b/lib/snmp/src/agent/Makefile @@ -0,0 +1,142 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 1996-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% + +include $(ERL_TOP)/make/target.mk + +EBIN = ../../ebin + +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk + +VSN = $(SNMP_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/snmp-$(VSN) + + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +include modules.mk + +ERL_FILES = $(MODULES:%=%.erl) + +HRL_FILES = $(INTERNAL_HRL_FILES:%=%.hrl) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + + +# ---------------------------------------------------- +# SNMP FLAGS +# ---------------------------------------------------- +ifeq ($(SNMP_DEFAULT_VERBOSITY),) + SNMP_FLAGS = -Ddefault_verbosity=silence +else + SNMP_FLAGS = -Ddefault_verbosity=$(SNMP_DEFAULT_VERBOSITY) +endif + +# ifeq ($(SNMP_DEBUG),) +# SNMP_DEBUG = d +# endif + +ifeq ($(SNMP_DEBUG),d) + SNMP_FLAGS += -Dsnmp_debug +endif + +ifeq ($(SNMP_QC),true) + SNMP_FLAGS += -Dsnmp_qc +endif + +ifeq ($(SNMP_EXT_VERBOSITY),true) + SNMP_FLAGS += -Dsnmp_extended_verbosity +endif + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += -pa $(ERL_TOP)/lib/snmp/ebin + +ifeq ($(WARN_UNUSED_VARS),true) +ERL_COMPILE_FLAGS += +warn_unused_vars +endif + +ERL_COMPILE_FLAGS += -I../../include \ + -I../misc \ + -Dversion=\"$(VSN)$(PRE_VSN)\" \ + +'{parse_transform,sys_pre_attributes}' \ + +'{attribute,insert,app_vsn,$(APP_VSN)}' \ + -I$(ERL_TOP)/lib/stdlib \ + $(SNMP_FLAGS) + + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug: + @$(MAKE) TYPE=debug opt + +opt: $(TARGET_FILES) + + +clean: + rm -f $(TARGET_FILES) + rm -f core *~ + +docs: + +info: + @echo "SNMP_FLAGS: $(SNMP_FLAGS)" + @echo "ERL_COMPILE_FLAGS: $(ERL_COMPILE_FLAGS)" + @echo "" + @echo "TARGET_FILES: $(TARGET_FILES)" + @echo "" + + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/agent + $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)/src/agent + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) \ + $(RELSYSDIR)/ebin +# $(INSTALL_DIR) $(RELSYSDIR)/include +# $(INSTALL_DATA) $(EXT_HRL_FILES) $(RELSYSDIR)/include + +release_docs_spec: + +include depend.mk diff --git a/lib/snmp/src/agent/depend.mk b/lib/snmp/src/agent/depend.mk new file mode 100644 index 0000000000..bc39e1fa35 --- /dev/null +++ b/lib/snmp/src/agent/depend.mk @@ -0,0 +1,249 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2004-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% + +$(EBIN)/snmpa_authentication_service.$(EMULATOR): \ + snmpa_authentication_service.erl + +$(EBIN)/snmpa_error_report.$(EMULATOR): \ + snmpa_error_report.erl + +$(EBIN)/snmpa_network_interface.$(EMULATOR): \ + snmpa_network_interface.erl + +$(EBIN)/snmpa_network_interface_filter.$(EMULATOR): \ + snmpa_network_interface_filter.erl + +$(EBIN)/snmpa_notification_filter.$(EMULATOR): \ + snmpa_notification_filter.erl + +$(EBIN)/snmpa_notification_delivery_info_receiver.$(EMULATOR): \ + snmpa_notification_delivery_info_receiver.erl + +$(EBIN)/snmpa_set_mechanism.$(EMULATOR): \ + snmpa_set_mechanism.erl + +$(EBIN)/snmpa.$(EMULATOR): \ + snmpa.erl + +$(EBIN)/snmpa_acm.$(EMULATOR): \ + snmpa_authentication_service.erl \ + snmpa_acm.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/STANDARD-MIB.hrl \ + ../../include/SNMP-FRAMEWORK-MIB.hrl \ + ../../include/SNMPv2-TM.hrl + +$(EBIN)/snmpa_agent.$(EMULATOR): \ + snmpa_agent.erl \ + ../misc/snmp_debug.hrl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmpa_agent_sup.$(EMULATOR): \ + snmpa_agent_sup.erl \ + ../misc/snmp_debug.hrl + +$(EBIN)/snmpa_app.$(EMULATOR): \ + snmpa_app.erl \ + ../misc/snmp_debug.hrl + +$(EBIN)/snmpa_error.$(EMULATOR): \ + snmpa_error_report.erl \ + snmpa_error.erl + +$(EBIN)/snmpa_error_io.$(EMULATOR): \ + snmpa_error_report.erl \ + snmpa_error_io.erl + +$(EBIN)/snmpa_error_logger.$(EMULATOR): \ + snmpa_error_report.erl \ + snmpa_error_logger.erl + +$(EBIN)/snmpa_general_db.$(EMULATOR): \ + snmpa_general_db.erl \ + ../misc/snmp_verbosity.hrl + +$(EBIN)/snmpa_local_db.$(EMULATOR): \ + snmpa_local_db.erl \ + ../misc/snmp_debug.hrl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/STANDARD-MIB.hrl + +$(EBIN)/snmpa_mib.$(EMULATOR): \ + snmpa_mib.erl \ + ../misc/snmp_debug.hrl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmpa_mib_data.$(EMULATOR): \ + snmpa_mib_data.erl \ + ../misc/snmp_debug.hrl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmpa_mib_lib.$(EMULATOR): \ + snmpa_mib_lib.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmpa_misc_sup.$(EMULATOR): \ + snmpa_misc_sup.erl \ + ../misc/snmp_debug.hrl + +$(EBIN)/snmpa_mpd.$(EMULATOR): \ + snmpa_mpd.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/SNMP-MPD-MIB.hrl \ + ../../include/SNMPv2-TM.hrl + +$(EBIN)/snmpa_net_if.$(EMULATOR): \ + snmpa_net_if.erl \ + ../misc/snmp_debug.hrl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmpa_net_if_filter.$(EMULATOR): \ + snmpa_net_if_filter.erl + +$(EBIN)/snmpa_set.$(EMULATOR): \ + snmpa_set_mechanism.erl \ + snmpa_set.erl \ + ../misc/snmp_verbosity.hrl + +$(EBIN)/snmpa_set_lib.$(EMULATOR): \ + snmpa_set_lib.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmpa_supervisor.$(EMULATOR): \ + snmpa_supervisor.erl \ + ../misc/snmp_debug.hrl \ + ../misc/snmp_verbosity.hrl + +$(EBIN)/snmpa_svbl.$(EMULATOR): \ + snmpa_svbl.erl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmpa_symbolic_store.$(EMULATOR): \ + snmpa_symbolic_store.erl \ + ../misc/snmp_debug.hrl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmpa_target_cache.$(EMULATOR): \ + snmpa_target_cache.erl \ + ../misc/snmp_verbosity.hrl \ + ../misc/snmp_debug.hrl \ + snmpa_internal.hrl \ + ../app/snmp_internal.hrl + +$(EBIN)/snmpa_trap.$(EMULATOR): \ + snmpa_trap.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/SNMPv2-MIB.hrl \ + ../../include/SNMP-FRAMEWORK-MIB.hrl + +$(EBIN)/snmpa_usm.$(EMULATOR): \ + snmpa_usm.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/SNMP-USER-BASED-SM-MIB.hrl \ + ../../include/SNMPv2-TC.hrl + +$(EBIN)/snmpa_vacm.$(EMULATOR): \ + snmpa_vacm.erl \ + snmpa_vacm.hrl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/SNMP-VIEW-BASED-ACM-MIB.hrl \ + ../../include/SNMPv2-TC.hrl \ + ../../include/SNMP-FRAMEWORK-MIB.hrl + +$(EBIN)/snmp_community_mib.$(EMULATOR): \ + snmp_community_mib.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/SNMP-COMMUNITY-MIB.hrl \ + ../../include/SNMP-TARGET-MIB.hrl \ + ../../include/SNMPv2-TC.hrl + +$(EBIN)/snmp_framework_mib.$(EMULATOR): \ + snmp_framework_mib.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/STANDARD-MIB.hrl + +$(EBIN)/snmp_generic.$(EMULATOR): \ + snmp_generic.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/STANDARD-MIB.hrl + +$(EBIN)/snmp_generic_mnesia.$(EMULATOR): \ + snmp_generic_mnesia.erl \ + ../../include/snmp_types.hrl \ + ../../include/STANDARD-MIB.hrl + +$(EBIN)/snmp_index.$(EMULATOR): \ + snmp_index.erl + +$(EBIN)/snmp_notification_mib.$(EMULATOR): \ + snmp_notification_mib.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_tables.hrl \ + ../../include/SNMP-NOTIFICATION-MIB.hrl \ + ../../include/SNMPv2-TC.hrl + +$(EBIN)/snmp_shadow_table.$(EMULATOR): \ + snmp_shadow_table.erl + +$(EBIN)/snmp_standard_mib.$(EMULATOR): \ + snmp_standard_mib.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/STANDARD-MIB.hrl + +$(EBIN)/snmp_target_mib.$(EMULATOR): \ + snmp_target_mib.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/snmp_tables.hrl \ + ../../include/SNMP-TARGET-MIB.hrl \ + ../../include/SNMPv2-TC.hrl \ + ../../include/SNMPv2-TM.hrl + +$(EBIN)/snmp_user_based_sm_mib.$(EMULATOR): \ + snmp_user_based_sm_mib.erl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/SNMP-USER-BASED-SM-MIB.hrl \ + ../../include/SNMPv2-TC.hrl + +$(EBIN)/snmp_view_based_acm_mib.$(EMULATOR): \ + snmp_view_based_acm_mib.erl \ + snmpa_vacm.hrl \ + ../misc/snmp_verbosity.hrl \ + ../../include/snmp_types.hrl \ + ../../include/SNMP-VIEW-BASED-ACM-MIB.hrl \ + ../../include/SNMPv2-TC.hrl + + diff --git a/lib/snmp/src/agent/modules.mk b/lib/snmp/src/agent/modules.mk new file mode 100644 index 0000000000..33ab41b434 --- /dev/null +++ b/lib/snmp/src/agent/modules.mk @@ -0,0 +1,78 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2004-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% + +BEHAVIOUR_MODULES = \ + snmpa_authentication_service \ + snmpa_discovery_handler \ + snmpa_error_report \ + snmpa_network_interface \ + snmpa_network_interface_filter \ + snmpa_notification_delivery_info_receiver \ + snmpa_notification_filter \ + snmpa_set_mechanism + +MODULES = \ + $(BEHAVIOUR_MODULES) \ + snmpa \ + snmpa_acm \ + snmpa_agent \ + snmpa_agent_sup \ + snmpa_app \ + snmpa_conf \ + snmpa_discovery_handler_default \ + snmpa_error \ + snmpa_error_io \ + snmpa_error_logger \ + snmpa_general_db \ + snmpa_local_db \ + snmpa_mib \ + snmpa_mib_data \ + snmpa_mib_lib \ + snmpa_misc_sup \ + snmpa_mpd \ + snmpa_net_if \ + snmpa_net_if_filter \ + snmpa_set \ + snmpa_set_lib \ + snmpa_supervisor \ + snmpa_svbl \ + snmpa_symbolic_store \ + snmpa_target_cache \ + snmpa_trap \ + snmpa_usm \ + snmpa_vacm \ + snmp_community_mib \ + snmp_framework_mib \ + snmp_generic \ + snmp_generic_mnesia \ + snmp_index \ + snmp_notification_mib \ + snmp_shadow_table \ + snmp_standard_mib \ + snmp_target_mib \ + snmp_user_based_sm_mib \ + snmp_view_based_acm_mib + + +INTERNAL_HRL_FILES = \ + snmpa_vacm \ + snmpa_atl \ + snmpa_internal + +EXT_HRL_FILES = diff --git a/lib/snmp/src/agent/snmp_community_mib.erl b/lib/snmp/src/agent/snmp_community_mib.erl new file mode 100644 index 0000000000..a2ee7bf0c9 --- /dev/null +++ b/lib/snmp/src/agent/snmp_community_mib.erl @@ -0,0 +1,590 @@ +%% +%% %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(snmp_community_mib). + +-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, delete_community/1]). +-export([check_community/1]). + +-include("SNMP-COMMUNITY-MIB.hrl"). +-include("SNMP-TARGET-MIB.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",[]), + Gen = fun(_) -> ok end, + Filter = fun(Comms) -> Comms end, + Check = fun(Entry) -> check_community(Entry) end, + [Comms] = + snmp_conf:read_files(Dir, [{Gen, Filter, Check, "community.conf"}]), + Comms. + +check_community({Index, CommunityName, SecName, 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), + EngineID = get_engine_id(), + 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}, + case (catch 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; + {error, Reason} -> + {error, Reason}; + Error -> + {error, Error} + 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), + 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} -> + {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, [])) 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, [])) of + {ok, Cols} -> + NCols = conv3(Cols), + snmp_generic:table_func(is_set_ok, RowIndex, NCols, + db(snmpTargetAddrExtTable)); + Error -> + Error + end. + + +verify_snmpTargetAddrExtTable_cols([], Cols) -> + {ok, lists:reverse(Cols)}; +verify_snmpTargetAddrExtTable_cols([{Col, Val0}|Cols], Acc) -> + Val = verify_snmpTargetAddrExtTable_col(Col, Val0), + verify_snmpTargetAddrExtTable_cols(Cols, [{Col, Val}|Acc]). + +verify_snmpTargetAddrExtTable_col(?snmpTargetAddrTMask, []) -> + []; +verify_snmpTargetAddrExtTable_col(?snmpTargetAddrTMask, TMask) -> + case (catch snmp_conf:check_taddress(TMask)) of + ok -> + TMask; + _ -> + wrongValue(?snmpTargetAddrTMask) + end; +verify_snmpTargetAddrExtTable_col(?snmpTargetAddrMMS, MMS) -> + case (catch snmp_conf:check_packet_size(MMS)) of + ok -> + MMS; + _ -> + wrongValue(?snmpTargetAddrMMS) + end; +verify_snmpTargetAddrExtTable_col(_, 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}). + +config_err(F, A) -> + snmpa_error:config_err("[COMMUNITY-MIB]: " ++ F, A). diff --git a/lib/snmp/src/agent/snmp_framework_mib.erl b/lib/snmp/src/agent/snmp_framework_mib.erl new file mode 100644 index 0000000000..0916e2ec74 --- /dev/null +++ b/lib/snmp/src/agent/snmp_framework_mib.erl @@ -0,0 +1,440 @@ +%% +%% %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(snmp_framework_mib). + +-include("snmp_types.hrl"). +-include("STANDARD-MIB.hrl"). + +-define(VMODULE,"FRAMEWORK-MIB"). +-include("snmp_verbosity.hrl"). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + + +%%%----------------------------------------------------------------- +%%% This module implements the init- configure- and instrumentation- +%%% functions for the SNMP-FRAMEWORK-MIB. +%%% +%%% We also keep internal datastructures here, e.g. a table +%%% over all known contexts. +%%%----------------------------------------------------------------- +%% External exports +-export([init/0, configure/1]). +-export([intContextTable/1, intContextTable/3, + intAgentUDPPort/1, intAgentIpAddress/1, + snmpEngineID/1, + snmpEngineBoots/1, + snmpEngineTime/1, + snmpEngineMaxMessageSize/1, + get_engine_id/0, get_engine_max_message_size/0, + get_engine_boots/0, get_engine_time/0, + set_engine_boots/1, set_engine_time/1, + table_next/2, check_status/3]). +-export([add_context/1, delete_context/1]). +-export([check_agent/1, check_context/1]). + + +%%----------------------------------------------------------------- +%% Func: init/0 +%% Purpose: Creates the tables and variables necessary for the SNMP +%% mechanism to work properly. +%% Note that this function won't destroy any old values. +%% This function should be called only once. +%%----------------------------------------------------------------- +init() -> + maybe_create_table(intContextTable), + init_engine(). + + +%%----------------------------------------------------------------- +%% Func: configure/1 +%% Args: Dir is the directory with trailing dir_separator where +%% the configuration files can be found. +%% Purpose: Reads the config-files for the internal 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) +%% PRE: init/1 has been successfully called +%%----------------------------------------------------------------- +configure(Dir) -> + set_sname(), + case snmpa_agent:get_agent_mib_storage() of + mnesia -> + ok; + _ -> + case (catch do_configure(Dir)) of + ok -> + ok; + {error, Reason} -> + ?vinfo("configure error: ~p", [Reason]), + config_err("configure failed: ~p", [Reason]), + exit(configuration_error); + Error -> + ?vinfo("configure failed: ~p", [Error]), + config_err("configure failed: ~p", [Error]), + exit(configuration_error) + end + end, + ok. + +do_configure(Dir) -> + ?vdebug("read internal config files",[]), + Contexts = read_internal_config_files(Dir), + ?vdebug("read agent config files",[]), + Agent = read_agent(Dir), + ?vdebug("initiate vars",[]), + init_vars(Agent), + %% Add default context, if not present. + NContexts = [{""} | lists:delete({""}, Contexts)], + ?vdebug("initiate tables",[]), + init_tabs(NContexts), + ok. + +read_internal_config_files(Dir) -> + ?vdebug("read context config file",[]), + Gen = fun(D) -> convert_context(D) end, + Filter = fun(Contexts) -> Contexts end, + Check = fun(Entry) -> check_context(Entry) end, + [Ctxs] = snmp_conf:read_files(Dir, [{Gen, Filter, Check, "context.conf"}]), + Ctxs. + + +read_agent(Dir) -> + ?vdebug("read agent config file",[]), + Check = fun(Entry) -> check_agent(Entry) end, + File = filename:join(Dir, "agent.conf"), + Agent = snmp_conf:read(File, Check), + sort_agent(Agent). + + +%%----------------------------------------------------------------- +%% Make sure that each mandatory agent attribute is present, and +%% provide default values for the other non-present attributes. +%%----------------------------------------------------------------- +sort_agent(L) -> + Mand = [{intAgentIpAddress, mandatory}, + {intAgentUDPPort, mandatory}, + {snmpEngineMaxMessageSize, mandatory}, + {snmpEngineID, mandatory}], + {ok, L2} = snmp_conf:check_mandatory(L, Mand), + lists:keysort(1, L2). + + +%%----------------------------------------------------------------- +%% Generate a context.conf file. +%%----------------------------------------------------------------- +convert_context(Dir) -> + config_err("missing context.conf file => generating a default file", []), + File = filename:join(Dir,"context.conf"), + case file:open(File, [write]) of + {ok, Fid} -> + ok = io:format(Fid, "~s\n", [context_header()]), + ok = io:format(Fid, "%% The default context\n\"\".\n", []), + file:close(Fid); + {error, Reason} -> + file:delete(File), + error({failed_creating_file, File, Reason}) + end. + +context_header() -> + {Y,Mo,D} = date(), + {H,Mi,S} = time(), + io_lib:format("%% This file was automatically generated by " + "snmp_config v~s ~w-~2.2.0w-~2.2.0w " + "~2.2.0w:~2.2.0w:~2.2.0w\n", + [?version,Y,Mo,D,H,Mi,S]). + + +%%----------------------------------------------------------------- +%% Context +%% Context. +%%----------------------------------------------------------------- +check_context(Context) -> + ?vtrace("check_context -> entry with" + "~n Context: ~p", [Context]), + case (catch snmp_conf:check_string(Context)) of + ok -> + {ok, {Context}}; + _ -> + error({invalid_context, Context}) + end. + + +%%----------------------------------------------------------------- +%% Agent +%% {Name, Value}. +%%----------------------------------------------------------------- +check_agent({intAgentIpAddress, Value}) -> + snmp_conf:check_ip(Value); +check_agent({intAgentUDPPort, Value}) -> + snmp_conf:check_integer(Value); +%% This one is kept for backwards compatibility +check_agent({intAgentMaxPacketSize, Value}) -> + snmp_conf:check_packet_size(Value); +check_agent({snmpEngineMaxMessageSize, Value}) -> + snmp_conf:check_packet_size(Value); +check_agent({snmpEngineID, Value}) -> + snmp_conf:check_string(Value); +check_agent(X) -> + error({invalid_agent_attribute, X}). + + +maybe_create_table(Name) -> + case snmpa_local_db:table_exists(db(Name)) of + true -> + ok; + _ -> + ?vtrace("create table: ~w",[Name]), + snmpa_local_db:table_create(db(Name)) + end. + +init_vars(Vars) -> + lists:map(fun init_var/1, Vars). + +init_var({Var, Val}) -> + ?vtrace("init var: " + "~n set ~w to ~w",[Var, Val]), + snmp_generic:variable_set(db(Var), Val). + +init_tabs(Contexts) -> + ?vdebug("create context table",[]), + snmpa_local_db:table_delete(db(intContextTable)), + snmpa_local_db:table_create(db(intContextTable)), + init_context_table(Contexts). + +init_context_table([Row | T]) -> + Context = element(1, Row), + Key = [length(Context) | Context], + ?vtrace("create intContextTable table row for: ~w",[Key]), + snmpa_local_db:table_create_row(db(intContextTable), Key, Row), + init_context_table(T); +init_context_table([]) -> 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_context(Ctx) -> + case (catch check_context(Ctx)) of + {ok, Row} -> + Context = element(1, Row), + Key = [length(Context) | Context], + case table_cre_row(intContextTable, Key, Row) of + true -> + {ok, Key}; + false -> + {error, create_failed} + end; + {error, Reason} -> + {error, Reason}; + Error -> + {error, Error} + end. + +%% FIXME: does not work with mnesia +delete_context(Key) -> + case table_del_row(intContextTable, Key) of + true -> + ok; + false -> + {error, delete_failed} + end. + + +%%----------------------------------------------------------------- +%% Instrumentation functions +%% Retreive functions are also used internally by the agent, so +%% don't change the interface without changing those functions. +%% Note that if these functions implementations are changed, +%% an error can make the agent crash, as no error detection is +%% performed for the internal data. +%% These functions cannot use the default functions as is, because +%% the default functions rely on that the mib is loaded, and +%% these functions must work even if the OTP-FRAMEWORK-MIB isn't loaded. +%% So we hardcode the information necessary for the functions +%% called by the default functions in snmp_generic. This info is +%% normally provided by the mib compiler, and inserted into +%% snmpa_symbolic_store at load mib time. +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% None if the int* objects are defined in any MIB. +%% +%% intContextTable keeps all +%% known contexts internally, but is ordered as an SNMP table. It +%% could be defined as: +%% intContextTable OBJECT-TYPE +%% SYNTAX SEQUENCE OF IntContextEntry +%% MAX-ACCESS not-accessible +%% STATUS current +%% DESCRIPTION "A table of locally available contexts." +%% ::= { xx } +%% +%% intContextEntry OBJECT-TYPE +%% SYNTAX IntContextEntry +%% MAX-ACCESS not-accessible +%% STATUS current +%% DESCRIPTION "Information about a particular context." +%% INDEX { +%% intContextName +%% } +%% ::= { intContextTable 1 } +%% +%% IntContextEntry ::= SEQUENCE +%% { +%% intContextName SnmpAdminString +%% } +%% +%% intContextName OBJECT-TYPE +%% SYNTAX SnmpAdminString (SIZE(0..32)) +%% MAX-ACCESS read-only +%% STATUS current +%% DESCRIPTION "A human readable name identifying a particular +%% context at a particular SNMP entity. +%% +%% The empty contextName (zero length) represents the +%% default context. +%% " +%% ::= { intContextEntry 1 } +%%----------------------------------------------------------------- + +%% Op == new | delete +intContextTable(Op) -> + snmp_generic:table_func(Op, db(intContextTable)). + +%% Op == get get_next -- READ only table +intContextTable(get, RowIndex, Cols) -> + get(intContextTable, RowIndex, Cols); +intContextTable(get_next, RowIndex, Cols) -> + next(intContextTable, RowIndex, Cols); +intContextTable(Op, Arg1, Arg2) -> + snmp_generic:table_func(Op, Arg1, Arg2, db(intContextTable)). + +%% FIXME: exported, not used by agent, not documented - remove? +table_next(Name, RestOid) -> + snmp_generic:table_next(db(Name), RestOid). + +%% FIXME: exported, not used by agent, not documented - remove? +%% FIXME: does not work with mnesia +check_status(Name, Indexes, StatusNo) -> + case snmpa_local_db:table_get_element(db(Name), Indexes, StatusNo) of + {value, ?'RowStatus_active'} -> true; + _ -> false + end. + +db(intContextTable) -> {intContextTable, volatile}; +db(X) -> snmpa_agent:db(X). + +fa(intContextTable) -> 1. + +foi(intContextTable) -> 1. + +noc(intContextTable) -> 1. + +next(Name, RowIndex, Cols) -> + snmp_generic:handle_table_next(db(Name), RowIndex, Cols, + fa(Name), foi(Name), noc(Name)). + +get(Name, RowIndex, Cols) -> + snmp_generic:handle_table_get(db(Name), RowIndex, Cols, foi(Name)). + +%% Op == new | delete | get +intAgentUDPPort(Op) -> + snmp_generic:variable_func(Op, db(intAgentUDPPort)). + +intAgentIpAddress(Op) -> + snmp_generic:variable_func(Op, db(intAgentIpAddress)). + +snmpEngineID(Op) -> + snmp_generic:variable_func(Op, db(snmpEngineID)). + +snmpEngineMaxMessageSize(Op) -> + snmp_generic:variable_func(Op, db(snmpEngineMaxMessageSize)). + +snmpEngineBoots(Op) -> + snmp_generic:variable_func(Op, db(snmpEngineBoots)). + +snmpEngineTime(get) -> + {value, get_engine_time()}. + +init_engine() -> + case snmp_generic:variable_get(db(snmpEngineBoots)) of + {value, Val} when Val < 2147483647 -> + snmp_generic:variable_set(db(snmpEngineBoots), Val+1); + {value, _} -> + ok; + undefined -> + snmp_generic:variable_set(db(snmpEngineBoots), 1) + end, + reset_engine_base(). + + +reset_engine_base() -> + ets:insert(snmp_agent_table, {snmp_engine_base, snmp_misc:now(sec)}). + +get_engine_id() -> + {value, EngineID} = snmpEngineID(get), + EngineID. + +get_engine_max_message_size() -> + {value, MPS} = snmpEngineMaxMessageSize(get), + MPS. + +get_engine_time() -> + [{_, EngineBase}] = ets:lookup(snmp_agent_table, snmp_engine_base), + snmp_misc:now(sec) - EngineBase. + +get_engine_boots() -> + {value, Val} = snmpEngineBoots(get), + Val. + +set_engine_boots(Boots) -> + snmp_generic:variable_func(set, Boots, db(snmpEngineBoots)). + +set_engine_time(Time) -> + Base = snmp_misc:now(sec) - Time, + ets:insert(snmp_agent_table, {snmp_engine_base, Base}). + + +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}). + +config_err(F, A) -> + snmpa_error:config_err("[FRAMEWORK-MIB]: " ++ F, A). + diff --git a/lib/snmp/src/agent/snmp_generic.erl b/lib/snmp/src/agent/snmp_generic.erl new file mode 100644 index 0000000000..508aa090d9 --- /dev/null +++ b/lib/snmp/src/agent/snmp_generic.erl @@ -0,0 +1,891 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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(snmp_generic). + +-export([variable_func/2, variable_func/3, variable_get/1, variable_set/2]). +-export([table_func/2, table_func/4, + table_set_row/5, table_set_cols/3, table_set_cols/4, + table_row_exists/2, table_foreach/2, table_foreach/3, + table_try_row/4, table_get_row/2, table_get_row/3, + table_get_elements/3, table_get_elements/4, table_get_element/3, + table_set_element/4, table_set_elements/3, + table_next/2, handle_table_next/6, + table_try_make_consistent/3, table_max_col/2, + find_col/2, table_check_status/5, + table_find/3,split_index_to_keys/2, init_defaults/2, init_defaults/3, + table_info/1, + try_apply/2, get_own_indexes/2, table_create_rest/6, + handle_table_get/4, variable_inc/2, + get_status_col/2, get_table_info/2, get_index_types/1]). + +-include("STANDARD-MIB.hrl"). +-include("snmp_types.hrl"). + +-define(VMODULE,"GENERIC"). +-include("snmp_verbosity.hrl"). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + + +%%%----------------------------------------------------------------- +%%% Generic functions for implementing software tables +%%% and variables. +%%%----------------------------------------------------------------- +%% NameDb is {TableName, Db} where Db is volatile | persistent | mnesia + +%%------------------------------------------------------------------ +%% Access functions to the database. +%%------------------------------------------------------------------ +variable_get({Name, mnesia}) -> + snmp_generic_mnesia:variable_get(Name); +variable_get(NameDb) -> % ret {value, Val} | undefined + snmpa_local_db:variable_get(NameDb). +variable_set({Name, mnesia}, Val) -> + snmp_generic_mnesia:variable_set(Name, Val); +variable_set(NameDb, Val) -> % ret true + snmpa_local_db:variable_set(NameDb, Val). + +variable_inc({Name, mnesia}, N) -> + snmp_generic_mnesia:variable_inc(Name, N); +variable_inc(NameDb, N) -> % ret true + snmpa_local_db:variable_inc(NameDb, N). + +%%----------------------------------------------------------------- +%% Returns: {value, Val} | undefined +%% +%% snmpa_local_db overloads (for performance reasons? (mbj?)) +%%----------------------------------------------------------------- +table_get_element({Name, volatile}, RowIndex, Col) -> + snmpa_local_db:table_get_element({Name, volatile}, RowIndex, Col); +table_get_element({Name, persistent}, RowIndex, Col) -> + snmpa_local_db:table_get_element({Name, persistent}, RowIndex, Col); +table_get_element(NameDb, RowIndex, Col) -> + TableInfo = table_info(NameDb), + case handle_table_get(NameDb,RowIndex,[Col], + TableInfo#table_info.first_own_index) of + [{value, Val}] -> {value, Val}; + _ -> undefined + end. + +table_get_elements(NameDb, RowIndex, Cols) -> + TableInfo = snmp_generic:table_info(NameDb), + table_get_elements(NameDb, RowIndex, Cols, + TableInfo#table_info.first_own_index). + +%%---------------------------------------------------------------------- +%% Returns: list of vals | undefined +%%---------------------------------------------------------------------- +table_get_elements({Name, mnesia}, RowIndex, Cols, FirstOwnIndex) -> + ?vtrace("table_get_elements(mnesia) -> entry with" + "~n Name: ~p" + "~n RowIndex: ~p" + "~n Cols: ~p" + "~n FirstOwnIndex: ~p", [Name, RowIndex, Cols, FirstOwnIndex]), + snmp_generic_mnesia:table_get_elements(Name, RowIndex, Cols, FirstOwnIndex); +table_get_elements(NameDb, RowIndex, Cols, FirstOwnIndex) -> + ?vtrace("table_get_elements -> entry with" + "~n NameDb: ~p" + "~n RowIndex: ~p" + "~n Cols: ~p" + "~n FirstOwnIndex: ~p", [NameDb, RowIndex, Cols, FirstOwnIndex]), + snmpa_local_db:table_get_elements(NameDb, RowIndex, Cols, FirstOwnIndex). + + +%% ret true +table_set_element({Name,mnesia}, RowIndex, Col, NewVal) -> + snmp_generic_mnesia:table_set_elements(Name, RowIndex, + [{Col, NewVal}]); +table_set_element(NameDb, RowIndex, Col, NewVal) -> + snmpa_local_db:table_set_elements(NameDb, RowIndex, [{Col, NewVal}]). + +table_set_elements({Name, mnesia}, RowIndex, Cols) -> + snmp_generic_mnesia:table_set_elements(Name, RowIndex, Cols); +table_set_elements(NameDb, RowIndex, Cols) -> % ret true + snmpa_local_db:table_set_elements(NameDb, RowIndex, Cols). + +table_next({Name, mnesia}, RestOid) -> + snmp_generic_mnesia:table_next(Name, RestOid); +table_next(NameDb, RestOid) -> % ret RRestOid | endOfTable + snmpa_local_db:table_next(NameDb, RestOid). +table_max_col(NameDb, Col) -> % ret largest element in Col + % in the table NameDb. + snmpa_local_db:table_max_col(NameDb, Col). + + +%%------------------------------------------------------------------ +%% Theses functions could be in the MIB for simple +%% variables or tables, i.e. vars without complex +%% set-operations. If there are complex set op, an +%% extra layer-function should be added, and that +%% function should be in the MIB, and it can call these +%% functions. +%% The MIB functions just provide the table name, column +%% and a list of the keys for the table. +%%------------------------------------------------------------------ + +%%------------------------------------------------------------------ +%% Variables +%%------------------------------------------------------------------ +%%------------------------------------------------------------------ +%% This is the default function for variables. +%%------------------------------------------------------------------ + +variable_func(new, NameDb) -> + case variable_get(NameDb) of + {value, _} -> ok; + undefined -> + #variable_info{defval = Defval} = variable_info(NameDb), + variable_set(NameDb, Defval) + end; + +variable_func(delete, _NameDb) -> + ok; + +variable_func(get, NameDb) -> + case variable_get(NameDb) of + {value, Val} -> {value, Val}; + _ -> genErr + end. + +variable_func(is_set_ok, _Val, _NameDb) -> + noError; +variable_func(set, Val, NameDb) -> + case variable_set(NameDb, Val) of + true -> noError; + false -> commitFailed + end; +variable_func(undo, _Val, _NameDb) -> + noError. + +%%------------------------------------------------------------------ +%% Tables +%% Assumes the RowStatus is the last column in the +%% table. +%%------------------------------------------------------------------ +%%------------------------------------------------------------------ +%% This is the default function for tables. +%% +%% NameDb is the name of the table (atom) +%% RowIndex is a flat list of the indexes for the row. +%% Col is the column number. +%%------------------------------------------------------------------ +%% Each database implements its own table_func +%%------------------------------------------------------------------ +table_func(Op, {Name, mnesia}) -> + snmp_generic_mnesia:table_func(Op, Name); + +table_func(Op, NameDb) -> + snmpa_local_db:table_func(Op, NameDb). + +table_func(Op, RowIndex, Cols, {Name, mnesia}) -> + snmp_generic_mnesia:table_func(Op, RowIndex, Cols, Name); + +table_func(Op, RowIndex, Cols, NameDb) -> + snmpa_local_db:table_func(Op, RowIndex, Cols, NameDb). + +%%---------------------------------------------------------------------- +%% DB independent. +%%---------------------------------------------------------------------- +handle_table_get(NameDb, RowIndex, Cols, FirstOwnIndex) -> + case table_get_elements(NameDb, RowIndex, Cols, FirstOwnIndex) of + undefined -> + ?vdebug("handle_table_get -> undefined", []), + make_list(length(Cols), {noValue, noSuchInstance}); + Res -> + ?vtrace("handle_table_get -> Res: ~n ~p", [Res]), + validate_get(Cols, Res) + end. + +validate_get([_Col | Cols], [Res | Ress]) -> + NewVal = + case Res of + noinit -> {noValue, unSpecified}; + noacc -> {noAccess, unSpecified}; + Val -> {value, Val} + end, + [NewVal | validate_get(Cols, Ress)]; +validate_get([], []) -> []. + +make_list(N, X) when N > 0 -> [X | make_list(N-1, X)]; +make_list(_, _) -> []. + +table_foreach(Tab, Fun) -> + ?vdebug("apply fun to all in table ~w",[Tab]), + table_foreach(Tab, Fun, undefined, []). +table_foreach(Tab, Fun, FOI) -> + ?vdebug("apply fun to all in table ~w",[Tab]), + table_foreach(Tab, Fun, FOI, []). +table_foreach(Tab, Fun, FOI, Oid) -> + case table_next(Tab, Oid) of + endOfTable -> + ?vdebug("end of table",[]), + ok; + Oid -> + %% OOUPS, circular ref, major db fuckup + ?vinfo("cyclic reference: ~w -> ~w",[Oid,Oid]), + exit({cyclic_db_reference,Oid}); + NextOid -> + ?vtrace("get row for oid ~w",[NextOid]), + case table_get_row(Tab, NextOid, FOI) of + undefined -> ok; + Row -> + ?vtrace("row: ~w",[Row]), + Fun(NextOid, Row) + end, + table_foreach(Tab, Fun, FOI, NextOid) + end. + +%%------------------------------------------------------------------ +%% Used to implement next, and to find next entry's +%% keys in a table when not all of the keys are known. +%% +%% FirstCol is the first column in the search. +%% LastCol is the last column. +%% Col is the current column. +%% If Col is less than FirstCol, (or not present), the +%% search shall begin in the first row (no indexes) of +%% column FirstCol. +%% Returns: List of endOfTable | {NextOid, Value} +%%------------------------------------------------------------------ +handle_table_next(_NameDb, _RowIndex, [], _FirstCol, _FOI, _LastCol) -> + []; +handle_table_next(NameDb, RowIndex, OrgCols, FirstCol, FOI, LastCol) -> + FirstVals = + case split_cols(OrgCols, FirstCol, LastCol) of + {[], Cols, LastCols} -> + []; + {FirstCols, Cols, LastCols} -> + handle_table_next(NameDb, [], FirstCols, FirstCol, FOI, LastCol) + end, + NextVals = + case table_next(NameDb, RowIndex) of + endOfTable -> + {NewCols, EndOfTabs} = make_new_cols(Cols, LastCol), + NewVals = + handle_table_next(NameDb, [], NewCols,FirstCol,FOI,LastCol), + lists:append(NewVals, EndOfTabs); + NextIndex -> + % We found next Row; check if all Cols are initialized. + Row = table_get_elements(NameDb, NextIndex, Cols, FOI), + check_all_initalized(Row,Cols,NameDb,NextIndex, + FirstCol, FOI, LastCol) + end, + lists:append([FirstVals, NextVals, LastCols]). + +%% Split into three parts A,B,C; A < FirstCol =< B =< LastCol < C +split_cols([Col | Cols], FirstCol, LastCol) when Col < FirstCol -> + {A, B, C} = split_cols(Cols, FirstCol, LastCol), + {[FirstCol | A], B, C}; +split_cols([Col | Cols], FirstCol, LastCol) when Col > LastCol -> + {A, B, C} = split_cols(Cols, FirstCol, LastCol), + {A, B, [endOfTable | C]}; +split_cols([Col | Cols], FirstCol, LastCol) -> + {A, B, C} = split_cols(Cols, FirstCol, LastCol), + {A, [Col | B], C}; +split_cols([], _FirstCol, _LastCol) -> + {[], [], []}. + +%% Add 1 to each col < lastcol. Otherwise make it into +%% endOfTable. +make_new_cols([Col | Cols], LastCol) when Col < LastCol -> + {NewCols, Ends} = make_new_cols(Cols, LastCol), + {[Col+1 | NewCols], Ends}; +make_new_cols([_Col | Cols], LastCol) -> + {NewCols, Ends} = make_new_cols(Cols, LastCol), + {NewCols, [endOfTable | Ends]}; +make_new_cols([], _LastCol) -> + {[], []}. + +check_all_initalized([noinit|Vals],[Col|Cols],Name,RowIndex, + FirstCol, FOI, LastCol) -> + [NextValForThisCol] = + handle_table_next(Name, RowIndex, [Col], FirstCol, FOI, LastCol), + [NextValForThisCol | + check_all_initalized(Vals, Cols, Name, RowIndex, FirstCol, FOI, LastCol)]; +check_all_initalized([noacc|Vals],[Col|Cols],Name,RowIndex, + FirstCol, FOI, LastCol) -> + [NextValForThisCol] = + handle_table_next(Name, RowIndex, [Col], FirstCol, FOI, LastCol), + [NextValForThisCol | + check_all_initalized(Vals, Cols, Name, RowIndex, FirstCol, FOI, LastCol)]; +check_all_initalized([Val | Vals], [Col | Cols], Name, RowIndex, + FirstCol, FOI, LastCol) -> + [{[Col | RowIndex], Val} | + check_all_initalized(Vals, Cols, Name, RowIndex, FirstCol, FOI, LastCol)]; +check_all_initalized([], [], _Name, _RowIndex, _FirstCol, _FOI, _LastCol) -> + []. + + +%%------------------------------------------------------------------ +%% Implements is_set_ok. +%%------------------------------------------------------------------ +%% TryChangeStatusFunc is a function that will be +%% called if the rowstatus column is changed. +%% Arguments: (StatusVal, RowIndex, Cols) +%% Two cases: +%% 1) Last col is RowStatus - check status +%% 2) No modification to RowStatus - check that row exists. +%%------------------------------------------------------------------ +table_try_row(_NameDb, _TryChangeStatusFunc, _RowIndex, []) -> {noError, 0}; +table_try_row(NameDb, TryChangeStatusFunc, RowIndex, Cols) -> + #table_info{status_col = StatusCol} = table_info(NameDb), + case lists:keysearch(StatusCol, 1, Cols) of + {value, {StatusCol, Val}} -> + case table_check_status(NameDb, StatusCol, + Val, RowIndex, Cols) of + {noError, 0} -> + try_apply(TryChangeStatusFunc, [NameDb, Val, + RowIndex, Cols]); + Error -> Error + end; + _ -> + case table_row_exists(NameDb, RowIndex) of + true -> {noError, 0}; + false -> + [{ColNo, _Val}|_] = Cols, + {inconsistentName, ColNo} + end + end. + +%%------------------------------------------------------------------ +%% table_check_status can be used by the is_set_ok +%% procedure of all tables, to check the +%% status variable, if present in Cols. +%% table_check_status(NameDb, Col, Val, RowIndex, Cols) -> +%% NameDb : the name of the table +%% Col : the columnnumber of RowStatus +%% Val : the value of the RowStatus Col +%%------------------------------------------------------------------ + +%% Try to make the row active. Ok if status != notReady +%% If it is notReady, make sure no row has value noinit. +table_check_status(NameDb, Col, ?'RowStatus_active', RowIndex, Cols) -> + case table_get_row(NameDb, RowIndex) of + Row when is_tuple(Row) andalso + (element(Col, Row) =:= ?'RowStatus_notReady') -> + case is_any_noinit(Row, Cols) of + false -> {noError, 0}; + true -> {inconsistentValue, Col} + end; + undefined -> {inconsistentValue, Col}; + _Else -> {noError, 0} + end; + +%% Try to make the row inactive. Ok if status != notReady +table_check_status(NameDb, Col, ?'RowStatus_notInService', RowIndex, Cols) -> + case table_get_row(NameDb, RowIndex) of + Row when is_tuple(Row) andalso + (element(Col, Row) =:= ?'RowStatus_notReady') -> + case is_any_noinit(Row, Cols) of + false -> {noError, 0}; + true -> {inconsistentValue, Col} + end; + undefined -> {inconsistentValue, Col}; + _Else -> {noError, 0} + end; + +%% Try to createAndGo +%% Ok if values are provided, or default values can be used for +%% all columns. +table_check_status(NameDb, Col, ?'RowStatus_createAndGo', RowIndex, Cols) -> + case table_row_exists(NameDb, RowIndex) of + false -> + % it's ok to use snmpa_local_db:table_construct_row since it's + % side effect free and we only use the result temporary. + case catch snmpa_local_db:table_construct_row( + NameDb, RowIndex, ?'RowStatus_createAndGo', Cols) of + {'EXIT', _} -> + {noCreation, Col}; % Bad RowIndex + Row -> + case lists:member(noinit, tuple_to_list(Row)) of + false -> {noError, 0}; + _Found -> {inconsistentValue, Col} + end + end; + true -> {inconsistentValue, Col} + end; + +%% Try to createAndWait - ok if row doesn't exist. +table_check_status(NameDb, Col, ?'RowStatus_createAndWait', RowIndex, Cols) -> + case table_row_exists(NameDb, RowIndex) of + false -> + case catch snmpa_local_db:table_construct_row( + NameDb, RowIndex, ?'RowStatus_createAndGo', Cols) of + {'EXIT', _} -> + {noCreation, Col}; % Bad RowIndex + _Row -> + {noError, 0} + end; + true -> {inconsistentValue, Col} + end; + +%% Try to destroy +table_check_status(_NameDb, _Col, ?'RowStatus_destroy', _RowIndex, _Cols) -> + {noError, 0}; + +%% Otherwise, notReady. It isn't possible to set a row to notReady. +table_check_status(_NameDb, Col, _, _RowIndex, _Cols) -> + {inconsistentValue, Col}. + +is_any_noinit(Row, Cols) -> + is_any_noinit(tuple_to_list(Row), Cols, 1). +is_any_noinit([noinit | Vals], [{N, _Value} | Cols], N) -> + is_any_noinit(Vals, Cols, N+1); +is_any_noinit([noinit | _Vals], _Cols, _N) -> + true; +is_any_noinit([_ | Vals], [{N, _Value} | Cols], N) -> + is_any_noinit(Vals, Cols, N+1); +is_any_noinit([_ | Vals], Cols, N) -> + is_any_noinit(Vals, Cols, N+1); +is_any_noinit([], _, _) -> + false. + +%%------------------------------------------------------------------ +%% Implements set. +%% ChangedStatusFunc is a function that will be +%% called if the rowstatus column is changed. +%% The function is called *after* the row is created or +%% otherwise modified, but *before* it is deleted. +%% Arguments: (StatusVal, RowIndex, Cols) +%% ConsFunc is a consistency-check function which will +%% be called with the RowIndex of this row, if +%% no operation on the row is made, when +%% all columns are set, OR when row is createAndWait:ed. +%% This is useful when the RowStatus +%% could change, e.g. if the manager has provided all +%% mandatory columns in this set operation. +%% If it is nofunc, no function will be called after all +%% sets. +%%------------------------------------------------------------------ +table_set_row(_NameDb, _, _, _RowIndex, []) -> {noError, 0}; +table_set_row(NameDb, ChangedStatusFunc, ConsFunc, RowIndex, Cols) -> + #table_info{status_col = StatusCol} = table_info(NameDb), + case lists:keysearch(StatusCol, 1, Cols) of + {value, {StatusCol, Val}} -> + table_set_status(NameDb, RowIndex, Val, StatusCol, + Cols, ChangedStatusFunc, ConsFunc); + _ -> table_set_cols(NameDb, RowIndex, Cols, ConsFunc) + end. + +%%---------------------------------------------------------------------- +%% Mnesia overloads for performance reasons. +%%---------------------------------------------------------------------- +table_set_status({Name, mnesia}, RowIndex, Status, StatusCol, Cols, + ChangedStatusFunc, ConsFunc) -> + snmp_generic_mnesia:table_set_status(Name, RowIndex, + Status, StatusCol, Cols, + ChangedStatusFunc, ConsFunc); + +table_set_status(NameDb,RowIndex, Status, StatusCol, Cols, + ChangedStatusFunc,ConsFunc) -> + snmpa_local_db:table_set_status(NameDb, RowIndex, + Status, StatusCol, Cols, + ChangedStatusFunc, ConsFunc). + +init_defaults(Defs, InitRow) -> + table_defaults(InitRow, Defs). +init_defaults(Defs, InitRow, StartCol) -> + table_defaults(InitRow, StartCol, Defs). +%%----------------------------------------------------------------- +%% Get, from a list of Keys, the Keys defined in this table. +%% (e.g. if INDEX { ifIndex, myOwnIndex }, the Keys is a list +%% of two elements, and returned from this func is a list of +%% the last of the two.) +%%----------------------------------------------------------------- +get_own_indexes(0, _Keys) -> []; +get_own_indexes(1, Keys) -> Keys; +get_own_indexes(Index, [_Key | Keys]) -> + get_own_indexes(Index - 1, Keys). + +%%----------------------------------------------------------------- +%% Creates everything but the INDEX columns. +%% Pre: The StatusColumn is present +%% Four cases: +%% 0) If a column is 'not-accessible' => use noacc +%% 1) If no value is provided for the column and column is +%% not StatusCol => use noinit +%% 2) If column is not StatusCol, use the provided value +%% 3) If column is StatusCol, use Status +%%----------------------------------------------------------------- +table_create_rest(Col, Max, _ , _ , [], _NoAcc) when Col > Max -> []; +table_create_rest(Col,Max,StatusCol,Status,[{Col,_Val}|Defs],[Col|NoAccs]) -> + % case 0 + [noacc | table_create_rest(Col+1, Max, StatusCol, Status, Defs, NoAccs)]; +table_create_rest(Col,Max,StatusCol,Status,Defs,[Col|NoAccs]) -> + % case 0 + [noacc | table_create_rest(Col+1, Max, StatusCol, Status, Defs, NoAccs)]; +table_create_rest(StatCol, Max, StatCol, Status, [{_Col, _Val} |Defs], NoAccs) -> + % case 3 + [Status | table_create_rest(StatCol+1, Max, StatCol, Status,Defs,NoAccs)]; +table_create_rest(Col, Max, StatusCol, Status, [{Col, Val} |Defs],NoAccs) -> + % case 2 + [Val | table_create_rest(Col+1, Max, StatusCol, Status,Defs,NoAccs)]; +table_create_rest(StatCol, Max, StatCol, Status, Cols, NoAccs) -> + % case 3 + [Status | table_create_rest(StatCol+1, Max, StatCol, Status, Cols, NoAccs)]; +table_create_rest(Col, Max, StatusCol, Status, Cols, NoAccs) when Col =< Max-> + % case 1 + [noinit | table_create_rest(Col+1, Max, StatusCol, Status, Cols, NoAccs)]. + +%%------------------------------------------------------------------ +%% Sets default values to a row. +%% InitRow is a list of values. +%% Defs is a list of {Col, DefVal}, in Column order. +%% Returns a new row (a list of values) with the same values as +%% InitRow, except if InitRow has value noinit in a column, and +%% the corresponing Col has a DefVal in Defs, then the DefVal +%% will be the new value. +%%------------------------------------------------------------------ +table_defaults(InitRow, Defs) -> table_defaults(InitRow, 1, Defs). + +table_defaults([], _, _Defs) -> []; +table_defaults([noinit | T], CurIndex, [{CurIndex, DefVal} | Defs]) -> + [DefVal | table_defaults(T, CurIndex+1, Defs)]; +%% 'not-accessible' columns don't get a value +table_defaults([noacc | T], CurIndex, [{CurIndex, _DefVal} | Defs]) -> + [noacc | table_defaults(T, CurIndex+1, Defs)]; +table_defaults([Val | T], CurIndex, [{CurIndex, _DefVal} | Defs]) -> + [Val | table_defaults(T, CurIndex+1, Defs)]; +table_defaults([Val | T], CurIndex, Defs) -> + [Val | table_defaults(T, CurIndex+1, Defs)]. + + +%%------------------------------------------------------------------ +%% table_set_cols/3,4 +%% can be used by the set procedure of all tables +%% to set all columns in Cols, one at a time. +%% ConsFunc is a check-consistency function, which will +%% be called with the RowIndex of this row, when +%% all columns are set. This is useful when the RowStatus +%% could change, e.g. if the manager has provided all +%% mandatory columns in this set operation. +%% If ConsFunc is nofunc, no function will be called after all +%% sets. +%% Returns: {noError, 0} | {Error, Col} +%%------------------------------------------------------------------ +%% mnesia uses its own for performance reasons. +%% ----------------------------------------------------------------- +table_set_cols({Name,mnesia}, RowIndex, Cols, ConsFunc) -> + snmp_generic_mnesia:table_set_cols(Name, RowIndex,Cols,ConsFunc); +table_set_cols(NameDb, RowIndex, Cols, ConsFunc) -> + case table_set_cols(NameDb, RowIndex, Cols) of + {noError, 0} -> try_apply(ConsFunc, [NameDb, RowIndex, Cols]); + Error -> Error + end. + +table_set_cols(_NameDb, _RowIndex, []) -> + {noError, 0}; +table_set_cols(NameDb, RowIndex, [{Col, Val} | Cols]) -> + case catch table_set_element(NameDb, RowIndex, Col, Val) of + true -> + table_set_cols(NameDb, RowIndex, Cols); + X -> + user_err("snmp_generic:table_set_cols set ~w to" + " ~w returned ~w", + [{NameDb, RowIndex}, {Col, Val}, X]), + {undoFailed, Col} + end. + +%%------------------------------------------------------------------ +%% This function splits RowIndex which is part +%% of a OID, into a list of the indexes for the +%% table. So a table with indexes {integer, octet string}, +%% and a RowIndex [4,3,5,6,7], will be split into +%% [4, [5,6,7]]. +%%------------------------------------------------------------------ +split_index_to_keys(Indexes, RowIndex) -> + collect_keys(Indexes, RowIndex). + +collect_keys([#asn1_type{bertype = 'INTEGER'} | Indexes], [IntKey | Keys]) -> + [IntKey | collect_keys(Indexes, Keys)]; +collect_keys([#asn1_type{bertype = 'Unsigned32'} | Indexes], [IntKey | Keys]) -> + [IntKey | collect_keys(Indexes, Keys)]; +collect_keys([#asn1_type{bertype = 'Counter32'} | Indexes], [IntKey | Keys]) -> + %% Should we allow this - counter in INDEX is strange! + [IntKey | collect_keys(Indexes, Keys)]; +collect_keys([#asn1_type{bertype = 'TimeTicks'} | Indexes], [IntKey | Keys]) -> + %% Should we allow this - timeticks in INDEX is strange! + [IntKey | collect_keys(Indexes, Keys)]; +collect_keys([#asn1_type{bertype = 'IpAddress'} | Indexes], + [A, B, C, D | Keys]) -> + [[A, B, C, D] | collect_keys(Indexes, Keys)]; +%% Otherwise, check if it has constant size +collect_keys([#asn1_type{lo = X, hi = X} | Indexes], Keys) + when is_integer(X) andalso (length(Keys) >= X) -> + {StrKey, Rest} = collect_length(X, Keys, []), + [StrKey | collect_keys(Indexes, Rest)]; +collect_keys([#asn1_type{lo = X, hi = X} | _Indexes], Keys) + when is_integer(X) -> + exit({error, {size_mismatch, X, Keys}}); +%% Otherwise, its a dynamic-length type => its a list +%% OBJECT IDENTIFIER, OCTET STRING or BITS (or derivatives) +%% Check if it is IMPLIED (only last element can be IMPLIED) +collect_keys([#asn1_type{implied = true}], Keys) -> + [Keys]; +collect_keys([_Type | Indexes], [Length | Keys]) when length(Keys) >= Length -> + {StrKey, Rest} = collect_length(Length, Keys, []), + [StrKey | collect_keys(Indexes, Rest)]; +collect_keys([_Type | _Indexes], [Length | Keys]) -> + exit({error, {size_mismatch, Length, Keys}}); +collect_keys([], []) -> []; +collect_keys([], Keys) -> + exit({error, {bad_keys, Keys}}); +collect_keys(_Any, Key) -> [Key]. + +collect_length(0, Rest, Rts) -> + {lists:reverse(Rts), Rest}; +collect_length(N, [El | Rest], Rts) -> + collect_length(N-1, Rest, [El | Rts]). + +%%------------------------------------------------------------------ +%% Checks if a certain row exists. +%% Returns true or false. +%%------------------------------------------------------------------ +table_row_exists(NameDb, RowIndex) -> + case table_get_element(NameDb, RowIndex, 1) of + undefined -> false; + _ -> true + end. + +%%------------------------------------------------------------------ +%% table_find(NameDb, Col, Value) +%% Finds a row (if one exists) in table NameDb +%% with column Col equals to Value. +%% Returns the RowIndex of the row, or false +%% if no row exists. +%%------------------------------------------------------------------ +table_find(NameDb, Col, Value) -> table_find(NameDb, Col, Value, []). +table_find(NameDb, Col, Value, Indexes) -> + case table_next(NameDb, Indexes) of + endOfTable -> + false; + NewIndexes -> + case table_get_element(NameDb, NewIndexes, Col) of + {value, Value} -> NewIndexes; + _Else -> table_find(NameDb, Col, Value, NewIndexes) + end + end. + + +%%------------------------------------------------------------------ +%% find_col(Col, Cols) +%% undefined if a Col for column Col doesn't exist. +%% {value, Val} if a Col for Col with value Val exists. +%%------------------------------------------------------------------ +find_col(_Col, []) -> undefined; +find_col(Col, [{Col, Val} | _T]) -> {value, Val}; +find_col(Col, [_H | T]) -> find_col(Col, T). + +%%------------------------------------------------------------------ +%% check_mandatory_cols(ListOfCols, Cols) +%% {noError 0}if all columns in ListOfCols are present in Cols. +%% {inconsistentValue 0} otherwise. (Index = 0. It's hard to tell +%% which Col is wrong, when the problem is that one is missing!) +%%------------------------------------------------------------------ +% check_mandatory_cols([], _) -> {noError, 0}; +% check_mandatory_cols(_, []) -> {inconsistentValue, 0}; +% check_mandatory_cols([Col | Cols], [{Col, Val} | T]) -> +% check_mandatory_cols(Cols, T); +% check_mandatory_cols([Col | Cols], [{Col2, Val} | T]) -> +% check_mandatory_cols([Col | Cols], T). + + +try_apply(nofunc, _) -> {noError, 0}; +try_apply(F, Args) -> apply(F, Args). + +table_info({Name, _Db}) -> + case snmpa_symbolic_store:table_info(Name) of + {value, TI} -> + TI; + false -> + error({table_not_found, Name}) + end; +table_info(Name) -> + case snmpa_symbolic_store:table_info(Name) of + {value, TI} -> + TI; + false -> + error({table_not_found, Name}) + end. + +variable_info({Name, _Db}) -> + case snmpa_symbolic_store:variable_info(Name) of + {value, VI} -> + VI; + false -> + error({variable_not_found, Name}) + end; +variable_info(Name) -> + case snmpa_symbolic_store:variable_info(Name) of + {value, VI} -> + VI; + false -> + error({variable_not_found, Name}) + end. + + +%%------------------------------------------------------------------ +%% This function is a simple consistency check +%% function which could be used by the user-defined +%% table functions. +%% Check if the row has all information needed to +%% make row notInService (from notReady). This is +%% a simple check, which just checks if some col +%% in the row has the value 'noinit'. +%% If it has the information, the status is changed +%% to notInService. +%%------------------------------------------------------------------ +table_try_make_consistent(Name, RowIndex, _Cols) -> + TableInfo = table_info(Name), + case TableInfo#table_info.status_col of + StatusCol when is_integer(StatusCol) -> + {value, StatusVal} = table_get_element(Name, RowIndex, StatusCol), + table_try_make_consistent(Name, RowIndex, StatusVal, TableInfo); + _ -> + {noError, 0} + end. + +table_try_make_consistent(Name, RowIndex, ?'RowStatus_notReady', TableInfo) -> + %% this *should* be a generic function, + %% but since mnesia got its own try_mk_cons + %% and I don't have time to impl table_get_row + %% for mnesia I call snmpa_local_db: + Row = snmpa_local_db:table_get_row(Name, RowIndex), + case lists:member(noinit, tuple_to_list(Row)) of + true -> {noError, 0}; + false -> + case catch table_set_element(Name, RowIndex, + TableInfo#table_info.status_col, + ?'RowStatus_notInService') of + true -> {noError, 0}; + X -> + user_err("snmp_generic:table_try_make_consistent " + "set ~w to notInService returned ~w", + [{Name, RowIndex}, X]), + {commitFailed, TableInfo#table_info.status_col} + end + end; + +table_try_make_consistent(_Name, _RowIndex, _StatusVal, _TableInfo) -> + {noError, 0}. + +table_get_row({Name, mnesia}, RowIndex) -> + snmp_generic_mnesia:table_get_row(Name, RowIndex); +table_get_row(NameDb, RowIndex) -> + snmpa_local_db:table_get_row(NameDb, RowIndex). + +table_get_row(NameDb, RowIndex, undefined) -> + table_get_row(NameDb, RowIndex); +table_get_row({Name, mnesia}, RowIndex, FOI) -> + snmp_generic_mnesia:table_get_row(Name, RowIndex, FOI); +table_get_row(NameDb, RowIndex, _FOI) -> + snmpa_local_db:table_get_row(NameDb, RowIndex). + + +%%----------------------------------------------------------------- +%% Purpose: These functions can be used by the user's instrum func +%% to retrieve various table info. +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Description: +%% Used by user's instrum func to check if mstatus column is +%% modified. +%%----------------------------------------------------------------- +get_status_col(Name, Cols) -> + #table_info{status_col = StatusCol} = table_info(Name), + case lists:keysearch(StatusCol, 1, Cols) of + {value, {StatusCol, Val}} -> {ok, Val}; + _ -> false + end. + + +%%----------------------------------------------------------------- +%% Description: +%% Used by user's instrum func to get the table info. Specific parts +%% or all of it. If all is selected then the result will be a tagged +%% list of values. +%%----------------------------------------------------------------- +get_table_info(Name, nbr_of_cols) -> + get_nbr_of_cols(Name); +get_table_info(Name, defvals) -> + get_defvals(Name); +get_table_info(Name, status_col) -> + get_status_col(Name); +get_table_info(Name, not_accessible) -> + get_not_accessible(Name); +get_table_info(Name, index_types) -> + get_index_types(Name); +get_table_info(Name, first_accessible) -> + get_first_accessible(Name); +get_table_info(Name, first_own_index) -> + get_first_own_index(Name); +get_table_info(Name, all) -> + TableInfo = table_info(Name), + [{nbr_of_cols, TableInfo#table_info.nbr_of_cols}, + {defvals, TableInfo#table_info.defvals}, + {status_col, TableInfo#table_info.status_col}, + {not_accessible, TableInfo#table_info.not_accessible}, + {index_types, TableInfo#table_info.index_types}, + {first_accessible, TableInfo#table_info.first_accessible}, + {first_own_index, TableInfo#table_info.first_own_index}]. + + +%%----------------------------------------------------------------- +%% Description: +%% Used by user's instrum func to get the index types. +%%----------------------------------------------------------------- +get_index_types(Name) -> + #table_info{index_types = IndexTypes} = table_info(Name), + IndexTypes. + +get_nbr_of_cols(Name) -> + #table_info{nbr_of_cols = NumberOfCols} = table_info(Name), + NumberOfCols. + +get_defvals(Name) -> + #table_info{defvals = DefVals} = table_info(Name), + DefVals. + +get_status_col(Name) -> + #table_info{status_col = StatusCol} = table_info(Name), + StatusCol. + +get_not_accessible(Name) -> + #table_info{not_accessible = NotAcc} = table_info(Name), + NotAcc. + +get_first_accessible(Name) -> + #table_info{first_accessible = FirstAcc} = table_info(Name), + FirstAcc. + +get_first_own_index(Name) -> + #table_info{first_own_index = FirstOwnIdx} = table_info(Name), + FirstOwnIdx. + + +%%----------------------------------------------------------------- + +error(Reason) -> + throw({error, Reason}). + +user_err(F, A) -> + snmpa_error:user_err(F, A). diff --git a/lib/snmp/src/agent/snmp_generic_mnesia.erl b/lib/snmp/src/agent/snmp_generic_mnesia.erl new file mode 100644 index 0000000000..a73aad5b33 --- /dev/null +++ b/lib/snmp/src/agent/snmp_generic_mnesia.erl @@ -0,0 +1,400 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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(snmp_generic_mnesia). + +-export([variable_get/1, variable_set/2, variable_inc/2]). +-export([table_func/2, table_func/4, + table_set_cols/4, table_set_element/4, table_set_elements/3, + table_get_elements/4, table_get_row/2, table_get_row/3, + table_next/2,table_set_status/7, + table_try_make_consistent/2, + table_delete_row/2]). + +-include("STANDARD-MIB.hrl"). +-include("snmp_types.hrl"). +%% -include("snmp_generic.hrl"). + +%%%----------------------------------------------------------------- +%%% Generic functions for implementing software tables +%%% and variables. Mnesia is used. +%%%----------------------------------------------------------------- + +%%------------------------------------------------------------------ +%% Theses functions could be in the MIB for simple +%% variables or tables, i.e. vars without complex +%% set-operations. If there are complex set op, an +%% extra layer-function should be added, and that +%% function should be in the MIB, and it can call these +%% functions. +%%------------------------------------------------------------------ + +%%------------------------------------------------------------------ +%% Variables +%%------------------------------------------------------------------ +%%------------------------------------------------------------------ +%% This is the default function for variables. +%%------------------------------------------------------------------ +variable_get(Name) -> + case mnesia:dirty_read({snmp_variables, Name}) of + [{_Db, _Name, Val}] -> {value, Val}; + _ -> undefined + end. + +variable_set(Name, Val) -> + mnesia:dirty_write({snmp_variables, Name, Val}), + true. + +variable_inc(Name, N) -> + case mnesia:dirty_update_counter({snmp_variables, Name}, N) of + NewVal when NewVal < 4294967296 -> + ok; + NewVal -> + mnesia:dirty_write({snmp_variables, Name, NewVal rem 4294967296}) + end. + +%%------------------------------------------------------------------ +%% Tables +%% Assumes the RowStatus is the last column in the +%% table. +%%------------------------------------------------------------------ +%%------------------------------------------------------------------ +%% This is the default function for tables. +%% +%% Name is the name of the table (atom) +%% RowIndex is a flat list of the indexes for the row. +%% Cols is a list of column numbers. +%%------------------------------------------------------------------ +table_func(new, _Name) -> + ok; + +table_func(delete, _Name) -> + ok. + +table_func(get, RowIndex, Cols, Name) -> + TableInfo = snmp_generic:table_info(Name), + snmp_generic:handle_table_get({Name, mnesia}, RowIndex, Cols, + TableInfo#table_info.first_own_index); + +%%------------------------------------------------------------------ +%% Returns: List of endOfTable | {NextOid, Value}. +%% Implements the next operation, with the function +%% handle_table_next. Next should return the next accessible +%% instance, which cannot be a key (well, it could, but it +%% shouldn't). +%%------------------------------------------------------------------ +table_func(get_next, RowIndex, Cols, Name) -> + #table_info{first_accessible = FirstCol, first_own_index = FOI, + nbr_of_cols = LastCol} = snmp_generic:table_info(Name), + snmp_generic:handle_table_next({Name,mnesia},RowIndex,Cols, + FirstCol, FOI, LastCol); + +table_func(is_set_ok, RowIndex, Cols, Name) -> + snmp_generic:table_try_row({Name, mnesia}, nofunc, RowIndex, Cols); + +%%------------------------------------------------------------------ +%% Cols is here a list of {ColumnNumber, NewValue} +%% This function must only be used by tables with a RowStatus col! +%% Other tables should use table_set_cols/4. +%% All set functionality is handled within a transaction. +%% +%% GenericMnesia uses its own table_set_status and own table_try_make_consistent +%% for performance reasons. +%%------------------------------------------------------------------ +table_func(set, RowIndex, Cols, Name) -> + case mnesia:transaction( + fun() -> + snmp_generic:table_set_row( + {Name, mnesia}, nofunc, + {snmp_generic_mnesia, table_try_make_consistent}, + RowIndex, Cols) + end) of + {atomic, Value} -> + Value; + {aborted, Reason} -> + user_err("set transaction aborted. Tab ~w, RowIndex" + " ~w, Cols ~w. Reason ~w", + [Name, RowIndex, Cols, Reason]), + {Col, _Val} = hd(Cols), + {commitFailed, Col} + end; + +table_func(undo, _RowIndex, _Cols, _Name) -> + {noError, 0}. + + +table_get_row(Name, RowIndex) -> + case mnesia:snmp_get_row(Name, RowIndex) of + {ok, DbRow} -> + TableInfo = snmp_generic:table_info(Name), + make_row(DbRow, TableInfo#table_info.first_own_index); + undefined -> + undefined + end. +table_get_row(Name, RowIndex, FOI) -> + case mnesia:snmp_get_row(Name, RowIndex) of + {ok, DbRow} -> + make_row(DbRow, FOI); + undefined -> + undefined + end. + +%%----------------------------------------------------------------- +%% Returns: [Val | noacc | noinit] | undefined +%%----------------------------------------------------------------- +table_get_elements(Name, RowIndex, Cols, FirstOwnIndex) -> + case mnesia:snmp_get_row(Name, RowIndex) of + {ok, DbRow} -> + Row = make_row(DbRow, FirstOwnIndex), + get_elements(Cols, Row); + undefined -> + undefined + end. + +get_elements([Col | Cols], Row) -> + [element(Col, Row) | get_elements(Cols, Row)]; +get_elements([], _Row) -> []. + +%%----------------------------------------------------------------- +%% Args: DbRow is a mnesia row ({name, Keys, Cols, ...}). +%% Returns: A tuple with a SNMP-table row. Each SNMP-col is one +%% element, list or int. +%%----------------------------------------------------------------- +make_row(DbRow, 0) -> + [_Name, _Keys | Cols] = tuple_to_list(DbRow), + list_to_tuple(Cols); +make_row(DbRow, FirstOwnIndex) -> + list_to_tuple(make_row2(make_row_list(DbRow), FirstOwnIndex)). +make_row2(RowList, 1) -> RowList; +make_row2([_OtherIndex | RowList], N) -> + make_row2(RowList, N-1). + +make_row_list(Row) -> + make_row_list(size(Row), Row, []). +make_row_list(N, Row, Acc) when N > 2 -> + make_row_list(N-1, Row, [element(N, Row) | Acc]); +make_row_list(2, Row, Acc) -> + case element(2, Row) of + Keys when is_tuple(Keys) -> + lists:append(tuple_to_list(Keys), Acc); + Key -> + [Key | Acc] + end. + +%% createAndGo +table_set_status(Name, RowIndex, ?'RowStatus_createAndGo', _StatusCol, Cols, + ChangedStatusFunc, _ConsFunc) -> + Row = table_construct_row(Name, RowIndex, ?'RowStatus_active', Cols), + mnesia:write(Row), + snmp_generic:try_apply(ChangedStatusFunc, [Name, ?'RowStatus_createAndGo', + RowIndex, Cols]); + +%%------------------------------------------------------------------ +%% createAndWait - set status to notReady, and try to +%% make row consistent. +%%------------------------------------------------------------------ +table_set_status(Name, RowIndex, ?'RowStatus_createAndWait', _StatusCol, + Cols, ChangedStatusFunc, ConsFunc) -> + Row = table_construct_row(Name, RowIndex, ?'RowStatus_notReady', Cols), + mnesia:write(Row), + case snmp_generic:try_apply(ConsFunc, [RowIndex, Row]) of + {noError, 0} -> snmp_generic:try_apply(ChangedStatusFunc, + [Name, ?'RowStatus_createAndWait', + RowIndex, Cols]); + Error -> Error + end; + +%% destroy +table_set_status(Name, RowIndex, ?'RowStatus_destroy', _StatusCol, Cols, + ChangedStatusFunc, _ConsFunc) -> + case snmp_generic:try_apply(ChangedStatusFunc, + [Name, ?'RowStatus_destroy', RowIndex, Cols]) of + {noError, 0} -> + #table_info{index_types = Indexes} = snmp_generic:table_info(Name), + Key = + case snmp_generic:split_index_to_keys(Indexes, RowIndex) of + [Key1] -> Key1; + KeyList -> list_to_tuple(KeyList) + end, + mnesia:delete({Name, Key}), + {noError, 0}; + Error -> Error + end; + +%% Otherwise, active or notInService +table_set_status(Name, RowIndex, Val, _StatusCol, Cols, + ChangedStatusFunc, ConsFunc) -> + table_set_cols(Name, RowIndex, Cols, ConsFunc), + snmp_generic:try_apply(ChangedStatusFunc, [Name, Val, RowIndex, Cols]). + +table_delete_row(Name, RowIndex) -> + case mnesia:snmp_get_mnesia_key(Name, RowIndex) of + {ok, Key} -> + mnesia:delete({Name, Key}); + undefined -> + ok + end. + + +%%------------------------------------------------------------------ +%% This function is a simple consistency check +%% function which could be used by the user-defined +%% table functions. +%% Check if the row has all information needed to +%% make row notInService (from notReady). This is +%% a simple check, which just checks if some col +%% in the row has the value 'noinit'. +%% If it has the information, the status is changed +%% to notInService. +%%------------------------------------------------------------------ +table_try_make_consistent(RowIndex, NewDbRow) -> + Name = element(1, NewDbRow), + #table_info{first_own_index = FirstOwnIndex, + status_col = StatusCol, index_types = IT} = + snmp_generic:table_info(Name), + if + is_integer(StatusCol) -> + NewRow = make_row(NewDbRow, FirstOwnIndex), + StatusVal = element(StatusCol, NewRow), + AddCol = if + FirstOwnIndex == 0 -> 2; + true -> 1 + FirstOwnIndex - length(IT) + end, + table_try_make_consistent(Name, RowIndex, NewRow, NewDbRow, + AddCol, StatusCol, StatusVal); + true -> + {noError, 0} + end. + + +table_try_make_consistent(Name, RowIndex, NewRow, NewDbRow, + AddCol, StatusCol, ?'RowStatus_notReady') -> + case lists:member(noinit, tuple_to_list(NewRow)) of + true -> {noError, 0}; + false -> + table_set_element(Name, RowIndex, StatusCol, + ?'RowStatus_notInService'), + NewDbRow2 = set_new_row([{StatusCol, ?'RowStatus_notInService'}], + AddCol, NewDbRow), + mnesia:write(NewDbRow2), + {noError, 0} + end; + +table_try_make_consistent(_Name, _RowIndex, _NewRow, _NewDBRow, + _AddCol, _StatusCol, _StatusVal) -> + {noError, 0}. + +%%------------------------------------------------------------------ +%% Constructs a row that is to be stored in Mnesia, i.e. +%% {Name, Key, Col1, ...} | +%% {Name, {Key1, Key2, ..}, ColN, ColN+1...} +%% dynamic key values are stored without length first. +%% RowIndex is a list of the first elements. RowStatus is needed, +%% because the provided value may not be stored, e.g. createAndGo +%% should be active. If a value isn't specified in the Col list, +%% then the corresponding value will be noinit. +%%------------------------------------------------------------------ +table_construct_row(Name, RowIndex, Status, Cols) -> + #table_info{nbr_of_cols = LastCol, index_types = Indexes, + defvals = Defs, status_col = StatusCol, + first_own_index = FirstOwnIndex, not_accessible = NoAccs} = + snmp_generic:table_info(Name), + KeyList = snmp_generic:split_index_to_keys(Indexes, RowIndex), + OwnKeyList = snmp_generic:get_own_indexes(FirstOwnIndex, KeyList), + StartCol = length(OwnKeyList) + 1, + RowList = snmp_generic:table_create_rest(StartCol, LastCol, + StatusCol, Status, Cols, NoAccs), + L = snmp_generic:init_defaults(Defs, RowList, StartCol), + Keys = case KeyList of + [H] -> H; + _ -> list_to_tuple(KeyList) + end, + list_to_tuple([Name, Keys | L]). + +%%------------------------------------------------------------------ +%% table_set_cols/4 +%% can be used by the set procedure of all tables +%% to set all columns in Cols, one at a time. +%% ConsFunc is a check-consistency function, which will +%% be called with the RowIndex of this row, when +%% all columns are set. This is useful when the RowStatus +%% could change, e.g. if the manager has provided all +%% mandatory columns in this set operation. +%% If ConsFunc is nofunc, no function will be called after all +%% sets. +%% Returns: {noError, 0} | {Error, Col} +%%------------------------------------------------------------------ +table_set_cols(Name, RowIndex, Cols, ConsFunc) -> + table_set_elements(Name, RowIndex, Cols, ConsFunc). + +%%----------------------------------------------------------------- +%% Col is _not_ a key column. A row in the db is stored as +%% {Name, {Key1, Key2,...}, Col1, Col2, ...} +%%----------------------------------------------------------------- +table_set_element(Name, RowIndex, Col, NewVal) -> + #table_info{index_types = Indexes, first_own_index = FirstOwnIndex} = + snmp_generic:table_info(Name), + DbCol = if + FirstOwnIndex == 0 -> Col + 2; + true -> 1 + FirstOwnIndex - length(Indexes) + Col + end, + case mnesia:snmp_get_row(Name, RowIndex) of + {ok, DbRow} -> + NewDbRow = setelement(DbCol, DbRow, NewVal), + mnesia:write(NewDbRow), + true; + undefined -> + false + end. + +table_set_elements(Name, RowIndex, Cols) -> + case table_set_elements(Name, RowIndex, Cols, nofunc) of + {noError, 0} -> true; + _ -> false + end. +table_set_elements(Name, RowIndex, Cols, ConsFunc) -> + #table_info{index_types = Indexes, first_own_index = FirstOwnIndex} = + snmp_generic:table_info(Name), + AddCol = if + FirstOwnIndex == 0 -> 2; + true -> 1 + FirstOwnIndex - length(Indexes) + end, + case mnesia:snmp_get_row(Name, RowIndex) of + {ok, DbRow} -> + NewDbRow = set_new_row(Cols, AddCol, DbRow), + mnesia:write(NewDbRow), + snmp_generic:try_apply(ConsFunc, [RowIndex, NewDbRow]); + undefined -> + {Col, _Val} = hd(Cols), + {commitFailed, Col} + end. + +set_new_row([{Col, Val} | Cols], AddCol, Row) -> + set_new_row(Cols, AddCol, setelement(Col+AddCol, Row, Val)); +set_new_row([], _AddCol, Row) -> + Row. + +table_next(Name, RestOid) -> + case mnesia:snmp_get_next_index(Name, RestOid) of + {ok, NextIndex} -> NextIndex; + endOfTable -> endOfTable + end. + + +user_err(F, A) -> + snmpa_error:user_err(F, A). diff --git a/lib/snmp/src/agent/snmp_index.erl b/lib/snmp/src/agent/snmp_index.erl new file mode 100644 index 0000000000..1902fe2613 --- /dev/null +++ b/lib/snmp/src/agent/snmp_index.erl @@ -0,0 +1,163 @@ +%% +%% %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(snmp_index). + +-export([new/1, new/2, + insert/3, + delete/1, delete/2, + get/2, get_next/2, + get_last/1, + key_to_oid/2]). + + +-define(VMODULE,"IDX"). +-include("snmp_verbosity.hrl"). + +-record(tab, {id, keys}). + +-define(badarg(F, A), exit({badarg, {?MODULE, F, A}})). +-define(bad_new(A), ?badarg(new, A)). +-define(bad_get(A), ?badarg(get, A)). + + +%%%----------------------------------------------------------------- +%%% This module implements an SNMP index structure as an ADT. +%%% It is supposed to be used as a separate structure which implements +%%% the SNMP ordering of the keys in the SNMP table. The advantage +%%% with this is that the get-next operation is automatically +%%% taken care of. +%%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Args: KeyTypes = key() | {key(), ...} +%% key() = integer | string | fix_string +%% Returns: handle() +%%----------------------------------------------------------------- + +new(KeyTypes) -> + ?vlog("new -> entry with" + "~n KeyTypes: ~p", [KeyTypes]), + do_new(KeyTypes, ?MODULE, [public, ordered_set]). + +new(KeyTypes, Name) when is_atom(Name) -> + ?vlog("new -> entry with" + "~n KeyTypes: ~p" + "~n Name: ~p", [KeyTypes, Name]), + do_new(KeyTypes, Name, [public, ordered_set, named_table]); +new(KeyTypes, Name) -> + ?vinfo("new -> bad data" + "~n KeyTypes: ~p" + "~n Name: ~p", [KeyTypes, Name]), + ?bad_new([KeyTypes, Name]). + +do_new(KeyTypes, EtsName, EtsOpts) -> + ?vdebug("do_new -> entry with" + "~n KeyTypes: ~p" + "~n EtsName: ~p" + "~n EtsOpts: ~p", [KeyTypes, EtsName, EtsOpts]), + case is_snmp_type(to_list(KeyTypes)) of + true -> + Tab = #tab{id = ets:new(EtsName, EtsOpts), keys = KeyTypes}, + ?vtrace("do_new -> " + "~n Tab: ~p", [Tab]), + Tab; + false -> + ?bad_new([KeyTypes, EtsName]) + end. + + +get(#tab{id = OrdSet}, KeyOid) -> + ?vlog("get -> entry with" + "~n OrdSet: ~p" + "~n KeyOid: ~p", [OrdSet, KeyOid]), + case ets:lookup(OrdSet, KeyOid) of + [X] -> + {ok, X}; + _ -> + undefined + end. + + + +get_next(#tab{id = OrdSet} = Tab, KeyOid) -> + ?vlog("get_next -> entry with" + "~n Tab: ~p" + "~n KeyOid: ~p", [Tab, KeyOid]), + case ets:next(OrdSet, KeyOid) of + '$end_of_table' -> + undefined; + Key -> + get(Tab, Key) + end. + +get_last(#tab{id = OrdSet} = Tab) -> + ?vlog("get_last -> entry with" + "~n Tab: ~p", [Tab]), + case ets:last(OrdSet) of + '$end_of_table' -> + undefined; + Key -> + get(Tab, Key) + end. + +insert(#tab{id = OrdSet, keys = KeyTypes} = Tab, Key, Val) -> + ets:insert(OrdSet, {key_to_oid_i(Key, KeyTypes), Val}), + Tab. + +delete(#tab{id = OrdSet, keys = KeyTypes} = Tab, Key) -> + ets:delete(OrdSet, key_to_oid_i(Key, KeyTypes)), + Tab. + +delete(#tab{id = OrdSet}) -> + ets:delete(OrdSet). + +key_to_oid(#tab{keys = KeyTypes}, Key) -> + key_to_oid_i(Key, KeyTypes). + +to_list(Tuple) when is_tuple(Tuple) -> tuple_to_list(Tuple); +to_list(X) -> [X]. + +is_snmp_type([integer | T]) -> is_snmp_type(T); +is_snmp_type([string | T]) -> is_snmp_type(T); +is_snmp_type([fix_string | T]) -> is_snmp_type(T); +is_snmp_type([]) -> true; +is_snmp_type(_) -> false. + + +%%----------------------------------------------------------------- +%% Args: Key = key() +%% key() = int() | string() | {int() | string(), ...} +%% Type = {fix_string | term()} +%% Make an OBJECT IDENTIFIER out of it. +%% Variable length objects are prepended by their length. +%% Ex. Key = {"pelle", 42} AND Type = {string, integer} => +%% OID [5, $p, $e, $l, $l, $e, 42] +%% Key = {"pelle", 42} AND Type = {fix_string, integer} => +%% OID [$p, $e, $l, $l, $e, 42] +%%----------------------------------------------------------------- +key_to_oid_i(Key, _Type) when is_integer(Key) -> [Key]; +key_to_oid_i(Key, fix_string) -> Key; +key_to_oid_i(Key, _Type) when is_list(Key) -> [length(Key) | Key]; +key_to_oid_i(Key, Types) -> keys_to_oid(size(Key), Key, [], Types). + +keys_to_oid(0, _Key, Oid, _Types) -> Oid; +keys_to_oid(N, Key, Oid, Types) -> + Oid2 = lists:append(key_to_oid_i(element(N, Key), element(N, Types)), Oid), + keys_to_oid(N-1, Key, Oid2, Types). + diff --git a/lib/snmp/src/agent/snmp_notification_mib.erl b/lib/snmp/src/agent/snmp_notification_mib.erl new file mode 100644 index 0000000000..16e43f05d7 --- /dev/null +++ b/lib/snmp/src/agent/snmp_notification_mib.erl @@ -0,0 +1,457 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-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(snmp_notification_mib). + +-export([configure/1, reconfigure/1, invalidate_cache/0, + snmpNotifyTable/1, snmpNotifyTable/3, + snmpNotifyFilterTable/3, snmpNotifyFilterProfileTable/3, + get_targets/0, get_targets/1]). +-export([add_notify/3, delete_notify/1]). +-export([check_notify/1]). + +-include("SNMP-NOTIFICATION-MIB.hrl"). +-include("SNMPv2-TC.hrl"). +-include("snmp_tables.hrl"). + +-define(VMODULE,"NOTIFICATION-MIB"). +-include("snmp_verbosity.hrl"). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + + +%%----------------------------------------------------------------- +%% 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 notify 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(snmpNotifyTable) of + {_, mnesia} -> + ?vlog("notification table in mnesia: cleanup",[]), + gc_tabs(); + TabDb -> + case snmpa_local_db:table_exists(TabDb) of + true -> + ?vlog("notification table exist: cleanup",[]), + gc_tabs(); + false -> + ?vlog("notification 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 notify 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 notify config files",[]), + Notifs = read_notify_config_files(Dir), + init_tabs(Notifs), + ?vdebug("invalidate cache",[]), + invalidate_cache(), + ok. + + +read_notify_config_files(Dir) -> + ?vdebug("read notify config file",[]), + Gen = fun(_) -> ok end, + Filter = fun(Notifs) -> Notifs end, + Check = fun(Entry) -> check_notify(Entry) end, + [Notifs] = + snmp_conf:read_files(Dir, [{Gen, Filter, Check, "notify.conf"}]), + Notifs. + +check_notify({Name, Tag, Type}) -> + snmp_conf:check_string(Name,{gt,0}), + snmp_conf:check_string(Tag), + {ok, Val} = snmp_conf:check_atom(Type, [{trap, 1}, {inform, 2}]), + Notify = {Name, Tag, Val, + ?'StorageType_nonVolatile', ?'RowStatus_active'}, + {ok, Notify}; +check_notify(X) -> + error({invalid_notify, X}). + + +init_tabs(Notifs) -> + ?vdebug("create notify table",[]), + snmpa_local_db:table_delete(db(snmpNotifyTable)), + snmpa_local_db:table_create(db(snmpNotifyTable)), + ?vdebug("initiate notify table",[]), + init_notify_table(Notifs). + +init_notify_table([Row | T]) -> + Key = element(1, Row), + snmpa_local_db:table_create_row(db(snmpNotifyTable), Key, Row), + init_notify_table(T); +init_notify_table([]) -> 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_notify(Name, Tag, Type) -> + Notif = {Name, Tag, Type}, + case (catch check_notify(Notif)) of + {ok, Row} -> + Key = element(1, Row), + case table_cre_row(snmpNotifyTable, Key, Row) of + true -> + {ok, Key}; + false -> + {error, create_failed} + end; + {error, Reason} -> + {error, Reason}; + Error -> + {error, Error} + end. + +%% FIXME: does not work with mnesia +delete_notify(Key) -> + case table_del_row(snmpNotifyTable, Key) of + true -> + ok; + false -> + {error, delete_failed} + end. + +gc_tabs() -> + DB = db(snmpNotifyTable), + STC = stc(snmpNotifyTable), + FOI = foi(snmpNotifyTable), + snmpa_mib_lib:gc_tab(DB, STC, FOI), + ok. + + +%%----------------------------------------------------------------- +%% Func: get_targets() +%% get_targets(NotifyName) -> [Target] +%% Types: Target = {DestAddr, TargetName, TargetParams, NotifyType} +%% NotifyName = string() - the INDEX +%% DestAddr = {TDomain, TAddr} +%% TagrgetName = string() +%% TargetParams = {MpModel, SecModel, SecName, SecLevel} +%% NotifyType = trap | {inform, Timeout, Retry} +%% Purpose: Returns a list of all targets. Called by snmpa_trap +%% when a trap should be sent. +%% If a NotifyName is specified, the targets for that +%% name is returned. +%%----------------------------------------------------------------- +get_targets() -> + TargetsFun = fun find_targets/0, + snmpa_target_cache:targets(TargetsFun). + +get_targets(NotifyName) -> + TargetsFun = fun find_targets/0, + snmpa_target_cache:targets(TargetsFun, NotifyName). + + +%%----------------------------------------------------------------- +%% We use a cache of targets to avoid searching the tables each +%% time a trap is sent. When some of the 3 tables (notify, +%% targetAddr, targetParams) is modified, the cache is invalidated. +%%----------------------------------------------------------------- + +invalidate_cache() -> + snmpa_target_cache:invalidate(). + + +%% Ret: [{NotifyName, {DestAddr, TargetName, TargetParams, NotifyType}}] +%% NotifyType = trap | {inform, Timeout. Retry} +%% DestAddr = {Domain, Addr} ; e.g. {snmpUDPDomain, {IPasList, UdpPort}} + +find_targets() -> + TargAddrs = snmp_target_mib:get_target_addrs(), + %% TargAddrs = [{TagList,DestAddr,TargetName,TargetParams,Timeout,Retry}] + {_, Db} = db(snmpNotifyTable), + find_targets([], TargAddrs, Db, []). +find_targets(Key, TargAddrs, Db, Res) -> + case table_next(snmpNotifyTable, Key) of + endOfTable -> + Res; + NextKey when Db == mnesia -> + case mnesia:snmp_get_row(snmpNotifyTable, NextKey) of + {ok, #snmpNotifyTable{ + snmpNotifyTag = Tag, + snmpNotifyType = Type, + snmpNotifyRowStatus = ?'RowStatus_active'}} -> + ?vtrace("found notify entry for ~w" + "~n Tag: ~w" + "~n Type: ~w", [NextKey, Tag, Type]), + Targs = get_targets(TargAddrs, Tag, Type, NextKey), + find_targets(NextKey, TargAddrs, Db, Targs ++ Res); + {ok, #snmpNotifyTable{ + snmpNotifyTag = Tag, + snmpNotifyType = Type, + snmpNotifyRowStatus = RowStatus}} -> + ?vtrace("found invalid notify entry for ~w" + "~n Tag: ~w" + "~n Type: ~w" + "~n RowStatus: ~p", + [NextKey, Tag, Type, RowStatus]), + find_targets(NextKey, TargAddrs, Db, Res); + _ -> + ?vtrace("notify entry not found for ~w", [NextKey]), + find_targets(NextKey, TargAddrs, Db, Res) + end; + NextKey -> + Elements = [?snmpNotifyTag, ?snmpNotifyType, ?snmpNotifyRowStatus], + case snmpNotifyTable(get, NextKey, Elements) of + [{value, Tag}, {value, Type}, {value, ?'RowStatus_active'}] -> + ?vtrace("found notify entry for ~w" + "~n Tag: ~w" + "~n Type: ~w", [NextKey, Tag, Type]), + Targs = get_targets(TargAddrs, Tag, Type, NextKey), + find_targets(NextKey, TargAddrs, Db, Targs ++ Res); + [{value, Tag1}, {value, Type1}, {value, RowStatus}] -> + ?vtrace("found invalid notify entry for ~w" + "~n Tag: ~w" + "~n Type: ~w" + "~n RowStatus: ~w", + [NextKey, Tag1, Type1, RowStatus]), + find_targets(NextKey, TargAddrs, Db, Res); + _ -> + ?vtrace("notify entry not found for ~w", [NextKey]), + find_targets(NextKey, TargAddrs, Db, Res) + end + end. + +get_targets([{TagList, Addr, TargetName, Params, Timeout, Retry}|T], + Tag, Type, Name) -> + case snmp_misc:is_tag_member(Tag, TagList) of + true -> [{Name, {Addr, TargetName, Params, type(Type, Timeout, Retry)}}| + get_targets(T, Tag, Type, Name)]; + false -> + get_targets(T, Tag, Type, Name) + end; +get_targets([], _Tag, _Type, _Name) -> + []. + +type(trap, _, _) -> trap; +type(1, _, _) -> trap; %% OTP-4329 +type(inform, Timeout, Retry) -> {inform, Timeout, Retry}; +type(2, Timeout, Retry) -> {inform, Timeout, Retry}. %% OTP-4329 + + +%%----------------------------------------------------------------- +%% Instrumentation Functions +%%----------------------------------------------------------------- +%% Op = print - Used for debugging purposes +snmpNotifyTable(print) -> + Table = snmpNotifyTable, + DB = db(Table), + FOI = foi(Table), + PrintRow = + fun(Prefix, Row) -> + lists:flatten( + io_lib:format("~sName: ~p" + "~n~sTag: ~p" + "~n~sType: ~p (~w)" + "~n~sStorageType: ~p (~w)" + "~n~sStatus: ~p (~w)", + [Prefix, element(?snmpNotifyName, Row), + Prefix, element(?snmpNotifyTag, Row), + Prefix, element(?snmpNotifyType, Row), + case element(?snmpNotifyType, Row) of + ?snmpNotifyType_inform -> inform; + ?snmpNotifyType_trap -> trap; + _ -> undefined + end, + Prefix, element(?snmpNotifyStorageType, Row), + case element(?snmpNotifyStorageType, Row) of + ?'snmpNotifyStorageType_readOnly' -> readOnly; + ?'snmpNotifyStorageType_permanent' -> permanent; + ?'snmpNotifyStorageType_nonVolatile' -> nonVolatile; + ?'snmpNotifyStorageType_volatile' -> volatile; + ?'snmpNotifyStorageType_other' -> other; + _ -> undefined + end, + Prefix, element(?snmpNotifyRowStatus, Row), + case element(?snmpNotifyRowStatus, Row) of + ?'snmpNotifyRowStatus_destroy' -> destroy; + ?'snmpNotifyRowStatus_createAndWait' -> createAndWait; + ?'snmpNotifyRowStatus_createAndGo' -> createAndGo; + ?'snmpNotifyRowStatus_notReady' -> notReady; + ?'snmpNotifyRowStatus_notInService' -> notInService; + ?'snmpNotifyRowStatus_active' -> active; + _ -> undefined + end])) + end, + snmpa_mib_lib:print_table(Table, DB, FOI, PrintRow); +%% Op == new | delete +snmpNotifyTable(Op) -> + snmp_generic:table_func(Op, db(snmpNotifyTable)). + +%% Op == get | is_set_ok | set | get_next +snmpNotifyTable(get, RowIndex, Cols) -> + %% BMK BMK BMK BMK + get(snmpNotifyTable, RowIndex, Cols); +snmpNotifyTable(get_next, RowIndex, Cols) -> + %% BMK BMK BMK BMK + next(snmpNotifyTable, RowIndex, Cols); +snmpNotifyTable(set, RowIndex, Cols0) -> + %% BMK BMK BMK BMK + case (catch verify_snmpNotifyTable_cols(Cols0, [])) of + {ok, Cols} -> + invalidate_cache(), + %% invalidate_cache(RowIndex), + Db = db(snmpNotifyTable), + snmp_generic:table_func(set, RowIndex, Cols, Db); + Error -> + Error + end; +snmpNotifyTable(is_set_ok, RowIndex, Cols0) -> + case (catch verify_snmpNotifyTable_cols(Cols0, [])) of + {ok, Cols} -> + Db = db(snmpNotifyTable), + snmp_generic:table_func(is_set_ok, RowIndex, Cols, Db); + Error -> + Error + end; +snmpNotifyTable(Op, Arg1, Arg2) -> + snmp_generic:table_func(Op, Arg1, Arg2, db(snmpNotifyTable)). + + +verify_snmpNotifyTable_cols([], Cols) -> + {ok, lists:reverse(Cols)}; +verify_snmpNotifyTable_cols([{Col, Val0}|Cols], Acc) -> + Val = verify_snmpNotifyTable_col(Col, Val0), + verify_snmpNotifyTable_cols(Cols, [{Col, Val}|Acc]). + +verify_snmpNotifyTable_col(?snmpNotifyName, Name) -> + case (catch snmp_conf:check_string(Name, {gt, 0})) of + ok -> + Name; + _ -> + wrongValue(?snmpNotifyName) + end; +verify_snmpNotifyTable_col(?snmpNotifyTag, Tag) -> + case (catch snmp_conf:check_string(Tag)) of + ok -> + Tag; + _ -> + wrongValue(?snmpNotifyTag) + end; +verify_snmpNotifyTable_col(?snmpNotifyType, Type) -> + case Type of + trap -> 1; + inform -> 2; + 1 -> 1; + 2 -> 2; + _ -> wrongValue(?snmpNotifyType) + end; +verify_snmpNotifyTable_col(_, Val) -> + Val. + + +%%----------------------------------------------------------------- +%% In this version of the agent, we don't support notification +%% filters. +%%----------------------------------------------------------------- +snmpNotifyFilterTable(get, _RowIndex, Cols) -> + lists:map(fun(_Col) -> {noValue, noSuchObject} end, Cols); +snmpNotifyFilterTable(get_next, _RowIndex, Cols) -> + lists:map(fun(_Col) -> endOfTable end, Cols); +snmpNotifyFilterTable(is_set_ok, _RowIndex, Cols) -> + {notWritable, element(1, hd(Cols))}. + +snmpNotifyFilterProfileTable(get, _RowIndex, Cols) -> + lists:map(fun(_Col) -> {noValue, noSuchObject} end, Cols); +snmpNotifyFilterProfileTable(get_next, _RowIndex, Cols) -> + lists:map(fun(_Col) -> endOfTable end, Cols); +snmpNotifyFilterProfileTable(is_set_ok, _RowIndex, Cols) -> + {notWritable, element(1, hd(Cols))}. + + +db(X) -> snmpa_agent:db(X). + +fa(snmpNotifyTable) -> ?snmpNotifyTag. + +foi(snmpNotifyTable) -> ?snmpNotifyName. + +noc(snmpNotifyTable) -> 5. + +stc(snmpNotifyTable) -> ?snmpNotifyStorageType. + +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)). + + +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}). + +config_err(F, A) -> + snmpa_error:config_err("[NOTIFICATION-MIB]: " ++ F, A). diff --git a/lib/snmp/src/agent/snmp_shadow_table.erl b/lib/snmp/src/agent/snmp_shadow_table.erl new file mode 100644 index 0000000000..34543d542b --- /dev/null +++ b/lib/snmp/src/agent/snmp_shadow_table.erl @@ -0,0 +1,187 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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(snmp_shadow_table). + +-export([table_func/2, table_func/4]). + +-include("snmpa_internal.hrl"). + +-record(time_stamp, {key, data}). + +-define(verify(Expr, Error), verify(catch Expr, Error, ?FILE, ?LINE)). + +verify(Res, Error, File, Line) -> + case Res of + {atomic, _} -> + Res; + ok -> + Res; + _ -> + error_msg("~s(~w): crashed ~p -> ~p ~p~n", + [File, Line, Error, Res, process_info(self())]), + Res + end. + + +%%%----------------------------------------------------------------- +%%% This module contains generic functions for implementing an SNMP +%%% table as a 'shadow'-table in Mnesia. This means that for the +%%% SNMP table, there exists one Mnesia table with all information +%%% of the table. +%%% The Mnesia table is updated whenever an SNMP request is issued +%%% for the table and a specified amount of time has past since the +%%% last update. +%%% This is implemented as instrumentation functions to be used +%%% for the table. +%%%----------------------------------------------------------------- + +create_time_stamp_table() -> + Props = [{type, set}, + {attributes, record_info(fields, time_stamp)}], + create_table(time_stamp, Props, ram_copies, false), + NRef = + case mnesia:dirty_read({time_stamp, ref_count}) of + [] -> 1; + [#time_stamp{data = Ref}] -> Ref + 1 + end, + ok = mnesia:dirty_write(#time_stamp{key = ref_count, data = NRef}). + +delete_time_stamp_table() -> + Tab = time_stamp, + case catch mnesia:dirty_read({Tab, ref_count}) of + {'EXIT', _Reason} -> + delete_table(Tab); + [] -> + delete_table(Tab); + [#time_stamp{data = 1}] -> + delete_table(Tab); + [#time_stamp{data = Ref}] -> + ok = mnesia:dirty_write(#time_stamp{key = ref_count, data = Ref - 1}) + end. + +update(Name, UpdateFunc, Interval) -> + CurrentTime = get_time(), + case mnesia:dirty_read({time_stamp, Name}) of + [#time_stamp{data = Expire}] when CurrentTime =< Expire -> ok; + _ -> + UpdateFunc(), + ok = mnesia:dirty_write(#time_stamp{key = Name, + data = CurrentTime + Interval}) + end. + + +%%----------------------------------------------------------------- +%% Func: table_func(Op, Extra) +%% table_func(Op, RowIndex, Cols, Extra) +%% Args: Extra = {Name, SnmpKey, Attributes, Interval, UpdateFunc} +%% Purpose: Instrumentation function for the table. +%% Name is the name of the table +%% SnmpKey is the snmpkey as it should be specifed in order +%% to create the Mnesia table as an SNMP table +%% Attributes is the attributes as it should be specifed in order +%% to create the Mnesia table as an SNMP table +%% Interval is the minimum time in milliseconds between two +%% updates of the table +%% UpdateFunc is a function with no arguments that is called +%% whenever the table must be updated +%% Returns: As specified for an SNMP table instrumentation function. +%%----------------------------------------------------------------- +table_func(new, {Name, SnmpKey, Attribs, _Interval, _UpdateFunc}) -> + create_time_stamp_table(), + Props = [{type, set}, + {snmp, [{key, SnmpKey}]}, + {attributes, Attribs}], + create_table(Name, Props, ram_copies, true); +table_func(delete, {Name, _SnmpKey, _Attribs, _Interval, _UpdateFunc}) -> + delete_time_stamp_table(), + delete_table(Name). + +table_func(Op, RowIndex, Cols, + {Name, _SnmpKey, _Attribs, Interval, UpdateFunc}) -> + update(Name, UpdateFunc, Interval), + snmp_generic:table_func(Op, RowIndex, Cols, {Name, mnesia}). + +get_time() -> + {M,S,U} = erlang:now(), + 1000000000 * M + 1000 * S + (U div 1000). + +%%----------------------------------------------------------------- +%% Urrk. +%% We want named tables, without schema info; the tables should +%% be locally named, but if the node crashes, info about the +%% table shouldn't be kept. We could use ets tables for this. +%% BUT, we also want the snmp functionality, therefore we must +%% use mnesia. +%% The problem arises when the node that implements these tables +%% crashes, and another node takes over the MIB-implementations. +%% That node cannot create the shadow tables again, because they +%% already exist (according to mnesia...). Therefore, we must +%% check if we maybe must delete the table first, and then create +%% it again. +%%----------------------------------------------------------------- +create_table(Tab, Props, Storage, DeleteAll) -> + case lists:member(Tab, mnesia:system_info(tables)) of + true -> + case mnesia:table_info(Tab, storage_type) of + unknown -> + ?verify(mnesia:add_table_copy(Tab, node(), Storage), + [add_table_copy, Tab, node(), Storage]); + Storage when DeleteAll == true -> + delete_all(Tab); + _ -> + ignore + end; + false -> + Nodes = [node()], + Props2 = [{local_content, true}, {Storage, Nodes}] ++ Props, + ?verify(mnesia:create_table(Tab, Props2), + [create_table, Tab, Props2]) + end. + +delete_all(Tab) -> + delete_all(mnesia:dirty_first(Tab), Tab). + +delete_all('$end_of_table', _Tab) -> + ok; +delete_all(Key, Tab) -> + ok = mnesia:dirty_delete({Tab, Key}), + delete_all(mnesia:dirty_next(Tab, Key), Tab). + +delete_table(Tab) -> + case lists:member(Tab, mnesia:system_info(tables)) of + true -> + case ?verify(mnesia:del_table_copy(Tab, node()), + [del_table_copy, Tab, node()]) of + {atomic, ok} -> + ok; + {aborted, _Reason} -> + catch delete_all(Tab), + ok + end; + false -> + ok + end. + + +%%----------------------------------------------------------------- + +error_msg(F, A) -> + ?snmpa_error(F, A). + + diff --git a/lib/snmp/src/agent/snmp_standard_mib.erl b/lib/snmp/src/agent/snmp_standard_mib.erl new file mode 100644 index 0000000000..3928a8afe6 --- /dev/null +++ b/lib/snmp/src/agent/snmp_standard_mib.erl @@ -0,0 +1,316 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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(snmp_standard_mib). + +%%%----------------------------------------------------------------- +%%% This module implements the configure- and reinit-functions +%%% for the STANDARD-MIB and SNMPv2-MIB. +%%%----------------------------------------------------------------- + +-include("snmp_types.hrl"). +-include("STANDARD-MIB.hrl"). + +-define(VMODULE,"STANDARD-MIB"). +-include("snmp_verbosity.hrl"). + +-define(enabled, 1). +-define(disabled, 2). + +%% External exports +-export([configure/1, reconfigure/1, reset/0, sys_up_time/0, sys_up_time/1, + snmp_enable_authen_traps/1, snmp_enable_authen_traps/2, + sys_object_id/1, sys_object_id/2, sys_or_table/3, + variable_func/1, variable_func/2, + inc/1, inc/2]). +-export([dummy/1, snmp_set_serial_no/1, snmp_set_serial_no/2]). +-export([add_agent_caps/2, del_agent_caps/1, get_agent_caps/0]). +-export([check_standard/1]). + + +%%----------------------------------------------------------------- +%% Func: configure/1 +%% Args: Dir is the directory with trailing dir_separator where +%% the configuration files can be found. +%% Purpose: Reads the config-files for the standard mib, and +%% inserts the data. Persistent data that is already +%% present is *not* changed! (use reconfigure for that) +%% Returns: ok +%% Fails: exit(configuration_error) +%%----------------------------------------------------------------- +configure(Dir) -> + case (catch do_configure(Dir)) of + ok -> + ok; + {error, Reason} -> + ?vinfo("configure error: ~p", [Reason]), + config_err("configure failed: ~p", [Reason]), + exit(configuration_error); + Error -> + ?vinfo("configure failed: ~p", [Error]), + config_err("configure failed: ~p", [Error]), + exit(configuration_error) + end. + +do_configure(Dir) -> + case snmpa_agent:get_agent_mib_storage() of + mnesia -> + ok; + _ -> + Standard = read_standard(Dir), + lists:map(fun maybe_create_persistent_var/1, Standard) + end, + snmpa_local_db:variable_set({next_sys_or_index, volatile}, 1), + %% sysORTable is always volatile + snmp_generic:table_func(new, {sysORTable, volatile}), + ok. + + +%%----------------------------------------------------------------- +%% Func: reconfigure/1 +%% Args: Dir is the directory with trailing dir_separator where +%% the configuration files can be found. +%% Purpose: Reads the config-files for the standard mib, and +%% inserts the data. Persistent data that is already +%% present is deleted. Makes sure the config file +%% data is used. +%% 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) -> + Standard = read_standard(Dir), + lists:map(fun create_persistent_var/1, Standard), + snmpa_local_db:variable_set({next_sys_or_index, volatile}, 1), + snmp_generic:table_func(new, {sysORTable, volatile}), + ok. + + +%%----------------------------------------------------------------- +%% Func: read_standard/1 +%% Args: Dir is the directory with trailing dir_separator where +%% the configuration files can be found. +%% Purpose: Reads th standard configuration file. +%% Returns: A list of standard variables +%% Fails: If an error occurs, the process will die with Reason +%% configuration_error. +%%----------------------------------------------------------------- +read_standard(Dir) -> + ?vdebug("check standard config file",[]), + Gen = fun(_) -> ok end, + Filter = fun(Standard) -> sort_standard(Standard) end, + Check = fun(Entry) -> check_standard(Entry) end, + [Standard] = + snmp_conf:read_files(Dir, [{Gen, Filter, Check, "standard.conf"}]), + Standard. + + +%%----------------------------------------------------------------- +%% Make sure that each mandatory standard attribute is present, and +%% provide default values for the other non-present attributes. +%%----------------------------------------------------------------- +sort_standard(L) -> + Mand = [{sysContact, {value, ""}}, + {sysDescr, {value, ""}}, + {sysLocation, {value, ""}}, + {sysName, {value, ""}}, + {sysObjectID, mandatory}, + {sysServices, mandatory}, + {snmpEnableAuthenTraps, mandatory}], + {ok, L2} = snmp_conf:check_mandatory(L, Mand), + lists:keysort(1, L2). + + +%%----------------------------------------------------------------- +%% Standard +%% {Name, Value}. +%%----------------------------------------------------------------- +check_standard({sysDescr, Value}) -> snmp_conf:check_string(Value); +check_standard({sysObjectID, Value}) -> snmp_conf:check_oid(Value); +check_standard({sysContact, Value}) -> snmp_conf:check_string(Value); +check_standard({sysName, Value}) -> snmp_conf:check_string(Value); +check_standard({sysLocation, Value}) -> snmp_conf:check_string(Value); +check_standard({sysServices, Value}) -> snmp_conf:check_integer(Value); +check_standard({snmpEnableAuthenTraps, Value}) -> + Atoms = [{enabled, ?snmpEnableAuthenTraps_enabled}, + {disabled, ?snmpEnableAuthenTraps_disabled}], + {ok, Val} = snmp_conf:check_atom(Value, Atoms), + {ok, {snmpEnableAuthenTraps, Val}}; +check_standard({Attrib, _Value}) -> error({unknown_attribute, Attrib}); +check_standard(X) -> error({invalid_standard_specification, X}). + + +%%----------------------------------------------------------------- +%% Func: reset/0 +%% Purpose: Resets all counters (sets them to 0). +%%----------------------------------------------------------------- +reset() -> + snmpa_mpd:reset(). + +maybe_create_persistent_var({Var, Val}) -> + VarDB = db(Var), + case snmp_generic:variable_get(VarDB) of + {value, _} -> ok; + _ -> snmp_generic:variable_set(VarDB, Val) + end. + +create_persistent_var({Var, Val}) -> + snmp_generic:variable_set(db(Var), Val). + +variable_func(_Op) -> ok. + +variable_func(get, Name) -> + [{_, Val}] = ets:lookup(snmp_agent_table, Name), + {value, Val}. + + +%%----------------------------------------------------------------- +%% inc(VariableName) increments the variable (Counter) in +%% the local mib. (e.g. snmpInPkts) +%%----------------------------------------------------------------- +inc(Name) -> inc(Name, 1). +inc(Name, N) -> ets:update_counter(snmp_agent_table, Name, N). + +%%----------------------------------------------------------------- +%% This is the instrumentation function for sysUpTime. +%%----------------------------------------------------------------- +sys_up_time() -> + snmpa:sys_up_time(). + +sys_up_time(get) -> + {value, snmpa:sys_up_time()}. + +%%----------------------------------------------------------------- +%% This is the instrumentation function for snmpEnableAuthenTraps +%%----------------------------------------------------------------- +snmp_enable_authen_traps(new) -> + snmp_generic:variable_func(new, db(snmpEnableAuthenTraps)); + +snmp_enable_authen_traps(delete) -> + ok; + +snmp_enable_authen_traps(get) -> + snmp_generic:variable_func(get, db(snmpEnableAuthenTraps)). + +snmp_enable_authen_traps(set, NewVal) -> + snmp_generic:variable_func(set, NewVal, db(snmpEnableAuthenTraps)). + +%%----------------------------------------------------------------- +%% This is the instrumentation function for sysObjectId +%%----------------------------------------------------------------- +sys_object_id(new) -> + snmp_generic:variable_func(new, db(sysObjectID)); + +sys_object_id(delete) -> + ok; + +sys_object_id(get) -> + snmp_generic:variable_func(get, db(sysObjectID)). + +sys_object_id(set, NewVal) -> + snmp_generic:variable_func(set, NewVal, db(sysObjectID)). + +%%----------------------------------------------------------------- +%% This is a dummy instrumentation function for objects like +%% snmpTrapOID, that is accessible-for-notify, with different +%% values each time. This function will only be called with +%% new/delete. +%%----------------------------------------------------------------- +dummy(_Op) -> ok. + +%%----------------------------------------------------------------- +%% This is the instrumentation function for snmpSetSerialNo. +%% It is always volatile. +%%----------------------------------------------------------------- +snmp_set_serial_no(new) -> + snmp_generic:variable_func(new, {snmpSetSerialNo, volatile}), + {A1,A2,A3} = erlang:now(), + random:seed(A1,A2,A3), + Val = random:uniform(2147483648) - 1, + snmp_generic:variable_func(set, Val, {snmpSetSerialNo, volatile}); + +snmp_set_serial_no(delete) -> + ok; + +snmp_set_serial_no(get) -> + snmp_generic:variable_func(get, {snmpSetSerialNo, volatile}). + +snmp_set_serial_no(is_set_ok, NewVal) -> + case snmp_generic:variable_func(get, {snmpSetSerialNo, volatile}) of + {value, NewVal} -> noError; + _ -> inconsistentValue + end; +snmp_set_serial_no(set, NewVal) -> + snmp_generic:variable_func(set, (NewVal + 1) rem 2147483648, + {snmpSetSerialNo, volatile}). + +%%----------------------------------------------------------------- +%% This is the instrumentation function for sysOrTable +%%----------------------------------------------------------------- +sys_or_table(Op, RowIndex, Cols) -> + snmp_generic:table_func(Op, RowIndex, Cols, {sysORTable, volatile}). + +add_agent_caps(Oid, Descr) when is_list(Oid) andalso is_list(Descr) -> + {value, Next} = snmpa_local_db:variable_get({next_sys_or_index, volatile}), + snmpa_local_db:variable_set({next_sys_or_index, volatile}, Next+1), + SysUpTime = sys_up_time(), + Row = {Next, Oid, Descr, SysUpTime}, + snmpa_local_db:table_create_row({sysORTable, volatile}, [Next], Row), + snmpa_local_db:variable_set({sysORLastChange, volatile}, SysUpTime), + Next. + +del_agent_caps(Index) -> + snmpa_local_db:table_delete_row({sysORTable, volatile}, [Index]), + snmpa_local_db:variable_set({sysORLastChange, volatile}, sys_up_time()). + +get_agent_caps() -> + snmpa_local_db:match({sysORTable, volatile}, {'$1', '$2', '$3', '$4'}). + + +db(Var) -> snmpa_agent:db(Var). + + +%% ----- + +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}). + +config_err(F, A) -> + snmpa_error:config_err("[STANDARD-MIB]: " ++ F, A). diff --git a/lib/snmp/src/agent/snmp_target_mib.erl b/lib/snmp/src/agent/snmp_target_mib.erl new file mode 100644 index 0000000000..a3ac67b533 --- /dev/null +++ b/lib/snmp/src/agent/snmp_target_mib.erl @@ -0,0 +1,888 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-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(snmp_target_mib). + +-export([configure/1, reconfigure/1, + snmpTargetSpinLock/1, snmpTargetSpinLock/2, + snmpTargetAddrTable/1, snmpTargetAddrTable/3, + snmpTargetParamsTable/1, snmpTargetParamsTable/3, + get_target_addrs/0, get_target_engine_id/1, set_target_engine_id/2, + is_valid_tag/3, get/3, table_next/2]). +-export([add_addr/10, delete_addr/1, + add_params/5, delete_params/1]). +-export([check_target_addr/1, check_target_params/1]). + +-include("snmp_types.hrl"). +-include("snmp_tables.hrl"). +-include("SNMP-TARGET-MIB.hrl"). +-include("SNMPv2-TC.hrl"). +-include("SNMPv2-TM.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). + +-define(VMODULE,"TARGET-MIB"). +-include("snmp_verbosity.hrl"). + + +%% Column not accessible via SNMP - needed when the agent sends informs +-define(snmpTargetAddrEngineId, 10). +%% Extra comlumns for the augmented table snmpTargetAddrExtTable +-define(snmpTargetAddrTMask, 11). +-define(snmpTargetAddrMMS, 12). + + +%%----------------------------------------------------------------- +%% 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 target 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(snmpTargetParamsTable) of + {_, mnesia} -> + ?vdebug("tables in mnesia: init vars & cleanup",[]), + init_vars(), + gc_tabs(); + TabDb -> + case snmpa_local_db:table_exists(TabDb) of + true -> + ?vdebug("tables already exist: init vars & cleanup",[]), + init_vars(), + gc_tabs(); + false -> + ?vdebug("no tables: 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 target 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 target configuration files",[]), + {Addrs, Params} = read_target_config_files(Dir), + ?vdebug("initiate tables",[]), + init_tabs(Addrs, Params), + ?vdebug("initiate vars",[]), + init_vars(), + ?vdebug("invalidate cache for notification mib",[]), + snmp_notification_mib:invalidate_cache(), + ok. + + +read_target_config_files(Dir) -> + ?vdebug("check target address config file",[]), + TAGen = fun(_D) -> ok end, + TAFilter = fun(Addr) -> Addr end, + TACheck = fun(Entry) -> check_target_addr(Entry) end, + + TPGen = fun(_D) -> ok end, + TPFilter = fun(Params) -> Params end, + TPCheck = fun(Entry) -> check_target_params(Entry) end, + + [Addrs, Params] = + snmp_conf:read_files(Dir, + [{TAGen, TAFilter, TACheck, "target_addr.conf"}, + {TPGen, TPFilter, TPCheck, "target_params.conf"}]), + {Addrs, Params}. + + +%%----------------------------------------------------------------- +%% TargetAddr +%% {Name, Ip, Udp, Timeout, RetryCount, TagList, Params, EngineId, +%% TMask, MMS} +%%----------------------------------------------------------------- +check_target_addr({Name, Ip, Udp, Timeout, RetryCount, TagList, + Params, EngineId, TMask, MMS}) -> + ?vtrace("check target address with:" + "~n Name: ~s" + "~n Ip: ~p" + "~n Udp: ~p" + "~n Timeout: ~p" + "~n RetryCount: ~p" + "~n TagList: ~p" + "~n Params: ~p" + "~n EngineId: ~p" + "~n TMask: ~p" + "~n MMS: ~p", + [Name,Ip,Udp,Timeout,RetryCount, + TagList,Params,EngineId,TMask,MMS]), + snmp_conf:check_string(Name,{gt,0}), + snmp_conf:check_ip(Ip), + snmp_conf:check_integer(Udp, {gt, 0}), + snmp_conf:check_integer(Timeout, {gte, 0}), + snmp_conf:check_integer(RetryCount, {gte,0}), + snmp_conf:check_string(TagList), + snmp_conf:check_string(Params), + check_engine_id(EngineId), + TAddr = Ip ++ [Udp div 256, Udp rem 256], + check_mask(TMask, TAddr), + snmp_conf:check_packet_size(MMS), + ?vtrace("check target address done",[]), + + Addr = {Name, ?snmpUDPDomain, TAddr, Timeout, + RetryCount, TagList, Params, + ?'StorageType_nonVolatile', ?'RowStatus_active', EngineId, + TMask, MMS}, % Values for Augmenting table in SNMP-COMMUNITY-MIB + {ok, Addr}; +check_target_addr({Name, Ip, Udp, Timeout, RetryCount, TagList, Params, + EngineId}) -> + check_target_addr({Name, Ip, Udp, Timeout, RetryCount, TagList, + Params, EngineId, [], 2048}); +%% Use dummy engine id if the old style is found +check_target_addr({Name, Ip, Udp, Timeout, RetryCount, TagList, Params}) -> + check_target_addr({Name, Ip, Udp, Timeout, RetryCount, TagList, + Params, "dummy", [], 2048}); +%% Use dummy engine id if the old style is found +check_target_addr({Name, Ip, Udp, Timeout, RetryCount, TagList, Params, + TMask, MMS}) -> + check_target_addr({Name, Ip, Udp, Timeout, RetryCount, TagList, + Params, "dummy", TMask, MMS}); +check_target_addr(X) -> + error({invalid_target_addr, X}). + + +check_engine_id(discovery) -> + ok; +check_engine_id(EngineId) -> + snmp_conf:check_string(EngineId). + +check_mask([], _TAddr) -> + ok; +check_mask(TMask, TAddr) when length(TMask) == length(TAddr) -> + snmp_conf:check_taddress(TMask); +check_mask(TMask, _TAddr) -> + throw({error, {invalid_mask, TMask}}). + + +%%----------------------------------------------------------------- +%% TargetParams +%% {Name, MPModel, SecurityModel, SecurityName, SecurityLevel} +%%----------------------------------------------------------------- +check_target_params({Name, MPModel, SecModel, SecName, SecLevel}) -> + snmp_conf:check_string(Name,{gt,0}), + {ok, MP} = snmp_conf:check_atom(MPModel, [{v1, ?MP_V1}, + {v2c, ?MP_V2C}, + {v3, ?MP_V3}]), + {ok, SecM} = snmp_conf:check_sec_model(SecModel, [any]), + snmp_conf:check_string(SecName), + {ok, SecL} = snmp_conf:check_sec_level(SecLevel), + Params = {Name, MP, SecM, SecName, SecL, + ?'StorageType_nonVolatile', ?'RowStatus_active'}, + {ok, Params}; +check_target_params(X) -> + error({invalid_target_params, X}). + + + +%% maybe_create_table(Name) -> +%% case snmpa_local_db:table_exists(db(Name)) of +%% true -> ok; +%% _ -> snmpa_local_db:table_create(db(Name)) +%% end. + +init_tabs(Addrs, Params) -> + ?vdebug("create target address table",[]), + AddrDB = db(snmpTargetAddrTable), + snmpa_local_db:table_delete(AddrDB), + snmpa_local_db:table_create(AddrDB), + init_addr_table(Addrs), + ?vdebug("create target params table",[]), + ParmDB = db(snmpTargetParamsTable), + snmpa_local_db:table_delete(ParmDB), + snmpa_local_db:table_create(ParmDB), + init_params_table(Params). + +init_addr_table([Row | T]) -> + Key = element(1, Row), + snmpa_local_db:table_create_row(db(snmpTargetAddrTable), Key, Row), + init_addr_table(T); +init_addr_table([]) -> true. + +init_params_table([Row | T]) -> + Key = element(1, Row), + snmpa_local_db:table_create_row(db(snmpTargetParamsTable), Key, Row), + init_params_table(T); +init_params_table([]) -> 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). + + +add_addr(Name, Ip, Port, Timeout, Retry, TagList, + Params, EngineId, TMask, MMS) -> + Addr = {Name, Ip, Port, Timeout, Retry, TagList, + Params, EngineId, TMask, MMS}, + case (catch check_target_addr(Addr)) of + {ok, Row} -> + Key = element(1, Row), + case table_cre_row(snmpTargetAddrTable, Key, Row) of + true -> + {ok, Key}; + false -> + {error, create_failed} + end; + {error, Reason} -> + {error, Reason}; + Error -> + {error, Error} + end. + +delete_addr(Key) -> + case table_del_row(snmpTargetAddrTable, Key) of + true -> + ok; + false -> + {error, delete_failed} + end. + + +add_params(Name, MPModel, SecModel, SecName, SecLevel) -> + Params = {Name, MPModel, SecModel, SecName, SecLevel}, + case (catch check_target_params(Params)) of + {ok, Row} -> + Key = element(1, Row), + case table_cre_row(snmpTargetParamsTable, Key, Row) of + true -> + {ok, Key}; + false -> + {create_failed} + end; + {error, Reason} -> + {error, Reason}; + Error -> + {error, Error} + end. + +delete_params(Key) -> + case table_del_row(snmpTargetParamsTable, Key) of + true -> + ok; + false -> + {error, delete_failed} + end. + + +gc_tabs() -> + AddrDB = db(snmpTargetAddrTable), + AddrSTC = stc(snmpTargetAddrTable), + AddrFOI = foi(snmpTargetAddrTable), + snmpa_mib_lib:gc_tab(AddrDB, AddrSTC, AddrFOI), + ParmDB = db(snmpTargetParamsTable), + ParmSTC = stc(snmpTargetParamsTable), + ParmFOI = foi(snmpTargetParamsTable), + snmpa_mib_lib:gc_tab(ParmDB, ParmSTC, ParmFOI), + ok. + + +%%----------------------------------------------------------------- +%% Counter functions +%%----------------------------------------------------------------- +init_vars() -> + ?vtrace("initiate 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() -> + [snmpUnavailableContexts, + snmpUnknownContexts]. + +%%----------------------------------------------------------------- +%% API functions +%%----------------------------------------------------------------- +is_valid_tag("", _TDomain, _TAddress) -> + true; +is_valid_tag(Tag, TDomain, TAddress) -> + is_valid_tag(TDomain, TAddress, Tag, []). + +is_valid_tag(TDomain, TAddress, Tag, Key) -> + case table_next(snmpTargetAddrTable, Key) of + endOfTable -> + false; + NextKey -> + case get(snmpTargetAddrTable, NextKey, [?snmpTargetAddrTDomain, + ?snmpTargetAddrTAddress, + ?snmpTargetAddrTagList, + ?snmpTargetAddrTMask]) of + [{value, TDomain}, % must match exactly + {value, TAddress}, % RFC2576: chapters 5.2.1 & 5.3 + {value, TagList}, + {value, []}] -> + case snmp_misc:is_tag_member(Tag, TagList) of + true -> + ?vtrace("is_valid_tag -> exact: " + "tag IS member of taglist", []), + true; + false -> + ?vtrace("is_valid_tag -> exact: " + "tag is NOT member of taglist", []), + is_valid_tag(TDomain, TAddress, + Tag, NextKey) + end; + [{value, TDomain}, % must match exactly + {value, TAddress2}, + {value, TagList}, + {value, TMask}] when TMask =/= [] -> + case snmp_misc:is_tmask_match(TAddress, TAddress2, + TMask) of + true -> + case snmp_misc:is_tag_member(Tag, TagList) of + true -> + ?vtrace("is_valid_tag -> masked: " + "tag IS member of taglist", []), + true; + false -> + ?vtrace("is_valid_tag -> masked: " + "tag is NOT member of taglist",[]), + is_valid_tag(TDomain, TAddress, + Tag, NextKey) + end; + false -> + is_valid_tag(TDomain, TAddress, + Tag, NextKey) + end; + _ -> + is_valid_tag(TDomain, TAddress, Tag, NextKey) + end + end. + + +%% TargAddrs = +%% [{TagList, TargetAddr, TargetAddrName, TargetParams, Timeout, Retry}] +%% TargetAddr = {TDomain, TAddr}; e.g. {?snmpUDPDomain, IpAndUdpPortAsList} +get_target_addrs() -> + get_target_addrs([], db(snmpTargetAddrTable), []). + + +get_target_addrs(Key, {Tab, _} = TabDb, Acc) -> + case table_next(Tab, Key) of + endOfTable -> + Acc; + NextKey -> + case get_target_addr(TabDb, NextKey) of + {ok, Targ} -> + get_target_addrs(NextKey, TabDb, [Targ| Acc]); + {error, Reason} -> + ?vlog("failed getting target address: " + "~n Reason: ~p", [Reason]), + get_target_addrs(NextKey, TabDb, Acc) + end + end. + +get_target_addr({Tab, mnesia}, Key) -> + case mnesia:snmp_get_row(Tab, Key) of + {ok, #snmpTargetAddrTable{ + snmpTargetAddrTDomain = TDomain, + snmpTargetAddrTAddress = TAddress, + snmpTargetAddrTimeout = Timeout, + snmpTargetAddrRetryCount = RetryCount, + snmpTargetAddrTagList = TagList, + snmpTargetAddrParams = Params, + snmpTargetAddrRowStatus = ?'RowStatus_active'}} -> + case get_target_params(Params) of + undefined -> + config_err("Failed retreiving target params [~p]" + "~n for ~p [~p]", [Params, Key, TAddress]), + {error, {not_found, {target_params, Key, Params}}}; + TargetParams -> + Targ = {TagList, {TDomain, TAddress}, Key, + TargetParams, Timeout, RetryCount}, + {ok, Targ} + end; + _ -> + {error, {not_found, {target_addr, Key}}} + end; +get_target_addr(TabDb, Key) -> + case snmpa_local_db:table_get_row(TabDb, Key) of + {_Key, TDomain, TAddress, Timeout, RetryCount, TagList, Params, + _Storage, ?'RowStatus_active', _TargetEngineId,_TMask,_MMS} -> + case get_target_params(Params) of + undefined -> + config_err("Failed retreiving target params [~p]" + "~n for target ~p [~p]", + [Params, Key, TAddress]), + {error, {not_found, {target_params, Key, Params}}}; + TargetParams -> + Targ = {TagList, {TDomain, TAddress}, Key, + TargetParams, Timeout, RetryCount}, + {ok, Targ} + end; + _ -> + {error, {not_found, {target_addr, Key}}} + end. + + +get_target_params(Params) -> + case snmpTargetParamsTable(get, Params, [?snmpTargetParamsMPModel, + ?snmpTargetParamsSecurityModel, + ?snmpTargetParamsSecurityName, + ?snmpTargetParamsSecurityLevel, + ?snmpTargetParamsRowStatus]) of + [{value, MpModel}, + {value, SecModel}, {value, SecName}, {value, SecLevel}, + {value, ?'RowStatus_active'}] -> + {MpModel, SecModel, SecName, SecLevel}; + _ -> + undefined + end. + +get_target_engine_id(TargetAddrName) -> + case db(snmpTargetAddrTable) of + {_, mnesia} -> + case mnesia:snmp_get_row(snmpTargetAddrTable, TargetAddrName) of + {ok, T} -> + {ok, T#snmpTargetAddrTable.snmpTargetAddrEngineId}; + _ -> + undefined + end; + TabDb -> + case snmpa_local_db:table_get_element(TabDb, + TargetAddrName, + ?snmpTargetAddrEngineId) of + {value, Val} -> + {ok, Val}; + _ -> + undefined + end + end. + +set_target_engine_id(TargetAddrName, EngineId) -> + snmp_generic:table_set_elements(db(snmpTargetAddrTable), + TargetAddrName, + [{?snmpTargetAddrEngineId, EngineId}]). + +%%----------------------------------------------------------------- +%% Instrumentation Functions +%%----------------------------------------------------------------- +snmpTargetSpinLock(new) -> + snmp_generic:variable_func(new, {snmpTargetSpinLock, volatile}), + {A1,A2,A3} = erlang:now(), + random:seed(A1,A2,A3), + Val = random:uniform(2147483648) - 1, + snmp_generic:variable_func(set, Val, {snmpTargetSpinLock, volatile}); + +snmpTargetSpinLock(delete) -> + ok; + +snmpTargetSpinLock(get) -> + snmp_generic:variable_func(get, {snmpTargetSpinLock, volatile}). + +snmpTargetSpinLock(is_set_ok, NewVal) -> + case snmp_generic:variable_func(get, {snmpTargetSpinLock, volatile}) of + {value, NewVal} -> noError; + _ -> inconsistentValue + end; +snmpTargetSpinLock(set, NewVal) -> + snmp_generic:variable_func(set, (NewVal + 1) rem 2147483648, + {snmpTargetSpinLock, volatile}). + + +%% Op = print - Used for debugging purposes +snmpTargetAddrTable(print) -> + Table = snmpTargetAddrTable, + DB = db(Table), + FOI = foi(Table), + PrintRow = + fun(Prefix, Row) -> + lists:flatten( + io_lib:format("~sName: ~p" + "~n~sTDomain: ~p (~w)" + "~n~sTAddress: ~p (~s)" + "~n~sTimeout: ~p" + "~n~sRetryCount: ~p" + "~n~sTagList: ~p" + "~n~sParams: ~p" + "~n~sStorageType: ~p (~w)" + "~n~sStatus: ~p (~w)" + "~n~s[NonAcc] EngineID: ~p" + "~n~s[Ext] TMask: ~p" + "~n~s[Ext] MMS: ~p", + [Prefix, element(?snmpTargetAddrName, Row), + Prefix, element(?snmpTargetAddrTDomain, Row), + case element(?snmpTargetAddrTDomain, Row) of + ?snmpUDPDomain -> udp; + _ -> undefined + end, + Prefix, element(?snmpTargetAddrTAddress, Row), + case element(?snmpTargetAddrTAddress, Row) of + [A,B,C,D,U1,U2] -> + lists:flatten( + io_lib:format("~w.~w.~w.~w:~w", + [A, B, C, D, U1 bsl 8 + U2])); + _ -> "-" + end, + Prefix, element(?snmpTargetAddrTimeout, Row), + Prefix, element(?snmpTargetAddrRetryCount, Row), + Prefix, element(?snmpTargetAddrTagList, Row), + Prefix, element(?snmpTargetAddrParams, Row), + Prefix, element(?snmpTargetAddrStorageType, Row), + case element(?snmpTargetAddrStorageType, Row) of + ?'snmpTargetAddrStorageType_readOnly' -> readOnly; + ?'snmpTargetAddrStorageType_permanent' -> permanent; + ?'snmpTargetAddrStorageType_nonVolatile' -> nonVolatile; + ?'snmpTargetAddrStorageType_volatile' -> volatile; + ?'snmpTargetAddrStorageType_other' -> other; + _ -> undefined + end, + Prefix, element(?snmpTargetAddrRowStatus, Row), + case element(?snmpTargetAddrRowStatus, Row) of + ?'snmpTargetAddrRowStatus_destroy' -> destroy; + ?'snmpTargetAddrRowStatus_createAndWait' -> createAndWait; + ?'snmpTargetAddrRowStatus_createAndGo' -> createAndGo; + ?'snmpTargetAddrRowStatus_notReady' -> notReady; + ?'snmpTargetAddrRowStatus_notInService' -> notInService; + ?'snmpTargetAddrRowStatus_active' -> active; + _ -> undefined + end, + Prefix, + element(?snmpTargetAddrEngineId, Row), + Prefix, + element(?snmpTargetAddrTMask, Row), + Prefix, + element(?snmpTargetAddrMMS, Row)])) + end, + snmpa_mib_lib:print_table(Table, DB, FOI, PrintRow); +%% Op == new | delete +snmpTargetAddrTable(Op) -> + snmp_generic:table_func(Op, db(snmpTargetAddrTable)). + +%% Op == get | is_set_ok | set | get_next +snmpTargetAddrTable(get, RowIndex, Cols) -> + get(snmpTargetAddrTable, RowIndex, Cols); +snmpTargetAddrTable(get_next, RowIndex, Cols) -> + next(snmpTargetAddrTable, RowIndex, Cols); +snmpTargetAddrTable(set, RowIndex, Cols0) -> + %% BMK BMK BMK + case (catch verify_targetAddrTable_cols(Cols0, [])) of + {ok, Cols} -> + snmp_notification_mib:invalidate_cache(), + %% Add columns for augmenting table snmpTargetAddrExtTable and for + %% target engine ID. Target engine ID is set to "". The function + %% get_target_engine_id will return "" unless a value is set using + %% set_target_engine_id. If it is "" Informs can't be sent to the + %% target. + NCols = Cols ++ [{?snmpTargetAddrEngineId, ""}, + {?snmpTargetAddrTMask, []}, + {?snmpTargetAddrMMS, 2048}], + Db = db(snmpTargetAddrTable), + snmp_generic:table_func(set, RowIndex, NCols, Db); + Error -> + Error + end; +snmpTargetAddrTable(is_set_ok, RowIndex, Cols0) -> + case (catch verify_targetAddrTable_cols(Cols0, [])) of + {ok, Cols} -> + %% Add columns for augmenting table snmpTargetAddrExtTable and for + %% target engine ID. Target engine ID is set to "". The function + %% get_target_engine_id will return "" unless a value is set using + %% set_target_engine_id. If it is "" Informs can't be sent to the + %% target. + NCols = Cols ++ [{?snmpTargetAddrEngineId, ""}, + {?snmpTargetAddrTMask, []}, + {?snmpTargetAddrMMS, 2048}], + Db = db(snmpTargetAddrTable), + snmp_generic:table_func(is_set_ok, RowIndex, NCols, Db); + Error -> + Error + end; +snmpTargetAddrTable(Op, Arg1, Arg2) -> + Db = db(snmpTargetAddrTable), + snmp_generic:table_func(Op, Arg1, Arg2, Db). + +verify_targetAddrTable_cols([], Cols) -> + {ok, lists:reverse(Cols)}; +verify_targetAddrTable_cols([{Col, Val0}|Cols], Acc) -> + Val = verify_targetAddrTable_col(Col, Val0), + verify_targetAddrTable_cols(Cols, [{Col, Val}|Acc]). + +verify_targetAddrTable_col(?snmpTargetAddrName, Name) -> + case (catch snmp_conf:check_string(Name)) of + ok -> + Name; + _ -> + wrongValue(?snmpTargetAddrName) + end; +verify_targetAddrTable_col(?snmpTargetAddrTAddress, TAddr) -> + case (catch snmp_conf:check_taddress(TAddr)) of + ok -> + TAddr; + _ -> + wrongValue(?snmpTargetAddrTAddress) + end; +verify_targetAddrTable_col(?snmpTargetAddrTimeout, Timeout) -> + case (catch snmp_conf:check_integer(Timeout)) of + ok when Timeout >= 0 -> + Timeout; + _ -> + wrongValue(?snmpTargetAddrTimeout) + end; +verify_targetAddrTable_col(?snmpTargetAddrRetryCount, Retry) -> + case (catch snmp_conf:check_integer(Retry)) of + ok when Retry >= 0 -> + Retry; + _ -> + wrongValue(?snmpTargetAddrRetryCount) + end; +verify_targetAddrTable_col(?snmpTargetAddrTagList, TagList) -> + case (catch snmp_conf:check_string(TagList)) of + ok -> + TagList; + _ -> + wrongValue(?snmpTargetAddrTagList) + end; +verify_targetAddrTable_col(?snmpTargetAddrParams, Params) -> + case (catch snmp_conf:check_string(Params)) of + ok -> + Params; + _ -> + wrongValue(?snmpTargetAddrParams) + end; +verify_targetAddrTable_col(_, Val) -> + Val. + + +%% Op = print - Used for debugging purposes +snmpTargetParamsTable(print) -> + Table = snmpTargetParamsTable, + DB = db(Table), + FOI = foi(Table), + PrintRow = + fun(Prefix, Row) -> + lists:flatten( + io_lib:format("~sName: ~p" + "~n~sMPModel: ~p (~w)" + "~n~sSecurityModel: ~p (~w)" + "~n~sSecurityName: ~p" + "~n~sSecurityLevel: ~p (~w)" + "~n~sStorageType: ~p (~w)" + "~n~sStatus: ~p (~w)", + [Prefix, + element(?snmpTargetParamsName, Row), + Prefix, + element(?snmpTargetParamsMPModel, Row), + case element(?snmpTargetParamsMPModel, Row) of + ?MP_V1 -> v1; + ?MP_V2C -> v2c; + ?MP_V3 -> v3; + _ -> undefined + end, + Prefix, + element(?snmpTargetParamsSecurityModel, Row), + case element(?snmpTargetParamsSecurityModel, Row) of + ?SEC_ANY -> any; + ?SEC_V1 -> v1; + ?SEC_V2C -> v2c; + ?SEC_USM -> usm; + _ -> undefined + end, + Prefix, + element(?snmpTargetParamsSecurityName, Row), + Prefix, + element(?snmpTargetParamsSecurityLevel, Row), + case element(?snmpTargetParamsSecurityLevel, Row) of + ?'SnmpSecurityLevel_noAuthNoPriv' -> noAuthNoPriv; + ?'SnmpSecurityLevel_authNoPriv' -> authNoPriv; + ?'SnmpSecurityLevel_authPriv' -> authPriv; + _ -> undefined + end, + Prefix, + element(?snmpTargetParamsStorageType, Row), + case element(?snmpTargetParamsStorageType, Row) of + ?'snmpTargetParamsStorageType_readOnly' -> readOnly; + ?'snmpTargetParamsStorageType_permanent' -> permanent; + ?'snmpTargetParamsStorageType_nonVolatile' -> nonVolatile; + ?'snmpTargetParamsStorageType_volatile' -> volatile; + ?'snmpTargetParamsStorageType_other' -> other; + _ -> undefined + end, + Prefix, + element(?snmpTargetParamsRowStatus, Row), + case element(?snmpTargetParamsRowStatus, Row) of + ?'snmpTargetParamsRowStatus_destroy' -> destroy; + ?'snmpTargetParamsRowStatus_createAndWait' -> createAndWait; + ?'snmpTargetParamsRowStatus_createAndGo' -> createAndGo; + ?'snmpTargetParamsRowStatus_notReady' -> notReady; + ?'snmpTargetParamsRowStatus_notInService' -> notInService; + ?'snmpTargetParamsRowStatus_active' -> active; + _ -> undefined + end])) + end, + snmpa_mib_lib:print_table(Table, DB, FOI, PrintRow); +%% Op == new | delete +snmpTargetParamsTable(Op) -> + snmp_generic:table_func(Op, db(snmpTargetParamsTable)). + +%% Op == get | is_set_ok | set | get_next +snmpTargetParamsTable(get, RowIndex, Cols) -> + %% BMK BMK BMK + get(snmpTargetParamsTable, RowIndex, Cols); +snmpTargetParamsTable(get_next, RowIndex, Cols) -> + %% BMK BMK BMK + next(snmpTargetParamsTable, RowIndex, Cols); +snmpTargetParamsTable(set, RowIndex, Cols0) -> + %% BMK BMK BMK + case (catch verify_snmpTargetParamsTable_cols(Cols0, [])) of + {ok, Cols} -> + snmp_notification_mib:invalidate_cache(), + snmp_generic:table_func(set, RowIndex, Cols, + db(snmpTargetParamsTable)); + Error -> + Error + end; +snmpTargetParamsTable(Op, Arg1, Arg2) -> + snmp_generic:table_func(Op, Arg1, Arg2, db(snmpTargetParamsTable)). + +verify_snmpTargetParamsTable_cols([], Cols) -> + {ok, lists:reverse(Cols)}; +verify_snmpTargetParamsTable_cols([{Col, Val0}|Cols], Acc) -> + Val = verify_snmpTargetParamsTable_col(Col, Val0), + verify_snmpTargetParamsTable_cols(Cols, [{Col, Val}|Acc]). + +verify_snmpTargetParamsTable_col(?snmpTargetParamsName, Name) -> + case (catch snmp_conf:check_string(Name)) of + ok -> + Name; + _ -> + wrongValue(?snmpTargetParamsName) + end; +verify_snmpTargetParamsTable_col(?snmpTargetParamsMPModel, Model) -> + case Model of + v1 -> ?MP_V1; + v2c -> ?MP_V2C; + v3 -> ?MP_V3; + ?MP_V1 -> ?MP_V1; + ?MP_V2C -> ?MP_V2C; + ?MP_V3 -> ?MP_V3; + _ -> wrongValue(?snmpTargetParamsMPModel) + end; +verify_snmpTargetParamsTable_col(?snmpTargetParamsSecurityModel, Model) -> + case Model of + v1 -> ?SEC_V1; + v2c -> ?SEC_V2C; + usm -> ?SEC_USM; + ?SEC_V1 -> ?SEC_V1; + ?SEC_V2C -> ?SEC_V2C; + ?SEC_USM -> ?SEC_USM; + _ -> wrongValue(?snmpTargetParamsSecurityModel) + end; +verify_snmpTargetParamsTable_col(?snmpTargetParamsSecurityName, Name) -> + case (catch snmp_conf:check_string(Name)) of + ok -> + Name; + _ -> + wrongValue(?snmpTargetParamsSecurityName) + end; +verify_snmpTargetParamsTable_col(?snmpTargetParamsSecurityLevel, Level) -> + case Level of + noAuthNoPriv -> 1; + authNoPriv -> 2; + authPriv -> 3; + 1 -> 1; + 2 -> 2; + 3 -> 3; + _ -> wrongValue(?snmpTargetParamsSecurityLevel) + end; +verify_snmpTargetParamsTable_col(_, Val) -> + Val. + +db(X) -> snmpa_agent:db(X). + +fa(snmpTargetAddrTable) -> ?snmpTargetAddrTDomain; +fa(snmpTargetParamsTable) -> ?snmpTargetParamsMPModel. + +foi(snmpTargetAddrTable) -> ?snmpTargetAddrName; +foi(snmpTargetParamsTable) -> ?snmpTargetParamsName. + +noc(snmpTargetAddrTable) -> 9; +noc(snmpTargetParamsTable) -> 7. + +stc(snmpTargetAddrTable) -> ?snmpTargetAddrStorageType; +stc(snmpTargetParamsTable) -> ?snmpTargetParamsStorageType. + +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)). + + +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}). + +config_err(F, A) -> + snmpa_error:config_err("[TARGET-MIB]: " ++ F, A). + + diff --git a/lib/snmp/src/agent/snmp_user_based_sm_mib.erl b/lib/snmp/src/agent/snmp_user_based_sm_mib.erl new file mode 100644 index 0000000000..7b881f888c --- /dev/null +++ b/lib/snmp/src/agent/snmp_user_based_sm_mib.erl @@ -0,0 +1,1182 @@ +%% +%% %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(snmp_user_based_sm_mib). + +-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([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 +%%----------------------------------------------------------------- +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(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). + diff --git a/lib/snmp/src/agent/snmp_view_based_acm_mib.erl b/lib/snmp/src/agent/snmp_view_based_acm_mib.erl new file mode 100644 index 0000000000..873ab00545 --- /dev/null +++ b/lib/snmp/src/agent/snmp_view_based_acm_mib.erl @@ -0,0 +1,832 @@ +%% +%% %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(snmp_view_based_acm_mib). + +-export([configure/1, reconfigure/1, table_next/2, get/3]). + +-export([vacmAccessTable/1, vacmAccessTable/3, + vacmContextTable/1, vacmContextTable/3, + vacmSecurityToGroupTable/1, vacmSecurityToGroupTable/3, + vacmViewSpinLock/1, vacmViewSpinLock/2, + vacmViewTreeFamilyTable/1, vacmViewTreeFamilyTable/3]). +-export([add_sec2group/3, delete_sec2group/1, + add_access/8, delete_access/1, + add_view_tree_fam/4, delete_view_tree_fam/1]). + +%% Internal exports +-export([check_vacm/1]). + + +-include("snmp_types.hrl"). +-include("SNMPv2-TC.hrl"). +-include("SNMP-VIEW-BASED-ACM-MIB.hrl"). +-include("snmpa_vacm.hrl"). + + +-define(VMODULE,"VACM-MIB"). +-include("snmp_verbosity.hrl"). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + + +%%----------------------------------------------------------------- +%% 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 VACM 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(vacmSecurityToGroupTable) of + {_, mnesia} -> + ?vdebug("vacm security-to-group table in mnesia: cleanup",[]), + gc_tabs(), + init_vacm_mnesia(); + TabDb -> + case snmpa_local_db:table_exists(TabDb) of + true -> + ?vdebug("vacm security-to-group table already exist: " + "cleanup",[]), + gc_tabs(); + false -> + ?vdebug("vacm security-to-group 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 VACM 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 vacm configuration files",[]), + {Sec2Group, Access, View} = read_vacm_config_files(Dir), + ?vdebug("initiate tables",[]), + init_tabs(Sec2Group, Access, View), + ok. + +read_vacm_config_files(Dir) -> + ?vdebug("read vacm config file",[]), + Gen = fun(_) -> ok end, + Filter = fun(Vacms) -> + Sec2Group = [X || {vacmSecurityToGroup, X} <- Vacms], + Access = [X || {vacmAccess, X} <- Vacms], + View = [X || {vacmViewTreeFamily, X} <- Vacms], + {Sec2Group, Access, View} + end, + Check = fun(Entry) -> check_vacm(Entry) end, + [Vacms] = snmp_conf:read_files(Dir, [{Gen, Filter, Check, "vacm.conf"}]), + Vacms. + +%%----------------------------------------------------------------- +%% VACM tables +%%----------------------------------------------------------------- +check_vacm({vacmSecurityToGroup, SecModel, SecName, GroupName}) -> + {ok, SecM} = snmp_conf:check_sec_model(SecModel, []), + snmp_conf:check_string(SecName), + snmp_conf:check_string(GroupName), + + Vacm = {SecM, SecName, GroupName, + ?'StorageType_nonVolatile', ?'RowStatus_active'}, + {ok, {vacmSecurityToGroup, Vacm}}; +check_vacm({vacmAccess, GroupName, Prefix, SecModel, SecLevel, + Match, RV, WV, NV}) -> + snmp_conf:check_string(GroupName), + snmp_conf:check_string(Prefix), + {ok, SecM} = snmp_conf:check_sec_model(SecModel, []), + {ok, SecL} = snmp_conf:check_sec_level(SecLevel), + MatchAlt = [{exact, ?vacmAccessContextMatch_exact}, + {prefix, ?vacmAccessContextMatch_prefix}], + {ok, M} = snmp_conf:check_atom(Match, MatchAlt), + snmp_conf:check_string(RV), + snmp_conf:check_string(WV), + snmp_conf:check_string(NV), + + %% GN, Prefix, Model, Level, Row + Vacm = {GroupName, Prefix, SecM, SecL, + {M, RV, WV, NV, + ?'StorageType_nonVolatile', ?'RowStatus_active'}}, + {ok, {vacmAccess, Vacm}}; +check_vacm({vacmViewTreeFamily, ViewName, Tree, Type, Mask}) -> + snmp_conf:check_string(ViewName), + snmp_conf:check_oid(Tree), + {ok, TypeVal} = + snmp_conf:check_atom(Type, [{included, ?view_included}, + {excluded, ?view_excluded}]), + MaskVal = + case (catch snmp_conf:check_atom(Mask, [{null, []}])) of + {error, _} -> + snmp_conf:check_oid(Mask), + Mask; + {ok, X} -> + X + end, + Vacm = {ViewName, Tree, MaskVal, TypeVal, + ?'StorageType_nonVolatile', ?'RowStatus_active'}, + {ok, {vacmViewTreeFamily, Vacm}}; +check_vacm(X) -> + error({invalid_vacm, X}). + + +init_tabs(Sec2Group, Access, View) -> + ?vdebug("create vacm security-to-group table",[]), + snmpa_local_db:table_delete(db(vacmSecurityToGroupTable)), + snmpa_local_db:table_create(db(vacmSecurityToGroupTable)), + init_sec2group_table(Sec2Group), + init_access_table(Access), + ?vdebug("create vacm view-tree-family table",[]), + snmpa_local_db:table_delete(db(vacmViewTreeFamilyTable)), + snmpa_local_db:table_create(db(vacmViewTreeFamilyTable)), + init_view_table(View). + +init_sec2group_table([Row | T]) -> +% ?vtrace("init security-to-group table: " +% "~n Row: ~p",[Row]), + Key1 = element(1, Row), + Key2 = element(2, Row), + Key = [Key1, length(Key2) | Key2], + snmpa_local_db:table_create_row(db(vacmSecurityToGroupTable), Key, Row), + init_sec2group_table(T); +init_sec2group_table([]) -> true. + +init_access_table([{GN, Prefix, Model, Level, Row} | T]) -> +% ?vtrace("init access table: " +% "~n GN: ~p" +% "~n Prefix: ~p" +% "~n Model: ~p" +% "~n Level: ~p" +% "~n Row: ~p",[GN, Prefix, Model, Level, Row]), + Key = [length(GN) | GN] ++ [length(Prefix) | Prefix] ++ [Model, Level], + snmpa_vacm:insert([{Key, Row}], false), + init_access_table(T); +init_access_table([]) -> + snmpa_vacm:dump_table(). + +init_view_table([Row | T]) -> +% ?vtrace("init view table: " +% "~n Row: ~p",[Row]), + Key1 = element(1, Row), + Key2 = element(2, Row), + Key = [length(Key1) | Key1] ++ [length(Key2) | Key2], + snmpa_local_db:table_create_row(db(vacmViewTreeFamilyTable), Key, Row), + init_view_table(T); +init_view_table([]) -> 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). + + +%% add_sec2group(SecModel, SecName, GroupName) -> Result +%% Result -> {ok, Key} | {error, Reason} +%% Key -> term() +%% Reason -> term() +add_sec2group(SecModel, SecName, GroupName) -> + Sec2Grp = {vacmSecurityToGroup, SecModel, SecName, GroupName}, + case (catch check_vacm(Sec2Grp)) of + {ok, {vacmSecurityToGroup, Row}} -> + Key1 = element(1, Row), + Key2 = element(2, Row), + Key = [Key1, length(Key2) | Key2], + case table_cre_row(vacmSecurityToGroupTable, Key, Row) of + true -> + {ok, Key}; + false -> + {error, create_failed} + end; + {error, Reason} -> + {error, Reason}; + Error -> + {error, Error} + end. + +delete_sec2group(Key) -> + case table_del_row(vacmSecurityToGroupTable, Key) of + true -> + ok; + false -> + {error, delete_failed} + end. + +%% NOTE: This function must be used in conjuction with +%% snmpa_vacm:dump_table. +%% That is, when all access has been added, call +%% snmpa_vacm:dump_table/0 +add_access(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> + Access = {vacmAccess, GroupName, Prefix, SecModel, SecLevel, + Match, RV, WV, NV}, + case (catch check_vacm(Access)) of + {ok, {vacmAccess, {GN, Pref, SM, SL, Row}}} -> + Key1 = [length(GN) | GN], + Key2 = [length(Pref) | Pref], + Key3 = [SM, SL], + Key = Key1 ++ Key2 ++ Key3, + snmpa_vacm:insert([{Key, Row}], false), + {ok, Key}; + {error, Reason} -> + {error, Reason}; + Error -> + {error, Error} + end. + +delete_access(Key) -> + snmpa_vacm:delete(Key). + + +add_view_tree_fam(ViewIndex, SubTree, Status, Mask) -> + VTF = {vacmViewTreeFamily, ViewIndex, SubTree, Status, Mask}, + case (catch check_vacm(VTF)) of + {ok, {vacmViewTreeFamily, Row}} -> + Key1 = element(1, Row), + Key2 = element(2, Row), + Key = [length(Key1) | Key1] ++ [length(Key2) | Key2], + case table_cre_row(vacmViewTreeFamilyTable, Key, Row) of + true -> + {ok, Key}; + false -> + {error, create_failed} + end; + {error, Reason} -> + {error, Reason}; + Error -> + {error, Error} + end. + +delete_view_tree_fam(Key) -> + case table_del_row(vacmViewTreeFamilyTable, Key) of + true -> + ok; + false -> + {error, delete_failed} + end. + + +gc_tabs() -> + SecDB = db(vacmSecurityToGroupTable), + SecSTC = stc(vacmSecurityToGroupTable), + SecFOI = foi(vacmSecurityToGroupTable), + snmpa_mib_lib:gc_tab(SecDB, SecSTC, SecFOI), + ViewDB = db(vacmViewTreeFamilyTable), + ViewSTC = stc(vacmViewTreeFamilyTable), + ViewFOI = foi(vacmViewTreeFamilyTable), + snmpa_mib_lib:gc_tab(ViewDB, ViewSTC, ViewFOI), + ok. + +init_vacm_mnesia() -> + F = fun(RowIndex, Row) -> + snmpa_vacm:insert([{RowIndex, Row}], false) + end, + + %% The 5 is intentional: It is a trick to get a tuple with the + %% columns needed by the vacm ets-table (corresponding to the + %% tuple read from the config files). Therefor, 5 since it it + %% is not a real foi... + snmp_generic:table_foreach({vacmAccessTable, mnesia}, F, 5). + + +%%----------------------------------------------------------------- +%% The context table is actually implemented in an internal, +%% non-snmp visible table intContextTable. +%%----------------------------------------------------------------- +vacmContextTable(_Op) -> + ok. +vacmContextTable(set = Op, Arg1, Arg2) -> + snmpa_agent:invalidate_ca_cache(), + snmp_framework_mib:intContextTable(Op, Arg1, Arg2); +vacmContextTable(Op, Arg1, Arg2) -> + snmp_framework_mib:intContextTable(Op, Arg1, Arg2). + + +vacmSecurityToGroupTable(Op) -> + snmp_generic:table_func(Op, db(vacmSecurityToGroupTable)). + +vacmSecurityToGroupTable(get_next, RowIndex, Cols) -> + next(vacmSecurityToGroupTable, RowIndex, Cols); +vacmSecurityToGroupTable(get, RowIndex, Cols) -> + get(vacmSecurityToGroupTable, RowIndex, Cols); +vacmSecurityToGroupTable(set, RowIndex, Cols0) -> + ?vtrace("vacmSecurityToGroupTable(set) -> entry with" + "~n RowIndex: ~p" + "~n Cols0: ~p", [RowIndex, Cols0]), + case (catch verify_vacmSecurityToGroupTable_cols(Cols0, [])) of + {ok, Cols} -> + ?vtrace("vacmSecurityToGroupTable(set) -> verified: " + "~n Cols: ~p", [Cols]), + snmpa_agent:invalidate_ca_cache(), + snmp_generic:table_func(set, RowIndex, Cols, + db(vacmSecurityToGroupTable)); + Error -> + Error + end; +vacmSecurityToGroupTable(is_set_ok, RowIndex, Cols0) -> + ?vtrace("vacmSecurityToGroupTable(is_set_ok) -> entry with" + "~n RowIndex: ~p" + "~n Cols0: ~p", [RowIndex, Cols0]), + case (catch verify_vacmSecurityToGroupTable_cols(Cols0, [])) of + {ok, Cols} -> + ?vtrace("vacmSecurityToGroupTable(is_set_ok) -> verified: " + "~n Cols: ~p", [Cols]), + snmp_generic:table_func(is_set_ok, RowIndex, Cols, + db(vacmSecurityToGroupTable)); + Error -> + Error + end; +vacmSecurityToGroupTable(Op, Arg1, Arg2) -> + snmp_generic:table_func(Op, Arg1, Arg2, db(vacmSecurityToGroupTable)). + + +verify_vacmSecurityToGroupTable_cols([], Cols) -> + ?vtrace("verify_vacmSecurityToGroupTable_cols -> entry when done with" + "~n Cols: ~p", [Cols]), + {ok, lists:reverse(Cols)}; +verify_vacmSecurityToGroupTable_cols([{Col, Val0}|Cols], Acc) -> + ?vtrace("verify_vacmSecurityToGroupTable_cols -> entry with" + "~n Col: ~p" + "~n Val0: ~p", [Col, Val0]), + Val = verify_vacmSecurityToGroupTable_col(Col, Val0), + ?vtrace("verify_vacmSecurityToGroupTable_cols -> verified: " + "~n Val: ~p", [Val]), + verify_vacmSecurityToGroupTable_cols(Cols, [{Col, Val}|Acc]). + +verify_vacmSecurityToGroupTable_col(?vacmSecurityModel, Model) -> + case Model of + any -> ?SEC_ANY; + v1 -> ?SEC_ANY; + v2c -> ?SEC_ANY; + usm -> ?SEC_ANY; + ?SEC_ANY -> ?SEC_ANY; + ?SEC_V1 -> ?SEC_ANY; + ?SEC_V2C -> ?SEC_ANY; + ?SEC_USM -> ?SEC_ANY; + _ -> + ?vlog("verification of vacmSecurityModel(~w) ~p failed", + [?vacmSecurityModel, Model]), + wrongValue(?vacmSecurityModel) + end; +verify_vacmSecurityToGroupTable_col(?vacmSecurityName, Name) -> + case (catch snmp_conf:check_string(Name)) of + ok -> + Name; + Reason -> + ?vlog("verification of vacmSecurityName(~w) ~p failed: " + "~n Reason: ~p", [?vacmSecurityName, Name, Reason]), + wrongValue(?vacmSecurityName) + end; +verify_vacmSecurityToGroupTable_col(?vacmGroupName, Name) -> + case (catch snmp_conf:check_string(Name)) of + ok -> + Name; + Reason -> + ?vlog("verification of vacmGroupName(~w) ~p failed: " + "~n Reason: ~p", [?vacmGroupName, Name, Reason]), + wrongValue(?vacmGroupName) + end; +verify_vacmSecurityToGroupTable_col(_, Val) -> + Val. + + +%%----------------------------------------------------------------- +%% The vacmAccesTable is implemented as a bplus_tree in the +%% snmpa_vacm_access_server process. That means that we'll have +%% to implement everything by ourselves, most notably is_set_ok +%% and set. +%% Each row is stored in the bplus_tree as +%% {RowIndex, {Col4, Col5, ..., Col9}} +%% +%%----------------------------------------------------------------- +vacmAccessTable(_Op) -> + ok. +vacmAccessTable(get, RowIndex, Cols) -> + %% For GET, Cols are guaranteed to be accessible columns. + case snmpa_vacm:get_row(RowIndex) of + {ok, Row} -> + lists:map(fun(Col) -> {value, element(Col-3, Row)} end, Cols); + false -> + {noValue, noSuchInstance} + end; +vacmAccessTable(get_next, RowIndex, Cols) -> + %% For GET-NEXT, Cols can be anything, but they are sorted. + %% Thus, we translate each + %% Example: GET-NEXT -1.3.4.5 + %% 4.3.4.5 + %% 10.3.4.5 + %% Table: Idx= 1.2.3 Col4= 1 + %% Idx= 4.5.6. Col4= 2 + %% Returns: 4.1.2.3 = 1, 4.4.5.6 = 2, endOfTable + {PreCols, ValidCols} = split_cols(Cols, []), + do_get_next([], PreCols) ++ do_get_next(RowIndex, ValidCols); +%% vacmAccessContextMatch does not have a default value => we'll have +%% to treat that col specially +vacmAccessTable(is_set_ok, RowIndex, Cols0) -> + case (catch verify_vacmAccessTable_cols(Cols0, [])) of + {ok, Cols} -> + IsValidKey = is_valid_key(RowIndex), + case lists:keysearch(?vacmAccessStatus, 1, Cols) of + %% Ok, if contextMatch is init + {value, {Col, ?'RowStatus_active'}} -> + {ok, Row} = snmpa_vacm:get_row(RowIndex), + case element(?vacmAContextMatch, Row) of + noinit -> {inconsistentValue, Col}; + _ -> {noError, 0} + end; + {value, {Col, ?'RowStatus_notInService'}} -> % Ok, if not notReady + {ok, Row} = snmpa_vacm:get_row(RowIndex), + case element(?vacmAStatus, Row) of + ?'RowStatus_notReady' -> {inconsistentValue, Col}; + _ -> {noError, 0} + end; + {value, {Col, ?'RowStatus_notReady'}} -> % never ok! + {inconsistentValue, Col}; + {value, {Col, ?'RowStatus_createAndGo'}} -> % ok, if it doesn't exist + Res = lists:keysearch(?vacmAccessContextMatch, 1, Cols), + case snmpa_vacm:get_row(RowIndex) of + false when (IsValidKey =:= true) andalso + is_tuple(Res) -> {noError, 0}; + false -> {noCreation, Col}; % Bad RowIndex + _ -> {inconsistentValue, Col} + end; + {value, {Col, ?'RowStatus_createAndWait'}} -> % ok, if it doesn't exist + case snmpa_vacm:get_row(RowIndex) of + false when (IsValidKey =:= true) -> {noError, 0}; + false -> {noCreation, Col}; % Bad RowIndex + _ -> {inconsistentValue, Col} + end; + {value, {_Col, ?'RowStatus_destroy'}} -> % always ok! + {noError, 0}; + _ -> % otherwise, it's a change; it must exist + case snmpa_vacm:get_row(RowIndex) of + {ok, _} -> + {noError, 0}; + false -> + {inconsistentName, element(1, hd(Cols))} + end + end; + Error -> + Error + end; +vacmAccessTable(set, RowIndex, Cols0) -> + case (catch verify_vacmAccessTable_cols(Cols0, [])) of + {ok, Cols} -> + snmpa_agent:invalidate_ca_cache(), + do_vacmAccessTable_set(RowIndex, Cols); + Error -> + Error + end. + +verify_vacmAccessTable_cols([], Cols) -> + {ok, lists:reverse(Cols)}; +verify_vacmAccessTable_cols([{Col, Val0}|Cols], Acc) -> + Val = verify_vacmAccessTable_col(Col, Val0), + verify_vacmAccessTable_cols(Cols, [{Col, Val}|Acc]). + +verify_vacmAccessTable_col(?vacmAccessContextPrefix, Pref) -> + case (catch snmp_conf:check_string(Pref)) of + ok -> + Pref; + _ -> + wrongValue(?vacmAccessContextPrefix) + end; +verify_vacmAccessTable_col(?vacmAccessSecurityModel, Model) -> + case Model of + any -> ?SEC_ANY; + v1 -> ?SEC_ANY; + v2c -> ?SEC_ANY; + usm -> ?SEC_ANY; + ?SEC_ANY -> ?SEC_ANY; + ?SEC_V1 -> ?SEC_ANY; + ?SEC_V2C -> ?SEC_ANY; + ?SEC_USM -> ?SEC_ANY; + _ -> + wrongValue(?vacmAccessSecurityModel) + end; +verify_vacmAccessTable_col(?vacmAccessSecurityLevel, Level) -> + case Level of + noAuthNoPriv -> 1; + authNoPriv -> 2; + authPriv -> 3; + 1 -> 1; + 2 -> 2; + 3 -> 3; + _ -> wrongValue(?vacmAccessSecurityLevel) + end; +verify_vacmAccessTable_col(?vacmAccessContextMatch, Match) -> + case Match of + exact -> ?vacmAccessContextMatch_exact; + prefix -> ?vacmAccessContextMatch_prefix; + ?vacmAccessContextMatch_exact -> ?vacmAccessContextMatch_exact; + ?vacmAccessContextMatch_prefix -> ?vacmAccessContextMatch_prefix; + _ -> + wrongValue(?vacmAccessContextMatch) + end; +verify_vacmAccessTable_col(?vacmAccessReadViewName, RVN) -> + case (catch snmp_conf:check_string(RVN)) of + ok -> + RVN; + _ -> + wrongValue(?vacmAccessReadViewName) + end; +verify_vacmAccessTable_col(?vacmAccessWriteViewName, WVN) -> + case (catch snmp_conf:check_string(WVN)) of + ok -> + WVN; + _ -> + wrongValue(?vacmAccessWriteViewName) + end; +verify_vacmAccessTable_col(?vacmAccessNotifyViewName, NVN) -> + case (catch snmp_conf:check_string(NVN)) of + ok -> + NVN; + _ -> + wrongValue(?vacmAccessNotifyViewName) + end; +verify_vacmAccessTable_col(_, Val) -> + Val. + +do_vacmAccessTable_set(RowIndex, Cols) -> + case lists:keysearch(?vacmAccessStatus, 1, Cols) of + {value, {_Col, ?'RowStatus_createAndGo'}} -> + Row = mk_row(Cols), + Row2 = setelement(?vacmAStatus, Row, ?'RowStatus_active'), + snmpa_vacm:insert([{RowIndex, Row2}]), + {noError, 0}; + {value, {_Col, ?'RowStatus_createAndWait'}} -> + Row = mk_row(Cols), + Row2 = case element(?vacmAContextMatch, Row) of + noinit -> setelement(?vacmAStatus, Row, + ?'RowStatus_notReady'); + _ -> setelement(?vacmAStatus, Row, + ?'RowStatus_notInService') + end, + snmpa_vacm:insert([{RowIndex, Row2}]), + {noError, 0}; + {value, {_Col, ?'RowStatus_destroy'}} -> + snmpa_vacm:delete(RowIndex), + {noError, 0}; + {value, {_Col, ?'RowStatus_active'}} -> + {ok, Row} = snmpa_vacm:get_row(RowIndex), + NRow = ch_row(Cols, Row), + NRow2 = + case element(?vacmAContextMatch, NRow) of + noinit -> setelement(?vacmAStatus, NRow, + ?'RowStatus_notReady'); + _ -> setelement(?vacmAStatus, NRow, + ?'RowStatus_active') + end, + snmpa_vacm:insert([{RowIndex, NRow2}]), + {noError, 0}; + _ -> + {ok, Row} = snmpa_vacm:get_row(RowIndex), + NRow = ch_row(Cols, Row), + NRow2 = + case element(?vacmAContextMatch, NRow) of + noinit -> setelement(?vacmAStatus, NRow, + ?'RowStatus_notReady'); + _ -> setelement(?vacmAStatus, NRow, + ?'RowStatus_notInService') + end, + snmpa_vacm:insert([{RowIndex, NRow2}]), + {noError, 0} + end. + + +%% Cols are sorted, and all columns are > 3. +do_get_next(RowIndex, Cols) -> + case snmpa_vacm:get_next_row(RowIndex) of + {NextIndex, Row} -> + F1 = fun(Col) when Col < ?vacmAccessStatus -> + {[Col | NextIndex], element(Col-3, Row)}; + (_) -> + endOfTable + end, + lists:map(F1, Cols); + false -> + case snmpa_vacm:get_next_row([]) of + {_NextIndex, Row} -> + F2 = fun(Col) when Col < ?vacmAccessStatus -> + {[Col+1 | RowIndex], element(Col-2, Row)}; + (_) -> + endOfTable + end, + lists:map(F2, Cols); + false -> + lists:map(fun(_Col) -> endOfTable end, Cols) + end + end. + +%%----------------------------------------------------------------- +%% Functions to manipulate vacmAccessRows. +%%----------------------------------------------------------------- +is_valid_key(RowIndex) -> + case catch mk_key(RowIndex) of + true -> true; + _ -> false + end. + +mk_key([L1 | T1]) -> + [L2 | T2] = spx(L1, T1), + [_SM, _SL] = spx(L2, T2), + true. + +spx(N, L) -> spx(N, [], L). +spx(0, _L1, L2) -> L2; +spx(N, L1, [H | L2]) -> spx(N-1, [H | L1], L2). + +mk_row(Cols) -> + ch_row(Cols, {noinit, "", "", "", ?'StorageType_nonVolatile', noinit}). + +ch_row([], Row) -> Row; +ch_row([{Col, Val} | T], Row) -> ch_row(T, setelement(Col-3, Row, Val)). + + +%% Split a list of columns in 2 lists - the first is all columns +%% that are =< 3. For these, use the first accessible column number: 4. +split_cols([Col | Cols], PreCols) when Col =< 3 -> + split_cols(Cols, [4 | PreCols]); +split_cols(Cols, PreCols) -> + {PreCols, Cols}. + +vacmViewSpinLock(new) -> + snmp_generic:variable_func(new, {vacmViewSpinLock, volatile}), + {A1,A2,A3} = erlang:now(), + random:seed(A1,A2,A3), + Val = random:uniform(2147483648) - 1, + snmp_generic:variable_func(set, Val, {vacmViewSpinLock, volatile}); + +vacmViewSpinLock(delete) -> + ok; + +vacmViewSpinLock(get) -> + snmp_generic:variable_func(get, {vacmViewSpinLock, volatile}). + +vacmViewSpinLock(is_set_ok, NewVal) -> + case snmp_generic:variable_func(get, {vacmViewSpinLock, volatile}) of + {value, NewVal} -> noError; + _ -> inconsistentValue + end; +vacmViewSpinLock(set, NewVal) -> + snmp_generic:variable_func(set, (NewVal + 1) rem 2147483648, + {vacmViewSpinLock, volatile}). + + +vacmViewTreeFamilyTable(Op) -> + snmp_generic:table_func(Op, db(vacmViewTreeFamilyTable)). +vacmViewTreeFamilyTable(get_next, RowIndex, Cols) -> + next(vacmViewTreeFamilyTable, RowIndex, Cols); +vacmViewTreeFamilyTable(get, RowIndex, Cols) -> + get(vacmViewTreeFamilyTable, RowIndex, Cols); +vacmViewTreeFamilyTable(set, RowIndex, Cols0) -> + case (catch verify_vacmViewTreeFamilyTable_cols(Cols0, [])) of + {ok, Cols} -> + snmpa_agent:invalidate_ca_cache(), + snmp_generic:table_func(set, RowIndex, Cols, + db(vacmViewTreeFamilyTable)); + Error -> + Error + end; +vacmViewTreeFamilyTable(is_set_ok, RowIndex, Cols0) -> + case (catch verify_vacmViewTreeFamilyTable_cols(Cols0, [])) of + {ok, Cols} -> + snmp_generic:table_func(is_set_ok, RowIndex, Cols, + db(vacmViewTreeFamilyTable)); + Error -> + Error + end; +vacmViewTreeFamilyTable(Op, Arg1, Arg2) -> + snmp_generic:table_func(Op, Arg1, Arg2, db(vacmViewTreeFamilyTable)). + + +verify_vacmViewTreeFamilyTable_cols([], Cols) -> + {ok, lists:reverse(Cols)}; +verify_vacmViewTreeFamilyTable_cols([{Col, Val0}|Cols], Acc) -> + Val = verify_vacmViewTreeFamilyTable_col(Col, Val0), + verify_vacmViewTreeFamilyTable_cols(Cols, [{Col, Val}|Acc]). + +verify_vacmViewTreeFamilyTable_col(?vacmViewTreeFamilyViewName, Name) -> + case (catch snmp_conf:check_string(Name)) of + ok -> + Name; + _ -> + wrongValue(?vacmViewTreeFamilyViewName) + end; +verify_vacmViewTreeFamilyTable_col(?vacmViewTreeFamilySubtree, Tree) -> + case (catch snmp_conf:check_oid(Tree)) of + ok -> + Tree; + _ -> + wrongValue(?vacmViewTreeFamilySubtree) + end; +verify_vacmViewTreeFamilyTable_col(?vacmViewTreeFamilyMask, Mask) -> + case Mask of + null -> []; + [] -> []; + _ -> + case (catch snmp_conf:check_oid(Mask)) of + ok -> + Mask; + _ -> + wrongValue(?vacmViewTreeFamilyMask) + end + end; +verify_vacmViewTreeFamilyTable_col(?vacmViewTreeFamilyType, Type) -> + case Type of + included -> ?view_included; + excluded -> ?view_excluded; + ?view_included -> ?view_included; + ?view_excluded -> ?view_excluded; + _ -> + wrongValue(?vacmViewTreeFamilyType) + end; +verify_vacmViewTreeFamilyTable_col(_, Val) -> + Val. + + +table_next(Name, RestOid) -> + snmp_generic:table_next(db(Name), RestOid). + + +db(X) -> snmpa_agent:db(X). + +fa(vacmSecurityToGroupTable) -> ?vacmGroupName; +fa(vacmViewTreeFamilyTable) -> ?vacmViewTreeFamilyMask. + +foi(vacmSecurityToGroupTable) -> ?vacmSecurityModel; +foi(vacmViewTreeFamilyTable) -> ?vacmViewTreeFamilyViewName. + +noc(vacmSecurityToGroupTable) -> 5; +noc(vacmViewTreeFamilyTable) -> 6. + +stc(vacmSecurityToGroupTable) -> ?vacmSecurityToGroupStorageType; +stc(vacmViewTreeFamilyTable) -> ?vacmViewTreeFamilyStorageType. + +next(Name, RowIndex, Cols) -> + snmp_generic:handle_table_next(db(Name), RowIndex, Cols, + fa(Name), foi(Name), noc(Name)). + +get(Name, RowIndex, Cols) -> + snmp_generic:handle_table_get(db(Name), RowIndex, Cols, foi(Name)). + +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}). + +config_err(F, A) -> + snmpa_error:config_err("[VIEW-BASED-ACM-MIB]: " ++ F, A). + diff --git a/lib/snmp/src/agent/snmpa.erl b/lib/snmp/src/agent/snmpa.erl new file mode 100644 index 0000000000..79493bd892 --- /dev/null +++ b/lib/snmp/src/agent/snmpa.erl @@ -0,0 +1,580 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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). + + +%%---------------------------------------------------------------------- +%% This module contains the user interface to the snmp agent toolkit. +%%---------------------------------------------------------------------- + +-export([verbosity/2, + + current_request_id/0, current_community/0, current_address/0, + current_context/0, current_net_if_data/0, + + get_symbolic_store_db/0, + which_aliasnames/0, + which_tables/0, + which_variables/0, + which_notifications/0, + name_to_oid/1, name_to_oid/2, + oid_to_name/1, oid_to_name/2, + int_to_enum/2, int_to_enum/3, + enum_to_int/2, enum_to_int/3, + + info/0, info/1, old_info_format/1, + load_mibs/1, load_mibs/2, + unload_mibs/1, unload_mibs/2, + which_mibs/0, which_mibs/1, + whereis_mib/1, whereis_mib/2, + dump_mibs/0, dump_mibs/1, + mib_of/1, mib_of/2, + me_of/1, me_of/2, + invalidate_mibs_cache/0, invalidate_mibs_cache/1, + enable_mibs_cache/0, enable_mibs_cache/1, + disable_mibs_cache/0, disable_mibs_cache/1, + gc_mibs_cache/0, gc_mibs_cache/1, gc_mibs_cache/2, gc_mibs_cache/3, + enable_mibs_cache_autogc/0, enable_mibs_cache_autogc/1, + disable_mibs_cache_autogc/0, disable_mibs_cache_autogc/1, + update_mibs_cache_age/1, update_mibs_cache_age/2, + update_mibs_cache_gclimit/1, update_mibs_cache_gclimit/2, + + get/2, get/3, get_next/2, get_next/3, + + register_subagent/3, unregister_subagent/2, + + send_notification/3, send_notification/4, send_notification/5, + send_notification/6, + send_trap/3, send_trap/4, + + discovery/2, discovery/3, discovery/4, discovery/5, discovery/6, + + sys_up_time/0, system_start_time/0, + + backup/1, backup/2, + + convert_config/1, + + restart_worker/0, restart_worker/1, + restart_set_worker/0, restart_set_worker/1]). + +%% USM functions: +-export([passwd2localized_key/3, localize_key/3]). + +%% Agent Capabilities functions +-export([add_agent_caps/2, del_agent_caps/1, get_agent_caps/0]). + +%% Audit Trail Log functions +-export([log_to_txt/2, log_to_txt/3, log_to_txt/4, + log_to_txt/5, log_to_txt/6, log_to_txt/7, + change_log_size/1, + get_log_type/0, get_log_type/1, + change_log_type/1, change_log_type/2, + set_log_type/1, set_log_type/2 + ]). + +%% Message filter / load regulation functions +-export([ + register_notification_filter/3, + register_notification_filter/4, + register_notification_filter/5, + unregister_notification_filter/1, + unregister_notification_filter/2, + which_notification_filter/0, + which_notification_filter/1, + + get_request_limit/0, get_request_limit/1, + set_request_limit/1, set_request_limit/2 + ]). + +-include("snmpa_atl.hrl"). + +-define(EXTRA_INFO, undefined). + + +%%----------------------------------------------------------------- +%% This utility function is used to convert an old SNMP application +%% config (prior to snmp-4.0) to a SNMP agent config (as of +%% snmp-4.0). +%% This is the config structure of the SNMP application as of +%% snmp-4.0: +%% {snmp, snmp_config()} +%% snmp_config() -> [snmp_config_item()] +%% snmp_config_item() -> {agent, agent_config()} | +%% {manager, manager_config()} +%%----------------------------------------------------------------- + +convert_config(Opts) -> + snmpa_app:convert_config(Opts). + + +%%----------------------------------------------------------------- +%% Note that verbosity for the agents is actually only implemented +%% (properly) for the master agent. +%%----------------------------------------------------------------- + +verbosity(all,Verbosity) -> + catch snmpa_agent:verbosity(sub_agents,Verbosity), + catch snmpa_agent:verbosity(master_agent,Verbosity), + catch snmpa_agent:verbosity(net_if,Verbosity), + catch snmpa_agent:verbosity(mib_server,Verbosity), + catch snmpa_agent:verbosity(note_store,Verbosity), + catch snmpa_symbolic_store:verbosity(Verbosity), + catch snmpa_local_db:verbosity(Verbosity); +verbosity(master_agent,Verbosity) -> + catch snmpa_agent:verbosity(master_agent,Verbosity); +verbosity(net_if,Verbosity) -> + catch snmpa_agent:verbosity(net_if,Verbosity); +verbosity(note_store,Verbosity) -> + catch snmpa_agent:verbosity(note_store, Verbosity); +verbosity(mib_server,Verbosity) -> + catch snmpa_agent:verbosity(mib_server,Verbosity); +verbosity(symbolic_store,Verbosity) -> + catch snmpa_symbolic_store:verbosity(Verbosity); +verbosity(local_db,Verbosity) -> + catch snmpa_local_db:verbosity(Verbosity); +verbosity(Agent,{subagents,Verbosity}) -> + catch snmpa_agent:verbosity(Agent,{sub_agents,Verbosity}); +verbosity(Agent,Verbosity) -> + catch snmpa_agent:verbosity(Agent,Verbosity). + + +%%----------------------------------------------------------------- +%% +%% Some symbolic store (internal database) utility functions +%% +%%----------------------------------------------------------------- + +get_symbolic_store_db() -> + snmpa_symbolic_store:get_db(). + + +which_aliasnames() -> + snmpa_symbolic_store:which_aliasnames(). + +which_tables() -> + snmpa_symbolic_store:which_tables(). + +which_variables() -> + snmpa_symbolic_store:which_variables(). + +which_notifications() -> + snmpa_symbolic_store:which_notifications(). + + +%%----------------------------------------------------------------- +%% These 8 functions returns {value, Val} | false +%%----------------------------------------------------------------- +name_to_oid(Name) -> + snmpa_symbolic_store:aliasname_to_oid(Name). + +name_to_oid(Db, Name) -> + snmpa_symbolic_store:aliasname_to_oid(Db, Name). + +oid_to_name(OID) -> + snmpa_symbolic_store:oid_to_aliasname(OID). + +oid_to_name(Db, OID) -> + snmpa_symbolic_store:oid_to_aliasname(Db, OID). + +enum_to_int(Name, Enum) -> + snmpa_symbolic_store:enum_to_int(Name, Enum). + +enum_to_int(Db, Name, Enum) -> + snmpa_symbolic_store:enum_to_int(Db, Name, Enum). + +int_to_enum(Name, Int) -> + snmpa_symbolic_store:int_to_enum(Name, Int). + +int_to_enum(Db, Name, Int) -> + snmpa_symbolic_store:int_to_enum(Db, Name, Int). + + +%%----------------------------------------------------------------- +%% These functions must only be called in the process context +%% where the instrumentation functions are called! +%%----------------------------------------------------------------- +current_request_id() -> current_get(snmp_request_id). +current_context() -> current_get(snmp_context). +current_community() -> current_get(snmp_community). +current_address() -> current_get(snmp_address). +current_net_if_data() -> current_get(net_if_data). + +current_get(Tag) -> + case get(Tag) of + undefined -> false; + X -> {value, X} + end. + + +%% - + +get(Agent, Vars) -> snmpa_agent:get(Agent, Vars). +get(Agent, Vars, Context) -> snmpa_agent:get(Agent, Vars, Context). + +get_next(Agent, Vars) -> snmpa_agent:get_next(Agent, Vars). +get_next(Agent, Vars, Context) -> snmpa_agent:get_next(Agent, Vars, Context). + + +info() -> info(snmp_master_agent). +info(Agent) -> snmpa_agent:info(Agent). + +old_info_format(Info) when is_list(Info) -> + {value, Vsns} = lists:keysearch(vsns, 1, Info), + {value, {_, MibInfo}} = lists:keysearch(mib_server, 1, Info), + {value, SAa} = lists:keysearch(subagents, 1, MibInfo), + {value, LoadedMibs} = lists:keysearch(loaded_mibs, 1, MibInfo), + {value, TreeSz} = lists:keysearch(tree_size_bytes, 1, MibInfo), + {value, ProcMem} = lists:keysearch(process_memory, 1, MibInfo), + {value, DbMem} = lists:keysearch(db_memory, 1, MibInfo), + [Vsns, SAa, LoadedMibs, TreeSz, ProcMem, DbMem]. + + +%% - + +backup(BackupDir) -> + backup(snmp_master_agent, BackupDir). + +backup(Agent, BackupDir) -> + snmpa_agent:backup(Agent, BackupDir). + + +%% - + +dump_mibs() -> snmpa_agent:dump_mibs(snmp_master_agent). +dump_mibs(File) -> snmpa_agent:dump_mibs(snmp_master_agent, File). + +load_mibs(Mibs) -> + load_mibs(snmp_master_agent, Mibs). +load_mibs(Agent, Mibs) when is_list(Mibs) -> + snmpa_agent:load_mibs(Agent, Mibs). + +unload_mibs(Mibs) -> + unload_mibs(snmp_master_agent, Mibs). +unload_mibs(Agent, Mibs) when is_list(Mibs) -> + snmpa_agent:unload_mibs(Agent, Mibs). + +which_mibs() -> which_mibs(snmp_master_agent). +which_mibs(Agent) -> snmpa_agent:which_mibs(Agent). + +whereis_mib(Mib) -> + whereis_mib(snmp_master_agent, Mib). +whereis_mib(Agent, Mib) when is_atom(Mib) -> + snmpa_agent:whereis_mib(Agent, Mib). + + +%% - + +mib_of(Oid) -> + snmpa_agent:mib_of(Oid). + +mib_of(Agent, Oid) -> + snmpa_agent:mib_of(Agent, Oid). + +me_of(Oid) -> + snmpa_agent:me_of(Oid). + +me_of(Agent, Oid) -> + snmpa_agent:me_of(Agent, Oid). + + +invalidate_mibs_cache() -> + invalidate_mibs_cache(snmp_master_agent). + +invalidate_mibs_cache(Agent) -> + snmpa_agent:invalidate_mibs_cache(Agent). + + +enable_mibs_cache() -> + enable_mibs_cache(snmp_master_agent). + +enable_mibs_cache(Agent) -> + snmpa_agent:enable_mibs_cache(Agent). + + +disable_mibs_cache() -> + disable_mibs_cache(snmp_master_agent). + +disable_mibs_cache(Agent) -> + snmpa_agent:disable_mibs_cache(Agent). + + +gc_mibs_cache() -> + gc_mibs_cache(snmp_master_agent). + +gc_mibs_cache(Agent) when is_atom(Agent) orelse is_pid(Agent) -> + snmpa_agent:gc_mibs_cache(Agent); +gc_mibs_cache(Age) -> + gc_mibs_cache(snmp_master_agent, Age). + +gc_mibs_cache(Agent, Age) when is_atom(Agent) orelse is_pid(Agent) -> + snmpa_agent:gc_mibs_cache(Agent, Age); +gc_mibs_cache(Age, GcLimit) -> + gc_mibs_cache(snmp_master_agent, Age, GcLimit). + +gc_mibs_cache(Agent, Age, GcLimit) when is_atom(Agent) orelse is_pid(Agent) -> + snmpa_agent:gc_mibs_cache(Agent, Age, GcLimit). + + +enable_mibs_cache_autogc() -> + enable_mibs_cache_autogc(snmp_master_agent). + +enable_mibs_cache_autogc(Agent) -> + snmpa_agent:enable_mibs_cache_autogc(Agent). + + +disable_mibs_cache_autogc() -> + disable_mibs_cache_autogc(snmp_master_agent). + +disable_mibs_cache_autogc(Agent) -> + snmpa_agent:disable_mibs_cache_autogc(Agent). + + +update_mibs_cache_age(Age) -> + update_mibs_cache_age(snmp_master_agent, Age). + +update_mibs_cache_age(Agent, Age) -> + snmpa_agent:update_mibs_cache_age(Agent, Age). + + +update_mibs_cache_gclimit(GcLimit) -> + update_mibs_cache_age(snmp_master_agent, GcLimit). + +update_mibs_cache_gclimit(Agent, GcLimit) -> + snmpa_agent:update_mibs_cache_gclimit(Agent, GcLimit). + + + + +%% - message filter / load regulation + +register_notification_filter(Id, Mod, Data) when is_atom(Mod) -> + register_notification_filter(snmp_master_agent, Id, Mod, Data, last). + +register_notification_filter(Agent, Id, Mod, Data) + when is_atom(Agent) andalso is_atom(Mod) -> + register_notification_filter(Agent, Id, Mod, Data, last); +register_notification_filter(Agent, Id, Mod, Data) + when is_pid(Agent) andalso is_atom(Mod) -> + register_notification_filter(Agent, Id, Mod, Data, last); +register_notification_filter(Id, Mod, Data, Where) when is_atom(Mod) -> + register_notification_filter(snmp_master_agent, Id, Mod, Data, Where). + +register_notification_filter(Agent, Id, Mod, Data, Where) -> + snmpa_agent:register_notification_filter(Agent, Id, Mod, Data, Where). + +unregister_notification_filter(Id) -> + unregister_notification_filter(snmp_master_agent, Id). + +unregister_notification_filter(Agent, Id) -> + snmpa_agent:unregister_notification_filter(Agent, Id). + +which_notification_filter() -> + which_notification_filter(snmp_master_agent). + +which_notification_filter(Agent) -> + snmpa_agent:which_notification_filter(Agent). + + +get_request_limit() -> + get_request_limit(snmp_master_agent). +get_request_limit(Agent) -> + snmpa_agent:get_request_limit(Agent). + +set_request_limit(NewLimit) -> + set_request_limit(snmp_master_agent, NewLimit). +set_request_limit(Agent, NewLimit) -> + snmpa_agent:set_request_limit(Agent, NewLimit). + + +%% - + +send_notification(Agent, Notification, Recv) -> + send_notification(Agent, Notification, Recv, "", "", []). + +send_notification(Agent, Notification, Recv, Varbinds) -> + send_notification(Agent, Notification, Recv, "", "", Varbinds). + +send_notification(Agent, Notification, Recv, NotifyName, Varbinds) -> + send_notification(Agent, Notification, Recv, NotifyName, "", Varbinds). + +send_notification(Agent, Notification, Recv, + NotifyName, ContextName, Varbinds) + when (is_list(NotifyName) andalso + is_list(ContextName) andalso + is_list(Varbinds)) -> + snmpa_agent:send_trap(Agent, Notification, NotifyName, + ContextName, Recv, Varbinds). + +%% Kept for backwards compatibility +send_trap(Agent, Trap, Community) -> + send_notification(Agent, Trap, no_receiver, Community, "", []). + +send_trap(Agent, Trap, Community, Varbinds) -> + send_notification(Agent, Trap, no_receiver, Community, "", Varbinds). + + +%%%----------------------------------------------------------------- + +discovery(TargetName, Notification) -> + Varbinds = [], + discovery(TargetName, Notification, Varbinds). + +discovery(TargetName, Notification, Varbinds) when is_list(Varbinds) -> + ContextName = "", + discovery(TargetName, Notification, ContextName, Varbinds); +discovery(TargetName, Notification, DiscoHandler) + when is_atom(DiscoHandler) -> + Varbinds = [], + discovery(TargetName, Notification, Varbinds, DiscoHandler). + +discovery(TargetName, Notification, ContextName, Varbinds) + when is_list(Varbinds) -> + DiscoHandler = snmpa_discovery_handler_default, + discovery(TargetName, Notification, ContextName, Varbinds, + DiscoHandler); +discovery(TargetName, Notification, Varbinds, DiscoHandler) + when is_atom(DiscoHandler) -> + ContextName = "", + discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler). + +discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler) -> + ExtraInfo = ?EXTRA_INFO, + discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler, + ExtraInfo). + +discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler, + ExtraInfo) + when (is_list(TargetName) andalso (length(TargetName) > 0) andalso + is_atom(Notification) andalso + is_list(ContextName) andalso + is_list(Varbinds) andalso + is_atom(DiscoHandler)) -> + case (catch snmpa_discovery_handler:verify(DiscoHandler)) of + ok -> + snmpa_agent:discovery(TargetName, Notification, ContextName, + Varbinds, DiscoHandler, ExtraInfo); + Error -> + Error + end. + + +%%%----------------------------------------------------------------- + +register_subagent(Agent, SubTree, SubAgent) -> + snmpa_agent:register_subagent(Agent, SubTree, SubAgent). + +unregister_subagent(Agent, SubOidOrPid) -> + snmpa_agent:unregister_subagent(Agent, SubOidOrPid). + +system_start_time() -> + [{_, Time}] = ets:lookup(snmp_agent_table, system_start_time), + Time. + +sys_up_time() -> + % time in 0.01 seconds. + StartTime = system_start_time(), + (snmp_misc:now(cs) - StartTime) rem (2 bsl 31). + + +%%%----------------------------------------------------------------- + +restart_worker() -> + restart_worker(snmp_master_agent). + +restart_worker(Agent) -> + snmpa_agent:restart_worker(Agent). + + +restart_set_worker() -> + restart_set_worker(snmp_master_agent). + +restart_set_worker(Agent) -> + snmpa_agent:restart_set_worker(Agent). + + +%%%----------------------------------------------------------------- +%%% USM functions +%%%----------------------------------------------------------------- +passwd2localized_key(Alg, Passwd, EngineID) -> + snmp_usm:passwd2localized_key(Alg, Passwd, EngineID). + +localize_key(Alg, Key, EngineID) -> + snmp_usm:localize_key(Alg, Key, EngineID). + + +%%%----------------------------------------------------------------- +%%% Agent Capabilities functions +%%%----------------------------------------------------------------- +add_agent_caps(Oid, Descr) -> + snmp_standard_mib:add_agent_caps(Oid, Descr). + +del_agent_caps(Index) -> + snmp_standard_mib:del_agent_caps(Index). + +get_agent_caps() -> + snmp_standard_mib:get_agent_caps(). + + +%%%----------------------------------------------------------------- +%%% Audit Trail Log functions +%%%----------------------------------------------------------------- +log_to_txt(LogDir, Mibs) -> + OutFile = "snmpa_log.txt", + LogName = ?audit_trail_log_name, + LogFile = ?audit_trail_log_file, + snmp:log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile). +log_to_txt(LogDir, Mibs, OutFile) -> + LogName = ?audit_trail_log_name, + LogFile = ?audit_trail_log_file, + snmp:log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile). +log_to_txt(LogDir, Mibs, OutFile, LogName) -> + LogFile = ?audit_trail_log_file, + snmp:log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile). +log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile) -> + snmp:log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile). +log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start) -> + snmp:log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start). +log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> + snmp:log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop). + + +change_log_size(NewSize) -> + LogName = ?audit_trail_log_name, % The old (agent) default + snmp:change_log_size(LogName, NewSize). + + +get_log_type() -> + get_log_type(snmp_master_agent). + +get_log_type(Agent) -> + snmpa_agent:get_log_type(Agent). + +%% NewType -> atl_type() +change_log_type(NewType) -> + set_log_type(NewType). + +change_log_type(Agent, NewType) -> + set_log_type(Agent, NewType). + +set_log_type(NewType) -> + set_log_type(snmp_master_agent, NewType). + +set_log_type(Agent, NewType) -> + snmpa_agent:set_log_type(Agent, NewType). 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. diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl new file mode 100644 index 0000000000..508a1da514 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_agent.erl @@ -0,0 +1,3964 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_agent). + +-include_lib("kernel/include/file.hrl"). +-include("snmpa_internal.hrl"). +-include("snmp_types.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). + +%% External exports +-export([start_link/4, start_link/5, stop/1]). +-export([subagent_set/2, + load_mibs/2, unload_mibs/2, which_mibs/1, whereis_mib/2, info/1, + register_subagent/3, unregister_subagent/2, + send_trap/6, + register_notification_filter/5, + unregister_notification_filter/2, + which_notification_filter/1, + get_net_if/1]). +-export([ + discovery/6, + is_originating_discovery_enabled/0, + is_terminating_discovery_enabled/0, + terminating_discovery_stage2/0, + terminating_trigger_username/0 + ]). +-export([verbosity/2, dump_mibs/1, dump_mibs/2]). +-export([validate_err/3, make_value_a_correct_value/3, + do_get/3, do_get/4, + get/2, get/3, get_next/2, get_next/3]). +-export([mib_of/1, mib_of/2, me_of/1, me_of/2, + invalidate_mibs_cache/1, + enable_mibs_cache/1, disable_mibs_cache/1, + gc_mibs_cache/1, gc_mibs_cache/2, gc_mibs_cache/3, + enable_mibs_cache_autogc/1, disable_mibs_cache_autogc/1, + update_mibs_cache_age/2, + update_mibs_cache_gclimit/2]). +-export([get_agent_mib_storage/0, db/1, + backup/2]). +-export([get_log_type/1, set_log_type/2]). +-export([get_request_limit/1, set_request_limit/2]). +-export([invalidate_ca_cache/0]). +-export([restart_worker/1, restart_set_worker/1]). + +%% Internal exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3, tr_var/2, tr_varbind/1, + handle_pdu/7, worker/2, worker_loop/1, do_send_trap/6]). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + +-define(empty_pdu_size, 21). + +-ifdef(snmp_extended_verbosity). +-define(vt(F,A), ?vtrace(F, A)). +-else. +-define(vt(_F, _A), ok). +-endif. + +-define(DISCO_TERMINATING_TRIGGER_USERNAME, ""). + + +-ifdef(snmp_debug). +-define(GS_START_LINK3(Prio, Parent, Ref, Opts), + gen_server:start_link(?MODULE, [Prio, Parent, Ref, Opts], + [{debug,[trace]}])). +-define(GS_START_LINK4(Prio, Name, Parent, Ref, Opts), + gen_server:start_link({local, Name}, ?MODULE, + [Prio, Parent, Ref, Opts], + [{debug,[trace]}])). +-else. +-define(GS_START_LINK3(Prio, Parent, Ref, Opts), + gen_server:start_link(?MODULE, [Prio, Parent, Ref, Opts],[])). +-define(GS_START_LINK4(Prio, Name, Parent, Ref, Opts), + gen_server:start_link({local, Name}, ?MODULE, + [Prio, Parent, Ref, Opts],[])). +-endif. + + +-record(notification_filter, {id, mod, data}). +-record(disco, + {from, rec, sender, target, engine_id, + sec_level, ctx, ivbs, stage, handler, extra}). + + +%%----------------------------------------------------------------- +%% The agent is multi-threaded, i.e. each request is handled +%% by a separate process. However, in the normal case, there +%% is just one request handled at the time. In order to improve +%% performance, there is always two worker processes alive. They are +%% created at initialization time. There is always one worker +%% dedicated to SET-handling. When a get*-request is received, +%% it is sent to the worker, and the worker is marked as busy. +%% If a request is received when the worker is busy, a new temporary +%% worker is spawned. +%% Code change +%% =========== +%% Note that the worker(s) execute the same module as the master +%% agent. For code change we have two options - ignore the workers, +%% or send them a code change message. +%%----------------------------------------------------------------- +-record(state, {type, + parent, + worker, + worker_state = ready, + set_worker, + multi_threaded, + ref, + vsns, + nfilters = [], + note_store, + mib_server, %% Currently unused + net_if, %% Currently unused + net_if_mod, + backup, + disco, + mibs_cache_request}). + + +%%%----------------------------------------------------------------- +%%% This module implements the agent machinery; both for the master +%%% agent and the subagents. +%%%----------------------------------------------------------------- +%%% Table of contents +%%% ================= +%%% 1. Interface +%%% 2. Main loop +%%% 3. GET REQUEST +%%% 4. GET-NEXT REQUEST +%%% 5. GET-BULK REQUEST +%%% 6. SET REQUEST +%%% 7. Misc functions +%%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Parent is a Pid (of master_agent) or none +%% Options is a list of Option, where Option is +%% {mibs, Mibs} +%% {net_if, NetIfModule} +%% {priority, Prio} +%% {verbosity, Verbosity} +%% {multi_threaded, Bool} true means that SETs are serialized, +%% while GETs are concurrent, even with a SET. +%% {set_mechanism, SetModule} % undocumented feature +%% +%% The following options are now removed - they are not needed +%% anymore when VACM is standard for authentication, and works +%% with all versions, and trap sending is standardized too. +%% {authentication_service, AuthModule} % undocumented feature +%% {trap_mechanism, TrapModule} % undocumented feature +%% Note: authentication_service is reintroduced for AXD301 (OTP-3324). +%%----------------------------------------------------------------- +start_link(Prio, Parent, Ref, Options) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p", [Prio, Parent, Ref, Options]), + %% gen_server:start_link(?MODULE, [Prio, Parent, Ref, Options], []). + ?GS_START_LINK3(Prio, Parent, Ref, Options). + +start_link(Prio, Name, Parent, Ref, Options) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Name: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p", [Prio, Name, Parent, Ref, Options]), +% gen_server:start_link({local, Name}, ?MODULE, +% [Prio, Parent, Ref, Options], []). + ?GS_START_LINK4(Prio, Name, Parent, Ref, Options). + +stop(Agent) -> call(Agent, stop). + +restart_worker(Agent) -> + call(Agent, restart_worker). + +restart_set_worker(Agent) -> + call(Agent, restart_set_worker). + +get_log_type(Agent) -> + call(Agent, get_log_type). + +set_log_type(Agent, NewType) -> + call(Agent, {set_log_type, NewType}). + +get_request_limit(Agent) -> + call(Agent, get_request_limit). + +set_request_limit(Agent, NewLimit) -> + call(Agent, {set_request_limit, NewLimit}). + +mib_of(Oid) when is_list(Oid) -> + mib_of(snmp_master_agent, Oid). + +mib_of(Agent, Oid) when is_list(Oid) -> + call(Agent, {mib_of, Oid}). + +me_of(Oid) when is_list(Oid) -> + me_of(snmp_master_agent, Oid). + +me_of(Agent, Oid) when is_list(Oid) -> + call(Agent, {me_of, Oid}). + + +invalidate_mibs_cache(Agent) -> + call(Agent, {mibs_cache_request, invalidate_cache}). + + +gc_mibs_cache(Agent) -> + call(Agent, {mibs_cache_request, gc_cache}). + +gc_mibs_cache(Agent, Age) -> + call(Agent, {mibs_cache_request, {gc_cache, Age}}). + +gc_mibs_cache(Agent, Age, GcLimit) -> + call(Agent, {mibs_cache_request, {gc_cache, Age, GcLimit}}). + + +enable_mibs_cache(Agent) -> + call(Agent, {mibs_cache_request, enable_cache}). + +disable_mibs_cache(Agent) -> + call(Agent, {mibs_cache_request, disable_cache}). + + +enable_mibs_cache_autogc(Agent) -> + call(Agent, {mibs_cache_request, enable_autogc}). + +disable_mibs_cache_autogc(Agent) -> + call(Agent, {mibs_cache_request, disable_autogc}). + + +update_mibs_cache_gclimit(Agent, GcLimit) -> + call(Agent, {mibs_cache_request, {update_gclimit, GcLimit}}). + + +update_mibs_cache_age(Agent, Age) -> + call(Agent, {mibs_cache_request, {update_age, Age}}). + + +init([Prio, Parent, Ref, Options]) -> + ?d("init -> entry with" + "~n Prio: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p", [Prio, Parent, Ref, Options]), + case (catch do_init(Prio, Parent, Ref, Options)) of + {ok, State} -> + ?vdebug("started",[]), + {ok, State}; + {error, Reason} -> + config_err("failed starting agent: ~n~p", [Reason]), + {stop, Reason} + end. + +do_init(Prio, Parent, Ref, Options) -> + process_flag(priority, Prio), + put(sname,short_name(Parent)), + put(verbosity,get_verbosity(Options)), + ?vlog("starting with: " + "~n Prio: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p",[Prio, Parent, Ref, Options]), + + Mibs = get_mibs(Options), + SetModule = get_set_mechanism(Options), + put(set_module, SetModule), + + %% OTP-3324. For AXD301. + AuthModule = get_authentication_service(Options), + put(auth_module, AuthModule), + + MultiT = get_multi_threaded(Options), + Vsns = get_versions(Options), + + NS = start_note_store(Prio, Ref, Options), + {Type, NetIfPid, NetIfMod} = + start_net_if(Parent, Prio, Ref, Vsns, NS, Options), + + MibPid = start_mib_server(Prio, Ref, Mibs, Options), + + put(net_if, NetIfPid), + put(mibserver, MibPid), + process_flag(trap_exit, true), + {Worker, SetWorker} = workers_start(MultiT), + {ok, #state{type = Type, + parent = Parent, + worker = Worker, + set_worker = SetWorker, + multi_threaded = MultiT, + ref = Ref, + vsns = Vsns, + note_store = NS, + net_if_mod = NetIfMod}}. + + +start_note_store(Prio, Ref, Options) -> + ?vdebug("start_note_store -> with Prio: ~p", [Prio]), + NsOpts = get_note_store_opt(Options), + + ?vtrace("start_note_store -> NsOpts: ~p", [NsOpts]), + + case (catch snmpa_misc_sup:start_note_store(Prio, Ref, NsOpts)) of + {ok, Pid} -> + ?vdebug("start_note_store -> Pid: ~p", [Pid]), + Pid; + {error, Reason} -> + ?vinfo("error starting note store: ~n~p",[Reason]), + throw({error, {note_store_error, Reason}}); + {'EXIT', Reason} -> + ?vinfo("exit starting note store: ~n~p",[Reason]), + throw({error, {note_store_exit, Reason}}); + Error -> + ?vinfo("failed starting note store: ~n~p",[Error]), + throw({error, {note_store_failed, Error}}) + end. + + +start_net_if(none, Prio, Ref, Vsns, NoteStore, Options) -> + ?vdebug("start_net_if(none) -> with Prio: ~p", [Prio]), + NetIfOpts = get_net_if_opt(Options), + Verbosity = get_net_if_verbosity(NetIfOpts), + Mod = get_net_if_module(NetIfOpts), + NiOptions = get_net_if_options(NetIfOpts), + NiOpts = [{versions, Vsns}, + {verbosity, Verbosity} | NiOptions], + + ?vtrace("start_net_if -> " + "~n Mod: ~p" + "~n NiOpts: ~p",[Mod, NiOpts]), + + case (catch snmpa_misc_sup:start_net_if(Prio, NoteStore, Ref, self(), + Mod, NiOpts)) of + {ok, Pid} -> + ?vdebug("start_net_if -> Pid: ~p", [Pid]), + {master_agent, Pid, Mod}; + {error, Reason} -> + ?vinfo("error starting net if: ~n~p",[Reason]), + throw({error, {net_if_error, Reason}}); + {'EXIT', Reason} -> + ?vinfo("exit starting net if: ~n~p",[Reason]), + throw({error, {net_if_exit, Reason}}); + Error -> + ?vinfo("failed starting net if: ~n~p",[Error]), + throw({error, {net_if_failed, Error}}) + end; +start_net_if(Parent, _Prio, _Ref, _Vsns, _NoteStore, _Options) + when is_pid(Parent) -> + ?vdebug("start_net_if(~p) -> subagent => ignore", [Parent]), + {subagent, undefined, undefined}. + + +start_mib_server(Prio, Ref, Mibs, Options) -> + ?vdebug("start_mib_server -> with Prio: ~p", [Prio]), + MibStorage = get_mib_storage(Options), + MibsOpts = [{mib_storage, MibStorage}|get_option(mib_server, Options, [])], + + ?vtrace("start_mib_server -> " + "~n Mibs: ~p" + "~n MibsOpts: ~p", [Mibs, MibsOpts]), + + case (catch snmpa_misc_sup:start_mib_server(Prio, Ref, Mibs, MibsOpts)) of + {ok, Pid} -> + ?vdebug("start_mib_server -> Pid: ~p", [Pid]), + Pid; + {error, Reason} -> + ?vinfo("error starting mib server: ~n~p",[Reason]), + throw({error, {mib_server_error, Reason}}); + {'EXIT', Reason} -> + ?vinfo("exit starting mib server: ~n~p",[Reason]), + throw({error, {mib_server_exit, Reason}}); + Error -> + ?vinfo("failed starting mib server: ~n~p",[Error]), + throw({error, {mib_server_failed, Error}}) + end. + + +%%----------------------------------------------------------------- +%% Purpose: We must calculate the length of an empty Pdu. This +%% length is used to calculate the max pdu size allowed +%% for each get-bulk-request. This size is +%% dependent on the varbinds. It is calculated +%% as EmptySize + 8. 8 comes from the fact that the +%% maximum pdu size needs 31 bits which needs 5 * 7 bits to be +%% expressed. One 7bit octet is already present in the +%% empty pdu, leaving 4 more 7bit octets. The length is +%% repeated twice, once for the varbinds, and once for the +%% entire pdu; 2 * 4 = 8. +%% Actually, this function is not used, we use a constant instead. +%%----------------------------------------------------------------- +%% Ret: 21 +%% empty_pdu() -> +%% Pdu = #pdu{type = 'get-response', +%% request_id = 1, +%% error_status = noError, +%% error_index = 0, +%% varbinds = []}, +%% length(snmp_pdus:enc_pdu(Pdu)) + 8. + + +%%%-------------------------------------------------- +%%% 1. Interface +%%%-------------------------------------------------- +%% Called by administrator (not subagent; deadlock could occur) +register_subagent(Agent, SubTreeOid, SubagentPid) -> + call(Agent, {register_subagent, SubTreeOid, SubagentPid}). + +%% Called by administrator (not subagent; deadlock could occur) +unregister_subagent(Agent, SubagentOidOrPid) -> + call(Agent, {unregister_subagent, SubagentOidOrPid}). + + +%%----------------------------------------------------------------- +%% These subagent_ functions either return a value, or exits +%% with {nodedown, Node} | Reason. +%%----------------------------------------------------------------- +subagent_get(SubAgent, Varbinds, IsNotification) -> + PduData = get_pdu_data(), + call(SubAgent, {subagent_get, Varbinds, PduData, IsNotification}). + +subagent_get_next(SubAgent, MibView, Varbinds) -> + PduData = get_pdu_data(), + call(SubAgent, {subagent_get_next, MibView, Varbinds, PduData}). + +subagent_set(SubAgent, Arguments) -> + PduData = get_pdu_data(), + call(SubAgent, {subagent_set, Arguments, PduData}). + + +%% Called by administrator (not agent; deadlock would occur) +load_mibs(Agent, Mibs) -> + call(Agent, {load_mibs, Mibs}). + +%% Called by administrator (not agent; deadlock would occur) +unload_mibs(Agent, Mibs) -> + call(Agent, {unload_mibs, Mibs}). + +which_mibs(Agent) -> + call(Agent, which_mibs). + +whereis_mib(Agent, Mib) -> + call(Agent, {whereis_mib, Mib}). + +info(Agent) -> + call(Agent, info). + + +get_net_if(Agent) -> + call(Agent, get_net_if). + + +register_notification_filter(Agent, Id, Mod, Args, Where) + when (Where =:= first) orelse (Where =:= last) -> + case (catch verify_notification_filter_behaviour(Mod)) of + ok -> + call(Agent, {register_notification_filter, Id, Mod, Args, Where}); + Error -> + Error + end; +register_notification_filter(Agent, Id, Mod, Args, {Loc, _Id} = Where) + when (Loc =:= insert_before) orelse (Loc =:= insert_after) -> + case (catch verify_notification_filter_behaviour(Mod)) of + ok -> + call(Agent, {register_notification_filter, Id, Mod, Args, Where}); + Error -> + Error + end. + +verify_notification_filter_behaviour(Mod) -> + snmp_misc:verify_behaviour(snmpa_notification_filter, Mod). + +unregister_notification_filter(Agent, Id) -> + call(Agent, {unregister_notification_filter, Id}). + +which_notification_filter(Agent) -> + call(Agent, which_notification_filter). + + +send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds) -> + ?d("send_trap -> entry with" + "~n self(): ~p" + "~n Agent: ~p [~p]" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n CtxName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [self(), Agent, wis(Agent), Trap, NotifyName, CtxName, Recv, Varbinds]), + Msg = {send_trap, Trap, NotifyName, CtxName, Recv, Varbinds}, + case (wis(Agent) =:= self()) of + false -> + call(Agent, Msg); + true -> + Agent ! Msg + end. + + +%% -- Discovery functions -- + +disco_opts() -> + case ets:lookup(snmp_agent_table, discovery) of + [] -> + []; + [{discovery, DiscoOptions}] -> + DiscoOptions + end. + +originating_disco_opts() -> + DiscoOpts = disco_opts(), + case lists:keysearch(originating, 1, DiscoOpts) of + {value, {originating, OrigDisco}} -> + OrigDisco; + _ -> + [] + end. + +is_originating_discovery_enabled() -> + OrigDisco = originating_disco_opts(), + case lists:keysearch(enable, 1, OrigDisco) of + {value, {enable, false}} -> + false; + _ -> + true + end. + +terminating_disco_opts() -> + DiscoOpts = disco_opts(), + case lists:keysearch(terminating, 1, DiscoOpts) of + {value, {terminating, TermDisco}} -> + TermDisco; + _ -> + [] + end. + +is_terminating_discovery_enabled() -> + TermDisco = terminating_disco_opts(), + case lists:keysearch(enable, 1, TermDisco) of + {value, {enable, false}} -> + false; + _ -> + true + end. + +terminating_discovery_stage2() -> + Default = discovery, + TermDisco = terminating_disco_opts(), + case lists:keysearch(stage2, 1, TermDisco) of + {value, {stage2, Stage2}} when ((Stage2 =:= discovery) orelse (Stage2 =:= plain)) -> + Stage2; + _ -> + Default + end. + +terminating_trigger_username() -> + Default = ?DISCO_TERMINATING_TRIGGER_USERNAME, + TermDisco = terminating_disco_opts(), + case lists:keysearch(trigger_username, 1, TermDisco) of + {value, {trigger_username, Trigger}} when is_list(Trigger) -> + Trigger; + _ -> + Default + end. + + +discovery(TargetName, Notification, ContextName, Varbinds, + DiscoHandler, ExtraInfo) -> + case is_originating_discovery_enabled() of + true -> + Agent = snmp_master_agent, + call(Agent, + {discovery, + TargetName, Notification, ContextName, Varbinds, + DiscoHandler, ExtraInfo}); + false -> + {error, not_enabled} + end. + +wis(Pid) when is_pid(Pid) -> + Pid; +wis(Atom) when is_atom(Atom) -> + whereis(Atom). + +forward_trap(Agent, TrapRecord, NotifyName, CtxName, Recv, Varbinds) -> + Agent ! {forward_trap, TrapRecord, NotifyName, CtxName, Recv, Varbinds}. + + +%%----------------------------------------------------------------- +%% Args: Vars = [Oid] +%% Returns: [Value] +%% Called from a program to get variables. Don't call this from +%% an instrumentation function; deadlock can occur! +%%----------------------------------------------------------------- +get(Agent, Vars) -> + call(Agent, {get, Vars, ""}). + +get(Agent, Vars, Context) -> + call(Agent, {get, Vars, Context}). + +get_next(Agent, Vars) -> + call(Agent, {get_next, Vars, ""}). + +get_next(Agent, Vars, Context) -> + call(Agent, {get_next, Vars, Context}). + + +%%----------------------------------------------------------------- + +backup(Agent, BackupDir) when is_list(BackupDir) -> + call(Agent, {backup, BackupDir}). + + +%%----------------------------------------------------------------- +%% Runtime debug support. +%%----------------------------------------------------------------- +dump_mibs(Agent) -> + call(Agent, dump_mibs). +dump_mibs(Agent, File) when is_list(File) -> + call(Agent, {dump_mibs, File}). + + +%%----------------------------------------------------------------- +%% Runtime debug (verbosity) support. +%%----------------------------------------------------------------- +verbosity(net_if,Verbosity) -> + cast(snmp_master_agent,{net_if_verbosity,Verbosity}); +verbosity(mib_server,Verbosity) -> + cast(snmp_master_agent,{mib_server_verbosity,Verbosity}); +verbosity(note_store,Verbosity) -> + cast(snmp_master_agent,{note_store_verbosity,Verbosity}); +verbosity(sub_agents,Verbosity) -> + cast(snmp_master_agent,{sub_agents_verbosity,Verbosity}); +verbosity(master_agent,Verbosity) -> + cast(snmp_master_agent,{verbosity,Verbosity}); +verbosity(Agent,{sub_agents,Verbosity}) -> + cast(Agent,{sub_agents_verbosity,Verbosity}); +verbosity(Agent,Verbosity) -> + cast(Agent,{verbosity,Verbosity}). + + +%%%-------------------------------------------------- + +get_agent_mib_storage() -> + ets:lookup_element(snmp_agent_table, agent_mib_storage, 2). + +db(Tab) -> + {Tab, get_agent_mib_storage()}. + + +%%%-------------------------------------------------- +%%% 2. Main loop +%%%-------------------------------------------------- +%% gen_server:reply(From, Reply) +handle_info({discovery_response, Response}, S) -> + ?vdebug("handle_info(discovery_response) -> entry with" + "~n Response: ~p", [Response]), + NewS = handle_discovery_response(S, Response), + {noreply, NewS}; + +handle_info({snmp_pdu, Vsn, Pdu, PduMS, ACMData, Address, Extra}, S) -> + ?vdebug("handle_info(snmp_pdu) -> entry with" + "~n Vsn: ~p" + "~n Pdu: ~p" + "~n Address: ~p", [Vsn, Pdu, Address]), + + NewS = handle_snmp_pdu(is_valid_pdu_type(Pdu#pdu.type), + Vsn, Pdu, PduMS, ACMData, Address, Extra, S), + + {noreply, NewS}; + +handle_info(worker_available, S) -> + ?vdebug("worker available",[]), + {noreply, S#state{worker_state = ready}}; + +handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, S) -> + ?vlog("[handle_info] send trap request:" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [Trap,NotifyName,ContextName,Recv,Varbinds]), + case catch handle_send_trap(S, Trap, NotifyName, ContextName, + Recv, Varbinds) of + {ok, NewS} -> + {noreply, NewS}; + {'EXIT', R} -> + ?vinfo("Trap not sent:~n ~p", [R]), + {noreply, S}; + _ -> + {noreply, S} + end; + +handle_info({forward_trap, TrapRecord, NotifyName, ContextName, + Recv, Varbinds},S) -> + ?vlog("[handle_info] forward trap request:" + "~n TrapRecord: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [TrapRecord,NotifyName,ContextName,Recv,Varbinds]), + case (catch maybe_send_trap(S, TrapRecord, NotifyName, ContextName, + Recv, Varbinds)) of + {ok, NewS} -> + {noreply, NewS}; + {'EXIT', R} -> + ?vinfo("Trap not sent:~n ~p", [R]), + {noreply, S}; + _ -> + {noreply, S} + end; + +handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) -> + ?vlog("[handle_info] backup done:" + "~n Reply: ~p", [Reply]), + gen_server:reply(From, Reply), + {noreply, S#state{backup = undefined}}; + + +handle_info(invalidate_ca_cache, S) -> + invalidate_ca_cache(), + {noreply, S}; + + +%%----------------------------------------------------------------- +%% If a process crashes, we first check to see if it was the mib, +%% net-if or note-store. +%% Otherwise, we check to see if it was a subagent. In this case +%% we unregister the sa, and unlink us from the sa. +%%----------------------------------------------------------------- +handle_info({'EXIT', Pid, Reason}, #state{note_store = Pid} = S) -> + ?vlog("note store (~p) exited for reason ~n~p", [Pid, Reason]), + error_msg("note-store exited: ~n~p", [Reason]), + {stop, {note_store_exit, Reason}, S#state{note_store = undefined}}; +handle_info({'EXIT', Pid, Reason}, #state{worker = Pid} = S) -> + ?vlog("worker (~p) exited -> create new ~n ~p", [Pid, Reason]), + NewWorker = worker_start(), + {noreply, S#state{worker = NewWorker}}; +handle_info({'EXIT', Pid, Reason}, #state{set_worker = Pid} = S) -> + ?vlog("set-worker (~p) exited -> create new ~n ~p", [Pid,Reason]), + NewWorker = set_worker_start(), + {noreply, S#state{set_worker = NewWorker}}; +handle_info({'EXIT', Pid, Reason}, #state{parent = Pid} = S) -> + ?vlog("parent (~p) exited for reason ~n~p", [Pid,Reason]), + {stop, {parent_died, Reason}, S}; +handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) -> + ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]), + case Reason of + normal -> + {noreply, S}; + _ -> + gen_server:reply(From, {error, Reason}), + {noreply, S#state{backup = undefined}} + end; +handle_info({'EXIT', Pid, Reason}, S) -> + ?vlog("~p exited for reason ~p", [Pid, Reason]), + Mib = get(mibserver), + NetIf = get(net_if), + case Pid of + Mib -> + error_msg("mib-server exited: ~n~p", [Reason]), + {stop, {mib_server_exit, Reason}, S}; + NetIf -> + error_msg("net-if exited: ~n~p", [Reason]), + {stop, {net_if_exit, Reason}, S}; + _ -> + %% Could be a sub-agent + SAs = snmpa_mib:info(Mib, subagents), + case lists:keysearch(Pid, 1, SAs) of + {value, _} -> + ?vlog("subagent exit", []), + snmpa_mib:unregister_subagent(Mib, Pid), + unlink(Pid); + _ -> + %% Otherwise it was probably a worker thread - ignore + ok + end, + {noreply, S} + end; +handle_info({'DOWN', Ref, process, Pid, {mibs_cache_reply, Reply}}, + #state{mibs_cache_request = {Pid, Ref, From}} = S) -> + ?vlog("reply from the mibs cache request handler (~p): ~n~p", + [Pid, Reply]), + gen_server:reply(From, Reply), + {noreply, S#state{mibs_cache_request = undefined}}; + +handle_info(Info, S) -> + warning_msg("received unexpected info: ~n~p", [Info]), + {noreply, S}. + +handle_call(restart_worker, _From, #state{worker = Pid} = S) -> + if + is_pid(Pid) -> + ?vlog("[handle_call] restart worker ~p", [Pid]), + exit(Pid, kill); + true -> + ?vlog("[handle_call] not multi-threaded => " + "ignoring restart request", []), + ok + end, + {reply, ok, S}; +handle_call(restart_set_worker, _From, #state{set_worker = Pid} = S) -> + if + is_pid(Pid) -> + ?vlog("[handle_call] restart set worker: ~p", [Pid]), + exit(Pid, kill); + true -> + ?vlog("[handle_call] not multi-threaded => " + "ignoring restart request", []), + ok + end, + {reply, ok, S}; +handle_call({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, + _From, S) -> + ?vlog("[handle_call] send trap request:" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [Trap,NotifyName,ContextName,Recv,Varbinds]), + case (catch handle_send_trap(S, Trap, NotifyName, ContextName, + Recv, Varbinds)) of + {ok, NewS} -> + {reply, ok, NewS}; + {'EXIT', Reason} -> + ?vinfo("Trap not sent:~n ~p", [Reason]), + {reply, {error, {send_failed, Reason}}, S}; + _ -> + ?vinfo("Trap not sent", []), + {reply, {error, send_failed}, S} + end; +handle_call({discovery, + TargetName, Notification, ContextName, Vbs, DiscoHandler, ExtraInfo}, + From, + #state{disco = undefined} = S) -> + ?vlog("[handle_call] initiate discovery process:" + "~n TargetName: ~p" + "~n Notification: ~p" + "~n ContextName: ~p" + "~n Vbs: ~p" + "~n DiscoHandler: ~p" + "~n ExtraInfo: ~p", + [TargetName, Notification, ContextName, Vbs, + DiscoHandler, ExtraInfo]), + case handle_discovery(S, From, TargetName, + Notification, ContextName, Vbs, DiscoHandler, + ExtraInfo) of + {ok, NewS} -> + ?vtrace("[handle_call] first stage of discovery process initiated", + []), + {noreply, NewS}; + {error, _} = Error -> + {reply, Error, S} + end; +handle_call({discovery, _TargetName, _Notification, _ContextName, _Vbs, _DiscoHandler, _ExtraInfo}, _From, + #state{disco = DiscoData} = S) -> + Reply = {error, {discovery_in_progress, DiscoData}}, + {reply, Reply, S}; +handle_call({subagent_get, Varbinds, PduData, IsNotification}, _From, S) -> + ?vlog("[handle_call] subagent get:" + "~n Varbinds: ~p" + "~n PduData: ~p", + [Varbinds,PduData]), + put_pdu_data(PduData), + {reply, do_get(Varbinds, IsNotification), S}; +handle_call({subagent_get_next, MibView, Varbinds, PduData}, _From, S) -> + ?vlog("[handle_call] subagent get-next:" + "~n MibView: ~p" + "~n Varbinds: ~p" + "~n PduData: ~p", + [MibView,Varbinds,PduData]), + put_pdu_data(PduData), + {reply, do_get_next(MibView, Varbinds), S}; +handle_call({subagent_set, Arguments, PduData}, _From, S) -> + ?vlog("[handle_call] subagent set:" + "~n Arguments: ~p" + "~n PduData: ~p", + [Arguments,PduData]), + put_pdu_data(PduData), + {reply, do_subagent_set(Arguments), S}; + +handle_call({get, Vars, Context}, _From, S) -> + ?vlog("[handle_call] get:" + "~n Vars: ~p" + "~n Context: ~p", [Vars, Context]), + put_pdu_data({undefined, undefined, undefined, undefined, Context}), + case catch mapfoldl({?MODULE, tr_var}, [], 1, Vars) of + {error, Reason} -> {reply, {error, Reason}, S}; + {_, Varbinds} -> + ?vdebug("Varbinds: ~p",[Varbinds]), + Reply = + case do_get(Varbinds, false) of + {noError, 0, NewVarbinds} -> + Vbs = lists:keysort(#varbind.org_index, NewVarbinds), + [Value || #varbind{value = Value} <- Vbs]; + {ErrorStatus, ErrIndex, _} -> + N = lists:nth(ErrIndex, Vars), + {error, {ErrorStatus, N}} + end, + {reply, Reply, S} + end; + +handle_call({get_next, Vars, Context}, _From, S) -> + ?vlog("[handle_call] get_next:" + "~n Vars: ~p" + "~n Context: ~p",[Vars, Context]), + put_pdu_data({undefined, undefined, undefined, undefined, Context}), + case catch mapfoldl({?MODULE, tr_var}, [], 1, Vars) of + {error, Reason} -> {reply, {error, Reason}, S}; + {_, Varbinds} -> + ?vdebug("Varbinds: ~p",[Varbinds]), + MibView = snmpa_acm:get_root_mib_view(), + Reply = + case do_get_next(MibView, Varbinds) of + {noError, 0, NewVarbinds} -> + Vbs = lists:keysort(#varbind.org_index, NewVarbinds), + [{Oid,Val} || #varbind{oid = Oid, value = Val} <- Vbs]; + {ErrorStatus, ErrIndex, _} -> + N = lists:nth(ErrIndex, Vars), + {error, {ErrorStatus, N}} + end, + {reply, Reply, S} + end; + +handle_call({do_get, MibView, UnsortedVarbinds, IsNotification, PduData}, + _From, S) -> + ?vlog("[handle_call] do_get:" + "~n MibView: ~p" + "~n UnsortedVarbinds: ~p" + "~n IsNotification: ~p" + "~n PduData: ~p", + [MibView, UnsortedVarbinds, IsNotification, PduData]), + put_pdu_data(PduData), + Reply = do_get(MibView, UnsortedVarbinds, IsNotification), + {reply, Reply, S}; + +handle_call({register_subagent, SubTreeOid, SubagentPid}, _From, S) -> + Reply = + case snmpa_mib:register_subagent(get(mibserver), + SubTreeOid, SubagentPid) of + ok -> link(SubagentPid), ok; + Error -> Error + end, + {reply, Reply, S}; + +handle_call({unregister_subagent, SubagentPid}, _From, S) + when is_pid(SubagentPid) -> + ?vlog("[handle_call] unregister subagent ~p", [SubagentPid]), + Reply = snmpa_mib:unregister_subagent(get(mibserver), SubagentPid), + unlink(SubagentPid), + {reply, Reply, S}; + +handle_call({unregister_subagent, SubTreeOid}, _From, S) -> + ?vlog("[handle_call] unregister subagent ~p", [SubTreeOid]), + Reply = + case snmpa_mib:unregister_subagent(get(mibserver), SubTreeOid) of + {ok, DeletedSubagentPid} -> + SAs = snmpa_mib:info(get(mibserver), subagents), + case lists:keysearch(DeletedSubagentPid, 1, SAs) of + {value, _} -> ok; + _ -> unlink(DeletedSubagentPid) + end, + ok; + Error -> + Error + end, + {reply, Reply, S}; + +handle_call({load_mibs, Mibs}, _From, S) -> + ?vlog("load mibs ~p", [Mibs]), + {reply, snmpa_mib:load_mibs(get(mibserver), Mibs), S}; + +handle_call({unload_mibs, Mibs}, _From, S) -> + ?vlog("unload mibs ~p", [Mibs]), + {reply, snmpa_mib:unload_mibs(get(mibserver), Mibs), S}; + +handle_call(which_mibs, _From, S) -> + ?vlog("which mibs", []), + {reply, snmpa_mib:which_mibs(get(mibserver)), S}; + +handle_call({whereis_mib, Mib}, _From, S) -> + ?vlog("whereis mib ~p", [Mib]), + {reply, snmpa_mib:whereis_mib(get(mibserver), Mib), S}; + +handle_call({mibs_cache_request, MibsCacheReq}, From, S) -> + ?vlog("mibs_cache_request: ~p", [MibsCacheReq]), + {MibsCacheWorker, Ref} = + handle_mibs_cache_request(get(mibserver), MibsCacheReq), + NewS = S#state{mibs_cache_request = {MibsCacheWorker, Ref, From}}, + {noreply, NewS}; + +handle_call(info, _From, S) -> + ?vlog("info", []), + Vsns = S#state.vsns, + Stats = get_stats_counters(), + AI = agent_info(S), + NI = net_if_info(S), + NS = note_store_info(S), + SS = symbolic_store_info(), + LD = local_db_info(), + MS = mib_server_info(), + Info = [{vsns, Vsns}, + {stats_counters, Stats}, + {agent, AI}, + {net_if, NI}, + {note_store, NS}, + {symbolic_store, SS}, + {local_db, LD}, + {mib_server, MS}], + {reply, Info, S}; + +handle_call(get_net_if, _From, S) -> + {reply, get(net_if), S}; + +handle_call({backup, BackupDir}, From, S) -> + ?vlog("backup: ~p", [BackupDir]), + Pid = self(), + V = get(verbosity), + MS = get(mibserver), + BackupServer = + erlang:spawn_link( + fun() -> + put(sname, abs), + put(verbosity, V), + Dir = filename:join([BackupDir]), + Reply = handle_backup(Dir, MS), + Pid ! {backup_done, Reply}, + unlink(Pid) + end), + ?vtrace("backup server: ~p", [BackupServer]), + {noreply, S#state{backup = {BackupServer, From}}}; + +handle_call(dump_mibs, _From, S) -> + Reply = snmpa_mib:dump(get(mibserver)), + {reply, Reply, S}; + +handle_call({dump_mibs,File}, _From, S) -> + Reply = snmpa_mib:dump(get(mibserver),File), + {reply, Reply, S}; + +handle_call({register_notification_filter, Id, Mod, Data, Where}, _From, + #state{nfilters = NFs} = S) -> + ?vlog("register_notification_filter -> " + "~n Id: ~p" + "~n Mod: ~p" + "~n Where: ~p", [Id, Mod, Where]), + case lists:keymember(Id, 2, NFs) of + true -> + {reply, {error, {already_registered, Id}}, S}; + false -> + NF = #notification_filter{id = Id, mod = Mod, data = Data}, + {Reply, NewNFs} = add_notification_filter(Where, NF, NFs), + {reply, Reply, S#state{nfilters = NewNFs}} + end; + +handle_call({unregister_notification_filter, Id}, _From, + #state{nfilters = NFs} = S) -> + ?vlog("unregister_notification_filter -> " + "~n Id: ~p", [Id]), + case lists:keydelete(Id, 2, NFs) of + NFs -> + {reply, {error, {not_found, Id}}, S}; + NFs2 -> + {reply, ok, S#state{nfilters = NFs2}} + end; + +handle_call(which_notification_filter, _From, + #state{nfilters = NFs} = S) -> + ?vlog("which_notification_filter", []), + {reply, [Id || #notification_filter{id = Id} <- NFs], S}; + +handle_call({mib_of, Oid}, _From, S) -> + Reply = handle_mib_of(get(mibserver), Oid), + {reply, Reply, S}; + +handle_call({me_of, Oid}, _From, S) -> + Reply = handle_me_of(get(mibserver), Oid), + {reply, Reply, S}; + +handle_call(get_log_type, _From, S) -> + ?vlog("get_log_type", []), + Reply = handle_get_log_type(S), + {reply, Reply, S}; + +handle_call({set_log_type, NewType}, _From, S) -> + ?vlog("set_log_type -> " + "~n NewType: ~p", [NewType]), + Reply = handle_set_log_type(S, NewType), + {reply, Reply, S}; + +handle_call(get_request_limit, _From, S) -> + ?vlog("get_request_limit", []), + Reply = handle_get_request_limit(S), + {reply, Reply, S}; + +handle_call({set_request_limit, NewLimit}, _From, S) -> + ?vlog("set_request_limit -> " + "~n NewLimit: ~p", [NewLimit]), + Reply = handle_set_request_limit(S, NewLimit), + {reply, Reply, S}; + +handle_call(stop, _From, S) -> + {stop, normal, ok, S}; + +handle_call(Req, _From, S) -> + warning_msg("received unknown request: ~n~p", [Req]), + Reply = {error, {unknown, Req}}, + {reply, Reply, S}. + +handle_cast({verbosity,Verbosity}, S) -> + ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]), + put(verbosity,snmp_verbosity:validate(Verbosity)), + case S#state.worker of + Pid when is_pid(Pid) -> Pid ! {verbosity,Verbosity}; + _ -> ok + end, + case S#state.set_worker of + Pid2 when is_pid(Pid2) -> Pid2 ! {verbosity,Verbosity}; + _ -> ok + end, + {noreply, S}; + +handle_cast({sub_agents_verbosity,Verbosity}, S) -> + ?vlog("sub_agents verbosity: ~p",[Verbosity]), + subagents_verbosity(Verbosity), + {noreply, S}; + +%% This should only happen if we are a master_agent +handle_cast({net_if_verbosity, Verbosity}, S) -> + net_if_verbosity(get(net_if), Verbosity), + {noreply, S}; + +handle_cast({mib_server_verbosity, Verbosity}, S) -> + mib_server_verbosity(get(mibserver),Verbosity), + {noreply, S}; + +handle_cast({note_store_verbosity, Verbosity}, #state{note_store = Pid} = S) -> + note_store_verbosity(Pid,Verbosity), + {noreply, S}; + +handle_cast(Msg, S) -> + warning_msg("received unknown message: ~n~p", [Msg]), + {noreply, S}. + + +terminate(shutdown, #state{worker = Worker, + set_worker = SetWorker, + backup = Backup, + ref = Ref}) -> + %% Ordered shutdown - stop misc-workers, net_if, mib-server and note-store. + backup_server_stop(Backup), + worker_stop(Worker, 100), + worker_stop(SetWorker, 100), + snmpa_misc_sup:stop_net_if(Ref), + snmpa_misc_sup:stop_mib_server(Ref); +terminate(_Reason, _S) -> + %% We crashed! We will reuse net_if and mib if we get restarted. + ok. + + +handle_mibs_cache_request(MibServer, Req) -> + {MibsCacheWorker, MibsCacheRef} = + spawn_monitor( + fun() -> + Reply = + case Req of + invalidate_cache -> + snmpa_mib:invalidate_cache(MibServer); + gc_cache -> + snmpa_mib:gc_cache(MibServer); + {gc_cache, Age} -> + snmpa_mib:gc_cache(MibServer, Age); + {gc_cache, Age, GcLimit} -> + snmpa_mib:gc_cache(MibServer, Age, GcLimit); + enable_cache -> + snmpa_mib:enable_cache(MibServer); + disable_cache -> + snmpa_mib:disable_cache(MibServer); + enable_autogc -> + snmpa_mib:enable_cache_autogc(MibServer); + disable_autogc -> + snmpa_mib:disable_cache_autogc(MibServer); + {update_gclimit, GcLimit} -> + snmpa_mib:update_cache_gclimit(MibServer, + GcLimit); + {update_age, Age} -> + snmpa_mib:update_cache_age(MibServer, Age); + _ -> + {error, {unknown_mibs_cache_request, Req}} + end, + exit({mibs_cache_reply, Reply}) + end), + {MibsCacheWorker, MibsCacheRef}. + + +%%----------------------------------------------------------------- +%% Code replacement +%% +%%----------------------------------------------------------------- + +%% Downgrade +%% +code_change({down, _Vsn}, S, downgrade_to_pre_4_13) -> + S1 = workers_restart(S), + case S1#state.disco of + undefined -> + ok; + #disco{from = From, + sender = Sender, + stage = Stage} -> + gen_server:reply(From, {error, {upgrade, Stage, Sender}}), + exit(Sender, kill) + end, + S2 = {state, + S1#state.type, + S1#state.parent, + S1#state.worker, + S1#state.worker_state, + S1#state.set_worker, + S1#state.multi_threaded, + S1#state.ref, + S1#state.vsns, + S1#state.nfilters, + S1#state.note_store, + S1#state.mib_server, + S1#state.net_if, + S1#state.net_if_mod, + S1#state.backup, + S1#state.disco}, + {ok, S2}; + +%% Upgrade +%% +code_change(_Vsn, S, upgrade_from_pre_4_13) -> + {state, + Type, + Parent, + Worker, + WorkerState, + SetWorker, + MultiThreaded, + Ref, + Vsns, + NFilters = [], + NoteStore, + MibServer, %% Currently unused + NetIf, %% Currently unused + NetIfMod, + Backup} = S, + S1 = #state{type = Type, + parent = Parent, + worker = Worker, + worker_state = WorkerState, + set_worker = SetWorker, + multi_threaded = MultiThreaded, + ref = Ref, + vsns = Vsns, + nfilters = NFilters, + note_store = NoteStore, + mib_server = MibServer, + net_if = NetIf, + net_if_mod = NetIfMod, + backup = Backup}, + S2 = workers_restart(S1), + {ok, S2}; + +code_change(_Vsn, S, _Extra) -> + {ok, S}. + + +workers_restart(#state{worker = W, set_worker = SW} = S) -> + Worker = worker_restart(W), + SetWorker = set_worker_restart(SW), + S#state{worker = Worker, + set_worker = SetWorker}. + + +%%----------------------------------------------------------------- + +backup_server_stop({Pid, _}) when is_pid(Pid) -> + exit(Pid, kill); +backup_server_stop(_) -> + ok. + + +workers_start(true) -> + ?vdebug("start worker and set-worker",[]), + {worker_start(), set_worker_start()}; +workers_start(_) -> + {undefined, undefined}. + +worker_start() -> + worker_start(get()). + +set_worker_start() -> + worker_start([{master, self()} | get()]). + +worker_start(Dict) -> + proc_lib:spawn_link(?MODULE, worker, [self(), Dict]). + +worker_stop(Pid) -> + worker_stop(Pid, infinity). + +worker_stop(Pid, Timeout) when is_pid(Pid) -> + Pid ! terminate, + receive + {'EXIT', Pid, normal} -> + ok + after Timeout -> + (catch exit(Pid, kill)), + ok + end; +worker_stop(_, _) -> + ok. + +set_worker_restart(Pid) -> + worker_restart(Pid, [{master, self()} | get()]). + +worker_restart(Pid) -> + worker_restart(Pid, get()). + +worker_restart(Pid, Dict) when is_pid(Pid) -> + worker_stop(Pid), + worker_start(Dict); +worker_restart(Any, _Dict) -> + Any. + + +%%----------------------------------------------------------------- + +handle_backup(BackupDir, MibServer) -> + ?vlog("handle_backup -> entry with" + "~n BackupDir: ~p", [BackupDir]), + case ets:lookup(snmp_agent_table, db_dir) of + [{db_dir, BackupDir}] -> + ?vinfo("handle_backup -> backup dir and db dir the same", []), + {error, db_dir}; + _ -> + case file:read_file_info(BackupDir) of + {ok, #file_info{type = directory}} -> + ?vdebug("handle_backup -> backup dir ok", []), + + VacmRes = (catch snmpa_vacm:backup(BackupDir)), + ?vtrace("handle_backup -> " + "~n VacmRes: ~p", [VacmRes]), + + LdbRes = (catch snmpa_local_db:backup(BackupDir)), + ?vtrace("handle_backup -> " + "~n LdbRes: ~p", [LdbRes]), + + MsRes = (catch snmpa_mib:backup(MibServer, BackupDir)), + ?vtrace("handle_backup -> " + "~n MsRes: ~p", [MsRes]), + + SsRes = (catch snmpa_symbolic_store:backup(BackupDir)), + ?vtrace("handle_backup -> " + "~n SsRes: ~p", [SsRes]), + handle_backup_res([{vacm, VacmRes}, + {local_db, LdbRes}, + {mib_server, MsRes}, + {symbolic_store, SsRes}]); + {ok, _} -> + ?vinfo("handle_backup -> backup dir not a dir", []), + {error, not_a_directory}; + Error -> + ?vinfo("handle_backup -> Error: ~p", [Error]), + Error + end + end. + + +handle_backup_res(Results) -> + handle_backup_res(Results, []). + +handle_backup_res([], []) -> + ok; +handle_backup_res([], Acc) -> + {error, lists:reverse(Acc)}; +handle_backup_res([{_, ok}|Results], Acc) -> + handle_backup_res(Results, Acc); +handle_backup_res([{Who, {error, Reason}}|Results], Acc) -> + handle_backup_res(Results, [{Who, Reason}|Acc]); +handle_backup_res([{Who, Crap}|Results], Acc) -> + handle_backup_res(Results, [{Who, Crap}|Acc]). + + +%%----------------------------------------------------------------- +%% We must cheat to get the community string out of the ACM data, +%% because we (for some reason) support the function +%% snmpa:current_community(). +%%----------------------------------------------------------------- +cheat({community, _SecModel, Community, _IpUdp}, Address, ContextName) -> + {Community, Address, ContextName}; +cheat(_, Address, ContextName) -> + {"", Address, ContextName}. + + +%% This function will either be in the context of the: +%% 1) master-agent +%% 2) set-worker +%% 3) user code +%% ( calling e.g. snmp_community_mib:snmpCommunityTable(set, ...) ) +invalidate_ca_cache() -> + case get(master) of + undefined -> % 1 or 3 + case get(auth_module) of + undefined -> % 3 + %% Ouch, we are not running in the context of the + %% master agent either. Check if we are on the + %% master-agent node. If so, sent it there, + case whereis(snmp_master_agent) of + MasterAgent when is_pid(MasterAgent) -> + case node(MasterAgent) =:= node() of + true -> + %% Ok, we are on the node running the + %% master_agent process, so sent it there + MasterAgent ! invalidate_ca_cache; + false -> + %% This is running on a sub-agent node, + %% so sent skip it + ok + end; + _ -> % Not on this node + ok + end; + AuthMod -> % 1 + AuthMod:invalidate_ca_cache() + end; + Pid -> % 2 + Pid ! invalidate_ca_cache + end. + + +%%----------------------------------------------------------------- +%% Threads and workers +%% +%%----------------------------------------------------------------- + +spawn_thread(Vsn, Pdu, PduMS, ACMData, Address, Extra) -> + Dict = get(), + Args = [Vsn, Pdu, PduMS, ACMData, Address, Extra, Dict], + proc_lib:spawn_link(?MODULE, handle_pdu, Args). + +spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, V) -> + Dict = get(), + proc_lib:spawn_link(?MODULE, do_send_trap, + [TrapRec, NotifyName, ContextName, Recv, V, Dict]). + +do_send_trap(TrapRec, NotifyName, ContextName, Recv, V, Dict) -> + lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), + put(sname,trap_sender_short_name(get(sname))), + ?vlog("starting",[]), + snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, V, + get(net_if)). + +worker(Master, Dict) -> + lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), + put(sname, worker_short_name(get(sname))), + ?vlog("starting",[]), + worker_loop(Master). + +worker_loop(Master) -> + receive + {Vsn, Pdu, PduMS, ACMData, Address, Extra} -> + ?vtrace("worker_loop -> received request", []), + handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra), + Master ! worker_available; + + %% Old style message + {MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra} -> + ?vtrace("worker_loop -> received (old) request", []), + do_handle_pdu(MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra), + Master ! worker_available; + + {TrapRec, NotifyName, ContextName, Recv, V} -> % We don't trap exits! + ?vtrace("worker_loop -> send trap:" + "~n ~p", [TrapRec]), + snmpa_trap:send_trap(TrapRec, NotifyName, + ContextName, Recv, V, get(net_if)), + Master ! worker_available; + + {verbosity, Verbosity} -> + put(verbosity,snmp_verbosity:validate(Verbosity)); + + terminate -> + exit(normal); + + _X -> + %% ignore + ok + + after 30000 -> + %% This is to assure that the worker process leaves a + %% possibly old version of this module. + ok + end, + ?MODULE:worker_loop(Master). + + +%%----------------------------------------------------------------- +%%----------------------------------------------------------------- + +handle_snmp_pdu(true, Vsn, Pdu, PduMS, ACMData, Address, Extra, + #state{multi_threaded = false} = S) -> + ?vtrace("handle_snmp_pdu -> single-thread agent",[]), + handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra), + S; +handle_snmp_pdu(true, Vsn, #pdu{type = 'set-request'} = Pdu, PduMS, + ACMData, Address, Extra, + #state{set_worker = Worker} = S) -> + ?vtrace("handle_snmp_pdu -> multi-thread agent: " + "send set-request to main worker",[]), + Worker ! {Vsn, Pdu, PduMS, ACMData, Address, Extra}, + S#state{worker_state = busy}; +handle_snmp_pdu(true, Vsn, Pdu, PduMS, + ACMData, Address, Extra, + #state{worker_state = busy} = S) -> + ?vtrace("handle_snmp_pdu -> multi-thread agent: " + "main worker busy - create new worker",[]), + spawn_thread(Vsn, Pdu, PduMS, ACMData, Address, Extra), + S; +handle_snmp_pdu(true, Vsn, Pdu, PduMS, ACMData, Address, Extra, + #state{worker = Worker} = S) -> + ?vtrace("handle_snmp_pdu -> multi-thread agent: " + "send to main worker",[]), + Worker ! {Vsn, Pdu, PduMS, ACMData, Address, Extra}, + S#state{worker_state = busy}; +handle_snmp_pdu(_, _Vsn, _Pdu, _PduMS, _ACMData, _Address, _Extra, S) -> + S. + + +%% Called via the spawn_thread function +handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra, Dict) -> + lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), + put(sname, pdu_handler_short_name(get(sname))), + ?vlog("new worker starting",[]), + handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra). + +handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra) -> + %% OTP-3324 + AuthMod = get(auth_module), + case AuthMod:init_check_access(Pdu, ACMData) of + {ok, MibView, ContextName} -> + ?vlog("handle_pdu -> ok:" + "~n MibView: ~p" + "~n ContextName: ~p", [MibView, ContextName]), + AgentData = cheat(ACMData, Address, ContextName), + do_handle_pdu(MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra); + {error, Reason} -> + ?vlog("handle_pdu -> error:" + "~n Reason: ~p", [Reason]), + handle_acm_error(Vsn, Reason, Pdu, ACMData, Address, Extra); + {discarded, Variable, Reason} -> + ?vlog("handle_pdu -> discarded:" + "~n Variable: ~p" + "~n Reason: ~p", [Variable, Reason]), + get(net_if) ! {discarded_pdu, Vsn, Pdu#pdu.request_id, + ACMData, Variable, Extra} + end. + +do_handle_pdu(MibView, Vsn, Pdu, PduMS, + ACMData, {Community, Address, ContextName}, Extra) -> + + put(net_if_data, Extra), + RePdu = process_msg(MibView, Vsn, Pdu, PduMS, Community, + Address, ContextName), + + ?vtrace("do_handle_pdu -> processed:" + "~n RePdu: ~p", [RePdu]), + get(net_if) ! {snmp_response, Vsn, RePdu, + RePdu#pdu.type, ACMData, Address, Extra}. + + +handle_acm_error(Vsn, Reason, Pdu, ACMData, Address, Extra) -> + #pdu{type = Type, request_id = ReqId, varbinds = Vbs} = Pdu, + RawErrStatus = snmpa_acm:error2status(Reason), + case is_valid_pdu_type(Type) of + true -> + %% RawErrStatus can be authorizationError or genErr. If it is + %% authorizationError, we'll have to do different things, + %% depending on which SNMP version is used. + %% v1 - noSuchName error + %% v2 - GET: all variables 'noSuchObject' + %% NEXT/BULK: all variables 'endOfMibView' + %% SET: noAccess error + %% v3 - authorizationError error + %% + %% NOTE: this procedure is not yet defined in the coex document! + ?vdebug("~n Raw error status: ~w",[RawErrStatus]), + Idx = case Vbs of + [] -> 0; + _ -> 1 + end, + RePdu = + if + Vsn =:= 'version-1' -> + ErrStatus = v2err_to_v1err(RawErrStatus), + make_response_pdu(ReqId, ErrStatus, Idx, Vbs, Vbs); + Vsn =:= 'version-3' -> + make_response_pdu(ReqId, RawErrStatus, Idx, Vbs, Vbs); + Type =:= 'get-request' -> % this is v2 + ReVbs = lists:map( + fun(Vb) -> + Vb#varbind{value=noSuchObject} + end, + Vbs), + make_response_pdu(ReqId, noError, 0, Vbs, ReVbs); + Type =:= 'set-request' -> + make_response_pdu(ReqId, noAccess, Idx, Vbs, Vbs); + true -> % next or bulk + ReVbs = lists:map( + fun(Vb) -> + Vb#varbind{value=endOfMibView} + end, + Vbs), + make_response_pdu(ReqId, noError, 0, Vbs, ReVbs) + end, + get(net_if) ! {snmp_response, Vsn, RePdu, + 'get-response', ACMData, Address, Extra}; + false -> + ?vdebug("~n Raw error status: ~w" + "~n invalid pdu type: ~w", + [RawErrStatus,Type]), + ok + end. + + +handle_send_trap(S, TrapName, NotifyName, ContextName, Recv, Varbinds) -> + ?vtrace("handle_send_trap -> entry with" + "~n S#state.type: ~p" + "~n TrapName: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p", + [S#state.type, TrapName, NotifyName, ContextName]), + case snmpa_trap:construct_trap(TrapName, Varbinds) of + {ok, TrapRecord, VarList} -> + ?vtrace("handle_send_trap -> construction complete: " + "~n TrapRecord: ~p" + "~n VarList: ~p", + [TrapRecord, VarList]), + case S#state.type of + subagent -> + ?vtrace("handle_send_trap -> [sub] forward trap",[]), + maybe_forward_trap(S, TrapRecord, NotifyName, + ContextName, Recv, VarList), + {ok, S}; + master_agent -> + ?vtrace("handle_send_trap -> " + "[master] handle send trap",[]), + maybe_send_trap(S, TrapRecord, NotifyName, + ContextName, Recv, VarList) + end; + error -> + error + end. + + +maybe_forward_trap(#state{parent = Parent, nfilters = NFs} = S, + TrapRec, NotifyName, ContextName, Recv, V) -> + ?vtrace("maybe_forward_trap -> entry with" + "~n NFs: ~p", [NFs]), + case filter_notification(NFs, [], TrapRec) of + {dont_send, [], Id} -> + ?vdebug("trap not forwarded [filter ~p]", [Id]), + {ok, S}; + + {dont_send, Removed, Id} -> + ?vdebug("trap not forwarded [filter ~p]", [Id]), + NFs2 = del_notification_filter(Removed, NFs), + {ok, S#state{nfilters = NFs2}}; + + {send, [], TrapRec2} -> + ?vtrace("maybe_forward_trap -> forward trap:" + "~n ~p", [TrapRec2]), + forward_trap(Parent, TrapRec2, NotifyName, ContextName, Recv, V), + {ok, S}; + + {send, Removed, TrapRec2} -> + ?vtrace("maybe_forward_trap -> forward trap:" + "~n ~p", [TrapRec2]), + forward_trap(Parent, TrapRec2, NotifyName, ContextName, Recv, V), + NFs2 = del_notification_filter(Removed, NFs), + {ok, S#state{nfilters = NFs2}} + end. + + +maybe_send_trap(#state{nfilters = NFs} = S, + TrapRec, NotifyName, ContextName, Recv, Varbinds) -> + ?vtrace("maybe_send_trap -> entry with" + "~n NFs: ~p", [NFs]), + case filter_notification(NFs, [], TrapRec) of + {dont_send, [], Id} -> + ?vdebug("trap not sent [filter ~p]",[Id]), + {ok, S}; + + {dont_send, Removed, Id} -> + ?vdebug("trap not sent [filter ~p]",[Id]), + NFs2 = del_notification_filter(Removed, NFs), + {ok, S#state{nfilters = NFs2}}; + + {send, [], TrapRec2} -> + ?vtrace("maybe_send_trap -> send trap:" + "~n ~p", [TrapRec2]), + do_handle_send_trap(S, TrapRec2, + NotifyName, ContextName, Recv, Varbinds); + + {send, Removed, TrapRec2} -> + ?vtrace("maybe_send_trap -> send trap:" + "~n ~p", [TrapRec2]), + NFs2 = del_notification_filter(Removed, NFs), + do_handle_send_trap(S#state{nfilters = NFs2}, TrapRec2, + NotifyName, ContextName, Recv, Varbinds) + end. + +do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds) -> + V = snmpa_trap:try_initialise_vars(get(mibserver), Varbinds), + case S#state.type of + subagent -> + forward_trap(S#state.parent, TrapRec, NotifyName, ContextName, + Recv, V), + {ok, S}; + master_agent when S#state.multi_threaded =:= false -> + ?vtrace("do_handle_send_trap -> send trap:" + "~n ~p", [TrapRec]), + snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, + Recv, V, get(net_if)), + {ok, S}; + master_agent when S#state.worker_state =:= busy -> + %% Main worker busy => create new worker + ?vtrace("do_handle_send_trap -> main worker busy: " + "spawn a trap sender", []), + spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, V), + {ok, S}; + master_agent -> + %% Send to main worker + ?vtrace("do_handle_send_trap -> send to main worker",[]), + S#state.worker ! {TrapRec, NotifyName, ContextName, Recv, V}, + {ok, S#state{worker_state = busy}} + end. + + +filter_notification([], RemoveNFs, Notif) -> + ?vtrace("filter_notification -> done when" + "~n RemoveNFs: ~p" + "~n Notif: ~p", [RemoveNFs, Notif]), + {send, RemoveNFs, Notif}; +filter_notification([NF|NFs], RemoveNFs, Notif0) -> + ?vtrace("filter_notification -> entry with" + "~n NF: ~p" + "~n RemoveNFs: ~p" + "~n Notif0: ~p", [NF, RemoveNFs, Notif0]), + case do_filter_notification(NF, Notif0) of + {dont_send, Id} -> + {dont_send, RemoveNFs, Id}; + {send, Notif} -> + filter_notification(NFs, RemoveNFs, Notif); + {error, Id} -> + filter_notification(NFs, [Id|RemoveNFs], Notif0) + end. + +do_filter_notification(#notification_filter{id = Id, mod = Mod, data = Data}, + Notif) -> + case (catch Mod:handle_notification(Notif, Data)) of + dont_send -> + {dont_send, Id}; + send -> + {send, Notif}; + {send, NewNotif} -> + {send, NewNotif}; + Else -> + user_err("notification filter ~p removed: ~n~p", [Id, Else]), + {error, Id} + end. + + +add_notification_filter(first, NewNF, NFs) -> + {ok, [NewNF | NFs]}; +add_notification_filter(last, NewNF, NFs) -> + {ok, lists:append(NFs, [NewNF])}; +add_notification_filter(Where, NewNF, NFs) -> + case add_nf(Where, NewNF, NFs, []) of + {ok, NFs2} -> + {ok, NFs2}; + Error -> + {Error, NFs} + end. + +add_nf({_Loc, Id}, _NewNF, [], _Acc) -> + {error, {not_found, Id}}; +add_nf({insert_before, Id}, NewNF, [NF|NFs], Acc) + when Id =:= NF#notification_filter.id -> + {ok, lists:reverse(Acc) ++ [NewNF,NF|NFs]}; +add_nf({insert_after, Id}, NewNF, [NF|NFs], Acc) + when Id =:= NF#notification_filter.id -> + {ok, lists:reverse(Acc) ++ [NF,NewNF|NFs]}; +add_nf(Where, NewNF, [NF|NFs], Acc) -> + add_nf(Where, NewNF, NFs, [NF|Acc]). + + +del_notification_filter(IDs, NFs) -> + Fun = fun(Id, NFilters) -> lists:keydelete(Id, 2, NFilters) end, + lists:foldl(Fun, NFs, IDs). + + +handle_discovery(#state{type = master_agent} = S, From, + TargetName, Notification, ContextName, Varbinds, + DiscoHandler, ExtraInfo) -> + ?vtrace("handle_discovery -> entry with" + "~n TargetName: ~p" + "~n Notification: ~p" + "~n ContextName: ~p" + "~n Varbinds: ~p", + [TargetName, Notification, ContextName, Varbinds]), + case snmpa_trap:construct_trap(Notification, Varbinds) of + {ok, Record, InitVars} -> + ?vtrace("handle_discovery -> trap construction complete: " + "~n Record: ~p" + "~n InitVars: ~p", [Record, InitVars]), + send_discovery(S, From, TargetName, + Record, ContextName, InitVars, + DiscoHandler, ExtraInfo); + error -> + {error, failed_constructing_notification} + end; +handle_discovery(_S, _From, + _TargetName, _Notification, _ContextName, _Varbinds, + _DiscoHandler, _ExtraInfo) -> + {error, only_master_discovery}. + +%% We ignore if the master agent is multi-threaded or not. +%% +send_discovery(S, From, + TargetName, Record, ContextName, InitVars, + DiscoHandler, ExtraInfo) -> + case snmpa_trap:send_discovery(TargetName, Record, ContextName, + InitVars, get(net_if)) of + {ok, Sender, SecLevel} -> + Disco = #disco{from = From, + rec = Record, + sender = Sender, + target = TargetName, + sec_level = SecLevel, + ctx = ContextName, + ivbs = InitVars, + stage = 1, + handler = DiscoHandler, + extra = ExtraInfo}, + {ok, S#state{disco = Disco}}; + Error -> + ?vlog("send_discovery -> failed sending discovery: " + "~n Error: ~p", [Error]), + Error + end. + + +handle_discovery_response(#state{disco = #disco{from = From}} = S, + {error, _} = Error) -> + ?vlog("handle_discovery_response -> entry with" + "~n From: ~p" + "~n Error: ~p", [From, Error]), + gen_server:reply(From, Error), + S#state{disco = undefined}; + +handle_discovery_response(#state{disco = #disco{target = TargetName, + stage = 1, + extra = ExtraInfo} = Disco} = S, + {ok, _Pdu, ManagerEngineId}) + when is_record(Disco, disco) -> + ?vlog("handle_discovery_response(1) -> entry with" + "~n TargetName: ~p" + "~n ManagerEngineId: ~p", [TargetName, ManagerEngineId]), + %% This is end of stage 1. + %% So, first we need to update the database with the EngineId of the + %% manager and then deside if we should continue with stage 2. E.g. + %% establish authenticated communication. + case snmp_target_mib:set_target_engine_id(TargetName, ManagerEngineId) of + true when Disco#disco.sec_level =:= ?'SnmpSecurityLevel_noAuthNoPriv' -> + %% Ok, we are done + From = Disco#disco.from, + Handler = Disco#disco.handler, + Reply = + case handle_discovery_stage1_finish(Handler, + TargetName, + ManagerEngineId, + ExtraInfo) of + {ok, _} -> + {ok, ManagerEngineId}; + Error -> + Error + end, + gen_server:reply(From, Reply), + S#state{disco = undefined}; + + true when Disco#disco.sec_level =/= ?'SnmpSecurityLevel_noAuthNoPriv' -> + %% Ok, time for stage 2 + %% Send the same inform again, + %% this time we have the proper EngineId + + From = Disco#disco.from, + Handler = Disco#disco.handler, + + case handle_discovery_stage1_finish(Handler, + TargetName, + ManagerEngineId, + ExtraInfo) of + {ok, NewExtraInfo} -> + ?vdebug("handle_discovery_response(1) -> " + "we are done with stage 1 - " + "continue with stage 2", []), + #disco{rec = Record, + ctx = ContextName, + ivbs = InitVars} = Disco, + case snmpa_trap:send_discovery(TargetName, Record, + ContextName, + InitVars, get(net_if)) of + {ok, Sender, _SecLevel} -> + ?vdebug("handle_discovery_response(1) -> " + "stage 2 trap sent", []), + Disco2 = Disco#disco{sender = Sender, + engine_id = ManagerEngineId, + stage = 2, + extra = NewExtraInfo}, + S#state{disco = Disco2}; + Error -> + ?vlog("handle_discovery_response(1) -> " + "failed sending stage 2 trap: " + "~n ~p", [Error]), + error_msg("failed sending second " + "discovery message: " + "~n ~p", [Error]), + Reply = {error, {second_send_failed, Error}}, + gen_server:reply(From, Reply), + S#state{disco = undefined} + end; + {error, Reason} = Error -> + ?vlog("handle_discovery_response(1) -> " + "stage 1 finish failed: " + "~n ~p", [Reason]), + gen_server:reply(From, Error), + S#state{disco = undefined} + end; + false -> + ?vinfo("handle_discovery_response(1) -> " + "failed setting doscovered engine-id - " + "inform the user", []), + From = Disco#disco.from, + Reason = {failed_setting_engine_id, TargetName, ManagerEngineId}, + gen_server:reply(From, {error, Reason}), + S#state{disco = undefined} + + end; + +handle_discovery_response(#state{disco = #disco{from = From, + engine_id = EngineID, + stage = 2}} = S, + {ok, _Pdu}) -> + ?vlog("handle_discovery_response(2) -> entry with" + "~n From: ~p", [From]), + gen_server:reply(From, {ok, EngineID}), + S#state{disco = undefined}; + +handle_discovery_response(#state{disco = #disco{from = From}} = S, Crap) -> + Reason = {invalid_response, Crap}, + gen_server:reply(From, {error, Reason}), + S#state{disco = undefined}; + +handle_discovery_response(S, Crap) -> + warning_msg("Received unexpected discovery response: ~p", [Crap]), + S. + +handle_discovery_stage1_finish(Handler, TargetName, ManagerEngineID, + ExtraInfo) -> + case (catch Handler:stage1_finish(TargetName, ManagerEngineID, + ExtraInfo)) of + ignore -> + ?vtrace("handle_discovery_stage1_finish -> " + "we are done - [ignore] inform the user", []), + {ok, ExtraInfo}; + + {ok, UsmEntry} when is_tuple(UsmEntry) -> + ?vtrace("handle_discovery_stage1_finish -> " + "received usm entry - attempt to add it", []), + case add_usm_users([UsmEntry]) of + ok -> + {ok, ExtraInfo}; + Error -> + Error + end; + + {ok, UsmEntry, NewExtraInfo} when is_tuple(UsmEntry) -> + ?vtrace("handle_discovery_stage1_finish -> " + "received usm entry - attempt to add it", []), + case add_usm_users([UsmEntry]) of + ok -> + {ok, NewExtraInfo}; + Error -> + Error + end; + + {ok, UsmEntries} when is_list(UsmEntries) -> + ?vtrace("handle_discovery_stage1_finish -> " + "received usm entries - attempt to add them", []), + case add_usm_users(UsmEntries) of + ok -> + {ok, ExtraInfo}; + Error -> + Error + end; + + {ok, UsmEntries, NewExtraInfo} when is_list(UsmEntries) -> + ?vtrace("handle_discovery_stage1_finish -> " + "received usm entries - attempt to add them", []), + case add_usm_users(UsmEntries) of + ok -> + {ok, NewExtraInfo}; + Error -> + Error + end; + + {'EXIT', Reason} -> + ?vlog("handle_discovery_stage1_finish -> stage 1 function exited: " + "~n ~p", [Reason]), + {error, {finish_exit, Reason, ManagerEngineID}}; + + {error, Reason} -> + ?vlog("handle_discovery_stage1_finish -> stage 1 function error: " + "~n ~p", [Reason]), + {error, {finish_error, Reason, ManagerEngineID}}; + + Unknown -> + ?vlog("handle_discovery_stage1_finish -> stage 1 function unknown: " + "~n ~p", [Unknown]), + {error, {finish_failed, Unknown, ManagerEngineID}} + end. + +add_usm_users([]) -> + ok; +add_usm_users([UsmEntry|UsmEntries]) when is_tuple(UsmEntry) -> + ?vtrace("add_usm_users -> attempt to add entry (~w)", + [element(1, UsmEntry)]), + case snmp_user_based_sm_mib:add_user(UsmEntry) of + {ok, _} -> + add_usm_users(UsmEntries); + {error, Reason} -> + ?vlog("add_usm_users -> failed adding usm entry: " + "~n ~p", [Reason]), + {error, {failed_adding_entry, Reason, UsmEntry}} + end. + + +handle_me_of(MibServer, Oid) -> + case snmpa_mib:lookup(MibServer, Oid) of + {variable, ME} -> + {ok, ME}; + {table_column, ME, _TableEntryOid} -> + {ok, ME}; + {subagent, SubAgentPid, _SANextOid} -> + {error, {subagent, SubAgentPid}}; + {false, Reason} -> + {error, Reason}; + Else -> + {error, Else} + end. + + +handle_mib_of(MibServer, Oid) -> + snmpa_mib:which_mib(MibServer, Oid). + + +%%----------------------------------------------------------------- +%% Func: process_msg/7 +%% Returns: RePdu +%%----------------------------------------------------------------- +process_msg(MibView, Vsn, Pdu, PduMS, Community, {Ip, Udp}, ContextName) -> + #pdu{request_id = ReqId} = Pdu, + put(snmp_address, {tuple_to_list(Ip), Udp}), + put(snmp_request_id, ReqId), + put(snmp_community, Community), + put(snmp_context, ContextName), + ?vtrace("process ~p",[Pdu#pdu.type]), + process_pdu(Pdu, PduMS, Vsn, MibView). + +process_pdu(#pdu{type='get-request', request_id = ReqId, varbinds=Vbs}, + _PduMS, Vsn, MibView) -> + ?vtrace("get ~p",[ReqId]), + Res = get_err(do_get(MibView, Vbs, false)), + ?vtrace("get result: " + "~n ~p",[Res]), + {ErrStatus, ErrIndex, ResVarbinds} = + if + Vsn =:= 'version-1' -> validate_get_v1(Res); + true -> Res + end, + ?vtrace("get final result: " + "~n Error status: ~p" + "~n Error index: ~p" + "~n Varbinds: ~p", + [ErrStatus,ErrIndex,ResVarbinds]), + ResponseVarbinds = lists:keysort(#varbind.org_index, ResVarbinds), + ?vtrace("response varbinds: " + "~n ~p",[ResponseVarbinds]), + make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds); + +process_pdu(#pdu{type = 'get-next-request', request_id = ReqId, varbinds = Vbs}, + _PduMS, Vsn, MibView) -> + ?vtrace("process get-next-request -> entry with" + "~n ReqId: ~p" + "~n Vbs: ~p" + "~n MibView: ~p",[ReqId, Vbs, MibView]), + Res = get_err(do_get_next(MibView, Vbs)), + ?vtrace("get-next result: " + "~n ~p",[Res]), + {ErrStatus, ErrIndex, ResVarbinds} = + if + Vsn =:= 'version-1' -> validate_next_v1(Res, MibView); + true -> Res + end, + ?vtrace("get-next final result -> validation result:" + "~n Error status: ~p" + "~n Error index: ~p" + "~n Varbinds: ~p",[ErrStatus,ErrIndex,ResVarbinds]), + ResponseVarbinds = lists:keysort(#varbind.org_index, ResVarbinds), + ?vtrace("get-next final result -> response varbinds: " + "~n ~p",[ResponseVarbinds]), + make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds); + +process_pdu(#pdu{type = 'get-bulk-request',request_id = ReqId,varbinds = Vbs, + error_status = NonRepeaters, error_index = MaxRepetitions}, + PduMS, _Vsn, MibView)-> + {ErrStatus, ErrIndex, ResponseVarbinds} = + get_err(do_get_bulk(MibView,NonRepeaters,MaxRepetitions,PduMS,Vbs)), + ?vtrace("get-bulk final result: " + "~n Error status: ~p" + "~n Error index: ~p" + "~n Respons varbinds: ~p", + [ErrStatus,ErrIndex,ResponseVarbinds]), + make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds); + +process_pdu(#pdu{type = 'set-request', request_id = ReqId, varbinds = Vbs}, + _PduMS, Vsn, MibView)-> + Res = do_set(MibView, Vbs), + ?vtrace("set result: " + "~n ~p",[Res]), + {ErrStatus, ErrIndex} = + if + Vsn =:= 'version-1' -> validate_err(v2_to_v1, Res); + true -> Res + end, + ?vtrace("set final result: " + "~n Error status: ~p" + "~n Error index: ~p",[ErrStatus,ErrIndex]), + make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, Vbs). + +%%----------------------------------------------------------------- +%% Transform a value == noSuchInstance | noSuchObject or a +%% Counter64 type to a noSuchName error for the whole pdu. +%% Args: {Error, Index, Vbs} +%% Returns: {NewError, NewIndex, NewVbs} +%%----------------------------------------------------------------- +validate_get_v1({noError, _, ResponseVarbinds}) -> + case validate_get_v1_2(ResponseVarbinds) of + true -> {noError, 0, ResponseVarbinds}; + {Error, Index} -> {Error, Index, []} % dummy vbs + end; +validate_get_v1({ErrStatus, ErrIndex, ResponseVarbinds}) -> + {v2err_to_v1err(ErrStatus), ErrIndex, ResponseVarbinds}. + +validate_get_v1_2([Vb | Vbs]) + when ((Vb#varbind.value =/= noSuchInstance) andalso + (Vb#varbind.value =/= noSuchObject) andalso + (Vb#varbind.variabletype =/= 'Counter64')) -> + validate_get_v1_2(Vbs); +validate_get_v1_2([Vb | _Vbs]) -> + {noSuchName, Vb#varbind.org_index}; +validate_get_v1_2([]) -> + true. + +%%----------------------------------------------------------------- +%% Transform a value == endOfMibView to a noSuchName for the +%% whole pdu, and do another get-next for any Counter64 value. +%% Args: {Error, Index, Vbs} +%% Returns: {NewError, NewIndex, NewVbs} +%%----------------------------------------------------------------- +validate_next_v1({noError, _, ResponseVarbinds}, MibView) -> + case validate_next_v1_2(ResponseVarbinds, MibView, []) of + {true, NVbs} -> {noError, 0, NVbs}; + {Error, Index} -> {Error, Index, []} % dummy vbs + end; +validate_next_v1({ErrStatus, ErrIndex, ResponseVarbinds}, _MibView) -> + {v2err_to_v1err(ErrStatus), ErrIndex, ResponseVarbinds}. + +validate_next_v1_2([Vb | _Vbs], _MibView, _Res) + when Vb#varbind.value =:= endOfMibView -> + {noSuchName, Vb#varbind.org_index}; +validate_next_v1_2([Vb | Vbs], MibView, Res) + when Vb#varbind.variabletype =:= 'Counter64' -> + case validate_next_v1(do_get_next(MibView, [mk_next_oid(Vb)]), MibView) of + {noError, 0, [NVb]} -> + validate_next_v1_2(Vbs, MibView, [NVb | Res]); + {Error, Index, _OrgVb} -> + {Error, Index} + end; +validate_next_v1_2([Vb | Vbs], MibView, Res) -> + validate_next_v1_2(Vbs, MibView, [Vb | Res]); +validate_next_v1_2([], _MibView, Res) -> + {true, Res}. + +%%----------------------------------------------------------------- +%% Optimization. When we get to a Counter64 object that is a table +%% column, we'll try to find the next instance. This will be the +%% next row in the table, which is a Counter64 value as well. This +%% means that we will loop through the entire table, until we find +%% a column that isn't a Counter64 column. We can optimze this by +%% adding 1 to the column-no in the oid of this instance. +%% If the table is implemented by a subagent this does not help, +%% we'll call that subagent many times. But it shouldn't be any +%% problems. +%%----------------------------------------------------------------- +mk_next_oid(Vb) -> + case snmpa_mib:lookup(get(mibserver), Oid = Vb#varbind.oid) of + {table_column, _MibEntry, TableEntryOid} -> + [Col | _] = Oid -- TableEntryOid, + Vb#varbind{oid = TableEntryOid ++ [Col+1]}; + _ -> + Vb + end. + +%%%----------------------------------------------------------------- +%%% 3. GET REQUEST +%%% -------------- +%%% According to RFC1157, section 4.1.2 and RFC1905, section 4.2.1. +%%% In rfc1157:4.1.2 it isn't specified if noSuchName should be +%%% returned even if some other varbind generates a genErr. +%%% In rfc1905:4.2.1 this is not a problem since exceptions are +%%% used, and thus a genErr will be returned anyway. +%%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Func: do_get/3 +%% Purpose: do_get handles "getRequests". +%% Pre: incoming varbinds have type == 'NULL', value == unSpecified +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- + +%% If this function is called from a worker-process, we *may* +%% need to tunnel into the master-agent and let it do the +%% work + +do_get(MibView, UnsortedVarbinds, IsNotification) -> + do_get(MibView, UnsortedVarbinds, IsNotification, false). + +do_get(MibView, UnsortedVarbinds, IsNotification, ForceMaster) -> + ?vtrace("do_get -> entry with" + "~n MibView: ~p" + "~n UnsortedVarbinds: ~p" + "~n IsNotification: ~p", + [MibView, UnsortedVarbinds, IsNotification]), + case (whereis(snmp_master_agent) =:= self()) of + false when (ForceMaster =:= true) -> + %% I am a lowly worker process, handoff to the master agent + PduData = get_pdu_data(), + call(snmp_master_agent, + {do_get, MibView, UnsortedVarbinds, IsNotification, PduData}); + + _ -> + %% This is me, the master, so go ahead + {OutSideView, InSideView} = + split_vbs_view(UnsortedVarbinds, MibView), + {Error, Index, NewVbs} = + do_get(InSideView, IsNotification), + {Error, Index, NewVbs ++ OutSideView} + + end. + + +split_vbs_view(Vbs, MibView) -> + ?vtrace("split the varbinds view", []), + split_vbs_view(Vbs, MibView, [], []). + +split_vbs_view([Vb | Vbs], MibView, Out, In) -> + case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of + true -> split_vbs_view(Vbs, MibView, Out, [Vb | In]); + false -> split_vbs_view(Vbs, MibView, + [Vb#varbind{value = noSuchObject} | Out], In) + end; +split_vbs_view([], _MibView, Out, In) -> + {Out, In}. + +do_get(UnsortedVarbinds, IsNotification) -> + {MyVarbinds, SubagentVarbinds} = sort_varbindlist(UnsortedVarbinds), + case do_get_local(MyVarbinds, [], IsNotification) of + {noError, 0, NewMyVarbinds} -> + case do_get_subagents(SubagentVarbinds, IsNotification) of + {noError, 0, NewSubagentVarbinds} -> + {noError, 0, NewMyVarbinds ++ NewSubagentVarbinds}; + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex, []} + end; + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex, []} + end. + +%%----------------------------------------------------------------- +%% Func: do_get_local/3 +%% Purpose: Loop the variablebindings list. We know that each varbind +%% in that list belongs to us. +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- +do_get_local([Vb | Vbs], Res, IsNotification) -> + case try_get(Vb, IsNotification) of + NewVb when is_record(NewVb, varbind) -> + do_get_local(Vbs, [NewVb | Res], IsNotification); + ListOfNewVb when is_list(ListOfNewVb) -> + do_get_local(Vbs, lists:append(ListOfNewVb, Res), IsNotification); + {error, Error, OrgIndex} -> + {Error, OrgIndex, []} + end; +do_get_local([], Res, _IsNotification) -> + {noError, 0, Res}. + +%%----------------------------------------------------------------- +%% Func: do_get_subagents/2 +%% Purpose: Loop the list of varbinds for different subagents. +%% For each of them, call sub_agent_get to retreive +%% the values for them. +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- +do_get_subagents(SubagentVarbinds, IsNotification) -> + do_get_subagents(SubagentVarbinds, [], IsNotification). +do_get_subagents([{SubAgentPid, SAVbs} | Tail], Res, IsNotification) -> + {_SAOids, Vbs} = sa_split(SAVbs), + case catch subagent_get(SubAgentPid, Vbs, IsNotification) of + {noError, 0, NewVbs} -> + do_get_subagents(Tail, lists:append(NewVbs, Res), IsNotification); + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex, []}; + {'EXIT', Reason} -> + user_err("Lost contact with subagent (get) ~w. Using genErr", + [Reason]), + {genErr, 0, []} + end; +do_get_subagents([], Res, _IsNotification) -> + {noError, 0, Res}. + + +%%----------------------------------------------------------------- +%% Func: try_get/2 +%% Returns: {error, ErrorStatus, OrgIndex} | +%% #varbind | +%% List of #varbind +%%----------------------------------------------------------------- +try_get(IVb, IsNotification) when is_record(IVb, ivarbind) -> + ?vtrace("try_get(ivarbind) -> entry with" + "~n IVb: ~p", [IVb]), + get_var_value_from_ivb(IVb, IsNotification); +try_get({TableOid, TableVbs}, IsNotification) -> + ?vtrace("try_get(table) -> entry with" + "~n TableOid: ~p" + "~n TableVbs: ~p", [TableOid, TableVbs]), + [#ivarbind{mibentry = MibEntry}|_] = TableVbs, + {NoAccessVbs, AccessVbs} = + check_all_table_vbs(TableVbs, IsNotification, [], []), + case get_tab_value_from_mib(MibEntry, TableOid, AccessVbs) of + {error, ErrorStatus, OrgIndex} -> + {error, ErrorStatus, OrgIndex}; + NVbs -> + NVbs ++ NoAccessVbs + end. + +%%----------------------------------------------------------------- +%% Make sure all requested columns are accessible. +%%----------------------------------------------------------------- +check_all_table_vbs([IVb| IVbs], IsNotification, NoA, A) -> + #ivarbind{mibentry = Me, varbind = Vb} = IVb, + case Me#me.access of + 'not-accessible' -> + NNoA = [Vb#varbind{value = noSuchInstance} | NoA], + check_all_table_vbs(IVbs, IsNotification, NNoA, A); + 'accessible-for-notify' when IsNotification =:= false -> + NNoA = [Vb#varbind{value = noSuchInstance} | NoA], + check_all_table_vbs(IVbs, IsNotification, NNoA, A); + 'write-only' -> + NNoA = [Vb#varbind{value = noSuchInstance} | NoA], + check_all_table_vbs(IVbs, IsNotification, NNoA, A); + _ -> + check_all_table_vbs(IVbs, IsNotification, NoA, [IVb | A]) + end; +check_all_table_vbs([], _IsNotification, NoA, A) -> {NoA, A}. + +%%----------------------------------------------------------------- +%% Returns: {error, ErrorStatus, OrgIndex} | +%% #varbind +%%----------------------------------------------------------------- +get_var_value_from_ivb(IVb, IsNotification) + when IVb#ivarbind.status =:= noError -> + ?vtrace("get_var_value_from_ivb(noError) -> entry", []), + #ivarbind{mibentry = Me, varbind = Vb} = IVb, + #varbind{org_index = OrgIndex, oid = Oid} = Vb, + case Me#me.access of + 'not-accessible' -> + Vb#varbind{value = noSuchInstance}; + 'accessible-for-notify' when IsNotification =:= false -> + Vb#varbind{value = noSuchInstance}; + 'write-only' -> + Vb#varbind{value = noSuchInstance}; + _ -> + case get_var_value_from_mib(Me, Oid) of + {value, Type, Value} -> + Vb#varbind{variabletype = Type, value = Value}; + {error, ErrorStatus} -> + {error, ErrorStatus, OrgIndex} + end + end; +get_var_value_from_ivb(#ivarbind{status = Status, varbind = Vb}, _) -> + ?vtrace("get_var_value_from_ivb(~p) -> entry", [Status]), + Vb#varbind{value = Status}. + +%%----------------------------------------------------------------- +%% Func: get_var_value_from_mib/1 +%% Purpose: +%% Returns: {error, ErrorStatus} | +%% {value, Type, Value} +%%----------------------------------------------------------------- +%% Pre: Oid is a correct instance Oid (lookup checked that). +%% Returns: A correct return value (see make_value_a_correct_value) +get_var_value_from_mib(#me{entrytype = variable, + asn1_type = ASN1Type, + mfa = {Mod, Func, Args}}, + _Oid) -> + ?vtrace("get_var_value_from_mib(variable) -> entry when" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p", [Mod, Func, Args]), + Result = (catch dbg_apply(Mod, Func, [get | Args])), + % mib shall return {value, <a-nice-value-within-range>} | + % {noValue, noSuchName} (v1) | + % {noValue, noSuchObject | noSuchInstance} (v2, v1) + % everything else (including 'genErr') will generate 'genErr'. + make_value_a_correct_value(Result, ASN1Type, {Mod, Func, Args}); + +get_var_value_from_mib(#me{entrytype = table_column, + oid = MeOid, + asn1_type = ASN1Type, + mfa = {Mod, Func, Args}}, + Oid) -> + ?vtrace("get_var_value_from_mib(table_column) -> entry when" + "~n MeOid: ~p" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p" + "~n Oid: ~p", [MeOid, Mod, Func, Args, Oid]), + Col = lists:last(MeOid), + Indexes = snmp_misc:diff(Oid, MeOid), + [Result] = (catch dbg_apply(Mod, Func, [get, Indexes, [Col] | Args])), + make_value_a_correct_value(Result, ASN1Type, + {Mod, Func, Args, Indexes, Col}). + + +%% For table operations we need to pass RestOid down to the table-function. +%% Its up to the table-function to check for noSuchInstance (ex: a +%% non-existing row). +%% Returns: {error, ErrorStatus, OrgIndex} | +%% {value, Type, Value} +get_tab_value_from_mib(#me{mfa = {Mod, Func, Args}}, TableOid, TableVbs) -> + ?vtrace("get_tab_value_from_mib -> entry when" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p", [Mod, Func, Args]), + TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), + SortedVBsRows = snmpa_svbl:sort_varbinds_rows(TableOpsWithShortOids), + case get_value_all_rows(SortedVBsRows, Mod, Func, Args, []) of + {Error, Index} -> + #ivarbind{varbind = Vb} = lists:nth(Index, TableVbs), + {error, Error, Vb#varbind.org_index}; + ListOfValues -> + merge_varbinds_and_value(TableVbs, ListOfValues) + end. + +%%----------------------------------------------------------------- +%% Values is a scrambled list of {CorrectValue, Index}, where Index +%% is index into the #ivarbind list. So for each Value, we must +%% find the corresponding #ivarbind, and merge them into a new +%% #varbind. +%% The Values list comes from validate_tab_res. +%%----------------------------------------------------------------- +merge_varbinds_and_value(IVbs, [{{value, Type, Value}, Index} | Values]) -> + #ivarbind{varbind = Vb} = lists:nth(Index, IVbs), + [Vb#varbind{variabletype = Type, value = Value} | + merge_varbinds_and_value(IVbs, Values)]; +merge_varbinds_and_value(_, []) -> []. + +get_value_all_rows([{[], OrgCols} | Rows], Mod, Func, Args, Res) -> + ?vtrace("get_value_all_rows -> entry when" + "~n OrgCols: ~p", [OrgCols]), + Cols = [{{value, noValue, noSuchInstance}, Index} || + {_Col, _ASN1Type, Index} <- OrgCols], + NewRes = lists:append(Cols, Res), + get_value_all_rows(Rows, Mod, Func, Args, NewRes); +get_value_all_rows([{RowIndex, OrgCols} | Rows], Mod, Func, Args, Res) -> + ?vtrace("get_value_all_rows -> entry when" + "~n RowIndex: ~p" + "~n OrgCols: ~p", [RowIndex, OrgCols]), + {DOrgCols, Dup} = remove_duplicates(OrgCols), + Cols = delete_index(DOrgCols), + Result = (catch dbg_apply(Mod, Func, [get, RowIndex, Cols | Args])), + case validate_tab_res(Result, DOrgCols, {Mod, Func, Args}) of + Values when is_list(Values) -> + NVals = restore_duplicates(Dup, Values), + NewRes = lists:append(NVals, Res), + get_value_all_rows(Rows, Mod, Func, Args, NewRes); + {error, ErrorStatus, Index} -> + validate_err(row_set, {ErrorStatus, Index}, {Mod, Func, Args}) + end; +get_value_all_rows([], _Mod, _Func, _Args, Res) -> + ?vtrace("get_value_all_rows -> entry when done" + "~n Res: ~p", [Res]), + Res. + +%%----------------------------------------------------------------- +%% Returns: list of {ShortOid, ASN1TYpe} +%%----------------------------------------------------------------- +deletePrefixes(Prefix, [#ivarbind{varbind = Varbind, mibentry = ME} | Vbs]) -> + #varbind{oid = Oid} = Varbind, + [{snmp_misc:diff(Oid, Prefix), ME#me.asn1_type} | + deletePrefixes(Prefix, Vbs)]; +deletePrefixes(_Prefix, []) -> []. + +%%----------------------------------------------------------------- +%% Args: {RowIndex, list of {ShortOid, ASN1Type}} +%% Returns: list of Col +%%----------------------------------------------------------------- +delete_index([{Col, _Val, _OrgIndex} | T]) -> + [Col | delete_index(T)]; +delete_index([]) -> []. + +%%----------------------------------------------------------------- +%% This function is called before 'get' on a table, and removes +%% any duplicate columns. It returns {Cols, DupInfo}. The Cols +%% are the unique columns. The instrumentation function is +%% called to get the values. These values, together with the +%% DupInfo, is later passed to restore_duplicates, which uses +%% the retrieved values to reconstruct the original column list, +%% but with the retrieved value for each column. +%%----------------------------------------------------------------- +remove_duplicates(Cols) -> + remove_duplicates(Cols, [], []). + + +remove_duplicates([{Col, V1, OrgIdx1}, {Col, V2, OrgIdx2} | T], NCols, Dup) -> + remove_duplicates([{Col, V1, OrgIdx1} | T], NCols, + [{Col, V2, OrgIdx2} | Dup]); +remove_duplicates([Col | T], NCols, Dup) -> + remove_duplicates(T, [Col | NCols], Dup); +remove_duplicates([], NCols, Dup) -> + {lists:reverse(NCols), lists:reverse(Dup)}. + +restore_duplicates([], Cols) -> + [{Val, OrgIndex} || {_Col, Val, OrgIndex} <- Cols]; +restore_duplicates([{Col, _Val2, OrgIndex2} | Dup], + [{Col, NVal, OrgIndex1} | Cols]) -> + [{NVal, OrgIndex2} | + restore_duplicates(Dup, [{Col, NVal, OrgIndex1} | Cols])]; +restore_duplicates(Dup, [{_Col, Val, OrgIndex} | T]) -> + [{Val, OrgIndex} | restore_duplicates(Dup, T)]. + +%% Maps the column number to Index. +% col_to_index(0, _) -> 0; +% col_to_index(Col, [{Col, _, Index}|_]) -> +% Index; +% col_to_index(Col, [_|Cols]) -> +% col_to_index(Col, Cols). + +%%----------------------------------------------------------------- +%% Three cases: +%% 1) All values ok +%% 2) table_func returned {Error, ...} +%% 3) Some value in Values list is erroneous. +%% Args: Value is a list of values from table_func(get..) +%% OrgCols is a list with {Col, ASN1Type, OrgIndex} +%% each element in Values and OrgCols correspond to each +%% other. +%%----------------------------------------------------------------- +validate_tab_res(Values, OrgCols, Mfa) when is_list(Values) -> + {_Col, _ASN1Type, OneIdx} = hd(OrgCols), + validate_tab_res(Values, OrgCols, Mfa, [], OneIdx); +validate_tab_res({noValue, Error}, OrgCols, Mfa) -> + Values = lists:duplicate(length(OrgCols), {noValue, Error}), + validate_tab_res(Values, OrgCols, Mfa); +validate_tab_res({genErr, Col}, OrgCols, Mfa) -> + case lists:keysearch(Col, 1, OrgCols) of + {value, {_Col, _ASN1Type, Index}} -> + {error, genErr, Index}; + _ -> + user_err("Invalid column in {genErr, ~w} from ~w (get)", + [Col, Mfa]), + [{_Col, _ASN1Type, Index} | _] = OrgCols, + {error, genErr, Index} + end; +validate_tab_res(genErr, [{_Col, __ASN1Type, Index} | _OrgCols], _Mfa) -> + {error, genErr, Index}; +validate_tab_res(Error, [{_Col, _ASN1Type, Index} | _OrgCols], Mfa) -> + user_err("Invalid return value ~w from ~w (get)",[Error, Mfa]), + {error, genErr, Index}. + +validate_tab_res([Value | Values], + [{Col, ASN1Type, Index} | OrgCols], + Mfa, Res, I) -> + %% This one makes it possible to return a list of genErr, which + %% is not allowed according to the manual. But that's ok, as + %% everything else will generate a genErr! (the only problem is + %% that it won't generate a user_error). + case make_value_a_correct_value(Value, ASN1Type, Mfa) of + {error, ErrorStatus} -> + {error, ErrorStatus, Index}; + CorrectValue -> + NewRes = [{Col, CorrectValue, Index} | Res], + validate_tab_res(Values, OrgCols, Mfa, NewRes, I) + end; +validate_tab_res([], [], _Mfa, Res, _I) -> + lists:reverse(Res); +validate_tab_res([], [{_Col, _ASN1Type, Index}|_], Mfa, _Res, _I) -> + user_err("Too few values returned from ~w (get)", [Mfa]), + {error, genErr, Index}; +validate_tab_res(_TooMany, [], Mfa, _Res, I) -> + user_err("Too many values returned from ~w (get)", [Mfa]), + {error, genErr, I}. + + +%%%----------------------------------------------------------------- +%%% 4. GET-NEXT REQUEST +%%% -------------- +%%% According to RFC1157, section 4.1.3 and RFC1905, section 4.2.2. +%%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% Func: do_get_next/2 +%% Purpose: do_get_next handles "getNextRequests". +%% Note: Even if it is SNMPv1, a varbind's value can be +%% endOfMibView. This is converted to noSuchName in process_pdu. +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%% Note2: ListOfNewVarbinds is not sorted in any order!!! +%% Alg: First, the variables are sorted in OID order. +%% +%% Second, next in the MIB is performed for each OID, and +%% the result is collected as: if next oid is a variable, +%% perform a get to retrieve its value; if next oid is in a +%% table, save this value and continue until we get an oid +%% outside this table. Then perform get_next on the table, +%% and continue with all endOfTables and the oid outside the +%% table; if next oid is an subagent, save this value and +%% continue as in the table case. +%% +%% Third, each response is checked for endOfMibView, or (for +%% subagents) that the Oid returned has the correct prefix. +%% (This is necessary since an SA can be registered under many +%% separated subtrees, and if the last variable in the first +%% subtree is requested in a next, the SA will return the first +%% variable in the second subtree. This might be working, since +%% there may be a variable in between these subtrees.) For each +%% of these, a new get-next is performed, one at a time. +%% This alg. might be optimised in several ways. The most +%% striking one is that the same SA might be called several +%% times, when one time should be enough. But it isn't clear +%% that this really matters, since many nexts across the same +%% subagent must be considered to be very rare. +%%----------------------------------------------------------------- +do_get_next(MibView, UnsortedVarbinds) -> + SortedVarbinds = oid_sort_varbindlist(UnsortedVarbinds), + next_loop_varbinds([], SortedVarbinds, MibView, [], []). + +oid_sort_varbindlist(Vbs) -> + lists:keysort(#varbind.oid, Vbs). + +%% LAVb is Last Accessible Vb +next_loop_varbinds([], [Vb | Vbs], MibView, Res, LAVb) -> + ?vt("next_loop_varbinds -> entry when" + "~n Vb: ~p" + "~n MibView: ~p", [Vb, MibView]), + case varbind_next(Vb, MibView) of + endOfMibView -> + RVb = if LAVb =:= [] -> Vb; + true -> LAVb + end, + NewVb = RVb#varbind{variabletype = 'NULL', value = endOfMibView}, + next_loop_varbinds([], Vbs, MibView, [NewVb | Res], []); + {variable, ME, VarOid} when ((ME#me.access =/= 'not-accessible') andalso + (ME#me.access =/= 'write-only') andalso + (ME#me.access =/= 'accessible-for-notify')) -> + case try_get_instance(Vb, ME) of + {value, noValue, _NoSuchSomething} -> + %% Try next one + NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, + next_loop_varbinds([], [NewVb | Vbs], MibView, Res, []); + {value, Type, Value} -> + NewVb = Vb#varbind{oid = VarOid, variabletype = Type, + value = Value}, + next_loop_varbinds([], Vbs, MibView, [NewVb | Res], []); + {error, ErrorStatus} -> + ?vdebug("next loop varbinds:" + "~n ErrorStatus: ~p",[ErrorStatus]), + {ErrorStatus, Vb#varbind.org_index, []} + end; + {variable, _ME, VarOid} -> + RVb = if LAVb =:= [] -> Vb; + true -> LAVb + end, + NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, + next_loop_varbinds([], [NewVb | Vbs], MibView, Res, RVb); + {table, TableOid, TableRestOid, ME} -> + next_loop_varbinds({table, TableOid, ME, + [{tab_oid(TableRestOid), Vb}]}, + Vbs, MibView, Res, []); + {subagent, SubAgentPid, SAOid} -> + NewVb = Vb#varbind{variabletype = 'NULL', value = 'NULL'}, + next_loop_varbinds({subagent, SubAgentPid, SAOid, [NewVb]}, + Vbs, MibView, Res, []) + end; +next_loop_varbinds({table, TableOid, ME, TabOids}, + [Vb | Vbs], MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds(table) -> entry with" + "~n TableOid: ~p" + "~n Vb: ~p", [TableOid, Vb]), + case varbind_next(Vb, MibView) of + {table, TableOid, TableRestOid, _ME} -> + next_loop_varbinds({table, TableOid, ME, + [{tab_oid(TableRestOid), Vb} | TabOids]}, + Vbs, MibView, Res, []); + _ -> + case get_next_table(ME, TableOid, TabOids, MibView) of + {ok, TabRes, TabEndOfTabVbs} -> + NewVbs = lists:append(TabEndOfTabVbs, [Vb | Vbs]), + NewRes = lists:append(TabRes, Res), + next_loop_varbinds([], NewVbs, MibView, NewRes, []); + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next varbind" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end + end; +next_loop_varbinds({table, TableOid, ME, TabOids}, + [], MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds(table) -> entry with" + "~n TableOid: ~p", [TableOid]), + case get_next_table(ME, TableOid, TabOids, MibView) of + {ok, TabRes, TabEndOfTabVbs} -> + ?vt("next_loop_varbinds(table) -> get_next_table result:" + "~n TabRes: ~p" + "~n TabEndOfTabVbs: ~p", [TabRes, TabEndOfTabVbs]), + NewRes = lists:append(TabRes, Res), + next_loop_varbinds([], TabEndOfTabVbs, MibView, NewRes, []); + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next table" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end; +next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, + [Vb | Vbs], MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds(subagent) -> entry with" + "~n SAPid: ~p" + "~n SAOid: ~p" + "~n Vb: ~p", [SAPid, SAOid, Vb]), + case varbind_next(Vb, MibView) of + {subagent, _SubAgentPid, SAOid} -> + next_loop_varbinds({subagent, SAPid, SAOid, + [Vb | SAVbs]}, + Vbs, MibView, Res, []); + _ -> + case get_next_sa(SAPid, SAOid, SAVbs, MibView) of + {ok, SARes, SAEndOfMibViewVbs} -> + NewVbs = lists:append(SAEndOfMibViewVbs, [Vb | Vbs]), + NewRes = lists:append(SARes, Res), + next_loop_varbinds([], NewVbs, MibView, NewRes, []); + {noSuchName, OrgIndex} -> + %% v1 reply, treat this Vb as endOfMibView, and try again + %% for the others. + case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of + {value, EVb} -> + NextOid = next_oid(SAOid), + EndOfVb = + EVb#varbind{oid = NextOid, + value = {endOfMibView, NextOid}}, + case lists:delete(EVb, SAVbs) of + [] -> + next_loop_varbinds([], [EndOfVb, Vb | Vbs], + MibView, Res, []); + TryAgainVbs -> + next_loop_varbinds({subagent, SAPid, SAOid, + TryAgainVbs}, + [EndOfVb, Vb | Vbs], + MibView, Res, []) + end; + false -> + %% bad index from subagent + {genErr, (hd(SAVbs))#varbind.org_index, []} + end; + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next subagent" + "~n Vb: ~p" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [Vb,ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end + end; +next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, + [], MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds(subagent) -> entry with" + "~n SAPid: ~p" + "~n SAOid: ~p", [SAPid, SAOid]), + case get_next_sa(SAPid, SAOid, SAVbs, MibView) of + {ok, SARes, SAEndOfMibViewVbs} -> + NewRes = lists:append(SARes, Res), + next_loop_varbinds([], SAEndOfMibViewVbs, MibView, NewRes, []); + {noSuchName, OrgIndex} -> + %% v1 reply, treat this Vb as endOfMibView, and try again for + %% the others. + case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of + {value, EVb} -> + NextOid = next_oid(SAOid), + EndOfVb = EVb#varbind{oid = NextOid, + value = {endOfMibView, NextOid}}, + case lists:delete(EVb, SAVbs) of + [] -> + next_loop_varbinds([], [EndOfVb], MibView, Res, []); + TryAgainVbs -> + next_loop_varbinds({subagent, SAPid, SAOid, + TryAgainVbs}, + [EndOfVb], MibView, Res, []) + end; + false -> + %% bad index from subagent + {genErr, (hd(SAVbs))#varbind.org_index, []} + end; + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next subagent" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end; +next_loop_varbinds([], [], _MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds -> entry when done", []), + {noError, 0, Res}. + +try_get_instance(_Vb, #me{mfa = {M, F, A}, asn1_type = ASN1Type}) -> + ?vtrace("try get instance from <~p,~p,~p>",[M,F,A]), + Result = (catch dbg_apply(M, F, [get | A])), + % mib shall return {value, <a-nice-value-within-range>} | + % {noValue, noSuchName} (v1) | + % {noValue, noSuchObject | noSuchInstance} (v2, v1) + % everything else (including 'genErr') will generate 'genErr'. + make_value_a_correct_value(Result, ASN1Type, {M, F, A}). + +tab_oid([]) -> [0]; +tab_oid(X) -> X. + +%%----------------------------------------------------------------- +%% Perform a next, using the varbinds Oid if value is simple +%% value. If value is {endOf<something>, NextOid}, use NextOid. +%% This case happens when a table has returned endOfTable, or +%% a subagent has returned endOfMibView. +%%----------------------------------------------------------------- +varbind_next(#varbind{value = Value, oid = Oid}, MibView) -> + ?vt("varbind_next -> entry with" + "~n Value: ~p" + "~n Oid: ~p" + "~n MibView: ~p", [Value, Oid, MibView]), + case Value of + {endOfTable, NextOid} -> + snmpa_mib:next(get(mibserver), NextOid, MibView); + {endOfMibView, NextOid} -> + snmpa_mib:next(get(mibserver), NextOid, MibView); + _ -> + snmpa_mib:next(get(mibserver), Oid, MibView) + end. + +get_next_table(#me{mfa = {M, F, A}}, TableOid, TableOids, MibView) -> + % We know that all TableOids have at least a column number as oid + ?vt("get_next_table -> entry with" + "~n M: ~p" + "~n F: ~p" + "~n A: ~p" + "~n TableOid: ~p" + "~n TableOids: ~p" + "~n MibView: ~p", [M, F, A, TableOid, TableOids, MibView]), + Sorted = snmpa_svbl:sort_varbinds_rows(TableOids), + case get_next_values_all_rows(Sorted, M,F,A, [], TableOid) of + NewVbs when is_list(NewVbs) -> + ?vt("get_next_table -> " + "~n NewVbs: ~p", [NewVbs]), + % We must now check each Vb for endOfTable and that it is + % in the MibView. If not, it becomes a endOfTable. We + % collect all of these together. + transform_tab_next_result(NewVbs, {[], []}, MibView); + {ErrorStatus, OrgIndex} -> + {ErrorStatus, OrgIndex} + end. + +get_next_values_all_rows([Row | Rows], M, F, A, Res, TabOid) -> + {RowIndex, TableOids} = Row, + Cols = delete_index(TableOids), + ?vt("get_next_values_all_rows -> " + "~n Cols: ~p", [Cols]), + Result = (catch dbg_apply(M, F, [get_next, RowIndex, Cols | A])), + ?vt("get_next_values_all_rows -> " + "~n Result: ~p", [Result]), + case validate_tab_next_res(Result, TableOids, {M, F, A}, TabOid) of + Values when is_list(Values) -> + ?vt("get_next_values_all_rows -> " + "~n Values: ~p", [Values]), + NewRes = lists:append(Values, Res), + get_next_values_all_rows(Rows, M, F, A, NewRes, TabOid); + {ErrorStatus, OrgIndex} -> + {ErrorStatus, OrgIndex} + end; +get_next_values_all_rows([], _M, _F, _A, Res, _TabOid) -> + Res. + +transform_tab_next_result([Vb | Vbs], {Res, EndOfs}, MibView) -> + case Vb#varbind.value of + {endOfTable, _} -> +%% ?vtrace("transform_tab_next_result -> endOfTable: " +%% "split varbinds",[]), +%% R = split_varbinds(Vbs, Res, [Vb | EndOfs]), +%% ?vtrace("transform_tab_next_result -> " +%% "~n R: ~p", [R]), +%% R; + split_varbinds(Vbs, Res, [Vb | EndOfs]); + _ -> + case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of + true -> + transform_tab_next_result(Vbs, {[Vb|Res], EndOfs},MibView); + _ -> + Oid = Vb#varbind.oid, + NewEndOf = Vb#varbind{value = {endOfTable, Oid}}, + transform_tab_next_result(Vbs, {Res, [NewEndOf | EndOfs]}, + MibView) + end + end; +transform_tab_next_result([], {Res, EndOfs}, _MibView) -> + ?vt("transform_tab_next_result -> entry with: " + "~n Res: ~p" + "~n EndIfs: ~p",[Res, EndOfs]), + {ok, Res, EndOfs}. + +%%----------------------------------------------------------------- +%% Three cases: +%% 1) All values ok +%% 2) table_func returned {Error, ...} +%% 3) Some value in Values list is erroneous. +%% Args: Value is a list of values from table_func(get_next, ...) +%% TableOids is a list of {TabRestOid, OrgVb} +%% each element in Values and TableOids correspond to each +%% other. +%% Returns: List of NewVarbinds | +%% {ErrorStatus, OrgIndex} +%% (In the NewVarbinds list, the value may be endOfTable) +%%----------------------------------------------------------------- +validate_tab_next_res(Values, TableOids, Mfa, TabOid) -> + ?vt("validate_tab_next_res -> entry with: " + "~n Values: ~p" + "~n TableOids: ~p" + "~n Mfa: ~p" + "~n TabOid: ~p", [Values, TableOids, Mfa, TabOid]), + {_Col, _ASN1Type, OneIdx} = hd(TableOids), + validate_tab_next_res(Values, TableOids, Mfa, [], TabOid, + next_oid(TabOid), OneIdx). +validate_tab_next_res([{NextOid, Value} | Values], + [{_ColNo, OrgVb, _Index} | TableOids], + Mfa, Res, TabOid, TabNextOid, I) -> + ?vt("validate_tab_next_res -> entry with: " + "~n NextOid: ~p" + "~n Value: ~p" + "~n Values: ~p" + "~n TableOids: ~p" + "~n Mfa: ~p" + "~n TabOid: ~p", + [NextOid, Value, Values, TableOids, Mfa, TabOid]), + #varbind{org_index = OrgIndex} = OrgVb, + ?vt("validate_tab_next_res -> OrgIndex: ~p", [OrgIndex]), + NextCompleteOid = lists:append(TabOid, NextOid), + case snmpa_mib:lookup(get(mibserver), NextCompleteOid) of + {table_column, #me{asn1_type = ASN1Type}, _TableEntryOid} -> + ?vt("validate_tab_next_res -> ASN1Type: ~p", [ASN1Type]), + case make_value_a_correct_value({value, Value}, ASN1Type, Mfa) of + {error, ErrorStatus} -> + ?vt("validate_tab_next_res -> " + "~n ErrorStatus: ~p", [ErrorStatus]), + {ErrorStatus, OrgIndex}; + {value, Type, NValue} -> + ?vt("validate_tab_next_res -> " + "~n Type: ~p" + "~n NValue: ~p", [Type, NValue]), + NewVb = OrgVb#varbind{oid = NextCompleteOid, + variabletype = Type, value = NValue}, + validate_tab_next_res(Values, TableOids, Mfa, + [NewVb | Res], TabOid, TabNextOid, I) + end; + Error -> + user_err("Invalid oid ~w from ~w (get_next). Using genErr => ~p", + [NextOid, Mfa, Error]), + {genErr, OrgIndex} + end; +validate_tab_next_res([endOfTable | Values], + [{_ColNo, OrgVb, _Index} | TableOids], + Mfa, Res, TabOid, TabNextOid, I) -> + ?vt("validate_tab_next_res(endOfTable) -> entry with: " + "~n Values: ~p" + "~n OrgVb: ~p" + "~n TableOids: ~p" + "~n Mfa: ~p" + "~n Res: ~p" + "~n TabOid: ~p" + "~n TabNextOid: ~p" + "~n I: ~p", + [Values, OrgVb, TableOids, Mfa, Res, TabOid, TabNextOid, I]), + NewVb = OrgVb#varbind{value = {endOfTable, TabNextOid}}, + validate_tab_next_res(Values, TableOids, Mfa, [NewVb | Res], + TabOid, TabNextOid, I); +validate_tab_next_res([], [], _Mfa, Res, _TabOid, _TabNextOid, _I) -> + Res; +validate_tab_next_res([], [{_Col, _OrgVb, Index}|_], Mfa, _Res, _, _, _I) -> + user_err("Too few values returned from ~w (get_next)", [Mfa]), + {genErr, Index}; +validate_tab_next_res({genErr, ColNumber}, OrgCols, + Mfa, _Res, _TabOid, _TabNextOid, _I) -> + OrgIndex = snmpa_svbl:col_to_orgindex(ColNumber, OrgCols), + validate_err(table_next, {genErr, OrgIndex}, Mfa); +validate_tab_next_res({error, Reason}, [{_ColNo, OrgVb, _Index} | _TableOids], + Mfa, _Res, _TabOid, _TabNextOid, _I) -> + #varbind{org_index = OrgIndex} = OrgVb, + user_err("Erroneous return value ~w from ~w (get_next)", + [Reason, Mfa]), + {genErr, OrgIndex}; +validate_tab_next_res(Error, [{_ColNo, OrgVb, _Index} | _TableOids], + Mfa, _Res, _TabOid, _TabNextOid, _I) -> + #varbind{org_index = OrgIndex} = OrgVb, + user_err("Invalid return value ~w from ~w (get_next)", + [Error, Mfa]), + {genErr, OrgIndex}; +validate_tab_next_res(TooMany, [], Mfa, _Res, _, _, I) -> + user_err("Too many values ~w returned from ~w (get_next)", + [TooMany, Mfa]), + {genErr, I}. + +%%----------------------------------------------------------------- +%% Func: get_next_sa/4 +%% Purpose: Loop the list of varbinds for the subagent. +%% Call subagent_get_next to retreive +%% the next varbinds. +%% Returns: {ok, ListOfNewVbs, ListOfEndOfMibViewsVbs} | +%% {ErrorStatus, ErrorIndex} +%%----------------------------------------------------------------- +get_next_sa(SAPid, SAOid, SAVbs, MibView) -> + case catch subagent_get_next(SAPid, MibView, SAVbs) of + {noError, 0, NewVbs} -> + NewerVbs = transform_sa_next_result(NewVbs,SAOid,next_oid(SAOid)), + split_varbinds(NewerVbs, [], []); + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex}; + {'EXIT', Reason} -> + user_err("Lost contact with subagent (next) ~w. Using genErr", + [Reason]), + {genErr, 0} + end. + +%%----------------------------------------------------------------- +%% Check for wrong prefix returned or endOfMibView, and convert +%% into {endOfMibView, SANextOid}. +%%----------------------------------------------------------------- +transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) + when Vb#varbind.value =:= endOfMibView -> + [Vb#varbind{value = {endOfMibView, SANextOid}} | + transform_sa_next_result(Vbs, SAOid, SANextOid)]; +transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) -> + case lists:prefix(SAOid, Vb#varbind.oid) of + true -> + [Vb | transform_sa_next_result(Vbs, SAOid, SANextOid)]; + _ -> + [Vb#varbind{oid = SANextOid, value = {endOfMibView, SANextOid}} | + transform_sa_next_result(Vbs, SAOid, SANextOid)] + end; +transform_sa_next_result([], _SAOid, _SANextOid) -> + []. + +split_varbinds([Vb | Vbs], Res, EndOfs) -> + case Vb#varbind.value of + {endOfMibView, _} -> split_varbinds(Vbs, Res, [Vb | EndOfs]); + {endOfTable, _} -> split_varbinds(Vbs, Res, [Vb | EndOfs]); + _ -> split_varbinds(Vbs, [Vb | Res], EndOfs) + end; +split_varbinds([], Res, EndOfs) -> {ok, Res, EndOfs}. + +next_oid(Oid) -> + case lists:reverse(Oid) of + [H | T] -> lists:reverse([H+1 | T]); + [] -> [] + end. + + +%%%----------------------------------------------------------------- +%%% 5. GET-BULK REQUEST +%%%----------------------------------------------------------------- +do_get_bulk(MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds) -> + ?vtrace("do get bulk: start with" + "~n MibView: ~p" + "~n NonRepeaters: ~p" + "~n MaxRepetitions: ~p" + "~n PduMS: ~p" + "~n Varbinds: ~p", + [MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds]), + {NonRepVbs, RestVbs} = split_vbs(NonRepeaters, Varbinds, []), + ?vt("do get bulk -> split: " + "~n NonRepVbs: ~p" + "~n RestVbs: ~p", [NonRepVbs, RestVbs]), + case do_get_next(MibView, NonRepVbs) of + {noError, 0, UResNonRepVbs} -> + ?vt("do get bulk -> next: " + "~n UResNonRepVbs: ~p", [UResNonRepVbs]), + ResNonRepVbs = lists:keysort(#varbind.org_index, UResNonRepVbs), + %% Decode the first varbinds, produce a reversed list of + %% listOfBytes. + case (catch enc_vbs(PduMS - ?empty_pdu_size, ResNonRepVbs)) of + {error, Idx, Reason} -> + user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), + {genErr, Idx, []}; + {SizeLeft, Res} when is_integer(SizeLeft) and is_list(Res) -> + ?vtrace("do get bulk -> encoded: " + "~n SizeLeft: ~p" + "~n Res: ~w", [SizeLeft, Res]), + case (catch do_get_rep(SizeLeft, MibView, MaxRepetitions, + RestVbs, Res)) of + {error, Idx, Reason} -> + user_err("failed encoding varbind ~w:~n~p", + [Idx, Reason]), + {genErr, Idx, []}; + Res when is_list(Res) -> + ?vtrace("do get bulk -> Res: " + "~n ~w", [Res]), + {noError, 0, conv_res(Res)}; + Else -> + ?vtrace("do get bulk -> Else: " + "~n ~w", [Else]), + Else + end; + Res when is_list(Res) -> + {noError, 0, conv_res(Res)} + end; + {ErrorStatus, Index, _} -> + ?vdebug("do get bulk: " + "~n ErrorStatus: ~p" + "~n Index: ~p",[ErrorStatus, Index]), + {ErrorStatus, Index, []} + end. + +% sz(L) when list(L) -> length(L); +% sz(B) when binary(B) -> size(B); +% sz(_) -> unknown. + +split_vbs(N, Varbinds, Res) when N =< 0 -> {Res, Varbinds}; +split_vbs(N, [H | T], Res) -> split_vbs(N-1, T, [H | Res]); +split_vbs(_N, [], Res) -> {Res, []}. + +enc_vbs(SizeLeft, Vbs) -> + ?vt("enc_vbs -> entry with" + "~n SizeLeft: ~w", [SizeLeft]), + Fun = fun(Vb, {Sz, Res}) when Sz > 0 -> + ?vt("enc_vbs -> (fun) entry with" + "~n Vb: ~p" + "~n Sz: ~p" + "~n Res: ~w", [Vb, Sz, Res]), + case (catch snmp_pdus:enc_varbind(Vb)) of + {'EXIT', Reason} -> + ?vtrace("enc_vbs -> encode failed: " + "~n Reason: ~p", [Reason]), + throw({error, Vb#varbind.org_index, Reason}); + X -> + ?vt("enc_vbs -> X: ~w", [X]), + Lx = length(X), + ?vt("enc_vbs -> Lx: ~w", [Lx]), + if + Lx < Sz -> + {Sz - length(X), [X | Res]}; + true -> + throw(Res) + end + end; + (_Vb, {_Sz, [_H | T]}) -> + ?vt("enc_vbs -> (fun) entry with" + "~n T: ~p", [T]), + throw(T); + (_Vb, {_Sz, []}) -> + ?vt("enc_vbs -> (fun) entry", []), + throw([]) + end, + lists:foldl(Fun, {SizeLeft, []}, Vbs). + +do_get_rep(Sz, MibView, MaxRepetitions, Varbinds, Res) + when MaxRepetitions >= 0 -> + do_get_rep(Sz, MibView, 0, MaxRepetitions, Varbinds, Res); +do_get_rep(Sz, MibView, _MaxRepetitions, Varbinds, Res) -> + do_get_rep(Sz, MibView, 0, 0, Varbinds, Res). + +conv_res(ResVarbinds) -> + conv_res(ResVarbinds, []). +conv_res([VbListOfBytes | T], Bytes) -> + conv_res(T, VbListOfBytes ++ Bytes); +conv_res([], Bytes) -> + Bytes. + +do_get_rep(_Sz, _MibView, Max, Max, _, Res) -> + ?vt("do_get_rep -> done when: " + "~n Res: ~p", [Res]), + {noError, 0, conv_res(Res)}; +do_get_rep(Sz, MibView, Count, Max, Varbinds, Res) -> + ?vt("do_get_rep -> entry when: " + "~n Sz: ~p" + "~n Count: ~p" + "~n Res: ~w", [Sz, Count, Res]), + case try_get_bulk(Sz, MibView, Varbinds) of + {noError, NextVarbinds, SizeLeft, Res2} -> + ?vt("do_get_rep -> noError: " + "~n SizeLeft: ~p" + "~n Res2: ~p", [SizeLeft, Res2]), + do_get_rep(SizeLeft, MibView, Count+1, Max, NextVarbinds, + Res2 ++ Res); + {endOfMibView, _NextVarbinds, _SizeLeft, Res2} -> + ?vt("do_get_rep -> endOfMibView: " + "~n Res2: ~p", [Res2]), + {noError, 0, conv_res(Res2 ++ Res)}; + {ErrorStatus, Index} -> + ?vtrace("do_get_rep -> done when error: " + "~n ErrorStatus: ~p" + "~n Index: ~p", [ErrorStatus, Index]), + {ErrorStatus, Index, []} + end. + +try_get_bulk(Sz, MibView, Varbinds) -> + ?vt("try_get_bulk -> entry with" + "~n Sz: ~w", [Sz]), + case do_get_next(MibView, Varbinds) of + {noError, 0, UNextVarbinds} -> + ?vt("try_get_bulk -> noError", []), + NextVarbinds = lists:keysort(#varbind.org_index, UNextVarbinds), + case (catch enc_vbs(Sz, NextVarbinds)) of + {error, Idx, Reason} -> + user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), + ?vtrace("try_get_bulk -> error: " + "~n Idx: ~p" + "~n Reason: ~p", [Idx, Reason]), + {genErr, Idx}; + {SizeLeft, Res} when is_integer(SizeLeft) andalso is_list(Res) -> + ?vt("try get bulk -> " + "~n SizeLeft: ~w" + "~n Res: ~w", [SizeLeft, Res]), + {check_end_of_mibview(NextVarbinds), + NextVarbinds, SizeLeft, Res}; + Res when is_list(Res) -> + ?vt("try get bulk -> Res: " + "~n ~w", [Res]), + {endOfMibView, [], 0, Res} + end; + {ErrorStatus, Index, _} -> + ?vt("try get bulk: " + "~n ErrorStatus: ~p" + "~n Index: ~p",[ErrorStatus, Index]), + {ErrorStatus, Index} + end. + +%% If all variables in this pass are endOfMibView, +%% there is no reason to continue. +check_end_of_mibview([#varbind{value = endOfMibView} | T]) -> + check_end_of_mibview(T); +check_end_of_mibview([]) -> endOfMibView; +check_end_of_mibview(_) -> noError. + + +%%%-------------------------------------------------- +%%% 6. SET REQUEST +%%%-------------------------------------------------- +%% return: {ErrStatus, ErrIndex} +%% where ErrIndex is an index in Varbinds list (not org_index (user-functions +%% doesn't see org_index)). +do_set(MibView, UnsortedVarbinds) -> + SetModule = get(set_module), + ?vtrace("set module: ~p",[SetModule]), + apply(SetModule, do_set, [MibView, UnsortedVarbinds]). + +do_subagent_set(Arguments) -> + SetModule = get(set_module), + apply(SetModule, do_subagent_set, [Arguments]). + +%%%----------------------------------------------------------------- +%%% 7. Misc functions +%%%----------------------------------------------------------------- +sort_varbindlist(Varbinds) -> + snmpa_svbl:sort_varbindlist(get(mibserver), Varbinds). + +sa_split(SubagentVarbinds) -> + snmpa_svbl:sa_split(SubagentVarbinds). + +make_response_pdu(ReqId, ErrStatus, ErrIndex, OrgVarbinds, _ResponseVarbinds) + when ErrIndex =/= 0 -> + #pdu{type = 'get-response', request_id = ReqId, error_status = ErrStatus, + error_index = ErrIndex, varbinds = OrgVarbinds}; +make_response_pdu(ReqId, ErrStatus, ErrIndex, _OrgVarbinds, ResponseVarbinds) -> + #pdu{type = 'get-response', request_id = ReqId, error_status = ErrStatus, + error_index = ErrIndex, varbinds = ResponseVarbinds}. + +%% Valid errormsgs for different operations. +validate_err(consistency_check, {'EXIT', _Reason}, _) -> + {genErr, 0}; +validate_err(consistency_check, X, _) -> + X; + +validate_err(is_set_ok, noError, _) -> noError; +validate_err(is_set_ok, noCreation, _) -> noCreation; +validate_err(is_set_ok, inconsistentValue, _) -> inconsistentValue; +validate_err(is_set_ok, resourceUnavailable, _) -> resourceUnavailable; +validate_err(is_set_ok, inconsistentName, _) -> inconsistentName; +validate_err(is_set_ok, badValue, _) -> badValue; +validate_err(is_set_ok, wrongValue, _) -> wrongValue; +validate_err(is_set_ok, noSuchName, _) -> noSuchName; +validate_err(is_set_ok, noAccess, _) -> noAccess; +validate_err(is_set_ok, notWritable, _) -> notWritable; +validate_err(is_set_ok, genErr, _) -> genErr; +validate_err(is_set_ok, X, Mfa) -> + user_err("~w with is_set_ok, returned: ~w. Using genErr.", + [Mfa, X]), + genErr; + +validate_err(set, commitFailed, _) -> commitFailed; +validate_err(set, undoFailed, _) -> undoFailed; +validate_err(set, noError, _) -> noError; +validate_err(set, genErr, _) -> genErr; +validate_err(set, X, Mfa) -> + user_err("~w with set, returned: ~w. Using genErr.", + [Mfa, X]), + genErr; + +validate_err(undo, undoFailed, _) -> undoFailed; +validate_err(undo, noError, _) -> noError; +validate_err(undo, genErr, _) -> genErr; +validate_err(undo, X, Mfa) -> + user_err("~w with undo, returned: ~w. Using genErr.", + [Mfa, X]), + genErr; + +validate_err(table_is_set_ok, {Err, Idx}, Mfa) when is_integer(Idx) -> + {validate_err(is_set_ok, Err, Mfa), Idx}; +validate_err(table_is_set_ok, X, Mfa) -> + user_err("~w with is_set_ok (table), returned: ~w. Using genErr.", + [Mfa, X]), + {genErr, 0}; + +validate_err(row_is_set_ok, {Err, Idx}, _) when is_integer(Idx) -> + {Err, Idx}; +validate_err(row_is_set_ok, {_Err, {false, BadCol}}, Mfa) -> + user_err("~w with is_set_ok (table), returned bad column: " + "~w. Using genErr.", [Mfa, BadCol]), + {genErr, 0}; + +validate_err(table_undo, {Err, Idx}, Mfa) when is_integer(Idx) -> + {validate_err(undo, Err, Mfa), Idx}; +validate_err(table_undo, X, Mfa) -> + user_err("~w with undo (table), returned: ~w. Using genErr.", + [Mfa, X]), + {genErr, 0}; + +validate_err(row_undo, {Err, Idx}, _) when is_integer(Idx) -> + {Err, Idx}; +validate_err(row_undo, {_Err, {false, BadCol}}, Mfa) -> + user_err("~w with undo (table), returned bad column: " + "~w. Using genErr.", [Mfa, BadCol]), + {genErr, 0}; + +validate_err(table_set, {Err, Idx}, Mfa) when is_integer(Idx) -> + {validate_err(set, Err, Mfa), Idx}; +validate_err(table_set, X, Mfa) -> + user_err("~w with set (table), returned: ~w. Using genErr.", + [Mfa, X]), + {genErr, 0}; + +validate_err(row_set, {Err, Idx}, _) when is_integer(Idx) -> + {Err, Idx}; +validate_err(row_set, {_Err, {false, BadCol}}, Mfa) -> + user_err("~w with set (table), returned bad column: " + "~w. Using genErr.", [Mfa, BadCol]), + {genErr, 0}; + +validate_err(table_next, {Err, Idx}, _Mfa) when is_integer(Idx) -> + {Err, Idx}; +validate_err(table_next, {_Err, {false, BadCol}}, Mfa) -> + user_err("~w with get_next, returned bad column: " + "~w. Using genErr.", [Mfa, BadCol]), + {genErr, 0}. + +validate_err(v2_to_v1, {V2Err, Index}) -> + {v2err_to_v1err(V2Err), Index}; +validate_err(v2_to_v1, _) -> + {genErr, 0}. + +get_err({ErrC, ErrI, Vbs}) -> + {get_err_i(ErrC), ErrI, Vbs}. + +get_err_i(noError) -> noError; +get_err_i(S) -> + ?vtrace("convert '~p' to 'genErr'",[S]), + genErr. + +v2err_to_v1err(noError) -> noError; +v2err_to_v1err(noAccess) -> noSuchName; +v2err_to_v1err(noCreation) -> noSuchName; +v2err_to_v1err(notWritable) -> noSuchName; +v2err_to_v1err(wrongLength) -> badValue; +v2err_to_v1err(wrongEncoding) -> badValue; +v2err_to_v1err(wrongType) -> badValue; +v2err_to_v1err(wrongValue) -> badValue; +v2err_to_v1err(inconsistentValue) -> badValue; +v2err_to_v1err(inconsistentName) -> noSuchName; +v2err_to_v1err(noSuchName) -> noSuchName; +v2err_to_v1err(badValue) -> badValue; +v2err_to_v1err(authorizationError) -> noSuchName; +%% genErr | resourceUnavailable | undoFailed | commitFailed -> genErr +v2err_to_v1err(_Error) -> genErr. + +%%----------------------------------------------------------------- +%% transforms a (hopefully correct) return value ((perhaps) from a +%% mib-function) to a typed and guaranteed correct return value. +%% An incorrect return value is transformed to {error, genErr}. +%% A correct return value is on the form: +%% {error, <error-msg>} | {value, <variable-type>, <value>} +%%----------------------------------------------------------------- +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'INTEGER' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'Counter32' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'Unsigned32' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'TimeTicks' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'Counter64' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when (Asn1#asn1_type.bertype =:= 'BITS') andalso is_list(Val) -> + {value,Kibbles} = snmp_misc:assq(kibbles,Asn1#asn1_type.assocList), + case snmp_misc:bits_to_int(Val,Kibbles) of + error -> + wrongValue(Val, Mfa); + Int -> + make_value_a_correct_value({value,Int},Asn1,Mfa) + end; + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when (Asn1#asn1_type.bertype =:= 'BITS') andalso is_integer(Val) -> + {value,Kibbles} = snmp_misc:assq(kibbles,Asn1#asn1_type.assocList), + {_Kibble,BitNo} = lists:last(Kibbles), + case (1 bsl (BitNo+1)) of + X when Val < X -> + {value,'BITS',Val}; + _Big -> + wrongValue(Val, Mfa) + end; + +make_value_a_correct_value({value, String}, + #asn1_type{bertype = 'OCTET STRING', + hi = Hi, lo = Lo}, Mfa) -> + check_octet_string(String, Hi, Lo, Mfa, 'OCTET STRING'); + +make_value_a_correct_value({value, String}, + #asn1_type{bertype = 'IpAddress', + hi = Hi, lo = Lo}, Mfa) -> + check_octet_string(String, Hi, Lo, Mfa, 'IpAddress'); + +make_value_a_correct_value({value, Oid}, + #asn1_type{bertype = 'OBJECT IDENTIFIER'}, + _Mfa) -> + case snmp_misc:is_oid(Oid) of + true -> {value, 'OBJECT IDENTIFIER', Oid}; + _Else -> {error, wrongType} + end; + +make_value_a_correct_value({value, Val}, Asn1, _Mfa) + when Asn1#asn1_type.bertype =:= 'Opaque' -> + if is_list(Val) -> {value, 'Opaque', Val}; + true -> {error, wrongType} + end; + +make_value_a_correct_value({noValue, noSuchObject}, _ASN1Type, _Mfa) -> + {value, noValue, noSuchObject}; +make_value_a_correct_value({noValue, noSuchInstance}, _ASN1Type, _Mfa) -> + {value, noValue, noSuchInstance}; +make_value_a_correct_value({noValue, noSuchName}, _ASN1Type, _Mfa) -> + %% Transform this into a v2 value. It is converted to noSuchName + %% later if it was v1. If it was v2, we use noSuchInstance. + {value, noValue, noSuchInstance}; +%% For backwards compatibility only - we really shouldn't allow this; +%% it makes no sense to return unSpecified for a variable! But we did +%% allow it previously. -- We transform unSpecified to noSuchInstance +%% (OTP-3303). +make_value_a_correct_value({noValue, unSpecified}, _ASN1Type, _Mfa) -> + {value, noValue, noSuchInstance}; +make_value_a_correct_value(genErr, _ASN1Type, _MFA) -> + {error, genErr}; + +make_value_a_correct_value(_WrongVal, _ASN1Type, undef) -> + {error, genErr}; + +make_value_a_correct_value(WrongVal, ASN1Type, Mfa) -> + user_err("Got ~w from ~w. (~w) Using genErr", + [WrongVal, Mfa, ASN1Type]), + {error, genErr}. + +check_integer(Val, Asn1, Mfa) -> + case Asn1#asn1_type.assocList of + undefined -> check_size(Val, Asn1, Mfa); + Alist -> + case snmp_misc:assq(enums, Alist) of + {value, Enums} -> check_enums(Val, Asn1, Enums, Mfa); + false -> check_size(Val, Asn1, Mfa) + end + end. + +check_octet_string(String, Hi, Lo, Mfa, Type) -> + Len = (catch length(String)), % it might not be a list + case snmp_misc:is_string(String) of + true when Lo =:= undefined -> {value, Type, String}; + true when Len =< Hi, Len >= Lo -> + {value, Type, String}; + true -> + wrongLength(String, Mfa); + _Else -> + wrongType(String, Mfa) + end. + +check_size(Val, #asn1_type{lo = Lo, hi = Hi, bertype = Type}, Mfa) + when is_integer(Val) -> + ?vtrace("check size of integer: " + "~n Value: ~p" + "~n Upper limit: ~p" + "~n Lower limit: ~p" + "~n BER-type: ~p", + [Val,Hi,Lo,Type]), + if + (Lo =:= undefined) andalso (Hi =:= undefined) -> {value, Type, Val}; + (Lo =:= undefined) andalso is_integer(Hi) andalso (Val =< Hi) -> + {value, Type, Val}; + is_integer(Lo) andalso (Val >= Lo) andalso (Hi =:= undefined) -> + {value, Type, Val}; + is_integer(Lo) andalso is_integer(Hi) andalso (Val >= Lo) andalso (Val =< Hi) -> + {value, Type, Val}; + true -> + wrongValue(Val, Mfa) + end; +check_size(Val, _, Mfa) -> + wrongType(Val, Mfa). + +check_enums(Val, Asn1, Enums, Mfa) -> + Association = + if + is_integer(Val) -> lists:keysearch(Val, 2, Enums); + is_atom(Val) -> lists:keysearch(Val, 1, Enums); + true -> {error, wrongType} + end, + case Association of + {value, {_AliasIntName, Val2}} -> + {value, Asn1#asn1_type.bertype, Val2}; + false -> + wrongValue(Val, Mfa); + {error, wrongType} -> + wrongType(Val, Mfa) + end. + +wrongLength(Val, Mfa) -> + report_err(Val, Mfa, wrongLength). + +wrongValue(Val, Mfa) -> + report_err(Val, Mfa, wrongValue). + +wrongType(Val, Mfa) -> + report_err(Val, Mfa, wrongType). + +report_err(_Val, undef, Err) -> + {error, Err}; +report_err(Val, Mfa, Err) -> + user_err("Got ~p from ~w. Using ~w", [Val, Mfa, Err]), + {error, Err}. + +is_valid_pdu_type('get-request') -> true; +is_valid_pdu_type('get-next-request') -> true; +is_valid_pdu_type('get-bulk-request') -> true; +is_valid_pdu_type('set-request') -> true; +is_valid_pdu_type(_) -> false. + +get_pdu_data() -> + {get(net_if_data), + get(snmp_request_id), + get(snmp_address), + get(snmp_community), + get(snmp_context)}. + +put_pdu_data({Extra, ReqId, Address, Community, ContextName}) -> + {put(net_if_data, Extra), + put(snmp_address, Address), + put(snmp_request_id, ReqId), + put(snmp_community, Community), + put(snmp_context, ContextName)}. + +tr_var(Oid, Idx) -> + case snmp_misc:is_oid(Oid) of + true -> + {#varbind{oid = Oid, value = unSpecified, org_index = Idx}, + Idx+1}; + false -> throw({error, {bad_oid, Oid}}) + end. + +tr_varbind(#varbind{value = Value}) -> Value. + +mapfoldl(F, Eas, Accu0, [Hd|Tail]) -> + {R,Accu1} = apply(F, [Hd,Accu0|Eas]), + {Accu2,Rs} = mapfoldl(F, Eas, Accu1, Tail), + {Accu2,[R|Rs]}; +mapfoldl(_F, _Eas, Accu, []) -> {Accu,[]}. + +%%----------------------------------------------------------------- +%% Runtime debugging of the agent. +%%----------------------------------------------------------------- + +dbg_apply(M,F,A) -> + case get(verbosity) of + silence -> + apply(M,F,A); + _ -> + ?vlog("~n apply: ~w,~w,~p~n", [M,F,A]), + Res = (catch apply(M,F,A)), + case Res of + {'EXIT', Reason} -> + ?vinfo("Call to: " + "~n Module: ~p" + "~n Function: ~p" + "~n Args: ~p" + "~n" + "~nresulted in an exit" + "~n" + "~n ~p", [M, F, A, Reason]); + _ -> + ?vlog("~n returned: ~p", [Res]) + end, + Res + end. + + +short_name(none) -> ma; +short_name(_Pid) -> sa. + +worker_short_name(ma) -> maw; +worker_short_name(_) -> saw. + +trap_sender_short_name(ma) -> mats; +trap_sender_short_name(_) -> sats. + +pdu_handler_short_name(ma) -> maph; +pdu_handler_short_name(_) -> saph. + + +mib_server_verbosity(Pid,Verbosity) when is_pid(Pid) -> + snmpa_mib:verbosity(Pid,Verbosity); +mib_server_verbosity(_Pid,_Verbosity) -> + ok. + +note_store_verbosity(Pid,Verbosity) when is_pid(Pid) -> + snmp_note_store:verbosity(Pid,Verbosity); +note_store_verbosity(_Pid,_Verbosity) -> + ok. + +subagents_verbosity(V) -> + subagents_verbosity(catch snmpa_mib:info(get(mibserver),subagents),V). + +subagents_verbosity([],_V) -> + ok; +subagents_verbosity([{Pid,_Oid}|T],V) -> + catch verbosity(Pid,V), %% On the agent + catch verbosity(Pid,{subagents,V}), %% and it's subagents + subagents_verbosity(T,V); +subagents_verbosity(_,_V) -> + ok. + + +%% --------------------------------------------------------------------- + +handle_get_log_type(#state{net_if_mod = Mod}) + when Mod =/= undefined -> + case (catch Mod:get_log_type(get(net_if))) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end; +handle_get_log_type(_) -> + {error, not_supported}. + +handle_set_log_type(#state{net_if_mod = Mod}, NewType) + when Mod =/= undefined -> + case (catch Mod:set_log_type(get(net_if), NewType)) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end; +handle_set_log_type(_, _) -> + {error, not_supported}. + + +handle_get_request_limit(#state{net_if_mod = Mod}) + when Mod =/= undefined -> + case (catch Mod:get_request_limit(get(net_if))) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end; +handle_get_request_limit(_) -> + {error, not_supported}. + +handle_set_request_limit(#state{net_if_mod = Mod}, NewLimit) + when Mod =/= undefined -> + case (catch Mod:set_request_limit(get(net_if), NewLimit)) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end; +handle_set_request_limit(_, _) -> + {error, not_supported}. + + +agent_info(#state{worker = W, set_worker = SW}) -> + case (catch get_agent_info(W, SW)) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +get_agent_info(W, SW) -> + MASz = proc_mem(self()), + WSz = proc_mem(W), + SWSz = proc_mem(SW), + ATSz = tab_mem(snmp_agent_table), + CCSz = tab_mem(snmp_community_cache), + VacmSz = tab_mem(snmpa_vacm), + [{process_memory, [{master_agent, MASz}, + {worker, WSz}, + {set_worker, SWSz}]}, + {db_memory, [{agent, ATSz}, + {community_cache, CCSz}, + {vacm, VacmSz}]}]. + +proc_mem(P) when is_pid(P) -> + case (catch erlang:process_info(P, memory)) of + {memory, Sz} when is_integer(Sz) -> + Sz; + _ -> + undefined + end; +proc_mem(_) -> + undefined. + +tab_mem(T) -> + case (catch ets:info(T, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + +net_if_info(#state{net_if_mod = Mod}) when Mod =/= undefined -> + case (catch Mod:info(get(net_if))) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end; +net_if_info(_) -> + %% This could be a result of a code upgrade + %% Make best effert + [{process_memory, proc_mem(get(net_if))}]. + +note_store_info(#state{note_store = NS}) -> + case (catch snmp_note_store:info(NS)) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +symbolic_store_info() -> + case (catch snmpa_symbolic_store:info()) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +local_db_info() -> + case (catch snmpa_local_db:info()) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +mib_server_info() -> + case (catch snmpa_mib:info(get(mibserver))) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +get_stats_counters() -> + Counters = snmpa_mpd:counters(), + get_stats_counters(Counters, []). + +get_stats_counters([], Acc) -> + lists:reverse(Acc); +get_stats_counters([Counter|Counters], Acc) -> + case ets:lookup(snmp_agent_table, Counter) of + [CounterVal] -> + get_stats_counters(Counters, [CounterVal|Acc]); + _ -> + get_stats_counters(Counters, Acc) + end. + + +%% --------------------------------------------------------------------- + +%% info_msg(F, A) -> +%% ?snmpa_info(F, A). + +warning_msg(F, A) -> + ?snmpa_warning(F, A). + +error_msg(F, A) -> + ?snmpa_error(F, A). + +%% --- + +config_err(F, A) -> + snmpa_error:config_err(F, A). + +user_err(F, A) -> + snmpa_error:user_err(F, A). + + +%% --------------------------------------------------------------------- + +call(Server, Req) -> + gen_server:call(Server, Req, infinity). + +cast(Server, Msg) -> + gen_server:cast(Server, Msg). + + +%% --------------------------------------------------------------------- + +get_verbosity(Opts) -> + get_option(verbosity, Opts, ?default_verbosity). + +get_mibs(Opts) -> + get_option(mibs, Opts, []). + +get_mib_storage(Opts) -> + get_option(mib_storage, Opts, ets). + +get_set_mechanism(Opts) -> + get_option(set_mechanism, Opts, snmpa_set). + +get_authentication_service(Opts) -> + get_option(authentication_service, Opts, snmpa_acm). + +get_multi_threaded(Opts) -> + get_option(multi_threaded, Opts, false). + +get_versions(Opts) -> + get_option(versions, Opts, [v1,v2,v3]). + +get_note_store_opt(Opts) -> + get_option(note_store, Opts, []). + +get_net_if_opt(Opts) -> + get_option(net_if, Opts, []). + +get_net_if_verbosity(Opts) -> + get_option(verbosity, Opts, silence). + +get_net_if_module(Opts) -> + get_option(module, Opts, snmpa_net_if). + +get_net_if_options(Opts) -> + get_option(options, Opts, []). + + +net_if_verbosity(Pid,Verbosity) when is_pid(Pid) -> + Pid ! {verbosity,Verbosity}; +net_if_verbosity(_Pid,_Verbosity) -> + ok. + + +get_option(Key, Opts, Default) -> + snmp_misc:get_option(Key, Opts, Default). + + +%% --------------------------------------------------------------------- + + +%% i(F) -> +%% i(F, []). + +%% i(F, A) -> +%% io:format("~p: " ++ F ++ "~n", [?MODULE|A]). + + + diff --git a/lib/snmp/src/agent/snmpa_agent_sup.erl b/lib/snmp/src/agent/snmpa_agent_sup.erl new file mode 100644 index 0000000000..9b8c4d12a6 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_agent_sup.erl @@ -0,0 +1,116 @@ +%% +%% %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_agent_sup). + +-include("snmp_debug.hrl"). + +-behaviour(supervisor). + +%% External exports +-export([start_link/0, start_link/1, start_subagent/3, stop_subagent/1]). + +%% Internal exports +-export([init/1]). + +-define(SERVER, ?MODULE). +-ifdef(snmp_debug). +-define(DEFAULT_OPTS, [{verbosity, trace}]). +-else. +-define(DEFAULT_OPTS, []). +-endif. + + +%%%----------------------------------------------------------------- +%%% This is a supervisor for the mib processes. Each agent has one +%%% mib process. +%%%----------------------------------------------------------------- +start_link() -> + ?d("start_link -> entry", []), + supervisor:start_link({local, ?SERVER}, ?MODULE, [[]]). + +start_link(AgentSpec) -> + ?d("start_link -> entry with" + "~n AgentSpec: ~p", [AgentSpec]), + supervisor:start_link({local, ?SERVER}, ?MODULE, [[AgentSpec]]). + +start_subagent(ParentAgent, Subtree, Mibs) -> + ?d("start_subagent -> entry with" + "~n ParentAgent: ~p" + "~n Subtree: ~p" + "~n Mibs: ~p", [ParentAgent, Subtree, Mibs]), + Children = supervisor:which_children(?SERVER), + ?d("start_subagent -> Children: ~n~p", [Children]), + Max = find_max(Children, 1), + ?d("start_subagent -> Max: ~p", [Max]), + [{_, Prio}] = ets:lookup(snmp_agent_table, priority), + ?d("start_subagent -> Prio: ~p", [Prio]), + Ref = make_ref(), + ?d("start_subagent -> Ref: ~p", [Ref]), + Options = [{priority, Prio}, + {mibs, Mibs}, + {misc_sup, snmpa_misc_sup} | ?DEFAULT_OPTS], + Agent = {{sub_agent, Max}, + {snmpa_agent, start_link, + [Prio, ParentAgent, Ref, Options]}, + permanent, 2000, worker, [snmpa_agent]}, + case supervisor:start_child(?SERVER, Agent) of + {ok, SA} -> + ?d("start_subagent -> SA: ~p", [SA]), + snmpa_agent:register_subagent(ParentAgent, Subtree, SA), + {ok, SA}; + Error -> + ?d("start_subagent -> Error: ~p", [Error]), + Error + end. + +stop_subagent(SubAgentPid) -> + case find_name(supervisor:which_children(?SERVER), SubAgentPid) of + undefined -> + no_such_child; + Name -> + supervisor:terminate_child(?SERVER, Name), + supervisor:delete_child(?SERVER, Name), + ok + end. + +init([Children]) -> + ?d("init -> entry with" + "~n Children: ~p", [Children]), + %% 20 restarts in ten minutes. If the agent crashes and restarts, + %% it may very well crash again, because the management application + %% tries to resend the very same request. This depends on the resend + %% strategy used by the management application. + SupFlags = {one_for_one, 20, 600}, + {ok, {SupFlags, Children}}. + + +find_max([{{sub_agent, N}, _, _, _} | T], M) when N >= M -> find_max(T, N+1); +find_max([_|T], M) -> find_max(T, M); +find_max([], M) -> M. + +find_name([{Name, Pid, _, _} | _T], Pid)-> Name; +find_name([_|T], Pid) -> find_name(T, Pid); +find_name([], _Pid) -> undefined. + + +% i(F) -> +% i(F, []). + +% i(F, A) -> +% io:format("~p:~p: " ++ F ++ "~n", [node(),?MODULE|A]). diff --git a/lib/snmp/src/agent/snmpa_app.erl b/lib/snmp/src/agent/snmpa_app.erl new file mode 100644 index 0000000000..4e65e8e283 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_app.erl @@ -0,0 +1,372 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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% +%% +%%----------------------------------------------------------------- +%% This module implements the config conversion for the old SNMP +%% application start method +%% The purpose is to extract all agent app-env and convert to the +%% new format +%% +%% --- +%% +%% What about the restart times for the children +%% note_store (snmpa_supervisor) and +%% net_if and mib_server (snmpa_misc_sup)? +%% +%%----------------------------------------------------------------- + +-module(snmpa_app). + +-include("snmp_debug.hrl"). + +-export([convert_config/0, convert_config/1]). + +%% Internal (test) +-export([start/1]). + + +convert_config() -> + ?d("convert_config -> entry", []), + convert_config( application:get_all_env(snmp) ). + +convert_config(Opts) -> + ?d("convert_config -> Opts: ~p", [Opts]), + Prio = get_priority(Opts), + DbDir = get_db_dir(Opts), + DbInitError = terminate, + LdbOpts = get_local_db_opts(Opts), + MibStorage = get_mib_storage(Opts), + MibsOpts = get_mib_server_opts(Opts), + SymOpts = get_symbolic_store_opts(Opts), + SetModule = get_set_mechanism(Opts), + AuthModule = get_authentication_service(Opts), + MultiT = get_multi_threaded(Opts), + Vsns = get_versions(Opts), + SupOpts = get_supervisor_opts(Opts), + ErrorReportMod = get_error_report_mod(Opts), + AgentType = get_agent_type(Opts), + case AgentType of + sub -> + ?d("convert_config -> agent type: sub",[]), + SaVerb = get_sub_agent_verbosity(Opts), + [{agent_type, AgentType}, + {agent_verbosity, SaVerb}, + {set_mechanism, SetModule}, + {authentication_service, AuthModule}, + {priority, Prio}, + {versions, Vsns}, + {db_dir, DbDir}, + {db_init_error, DbInitError}, + {multi_threaded, MultiT}, + {error_report_mod, ErrorReportMod}, + {mib_storage, MibStorage}, + {mib_server, MibsOpts}, + {local_db, LdbOpts}, + {supervisor, SupOpts}, + {symbolic_store, SymOpts}]; + + master -> + ?d("convert_config -> agent type: master",[]), + MaVerb = get_master_agent_verbosity(Opts), + NoteOpts = get_note_store_opts(Opts), + NiOptions = get_net_if_options(Opts), + NiOpts = [{options, NiOptions}|get_net_if_opts(Opts)], + AtlOpts = get_audit_trail_log_opts(Opts), + Mibs = get_master_agent_mibs(Opts), + ForceLoad = get_force_config_load(Opts), + ConfVerb = get_opt(verbosity, SupOpts, silence), + ConfDir = get_config_dir(Opts), + ConfOpts = [{dir, ConfDir}, + {force_load, ForceLoad}, + {verbosity, ConfVerb}], + [{agent_type, AgentType}, + {agent_verbosity, MaVerb}, + {set_mechanism, SetModule}, + {authentication_service, AuthModule}, + {db_dir, DbDir}, + {db_init_error, DbInitError}, + {config, ConfOpts}, + {priority, Prio}, + {versions, Vsns}, + {multi_threaded, MultiT}, + {error_report_mod, ErrorReportMod}, + {supervisor, SupOpts}, + {mibs, Mibs}, + {mib_storage, MibStorage}, + {symbolic_store, SymOpts}, + {note_store, NoteOpts}, + {net_if, NiOpts}, + {mib_server, MibsOpts}, + {local_db, LdbOpts}] ++ AtlOpts + end. + + +start(Type) -> + snmp_app_sup:start_agent(Type, convert_config()). + + +%% ------------------------------------------------------------------ + +get_db_dir(Opts) -> + case get_opt(snmp_db_dir, Opts) of + {value, Dir} when is_list(Dir) -> + Dir; + {value, Bad} -> + exit({bad_config, {db_dir, Bad}}); + false -> + exit({undefined_config, db_dir}) + end. + + +%% -- + +get_priority(Opts) -> + case get_opt(snmp_priority, Opts) of + {value, Prio} when is_atom(Prio) -> + Prio; + _ -> + normal + end. + + +%% -- + +get_mib_storage(Opts) -> + get_opt(snmp_mib_storage, Opts, ets). + +get_mib_server_opts(Opts) -> + Options = [{snmp_mibserver_verbosity, verbosity, silence}, + {mibentry_override, mibentry_override, false}, + {trapentry_override, trapentry_override, false}], + get_opts(Options, Opts, []). + + +%% -- + +get_audit_trail_log_opts(Opts) -> + case get_audit_trail_log(Opts) of + false -> + []; + Type -> + Dir = get_audit_trail_log_dir(Opts), + Size = get_audit_trail_log_size(Opts), + AtlOpts0 = [{type, Type}, + {dir, Dir}, + {size, Size}, + {repair, true}], + [{audit_trail_log, AtlOpts0}] + end. + + +get_audit_trail_log(Opts) -> + case get_opt(audit_trail_log, Opts) of + {value, write_log} -> write; + {value, read_log} -> read; + {value, read_write_log} -> read_write; + _ -> false + end. + +get_audit_trail_log_dir(Opts) -> + case get_opt(audit_trail_log_dir, Opts) of + {value, Dir} when is_list(Dir) -> + Dir; + {value, Bad} -> + exit({bad_config, {audit_trail_log_dir, Bad}}); + _ -> + exit({undefined_config, audit_trail_log_dir}) + end. + +get_audit_trail_log_size(Opts) -> + case get_opt(audit_trail_log_size, Opts) of + {value, {MB, MF} = Sz} when is_integer(MB) andalso is_integer(MF) -> + Sz; + {value, Bad} -> + exit({bad_config, {audit_trail_log_size, Bad}}); + _ -> + exit({undefined_config, audit_trail_log_size}) + end. + + +%% -- + +get_master_agent_verbosity(Opts) -> + get_opt(snmp_master_agent_verbosity, Opts, silence). + + +%% -- + +get_sub_agent_verbosity(Opts) -> + get_opt(snmp_subagent_verbosity, Opts, silence). + + +%% -- + +get_supervisor_opts(Opts) -> + Options = [{snmp_supervisor_verbosity, verbosity, silence}], + get_opts(Options, Opts, []). + + +%% -- + +get_symbolic_store_opts(Opts) -> + Options = [{snmp_symbolic_store_verbosity, verbosity, silence}], + get_opts(Options, Opts, []). + + +%% -- + +get_note_store_opts(Opts) -> + Options = [{snmp_note_store_verbosity, verbosity, silence}], + get_opts(Options, Opts, []). + + +%% -- + +get_local_db_opts(Opts) -> + Options = [{snmp_local_db_auto_repair, repair, true}, + {snmp_local_db_verbosity, verbosity, silence}], + get_opts(Options, Opts, []). + + +%% -- + +get_multi_threaded(Opts) -> + case get_opt(snmp_multi_threaded, Opts) of + {value, true} -> + true; + {value, false} -> + false; + _ -> + false + end. + +get_versions(Opts) -> + F = fun(Ver) -> + case get_opt(Ver, Opts) of + {value, true} -> + [Ver]; + {value, false} -> + []; + _ -> + [Ver] % Default is true + end + end, + V1 = F(v1), + V2 = F(v2), + V3 = F(v3), + V1 ++ V2 ++ V3. + +get_set_mechanism(Opts) -> + get_opt(set_mechanism, Opts, snmpa_set). + +get_authentication_service(Opts) -> + get_opt(authentication_service, Opts, snmpa_acm). + +get_error_report_mod(Opts) -> + get_opt(snmp_error_report_mod, Opts, snmpa_error_logger). + + +%% -- + +get_net_if_opts(Opts) -> + Options = [{snmp_net_if_module, module, snmpa_net_if}, + {snmp_net_if_verbosity, verbosity, silence}], + get_opts(Options, Opts, []). + +get_net_if_options(Opts) -> + Options = [recbuf, + {req_limit, req_limit, infinity}, + {bind_to_ip_address, bind_to, false}, + {no_reuse_address, no_reuse, false}], + get_opts(Options, Opts, []). + + +%% -- + +get_agent_type(Opts) -> + get_opt(snmp_agent_type, Opts, master). + + +%% -- + +get_config_dir(Opts) -> + case get_opt(snmp_config_dir, Opts) of + {value, Dir} when is_list(Dir) -> Dir; + {value, Bad} -> + exit({bad_config, {config_dir, Bad}}); + _ -> + exit({undefined_config, config_dir}) + end. + +get_master_agent_mibs(Opts) -> + get_opt(snmp_master_agent_mibs, Opts, []). + +get_force_config_load(Opts) -> + case get_opt(force_config_load, Opts) of + {value, true} -> true; + {value, false} -> false; + _ -> false + end. + + +%% -- + +get_opts([], _Options, Opts) -> + Opts; +get_opts([{Key1, Key2, Def}|KeyVals], Options, Opts) -> + %% If not found among Options, then use default value + case lists:keysearch(Key1, 1, Options) of + {value, {Key1, Val}} -> + get_opts(KeyVals, Options, [{Key2, Val}|Opts]); + false -> + get_opts(KeyVals, Options, [{Key2, Def}|Opts]) + end; +get_opts([Key|KeyVals], Options, Opts) -> + %% If not found among Options, then ignore + case lists:keysearch(Key, 1, Options) of + {value, KeyVal} -> + get_opts(KeyVals, Options, [KeyVal|Opts]); + false -> + get_opts(KeyVals, Options, Opts) + end. + + +%% -- + + +get_opt(Key, Opts, Def) -> + case get_opt(Key, Opts) of + {value, Val} -> + Val; + false -> + Def + end. + +get_opt(Key, Opts) -> + case lists:keysearch(Key, 1, Opts) of + {value, {_, Val}} -> + {value, Val}; + false -> + false + end. + +% i(F) -> +% i(F, []). + +% i(F, A) -> +% io:format("~p: " ++ F ++ "~n", [?MODULE|A]). diff --git a/lib/snmp/src/agent/snmpa_atl.hrl b/lib/snmp/src/agent/snmpa_atl.hrl new file mode 100644 index 0000000000..ec6beb3542 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_atl.hrl @@ -0,0 +1,21 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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% +%% + +-define(audit_trail_log_name, "snmpa_log"). +-define(audit_trail_log_file, "snmpa.log"). diff --git a/lib/snmp/src/agent/snmpa_authentication_service.erl b/lib/snmp/src/agent/snmpa_authentication_service.erl new file mode 100644 index 0000000000..572fab7fbf --- /dev/null +++ b/lib/snmp/src/agent/snmpa_authentication_service.erl @@ -0,0 +1,57 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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_authentication_service). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{init_check_access, 2}]; +behaviour_info(_) -> + undefined. + + +%%----------------------------------------------------------------- +%% init_check_access(Pdu, ACMData) +%% 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 +%%----------------------------------------------------------------- diff --git a/lib/snmp/src/agent/snmpa_conf.erl b/lib/snmp/src/agent/snmpa_conf.erl new file mode 100644 index 0000000000..b14a0c806c --- /dev/null +++ b/lib/snmp/src/agent/snmpa_conf.erl @@ -0,0 +1,937 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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_conf). + +-export([ + %% agent.conf + agent_entry/2, + write_agent_config/2, write_agent_config/3, + append_agent_config/2, + read_agent_config/1, + + %% context.conf + context_entry/1, + write_context_config/2, write_context_config/3, + append_context_config/2, + read_context_config/1, + + %% community.conf + community_entry/1, community_entry/5, + write_community_config/2, write_community_config/3, + append_community_config/2, + read_community_config/1, + + %% standard.conf + standard_entry/2, + write_standard_config/2, write_standard_config/3, + append_standard_config/2, + read_standard_config/1, + + %% target_addr.conf + target_addr_entry/5, target_addr_entry/6, + target_addr_entry/8, target_addr_entry/10, + write_target_addr_config/2, write_target_addr_config/3, + append_target_addr_config/2, + read_target_addr_config/1, + + %% target_params.conf + target_params_entry/2, target_params_entry/4, target_params_entry/5, + write_target_params_config/2, write_target_params_config/3, + append_target_params_config/2, + read_target_params_config/1, + + %% xyz.conf + notify_entry/3, + write_notify_config/2, write_notify_config/3, + append_notify_config/2, + read_notify_config/1, + + %% xyz.conf + usm_entry/1, usm_entry/13, + write_usm_config/2, write_usm_config/3, + append_usm_config/2, + read_usm_config/1, + + %% xyz.conf + vacm_s2g_entry/3, + vacm_acc_entry/8, + vacm_vtf_entry/2, vacm_vtf_entry/4, + write_vacm_config/2, write_vacm_config/3, + append_vacm_config/2, + read_vacm_config/1 + ]). + + + +%% +%% ------ agent.conf ------ +%% + +agent_entry(Tag, Val) -> + {Tag, Val}. + + +write_agent_config(Dir, Conf) -> + Comment = +"%% This file defines the Agent local configuration info\n" +"%% The data is inserted into the snmpEngine* variables defined\n" +"%% in SNMP-FRAMEWORK-MIB, and the intAgent* variables defined\n" +"%% in OTP-SNMPEA-MIB.\n" +"%% Each row is a 2-tuple:\n" +"%% {AgentVariable, Value}.\n" +"%% For example\n" +"%% {intAgentUDPPort, 4000}.\n" +"%% The ip address for the agent is sent as id in traps.\n" +"%% {intAgentIpAddress, [127,42,17,5]}.\n" +"%% {snmpEngineID, \"agentEngine\"}.\n" +"%% {snmpEngineMaxMessageSize, 484}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_agent_config(Dir, Hdr, Conf). + +write_agent_config(Dir, Hdr, Conf) + when is_list(Dir) and is_list(Hdr) and is_list(Conf) -> + Verify = fun() -> verify_agent_conf(Conf) end, + Write = fun(Fd) -> write_agent_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, "agent.conf", Verify, Write). + + +append_agent_config(Dir, Conf) + when is_list(Dir) and is_list(Conf) -> + Verify = fun() -> verify_agent_conf(Conf) end, + Write = fun(Fd) -> write_agent_conf(Fd, Conf) end, + append_config_file(Dir, "agent.conf", Verify, Write). + + +read_agent_config(Dir) -> + Verify = fun(Entry) -> verify_agent_conf_entry(Entry) end, + read_config_file(Dir, "agent.conf", Verify). + + +verify_agent_conf([]) -> + ok; +verify_agent_conf([H|T]) -> + verify_agent_conf_entry(H), + verify_agent_conf(T); +verify_agent_conf(X) -> + error({bad_agent_config, X}). + +verify_agent_conf_entry(Entry) -> + ok = snmp_framework_mib:check_agent(Entry), + ok. + +write_agent_conf(Fd, "", Conf) -> + write_agent_conf(Fd, Conf); +write_agent_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_agent_conf(Fd, Conf). + +write_agent_conf(_Fd, []) -> + ok; +write_agent_conf(Fd, [H|T]) -> + do_write_agent_conf(Fd, H), + write_agent_conf(Fd, T). + +do_write_agent_conf(Fd, {intAgentIpAddress = Tag, Val}) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_agent_conf(Fd, {intAgentUDPPort = Tag, Val} ) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_agent_conf(Fd, {intAgentMaxPacketSize = Tag, Val} ) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_agent_conf(Fd, {snmpEngineMaxMessageSize = Tag, Val} ) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_agent_conf(Fd, {snmpEngineID = Tag, Val} ) -> + io:format(Fd, "{~w, \"~s\"}.~n", [Tag, Val]); +do_write_agent_conf(_Fd, Crap) -> + error({bad_agent_config, Crap}). + + +%% +%% ------ context.conf ------ +%% + +context_entry(Ctx) -> + Ctx. + + +write_context_config(Dir, Conf) -> + Comment = +"%% This file defines the contexts known to the agent.\n" +"%% The data is inserted into the vacmContextTable defined\n" +"%% in SNMP-VIEW-BASED-ACM-MIB.\n" +"%% Each row is a string:\n" +"%% ContextName.\n" +"%%\n" +"%% The empty string is the default context.\n" +"%% For example\n" +"%% \"bridge1\".\n" +"%% \"bridge2\".\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_context_config(Dir, Hdr, Conf). + +write_context_config(Dir, Hdr, Conf) + when is_list(Dir) and is_list(Hdr) and is_list(Conf) -> + Verify = fun() -> verify_context_conf(Conf) end, + Write = fun(Fd) -> write_context_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, "context.conf", Verify, Write). + + +append_context_config(Dir, Conf) + when is_list(Dir) and is_list(Conf) -> + Verify = fun() -> verify_context_conf(Conf) end, + Write = fun(Fd) -> write_context_conf(Fd, Conf) end, + append_config_file(Dir, "context.conf", Verify, Write). + + +read_context_config(Dir) -> + Verify = fun(Entry) -> verify_context_conf_entry(Entry) end, + read_config_file(Dir, "context.conf", Verify). + + +verify_context_conf([]) -> + ok; +verify_context_conf([H|T]) -> + verify_context_conf_entry(H), + verify_context_conf(T); +verify_context_conf(X) -> + error({error_context_config, X}). + +verify_context_conf_entry(Context) -> + {ok, _} = snmp_framework_mib:check_context(Context), + ok. + +write_context_conf(Fd, "", Conf) -> + write_context_conf(Fd, Conf); +write_context_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_context_conf(Fd, Conf). + +write_context_conf(_Fd, []) -> + ok; +write_context_conf(Fd, [H|T]) when is_list(H) -> + io:format(Fd, "\"~s\".~n", [H]), + write_context_conf(Fd, T); +write_context_conf(_Fd, X) -> + error({invalid_context_config, X}). + + +%% +%% ------ community.conf ------ +%% + +community_entry(CommIndex) when CommIndex == "public" -> + CommName = CommIndex, + SecName = "initial", + CtxName = "", + TransportTag = "", + community_entry(CommIndex, CommName, SecName, CtxName, TransportTag); +community_entry(CommIndex) when CommIndex == "all-rights" -> + CommName = CommIndex, + SecName = CommIndex, + CtxName = "", + TransportTag = "", + community_entry(CommIndex, CommName, SecName, CtxName, TransportTag). + +community_entry(CommIndex, CommName, SecName, CtxName, TransportTag) -> + {CommIndex, CommName, SecName, CtxName, TransportTag}. + + +write_community_config(Dir, Conf) -> + Comment = +"%% This file defines the community info which maps to VACM parameters.\n" +"%% The data is inserted into the snmpCommunityTable defined\n" +"%% in SNMP-COMMUNITY-MIB.\n" +"%% Each row is a 5-tuple:\n" +"%% {CommunityIndex, CommunityName, SecurityName, ContextName, TransportTag}.\n" +"%% For example\n" +"%% {\"1\", \"public\", \"initial\", \"\", \"\"}.\n" +"%% {\"2\", \"secret\", \"secret_name\", \"\", \"tag\"}.\n" +"%% {\"3\", \"bridge1\", \"initial\", \"bridge1\", \"\"}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_community_config(Dir, Hdr, Conf). + +write_community_config(Dir, Hdr, Conf) + when is_list(Dir) and is_list(Hdr) and is_list(Conf) -> + Verify = fun() -> verify_community_conf(Conf) end, + Write = fun(Fd) -> write_community_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, "community.conf", Verify, Write). + + +append_community_config(Dir, Conf) + when is_list(Dir) and is_list(Conf) -> + Verify = fun() -> verify_community_conf(Conf) end, + Write = fun(Fd) -> write_community_conf(Fd, Conf) end, + append_config_file(Dir, "community.conf", Verify, Write). + + +read_community_config(Dir) -> + Verify = fun(Entry) -> verify_community_conf_entry(Entry) end, + read_config_file(Dir, "community.conf", Verify). + + +verify_community_conf([]) -> + ok; +verify_community_conf([H|T]) -> + verify_community_conf_entry(H), + verify_community_conf(T); +verify_community_conf(X) -> + error({invalid_community_config, X}). + +verify_community_conf_entry(Context) -> + {ok, _} = snmp_community_mib:check_community(Context), + ok. + +write_community_conf(Fd, "", Conf) -> + write_community_conf(Fd, Conf); +write_community_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_community_conf(Fd, Conf). + +write_community_conf(Fd, Conf) -> + Fun = fun({Idx, Name, SecName, CtxName, TranspTag}) -> + io:format(Fd, "{\"~s\", \"~s\", \"~s\", \"~s\", \"~s\"}.~n", + [Idx, Name, SecName, CtxName, TranspTag]); + (Crap) -> + error({bad_community_config, Crap}) + end, + lists:foreach(Fun, Conf). + + +%% +%% ------ standard.conf ------ +%% + +standard_entry(Tag, Val) -> + {Tag, Val}. + + +write_standard_config(Dir, Conf) -> + Comment = +"%% This file defines the STANDARD-MIB info.\n" +"%% Each row is a 2-tuple:\n" +"%% {StandardVariable, Value}.\n" +"%% For example\n" +"%% {sysDescr, \"Erlang SNMP agent\"}.\n" +"%% {sysObjectID, [1,2,3]}.\n" +"%% {sysContact, \"{mbj,eklas}@erlang.ericsson.se\"}.\n" +"%% {sysName, \"test\"}.\n" +"%% {sysLocation, \"erlang\"}.\n" +"%% {sysServices, 72}.\n" +"%% {snmpEnableAuthenTraps, enabled}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_standard_config(Dir, Hdr, Conf). + +write_standard_config(Dir, Hdr, Conf) + when is_list(Dir) and is_list(Hdr) and is_list(Conf) -> + Verify = fun() -> verify_standard_conf(Conf) end, + Write = fun(Fd) -> write_standard_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, "standard.conf", Verify, Write). + + +append_standard_config(Dir, Conf) + when is_list(Dir) and is_list(Conf) -> + Verify = fun() -> verify_standard_conf(Conf) end, + Write = fun(Fd) -> write_standard_conf(Fd, Conf) end, + append_config_file(Dir, "standard.conf", Verify, Write). + + +read_standard_config(Dir) -> + Verify = fun(Entry) -> verify_standard_conf_entry(Entry) end, + read_config_file(Dir, "standard.conf", Verify). + + +verify_standard_conf([]) -> + ok; +verify_standard_conf([H|T]) -> + verify_standard_conf_entry(H), + verify_standard_conf(T); +verify_standard_conf(X) -> + error({bad_standard_config, X}). + +verify_standard_conf_entry(Std) -> + case snmp_standard_mib:check_standard(Std) of + ok -> + ok; + {ok, _} -> + ok + end. + +write_standard_conf(Fd, "", Conf) -> + write_standard_conf(Fd, Conf); +write_standard_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_standard_conf(Fd, Conf). + +write_standard_conf(Fd, Conf) -> + Fun = fun({Tag, Val}) -> do_write_standard_conf(Fd, Tag, Val) end, + lists:foreach(Fun, Conf). + +do_write_standard_conf(Fd, sysDescr = Tag, Val) -> + io:format(Fd, "{~w, \"~s\"}.~n", [Tag, Val]); +do_write_standard_conf(Fd, sysObjectID = Tag, Val) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_standard_conf(Fd, sysContact = Tag, Val) -> + io:format(Fd, "{~w, \"~s\"}.~n", [Tag, Val]); +do_write_standard_conf(Fd, sysName = Tag, Val) -> + io:format(Fd, "{~w, \"~s\"}.~n", [Tag, Val]); +do_write_standard_conf(Fd, sysLocation = Tag, Val) -> + io:format(Fd, "{~w, \"~s\"}.~n", [Tag, Val]); +do_write_standard_conf(Fd, sysServices = Tag, Val) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_standard_conf(Fd, snmpEnableAuthenTraps = Tag, Val) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_standard_conf(_Fd, Tag, Val) -> + error({bad_standard_config, {Tag, Val}}). + + +%% +%% ------ target_addr.conf ------ +%% + +target_addr_entry(Name, + Ip, + TagList, + ParamsName, + EngineId) -> + target_addr_entry(Name, Ip, TagList, ParamsName, EngineId, []). + +target_addr_entry(Name, + Ip, + TagList, + ParamsName, + EngineId, + TMask) -> + target_addr_entry(Name, Ip, 162, TagList, + ParamsName, EngineId, TMask, 2048). + +target_addr_entry(Name, + Ip, + Udp, + TagList, + ParamsName, + EngineId, + TMask, + MaxMessageSize) -> + target_addr_entry(Name, Ip, Udp, 1500, 3, TagList, + ParamsName, EngineId, TMask, MaxMessageSize). + +target_addr_entry(Name, + Ip, + Udp, + Timeout, + RetryCount, + TagList, + ParamsName, + EngineId, + TMask, + MaxMessageSize) -> + {Name, + Ip, + Udp, + Timeout, + RetryCount, + TagList, + ParamsName, + EngineId, + TMask, + MaxMessageSize}. + + +write_target_addr_config(Dir, Conf) -> + Comment = +"%% This file defines the target address parameters.\n" +"%% The data is inserted into the snmpTargetAddrTable defined\n" +"%% in SNMP-TARGET-MIB, and in the snmpTargetAddrExtTable defined\n" +"%% in SNMP-COMMUNITY-MIB.\n" +"%% Each row is a 10-tuple:\n" +"%% {Name, Ip, Udp, Timeout, RetryCount, TagList, ParamsName, EngineId,\n" +"%% TMask, MaxMessageSize}.\n" +"%% The EngineId value is only used if Inform-Requests are sent to this\n" +"%% target. If Informs are not sent, this value is ignored, and can be\n" +"%% e.g. an empty string. However, if Informs are sent, it is essential\n" +"%% that the value of EngineId matches the value of the target's\n" +"%% actual snmpEngineID.\n" +"%% For example\n" +"%% {\"1.2.3.4 v1\", [1,2,3,4], 162, \n" +"%% 1500, 3, \"std_inform\", \"otp_v2\", \"\",\n" +"%% [127,0,0,0], 2048}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_target_addr_config(Dir, Hdr, Conf). + +write_target_addr_config(Dir, Hdr, Conf) + when is_list(Dir) and is_list(Hdr) and is_list(Conf) -> + Verify = fun() -> verify_target_addr_conf(Conf) end, + Write = fun(Fd) -> write_target_addr_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, "target_addr.conf", Verify, Write). + + + +append_target_addr_config(Dir, Conf) + when is_list(Dir) and is_list(Conf) -> + Verify = fun() -> verify_target_addr_conf(Conf) end, + Write = fun(Fd) -> write_target_addr_conf(Fd, Conf) end, + append_config_file(Dir, "target_addr.conf", Verify, Write). + + +read_target_addr_config(Dir) -> + Verify = fun(Entry) -> verify_target_addr_conf_entry(Entry) end, + read_config_file(Dir, "target_addr.conf", Verify). + + +verify_target_addr_conf([]) -> + ok; +verify_target_addr_conf([H|T]) -> + verify_target_addr_conf_entry(H), + verify_target_addr_conf(T); +verify_target_addr_conf(X) -> + error({bad_target_addr_config, X}). + +verify_target_addr_conf_entry(Entry) -> + {ok, _} = snmp_target_mib:check_target_addr(Entry), + ok. + +write_target_addr_conf(Fd, "", Conf) -> + write_target_addr_conf(Fd, Conf); +write_target_addr_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_target_addr_conf(Fd, Conf). + +write_target_addr_conf(Fd, Conf) -> + Fun = fun(Entry) -> do_write_target_addr_conf(Fd, Entry) end, + lists:foreach(Fun, Conf). + +do_write_target_addr_conf(Fd, + {Name, Ip, Udp, + Timeout, RetryCount, TagList, + ParamsName, EngineId, + TMask, MaxMessageSize}) -> + io:format(Fd, + "{\"~s\", ~w, ~w, ~w, ~w, \"~s\", \"~s\", \"~s\", ~w, ~w}.~n", + [Name, Ip, Udp, Timeout, RetryCount, TagList, + ParamsName, EngineId, TMask, MaxMessageSize]); +do_write_target_addr_conf(_Fd, Crap) -> + error({bad_target_addr_config, Crap}). + + +%% +%% ------ target_params.conf ------ +%% + +target_params_entry(Name, Vsn) -> + SecName = "initial", + SecLevel = noAuthNoPriv, + target_params_entry(Name, Vsn, SecName, SecLevel). + +target_params_entry(Name, Vsn, SecName, SecLevel) -> + MPModel = if Vsn == v1 -> v1; + Vsn == v2 -> v2c; + Vsn == v3 -> v3 + end, + SecModel = if Vsn == v1 -> v1; + Vsn == v2 -> v2c; + Vsn == v3 -> usm + end, + target_params_entry(Name, MPModel, SecModel, SecName, SecLevel). + +target_params_entry(Name, MPModel, SecModel, SecName, SecLevel) -> + {Name, MPModel, SecModel, SecName, SecLevel}. + + +write_target_params_config(Dir, Conf) -> + Comment = +"%% This file defines the target parameters.\n" +"%% The data is inserted into the snmpTargetParamsTable defined\n" +"%% in SNMP-TARGET-MIB.\n" +"%% Each row is a 5-tuple:\n" +"%% {Name, MPModel, SecurityModel, SecurityName, SecurityLevel}.\n" +"%% For example\n" +"%% {\"target_v3\", v3, usm, \"\", noAuthNoPriv}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_target_params_config(Dir, Hdr, Conf). + +write_target_params_config(Dir, Hdr, Conf) + when is_list(Dir) and is_list(Hdr) and is_list(Conf) -> + Verify = fun() -> verify_target_params_conf(Conf) end, + Write = fun(Fd) -> write_target_params_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, "target_params.conf", Verify, Write). + + +append_target_params_config(Dir, Conf) + when is_list(Dir) and is_list(Conf) -> + Verify = fun() -> verify_target_params_conf(Conf) end, + Write = fun(Fd) -> write_target_params_conf(Fd, Conf) end, + append_config_file(Dir, "target_params.conf", Verify, Write). + + +read_target_params_config(Dir) -> + Verify = fun(Entry) -> verify_target_params_conf_entry(Entry) end, + read_config_file(Dir, "target_params.conf", Verify). + + +verify_target_params_conf([]) -> + ok; +verify_target_params_conf([H|T]) -> + verify_target_params_conf_entry(H), + verify_target_params_conf(T); +verify_target_params_conf(X) -> + error({bad_target_params_config, X}). + +verify_target_params_conf_entry(Entry) -> + {ok, _} = snmp_target_mib:check_target_params(Entry), + ok. + +write_target_params_conf(Fd, "", Conf) -> + write_target_params_conf(Fd, Conf); +write_target_params_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_target_params_conf(Fd, Conf). + +write_target_params_conf(Fd, Conf) -> + Fun = fun(Entry) -> do_write_target_params_conf(Fd, Entry) end, + lists:foreach(Fun, Conf). + +do_write_target_params_conf(Fd, + {Name, MpModel, SecModel, SecName, SecLevel}) -> + io:format(Fd, "{\"~s\", ~w, ~w, \"~s\", ~w}.~n", + [Name, MpModel, SecModel, SecName, SecLevel]); +do_write_target_params_conf(_Fd, Crap) -> + error({bad_target_params_config, Crap}). + + +%% +%% ------ notify.conf ------ +%% + +notify_entry(Name, Tag, Type) -> + {Name, Tag, Type}. + + +write_notify_config(Dir, Conf) -> + Comment = +"%% This file defines the notification parameters.\n" +"%% The data is inserted into the snmpNotifyTable defined\n" +"%% in SNMP-NOTIFICATION-MIB.\n" +"%% The Name is used as CommunityString for v1 and v2c.\n" +"%% Each row is a 3-tuple:\n" +"%% {Name, Tag, Type}.\n" +"%% For example\n" +"%% {\"standard trap\", \"std_trap\", trap}.\n" +"%% {\"standard inform\", \"std_inform\", inform}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_notify_config(Dir, Hdr, Conf). + +write_notify_config(Dir, Hdr, Conf) + when is_list(Dir) and is_list(Hdr) and is_list(Conf) -> + Verify = fun() -> verify_notify_conf(Conf) end, + Write = fun(Fd) -> write_notify_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, "notify.conf", Verify, Write). + + +append_notify_config(Dir, Conf) + when is_list(Dir) and is_list(Conf) -> + Verify = fun() -> verify_notify_conf(Conf) end, + Write = fun(Fd) -> write_notify_conf(Fd, Conf) end, + append_config_file(Dir, "notify.conf", Verify, Write). + + +read_notify_config(Dir) -> + Verify = fun(Entry) -> verify_notify_conf_entry(Entry) end, + read_config_file(Dir, "notify.conf", Verify). + + +verify_notify_conf([]) -> + ok; +verify_notify_conf([H|T]) -> + verify_notify_conf_entry(H), + verify_notify_conf(T); +verify_notify_conf(X) -> + error({bad_notify_config, X}). + +verify_notify_conf_entry(Entry) -> + {ok, _} = snmp_notification_mib:check_notify(Entry), + ok. + +write_notify_conf(Fd, "", Conf) -> + write_notify_conf(Fd, Conf); +write_notify_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_notify_conf(Fd, Conf). + +write_notify_conf(Fd, Conf) -> + Fun = fun(Entry) -> do_write_notify_conf(Fd, Entry) end, + lists:foreach(Fun, Conf). + +do_write_notify_conf(Fd, {Name, Tag, Type}) -> + io:format(Fd, "{\"~s\", \"~s\", ~w}.~n", [Name, Tag, Type]); +do_write_notify_conf(_Fd, Crap) -> + error({bad_notify_config, Crap}). + + +%% +%% ------ usm.conf ------ +%% + +usm_entry(EngineID) -> + UserName = "initial", + SecName = "initial", + Clone = zeroDotZero, + AuthP = usmNoAuthProtocol, + AuthKeyC = "", + OwnAuthKeyC = "", + PrivP = usmNoPrivProtocol, + PrivKeyC = "", + OwnPrivKeyC = "", + Public = "", + AuthKey = "", + PrivKey = "", + usm_entry(EngineID, UserName, SecName, Clone, + AuthP, AuthKeyC, OwnAuthKeyC, + PrivP, PrivKeyC, OwnPrivKeyC, + Public, AuthKey, PrivKey). + +usm_entry(EngineID, UserName, SecName, Clone, + AuthP, AuthKeyC, OwnAuthKeyC, + PrivP, PrivKeyC, OwnPrivKeyC, + Public, AuthKey, PrivKey) -> + {EngineID, UserName, SecName, Clone, + AuthP, AuthKeyC, OwnAuthKeyC, + PrivP, PrivKeyC, OwnPrivKeyC, + Public, AuthKey, PrivKey}. + + +write_usm_config(Dir, Conf) -> + Comment = +"%% This file defines the security parameters for the user-based\n" +"%% security model.\n" +"%% The data is inserted into the usmUserTable defined\n" +"%% in SNMP-USER-BASED-SM-MIB.\n" +"%% Each row is a 13-tuple:\n" +"%% {EngineID, UserName, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC,\n" +"%% PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey}.\n" +"%% For example\n" +"%% {\"agentEngine\", \"initial\", \"initial\", zeroDotZero,\n" +"%% usmNoAuthProtocol, \"\", \"\", usmNoPrivProtocol, \"\", \"\", \"\",\n" +"%% \"\", \"\"}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_usm_config(Dir, Hdr, Conf). + +write_usm_config(Dir, Hdr, Conf) + when is_list(Dir) and is_list(Hdr) and is_list(Conf) -> + Verify = fun() -> verify_usm_conf(Conf) end, + Write = fun(Fd) -> write_usm_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, "usm.conf", Verify, Write). + + +append_usm_config(Dir, Conf) + when is_list(Dir) and is_list(Conf) -> + Verify = fun() -> verify_usm_conf(Conf) end, + Write = fun(Fd) -> write_usm_conf(Fd, Conf) end, + append_config_file(Dir, "usm.conf", Verify, Write). + + +read_usm_config(Dir) -> + Verify = fun(Entry) -> verify_usm_conf_entry(Entry) end, + read_config_file(Dir, "usm.conf", Verify). + + +verify_usm_conf([]) -> + ok; +verify_usm_conf([H|T]) -> + verify_usm_conf_entry(H), + verify_usm_conf(T); +verify_usm_conf(X) -> + error({bad_usm_conf, X}). + +verify_usm_conf_entry(Entry) -> + {ok, _} = snmp_user_based_sm_mib:check_usm(Entry), + ok. + +write_usm_conf(Fd, "", Conf) -> + write_usm_conf(Fd, Conf); +write_usm_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_usm_conf(Fd, Conf). + +write_usm_conf(Fd, Conf) -> + Fun = fun(Entry) -> do_write_usm_conf(Fd, Entry) end, + lists:foreach(Fun, Conf). + +do_write_usm_conf(Fd, + {EngineID, UserName, SecName, Clone, + AuthP, AuthKeyC, OwnAuthKeyC, + PrivP, PrivKeyC, OwnPrivKeyC, + Public, AuthKey, PrivKey}) -> + io:format(Fd, "{", []), + io:format(Fd, "\"~s\", ", [EngineID]), + io:format(Fd, "\"~s\", ", [UserName]), + io:format(Fd, "\"~s\", ", [SecName]), + io:format(Fd, "~w, ", [Clone]), + io:format(Fd, "~w, ", [AuthP]), + do_write_usm2(Fd, AuthKeyC, ", "), + do_write_usm2(Fd, OwnAuthKeyC, ", "), + io:format(Fd, "~w, ", [PrivP]), + do_write_usm2(Fd, PrivKeyC, ", "), + do_write_usm2(Fd, OwnPrivKeyC, ", "), + do_write_usm2(Fd, Public, ", "), + do_write_usm2(Fd, AuthKey, ", "), + do_write_usm2(Fd, PrivKey, ""), + io:format(Fd, "}.~n", []); +do_write_usm_conf(_Fd, Crap) -> + error({bad_usm_config, Crap}). + +do_write_usm2(Fd, "", P) -> + io:format(Fd, "\"\"~s", [P]); +do_write_usm2(Fd, X, P) -> + io:format(Fd, "~w~s", [X, P]). + + +%% +%% ------ vacm.conf ------ +%% + +vacm_s2g_entry(SecModel, SecName, GroupName) -> + {vacmSecurityToGroup, SecModel, SecName, GroupName}. + +vacm_acc_entry(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> + {vacmAccess, GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV}. + +vacm_vtf_entry(ViewIndex, ViewSubtree) -> + vacm_vtf_entry(ViewIndex, ViewSubtree, included, null). +vacm_vtf_entry(ViewIndex, ViewSubtree, ViewStatus, ViewMask) -> + {vacmViewTreeFamily, ViewIndex, ViewSubtree, ViewStatus, ViewMask}. + + +write_vacm_config(Dir, Conf) -> + Comment = +"%% This file defines the Mib Views.\n" +"%% The data is inserted into the vacm* tables defined\n" +"%% in SNMP-VIEW-BASED-ACM-MIB.\n" +"%% Each row is one of 3 tuples; one for each table in the MIB:\n" +"%% {vacmSecurityToGroup, SecModel, SecName, GroupName}.\n" +"%% {vacmAccess, GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV}.\n" +"%% {vacmViewTreeFamily, ViewIndex, ViewSubtree, ViewStatus, ViewMask}.\n" +"%% For example\n" +"%% {vacmSecurityToGroup, v2c, \"initial\", \"initial\"}.\n" +"%% {vacmSecurityToGroup, usm, \"initial\", \"initial\"}.\n" +"%% read/notify access to system\n" +"%% {vacmAccess, \"initial\", \"\", any, noAuthNoPriv, exact,\n" +"%% \"system\", \"\", \"system\"}.\n" +"%% {vacmViewTreeFamily, \"system\", [1,3,6,1,2,1,1], included, null}.\n" +"%% {vacmViewTreeFamily, \"exmib\", [1,3,6,1,3], included, null}." +" % for EX1-MIB\n" +"%% {vacmViewTreeFamily, \"internet\", [1,3,6,1], included, null}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_vacm_config(Dir, Hdr, Conf). + +write_vacm_config(Dir, Hdr, Conf) + when is_list(Dir) and is_list(Hdr) and is_list(Conf) -> + Verify = fun() -> verify_vacm_conf(Conf) end, + Write = fun(Fd) -> write_vacm_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, "vacm.conf", Verify, Write). + + +append_vacm_config(Dir, Conf) + when is_list(Dir) and is_list(Conf) -> + Verify = fun() -> verify_vacm_conf(Conf) end, + Write = fun(Fd) -> write_vacm_conf(Fd, Conf) end, + append_config_file(Dir, "vacm.conf", Verify, Write). + + +read_vacm_config(Dir) -> + Verify = fun(Entry) -> verify_vacm_conf_entry(Entry) end, + read_config_file(Dir, "vacm.conf", Verify). + + +verify_vacm_conf([]) -> + ok; +verify_vacm_conf([H|T]) -> + verify_vacm_conf_entry(H), + verify_vacm_conf(T); +verify_vacm_conf(X) -> + error({bad_vacm_conf, X}). + +verify_vacm_conf_entry(Entry) -> + {ok, _} = snmp_view_based_acm_mib:check_vacm(Entry), + ok. + +write_vacm_conf(Fd, "", Conf) -> + write_vacm_conf(Fd, Conf); +write_vacm_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_vacm_conf(Fd, Conf). + +write_vacm_conf(Fd, Conf) -> + Fun = fun(Entry) -> do_write_vacm_conf(Fd, Entry) end, + lists:foreach(Fun, Conf). + +do_write_vacm_conf(Fd, + {vacmSecurityToGroup, + SecModel, SecName, GroupName}) -> + io:format(Fd, "{vacmSecurityToGroup, ~w, \"~s\", \"~s\"}.~n", + [SecModel, SecName, GroupName]); +do_write_vacm_conf(Fd, + {vacmAccess, + GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV}) -> + io:format(Fd, "{vacmAccess, \"~s\", \"~s\", ~w, ~w, ~w, " + "\"~s\", \"~s\", \"~s\"}.~n", + [GroupName, Prefix, SecModel, SecLevel, + Match, RV, WV, NV]); +do_write_vacm_conf(Fd, + {vacmViewTreeFamily, + ViewIndex, ViewSubtree, ViewStatus, ViewMask}) -> + io:format(Fd, "{vacmViewTreeFamily, \"~s\", ~w, ~w, ~w}.~n", + [ViewIndex, ViewSubtree, ViewStatus, ViewMask]); +do_write_vacm_conf(_Fd, Crap) -> + error({bad_vacm_config, Crap}). + + +%% ---- config file wrapper functions ---- + +write_config_file(Dir, File, Verify, Write) -> + snmp_config:write_config_file(Dir, File, Verify, Write). + +append_config_file(Dir, File, Verify, Write) -> + snmp_config:append_config_file(Dir, File, Verify, Write). + +read_config_file(Dir, File, Verify) -> + snmp_config:read_config_file(Dir, File, Verify). + + +%% ---- config file utility functions ---- + +header() -> + {Y,Mo,D} = date(), + {H,Mi,S} = time(), + io_lib:format("%% This file was generated by " + "~w (version-~s) ~w-~2.2.0w-~2.2.0w " + "~2.2.0w:~2.2.0w:~2.2.0w\n", + [?MODULE, ?version, Y, Mo, D, H, Mi, S]). + + +error(R) -> + throw({error, R}). diff --git a/lib/snmp/src/agent/snmpa_discovery_handler.erl b/lib/snmp/src/agent/snmpa_discovery_handler.erl new file mode 100644 index 0000000000..cf38583054 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_discovery_handler.erl @@ -0,0 +1,29 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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_discovery_handler). + +-export([behaviour_info/1, verify/1]). + +behaviour_info(callbacks) -> + [{stage1_finish, 3}]; +behaviour_info(_) -> + undefined. + +verify(Mod) -> + snmp_misc:verify_behaviour(?MODULE, Mod). diff --git a/lib/snmp/src/agent/snmpa_discovery_handler_default.erl b/lib/snmp/src/agent/snmpa_discovery_handler_default.erl new file mode 100644 index 0000000000..12a27993ab --- /dev/null +++ b/lib/snmp/src/agent/snmpa_discovery_handler_default.erl @@ -0,0 +1,38 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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_discovery_handler_default). + +-behaviour(snmpa_discovery_handler). + + +%%%----------------------------------------------------------------- +%%% Implements different error mechanisms. +%%%----------------------------------------------------------------- +-export([stage1_finish/3]). + + +%%----------------------------------------------------------------- +%% This function is called at the end of stage 1 of the discovery +%% process. If security-level is noAuthNoPriv, then +%% +%% +%%----------------------------------------------------------------- +stage1_finish(_TargetName, _ManagerEngineID, _ExtraInfo) -> + ignore. + diff --git a/lib/snmp/src/agent/snmpa_error.erl b/lib/snmp/src/agent/snmpa_error.erl new file mode 100644 index 0000000000..db2b8d0178 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_error.erl @@ -0,0 +1,66 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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_error). + +-behaviour(snmpa_error_report). + + +%%%----------------------------------------------------------------- +%%% Implements different error mechanisms. +%%%----------------------------------------------------------------- +-export([user_err/2, config_err/2]). + + +%%----------------------------------------------------------------- +%% This function is called when there is an error in a user +%% supplied item, e.g. instrumentation function. +%%----------------------------------------------------------------- +user_err(F, A) -> + report_err(user_err, F, A). + + +%%----------------------------------------------------------------- +%% This function is called when there is a configuration error, +%% either at startup (in a conf-file) or at run-time (e.g. when +%% information in the configuration tables are inconsistent.) +%%----------------------------------------------------------------- +config_err(F, A) -> + report_err(config_err, F, A). + + +%% ----------------------------------------------------------------- + + +report_err(Func, Format, Args) -> + case report_module() of + {ok, Mod} -> + (catch Mod:Func(Format, Args)); + _ -> + ok + end. + + + +report_module() -> + case (catch ets:lookup(snmp_agent_table, error_report_mod)) of + [{error_report_mod, Mod}] -> + {ok, Mod}; + _ -> + error + end. diff --git a/lib/snmp/src/agent/snmpa_error_io.erl b/lib/snmp/src/agent/snmpa_error_io.erl new file mode 100644 index 0000000000..54cdb6baac --- /dev/null +++ b/lib/snmp/src/agent/snmpa_error_io.erl @@ -0,0 +1,49 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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_error_io). + +-behaviour(snmpa_error_report). + + +%%%----------------------------------------------------------------- +%%% Implements different error mechanisms. +%%%----------------------------------------------------------------- +-export([user_err/2, config_err/2]). + + +%%----------------------------------------------------------------- +%% This function is called when there is an error in a user +%% supplied item, e.g. instrumentation function. +%%----------------------------------------------------------------- +user_err(F, A) -> + error_msg("User error", F, A). + + +%%----------------------------------------------------------------- +%% This function is called when there is a configuration error, +%% either at startup (in a conf-file) or at run-time (e.g. when +%% information in the configuration tables are inconsistent.) +%%----------------------------------------------------------------- +config_err(F, A) -> + error_msg("Configuration error", F, A). + + +error_msg(P, F, A) -> + io:format("*** SNMP ~s *** ~n" ++ F ++ "~n", [P|A]). + diff --git a/lib/snmp/src/agent/snmpa_error_logger.erl b/lib/snmp/src/agent/snmpa_error_logger.erl new file mode 100644 index 0000000000..b1124fd728 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_error_logger.erl @@ -0,0 +1,50 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_error_logger). + +-behaviour(snmpa_error_report). + + +%%%----------------------------------------------------------------- +%%% Implements different error mechanisms. +%%%----------------------------------------------------------------- +-export([user_err/2, config_err/2]). + + +%%----------------------------------------------------------------- +%% This function is called when there is an error in a user +%% supplied item, e.g. instrumentation function. +%%----------------------------------------------------------------- +user_err(F, A) -> + error_msg("** User error: ", F, A). + + +%%----------------------------------------------------------------- +%% This function is called when there is a configuration error, +%% either at startup (in a conf-file) or at run-time (e.g. when +%% information in the configuration tables are inconsistent.) +%%----------------------------------------------------------------- +config_err(F, A) -> + error_msg("** Configuration error: ", F, A). + + +error_msg(P, F, A) -> + S = snmp_misc:format(1024, lists:concat([P, F, "\n"]), A), + catch error_logger:error_msg("~s", [S]). + diff --git a/lib/snmp/src/agent/snmpa_error_report.erl b/lib/snmp/src/agent/snmpa_error_report.erl new file mode 100644 index 0000000000..35c02edcab --- /dev/null +++ b/lib/snmp/src/agent/snmpa_error_report.erl @@ -0,0 +1,27 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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_error_report). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{user_err, 2}, + {config_err, 2}]; +behaviour_info(_) -> + undefined. diff --git a/lib/snmp/src/agent/snmpa_general_db.erl b/lib/snmp/src/agent/snmpa_general_db.erl new file mode 100644 index 0000000000..795c353a9e --- /dev/null +++ b/lib/snmp/src/agent/snmpa_general_db.erl @@ -0,0 +1,578 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-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_general_db). + + +%%%----------------------------------------------------------------- +%%% This module implements a very simple "generic" MIB database +%%% interface. It contains the most common functions performed. +%%% It is generic in the sense that it implements both an interface +%%% to mnesia and ets. +%%% +%%% Note that this module has nothing to do with the snmp_generic +%%% and snmp_generic_mnesia modules. +%%%----------------------------------------------------------------- + +-export([open/5, close/1, read/2, write/2, delete/1, delete/2]). +-export([sync/1, backup/2]). +-export([match_object/2, match_delete/2]). +-export([tab2list/1, info/1, info/2]). + + +-define(VMODULE,"GDB"). +-include("snmp_verbosity.hrl"). + + +%% --------------------------------------------------------------- +%% open(Info,Name,RecName,Attr,Type) -> term() +%% Info -> ets | {ets, Dir} | +%% {dets, Dir} | {dets, Dir, Action} | +%% {mnesia, Nodes} | {mnesia, Nodes, Action} +%% Name -> atom() +%% RecName -> Name of the record to store +%% Attr -> Attributes of the record stored in the table +%% Type -> set | bag +%% Dir -> string() +%% Nodes -> [node()] +%% Action -> keep | clear +%% +%% Open or create a database table. In the mnesia/dets case, +%% where the table is stored on disc, it could be of interest +%% to clear the table. This is controlled by the Action parameter. +%% An empty node list ([]), is traslated into a list containing +%% only the own node. +%% --------------------------------------------------------------- +open({mnesia,Nodes,clear}, Name, RecName, Attr, Type) when is_list(Nodes) -> + ?vtrace("[mnesia] open ~p database ~p for ~p on ~p; clear", + [Type, Name, RecName, Nodes]), + mnesia_open(mnesia_table_check(Name), Nodes, RecName, Attr, Type, clear); +open({mnesia,Nodes,_}, Name, RecName, Attr, Type) -> + ?vtrace("[mnesia] open ~p database ~p for ~p on ~p; keep", + [Type, Name, RecName, Nodes]), + open({mnesia,Nodes}, Name, RecName, Attr, Type); +open({mnesia,Nodes}, Name, RecName, Attr, Type) when is_list(Nodes) -> + ?vtrace("[mnesia] open ~p database ~p for ~p on ~p", + [Type, Name, RecName, Nodes]), + mnesia_open(mnesia_table_check(Name), Nodes, RecName, Attr, Type, keep); + +open({dets, Dir, Action}, Name, _RecName, _Attr, Type) -> + dets_open(Name, dets_filename(Name, Dir), Type, Action); +open({dets, Dir}, Name, _RecName, _Attr, Type) -> + dets_open(Name, dets_filename(Name, Dir), Type, keep); + +%% This function creates the ets table +open(ets, Name, _RecName, _Attr, Type) -> + ?vtrace("[ets] open ~p database ~p", [Type, Name]), + Ets = ets:new(Name, [Type, protected, {keypos, 2}]), + {ets, Ets, undefined}; +open({ets, Dir}, Name, _RecName, _Attr, Type) -> + ets_open(Name, Dir, keep, Type); +open({ets, Dir, Action}, Name, _RecName, _Attr, Type) -> + ets_open(Name, Dir, Action, Type). + +ets_open(Name, Dir, keep, Type) -> + ?vtrace("[ets] open ~p database ~p", [Type, Name]), + File = filename:join(Dir, atom_to_list(Name) ++ ".db"), + case file:read_file_info(File) of + {ok, _} -> + case ets:file2tab(File) of + {ok, Tab} -> + {ets, Tab, File}; + {error, Reason} -> + user_err("failed converting file to (ets-) tab: " + "File: ~p" + "~n~p", [File, Reason]), + Ets = ets:new(Name, [Type, protected, {keypos, 2}]), + {ets, Ets, File} + end; + {error, _} -> + Ets = ets:new(Name, [Type, protected, {keypos, 2}]), + {ets, Ets, File} + end; +ets_open(Name, Dir, clear, Type) -> + File = filename:join(Dir, atom_to_list(Name) ++ ".db"), + Ets = ets:new(Name, [Type, protected, {keypos, 2}]), + {ets, Ets, File}. + + + +mnesia_open({table_exist,Name},_Nodes,_RecName,_Attr,_Type,clear) -> + ?vtrace("[mnesia] database ~p already exists; clear content",[Name]), + Pattern = '_', + F = fun() -> + Recs = mnesia:match_object(Name,Pattern,read), + lists:foreach(fun(Rec) -> + mnesia:delete_object(Name,Rec,write) + end, Recs), + Recs + end, + case mnesia:transaction(F) of + {aborted,Reason} -> + exit({aborted,Reason}); + {atomic,_} -> + {mnesia,Name} + end; +mnesia_open({table_exist,Name},_Nodes,_RecName,_Attr,_Type,keep) -> + ?vtrace("[mnesia] database ~p already exists; keep content",[Name]), + {mnesia,Name}; +mnesia_open({no_table,Name},[],Type,RecName,Attr,Action) -> + mnesia_open({no_table,Name},[node()],Type,RecName,Attr,Action); +mnesia_open({no_table,Name},Nodes,RecName,Attr,Type,_) -> + ?vtrace("[mnesia] no database ~p: create for ~p of type ~p", + [Name,RecName,Type]), + %% Ok, we assume that this means that the table does not exist + Args = [{record_name,RecName}, {attributes,Attr}, + {type,Type}, {disc_copies,Nodes}], + case mnesia:create_table(Name,Args) of + {atomic,ok} -> + {mnesia,Name}; + {aborted,Reason} -> + %% ?vinfo("[mnesia] aborted: ~p", [Reason]), + exit({failed_create_mnesia_table,Reason}) + end. + + +mnesia_table_check(Name) -> + ?vtrace("[mnesia] check existens of database ~p",[Name]), + case (catch mnesia:table_info(Name,type)) of + {'EXIT', _Reason} -> + {no_table, Name}; + _ -> + {table_exist, Name} + end. + + +dets_open(Name, File, Type, Action) -> + ?vtrace("[dets] open database ~p (~p)", [Name, Action]), + N = dets_open1(Name, File, Type), + dets_open2(N, Action). + +dets_open1(Name, File, Type) -> + ?vtrace("[dets] open database ~p of type ~p",[Name, Type]), + {ok, N} = dets:open_file(Name, [{file, File}, {type, Type}, {keypos, 2}]), + N. + +dets_open2(N, clear) -> + dets:match_delete(N,'_'), + {dets, N}; +dets_open2(N, _) -> + {dets, N}. + +%% dets_table_check(Name, Dir) -> +%% Filename = dets_filename(Name, Dir), +%% ?vtrace("[dets] check existens of database: " +%% "~n ~p -> ~s" +%% "~n ~p" +%% "~n ~p" +%% , +%% [Name, Filename, file:list_dir(Dir), file:read_file_info(Filename)]), +%% case (catch dets:info(Filename, size)) of +%% {'EXIT', Reason} -> +%% {no_table, Name, Filename}; +%% undefined -> %% Introduced in R8 +%% {no_table, Name, Filename}; +%% _ -> +%% {table_exist, Name, Filename} +%% end. + + +dets_filename(Name, Dir) -> + Dir1 = dets_filename1(Dir), + Dir2 = string:strip(Dir1, right, $/), + io_lib:format("~s/~p.dat", [Dir2, Name]). + +dets_filename1([]) -> "."; +dets_filename1(Dir) -> Dir. + + +%% --------------------------------------------------------------- +%% close(DbRef) -> +%% DbRef -> term() +%% +%% Close the database. This does nothing in the mnesia case, but +%% deletes the table in the ets case. +%% --------------------------------------------------------------- +close({mnesia,_}) -> + ?vtrace("[mnesia] close database: NO ACTION",[]), + ok; +close({dets, Name}) -> + ?vtrace("[dets] close database ~p",[Name]), + dets:close(Name); +close({ets, Name, undefined}) -> + ?vtrace("[ets] close (delete) table ~p",[Name]), + ets:delete(Name); +close({ets, Name, File}) -> + ?vtrace("[ets] close (delete) table ~p",[Name]), + write_ets_file(Name, File), + ets:delete(Name). + + +%% --------------------------------------------------------------- +%% read(DbRef,Key) -> false | {value,Rec} +%% DbRef -> term() +%% Rec -> tuple() +%% +%% Retrieve a record from the database. +%% --------------------------------------------------------------- +read({mnesia, Name}, Key) -> + ?vtrace("[mnesia] read (dirty) from database ~p: ~p",[Name,Key]), + case (catch mnesia:dirty_read(Name,Key)) of + [Rec|_] -> {value,Rec}; + _ -> false + end; +read({dets, Name}, Key) -> + ?vtrace("[dets] read from table ~p: ~p",[Name,Key]), + case dets:lookup(Name, Key) of + [Rec|_] -> {value, Rec}; + _ -> false + end; +read({ets, Name, _}, Key) -> + ?vtrace("[ets] read from table ~p: ~p",[Name,Key]), + case ets:lookup(Name, Key) of + [Rec|_] -> {value, Rec}; + _ -> false + end. + + +%% --------------------------------------------------------------- +%% write(DbRef,Rec) -> ok +%% DbRef -> term() +%% Rec -> tuple() +%% +%% Write a record to the database. +%% --------------------------------------------------------------- +write({mnesia, Name}, Rec) -> + ?vtrace("[mnesia] write to database ~p",[Name]), + F = fun() -> mnesia:write(Name, Rec, write) end, + case mnesia:transaction(F) of + {aborted, Reason} -> + exit({aborted, Reason}); + {atomic,_} -> + ok + end; +write({dets, Name}, Rec) -> + ?vtrace("[dets] write to table ~p",[Name]), + dets:insert(Name, Rec); +write({ets, Name, _}, Rec) -> + ?vtrace("[ets] write to table ~p",[Name]), + ets:insert(Name, Rec). + + +%% --------------------------------------------------------------- +%% delete(DbRef) -> +%% DbRef -> term() +%% +%% Delete the database. +%% --------------------------------------------------------------- +delete({mnesia, Name}) -> + ?vtrace("[mnesia] delete database: ~p",[Name]), + mnesia:delete_table(Name); +delete({dets, Name}) -> + ?vtrace("[dets] delete database ~p",[Name]), + File = dets:info(Name, filename), + case dets:close(Name) of + ok -> + file:delete(File); + Error -> + Error + end; +delete({ets, Name, undefined}) -> + ?vtrace("[dets] delete table ~p",[Name]), + ets:delete(Name); +delete({ets, Name, File}) -> + ?vtrace("[dets] delete table ~p",[Name]), + file:delete(File), + ets:delete(Name). + + +%% --------------------------------------------------------------- +%% delete(DbRef, Key) -> ok +%% DbRef -> term() +%% Key -> term() +%% +%% Delete a record from the database. +%% --------------------------------------------------------------- +delete({mnesia, Name}, Key) -> + ?vtrace("[mnesia] delete from database ~p: ~p", [Name, Key]), + F = fun() -> mnesia:delete(Name, Key, write) end, + case mnesia:transaction(F) of + {aborted,Reason} -> + exit({aborted,Reason}); + {atomic,_} -> + ok + end; +delete({dets, Name}, Key) -> + ?vtrace("[dets] delete from table ~p: ~p", [Name, Key]), + dets:delete(Name, Key); +delete({ets, Name, _}, Key) -> + ?vtrace("[ets] delete from table ~p: ~p", [Name, Key]), + ets:delete(Name, Key). + + +%% --------------------------------------------------------------- +%% match_object(DbRef,Pattern) -> [tuple()] +%% DbRef -> term() +%% Pattern -> tuple() +%% +%% Search the database for records witch matches the pattern. +%% --------------------------------------------------------------- +match_object({mnesia, Name}, Pattern) -> + ?vtrace("[mnesia] match_object in ~p of ~p",[Name, Pattern]), + F = fun() -> mnesia:match_object(Name, Pattern, read) end, + case mnesia:transaction(F) of + {aborted, Reason} -> + exit({aborted, Reason}); + {atomic, Recs} -> + Recs + end; +match_object({dets, Name}, Pattern) -> + ?vtrace("[dets] match_object in ~p of ~p",[Name, Pattern]), + dets:match_object(Name, Pattern); +match_object({ets, Name, _}, Pattern) -> + ?vtrace("[ets] match_object in ~p of ~p",[Name, Pattern]), + ets:match_object(Name, Pattern). + + +%% --------------------------------------------------------------- +%% match_delete(DbRef,Pattern) -> +%% DbRef -> term() +%% Pattern -> tuple() +%% +%% Search the database for records witch matches the pattern and +%% deletes them from the database. +%% --------------------------------------------------------------- +match_delete({mnesia, Name}, Pattern) -> + ?vtrace("[mnesia] match_delete in ~p with pattern ~p",[Name,Pattern]), + F = fun() -> + Recs = mnesia:match_object(Name, Pattern, read), + lists:foreach(fun(Rec) -> + mnesia:delete_object(Name, Rec, write) + end, Recs), + Recs + end, + case mnesia:transaction(F) of + {aborted, Reason} -> + exit({aborted, Reason}); + {atomic,R} -> + R + end; +match_delete({dets, Name}, Pattern) -> + ?vtrace("[dets] match_delete in ~p with pattern ~p",[Name,Pattern]), + Recs = dets:match_object(Name, Pattern), + dets:match_delete(Name, Pattern), + Recs; +match_delete({ets, Name, _}, Pattern) -> + ?vtrace("[ets] match_delete in ~p with pattern ~p",[Name,Pattern]), + Recs = ets:match_object(Name, Pattern), + ets:match_delete(Name, Pattern), + Recs. + + +%% --------------------------------------------------------------- +%% tab2list(DbRef) -> [tuple()] +%% DbRef -> term() +%% +%% Return all records in the table in the form of a list. +%% --------------------------------------------------------------- +tab2list({mnesia, Name}) -> + ?vtrace("[mnesia] tab2list -> list of ~p", [Name]), + match_object({mnesia, Name}, mnesia:table_info(Name, wild_pattern)); +tab2list({dets, Name}) -> + ?vtrace("[dets] tab2list -> list of ~p", [Name]), + match_object({dets, Name}, '_'); +tab2list({ets, Name, _}) -> + ?vtrace("[ets] tab2list -> list of ~p", [Name]), + ets:tab2list(Name). + + + +%% --------------------------------------------------------------- +%% info(Db) -> taglist() +%% info(Db, Item) -> Info +%% Db -> term() +%% Item -> atom() +%% tablist() -> [{key(),value()}] +%% key() -> atom() +%% value() -> term() +%% +%% Retrieve table information. +%% --------------------------------------------------------------- +info({mnesia, Name}) -> + case (catch mnesia:table_info(Name, all)) of + Info when is_list(Info) -> + Info; + {'EXIT', {aborted, Reason}} -> + {error, Reason}; + Else -> + {error, Else} + end; +info({dets, Name}) -> + dets:info(Name); +info({ets, Name, _}) -> + case ets:info(Name) of + undefined -> + []; + L -> + L + end. + + +info({mnesia, Name}, Item) -> + case (catch mnesia:table_info(Name, Item)) of + {'EXIT', {aborted, Reason}} -> + {error, Reason}; + Info -> + Info + end; +info({dets, Name}, memory) -> + dets:info(Name, file_size); +info({dets, Name}, Item) -> + dets:info(Name, Item); +info({ets, Name, _}, Item) -> + ets:info(Name, Item). + + +%% --------------------------------------------------------------- +%% sync(Db) -> ok | {error, Reason} +%% Db -> term() +%% Reason -> term() +%% +%% Dump table to disc (if possible) +%% --------------------------------------------------------------- + +sync({mnesia, _}) -> + ok; +sync({dets, Name}) -> + dets:sync(Name); +sync({ets, _Name, undefined}) -> + ok; +sync({ets, Name, File}) -> + write_ets_file(Name, File). + + +%% --------------------------------------------------------------- +%% backup(Db, BackupDir) -> ok | {error, Reason} +%% Db -> term() +%% Reason -> term() +%% +%% Make a backup copy of the DB (only valid for det and ets) +%% --------------------------------------------------------------- + +backup({mnesia, _}, _) -> + ok; +backup({dets, Name}, BackupDir) -> + case dets:info(Name, filename) of + undefined -> + {error, no_file}; + Filename -> + case filename:dirname(Filename) of + BackupDir -> + {error, db_dir}; + _ -> + Type = dets:info(Name, type), + KP = dets:info(Name, keypos), + dets_backup(Name, + filename:basename(Filename), + BackupDir, Type, KP) + end + end; +backup({ets, _Name, undefined}, _BackupDir) -> + ok; +backup({ets, Name, File}, BackupDir) -> + Filename = filename:basename(File), + case filename:join(BackupDir, Filename) of + File -> + %% Oups: backup-dir and db-dir the same + {error, db_dir}; + BackupFile -> + write_ets_file(Name, BackupFile) + end. + + +dets_backup(Name, Filename, BackupDir, Type, KP) -> + ?vtrace("dets_backup -> entry with" + "~n Name: ~p" + "~n Filename: ~p" + "~n BackupDir: ~p" + "~n Type: ~p" + "~n KP: ~p", [Name, Filename, BackupDir, Type, KP]), + BackupFile = filename:join(BackupDir, Filename), + ?vtrace("dets_backup -> " + "~n BackupFile: ~p", [BackupFile]), + Backup = list_to_atom(atom_to_list(Name) ++ "_backup"), + Opts = [{file, BackupFile}, {type, Type}, {keypos, KP}], + case dets:open_file(Backup, Opts) of + {ok, B} -> + ?vtrace("dets_backup -> create fun", []), + F = fun(Arg) -> + dets_backup(Arg, start, Name, B) + end, + dets:safe_fixtable(Name, true), + Res = dets:init_table(Backup, F, [{format, bchunk}]), + dets:safe_fixtable(Name, false), + ?vtrace("dets_backup -> Res: ~p", [Res]), + Res; + Error -> + ?vinfo("dets_backup -> open_file failed: " + "~n ~p", [Error]), + Error + end. + + +dets_backup(close, _Cont, _Name, B) -> + dets:close(B), + ok; +dets_backup(read, Cont1, Name, B) -> + case dets:bchunk(Name, Cont1) of + {Cont2, Data} -> + F = fun(Arg) -> + dets_backup(Arg, Cont2, Name, B) + end, + {Data, F}; + '$end_of_table' -> + dets:close(B), + end_of_input; + Error -> + Error + end. + + +%%---------------------------------------------------------------------- + +write_ets_file(Name, File) -> + TmpFile = File ++ ".tmp", + case ets:tab2file(Name, TmpFile) of + ok -> + case file:rename(TmpFile, File) of + ok -> + ok; + Else -> + user_err("Warning: could not move file ~p" + " (~p)", [File, Else]) + end; + {error, Reason} -> + user_err("Warning: could not save file ~p (~p)", + [File, Reason]) + end. + + +%%---------------------------------------------------------------------- + +user_err(F, A) -> + snmpa_error:user_err(F, A). diff --git a/lib/snmp/src/agent/snmpa_internal.hrl b/lib/snmp/src/agent/snmpa_internal.hrl new file mode 100644 index 0000000000..a33a6809dc --- /dev/null +++ b/lib/snmp/src/agent/snmpa_internal.hrl @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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% +%% + +-ifndef(snmpa_internal). +-define(snmpa_internal, true). + +-include_lib("snmp/src/app/snmp_internal.hrl"). + +-define(snmpa_info(F, A), ?snmp_info("agent", F, A)). +-define(snmpa_warning(F, A), ?snmp_warning("agent", F, A)). +-define(snmpa_error(F, A), ?snmp_error("agent", F, A)). + +-endif. % -ifdef(snmpa_internal). + + + diff --git a/lib/snmp/src/agent/snmpa_local_db.erl b/lib/snmp/src/agent/snmpa_local_db.erl new file mode 100644 index 0000000000..d9d6e633de --- /dev/null +++ b/lib/snmp/src/agent/snmpa_local_db.erl @@ -0,0 +1,1226 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_local_db). + +-include_lib("kernel/include/file.hrl"). +-include("snmpa_internal.hrl"). +-include("snmp_types.hrl"). +-include("STANDARD-MIB.hrl"). + +%% -define(VMODULE, "LDB"). +-include("snmp_verbosity.hrl"). + + +%% External exports +-export([start_link/3, start_link/4, stop/0, info/0, verbosity/1]). +-export([dump/0, backup/1, + register_notify_client/2, unregister_notify_client/1]). +-export([table_func/2, table_func/4, + variable_get/1, variable_set/2, variable_delete/1, variable_inc/2, + table_create/1, table_exists/1, table_delete/1, + table_create_row/3, table_create_row/4, table_construct_row/4, + table_delete_row/2, + table_get_row/2, + table_get_element/3, table_get_elements/4, + table_set_elements/3, table_set_status/7, + table_next/2, + table_max_col/2, + table_get/1]). + +-export([get_elements/2]). + +-export([match/2]). + +%% Debug exports +-export([print/0, print/1, print/2]). + +%% Internal exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + + +-define(BACKUP_TAB, snmpa_local_backup). +-define(DETS_TAB, snmpa_local_db1). +-define(ETS_TAB, snmpa_local_db2). +-define(SERVER, ?MODULE). + +-record(state, {dets, ets, notify_clients = [], backup}). +-record(dets, {tab, shadow}). + +%% -define(snmp_debug,true). +-include("snmp_debug.hrl"). + + +-ifdef(snmp_debug). +-define(GS_START_LINK(Prio, Dir, DbInitError, Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, + [Prio, Dir, DbInitError, Opts], + [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Prio, Dir, DbInitError, Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, + [Prio, Dir, DbInitError, Opts], + [])). +-endif. + + +%%%----------------------------------------------------------------- +%%% Implements a general database in which its possible +%%% to store variables and tables. Provide functions for +%%% tableaccess by row or by element, and for next. +%%% +%%% Opts = [Opt] +%%% Opt = {auto_repair, false | true | true_verbose} | +%%% {verbosity,silence | log | debug} +%%%----------------------------------------------------------------- +start_link(Prio, DbDir, Opts) when is_list(Opts) -> + start_link(Prio, DbDir, terminate, Opts). + +start_link(Prio, DbDir, DbInitError, Opts) when is_list(Opts) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n DbDir: ~p" + "~n DbInitError: ~p" + "~n Opts: ~p", [Prio, DbDir, DbInitError, Opts]), + ?GS_START_LINK(Prio, DbDir, DbInitError, Opts). + +stop() -> + call(stop). + +register_notify_client(Client,Module) -> + call({register_notify_client,Client,Module}). + + +unregister_notify_client(Client) -> + call({unregister_notify_client,Client}). + +backup(BackupDir) -> + call({backup, BackupDir}). + +dump() -> + call(dump). + +info() -> + call(info). + +verbosity(Verbosity) -> + cast({verbosity,Verbosity}). + + +%%%----------------------------------------------------------------- + +init([Prio, DbDir, DbInitError, Opts]) -> + ?d("init -> entry with" + "~n Prio: ~p" + "~n DbDir: ~p" + "~n DbInitError: ~p" + "~n Opts: ~p", [Prio, DbDir, DbInitError, Opts]), + case (catch do_init(Prio, DbDir, DbInitError, Opts)) of + {ok, State} -> + ?vdebug("started",[]), + {ok, State}; + {error, Reason} -> + config_err("failed starting local-db: ~n~p", [Reason]), + {stop, {error, Reason}}; + Error -> + config_err("failed starting local-db: ~n~p", [Error]), + {stop, {error, Error}} + end. + +do_init(Prio, DbDir, DbInitError, Opts) -> + process_flag(priority, Prio), + process_flag(trap_exit, true), + put(sname,ldb), + put(verbosity,get_opt(verbosity, Opts, ?default_verbosity)), + ?vlog("starting",[]), + Dets = dets_open(DbDir, DbInitError, Opts), + Ets = ets:new(?ETS_TAB, [set, protected]), + ?vdebug("started",[]), + {ok, #state{dets = Dets, ets = Ets}}. + +dets_open(DbDir, DbInitError, Opts) -> + Name = ?DETS_TAB, + Filename = dets_filename(Name, DbDir), + case file:read_file_info(Filename) of + {ok, _} -> + %% File exists + case do_dets_open(Name, Filename, Opts) of + {ok, Dets} -> + ?vdebug("dets open done",[]), + Shadow = ets:new(snmp_local_db1_shadow, [set, protected]), + dets:to_ets(Dets, Shadow), + ?vtrace("shadow table created and populated",[]), + #dets{tab = Dets, shadow = Shadow}; + {error, Reason1} -> + user_err("Corrupt local database: ~p", [Filename]), + case DbInitError of + terminate -> + throw({error, {failed_open_dets, Reason1}}); + _ -> + Saved = Filename ++ ".saved", + file:rename(Filename, Saved), + case do_dets_open(Name, Filename, Opts) of + {ok, Dets} -> + Shadow = ets:new(snmp_local_db1_shadow, + [set, protected]), + #dets{tab = Dets, shadow = Shadow}; + {error, Reason2} -> + user_err("Could not create local " + "database: ~p" + "~n ~p" + "~n ~p", + [Filename, Reason1, Reason2]), + throw({error, {failed_open_dets, Reason2}}) + end + end + end; + _ -> + case do_dets_open(Name, Filename, Opts) of + {ok, Dets} -> + ?vdebug("dets open done",[]), + Shadow = ets:new(snmp_local_db1_shadow, [set, protected]), + ?vtrace("shadow table created",[]), + #dets{tab = Dets, shadow = Shadow}; + {error, Reason} -> + user_err("Could not create local database ~p" + "~n ~p", [Filename, Reason]), + throw({error, {failed_open_dets, Reason}}) + end + end. + +do_dets_open(Name, Filename, Opts) -> + Repair = get_opt(repair, Opts, true), + AutoSave = get_opt(auto_save, Opts, 5000), + Args = [{auto_save, AutoSave}, + {file, Filename}, + {repair, Repair}], + dets:open_file(Name, Args). + + +dets_filename(Name, Dir) when is_atom(Name) -> + dets_filename(atom_to_list(Name), Dir); +dets_filename(Name, Dir) -> + filename:join(dets_filename1(Dir), Name). + +dets_filename1([]) -> "."; +dets_filename1(Dir) -> Dir. + + +%%----------------------------------------------------------------- +%% Interface functions. +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Functions for debugging. +%%----------------------------------------------------------------- +print() -> call(print). +print(Table) -> call({print,Table,volatile}). +print(Table, Db) -> call({print,Table,Db}). + +variable_get({Name, Db}) -> + call({variable_get, Name, Db}); +variable_get(Name) -> + call({variable_get, Name, volatile}). + +variable_set({Name, Db}, Val) -> + call({variable_set, Name, Db, Val}); +variable_set(Name, Val) -> + call({variable_set, Name, volatile, Val}). + +variable_inc({Name, Db}, N) -> + cast({variable_inc, Name, Db, N}); +variable_inc(Name, N) -> + cast({variable_inc, Name, volatile, N}). + +variable_delete({Name, Db}) -> + call({variable_delete, Name, Db}); +variable_delete(Name) -> + call({variable_delete, Name, volatile}). + + +table_create({Name, Db}) -> + call({table_create, Name, Db}); +table_create(Name) -> + call({table_create, Name, volatile}). + +table_exists({Name, Db}) -> + call({table_exists, Name, Db}); +table_exists(Name) -> + call({table_exists, Name, volatile}). + +table_delete({Name, Db}) -> + call({table_delete, Name, Db}); +table_delete(Name) -> + call({table_delete, Name, volatile}). + +table_delete_row({Name, Db}, RowIndex) -> + call({table_delete_row, Name, Db, RowIndex}); +table_delete_row(Name, RowIndex) -> + call({table_delete_row, Name, volatile, RowIndex}). + +table_get_row({Name, Db}, RowIndex) -> + call({table_get_row, Name, Db, RowIndex}); +table_get_row(Name, RowIndex) -> + call({table_get_row, Name, volatile, RowIndex}). + +table_get_element({Name, Db}, RowIndex, Col) -> + call({table_get_element, Name, Db, RowIndex, Col}); +table_get_element(Name, RowIndex, Col) -> + call({table_get_element, Name, volatile, RowIndex, Col}). + +table_set_elements({Name, Db}, RowIndex, Cols) -> + call({table_set_elements, Name, Db, RowIndex, Cols}); +table_set_elements(Name, RowIndex, Cols) -> + call({table_set_elements, Name, volatile, RowIndex, Cols}). + +table_next({Name, Db}, RestOid) -> + call({table_next, Name, Db, RestOid}); +table_next(Name, RestOid) -> + call({table_next, Name, volatile, RestOid}). + +table_max_col({Name, Db}, Col) -> + call({table_max_col, Name, Db, Col}); +table_max_col(Name, Col) -> + call({table_max_col, Name, volatile, Col}). + +table_create_row({Name, Db}, RowIndex, Row) -> + call({table_create_row, Name, Db,RowIndex, Row}); +table_create_row(Name, RowIndex, Row) -> + call({table_create_row, Name, volatile, RowIndex, Row}). +table_create_row(NameDb, RowIndex, Status, Cols) -> + Row = table_construct_row(NameDb, RowIndex, Status, Cols), + table_create_row(NameDb, RowIndex, Row). + +match({Name, Db}, Pattern) -> + call({match, Name, Db, Pattern}); +match(Name, Pattern) -> + call({match, Name, volatile, Pattern}). + + +table_get(Table) -> + table_get(Table, [], []). + +table_get(Table, Idx, Acc) -> + case table_next(Table, Idx) of + endOfTable -> + lists:reverse(Acc); + NextIdx -> + case table_get_row(Table, NextIdx) of + undefined -> + {error, {failed_get_row, NextIdx, lists:reverse(Acc)}}; + Row -> + NewAcc = [{NextIdx, Row}|Acc], + table_get(Table, NextIdx, NewAcc) + end + end. + + +%%----------------------------------------------------------------- +%% Implements the variable functions. +%%----------------------------------------------------------------- +handle_call({variable_get, Name, Db}, _From, State) -> + ?vlog("variable get: ~p [~p]",[Name, Db]), + {reply, lookup(Db, Name, State), State}; + +handle_call({variable_set, Name, Db, Val}, _From, State) -> + ?vlog("variable ~p set [~p]: " + "~n Val: ~p",[Name, Db, Val]), + {reply, insert(Db, Name, Val, State), State}; + +handle_call({variable_delete, Name, Db}, _From, State) -> + ?vlog("variable delete: ~p [~p]",[Name, Db]), + {reply, delete(Db, Name, State), State}; + + +%%----------------------------------------------------------------- +%% Implements the table functions. +%%----------------------------------------------------------------- +%% Entry in ets for a tablerow: +%% Key = {<tableName>, <(flat) list of indexes>} +%% Val = {{Row}, <Prev>, <Next>} +%% Where Prev and Next = <list of indexes>; "pointer to prev/next" +%% Each table is a double linked list, with a head-element, with +%% direct access to each individual element. +%% Head-el: Key = {<tableName>, first} +%% Operations: +%% table_create_row(<tableName>, <list of indexes>, <row>) O(n) +%% table_delete_row(<tableName>, <list of indexes>) O(1) +%% get(<tableName>, <list of indexes>, Col) O(1) +%% set(<tableName>, <list of indexes>, Col, Val) O(1) +%% next(<tableName>, <list of indexes>) if Row exist O(1), else O(n) +%%----------------------------------------------------------------- +handle_call({table_create, Name, Db}, _From, State) -> + ?vlog("table create: ~p [~p]",[Name, Db]), + catch handle_delete(Db, Name, State), + {reply, insert(Db, {Name, first}, {undef, first, first}, State), State}; + +handle_call({table_exists, Name, Db}, _From, State) -> + ?vlog("table exist: ~p [~p]",[Name, Db]), + Res = + case lookup(Db, {Name, first}, State) of + {value, _} -> true; + undefined -> false + end, + ?vdebug("table exist result: " + "~n ~p",[Res]), + {reply, Res, State}; + +handle_call({table_delete, Name, Db}, _From, State) -> + ?vlog("table delete: ~p [~p]",[Name, Db]), + catch handle_delete(Db, Name, State), + {reply, true, State}; + +handle_call({table_create_row, Name, Db, Indexes, Row}, _From, State) -> + ?vlog("table create row [~p]: " + "~n Name: ~p" + "~n Indexes: ~p" + "~n Row: ~p",[Db, Name, Indexes, Row]), + Res = + case catch handle_create_row(Db, Name, Indexes, Row, State) of + {'EXIT', _} -> false; + _ -> true + end, + ?vdebug("table create row result: " + "~n ~p",[Res]), + {reply, Res, State}; + +handle_call({table_delete_row, Name, Db, Indexes}, _From, State) -> + ?vlog("table delete row [~p]: " + "~n Name: ~p" + "~n Indexes: ~p", [Db, Name, Indexes]), + Res = + case catch handle_delete_row(Db, Name, Indexes, State) of + {'EXIT', _} -> false; + _ -> true + end, + ?vdebug("table delete row result: " + "~n ~p",[Res]), + {reply, Res, State}; + +handle_call({table_get_row, Name, Db, Indexes}, _From, State) -> + ?vlog("table get row [~p]: " + "~n Name: ~p" + "~n Indexes: ~p",[Db, Name, Indexes]), + Res = case lookup(Db, {Name, Indexes}, State) of + undefined -> + undefined; + {value, {Row, _Prev, _Next}} -> + Row + end, + ?vdebug("table get row result: " + "~n ~p",[Res]), + {reply, Res, State}; + +handle_call({table_get_element, Name, Db, Indexes, Col}, _From, State) -> + ?vlog("table ~p get element [~p]: " + "~n Indexes: ~p" + "~n Col: ~p", [Name, Db, Indexes, Col]), + Res = case lookup(Db, {Name, Indexes}, State) of + undefined -> undefined; + {value, {Row, _Prev, _Next}} -> {value, element(Col, Row)} + end, + ?vdebug("table get element result: " + "~n ~p",[Res]), + {reply, Res, State}; + +handle_call({table_set_elements, Name, Db, Indexes, Cols}, _From, State) -> + ?vlog("table ~p set element [~p]: " + "~n Indexes: ~p" + "~n Cols: ~p", [Name, Db, Indexes, Cols]), + Res = + case catch handle_set_elements(Db, Name, Indexes, Cols, State) of + {'EXIT', _} -> false; + _ -> true + end, + ?vdebug("table set element result: " + "~n ~p",[Res]), + {reply, Res, State}; + +handle_call({table_next, Name, Db, []}, From, State) -> + ?vlog("table next: ~p [~p]",[Name, Db]), + handle_call({table_next, Name, Db, first}, From, State); + +handle_call({table_next, Name, Db, Indexes}, _From, State) -> + ?vlog("table ~p next [~p]: " + "~n Indexes: ~p",[Name, Db, Indexes]), + Res = case lookup(Db, {Name, Indexes}, State) of + {value, {_Row, _Prev, Next}} -> + if + Next =:= first -> endOfTable; + true -> Next + end; + undefined -> + table_search_next(Db, Name, Indexes, State) + end, + ?vdebug("table next result: " + "~n ~p",[Res]), + {reply, Res, State}; + +handle_call({table_max_col, Name, Db, Col}, _From, State) -> + ?vlog("table ~p max col [~p]: " + "~n Col: ~p",[Name, Db, Col]), + Res = table_max_col(Db, Name, Col, 0, first, State), + ?vdebug("table max col result: " + "~n ~p",[Res]), + {reply, Res, State}; + +handle_call({match, Name, Db, Pattern}, _From, State) -> + ?vlog("match ~p [~p]:" + "~n Pat: ~p", [Name, Db, Pattern]), + L1 = match(Db, Name, Pattern, State), + {reply, lists:delete([undef], L1), State}; + +handle_call({backup, BackupDir}, From, #state{dets = Dets} = State) -> + ?vlog("backup: ~p",[BackupDir]), + Pid = self(), + V = get(verbosity), + case file:read_file_info(BackupDir) of + {ok, #file_info{type = directory}} -> + BackupServer = + erlang:spawn_link( + fun() -> + put(sname, albs), + put(verbosity, V), + Dir = filename:join([BackupDir]), + #dets{tab = Tab} = Dets, + Reply = handle_backup(Tab, Dir), + Pid ! {backup_done, Reply}, + unlink(Pid) + end), + ?vtrace("backup server: ~p", [BackupServer]), + {noreply, State#state{backup = {BackupServer, From}}}; + {ok, _} -> + {reply, {error, not_a_directory}, State}; + Error -> + {reply, Error, State} + end; + +handle_call(dump, _From, #state{dets = Dets} = State) -> + ?vlog("dump",[]), + dets_sync(Dets), + {reply, ok, State}; + +handle_call(info, _From, #state{dets = Dets, ets = Ets} = State) -> + ?vlog("info",[]), + Info = get_info(Dets, Ets), + {reply, Info, State}; + +handle_call(print, _From, #state{dets = Dets, ets = Ets} = State) -> + ?vlog("print",[]), + L1 = ets:tab2list(Ets), + L2 = dets_match_object(Dets, '_'), + {reply, {{ets, L1}, {dets, L2}}, State}; + +handle_call({print, Table, Db}, _From, State) -> + ?vlog("print: ~p [~p]", [Table, Db]), + L = match(Db, Table, '$1', State), + {reply, lists:delete([undef], L), State}; + +handle_call({register_notify_client, Client, Module}, _From, State) -> + ?vlog("register_notify_client: " + "~n Client: ~p" + "~n Module: ~p", [Client, Module]), + Nc = State#state.notify_clients, + case lists:keysearch(Client,1,Nc) of + {value,{Client,Mod}} -> + ?vlog("register_notify_client: already registered to: ~p", + [Module]), + {reply, {error,{already_registered,Mod}}, State}; + false -> + {reply, ok, State#state{notify_clients = [{Client,Module}|Nc]}} + end; + +handle_call({unregister_notify_client, Client}, _From, State) -> + ?vlog("unregister_notify_client: ~p",[Client]), + Nc = State#state.notify_clients, + case lists:keysearch(Client,1,Nc) of + {value,{Client,_Module}} -> + Nc1 = lists:keydelete(Client,1,Nc), + {reply, ok, State#state{notify_clients = Nc1}}; + false -> + ?vlog("unregister_notify_client: not registered",[]), + {reply, {error,not_registered}, State} + end; + +handle_call(stop, _From, State) -> + ?vlog("stop",[]), + {stop, normal, stopped, State}; + +handle_call(Req, _From, State) -> + warning_msg("received unknown request: ~n~p", [Req]), + Reply = {error, {unknown, Req}}, + {reply, Reply, State}. + + +handle_cast({variable_inc, Name, Db, N}, State) -> + ?vlog("variable ~p inc" + "~n N: ~p", [Name, N]), + M = case lookup(Db, Name, State) of + {value, Val} -> Val; + _ -> 0 + end, + insert(Db, Name, M+N rem 4294967296, State), + {noreply, State}; + +handle_cast({verbosity,Verbosity}, State) -> + ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]), + put(verbosity,?vvalidate(Verbosity)), + {noreply, State}; + +handle_cast(Msg, State) -> + warning_msg("received unknown message: ~n~p", [Msg]), + {noreply, State}. + + +handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) -> + ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]), + gen_server:reply(From, {error, Reason}), + {noreply, S#state{backup = undefined}}; + +handle_info({'EXIT', Pid, Reason}, S) -> + %% The only other processes we should be linked to are + %% either the master agent or our supervisor, so die... + {stop, {received_exit, Pid, Reason}, S}; + +handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) -> + ?vlog("backup done:" + "~n Reply: ~p", [Reply]), + gen_server:reply(From, Reply), + {noreply, S#state{backup = undefined}}; + +handle_info(Info, State) -> + warning_msg("received unknown info: ~n~p", [Info]), + {noreply, State}. + + +terminate(Reason, State) -> + ?vlog("terminate: ~p",[Reason]), + close(State). + + +%%---------------------------------------------------------- +%% Code change +%%---------------------------------------------------------- + +%% downgrade +%% +code_change({down, _Vsn}, S1, downgrade_to_pre_4_11) -> + #state{dets = D} = S1, + #dets{tab = Dets, shadow = Shadow} = D, + ets:delete(Shadow), + S2 = S1#state{dets = Dets}, + {ok, S2}; + +%% upgrade +%% +code_change(_Vsn, S1, upgrade_from_pre_4_11) -> + #state{dets = D} = S1, + Shadow = ets:new(snmp_local_db1_shadow, [set, protected]), + dets:to_ets(D, Shadow), + Dets = #dets{tab = D, shadow = Shadow}, + S2 = S1#state{dets = Dets}, + {ok, S2}; + +code_change(_Vsn, State, _Extra) -> + {ok, State}. + + + +%%---------------------------------------------------------- +%% Backup +%%---------------------------------------------------------- + +handle_backup(D, BackupDir) -> + %% First check that we do not wrote to the corrent db-dir... + ?vtrace("handle_backup -> entry with" + "~n D: ~p" + "~n BackupDir: ~p", [D, BackupDir]), + case dets:info(D, filename) of + undefined -> + ?vinfo("handle_backup -> no file to backup", []), + {error, no_file}; + Filename -> + ?vinfo("handle_backup -> file to backup: ~n ~p", [Filename]), + case filename:dirname(Filename) of + BackupDir -> + ?vinfo("handle_backup -> backup dir and db dir the same", + []), + {error, db_dir}; + _ -> + case file:read_file_info(BackupDir) of + {ok, #file_info{type = directory}} -> + ?vdebug("handle_backup -> backup dir ok", []), + %% All well so far... + Type = dets:info(D, type), + KP = dets:info(D, keypos), + dets_backup(D, + filename:basename(Filename), + BackupDir, Type, KP); + {ok, _} -> + ?vinfo("handle_backup -> backup dir not a dir", + []), + {error, not_a_directory}; + Error -> + ?vinfo("handle_backup -> Error: ~p", [Error]), + Error + end + end + end. + +dets_backup(D, Filename, BackupDir, Type, KP) -> + ?vtrace("dets_backup -> entry with" + "~n D: ~p" + "~n Filename: ~p" + "~n BackupDir: ~p", [D, Filename, BackupDir]), + BackupFile = filename:join(BackupDir, Filename), + ?vtrace("dets_backup -> " + "~n BackupFile: ~p", [BackupFile]), + Opts = [{file, BackupFile}, {type, Type}, {keypos, KP}], + case dets:open_file(?BACKUP_TAB, Opts) of + {ok, B} -> + F = fun(Arg) -> + dets_backup(Arg, start, D, B) + end, + ?vtrace("dets_backup -> fix table", []), + dets:safe_fixtable(D, true), + ?vtrace("dets_backup -> copy table", []), + Res = dets:init_table(?BACKUP_TAB, F, [{format, bchunk}]), + ?vtrace("dets_backup -> unfix table", []), + dets:safe_fixtable(D, false), + ?vtrace("dets_backup -> Res: ~p", [Res]), + Res; + Error -> + ?vinfo("dets_backup -> open_file failed: " + "~n ~p", [Error]), + Error + end. + + +dets_backup(close, _Cont, _D, B) -> + dets:close(B), + ok; +dets_backup(read, Cont1, D, B) -> + case dets:bchunk(D, Cont1) of + {Cont2, Data} -> + F = fun(Arg) -> + dets_backup(Arg, Cont2, D, B) + end, + {Data, F}; + '$end_of_table' -> + dets:close(B), + end_of_input; + Error -> + Error + end. + + +%%----------------------------------------------------------------- +%% All handle_ functions exists so we can catch the call to +%% them, because we don't want to crash the server if a user +%% forgets to call e.g. table_create. +%%----------------------------------------------------------------- +%% Find larger element and insert before. +handle_create_row(Db, Name, Indexes, Row, State) -> + case table_find_first_after_maybe_same(Db, Name, Indexes, State) of + {{Name, Next}, {NRow, NPrev, NNext}} -> + {value, {PRow, PPrev, _PNext}} = lookup(Db, {Name, NPrev}, State), + if + Next =:= NPrev -> + % Insert before first + insert(Db, {Name, NPrev}, {PRow, Indexes, Indexes}, State); + true -> + insert(Db, {Name, NPrev}, {PRow, PPrev, Indexes}, State), + insert(Db, {Name, Next}, {NRow, Indexes, NNext}, State) + end, + insert(Db, {Name, Indexes}, {Row, NPrev, Next}, State); + {same_row, {Prev, Next}} -> + insert(Db, {Name, Indexes}, {Row, Prev, Next}, State) + end. + +handle_delete_row(Db, Name, Indexes, State) -> + {value, {_, Prev, Next}} = lookup(Db, {Name, Indexes}, State), + {value, {PRow, PPrev, Indexes}} = lookup(Db, {Name, Prev}, State), + insert(Db, {Name, Prev}, {PRow, PPrev, Next}, State), + {value, {NRow, Indexes, NNext}} = lookup(Db, {Name, Next}, State), + insert(Db, {Name, Next}, {NRow, Prev, NNext}, State), + delete(Db, {Name, Indexes}, State). + +handle_set_elements(Db, Name, Indexes, Cols, State) -> + {value, {Row, Prev, Next}} = lookup(Db, {Name, Indexes}, State), + NewRow = set_new_row(Cols, Row), + insert(Db, {Name, Indexes}, {NewRow, Prev, Next}, State). + +set_new_row([{Col, Val} | Cols], Row) -> + set_new_row(Cols, setelement(Col, Row, Val)); +set_new_row([], Row) -> + Row. + +handle_delete(Db, Name, State) -> + {value, {_, _, Next}} = lookup(Db, {Name, first}, State), + delete(Db, {Name, first}, State), + handle_delete(Db, Name, Next, State). +handle_delete(_Db, _Name, first, _State) -> true; +handle_delete(Db, Name, Indexes, State) -> + {value, {_, _, Next}} = lookup(Db, {Name, Indexes}, State), + delete(Db, {Name, Indexes}, State), + handle_delete(Db, Name, Next, State). + +%%----------------------------------------------------------------- +%% Implementation of next. +%%----------------------------------------------------------------- +table_search_next(Db, Name, Indexes, State) -> + case catch table_find_first_after(Db, Name, Indexes, State) of + {{Name, Key}, {_, _, _Next}} -> + case Key of + first -> endOfTable; + _ -> Key + end; + {'EXIT', _} -> endOfTable + end. + +table_find_first_after(Db, Name, Indexes, State) -> + {value, {_Row, _Prev, Next}} = lookup(Db, {Name, first}, State), + table_loop(Db, Name, Indexes, Next, State). + +table_loop(Db, Name, _Indexes, first, State) -> + {value, FirstVal} = lookup(Db, {Name, first}, State), + {{Name, first}, FirstVal}; + +table_loop(Db, Name, Indexes, Cur, State) -> + {value, {Row, Prev, Next}} = lookup(Db, {Name, Cur}, State), + if + Cur > Indexes -> + {{Name, Cur}, {Row, Prev, Next}}; + true -> + table_loop(Db, Name, Indexes, Next, State) + end. + +table_find_first_after_maybe_same(Db, Name, Indexes, State) -> + {value, {_Row, _Prev, Next}} = lookup(Db, {Name, first}, State), + table_loop2(Db, Name, Indexes, Next, State). + +table_loop2(Db, Name, _Indexes, first, State) -> + {value, FirstVal} = lookup(Db, {Name, first}, State), + {{Name, first}, FirstVal}; + +table_loop2(Db, Name, Indexes, Cur, State) -> + {value, {Row, Prev, Next}} = lookup(Db, {Name, Cur}, State), + if + Cur > Indexes -> + {{Name, Cur}, {Row, Prev, Next}}; + Cur =:= Indexes -> + {same_row, {Prev, Next}}; + true -> + table_loop2(Db, Name, Indexes, Next, State) + end. + +%%----------------------------------------------------------------- +%% Implementation of max. +%% The value in a column could be noinit or undefined, +%% so we must check only those with an integer. +%%----------------------------------------------------------------- +table_max_col(Db, Name, Col, Max, Indexes, State) -> + case lookup(Db, {Name, Indexes}, State) of + {value, {Row, _Prev, Next}} -> + if + Next =:= first -> + if + is_integer(element(Col, Row)) andalso + (element(Col, Row) > Max) -> + element(Col, Row); + true -> + Max + end; + is_integer(element(Col, Row)) andalso + (element(Col, Row) > Max) -> + table_max_col(Db,Name, Col,element(Col, Row),Next, State); + true -> + table_max_col(Db, Name, Col, Max, Next, State) + end; + undefined -> table_search_next(Db, Name, Indexes, State) + end. + +%%----------------------------------------------------------------- +%% Interface to Pets. +%%----------------------------------------------------------------- +insert(volatile, Key, Val, #state{ets = Ets}) -> + ?vtrace("insert(volatile) -> entry with" + "~n Key: ~p" + "~n Val: ~p", + [Key,Val]), + ets:insert(Ets, {Key, Val}), + true; +insert(persistent, Key, Val, #state{dets = Dets, notify_clients = NC}) -> + ?vtrace("insert(persistent) -> entry with" + "~n Key: ~p" + "~n Val: ~p", + [Key,Val]), + case dets_insert(Dets, {Key, Val}) of + ok -> + notify_clients(insert,NC), + true; + {error, Reason} -> + error_msg("DETS (persistent) insert failed for {~w,~w}: ~n~w", + [Key, Val, Reason]), + false + end; +insert(permanent, Key, Val, #state{dets = Dets, notify_clients = NC}) -> + ?vtrace("insert(permanent) -> entry with" + "~n Key: ~p" + "~n Val: ~p", + [Key,Val]), + case dets_insert(Dets, {Key, Val}) of + ok -> + notify_clients(insert,NC), + true; + {error, Reason} -> + error_msg("DETS (permanent) insert failed for {~w,~w}: ~n~w", + [Key, Val, Reason]), + false + end; +insert(UnknownDb, Key, Val, _) -> + error_msg("Tried to insert ~w = ~w into unknown db ~w", + [Key, Val, UnknownDb]), + false. + +delete(volatile, Key, State) -> + ets:delete(State#state.ets, Key), + true; +delete(persistent, Key, #state{dets = Dets, notify_clients = NC}) -> + case dets_delete(Dets, Key) of + ok -> + notify_clients(delete,NC), + true; + {error, Reason} -> + error_msg("DETS (persistent) delete failed for ~w: ~n~w", + [Key, Reason]), + false + end; +delete(permanent, Key, #state{dets = Dets, notify_clients = NC}) -> + case dets_delete(Dets, Key) of + ok -> + notify_clients(delete,NC), + true; + {error, Reason} -> + error_msg("DETS (permanent) delete failed for ~w: ~n~w", + [Key, Reason]), + false + end; +delete(UnknownDb, Key, _) -> + error_msg("Tried to delete ~w from unknown db ~w", + [Key, UnknownDb]), + false. + + +match(volatile, Name, Pattern, #state{ets = Ets}) -> + ets:match(Ets, {{Name,'_'},{Pattern,'_','_'}}); +match(persistent, Name, Pattern, #state{dets = Dets}) -> + dets_match(Dets, {{Name,'_'},{Pattern,'_','_'}}); +match(permanent, Name, Pattern, #state{dets = Dets}) -> + dets_match(Dets, {{Name,'_'},{Pattern,'_','_'}}); +match(UnknownDb, Name, Pattern, _) -> + error_msg("Tried to match [~p,~p] from unknown db ~w", + [Name, Pattern, UnknownDb]), + []. + +lookup(volatile, Key, #state{ets = Ets}) -> + case ets:lookup(Ets, Key) of + [{_, Val}] -> + {value, Val}; + [] -> + undefined + end; +lookup(persistent, Key, #state{dets = Dets}) -> + case dets_lookup(Dets, Key) of + [{_, Val}] -> + {value, Val}; + [] -> + undefined + end; +lookup(permanent, Key, #state{dets = Dets}) -> + case dets_lookup(Dets, Key) of + [{_, Val}] -> + {value, Val}; + [] -> + undefined + end; +lookup(UnknownDb, Key, _) -> + error_msg("Tried to lookup ~w in unknown db ~w", [Key, UnknownDb]), + false. + +close(#state{dets = Dets, ets = Ets}) -> + ets:delete(Ets), + dets_close(Dets). + + +%%----------------------------------------------------------------- +%% Notify clients interface +%%----------------------------------------------------------------- +notify_clients(Event, Clients) -> + F = fun(Client) -> notify_client(Client, Event) end, + lists:foreach(F, Clients). + +notify_client({Client,Module}, Event) -> + (catch Module:notify(Client,Event)). + + +%%------------------------------------------------------------------ +%% Constructs a row with first elements the own part of RowIndex, +%% and last element RowStatus. All values are stored "as is", i.e. +%% dynamic key values are stored without length first. +%% RowIndex is a list of the +%% first elements. RowStatus is needed, because the +%% provided value may not be stored, e.g. createAndGo +%% should be active. +%% Returns a tuple of values for the row. If a value +%% isn't specified in the Col list, then the +%% corresponding value will be noinit. +%%------------------------------------------------------------------ +table_construct_row(Name, RowIndex, Status, Cols) -> + #table_info{nbr_of_cols = LastCol, index_types = Indexes, + defvals = Defs, status_col = StatusCol, + first_own_index = FirstOwnIndex, not_accessible = NoAccs} = + snmp_generic:table_info(Name), + Keys = snmp_generic:split_index_to_keys(Indexes, RowIndex), + OwnKeys = snmp_generic:get_own_indexes(FirstOwnIndex, Keys), + Row = OwnKeys ++ snmp_generic:table_create_rest(length(OwnKeys) + 1, + LastCol, StatusCol, + Status, Cols, NoAccs), + L = snmp_generic:init_defaults(Defs, Row), + list_to_tuple(L). + + +table_get_elements(NameDb, RowIndex, Cols, _FirstOwnIndex) -> + get_elements(Cols, table_get_row(NameDb, RowIndex)). + +get_elements(_Cols, undefined) -> + undefined; +get_elements([Col | Cols], Row) when is_tuple(Row) and (size(Row) >= Col) -> + [element(Col, Row) | get_elements(Cols, Row)]; +get_elements([], _Row) -> + []; +get_elements(Cols, Row) -> + erlang:error({bad_arguments, Cols, Row}). + + +%%---------------------------------------------------------------------- +%% This should/could be a generic function, but since Mnesia implements +%% its own and this version still is local_db dependent, it's not generic yet. +%%---------------------------------------------------------------------- +%% createAndGo +table_set_status(NameDb, RowIndex, ?'RowStatus_createAndGo', StatusCol, Cols, + ChangedStatusFunc, _ConsFunc) -> + case table_create_row(NameDb, RowIndex, ?'RowStatus_active', Cols) of + true -> snmp_generic:try_apply(ChangedStatusFunc, + [NameDb, ?'RowStatus_createAndGo', + RowIndex, Cols]); + _ -> {commitFailed, StatusCol} + end; + +%%------------------------------------------------------------------ +%% createAndWait - set status to notReady, and try to +%% make row consistent. +%%------------------------------------------------------------------ +table_set_status(NameDb, RowIndex, ?'RowStatus_createAndWait', StatusCol, Cols, + ChangedStatusFunc, ConsFunc) -> + case table_create_row(NameDb, RowIndex, ?'RowStatus_notReady', Cols) of + true -> + case snmp_generic:try_apply(ConsFunc, [NameDb, RowIndex, Cols]) of + {noError, 0} -> + snmp_generic:try_apply(ChangedStatusFunc, + [NameDb, ?'RowStatus_createAndWait', + RowIndex, Cols]); + Error -> Error + end; + _ -> {commitFailed, StatusCol} + end; + +%% destroy +table_set_status(NameDb, RowIndex, ?'RowStatus_destroy', _StatusCol, Cols, + ChangedStatusFunc, _ConsFunc) -> + case snmp_generic:try_apply(ChangedStatusFunc, + [NameDb, ?'RowStatus_destroy', + RowIndex, Cols]) of + {noError, 0} -> + table_delete_row(NameDb, RowIndex), + {noError, 0}; + Error -> Error + end; + +%% Otherwise, active or notInService +table_set_status(NameDb, RowIndex, Val, _StatusCol, Cols, + ChangedStatusFunc, ConsFunc) -> + snmp_generic:table_set_cols(NameDb, RowIndex, Cols, ConsFunc), + snmp_generic:try_apply(ChangedStatusFunc, [NameDb, Val, RowIndex, Cols]). + +table_func(new, NameDb) -> + case table_exists(NameDb) of + true -> ok; + _ -> table_create(NameDb) + end; + +table_func(delete, _NameDb) -> + ok. + +table_func(get, RowIndex, Cols, NameDb) -> + TableInfo = snmp_generic:table_info(NameDb), + snmp_generic:handle_table_get(NameDb, RowIndex, Cols, + TableInfo#table_info.first_own_index); + +%%------------------------------------------------------------------ +%% Returns: List of endOfTable | {NextOid, Value}. +%% Implements the next operation, with the function +%% handle_table_next. Next should return the next accessible +%% instance, which cannot be a key. +%%------------------------------------------------------------------ +table_func(get_next, RowIndex, Cols, NameDb) -> + #table_info{first_accessible = FirstCol, first_own_index = FOI, + nbr_of_cols = LastCol} = snmp_generic:table_info(NameDb), + snmp_generic:handle_table_next(NameDb, RowIndex, Cols, + FirstCol, FOI, LastCol); + +%%----------------------------------------------------------------- +%% This function must only be used by tables with a RowStatus col! +%% Other tables must check if row exist themselves. +%%----------------------------------------------------------------- +table_func(is_set_ok, RowIndex, Cols, NameDb) -> + snmp_generic:table_try_row(NameDb, nofunc, RowIndex, Cols); + +%%------------------------------------------------------------------ +%% Cols is here a list of {ColumnNumber, NewValue} +%% This function must only be used by tables with a RowStatus col! +%% Other tables should use table_set_cols/3,4. +%%------------------------------------------------------------------ +table_func(set, RowIndex, Cols, NameDb) -> + snmp_generic:table_set_row(NameDb, + nofunc, + {snmp_generic, table_try_make_consistent}, + RowIndex, + Cols); + +table_func(undo, _RowIndex, _Cols, _NameDb) -> + {noError, 0}. + + + +%%------------------------------------------------------------------ +%% This functions retrieves option values from the Options list. +%%------------------------------------------------------------------ +get_opt(Key, Opts, Def) -> + snmp_misc:get_option(Key, Opts, Def). + + +%%------------------------------------------------------------------ + +get_info(Dets, Ets) -> + ProcSize = proc_mem(self()), + DetsSz = dets_size(Dets), + EtsSz = ets_size(Ets), + [{process_memory, ProcSize}, + {db_memory, [{persistent, DetsSz}, {volatile, EtsSz}]}]. + +proc_mem(P) when is_pid(P) -> + case (catch erlang:process_info(P, memory)) of + {memory, Sz} -> + Sz; + _ -> + undefined + end. +%% proc_mem(_) -> +%% undefined. + +dets_size(#dets{tab = Tab, shadow = Shadow}) -> + TabSz = + case (catch dets:info(Tab, file_size)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end, + ShadowSz = ets_size(Shadow), + [{tab, TabSz}, {shadow, ShadowSz}]. + +ets_size(T) -> + case (catch ets:info(T, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + +%%------------------------------------------------------------------ + +%% info_msg(F, A) -> +%% ?snmpa_info("Local DB server: " ++ F, A). + +warning_msg(F, A) -> + ?snmpa_warning("Local DB server: " ++ F, A). + +error_msg(F, A) -> + ?snmpa_error("Local DB server: " ++ F, A). + +%% --- + +user_err(F, A) -> + snmpa_error:user_err(F, A). + +config_err(F, A) -> + snmpa_error:config_err(F, A). + + +%% ---------------------------------------------------------------- + +call(Req) -> + gen_server:call(?SERVER, Req, infinity). + +cast(Msg) -> + gen_server:cast(?SERVER, Msg). + + +%% ---------------------------------------------------------------- +%% DETS wrapper functions +%% The purpose of these fuctions is basically to hide the shadow +%% table. +%% Changes are made both in dets and in the shadow table. +%% Reads are made from the shadow table. +%% ---------------------------------------------------------------- + +dets_sync(#dets{tab = Dets}) -> + dets:sync(Dets). + +dets_insert(#dets{tab = Tab, shadow = Shadow}, Data) -> + ets:insert(Shadow, Data), + dets:insert(Tab, Data). + +dets_delete(#dets{tab = Tab, shadow = Shadow}, Key) -> + ets:delete(Shadow, Key), + dets:delete(Tab, Key). + +dets_match(#dets{shadow = Shadow}, Pat) -> + ets:match(Shadow, Pat). + +dets_match_object(#dets{shadow = Shadow}, Pat) -> + ets:match_object(Shadow, Pat). + +dets_lookup(#dets{shadow = Shadow}, Key) -> + ets:lookup(Shadow, Key). + +dets_close(#dets{tab = Tab, shadow = Shadow}) -> + ets:delete(Shadow), + dets:close(Tab). diff --git a/lib/snmp/src/agent/snmpa_mib.erl b/lib/snmp/src/agent/snmpa_mib.erl new file mode 100644 index 0000000000..370989d0be --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mib.erl @@ -0,0 +1,892 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_mib). + +%% c(snmpa_mib). + +%%%----------------------------------------------------------------- +%%% This module implements a MIB server. +%%%----------------------------------------------------------------- + +%% External exports +-export([start_link/3, stop/1, + lookup/2, next/3, which_mib/2, which_mibs/1, whereis_mib/2, + load_mibs/2, unload_mibs/2, + register_subagent/3, unregister_subagent/2, info/1, info/2, + verbosity/2, dump/1, dump/2, + backup/2, + invalidate_cache/1, + gc_cache/1, gc_cache/2, gc_cache/3, + enable_cache/1, disable_cache/1, + enable_cache_autogc/1, disable_cache_autogc/1, + update_cache_gclimit/2, + update_cache_age/2, + which_cache_size/1 + ]). + +%% Internal exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-include_lib("kernel/include/file.hrl"). +-include("snmpa_internal.hrl"). +-include("snmp_types.hrl"). +-include("snmp_verbosity.hrl"). +-include("snmp_debug.hrl"). + + +-define(SERVER, ?MODULE). +-define(NO_CACHE, no_mibs_cache). +-define(DEFAULT_CACHE_USAGE, true). +-define(CACHE_GC_TICKTIME, timer:minutes(1)). +-define(DEFAULT_CACHE_AUTOGC, false). +-define(DEFAULT_CACHE_GCLIMIT, 100). +-define(DEFAULT_CACHE_AGE, timer:minutes(10)). +-define(CACHE_GC_TRIGGER, cache_gc_trigger). + + + +-ifdef(snmp_debug). +-define(GS_START_LINK(Prio, Mibs, Opts), + gen_server:start_link(?MODULE, [Prio, Mibs, Opts], [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Prio, Mibs, Opts), + gen_server:start_link(?MODULE, [Prio, Mibs, Opts], [])). +-endif. + + +%%----------------------------------------------------------------- +%% Internal Data structures +%% +%% State +%% data - is the MIB data (defined in snmpa_mib_data) +%% meo - mib entry override +%% teo - trap (notification) entry override +%%----------------------------------------------------------------- +-record(state, {data, meo, teo, backup, + cache, cache_tmr, cache_autogc, cache_gclimit, cache_age}). + + + +%%----------------------------------------------------------------- +%% Func: start_link/1 +%% Args: Mibs is a list of mibnames. +%% Prio is priority of mib-server +%% Opts is a list of options +%% Purpose: starts the mib server synchronized +%% Returns: {ok, Pid} | {error, Reason} +%%----------------------------------------------------------------- +start_link(Prio, Mibs, Opts) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Mibs: ~p" + "~n Opts: ~p", [Prio, Mibs, Opts]), + ?GS_START_LINK(Prio, Mibs, Opts). + +verbosity(MibServer, Verbosity) -> + cast(MibServer, {verbosity,Verbosity}). + +stop(MibServer) -> + call(MibServer, stop). + +invalidate_cache(MibServer) -> + call(MibServer, invalidate_cache). + +gc_cache(MibServer) -> + call(MibServer, gc_cache). + +gc_cache(MibServer, Age) -> + call(MibServer, {gc_cache, Age}). + +gc_cache(MibServer, Age, GcLimit) -> + call(MibServer, {gc_cache, Age, GcLimit}). + +which_cache_size(MibServer) -> + call(MibServer, cache_size). + +enable_cache(MibServer) -> + update_cache_opts(MibServer, cache, true). +disable_cache(MibServer) -> + update_cache_opts(MibServer, cache, false). + +enable_cache_autogc(MibServer) -> + update_cache_opts(MibServer, autogc, true). +disable_cache_autogc(MibServer) -> + update_cache_opts(MibServer, autogc, false). + +update_cache_gclimit(MibServer, GcLimit) + when ((is_integer(GcLimit) andalso (GcLimit > 0)) orelse + (GcLimit =:= infinity)) -> + update_cache_opts(MibServer, gclimit, GcLimit); +update_cache_gclimit(_, BadLimit) -> + {error, {bad_gclimit, BadLimit}}. + +update_cache_age(MibServer, Age) + when is_integer(Age) andalso (Age > 0) -> + update_cache_opts(MibServer, age, Age); +update_cache_age(_, BadAge) -> + {error, {bad_age, BadAge}}. + +update_cache_opts(MibServer, Key, Value) -> + call(MibServer, {update_cache_opts, Key, Value}). + + +%%----------------------------------------------------------------- +%% Func: lookup/2 +%% Purpose: Finds the mib entry corresponding to the Oid. If it is a +%% variable, the Oid must be <Oid for var>.0 and if it is +%% a table, Oid must be <table>.<entry>.<col>.<any> +%% Returns: {variable, MibEntry} | +%% {table_column, MibEntry, TableEntryOid} | +%% {subagent, SubAgentPid} | +%% false +%%----------------------------------------------------------------- +lookup(MibServer, Oid) -> + call(MibServer, {lookup, Oid}). + +which_mib(MibServer, Oid) -> + call(MibServer, {which_mib, Oid}). + + +%%----------------------------------------------------------------- +%% Func: next/3 +%% Purpose: Finds the lexicographically next oid. +%% Returns: {subagent, SubAgentPid, SANextOid} | +%% endOfMibView | +%% genErr | +%% NextOid +%% The SANextOid is used by the agent if the SubAgent returns +%% endOfMib, in a new call to next/2. +%%----------------------------------------------------------------- +next(MibServer, Oid, MibView) -> + call(MibServer, {next, Oid, MibView}). + + +%%---------------------------------------------------------------------- +%% Purpose: Loads mibs into the mib process. +%% Args: Mibs is a list of Filenames (compiled mibs). +%% Returns: ok | {error, Reason} +%%---------------------------------------------------------------------- +load_mibs(MibServer, Mibs) -> + call(MibServer, {load_mibs, Mibs}). + + +%%---------------------------------------------------------------------- +%% Purpose: Loads mibs into the mib process. +%% Args: Mibs is a list of Filenames (compiled mibs). +%% Returns: ok | {error, Reason} +%%---------------------------------------------------------------------- +unload_mibs(MibServer, Mibs) -> + call(MibServer, {unload_mibs, Mibs}). + + +%%---------------------------------------------------------------------- +%% Purpose: Simple management functions +%% Args: Mib is the name of the mib (atom) +%% Returns: ok | {error, Reason} +%%---------------------------------------------------------------------- +which_mibs(MibServer) -> + call(MibServer, which_mibs). + +whereis_mib(MibServer, Mib) -> + call(MibServer, {whereis_mib, Mib}). + + +%%---------------------------------------------------------------------- +%% Registers subagent with pid Pid under subtree Oid. +%%---------------------------------------------------------------------- +register_subagent(MibServer, Oid, Pid) -> + call(MibServer, {register_subagent, Oid, Pid}). + +unregister_subagent(MibServer, OidOrPid) -> + call(MibServer, {unregister_subagent, OidOrPid}). + +info(MibServer) -> + call(MibServer, info). + +info(MibServer, Type) -> + call(MibServer, {info, Type}). + +dump(MibServer) -> + call(MibServer, dump). + +dump(MibServer, File) when is_list(File) -> + call(MibServer, {dump, File}). + +backup(MibServer, BackupDir) when is_list(BackupDir) -> + call(MibServer, {backup, BackupDir}). + + +%%-------------------------------------------------- +%% The standard MIB 'stdmib' must be present in the +%% current directory. +%%-------------------------------------------------- +init([Prio, Mibs, Opts]) -> + ?d("init -> entry with" + "~n Prio: ~p" + "~n Mibs: ~p" + "~n Opts: ~p", [Prio, Mibs, Opts]), + case (catch do_init(Prio, Mibs, Opts)) of + {ok, State} -> + {ok, State}; + {error, Reason} -> + config_err("failed starting mib-server: ~n~p", [Reason]), + {stop, {error, Reason}}; + Error -> + config_err("failed starting mib-server: ~n~p", [Error]), + {stop, {error, Error}} + end. + +do_init(Prio, Mibs, Opts) -> + process_flag(priority, Prio), + process_flag(trap_exit, true), + put(sname,ms), + put(verbosity, ?vvalidate(get_verbosity(Opts))), + ?vlog("starting",[]), + + %% Extract the cache options + {Cache, CacheOptions} = + case get_opt(cache, Opts, ?DEFAULT_CACHE_USAGE) of + true -> + {new_cache(), []}; + false -> + {?NO_CACHE, []}; + CacheOpts when is_list(CacheOpts) -> + {new_cache(), CacheOpts}; + Bad -> + throw({error, {bad_option, {cache, Bad}}}) + end, + CacheAutoGC = get_cacheopt_autogc(Cache, CacheOptions), + CacheGcLimit = get_cacheopt_gclimit(Cache, CacheOptions), + CacheAge = get_cacheopt_age(Cache, CacheOptions), + + %% Maybe start the cache gc timer + CacheGcTimer = + if + ((Cache =/= ?NO_CACHE) andalso + (CacheAutoGC =:= true)) -> + start_cache_gc_timer(); + true -> + undefined + end, + + MeOverride = get_me_override(Opts), + TeOverride = get_te_override(Opts), + MibStorage = get_mib_storage(Opts), + Data = snmpa_mib_data:new(MibStorage), + ?vtrace("init -> mib data created",[]), + case (catch mib_operations(load_mib, Mibs, Data, + MeOverride, TeOverride, true)) of + {ok, Data2} -> + ?vdebug("started",[]), + snmpa_mib_data:sync(Data2), + ?vdebug("mib data synced",[]), + {ok, #state{data = Data2, + teo = TeOverride, + meo = MeOverride, + cache = Cache, + cache_tmr = CacheGcTimer, + cache_autogc = CacheAutoGC, + cache_gclimit = CacheGcLimit, + cache_age = CacheAge}}; + {'aborted at', Mib, _NewData, Reason} -> + ?vinfo("failed loading mib ~p: ~p",[Mib,Reason]), + {error, {Mib, Reason}} + end. + + +%%---------------------------------------------------------------------- +%% Returns: {ok, NewMibData} | {'aborted at', Mib, NewData, Reason} +%% Args: Operation is load_mib | unload_mib. +%%---------------------------------------------------------------------- +mib_operations(Operation, Mibs, Data, MeOverride, TeOverride) -> + mib_operations(Operation, Mibs, Data, MeOverride, TeOverride, false). + + +mib_operations(_Operation, [], Data, _MeOverride, _TeOverride, _Force) -> + {ok, Data}; +mib_operations(Operation, [Mib|Mibs], Data0, MeOverride, TeOverride, Force) -> + ?vtrace("mib operations ~p on" + "~n Mibs: ~p" + "~n with " + "~n MeOverride: ~p" + "~n TeOverride: ~p" + "~n Force: ~p", [Operation,Mibs,MeOverride,TeOverride,Force]), + Data = mib_operation(Operation, Mib, Data0, MeOverride, TeOverride, Force), + mib_operations(Operation, Mibs, Data, MeOverride, TeOverride, Force). + +mib_operation(Operation, Mib, Data0, MeOverride, TeOverride, Force) + when is_list(Mib) -> + ?vtrace("mib operation on mib ~p", [Mib]), + case apply(snmpa_mib_data, Operation, [Data0,Mib,MeOverride,TeOverride]) of + {error, 'already loaded'} when (Operation =:= load_mib) andalso + (Force =:= true) -> + ?vlog("ignore mib ~p -> already loaded", [Mib]), + Data0; + {error, 'not loaded'} when (Operation =:= unload_mib) andalso + (Force =:= true) -> + ?vlog("ignore mib ~p -> not loaded", [Mib]), + Data0; + {error, Reason} -> + ?vlog("mib_operation -> failed ~p of mib ~p for ~p", + [Operation, Mib, Reason]), + throw({'aborted at', Mib, Data0, Reason}); + {ok, Data} -> + Data + end; +mib_operation(_Op, Mib, Data, _MeOverride, _TeOverride, _Force) -> + throw({'aborted at', Mib, Data, bad_mibname}). + + +%%----------------------------------------------------------------- +%% Handle messages +%%----------------------------------------------------------------- + +handle_call(invalidate_cache, _From, #state{cache = Cache} = State) -> + ?vlog("invalidate_cache", []), + NewCache = maybe_invalidate_cache(Cache), + {reply, ignore, State#state{cache = NewCache}}; + +handle_call(cache_size, _From, #state{cache = Cache} = State) -> + ?vlog("cache_size", []), + Reply = maybe_cache_size(Cache), + {reply, Reply, State}; + +handle_call(gc_cache, _From, + #state{cache = Cache, + cache_age = Age, + cache_gclimit = GcLimit} = State) -> + ?vlog("gc_cache", []), + Result = maybe_gc_cache(Cache, Age, GcLimit), + {reply, Result, State}; + +handle_call({gc_cache, Age}, _From, + #state{cache = Cache, + cache_gclimit = GcLimit} = State) -> + ?vlog("gc_cache with Age = ~p", [Age]), + Result = maybe_gc_cache(Cache, Age, GcLimit), + {reply, Result, State}; + +handle_call({gc_cache, Age, GcLimit}, _From, + #state{cache = Cache} = State) -> + ?vlog("gc_cache with Age = ~p and GcLimut = ~p", [Age, GcLimit]), + Result = maybe_gc_cache(Cache, Age, GcLimit), + {reply, Result, State}; + +handle_call({update_cache_opts, Key, Value}, _From, State) -> + ?vlog("update_cache_opts: ~p -> ~p", [Key, Value]), + {Result, NewState} = handle_update_cache_opts(Key, Value, State), + {reply, Result, NewState}; + +handle_call({lookup, Oid}, _From, + #state{data = Data, cache = Cache} = State) -> + ?vlog("lookup ~p", [Oid]), + Key = {lookup, Oid}, + {Reply, NewState} = + case maybe_cache_lookup(Cache, Key) of + ?NO_CACHE -> + {snmpa_mib_data:lookup(Data, Oid), State}; + [] -> + Rep = snmpa_mib_data:lookup(Data, Oid), + ets:insert(Cache, {Key, Rep, timestamp()}), + {Rep, maybe_start_cache_gc_timer(State)}; + [{Key, Rep, _}] -> + ?vdebug("lookup -> found in cache", []), + ets:update_element(Cache, Key, {3, timestamp()}), + {Rep, State} + end, + ?vdebug("lookup -> Reply: ~p", [Reply]), + {reply, Reply, NewState}; + +handle_call({which_mib, Oid}, _From, #state{data = Data} = State) -> + ?vlog("which_mib ~p",[Oid]), + Reply = snmpa_mib_data:which_mib(Data, Oid), + ?vdebug("which_mib: ~p",[Reply]), + {reply, Reply, State}; + +handle_call({next, Oid, MibView}, _From, + #state{data = Data, cache = Cache} = State) -> + ?vlog("next ~p [~p]", [Oid, MibView]), + Key = {next, Oid, MibView}, + {Reply, NewState} = + case maybe_cache_lookup(Cache, Key) of + ?NO_CACHE -> + {snmpa_mib_data:next(Data, Oid, MibView), State}; + [] -> + Rep = snmpa_mib_data:next(Data, Oid, MibView), + ets:insert(Cache, {Key, Rep, timestamp()}), + {Rep, maybe_start_cache_gc_timer(State)}; + [{Key, Rep, _}] -> + ?vdebug("lookup -> found in cache", []), + ets:update_element(Cache, Key, {3, timestamp()}), + {Rep, State} + end, + ?vdebug("next -> Reply: ~p", [Reply]), + {reply, Reply, NewState}; + +handle_call({load_mibs, Mibs}, _From, + #state{data = Data, + teo = TeOverride, + meo = MeOverride, + cache = Cache} = State) -> + ?vlog("load mibs ~p",[Mibs]), + %% Invalidate cache + NewCache = maybe_invalidate_cache(Cache), + {NData,Reply} = + case (catch mib_operations(load_mib, Mibs, Data, + MeOverride, TeOverride)) of + {'aborted at', Mib, NewData, Reason} -> + ?vlog("aborted at ~p for reason ~p",[Mib,Reason]), + {NewData,{error, {'load aborted at', Mib, Reason}}}; + {ok, NewData} -> + {NewData,ok} + end, + snmpa_mib_data:sync(NData), + {reply, Reply, State#state{data = NData, cache = NewCache}}; + +handle_call({unload_mibs, Mibs}, _From, + #state{data = Data, + teo = TeOverride, + meo = MeOverride, + cache = Cache} = State) -> + ?vlog("unload mibs ~p",[Mibs]), + %% Invalidate cache + NewCache = maybe_invalidate_cache(Cache), + %% Unload mib(s) + {NData,Reply} = + case (catch mib_operations(unload_mib, Mibs, Data, + MeOverride, TeOverride)) of + {'aborted at', Mib, NewData, Reason} -> + ?vlog("aborted at ~p for reason ~p",[Mib,Reason]), + {NewData, {error, {'unload aborted at', Mib, Reason}}}; + {ok, NewData} -> + {NewData,ok} + end, + snmpa_mib_data:sync(NData), + {reply, Reply, State#state{data = NData, cache = NewCache}}; + +handle_call(which_mibs, _From, #state{data = Data} = State) -> + ?vlog("which mibs",[]), + Reply = snmpa_mib_data:which_mibs(Data), + {reply, Reply, State}; + +handle_call({whereis_mib, Mib}, _From, #state{data = Data} = State) -> + ?vlog("whereis mib: ~p",[Mib]), + Reply = snmpa_mib_data:whereis_mib(Data, Mib), + {reply, Reply, State}; + +handle_call({register_subagent, Oid, Pid}, _From, + #state{data = Data, cache = Cache} = State) -> + ?vlog("register subagent ~p, ~p",[Oid,Pid]), + %% Invalidate cache + NewCache = maybe_invalidate_cache(Cache), + case snmpa_mib_data:register_subagent(Data, Oid, Pid) of + {error, Reason} -> + ?vlog("registration failed: ~p",[Reason]), + {reply, {error, Reason}, State#state{cache = NewCache}}; + NewData -> + {reply, ok, State#state{data = NewData, cache = NewCache}} + end; + +handle_call({unregister_subagent, OidOrPid}, _From, + #state{data = Data, cache = Cache} = State) -> + ?vlog("unregister subagent ~p",[OidOrPid]), + %% Invalidate cache + NewCache = maybe_invalidate_cache(Cache), + case snmpa_mib_data:unregister_subagent(Data, OidOrPid) of + {ok, NewData, DeletedSubagentPid} -> + {reply, {ok, DeletedSubagentPid}, State#state{data = NewData, + cache = NewCache}}; + {error, Reason} -> + ?vlog("unregistration failed: ~p",[Reason]), + {reply, {error, Reason}, State#state{cache = NewCache}}; + NewData -> + {reply, ok, State#state{data = NewData, cache = NewCache}} + end; + +handle_call(info, _From, #state{data = Data, cache = Cache} = State) -> + ?vlog("info",[]), + Reply = + case (catch snmpa_mib_data:info(Data)) of + Info when is_list(Info) -> + [{cache, size_cache(Cache)} | Info]; + E -> + [{error, E}] + end, + {reply, Reply, State}; + +handle_call({info, Type}, _From, #state{data = Data} = State) -> + ?vlog("info ~p",[Type]), + Reply = + case (catch snmpa_mib_data:info(Data, Type)) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end, + {reply, Reply, State}; + +handle_call(dump, _From, State) -> + ?vlog("dump",[]), + Reply = snmpa_mib_data:dump(State#state.data), + {reply, Reply, State}; + +handle_call({dump, File}, _From, #state{data = Data} = State) -> + ?vlog("dump on ~s",[File]), + Reply = snmpa_mib_data:dump(Data, File), + {reply, Reply, State}; + +handle_call({backup, BackupDir}, From, #state{data = Data} = State) -> + ?vlog("backup to ~s",[BackupDir]), + Pid = self(), + V = get(verbosity), + case file:read_file_info(BackupDir) of + {ok, #file_info{type = directory}} -> + BackupServer = + erlang:spawn_link( + fun() -> + put(sname, ambs), + put(verbosity, V), + Dir = filename:join([BackupDir]), + Reply = snmpa_mib_data:backup(Data, Dir), + Pid ! {backup_done, Reply}, + unlink(Pid) + end), + ?vtrace("backup server: ~p", [BackupServer]), + {noreply, State#state{backup = {BackupServer, From}}}; + {ok, _} -> + {reply, {error, not_a_directory}, State}; + Error -> + {reply, Error, State} + end; + +handle_call(stop, _From, State) -> + ?vlog("stop",[]), + {stop, normal, ok, State}; + +handle_call(Req, _From, State) -> + warning_msg("received unknown request: ~n~p", [Req]), + Reply = {error, {unknown, Req}}, + {reply, Reply, State}. + +handle_cast({verbosity, Verbosity}, State) -> + ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]), + put(verbosity,snmp_verbosity:validate(Verbosity)), + {noreply, State}; + +handle_cast(Msg, State) -> + warning_msg("received unknown message: ~n~p", [Msg]), + {noreply, State}. + + +handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) -> + ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]), + gen_server:reply(From, {error, Reason}), + {noreply, S#state{backup = undefined}}; + +handle_info({'EXIT', Pid, Reason}, S) -> + %% The only other processes we should be linked to are + %% either the master agent or our supervisor, so die... + {stop, {received_exit, Pid, Reason}, S}; + +handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) -> + ?vlog("backup done:" + "~n Reply: ~p", [Reply]), + gen_server:reply(From, Reply), + {noreply, S#state{backup = undefined}}; + +handle_info(?CACHE_GC_TRIGGER, #state{cache = Cache, + cache_age = Age, + cache_gclimit = GcLimit, + cache_autogc = true} = S) + when (Cache =/= ?NO_CACHE) -> + ?vlog("cache gc trigger event", []), + maybe_gc_cache(Cache, Age, GcLimit), + Tmr = start_cache_gc_timer(), + {noreply, S#state{cache_tmr = Tmr}}; + +handle_info(?CACHE_GC_TRIGGER, S) -> + ?vlog("out-of-date cache gc trigger event - ignore", []), + {noreply, S#state{cache_tmr = undefined}}; + +handle_info(Info, State) -> + warning_msg("received unknown info: ~n~p", [Info]), + {noreply, State}. + +terminate(_Reason, #state{data = Data}) -> + catch snmpa_mib_data:close(Data), + ok. + + + +%%---------------------------------------------------------- +%% Code change +%%---------------------------------------------------------- + +%% downgrade +%% +%% code_change({down, _Vsn}, S1, downgrade_to_pre_4_12) -> +%% #state{data = Data, meo = MEO, teo = TEO, backup = B, cache = Cache} = S1, +%% del_cache(Cache), +%% S2 = {state, Data, MEO, TEO, B}, +%% {ok, S2}; + +%% %% upgrade +%% %% +%% code_change(_Vsn, S1, upgrade_from_pre_4_12) -> +%% {state, Data, MEO, TEO, B} = S1, +%% Cache = new_cache(), +%% S2 = #state{data = Data, meo = MEO, teo = TEO, backup = B, cache = Cache}, +%% {ok, S2}; + +code_change(_Vsn, State, _Extra) -> + {ok, State}. + + +%%----------------------------------------------------------------- +%% Option access functions +%%----------------------------------------------------------------- + +get_verbosity(Options) -> + get_opt(verbosity, Options, ?default_verbosity). + +get_me_override(Options) -> + get_opt(mibentry_override, Options, false). + +get_te_override(Options) -> + get_opt(trapentry_override, Options, false). + +get_mib_storage(Options) -> + get_opt(mib_storage, Options, ets). + +get_cacheopt_autogc(Cache, CacheOpts) -> + IsValid = fun(AutoGC) when ((AutoGC =:= true) orelse + (AutoGC =:= false)) -> + true; + (_) -> + false + end, + get_cacheopt(Cache, autogc, CacheOpts, + false, ?DEFAULT_CACHE_AUTOGC, + IsValid). + +get_cacheopt_gclimit(Cache, CacheOpts) -> + IsValid = fun(Limit) when ((is_integer(Limit) andalso (Limit > 0)) orelse + (Limit =:= infinity)) -> + true; + (_) -> + false + end, + get_cacheopt(Cache, gclimit, CacheOpts, + infinity, ?DEFAULT_CACHE_GCLIMIT, + IsValid). + +get_cacheopt_age(Cache, CacheOpts) -> + IsValid = fun(Age) when is_integer(Age) andalso (Age > 0) -> + true; + (_) -> + false + end, + get_cacheopt(Cache, age, CacheOpts, + ?DEFAULT_CACHE_AGE, ?DEFAULT_CACHE_AGE, + IsValid). + +get_cacheopt(?NO_CACHE, _, _, NoCacheVal, _, _) -> + NoCacheVal; +get_cacheopt(_, Key, Opts, _, Default, IsValid) -> + Val = get_opt(Key, Opts, Default), + case IsValid(Val) of + true -> + Val; + false -> + throw({error, {bad_option, {Key, Val}}}) + end. + + +%% ---------------------------------------------------------------- + +handle_update_cache_opts(cache, true = _Value, + #state{cache = ?NO_CACHE} = State) -> + {ok, State#state{cache = new_cache()}}; +handle_update_cache_opts(cache, true = _Value, State) -> + {ok, State}; + +handle_update_cache_opts(cache, false = _Value, + #state{cache = ?NO_CACHE} = State) -> + {ok, State}; +handle_update_cache_opts(cache, false = _Value, + #state{cache = Cache, + cache_tmr = Tmr} = State) -> + maybe_stop_cache_gc_timer(Tmr), + del_cache(Cache), + {ok, State#state{cache = ?NO_CACHE, cache_tmr = undefined}}; + +handle_update_cache_opts(autogc, true = _Value, + #state{cache_autogc = true} = State) -> + {ok, State}; +handle_update_cache_opts(autogc, true = Value, State) -> + {ok, maybe_start_cache_gc_timer(State#state{cache_autogc = Value})}; +handle_update_cache_opts(autogc, false = _Value, + #state{cache_autogc = false} = State) -> + {ok, State}; +handle_update_cache_opts(autogc, false = Value, + #state{cache_tmr = Tmr} = State) -> + maybe_stop_cache_gc_timer(Tmr), + {ok, State#state{cache_autogc = Value, cache_tmr = undefined}}; + +handle_update_cache_opts(age, Age, State) -> + {ok, State#state{cache_age = Age}}; + +handle_update_cache_opts(gclimit, GcLimit, State) -> + {ok, State#state{cache_gclimit = GcLimit}}; + +handle_update_cache_opts(BadKey, Value, State) -> + {{error, {bad_cache_opt, BadKey, Value}}, State}. + + +maybe_stop_cache_gc_timer(undefined) -> + ok; +maybe_stop_cache_gc_timer(Tmr) -> + erlang:cancel_timer(Tmr). + + +maybe_start_cache_gc_timer(#state{cache = Cache, + cache_autogc = true, + cache_tmr = undefined} = State) + when (Cache =/= ?NO_CACHE) -> + Tmr = start_cache_gc_timer(), + State#state{cache_tmr = Tmr}; +maybe_start_cache_gc_timer(State) -> + State. + +start_cache_gc_timer() -> + erlang:send_after(?CACHE_GC_TICKTIME, self(), ?CACHE_GC_TRIGGER). + + +%% ---------------------------------------------------------------- + +maybe_gc_cache(?NO_CACHE, _Age) -> + ?vtrace("cache not enabled", []), + ok; +maybe_gc_cache(Cache, Age) -> + MatchSpec = gc_cache_matchspec(Age), + Keys = ets:select(Cache, MatchSpec), + do_gc_cache(Cache, Keys), + {ok, length(Keys)}. + +maybe_gc_cache(?NO_CACHE, _Age, _GcLimit) -> + ok; +maybe_gc_cache(Cache, Age, infinity = _GcLimit) -> + maybe_gc_cache(Cache, Age); +maybe_gc_cache(Cache, Age, GcLimit) -> + MatchSpec = gc_cache_matchspec(Age), + Keys = + case ets:select(Cache, MatchSpec, GcLimit) of + {Match, _Cont} -> + Match; + '$end_of_table' -> + [] + end, + do_gc_cache(Cache, Keys), + {ok, length(Keys)}. + +gc_cache_matchspec(Age) -> + Oldest = timestamp() - Age, + MatchHead = {'$1', '_', '$2'}, + Guard = [{'<', '$2', Oldest}], + MatchFunc = {MatchHead, Guard, ['$1']}, + MatchSpec = [MatchFunc], + MatchSpec. + +do_gc_cache(_, []) -> + ok; +do_gc_cache(Cache, [Key|Keys]) -> + ets:delete(Cache, Key), + do_gc_cache(Cache, Keys). + +maybe_invalidate_cache(?NO_CACHE) -> + ?NO_CACHE; +maybe_invalidate_cache(Cache) -> + del_cache(Cache), + new_cache(). + +maybe_cache_size(?NO_CACHE) -> + {error, not_enabled}; +maybe_cache_size(Cache) -> + {ok, ets:info(Cache, size)}. + +new_cache() -> + ets:new(snmpa_mib_cache, [set, protected, {keypos, 1}]). + +del_cache(?NO_CACHE) -> + ok; +del_cache(Cache) -> + ets:delete(Cache). + +maybe_cache_lookup(?NO_CACHE, _) -> + ?NO_CACHE; +maybe_cache_lookup(Cache, Key) -> + ets:lookup(Cache, Key). + +size_cache(?NO_CACHE) -> + undefined; +size_cache(Cache) -> + case (catch ets:info(Cache, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + +timestamp() -> + snmp_misc:now(ms). + + +%% ---------------------------------------------------------------- + +get_opt(Key, Options, Default) -> + snmp_misc:get_option(Key, Options, Default). + + +%% ---------------------------------------------------------------- + +cast(MibServer, Msg) -> + gen_server:cast(MibServer, Msg). + +call(MibServer, Req) -> + call(MibServer, Req, infinity). + +call(MibServer, Req, To) -> + gen_server:call(MibServer, Req, To). + + +%% ---------------------------------------------------------------- + +%% info_msg(F, A) -> +%% ?snmpa_info("Mib server: " ++ F, A). + +warning_msg(F, A) -> + ?snmpa_warning("Mib server: " ++ F, A). + +%% error_msg(F, A) -> +%% ?snmpa_error("Mib server: " ++ F, A). + +config_err(F, A) -> + snmpa_error:config_err(F, A). + diff --git a/lib/snmp/src/agent/snmpa_mib_data.erl b/lib/snmp/src/agent/snmpa_mib_data.erl new file mode 100644 index 0000000000..b80d85d2ee --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mib_data.erl @@ -0,0 +1,1355 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_mib_data). + +%%%----------------------------------------------------------------- +%%% This module implements the MIB internal data structures. +%%% An MIB Data Structure consists of three items; an ets-table, +%%% a tree and a list of registered subagents. +%%% The subagent information is consequently duplicated. It resides +%%% both in the tree and in the list. +%%% The ets-table contains all data associated with each variable, +%%% table, tableentry and tablecolumn in the MIB. +%%% The tree contains information of the Oids in the MIB. +%%% +%%% When a mib is loaded, the tree is built from the plain list +%%% in the binary file. +%%%----------------------------------------------------------------- +-include("snmp_types.hrl"). +-include("snmp_debug.hrl"). + +-define(VMODULE,"MDATA"). +-include("snmp_verbosity.hrl"). + +-define(MIB_DATA,snmpa_mib_data). +-define(MIB_NODE,snmpa_mib_node). +-define(MIB_TREE,snmpa_mib_tree). +-define(DUMMY_TREE_GENERATION,1). +-define(DEFAULT_TREE,{tree,{undefined_node},internal}). +%%-define(DUMMY_TREE_DB,dummy_tree_db). +%%-define(DUMMY_TREE_DB_INIT,{?DUMMY_TREE_DB,?DEFAULT_TREE}). + + +%%%----------------------------------------------------------------- +%%% Table of contents +%%% ================= +%%% 1. Interface +%%% 2. Implementation of tree access +%%% 3. Tree building functions +%%% 4. Tree merging +%%% 5. Tree deletion routines +%%% 6. Functions for subagent handling +%%% 7. Misc functions +%%%----------------------------------------------------------------- + + +%%---------------------------------------------------------------------- +%% data_db is an database containing loaded mibs as: +%% {MibName = atom(), Symbolic = ?, FullFileName = string()} +%% it is either ets or mnesia +%% tree_db is a database containing _one_ record with the tree! +%% (the reason for this is part to get replication and part out of convenience) +%% ref_tree is the root node, without any subagent. +%% tree is the root node (same as ref_tree but with the subagents added). +%% subagents is a list of {SAPid, Oid} +%%---------------------------------------------------------------------- +-record(mib_data, {mib_db, % table of #mib_info + node_db, % table of #node_info + tree_db, % table of #tree + tree, % The actual tree + subagents = []}). + +-record(mib_info, {name, symbolic, file_name}). +-record(node_info, {oid, mib_name, me}). + + +%% API +-export([new/0, new/1, sync/1, close/1, + load_mib/4, unload_mib/4, which_mibs/1, whereis_mib/2, + info/1, info/2, + dump/1, dump/2, + backup/2, + lookup/2, next/3, which_mib/2, + register_subagent/3, unregister_subagent/2]). + +%% Internal exports +-export([code_change/2]). + + +%%----------------------------------------------------------------- +%% A tree is represented as a N-tuple, where each element is a +%% node. A node is: +%% 1) {tree, Tree, Info} where Info can be {table, Id}, {table_entry, Id} +%% or perhaps 'internal' +%% 2) undefined_node (memory optimization (instead of {node, undefined})) +%% 3) {node, Info} where Info can be {subagent, Pid}, {variable, Id}, +%% {table_column, Id} +%% Id is {MibName, MibEntry} +%% The over all root is represented as {tree, Tree, internal}. +%% +%% tree() = {tree, nodes(), tree_info()} +%% nodes() = {tree() | node() | undefined_node, ...} +%% node() = {node, node_info()} +%% tree_info() = {table, Id} | {table_entry, Id} | internal +%% node_info() = {subagent, Pid} | {variable, Id} | {table_colum, Id} +%%----------------------------------------------------------------- + +%% This record is what is stored in the database. The 'tree' part +%% is described above... +-record(tree,{generation = ?DUMMY_TREE_GENERATION, root = ?DEFAULT_TREE}). + + +%%%====================================================================== +%%% 1. Interface +%%%====================================================================== + +%%----------------------------------------------------------------- +%% Func: new/0, new/1 +%% Returns: A representation of mib data. +%%----------------------------------------------------------------- +new() -> + new(ets). + +%% Where -> A list of nodes where the tables will be created +new(Storage) -> + %% First we must check if there is already something to read + %% If a database already exists, then the tree structure has to be read + ?vtrace("open (mib) database",[]), + MibDb = snmpa_general_db:open(Storage, ?MIB_DATA, + mib_info, + record_info(fields,mib_info), set), + ?vtrace("open (mib) node database",[]), + NodeDb = snmpa_general_db:open(Storage, ?MIB_NODE, + node_info, + record_info(fields,node_info), set), + ?vtrace("open (mib) tree database",[]), + TreeDb = snmpa_general_db:open(Storage, ?MIB_TREE, + tree, + record_info(fields,tree), set), + Tree = + case snmpa_general_db:read(TreeDb, ?DUMMY_TREE_GENERATION) of + false -> + T = #tree{}, + snmpa_general_db:write(TreeDb, T), + T; + {value, T} -> + T + end, + install_mibs(MibDb, NodeDb), + #mib_data{mib_db = MibDb, + node_db = NodeDb, + tree_db = TreeDb, + tree = Tree}. + + +%%---------------------------------------------------------------------- +%% Returns: new mib data | {error, Reason} +%%---------------------------------------------------------------------- +load_mib(MibData,FileName,MeOverride,TeOverride) + when is_record(MibData,mib_data) andalso is_list(FileName) -> + ?vlog("load mib file: ~p",[FileName]), + ActualFileName = filename:rootname(FileName, ".bin") ++ ".bin", + MibName = list_to_atom(filename:basename(FileName, ".bin")), + (catch do_load_mib(MibData, ActualFileName, MibName, + MeOverride, TeOverride)). + +do_load_mib(MibData, ActualFileName, MibName, MeOverride, TeOverride) -> + ?vtrace("do_load_mib -> entry with" + "~n ActualFileName: ~s" + "~n MibName: ~p",[ActualFileName, MibName]), + #mib_data{mib_db = MibDb, + node_db = NodeDb, + %% tree_db = TreeDb, + tree = Tree} = MibData, + verify_not_loaded(MibDb, MibName), + ?vtrace("do_load_mib -> already loaded mibs:" + "~n ~p",[loaded(MibDb)]), + Mib = do_read_mib(ActualFileName), + ?vtrace("do_load_mib -> read mib ~s",[Mib#mib.name]), + NonInternalMes = + lists:filter(fun(ME) -> maybe_drop_me(ME) end, Mib#mib.mes), + OldRoot = Tree#tree.root, + T = build_tree(NonInternalMes, MibName), + ?d("load_mib -> " + "~n OldRoot: ~p" + "~n T: ~p", [OldRoot, T]), + case (catch merge_nodes(T, OldRoot)) of + {error_merge_nodes, Node1, Node2} -> + ?vlog("error merging nodes:" + "~n~p~nand~n~p", [Node1,Node2]), + {error, oid_conflict}; + NewRoot when is_tuple(NewRoot) andalso (element(1,NewRoot) =:= tree) -> + ?d("load_mib -> " + "~n NewRoot: ~p", [NewRoot]), + Symbolic = not lists:member(no_symbolic_info, Mib#mib.misc), + case (catch check_notif_and_mes(TeOverride, MeOverride, Symbolic, + Mib#mib.traps, NonInternalMes)) of + true -> + install_mes(NodeDb, MibName, NonInternalMes), + install_mib(MibDb, Symbolic, Mib, + MibName, ActualFileName, NonInternalMes), + ?vtrace("installed mib ~s", [Mib#mib.name]), + Tree2 = Tree#tree{root = NewRoot}, + %% snmpa_general_db:write(TreeDb, Tree2), %% Store later? + {ok, MibData#mib_data{tree = Tree2}}; + Else -> + Else + end + end. + + +verify_not_loaded(Db, Name) -> + case snmpa_general_db:read(Db, Name) of + {value, #mib_info{name = Name}} -> + throw({error, 'already loaded'}); + false -> + ok + end. + +do_read_mib(ActualFileName) -> + case snmp_misc:read_mib(ActualFileName) of + {error, Reason} -> + ?vlog("Failed reading mib file ~p with reason: ~p", + [ActualFileName,Reason]), + throw({error, Reason}); + {ok, Mib} -> + Mib + end. + +%% The Tree DB is handled in a special way since it can be very large. +sync(#mib_data{mib_db = M, + node_db = N, + tree_db = T, tree = Tree, subagents = []}) -> + snmpa_general_db:sync(M), + snmpa_general_db:sync(N), + snmpa_general_db:write(T, Tree), + snmpa_general_db:sync(T); +sync(#mib_data{mib_db = M, + node_db = N, + tree_db = T, tree = Tree, subagents = SAs}) -> + + snmpa_general_db:sync(M), + snmpa_general_db:sync(N), + + %% Ouch. Since the subagent info is dynamic we do not + %% want to store the tree containing subagent info. So, we + %% have to create a tmp tree without those and store it. + + case delete_subagents(Tree, SAs) of + {ok, TreeWithoutSAs} -> + snmpa_general_db:write(T, TreeWithoutSAs), + snmpa_general_db:sync(T); + Error -> + Error + end. + +delete_subagents(Tree, []) -> + {ok, Tree}; +delete_subagents(Tree0, [{_, Oid}|SAs]) -> + case (catch delete_subagent(Tree0, Oid)) of + {tree, _Tree, _Info} = Tree1 -> + delete_subagents(Tree1, SAs); + _Error -> + {error, {'invalid oid', Oid}} + end. + +%%---------------------------------------------------------------------- +%% (OTP-3601) +%%---------------------------------------------------------------------- +check_notif_and_mes(TeOverride,MeOverride,Symbolic,Traps,MEs) -> + ?vtrace("check notifications and mib entries",[]), + check_notifications(TeOverride,Symbolic,Traps), + check_mes(MeOverride,MEs). + +check_notifications(true, _Symbolic, _Traps) -> + ?vtrace("trapentry override = true => skip check",[]), + true; +check_notifications(_, Symbolic, Traps) -> + check_notifications(Symbolic, Traps). + +check_notifications(true, Traps) -> + check_notifications(Traps); +check_notifications(_, _) -> true. + +check_notifications([]) -> true; +check_notifications([#trap{trapname = Key} = Trap | Traps]) -> + ?vtrace("check notification [trap] with Key: ~p",[Key]), + case snmpa_symbolic_store:get_notification(Key) of + {value, Trap} -> check_notifications(Traps); + {value, _} -> throw({error, {'trap already defined', Key}}); + undefined -> check_notifications(Traps) + end; +check_notifications([#notification{trapname = Key} = Notif | Traps]) -> + ?vtrace("check notification [notification] with Key: ~p",[Key]), + case snmpa_symbolic_store:get_notification(Key) of + {value, Notif} -> + check_notifications(Traps); + {value, _} -> + throw({error, {'notification already defined', Key}}); + undefined -> + check_notifications(Traps) + end; +check_notifications([Crap | Traps]) -> + ?vlog("skipped check of: ~n~p",[Crap]), + check_notifications(Traps). + +check_mes(true,_) -> + ?vtrace("mibentry override = true => skip check",[]), + true; +check_mes(_,MEs) -> + check_mes(MEs). + +check_mes([]) -> true; +check_mes([#me{aliasname = Name, oid = Oid1} | MEs]) -> + ?vtrace("check mib entries with aliasname: ~p",[Name]), + case snmpa_symbolic_store:aliasname_to_oid(Name) of + {value, Oid1} -> + check_mes(MEs); + {value, Oid2} -> + ?vinfo("~n expecting '~p'~n but found '~p'",[Oid1, Oid2]), + throw({error, {'mibentry already defined', Name}}); + false -> + check_mes(MEs) + end; +check_mes([Crap | MEs]) -> + ?vlog("skipped check of: ~n~p",[Crap]), + check_mes(MEs). + + + +%%---------------------------------------------------------------------- +%% Returns: new mib data | {error, Reason} +%%---------------------------------------------------------------------- +unload_mib(MibData, FileName, _, _) when is_list(FileName) -> + MibName = list_to_atom(filename:basename(FileName, ".bin")), + (catch do_unload_mib(MibData, MibName)). + +do_unload_mib(MibData, MibName) -> + ?vtrace("do_unload_mib -> entry with" + "~n MibName: ~p", [MibName]), + #mib_data{mib_db = MibDb, + node_db = NodeDb, + %% tree_db = TreeDb, + tree = Tree} = MibData, + #mib_info{symbolic = Symbolic} = verify_loaded(MibDb, MibName), + NewRoot = delete_mib_from_tree(MibName, Tree#tree.root), + MEs = uninstall_mes(NodeDb, MibName), + uninstall_mib(MibDb, Symbolic, MibName, MEs), + NewMibData = MibData#mib_data{tree = Tree#tree{root = NewRoot}}, + {ok, NewMibData}. + +verify_loaded(Db, Name) -> + case snmpa_general_db:read(Db, Name) of + {value, MibInfo} -> + MibInfo; + false -> + throw({error, 'not loaded'}) + end. + + +close(#mib_data{mib_db = MibDb, node_db = NodeDb, tree_db = TreeDb}) -> + snmpa_general_db:close(MibDb), + snmpa_general_db:close(NodeDb), + snmpa_general_db:close(TreeDb), + ok. + +register_subagent(#mib_data{tree = T} = MibData, Oid, Pid) -> + case insert_subagent(Oid, T#tree.root) of + {error, Reason} -> + {error, Reason}; + NewRootTree -> + SAs = [{Pid, Oid} | MibData#mib_data.subagents], + T2 = T#tree{root = NewRootTree}, + MibData#mib_data{tree = T2, subagents = SAs} + end. + + +%%---------------------------------------------------------------------- +%% Purpose: Get a list of all loaded mibs +%% Returns: [{Name, File}] +%%---------------------------------------------------------------------- + +which_mibs(#mib_data{mib_db = Db}) -> + Mibs = snmpa_general_db:tab2list(Db), + [{Name, File} || #mib_info{name = Name, file_name = File} <- Mibs]. + + +%%---------------------------------------------------------------------- +%% Purpose: Get a list of all loaded mibs +%% Returns: [{Name, File}] +%%---------------------------------------------------------------------- + +whereis_mib(#mib_data{mib_db = Db}, Name) -> + case snmpa_general_db:read(Db, Name) of + {value, #mib_info{file_name = File}} -> + {ok, File}; + false -> + {error, not_found} + end. + + +%%---------------------------------------------------------------------- +%% Purpose: Deletes SA with Pid from all subtrees it handles. +%% Returns: NewMibData. +%%---------------------------------------------------------------------- +unregister_subagent(MibData, Pid) when is_pid(Pid) -> + SAs = MibData#mib_data.subagents, + case lists:keysearch(Pid, 1, SAs) of + false -> MibData; + {value, {Pid, Oid}} -> + % we should never get an error since Oid is found in MibData. + {ok, NewMibData, _DeletedSA} = unregister_subagent(MibData, Oid), + % continue if the same Pid handles other mib subtrees. + unregister_subagent(NewMibData, Pid) + end; + +%%---------------------------------------------------------------------- +%% Purpose: Deletes one unique subagent. +%% Returns: {error, Reason} | {ok, NewMibData, DeletedSubagentPid} +%%---------------------------------------------------------------------- +unregister_subagent(#mib_data{tree = T} = MibData, Oid) when is_list(Oid) -> + case catch delete_subagent(T#tree.root, Oid) of + {tree, Tree, Info} -> + OldSAs = MibData#mib_data.subagents, + {value, {Pid, _Oid}} = lists:keysearch(Oid, 2, OldSAs), + SAs = lists:keydelete(Oid, 2, OldSAs), + T2 = T#tree{root = {tree, Tree, Info}}, + {ok, + MibData#mib_data{tree = T2, subagents = SAs}, + Pid}; + _ -> + {error, {'invalid oid', Oid}} + end. + +%%---------------------------------------------------------------------- +%% Purpose: To inpect memory usage, loaded mibs, registered subagents +%%---------------------------------------------------------------------- +info(MibData) -> + ?vtrace("retrieve info",[]), + #mib_data{mib_db = MibDb, node_db = NodeDb, tree_db = TreeDb, + tree = Tree, subagents = SAs} = MibData, + LoadedMibs = old_format(snmpa_general_db:tab2list(MibDb)), + TreeSize = snmp_misc:mem_size(Tree), + {memory, ProcSize} = erlang:process_info(self(),memory), + MibDbSize = snmpa_general_db:info(MibDb, memory), + NodeDbSize = snmpa_general_db:info(NodeDb, memory), + TreeDbSize = snmpa_general_db:info(TreeDb, memory), + [{loaded_mibs, LoadedMibs}, {subagents, SAs}, {tree_size_bytes, TreeSize}, + {process_memory, ProcSize}, + {db_memory, [{mib,MibDbSize},{node,NodeDbSize},{tree,TreeDbSize}]}]. + +info(#mib_data{mib_db = MibDb}, loaded_mibs) -> + Mibs = snmpa_general_db:tab2list(MibDb), + [filename:rootname(FN, ".bin") || #mib_info{file_name = FN} <- Mibs]; +info(#mib_data{tree = Tree}, tree_size_bytes) -> + snmp_misc:mem_size(Tree); +info(_, process_memory) -> + {memory, ProcSize} = erlang:process_info(self(),memory), + ProcSize; +info(#mib_data{mib_db = MibDb, node_db = NodeDb, tree_db = TreeDb}, + db_memory) -> + MibDbSize = snmpa_general_db:info(MibDb, memory), + NodeDbSize = snmpa_general_db:info(NodeDb, memory), + TreeDbSize = snmpa_general_db:info(TreeDb, memory), + [{mib,MibDbSize},{node,NodeDbSize},{tree,TreeDbSize}]; +info(#mib_data{subagents = SAs}, subagents) -> + SAs. + +old_format(LoadedMibs) -> + ?vtrace("convert mib info to old format",[]), + [{N,S,F} || #mib_info{name=N,symbolic=S,file_name=F} <- LoadedMibs]. + + +%%---------------------------------------------------------------------- +%% A total dump for debugging. +%%---------------------------------------------------------------------- +dump(#mib_data{mib_db = MibDb, node_db = NodeDb, tree = Tree}) -> + (catch io:format("MIB-tables:~n~p~n~n", + [snmpa_general_db:tab2list(MibDb)])), + (catch io:format("MIB-entries:~n~p~n~n", + [snmpa_general_db:tab2list(NodeDb)])), + (catch io:format("Tree:~n~p~n", [Tree])), % good luck reading it! + ok. + +dump(#mib_data{mib_db = MibDb, node_db = NodeDb, tree = Tree}, File) -> + case file:open(File,[write]) of + {ok, Fd} -> + io:format(Fd,"~s~n", + [snmp:date_and_time_to_string(snmp:date_and_time())]), + (catch io:format(Fd,"MIB-tables:~n~p~n~n", + [snmpa_general_db:tab2list(MibDb)])), + (catch io:format(Fd, "MIB-entries:~n~p~n~n", + [snmpa_general_db:tab2list(NodeDb)])), + io:format(Fd,"Tree:~n~p~n", [Tree]), % good luck reading it! + file:close(Fd), + ok; + {error,Reason} -> + ?vinfo("~n Failed opening file '~s' for reason ~p", + [File,Reason]), + {error,Reason} + end. + + +backup(#mib_data{mib_db = M, node_db = N, tree_db = T}, BackupDir) -> + MRes = snmpa_general_db:backup(M, BackupDir), + NRes = snmpa_general_db:backup(N, BackupDir), + TRes = snmpa_general_db:backup(T, BackupDir), + handle_backup_res([{mib_db, MRes}, {node_db, NRes}, {tree_db, TRes}]). + +handle_backup_res(Res) -> + handle_backup_res(Res, []). + +handle_backup_res([], []) -> + ok; +handle_backup_res([], Err) -> + {error, lists:reverse(Err)}; +handle_backup_res([{_, ok}|Res], Err) -> + handle_backup_res(Res, Err); +handle_backup_res([{Tag, {error, Reason}}|Res], Err) -> + handle_backup_res(Res, [{Tag, Reason}|Err]); +handle_backup_res([{Tag, Error}|Res], Err) -> + handle_backup_res(Res, [{Tag, Error}|Err]). + + +%%%====================================================================== +%%% 2. Implementation of tree access +%%% lookup and next. +%%%====================================================================== + + +which_mib(#mib_data{tree = T} = D, Oid) -> + ?vtrace("which_mib -> entry with" + "~n Oid: ~p",[Oid]), + case (catch find_node(D, T#tree.root, Oid, [])) of + {variable, _ME, Mib} -> + ?vtrace("which_mib -> variable:" + "~n Mib: ~p", [Mib]), + {ok, Mib}; + {table, _EntryME, _, Mib} -> + ?vtrace("which_mib -> table:" + "~n Mib: ~p", [Mib]), + {ok, Mib}; + {subagent, SubAgentPid, _SANextOid} -> + ?vtrace("which_mib -> subagent:" + "~n SubAgentPid: ~p", [SubAgentPid]), + {error, {subagent, SubAgentPid}}; + {false, ErrorCode} -> + ?vtrace("which_mib -> false:" + "~n ErrorCode: ~p",[ErrorCode]), + {error, ErrorCode}; + false -> + ?vtrace("which_mib -> false",[]), + {error, noSuchObject}; + {'EXIT', R} -> + ?vtrace("which_mib -> exit:" + "~n R: ~p",[R]), + {error, noSuchObject} + end. + + +%%----------------------------------------------------------------- +%% Func: lookup/2 +%% Purpose: Finds the mib entry corresponding to the Oid. If it is a +%% variable, the Oid must be <Oid for var>.0 and if it is +%% a table, Oid must be <table>.<entry>.<col>.<any> +%% Returns: {variable, MibEntry} | +%% {table_column, MibEntry, TableEntryOid} | +%% {subagent, SubAgentPid, SAOid} | +%% {false, Reason} +%%----------------------------------------------------------------- +lookup(#mib_data{tree = T} = D, Oid) -> + ?vtrace("lookup -> entry with" + "~n Oid: ~p",[Oid]), + case (catch find_node(D, T#tree.root, Oid, [])) of + {variable, ME, _Mib} when is_record(ME, me) -> + ?vtrace("lookup -> variable:" + "~n ME: ~p",[ME]), + {variable, ME}; + {table, EntryME, {ColME, TableEntryOid}, _Mib} -> + ?vtrace("lookup -> table:" + "~n EntryME: ~p" + "~n ColME: ~p" + "~n RevTableEntryOid: ~p", + [EntryME, ColME, TableEntryOid]), + MFA = EntryME#me.mfa, + RetME = ColME#me{mfa = MFA}, + {table_column, RetME, TableEntryOid}; + {subagent, SubAgentPid, SANextOid} -> + ?vtrace("lookup -> subagent:" + "~n SubAgentPid: ~p" + "~n SANextOid: ~p", [SubAgentPid, SANextOid]), + {subagent, SubAgentPid, SANextOid}; + {false, ErrorCode} -> + ?vtrace("lookup -> false:" + "~n ErrorCode: ~p",[ErrorCode]), + {false, ErrorCode}; + false -> + ?vtrace("lookup -> false",[]), + {false, noSuchObject}; + {'EXIT', R} -> + ?vtrace("lookup -> exit:" + "~n R: ~p",[R]), + {false, noSuchObject} + end. + + +find_node(D, {tree, Tree, {table, _}}, RestOfOid, RevOid) -> + ?vtrace("find_node(tree,table) -> entry with" + "~n RestOfOid: ~p" + "~n RevOid: ~p",[RestOfOid, RevOid]), + find_node(D, {tree, Tree, internal}, RestOfOid, RevOid); +find_node(D, {tree, Tree, {table_entry, _}}, RestOfOid, RevOid) -> + ?vtrace("find_node(tree,table_entry) -> entry with" + "~n RestOfOid: ~p" + "~n RevOid: ~p",[RestOfOid, RevOid]), + #mib_data{node_db = Db} = D, + Oid = lists:reverse(RevOid), + case snmpa_general_db:read(Db, Oid) of + {value, #node_info{me = ME, mib_name = Mib}} -> + case find_node(D, {tree, Tree, internal}, RestOfOid, RevOid) of + {false, ErrorCode} -> {false, ErrorCode}; + Val -> {table, ME, Val, Mib} + end; + false -> + ?vinfo("find_node -> could not find table_entry ME with" + "~n RevOid: ~p" + "~n when" + "~n RestOfOid: ~p", + [RevOid, RestOfOid]), + false + end; +find_node(D, {tree, Tree, _Internal}, [Int | RestOfOid], RevOid) -> + ?vtrace("find_node(tree) -> entry with" + "~n Int: ~p" + "~n RestOfOid: ~p" + "~n RevOid: ~p",[Int, RestOfOid, RevOid]), + find_node(D, element(Int+1, Tree), RestOfOid, [Int | RevOid]); +find_node(D, {node, {table_column, _}}, RestOfOid, [ColInt | RevOid]) -> + ?vtrace("find_node(tree,table_column) -> entry with" + "~n RestOfOid: ~p" + "~n ColInt: ~p" + "~n RevOid: ~p",[RestOfOid, ColInt, RevOid]), + #mib_data{node_db = Db} = D, + Oid = lists:reverse([ColInt | RevOid]), + case snmpa_general_db:read(Db, Oid) of + {value, #node_info{me = ME}} -> + {ME, lists:reverse(RevOid)}; + false -> + X = snmpa_general_db:read(Db, lists:reverse([ColInt | RevOid])), + ?vinfo("find_node -> could not find table_column ME with" + "~n RevOid: ~p" + "~n trying [~p|~p]" + "~n X: ~p", + [RevOid, [ColInt | RevOid], X]), + false + end; +find_node(D, {node, {variable, _MibName}}, [0], RevOid) -> + ?vtrace("find_node(tree,variable,[0]) -> entry with" + "~n RevOid: ~p",[RevOid]), + #mib_data{node_db = Db} = D, + Oid = lists:reverse(RevOid), + %% {value, #node_info{me = ME}} = snmpa_general_db:read(Db, Oid), + case snmpa_general_db:read(Db, Oid) of + {value, #node_info{me = ME, mib_name = Mib}} -> + {variable, ME, Mib}; + false -> + ?vinfo("find_node -> could not find variable ME with" + "~n RevOid: ~p", [RevOid]), + false + end; +find_node(_D, {node, {variable, _MibName}}, [], _RevOid) -> + ?vtrace("find_node(tree,variable,[]) -> entry",[]), + {false, noSuchObject}; +find_node(_D, {node, {variable, _MibName}}, _, _RevOid) -> + ?vtrace("find_node(tree,variable) -> entry",[]), + {false, noSuchInstance}; +find_node(D, {node, subagent}, _RestOfOid, SARevOid) -> + ?vtrace("find_node(tree,subagent) -> entry with" + "~n SARevOid: ~p",[SARevOid]), + #mib_data{subagents = SAs} = D, + SAOid = lists:reverse(SARevOid), + case lists:keysearch(SAOid, 2, SAs) of + {value, {SubAgentPid, SAOid}} -> + {subagent, SubAgentPid, SAOid}; + false -> + ?vinfo("find_node -> could not find subagent with" + "~n SAOid: ~p" + "~n SAs: ~p", [SAOid, SAs]), + false + end; +find_node(_D, Node, _RestOfOid, _RevOid) -> + ?vtrace("find_node -> failed:~n~p",[Node]), + {false, noSuchObject}. + + +%%----------------------------------------------------------------- +%% Func: next/3 +%% Purpose: Finds the lexicographically next oid. +%% Returns: endOfMibView | +%% {subagent, SubAgentPid, SAOid} | +%% {variable, MibEntry, VarOid} | +%% {table, TableOid, TableRestOid, MibEntry} +%% If a variable is returnes, it is in the MibView. +%% If a table or subagent is returned, it *may* be in the MibView. +%%----------------------------------------------------------------- +next(#mib_data{tree = T} = D, Oid, MibView) -> + case catch next_node(D, T#tree.root, Oid, [], MibView) of + false -> endOfMibView; + Else -> Else + end. + +%%----------------------------------------------------------------- +%% This function is used as long as we have any Oid left. Take +%% one integer at a time from the Oid, and traverse the tree +%% accordingly. When the Oid is empty, call find_next. +%% Returns: {subagent, SubAgentPid, SAOid} | +%% false | +%% {variable, MibEntry, VarOid} | +%% {table, TableOid, TableRestOid, MibEntry} +%%----------------------------------------------------------------- +next_node(_D, undefined_node, _Oid, _RevOidSoFar, _MibView) -> + ?vtrace("next_node(undefined_node) -> entry", []), + false; + +next_node(_D, {tree, Tree, {table_entry, _Id}}, [Int | _Oid], + _RevOidSoFar, _MibView) + when Int+1 > size(Tree) -> + ?vtrace("next_node(tree,table_entry) -> entry when not found whith" + "~n Int: ~p" + "~n size(Tree): ~p", [Int, size(Tree)]), + false; +next_node(D, {tree, Tree, {table_entry, _MibName}}, + Oid, RevOidSoFar, MibView) -> + ?vtrace("next_node(tree,table_entry) -> entry when" + "~n size(Tree): ~p" + "~n Oid: ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", [size(Tree), Oid, RevOidSoFar, MibView]), + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + ?vdebug("next_node(tree,table_entry) -> not in mib view",[]), + false; + _ -> + #mib_data{node_db = Db} = D, + case snmpa_general_db:read(Db, OidSoFar) of + false -> + ?vinfo("next_node -> could not find table_entry with" + "~n OidSoFar: ~p", [OidSoFar]), + false; + {value, #node_info{me = ME}} -> + ?vtrace("next_node(tree,table_entry) -> found: ~n ~p", + [ME]), + {table, OidSoFar, Oid, ME} + end + end; + +next_node(D, {tree, Tree, _Info}, [Int | RestOfOid], RevOidSoFar, MibView) + when (Int < size(Tree)) andalso (Int >= 0) -> + ?vtrace("next_node(tree) -> entry when" + "~n size(Tree): ~p" + "~n Int: ~p" + "~n RestOfOid: ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [size(Tree), Int, RestOfOid, RevOidSoFar, MibView]), + case next_node(D, element(Int+1,Tree), + RestOfOid, [Int|RevOidSoFar], MibView) of + false -> + find_next(D, {tree, Tree, _Info}, Int+1, RevOidSoFar, MibView); + Else -> + Else + end; +%% no solution +next_node(D, {tree, Tree, _Info}, [], RevOidSoFar, MibView) -> + ?vtrace("next_node(tree,[]) -> entry when" + "~n size(Tree): ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [size(Tree), RevOidSoFar, MibView]), + find_next(D, {tree, Tree, _Info}, 0, RevOidSoFar, MibView); +next_node(_D, {tree, Tree, _Info}, _RestOfOid, _RevOidSoFar, _MibView) -> + ?vtrace("next_node(tree) -> entry when" + "~n size(Tree): ~p", [size(Tree)]), + false; + +next_node(D, {node, subagent}, Oid, RevOidSoFar, MibView) -> + ?vtrace("next_node(node,subagent) -> entry when" + "~n Oid: ~p" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [Oid, RevOidSoFar, MibView]), + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + false; + _ -> + #mib_data{subagents = SAs} = D, + case lists:keysearch(OidSoFar, 2, SAs) of + {value, {SubAgentPid, OidSoFar}} -> + {subagent, SubAgentPid, OidSoFar}; + _ -> + ?vinfo("next_node -> could not find subagent with" + "~n OidSoFar: ~p" + "~n SAs: ~p", [OidSoFar, SAs]), + false + end + end; + +next_node(D, {node, {variable, _MibName}}, [], RevOidSoFar, MibView) -> + ?vtrace("next_node(node,variable,[]) -> entry when" + "~n RevOidSoFar: ~p" + "~n MibView: ~p", + [RevOidSoFar, MibView]), + OidSoFar = lists:reverse([0 | RevOidSoFar]), + case snmpa_acm:validate_mib_view(OidSoFar, MibView) of + true -> + #mib_data{node_db = Db} = D, + case snmpa_general_db:read(Db, lists:reverse(RevOidSoFar)) of + false -> + ?vinfo("next_node -> could not find variable with" + "~n RevOidSoFar: ~p", [RevOidSoFar]), + false; + {value, #node_info{me = ME}} -> + {variable, ME, OidSoFar} + end; + _ -> + false + end; + +next_node(_D, {node, {variable, _MibName}}, _Oid, _RevOidSoFar, _MibView) -> + ?vtrace("next_node(node,variable) -> entry", []), + false. + +%%----------------------------------------------------------------- +%% This function is used to find the first leaf from where we +%% are. +%% Returns: {subagent, SubAgentPid, SAOid} | +%% false | +%% {variable, MibEntry, VarOid} | +%% {table, TableOid, TableRestOid, MibEntry} +%% PRE: This function must always be called with a {internal, Tree} +%% node. +%%----------------------------------------------------------------- +find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView) + when Idx < size(Tree) -> + case find_next(D, element(Idx+1, Tree), 0, [Idx| RevOidSoFar], MibView) of + false -> + find_next(D, {tree, Tree, internal}, Idx+1, RevOidSoFar, MibView); + Other -> + Other + end; +find_next(_D, {tree, _Tree, internal}, _Idx, _RevOidSoFar, _MibView) -> + false; +find_next(_D, undefined_node, _Idx, _RevOidSoFar, _MibView) -> + false; +find_next(D, {tree, Tree, {table, _MibName}}, Idx, RevOidSoFar, MibView) -> + find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView); +find_next(D, {tree, _Tree, {table_entry, _MibName}}, _Index, + RevOidSoFar, MibView) -> + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + false; + _ -> + #mib_data{node_db = Db} = D, + case snmpa_general_db:read(Db, OidSoFar) of + false -> + ?vinfo("find_next -> could not find table_entry ME with" + "~n OidSoFar: ~p", [OidSoFar]), + false; + {value, #node_info{me = ME}} -> + {table, OidSoFar, [], ME} + end + end; +find_next(D, {node, {variable, _MibName}}, _Idx, RevOidSoFar, MibView) -> + OidSoFar = lists:reverse([0 | RevOidSoFar]), + case snmpa_acm:validate_mib_view(OidSoFar, MibView) of + true -> + #mib_data{node_db = Db} = D, + case snmpa_general_db:read(Db, lists:reverse(RevOidSoFar)) of + false -> + ?vinfo("find_next -> could not find variable with" + "~n RevOidSoFar: ~p", [RevOidSoFar]), + false; + {value, #node_info{me = ME}} -> + {variable, ME, OidSoFar} + end; + _ -> + false + end; +find_next(D, {node, subagent}, _Idx, RevOidSoFar, MibView) -> + OidSoFar = lists:reverse(RevOidSoFar), + case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of + true -> + false; + _ -> + #mib_data{subagents = SAs} = D, + case lists:keysearch(OidSoFar, 2, SAs) of + {value, {SubAgentPid, OidSoFar}} -> + {subagent, SubAgentPid, OidSoFar}; + false -> + ?vinfo("find_node -> could not find subagent with" + "~n OidSoFar: ~p" + "~n SAs: ~p", [OidSoFar, SAs]), + false + end + end. + +%%%====================================================================== +%%% 3. Tree building functions +%%% Used when loading mibs. +%%%====================================================================== + +build_tree(Mes, MibName) -> + ?d("build_tree -> " + "~n Mes: ~p", [Mes]), + {ListTree, []} = build_subtree([], Mes, MibName), + {tree, convert_tree(ListTree), internal}. + +%%---------------------------------------------------------------------- +%% Purpose: Builds the tree where all oids have prefix equal to LevelPrefix. +%% Returns: {Tree, RestMes} +%% RestMes are Mes that should not be in this subtree. +%% The Tree is a temporary and simplified data structure that is easy to +%% convert to the final tuple tree used by the MIB process. +%% A Node is represented as in the final tree. +%% The tree is not represented as a N-tuple, but as an Index-list. +%% Example: Temporary: [{1, Node1}, {3, Node3}] +%% Final: {Node1, undefined_node, Node3} +%% Pre: Mes are sorted on oid. +%%---------------------------------------------------------------------- +build_subtree(LevelPrefix, [Me | Mes], MibName) -> + ?vtrace("build subtree -> ~n" + " oid: ~p~n" + " LevelPrefix: ~p~n" + " MibName: ~p", [Me#me.oid, LevelPrefix, MibName]), + EType = Me#me.entrytype, + ?vtrace("build subtree -> EType = ~p",[EType]), + case in_subtree(LevelPrefix, Me) of + above -> + ?vtrace("build subtree -> above",[]), + {[], [Me|Mes]}; + {node, Index} -> + ?vtrace("build subtree -> node at ~p",[Index]), + {Tree, RestMes} = build_subtree(LevelPrefix, Mes, MibName), + {[{Index, {node, {EType, MibName}}} | Tree], RestMes}; + {subtree, Index, NewLevelPrefix} -> + ?vtrace("build subtree -> subtree at" + "~n ~w with ~w", + [Index, NewLevelPrefix]), + {BelowTree, RestMes} = + build_subtree(NewLevelPrefix, Mes, MibName), + {CurTree, RestMes2} = + build_subtree(LevelPrefix, RestMes, MibName), + {[{Index, {tree, BelowTree, {EType,MibName}}}| CurTree], RestMes2}; + {internal_subtree, Index, NewLevelPrefix} -> + ?vtrace("build subtree -> internal_subtree at" + "~n ~w with ~w", + [Index,NewLevelPrefix]), + {BelowTree, RestMes} = + build_subtree(NewLevelPrefix, [Me | Mes], MibName), + {CurTree, RestMes2} = + build_subtree(LevelPrefix, RestMes, MibName), + {[{Index, {tree, BelowTree, internal}} | CurTree], RestMes2} + end; + +build_subtree(_LevelPrefix, [], _MibName) -> + ?vtrace("build subtree -> done", []), + {[], []}. + +%%-------------------------------------------------- +%% Purpose: Determine how/if/where Me should be inserted in subtree +%% with LevelPrefix. This function does not build any tree, only +%% determinses what should be done (by build subtree). +%% Returns: +%% above - Indicating that this ME should _not_ be in this subtree. +%% {node, Index} - yes, construct a node with index Index on this level +%% {internal_subtree, Index, NewLevelPrefix} - yes, there should be an +%% internal subtree at this index. +%% {subtree, Index, NewLevelPrefix} - yes, construct a subtree with +%% NewLevelPrefix and insert this on current level in position Index. +%%-------------------------------------------------- +in_subtree(LevelPrefix, Me) -> + case lists:prefix(LevelPrefix, Me#me.oid) of + true when length(Me#me.oid) > length(LevelPrefix) -> + classify_how_in_subtree(LevelPrefix, Me); + _ -> + above + end. + +%%-------------------------------------------------- +%% See comment about in_subtree/2. This function takes care of all cases +%% where the ME really should be in _this_ subtree (not above). +%%-------------------------------------------------- +classify_how_in_subtree(LevelPrefix, Me) + when (length(Me#me.oid) =:= (length(LevelPrefix) + 1)) -> + Oid = Me#me.oid, + case node_or_subtree(Me#me.entrytype) of + subtree -> + {subtree, lists:last(Oid), Oid}; + node -> + {node, lists:last(Oid)} + end; + +classify_how_in_subtree(LevelPrefix, Me) + when (length(Me#me.oid) > (length(LevelPrefix) + 1)) -> + L1 = length(LevelPrefix) + 1, + Oid = Me#me.oid, + {internal_subtree, lists:nth(L1, Oid), lists:sublist(Oid, 1, L1)}. + +%%-------------------------------------------------- +%% Determines how to treat different kinds om MEs in the tree building process. +%% Pre: all internal nodes have been removed. +%%-------------------------------------------------- +node_or_subtree(table) -> subtree; +node_or_subtree(table_entry) -> subtree; +node_or_subtree(variable) -> node; +node_or_subtree(table_column) -> node. + +%%-------------------------------------------------- +%% Purpose: (Recursively) Converts a temporary tree (see above) to a final tree. +%% If input is a ListTree, output is a TupleTree. +%% If input is a Node, output is the same Node. +%% Pre: All Indexes are >= 0. +%%-------------------------------------------------- +convert_tree({Index, {tree, Tree, Info}}) when Index >= 0 -> + L = lists:map(fun convert_tree/1, Tree), + {Index, {tree, dict_list_to_tuple(L), Info}}; +convert_tree({Index, {node, Info}}) when Index >= 0 -> + {Index, {node, Info}}; +convert_tree(Tree) when is_list(Tree) -> + L = lists:map(fun convert_tree/1, Tree), + dict_list_to_tuple(L). + +%%---------------------------------------------------------------------- +%% Purpose: Converts a single level (that is non-recursively) from +%% the temporary indexlist to the N-tuple. +%% Input: A list of {Index, Data}. +%% Output: A tuple where element Index is Data. +%%---------------------------------------------------------------------- +dict_list_to_tuple(L) -> + L2 = lists:keysort(1, L), + list_to_tuple(integrate_indexes(0, L2)). + +%%---------------------------------------------------------------------- +%% Purpose: Helper function for dict_list_to_tuple/1. +%% Converts an indexlist to a N-list. +%% Input: A list of {Index, Data}. +%% Output: A (usually longer, never shorter) list where element Index is Data. +%% Example: [{1,hej}, {3, sven}] will give output +%% [undefined_node, hej, undefined_node, sven]. +%% Initially CurIndex should be 0. +%%---------------------------------------------------------------------- +integrate_indexes(CurIndex, [{CurIndex, Data} | T]) -> + [Data | integrate_indexes(CurIndex + 1, T)]; +integrate_indexes(_Index, []) -> + []; +integrate_indexes(CurIndex, L) -> + [undefined_node | integrate_indexes(CurIndex + 1, L)]. + +%%%====================================================================== +%%% 4. Tree merging +%%% Used by: load mib, insert subagent. +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Arg: Two root nodes (that is to be merged). +%% Returns: A new root node where the nodes have been merger to one. +%%---------------------------------------------------------------------- +merge_nodes(Same, Same) -> + Same; +merge_nodes(Node, undefined_node) -> + Node; +merge_nodes(undefined_node, Node) -> + Node; +merge_nodes({tree, Tree1, internal}, {tree, Tree2, internal}) -> + {tree, merge_levels(tuple_to_list(Tree1),tuple_to_list(Tree2)), internal}; +merge_nodes(Node1, Node2) -> + throw({error_merge_nodes, Node1, Node2}). + +%%---------------------------------------------------------------------- +%% Arg: Two levels to be merged. +%% Here, a level is represented as a list of nodes. A list is easier +%% to extend than a tuple. +%% Returns: The resulting, merged level tuple. +%%---------------------------------------------------------------------- +merge_levels(Level1, Level2) when length(Level1) =:= length(Level2) -> + MergeNodes = fun(N1, N2) -> merge_nodes(N1, N2) end, + list_to_tuple(snmp_misc:multi_map(MergeNodes, [Level1, Level2])); +merge_levels(Level1, Level2) when length(Level1) > length(Level2) -> + merge_levels(Level1, Level2 ++ + undefined_nodes_list(length(Level1) - length(Level2))); +merge_levels(Level1, Level2) when length(Level1) < length(Level2) -> + merge_levels(Level2, Level1). + +undefined_nodes_list(N) -> lists:duplicate(N, undefined_node). + + +%%%====================================================================== +%%% 5. Tree deletion routines +%%% (for unload mib) +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Purpose: Actually kicks of the tree reconstruction. +%% Returns: {list of removed MEs, NewTree} +%%---------------------------------------------------------------------- +delete_mib_from_tree(MibName, {tree, Tree, internal}) -> + case delete_tree(Tree, MibName) of + [] -> + {tree, {undefined_node}, internal}; % reduce + LevelList -> + {tree, list_to_tuple(LevelList), internal} + end. + +%%---------------------------------------------------------------------- +%% Purpose: Deletes all nodes associated to MibName from this level and +%% all levels below. +%% If the new level does not contain information (that is, no +%% other mibs use it) anymore the empty list is returned. +%% Returns: {MEs, The new level represented as a list} +%%---------------------------------------------------------------------- +delete_tree(Tree, MibName) when is_tuple(Tree) -> + NewLevel = delete_nodes(tuple_to_list(Tree), MibName, []), + case lists:filter(fun drop_undefined_nodes/1,NewLevel) of + [] -> []; + _A_perhaps_shorted_list -> + NewLevel % some other mib needs this level + end. + +%%---------------------------------------------------------------------- +%% Purpose: Nodes belonging to MibName are removed from the tree. +%% Recursively deletes sub trees to this node. +%% Returns: {MEs, NewNodesList} +%%---------------------------------------------------------------------- +delete_nodes([], _MibName, AccNodes) -> + lists:reverse(AccNodes); + +delete_nodes([{node, {variable, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{node, {table_column, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{tree, _Tree, {table, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{tree, _Tree, {table_entry, MibName}}|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [undefined_node | AccNodes]); + +delete_nodes([{tree, Tree, Info}|T], MibName, AccNodes) -> + case delete_tree(Tree, MibName) of + [] -> % tree completely deleted + delete_nodes(T, MibName, [undefined_node | AccNodes]); + LevelList -> + delete_nodes(T, MibName, + [{tree, list_to_tuple(LevelList), Info} | AccNodes]) + end; + +delete_nodes([NodeToKeep|T], MibName, AccNodes) -> + delete_nodes(T, MibName, [NodeToKeep | AccNodes]). + +drop_undefined_nodes(undefined_node) -> false; +drop_undefined_nodes(_) -> true. + + +%%%====================================================================== +%%% 6. Functions for subagent handling +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Returns: A new Root|{error, reason} +%%---------------------------------------------------------------------- +insert_subagent(Oid, OldRoot) -> + ListTree = build_tree_for_subagent(Oid), + case catch convert_tree(ListTree) of + {'EXIT', _Reason} -> + {error, 'cannot construct tree from oid'}; + Level when is_tuple(Level) -> + T = {tree, Level, internal}, + case catch merge_nodes(T, OldRoot) of + {error_merge_nodes, _Node1, _Node2} -> + {error, oid_conflict}; + NewRoot when is_tuple(NewRoot) andalso + (element(1, NewRoot) =:= tree) -> + NewRoot + end + end. + +build_tree_for_subagent([Index]) -> + [{Index, {node, subagent}}]; + +build_tree_for_subagent([Index | T]) -> + [{Index, {tree, build_tree_for_subagent(T), internal}}]. + +%%---------------------------------------------------------------------- +%% Returns: A new tree where the subagent at Oid (2nd arg) has been deleted. +%%---------------------------------------------------------------------- +delete_subagent({tree, Tree, Info}, [Index]) -> + {node, subagent} = element(Index+1, Tree), + {tree, setelement(Index+1, Tree, undefined_node), Info}; +delete_subagent({tree, Tree, Info}, [Index | TI]) -> + {tree, setelement(Index+1, Tree, + delete_subagent(element(Index+1, Tree), TI)), Info}. + +%%%====================================================================== +%%% 7. Misc functions +%%%====================================================================== + +%%---------------------------------------------------------------------- +%% Installs the mibs found in the database when starting the agent. +%% Basically calls the instrumentation functions for all non-internal +%% mib-entries +%%---------------------------------------------------------------------- +install_mibs(MibDb, NodeDb) -> + MibNames = loaded(MibDb), + ?vtrace("install_mibs -> found following mibs in database: ~n" + "~p", [MibNames]), + install_mibs2(NodeDb, MibNames). + +install_mibs2(_, []) -> + ok; +install_mibs2(NodeDb, [MibName|MibNames]) -> + Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'}, + Nodes = snmpa_general_db:match_object(NodeDb, Pattern), + MEs = [ME || #node_info{me = ME} <- Nodes], + ?vtrace("install_mibs2 -> installing ~p MEs for mib ~p", + [length(MEs),MibName]), + NewF = fun(ME) -> call_instrumentation(ME, new) end, + lists:foreach(NewF, MEs), + install_mibs2(NodeDb, MibNames). + + +%%---------------------------------------------------------------------- +%% Does all side effect stuff during load_mib. +%%---------------------------------------------------------------------- +install_mib(Db, Symbolic, Mib, MibName, FileName, NonInternalMes) -> + ?vdebug("install_mib -> entry with" + "~n Symbolic: ~p" + "~n MibName: ~p" + "~n FileName: ~p", [Symbolic, MibName, FileName]), + Rec = #mib_info{name = MibName, symbolic = Symbolic, file_name = FileName}, + snmpa_general_db:write(Db, Rec), + install_mib2(Symbolic, MibName, Mib), + NewF = fun(ME) -> call_instrumentation(ME, new) end, + lists:foreach(NewF, NonInternalMes). + +install_mib2(true, MibName, Mib) -> + #mib{table_infos = TabInfos, + variable_infos = VarInfos, + mes = MEs, + asn1_types = ASN1Types, + traps = Traps} = Mib, + snmpa_symbolic_store:add_table_infos(MibName, TabInfos), + snmpa_symbolic_store:add_variable_infos(MibName, VarInfos), + snmpa_symbolic_store:add_aliasnames(MibName, MEs), + snmpa_symbolic_store:add_types(MibName, ASN1Types), + SetF = fun(Trap) -> + snmpa_symbolic_store:set_notification(Trap, MibName) + end, + lists:foreach(SetF, Traps); +install_mib2(_, _, _) -> + ok. + +install_mes(_Db, _MibName, []) -> + ok; +install_mes(Db, MibName, [ME|MEs]) -> + Node = #node_info{oid = ME#me.oid, mib_name = MibName, me = ME}, + snmpa_general_db:write(Db, Node), + install_mes(Db, MibName, MEs). + + +%%---------------------------------------------------------------------- +%% Does all side effect stuff during unload_mib. +%%---------------------------------------------------------------------- +uninstall_mib(Db, Symbolic, MibName, MEs) -> + ?vtrace("uninstall_mib -> entry with" + "~n Db: ~p" + "~n Symbolic: ~p" + "~n MibName: ~p", [Db, Symbolic, MibName]), + Res = snmpa_general_db:delete(Db, MibName), + ?vtrace("uninstall_mib -> (mib) db delete result: ~p", [Res]), + uninstall_mib2(Symbolic, MibName), + DelF = fun(ME) -> call_instrumentation(ME, delete) end, + lists:foreach(DelF, MEs). + +uninstall_mib2(true, MibName) -> + snmpa_symbolic_store:delete_table_infos(MibName), + snmpa_symbolic_store:delete_variable_infos(MibName), + snmpa_symbolic_store:delete_aliasnames(MibName), + snmpa_symbolic_store:delete_types(MibName), + snmpa_symbolic_store:delete_notifications(MibName); +uninstall_mib2(_, _) -> + ok. + +uninstall_mes(Db, MibName) -> + Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'}, + snmpa_general_db:match_delete(Db, Pattern). + + +%%---------------------------------------------------------------------- +%% Create a list of the names of all the loaded mibs +%%---------------------------------------------------------------------- +loaded(Db) -> + [N || #mib_info{name = N} <- snmpa_general_db:tab2list(Db)]. + + +%%---------------------------------------------------------------------- +%% Calls MFA-instrumentation with 'new' or 'delete' operation. +%%---------------------------------------------------------------------- +call_instrumentation(#me{entrytype = variable, mfa={M,F,A}}, Operation) -> + ?vtrace("call instrumentation with" + "~n entrytype: variable" + "~n MFA: {~p,~p,~p}" + "~n Operation: ~p", + [M,F,A,Operation]), + catch apply(M, F, [Operation | A]); +call_instrumentation(#me{entrytype = table_entry, mfa={M,F,A}}, Operation) -> + ?vtrace("call instrumentation with" + "~n entrytype: table_entry" + "~n MFA: {~p,~p,~p}" + "~n Operation: ~p", + [M,F,A,Operation]), + catch apply(M, F, [Operation | A]); +call_instrumentation(_ShitME, _Operation) -> + done. + + +maybe_drop_me(#me{entrytype = internal}) -> false; +maybe_drop_me(#me{entrytype = group}) -> false; +maybe_drop_me(#me{imported = true}) -> false; +maybe_drop_me(_) -> true. + + +%%---------------------------------------------------------------------- +%% Code change functions +%%---------------------------------------------------------------------- + +code_change(down, State) -> + ?d("code_change(down) -> entry",[]), + State; + +code_change(up, State) -> + ?d("code_change(up)",[]), + State; + +code_change(_Vsn, State) -> + State. + diff --git a/lib/snmp/src/agent/snmpa_mib_lib.erl b/lib/snmp/src/agent/snmpa_mib_lib.erl new file mode 100644 index 0000000000..441228b9ee --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mib_lib.erl @@ -0,0 +1,207 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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_mib_lib). + +-export([table_cre_row/3, table_del_row/2]). +-export([get_table/2, print_table/3, print_table/4, print_tables/1]). +-export([gc_tab/3, gc_tab/5]). + +-include("SNMPv2-TC.hrl"). +-include("snmp_types.hrl"). + +-define(VMODULE,"MIB-LIB"). +-include("snmp_verbosity.hrl"). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + + +%%%----------------------------------------------------------------- +%%%----------------------------------------------------------------- + +%% returns: bool() +table_cre_row({Tab, mnesia}, Key, _Row) -> + ?vtrace("create mnesia table ~w row with Key: ~w",[Tab, Key]), + {error, mnesia_not_supported}; +table_cre_row({Tab, Db} = TabDb, Key, Row) -> + ?vtrace("create ~w table ~w row with Key: ~w",[Db, Tab, Key]), + snmpa_local_db:table_create_row(TabDb, Key, Row). + +%% returns: bool() +table_del_row({Tab, mnesia}, Key) -> + ?vtrace("delete mnesia table ~w row with Key: ~w",[Tab, Key]), + {error, mnesia_not_supported}; +table_del_row({Tab, Db} = TabDb, Key) -> + ?vtrace("delete ~w table ~w row with Key: ~w", [Db, Tab, Key]), + snmpa_local_db:table_delete_row(TabDb, Key). + + +%%%----------------------------------------------------------------- +%%% Retreives the entire table. Used for debugging +%%%----------------------------------------------------------------- + +get_table(NameDb, FOI) -> + (catch get_table(NameDb, FOI, [], [])). + +get_table(NameDb, FOI, Oid, Acc) -> + case table_next(NameDb, Oid) of + endOfTable -> + ?vdebug("end of table",[]), + {ok, lists:reverse(Acc)}; + Oid -> + %% Crap, circular ref + ?vinfo("cyclic reference: ~w -> ~w", [Oid,Oid]), + throw({error, {cyclic_db_reference, Oid, Acc}}); + NextOid -> + ?vtrace("get row for oid ~w", [NextOid]), + case table_get_row(NameDb, NextOid, FOI) of + undefined -> + throw({error, {invalid_rowindex, NextOid, Acc}}); + Row -> + ?vtrace("row: ~w", [Row]), + get_table(NameDb, FOI, NextOid, [{NextOid, Row}|Acc]) + end + end. + + +print_tables(Tables) when is_list(Tables) -> + lists:foreach(fun({Table, DB, FOI, PrintRow}) -> + print_table(Table, DB, FOI, PrintRow) + end, Tables), + ok. + +%% print_table(Table, DB, FOI, PrintRow) -> +%% TableInfo = get_table(DB(Table), FOI(Table)), +%% print_table(Table, TableInfo, PrintRow), +%% ok. + +print_table(Table, DB, FOI, PrintRow) -> + TableInfo = get_table(DB, FOI), + print_table(Table, TableInfo, PrintRow). + +print_table(Table, TableInfo, PrintRow) when is_function(PrintRow, 2) -> + io:format("~w => ~n", [Table]), + do_print_table(TableInfo, PrintRow). + +do_print_table({ok, TableInfo}, PrintRow) when is_function(PrintRow, 2) -> + lists:foreach(fun({RowIdx, Row}) -> + io:format(" ~w => ~n~s~n", + [RowIdx, PrintRow(" ", Row)]) + end, TableInfo), + io:format("~n", []); +do_print_table({error, {invalid_rowindex, BadRowIndex, []}}, _PrintRow) -> + io:format("Error: Bad rowindex ~w~n", [BadRowIndex]); +do_print_table({error, {invalid_rowindex, BadRowIndex, TableInfo}}, PrintRow) -> + io:format("Error: Bad rowindex ~w", [BadRowIndex]), + do_print_table(TableInfo, PrintRow); +do_print_table(Error, _PrintRow) -> + io:format("Error: ~p~n", [Error]). + + +%%%----------------------------------------------------------------- +%%% +%%%----------------------------------------------------------------- + +table_next({Name, mnesia}, RestOid) -> + snmp_generic_mnesia:table_next(Name, RestOid); +table_next(NameDb, RestOid) -> + snmpa_local_db:table_next(NameDb, RestOid). + + +table_get_row({Name, mnesia}, RowIndex) -> + snmp_generic_mnesia:table_get_row(Name, RowIndex); +table_get_row(NameDb, RowIndex) -> + snmpa_local_db:table_get_row(NameDb, RowIndex). + +table_get_row(NameDb, RowIndex, undefined) -> + table_get_row(NameDb, RowIndex); +table_get_row({Name, mnesia}, RowIndex, FOI) -> + snmp_generic_mnesia:table_get_row(Name, RowIndex, FOI); +table_get_row(NameDb, RowIndex, _FOI) -> + snmpa_local_db:table_get_row(NameDb, RowIndex). + + +%%%----------------------------------------------------------------- +%%% Utility module for the mib-implementation modules (such as +%%% snmp_target_mib). +%%%----------------------------------------------------------------- + +gc_tab(TabDb, STC, FOI) -> + InvalidateRow = fun(_) -> ok end, + UpdateRow = fun(_) -> ok end, + gc_tab(TabDb, STC, FOI, InvalidateRow, UpdateRow). + +gc_tab({Tab,mnesia} = TabDb, STC, FOI, InvalidateRow, UpdateRow) -> + F = fun(RowIndex, Row) -> + case element(STC, Row) of + ?'StorageType_volatile' -> + snmp_generic_mnesia:table_delete_row(Tab, RowIndex), + InvalidateRow(RowIndex); + _ -> + UpdateRow(RowIndex) + end + end, + gc_tab1(F, TabDb, FOI); + +gc_tab(TabDb, STC, FOI, InvalidateRow, UpdateRow) -> + F = fun(RowIndex, Row) -> + case element(STC, Row) of + ?'StorageType_volatile' -> + snmpa_local_db:table_delete_row(TabDb, RowIndex), + InvalidateRow(RowIndex); + _ -> + UpdateRow(RowIndex), + ok + end + end, + gc_tab1(F, TabDb, FOI). + + +gc_tab1(F, {Tab,_} = TabDb, FOI) -> + case (catch snmp_generic:table_foreach(TabDb, F, FOI)) of + {'EXIT',{cyclic_db_reference,Oid}} -> + %% Remove the row regardless of storage type since this + %% is a major error. This row must be removed. + case table_delete_row(TabDb, Oid) of + true -> + ?vlog("deleted cyclic ref row for: ~w;~w", + [Tab, Oid]), + config_err("cyclic reference in table ~w: " + "~w -> ~w. Row deleted", + [Tab, Oid, Oid]), + gc_tab1(F, TabDb, FOI); + false -> + ?vlog("unable to remove faulty row from table ~w", + [Tab]), + config_err("failed removing faulty row. " + "Giving up on table ~w cleanup", [Tab]) + end; + _ -> + ok + end. + +table_delete_row({Tab, mnesia}, Oid) -> + snmp_generic_mnesia:table_delete_row(Tab, Oid), + true; +table_delete_row(TabDb, Oid) -> + snmpa_local_db:table_delete_row(TabDb, Oid). + +config_err(F, A) -> + snmpa_error:config_err(F, A). diff --git a/lib/snmp/src/agent/snmpa_misc_sup.erl b/lib/snmp/src/agent/snmpa_misc_sup.erl new file mode 100644 index 0000000000..488d3f7921 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_misc_sup.erl @@ -0,0 +1,158 @@ +%% +%% %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_misc_sup). + +-include("snmp_debug.hrl"). + +-behaviour(supervisor). + +%% External exports +-export([ + start_link/0, + start_mib_server/4, stop_mib_server/1, + start_net_if/6, stop_net_if/1, + start_note_store/3, stop_note_store/1 + ]). + +%% Internal exports +-export([init/1]). + +-define(SERVER, ?MODULE). + + +%%%----------------------------------------------------------------- +%%% This is a supervisor for the mib and net_ifprocesses. +%%% Each agent has one mib process. +%%%----------------------------------------------------------------- + +start_link() -> + ?d("start_link -> entry", []), + supervisor:start_link({local, ?SERVER}, ?MODULE, []). + + +%%----------------------------------------------------------------- +%% When the agent starts, it calls this function. If there already +%% exist a mib process for the agent, this one is used. Otherwise +%% a new one is started. +%%----------------------------------------------------------------- +start_mib_server(Prio, Ref, Mibs, Opts) -> + ?d("start_mib_server -> entry with" + "~n Prio: ~p" + "~n Ref: ~p" + "~n Mibs: ~p" + "~n Opts: ~p", [Prio, Ref, Mibs, Opts]), + SupName = ?SERVER, + start_mibserver(SupName, Ref, [Prio, Mibs, Opts]). + +start_mibserver(SupName, Ref, Args) -> + Children = supervisor:which_children(SupName), + case lists:keysearch({mib, Ref}, 1, Children) of + {value, {_, Pid, _, _}} -> {ok, Pid}; + _ -> + Mib = {{mib, Ref}, + {snmpa_mib, start_link, Args}, + transient, 10000, worker, [snmpa_mib]}, + supervisor:start_child(SupName, Mib) + end. + +stop_mib_server(Ref) -> + SupName = ?SERVER, + case whereis(SupName) of + undefined -> + ok; + _ -> + supervisor:terminate_child(SupName, {mib, Ref}), + supervisor:delete_child(SupName, {mib, Ref}) + end. + + +start_net_if(Prio, NoteStore, Ref, Master, Mod, Opts) -> + ?d("start_mib -> entry with" + "~n Prio: ~p" + "~n NoteStore: ~p" + "~n Ref: ~p" + "~n Master: ~p" + "~n Mod: ~p" + "~n Opts: ~p", + [Prio, NoteStore, Ref, Master, Mod, Opts]), + SupName = ?SERVER, + start_netif(SupName, Ref, Mod, [Prio, NoteStore, Master, Opts]). + +start_netif(SupName, Ref, Mod, Args) -> + %% make sure we start from scratch... + Children = supervisor:which_children(SupName), + case lists:keysearch({net_if, Ref}, 1, Children) of + {value, {_, _Pid, _, _}} -> + stop_net_if(Ref); + _ -> + ok + end, + NetIf = {{net_if, Ref}, + {Mod, start_link, Args}, + permanent, 2000, worker, [Mod]}, + supervisor:start_child(SupName, NetIf). + +stop_net_if(Ref) -> + SupName = ?SERVER, + case whereis(SupName) of + undefined -> + ok; + _ -> + supervisor:terminate_child(SupName, {net_if, Ref}), + supervisor:delete_child(SupName, {net_if, Ref}) + end. + + +start_note_store(Prio, Ref, Opts) -> + ?d("start_note_store -> entry with" + "~n Prio: ~p" + "~n Ref: ~p" + "~n Opts: ~p", [Prio, Ref, Opts]), + SupName = ?SERVER, + start_notestore(SupName, Ref, [Prio, snmpa, Opts]). + +start_notestore(SupName, Ref, Args) -> + %% make sure we start from scratch... + Children = supervisor:which_children(SupName), + case lists:keysearch({note_store, Ref}, 1, Children) of + {value, {_, _Pid, _, _}} -> + stop_note_store(Ref); + _ -> + ok + end, + Mod = snmp_note_store, + Note = {{note_store, Ref}, + {Mod, start_link, Args}, + permanent, 2000, worker, [Mod]}, + supervisor:start_child(SupName, Note). + +stop_note_store(Ref) -> + SupName = ?SERVER, + case whereis(SupName) of + undefined -> + ok; + _ -> + supervisor:terminate_child(SupName, {note_store, Ref}), + supervisor:delete_child(SupName, {note_store, Ref}) + end. + + +init([]) -> + SupFlags = {one_for_all, 0, 3600}, + {ok, {SupFlags, []}}. diff --git a/lib/snmp/src/agent/snmpa_mpd.erl b/lib/snmp/src/agent/snmpa_mpd.erl new file mode 100644 index 0000000000..2e09286b87 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_mpd.erl @@ -0,0 +1,1386 @@ +%% +%% %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_mpd). + +-export([init/1, reset/0, inc/1, counters/0, + discarded_pdu/1, + process_packet/6, + generate_response_msg/5, generate_msg/5, + generate_discovery_msg/4, + process_taddrs/1, + generate_req_id/0]). + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). +-include("SNMP-MPD-MIB.hrl"). +-include("SNMPv2-TM.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). + +-define(VMODULE,"MPD"). +-include("snmp_verbosity.hrl"). + +-define(empty_msg_size, 24). + +-record(state, {v1 = false, v2c = false, v3 = false}). +-record(note, {sec_engine_id, + sec_model, + sec_name, + sec_level, + ctx_engine_id, + ctx_name, + disco = false, + req_id}). + + +%%%----------------------------------------------------------------- +%%% This module implemets the Message Processing and Dispatch part of +%%% the multi-lingual SNMP agent. +%%% +%%% The MPD is responsible for: +%%% *) call the security module (auth/priv). +%%% *) decoding the message into a PDU. +%%% *) decide a suitable Access Control Model, and provide it with +%%% the data it needs. +%%% *) maintaining SNMP counters. +%%% +%%% In order to take care of the different versions of counters, it +%%% implements and maintains the union of all SNMP counters (i.e. from +%%% rfc1213 and from rfc1907). It is up to the administrator of the +%%% agent to load the correct MIB. Note that this module implements +%%% the counters only, it does not provide instrumentation functions +%%% for the counters. +%%% +%%% With the terms defined in rfc2271, this module implememts part +%%% of the Dispatcher and the Message Processing functionality. +%%%----------------------------------------------------------------- +init(Vsns) -> + ?vlog("init -> entry with" + "~n Vsns: ~p", [Vsns]), + {A,B,C} = erlang:now(), + random:seed(A,B,C), + ets:insert(snmp_agent_table, {msg_id, random:uniform(2147483647)}), + ets:insert(snmp_agent_table, {req_id, random:uniform(2147483647)}), + init_counters(), + init_versions(Vsns, #state{}). + + +reset() -> + reset_counters(), + ok. + + +%%----------------------------------------------------------------- +%% Purpose: We must calculate the length of a +%% message with an empty Pdu, and zero-length community +%% string. This length is used to calculate the max +%% pdu size allowed for each request. This size is +%% dependent on two dynamic fields, the community string +%% and the pdu (varbinds actually). It is calculated +%% as EmptySize + length(CommunityString) + 4. +%% We assume that the length of the CommunityString is +%% less than 128 (thus requiring just one octet for the +%% length field (the same as the zero-length community +%% string)). 4 comes from the fact that the maximum pdu +%% size needs 31 bits which needs 5 * 7 bits to be +%% expressed. One 7bit octet is already present in the +%% empty msg, leaving 4 more 7bit octets. +%% Actually, this function is not used, we use a constant instead. +%%----------------------------------------------------------------- +%% Ret: 24 +%empty_msg() -> +% M = #message{version = 'version-1', community = "", data = +% #pdu{type = 'get-response', request_id = 1, +% error_status = noError, error_index = 0, varbinds = []}}, +% length(snmp_pdus:enc_message(M)) + 4. + +%%----------------------------------------------------------------- +%% Func: process_packet(Packet, TDomain, TAddress, State, Log) -> +%% {ok, SnmpVsn, Pdu, PduMS, ACMData} | {discarded, Reason} +%% Types: Packet = binary() +%% TDomain = snmpUDPDomain | atom() +%% TAddress = {Ip, Udp} +%% State = #state +%% Purpose: This is the main Message Dispatching function. (see +%% section 4.2.1 in rfc2272) +%%----------------------------------------------------------------- +process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) -> + inc(snmpInPkts), + case catch snmp_pdus:dec_message_only(binary_to_list(Packet)) of + + #message{version = 'version-1', vsn_hdr = Community, data = Data} + when State#state.v1 =:= true -> + ?vlog("v1, community: ~s", [Community]), + HS = ?empty_msg_size + length(Community), + v1_v2c_proc('version-1', NoteStore, Community, TDomain, TAddress, + Data, HS, Log, Packet); + + #message{version = 'version-2', vsn_hdr = Community, data = Data} + when State#state.v2c =:= true -> + ?vlog("v2c, community: ~s", [Community]), + HS = ?empty_msg_size + length(Community), + v1_v2c_proc('version-2', NoteStore, Community, TDomain, TAddress, + Data, HS, Log, Packet); + + #message{version = 'version-3', vsn_hdr = V3Hdr, data = Data} + when State#state.v3 =:= true -> + ?vlog("v3, msgID: ~p, msgFlags: ~p, msgSecModel: ~p", + [V3Hdr#v3_hdr.msgID, + V3Hdr#v3_hdr.msgFlags, + V3Hdr#v3_hdr.msgSecurityModel]), + v3_proc(NoteStore, Packet, TDomain, TAddress, V3Hdr, Data, Log); + + {'EXIT', {bad_version, Vsn}} -> + ?vtrace("exit: bad version: ~p",[Vsn]), + inc(snmpInBadVersions), + {discarded, snmpInBadVersions}; + + {'EXIT', Reason} -> + ?vtrace("exit: ~p", [Reason]), + inc(snmpInASNParseErrs), + {discarded, Reason}; + + UnknownMessage -> + ?vtrace("Unknown message: ~n ~p" + "~nwhen" + "~n State: ~p", [UnknownMessage, State]), + inc(snmpInBadVersions), + {discarded, snmpInBadVersions} + end. + +discarded_pdu(false) -> ok; +discarded_pdu(Variable) -> inc(Variable). + + +%%----------------------------------------------------------------- +%% Handles a Community based message (v1 or v2c). +%%----------------------------------------------------------------- +v1_v2c_proc(Vsn, NoteStore, Community, snmpUDPDomain, {Ip, Udp}, + Data, HS, Log, Packet) -> + TAddress = tuple_to_list(Ip) ++ [Udp div 256, Udp rem 256], + AgentMS = snmp_framework_mib:get_engine_max_message_size(), + MgrMS = snmp_community_mib:get_target_addr_ext_mms(?snmpUDPDomain, + TAddress), + PduMS = case MgrMS of + {ok, MMS} when MMS < AgentMS -> MMS - HS; + _ -> AgentMS - HS + end, + case (catch snmp_pdus:dec_pdu(Data)) of + Pdu when is_record(Pdu, pdu) -> + Log(Pdu#pdu.type, Packet), + inc_snmp_in_vars(Pdu), + #pdu{request_id = ReqId} = Pdu, + OkRes = {ok, Vsn, Pdu, PduMS, + {community, sec_model(Vsn), Community, TAddress}}, + %% Make sure that we don't process duplicate SET request + %% twice. We don't know what could happen in that case. + %% The mgr does, so he has to generate a new SET request. + ?vdebug("PDU type: ~p", [Pdu#pdu.type]), + case Pdu#pdu.type of + 'set-request' -> + %% Check if this message has already been processed + Key = {agent, Ip, ReqId}, + case snmp_note_store:get_note(NoteStore, Key) of + undefined -> + %% Set the processed note _after_ pdu processing. + %% This makes duplicated requests be ignored even + %% if pdu processing took long time. + snmp_note_store:set_note(NoteStore, + 100, Key, true), + %% Uses ACMData that snmpa_acm knows of. + %% snmpUDPDomain is implicit, since that's the only + %% one we handle. + OkRes; + true -> + {discarded, duplicate_pdu} + end; + _ -> + OkRes + end; + {'EXIT', Reason} -> + ?vtrace("PDU decode exit: ~p",[Reason]), + inc(snmpInASNParseErrs), + {discarded, Reason}; + _TrapPdu -> + {discarded, trap_pdu} + end; +v1_v2c_proc(_Vsn, _NoteStore, _Community, snmpUDPDomain, TAddress, + _Data, _HS, _Log, _Packet) -> + {discarded, {badarg, TAddress}}; +v1_v2c_proc(_Vsn, _NoteStore, _Community, TDomain, _TAddress, + _Data, _HS, _Log, _Packet) -> + {discarded, {badarg, TDomain}}. + +sec_model('version-1') -> ?SEC_V1; +sec_model('version-2') -> ?SEC_V2C. + + +%%----------------------------------------------------------------- +%% Handles a SNMPv3 Message, following the procedures in rfc2272, +%% section 4.2 and 7.2 +%%----------------------------------------------------------------- +v3_proc(NoteStore, Packet, _TDomain, _TAddress, V3Hdr, Data, Log) -> + case (catch v3_proc(NoteStore, Packet, V3Hdr, Data, Log)) of + {'EXIT', Reason} -> + exit(Reason); + Result -> + Result + end. + +v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> + %% 7.2.3 + #v3_hdr{msgID = MsgID, + msgMaxSize = MMS, + msgFlags = MsgFlags, + msgSecurityModel = MsgSecurityModel, + msgSecurityParameters = SecParams, + hdr_size = HdrSize} = V3Hdr, + ?vdebug("v3_proc -> version 3 message header:" + "~n msgID = ~p" + "~n msgMaxSize = ~p" + "~n msgFlags = ~p" + "~n msgSecurityModel = ~p" + "~n msgSecurityParameters = ~w", + [MsgID, MMS, MsgFlags, MsgSecurityModel, SecParams]), + %% 7.2.4 + SecModule = get_security_module(MsgSecurityModel), + %% 7.2.5 + SecLevel = check_sec_level(MsgFlags), + IsReportable = snmp_misc:is_reportable(MsgFlags), + %% 7.2.6 + ?vtrace("v3_proc -> " + "~n SecModule = ~p" + "~n SecLevel = ~p" + "~n IsReportable = ~p", + [SecModule,SecLevel,IsReportable]), + SecRes = (catch SecModule:process_incoming_msg(Packet, Data, + SecParams, SecLevel)), + ?vtrace("v3_proc -> message processing result: " + "~n SecRes: ~p", [SecRes]), + {SecEngineID, SecName, ScopedPDUBytes, SecData, DiscoOrPlain} = + check_sec_module_result(SecRes, V3Hdr, Data, IsReportable, Log), + ?vtrace("v3_proc -> " + "~n DiscoOrPlain: ~w" + "~n SecEngineID: ~w" + "~n SecName: ~p", [DiscoOrPlain, SecEngineID, SecName]), + %% 7.2.7 + #scopedPdu{contextEngineID = ContextEngineID, + contextName = ContextName, + data = PDU} = + case (catch snmp_pdus:dec_scoped_pdu(ScopedPDUBytes)) of + ScopedPDU when is_record(ScopedPDU, scopedPdu) -> + ?vtrace("v3_proc -> message processing result: " + "~n ScopedPDU: ~p", [ScopedPDU]), + ScopedPDU; + {'EXIT', Reason} -> + inc(snmpInASNParseErrs), + throw({discarded, Reason}) + end, + %% We'll have to take care of the unlikely case that we receive an + %% v1 trappdu in a v3 message explicitly... + if + is_record(PDU, trappdu) -> + inc(snmpUnknownPDUHandlers), + throw({discarded, received_v1_trap}); + true -> + ok + end, + ?vlog("7.2.7 result: " + "~n contextEngineID: ~w" + "~n ContextName: \"~s\"", [ContextEngineID, ContextName]), + if + SecLevel =:= ?'SnmpSecurityLevel_authPriv' -> + %% encrypted message - log decrypted pdu + Log(PDU#pdu.type, {V3Hdr, ScopedPDUBytes}); + true -> % otherwise, log binary + Log(PDU#pdu.type, Packet) + end, + %% Make sure a get_bulk doesn't get too big. + AgentMS = snmp_framework_mib:get_engine_max_message_size(), + %% PduMMS is supposed to be the maximum total length of the response + %% PDU we can send. From the MMS, we need to subtract everything before + %% the PDU, i.e. Message and ScopedPDU. + %% Message: [48, TotalLen, Vsn, [Tag, LH, Hdr], [Tag, LM, MsgSec], Data] + %% 1 3 <----------- HdrSize -----------> + %% HdrSize = everything up to and including msgSecurityParameters. + %% ScopedPduData follows. This is + %% [Tag, Len, [Tag, L1, CtxName], [Tag, L2, CtxEID]] + %% i.e. 6 + length(CtxName) + length(CtxEID) + %% + %% Total: 1 + TotalLenOctets + 3 + ScopedPduDataLen + TotMMS = if AgentMS > MMS -> MMS; + true -> AgentMS + end, + TotalLenOctets = snmp_pdus:get_encoded_length(TotMMS - 1), + PduMMS = TotMMS - TotalLenOctets - 10 - HdrSize - + length(ContextName) - length(ContextEngineID), + ?vdebug("v3_proc -> PDU type: ~p", [PDU#pdu.type]), + case PDU#pdu.type of + report when DiscoOrPlain =:= discovery -> + %% Discovery stage 1 response + Key = {agent, MsgID}, + Note = snmp_note_store:get_note(NoteStore, Key), + case Note of + #note{sec_engine_id = "", + sec_model = _MsgSecModel, + sec_name = "", + sec_level = _SecLevel, + ctx_engine_id = _CtxEngineID, + ctx_name = _CtxName, + disco = true, + req_id = _ReqId} -> + %% This is part of the discovery process initiated by us. + %% Response to the discovery stage 1 request + ?vdebug("v3_proc -> discovery stage 1 response", []), + {ok, 'version-3', PDU, PduMMS, {discovery, SecEngineID}}; + #note{sec_engine_id = SecEngineID, + sec_model = _MsgSecModel, + sec_name = SecName, + sec_level = SecLevel, + ctx_engine_id = _CtxEngineID, + ctx_name = _CtxName, + disco = true, + req_id = _ReqId} -> + %% This is part of the discovery process initiated by us. + %% Response to the discovery stage 2 request + ?vdebug("v3_proc -> discovery stage 2 response", []), + {ok, 'version-3', PDU, PduMMS, discovery}; + _ -> + %% 7.2.11 + DiscardReason = {bad_disco_note, Key, Note}, + throw({discarded, DiscardReason}) + end; + report -> + %% 7.2.11 + throw({discarded, report}); + 'get-response' -> %% As a result of a sent inform-request? + %% 7.2.12 + Key = {agent, MsgID}, + Note = snmp_note_store:get_note(NoteStore, Key), + case Note of + #note{sec_engine_id = "", + sec_model = _MsgSecModel, + sec_name = "", + sec_level = _SecLevel, + ctx_engine_id = _CtxEngineID, + ctx_name = _CtxName, + disco = true, + req_id = _ReqId} -> + %% This is part of the discovery process initiated by us. + %% Response to the discovery stage 1 request + ?vdebug("v3_proc -> discovery stage 1 response", []), + {ok, 'version-3', PDU, PduMMS, {discovery, SecEngineID}}; + #note{sec_engine_id = SecEngineID, + sec_model = _MsgSecModel, + sec_name = SecName, + sec_level = SecLevel, + ctx_engine_id = _CtxEngineID, + ctx_name = _CtxName, + disco = true, + req_id = _ReqId} -> + %% This is part of the discovery process initiated by us. + %% Response to the discovery stage 2 request + ?vdebug("v3_proc -> discovery stage 2 response", []), + {ok, 'version-3', PDU, PduMMS, discovery}; + #note{sec_engine_id = SecEngineID, + sec_model = MsgSecurityModel, + sec_name = SecName, + sec_level = SecLevel, + ctx_engine_id = ContextEngineID, + ctx_name = ContextName, + disco = false, + req_id = _ReqId} -> + {ok, 'version-3', PDU, PduMMS, undefined}; + _ -> + inc(snmpUnknownPDUHandlers), + throw({discarded, {no_outstanding_req, MsgID}}) + end; + 'snmpv2-trap' -> + inc(snmpUnknownPDUHandlers), + throw({discarded, received_v2_trap}); + Type -> + %% 7.2.13 + SnmpEngineID = snmp_framework_mib:get_engine_id(), + ?vtrace("v3_proc -> SnmpEngineID = ~w", [SnmpEngineID]), + case SecEngineID of + SnmpEngineID when (DiscoOrPlain =:= discovery) -> + %% This is a discovery step 2 message! + ?vtrace("v3_proc -> discovery stage 2", []), + generate_discovery2_report_msg(MsgID, + MsgSecurityModel, + SecName, + SecLevel, + ContextEngineID, + ContextName, + SecData, + PDU, + Log); + + SnmpEngineID when (DiscoOrPlain =:= plain) -> + %% 4.2.2.1.1 - we don't handle proxys yet => we only + %% handle ContextEngineID to ourselves + case ContextEngineID of + SnmpEngineID -> + %% Uses ACMData that snmpa_acm knows of. + {ok, 'version-3', PDU, PduMMS, + {v3, MsgID, MsgSecurityModel, SecName, SecLevel, + ContextEngineID, ContextName, SecData}}; + _ -> + %% 4.2.2.1.2 + NIsReportable = snmp_misc:is_reportable_pdu(Type), + Val = inc(snmpUnknownPDUHandlers), + ErrorInfo = {#varbind{oid = ?snmpUnknownPDUHandlers, + variabletype = 'Counter32', + value = Val}, + SecName, + [{securityLevel, SecLevel}, + {contextEngineID, ContextEngineID}, + {contextName, ContextName}]}, + case generate_v3_report_msg(MsgID, + MsgSecurityModel, + Data, ErrorInfo, + Log) of + {ok, Report} when NIsReportable =:= true -> + {discarded, snmpUnknownPDUHandlers, Report}; + _ -> + {discarded, snmpUnknownPDUHandlers} + end + end; + + "" -> + %% This is a discovery step 1 message!! + ?vtrace("v3_proc -> discovery step 1", []), + generate_discovery1_report_msg(MsgID, + MsgSecurityModel, + SecName, + SecLevel, + ContextEngineID, + ContextName, + SecData, + PDU, + Log); + + _ -> + {discarded, {badSecurityEngineID, SecEngineID}} + end + end. + + +get_security_module(?SEC_USM) -> + snmpa_usm; +get_security_module(_) -> + inc(snmpUnknownSecurityModels), + throw({discarded, snmpUnknownSecurityModels}). + +check_sec_level([MsgFlag]) -> + SecLevel = MsgFlag band 3, + if + SecLevel == 2 -> + inc(snmpInvalidMsgs), + throw({discarded, snmpInvalidMsgs}); + true -> + SecLevel + end; +check_sec_level(Unknown) -> + ?vlog("invalid msgFlags: ~p",[Unknown]), + inc(snmpInvalidMsgs), + throw({discarded, snmpInvalidMsgs}). + +check_sec_module_result(Res, V3Hdr, Data, IsReportable, Log) -> + case Res of + {ok, X} -> + X; + {error, Reason, []} -> % case 7.2.6 b + ?vdebug("security module result [7.2.6-b]:" + "~n Reason: ~p", [Reason]), + throw({discarded, {securityError, Reason}}); + {error, Reason, ErrorInfo} when IsReportable == true -> % case 7.2.6 a + ?vdebug("security module result when reportable [7.2.6-a]:" + "~n Reason: ~p" + "~n ErrorInfo: ~p", [Reason, ErrorInfo]), + #v3_hdr{msgID = MsgID, msgSecurityModel = MsgSecModel} = V3Hdr, + Pdu = get_scoped_pdu(Data), + case generate_v3_report_msg(MsgID, MsgSecModel, Pdu, + ErrorInfo, Log) of + {ok, Report} -> + throw({discarded, {securityError, Reason}, Report}); + {discarded, _SomeOtherReason} -> + throw({discarded, {securityError, Reason}}) + end; + {error, Reason, ErrorInfo} -> + ?vdebug("security module result when not reportable:" + "~n Reason: ~p" + "~n ErrorInfo: ~p", [Reason, ErrorInfo]), + throw({discarded, {securityError, Reason}}); + Else -> + ?vdebug("security module result:" + "~n Else: ~p", [Else]), + throw({discarded, {securityError, Else}}) + end. + +get_scoped_pdu(D) when is_list(D) -> + (catch snmp_pdus:dec_scoped_pdu(D)); +get_scoped_pdu(D) -> + D. + + +%%----------------------------------------------------------------- +%% Executed when a response or report message is generated. +%%----------------------------------------------------------------- +generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> + generate_response_msg(Vsn, RePdu, Type, ACMData, Log, 1). + +generate_response_msg(Vsn, RePdu, Type, + {community, _SecModel, Community, _IpUdp}, + Log, _) -> + case catch snmp_pdus:enc_pdu(RePdu) of + {'EXIT', Reason} -> + user_err("failed encoding pdu: " + "(pdu: ~w, community: ~w): ~n~w", + [RePdu, Community, Reason]), + {discarded, Reason}; + PduBytes -> + Message = #message{version = Vsn, vsn_hdr = Community, + data = PduBytes}, + case catch list_to_binary( + snmp_pdus:enc_message_only(Message)) of + {'EXIT', Reason} -> + user_err("failed encoding message only " + "(pdu: ~w, community: ~w): ~n~w", + [RePdu, Community, Reason]), + {discarded, Reason}; + Packet -> + MMS = snmp_framework_mib:get_engine_max_message_size(), + case size(Packet) of + Len when Len =< MMS -> + Log(Type, Packet), + inc_snmp_cnt_vars(Type, RePdu), + inc_snmp_out_vars(RePdu), + {ok, Packet}; + Len -> + ?vlog("pdu to big:" + "~n Max message size: ~p" + "~n Encoded message size: ~p", + [MMS,Len]), + too_big(Vsn, RePdu, Community, Log, MMS, Len) + end + end + end; +generate_response_msg(Vsn, RePdu, Type, + {v3, MsgID, MsgSecurityModel, SecName, SecLevel, + ContextEngineID, ContextName, SecData}, + Log, N) -> + %% rfc2272: 7.1 steps 6-8 + ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID, + contextName = ContextName, + data = RePdu}, + case catch snmp_pdus:enc_scoped_pdu(ScopedPDU) of + {'EXIT', Reason} -> + user_err("failed encoded scoped pdu " + "(pdu: ~w, contextName: ~w): ~n~w", + [RePdu, ContextName, Reason]), + {discarded, Reason}; + ScopedPDUBytes -> + AgentMS = snmp_framework_mib:get_engine_max_message_size(), + V3Hdr = #v3_hdr{msgID = MsgID, + msgMaxSize = AgentMS, + msgFlags = snmp_misc:mk_msg_flags(Type, SecLevel), + msgSecurityModel = MsgSecurityModel}, + Message = #message{version = Vsn, + vsn_hdr = V3Hdr, + data = ScopedPDUBytes}, + %% We know that the security model is valid when we + %% generate a response. + SecModule = + case MsgSecurityModel of + ?SEC_USM -> + snmpa_usm + end, + SecEngineID = snmp_framework_mib:get_engine_id(), + ?vtrace("generate_response_msg -> SecEngineID: ~w", [SecEngineID]), + case (catch SecModule:generate_outgoing_msg(Message, + SecEngineID, + SecName, + SecData, + SecLevel)) of + {'EXIT', Reason} -> + config_err("~p (message: ~p)", [Reason, Message]), + {discarded, Reason}; + {error, Reason} -> + config_err("~p (message: ~p)", [Reason, Message]), + {discarded, Reason}; + OutMsg when is_list(OutMsg) -> + %% Check the packet size. Send the msg even + %% if it's larger than the mgr can handle - it + %% will be dropped. Just check against the + %% internal size. For GET-BULk responses: we + %% *know* that we're within the right limits, + %% because of the calculation we do when we + %% receive the bulk-request. + Packet = list_to_binary(OutMsg), + case size(Packet) of + Len when Len =< AgentMS -> + if + SecLevel =:= 3 -> + %% encrypted - log decrypted pdu + Log(Type, {V3Hdr, ScopedPDUBytes}); + true -> + %% otherwise log the entire msg + Log(Type, Packet) + end, + inc_snmp_cnt_vars(Type, RePdu), + inc_snmp_out_vars(RePdu), + {ok, Packet}; + Len when N =:= 2 -> + ?vlog("packet max size exceeded: " + "~n Max: ~p" + "~n Len: ~p", + [AgentMS,Len]), + inc(snmpSilentDrops), + {discarded, tooBig}; + Len -> + ?vlog("packet max size exceeded: " + "~n N: ~p" + "~n Max: ~p" + "~n Len: ~p", + [N, AgentMS, Len]), + TooBigPdu = RePdu#pdu{error_status = tooBig, + error_index = 0, + varbinds = []}, + generate_response_msg(Vsn, TooBigPdu, Type, + {v3, MsgID, + MsgSecurityModel, + SecName, SecLevel, + ContextEngineID, + ContextName, + SecData}, Log, N+1) + end + end + end. + +generate_v3_report_msg(MsgID, MsgSecurityModel, Data, ErrorInfo, Log) -> + {Varbind, SecName, Opts} = ErrorInfo, + ReqId = + if + is_record(Data, scopedPdu) -> + (Data#scopedPdu.data)#pdu.request_id; + true -> + 0 %% RFC2572, 7.1.3.c.4 + end, + ?vtrace("Report ReqId: ~p",[ReqId]), + Pdu = #pdu{type = report, + request_id = ReqId, + error_status = noError, + error_index = 0, + varbinds = [Varbind]}, + SecLevel = snmp_misc:get_option(securityLevel, Opts, 0), + SnmpEngineID = snmp_framework_mib:get_engine_id(), + ContextEngineID = + snmp_misc:get_option(contextEngineID, Opts, SnmpEngineID), + ContextName = snmp_misc:get_option(contextName, Opts, ""), + SecData = snmp_misc:get_option(sec_data, Opts, []), + + generate_response_msg('version-3', Pdu, report, + {v3, MsgID, MsgSecurityModel, SecName, SecLevel, + ContextEngineID, ContextName, SecData}, Log). + +%% req_id(#scopedPdu{data = #pdu{request_id = ReqId}}) -> +%% ?vtrace("Report ReqId: ~p",[ReqId]), +%% ReqId; +%% req_id(_) -> +%% 0. % RFC2572, 7.1.3.c.4 + + +%% maybe_generate_discovery1_report_msg() -> +%% case (catch DiscoveryHandler:handle_discovery1(Ip, Udp, EngineId)) of +%% {ok, Entry} when is_record(Entry, snmp_discovery_data1) -> +%% ok; +%% ignore -> +%% ok; +%% {error, Reason} -> + +%% Response to stage 1 discovery message (terminating, i.e. from the manager) +generate_discovery1_report_msg(MsgID, MsgSecurityModel, + SecName, SecLevel, + ContextEngineID, ContextName, + {SecData, Oid, Value}, + #pdu{request_id = ReqId}, Log) -> + ?vtrace("generate_discovery1_report_msg -> entry with" + "~n ReqId: ~p" + "~n Value: ~p", [ReqId, Value]), + Varbind = #varbind{oid = Oid, + variabletype = 'Counter32', + value = Value, + org_index = 1}, + PduOut = #pdu{type = report, + request_id = ReqId, + error_status = noError, + error_index = 0, + varbinds = [Varbind]}, + case generate_response_msg('version-3', PduOut, report, + {v3, MsgID, MsgSecurityModel, SecName, SecLevel, + ContextEngineID, ContextName, SecData}, Log) of + {ok, Packet} -> + {discovery, Packet}; + Error -> + Error + end. + +%% Response to stage 2 discovery message (terminating, i.e. from the manager) +generate_discovery2_report_msg(MsgID, MsgSecurityModel, + SecName, SecLevel, + ContextEngineID, ContextName, + SecData, #pdu{request_id = ReqId}, Log) -> + ?vtrace("generate_discovery2_report_msg -> entry with" + "~n ReqId: ~p", [ReqId]), + SecModule = get_security_module(MsgSecurityModel), + Vb = SecModule:current_statsNotInTimeWindows_vb(), + PduOut = #pdu{type = report, + request_id = ReqId, + error_status = noError, + error_index = 0, + varbinds = [Vb]}, + case generate_response_msg('version-3', PduOut, report, + {v3, MsgID, MsgSecurityModel, SecName, SecLevel, + ContextEngineID, ContextName, SecData}, Log) of + {ok, Packet} -> + {discovery, Packet}; + Error -> + Error + end. + + +too_big(Vsn, Pdu, Community, Log, _MMS, _Len) + when Pdu#pdu.type =:= 'get-response' -> + ErrPdu = + if + Vsn =:= 'version-1' -> + %% In v1, the varbinds should be identical to the incoming + %% request. It isn't identical now! + %% Make acceptable (?) approximation. + V = set_vb_null(Pdu#pdu.varbinds), + Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = V}; + true -> + %% In v2, varbinds should be empty (reasonable!) + Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = []} + end, + + case catch snmp_pdus:enc_pdu(ErrPdu) of + {'EXIT', Reason} -> + user_err("failed encoding pdu (pdu: ~w, community: ~w): ~n~w", + [ErrPdu, Community, Reason]), + {discarded, Reason}; + PduBytes -> + Message = #message{version = Vsn, vsn_hdr = Community, + data = PduBytes}, + case catch snmp_pdus:enc_message_only(Message) of + {'EXIT', Reason} -> + user_err("failed encoding message only" + "(pdu: ~w, community: ~w): ~n~w", + [ErrPdu, Community, Reason]), + {discarded, Reason}; + Packet -> + Bin = list_to_binary(Packet), + Log(Pdu#pdu.type, Bin), + inc_snmp_out_vars(ErrPdu), + {ok, Bin} + end + end; +too_big(_Vsn, Pdu, _Community, _Log, MMS, Len) -> + user_err("encoded pdu, ~p bytes, exceeded " + "max message size of ~p bytes. Pdu: ~n~w", + [Len, MMS, Pdu]), + {discarded, tooBig}. + +set_vb_null([Vb | Vbs]) -> + [Vb#varbind{variabletype = 'NULL', value = 'NULL'} | set_vb_null(Vbs)]; +set_vb_null([]) -> + []. + +%%----------------------------------------------------------------- +%% Executed when a message that isn't a response is generated, i.e. +%% a trap or an inform. +%%----------------------------------------------------------------- +generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, To) -> + Message = #message{version = Vsn, vsn_hdr = Community, data = Pdu}, + case catch list_to_binary(snmp_pdus:enc_message(Message)) of + {'EXIT', Reason} -> + user_err("failed encoding message " + "(pdu: ~w, community: ~w): ~n~w", + [Pdu, Community, Reason]), + {discarded, Reason}; + Packet -> + AgentMax = snmp_framework_mib:get_engine_max_message_size(), + case size(Packet) of + Len when Len =< AgentMax -> + {ok, mk_v1_v2_packet_list(To, Packet, Len, Pdu)}; + Len -> + ?vlog("packet max size exceeded: " + "~n Max: ~p" + "~n Len: ~p", + [AgentMax, Len]), + {discarded, tooBig} + end + end; +generate_msg('version-3', NoteStore, Pdu, + {v3, ContextEngineID, ContextName}, To) -> + %% rfc2272: 7.1.6 + ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID, + contextName = ContextName, + data = Pdu}, + case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of + {'EXIT', Reason} -> + user_err("failed encoding scoped pdu " + "(pdu: ~w, contextName: ~w): ~n~w", + [Pdu, ContextName, Reason]), + {discarded, Reason}; + ScopedPDUBytes -> + {ok, mk_v3_packet_list(NoteStore, To, ScopedPDUBytes, Pdu, + ContextEngineID, ContextName)} + end. + + +generate_discovery_msg(NoteStore, Pdu, MsgData, To) -> + Timeout = 1500, + generate_discovery_msg(NoteStore, Pdu, MsgData, Timeout, To). + +generate_discovery_msg(NoteStore, Pdu, MsgData, Timeout, To) -> + {SecData, ContextEngineID, ContextName} = MsgData, + {SecModel, SecName, SecLevelFlag, TargetName} = SecData, + {ManagerEngineId, InitialUserName} = + case get_target_engine_id(TargetName) of + {ok, discovery} -> + {"", ""}; % Discovery stage 1 + {ok, {discovery, IUN}} -> + {"", IUN}; % Discovery stage 1 + {ok, TargetEngineId} -> + {TargetEngineId, ""} % Discovery stage 2 + end, + generate_discovery_msg(NoteStore, Pdu, + ContextEngineID, ContextName, + SecModel, SecName, SecLevelFlag, + ManagerEngineId, + InitialUserName, + Timeout, To). + +generate_discovery_msg(NoteStore, Pdu, + ContextEngineID, ContextName, + SecModel, _SecName, _SecLevelFlag, + "" = ManagerEngineID, + InitialUserName, + Timeout, To) -> + %% Discovery step 1 uses SecLevel = noAuthNoPriv + SecName = "", + SecLevelFlag = 0, % ?'SnmpSecurityLevel_noAuthNoPriv', + generate_discovery_msg2(NoteStore, Pdu, + ContextEngineID, ManagerEngineID, + SecModel, SecName, SecLevelFlag, + InitialUserName, + ContextName, Timeout, To); +generate_discovery_msg(NoteStore, Pdu, + ContextEngineID, ContextName, + SecModel, SecName, SecLevelFlag, + ManagerEngineID, + InitialUserName, + Timeout, To) -> + %% SecLevelFlag = 1, % ?'SnmpSecurityLevel_authNoPriv', + generate_discovery_msg2(NoteStore, Pdu, + ContextEngineID, ManagerEngineID, + SecModel, SecName, SecLevelFlag, + InitialUserName, + ContextName, Timeout, To). + +generate_discovery_msg2(NoteStore, Pdu, + ContextEngineID, ManagerEngineID, + SecModel, SecName, SecLevelFlag, + InitialUserName, + ContextName, Timeout, To) -> + %% rfc2272: 7.1.6 + ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID, + contextName = ContextName, + data = Pdu}, + case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of + {'EXIT', Reason} -> + user_err("failed encoding scoped pdu " + "(pdu: ~w, contextName: ~w): ~n~w", + [Pdu, ContextName, Reason]), + {discarded, Reason}; + ScopedPDUBytes -> + {ok, generate_discovery_msg(NoteStore, To, + Pdu, ScopedPDUBytes, + ContextEngineID, ManagerEngineID, + SecModel, SecName, SecLevelFlag, + InitialUserName, + ContextName, Timeout)} + end. + +%% Timeout is in msec but note timeout is in 1/10 seconds +discovery_note_timeout(Timeout) -> + (Timeout div 100) + 1. + +generate_discovery_msg(NoteStore, {?snmpUDPDomain, [A,B,C,D,U1,U2]}, + Pdu, ScopedPduBytes, + ContextEngineID, ManagerEngineID, + SecModel, SecName, SecLevelFlag, + InitialUserName, + ContextName, Timeout) -> + %% 7.1.7 + ?vdebug("generate_discovery_msg -> 7.1.7 (~w)", [ManagerEngineID]), + MsgID = generate_msg_id(), + PduType = Pdu#pdu.type, + MsgFlags = mk_msg_flags(PduType, SecLevelFlag), + V3Hdr = #v3_hdr{msgID = MsgID, + msgMaxSize = get_max_message_size(), + msgFlags = MsgFlags, + msgSecurityModel = SecModel}, + Message = #message{version = 'version-3', + vsn_hdr = V3Hdr, + data = ScopedPduBytes}, + SecModule = sec_module(SecModel), + + %% 7.1.9b + ?vdebug("generate_discovery_msg -> 7.1.9b", []), + case generate_sec_discovery_msg(Message, SecModule, + ManagerEngineID, + SecName, SecLevelFlag, + InitialUserName) of + {ok, Packet} -> + %% 7.1.9c + %% Store in cache for Timeout msec. + NoteTimeout = discovery_note_timeout(Timeout), + ?vdebug("generate_discovery_msg -> 7.1.9c [~w]", [NoteTimeout]), + %% The request id is just in case when we receive a + %% report with incorrect securityModel and/or securityLevel + Key = {agent, MsgID}, + Note = #note{sec_engine_id = ManagerEngineID, + sec_model = SecModel, + sec_name = SecName, + sec_level = SecLevelFlag, + ctx_engine_id = ContextEngineID, + ctx_name = ContextName, + disco = true, + req_id = Pdu#pdu.request_id}, + snmp_note_store:set_note(NoteStore, Timeout, Key, Note), + %% Log(Packet), + inc_snmp_out_vars(Pdu), + ?vdebug("generate_discovery_msg -> done", []), + {Packet, {{A,B,C,D}, U1 bsl 8 + U2}}; + + Error -> + throw(Error) + end. + +generate_sec_discovery_msg(Message, SecModule, + SecEngineID, SecName, SecLevelFlag, + InitialUserName) -> + case (catch SecModule:generate_discovery_msg(Message, SecEngineID, + SecName, SecLevelFlag, + InitialUserName)) of + {'EXIT', Reason} -> + config_err("~p (message: ~p)", [Reason, Message]), + {discarded, Reason}; + {error, Reason} -> + config_err("~p (message: ~p)", [Reason, Message]), + {discarded, Reason}; + Bin when is_binary(Bin) -> + {ok, Bin}; + OutMsg when is_list(OutMsg) -> + case (catch list_to_binary(OutMsg)) of + Bin when is_binary(Bin) -> + {ok, Bin}; + {'EXIT', Reason} -> + {error, Reason} + end + end. + + +process_taddrs(Dests) -> + ?vtrace("process_taddrs -> entry with" + "~n Dests: ~p", [Dests]), + process_taddrs(Dests, []). + +process_taddrs([], Acc) -> + ?vtrace("process_taddrs -> entry when done with" + "~n Acc: ~p", [Acc]), + lists:reverse(Acc); + +%% v3 +process_taddrs([{{?snmpUDPDomain, [A,B,C,D,U1,U2]}, SecData} | T], Acc) -> + ?vtrace("process_taddrs -> entry when v3 with" + "~n A: ~p" + "~n B: ~p" + "~n C: ~p" + "~n D: ~p" + "~n U1: ~p" + "~n U2: ~p" + "~n SecData: ~p", [A, B, C, D, U1, U2, SecData]), + Entry = {{snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}}, SecData}, + process_taddrs(T, [Entry | Acc]); +%% Bad v3 +process_taddrs([{{TDomain, TAddr}, _SecData} | T], Acc) -> + ?vtrace("process_taddrs -> entry when bad v3 with" + "~n TDomain: ~p" + "~n TAddr: ~p", [TDomain, TAddr]), + user_err("Bad TDomain/TAddr: ~w/~w", [TDomain, TAddr]), + process_taddrs(T, Acc); +%% v1 & v2 +process_taddrs([{?snmpUDPDomain, [A,B,C,D,U1,U2]} | T], Acc) -> + ?vtrace("process_taddrs -> entry when v1/v2 with" + "~n A: ~p" + "~n B: ~p" + "~n C: ~p" + "~n D: ~p" + "~n U1: ~p" + "~n U2: ~p", [A, B, C, D, U1, U2]), + Entry = {snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}}, + process_taddrs(T, [Entry | Acc]); +%% Bad v1 or v2 +process_taddrs([{TDomain, TAddr} | T], Acc) -> + ?vtrace("process_taddrs -> entry when bad v1/v2 with" + "~n TDomain: ~p" + "~n TAddr: ~p", [TDomain, TAddr]), + user_err("Bad TDomain/TAddr: ~w/~w", [TDomain, TAddr]), + process_taddrs(T, Acc); +process_taddrs(Crap, Acc) -> + throw({error, {taddrs_crap, Crap, Acc}}). + + +mk_v1_v2_packet_list(To, Packet, Len, Pdu) -> + mk_v1_v2_packet_list(To, Packet, Len, Pdu, []). + +mk_v1_v2_packet_list([], _Packet, _Len, _Pdu, Acc) -> + lists:reverse(Acc); + +%% This (old) clause is for backward compatibillity reasons +%% If this is called, then the filter function is not used +mk_v1_v2_packet_list([{?snmpUDPDomain, [A,B,C,D,U1,U2]} | T], + Packet, Len, Pdu, Acc) -> + %% Sending from default UDP port + inc_snmp_out_vars(Pdu), + Entry = {snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}, Packet}, + mk_v1_v2_packet_list(T, Packet, Len, Pdu, [Entry | Acc]); + +%% This is the new clause +%% This is only called if the actual target was accepted +%% (by the filter module) +mk_v1_v2_packet_list([{Domain, Addr} | T], + Packet, Len, Pdu, Acc) -> + %% Sending from default UDP port + inc_snmp_out_vars(Pdu), + Entry = {Domain, Addr, Packet}, + mk_v1_v2_packet_list(T, Packet, Len, Pdu, [Entry | Acc]). + + +get_max_message_size() -> + snmp_framework_mib:get_engine_max_message_size(). + +mk_msg_flags(PduType, SecLevel) -> + snmp_misc:mk_msg_flags(PduType, SecLevel). + +mk_v3_packet_entry(NoteStore, Domain, Addr, + {SecModel, SecName, SecLevel, TargetAddrName}, + ScopedPDUBytes, Pdu, ContextEngineID, ContextName) -> + %% 7.1.7 + ?vtrace("mk_v3_packet_entry -> entry - 7.1.7", []), + MsgID = generate_msg_id(), + PduType = Pdu#pdu.type, + MsgFlags = mk_msg_flags(PduType, SecLevel), + V3Hdr = #v3_hdr{msgID = MsgID, + msgMaxSize = get_max_message_size(), + msgFlags = MsgFlags, + msgSecurityModel = SecModel}, + Message = #message{version = 'version-3', + vsn_hdr = V3Hdr, + data = ScopedPDUBytes}, + SecModule = + case SecModel of + ?SEC_USM -> + snmpa_usm + end, + + %% 7.1.9a + ?vtrace("mk_v3_packet_entry -> sec engine id - 7.1.9a", []), + SecEngineID = + case PduType of + 'snmpv2-trap' -> + snmp_framework_mib:get_engine_id(); + _ -> + %% This is the implementation dependent target engine id + %% procedure. + case get_target_engine_id(TargetAddrName) of + {ok, discovery} -> + config_err("Discovery has not yet been performed for " + "snmpTargetAddrName ~p~n", + [TargetAddrName]), + throw({discarded, {discovery, TargetAddrName}}); + {ok, TargetEngineId} -> + ?vtrace("TargetEngineId: ~p", [TargetEngineId]), + TargetEngineId; + undefined -> + config_err("Can't find engineID for " + "snmpTargetAddrName ~p~n", + [TargetAddrName]), + "" % this will trigger error in secmodule + end + end, + + ?vdebug("mk_v3_packet_entry -> secEngineID: ~p", [SecEngineID]), + %% 7.1.9b + case catch SecModule:generate_outgoing_msg(Message, SecEngineID, + SecName, [], SecLevel) of + {'EXIT', Reason} -> + config_err("~p (message: ~p)", [Reason, Message]), + skip; + {error, Reason} -> + ?vlog("~n ~w error ~p\n", [SecModule, Reason]), + skip; + OutMsg when is_list(OutMsg) -> + %% 7.1.9c + %% Store in cache for 150 sec. + Packet = list_to_binary(OutMsg), + ?vdebug("mk_v3_packet_entry -> generated: ~w bytes", + [size(Packet)]), + Data = + if + SecLevel =:= 3 -> + %% encrypted - log decrypted pdu + {Packet, {V3Hdr, ScopedPDUBytes}}; + true -> + %% otherwise log the entire msg + Packet + end, + CacheKey = {agent, MsgID}, + CacheVal = #note{sec_engine_id = SecEngineID, + sec_model = SecModel, + sec_name = SecName, + sec_level = SecLevel, + ctx_engine_id = ContextEngineID, + ctx_name = ContextName, + disco = false, + req_id = Pdu#pdu.request_id}, + snmp_note_store:set_note(NoteStore, 1500, CacheKey, CacheVal), + inc_snmp_out_vars(Pdu), + {ok, {Domain, Addr, Data}} + end. + + +mk_v3_packet_list(NoteStore, To, + ScopedPDUBytes, Pdu, ContextEngineID, ContextName) -> + mk_v3_packet_list(NoteStore, To, + ScopedPDUBytes, Pdu, + ContextEngineID, ContextName, []). + +mk_v3_packet_list(_, [], + _ScopedPDUBytes, _Pdu, + _ContextEngineID, _ContextName, + Acc) -> + lists:reverse(Acc); + +%% This clause is for backward compatibillity reasons +%% If this is called the filter function is not used +mk_v3_packet_list(NoteStore, + [{{?snmpUDPDomain, [A,B,C,D,U1,U2]}, SecData} | T], + ScopedPDUBytes, Pdu, ContextEngineID, ContextName, + Acc) -> + case mk_v3_packet_entry(NoteStore, + snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}, SecData, + ScopedPDUBytes, Pdu, + ContextEngineID, ContextName) of + skip -> + mk_v3_packet_list(NoteStore, T, + ScopedPDUBytes, Pdu, + ContextEngineID, ContextName, + Acc); + {ok, Entry} -> + mk_v3_packet_list(NoteStore, T, + ScopedPDUBytes, Pdu, + ContextEngineID, ContextName, [Entry | Acc]) + end; + +%% This is the new clause +%% This is only called if the actual target was accepted +%% (by the filter module) +mk_v3_packet_list(NoteStore, + [{{Domain, Addr}, SecData} | T], + ScopedPDUBytes, Pdu, ContextEngineID, ContextName, + Acc) -> + case mk_v3_packet_entry(NoteStore, + Domain, Addr, SecData, + ScopedPDUBytes, Pdu, + ContextEngineID, ContextName) of + skip -> + mk_v3_packet_list(NoteStore, T, + ScopedPDUBytes, Pdu, + ContextEngineID, ContextName, Acc); + {ok, Entry} -> + mk_v3_packet_list(NoteStore, T, + ScopedPDUBytes, Pdu, + ContextEngineID, ContextName, [Entry | Acc]) + end. + + +generate_msg_id() -> + gen(msg_id). + +generate_req_id() -> + gen(req_id). + +gen(Id) -> + case ets:update_counter(snmp_agent_table, Id, 1) of + N when N =< 2147483647 -> + N; + _N -> + ets:insert(snmp_agent_table, {Id, 0}), + 0 + end. + + +get_target_engine_id(TargetAddrName) -> + snmp_target_mib:get_target_engine_id(TargetAddrName). + +sec_module(?SEC_USM) -> + snmpa_usm. + + +%%----------------------------------------------------------------- +%% Version(s) functions +%%----------------------------------------------------------------- +init_versions([], S) -> + S; +init_versions([v1|Vsns], S) -> + init_versions(Vsns, S#state{v1 = true}); +init_versions([v2|Vsns], S) -> + init_versions(Vsns, S#state{v2c = true}); +init_versions([v3|Vsns], S) -> + init_versions(Vsns, S#state{v3 = true}). + + +%%----------------------------------------------------------------- +%% Counter functions +%%----------------------------------------------------------------- +init_counters() -> + F = fun(Counter) -> maybe_create_counter(Counter) end, + lists:map(F, counters()). + +reset_counters() -> + F = fun(Counter) -> init_counter(Counter) end, + lists:map(F, counters()). + +maybe_create_counter(Counter) -> + case ets:lookup(snmp_agent_table, Counter) of + [_] -> ok; + _ -> init_counter(Counter) + end. + +init_counter(Counter) -> + ets:insert(snmp_agent_table, {Counter, 0}). + +counters() -> + [ + snmpInPkts, + snmpOutPkts, + snmpInBadVersions, + snmpInBadCommunityNames, + snmpInBadCommunityUses, + snmpInASNParseErrs, + snmpInTooBigs, + snmpInNoSuchNames, + snmpInBadValues, + snmpInReadOnlys, + snmpInGenErrs, + snmpInTotalReqVars, + snmpInTotalSetVars, + snmpInGetRequests, + snmpInGetNexts, + snmpInSetRequests, + snmpInGetResponses, + snmpInTraps, + snmpOutTooBigs, + snmpOutNoSuchNames, + snmpOutBadValues, + snmpOutGenErrs, + snmpOutGetRequests, + snmpOutGetNexts, + snmpOutSetRequests, + snmpOutGetResponses, + snmpOutTraps, + snmpSilentDrops, + snmpProxyDrops, + %% From SNMP-MPD-MIB + snmpUnknownSecurityModels, + snmpInvalidMsgs, + snmpUnknownPDUHandlers + ]. + + + +%%----------------------------------------------------------------- +%% inc(VariableName) increments the variable (Counter) in +%% the local mib. (e.g. snmpInPkts) +%%----------------------------------------------------------------- +inc(Name) -> ets:update_counter(snmp_agent_table, Name, 1). +inc(Name, N) -> ets:update_counter(snmp_agent_table, Name, N). + +inc_snmp_in_vars(#pdu{type = Type}) -> + inc_in_type(Type). + +inc_snmp_cnt_vars(_, #pdu{error_status = ErrStat}) when ErrStat =/= noError -> + ok; +inc_snmp_cnt_vars('get-request', #pdu{varbinds = Vbs}) -> + inc(snmpInTotalReqVars, length(Vbs)); +inc_snmp_cnt_vars('get-next-request', #pdu{varbinds = Vbs}) -> + inc(snmpInTotalReqVars, length(Vbs)); +inc_snmp_cnt_vars('set-request', #pdu{varbinds = Vbs}) -> + inc(snmpInTotalSetVars, length(Vbs)); +inc_snmp_cnt_vars(_, _) -> + ok. + +inc_snmp_out_vars(#pdu{type = Type, + error_status = ErrorStatus}) -> + inc(snmpOutPkts), + inc_out_err(ErrorStatus), + inc_out_vars_2(Type); +inc_snmp_out_vars(TrapPdu) when is_record(TrapPdu, trappdu) -> + inc(snmpOutPkts), + inc(snmpOutTraps). + +inc_out_vars_2('get-response') -> inc(snmpOutGetResponses); +inc_out_vars_2('get-request') -> inc(snmpOutGetRequests); +inc_out_vars_2('get-next-request') -> inc(snmpOutGetNexts); +inc_out_vars_2('set-request') -> inc(snmpOutSetRequests); +inc_out_vars_2(_) -> ok. + +inc_out_err(genErr) -> inc(snmpOutGenErrs); +inc_out_err(tooBig) -> inc(snmpOutTooBigs); +inc_out_err(noSuchName) -> inc(snmpOutNoSuchNames); +inc_out_err(badValue) -> inc(snmpOutBadValues); +% snmpOutReadOnlys is not used any more (rfc1213) +%inc_out_err(readOnly) -> inc(snmpOutReadOnlys); +inc_out_err(_) -> ok. + +inc_in_type('get-request') -> inc(snmpInGetRequests); +inc_in_type('get-next-request') -> inc(snmpInGetNexts); +inc_in_type('set-request') -> inc(snmpInSetRequests); +inc_in_type(_) -> ok. + + +user_err(F, A) -> + snmpa_error:user_err(F, A). + +config_err(F, A) -> + snmpa_error:config_err(F, A). diff --git a/lib/snmp/src/agent/snmpa_net_if.erl b/lib/snmp/src/agent/snmpa_net_if.erl new file mode 100644 index 0000000000..d703e5ac55 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_net_if.erl @@ -0,0 +1,1249 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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_net_if). + +-behaviour(snmpa_network_interface). + +-export([start_link/4, + info/1, + verbosity/2]). +-export([get_log_type/1, set_log_type/2]). +-export([get_request_limit/1, set_request_limit/2]). +-export([system_continue/3, system_terminate/4, system_code_change/4]). +-export([init/5]). +-export([filter_reset/1]). + +-include("snmp_types.hrl"). +-include("snmpa_internal.hrl"). +-include("snmpa_atl.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). + +-record(state, {parent, + note_store, + master_agent, + usock, + usock_opts, + mpd_state, + log, + reqs = [], + debug = false, + limit = infinity, + rcnt = [], + filter}). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + +-define(DEFAULT_FILTER_MODULE, snmpa_net_if_filter). +-define(DEFAULT_FILTER_OPTS, [{module, ?DEFAULT_FILTER_MODULE}]). + + +%%%----------------------------------------------------------------- +%%% This module implements the default Network Interface part +%%% of the SNMP agent. It uses UDP, and read the agent.conf to find +%%% the UDP port. +%%% +%%% Opts = [Opt] +%%% Opt = {verbosity,silence | log | debug} | +%%% {bind_to, bool()} | +%%% {recbuf,integer()} | +%%% {no_reuse, bool()} | +%%% {req_limit, integer() | infinity} +%%%----------------------------------------------------------------- +start_link(Prio, NoteStore, MasterAgent, Opts) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n NoteStore: ~p" + "~n MasterAgent: ~p" + "~n Opts: ~p", [Prio, NoteStore, MasterAgent, Opts]), + Args = [Prio, NoteStore, MasterAgent, self(), Opts], + proc_lib:start_link(?MODULE, init, Args). + + +info(Pid) -> + case call(Pid, info) of + Info when is_list(Info) -> + Info; + _ -> + [] + end. + +verbosity(Pid, Verbosity) -> + Pid ! {verbosity, Verbosity}. + +get_log_type(Pid) -> + call(Pid, get_log_type). + +set_log_type(Pid, NewType) -> + call(Pid, {set_log_type, NewType}). + +get_request_limit(Pid) -> + call(Pid, get_request_limit). + +set_request_limit(Pid, NewLimit) -> + call(Pid, {set_request_limit, NewLimit}). + +get_port() -> + {value, UDPPort} = snmp_framework_mib:intAgentUDPPort(get), + UDPPort. + +get_address() -> + {value, IPAddress} = snmp_framework_mib:intAgentIpAddress(get), + IPAddress. + +filter_reset(Pid) -> + Pid ! filter_reset. + + +%%----------------------------------------------------------------- +%%----------------------------------------------------------------- + +init(Prio, NoteStore, MasterAgent, Parent, Opts) -> + ?d("init -> entry with" + "~n Prio: ~p" + "~n NoteStore: ~p" + "~n MasterAgent: ~p" + "~n Parent: ~p" + "~n Opts: ~p", [Prio, NoteStore, MasterAgent, Parent, Opts]), + case (catch do_init(Prio, NoteStore, MasterAgent, Parent, Opts)) of + {ok, State} -> + proc_lib:init_ack({ok, self()}), + loop(State); + {error, Reason} -> + config_err("failed starting net-if: ~n~p", [Reason]), + proc_lib:init_ack({error, Reason}); + Error -> + config_err("failed starting net-if: ~n~p", [Error]), + proc_lib:init_ack({error, Error}) + end. + +do_init(Prio, NoteStore, MasterAgent, Parent, Opts) -> + process_flag(trap_exit, true), + process_flag(priority, Prio), + + %% -- Verbosity -- + put(sname,nif), + put(verbosity,get_verbosity(Opts)), + ?vlog("starting",[]), + + %% -- Port and address -- + UDPPort = get_port(), + ?vdebug("port: ~w",[UDPPort]), + IPAddress = get_address(), + ?vdebug("addr: ~w",[IPAddress]), + + %% -- Versions -- + Vsns = get_vsns(Opts), + ?vdebug("vsns: ~w",[Vsns]), + + %% Flow control -- + Limit = get_req_limit(Opts), + ?vdebug("Limit: ~w", [Limit]), + FilterOpts = get_filter_opts(Opts), + FilterMod = create_filter(FilterOpts), + ?vdebug("FilterMod: ~w", [FilterMod]), + + %% -- Audit trail log + Log = create_log(), + ?vdebug("Log: ~w",[Log]), + + + %% -- Socket -- + IPOpts1 = ip_opt_bind_to_ip_address(Opts, IPAddress), + IPOpts2 = ip_opt_no_reuse_address(Opts), + IPOpts3 = ip_opt_recbuf(Opts), + IPOpts4 = ip_opt_sndbuf(Opts), + IPOpts = [binary | IPOpts1 ++ IPOpts2 ++ IPOpts3 ++ IPOpts4], + ?vdebug("open socket with options: ~w",[IPOpts]), + case gen_udp_open(UDPPort, IPOpts) of + {ok, Sock} -> + MpdState = snmpa_mpd:init(Vsns), + init_counters(), + active_once(Sock), + S = #state{parent = Parent, + note_store = NoteStore, + master_agent = MasterAgent, + mpd_state = MpdState, + usock = Sock, + usock_opts = IPOpts, + log = Log, + limit = Limit, + filter = FilterMod}, + ?vdebug("started with MpdState: ~p", [MpdState]), + {ok, S}; + {error, Reason} -> + ?vinfo("Failed to open UDP socket: ~p", [Reason]), + {error, {udp_open, UDPPort, Reason}} + end. + +create_log() -> + case ets:lookup(snmp_agent_table, audit_trail_log) of + [] -> + {undefined, []}; + [{audit_trail_log, AtlOpts}] -> + ?vtrace("AtlOpts: ~p",[AtlOpts]), + Type = get_atl_type(AtlOpts), + Dir = get_atl_dir(AtlOpts), + Size = get_atl_size(AtlOpts), + Repair = get_atl_repair(AtlOpts), + Name = ?audit_trail_log_name, + File = filename:absname(?audit_trail_log_file, Dir), + case snmp_log:create(Name, File, Size, Repair, true) of + {ok, Log} -> + ?vdebug("log created: ~w",[Log]), + {Log, Type}; + {error, Reason} -> + throw({error, {create_log, Reason}}) + end + end. + + +create_filter(Opts) when is_list(Opts) -> + case get_filter_module(Opts) of + ?DEFAULT_FILTER_MODULE = Mod -> + Mod; + Module -> + snmpa_network_interface_filter:verify(Module), + Module + end; +create_filter(BadOpts) -> + throw({error, {bad_filter_opts, BadOpts}}). + + +log({_, []}, _, _, _, _) -> + ok; +log({Log, Types}, 'set-request', Packet, Addr, Port) -> + case lists:member(write, Types) of + true -> + snmp_log:log(Log, Packet, Addr, Port); + false -> + ok + end; +log({Log, Types}, _, Packet, Addr, Port) -> + case lists:member(read, Types) of + true -> + snmp_log:log(Log, Packet, Addr, Port); + false -> + ok + end; +log(_, _, _, _, _) -> + ok. + + +gen_udp_open(Port, Opts) -> + case init:get_argument(snmp_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + gen_udp:open(0, [{fd, Fd}|Opts]); + error -> + case init:get_argument(snmpa_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + gen_udp:open(0, [{fd, Fd}|Opts]); + error -> + gen_udp:open(Port, Opts) + end + end. + + +loop(S) -> + receive + {udp, _UdpId, Ip, Port, Packet} -> + ?vlog("got paket from ~w:~w",[Ip,Port]), + NewS = maybe_handle_recv(S, Ip, Port, Packet), + loop(NewS); + + {info, ReplyRef, Pid} -> + Info = get_info(S), + Pid ! {ReplyRef, Info, self()}, + loop(S); + + %% response (to get/get_next/get_bulk/set requests) + {snmp_response, Vsn, RePdu, Type, ACMData, Dest, []} -> + ?vlog("reply pdu: " + "~n ~s", + [?vapply(snmp_misc, format, [256, "~w", [RePdu]])]), + NewS = maybe_handle_reply_pdu(S, Vsn, RePdu, Type, ACMData, Dest), + loop(NewS); + + %% Traps/notification + {send_pdu, Vsn, Pdu, MsgData, To} -> + ?vdebug("send pdu: " + "~n Pdu: ~p" + "~n To: ~p", [Pdu, To]), + NewS = maybe_handle_send_pdu(S, Vsn, Pdu, MsgData, To, undefined), + loop(NewS); + + %% Informs + {send_pdu_req, Vsn, Pdu, MsgData, To, From} -> + ?vdebug("send pdu request: " + "~n Pdu: ~p" + "~n To: ~p" + "~n From: ~p", + [Pdu, To, toname(From)]), + NewS = maybe_handle_send_pdu(S, Vsn, Pdu, MsgData, To, From), + loop(NewS); + + %% Discovery Inform + {send_discovery, Pdu, MsgData, To, From} -> + ?vdebug("received send discovery request: " + "~n Pdu: ~p" + "~n To: ~p" + "~n From: ~p", + [Pdu, To, toname(From)]), + NewS = handle_send_discovery(S, Pdu, MsgData, To, From), + loop(NewS); + + {discarded_pdu, _Vsn, ReqId, _ACMData, Variable, _Extra} -> + ?vdebug("discard PDU: ~p", [Variable]), + snmpa_mpd:discarded_pdu(Variable), + NewS = update_req_counter_outgoing(S, ReqId), + loop(NewS); + + {get_log_type, ReplyRef, Pid} -> + ?vdebug("get log type: ~p", []), + #state{log = {_, LogType}} = S, + Pid ! {ReplyRef, {ok, LogType}, self()}, + loop(S); + + {{set_log_type, NewType}, ReplyRef, Pid} -> + ?vdebug("set log type: ~p", [NewType]), + {NewState, Reply} = (catch handle_set_log_type(S, NewType)), + Pid ! {ReplyRef, Reply, self()}, + loop(NewState); + + {get_request_limit, ReplyRef, Pid} -> + ?vdebug("get request limit: ~p", []), + #state{limit = Limit} = S, + Pid ! {ReplyRef, {ok, Limit}, self()}, + loop(S); + + {{set_request_limit, NewLimit}, ReplyRef, Pid} -> + ?vdebug("set request limit: ~p", [NewLimit]), + {NewState, Reply} = (catch handle_set_request_limit(S, NewLimit)), + Pid ! {ReplyRef, Reply, self()}, + loop(NewState); + + {disk_log, _Node, Log, Info} -> + ?vdebug("disk log event: ~p, ~p", [Log, Info]), + NewS = handle_disk_log(Log, Info, S), + loop(NewS); + + {verbosity, Verbosity} -> + ?vlog("verbosity: ~p -> ~p", [get(verbosity), Verbosity]), + put(verbosity, snmp_verbosity:validate(Verbosity)), + loop(S); + + filter_reset -> + reset_counters(), + loop(S); + + {'EXIT', Parent, Reason} when Parent == S#state.parent -> + ?vlog("parent (~p) exited: " + "~n ~p", [Parent, Reason]), + exit(Reason); + + {'EXIT', Port, Reason} when Port == S#state.usock -> + UDPPort = get_port(), + NewS = + case gen_udp_open(UDPPort, S#state.usock_opts) of + {ok, Id} -> + error_msg("Port ~p exited for reason" + "~n ~p" + "~n Re-opened (~p)", [Port, Reason, Id]), + S#state{usock = Id}; + {error, ReopenReason} -> + error_msg("Port ~p exited for reason" + "~n ~p" + "~n Re-open failed with reason" + "~n ~p", + [Port, Reason, ReopenReason]), + ok + end, + loop(NewS); + + {'EXIT', Port, Reason} when is_port(Port) -> + error_msg("Exit message from port ~p for reason ~p~n", + [Port, Reason]), + loop(S); + + {'EXIT', Pid, Reason} -> + ?vlog("~p exited: " + "~n ~p", [Pid, Reason]), + NewS = clear_reqs(Pid, S), + loop(NewS); + + {system, From, Msg} -> + ?vdebug("system event ~p from ~p", [Msg, From]), + sys:handle_system_msg(Msg, From, S#state.parent, ?MODULE, [], S); + + _ -> + loop(S) + end. + + +update_req_counter_incomming(#state{limit = infinity, usock = Sock} = S, _) -> + active_once(Sock), %% No limit so activate directly + S; +update_req_counter_incomming(#state{limit = Limit, + rcnt = RCnt, + usock = Sock} = S, Rid) + when length(RCnt) + 1 == Limit -> + %% Ok, one more and we are at the limit. + %% Just make sure we are not already processing this one... + case lists:member(Rid, RCnt) of + false -> + %% We are at the limit, do _not_ activate socket + S#state{rcnt = [Rid|RCnt]}; + true -> + active_once(Sock), + S + end; +update_req_counter_incomming(#state{rcnt = RCnt, + usock = Sock} = S, Rid) -> + active_once(Sock), + case lists:member(Rid, RCnt) of + false -> + S#state{rcnt = [Rid|RCnt]}; + true -> + S + end. + + +update_req_counter_outgoing(#state{limit = infinity} = S, _Rid) -> + %% Already activated (in the incoming function) + S; +update_req_counter_outgoing(#state{limit = Limit, + rcnt = RCnt, + usock = Sock} = S, Rid) + when length(RCnt) == Limit -> + ?vtrace("handle_req_counter_outgoing(~w) -> entry with" + "~n Rid: ~w" + "~n length(RCnt): ~w", [Limit, Rid, length(RCnt)]), + case lists:delete(Rid, RCnt) of + NewRCnt when length(NewRCnt) < Limit -> + ?vtrace("update_req_counter_outgoing -> " + "passed below limit: activate", []), + active_once(Sock), + S#state{rcnt = NewRCnt}; + _ -> + S + end; +update_req_counter_outgoing(#state{limit = Limit, rcnt = RCnt} = S, + Rid) -> + ?vtrace("handle_req_counter_outgoing(~w) -> entry with" + "~n Rid: ~w" + "~n length(RCnt): ~w", [Limit, Rid, length(RCnt)]), + NewRCnt = lists:delete(Rid, RCnt), + S#state{rcnt = NewRCnt}. + + +maybe_handle_recv(#state{filter = FilterMod} = S, + Ip, Port, Packet) -> + case (catch FilterMod:accept_recv(Ip, Port)) of + false -> + %% Drop the received packet + inc(netIfMsgInDrops), + S; + _ -> + handle_recv(S, Ip, Port, Packet) + end. + +handle_discovery_response(_Ip, _Port, #pdu{request_id = ReqId} = Pdu, + ManagerEngineId, + #state{usock = Sock, reqs = Reqs} = S) -> + case lists:keysearch(ReqId, 1, S#state.reqs) of + {value, {_, Pid}} -> + active_once(Sock), + Pid ! {snmp_discovery_response_received, Pdu, ManagerEngineId}, + NReqs = lists:keydelete(ReqId, 1, Reqs), + S#state{reqs = NReqs}; + _ -> + %% Ouch, timeout? resend? + S + end. + + +handle_recv(#state{usock = Sock, + mpd_state = MpdState, + note_store = NS, + log = Log} = S, Ip, Port, Packet) -> + put(n1, erlang:now()), + LogF = fun(Type, Data) -> + log(Log, Type, Data, Ip, Port) + end, + case (catch snmpa_mpd:process_packet(Packet, snmpUDPDomain, {Ip, Port}, + MpdState, NS, LogF)) of + {ok, _Vsn, Pdu, _PduMS, {discovery, ManagerEngineId}} -> + handle_discovery_response(Ip, Port, Pdu, ManagerEngineId, S); + + {ok, _Vsn, Pdu, _PduMS, discovery} -> + handle_discovery_response(Ip, Port, Pdu, undefined, S); + + {ok, Vsn, Pdu, PduMS, ACMData} -> + ?vlog("got pdu ~s", + [?vapply(snmp_misc, format, [256, "~w", [Pdu]])]), + %% handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S); + maybe_handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S); + + {discarded, Reason} -> + ?vlog("packet discarded for reason: ~s", + [?vapply(snmp_misc, format, [256, "~w", [Reason]])]), + active_once(Sock), + S; + + {discarded, Reason, ReportPacket} -> + ?vlog("sending report for reason: " + "~n ~s", + [?vapply(snmp_misc, format, [256, "~w", [Reason]])]), + (catch udp_send(S#state.usock, Ip, Port, ReportPacket)), + active_once(Sock), + S; + + {discovery, ReportPacket} -> + ?vlog("sending discovery report", []), + (catch udp_send(S#state.usock, Ip, Port, ReportPacket)), + active_once(Sock), + S; + + Error -> + error_msg("processing of received message failed: " + "~n ~p", [Error]), + active_once(Sock), + S + end. + +maybe_handle_recv_pdu(Ip, Port, Vsn, #pdu{type = Type} = Pdu, PduMS, ACMData, + #state{usock = Sock, filter = FilterMod} = S) -> + case (catch FilterMod:accept_recv_pdu(Ip, Port, Type)) of + false -> + inc(netIfPduInDrops), + active_once(Sock), + ok; + _ -> + handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S) + end; +maybe_handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S) -> + handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S). + +handle_recv_pdu(Ip, Port, Vsn, #pdu{type = 'get-response'} = Pdu, + _PduMS, _ACMData, #state{usock = Sock} = S) -> + active_once(Sock), + handle_response(Vsn, Pdu, {Ip, Port}, S), + S; +handle_recv_pdu(Ip, Port, Vsn, #pdu{request_id = Rid, type = Type} = Pdu, + PduMS, ACMData, #state{master_agent = Pid} = S) + when ((Type =:= 'get-request') orelse + (Type =:= 'get-next-request') orelse + (Type =:= 'get-bulk-request')) -> + ?vtrace("handle_recv_pdu -> received get (~w)", [Type]), + Pid ! {snmp_pdu, Vsn, Pdu, PduMS, ACMData, {Ip, Port}, []}, + update_req_counter_incomming(S, Rid); +handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, + #state{usock = Sock, master_agent = Pid} = S) -> + ?vtrace("handle_recv_pdu -> received other request", []), + active_once(Sock), + Pid ! {snmp_pdu, Vsn, Pdu, PduMS, ACMData, {Ip, Port}, []}, + S. + + +maybe_handle_reply_pdu(#state{filter = FilterMod} = S, Vsn, + #pdu{request_id = Rid} = Pdu, + Type, ACMData, {Ip, Port} = Dest) -> + S1 = update_req_counter_outgoing(S, Rid), + case (catch FilterMod:accept_send_pdu([{Ip, Port}], Type)) of + false -> + inc(netIfPduOutDrops), + ok; + _ -> + handle_reply_pdu(S1, Vsn, Pdu, Type, ACMData, Dest) + end, + S1. + +handle_reply_pdu(#state{log = Log, + usock = Sock, + filter = FilterMod}, + Vsn, Pdu, Type, ACMData, {Ip, Port}) -> + LogF = fun(Type2, Data) -> + log(Log, Type2, Data, Ip, Port) + end, + case (catch snmpa_mpd:generate_response_msg(Vsn, Pdu, Type, + ACMData, LogF)) of + {ok, Packet} -> + ?vinfo("time in agent: ~w mysec", [time_in_agent()]), + maybe_udp_send(FilterMod, Sock, Ip, Port, Packet); + {discarded, Reason} -> + ?vlog("handle_reply_pdu -> " + "~n reply discarded for reason: ~s", + [?vapply(snmp_misc, format, [256, "~w", [Reason]])]), + ok; + {'EXIT', Reason} -> + user_err("failed generating response message: " + "~nPDU: ~w~n~w", [Pdu, Reason]) + end. + + +process_taddrs(To) -> + process_taddrs(To, []). + +process_taddrs([], Acc) -> + lists:reverse(Acc); +%% v3 +process_taddrs([{{_Domain, AddrAndPort}, _SecData}|T], Acc) -> + process_taddrs(T, [AddrAndPort|Acc]); +%% v1 & v2 +process_taddrs([{_Domain, AddrAndPort}|T], Acc) -> + process_taddrs(T, [AddrAndPort|Acc]). + + +merge_taddrs(To1, To2) -> + merge_taddrs(To1, To2, []). + +merge_taddrs([], _To2, Acc) -> + lists:reverse(Acc); +%% v3 +merge_taddrs([{{_, AddrAndPort}, _} = H|To1], To2, Acc) -> + case lists:member(AddrAndPort, To2) of + true -> + merge_taddrs(To1, To2, [H|Acc]); + false -> + merge_taddrs(To1, To2, Acc) + end; +%% v1 & v2 +merge_taddrs([{_, AddrAndPort} = H|To1], To2, Acc) -> + case lists:member(AddrAndPort, To2) of + true -> + merge_taddrs(To1, To2, [H|Acc]); + false -> + merge_taddrs(To1, To2, Acc) + end; +merge_taddrs([_Crap|To1], To2, Acc) -> + merge_taddrs(To1, To2, Acc). + + +maybe_handle_send_pdu(#state{filter = FilterMod} = S, + Vsn, Pdu, MsgData, To0, From) -> + + ?vtrace("maybe_handle_send_pdu -> entry with" + "~n FilterMod: ~p" + "~n To0: ~p", [FilterMod, To0]), + + To1 = snmpa_mpd:process_taddrs(To0), + To2 = process_taddrs(To1), + + case (catch FilterMod:accept_send_pdu(To2, pdu_type_of(Pdu))) of + false -> + inc(netIfPduOutDrops), + ok; + true -> + handle_send_pdu(S, Vsn, Pdu, MsgData, To1, From); + To3 when is_list(To3) -> + To4 = merge_taddrs(To1, To3), + ?vtrace("maybe_handle_send_pdu -> To4: " + "~n ~p", [To4]), + handle_send_pdu(S, Vsn, Pdu, MsgData, To4, From); + _ -> + handle_send_pdu(S, Vsn, Pdu, MsgData, To1, From) + end. + +handle_send_pdu(#state{note_store = NS} = S, Vsn, Pdu, MsgData, To, From) -> + + ?vtrace("handle_send_pdu -> entry with" + "~n Pdu: ~p" + "~n To: ~p", [Pdu, To]), + + case (catch snmpa_mpd:generate_msg(Vsn, NS, Pdu, MsgData, To)) of + {ok, Addresses} -> + handle_send_pdu(S, Pdu, Addresses); + {discarded, Reason} -> + ?vlog("handle_send_pdu -> " + "~n PDU ~p not sent due to ~p", [Pdu, Reason]), + ok; + {'EXIT', Reason} -> + user_err("failed generating message: " + "~nPDU: ~w~n~w", [Pdu, Reason]), + ok + end, + case From of + undefined -> + S; + Pid -> + ?vtrace("link to ~p and add to request list", [Pid]), + link(Pid), + NReqs = snmp_misc:keyreplaceadd(Pid, 2, S#state.reqs, + {Pdu#pdu.request_id, From}), + S#state{reqs = NReqs} + end. + + +handle_send_discovery(#state{note_store = NS} = S, + Pdu, MsgData, + To, From) -> + + ?vtrace("handle_send_discovery -> entry with" + "~n Pdu: ~p" + "~n MsgData: ~p" + "~n To: ~p" + "~n From: ~p", [Pdu, MsgData, To, From]), + + case (catch snmpa_mpd:generate_discovery_msg(NS, Pdu, MsgData, To)) of + {ok, {Packet, {Ip, Port}}} -> + handle_send_discovery(S, Pdu, Packet, Ip, Port, From); + {discarded, Reason} -> + ?vlog("handle_send_discovery -> " + "~n Discovery PDU ~p not sent due to ~p", [Pdu, Reason]), + ok; + {'EXIT', Reason} -> + user_err("failed generating discovery message: " + "~n PDU: ~w" + "~n Reason: ~w", [Pdu, Reason]), + ok + end. + +handle_send_discovery(#state{log = Log, + usock = Sock, + reqs = Reqs} = S, + #pdu{type = Type, + request_id = ReqId}, + Packet, Ip, Port, From) + when is_binary(Packet) -> + log(Log, Type, Packet, Ip, Port), + udp_send(Sock, Ip, Port, Packet), + ?vtrace("handle_send_discovery -> sent (~w)", [ReqId]), + NReqs = snmp_misc:keyreplaceadd(From, 2, Reqs, {ReqId, From}), + S#state{reqs = NReqs}. + + +handle_send_pdu(S, #pdu{type = Type} = Pdu, Addresses) -> + handle_send_pdu(S, Type, Pdu, Addresses); +handle_send_pdu(S, Trap, Addresses) -> + handle_send_pdu(S, trappdu, Trap, Addresses). + +handle_send_pdu(S, Type, Pdu, Addresses) -> + case (catch handle_send_pdu1(S, Type, Addresses)) of + {Reason, Sz} -> + error_msg("Cannot send message " + "~n size: ~p" + "~n reason: ~p" + "~n pdu: ~p", + [Sz, Reason, Pdu]); + _ -> + ok + end. + +handle_send_pdu1(#state{log = Log, + usock = Sock, + filter = FilterMod}, Type, Addresses) -> + SendFun = + fun({snmpUDPDomain, {Ip, Port}, Packet}) when is_binary(Packet) -> + ?vdebug("sending packet:" + "~n size: ~p" + "~n to: ~p:~p", + [sz(Packet), Ip, Port]), + maybe_udp_send(FilterMod, Log, Type, Sock, Ip, Port, Packet); + + ({snmpUDPDomain, {Ip, Port}, {Packet, _LogData}}) when is_binary(Packet) -> + ?vdebug("sending encrypted packet:" + "~n size: ~p" + "~n to: ~p:~p", + [sz(Packet), Ip, Port]), + maybe_udp_send(FilterMod, Log, Type, Sock, Ip, Port, Packet); + + (_X) -> + ?vlog("** bad res: ~p", [_X]), + ok + end, + lists:foreach(SendFun, Addresses). + + +handle_response(Vsn, Pdu, From, S) -> + case lists:keysearch(Pdu#pdu.request_id, 1, S#state.reqs) of + {value, {_, Pid}} -> + ?vdebug("handle_response -> " + "~n send response to receiver ~p", [Pid]), + Pid ! {snmp_response_received, Vsn, Pdu, From}; + _ -> + ?vdebug("handle_response -> " + "~n No receiver available for response pdu", []) + end. + +maybe_udp_send(FilterMod, Sock, Ip, Port, Packet) -> + case (catch FilterMod:accept_send(Ip, Port)) of + false -> + inc(netIfMsgOutDrops), + ok; + _ -> + (catch udp_send(Sock, Ip, Port, Packet)) + end. + +maybe_udp_send(FilterMod, AtLog, Type, Sock, Ip, Port, Packet) -> + case (catch FilterMod:accept_send(Ip, Port)) of + false -> + inc(netIfMsgOutDrops), + ok; + _ -> + log(AtLog, Type, Packet, Ip, Port), + (catch udp_send(Sock, Ip, Port, Packet)) + end. + + +udp_send(UdpId, AgentIp, UdpPort, B) -> + case (catch gen_udp:send(UdpId, AgentIp, UdpPort, B)) of + {error, emsgsize} -> + %% From this message we cannot recover, so exit sending loop + throw({emsgsize, sz(B)}); + {error, ErrorReason} -> + error_msg("[error] cannot send message " + "(destination: ~p:~p, size: ~p, reason: ~p)", + [AgentIp, UdpPort, sz(B), ErrorReason]); + {'EXIT', ExitReason} -> + error_msg("[exit] cannot send message " + "(destination: ~p:~p, size: ~p, reason: ~p)", + [AgentIp, UdpPort, sz(B), ExitReason]); + _ -> + ok + end. + +sz(L) when is_list(L) -> length(L); +sz(B) when is_binary(B) -> size(B); +sz(_) -> undefined. + + +handle_disk_log(_Log, {wrap, NoLostItems}, State) -> + ?vlog("Audit Trail Log - wrapped: ~w previously logged items where lost", + [NoLostItems]), + State; +handle_disk_log(_Log, {truncated, NoLostItems}, State) -> + ?vlog("Audit Trail Log - truncated: ~w items where lost when truncating", + [NoLostItems]), + State; +handle_disk_log(_Log, full, State) -> + error_msg("Failure to write to Audit Trail Log (full)", []), + State; +handle_disk_log(_Log, {error_status, ok}, State) -> + State; +handle_disk_log(_Log, {error_status, {error, Reason}}, State) -> + error_msg("Error status received from Audit Trail Log: " + "~n~p", [Reason]), + State; +handle_disk_log(_Log, _Info, State) -> + State. + + +clear_reqs(Pid, S) -> + NReqs = lists:keydelete(Pid, 2, S#state.reqs), + S#state{reqs = NReqs}. + + +toname(P) when is_pid(P) -> + case process_info(P, registered_name) of + {registered_name, Name} -> + Name; + _ -> + P + end; +toname(Else) -> + Else. + + +active_once(Sock) -> + ?vtrace("activate once", []), + inet:setopts(Sock, [{active, once}]). + + +%%%----------------------------------------------------------------- + +handle_set_log_type(#state{log = {Log, OldValue}} = State, NewType) + when (Log /= undefined) -> + NewValue = + case NewType of + read -> + [read]; + write -> + [write]; + read_write -> + [read,write]; + _ -> + throw({State, {error, {bad_atl_type, NewType}}}) + end, + NewState = State#state{log = {Log, NewValue}}, + OldType = + case {lists:member(read, OldValue), + lists:member(write, OldValue)} of + {true, true} -> + read_write; + {true, false} -> + read; + {false, true} -> + write; + {false, false} -> + throw({State, {error, {bad_atl_type, OldValue}}}) + end, + {NewState, {ok, OldType}}; +handle_set_log_type(State, _NewType) -> + {State, {error, not_enabled}}. + + +handle_set_request_limit(#state{limit = OldLimit} = State, NewLimit) + when ((is_integer(NewLimit) andalso (NewLimit >= 0)) orelse + (NewLimit == infinity)) -> + NewState = State#state{limit = NewLimit}, + {NewState, {ok, OldLimit}}; +handle_set_request_limit(State, BadLimit) -> + {State, {error, {bad_request_limit, BadLimit}}}. + + +%%%----------------------------------------------------------------- +%%% System messages +%%%----------------------------------------------------------------- +system_continue(_Parent, _Dbg, S) -> + loop(S). + +system_terminate(Reason, _Parent, _Dbg, #state{log = Log}) -> + do_close_log(Log), + exit(Reason). + +system_code_change(OldState, _Module, _OldVsn, upgrade_from_pre_4_10) -> + {state, + parent = Parent, + note_store = NS, + master_agent = MA, + usock = Sock, + usock_opts = SockOpts, + mpd_state = MpdState, + log = Log, + reqs = Reqs, + debug = Dbg, + limit = Limit, + rcnt = RCNT} = OldState, + NewState = #state{parent = Parent, + note_store = NS, + master_agent = MA, + usock = Sock, + usock_opts = SockOpts, + mpd_state = MpdState, + log = Log, + reqs = Reqs, + debug = Dbg, + limit = Limit, + rcnt = RCNT, + filter = create_filter(?DEFAULT_FILTER_OPTS)}, + {ok, NewState}; +system_code_change(OldState, _Module, _OldVsn, downgrade_to_pre_4_10) -> + #state{parent = Parent, + note_store = NS, + master_agent = MA, + usock = Sock, + usock_opts = SockOpts, + mpd_state = MpdState, + log = Log, + reqs = Reqs, + debug = Dbg, + limit = Limit, + rcnt = RCNT} = OldState, + NewState = + {state, + parent = Parent, + note_store = NS, + master_agent = MA, + usock = Sock, + usock_opts = SockOpts, + mpd_state = MpdState, + log = Log, + reqs = Reqs, + debug = Dbg, + limit = Limit, + rcnt = RCNT}, + {ok, NewState}; +system_code_change(S, _Module, _OldVsn, _Extra) -> + {ok, S}. + +do_close_log({undefined, []}) -> + ok; +do_close_log({Log, _}) -> + (catch snmp_log:sync(Log)), + (catch snmp_log:close(Log)), + ok; +do_close_log(_) -> + ok. + + +%%%----------------------------------------------------------------- +%%% DEBUG FUNCTIONS +%%%----------------------------------------------------------------- +time_in_agent() -> + subtr(erlang:now(), get(n1)). + +subtr({X1,Y1,Z1}, {X1,Y1,Z2}) -> + Z1 - Z2; +subtr({X1,Y1,Z1}, {X1,Y2,Z2}) -> + ((Y1-Y2) * 1000000) + (Z1 - Z2); +subtr({X1,Y1,Z1}, {X2,Y2,Z2}) -> + ((X1 - X2) * 1000000000000) + ((Y1 - Y2) * 1000000) + (Z1 - Z2). + + +%% ---------------------------------------------------------------- + +pdu_type_of(#pdu{type = Type}) -> + Type; +pdu_type_of(TrapPdu) when is_record(TrapPdu, trappdu) -> + trap. + + +%%----------------------------------------------------------------- +%% Counter functions +%%----------------------------------------------------------------- + +init_counters() -> + F = fun(Counter) -> maybe_create_counter(Counter) end, + lists:map(F, counters()). + +reset_counters() -> + F = fun(Counter) -> init_counter(Counter) end, + lists:map(F, counters()). + +maybe_create_counter(Counter) -> + case ets:lookup(snmp_agent_table, Counter) of + [_] -> ok; + _ -> init_counter(Counter) + end. + +init_counter(Counter) -> + ets:insert(snmp_agent_table, {Counter, 0}). + +counters() -> + [ + netIfMsgOutDrops, + netIfMsgInDrops, + netIfPduOutDrops, + netIfPduInDrops + ]. + +inc(Name) -> + ets:update_counter(snmp_agent_table, Name, 1). + +get_counters() -> + Counters = counters(), + get_counters(Counters, []). + +get_counters([], Acc) -> + lists:reverse(Acc); +get_counters([Counter|Counters], Acc) -> + case ets:lookup(snmp_agent_table, Counter) of + [CounterVal] -> + get_counters(Counters, [CounterVal|Acc]); + _ -> + get_counters(Counters, Acc) + end. + + +%% ---------------------------------------------------------------- + +ip_opt_bind_to_ip_address(Opts, Ip) -> + case get_bind_to_ip_address(Opts) of + true -> + [{ip, list_to_tuple(Ip)}]; + _ -> + [] + end. + +ip_opt_no_reuse_address(Opts) -> + case get_no_reuse_address(Opts) of + false -> + [{reuseaddr, true}]; + _ -> + [] + end. + +ip_opt_recbuf(Opts) -> + case get_recbuf(Opts) of + use_default -> + []; + Sz -> + [{recbuf, Sz}] + end. + +ip_opt_sndbuf(Opts) -> + case get_sndbuf(Opts) of + use_default -> + []; + Sz -> + [{sndbuf, Sz}] + end. + + +%% ---------------------------------------------------------------- + +get_atl_type(Opts) -> + case snmp_misc:get_option(type, Opts, read_write) of + read_write -> + [read,write]; + write -> + [write]; + read -> + [read] + end. + +get_atl_dir(Opts) -> + snmp_misc:get_option(dir, Opts). + +get_atl_size(Opts) -> + snmp_misc:get_option(size, Opts). + +get_atl_repair(Opts) -> + snmp_misc:get_option(repair, Opts, true). + +get_verbosity(Opts) -> + snmp_misc:get_option(verbosity, Opts, ?default_verbosity). + +get_vsns(Opts) -> + snmp_misc:get_option(versions, Opts, [v1, v2, v3]). + +get_req_limit(O) -> + snmp_misc:get_option(req_limit, O, infinity). + +get_filter_opts(O) -> + snmp_misc:get_option(filter, O, []). + +get_filter_module(O) -> + snmp_misc:get_option(module, O, ?DEFAULT_FILTER_MODULE). + +get_recbuf(Opts) -> + snmp_misc:get_option(recbuf, Opts, use_default). + +get_sndbuf(Opts) -> + snmp_misc:get_option(sndbuf, Opts, use_default). + +get_no_reuse_address(Opts) -> + snmp_misc:get_option(no_reuse, Opts, false). + +get_bind_to_ip_address(Opts) -> + snmp_misc:get_option(bind_to, Opts, false). + + +%% ---------------------------------------------------------------- + +error_msg(F,A) -> + ?snmpa_error("NET-IF server: " ++ F, A). + +%% --- + +user_err(F, A) -> + snmpa_error:user_err(F, A). + +config_err(F, A) -> + snmpa_error:config_err(F, A). + + +%% ---------------------------------------------------------------- + +call(Pid, Req) -> + ReplyRef = make_ref(), + Pid ! {Req, ReplyRef, self()}, + receive + {ReplyRef, Reply, Pid} -> + Reply + after 5000 -> + {error, timeout} + end. + + +%% ---------------------------------------------------------------- + +get_info(#state{usock = Id, reqs = Reqs}) -> + ProcSize = proc_mem(self()), + PortInfo = get_port_info(Id), + Counters = get_counters(), + [{reqs, Reqs}, + {counters, Counters}, + {process_memory, ProcSize}, + {port_info, PortInfo}]. + +proc_mem(P) when is_pid(P) -> + case (catch erlang:process_info(P, memory)) of + {memory, Sz} when is_integer(Sz) -> + Sz; + _ -> + undefined + end. +%% proc_mem(_) -> +%% undefined. + +get_port_info(Id) -> + PortInfo = + case (catch erlang:port_info(Id)) of + PI when is_list(PI) -> + [{port_info, PI}]; + _ -> + [] + end, + PortStatus = + case (catch prim_inet:getstatus(Id)) of + {ok, PS} -> + [{port_status, PS}]; + _ -> + [] + end, + PortAct = + case (catch inet:getopts(Id, [active])) of + {ok, PA} -> + [{port_act, PA}]; + _ -> + [] + end, + PortStats = + case (catch inet:getstat(Id)) of + {ok, Stat} -> + [{port_stats, Stat}]; + _ -> + [] + end, + IfList = + case (catch inet:getif(Id)) of + {ok, IFs} -> + [{interfaces, IFs}]; + _ -> + [] + end, + BufSz = + case (catch inet:getopts(Id, [recbuf, sndbuf])) of + {ok, Sz} -> + [{buffer_size, Sz}]; + _ -> + [] + end, + [{socket, Id}] ++ + IfList ++ + PortStats ++ + PortInfo ++ + PortStatus ++ + PortAct ++ + BufSz. + + +%% ---------------------------------------------------------------- + +% i(F) -> +% i(F, []). + +% i(F, A) -> +% io:format("~p: " ++ F ++ "~n", [?MODULE|A]). + diff --git a/lib/snmp/src/agent/snmpa_net_if_filter.erl b/lib/snmp/src/agent/snmpa_net_if_filter.erl new file mode 100644 index 0000000000..989f7c95b3 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_net_if_filter.erl @@ -0,0 +1,52 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-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_net_if_filter). + +-export([accept_recv/2, + accept_send/2, + accept_recv_pdu/3, + accept_send_pdu/2]). + +-include("snmp_debug.hrl"). + +accept_recv(_Addr, _Port) -> + ?d("accept_recv -> entry with" + "~n Addr: ~p" + "~n Port: ~p", [_Addr, _Port]), + true. + +accept_send(_Addr, _Port) -> + ?d("accept_send -> entry with" + "~n Addr: ~p" + "~n Port: ~p", [_Addr, _Port]), + true. + +accept_recv_pdu(_Addr, _Port, _PduType) -> + ?d("accept_recv_pdu -> entry with" + "~n Addr: ~p" + "~n Port: ~p" + "~n PduType: ~p", [_Addr, _Port, _PduType]), + true. + +accept_send_pdu(_Targets, _PduType) -> + ?d("accept_send_pdu -> entry with" + "~n Targets: ~p" + "~n PduType: ~p", [_Targets, _PduType]), + true. + diff --git a/lib/snmp/src/agent/snmpa_network_interface.erl b/lib/snmp/src/agent/snmpa_network_interface.erl new file mode 100644 index 0000000000..887ea22549 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_network_interface.erl @@ -0,0 +1,45 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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_network_interface). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{start_link, 4}, + {get_log_type, 1}, + {set_log_type, 2}, + {get_request_limit, 1}, + {set_request_limit, 2}, + {info, 1}, + {verbosity, 2}]; +behaviour_info(_) -> + undefined. + + +%% behaviour_info(callbacks) -> +%% [{start_link, 4}, +%% {send_pdu, 5}, +%% {send_response_pdu, 6}, +%% {discard_pdu, 6}, +%% {send_pdu_req, 6}, +%% {verbosity, 2}, +%% {change_log_type, 2}]; +%% behaviour_info(_) -> +%% undefined. + diff --git a/lib/snmp/src/agent/snmpa_network_interface_filter.erl b/lib/snmp/src/agent/snmpa_network_interface_filter.erl new file mode 100644 index 0000000000..6fa131beee --- /dev/null +++ b/lib/snmp/src/agent/snmpa_network_interface_filter.erl @@ -0,0 +1,54 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-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_network_interface_filter). + +-export([behaviour_info/1]). +-export([verify/1]). + + +behaviour_info(callbacks) -> + [{accept_recv, 2}, + {accept_send, 2}, + {accept_recv_pdu, 3}, + {accept_send_pdu, 2}]; +behaviour_info(_) -> + undefined. + + +%% accept_recv(address(), port()) -> boolean() +%% Called at the receiption of a message +%% (before *any* processing has been done). +%% +%% accept_send(address(), port()) -> boolean() +%% Called before the sending of a message +%% (after *all* processing has been done). +%% +%% accept_recv_pdu(Addr, Port, pdu_type()) -> boolean() +%% Called after the basic message processing (MPD) has been done, +%% but before the pdu is handed over to the master-agent for +%% primary processing. +%% +%% accept_send_pdu(Targets, pdu_type()) -> boolean() | NewTargets +%% Called before the basic message processing (MPD) is done, +%% when a pdu has been received from the master-agent. +%% + + +verify(Module) -> + snmp_misc:verify_behaviour(?MODULE, Module). diff --git a/lib/snmp/src/agent/snmpa_notification_delivery_info_receiver.erl b/lib/snmp/src/agent/snmpa_notification_delivery_info_receiver.erl new file mode 100644 index 0000000000..b9ec5ff05e --- /dev/null +++ b/lib/snmp/src/agent/snmpa_notification_delivery_info_receiver.erl @@ -0,0 +1,35 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-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_notification_delivery_info_receiver). + +-export([behaviour_info/1]). +-export([verify/1]). + +behaviour_info(callbacks) -> + [ + {delivery_targets, 3}, + {delivery_info, 4} + ]; +behaviour_info(_) -> + undefined. + + +verify(Module) -> + snmp_misc:verify_behaviour(?MODULE, Module). diff --git a/lib/snmp/src/agent/snmpa_notification_filter.erl b/lib/snmp/src/agent/snmpa_notification_filter.erl new file mode 100644 index 0000000000..199cf725fd --- /dev/null +++ b/lib/snmp/src/agent/snmpa_notification_filter.erl @@ -0,0 +1,36 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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_notification_filter). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{handle_notification, 2}]; +behaviour_info(_) -> + undefined. + +%% handle_notification(Notification, Data) -> Reply +%% Notification -> notification() | trap() +%% Data -> term() +%% Reply -> send | {send, NewNotif} | ignore +%% NewNotif -> notification() | trap() +%% +%% send -> This means it is ok for this filter to send the notification as is +%% {send, NewNotif} -> Send this notification instead +%% dont_sent -> Dont send this notification. diff --git a/lib/snmp/src/agent/snmpa_set.erl b/lib/snmp/src/agent/snmpa_set.erl new file mode 100644 index 0000000000..0f85d0aaa0 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_set.erl @@ -0,0 +1,244 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_set). + +-behaviour(snmpa_set_mechanism). + +-define(VMODULE,"SET"). +-include("snmp_verbosity.hrl"). + + +%%%----------------------------------------------------------------- +%%% This module implements a simple, basic atomic set mechanism. +%%%----------------------------------------------------------------- +%%% Table of contents +%%% ================= +%%% 1. SET REQUEST +%%% 1.1 SET phase one +%%% 1.2 SET phase two +%%% 2. Misc functions +%%%----------------------------------------------------------------- + +%% External exports +-export([do_set/2, do_subagent_set/1]). + +%%%----------------------------------------------------------------- +%%% 1. SET REQUEST +%%% +%%% 1) Perform set_phase_one for all own vars +%%% 2) Perform set_phase_one for all SAs +%%% IF nok THEN 2.1 ELSE 3 +%%% 2.1) Perform set_phase_two(undo) for all SAs that have performed +%%% set_phase_one. +%%% 3) Perform set_phase_two for all own vars +%%% 4) Perform set_phase_two(set) for all SAs +%%% IF nok THEN 4.1 ELSE 5 +%%% 4.1) Perform set_phase_two(undo) for all SAs that have performed +%%% set_phase_one but not set_phase_two(set). +%%% 5) noError +%%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% First of all - validate MibView for all varbinds. In this way +%% we don't have to send the MibView to all SAs for validation. +%%----------------------------------------------------------------- +do_set(MibView, UnsortedVarbinds) -> + ?vtrace("do set with" + "~n MibView: ~p",[MibView]), + case snmpa_acm:validate_all_mib_view(UnsortedVarbinds, MibView) of + true -> + {MyVarbinds , SubagentVarbinds} = + sort_varbindlist(UnsortedVarbinds), + case set_phase_one(MyVarbinds, SubagentVarbinds) of + {noError, 0} -> set_phase_two(MyVarbinds, SubagentVarbinds); + {Reason, Index} -> {Reason, Index} + end; + {false, Index} -> + {noAccess, Index} + end. + +%%----------------------------------------------------------------- +%% This function is called when a subagents receives a message +%% concerning some set_phase. +%% Mandatory messages for all subagents: +%% [phase_one, UnsortedVarbinds] +%% [phase_two, set, UnsortedVarbinds] +%% [phase_two, undo, UnsortedVarbinds] +%%----------------------------------------------------------------- +do_subagent_set([phase_one, UnsortedVarbinds]) -> + ?vtrace("do subagent set, phase one",[]), + {MyVarbinds, SubagentVarbinds} = sort_varbindlist(UnsortedVarbinds), + set_phase_one(MyVarbinds, SubagentVarbinds); +do_subagent_set([phase_two, State, UnsortedVarbinds]) -> + ?vtrace("do subagent set, phase two",[]), + {MyVarbinds, SubagentVarbinds} = sort_varbindlist(UnsortedVarbinds), + set_phase_two(State, MyVarbinds, SubagentVarbinds). + +%%%----------------------------------------------------------------- +%%% 1.1 SET phase one +%%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% Func: set_phase_one/3 +%% Purpose: First, do set_phase_one for my own variables (i.e. +%% variables handled by this agent). Then, do set_phase_one +%% for all subagents. If any SA failed, do set_phase_two +%% (undo) for all SA that have done set_phase_one. +%% Returns: {noError, 0} | {ErrorStatus, Index} +%%----------------------------------------------------------------- +set_phase_one(MyVarbinds, SubagentVarbinds) -> + ?vtrace("set phase one: " + "~n MyVarbinds: ~p" + "~n SubagentVarbinds: ~p", + [MyVarbinds, SubagentVarbinds]), + case set_phase_one_my_variables(MyVarbinds) of + {noError, 0} -> + case set_phase_one_subagents(SubagentVarbinds, []) of + {noError, 0} -> + {noError, 0}; + {{ErrorStatus, Index}, PerformedSubagents} -> + case set_phase_two_undo(MyVarbinds, PerformedSubagents) of + {noError, 0} -> + {ErrorStatus, Index}; + {WorseErrorStatus, WorseIndex} -> + {WorseErrorStatus, WorseIndex} + end + end; + {ErrorStatus, Index} -> + {ErrorStatus, Index} + end. + +set_phase_one_my_variables(MyVarbinds) -> + ?vtrace("my variables set, phase one:" + "~n ~p",[MyVarbinds]), + case snmpa_set_lib:is_varbinds_ok(MyVarbinds) of + {noError, 0} -> + snmpa_set_lib:consistency_check(MyVarbinds); + {ErrorStatus, Index} -> + {ErrorStatus, Index} + end. + +%%----------------------------------------------------------------- +%% Loop all subagents, and perform set_phase_one for them. +%%----------------------------------------------------------------- +set_phase_one_subagents([{SubAgentPid, SAVbs}|SubagentVarbinds], Done) -> + {_SAOids, Vbs} = sa_split(SAVbs), + case (catch snmpa_agent:subagent_set(SubAgentPid, [phase_one, Vbs])) of + {noError, 0} -> + set_phase_one_subagents(SubagentVarbinds, + [{SubAgentPid, SAVbs} | Done]); + {'EXIT', Reason} -> + user_err("Lost contact with subagent (set phase_one)" + "~n~w. Using genErr", [Reason]), + {{genErr, 0}, Done}; + {ErrorStatus, ErrorIndex} -> + {{ErrorStatus, ErrorIndex}, Done} + end; +set_phase_one_subagents([], _Done) -> + {noError, 0}. + +%%%----------------------------------------------------------------- +%%% 1.2 SET phase two +%%%----------------------------------------------------------------- +%% returns: {ErrStatus, ErrIndex} +set_phase_two(MyVarbinds, SubagentVarbinds) -> + ?vtrace("set phase two: " + "~n MyVarbinds: ~p" + "~n SubagentVarbinds: ~p", + [MyVarbinds, SubagentVarbinds]), + case snmpa_set_lib:try_set(MyVarbinds) of + {noError, 0} -> + set_phase_two_subagents(SubagentVarbinds); + {ErrorStatus, Index} -> + set_phase_two_undo_subagents(SubagentVarbinds), + {ErrorStatus, Index} + end. + +%%----------------------------------------------------------------- +%% This function is called for each phase_two state in the +%% subagents. The undo state just pass undo along to each of its +%% subagents. +%%----------------------------------------------------------------- +set_phase_two(set, MyVarbinds, SubagentVarbinds) -> + set_phase_two(MyVarbinds, SubagentVarbinds); +set_phase_two(undo, MyVarbinds, SubagentVarbinds) -> + set_phase_two_undo(MyVarbinds, SubagentVarbinds). + +%%----------------------------------------------------------------- +%% Loop all subagents, and perform set_phase_two(set) for them. +%% If any fails, perform set_phase_two(undo) for the not yet +%% called SAs. +%%----------------------------------------------------------------- +set_phase_two_subagents([{SubAgentPid, SAVbs} | SubagentVarbinds]) -> + {_SAOids, Vbs} = sa_split(SAVbs), + case catch snmpa_agent:subagent_set(SubAgentPid, [phase_two, set, Vbs]) of + {noError, 0} -> + set_phase_two_subagents(SubagentVarbinds); + {'EXIT', Reason} -> + user_err("Lost contact with subagent (set)~n~w. Using genErr", + [Reason]), + set_phase_two_undo_subagents(SubagentVarbinds), + {genErr, 0}; + {ErrorStatus, ErrorIndex} -> + set_phase_two_undo_subagents(SubagentVarbinds), + {ErrorStatus, ErrorIndex} + end; +set_phase_two_subagents([]) -> + {noError, 0}. + +%%----------------------------------------------------------------- +%% This function undos phase_one, own and subagent. +%%----------------------------------------------------------------- +set_phase_two_undo(MyVarbinds, SubagentVarbinds) -> + case set_phase_two_undo_my_variables(MyVarbinds) of + {noError, 0} -> + set_phase_two_undo_subagents(SubagentVarbinds); + {ErrorStatus, Index} -> + set_phase_two_undo_subagents(SubagentVarbinds), + {ErrorStatus, Index} + end. + +set_phase_two_undo_my_variables(MyVarbinds) -> + snmpa_set_lib:undo_varbinds(MyVarbinds). + +set_phase_two_undo_subagents([{SubAgentPid, SAVbs} | SubagentVarbinds]) -> + {_SAOids, Vbs} = sa_split(SAVbs), + case catch snmpa_agent:subagent_set(SubAgentPid, [phase_two, undo, Vbs]) of + {noError, 0} -> + set_phase_two_undo_subagents(SubagentVarbinds); + {'EXIT', Reason} -> + user_err("Lost contact with subagent (undo)~n~w. Using genErr", + [Reason]), + {genErr, 0}; + {ErrorStatus, ErrorIndex} -> + {ErrorStatus, ErrorIndex} + end; +set_phase_two_undo_subagents([]) -> + {noError, 0}. + +%%%----------------------------------------------------------------- +%%% 2. Misc functions +%%%----------------------------------------------------------------- +sort_varbindlist(Varbinds) -> + snmpa_svbl:sort_varbindlist(get(mibserver), Varbinds). + +sa_split(SubagentVarbinds) -> + snmpa_svbl:sa_split(SubagentVarbinds). + + +user_err(F, A) -> + snmpa_error:user_err(F, A). diff --git a/lib/snmp/src/agent/snmpa_set_lib.erl b/lib/snmp/src/agent/snmpa_set_lib.erl new file mode 100644 index 0000000000..191029f6db --- /dev/null +++ b/lib/snmp/src/agent/snmpa_set_lib.erl @@ -0,0 +1,395 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_set_lib). + +-include("snmp_types.hrl"). + +-define(VMODULE,"SETLIB"). +-include("snmp_verbosity.hrl"). + + +%% External exports +-export([is_varbinds_ok/1, consistency_check/1, + undo_varbinds/1, try_set/1]). + +%%%----------------------------------------------------------------- +%%% This module implements functions common to all different set +%%% mechanisms. +%%%----------------------------------------------------------------- +%% Phase 1 of the set-operation. (see rfc1905:4.2.5) +%% Input is a sorted Varbinds list. +%% Output is either ok or {ErrIndex, ErrCode}. +%%* 1) IF the variable is outside the mib view THEN noAccess. +%%* 2a) IF the varaible doesn't exist THEN notWritable. +%% 2b) IF mib isn't able to create instances of the variable (for +%% example in a table) THEN noCreation. +%%* 3) IF the new value provided is of the wrong ASN.1 type THEN wrongType. +%%* 4) IF then new value is of wrong length THEN wrongLength. +%%# 5) IF value is incorrectly encoded THEN wrongEncoding. [nyi] +%%* 6) IF value is outside the acceptable range THEN wrongValue. +%% 7) IF variable does not exist and could not ever be created +%% THEN noCreation. +%% 8) IF variable can not be created now THEN inconsistentName. +%% 9) IF value can not be set now THEN inconsistentValue. +%%* 9) IF instances of the variable can not be modified THEN notWritable. +%% 10) IF an unavailable resource is needed THEN resourceUnavailable. +%% 11) IF any other error THEN genErr. +%% 12) Otherwise ok! +%% The Agent takes care of *-marked. (# should be done by the agent but is +%% nyi.) +%% The rest is taken care of in consistency_check that communicates with the mib +%% (through the is_set_ok-function (for each variable)). +%% +%% SNMPv1 (see rfc1157:4.1.5) +%%* 1) IF variable not available for set in the mibview THEN noSuchName. +%%* 2) IF variable exists, but doesn't permit writeing THEN readOnly. +%%* 3) IF provided value doesn't match ASN.1 type THEN badValue. +%% 4) IF any other error THEN genErr. +%% 5) Otherwise ok! +%% +%% Internally we use snmpv2 messages, and convert them to the appropriate +%% v1 msg. +%%----------------------------------------------------------------- +%% Func: is_varbinds_ok/2 +%% Purpose: Call is_varbind_ok for each varbind +%% Returns: {noError, 0} | {ErrorStatus, ErrorIndex} +%%----------------------------------------------------------------- +is_varbinds_ok([{_TableOid, TableVbs} | IVarbinds]) -> + is_varbinds_ok(lists:append(TableVbs, IVarbinds)); +is_varbinds_ok([IVarbind | IVarbinds]) -> + case catch is_varbind_ok(IVarbind) of + true -> is_varbinds_ok(IVarbinds); + ErrorStatus -> + Varbind = IVarbind#ivarbind.varbind, + ?vtrace("varbinds erroneous: ~p -> ~p", + [Varbind#varbind.org_index,ErrorStatus]), + {ErrorStatus, Varbind#varbind.org_index} + end; +is_varbinds_ok([]) -> + ?vtrace("varbinds ok",[]), + {noError, 0}. + +%%----------------------------------------------------------------- +%% Func: is_varbind_ok/1 +%% Purpose: Check everything we can check about the varbind against +%% the MIB. Here we don't call any instrumentation functions. +%% Returns: true | +%% Fails: with an <error-atom>. +%%----------------------------------------------------------------- +is_varbind_ok(#ivarbind{status = Status, + mibentry = MibEntry, + varbind = Varbind}) -> + variableExists(Status, MibEntry, Varbind), + % If we get here, MibEntry /= false + variableIsWritable(MibEntry, Varbind), + checkASN1Type(MibEntry, Varbind), + checkValEncoding(MibEntry, Varbind), + true. + +variableExists(noError, false, _Varbind) -> throw(notWritable); +variableExists(noError, _MibEntry, _Varbind) -> true; +%% ErrorStatus == noSuchObject | noSuchInstance +variableExists(noSuchObject, _MibEntry, _Varbind) -> throw(notWritable); +variableExists(_ErrorStatus, _MibEntry, _Varbind) -> throw(noCreation). + +variableIsWritable(#me{access = 'read-write'}, _Varbind) -> true; +variableIsWritable(#me{access = 'read-create'}, _Varbind) -> true; +variableIsWritable(#me{access = 'write-only'}, _Varbind) -> true; +variableIsWritable(_MibEntry, _Varbind) -> throw(notWritable). + +%% Uses make_value_a_correct_value to check type, length and range. +%% Returns: true | <error-atom> +checkASN1Type(#me{asn1_type = ASN1Type}, + #varbind{variabletype = Type, value = Value}) + when ASN1Type#asn1_type.bertype =:= Type -> + case make_value_a_correct_value({value, Value}, ASN1Type, + undef) of + {value, Type, Value} -> true; + {error, Error} when is_atom(Error) -> throw(Error) + end; + +checkASN1Type(_,_) -> throw(wrongType). + + +%% tricky... +checkValEncoding(_MibEntry, _Varbind) -> true. + +%%----------------------------------------------------------------- +%% Func: consistency_check/1 +%% Purpose: Scans all vbs, and checks whether there is a is_set_ok +%% function or not. If it exists, it is called with the +%% vb, to check if the set-op is ok. If it doesn't exist, +%% it is considered ok to set the variable. +%% Returns: {noError, 0} | {ErrorStatus, ErrorIndex] +%% PRE: #ivarbind.status == noError for each varbind +%%----------------------------------------------------------------- +consistency_check(Varbinds) -> + consistency_check(Varbinds, []). +consistency_check([{TableOid, TableVbs} | Varbinds], Done) -> + ?vtrace("consistency_check -> entry with" + "~n TableOid: ~p" + "~n TableVbs: ~p",[TableOid,TableVbs]), + TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), + [#ivarbind{mibentry = MibEntry}|_] = TableVbs, + case is_set_ok_table(MibEntry, TableOpsWithShortOids) of + {noError, 0} -> + consistency_check(Varbinds, [{TableOid, TableVbs} | Done]); + {Reason, Idx} -> + case undo_varbinds(Done) of + {noError, 0} -> {Reason, find_org_index(Idx, TableVbs)}; + Error -> Error + end + end; +consistency_check([IVarbind | Varbinds], Done) -> + ?vtrace("consistency_check -> entry with" + "~n IVarbind: ~p",[IVarbind]), + #ivarbind{varbind = Varbind, mibentry = MibEntry} = IVarbind, + #varbind{value = Value, org_index = OrgIndex} = Varbind, + case is_set_ok_variable(MibEntry, Value) of + noError -> consistency_check(Varbinds, [IVarbind | Done]); + Reason -> + case undo_varbinds(Done) of + {noError, 0} -> {Reason, OrgIndex}; + Error -> Error + end + end; +consistency_check([], _Done) -> + ?vtrace("consistency_check -> done",[]), + {noError, 0}. + +deletePrefixes(Prefix, [#ivarbind{varbind = Varbind} | Vbs]) -> + #varbind{oid = Oid, value = Value} = Varbind, + [{snmp_misc:diff(Oid, Prefix), Value} | deletePrefixes(Prefix, Vbs)]; +deletePrefixes(_Prefix, []) -> []. + +%% Val = <a-value> +is_set_ok_variable(#me{mfa = {Module, Func, Args} = MFA}, Val) -> + ?vtrace("is_set_ok_variable -> entry with" + "~n MFA: ~p" + "~n Val: ~p",[MFA,Val]), + case dbg_apply(Module, Func, [is_set_ok, Val | Args]) of + {'EXIT', {hook_undef, _}} -> noError; + {'EXIT', {hook_function_clause, _}} -> noError; + Result -> + ?vtrace("is_set_ok_variable -> Result: ~n ~p", [Result]), + validate_err(is_set_ok, Result, {Module, Func, Args}) + end. + +%% ValueArg: <list-of-simple-tableops> +is_set_ok_table(#me{mfa = {Module, Func, Args} = MFA}, ValueArg) -> + ?vtrace("is_set_ok_table -> entry with" + "~n MFA: ~p" + "~n ValueArg: ~p",[MFA,ValueArg]), + is_set_ok_all_rows(Module, Func, Args, sort_varbinds_rows(ValueArg), []). + +%% Try one row at a time. Sort varbinds to table-format. +is_set_ok_all_rows(Module, Func, Args, [Row | Rows], Done) -> + ?vtrace("is_set_ok_all_rows -> entry with" + "~n MFA: ~p" + "~n Row: ~p" + "~n Done: ~p",[{Module,Func,Args},Row,Done]), + [{RowIndex, Cols}] = delete_org_index([Row]), + case dbg_apply(Module, Func, [is_set_ok, RowIndex, Cols | Args]) of + {'EXIT', {hook_undef, _}} -> + is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]); + {'EXIT', {hook_function_clause, _}} -> + is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]); + Result -> + case validate_err(table_is_set_ok, Result, {Module, Func, Args}) of + {noError, 0} -> + is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]); + {ErrorStatus, ColNumber} -> + case undo_all_rows(Module, Func, Args, Done) of + {noError, 0} -> + {RowIndex, OrgCols} = Row, + validate_err(row_is_set_ok, + {ErrorStatus, + col_to_orgindex(ColNumber,OrgCols)}, + {Module, Func, Args}); + Error -> Error + end + end + end; +is_set_ok_all_rows(_Module, _Func, _Args, [], _Done) -> + ?vtrace("is_set_ok_all_rows -> done",[]), + {noError, 0}. + +undo_varbinds([{TableOid, TableVbs} | Varbinds]) -> + TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), + [#ivarbind{mibentry = MibEntry}|_] = TableVbs, + case undo_table(MibEntry, TableOpsWithShortOids) of + {noError, 0} -> + undo_varbinds(Varbinds); + {Reason, Idx} -> + undo_varbinds(Varbinds), + {Reason, Idx} + end; +undo_varbinds([IVarbind | Varbinds]) -> + #ivarbind{varbind = Varbind, mibentry = MibEntry} = IVarbind, + #varbind{value = Value, org_index = OrgIndex} = Varbind, + case undo_variable(MibEntry, Value) of + noError -> undo_varbinds(Varbinds); + Reason -> + undo_varbinds(Varbinds), + {Reason, OrgIndex} + end; +undo_varbinds([]) -> {noError, 0}. + +%% Val = <a-value> +undo_variable(#me{mfa = {Module, Func, Args}}, Val) -> + case dbg_apply(Module, Func, [undo, Val | Args]) of + {'EXIT', {hook_undef, _}} -> noError; + {'EXIT', {hook_function_clause, _}} -> noError; + Result -> validate_err(undo, Result, {Module, Func, Args}) + end. + +%% ValueArg: <list-of-simple-tableops> +undo_table(#me{mfa = {Module, Func, Args}}, ValueArg) -> + undo_all_rows(Module, Func, Args, sort_varbinds_rows(ValueArg)). + +undo_all_rows(Module, Func, Args, [Row | Rows]) -> + [{RowIndex, Cols}] = delete_org_index([Row]), + case dbg_apply(Module, Func, [undo, RowIndex, Cols | Args]) of + {'EXIT', {hook_undef, _}} -> + undo_all_rows(Module, Func, Args, Rows); + {'EXIT', {hook_function_clause, _}} -> + undo_all_rows(Module, Func, Args, Rows); + Result -> + case validate_err(table_undo, Result, {Module, Func, Args}) of + {noError, 0} -> undo_all_rows(Module, Func, Args, Rows); + {ErrorStatus, ColNumber} -> + {RowIndex, OrgCols} = Row, + undo_all_rows(Module, Func, Args, Rows), + OrgIdx = col_to_orgindex(ColNumber, OrgCols), + validate_err(row_undo, {ErrorStatus, OrgIdx}, + {Module, Func, Args}) + end + end; +undo_all_rows(_Module, _Func, _Args, []) -> + {noError, 0}. + +try_set([{TableOid, TableVbs} | Varbinds]) -> + TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), + [#ivarbind{mibentry = MibEntry}|_] = TableVbs, + case set_value_to_tab_mibentry(MibEntry, TableOpsWithShortOids) of + {noError, 0} -> + try_set(Varbinds); + {ErrorStatus, Index} -> + undo_varbinds(Varbinds), + {ErrorStatus, find_org_index(Index, TableVbs)} + end; +try_set([#ivarbind{varbind = Varbind, mibentry = MibEntry} | Varbinds]) -> + #varbind{value = Value, org_index = Index} = Varbind, + case set_value_to_var_mibentry(MibEntry, Value) of + noError -> try_set(Varbinds); + Error -> + undo_varbinds(Varbinds), + {Error, Index} + end; +try_set([]) -> {noError, 0}. + + +%% returns: ErrMsg +set_value_to_var_mibentry(#me{mfa = {Module, Func, Args}}, + SetArgs) -> + Result = dbg_apply(Module, Func, [set, SetArgs | Args]), + validate_err(set, Result, {Module, Func, Args}). + +%% returns: {ErrMsg, Idx} +set_value_to_tab_mibentry(#me{mfa = {Module, Func, Args}}, + SetArgs) -> + set_value_all_rows(Module, Func, Args, + sort_varbinds_rows(SetArgs)). + + +set_value_all_rows(_Module, _Func, _Args, []) -> {noError, 0}; +set_value_all_rows(Module, Func, Args, [Row | Rows]) -> + [{RowIndex, Cols}] = delete_org_index([Row]), + Res = dbg_apply(Module, Func, [set, RowIndex, Cols | Args]), + case validate_err(table_set, Res, {Module, Func, Args}) of + {noError, 0} -> set_value_all_rows(Module, Func, Args, Rows); + {ErrCode, ColNumber} -> + {RowIndex, OrgCols} = Row, + OrgIndex = col_to_orgindex(ColNumber, OrgCols), + validate_err(row_set, {ErrCode, OrgIndex}, {Module, Func, Args}) + end. + +sort_varbinds_rows(Varbinds) -> + snmpa_svbl:sort_varbinds_rows(Varbinds). +delete_org_index(Indexes) -> + snmpa_svbl:delete_org_index(Indexes). +col_to_orgindex(ColNumber, OrgCols) -> + snmpa_svbl:col_to_orgindex(ColNumber, OrgCols). + +find_org_index(ExternIndex, _SortedVarbinds) when ExternIndex =:= 0 -> 0; +find_org_index(ExternIndex, SortedVarbinds) -> + VBs = lists:flatten(SortedVarbinds), + case length(VBs) of + Len when (ExternIndex =< Len) andalso (ExternIndex >= 1) -> + IVB = lists:nth(ExternIndex, VBs), + VB = IVB#ivarbind.varbind, + VB#varbind.org_index; + _Else -> + 0 + end. + +validate_err(Type, Error, Mfa) -> + snmpa_agent:validate_err(Type, Error, Mfa). +make_value_a_correct_value(Value, ASN1Type, Mfa) -> + snmpa_agent:make_value_a_correct_value(Value, ASN1Type, Mfa). + +%%----------------------------------------------------------------- +%% Runtime debug support +%%----------------------------------------------------------------- + +% XXX: This function match on the exakt return codes from EXIT +% messages. As of this writing it was not decided if this is +% the right way so don't blindly do things this way. +% +% We fake a real EXIT signal as the return value because the +% result is passed to the function snmpa_agent:validate_err() +% that expect it. + +dbg_apply(M,F,A) -> + Result = + case get(verbosity) of + false -> + (catch apply(M,F,A)); + _ -> + ?vlog("~n apply: ~w,~w,~p~n", [M,F,A]), + Res = (catch apply(M,F,A)), + ?vlog("~n returned: ~p", [Res]), + Res + end, + case Result of + {'EXIT', {undef, [{M, F, A} | _]}} -> + {'EXIT', {hook_undef, {M, F, A}}}; + {'EXIT', {function_clause, [{M, F, A} | _]}} -> + {'EXIT', {hook_function_clause, {M, F, A}}}; + + % XXX: Old format for compatibility + {'EXIT', {undef, {M, F, A}}} -> + {'EXIT', {hook_undef, {M, F, A}}}; + {'EXIT', {function_clause, {M, F, A}}} -> + {'EXIT', {hook_function_clause, {M, F, A}}}; + + Result -> + Result + end. + diff --git a/lib/snmp/src/agent/snmpa_set_mechanism.erl b/lib/snmp/src/agent/snmpa_set_mechanism.erl new file mode 100644 index 0000000000..4561fb035b --- /dev/null +++ b/lib/snmp/src/agent/snmpa_set_mechanism.erl @@ -0,0 +1,42 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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_set_mechanism). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{do_set, 2}, {do_subagent_set, 1}]; +behaviour_info(_) -> + undefined. + + +%%----------------------------------------------------------------- +%% do_set(MibView, UnsortedVarbinds) +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% do_subagent_set(Args) +%% +%% This function is called when a subagent receives a message +%% concerning some set_phase. +%% Mandatory messages for all subagents: +%% [phase_one, UnsortedVarbinds] +%% [phase_two, set, UnsortedVarbinds] +%% [phase_two, undo, UnsortedVarbinds] +%%----------------------------------------------------------------- diff --git a/lib/snmp/src/agent/snmpa_supervisor.erl b/lib/snmp/src/agent/snmpa_supervisor.erl new file mode 100644 index 0000000000..5ef5914e18 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_supervisor.erl @@ -0,0 +1,564 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_supervisor). + +-behaviour(supervisor). + +%% External exports +-export([start_link/2]). +-export([start_sub_sup/1, start_master_sup/1]). +-export([start_sub_agent/3, stop_sub_agent/1]). + +%% Internal exports +-export([init/1, config/2]). + + +-define(SERVER, ?MODULE). + +-include("snmpa_internal.hrl"). +-include("snmp_verbosity.hrl"). +-include("snmp_debug.hrl"). + + +%%----------------------------------------------------------------- +%% Process structure +%% ================= +%% +%% ___________________ supervisor __________________ +%% / | | \ \ +%% ___misc_sup___ target_cache symbolic_store local_db agent_sup +%% / | \ | | +%% mib net_if note_store MA - SA +%% +%% The supervisor (one at each node) starts: +%% snmpa_symbolic_store (one at each node) +%% snmpa_local_db (one at each node) +%% snmpa_target_cache (one at each node) +%% MA - which starts +%% own mib (hangs it under misc_sup) +%% net_if (hangs it under misc_sup) +%% note_store (hangs it under misc_sup) +%% SAs - which starts +%% own mib (hangs it under misc_sup) +%% +%% This structure is intended to make the agent fault tolerant. The +%% agent processes (by far most code is in these processes) can be +%% restarted in case of a failure, as all data needed by these procs +%% are stored in the other procs. Any other process crash leads to +%% that the entire snmpa_supervisor crashes. If it is restarted, all +%% dynamically loaded mibs must be reloaded. +%% +%% It is up to the supervisor to configure the internal and +%% external mibs. Note that the +%% agent group (internal MIB) and sysObjectID *must* be configured +%% in some way. +%%----------------------------------------------------------------- + +start_link(Type, Opts) -> + ?d("start_link -> entry with" + "~n Type. ~p" + "~n Opts. ~p", [Type, Opts]), + start_link(get_agent_type(Opts), Opts, Type). + +start_link(sub, Opts, _Type) -> + start_sub_sup(Opts); +start_link(master, Opts, normal) -> + start_master_sup(Opts); +start_link(master, Opts, {takeover, Node}) -> + case start_master_sup(Opts) of + {ok, Pid} -> + OwnMibNames = get_own_loaded_mibs(), + try_load_other_loaded_mibs(Node, OwnMibNames), + {ok, Pid}; + Else -> + Else + end. + +get_own_loaded_mibs() -> + AgentInfo = snmpa:info(snmp_master_agent), + [ Name || {Name, _, _} <- loaded_mibs(AgentInfo) ]. + +try_load_other_loaded_mibs(Node, OwnMibs) -> + case rpc:call(Node, snmpa, info, [snmp_master_agent]) of + {badrpc, R} -> + error_msg("could not takeover loaded mibs: ~p", [R]); + AgentInfo -> + LoadedMibs = loaded_mibs(AgentInfo), + MibsToLoad = mibs_to_load(LoadedMibs, OwnMibs), + lists:foreach(fun(M) -> takeover_mib(M) end, MibsToLoad) + end. + +loaded_mibs(AgentInfo) -> + {value, {_, MibInfo}} = key1search(mib_server, AgentInfo), + {value, {_, LoadedMibs}} = key1search(loaded_mibs, MibInfo), + LoadedMibs. + +mibs_to_load(OtherMibs, OwnMibs) -> + [{N, S, F} || {N, S, F} <- OtherMibs, not lists:member(N, OwnMibs)]. + +takeover_mib({'STANDARD-MIB', _Symbolic, _FileName}) -> + ok; +takeover_mib({'SNMPv2-MIB', _Symbolic, _FileName}) -> + ok; +takeover_mib({_MibName, _Symbolic, FileName}) -> + case snmpa:load_mibs(snmp_master_agent, [FileName]) of + ok -> + ok; + {error, R} -> + error_msg("could not reload mib ~p: ~p", [FileName, R]) + end. + + +%% ---------------------------------------------------------------- + +start_sub_sup(Opts) -> + ?d("start_sub_sup -> entry with" + "~n Opts: ~p", [Opts]), + (catch do_start_sub_sup(Opts)). + +do_start_sub_sup(Opts) -> + verify_mandatory([db_dir], Opts), + ?d("do_start_sub_sup -> start (sub) supervisor",[]), + supervisor:start_link({local, ?SERVER}, ?MODULE, [sub, Opts]). + +start_master_sup(Opts) -> + (catch do_start_master_sup(Opts)). + +do_start_master_sup(Opts) -> + verify_mandatory([db_dir], Opts), + supervisor:start_link({local, ?SERVER}, ?MODULE, [master, Opts]). + +verify_mandatory([], _) -> + ok; +verify_mandatory([Key|Keys], Opts) -> + case lists:keymember(Key, 1, Opts) of + true -> + verify_mandatory(Keys, Opts); + false -> + throw({error, {missing_mandatory_option, Key}}) + end. + + +%% ---------------------------------------------------------------- + +start_sub_agent(ParentAgent, Subtree, Mibs) + when is_pid(ParentAgent) andalso is_list(Mibs) -> + ?d("start_sub_agent -> entry with" + "~n ParentAgent: ~p" + "~n Subtree: ~p" + "~n Mibs: ~p", [ParentAgent, Subtree, Mibs]), + snmpa_agent_sup:start_subagent(ParentAgent, Subtree, Mibs). + +stop_sub_agent(SubAgentPid) -> + snmpa_agent_sup:stop_subagent(SubAgentPid). + + +%% ---------------------------------------------------------------- + +init([AgentType, Opts]) -> + ?d("init -> entry with" + "~n AgentType: ~p" + "~n Opts: ~p", [AgentType, Opts]), + + put(sname, asup), + put(verbosity,get_verbosity(Opts)), + + ?vlog("starting",[]), + + ?vdebug("create agent table",[]), + ets:new(snmp_agent_table, [set, public, named_table]), + + ?vdebug("create community cache",[]), + ets:new(snmp_community_cache, [bag, public, named_table]), + + %% Get restart type for the agent + Restart = get_opt(restart_type, Opts, permanent), + ?vdebug("agent restart type: ~w", [Restart]), + + %% -- Agent type -- + ets:insert(snmp_agent_table, {agent_type, AgentType}), + + %% -- Prio -- + Prio = get_opt(priority, Opts, normal), + ?vdebug("[agent table] store priority: ~p",[Prio]), + ets:insert(snmp_agent_table, {priority, Prio}), + + %% -- Versions -- + Vsns = get_opt(versions, Opts, [v1,v2,v3]), + ?vdebug("[agent table] store versions: ~p",[Vsns]), + ets:insert(snmp_agent_table, {versions, Vsns}), + + %% -- DB-directory -- + DbDir = get_opt(db_dir, Opts), + ?vdebug("[agent table] store db_dir: ~n ~p",[DbDir]), + ets:insert(snmp_agent_table, {db_dir, filename:join([DbDir])}), + + DbInitError = get_opt(db_init_error, Opts, terminate), + ?vdebug("[agent table] store db_init_error: ~n ~p",[DbInitError]), + ets:insert(snmp_agent_table, {db_init_error, DbInitError}), + + %% -- Error report module -- + ErrorReportMod = get_opt(error_report_mod, Opts, snmpa_error_logger), + ?vdebug("[agent table] store error report module: ~w",[ErrorReportMod]), + ets:insert(snmp_agent_table, {error_report_mod, ErrorReportMod}), + + %% -- mib storage -- + MibStorage = + case get_opt(mib_storage, Opts, ets) of + dets -> + {dets, DbDir}; + {dets, default} -> + {dets, DbDir}; + {dets, default, Act} -> + {dets, DbDir, Act}; + {ets, default} -> + {ets, DbDir}; + mnesia -> + {mnesia, erlang:nodes()}; + {mnesia, visible} -> + {mnesia, erlang:nodes(visible)}; + {mnesia, connected} -> + {mnesia, erlang:nodes(connected)}; + Other -> + Other + end, + ?vdebug("[agent table] store mib storage: ~w",[MibStorage]), + ets:insert(snmp_agent_table, {mib_storage, MibStorage}), + + %% -- Agent mib storage -- + AgentMibStorage = get_opt(agent_mib_storage, Opts, persistent), + %% ?vdebug("[agent table] store agent mib storage: ~w",[AgentMibStorage]), + ets:insert(snmp_agent_table, {agent_mib_storage, AgentMibStorage}), + + %% -- System start time -- + ?vdebug("[agent table] store system start time",[]), + ets:insert(snmp_agent_table, {system_start_time, snmp_misc:now(cs)}), + + %% -- Symbolic store options -- + SsOpts = get_opt(symbolic_store, Opts, []), + ?vdebug("[agent table] store symbolic store options: ~w",[SsOpts]), + ets:insert(snmp_agent_table, {symbolic_store, SsOpts}), + + %% -- Local DB options -- + LdbOpts = get_opt(local_db, Opts, []), + ?vdebug("[agent table] store local db options: ~w",[LdbOpts]), + ets:insert(snmp_agent_table, {local_db, LdbOpts}), + + %% -- Target cache options -- + TargetCacheOpts = get_opt(target_cache, Opts, []), + ?vdebug("[agent table] store target cache options: ~w",[TargetCacheOpts]), + ets:insert(snmp_agent_table, {target_cache, TargetCacheOpts}), + + %% -- Specs -- + SupFlags = {one_for_all, 0, 3600}, + + MiscSupSpec = + sup_spec(snmpa_misc_sup, [], Restart, infinity), + + SymStoreOpts = [{mib_storage, MibStorage} | SsOpts], + SymStoreArgs = [Prio, SymStoreOpts], + SymStoreSpec = + worker_spec(snmpa_symbolic_store, SymStoreArgs, Restart, 2000), + + LdbArgs = [Prio, DbDir, LdbOpts], + LocalDbSpec = + worker_spec(snmpa_local_db, LdbArgs, Restart, 5000), + + ?vdebug("init VACM",[]), + snmpa_vacm:init(DbDir, DbInitError), + + TargetCacheArgs = [Prio, TargetCacheOpts], + TargetCacheSpec = + worker_spec(snmpa_target_cache, TargetCacheArgs, transient, 2000, []), + + Rest = + case AgentType of + master -> + % If we're starting the master, we must also + % configure the tables. + + %% -- Config -- + ConfOpts = get_opt(config, Opts, []), + ?vdebug("[agent table] store config options: ~p", [ConfOpts]), + ets:insert(snmp_agent_table, {config, ConfOpts}), + + ConfigArgs = [Vsns, ConfOpts], + ConfigSpec = + worker_spec(config, ?MODULE, config, ConfigArgs, + transient, 2000, [?MODULE]), + + %% -- Agent verbosity -- + AgentVerb = get_opt(agent_verbosity, Opts, silence), + + %% -- Discovery processing -- + DiscoOpts = get_opt(discovery, Opts, []), + ?vdebug("[agent table] store discovery options: ~p", [DiscoOpts]), + ets:insert(snmp_agent_table, {discovery, DiscoOpts}), + + %% -- Mibs -- + Mibs = get_mibs(get_opt(mibs, Opts, []), Vsns), + ?vdebug("[agent table] store mibs: ~n ~p",[Mibs]), + ets:insert(snmp_agent_table, {mibs, Mibs}), + + Ref = make_ref(), + + %% -- Set module -- + SetModule = get_opt(set_mechanism, Opts, snmpa_set), + ?vdebug("[agent table] store set-module: ~p",[SetModule]), + ets:insert(snmp_agent_table, {set_mechanism, ConfOpts}), + + %% -- Authentication service -- + AuthModule = get_opt(authentication_service, Opts, snmpa_acm), + ?vdebug("[agent table] store authentication service: ~w", + [AuthModule]), + ets:insert(snmp_agent_table, + {authentication_service, AuthModule}), + + %% -- Multi-threaded -- + MultiT = get_opt(multi_threaded, Opts, false), + ?vdebug("[agent table] store multi-threaded: ~p",[MultiT]), + ets:insert(snmp_agent_table, {multi_threaded, MultiT}), + + %% -- Audit trail log -- + case get_opt(audit_trail_log, Opts, not_found) of + not_found -> + ?vdebug("[agent table] no audit trail log",[]), + ok; + AtlOpts -> + ?vdebug("[agent table] " + "store audit trail log options: ~p", + [AtlOpts]), + ets:insert(snmp_agent_table, + {audit_trail_log, AtlOpts}), + ok + end, + + %% -- MIB server -- + MibsOpts = get_opt(mib_server, Opts, []), + ?vdebug("[agent table] store mib-server options: " + "~n ~p", [MibsOpts]), + ets:insert(snmp_agent_table, {mib_server, MibsOpts}), + + %% -- Network interface -- + NiOpts = get_opt(net_if, Opts, []), + ?vdebug("[agent table] store net-if options: " + "~n ~p", [NiOpts]), + ets:insert(snmp_agent_table, {net_if, NiOpts}), + + %% -- Note store -- + NsOpts = get_opt(note_store, Opts, []), + ?vdebug("[agent table] store note-store options: " + "~n ~p",[NsOpts]), + ets:insert(snmp_agent_table, {note_store, NsOpts}), + + AgentOpts = + [{verbosity, AgentVerb}, + {mibs, Mibs}, + {mib_storage, MibStorage}, + {set_mechanism, SetModule}, + {authentication_service, AuthModule}, + {multi_threaded, MultiT}, + {versions, Vsns}, + {net_if, NiOpts}, + {mib_server, MibsOpts}, + {note_store, NsOpts}| + get_opt(master_agent_options, Opts, [])], + + AgentSpec = + worker_spec(snmpa_agent, + [Prio,snmp_master_agent,none,Ref,AgentOpts], + Restart, 15000), + AgentSupSpec = + sup_spec(snmpa_agent_sup, [AgentSpec], + Restart, infinity), + [ConfigSpec, AgentSupSpec]; + _ -> + ?vdebug("[sub agent] spec for the agent supervisor",[]), + AgentSupSpec = + sup_spec(snmpa_agent_sup, [], Restart, infinity), + [AgentSupSpec] + end, + ?vdebug("init done",[]), + {ok, {SupFlags, [MiscSupSpec, SymStoreSpec, LocalDbSpec, TargetCacheSpec | + Rest]}}. + +get_mibs(Mibs, Vsns) -> + MibDir = filename:join(code:priv_dir(snmp), "mibs"), + StdMib = + case (lists:member(v2, Vsns) or lists:member(v3, Vsns)) of + true -> filename:join([MibDir, "SNMPv2-MIB"]); + false -> filename:join([MibDir, "STANDARD-MIB"]) + end, + ?vdebug("add standard and v2 mibs",[]), + NMibs = add_mib(StdMib, Mibs, ["SNMPv2-MIB", "STANDARD-MIB"]), + case lists:member(v3, Vsns) of + true -> + ?vdebug("add v3 mibs",[]), + add_v3_mibs(MibDir, NMibs); + false -> + NMibs + end. + +add_v3_mibs(MibDir, Mibs) -> + NMibs = add_mib(filename:join(MibDir, "SNMP-FRAMEWORK-MIB"), + Mibs, ["SNMP-FRAMEWORK-MIB"]), + add_mib(filename:join(MibDir, "SNMP-MPD-MIB"), + NMibs, ["SNMP-MPD-MIB"]). + +add_mib(DefaultMib, [], _BaseNames) -> [DefaultMib]; +add_mib(DefaultMib, [Mib | T], BaseNames) -> + case lists:member(filename:basename(Mib), BaseNames) of + true -> [Mib | T]; % The user defined his own version of the mib + false -> [Mib | add_mib(DefaultMib, T, BaseNames)] + end. + + +config(Vsns, Opts) -> + ?d("config -> entry with" + "~n Vsns. ~p" + "~n Opts. ~p", [Vsns, Opts]), + Verbosity = get_opt(verbosity, Opts, silence), + put(sname, conf), + put(verbosity, Verbosity), + + ?vlog("starting", []), + + ConfDir = get_opt(dir, Opts), + ForceLoad = get_opt(force_load, Opts, false), + + case (catch conf(ConfDir, Vsns, ForceLoad)) of + ok -> + ?d("config -> done", []), + ignore; + {'EXIT', Reason} -> + ?vlog("configure failed (exit) with reason: ~p",[Reason]), + {error, {config_error, Reason}} + end. + +conf(Dir, Vsns, true) -> + conf1(Dir, Vsns, reconfigure); +conf(Dir, Vsns, false) -> + conf1(Dir, Vsns, configure). + +conf1(Dir, Vsns, Func) -> + ?vlog("start ~w",[Func]), + + ?vdebug("~w snmp_standard_mib",[Func]), + snmp_standard_mib:Func(Dir), + ?vdebug("init snmp_framework_mib",[]), + snmp_framework_mib:init(), + ?vdebug("configure snmp_framework_mib",[]), + snmp_framework_mib:configure(Dir), + ?vdebug("~w snmp_target_mib",[Func]), + snmp_target_mib:Func(Dir), + ?vdebug("~w snmp_notification_mib",[Func]), + snmp_notification_mib:Func(Dir), + ?vdebug("~w snmp_view_based_acm_mib",[Func]), + snmp_view_based_acm_mib:Func(Dir), + case lists:member(v1, Vsns) or lists:member(v2, Vsns) of + true -> + ?vdebug("we need to handle v1 and/or v2 =>~n" + " ~w snmp_community_mib",[Func]), + snmp_community_mib:Func(Dir); + false -> + ?vdebug("no need to handle v1 or v2",[]), + ok + end, + case lists:member(v3, Vsns) of + true -> + ?vdebug("we need to handle v3 =>~n" + " ~w snmp_user_based_sm_mib",[Func]), + snmp_user_based_sm_mib:Func(Dir); + false -> + ?vdebug("no need to handle v3",[]), + ok + end, + ?vdebug("conf done",[]), + ok. + + +%% ------------------------------------- + +sup_spec(Name, Args, Type, Time) -> + ?d("sup_spec -> entry with" + "~n Name: ~p" + "~n Args: ~p" + "~n Type: ~p" + "~n Time: ~p", [Name, Args, Type, Time]), + {Name, + {Name, start_link, Args}, + Type, Time, supervisor, [Name, supervisor]}. + +worker_spec(Name, Args, Type, Time) -> + worker_spec(Name, Name, Args, Type, Time, []). + +worker_spec(Name, Args, Type, Time, Modules) -> + worker_spec(Name, Name, Args, Type, Time, Modules). + +worker_spec(Name, Mod, Args, Type, Time, Modules) -> + worker_spec(Name, Mod, start_link, Args, Type, Time, Modules). + +worker_spec(Name, Mod, Func, Args, Type, Time, Modules) + when is_atom(Name) andalso + is_atom(Mod) andalso + is_atom(Func) andalso + is_list(Args) andalso + is_atom(Type) andalso + is_list(Modules) -> + ?d("worker_spec -> entry with" + "~n Name: ~p" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p" + "~n Type: ~p" + "~n Time: ~p" + "~n Modules: ~p", [Name, Mod, Func, Args, Type, Time, Modules]), + {Name, + {Mod, Func, Args}, + Type, Time, worker, [Name] ++ Modules}. + + +get_verbosity(Opts) -> + SupOpts = get_opt(supervisor, Opts, []), + get_opt(verbosity, SupOpts, silence). + + +get_agent_type(Opts) -> + get_opt(agent_type, Opts, master). + +get_opt(Key, Opts) -> + snmp_misc:get_option(Key, Opts). + +get_opt(Key, Opts, Def) -> + snmp_misc:get_option(Key, Opts, Def). + +key1search(Key, List) -> + lists:keysearch(Key, 1, List). + + +%%----------------------------------------------------------------- + +error_msg(F, A) -> + ?snmpa_error(F, A). + +% i(F) -> +% i(F, []). + +% i(F, A) -> +% io:format("~p:~p: " ++ F ++ "~n", [node(),?MODULE|A]). diff --git a/lib/snmp/src/agent/snmpa_svbl.erl b/lib/snmp/src/agent/snmpa_svbl.erl new file mode 100644 index 0000000000..9c2910580e --- /dev/null +++ b/lib/snmp/src/agent/snmpa_svbl.erl @@ -0,0 +1,159 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_svbl). + +-include("snmp_types.hrl"). + +-define(VMODULE,"SVBL"). +-include("snmp_verbosity.hrl"). + +-export([sort_varbindlist/2, sort_varbinds_rows/1, sa_split/1, + delete_org_index/1, col_to_orgindex/2]). + +%%----------------------------------------------------------------- +%% Func: sort_varbindlist/2 +%% Args: Varbinds is a list of #varbind +%% Purpose: Group all variablebindings that corresponds to logically +%% the same entity, i.e. group all plain variables, all +%% table operations for each table, all varbinds to each +%% subagent. +%% Returns: {VarbindsForThisAgent +%% VarbindsForSubAgents} where +%% VarbindsForThisAgent = List of {TableOid, List of #ivarbinds} | +%% #ivarbinds +%% VarbindsForSubAgents = List of {SubAgentPid, +%% List of {SAOid, #varbinds}} +%%----------------------------------------------------------------- +sort_varbindlist(Mib, Varbinds) -> + {Vars, Tabs, Subagents} = partition(Mib, Varbinds), + {lists:append(Tabs, Vars), Subagents}. + +partition(Mib, Vbs) -> + partition(Mib, Vbs, [], [], []). +partition(Mib, [Varbind | Vbs], Vars, Tabs, Subs) -> + #varbind{oid = Oid} = Varbind, + ?vtrace("partition -> Oid: ~p", [Oid]), + case snmpa_mib:lookup(Mib, Oid) of + {table_column, MibEntry, TableOid} -> + IVarbind = #ivarbind{varbind = fix_bits(Varbind, MibEntry), + mibentry = MibEntry}, + NewTabs = insert_key(TableOid, IVarbind, Tabs), + partition(Mib, Vbs, Vars, NewTabs, Subs); + {subagent, SubagentPid, SAOid} -> + NewSubs = insert_key(SubagentPid, {SAOid, Varbind}, Subs), + partition(Mib, Vbs, Vars, Tabs, NewSubs); + {variable, MibEntry} -> + IVarbind = #ivarbind{varbind = fix_bits(Varbind, MibEntry), + mibentry = MibEntry}, + partition(Mib, Vbs, [IVarbind | Vars], Tabs, Subs); + {false, ErrorCode} -> % ErrorCode = noSuchObject | noSuchInstance + IVarbind = #ivarbind{status = ErrorCode, varbind = Varbind}, + partition(Mib, Vbs, [IVarbind | Vars], Tabs, Subs) + end; +partition(_Mib, [], Vars, Subs, Tabs) -> + {Vars, Subs, Tabs}. + +% fix_bits(#varbind{bertype = 'BITS', +% variabletype = 'OCTET STRING', +% value = V} = VarBind, #me{asn1_type = A}) -> +% VarBind#varbind{variabletype = 'BITS', +% value = snmp_pdus:octet_str_to_bits(V)}; +fix_bits(VarBind, #me{asn1_type=A}) + when ((A#asn1_type.bertype =:= 'BITS') andalso + (VarBind#varbind.variabletype =:= 'OCTET STRING') andalso + is_list(VarBind#varbind.value)) -> + VarBind#varbind{variabletype = 'BITS', + value = snmp_pdus:octet_str_to_bits(VarBind#varbind.value)}; +fix_bits(Vb,_me) -> Vb. + +insert_key(Key, Value, [{Key, Values} | Rest]) -> + [{Key, [Value | Values]} | Rest]; +insert_key(Key, Value, [{KeyX, Values} | Rest]) -> + [{KeyX, Values} | insert_key(Key, Value, Rest)]; +insert_key(Key, Value, []) -> + [{Key, [Value]}]. + +%%----------------------------------------------------------------- +%% Tranforms a list of {Oid, Vb} to a 2-tuple with all +%% Oids and all Vbs. These lists will be reversed. +%%----------------------------------------------------------------- +sa_split(Vbs) -> sa_split(Vbs, [], []). +sa_split([{SAOid, Vb} | T], Oids, Vbs) -> + sa_split(T, [SAOid | Oids], [Vb | Vbs]); +sa_split([], Oids, Vbs) -> + {Oids, Vbs}. + +%%----------------------------------------------------------------- +%% Func: sort_varbinds_rows/1 +%% Args: Varbinds is a list of {Oid, Value}. +%% Pre: Varbinds is for one table. +%% Purpose: Sorts all varbinds in Oid order, and in row order. +%% Returns: list of Row where +%% Row = {Indexes, List of Col} and +%% Col = {ColNo, Value, OrgIndex} and +%% OrgIndex is index in original varbind list. +%%----------------------------------------------------------------- +sort_varbinds_rows(Varbinds) -> + P = pack(Varbinds), + S = lists:keysort(1, P), + unpack(S). + +%% In: list of {Oid, Value} +%% Out: list of {{Indexes_for_row, Col}, Val, Index} +pack(V) -> pack(1, V). +pack(Index, [{[Col | Rest], Val} | T]) -> + [{{Rest, Col}, Val, Index} | pack(Index+1, T)]; +pack(_, []) -> []. + +unpack([{{Rest, Col}, Val, Index} | T]) -> + unpack(Rest, [[{Col, Val, Index}]], T); +unpack([]) -> []. + +unpack(Rest, [Row | Rows], [{{Rest, Col}, Val, Index} | T]) -> + unpack(Rest, [[{Col, Val, Index} | Row] | Rows], T); +unpack(Rest, [Row | Rows], [{{Rest2, Col}, Val, Index} | T]) -> + unpack(Rest2, [[{Col, Val, Index}], + {Rest, lists:reverse(Row)} | Rows], T); +unpack(Rest, [Row | Rows], []) -> + NewRow = {Rest, lists:reverse(Row)}, + lists:reverse([NewRow | Rows]). + +%% OrgIndex should not be present when we call the is_set_ok/set/undo +%% table functions. They just see the list of cols, and if an error +%% occurs, they return the column nunber. +%% Also, delete duplicate columns. If a SET is performed with duplicate +%% columns, it is undefined which column to use. We just pick one. +delete_org_index([{RowIndex, Cols} | Rows]) -> + [{RowIndex, doi(Cols)} | delete_org_index(Rows)]; +delete_org_index([]) -> []. + +doi([{Col, Val, OrgIndex}, {Col, _Val, _OrgIndex} | T]) -> + doi([{Col, Val, OrgIndex} | T]); +doi([{Col, Val, _OrgIndex} | T]) -> + [{Col, Val} | doi(T)]; +doi([]) -> []. + +%% Maps the column number to OrgIndex. +col_to_orgindex(0, _) -> 0; +col_to_orgindex(Col, [{Col, _Val, OrgIndex}|_]) -> + OrgIndex; +col_to_orgindex(Col, [_|Cols]) -> + col_to_orgindex(Col, Cols); +col_to_orgindex(BadCol, _) -> + {false, BadCol}. diff --git a/lib/snmp/src/agent/snmpa_symbolic_store.erl b/lib/snmp/src/agent/snmpa_symbolic_store.erl new file mode 100644 index 0000000000..6c58ffde41 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_symbolic_store.erl @@ -0,0 +1,711 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_symbolic_store). + +%%---------------------------------------------------------------------- +%% This module implements a multipurpose symbolic store. +%% 1) For internal and use from application: aliasname_to_value/1. +%% If this was stored in the mib, deadlock would occur. +%% 2 table_info/1. Getting information about a table. Used by default +%% implementation of tables. +%% 3) variable_info/1. Used by default implementation of variables. +%% 4) notification storage. Used by snmpa_trap. +%% There is one symbolic store per node and it uses the ets table +%% snmp_agent_table, owned by the snmpa_supervisor. +%% +%%---------------------------------------------------------------------- + +-include_lib("kernel/include/file.hrl"). +-include("snmp_types.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). +-include("SNMPv2-MIB.hrl"). + + +%% API +-export([start_link/2, + stop/0, + info/0, + backup/1, + aliasname_to_oid/1, oid_to_aliasname/1, + add_aliasnames/2, delete_aliasnames/1, + which_aliasnames/0, + which_tables/0, + which_variables/0, + enum_to_int/2, int_to_enum/2, + table_info/1, add_table_infos/2, delete_table_infos/1, + variable_info/1, add_variable_infos/2, delete_variable_infos/1, + get_notification/1, set_notification/2, + delete_notifications/1, which_notifications/0, + add_types/2, delete_types/1]). + +%% API (for quick access to the db, note that this is only reads). +-export([get_db/0, + aliasname_to_oid/2, oid_to_aliasname/2, + enum_to_int/3, int_to_enum/3]). + + +%% Internal exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-export([verbosity/1]). + +-define(SERVER, ?MODULE). + +-ifdef(snmp_debug). +-define(GS_START_LINK(Prio, Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, [Prio, Opts], + [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Prio, Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, [Prio, Opts], [])). +-endif. + +-record(state, {db, backup}). +-record(symbol, {key, mib_name, info}). + + +%%----------------------------------------------------------------- +%% Func: start_link/1 +%% Args: Prio is priority of mib-server +%% Opts is a list of options +%% Purpose: starts the mib server synchronized +%% Returns: {ok, Pid} | {error, Reason} +%%----------------------------------------------------------------- +start_link(Prio, Opts) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Opts: ~p", [Prio,Opts]), + ?GS_START_LINK(Prio,Opts). + +stop() -> + call(stop). + +info() -> + call(info). + +backup(BackupDir) -> + call({backup, BackupDir}). + + +%%---------------------------------------------------------------------- +%% Returns: Db +%%---------------------------------------------------------------------- + +get_db() -> + call(get_db). + + +%%---------------------------------------------------------------------- +%% Returns: {value, Oid} | false +%%---------------------------------------------------------------------- +aliasname_to_oid(Aliasname) -> + call({aliasname_to_oid, Aliasname}). + +oid_to_aliasname(OID) -> + call({oid_to_aliasname, OID}). + +int_to_enum(TypeOrObjName, Int) -> + call({int_to_enum,TypeOrObjName,Int}). + +enum_to_int(TypeOrObjName, Enum) -> + call({enum_to_int,TypeOrObjName,Enum}). + +add_types(MibName, Types) -> + cast({add_types, MibName, Types}). + +delete_types(MibName) -> + cast({delete_types, MibName}). + +add_aliasnames(MibName, MEs) -> + cast({add_aliasnames, MibName, MEs}). + +delete_aliasnames(MibName) -> + cast({delete_aliasname, MibName}). + +which_aliasnames() -> + call(which_aliasnames). + +which_tables() -> + call(which_tables). + +which_variables() -> + call(which_variables). + + +%%---------------------------------------------------------------------- +%% Returns: false|{value, Info} +%%---------------------------------------------------------------------- +table_info(TableName) -> + call({table_info, TableName}). + + +%%---------------------------------------------------------------------- +%% Returns: false|{value, Info} +%%---------------------------------------------------------------------- +variable_info(VariableName) -> + call({variable_info, VariableName}). + +add_table_infos(MibName, TableInfos) -> + cast({add_table_infos, MibName, TableInfos}). + +delete_table_infos(MibName) -> + cast({delete_table_infos, MibName}). + +add_variable_infos(MibName, VariableInfos) -> + cast({add_variable_infos, MibName, VariableInfos}). + +delete_variable_infos(MibName) -> + cast({delete_variable_infos, MibName}). + + +%%----------------------------------------------------------------- +%% Store traps +%%----------------------------------------------------------------- +%% A notification is stored as {Key, Value}, where +%% Key is the symbolic trap name, and Value is +%% a #trap record. +%%----------------------------------------------------------------- +%% Returns: {value, Val} | undefined +%%----------------------------------------------------------------- +get_notification(Key) -> + call({get_notification, Key}). +set_notification(Trap, MibName) -> + call({set_notification, MibName, Trap}). +delete_notifications(MibName) -> + call({delete_notifications, MibName}). +which_notifications() -> + call(which_notifications). + + +verbosity(Verbosity) -> + cast({verbosity,Verbosity}). + + +%%---------------------------------------------------------------------- +%% DB access (read) functions: Returns: {value, Oid} | false +%%---------------------------------------------------------------------- +aliasname_to_oid(Db, Aliasname) -> + case snmpa_general_db:read(Db, {alias, Aliasname}) of + {value,#symbol{info = {Oid, _Enums}}} -> {value, Oid}; + false -> false + end. + +oid_to_aliasname(Db,Oid) -> + case snmpa_general_db:read(Db, {oid, Oid}) of + {value,#symbol{info = Aliasname}} -> {value, Aliasname}; + _ -> false + end. + +which_notifications(Db) -> + Pattern = #symbol{key = {trap, '_'}, _ = '_'}, + Symbols = snmpa_general_db:match_object(Db, Pattern), + [{Name, Mib, Rec} || #symbol{key = {trap, Name}, + mib_name = Mib, + info = Rec} <- Symbols]. + +which_aliasnames(Db) -> + Pattern = #symbol{key = {alias, '_'}, _ = '_'}, + Symbols = snmpa_general_db:match_object(Db, Pattern), + [Alias || #symbol{key = {alias, Alias}} <- Symbols]. + +which_tables(Db) -> + Pattern = #symbol{key = {table_info, '_'}, _ = '_'}, + Symbols = snmpa_general_db:match_object(Db, Pattern), + [Name || #symbol{key = {table_info, Name}} <- Symbols]. + +which_variables(Db) -> + Pattern = #symbol{key = {variable_info, '_'}, _ = '_'}, + Symbols = snmpa_general_db:match_object(Db, Pattern), + [Name || #symbol{key = {variable_info, Name}} <- Symbols]. + +int_to_enum(Db,TypeOrObjName,Int) -> + case snmpa_general_db:read(Db, {alias, TypeOrObjName}) of + {value,#symbol{info = {_Oid, Enums}}} -> + case lists:keysearch(Int, 2, Enums) of + {value, {Enum, _Int}} -> {value, Enum}; + false -> false + end; + false -> % Not an Aliasname -> + case snmpa_general_db:read(Db, {type, TypeOrObjName}) of + {value,#symbol{info = Enums}} -> + case lists:keysearch(Int, 2, Enums) of + {value, {Enum, _Int}} -> {value, Enum}; + false -> false + end; + false -> + false + end + end. + +enum_to_int(Db, TypeOrObjName, Enum) -> + case snmpa_general_db:read(Db, {alias, TypeOrObjName}) of + {value,#symbol{info = {_Oid, Enums}}} -> + case lists:keysearch(Enum, 1, Enums) of + {value, {_Enum, Int}} -> {value, Int}; + false -> false + end; + false -> % Not an Aliasname + case snmpa_general_db:read(Db, {type, TypeOrObjName}) of + {value,#symbol{info = Enums}} -> + case lists:keysearch(Enum, 1, Enums) of + {value, {_Enum, Int}} -> {value, Int}; + false -> false + end; + false -> + false + end + end. + + +%%---------------------------------------------------------------------- +%% DB access (read) functions: Returns: false|{value, Info} +%%---------------------------------------------------------------------- +table_info(Db,TableName) -> + case snmpa_general_db:read(Db, {table_info, TableName}) of + {value,#symbol{info = Info}} -> {value, Info}; + false -> false + end. + + +%%---------------------------------------------------------------------- +%% DB access (read) functions: Returns: false|{value, Info} +%%---------------------------------------------------------------------- +variable_info(Db,VariableName) -> + case snmpa_general_db:read(Db, {variable_info, VariableName}) of + {value,#symbol{info = Info}} -> {value, Info}; + false -> false + end. + + +%%---------------------------------------------------------------------- +%% Implementation +%%---------------------------------------------------------------------- + +init([Prio,Opts]) -> + ?d("init -> entry with" + "~n Prio: ~p" + "~n Opts: ~p", [Prio,Opts]), + case (catch do_init(Prio, Opts)) of + {ok, State} -> + {ok, State}; + Error -> + config_err("failed starting symbolic-store: ~n~p", [Error]), + {stop, {error, Error}} + end. + +do_init(Prio, Opts) -> + process_flag(priority, Prio), + process_flag(trap_exit, true), + put(sname,ss), + put(verbosity,get_verbosity(Opts)), + ?vlog("starting",[]), + Storage = get_mib_storage(Opts), + %% type = bag solves the problem with import and multiple + %% object/type definitions. + Db = snmpa_general_db:open(Storage, snmpa_symbolic_store, + symbol, record_info(fields,symbol), bag), + S = #state{db = Db}, + ?vdebug("started",[]), + {ok, S}. + + +handle_call(get_db, _From, #state{db = DB} = S) -> + ?vlog("get db",[]), + {reply, DB, S}; + +handle_call({table_info, TableName}, _From, #state{db = DB} = S) -> + ?vlog("table info: ~p",[TableName]), + Res = table_info(DB, TableName), + ?vdebug("table info result: ~p",[Res]), + {reply, Res, S}; + +handle_call({variable_info, VariableName}, _From, #state{db = DB} = S) -> + ?vlog("variable info: ~p",[VariableName]), + Res = variable_info(DB, VariableName), + ?vdebug("variable info result: ~p",[Res]), + {reply, Res, S}; + +handle_call({aliasname_to_oid, Aliasname}, _From, #state{db = DB} = S) -> + ?vlog("aliasname to oid: ~p",[Aliasname]), + Res = aliasname_to_oid(DB,Aliasname), + ?vdebug("aliasname to oid result: ~p",[Res]), + {reply, Res, S}; + +handle_call({oid_to_aliasname, Oid}, _From, #state{db = DB} = S) -> + ?vlog("oid to aliasname: ~p",[Oid]), + Res = oid_to_aliasname(DB, Oid), + ?vdebug("oid to aliasname result: ~p",[Res]), + {reply, Res, S}; + +handle_call(which_aliasnames, _From, #state{db = DB} = S) -> + ?vlog("which aliasnames",[]), + Res = which_aliasnames(DB), + ?vdebug("which aliasnames: ~p",[Res]), + {reply, Res, S}; + +handle_call(which_tables, _From, #state{db = DB} = S) -> + ?vlog("which tables",[]), + Res = which_tables(DB), + ?vdebug("which tables: ~p",[Res]), + {reply, Res, S}; + +handle_call(which_variables, _From, #state{db = DB} = S) -> + ?vlog("which variables",[]), + Res = which_variables(DB), + ?vdebug("which variables: ~p",[Res]), + {reply, Res, S}; + +handle_call({enum_to_int, TypeOrObjName, Enum}, _From, #state{db = DB} = S) -> + ?vlog("enum to int: ~p, ~p",[TypeOrObjName,Enum]), + Res = enum_to_int(DB, TypeOrObjName, Enum), + ?vdebug("enum to int result: ~p",[Res]), + {reply, Res, S}; + +handle_call({int_to_enum, TypeOrObjName, Int}, _From, #state{db = DB} = S) -> + ?vlog("int to enum: ~p, ~p",[TypeOrObjName,Int]), + Res = int_to_enum(DB, TypeOrObjName, Int), + ?vdebug("int to enum result: ~p",[Res]), + {reply, Res, S}; + +handle_call({set_notification, MibName, Trap}, _From, #state{db = DB} = S) -> + ?vlog("set notification:" + "~n ~p~n ~p", [MibName,Trap]), + set_notif(DB, MibName, Trap), + {reply, true, S}; + +handle_call({delete_notifications, MibName}, _From, #state{db = DB} = S) -> + ?vlog("delete notification: ~p",[MibName]), + delete_notif(DB, MibName), + {reply, true, S}; + +handle_call(which_notifications, _From, #state{db = DB} = S) -> + ?vlog("which notifications", []), + Reply = which_notifications(DB), + {reply, Reply, S}; + +handle_call({get_notification, Key}, _From, #state{db = DB} = S) -> + ?vlog("get notification: ~p",[Key]), + Res = get_notif(DB, Key), + ?vdebug("get notification result: ~p",[Res]), + {reply, Res, S}; + +handle_call(info, _From, #state{db = DB} = S) -> + ?vlog("info",[]), + Info = get_info(DB), + {reply, Info, S}; + +handle_call({backup, BackupDir}, From, #state{db = DB} = S) -> + ?vlog("info to ~p",[BackupDir]), + Pid = self(), + V = get(verbosity), + case file:read_file_info(BackupDir) of + {ok, #file_info{type = directory}} -> + BackupServer = + erlang:spawn_link( + fun() -> + put(sname, albs), + put(verbosity, V), + Dir = filename:join([BackupDir]), + Reply = snmpa_general_db:backup(DB, Dir), + Pid ! {backup_done, Reply}, + unlink(Pid) + end), + ?vtrace("backup server: ~p", [BackupServer]), + {noreply, S#state{backup = {BackupServer, From}}}; + {ok, _} -> + {reply, {error, not_a_directory}, S}; + Error -> + {reply, Error, S} + end; + +handle_call(stop, _From, S) -> + ?vlog("stop",[]), + {stop, normal, ok, S}; + +handle_call(Req, _From, S) -> + info_msg("received unknown request: ~n~p", [Req]), + Reply = {error, {unknown, Req}}, + {reply, Reply, S}. + + +handle_cast({add_types, MibName, Types}, #state{db = DB} = S) -> + ?vlog("add types for ~p:",[MibName]), + F = fun(#asn1_type{assocList = Alist, aliasname = Name}) -> + case snmp_misc:assq(enums, Alist) of + {value, Es} -> + ?vlog("add type~n ~p -> ~p",[Name,Es]), + Rec = #symbol{key = {type, Name}, + mib_name = MibName, + info = Es}, + snmpa_general_db:write(DB, Rec); + false -> done + end + end, + lists:foreach(F, Types), + {noreply, S}; + +handle_cast({delete_types, MibName}, #state{db = DB} = S) -> + ?vlog("delete types: ~p",[MibName]), + Pattern = #symbol{key = {type, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(DB, Pattern), + {noreply, S}; + +handle_cast({add_aliasnames, MibName, MEs}, #state{db = DB} = S) -> + ?vlog("add aliasnames for ~p:",[MibName]), + F = fun(#me{aliasname = AN, oid = Oid, asn1_type = AT}) -> + Enums = + case AT of + #asn1_type{assocList = Alist} -> + case lists:keysearch(enums, 1, Alist) of + {value, {enums, Es}} -> Es; + _ -> [] + end; + _ -> [] + end, + write_alias(AN, DB, Enums, MibName, Oid) + end, + lists:foreach(F, MEs), + {noreply, S}; + +handle_cast({delete_aliasname, MibName}, #state{db = DB} = S) -> + ?vlog("delete aliasname: ~p",[MibName]), + Pattern1 = #symbol{key = {alias, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(DB, Pattern1), + Pattern2 = #symbol{key = {oid, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(DB, Pattern2), + {noreply, S}; + +handle_cast({add_table_infos, MibName, TableInfos}, #state{db = DB} = S) -> + ?vlog("add table infos for ~p:",[MibName]), + F = fun({Name, TableInfo}) -> + ?vlog("add table info~n ~p -> ~p", + [Name, TableInfo]), + Rec = #symbol{key = {table_info, Name}, + mib_name = MibName, + info = TableInfo}, + snmpa_general_db:write(DB, Rec) + end, + lists:foreach(F, TableInfos), + {noreply, S}; + +handle_cast({delete_table_infos, MibName}, #state{db = DB} = S) -> + ?vlog("delete table infos: ~p",[MibName]), + Pattern = #symbol{key = {table_info, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(DB, Pattern), + {noreply, S}; + +handle_cast({add_variable_infos, MibName, VariableInfos}, + #state{db = DB} = S) -> + ?vlog("add variable infos for ~p:",[MibName]), + F = fun({Name, VariableInfo}) -> + ?vlog("add variable info~n ~p -> ~p", + [Name,VariableInfo]), + Rec = #symbol{key = {variable_info, Name}, + mib_name = MibName, + info = VariableInfo}, + snmpa_general_db:write(DB, Rec) + end, + lists:foreach(F, VariableInfos), + {noreply, S}; + +handle_cast({delete_variable_infos, MibName}, #state{db = DB} = S) -> + ?vlog("delete variable infos: ~p",[MibName]), + Pattern = #symbol{key = {variable_info,'_'}, + mib_name = MibName, + info = '_'}, + snmpa_general_db:match_delete(DB, Pattern), + {noreply, S}; + +handle_cast({verbosity,Verbosity}, State) -> + ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]), + put(verbosity,snmp_verbosity:validate(Verbosity)), + {noreply, State}; + +handle_cast(Msg, S) -> + info_msg("received unknown message: ~n~p", [Msg]), + {noreply, S}. + + +handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) -> + ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]), + gen_server:reply(From, {error, Reason}), + {noreply, S#state{backup = undefined}}; + +handle_info({'EXIT', Pid, Reason}, S) -> + %% The only other processes we should be linked to are + %% either the master agent or our supervisor, so die... + {stop, {received_exit, Pid, Reason}, S}; + +handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) -> + ?vlog("backup done:" + "~n Reply: ~p", [Reply]), + gen_server:reply(From, Reply), + {noreply, S#state{backup = undefined}}; + +handle_info(Info, S) -> + info_msg("received unknown info: ~n~p", [Info]), + {noreply, S}. + + +terminate(Reason, S) -> + ?vlog("terminate: ~p",[Reason]), + snmpa_general_db:close(S#state.db). + + +%%---------------------------------------------------------- +%% Code change +%%---------------------------------------------------------- + +% downgrade +code_change({down, _Vsn}, #state{db = DB, backup = B}, downgrade_to_pre_4_7) -> + ?d("code_change(down) -> entry", []), + stop_backup_server(B), + S = {state, DB}, + {ok, S}; + +% upgrade +code_change(_Vsn, S, upgrade_from_pre_4_7) -> + ?d("code_change(up) -> entry", []), + {state, DB} = S, + S1 = #state{db = DB}, + {ok, S1}; + +code_change(_Vsn, S, _Extra) -> + ?d("code_change -> entry [do nothing]", []), + {ok, S}. + + +stop_backup_server(undefined) -> + ok; +stop_backup_server({Pid, _}) when is_pid(Pid) -> + exit(Pid, kill). + + + +%%----------------------------------------------------------------- +%% Trap operations (write, read, delete) +%%----------------------------------------------------------------- +%% A notification is stored as {Key, Value}, where +%% Key is the symbolic trap name, and Value is +%% a #trap or a #notification record. +%%----------------------------------------------------------------- +%% Returns: {value, Value} | undefined +%%----------------------------------------------------------------- +get_notif(Db, Key) -> + case snmpa_general_db:read(Db, {trap, Key}) of + {value,#symbol{info = Value}} -> {value, Value}; + false -> undefined + end. + +set_notif(Db, MibName, Trap) when is_record(Trap, trap) -> + #trap{trapname = Name} = Trap, + Rec = #symbol{key = {trap, Name}, mib_name = MibName, info = Trap}, + %% convert old v1 trap to oid + Oid = case Trap#trap.enterpriseoid of + ?snmp -> + ?snmpTraps ++ [Trap#trap.specificcode + 1]; + Oid0 -> + Oid0 ++ [0, Trap#trap.specificcode] + end, + write_alias(Name, Db, MibName, Oid), + snmpa_general_db:write(Db, Rec); +set_notif(Db, MibName, Trap) -> + #notification{trapname = Name, oid = Oid} = Trap, + Rec = #symbol{key = {trap, Name}, mib_name = MibName, info = Trap}, + write_alias(Name, Db, MibName, Oid), + snmpa_general_db:write(Db, Rec). + +delete_notif(Db, MibName) -> + Pattern = #symbol{key = {trap, '_'}, mib_name = MibName, info = '_'}, + snmpa_general_db:match_delete(Db, Pattern). + + +write_alias(AN, DB, MibName, Oid) -> + write_alias(AN, DB, [], MibName, Oid). + +write_alias(AN, DB, Enums, MibName, Oid) -> + ?vlog("add alias~n ~p -> {~p,~p}",[AN, Oid, Enums]), + Rec1 = #symbol{key = {alias, AN}, + mib_name = MibName, + info = {Oid,Enums}}, + snmpa_general_db:write(DB, Rec1), + ?vlog("add oid~n ~p -> ~p",[Oid, AN]), + Rec2 = #symbol{key = {oid, Oid}, + mib_name = MibName, + info = AN}, + snmpa_general_db:write(DB, Rec2). + +%% ------------------------------------- + +get_info(DB) -> + ProcSize = proc_mem(self()), + DbSz = tab_size(DB), + [{process_memory, ProcSize}, {db_memory, DbSz}]. + +proc_mem(P) when is_pid(P) -> + case (catch erlang:process_info(P, memory)) of + {memory, Sz} when is_integer(Sz) -> + Sz; + _ -> + undefined + end. +%% proc_mem(_) -> +%% undefined. + +tab_size(DB) -> + case (catch snmpa_general_db:info(DB, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + + + +%% ------------------------------------- + +get_verbosity(L) -> + snmp_misc:get_option(verbosity,L,?default_verbosity). + +get_mib_storage(L) -> + snmp_misc:get_option(mib_storage,L,ets). + + +%% ------------------------------------- + +call(Req) -> + call(Req, infinity). + +call(Req, Timeout) -> + gen_server:call(?SERVER, Req, Timeout). + +cast(Msg) -> + gen_server:cast(?SERVER, Msg). + + +%% ---------------------------------------------------------------- + +info_msg(F, A) -> + error_logger:info_msg("~w: " ++ F ++ "~n", [?MODULE|A]). + +config_err(F, A) -> + snmpa_error:config_err(F, A). + diff --git a/lib/snmp/src/agent/snmpa_target_cache.erl b/lib/snmp/src/agent/snmpa_target_cache.erl new file mode 100644 index 0000000000..6fdecacc68 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_target_cache.erl @@ -0,0 +1,891 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-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_target_cache). + +-behaviour(gen_server). + +%% External exports +-export([start_link/2, stop/0, verbosity/1]). + +-export([ + invalidate/0, % invalidate/1, invalidate/2, + targets/1, targets/2 + ]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, + code_change/3]). + +-ifdef(snmp_qc). +-export([ + lock/1, + unlock/0, + upgrade_lock/0, + downgrade_lock/0 + ]). +-endif. + +-include("snmpa_internal.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). + + +-record(state, + { + active_count = 0, + writer = false, % Active or waiting write-lock + waiting = [] % Waiting lockers + } + ). +-record(locker, {pid, from, mon_ref, type, state}). + + +-define(SERVER, ?MODULE). +-define(CACHE, ?MODULE). +-define(LOCKER_TAB, snmpa_target_cache_locker). + + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + +-ifdef(snmp_debug). +-define(GS_START_LINK(Prio, Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, + [Prio, Opts], [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Prio, Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, + [Prio, Opts], [])). +-endif. + + +%%%------------------------------------------------------------------- +%%% API +%%%------------------------------------------------------------------- +start_link(Prio, Opts) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Opts: ~p", [Prio, Opts]), + ?GS_START_LINK(Prio, Opts). + + +stop() -> + call(stop). + + +verbosity(V) -> + call({verbosity, V}). + + +%% Targets -> notify_targets() +%% notify_targets() -> [notify_target()] +%% notify_target() -> {NotifyName, target()} +%% target() -> {DestAddr, TargetName, TargetParams, NotifyType} + +targets(TargetsFun) when is_function(TargetsFun) -> + Pat = {{'_', '$1'}, '$2'}, + get_targets(Pat, TargetsFun). + +targets(TargetsFun, NotifyName) when is_function(TargetsFun) -> + Pat = {{NotifyName, '$1'}, '$2'}, + get_targets(Pat, TargetsFun). + +get_targets(Pat, TargetsFun) -> + lock(read), % Get a read lock + Targets = + case ets:lookup(?CACHE, state) of + [{state, invalid}] -> + upgrade_lock(), % Upgrade to write lock + %% Make sure it's still invalid + case ets:lookup(?CACHE, state) of + [{state, invalid}] -> + insert_all( TargetsFun() ), + ets:insert(?CACHE, {state, valid}); + _ -> + ok % This means that someone got there before us + end, + downgrade_lock(), % Downgrade to read lock + get_targets(Pat); + [{state, valid}] -> + get_targets(Pat) + end, + unlock(), % Release the read lock + Targets. + +get_targets(Pat) -> + NotifyTargets = ets:match(?CACHE, Pat), + [{DestAddr, TargetName, TargetParams, NotifyType} || + [TargetName, {DestAddr, TargetParams, NotifyType}] <- + NotifyTargets]. + +invalidate() -> + lock(write), + case ets:lookup(?CACHE, state) of + [{state, invalid}] -> + ok; + [{state, valid}] -> + delete_all(), + ets:insert(?CACHE, {state, invalid}) + end, + unlock(), + ok. + + +%%%------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%-------------------------------------------------------------------- +init([Prio, Opts]) -> + case (catch do_init(Prio, Opts)) of + {ok, State} -> + ?vdebug("started",[]), + {ok, State}; + {error, Reason} -> + config_err("failed starting target-cache server: ~n~p", [Reason]), + {stop, {error, Reason}}; + Error -> + config_err("failed starting target-cache server: ~n~p", [Error]), + {stop, {error, Error}} + end. + + +do_init(Prio, Opts) -> + process_flag(priority, Prio), + process_flag(trap_exit, true), + put(sname, tcs), + put(verbosity, get_opt(verbosity, Opts, ?default_verbosity)), + ?vlog("starting",[]), + ets:new(?CACHE, [set, named_table, public]), + ets:insert(?CACHE, {state, invalid}), + ets:new(?LOCKER_TAB, [set, named_table, {keypos, #locker.pid}]), + {ok, #state{}}. + + +%%-------------------------------------------------------------------- +%% Func: handle_call/3 +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- + +%%%----------------------------------------------------------------- + +%% +%% (1) As long as there are no _waiting_ or active write locks, +%% read-locks will allways be granted +%% (2) When there are no active readers, write-locks will be +%% granted. +%% (3) When there are active readers (clients with read-locks), +%% a write-lock will have to wait for _all_ the read-locks +%% to be released. +%% (4) If there is a waiting write-lock, all subsequent lock- +%% requests will have to wait. +%% (5) If there is an active write-lock, all subsequent lock- +%% requests will have to wait. +%% + +monitor(Pid) -> erlang:monitor(process, Pid). +-ifdef(SNMP_R10). +demonitor(Ref) -> + erlang:demonitor(Ref), + receive + {_, Ref, _, _, _} -> + true + after 0 -> + true + end. +-else. +demonitor(Ref) -> + erlang:demonitor(Ref, [flush]). +-endif. + + +%% (1) No write_lock active or waiting +handle_call({lock, read = Type, infinity}, {Pid, _} = From, + #state{active_count = Cnt, writer = false} = State) -> + ?vlog("lock(read, infinity) -> " + "entry when no waiting or active writer with" + "~n Pid: ~p" + "~n Cnt: ~p", [Pid, Cnt]), + MonRef = monitor(Pid), + Locker = #locker{pid = Pid, + from = From, + mon_ref = MonRef, + type = Type, + state = active}, + ets:insert(?LOCKER_TAB, Locker), +%% ?vtrace("lock(read, infinity) -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {reply, ok, State#state{active_count = inc(Cnt)}}; + +%% (4,5) There is waiting or active write locks +handle_call({lock, read = Type, infinity}, {Pid, _} = From, State) -> + ?vlog("lock(read, infinity) -> " + "entry when active or waiting write locks with" + "~n Pid: ~p", [Pid]), + MonRef = monitor(Pid), + Locker = #locker{pid = Pid, + from = From, + mon_ref = MonRef, + type = Type, + state = waiting}, + ets:insert(?LOCKER_TAB, Locker), + Waiting = lists:append(State#state.waiting, [Pid]), +%% ?vtrace("lock(read, infinity) -> done when" +%% "~n Waiting: ~p" +%% "~n Lockers: ~p", [Waiting, ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{waiting = Waiting}}; + +%% (2) No active locks +%% Since there are no active lockers, that also means that +%% there is no lockers waiting. +handle_call({lock, write = Type, infinity}, {Pid, _} = From, + #state{active_count = 0, writer = false} = State) -> + ?vlog("lock(write, infinity) -> " + "entry when no active lockers with" + "~n Pid: ~p", [Pid]), + MonRef = monitor(Pid), + Locker = #locker{pid = Pid, + from = From, + mon_ref = MonRef, + type = Type, + state = active}, + ets:insert(?LOCKER_TAB, Locker), +%% ?vtrace("lock(write, infinity) -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {reply, ok, State#state{active_count = 1, writer = true}}; + +%% (3) No waiting or active writers, but at least one active reader +handle_call({lock, write = Type, infinity}, {Pid, _} = From, + #state{writer = false} = State) -> + ?vlog("lock(write, infinity) -> " + "entry when active lockers with" + "~n Pid: ~p", [Pid]), + MonRef = monitor(Pid), + Locker = #locker{pid = Pid, + from = From, + mon_ref = MonRef, + type = Type, + state = waiting}, + ets:insert(?LOCKER_TAB, Locker), + Waiting = lists:append(State#state.waiting, [Pid]), +%% ?vtrace("lock(write, infinity) -> done when" +%% "~n Waiting: ~p" +%% "~n Lockers: ~p", [Waiting, ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{waiting = Waiting, writer = true}}; + +handle_call({lock, write = Type, infinity}, {Pid, _} = From, + #state{writer = true} = State) -> + ?vlog("lock(write, infinity) -> entry with" + "~n Pid: ~p", [Pid]), + MonRef = monitor(Pid), + Locker = #locker{pid = Pid, + from = From, + mon_ref = MonRef, + type = Type, + state = waiting}, + ets:insert(?LOCKER_TAB, Locker), + Waiting = lists:append(State#state.waiting, [Pid]), +%% ?vtrace("lock(write, infinity) -> done when" +%% "~n Waiting: ~p" +%% "~n Lockers: ~p", [Waiting, ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{waiting = Waiting}}; + +handle_call({verbosity, Verbosity}, _From, State) -> + ?vlog("verbosity: ~p -> ~p", [get(verbosity), Verbosity]), + Old = put(verbosity, ?vvalidate(Verbosity)), + {reply, Old, State}; + +%% If there are no more active read'ers, and no waiting, +%% then set to writer and reply now +handle_call({upgrade_lock, Pid}, _From, + #state{active_count = 1, waiting = []} = State) -> + ?vlog("upgrade_lock -> " + "entry when one active locker and no waiting with" + "~n Pid: ~p", [Pid]), + case ets:lookup(?LOCKER_TAB, Pid) of + [#locker{type = read} = Locker] -> + ets:insert(?LOCKER_TAB, Locker#locker{type = write}), +%% ?vtrace("upgrade_lock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {reply, ok, State#state{writer = true}}; + + [#locker{type = write}] -> + {reply, ok, State} + end; + +%% If there are no more active read'ers, and no waiting, +%% then set to writer and reply now +handle_call({upgrade_lock, Pid}, {Pid, _} = From, + #state{active_count = 1, waiting = Waiting} = State) -> + ?vlog("upgrade_lock -> " + "entry when one active locker with" + "~n Pid: ~p" + "~n Waiting: ~p", [Pid, Waiting]), + case ets:lookup(?LOCKER_TAB, Pid) of + [#locker{type = read} = Locker] -> + case active_waiting_writer(Waiting) of + {true, StillWaiting} -> + ?vtrace("upgrade_lock -> activated when" + "~n StillWaiting: ~p", [StillWaiting]), + ets:insert(?LOCKER_TAB, Locker#locker{from = From, + type = write, + state = waiting}), +%% ?vtrace("upgrade_lock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{waiting = StillWaiting ++ [Pid]}}; + {false, []} -> + ?vtrace("upgrade_lock -> none activated, " + "so we can let the upgrader in", []), + ets:insert(?LOCKER_TAB, Locker#locker{type = write}), +%% ?vtrace("upgrade_lock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {reply, ok, State#state{writer = true, + waiting = []}} + end; + + [#locker{type = write}] -> + {reply, ok, State}; + + _ -> + {reply, {error, not_found}, State} + end; + +%% There are active and waiting locker's +handle_call({upgrade_lock, Pid}, {Pid, _} = From, + #state{active_count = Cnt, waiting = Waiting} = State) -> + ?vlog("upgrade_lock -> entry with" + "~n Pid: ~p" + "~n Waiting: ~p", [Pid, Waiting]), + case ets:lookup(?LOCKER_TAB, Pid) of + [#locker{type = read} = Locker] -> + ets:insert(?LOCKER_TAB, Locker#locker{from = From, + type = write, + state = waiting}), +%% ?vtrace("upgrade_lock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = dec(Cnt), + waiting = Waiting ++ [Pid]}}; + + [#locker{type = write}] -> + {reply, ok, State}; + + _ -> + {reply, {error, not_found}, State} + end; + + +handle_call(stop, _From, State) -> + ?vlog("stop",[]), + {stop, normal, stopped, State}; + +handle_call(Req, _From, State) -> + warning_msg("received unknown request: ~n~p", [Req]), + Reply = {error, {unknown, Req}}, + {reply, Reply, State}. + + +%%-------------------------------------------------------------------- +%% Func: handle_cast/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- +handle_cast({unlock, Pid}, + #state{active_count = Cnt, waiting = []} = State) -> + ?vlog("unlock -> entry when no waiting with" + "~n Pid: ~p" + "~n Cnt: ~p", [Pid, Cnt]), + case ets:lookup(?LOCKER_TAB, Pid) of + [#locker{mon_ref = MonRef, type = read}] -> + ?vdebug("unlock -> found read locker" + "~n MonRef: ~p", [MonRef]), + demonitor(MonRef), + ets:delete(?LOCKER_TAB, Pid), +%% ?vtrace("unlock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = dec(Cnt)}}; + [#locker{mon_ref = MonRef, type = write}] -> + ?vdebug("unlock -> found write locker" + "~n MonRef: ~p", [MonRef]), + demonitor(MonRef), + ets:delete(?LOCKER_TAB, Pid), +%% ?vtrace("unlock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = dec(Cnt), writer = false}}; + _ -> + {noreply, State} + end; + +handle_cast({unlock, Pid}, + #state{active_count = Cnt, waiting = Waiting} = State) -> + ?vlog("unlock -> entry when waiting with" + "~n Pid: ~p" + "~n Cnt: ~p", [Pid, Cnt]), + case ets:lookup(?LOCKER_TAB, Pid) of + %% Last active reader: Time to let the waiting in + %% The first of the waiting _has_ to be a write-lock + %% (read-locks will only be set waiting if there is + %% a waiting or active write). + [#locker{mon_ref = MonRef, type = read}] when (Cnt == 1) -> + ?vdebug("unlock -> found read locker" + "~n MonRef: ~p", [MonRef]), + demonitor(MonRef), + ets:delete(?LOCKER_TAB, Pid), + case active_waiting_writer(Waiting) of + {true, StillWaiting} -> + ?vtrace("unlock -> activated when" + "~n StillWaiting: ~p", [StillWaiting]), +%% ?vtrace("unlock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = 1, + writer = true, + waiting = StillWaiting}}; + {false, []} -> + ?vtrace("unlock -> none activated", []), +%% ?vtrace("unlock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = 0, + writer = false, + waiting = []}} + end; + + [#locker{mon_ref = MonRef, type = read}] -> + ?vdebug("unlock -> found read locker" + "~n MonRef: ~p", [MonRef]), + demonitor(MonRef), + ets:delete(?LOCKER_TAB, Pid), +%% ?vtrace("unlock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = dec(Cnt)}}; + + [#locker{mon_ref = MonRef, type = write}] -> + %% Release the hord (maybe) + ?vdebug("unlock -> found write locker" + "~n MonRef: ~p", [MonRef]), + demonitor(MonRef), + ets:delete(?LOCKER_TAB, Pid), + {Active, StillWaiting, Writer} = + activate_waiting_readers_or_maybe_writer(Waiting), + ?vtrace("unlock -> new reader(s) or maybe writer activated:" + "~n Active: ~p" + "~n StillWaiting: ~p" + "~n Writer: ~p", [Active, StillWaiting, Writer]), +%% ?vtrace("unlock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = Active, + writer = Writer, + waiting = StillWaiting}}; + + %% If we have no active lockers, this may be a bug and therefor + %% see if we can activate some of the waiting + _ when (State#state.active_count == 0) -> + ?vdebug("unlock -> could not find locker", []), + {Active, StillWaiting, Writer} = + activate_waiting_readers_or_maybe_writer(Waiting), + ?vtrace("unlock -> new reader(s) or maybe writer activated:" + "~n Active: ~p" + "~n StillWaiting: ~p" + "~n Writer: ~p", [Active, StillWaiting, Writer]), +%% ?vtrace("unlock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, + State#state{active_count = Active, + writer = Writer, + waiting = StillWaiting}}; + + _ -> + {noreply, State} + end; + + +handle_cast({downgrade_lock, Pid}, #state{waiting = Waiting} = State) -> + ?vlog("downgrade_lock -> entry when waiting with" + "~n Pid: ~p", [Pid]), + case ets:lookup(?LOCKER_TAB, Pid) of + [#locker{type = read}] -> + {noreply, State}; + + [#locker{type = write} = Locker] -> + %% We need to check if this is the only write(r), + %% in that case we must update the writer field + ets:insert(?LOCKER_TAB, Locker#locker{type = read}), + {Cnt, NewWaiting} = activate_waiting_readers(Waiting), + ?vtrace("downgrade_lock -> entry when waiting with" + "~n Cnt: ~p" + "~n NewWaiting: ~p", [Cnt, NewWaiting]), +%% ?vtrace("downgrade_lock -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = Cnt, + waiting = NewWaiting, + writer = is_writer(NewWaiting)}} + end; + + +handle_cast(Msg, State) -> + warning_msg("received unknown message: ~n~p", [Msg]), + {noreply, State}. + + + + +%%-------------------------------------------------------------------- +%% Func: handle_info/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- + +%% This must be a glitch +handle_info({'DOWN', _MonRef, process, Pid, Reason}, + #state{active_count = 0, waiting = []} = State) -> + ?vlog("received DOWN message from ~p when no active and no waiting" + "~n exited for reason: ~n~p", [Pid, Reason]), + {noreply, State}; + +handle_info({'DOWN', _MonRef, process, Pid, Reason}, + #state{active_count = Cnt, waiting = []} = State) -> + ?vlog("received DOWN message from ~p when active but no waiting" + "~n exited for reason: ~n~p", [Pid, Reason]), + case handle_maybe_active_down(Cnt, Pid) of + {NewCnt, write} -> +%% ?vtrace("DOWN -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = NewCnt, writer = false}}; + {NewCnt, read} -> +%% ?vtrace("DOWN -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, State#state{active_count = NewCnt}} + end; + +handle_info({'DOWN', _MonRef, process, Pid, Reason}, State) -> + ?vlog("received DOWN message from ~p" + "~n exited for reason: ~n~p", [Pid, Reason]), + NewState = handle_maybe_active_or_waiting_down(Pid, State), +%% ?vtrace("DOWN -> done when" +%% "~n Lockers: ~p", [ets:tab2list(?LOCKER_TAB)]), + {noreply, NewState}; + +handle_info({'EXIT', Pid, Reason}, S) -> + %% The only other process we should be linked to is + %% our supervisor, so die... + {stop, {received_exit, Pid, Reason}, S}; + +handle_info(Info, State) -> + warning_msg("received unknown info: ~n~p", [Info]), + {noreply, State}. + + +%%-------------------------------------------------------------------- +%% Func: terminate/2 +%% Purpose: Shutdown the server +%% Returns: any (ignored by gen_server) +%%-------------------------------------------------------------------- +terminate(Reason, State) -> + ?vlog("terminate ->" + "~n Reason: ~p" + "~n State: ~p", [Reason, State]), + ets:delete(?CACHE), + ets:delete(?LOCKER_TAB), + ok. + + +%%%------------------------------------------------------------------- +%%% Internal functions +%%%------------------------------------------------------------------- + +%% Locks are initially exclusive which means that it is possible +%% to both read _and_ write. After a downgrade, it is only possible +%% to read. But since, by then, the process already has a lock, it +%% can just go ahead and read. + +lock(Type) -> + call({lock, Type, infinity}). + +%% Upgrade from read to lock write +upgrade_lock() -> + call({upgrade_lock, self()}). + +%% Downgrade from write to read lock +downgrade_lock() -> + cast({downgrade_lock, self()}). + +unlock() -> + cast({unlock, self()}). + + +insert_all(Targets) -> + Fun = fun({NotifyName, Data}) -> insert(NotifyName, Data) end, + lists:foreach(Fun, Targets). + +insert(NotifyName, {DestAddr, TargetName, TargetParams, NotifyType}) -> + Key = {NotifyName, TargetName}, + Data = {DestAddr, TargetParams, NotifyType}, + ets:insert(?CACHE, {Key, Data}). + +delete_all() -> + ets:delete_all_objects(?CACHE). + + + +%%---------------------------------------------------------- + +%% This function is called when we have active but no waiting +%% lockers. So, if we have it stored, it's an active locker. +handle_maybe_active_down(Cnt, Pid) -> + case ets:lookup(?LOCKER_TAB, Pid) of + [#locker{type = Type}] -> + ets:delete(?LOCKER_TAB, Pid), + {dec(Cnt), Type}; + _ -> + {Cnt, read} + end. + +handle_maybe_active_or_waiting_down(Pid, + #state{active_count = Cnt, + waiting = Waiting} = State) -> + case ets:lookup(?LOCKER_TAB, Pid) of + [#locker{state = active, type = read}] when (Cnt == 1) -> + %% 1) This means that the writer must be waiting + %% 2) The last reader terminated, + %% time to activate the wating writer + %% If this was the last one, then we must + %% activate the waiting writer. + ets:delete(?LOCKER_TAB, Pid), + case active_waiting_writer(Waiting) of + {true, StillWaiting} -> + %% active count is still 1, so no need to update that + State#state{writer = true, + waiting = StillWaiting}; + {false, []} -> + State#state{active_count = 0, + writer = false, + waiting = []} + end; + + [#locker{state = active, type = read}] -> + %% 1) This means that the writer must be waiting + %% 2) More then one (read-) locker active, just + %% clean up. + ets:delete(?LOCKER_TAB, Pid), + State#state{active_count = dec(Cnt)}; + + [#locker{state = active, type = write}] -> + ets:delete(?LOCKER_TAB, Pid), + {Active, StillWaiting, Writer} = + activate_waiting_readers_or_maybe_writer(Waiting), + State#state{active_count = Active, + writer = Writer, + waiting = StillWaiting}; + + [#locker{state = waiting, type = read}] -> + ets:delete(?LOCKER_TAB, Pid), + State#state{waiting = lists:delete(Pid, Waiting)}; + + [#locker{state = waiting, type = write}] -> + %% We need to check if this is the only waiting writer. + %% If it is we shall set the writer field to false + ets:delete(?LOCKER_TAB, Pid), + NewWaiting = lists:delete(Pid, Waiting), + Writer = + case ets:match_object(?LOCKER_TAB, + #locker{state = active, + type = write, + _ = '_'}) of + [] -> + is_writer(NewWaiting); + _ -> + true + end, + State#state{writer = Writer, + waiting = NewWaiting}; + + _Other -> + State + + end. + +is_writer([]) -> + false; +is_writer([Pid|Pids]) -> + case ets:lookup(?LOCKER_TAB, Pid) of + [#locker{type = write}] -> + true; + _Other -> + is_writer(Pids) + end. + + +%%---------------------------------------------------------- + +%% This is just a utility function to make sure we don't +%% end up in a lockout situation. +active_waiting_writer([]) -> + {false, []}; +active_waiting_writer([H|T]) -> + case ets:lookup(?LOCKER_TAB, H) of + [#locker{from = From} = L] -> + ets:insert(?LOCKER_TAB, L#locker{state = active}), + gen_server:reply(From, ok), + {true, T}; + [] -> + %% Oups + error_msg("Could not find locker record for ~p", [H]), + active_waiting_writer(T) + end. + + +%% Activate waiting read(ers) +activate_waiting_readers(Waiting) -> + activate_waiting_readers(Waiting, 1). + +activate_waiting_readers([], Cnt) -> + {Cnt, []}; +activate_waiting_readers([H|T] = Waiting, Cnt) -> + case ets:lookup(?LOCKER_TAB, H) of + [#locker{from = From, type = read} = L] -> + ets:insert(?LOCKER_TAB, L#locker{state = active}), + gen_server:reply(From, ok), + activate_waiting_readers(T, inc(Cnt)); + + %% Found a writer, time to stop starting readers + [#locker{type = write}] -> + {Cnt, Waiting}; + + [] -> + %% Oups + error_msg("Could not find locker record for ~p", [H]), + activate_waiting_readers(T, Cnt) + + end. + + +activate_waiting_readers_or_maybe_writer(Waiting) -> + activate_waiting_readers_or_maybe_writer(Waiting, 0). + +activate_waiting_readers_or_maybe_writer([], Cnt) -> + {Cnt, [], false}; +activate_waiting_readers_or_maybe_writer([H|T] = Waiting, Cnt) -> + case ets:lookup(?LOCKER_TAB, H) of + [#locker{from = From, type = read} = L] -> + ets:insert(?LOCKER_TAB, L#locker{state = active}), + gen_server:reply(From, ok), + activate_waiting_readers_or_maybe_writer(T, inc(Cnt)); + + %% Only active writer only if it's the first + [#locker{from = From, type = write} = L] when (Cnt == 0) -> + ets:insert(?LOCKER_TAB, L#locker{state = active}), + gen_server:reply(From, ok), + {1, T, true}; + + %% Found a writer, time to stop starting readers + [#locker{type = write}] -> + {Cnt, Waiting, false}; + + [] -> + %% Oups + error_msg("Could not find locker record for ~p", [H]), + activate_waiting_readers_or_maybe_writer(T, Cnt) + + end. + + +%%---------------------------------------------------------- +%% Code change +%%---------------------------------------------------------- + +%% downgrade +%% +%% code_change({down, _Vsn}, S1, downgrade_to_pre_4_7) -> +%% #state{dets = D, ets = E, notify_clients = NC, backup = B} = S1, +%% stop_backup_server(B), +%% S2 = {state, D, E, NC}, +%% {ok, S2}; + +%% upgrade +%% +%% code_change(_Vsn, S1, upgrade_from_pre_4_7) -> +%% {state, D, E, NC} = S1, +%% S2 = #state{dets = D, ets = E, notify_clients = NC}, +%% {ok, S2}; + +code_change(_Vsn, State, _Extra) -> + {ok, State}. + + +%%------------------------------------------------------------------ + +inc(Cnt) -> + Cnt + 1. + +dec(Cnt) when (Cnt =< 0) -> + 0; +dec(Cnt) -> + Cnt - 1. + + +%%------------------------------------------------------------------ +%% This functions retrieves option values from the Options list. +%%------------------------------------------------------------------ + +get_opt(Key, Opts, Def) -> + snmp_misc:get_option(Key, Opts, Def). + + +%%------------------------------------------------------------------ + +%% info_msg(F, A) -> +%% ?snmpa_info("Target cache server: " ++ F, A). + +warning_msg(F, A) -> + ?snmpa_warning("Target cache server: " ++ F, A). + +error_msg(F, A) -> + ?snmpa_error("Target cache server: " ++ F, A). + +%% --- + +%% user_err(F, A) -> +%% snmpa_error:user_err(F, A). + +config_err(F, A) -> + snmpa_error:config_err(F, A). + +%% error(Reason) -> +%% throw({error, Reason}). + + +%% ---------------------------------------------------------------- + +call(Req) -> + gen_server:call(?SERVER, Req, infinity). + +cast(Msg) -> + gen_server:cast(?SERVER, Msg). diff --git a/lib/snmp/src/agent/snmpa_trap.erl b/lib/snmp/src/agent/snmpa_trap.erl new file mode 100644 index 0000000000..b1096b1135 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_trap.erl @@ -0,0 +1,1051 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-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_trap). + +%%%----------------------------------------------------------------- +%%% This module takes care of all trap(notification handling. +%%%----------------------------------------------------------------- +%% External exports +-export([construct_trap/2, + try_initialise_vars/2, send_trap/6]). +-export([send_discovery/5]). + +%% Internal exports +-export([init_v2_inform/9, init_v3_inform/9, send_inform/6]). +-export([init_discovery_inform/12, send_discovery_inform/5]). + +-include("snmp_types.hrl"). +-include("SNMPv2-MIB.hrl"). +-include("SNMPv2-TM.hrl"). +-include("SNMPv2-TC.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). +-include("SNMP-TARGET-MIB.hrl"). +-define(enterpriseSpecific, 6). + + +-define(VMODULE,"TRAP"). +-include("snmp_verbosity.hrl"). + + +%%----------------------------------------------------------------- +%% Trap mechanism +%% ============== +%% Distributed subagent (dSA) case +%% The MIB with the TRAP-TYPE macro is loaded in dSA. This means +%% that dSA has info on all variables defined in the TRAP-TYPE, +%% even though some variables may be located in other SA:s (or +%% in the MA). Other variables that may be sent in the trap, +%% must be known by either the dSA, or some of its parent agents +%% (e.g. the master agent), if the variable should be referred +%% to by symbolic name. It is however possible to send other +%% variables as well, but then the entire OID must be provided. +%% The dSA locates the asn1 type, oid and value for as many +%% variables as possible. This information, together with the +%% variables for which the type, value or oid isn't known, is +%% sent to the dSA's parent. This agent performs the same +%% operation, and so on, until eventually the MA will receive the +%% info. The MA then fills in the gaps, and at this point all +%% oids and types must be known, otherwise an error is signalled, +%% and the opertaion is aborted. For the unknown values for some +%% oids, a get-operation is performed by the MA. This will +%% retreive the missing values. +%% At this point, all oid, types and values are known, so the MA +%% can distribute the traps according to the information in the +%% internal tables. +%% +%% Local subagent (lSA) case +%% This case is similar to the case above. +%% +%% Master agent (MA) case +%% This case is similar to the case above. +%% +%% NOTE: All trap forwarding between agents is made asynchronously. +%% +%% dSA: Distributed SA (the #trap is loaded here) +%% nSA: [many] SAs between dSA and MA +%% MA: Master Agent. (all trap info (destiniations is here)) +%% 1) application decides to send a trap. +%% 2) dSA calls send_trap which initialises vars +%% 3) dSA sends all to nSA +%% 4) nSA tries to map symbolic names to oids and find the types +%% of all variableoids with a value (and no type). +%% 5) nSA sends all to (n-1)SA +%% 6) MA tries to initialise vars +%% 7) MA makes a trappdu, and sends it to all destination. +%% +%% Problems with this implementation +%% ================================= +%% It's ok to send {Oid, Value} but not just Oid. (it should be for +%% any Oid) +%% It's ok to send {Name, Value} but not just Name. (it should be +%% for Names in the hierarchy) +%% This approach might be too flexible; will people use it? +%% *NOTE* +%% Therefore, in this version we *do not* allow extra variables +%% in traps. +%% *YES* In _this_ version we do. +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Func: construct_trap/2 +%% Args: Trap is an atom +%% Varbinds is a list of +%% {Variable, Value} | {SymbolicTableCol, RowIndex, Value} +%% where Variable is an atom or an OID, +%% where RowIndex is the indexes for the row. +%% We don't check the RowIndex. +%% Purpose: This is the initially-called function. It is called +%% by the agent that found out that a trap should be +%% sent. +%% Initialize as many variables as possible. +%% Returns: {ok, TrapRecord, <list of Var>} | error +%% where Var is returned from initiate_vars. +%% NOTE: Executed at the inital SA +%%----------------------------------------------------------------- +construct_trap(Trap, Varbinds) -> + ?vdebug("construct_trap -> entry with" + "~n Trap: ~p", [Trap]), + case snmpa_symbolic_store:get_notification(Trap) of + undefined -> + user_err("construct_trap got undef Trap: ~w" , [Trap]), + error; + + {value, #trap{oidobjects = ListOfVars} = TRec} -> + ?vdebug("construct_trap -> trap" + "~n ~p", [TRec]), + OidVbs = [alias_to_oid(Vb) || Vb <- Varbinds], + LV = initiate_vars(ListOfVars, OidVbs), + InitiatedVars = try_initialise_vars(get(mibserver), LV), + {ok, TRec, InitiatedVars}; + + {value, #notification{oidobjects = ListOfVars} = NRec} -> + ?vdebug("construct_trap -> notification" + "~n ~p", [NRec]), + OidVbs = [alias_to_oid(Vb) || Vb <- Varbinds], + LV = initiate_vars(ListOfVars, OidVbs), + InitiatedVars = try_initialise_vars(get(mibserver), LV), + {ok, NRec, InitiatedVars} + end. + +alias_to_oid({Alias, Val}) when is_atom(Alias) -> + case snmpa_symbolic_store:aliasname_to_oid(Alias) of + {value, Oid} -> {lists:append(Oid, [0]), {value, Val}}; + _ -> {Alias, {value, Val}} + end; +alias_to_oid({Alias, RowIndex, Val}) when is_atom(Alias) -> + case snmpa_symbolic_store:aliasname_to_oid(Alias) of + {value, Oid} -> {lists:append(Oid, RowIndex), {value, Val}}; + _ -> {Alias, RowIndex, {value, Val}} + end; +alias_to_oid({Oid, Val}) -> {Oid, {value, Val}}. + + +%%----------------------------------------------------------------- +%% Func: initiate_vars/2 +%% Args: ListOfVars is a list of {Oid, #asn1_type} +%% Varbinds is a list of +%% {VariableOid, Value} | +%% {VariableAtom, Value} | +%% {TableColAtom, RowIndex, Value} +%% Purpose: For each variable specified in the TRAP-TYPE macro +%% (each in ListOfVars), check if it's got a value given +%% in the Varbinds list. +%% For each Oid: +%% 1) It has corresponding VariableOid. Use Value. +%% 2) No corresponding VariableOid. No value. +%% Returns: A list of +%% {VariableOid, #asn1_type, Value} | +%% {VariableOid, #asn1_type} | +%% {VariableOid, Value} | +%% {VariableAtom, Value} | +%% {TableColAtom, RowIndex, Value} +%% NOTE: Executed at the inital SA +%%----------------------------------------------------------------- +initiate_vars([{Oid, Asn1Type} | T], Varbinds) -> + case delete_oid_from_varbinds(Oid, Varbinds) of + {undefined, _, _} -> + [{Oid, Asn1Type} | initiate_vars(T, Varbinds)]; + {Value, VarOid, RestOfVarbinds} -> + [{VarOid, Asn1Type, Value} | initiate_vars(T, RestOfVarbinds)] + end; +initiate_vars([], Varbinds) -> + Varbinds. + +delete_oid_from_varbinds(Oid, [{VarOid, Value} | T]) -> + case lists:prefix(Oid, VarOid) of + true -> + {Value, VarOid, T}; + _ -> + {Value2, VarOid2, T2} = delete_oid_from_varbinds(Oid, T), + {Value2, VarOid2, [{VarOid, Value} | T2]} + end; +delete_oid_from_varbinds(Oid, [H | T]) -> + {Value, VarOid, T2} = delete_oid_from_varbinds(Oid, T), + {Value, VarOid, [H | T2]}; +delete_oid_from_varbinds(_Oid, []) -> {undefined, undefined, []}. + +%%----------------------------------------------------------------- +%% Func: try_initialise_vars(Mib, Varbinds) +%% Args: Mib is the local mib process +%% Varbinds is a list returned from initiate_vars. +%% Purpose: Try to initialise uninitialised vars. +%% Returns: see initiate_vars +%% NOTE: Executed at the intermediate SAs +%%----------------------------------------------------------------- +try_initialise_vars(Mib, Varbinds) -> + V = try_map_symbolic(Varbinds), + try_find_type(V, Mib). + +%%----------------------------------------------------------------- +%% Func: try_map_symbolic/1 +%% Args: Varbinds is a list returned from initiate_vars. +%% Purpose: Try to map symbolic name to oid for the +%% symbolic names left in the Varbinds list. +%% Returns: see initiate_vars. +%% NOTE: Executed at the intermediate SAs +%%----------------------------------------------------------------- +try_map_symbolic([Varbind | Varbinds]) -> + [localise_oid(Varbind) | try_map_symbolic(Varbinds)]; +try_map_symbolic([]) -> []. + +localise_oid({VariableName, Value}) when is_atom(VariableName) -> + alias_to_oid({VariableName, Value}); +localise_oid({VariableName, RowIndex, Value}) when is_atom(VariableName) -> + alias_to_oid({VariableName, RowIndex, Value}); +localise_oid(X) -> X. + +%%----------------------------------------------------------------- +%% Func: try_find_type/2 +%% Args: Varbinds is a list returned from initiate_vars. +%% Mib is a ref to the Mib process corresponding to +%% this agent. +%% Purpose: Try to find the type for each variableoid with a value +%% but no type. +%% Returns: see initiate_vars. +%% NOTE: Executed at the intermediate SAs +%%----------------------------------------------------------------- +try_find_type([Varbind | Varbinds], Mib) -> + [localise_type(Varbind, Mib) | try_find_type(Varbinds, Mib)]; +try_find_type([], _) -> []. + +localise_type({VariableOid, Type}, _Mib) + when is_list(VariableOid) andalso is_record(Type, asn1_type) -> + {VariableOid, Type}; +localise_type({VariableOid, Value}, Mib) when is_list(VariableOid) -> + case snmpa_mib:lookup(Mib, VariableOid) of + {variable, ME} -> + {VariableOid, ME#me.asn1_type, Value}; + {table_column, ME, _} -> + {VariableOid, ME#me.asn1_type, Value}; + _ -> + {VariableOid, Value} + end; +localise_type(X, _) -> X. + +%%----------------------------------------------------------------- +%% Func: make_v1_trap_pdu/4 +%% Args: Enterprise = oid() +%% Specific = integer() +%% Varbinds is as returned from initiate_vars +%% (but only {Oid, Type[, Value} permitted) +%% SysUpTime = integer() +%% Purpose: Make a #trappdu +%% Checks the Varbinds to see that no symbolic names are +%% present, and that each var has a type. Performs a get +%% to find any missing value. +%% Returns: {#trappdu, [byte()] | error +%% Fails: yes +%% NOTE: Executed at the MA +%%----------------------------------------------------------------- +make_v1_trap_pdu(Enterprise, Specific, VarbindList, SysUpTime) -> + {Enterp,Generic,Spec} = + case Enterprise of + ?snmp -> + {sys_object_id(),Specific,0}; + _ -> + {Enterprise,?enterpriseSpecific,Specific} + end, + {value, AgentIp} = snmp_framework_mib:intAgentIpAddress(get), + #trappdu{enterprise = Enterp, + agent_addr = AgentIp, + generic_trap = Generic, + specific_trap = Spec, + time_stamp = SysUpTime, + varbinds = VarbindList}. + +make_discovery_pdu(Vbs) -> + #pdu{type = 'inform-request', + request_id = snmpa_mpd:generate_req_id(), + error_status = noError, + error_index = 0, + varbinds = Vbs}. + +make_v2_notif_pdu(Vbs, Type) -> + #pdu{type = Type, + request_id = snmpa_mpd:generate_req_id(), + error_status = noError, + error_index = 0, + varbinds = Vbs}. + +make_varbind_list(Varbinds) -> + {VariablesWithValueAndType, VariablesWithType} = + split_variables( order(Varbinds) ), + V = get_values(VariablesWithType), + Vars = lists:append([V, VariablesWithValueAndType]), + [make_varbind(Var) || Var <- unorder(lists:keysort(1, Vars))]. + + +%%----------------------------------------------------------------- +%% Func: send_trap/6 +%% Args: TrapRec = #trap | #notification +%% NotifyName = string() +%% ContextName = string() +%% Recv = no_receiver | {Ref, Receiver} +%% Receiver = pid() | atom() | {M,F,A} +%% Vbs = [varbind()] +%% NetIf = pid() +%% Purpose: Default trap sending function. +%% Sends the trap to the targets pointed out by NotifyName. +%% If NotifyName is ""; the normal procedure defined in +%% SNMP-NOTIFICATION-MIB is used, i.e. the trap is sent to +%% all managers. +%% Otherwise, the NotifyName is used to find an entry in the +%% SnmpNotifyTable which define how to send the notification +%% (as an Inform or a Trap), and to select targets from +%% SnmpTargetAddrTable (using the Tag). +%%----------------------------------------------------------------- +send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf) -> + (catch do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf)). + +do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf) -> + VarbindList = make_varbind_list(Vbs), + Dests = find_dests(NotifyName), + send_trap_pdus(Dests, ContextName, {TrapRec, VarbindList}, [], [], [], + Recv, NetIf). + +send_discovery(TargetName, Record, ContextName, Vbs, NetIf) -> + case find_dest(TargetName) of + {ok, Dest} -> + send_discovery_pdu(Dest, Record, ContextName, Vbs, NetIf); + Error -> + Error + end. + + +get_values(VariablesWithType) -> + {Order, Varbinds} = extract_order(VariablesWithType, 1), + case snmpa_agent:do_get(snmpa_acm:get_root_mib_view(), Varbinds, true) of + {noError, _, NewVarbinds} -> + %% NewVarbinds is the result of: + %% first a reverse, then a sort on the oid field and finally + %% a reverse during the get-processing so we need to re-sort + %% on the org_index field again before contract-order + NewVarbinds1 = lists:keysort(#varbind.org_index, NewVarbinds), + contract_order(Order, NewVarbinds1); + {ErrorStatus, ErrorIndex, _} -> + user_err("snmpa_trap: get operation failed: ~w" + "~n at ~w" + "~n in ~w", + [ErrorStatus, ErrorIndex, Varbinds]), + throw(error) + end. + +make_varbind(Varbind) when is_record(Varbind, varbind) -> + Varbind; +make_varbind({VarOid, ASN1Type, Value}) -> + case snmpa_agent:make_value_a_correct_value(Value, ASN1Type, undef) of + {value, Type, Val} -> + #varbind{oid = VarOid, variabletype = Type, value = Val}; + {error, Reason} -> + user_err("snmpa_trap: Invalid value: ~w" + "~n Oid: ~w" + "~n Val: ~w" + "~n Type: ~w", + [Reason, VarOid, Value, ASN1Type]), + throw(error) + end. + +order(Varbinds) -> + order(Varbinds, 1). + +order([H | T], No) -> [{No, H} | order(T, No + 1)]; +order([], _) -> []. + +unorder([{_No, H} | T]) -> [H | unorder(T)]; +unorder([]) -> []. + +extract_order([{No, {VarOid, _Type}} | T], Index) -> + {Order, V} = extract_order(T, Index+1), + {[No | Order], [#varbind{oid = VarOid, org_index = Index} | V]}; +extract_order([], _) -> {[], []}. + +contract_order([No | Order], [Varbind | T]) -> + [{No, Varbind} | contract_order(Order, T)]; +contract_order([], []) -> + []. + +split_variables([{No, {VarOid, Type, Val}} | T]) when is_list(VarOid) -> + {A, B} = split_variables(T), + {[{No, {VarOid, Type, Val}} | A], B}; +split_variables([{No, {VarOid, Type}} | T]) + when is_list(VarOid) andalso is_record(Type, asn1_type) -> + {A, B} = split_variables(T), + {A, [{No, {VarOid, Type}} | B]}; +split_variables([{_No, {VarName, Value}} | _T]) -> + user_err("snmpa_trap: Undefined variable ~w (~w)", [VarName, Value]), + throw(error); +split_variables([{_No, {VarName, RowIndex, Value}} | _T]) -> + user_err("snmpa_trap: Undefined variable ~w ~w (~w)", + [VarName, RowIndex, Value]), + throw(error); +split_variables([]) -> {[], []}. + + +%%----------------------------------------------------------------- +%% Func: find_dests(NotifyName) -> +%% [{DestAddr, TargetName, TargetParams, NotifyType}] +%% Types: NotifyType = string() +%% DestAddr = {TDomain, TAddr} +%% TargetName = string() +%% TargetParams = {MpModel, SecModel, SecName, SecLevel} +%% NotifyType = trap | {inform, Timeout, Retry} +%% Returns: A list of all Destination addresses for this community. +%% NOTE: This function is executed in the master agent's context +%%----------------------------------------------------------------- +find_dests("") -> + snmp_notification_mib:get_targets(); +find_dests(NotifyName) -> + case snmp_notification_mib:get_targets(NotifyName) of + [] -> + ?vlog("No dests found for snmpNotifyName: ~p",[NotifyName]), + []; + Dests -> + Dests + end. + +find_dest(TargetName) -> + AddrCols = [?snmpTargetAddrTDomain, + ?snmpTargetAddrTAddress, + ?snmpTargetAddrTimeout, + ?snmpTargetAddrRetryCount, + ?snmpTargetAddrParams, + ?snmpTargetAddrRowStatus], + case snmp_target_mib:snmpTargetAddrTable(get, TargetName, AddrCols) of + [{value, TDomain}, + {value, TAddress}, + {value, Timeout}, + {value, RetryCount}, + {value, Params}, + {value, ?'RowStatus_active'}] -> + ?vtrace("find_dest -> found snmpTargetAddrTable info:" + "~n TDomain: ~p" + "~n TAddress: ~p" + "~n Timeout: ~p" + "~n RetryCount: ~p" + "~n Params: ~p", + [TDomain, TAddress, Timeout, RetryCount, Params]), + ParmCols = [?snmpTargetParamsMPModel, + ?snmpTargetParamsSecurityModel, + ?snmpTargetParamsSecurityName, + ?snmpTargetParamsSecurityLevel, + ?snmpTargetParamsRowStatus], + case snmp_target_mib:snmpTargetParamsTable(get, Params, ParmCols) of + [{value, ?MP_V3}, + {value, SecModel}, + {value, SecName}, + {value, SecLevel}, + {value, ?'RowStatus_active'}] -> + ?vtrace("find_dest -> found snmpTargetParamsTable info:" + "~n SecModel: ~p" + "~n SecName: ~p" + "~n SecLevel: ~p", + [SecModel, SecName, SecLevel]), + DestAddr = {TDomain, TAddress}, + TargetParams = {SecModel, SecName, SecLevel}, + Val = {DestAddr, TargetName, TargetParams, Timeout, RetryCount}, + {ok, Val}; + [{value, ?MP_V3}, + {value, _SecModel}, + {value, _SecName}, + {value, _SecLevel}, + {value, RowStatus}] -> + {error, {invalid_RowStatus, RowStatus, snmpTargetParamsTable}}; + [{value, MpModel}, + {value, _SecModel}, + {value, _SecName}, + {value, _SecLevel}, + {value, ?'RowStatus_active'}] -> + {error, {invalid_MpModel, MpModel, snmpTargetParamsTable}}; + [{value, _MpModel}, + {value, _SecModel}, + {value, _SecName}, + {value, _SecLevel}, + {value, RowStatus}] -> + {error, {invalid_RowStatus, RowStatus, snmpTargetParamsTable}}; + Bad -> + ?vlog("find_dest -> " + "could not find snmpTargetParamsTable info: " + "~n Bad: ~p", [Bad]), + {error, {not_found, snmpTargetParamsTable}} + end; + + [{value, _TDomain}, + {value, _TAddress}, + {value, _Timeout}, + {value, _RetryCount}, + {value, _Params}, + {value, RowStatus}] -> + {error, {invalid_RowStatus, RowStatus, snmpTargetAddrTable}}; + _ -> + {error, {not_found, snmpTargetAddrTable}} + end. + +send_discovery_pdu({Dest, TargetName, {SecModel, SecName, SecLevel}, + Timeout, Retry}, + Record, ContextName, Vbs, NetIf) -> + ?vdebug("send_discovery_pdu -> entry with " + "~n Destination address: ~p" + "~n Target name: ~p" + "~n Sec model: ~p" + "~n Sec name: ~p" + "~n Sec level: ~p" + "~n Timeout: ~p" + "~n Retry: ~p" + "~n Record: ~p" + "~n ContextName: ~p", + [Dest, TargetName, SecModel, SecName, SecLevel, + Timeout, Retry, Record, ContextName]), + case get_mib_view(SecModel, SecName, SecLevel, ContextName) of + {ok, MibView} -> + case check_all_varbinds(Record, Vbs, MibView) of + true -> + SysUpTime = snmp_standard_mib:sys_up_time(), + send_discovery_pdu(Record, Dest, Vbs, + SecModel, SecName, SecLevel, + TargetName, ContextName, + Timeout, Retry, + SysUpTime, NetIf); + false -> + {error, {mibview_validation_failed, Vbs, MibView}} + end; + {discarded, Reason} -> + {error, {failed_get_mibview, Reason}} + end. + +send_discovery_pdu(Record, Dest, Vbs, + SecModel, SecName, SecLevel, TargetName, + ContextName, Timeout, Retry, SysUpTime, NetIf) -> + {_Oid, IVbs} = mk_v2_trap(Record, Vbs, SysUpTime), % v2 refers to SMIv2; + Sender = proc_lib:spawn_link(?MODULE, init_discovery_inform, + [self(), + Dest, + SecModel, SecName, SecLevel, TargetName, + ContextName, + Timeout, Retry, + IVbs, NetIf, + get(verbosity)]), + {ok, Sender, SecLevel}. + +init_discovery_inform(Parent, + Dest, + SecModel, SecName, SecLevel, TargetName, + ContextName, Timeout, Retry, Vbs, NetIf, Verbosity) -> + put(verbosity, Verbosity), + put(sname, madis), + Pdu = make_discovery_pdu(Vbs), + ContextEngineId = snmp_framework_mib:get_engine_id(), + SecLevelFlag = mk_flag(SecLevel), + SecData = {SecModel, SecName, SecLevelFlag, TargetName}, + MsgData = {SecData, ContextEngineId, ContextName}, + Msg = {send_discovery, Pdu, MsgData, Dest, self()}, + ?MODULE:send_discovery_inform(Parent, Timeout*10, Retry, Msg, NetIf). + +%% note_timeout(Timeout, Retry) +%% when ((is_integer(Timeout) andalso (Timeout > 0)) andalso +%% (is_integer(Retry) andalso (Retry > 0))) +%% note_timeout(Timeout*10, Retry, 0); +%% note_timeout(Timeout, Retry) +%% when (is_integer(Timeout) andalso (Timeout > 0)) -> +%% Timeout*10. + +%% note_timeout(_Timeout, -1, NoteTimeout) -> +%% NoteTimeout; +%% note_timeout(Timeout, Retry, NoteTimeout) when -> +%% note_timeout(Timeout*2, Retry-1, NoteTimeout+Timeout). + +send_discovery_inform(Parent, _Timeout, -1, _Msg, _NetIf) -> + Parent ! {discovery_response, {error, timeout}}; +send_discovery_inform(Parent, Timeout, Retry, Msg, NetIf) -> + NetIf ! Msg, + receive + {snmp_discovery_response_received, Pdu, undefined} -> + ?vtrace("received stage 2 discovery response: " + "~n Pdu: ~p", [Pdu]), + Parent ! {discovery_response, {ok, Pdu}}; + {snmp_discovery_response_received, Pdu, ManagerEngineId} -> + ?vtrace("received stage 1 discovery response: " + "~n Pdu: ~p" + "~n ManagerEngineId: ~p", [Pdu, ManagerEngineId]), + Parent ! {discovery_response, {ok, Pdu, ManagerEngineId}} + after + Timeout -> + ?MODULE:send_discovery_inform(Parent, + Timeout*2, Retry-1, Msg, NetIf) + end. + + +%%----------------------------------------------------------------- +%% NOTE: This function is executed in the master agent's context +%% For each target, check if it has access to the objects in the +%% notification, determine which message version (v1, v2c or v3) +%% should be used for the target, and determine the message +%% specific parameters to be used. +%%----------------------------------------------------------------- +send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel}, + Type} | T], + ContextName,{TrapRec, Vbs}, V1Res, V2Res, V3Res, Recv, NetIf) -> + ?vdebug("send trap pdus: " + "~n Destination address: ~p" + "~n Target name: ~p" + "~n MP model: ~p" + "~n Type: ~p" + "~n V1Res: ~p" + "~n V2Res: ~p" + "~n V3Res: ~p", + [DestAddr, TargetName, MpModel, Type, V1Res, V2Res, V3Res]), + case get_mib_view(SecModel, SecName, SecLevel, ContextName) of + {ok, MibView} -> + case check_all_varbinds(TrapRec, Vbs, MibView) of + true when MpModel =:= ?MP_V1 -> + ?vtrace("send_trap_pdus -> v1 mp model",[]), + ContextEngineId = snmp_framework_mib:get_engine_id(), + case snmp_community_mib:vacm2community({SecName, + ContextEngineId, + ContextName}, + DestAddr) of + {ok, Community} -> + ?vdebug("community found for v1 dest: ~p", + [element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + [{DestAddr, Community} | V1Res], + V2Res, V3Res, Recv, NetIf); + undefined -> + ?vdebug("No community found for v1 dest: ~p", + [element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf) + end; + true when MpModel =:= ?MP_V2C -> + ?vtrace("send_trap_pdus -> v2c mp model",[]), + ContextEngineId = snmp_framework_mib:get_engine_id(), + case snmp_community_mib:vacm2community({SecName, + ContextEngineId, + ContextName}, + DestAddr) of + {ok, Community} -> + ?vdebug("community found for v2c dest: ~p", + [element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, + [{DestAddr, Community, Type}|V2Res], + V3Res, Recv, NetIf); + undefined -> + ?vdebug("No community found for v2c dest: ~p", + [element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf) + end; + true when MpModel =:= ?MP_V3 -> + ?vtrace("send_trap_pdus -> v3 mp model",[]), + SecLevelF = mk_flag(SecLevel), + MsgData = {SecModel, SecName, SecLevelF, TargetName}, + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, + [{DestAddr, MsgData, Type} | V3Res], + Recv, NetIf); + true -> + ?vlog("bad MpModel ~p for dest ~p", + [MpModel, element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf); + _ -> + ?vlog("no access for dest: " + "~n ~p in target ~p", + [element(2, DestAddr), TargetName]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf) + end; + {discarded, Reason} -> + ?vlog("mib view error ~p for" + "~n dest: ~p" + "~n SecName: ~w", + [Reason, element(2, DestAddr), SecName]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf) + end; +send_trap_pdus([], ContextName, {TrapRec, Vbs}, V1Res, V2Res, V3Res, + Recv, NetIf) -> + SysUpTime = snmp_standard_mib:sys_up_time(), + ?vdebug("send trap pdus with sysUpTime ~p", [SysUpTime]), + InformRecvs = get_inform_recvs(V2Res ++ V3Res), + InformTargets = [Addr || {Addr, _, _, _} <- InformRecvs], + deliver_recv(Recv, snmp_targets, InformTargets), + send_v1_trap(TrapRec, V1Res, Vbs, NetIf, SysUpTime), + send_v2_trap(TrapRec, V2Res, Vbs, Recv, NetIf, SysUpTime), + send_v3_trap(TrapRec, V3Res, Vbs, Recv, NetIf, SysUpTime, ContextName). + +send_v1_trap(_TrapRec, [], _Vbs, _NetIf, _SysUpTime) -> + ok; +send_v1_trap(#trap{enterpriseoid = Enter, specificcode = Spec}, + V1Res, Vbs, NetIf, SysUpTime) -> + ?vdebug("prepare to send v1 trap " + "~n '~p'" + "~n with" + "~n ~p" + "~n to" + "~n ~p", [Enter, Spec, V1Res]), + TrapPdu = make_v1_trap_pdu(Enter, Spec, Vbs, SysUpTime), + AddrCommunities = mk_addr_communities(V1Res), + lists:foreach(fun({Community, Addrs}) -> + ?vtrace("send v1 trap pdu to ~p",[Addrs]), + NetIf ! {send_pdu, 'version-1', TrapPdu, + {community, Community}, Addrs} + end, AddrCommunities); +send_v1_trap(#notification{oid = Oid}, V1Res, Vbs, NetIf, SysUpTime) -> + %% Use alg. in rfc2089 to map a v2 trap to a v1 trap + % delete Counter64 objects from vbs + ?vdebug("prepare to send v1 trap '~p'",[Oid]), + NVbs = [Vb || Vb <- Vbs, Vb#varbind.variabletype =/= 'Counter64'], + {Enter,Spec} = + case Oid of + [1,3,6,1,6,3,1,1,5,Specific] -> + {?snmp,Specific - 1}; + _ -> + case lists:reverse(Oid) of + [Last, 0 | First] -> + {lists:reverse(First),Last}; + [Last | First] -> + {lists:reverse(First),Last} + end + end, + TrapPdu = make_v1_trap_pdu(Enter, Spec, NVbs, SysUpTime), + AddrCommunities = mk_addr_communities(V1Res), + lists:foreach(fun({Community, Addrs}) -> + ?vtrace("send v1 trap to ~p",[Addrs]), + NetIf ! {send_pdu, 'version-1', TrapPdu, + {community, Community}, Addrs} + end, AddrCommunities). + +send_v2_trap(_TrapRec, [], _Vbs, _Recv, _NetIf, _SysUpTime) -> + ok; +send_v2_trap(TrapRec, V2Res, Vbs, Recv, NetIf, SysUpTime) -> + ?vdebug("prepare to send v2 trap",[]), + {_Oid, IVbs} = mk_v2_trap(TrapRec, Vbs, SysUpTime), + TrapRecvs = get_trap_recvs(V2Res), + InformRecvs = get_inform_recvs(V2Res), + do_send_v2_trap(TrapRecvs, IVbs, NetIf), + do_send_v2_inform(InformRecvs, IVbs, Recv, NetIf). + +send_v3_trap(_TrapRec, [], _Vbs, _Recv, _NetIf, _SysUpTime, _ContextName) -> + ok; +send_v3_trap(TrapRec, V3Res, Vbs, Recv, NetIf, SysUpTime, ContextName) -> + ?vdebug("prepare to send v3 trap",[]), + {_Oid, IVbs} = mk_v2_trap(TrapRec, Vbs, SysUpTime), % v2 refers to SMIv2; + TrapRecvs = get_trap_recvs(V3Res), % same SMI for v3 + InformRecvs = get_inform_recvs(V3Res), + do_send_v3_trap(TrapRecvs, ContextName, IVbs, NetIf), + do_send_v3_inform(InformRecvs, ContextName, IVbs, Recv, NetIf). + + +mk_v2_trap(#notification{oid = Oid}, Vbs, SysUpTime) -> + ?vtrace("make v2 notification '~p'",[Oid]), + mk_v2_notif(Oid, Vbs, SysUpTime); +mk_v2_trap(#trap{enterpriseoid = Enter, specificcode = Spec}, Vbs, SysUpTime) -> + %% Use alg. in rfc1908 to map a v1 trap to a v2 trap + ?vtrace("make v2 trap for '~p' with ~p",[Enter,Spec]), + {Oid,Enterp} = + case Enter of + ?snmp -> + {?snmpTraps ++ [Spec + 1],sys_object_id()}; + _ -> + {Enter ++ [0, Spec],Enter} + end, + ExtraVb = #varbind{oid = ?snmpTrapEnterprise_instance, + variabletype = 'OBJECT IDENTIFIER', + value = Enterp}, + mk_v2_notif(Oid, Vbs ++ [ExtraVb], SysUpTime). + +mk_v2_notif(Oid, Vbs, SysUpTime) -> + IVbs = [#varbind{oid = ?sysUpTime_instance, + variabletype = 'TimeTicks', + value = SysUpTime}, + #varbind{oid = ?snmpTrapOID_instance, + variabletype = 'OBJECT IDENTIFIER', + value = Oid} | Vbs], + {Oid, IVbs}. + +get_trap_recvs(TrapRecvs) -> + [{Addr, MsgData} || {Addr, MsgData, trap} <- TrapRecvs]. + +get_inform_recvs(InformRecvs) -> + [{Addr, MsgData, Timeout, Retry} || + {Addr, MsgData, {inform, Timeout, Retry}} <- InformRecvs]. + +do_send_v2_trap([], _Vbs, _NetIf) -> + ok; +do_send_v2_trap(Recvs, Vbs, NetIf) -> + TrapPdu = make_v2_notif_pdu(Vbs, 'snmpv2-trap'), + AddrCommunities = mk_addr_communities(Recvs), + lists:foreach(fun({Community, Addrs}) -> + ?vtrace("~n send v2 trap to ~p",[Addrs]), + NetIf ! {send_pdu, 'version-2', TrapPdu, + {community, Community}, Addrs} + end, AddrCommunities), + ok. + +do_send_v2_inform([], _Vbs, _Recv, _NetIf) -> + ok; +do_send_v2_inform(Recvs, Vbs, Recv, NetIf) -> + lists:foreach( + fun({Addr, Community, Timeout, Retry}) -> + ?vtrace("~n start inform sender to send v2 inform to ~p", + [Addr]), + proc_lib:spawn_link(?MODULE, init_v2_inform, + [Addr, Timeout, Retry, Vbs, + Recv, NetIf, Community, + get(verbosity), get(sname)]) + end, + Recvs). + +do_send_v3_trap([], _ContextName, _Vbs, _NetIf) -> + ok; +do_send_v3_trap(Recvs, ContextName, Vbs, NetIf) -> + TrapPdu = make_v2_notif_pdu(Vbs, 'snmpv2-trap'), % Yes, v2 + ContextEngineId = snmp_framework_mib:get_engine_id(), + lists:foreach(fun(Recv) -> + ?vtrace("~n send v3 notif to ~p",[Recv]), + NetIf ! {send_pdu, 'version-3', TrapPdu, + {v3, ContextEngineId, ContextName}, [Recv]} + end, Recvs), + ok. + +do_send_v3_inform([], _ContextName, _Vbs, _Recv, _NetIf) -> + ok; +do_send_v3_inform(Recvs, ContextName, Vbs, Recv, NetIf) -> + lists:foreach( + fun({Addr, MsgData, Timeout, Retry}) -> + ?vtrace("~n start inform sender to send v3 inform to ~p", + [Addr]), + proc_lib:spawn_link(?MODULE, init_v3_inform, + [{Addr, MsgData}, Timeout, Retry, Vbs, + Recv, NetIf, ContextName, + get(verbosity), get(sname)]) + end, + Recvs). + +%% New process +init_v2_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, Community,V,S) -> + %% Make a new Inform for each recipient; they need unique + %% request-ids! + put(verbosity,V), + put(sname,inform_sender_short_name(S)), + ?vdebug("~n starting with timeout = ~p and retry = ~p", + [Timeout,Retry]), + InformPdu = make_v2_notif_pdu(Vbs, 'inform-request'), + Msg = {send_pdu_req, 'version-2', InformPdu, {community, Community}, + [Addr], self()}, + ?MODULE:send_inform(Addr, Timeout*10, Retry, Msg, Recv, NetIf). + + +%% New process +init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, ContextName,V,S) -> + %% Make a new Inform for each recipient; they need unique + %% request-ids! + put(verbosity,V), + put(sname,inform_sender_short_name(S)), + ?vdebug("~n starting with timeout = ~p and retry = ~p", + [Timeout,Retry]), + InformPdu = make_v2_notif_pdu(Vbs, 'inform-request'), % Yes, v2 + ContextEngineId = snmp_framework_mib:get_engine_id(), + Msg = {send_pdu_req, 'version-3', InformPdu, + {v3, ContextEngineId, ContextName}, [Addr], self()}, + ?MODULE:send_inform(Addr, Timeout*10, Retry, Msg, Recv, NetIf). + +send_inform(Addr, _Timeout, -1, _Msg, Recv, _NetIf) -> + ?vinfo("~n Delivery of send-pdu-request to net-if failed: reply timeout", + []), + deliver_recv(Recv, snmp_notification, {no_response, Addr}); +send_inform(Addr, Timeout, Retry, Msg, Recv, NetIf) -> + ?vtrace("deliver send-pdu-request to net-if when" + "~n Timeout: ~p" + "~n Retry: ~p",[Timeout, Retry]), + NetIf ! Msg, + receive + {snmp_response_received, _Vsn, _Pdu, _From} -> + ?vtrace("received response for ~p (when Retry = ~p)", + [Recv, Retry]), + deliver_recv(Recv, snmp_notification, {got_response, Addr}) + after + Timeout -> + ?MODULE:send_inform(Addr, Timeout*2, Retry-1, Msg, Recv, NetIf) + end. + +% A nasty bit of verbosity setup... +inform_sender_short_name(ma) -> mais; +inform_sender_short_name(maw) -> mais; +inform_sender_short_name(mats) -> mais; +inform_sender_short_name(_) -> sais. + +deliver_recv(no_receiver, _MsgId, _Result) -> + ?vtrace("deliver_recv -> no receiver", []), + ok; +deliver_recv(#snmpa_notification_delivery_info{tag = Tag, + mod = Mod, + extra = Extra}, + snmp_targets, TAddrs) when is_list(TAddrs) -> + ?vtrace("deliver_recv(snmp_targets) -> entry with" + "~n Tag: ~p" + "~n Mod: ~p" + "~n Extra: ~p" + "~n TAddrs: ~p" + "", [Tag, Mod, Extra, TAddrs]), + Addrs = transform_taddrs(TAddrs), + (catch Mod:delivery_targets(Tag, Addrs, Extra)); +deliver_recv(#snmpa_notification_delivery_info{tag = Tag, + mod = Mod, + extra = Extra}, + snmp_notification, {DeliveryResult, TAddr}) -> + ?vtrace("deliver_recv -> entry with" + "~n Tag: ~p" + "~n Mod: ~p" + "~n Extra: ~p" + "~n DeliveryResult: ~p" + "~n TAddr: ~p" + "", [Tag, Mod, Extra, DeliveryResult, TAddr]), + Addr = transform_taddr(TAddr), + (catch Mod:delivery_info(Tag, Addr, DeliveryResult, Extra)); +deliver_recv({Tag, Receiver}, MsgId, Result) -> + ?vtrace("deliver_recv -> entry with" + "~n Tag: ~p" + "~n Receiver: ~p" + "~n MsgId: ~p" + "~n Result: ~p" + "", [Tag, Receiver, MsgId, Result]), + Msg = {MsgId, Tag, Result}, + case Receiver of + Pid when is_pid(Pid) -> + Pid ! Msg; + Name when is_atom(Name) -> + catch Name ! Msg; + {M, F, A} -> + catch M:F([Msg | A]); + Else -> + ?vinfo("~n Cannot deliver acknowledgment: bad receiver = '~p'", + [Else]), + user_err("snmpa: bad receiver, ~w\n", [Else]) + end; +deliver_recv(Else, _MsgId, _Result) -> + ?vinfo("~n Cannot deliver acknowledgment: bad receiver = '~p'", + [Else]), + user_err("snmpa: bad receiver, ~w\n", [Else]). + +transform_taddrs(Addrs) -> + [transform_taddr(Addr) || Addr <- Addrs]. + +transform_taddr({?snmpUDPDomain, [A1, A2, A3, A4, P1, P2]}) -> % v2 + Addr = {A1, A2, A3, A4}, + Port = P1 bsl 8 + P2, + {Addr, Port}; +transform_taddr({{?snmpUDPDomain, [A1, A2, A3, A4, P1, P2]}, _MsgData}) -> % v3 + Addr = {A1, A2, A3, A4}, + Port = P1 bsl 8 + P2, + {Addr, Port}. + + +check_all_varbinds(#notification{oid = Oid}, Vbs, MibView) -> + case snmpa_acm:validate_mib_view(Oid, MibView) of + true -> check_all_varbinds(Vbs, MibView); + false -> false + end; +check_all_varbinds(#trap{enterpriseoid = Enter, specificcode = Spec}, + Vbs, MibView) -> + %% Use alg. in rfc1908 to map a v1 trap to a v2 trap + Oid = case Enter of + ?snmp -> ?snmpTraps ++ [Spec + 1]; + _ -> Enter ++ [0, Spec] + end, + case snmpa_acm:validate_mib_view(Oid, MibView) of + true -> check_all_varbinds(Vbs, MibView); + false -> false + end. + +check_all_varbinds([#varbind{oid = Oid} | Vbs], MibView) -> + case snmpa_acm:validate_mib_view(Oid, MibView) of + true -> check_all_varbinds(Vbs, MibView); + false -> false + end; +check_all_varbinds([], _MibView) -> + true. + + +%%-------------------------------------------------- +%% Functions to access the local mib. +%%-------------------------------------------------- +sys_object_id() -> + case snmpa_agent:do_get(snmpa_acm:get_root_mib_view(), + [#varbind{oid = ?sysObjectID_instance}], + true) of + {noError, _, [#varbind{value = Value}]} -> + Value; + X -> + user_err("sysObjectID bad return value ~w", [X]) + end. + +%% Collect all ADDRs for each community together. +%% In: [{Addr, Community}] +%% Out: [{Community, [Addr]}] +mk_addr_communities(Recvs) -> + [{Addr, Comm} | T] = lists:keysort(2, Recvs), + mic(T, Comm, [Addr], []). + +mic([{Addr, Comm} | T], CurComm, AddrList, Res) when Comm == CurComm -> + mic(T, CurComm, [Addr | AddrList], Res); +mic([{Addr, Comm} | T], CurComm, AddrList, Res) -> + mic(T, Comm, [Addr], [{CurComm, AddrList} | Res]); +mic([], CurComm, AddrList, Res) -> + [{CurComm, AddrList} | Res]. + +%%----------------------------------------------------------------- +%% Convert the SecurityLevel into a flag value used by snmpa_mpd +%%----------------------------------------------------------------- +mk_flag(?'SnmpSecurityLevel_noAuthNoPriv') -> 0; +mk_flag(?'SnmpSecurityLevel_authNoPriv') -> 1; +mk_flag(?'SnmpSecurityLevel_authPriv') -> 3. + + +%%-------------------------------------------------- +%% Mib view wrapper +%%-------------------------------------------------- +get_mib_view(SecModel, SecName, SecLevel, ContextName) -> + snmpa_vacm:get_mib_view(notify, + SecModel, SecName, SecLevel, ContextName). + + +user_err(F, A) -> + snmpa_error:user_err(F, A). diff --git a/lib/snmp/src/agent/snmpa_usm.erl b/lib/snmp/src/agent/snmpa_usm.erl new file mode 100644 index 0000000000..a8c395534f --- /dev/null +++ b/lib/snmp/src/agent/snmpa_usm.erl @@ -0,0 +1,745 @@ +%% +%% %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_usm). + +-export([ + process_incoming_msg/4, + generate_outgoing_msg/5, + generate_discovery_msg/4, generate_discovery_msg/5, + current_statsNotInTimeWindows_vb/0 + ]). + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). +-include("SNMP-USER-BASED-SM-MIB.hrl"). +-include("SNMP-USM-AES-MIB.hrl"). +-include("SNMPv2-TC.hrl"). + +-define(VMODULE,"A-USM"). +-include("snmp_verbosity.hrl"). + + +%%----------------------------------------------------------------- +%% This module implements the User Based Security Model for SNMP, +%% as defined in rfc2274. +%%----------------------------------------------------------------- + +%% Columns not accessible via SNMP +-define(usmUserAuthKey, 14). +-define(usmUserPrivKey, 15). + +-define(i32(Int), (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255). +-define(i64(Int), (Int bsr 56) band 255, (Int bsr 48) band 255, (Int bsr 40) band 255, (Int bsr 32) band 255, (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255). + + +%%----------------------------------------------------------------- +%% Func: process_incoming_msg(Packet, Data, SecParams, SecLevel) -> +%% {ok, {SecEngineID, SecName, ScopedPDUBytes, SecData}} | +%% {error, Reason} | {error, Reason, ErrorInfo} +%% Return value may be throwed. +%% Types: Reason -> term() +%% Purpose: +%%----------------------------------------------------------------- + +process_incoming_msg(Packet, Data, SecParams, SecLevel) -> + TermDiscoEnabled = is_terminating_discovery_enabled(), + TermTriggerUsername = terminating_trigger_username(), + %% 3.2.1 + ?vtrace("process_incoming_msg -> check security parms: 3.2.1",[]), + UsmSecParams = + case catch snmp_pdus:dec_usm_security_parameters(SecParams) of + {'EXIT', Reason} -> + inc(snmpInASNParseErrs), + error({parseError, Reason}, []); + Res -> + Res + end, + case UsmSecParams of + #usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID, + msgUserName = TermTriggerUsername} when TermDiscoEnabled =:= true -> + %% Step 1 discovery message + ?vtrace("process_incoming_msg -> [~p] discovery step 1", + [TermTriggerUsername]), + process_discovery_msg(MsgAuthEngineID, Data, SecLevel); + + #usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID, + msgUserName = MsgUserName} -> + ?vlog("process_incoming_msg -> USM security parms: " + "~n msgAuthEngineID: ~w" + "~n userName: ~p", [MsgAuthEngineID, MsgUserName]), + %% 3.2.3 + ?vtrace("process_incoming_msg -> check engine id: 3.2.3",[]), + case snmp_user_based_sm_mib:is_engine_id_known(MsgAuthEngineID) of + true -> + ok; + false -> + SecData1 = [MsgUserName], + error(usmStatsUnknownEngineIDs, + ?usmStatsUnknownEngineIDs_instance, %% OTP-3542 + undefined, [{sec_data, SecData1}]) + end, + %% 3.2.4 + ?vtrace("process_incoming_msg -> retrieve usm user: 3.2.4",[]), + UsmUser = + case snmp_user_based_sm_mib:get_user(MsgAuthEngineID, + MsgUserName) of + User when element(?usmUserStatus, User) =:= ?'RowStatus_active' -> + User; + {_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} -> + ?vdebug("process_incoming_msg -> " + "found user ~p with wrong row status: ~p", + [Name, RowStatus]), + SecData2 = [MsgUserName], + error(usmStatsUnknownUserNames, + ?usmStatsUnknownUserNames_instance, %% OTP-3542 + undefined, [{sec_data, SecData2}]); + _ -> % undefined or not active user + SecData2 = [MsgUserName], + error(usmStatsUnknownUserNames, + ?usmStatsUnknownUserNames_instance, %% OTP-3542 + undefined, [{sec_data, SecData2}]) + end, + SecName = element(?usmUserSecurityName, UsmUser), + ?vtrace("process_incoming_msg -> securityName: ~p",[SecName]), + %% 3.2.5 - implicit in following checks + %% 3.2.6 - 3.2.7 + ?vtrace("process_incoming_msg -> " + "authenticate incoming: 3.2.5 - 3.2.7" + "~n ~p",[UsmUser]), + DiscoOrPlain = authenticate_incoming(Packet, + UsmSecParams, UsmUser, + SecLevel), + %% 3.2.8 + ?vtrace("process_incoming_msg -> " + "decrypt scoped data: 3.2.8",[]), + ScopedPDUBytes = + decrypt(Data, UsmUser, UsmSecParams, SecLevel), + %% 3.2.9 + %% Means that if AuthKey/PrivKey are changed; + %% the old values will be used. + ?vtrace("process_incoming_msg -> " + "AuthKey/PrivKey are changed - " + "use old values: 3.2.9",[]), + CachedSecData = {MsgUserName, + element(?usmUserAuthProtocol, UsmUser), + element(?usmUserPrivProtocol, UsmUser), + element(?usmUserAuthKey, UsmUser), + element(?usmUserPrivKey, UsmUser)}, + {ok, {MsgAuthEngineID, SecName, ScopedPDUBytes, + CachedSecData, DiscoOrPlain}} + end. + +%% Process a step 1 discovery message +process_discovery_msg(MsgAuthEngineID, Data, SecLevel) -> + ?vtrace("process_discovery_msg -> entry with" + "~n Data: ~p" + "~n SecLevel: ~p", [Data, SecLevel]), + case (not snmp_misc:is_priv(SecLevel)) of + true -> % noAuthNoPriv + ?vtrace("process_discovery_msg -> noAuthNoPriv", []), + ScopedPDUBytes = Data, + SecData = {"", usmNoAuthProtocol, "", usmNoPrivProtocol, ""}, + NewData = {SecData, + ?usmStatsUnknownEngineIDs_instance, + get_counter(usmStatsUnknownEngineIDs)}, + {ok, {MsgAuthEngineID, "", ScopedPDUBytes, NewData, discovery}}; + false -> + error(usmStatsUnknownEngineIDs, + ?usmStatsUnknownEngineIDs_instance, + undefined, [{sec_data, ""}]) + end. + + +authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel) -> + %% 3.2.6 + ?vtrace("authenticate_incoming -> 3.2.6", []), + AuthProtocol = element(?usmUserAuthProtocol, UsmUser), + #usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID, + msgAuthoritativeEngineBoots = MsgAuthEngineBoots, + msgAuthoritativeEngineTime = MsgAuthEngineTime, + msgAuthenticationParameters = MsgAuthParams} = + UsmSecParams, + ?vtrace("authenticate_incoming -> Sec params: " + "~n MsgAuthEngineID: ~w" + "~n MsgAuthEngineBoots: ~p" + "~n MsgAuthEngineTime: ~p", + [MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime]), + case snmp_misc:is_auth(SecLevel) of + true -> + SecName = element(?usmUserSecurityName, UsmUser), + case is_auth(AuthProtocol, + element(?usmUserAuthKey, UsmUser), + MsgAuthParams, + Packet, + SecName, + MsgAuthEngineID, + MsgAuthEngineBoots, + MsgAuthEngineTime) of + discovery -> + discovery; + true -> + plain; + false -> + error(usmStatsWrongDigests, + ?usmStatsWrongDigests_instance, % OTP-5464 + SecName) + end; + + false -> % noAuth + plain + end. + +authoritative(SecName, MsgAuthEngineBoots, MsgAuthEngineTime) -> + ?vtrace("authoritative -> entry with" + "~n SecName: ~p" + "~n MsgAuthEngineBoots: ~p" + "~n MsgAuthEngineTime: ~p", + [SecName, MsgAuthEngineBoots, MsgAuthEngineTime]), + SnmpEngineBoots = snmp_framework_mib:get_engine_boots(), + ?vtrace("authoritative -> SnmpEngineBoots: ~p", [SnmpEngineBoots]), + SnmpEngineTime = snmp_framework_mib:get_engine_time(), + ?vtrace("authoritative -> SnmpEngineTime: ~p", [SnmpEngineTime]), + InTimeWindow = + if + SnmpEngineBoots =:= 2147483647 -> false; + MsgAuthEngineBoots =/= SnmpEngineBoots -> false; + MsgAuthEngineTime + 150 < SnmpEngineTime -> false; + MsgAuthEngineTime - 150 > SnmpEngineTime -> false; + true -> true + end, + case InTimeWindow of + true -> + true; + false -> + %% OTP-4090 (OTP-3542) + ?vinfo("NOT in time window: " + "~n SecName: ~p" + "~n SnmpEngineBoots: ~p" + "~n MsgAuthEngineBoots: ~p" + "~n SnmpEngineTime: ~p" + "~n MsgAuthEngineTime: ~p", + [SecName, + SnmpEngineBoots, MsgAuthEngineBoots, + SnmpEngineTime, MsgAuthEngineTime]), + error(usmStatsNotInTimeWindows, + ?usmStatsNotInTimeWindows_instance, + SecName, + [{securityLevel, 1}]) % authNoPriv + end. + +non_authoritative(SecName, + MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime) -> + ?vtrace("non_authoritative -> entry with" + "~n SecName: ~p" + "~n MsgAuthEngineID: ~p" + "~n MsgAuthEngineBoots: ~p" + "~n MsgAuthEngineTime: ~p", + [SecName, + MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime]), + SnmpEngineBoots = get_engine_boots(MsgAuthEngineID), + SnmpEngineTime = get_engine_time(MsgAuthEngineID), + LatestRecvTime = get_engine_latest_time(MsgAuthEngineID), + ?vtrace("non_authoritative -> " + "~n SnmpEngineBoots: ~p" + "~n SnmpEngineTime: ~p" + "~n LatestRecvTime: ~p", + [SnmpEngineBoots, SnmpEngineTime, LatestRecvTime]), + UpdateLCD = + if + MsgAuthEngineBoots > SnmpEngineBoots -> true; + ((MsgAuthEngineBoots =:= SnmpEngineBoots) andalso + (MsgAuthEngineTime > LatestRecvTime)) -> true; + true -> false + end, + case UpdateLCD of + true -> %% 3.2.7b1 + ?vtrace("non_authoritative -> " + "update msgAuthoritativeEngineID: 3.2.7b1", + []), + set_engine_boots(MsgAuthEngineID, MsgAuthEngineBoots), + set_engine_time(MsgAuthEngineID, MsgAuthEngineTime), + set_engine_latest_time(MsgAuthEngineID, MsgAuthEngineTime); + false -> + ok + end, + %% 3.2.7.b2 + ?vtrace("non_authoritative -> " + "check if message is outside time window: 3.2.7b2", []), + InTimeWindow = + if + SnmpEngineBoots =:= 2147483647 -> + false; + MsgAuthEngineBoots < SnmpEngineBoots -> + false; + ((MsgAuthEngineBoots =:= SnmpEngineBoots) andalso + (MsgAuthEngineTime < (SnmpEngineTime - 150))) -> + false; + true -> true + end, + case InTimeWindow of + false -> + ?vinfo("NOT in time window: " + "~n SecName: ~p" + "~n SnmpEngineBoots: ~p" + "~n MsgAuthEngineBoots: ~p" + "~n SnmpEngineTime: ~p" + "~n MsgAuthEngineTime: ~p", + [SecName, + SnmpEngineBoots, MsgAuthEngineBoots, + SnmpEngineTime, MsgAuthEngineTime]), + error(notInTimeWindow, []); + true -> + %% If the previous values where all zero's this is the + %% second stage discovery message + if + ((SnmpEngineBoots =:= 0) andalso + (SnmpEngineTime =:= 0) andalso + (LatestRecvTime =:= 0)) -> + ?vtrace("non_authoritative -> " + "[maybe] originating discovery stage 2", []), + discovery; + true -> + true + end + end. + + +is_auth(?usmNoAuthProtocol, _, _, _, SecName, _, _, _) -> % 3.2.5 + error(usmStatsUnsupportedSecLevels, + ?usmStatsUnsupportedSecLevels_instance, SecName); % OTP-5464 +is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName, + MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime) -> + TermDiscoEnabled = is_terminating_discovery_enabled(), + TermDiscoStage2 = terminating_discovery_stage2(), + IsAuth = auth_in(AuthProtocol, AuthKey, AuthParams, Packet), + ?vtrace("is_auth -> IsAuth: ~p", [IsAuth]), + case IsAuth of + true -> + %% 3.2.7 + ?vtrace("is_auth -> " + "retrieve EngineBoots and EngineTime: 3.2.7",[]), + SnmpEngineID = snmp_framework_mib:get_engine_id(), + ?vtrace("is_auth -> SnmpEngineID: ~p", [SnmpEngineID]), + case MsgAuthEngineID of + SnmpEngineID when ((MsgAuthEngineBoots =:= 0) andalso + (MsgAuthEngineTime =:= 0) andalso + (TermDiscoEnabled =:= true) andalso + (TermDiscoStage2 =:= discovery)) -> %% 3.2.7a + ?vtrace("is_auth -> terminating discovery stage 2 - discovery",[]), + discovery; + SnmpEngineID when ((MsgAuthEngineBoots =:= 0) andalso + (MsgAuthEngineTime =:= 0) andalso + (TermDiscoEnabled =:= true) andalso + (TermDiscoStage2 =:= plain)) -> %% 3.2.7a + ?vtrace("is_auth -> terminating discovery stage 2 - plain",[]), + %% This will *always* result in the manager *not* + %% beeing in timewindow + authoritative(SecName, + MsgAuthEngineBoots, MsgAuthEngineTime); + + SnmpEngineID -> %% 3.2.7a + ?vtrace("is_auth -> we are authoritative: 3.2.7a", []), + authoritative(SecName, + MsgAuthEngineBoots, MsgAuthEngineTime); + + _ -> %% 3.2.7b - we're non-authoritative + ?vtrace("is_auth -> we are non-authoritative: 3.2.7b",[]), + non_authoritative(SecName, + MsgAuthEngineID, + MsgAuthEngineBoots, MsgAuthEngineTime) + end; + + false -> + false + end. + + +decrypt(Data, UsmUser, UsmSecParams, SecLevel) -> + case snmp_misc:is_priv(SecLevel) of + true -> + do_decrypt(Data, UsmUser, UsmSecParams); + false -> + Data + end. + +do_decrypt(Data, UsmUser, UsmSecParams) -> + EncryptedPDU = snmp_pdus:dec_scoped_pdu_data(Data), + SecName = element(?usmUserSecurityName, UsmUser), + PrivP = element(?usmUserPrivProtocol, UsmUser), + PrivKey = element(?usmUserPrivKey, UsmUser), + ?vtrace("do_decrypt -> try decrypt with: " + "~n SecName: ~p" + "~n PrivP: ~p", [SecName, PrivP]), + try_decrypt(PrivP, PrivKey, UsmSecParams, EncryptedPDU, SecName). + +try_decrypt(?usmNoPrivProtocol, _, _, _, SecName) -> % 3.2.5 + error(usmStatsUnsupportedSecLevels, + ?usmStatsUnsupportedSecLevels_instance, SecName); % OTP-5464 +try_decrypt(?usmDESPrivProtocol, + PrivKey, UsmSecParams, EncryptedPDU, SecName) -> + case (catch des_decrypt(PrivKey, UsmSecParams, EncryptedPDU)) of + {ok, DecryptedData} -> + DecryptedData; + _ -> + error(usmStatsDecryptionErrors, + ?usmStatsDecryptionErrors_instance, % OTP-5464 + SecName) + end; +try_decrypt(?usmAesCfb128Protocol, + PrivKey, UsmSecParams, EncryptedPDU, SecName) -> + case (catch aes_decrypt(PrivKey, UsmSecParams, EncryptedPDU)) of + {ok, DecryptedData} -> + DecryptedData; + _ -> + error(usmStatsDecryptionErrors, + ?usmStatsDecryptionErrors_instance, % OTP-5464 + SecName) + end. + + +generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) -> + %% 3.1.1 + ?vtrace("generate_outgoing_msg -> [3.1.1] entry with" + "~n SecEngineID: ~p" + "~n SecName: ~p" + "~n SecLevel: ~w", + [SecEngineID, SecName, SecLevel]), + {UserName, AuthProtocol, PrivProtocol, AuthKey, PrivKey} = + case SecData of + [] -> % 3.1.1b + %% Not a response - read from LCD + case snmp_user_based_sm_mib:get_user_from_security_name( + SecEngineID, SecName) of + User when element(?usmUserStatus, User) =:= + ?'RowStatus_active' -> + {element(?usmUserName, User), + element(?usmUserAuthProtocol, User), + element(?usmUserPrivProtocol, User), + element(?usmUserAuthKey, User), + element(?usmUserPrivKey, User)}; + {_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} -> + ?vdebug("generate_outgoing_msg -> " + "found user ~p with wrong row status: ~p", + [Name, RowStatus]), + error(unknownSecurityName); + _ -> + error(unknownSecurityName) + end; + [MsgUserName] -> + %% This means the user at the engine is unknown + {MsgUserName, ?usmNoAuthProtocol, ?usmNoPrivProtocol, "", ""}; + _ -> % 3.1.1a + SecData + end, + %% 3.1.4 + ?vtrace("generate_outgoing_msg -> [3.1.4]" + "~n UserName: ~p" + "~n AuthProtocol: ~p" + "~n PrivProtocol: ~p", + [UserName, AuthProtocol, PrivProtocol]), + ScopedPduBytes = Message#message.data, + {ScopedPduData, MsgPrivParams} = + encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel), + SnmpEngineID = snmp_framework_mib:get_engine_id(), + ?vtrace("generate_outgoing_msg -> SnmpEngineID: ~p [3.1.6]", + [SnmpEngineID]), + %% 3.1.6 + {MsgAuthEngineBoots, MsgAuthEngineTime} = + case snmp_misc:is_auth(SecLevel) of + false when SecData =:= [] -> % not a response + {0, 0}; + false when UserName =:= "" -> % reply (report) to discovery step 1 + {0, 0}; + true when SecEngineID =/= SnmpEngineID -> + {get_engine_boots(SecEngineID), + get_engine_time(SecEngineID)}; + _ -> + {snmp_framework_mib:get_engine_boots(), + snmp_framework_mib:get_engine_time()} + end, + %% 3.1.5 - 3.1.7 + ?vtrace("generate_outgoing_msg -> [3.1.5 - 3.1.7]",[]), + UsmSecParams = + #usmSecurityParameters{msgAuthoritativeEngineID = SecEngineID, + msgAuthoritativeEngineBoots = MsgAuthEngineBoots, + msgAuthoritativeEngineTime = MsgAuthEngineTime, + msgUserName = UserName, + msgPrivacyParameters = MsgPrivParams}, + Message2 = Message#message{data = ScopedPduData}, + %% 3.1.8 + ?vtrace("generate_outgoing_msg -> [3.1.8]",[]), + authenticate_outgoing(Message2, UsmSecParams, + AuthKey, AuthProtocol, SecLevel). + + +generate_discovery_msg(Message, SecEngineID, SecName, SecLevel) -> + generate_discovery_msg(Message, SecEngineID, SecName, SecLevel, ""). + +generate_discovery_msg(Message, + SecEngineID, SecName, SecLevel, + InitialUserName) -> + ?vtrace("generate_discovery_msg -> entry with" + "~n SecEngineID: ~p" + "~n SecName: ~p" + "~n SecLevel: ~p" + "~n InitialUserName: ~p", + [SecEngineID, SecName, SecLevel, InitialUserName]), + {UserName, AuthProtocol, AuthKey, PrivProtocol, PrivKey} = + case SecEngineID of + "" -> + %% Discovery step 1 + %% Nothing except the user name will be used in this + %% tuple in this step, but since we need some values, + %% we fill in proper ones just in case + %% {"initial", usmNoAuthProtocol, "", usmNoPrivProtocol, ""}; + %% {"", usmNoAuthProtocol, "", usmNoPrivProtocol, ""}; + {InitialUserName, + usmNoAuthProtocol, "", usmNoPrivProtocol, ""}; + + _ -> + %% Discovery step 2 + case snmp_user_based_sm_mib:get_user_from_security_name( + SecEngineID, SecName) of + User when element(?usmUserStatus, User) =:= + ?'RowStatus_active' -> + {element(?usmUserName, User), + element(?usmUserAuthProtocol, User), + element(?usmUserAuthKey, User), + element(?usmUserPrivProtocol, User), + element(?usmUserPrivKey, User)}; + {_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} -> + ?vdebug("generate_discovery_msg -> " + "found user ~p with wrong row status: ~p", + [Name, RowStatus]), + error(unknownSecurityName); + _ -> + error(unknownSecurityName) + end + end, + ScopedPduBytes = Message#message.data, + {ScopedPduData, MsgPrivParams} = + encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel), + UsmSecParams = + #usmSecurityParameters{msgAuthoritativeEngineID = SecEngineID, + msgAuthoritativeEngineBoots = 0, % Boots, + msgAuthoritativeEngineTime = 0, % Time, + msgUserName = UserName, + msgPrivacyParameters = MsgPrivParams}, + Message2 = Message#message{data = ScopedPduData}, + authenticate_outgoing(Message2, UsmSecParams, + AuthKey, AuthProtocol, SecLevel). + + +%% Ret: {ScopedPDU, MsgPrivParams} - both are already encoded as OCTET STRINGs +encrypt(Data, PrivProtocol, PrivKey, SecLevel) -> + case snmp_misc:is_priv(SecLevel) of + false -> % 3.1.4b + ?vtrace("encrypt -> 3.1.4b",[]), + {Data, []}; + true -> % 3.1.4a + ?vtrace("encrypt -> 3.1.4a",[]), + case (catch try_encrypt(PrivProtocol, PrivKey, Data)) of + {ok, ScopedPduData, MsgPrivParams} -> + ?vtrace("encrypt -> encode tag",[]), + {snmp_pdus:enc_oct_str_tag(ScopedPduData), MsgPrivParams}; + {error, Reason} -> + error(Reason); + _Error -> + error(encryptionError) + end + end. + +try_encrypt(?usmNoPrivProtocol, _PrivKey, _Data) -> % 3.1.2 + error(unsupportedSecurityLevel); +try_encrypt(?usmDESPrivProtocol, PrivKey, Data) -> + des_encrypt(PrivKey, Data); +try_encrypt(?usmAesCfb128Protocol, PrivKey, Data) -> + aes_encrypt(PrivKey, Data). + + +authenticate_outgoing(Message, UsmSecParams, + AuthKey, AuthProtocol, SecLevel) -> + Message2 = + case snmp_misc:is_auth(SecLevel) of + true -> + auth_out(AuthProtocol, AuthKey, Message, UsmSecParams); + false -> + set_msg_auth_params(Message, UsmSecParams) + end, + ?vtrace("authenticate_outgoing -> encode message only",[]), + snmp_pdus:enc_message_only(Message2). + + + +%%----------------------------------------------------------------- +%% Auth and priv algorithms +%%----------------------------------------------------------------- +auth_in(AuthProtocol, AuthKey, AuthParams, Packet) -> + snmp_usm:auth_in(AuthProtocol, AuthKey, AuthParams, Packet). + +auth_out(AuthProtocol, AuthKey, Message, UsmSecParams) -> + snmp_usm:auth_out(AuthProtocol, AuthKey, Message, UsmSecParams). + +set_msg_auth_params(Message, UsmSecParams) -> + snmp_usm:set_msg_auth_params(Message, UsmSecParams, []). + +des_encrypt(PrivKey, Data) -> + snmp_usm:des_encrypt(PrivKey, Data, fun get_des_salt/0). + +des_decrypt(PrivKey, UsmSecParams, EncData) -> + #usmSecurityParameters{msgPrivacyParameters = PrivParms} = UsmSecParams, + snmp_usm:des_decrypt(PrivKey, PrivParms, EncData). + +get_des_salt() -> + SaltInt = + case catch ets:update_counter(snmp_agent_table, usm_des_salt, 1) of + N when N =< 4294967295 -> + N; + N when is_integer(N) -> % wrap + ets:insert(snmp_agent_table, {usm_des_salt, 0}), + 0; + _ -> % it doesn't exist, initialize + {A1,A2,A3} = erlang:now(), + random:seed(A1,A2,A3), + R = random:uniform(4294967295), + ets:insert(snmp_agent_table, {usm_des_salt, R}), + R + end, + EngineBoots = snmp_framework_mib:get_engine_boots(), + [?i32(EngineBoots), ?i32(SaltInt)]. + +aes_encrypt(PrivKey, Data) -> + snmp_usm:aes_encrypt(PrivKey, Data, fun get_aes_salt/0). + +aes_decrypt(PrivKey, UsmSecParams, EncData) -> + #usmSecurityParameters{msgPrivacyParameters = PrivParams, + msgAuthoritativeEngineTime = EngineTime, + msgAuthoritativeEngineBoots = EngineBoots} = + UsmSecParams, + snmp_usm:aes_decrypt(PrivKey, PrivParams, EncData, + EngineBoots, EngineTime). + +get_aes_salt() -> + SaltInt = + case catch ets:update_counter(snmp_agent_table, usm_aes_salt, 1) of + N when N =< 36893488147419103231 -> + N; + N when is_integer(N) -> % wrap + ets:insert(snmp_agent_table, {usm_aes_salt, 0}), + 0; + _ -> % it doesn't exist, initialize + {A1,A2,A3} = erlang:now(), + random:seed(A1,A2,A3), + R = random:uniform(36893488147419103231), + ets:insert(snmp_agent_table, {usm_aes_salt, R}), + R + end, + [?i64(SaltInt)]. + + + +%%----------------------------------------------------------------- +%% Discovery wrapper functions +%%----------------------------------------------------------------- + +is_terminating_discovery_enabled() -> + snmpa_agent:is_terminating_discovery_enabled(). + +terminating_discovery_stage2() -> + snmpa_agent:terminating_discovery_stage2(). + +terminating_trigger_username() -> + snmpa_agent:terminating_trigger_username(). + +current_statsNotInTimeWindows_vb() -> + #varbind{oid = ?usmStatsNotInTimeWindows_instance, + variabletype = 'Counter32', + value = get_counter(usmStatsNotInTimeWindows)}. + + +%%----------------------------------------------------------------- +%% We cache the local values of all non-auth engines we know. +%% Keep the values in the snmp_agent_table. +%% See section 2.3 of the RFC. +%%----------------------------------------------------------------- +get_engine_boots(SnmpEngineID) -> + case ets:lookup(snmp_agent_table, {usm_eboots, SnmpEngineID}) of + [{_Key, Boots}] -> Boots; + _ -> 0 + end. + +get_engine_time(SnmpEngineID) -> + case ets:lookup(snmp_agent_table, {usm_etime, SnmpEngineID}) of + [{_Key, Diff}] -> snmp_misc:now(sec) - Diff; + _ -> 0 + end. + +get_engine_latest_time(SnmpEngineID) -> + case ets:lookup(snmp_agent_table, {usm_eltime, SnmpEngineID}) of + [{_Key, Time}] -> Time; + _ -> 0 + end. + + +set_engine_boots(SnmpEngineID, EngineBoots) -> + ets:insert(snmp_agent_table, {{usm_eboots, SnmpEngineID}, EngineBoots}). + +set_engine_time(SnmpEngineID, EngineTime) -> + Diff = snmp_misc:now(sec) - EngineTime, + ets:insert(snmp_agent_table, {{usm_etime, SnmpEngineID}, Diff}). + +set_engine_latest_time(SnmpEngineID, EngineTime) -> + ets:insert(snmp_agent_table, {{usm_eltime, SnmpEngineID}, EngineTime}). + + +%%----------------------------------------------------------------- +%% Utility functions +%%----------------------------------------------------------------- +error(Reason) -> + throw({error, Reason}). + +error(Reason, ErrorInfo) -> + throw({error, Reason, ErrorInfo}). + +error(Variable, Oid, SecName) -> + error(Variable, Oid, SecName, []). +error(Variable, Oid, SecName, Opts) -> + Val = inc(Variable), + ErrorInfo = {#varbind{oid = Oid, + variabletype = 'Counter32', + value = Val}, + SecName, + Opts}, + throw({error, Variable, ErrorInfo}). + +inc(Name) -> ets:update_counter(snmp_agent_table, Name, 1). + + +get_counter(Name) -> + case (catch ets:lookup(snmp_agent_table, Name)) of + [{_, Val}] -> + Val; + _ -> + 0 + end. + + + + + 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). diff --git a/lib/snmp/src/agent/snmpa_vacm.hrl b/lib/snmp/src/agent/snmpa_vacm.hrl new file mode 100644 index 0000000000..681591d212 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_vacm.hrl @@ -0,0 +1,24 @@ +%% +%% %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% +%% +-define(vacmAContextMatch, 1). +-define(vacmAReadViewName, 2). +-define(vacmAWriteViewName, 3). +-define(vacmANotifyViewName, 4). +-define(vacmAStorageType, 5). +-define(vacmAStatus, 6). |