diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/snmp/src | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/snmp/src')
113 files changed, 51318 insertions, 0 deletions
diff --git a/lib/snmp/src/Makefile b/lib/snmp/src/Makefile new file mode 100644 index 0000000000..341107ce30 --- /dev/null +++ b/lib/snmp/src/Makefile @@ -0,0 +1,35 @@ +#-*-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% + +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Common Macros +# ---------------------------------------------------- + +include subdirs.mk + +SPECIAL_TARGETS = + +# ---------------------------------------------------- +# Default Subdir Targets +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_subdir.mk + 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). diff --git a/lib/snmp/src/app/Makefile b/lib/snmp/src/app/Makefile new file mode 100644 index 0000000000..d89eb4e723 --- /dev/null +++ b/lib/snmp/src/app/Makefile @@ -0,0 +1,141 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2003-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 = $(HRLS:%=%.hrl) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +EXT_HRL_FILES = \ + ../../include/snmp_types.hrl \ + ../../include/snmp_tables.hrl + +APP_FILE = snmp.app +APPUP_FILE = snmp.appup + +APP_SRC = $(APP_FILE).src +APP_TARGET = $(EBIN)/$(APP_FILE) + +APPUP_SRC = $(APPUP_FILE).src +APPUP_TARGET = $(EBIN)/$(APPUP_FILE) + +# ---------------------------------------------------- +# SNMP FLAGS +# ---------------------------------------------------- +ifeq ($(SNMP_DEFAULT_VERBOSITY),) + SNMP_FLAGS = -Ddefault_verbosity=silence +else + SNMP_FLAGS = -Ddefault_verbosity=$(SNMP_DEFAULT_VERBOSITY) +endif + +ifeq ($(SNMP_DEBUG),d) + SNMP_FLAGS += -Dsnmp_debug +endif + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- + +ifeq ($(WARN_UNUSED_VARS),true) +ERL_COMPILE_FLAGS += +warn_unused_vars +endif + +ERL_COMPILE_FLAGS += -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) $(APP_TARGET) $(APPUP_TARGET) + + +clean: + rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + rm -f core *~ + +docs: + +info: + @echo "VSN: $(VSN)" + @echo "APP_VSN: $(APP_VSN)" + + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + +$(APP_TARGET): $(APP_SRC) ../../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../../vsn.mk + sed -e 's;%VSN%;$(VSN);' $< > $@ + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/app + $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)/src/app + $(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/app/depend.mk b/lib/snmp/src/app/depend.mk new file mode 100644 index 0000000000..1080cb5693 --- /dev/null +++ b/lib/snmp/src/app/depend.mk @@ -0,0 +1,29 @@ +#-*-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)/snmp.$(EMULATOR): \ + snmp.erl + +$(EBIN)/snmp_app.$(EMULATOR): \ + snmp_app.erl + +$(EBIN)/snmp_app_sup.$(EMULATOR): \ + snmp_app_sup.erl + + diff --git a/lib/snmp/src/app/modules.mk b/lib/snmp/src/app/modules.mk new file mode 100644 index 0000000000..678f15de5c --- /dev/null +++ b/lib/snmp/src/app/modules.mk @@ -0,0 +1,28 @@ +#-*-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% + +MODULES = \ + snmp \ + snmp_app \ + snmp_app_sup + +HRLS = \ + snmp_internal + + diff --git a/lib/snmp/src/app/snmp.app.src b/lib/snmp/src/app/snmp.app.src new file mode 100644 index 0000000000..a880a14696 --- /dev/null +++ b/lib/snmp/src/app/snmp.app.src @@ -0,0 +1,134 @@ +%% +%% %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% +%% + +{application, snmp, + [{description, "SNMP CXC 138 13"}, + {vsn, "%VSN%"}, + {modules, [ + %% Compiler modules (not in the runtime part of the app) +% snmpc, +% snmpc_lib, +% snmpc_mib_gram, +% snmpc_mib_to_hrl, +% snmpc_misc, +% snmpc_tok, + + %% Application modules + snmp, + snmp_app, + snmp_app_sup, + + %% Agent modules + snmpa, + snmpa_acm, + snmpa_agent, + snmpa_agent_sup, + snmpa_app, + snmpa_authentication_service, + snmpa_conf, + snmpa_error, + snmpa_discovery_handler, + snmpa_discovery_handler_default, + snmpa_error_io, + snmpa_error_logger, + snmpa_error_report, + 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_network_interface, + snmpa_network_interface_filter, + snmpa_notification_delivery_info_receiver, + snmpa_notification_filter, + snmpa_set, + snmpa_set_lib, + snmpa_set_mechanism, + 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, + + %% Manager modules: + snmpm, + snmpm_conf, + snmpm_config, + snmpm_misc_sup, + snmpm_mpd, + snmpm_net_if, + snmpm_net_if_filter, + snmpm_network_interface, + snmpm_network_interface_filter, + snmpm_server, + snmpm_server_sup, + snmpm_supervisor, + snmpm_user, + snmpm_user_default, + snmpm_user_old, + snmpm_usm, + + %% Misc modules + snmp_conf, + snmp_config, + snmp_log, + snmp_mini_mib, + snmp_misc, + snmp_note_store, + snmp_pdus, + snmp_usm, + snmp_verbosity + + ]}, + %% Which registered process exist depend on the configuration: + %% If an agent is configured, then the following processes is + %% also started: snmp_agent_sup, snmp_local_db, snmp_master_agent, + %% snmp_misc_sup, snmpa_supervisor and + %% snmp_symbolic_store + %% If an manager is configured, the the following processes is + %% also started: snmpm_supervisor, snmpm_config, snmpm_server, + %% snmpm_net_if + %% + %% + {registered, [snmp_app_sup]}, + {env, []}, + %% If v3 authentication or encryption is used, 'crypto' must be started + %% before snmp. + %% The SNMP application _may_ also depend on mnesia (depends on the + %% configuration and use), and in that case mnesia must also be started, + %% before snmp. + {applications, [kernel, stdlib]}, + {mod, {snmp_app, []}}]}. diff --git a/lib/snmp/src/app/snmp.appup.src b/lib/snmp/src/app/snmp.appup.src new file mode 100644 index 0000000000..3abce3d759 --- /dev/null +++ b/lib/snmp/src/app/snmp.appup.src @@ -0,0 +1,73 @@ +%% +%% %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% +%% + +{"%VSN%", + +%% ----- U p g r a d e ------------------------------------------------------- + + [ + {"4.14", + [ + {load_module, snmpm_user, soft_purge, soft_purge, []}, + {load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]}, + {update, snmpm_server, soft, soft_purge, soft_purge, + [snmpm_user_default]} + ] + }, + {"4.13.5", + [ + {load_module, snmpm_user, soft_purge, soft_purge, []}, + {load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]}, + {load_module, snmpa_mib_data, soft_purge, soft_purge, []}, + {update, snmpa_agent, soft, soft_purge, soft_purge, []}, + {update, snmpa_net_if, soft, soft_purge, soft_purge, []}, + {update, snmpm_config, soft, soft_purge, soft_purge, []}, + {update, snmpm_server, soft, soft_purge, soft_purge, [snmpm_user_default]}, + {add_module, snmpm_net_if_filter}, + {add_module, snmpm_network_interface_filter} + ] + } + ], + +%% ------D o w n g r a d e --------------------------------------------------- + + [ + {"4.14", + [ + {load_module, snmpm_user, soft_purge, soft_purge, []}, + {load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]}, + {update, snmpm_server, soft, soft_purge, soft_purge, [snmpm_user_default]} + ] + }, + {"4.13.5", + [ + {load_module, snmpm_user, soft_purge, soft_purge, []}, + {load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]}, + {load_module, snmpa_mib_data, soft_purge, soft_purge, []}, + {update, snmpa_agent, soft, soft_purge, soft_purge, []}, + {update, snmpa_net_if, soft, soft_purge, soft_purge, []}, + {update, snmpm_config, soft, soft_purge, soft_purge, []}, + {update, snmpm_server, soft, soft_purge, soft_purge, [snmpm_user_default]}, + {remove, {snmpm_net_if_filter, soft_purge, brutal_purge}}, + {remove, {snmpm_network_interface_filter, soft_purge, brutal_purge}} + ] + } + ] +}. + diff --git a/lib/snmp/src/app/snmp.config b/lib/snmp/src/app/snmp.config new file mode 100644 index 0000000000..b66ef5d7df --- /dev/null +++ b/lib/snmp/src/app/snmp.config @@ -0,0 +1,154 @@ +%% Example snmp (node) config file +%% [{snmp, [snmp_app()] +%% snmp_app() -> {agent, agent_opts()} | {manager, manager_opts()} +%% +%% -- Agent types -- +%% agent_opts() -> [agent_opt()] +%% agent_opt() -> {agent_type, agent_type()} | +%% {agent_verbosity, verbosity()} | +%% {versions, versions()} | +%% {priority, atom()} | +%% {set_mechanism, module()} | +%% {authentication_service, module()} | +%% {multi_threaded, bool()} | +%% {db_dir, dir()} | +%% {local_db, local_db_opts()} | +%% {net_if, net_if_opts()} | +%% {mibs, [string()]} | +%% {mib_storage, mib_storage()} | +%% {mib_server, mib_server_opts()} | +%% {audit_trail_log, audit_trail_log_opts()} | +%% {error_report_mod, module()} | +%% {note_store, note_store_opts()} | +%% {symbolic_store, symbolic_store_opts()} | +%% {config, config_opts()} +%% {supervisor, supervisor_opts()} +%% agent_type() -> master | sub +%% local_db_opts() -> [local_db_opt()] +%% local_db_opt() -> {repair, repair()} | +%% {auto_save, auto_save()} | +%% {verbosity, verbosity()} +%% repair() -> true | false | force +%% auto_save() -> integer() | infinity +%% net_if_opts() -> [net_if_opt()] +%% net_if_opt() -> {module, atom()} | +%% {verbosity, verbosity()} | +%% {options, net_if_options()} +%% net_if_options() -> [net_if_option()] +%% net_if_option() -> Note that these are basically dependant on which net-if +%% module is beeing used, but the options described here +%% are the ones that snmp_net_if (the default value for +%% the module option) handles: +%% {bind_to, bool()} | +%% {recbuf, integer()} | +%% {no_reuse, bool()} +%% {req_limit, integer() | infinity} +%% mib_server_opts() -> [mib_server_opt()] +%% mib_server_opt() -> {mibentry_override, bool()} | +%% {trapentry_override, bool()} | +%% {verbosity, verbosity()} +%% mib_storage() -> ets | +%% {dets, dir()} | {dets, dir(), action()} | +%% {mnesia, [node()]} | +%% {mnesia, [node()], action()} | +%% action() -> clear | keep +%% symbolic_store_opts() -> [symbolic_store_opt()] +%% symbolic_store_opt() -> {verbosity, verbosity()} +%% supervisor_opts() -> [supervisor_opt()] +%% supervisor_opt() -> {verbosity, verbosity()} +%% config_opts() -> [config_opt()] +%% config_opt() -> {dir, dir()} | +%% {force_load, bool()} | +%% {verbosity, verbosity()} +%% +%% +%% -- Manager types -- +%% manager_options() -> [manager_option()] +%% manager_option() -> {net_if, mgr_net_if_opts()} | +%% {note_store, note_store_opts()} | +%% {config, mgr_config_opts()} | +%% {mibs, [string()]} | +%% {priority, priority()} | +%% {audit_trail_log, audit_trail_log_opts()} | +%% {versions, versions()} +%% mgr_net_if_opts() -> [mgr_net_if_opt()] +%% mgr_net_if_opt() -> {module, atom()} | +%% {verbosity, verbosity()} | +%% {options, mgr_net_if_options()} +%% mgr_net_if_options() -> [mgr_net_if_option()] +%% mgr_net_if_option() -> Note that these are basically dependant on which +%% net-if module is beeing used, but the options +%% described here are the ones of the snmpm_net_if +%% (the default value for the module option): +%% {recbuf, integer()} | +%% {bind_to, bool()} | +%% {no_reuse, bool()} +%% mgr_config_opts() -> {dir, dir()} | +%% {verbosity, verbosity()} +%% +%% -- Common types -- +%% module() -> atom() +%% verbosity() -> silence | info | log | debug | trace +%% versions() -> [version()] +%% version() -> v1 | v2 | v3 +%% audit_trail_log_opts() -> [audit_trail_log_opt()] +%% audit_trail_log_opt() -> {type, atl_type()} | +%% {dir, atl_dir()} | +%% {size, atl_size()} | +%% {repair, atl_repair()} +%% atl_type() -> read | write | read_write +%% atl_dir() -> dir() +%% atl_size() -> {max_bytes(), max_files()} +%% atl_repair() -> true | false | truncate +%% max_bytes() -> integer() +%% max_files() -> integer() +%% dir() -> string() +%% note_store_opts() -> [note_store_opt()] +%% note_store_opt() -> {verbosity, verbosity()} | +%% {timeout, integer()} +%% + +[{snmp, + [ + {agent, + [{agent_type, master}, + {agent_verbosity, trace}, + {priority, normal}, + {versions, [v1,v2,v3]}, + {multi_threaded, true}, + {config, [{verbosity, trace}, + {dir, "/ldisk/snmp/agent/conf"}, + {force_load, true}]}, + {db_dir, "/ldisk/snmp/agent/db"}, + {local_db, [{repair, true}, + {verbosity, silence}]}, + {net_if, + [{module, snmp_net_if}, + {verbosity, log}, + {options, [{recbuf, 10240}, {req_limit, 32}]}]}, + {audit_trail_log, [{type, read_write_log}, + {dir, "/ldisk/snmp/agent/log"}]}, + {mib_storage, {dets, "/ldisk/snmp/agent/mibs", clear}}, + {mib_server, [{mibentry_override,true}, + {trapentry_override,true}, + {verbosity,info}]} + ] + }, + {manager, + [{priority, normal}, + {versions, [v1,v2,v3]}, + {config, [{dir, "/ldisk/snmp/manager/conf"}, + {verbosity, trace}]}, + {server, [{verbosity, trace}]}, + {net_if, + [{module, snmpm_net_if}, + {verbosity, log}, + {options, [{recbuf, 10240}]}]}, + {audit_trail_log, [{dir, "/ldisk/snmp/manager/log"}, + {size, {10,10240}}, + {repair, true}]} + ] + } + ] + } +]. diff --git a/lib/snmp/src/app/snmp.erl b/lib/snmp/src/app/snmp.erl new file mode 100644 index 0000000000..3e0f05e604 --- /dev/null +++ b/lib/snmp/src/app/snmp.erl @@ -0,0 +1,939 @@ +%% +%% %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). + + +%%---------------------------------------------------------------------- +%% This module contains the user interface to the snmp toolkit. +%%---------------------------------------------------------------------- + +%% Application exports +-export([start/0, start/1, stop/0, + start_agent/0, start_agent/1, + start_manager/0, start_manager/1, + config/0, + + versions1/0, versions2/0, + print_versions/1, print_versions/2, + print_version_info/0, print_version_info/1, + + date_and_time/0, + universal_time_to_date_and_time/1, + local_time_to_date_and_time_dst/1, + date_and_time_to_universal_time_dst/1, + validate_date_and_time/1, validate_date_and_time/2, + date_and_time_to_string/1, date_and_time_to_string/2, + date_and_time_to_string2/1, + + str_apply/1, + + sys_up_time/1, system_start_time/1, + + passwd2localized_key/3, localize_key/3, + + read_mib/1, + + log_to_txt/5, log_to_txt/6, log_to_txt/7, + change_log_size/2, + + octet_string_to_bits/1, bits_to_octet_string/1, + + enable_trace/0, disable_trace/0, + set_trace/1, reset_trace/1, + set_trace/2, set_trace/3]). + +%% Compiler exports +-export([c/1, c/2, is_consistent/1, mib_to_hrl/1, + compile/3]). + +%% Agent exports (Dont use these, they will be removed eventually) +-export([current_request_id/0, current_community/0, current_address/0, + current_context/0, current_net_if_data/0, + + get_symbolic_store_db/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, + + get/2, + info/1, + load_mibs/2, unload_mibs/2, dump_mibs/0, dump_mibs/1, + + register_subagent/3, unregister_subagent/2, + + send_notification/3, send_notification/4, send_notification/5, + send_notification/6, + send_trap/3, send_trap/4, + + add_agent_caps/2, del_agent_caps/1, get_agent_caps/0, + + log_to_txt/2, log_to_txt/3, log_to_txt/4, + change_log_size/1 + + ]). + +%% This is for XREF +-deprecated([{c, 1, eventually}, + {c, 2, eventually}, + {compile, 3, eventually}, + {is_consistent, 1, eventually}, + {mib_to_hrl, 1, eventually}, + + {change_log_size, 1, eventually}, + {log_to_txt, 2, eventually}, + {log_to_txt, 3, eventually}, + {log_to_txt, 4, eventually}, + + {current_request_id, 0, eventually}, + {current_community, 0, eventually}, + {current_address, 0, eventually}, + {current_context, 0, eventually}, + {current_net_if_data, 0, eventually}, + + {get_symbolic_store_db, 0, eventually}, + {name_to_oid, 1, eventually}, + {name_to_oid, 2, eventually}, + {oid_to_name, 1, eventually}, + {oid_to_name, 2, eventually}, + {int_to_enum, 2, eventually}, + {int_to_enum, 3, eventually}, + {enum_to_int, 2, eventually}, + {enum_to_int, 3, eventually}, + + {get, 2, eventually}, + {info, 1, eventually}, + {load_mibs, 2, eventually}, + {unload_mibs, 2, eventually}, + {dump_mibs, 0, eventually}, + {dump_mibs, 1, eventually}, + + {register_subagent, 3, eventually}, + {unregister_subagent, 2, eventually}, + + {send_notification, 3, eventually}, + {send_notification, 4, eventually}, + {send_notification, 5, eventually}, + {send_notification, 6, eventually}, + {send_trap, 3, eventually}, + {send_trap, 4, eventually}, + + {add_agent_caps, 2, eventually}, + {del_agent_caps, 1, eventually}, + {get_agent_caps, 0, eventually}]). + + +-define(APPLICATION, snmp). + + +%%----------------------------------------------------------------- +%% Application +%%----------------------------------------------------------------- + +start() -> + application:start(?APPLICATION). + +stop() -> + application:stop(?APPLICATION). + +start(p) -> + start(permanent); +start(tr) -> + start(transient); +start(te) -> + start(temporary); +start(Type) -> + application:start(?APPLICATION, Type). + + +start_agent() -> + snmp_app:start_agent(). + +start_agent(Type) -> + snmp_app:start_agent(Type). + +start_manager() -> + snmp_app:start_manager(). + +start_manager(Type) -> + snmp_app:start_manager(Type). + + +config() -> snmp_config:config(). + + +%%----------------------------------------------------------------- + +enable_trace() -> + HandleSpec = {fun handle_trace_event/2, dummy}, + dbg:tracer(process, HandleSpec). + +disable_trace() -> + dbg:stop(). + +set_trace(Module) when is_atom(Module) -> + set_trace([Module]); +set_trace(Modules) when is_list(Modules) -> + Opts = [], % Use default values for all options + set_trace(Modules, Opts). + +reset_trace(Module) when is_atom(Module) -> + set_trace(Module, disable); +reset_trace(Modules) when is_list(Modules) -> + set_trace(Modules, disable). + +set_trace(Module, disable) when is_atom(Module) -> + dbg:ctp(Module); +set_trace(Module, Opts) when is_atom(Module) andalso is_list(Opts) -> + (catch set_trace(all, Module, Opts)); +set_trace(Modules, Opts) when is_list(Modules) -> + (catch set_trace(all, Modules, Opts)). + +set_trace(Item, Module, Opts) when is_atom(Module) -> + set_trace(Item, [{Module, []}], Opts); +set_trace(_Item, Modules, disable) when is_list(Modules) -> + DisableTrace = + fun(Module) when is_atom(Module) -> + dbg:ctp(Module); + (_) -> + ok + end, + lists:foreach(DisableTrace, Modules); +set_trace(Item, Modules, Opts) when is_list(Modules) -> + Mods = parse_modules(Modules, Opts), + SetTrace = + fun({Module, ModOpts}) -> + set_module_trace(Module, ModOpts) + end, + lists:foreach(SetTrace, Mods), + Flags = + case lists:keysearch(timestamp, 1, Opts) of + {value, {timestamp, false}} -> + [call]; + _ -> + [call, timestamp] + end, + case dbg:p(Item, Flags) of + {ok, _} -> + ok; + Error -> + Error + end. + +set_module_trace(Module, disable) -> + dbg:ctp(Module); +set_module_trace(Module, Opts) -> + ReturnTrace = + case lists:keysearch(return_trace, 1, Opts) of + {value, {return_trace, false}} -> + []; + _ -> + %% Default is allways to include return values + [{return_trace}] + end, + TraceRes = + case lists:keysearch(scope, 1, Opts) of + {value, {scope, all_functions}} -> + dbg:tpl(Module, [{'_', [], ReturnTrace}]); + {value, {scope, exported_functions}} -> + dbg:tp(Module, [{'_', [], ReturnTrace}]); + {value, {scope, Func}} when is_atom(Func) -> + dbg:tpl(Module, Func, [{'_', [], ReturnTrace}]); + {value, {scope, {Func, Arity}}} when is_atom(Func) andalso + is_integer(Arity) -> + dbg:tpl(Module, Func, Arity, [{'_', [], ReturnTrace}]); + false -> + %% Default scope is exported functions + dbg:tp(Module, [{'_', [], ReturnTrace}]) + end, + case TraceRes of + {error, Reason} -> + throw({error, {failed_enabling_trace, Module, Opts, Reason}}); + _ -> + ok + end. + + +parse_modules(Modules, Opts) -> + parse_modules(Modules, Opts, []). + +parse_modules([], _Opts, Acc) -> + lists:reverse(Acc); + +parse_modules([Module|Modules], Opts, Acc) + when is_atom(Module) andalso is_list(Opts) -> + parse_modules(Modules, Opts, [{Module, Opts}|Acc]); + +parse_modules([{Module, ModOpts}|Modules], Opts, Acc) + when is_atom(Module) andalso is_list(ModOpts) andalso is_list(Opts) -> + NewModOpts = update_trace_options(Opts, ModOpts), + parse_modules(Modules, Opts, [{Module, NewModOpts}|Acc]); + +parse_modules([_|Modules], Opts, Acc) -> + parse_modules(Modules, Opts, Acc). + + +update_trace_options([], Opts) -> + Opts; +update_trace_options([{Key, _} = Opt|Opts], ModOpts) -> + case lists:keysearch(Key, 1, ModOpts) of + {value, _} -> + update_trace_options(Opts, ModOpts); + _ -> + update_trace_options(Opts, [Opt|ModOpts]) + end; +update_trace_options([_|Opts], ModOpts) -> + update_trace_options(Opts, ModOpts). + + +handle_trace_event({trace, Who, call, Event}, Data) -> + io:format("*** call trace event *** " + "~n Who: ~p" + "~n Event: ~p" + "~n", [Who, Event]), + Data; +handle_trace_event({trace, Who, return_from, Func, Value}, Data) -> + io:format("*** return trace event *** " + "~n Who: ~p" + "~n Function: ~p" + "~n Value: ~p" + "~n", [Who, Func, Value]), + Data; +handle_trace_event({trace_ts, Who, call, {Mod, Func, Args}, Ts}, Data) + when is_atom(Mod) andalso is_atom(Func) andalso is_list(Args) -> + io:format("*** call trace event ~s *** " + "~n Who: ~p" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p" + "~n", [format_timestamp(Ts), Who, Mod, Func, Args]), + Data; +handle_trace_event({trace_ts, Who, call, Event, Ts}, Data) -> + io:format("*** call trace event ~s *** " + "~n Who: ~p" + "~n Event: ~p" + "~n", [format_timestamp(Ts), Who, Event]), + Data; +handle_trace_event({trace_ts, Who, return_from, {Mod, Func, Arity}, Value, Ts}, + Data) + when is_atom(Mod) andalso is_atom(Func) andalso is_integer(Arity) -> + io:format("*** return trace event ~s *** " + "~n Who: ~p" + "~n Mod: ~p" + "~n Func: ~p" + "~n Arity: ~p" + "~n Value: ~p" + "~n", [format_timestamp(Ts), Who, Mod, Func, Arity, Value]), + Data; +handle_trace_event({trace_ts, Who, return_from, Func, Value, Ts}, Data) -> + io:format("*** return trace event ~s *** " + "~n Who: ~p" + "~n Function: ~p" + "~n Value: ~p" + "~n", [format_timestamp(Ts), Who, Func, Value]), + Data; +handle_trace_event(TraceEvent, Data) -> + io:format("*** trace event *** " + "~n TraceEvent: ~p" + "~n", [TraceEvent]), + Data. + +format_timestamp({_N1, _N2, N3} = Now) -> + {Date, Time} = calendar:now_to_datetime(Now), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", + [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), + lists:flatten(FormatDate). + + +%%----------------------------------------------------------------- +%% {ok, Vs} = snmp:versions1(), snmp:print_versions(Vs). + +print_version_info() -> + {ok, Vs} = versions1(), + print_versions(Vs). + +print_version_info(Prefix) -> + {ok, Vs} = versions1(), + print_versions(Prefix, Vs). + +print_versions(Versions) -> + print_versions("", Versions). + +print_versions(Prefix, Versions) + when is_list(Prefix) andalso is_list(Versions) -> + do_print_versions(Prefix, Versions); +print_versions(Prefix, Versions) + when (is_integer(Prefix) andalso (Prefix >= 0)) andalso is_list(Versions) -> + do_print_versions(lists:duplicate(Prefix, $ ), Versions); +print_versions(Prefix, BadVersions) + when is_list(Prefix) orelse (is_integer(Prefix) andalso (Prefix >= 0)) -> + {error, {bad_versions, BadVersions}}; +print_versions(Prefix, BadVersions) + when is_list(BadVersions) -> + {error, {bad_prefix, Prefix}}; +print_versions(Prefix, BadVersions) -> + {error, {bad_args, Prefix, BadVersions}}. + +do_print_versions(Prefix, Versions) -> + print_sys_info(Prefix, Versions), + print_os_info(Prefix, Versions), + print_mods_info(Prefix, Versions). + +print_sys_info(Prefix, Versions) -> + case key1search(sys_info, Versions) of + {value, SysInfo} when is_list(SysInfo) -> + {value, Arch} = key1search(arch, SysInfo, "Not found"), + {value, Ver} = key1search(ver, SysInfo, "Not found"), + io:format("~sSystem info: " + "~n~s Arch: ~s" + "~n~s Ver: ~s" + "~n", [Prefix, + Prefix, Arch, + Prefix, Ver]), + ok; + _ -> + io:format("System info: Not found~n", []), + not_found + end. + +print_os_info(Prefix, Versions) -> + case key1search(os_info, Versions) of + {value, OsInfo} when is_list(OsInfo) -> + Fam = + case key1search(fam, OsInfo, "Not found") of + {value, F} when is_atom(F) -> + atom_to_list(F); + {value, LF} when is_list(LF) -> + LF; + {value, XF} -> + lists:flatten(io_lib:format("~p", [XF])) + end, + Name = + case key1search(name, OsInfo) of + {value, N} when is_atom(N) -> + "[" ++ atom_to_list(N) ++ "]"; + {value, LN} when is_list(LN) -> + "[" ++ LN ++ "]"; + not_found -> + "" + end, + Ver = + case key1search(ver, OsInfo, "Not found") of + {value, T} when is_tuple(T) -> + tversion(T); + {value, LV} when is_list(LV) -> + LV; + {value, XV} -> + lists:flatten(io_lib:format("~p", [XV])) + end, + io:format("~sOS info: " + "~n~s Family: ~s ~s" + "~n~s Ver: ~s" + "~n", [Prefix, + Prefix, Fam, Name, + Prefix, Ver]), + ok; + _ -> + io:format("~sOS info: Not found~n", [Prefix]), + not_found + end. + +tversion(T) -> + L = tuple_to_list(T), + lversion(L). + +lversion([]) -> + ""; +lversion([A]) -> + integer_to_list(A); +lversion([A|R]) -> + integer_to_list(A) ++ "." ++ lversion(R). + +print_mods_info(Prefix, Versions) -> + case key1search(mod_info, Versions) of + {value, ModsInfo} when is_list(ModsInfo) -> + io:format("~sModule info: ~n", [Prefix]), + F = fun(MI) -> print_mod_info(Prefix, MI) end, + lists:foreach(F, ModsInfo); + _ -> + io:format("~sModule info: Not found~n", [Prefix]), + not_found + end. + +print_mod_info(Prefix, {Module, Info}) -> + Vsn = + case key1search(vsn, Info) of + {value, I} when is_integer(I) -> + integer_to_list(I); + _ -> + "Not found" + end, + AppVsn = + case key1search(app_vsn, Info) of + {value, S1} when is_list(S1) -> + S1; + _ -> + "Not found" + end, + CompVer = + case key1search(compiler_version, Info) of + {value, S2} when is_list(S2) -> + S2; + _ -> + "Not found" + end, + CompDate = + case key1search(compile_time, Info) of + {value, {Year, Month, Day, Hour, Min, Sec}} -> + lists:flatten( + io_lib:format("~w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", + [Year, Month, Day, Hour, Min, Sec])); + _ -> + "Not found" + end, + io:format("~s ~w:~n" + "~s Vsn: ~s~n" + "~s App vsn: ~s~n" + "~s Compiler ver: ~s~n" + "~s Compile time: ~s~n", + [Prefix, Module, + Prefix, Vsn, + Prefix, AppVsn, + Prefix, CompVer, + Prefix, CompDate]), + ok. + +key1search(Key, Vals) -> + case lists:keysearch(Key, 1, Vals) of + {value, {Key, Val}} -> + {value, Val}; + false -> + not_found + end. + +key1search(Key, Vals, Def) -> + case key1search(Key, Vals) of + not_found -> + {value, Def}; + Value -> + Value + end. + + +%%----------------------------------------------------------------- + +versions1() -> + case ms1() of + {ok, Mods} -> + {ok, version_info(Mods)}; + Error -> + Error + end. + +versions2() -> + case ms2() of + {ok, Mods} -> + {ok, version_info(Mods)}; + Error -> + Error + end. + +version_info(Mods) -> + SysInfo = sys_info(), + OsInfo = os_info(), + ModInfo = [mod_version_info(Mod) || Mod <- Mods], + [{sys_info, SysInfo}, {os_info, OsInfo}, {mod_info, ModInfo}]. + +mod_version_info(Mod) -> + Info = Mod:module_info(), + {value, {attributes, Attr}} = lists:keysearch(attributes, 1, Info), + {value, {vsn, [Vsn]}} = lists:keysearch(vsn, 1, Attr), + {value, {app_vsn, AppVsn}} = lists:keysearch(app_vsn, 1, Attr), + {value, {compile, Comp}} = lists:keysearch(compile, 1, Info), + {value, {version, Ver}} = lists:keysearch(version, 1, Comp), + {value, {time, Time}} = lists:keysearch(time, 1, Comp), + {Mod, [{vsn, Vsn}, + {app_vsn, AppVsn}, + {compiler_version, Ver}, + {compile_time, Time}]}. + +sys_info() -> + SysArch = string:strip(erlang:system_info(system_architecture),right,$\n), + SysVer = string:strip(erlang:system_info(system_version),right,$\n), + [{arch, SysArch}, {ver, SysVer}]. + +os_info() -> + V = os:version(), + case os:type() of + {OsFam, OsName} -> + [{fam, OsFam}, {name, OsName}, {ver, V}]; + OsFam -> + [{fam, OsFam}, {ver, V}] + end. + +ms1() -> + App = ?APPLICATION, + LibDir = code:lib_dir(App), + File = filename:join([LibDir, "ebin", atom_to_list(App) ++ ".app"]), + case file:consult(File) of + {ok, [{application, App, AppFile}]} -> + case lists:keysearch(modules, 1, AppFile) of + {value, {modules, Mods}} -> + {ok, Mods}; + _ -> + {error, {invalid_format, modules}} + end; + Error -> + {error, {invalid_format, Error}} + end. + +ms2() -> + application:get_key(?APPLICATION, modules). + + +%%----------------------------------------------------------------- +%% Returns: current time as a DateAndTime type (defined in rfc1903) +%%----------------------------------------------------------------- +date_and_time() -> + UTC = calendar:universal_time(), + Local = calendar:universal_time_to_local_time(UTC), + date_and_time(Local, UTC). + +date_and_time(Local, UTC) -> + DiffSecs = calendar:datetime_to_gregorian_seconds(Local) - + calendar:datetime_to_gregorian_seconds(UTC), + short_time(Local) ++ diff(DiffSecs). + +short_time({{Y,M,D},{H,Mi,S}}) -> + [y1(Y), y2(Y), M, D, H, Mi, S, 0]. + +%% This function will only be called if there has been some +%% validation error, and as it is strict, it allways returns +%% false. +strict_validation(_What, _Data) -> + false. + +kiribati_validation(diff, Diff) -> + check_kiribati_diff(Diff); +kiribati_validation(_What, _Data) -> + false. + +check_kiribati_diff([$+, H, M]) + when ((0 =< H) andalso (H < 14) andalso (0 =< M) andalso (M < 60)) orelse + ((H =:= 14) andalso (M =:= 0)) -> + true; +check_kiribati_diff([$-, H, M]) + when ((0 =< H) andalso (H < 14) andalso (0 =< M) andalso (M < 60)) orelse + ((H =:= 14) andalso (M =:= 0)) -> + true; +check_kiribati_diff(_) -> + false. + + +date_and_time_to_string2(DAT) -> + Validate = fun(What, Data) -> kiribati_validation(What, Data) end, + date_and_time_to_string(DAT, Validate). + +date_and_time_to_string(DAT) -> + Validate = fun(What, Data) -> strict_validation(What, Data) end, + date_and_time_to_string(DAT, Validate). +date_and_time_to_string(DAT, Validate) when is_function(Validate) -> + case validate_date_and_time(DAT, Validate) of + true -> + dat2str(DAT); + false -> + exit({badarg, {?MODULE, date_and_time_to_string, [DAT]}}) + end. + +dat2str([Y1,Y2, Mo, D, H, M, S, Ds | Diff]) -> + lists:flatten(io_lib:format("~w-~w-~w,~w:~w:~w.~w", + [y(Y1,Y2),Mo,D,H,M,S,Ds]) ++ + case Diff of + [Sign,Hd,Md] -> + io_lib:format(",~c~w:~w", + [Sign,Hd,Md]); + _ -> [] + end). + + +y1(Y) -> (Y bsr 8) band 255. +y2(Y) -> Y band 255. + +y(Y1, Y2) -> 256 * Y1 + Y2. + +diff(Secs) -> + case calendar:seconds_to_daystime(Secs) of + {0, {H, M,_}} -> + [$+, H, M]; + {-1, _} -> + {0, {H, M, _}} = calendar:seconds_to_daystime(-Secs), + [$-, H, M] + end. + +universal_time_to_date_and_time(UTC) -> + short_time(UTC) ++ [$+, 0, 0]. + +local_time_to_date_and_time_dst(Local) -> + case calendar:local_time_to_universal_time_dst(Local) of + [] -> + []; + [UTC] -> + [date_and_time(Local, UTC)]; + [UTC1, UTC2] -> + [date_and_time(Local, UTC1), date_and_time(Local, UTC2)] + end. + +date_and_time_to_universal_time_dst([Y1, Y2, Mo, D, H, M, S, _Ds]) -> + %% Local time specified, convert to UTC + Local = {{y(Y1,Y2), Mo, D}, {H, M, S}}, + calendar:local_time_to_universal_time_dst(Local); +date_and_time_to_universal_time_dst([Y1, Y2, Mo, D, H, M, S, _Ds, Sign, Hd, Md]) -> + %% Time specified as local time + diff from UTC. Conv to UTC. + Local = {{y(Y1,Y2), Mo, D}, {H, M, S}}, + LocalSecs = calendar:datetime_to_gregorian_seconds(Local), + Diff = (Hd*60 + Md)*60, + UTCSecs = if Sign == $+ -> LocalSecs - Diff; + Sign == $- -> LocalSecs + Diff + end, + [calendar:gregorian_seconds_to_datetime(UTCSecs)]. + + +validate_date_and_time(DateAndTime) -> + Validate = fun(What, Data) -> strict_validation(What, Data) end, + validate_date_and_time(DateAndTime, Validate). + +validate_date_and_time(DateAndTime, Validate) when is_function(Validate) -> + do_validate_date_and_time(DateAndTime, Validate). + +do_validate_date_and_time([Y1,Y2, Mo, D, H, M, S, Ds | Diff], Validate) + when ((0 =< Y1) andalso (0 =< Y2)) andalso + ((0 < Mo) andalso (Mo < 13)) andalso + ((0 < D) andalso (D < 32) andalso (0 =< H)) andalso + (H < 24) andalso + ((0 =< M) andalso (M < 60)) andalso + ((0 =< S) andalso (S < 61)) andalso + ((0 =< Ds) andalso (Ds < 10)) -> + case check_diff(Diff, Validate) of + true -> + Year = y(Y1,Y2), + case calendar:valid_date(Year, Mo, D) of + true -> + true; + _ -> + Validate(valid_date, {Year, Mo, D}) + end; + false -> + false + end; +do_validate_date_and_time([Y1,Y2, Mo, D, H, M, S, Ds | Diff], Validate) -> + Valid = + Validate(year, {Y1, Y2}) andalso + Validate(month, Mo) andalso + Validate(day, D) andalso + Validate(hour, H) andalso + Validate(minute, M) andalso + Validate(seconds, S) andalso + Validate(deci_seconds, Ds), + if + Valid =:= true -> + case check_diff(Diff, Validate) of + true -> + Year = y(Y1,Y2), + case calendar:valid_date(Year, Mo, D) of + true -> + true; + _ -> + Validate(valid_date, {Year, Mo, D}) + end; + false -> + false + end; + true -> + false + end; +do_validate_date_and_time(_, _) -> + false. + +%% OTP-4206 (now according to RFC-2579) +check_diff([], _) -> + true; +check_diff([$+, H, M], _) + when (0 =< H) andalso (H < 14) andalso (0 =< M) andalso (M < 60) -> + true; +check_diff([$-, H, M], _) + when (0 =< H) andalso (H < 14) andalso (0 =< M) andalso (M < 60) -> + true; +check_diff(Diff, Validate) -> + Validate(diff, Diff). + + +%%----------------------------------------------------------------- +%% System start- and up-time +%%----------------------------------------------------------------- + +system_start_time(agent) -> + snmpa:system_start_time(); +system_start_time(manager) -> + snmpm:system_start_time(). + +sys_up_time(agent) -> + snmpa:sys_up_time(); +sys_up_time(manager) -> + snmpm:sys_up_time(). + + + +%%----------------------------------------------------------------- +%% Utility functions for OCTET-STRING / BITS conversion. +%%----------------------------------------------------------------- + +octet_string_to_bits(S) -> + snmp_pdus:octet_str_to_bits(S). + +bits_to_octet_string(B) -> + snmp_pdus:bits_to_str(B). + + +%%%----------------------------------------------------------------- +%%% 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). + + +%%%----------------------------------------------------------------- +%%% Read a mib +%%%----------------------------------------------------------------- + +read_mib(FileName) -> + snmp_misc:read_mib(FileName). + + +%%%----------------------------------------------------------------- +%%% Audit Trail Log functions +%%%----------------------------------------------------------------- + +log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile) -> + snmp_log:log_to_txt(LogName, LogFile, LogDir, Mibs, OutFile). +log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start) -> + snmp_log:log_to_txt(LogName, LogFile, LogDir, Mibs, OutFile, Start). +log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> + snmp_log:log_to_txt(LogName, LogFile, LogDir, Mibs, OutFile, Start, Stop). + + +change_log_size(LogName, NewSize) -> + snmp_log:change_size(LogName, NewSize). + + +%%%----------------------------------------------------------------- +%%% Misc +%%%----------------------------------------------------------------- + +%% Usage: erl -s snmp str_apply '{Mod,Func,ArgList}' +str_apply([Atom]) -> + Str = atom_to_list(Atom), + {Mod,Func,Args} = to_erlang_term(Str), + apply(Mod,Func,Args). + +to_erlang_term(String) -> + {ok, Tokens, _} = erl_scan:string(lists:append([String, ". "])), + {ok,Term} = erl_parse:parse_term(Tokens), + Term. + + +%%%----------------------------------------------------------------- +%%% BACKWARD COMPATIBILLITY CRAP +%%%----------------------------------------------------------------- + +c(File) -> snmpc:compile(File). +c(File, Options) -> snmpc:compile(File, Options). + +is_consistent(Filenames) -> + snmpc:is_consistent(Filenames). + +mib_to_hrl(MibName) -> + snmpc:mib_to_hrl(MibName). + +compile(Input, Output, Options) -> + snmpc:compile(Input, Output, Options). + +get_symbolic_store_db() -> snmpa:get_symbolic_store_db(). + +name_to_oid(Name) -> snmpa:name_to_oid(Name). +name_to_oid(Db, Name) -> snmpa:name_to_oid(Db, Name). +oid_to_name(OID) -> snmpa:oid_to_name(OID). +oid_to_name(Db, OID) -> snmpa:oid_to_name(Db, OID). +enum_to_int(Name, Enum) -> snmpa:enum_to_int(Name, Enum). +enum_to_int(Db, Name, Enum) -> snmpa:enum_to_int(Db, Name, Enum). +int_to_enum(Name, Int) -> snmpa:int_to_enum(Name, Int). +int_to_enum(Db, Name, Int) -> snmpa:int_to_enum(Db, Name, Int). + +current_request_id() -> snmpa:current_request_id(). +current_context() -> snmpa:current_context(). +current_community() -> snmpa:current_community(). +current_address() -> snmpa:current_address(). +current_net_if_data() -> snmpa:current_net_if_data(). + +get(Agent, Vars) -> snmpa:get(Agent, Vars). +info(Agent) -> snmpa:info(Agent). +dump_mibs() -> snmpa:dump_mibs(). +dump_mibs(File) -> snmpa:dump_mibs(File). +load_mibs(Agent, Mibs) -> snmpa:load_mibs(Agent, Mibs). +unload_mibs(Agent, Mibs) -> snmpa:unload_mibs(Agent, Mibs). +send_notification(Agent, Notification, Recv) -> + snmpa:send_notification(Agent, Notification, Recv). +send_notification(Agent, Notification, Recv, Varbinds) -> + snmpa:send_notification(Agent, Notification, Recv, Varbinds). +send_notification(Agent, Notification, Recv, NotifyName, Varbinds) -> + snmpa:send_notification(Agent, Notification, Recv, NotifyName, Varbinds). +send_notification(Agent, Notification, Recv, NotifyName, + ContextName, Varbinds) -> + snmpa:send_notification(Agent, Notification, Recv, NotifyName, + ContextName, Varbinds). +send_trap(Agent, Trap, Community) -> + snmpa:send_trap(Agent, Trap, Community). +send_trap(Agent, Trap, Community, Varbinds) -> + snmpa:send_trap(Agent, Trap, Community, Varbinds). +register_subagent(Agent, SubTree, SubAgent) -> + snmpa:register_subagent(Agent, SubTree, SubAgent). +unregister_subagent(Agent, SubOidOrPid) -> + snmpa:unregister_subagent(Agent, SubOidOrPid). + +add_agent_caps(Oid, Descr) -> snmpa:add_agent_caps(Oid, Descr). +del_agent_caps(Index) -> snmpa:del_agent_caps(Index). +get_agent_caps() -> snmpa:get_agent_caps(). + +log_to_txt(LogDir, Mibs) -> + snmpa:log_to_txt(LogDir, Mibs). +log_to_txt(LogDir, Mibs, OutFile) -> + snmpa:log_to_txt(LogDir, Mibs, OutFile). +log_to_txt(LogDir, Mibs, OutFile, LogName) -> + snmpa:log_to_txt(LogDir, Mibs, OutFile, LogName). +change_log_size(NewSize) -> + snmpa:change_log_size(NewSize). + + + diff --git a/lib/snmp/src/app/snmp_app.erl b/lib/snmp/src/app/snmp_app.erl new file mode 100644 index 0000000000..deb42cc373 --- /dev/null +++ b/lib/snmp/src/app/snmp_app.erl @@ -0,0 +1,175 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-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_app). + +-behaviour(application). + +-include("snmp_debug.hrl"). + + +%%%----------------------------------------------------------------- +%%% This module implements the SNMP application. +%%%----------------------------------------------------------------- +-export([start/2, stop/0, stop/1, config_change/3]). +-export([start_agent/0, start_agent/1, start_agent/2]). +-export([start_manager/0, start_manager/1, start_manager/2]). + +start(Type, []) -> + ?d("start -> entry with" + "~n Type. ~p", [Type]), + %% This is the new snmp application config format + %% First start the (new) central supervisor, + {ok, Pid} = snmp_app_sup:start_link(), + Entities = entities(), + ok = start_entities(Type, Entities), + {ok, Pid}. + +entities() -> + entities([agent, manager], []). + +entities([], []) -> + ?d("entities -> entry when no entities", []), + + %% Could be old style snmp (agent) application config format + %% but could also be a skeleton start, which means that neither + %% agent nor manager config has been specified + + case get_env() of + [] -> + %% Skeleton start + ?d("entities -> skeleton start", []), + []; + OldConf when is_list(OldConf) -> + ?d("entities -> old style config: ~n~p", [OldConf]), + %% Old style snmp (agent) application config format + Conf = snmpa_app:convert_config(OldConf), + ?d("entities -> converted config: ~n~p", [Conf]), + [{agent, Conf}] + end; +entities([], E) -> + ?d("entities -> done", []), + lists:reverse(E); +entities([ET|ETs], E) -> + ?d("entities -> entry with" + "~n ET: ~p", [ET]), + case application:get_env(snmp, ET) of + {ok, Conf} -> + entities(ETs, [{ET, Conf}|E]); + _ -> + entities(ETs, E) + end. + +start_entities(_Type, []) -> + ok; +start_entities(Type, [{agent, Opts}|Entities]) -> + case start_agent(Type, Opts) of + ok -> + start_entities(Type, Entities); + Error -> + Error + end; +start_entities(Type, [{manager, Opts}|Entities]) -> + case start_manager(Type, Opts) of + ok -> + start_entities(Type, Entities); + Error -> + Error + end; +start_entities(Type, [BadEntity|Entities]) -> + error_msg("Bad snmp configuration: ~n: ~p", [BadEntity]), + start_entities(Type, Entities). + + +start_agent() -> + start_agent(normal). + +start_agent(Type) when is_atom(Type) -> + case application:get_env(snmp, agent) of + {ok, Opts} -> + start_agent(Type, Opts); + _ -> + {error, missing_config} + end; +start_agent(Opts) when is_list(Opts) -> + start_agent(normal, Opts); +start_agent(BadArg) -> + {error, {bad_arg, BadArg}}. + +start_agent(Type, Opts) -> + ?d("start_agent -> entry", []), + case snmp_app_sup:start_agent(Type, Opts) of + {ok, _} -> + ok; + Error -> + Error + end. + +start_manager() -> + start_manager(normal). + +start_manager(Type) when is_atom(Type) -> + case application:get_env(snmp, manager) of + {ok, Opts} -> + start_manager(Type, Opts); + _ -> + {error, missing_config} + end; +start_manager(Opts) when is_list(Opts) -> + start_manager(normal, Opts); +start_manager(BadArg) -> + {error, {bad_arg, BadArg}}. + +start_manager(Type, Opts) -> + ?d("start manager -> entry", []), + case snmp_app_sup:start_manager(Type, Opts) of + {ok, _} -> + ok; + Error -> + Error + end. + + +stop(_) -> + ok. + +stop() -> + snmp_app_sup:stop(). + + +get_env() -> + Env = application:get_all_env(snmp), + DeleteElem = [included_applications], + F = fun({Key, _}) -> lists:member(Key, DeleteElem) end, + lists:dropwhile(F, Env). + + +%%----------------------------------------------------------------- +%% The presence of this function means that we will accept changes +%% in the configuration parameters. However, we won't react upon +%% those changes until the app is restarted. So we just return +%% ok. +%%----------------------------------------------------------------- +config_change(_Changed, _New, _Removed) -> + ok. + +%% --------------------------------------------------------------------- + +error_msg(F, A) -> + error_logger:error_msg("~w: " ++ F ++ "~n", [?MODULE|A]). + diff --git a/lib/snmp/src/app/snmp_app_sup.erl b/lib/snmp/src/app/snmp_app_sup.erl new file mode 100644 index 0000000000..d0c190b51f --- /dev/null +++ b/lib/snmp/src/app/snmp_app_sup.erl @@ -0,0 +1,119 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-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_app_sup). + +-include("snmp_debug.hrl"). + +-behaviour(supervisor). + + +%% External exports +-export([start_link/0, start_agent/2, start_manager/2]). + +%% Internal exports +-export([stop/0]). + + +%% supervisor callbacks +-export([init/1]). + +-define(SERVER, ?MODULE). + + +%%%------------------------------------------------------------------- +%%% API +%%%------------------------------------------------------------------- +start_link() -> + ?d("start_link -> entry",[]), + SupName = {local, ?MODULE}, + supervisor:start_link(SupName, ?MODULE, []). + +stop() -> + ?d("stop -> entry", []), + case whereis(?SERVER) of + Pid when is_pid(Pid) -> + ?d("stop -> Pid: ~p", [Pid]), + exit(Pid, shutdown), + ?d("stop -> stopped", []), + ok; + _ -> + ?d("stop -> not running", []), + not_running + end. + + +start_agent(Type, Opts) -> + ?d("start_agent -> entry with" + "~n Type: ~p" + "~n Opts: ~p", [Type, Opts]), + Restart = get_restart(Opts, permanent), + start_sup_child(snmpa_supervisor, Restart, [Type, Opts]). + + +start_manager(Type, Opts) -> + ?d("start_manager -> entry with" + "~n Type: ~p" + "~n Opts: ~p", [Type, Opts]), + Restart = get_restart(Opts, transient), + start_sup_child(snmpm_supervisor, Restart, [Type, Opts]). + + +%%%------------------------------------------------------------------- +%%% Callback functions from supervisor +%%%------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, {SupFlags, [ChildSpec]}} | +%% ignore | +%% {error, Reason} +%%-------------------------------------------------------------------- +init(_Args) -> + ?d("init -> entry", []), + Flags = {one_for_one, 0, 1}, + Sups = [], + {ok, {Flags, Sups}}. + + +%%%------------------------------------------------------------------- +%%% Internal functions +%%%------------------------------------------------------------------- + +get_restart(Opts, Def) -> + get_opt(Opts, restart_type, Def). + +get_opt(Opts, Key, Def) -> + snmp_misc:get_option(Key, Opts, Def). + +start_sup_child(Mod, Type, Args) -> + Spec = sup_spec(Mod, Type, Args), + supervisor:start_child(?MODULE, Spec). + +sup_spec(Name, Type, Args) -> + {Name, + {Name, start_link, Args}, + Type, 2000, supervisor, [Name, supervisor]}. + + +% i(F) -> +% i(F, []). + +% i(F, A) -> +% io:format("~p: " ++ F ++ "~n", [?MODULE|A]). + diff --git a/lib/snmp/src/app/snmp_internal.hrl b/lib/snmp/src/app/snmp_internal.hrl new file mode 100644 index 0000000000..5ff715e0b7 --- /dev/null +++ b/lib/snmp/src/app/snmp_internal.hrl @@ -0,0 +1,40 @@ +%% +%% %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(snmp_internal). +-define(snmp_internal, true). + +-ifndef(APPLICATION). +-define(APPLICATION, snmp). +-endif. + +-define(snmp_info(C, F, A), ?snmp_msg(info_msg, C, F, A)). +-define(snmp_warning(C, F, A), ?snmp_msg(warning_msg, C, F, A)). +-define(snmp_error(C, F, A), ?snmp_msg(error_msg, C, F, A)). + +-define(snmp_msg(Func, Component, Format, Args), +%% io:format("[ ~w : ~s : ~w : ~p ] ~n" ++ Format ++ "~n", +%% [?APPLICATION, Component, ?MODULE, self() | Args]), + (catch error_logger:Func("[ ~w : ~s : ~w : ~p ] ~n" ++ Format ++ "~n", + [?APPLICATION, Component, ?MODULE, self() | Args]))). + +-endif. % -ifdef(snmp_internal). + + + diff --git a/lib/snmp/src/compile/Makefile b/lib/snmp/src/compile/Makefile new file mode 100644 index 0000000000..4be60e1835 --- /dev/null +++ b/lib/snmp/src/compile/Makefile @@ -0,0 +1,125 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %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% + +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) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +GENERATED_PARSER = $(PARSER_MODULE:%=%.erl) + +PARSER_TARGET = $(PARSER_MODULE).$(EMULATOR) + + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ifeq ($(WARN_UNUSED_VARS),true) +ERL_COMPILE_FLAGS += +warn_unused_vars +endif + +ERL_COMPILE_FLAGS += -I../../include \ + -Dversion=\"$(VSN)$(PRE_VSN)\" \ + +'{parse_transform,sys_pre_attributes}' \ + +'{attribute,insert,app_vsn,$(APP_VSN)}' \ + -I$(ERL_TOP)/lib/stdlib + +YRL_FLAGS = -o . + + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug: + @${MAKE} TYPE=debug opt + +opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) $(GENERATED_PARSER) + rm -f core *~ + +docs: + +info: + @echo "PARSER_SRC: $(PARSER_SRC)" + @echo "PARSER_MODULE: $(PARSER_MODULE)" + @echo "" + @echo "GENERATED_PARSER: $(GENERATED_PARSER)" + @echo "PARSER_TARGET: $(PARSER_TARGET)" + @echo "" + @echo "MODULES: $(MODULES)" + @echo "" + @echo "TARGET_FILES: $(TARGET_FILES)" + @echo "" + @echo "EBIN: $(EBIN)" + @echo "" + @echo "" + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- + +parser: $(PARSER_TARGET) + +$(GENERATED_PARSER): $(PARSER_SRC) + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/compiler + $(INSTALL_DATA) $(PARSER_SRC) $(ERL_FILES) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src/compiler + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: + +include depend.mk + diff --git a/lib/snmp/src/compile/depend.mk b/lib/snmp/src/compile/depend.mk new file mode 100644 index 0000000000..75af1bf293 --- /dev/null +++ b/lib/snmp/src/compile/depend.mk @@ -0,0 +1,46 @@ +#-*-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% + +snmpc_mib_gram.erl: snmpc_mib_gram.yrl + +$(EBIN)/snmpc.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc.erl \ + snmpc.hrl + +$(EBIN)/snmpc_lib.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc_lib.erl \ + snmpc.hrl + +$(EBIN)/snmpc_tok.$(EMULATOR): \ + snmpc_tok.erl + +$(EBIN)/snmpc_misc.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc_misc.erl + +$(EBIN)/snmpc_mib_to_hrl.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc_mib_to_hrl.erl + +$(EBIN)/snmpc_mib_gram.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + snmpc_mib_gram.erl + diff --git a/lib/snmp/src/compile/modules.mk b/lib/snmp/src/compile/modules.mk new file mode 100644 index 0000000000..6365b0e694 --- /dev/null +++ b/lib/snmp/src/compile/modules.mk @@ -0,0 +1,36 @@ +#-*-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% + +PARSER_SRC = snmpc_mib_gram.yrl + +PARSER_MODULE = $(PARSER_SRC:%.yrl=%) + +MODULES = \ + $(PARSER_MODULE) \ + snmpc \ + snmpc_lib \ + snmpc_mib_to_hrl \ + snmpc_misc \ + snmpc_tok + + +INTERNAL_HRL_FILES = \ + snmpc.hrl \ + snmpc_lib.hrl \ + snmpc_misc.hrl diff --git a/lib/snmp/src/compile/snmpc.erl b/lib/snmp/src/compile/snmpc.erl new file mode 100644 index 0000000000..8a1f15d4a4 --- /dev/null +++ b/lib/snmp/src/compile/snmpc.erl @@ -0,0 +1,1358 @@ +%% +%% %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(snmpc). + +%% API +-export([compile/1, compile/2, compile/3, + mib_to_hrl/1, mib_to_hrl/3, + is_consistent/1]). + +%% Debug +-export([look_at/1]). + +%% Internal Exports +-export([init/3]). + +-include_lib("stdlib/include/erl_compile.hrl"). +-include("snmp_types.hrl"). +-include("snmpc.hrl"). +-include("snmpc_lib.hrl"). + + +look_at(Mib) -> + io:format("~p ~n", [snmpc_lib:look_at(Mib)]). + + +%%----------------------------------------------------------------- +%% Misc compiler stuff +%%----------------------------------------------------------------- + +is_consistent(Filenames) -> + snmpc_lib:is_consistent(Filenames). + +mib_to_hrl(MibName) -> + snmpc_mib_to_hrl:convert(MibName). + +mib_to_hrl(MibName, HrlFile, Opts) -> + snmpc_mib_to_hrl:compile(MibName, HrlFile, Opts). + + +%%%----------------------------------------------------------------- +%%% Interface for erl_compile. +%%%----------------------------------------------------------------- + +compile(Input, _Output, Options) -> + case compile(Input, make_options(Options)) of + {ok, _} -> + ok; + {error, Reason} -> + io:format("~p", [Reason]), + error + end. + +%% Converts generic options to format expected by compile/2 + +make_options(#options{includes = Incs, + outdir = Outdir, + warning = Warning, + specific = Spec}) -> + + OutdirOpt = {outdir, Outdir}, + + WarningOpt = + case Warning of + 0 -> {warnings, false}; + _ -> {warnings, true} + end, + + IncludeOpt = + {i, case Incs of + [] -> + [""]; + _ -> + lists:map(fun(Dir) -> Dir++"/" end, Incs) + end}, + + [WarningOpt, OutdirOpt, IncludeOpt | Spec]. + +%% Returns: {ok, File}|{error, Reason} +compile([AtomFilename]) when is_atom(AtomFilename) -> + compile(atom_to_list(AtomFilename), []), % from cmd line + halt(); +compile(FileName) -> + compile(FileName, []). + + +%%---------------------------------------------------------------------- +%% Options: +%% {deprecated, bool()} true +%% {group_check, bool()} true +%% {db, volatile|persistent|mnesia} volatile +%% {i, [import_dir_string()]} ["./"] +%% {il, [import_lib_dir_string()]} [] +%% {warnings, bool()} true +%% {outdir, string()} "./" +%% description +%% reference +%% imports +%% module_identity +%% {module, string()} +%% no_defs +%% (hidden) {verbosity, trace|debug|log|info|silence} silence +%% (hidden) version +%% (hidden) options +%%---------------------------------------------------------------------- + +compile(FileName, Options) when is_list(FileName) -> + true = snmpc_misc:is_string(FileName), + DefOpts = [{deprecated, true}, + {group_check, true}, + {i, ["./"]}, + {db, volatile}, + {warnings, true}, + {outdir, "./"}, + {il, []}], + Opts = update_options(DefOpts, Options), + case check_options(Opts) of + ok -> + maybe_display_version(Opts), + maybe_display_options(Opts), + Pid = spawn_link(?MODULE,init,[self(),FileName,Opts]), + receive + {compile_result,R} -> R; + {'EXIT',Pid, Reason} when Reason =/= normal -> + exit(Reason) + end; + {error, Reason} -> + {error, Reason} + end. + +maybe_display_version(Opts) -> + case lists:member(version, Opts) of + true -> + Vsn = (catch get_version()), + io:format("version: ~s~n", [Vsn]); + false -> + ok + end. + +get_version() -> + MI = ?MODULE:module_info(), + Attr = get_info(attributes, MI), + Vsn = get_info(app_vsn, Attr), + Comp = get_info(compile, MI), + Time = get_info(time, Comp), + {Year, Month, Day, Hour, Min, Sec} = Time, + io_lib:format("~s [~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w]", + [Vsn, Year, Month, Day, Hour, Min, Sec]). + +maybe_display_options(Opts) -> + case lists:member(options, Opts) of + true -> + {F, A} = get_options(Opts, [], []), + io:format("options: " ++ F ++ "~n", A); + false -> + ok + end. + +get_options([], Formats, Args) -> + {lists:concat(lists:reverse(Formats)), lists:reverse(Args)}; +get_options([{deprecated, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n deprecated: ~w"|Formats], [Val|Args]); +get_options([{group_check, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n group_check: ~w"|Formats], [Val|Args]); +get_options([{db, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n db: ~w"|Formats], [Val|Args]); +get_options([{i, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n i: ~p"|Formats], [Val|Args]); +get_options([{il, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n il: ~p"|Formats], [Val|Args]); +get_options([{outdir, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n outdir: ~s"|Formats], [Val|Args]); +get_options([{description, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n description: ~w"|Formats], [Val|Args]); +get_options([description|Opts], Formats, Args) -> + get_options(Opts, ["~n description"|Formats], Args); +get_options([{reference, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n reference: ~w"|Formats], [Val|Args]); +get_options([reference|Opts], Formats, Args) -> + get_options(Opts, ["~n reference"|Formats], Args); +get_options([{warnings, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n warnings: ~w"|Formats], [Val|Args]); +get_options([{verbosity, Val}|Opts], Formats, Args) -> + get_options(Opts, ["~n verbosity: ~w"|Formats], [Val|Args]); +get_options([imports|Opts], Formats, Args) -> + get_options(Opts, ["~n imports"|Formats], Args); +get_options([module_identity|Opts], Formats, Args) -> + get_options(Opts, ["~n module_identity"|Formats], Args); +get_options([_|Opts], Formats, Args) -> + get_options(Opts, Formats, Args). + + +get_info(Key, Info) -> + case lists:keysearch(Key, 1, Info) of + {value, {Key, Val}} -> + Val; + false -> + throw("undefined") + end. + +% p(F, A) -> +% io:format("DBG: " ++ F ++ "~n", A). + +update_options([], Options) -> + Options; +update_options([{Key,DefVal}|DefOpts], Options) -> + case snmpc_misc:assq(Key, Options) of + false -> + update_options(DefOpts, [{Key,DefVal}|Options]); + {value, Val} when Key =:= i -> + Options1 = + lists:keyreplace(Key, 1, Options, {Key, Val++DefVal}), + update_options(DefOpts, Options1); + {value, Val} when Key =:= il -> + Options1 = + lists:keyreplace(Key, 1, Options, {Key, Val++DefVal}), + update_options(DefOpts, Options1); + {value, DefVal} -> %% Same value, no need to update + update_options(DefOpts, Options); + {value, Val} -> %% New value, so update + Options1 = + lists:keyreplace(Key, 1, Options, {Key, Val}), + update_options(DefOpts, Options1) + end. + +check_options([]) -> ok; +check_options([no_symbolic_info|T]) -> check_options(T); +check_options([{outdir, Str} | T]) when is_list(Str) -> + check_options(T); +check_options([{debug, Atom} | T]) when is_atom(Atom) -> + check_options(T); +check_options([{deprecated, Atom} | T]) when is_atom(Atom) -> + check_options(T); +check_options([{group_check, Atom} | T]) when is_atom(Atom) -> + check_options(T); +check_options([{warnings, Bool} | T]) -> + check_bool(warnings, Bool), + check_options(T); +check_options([{db, volatile} | T]) -> + check_options(T); +check_options([{db, persistent} | T]) -> + check_options(T); +check_options([{db, mnesia} | T]) -> + check_options(T); +check_options([{i, [Str|_]} | T]) when is_list(Str) -> + check_options(T); +check_options([{il, []} | T]) -> + check_options(T); +check_options([{il, [Str|_]} | T]) when is_list(Str) -> + check_options(T); +check_options([{description, Bool}| T]) -> + check_bool(description, Bool), + check_options(T); +check_options([description| T]) -> %% same as {description, true} + check_options(T); +check_options([{reference, Bool}| T]) -> + check_bool(reference, Bool), + check_options(T); +check_options([reference| T]) -> %% same as {reference, true} + check_options(T); +check_options([{verbosity, V} | T]) when is_atom(V) -> + snmpc_lib:vvalidate(V), + check_options(T); +check_options([version| T]) -> + check_options(T); +check_options([options| T]) -> + check_options(T); +check_options([imports| T]) -> + check_options(T); +check_options([module_identity| T]) -> + check_options(T); +check_options([{module, M} | T]) when is_atom(M) -> + check_options(T); +check_options([no_defs| T]) -> + check_options(T); +check_options([Opt|_]) -> + {error, {invalid_option, Opt}}. + + +check_bool(_Key, Bool) when (Bool =:= true) orelse (Bool =:= false) -> + ok; +check_bool(Key, Val) -> + {error, {invalid_option, {Key, Val}}}. + +get_group_check(Options) -> + snmpc_lib:key1search(group_check, Options, true). + +get_deprecated(Options) -> + snmpc_lib:key1search(deprecated, Options, true). + +get_description(Options) -> + get_bool_option(description, Options). + +get_reference(Options) -> + get_bool_option(reference, Options). + +get_bool_option(Option, Options) -> + case lists:member(Option, Options) of + false -> + snmpc_lib:key1search(Option, Options, false); + true -> + true + end. + +make_description(Message) -> + case get(description) of + true -> + Message; + _ -> + undefined + end. + +make_reference(undefined) -> + []; +make_reference(Reference) -> + case get(reference) of + true -> + [{reference, Reference}]; + _ -> + [] + end. + + + +%%---------------------------------------------------------------------- +%% verbosity stuff +%%---------------------------------------------------------------------- + +%% Verbosity level is selected from three (historical reasons) +%% options: warnings, debug and verbosity +%% - If warnings is true, then verbosity is _atleast_ warning +%% (even if the verbosity flag is set to silence) +%% - If debug is true, the verbosity is _atleast_ log +%% - Otherwise, verbosity is used as is. +get_verbosity(Options) -> + WarningsSeverity = + case snmpc_lib:key1search(warnings, Options) of + true -> + warning; + _ -> + silence + end, + case snmpc_lib:key1search(verbosity, Options) of + undefined -> + %% Backward compatible: If not defined then try debug and convert + case snmpc_lib:key1search(debug, Options, false) of + true -> + log; + false -> + WarningsSeverity + end; + silence -> + WarningsSeverity; + Verbosity -> + Verbosity + end. + + +%%---------------------------------------------------------------------- +%% The compile process. +%%---------------------------------------------------------------------- + +init(From, MibFileName, Options) -> + {A,B,C} = now(), + random:seed(A,B,C), + put(options, Options), + put(verbosity, get_verbosity(Options)), + put(description, get_description(Options)), + put(reference, get_reference(Options)), + File = filename:rootname(MibFileName, ".mib"), + put(filename, filename:basename(File ++ ".mib")), + R = case catch c_impl(File) of + {ok, OutFile} -> {ok, OutFile}; + {'EXIT',error} -> {error, compilation_failed}; + Error -> exit(Error) + end, + From ! {compile_result, R}. + + +c_impl(File) -> + {ok, PData} = parse(File), + ?vtrace("Syntax analysis:" + "~n PData: ~p", [PData]), + MibName = compile_parsed_data(PData), + ?vtrace("Compiler output:" + "~n CDATA: ~p", [get(cdata)]), + save(File, MibName, get(options)). + +compile_parsed_data(#pdata{mib_name = MibName, + imports = Imports, + defs = Definitions}) -> + snmpc_lib:import(Imports), + update_imports(Imports), + Deprecated = get_deprecated(get(options)), + definitions_loop(Definitions, Deprecated), + MibName. + +update_imports(Imports) -> + case lists:member(imports, get(options)) of + true -> + IMPs = do_update_imports(Imports, []), + CDATA = get(cdata), + put(cdata, CDATA#cdata{imports = IMPs}); + false -> + ok + end. + +do_update_imports([], Acc) -> + lists:reverse(Acc); +do_update_imports([{{Mib, ImportsFromMib0},_Line}|Imports], Acc) -> + ImportsFromMib = [Name || {_, Name} <- ImportsFromMib0], + Import = {Mib, ImportsFromMib}, + do_update_imports(Imports, [Import|Acc]). + + +update_status(Name, Status) -> + #cdata{status_ets = Ets} = get(cdata), + ets:insert(Ets, {Name, Status}). + + +%% A deprecated object +definitions_loop([{#mc_object_type{name = ObjName, status = deprecated}, + Line}|T], + false) -> + %% May be implemented but the compiler chooses not to. + ?vinfo2("object_type ~w is deprecated => ignored", [ObjName], Line), + update_status(ObjName, deprecated), + definitions_loop(T, false); + +%% A obsolete object +definitions_loop([{#mc_object_type{name = ObjName, status = obsolete}, + Line}|T], + Deprecated) -> + ?vlog2("object_type ~w is obsolete => ignored", [ObjName], Line), + %% No need to implement a obsolete object + update_status(ObjName, obsolete), + ensure_macro_imported('OBJECT-TYPE', Line), + definitions_loop(T, Deprecated); + +%% Defining a table +definitions_loop([{#mc_object_type{name = NameOfTable, + syntax = {{sequence_of, SeqName}, _}, + max_access = Taccess, + kind = Kind, + status = Tstatus, + description = Desc1, + units = Tunits, + reference = Ref, + name_assign = Tindex}, + Tline}, + {#mc_object_type{name = NameOfEntry, + syntax = {{type, SeqName}, TEline}, + max_access = 'not-accessible', + kind = {table_entry, IndexingInfo}, + status = Estatus, + description = Desc2, + units = Eunits, + name_assign = {NameOfTable,[1]}}, + Eline}, + {#mc_sequence{name = SeqName, + fields = FieldList}, + Sline}|ColsEtc], + Deprecated) -> + ?vlog("defloop -> " + "[object_type(sequence_of),object_type(type,[1]),sequence]:" + "~n NameOfTable: ~p" + "~n SeqName: ~p" + "~n Taccess: ~p" + "~n Kind: ~p" + "~n Tstatus: ~p" + "~n Tindex: ~p" + "~n Tunits: ~p" + "~n Tline: ~p" + "~n NameOfEntry: ~p" + "~n TEline: ~p" + "~n IndexingInfo: ~p" + "~n Estatus: ~p" + "~n Eunits: ~p" + "~n Ref: ~p" + "~n Eline: ~p" + "~n FieldList: ~p" + "~n Sline: ~p", + [NameOfTable,SeqName,Taccess,Kind,Tstatus, + Tindex,Tunits,Tline, + NameOfEntry,TEline,IndexingInfo,Estatus,Eunits,Ref,Eline, + FieldList,Sline]), + update_status(NameOfTable, Tstatus), + update_status(NameOfEntry, Estatus), + update_status(SeqName, undefined), + ensure_macro_imported('OBJECT-TYPE', Tline), + test_table(NameOfTable,Taccess,Kind,Tindex,Tline), + {Tfather,Tsubindex} = Tindex, + snmpc_lib:register_oid(Tline,NameOfTable,Tfather,Tsubindex), + Description1 = make_description(Desc1), + TableME = #me{aliasname = NameOfTable, + entrytype = table, + access = 'not-accessible', + description = Description1, + units = Tunits}, + snmpc_lib:register_oid(TEline,NameOfEntry,NameOfTable,[1]), + Description2 = make_description(Desc2), + TableEntryME = #me{aliasname = NameOfEntry, + entrytype = table_entry, + assocList = [{table_entry_with_sequence, SeqName}], + access = 'not-accessible', + description = Description2, + units = Eunits}, + {ColMEs, RestObjs} = + define_cols(ColsEtc, 1, FieldList, NameOfEntry, NameOfTable, []), + TableInfo = snmpc_lib:make_table_info(Eline, NameOfTable, + IndexingInfo, ColMEs), + snmpc_lib:add_cdata(#cdata.mes, + [TableEntryME, + TableME#me{assocList=[{table_info, + TableInfo} | make_reference(Ref)]} | + ColMEs]), + definitions_loop(RestObjs, Deprecated); + +definitions_loop([{#mc_object_type{name = NameOfTable, + syntax = {{sequence_of, SeqName},_}, + max_access = Taccess, + kind = Kind, + status = Tstatus, + description = Desc1, + units = Tunits, + reference = Ref, + name_assign = Tindex}, Tline}, + {#mc_object_type{name = NameOfEntry, + syntax = {{type, SeqName},_}, + max_access = 'not-accessible', + kind = {table_entry,IndexingInfo}, + status = Estatus, + description = Desc2, + units = Eunits, + name_assign = BadOID}, Eline}, + {#mc_sequence{name = SeqName, + fields = FieldList}, Sline}|ColsEtc], + Deprecated) -> + ?vlog("defloop -> " + "[object_type(sequence_of),object_type(type),sequence(fieldList)]:" + "~n NameOfTable: ~p" + "~n SeqName: ~p" + "~n Taccess: ~p" + "~n Kind: ~p" + "~n Tstatus: ~p" + "~n Tindex: ~p" + "~n Tunits: ~p" + "~n Tline: ~p" + "~n NameOfEntry: ~p" + "~n IndexingInfo: ~p" + "~n Estatus: ~p" + "~n BadOID: ~p" + "~n Eunits: ~p" + "~n Ref: ~p" + "~n Eline: ~p" + "~n FieldList: ~p" + "~n Sline: ~p", + [NameOfTable,SeqName,Taccess,Kind,Tstatus, + Tindex,Tunits,Tline, + NameOfEntry,IndexingInfo,Estatus,BadOID,Eunits,Ref,Eline, + FieldList,Sline]), + update_status(NameOfTable, Tstatus), + update_status(NameOfEntry, Estatus), + update_status(SeqName, undefined), + ensure_macro_imported('OBJECT-TYPE', Tline), + snmpc_lib:print_error("Bad TableEntry OID definition (~w)", + [BadOID],Eline), + test_table(NameOfTable,Taccess,Kind,Tindex,Tline), + {Tfather,Tsubindex} = Tindex, + snmpc_lib:register_oid(Tline,NameOfTable,Tfather,Tsubindex), + Description1 = make_description(Desc1), + TableME = #me{aliasname = NameOfTable, + entrytype = table, + access = 'not-accessible', + description = Description1, + units = Tunits}, + Description2 = make_description(Desc2), + TableEntryME = #me{aliasname = NameOfEntry, + entrytype = table_entry, + access = 'not-accessible', + assocList = [{table_entry_with_sequence,SeqName}], + description = Description2, + units = Eunits}, + {ColMEs, RestObjs} = + define_cols(ColsEtc, 1, FieldList, NameOfEntry, NameOfTable, []), + TableInfo = snmpc_lib:make_table_info(Eline, NameOfTable, + IndexingInfo, ColMEs), + snmpc_lib:add_cdata(#cdata.mes, + [TableEntryME, + TableME#me{assocList=[{table_info, + TableInfo} | make_reference(Ref)]} | + ColMEs]), + definitions_loop(RestObjs, Deprecated); + +definitions_loop([{#mc_new_type{name = NewTypeName, + macro = Macro, + syntax = OldType, + display_hint = DisplayHint},Line}|T], + Deprecated) -> + ?vlog2("defloop -> new_type:" + "~n Macro: ~p" + "~n NewTypeName: ~p" + "~n OldType: ~p" + "~n DisplayHint: ~p", + [Macro, NewTypeName, OldType, DisplayHint], Line), + ensure_macro_imported(Macro,Line), + Types = (get(cdata))#cdata.asn1_types, + case lists:keysearch(NewTypeName, #asn1_type.aliasname, Types) of + {value,_} -> + snmpc_lib:print_error("Type ~w already defined.", + [NewTypeName],Line); + false -> + %% NameOfOldType = element(2,OldType), + ASN1 = snmpc_lib:make_ASN1type(OldType), + snmpc_lib:add_cdata(#cdata.asn1_types, + [ASN1#asn1_type{aliasname = NewTypeName, + imported = false, + display_hint = DisplayHint}]) + end, + definitions_loop(T, Deprecated); + +%% Plain variable +definitions_loop([{#mc_object_type{name = NewVarName, + syntax = Type, + max_access = Access, + kind = {variable, DefVal}, + status = Status, + description = Desc1, + units = Units, + name_assign = {Parent,SubIndex}},Line} |T], + Deprecated) -> + ?vlog2("defloop -> object_type (variable):" + "~n NewVarName: ~p" + "~n Type: ~p" + "~n Access: ~p" + "~n DefVal: ~p" + "~n Status: ~p" + "~n Units: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [NewVarName, Type, Access, DefVal, + Status, Units, Parent, SubIndex], Line), + update_status(NewVarName, Status), + snmpc_lib:test_father(Parent, NewVarName, SubIndex, Line), + ASN1type = snmpc_lib:make_ASN1type(Type), + snmpc_lib:register_oid(Line, NewVarName, Parent, SubIndex), + Description1 = make_description(Desc1), + NewME = #me{aliasname = NewVarName, + asn1_type = ASN1type, + entrytype = variable, + access = Access, + description = Description1, + units = Units, + assocList = DefVal}, + NewME2 = snmpc_lib:resolve_defval(NewME), + %% hmm, should this be done in resolve_defval? + VI = snmpc_lib:make_variable_info(NewME2), + snmpc_lib:add_cdata(#cdata.mes, + [NewME2#me{assocList = [{variable_info, VI}]}]), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_module_identity{name = NewVarName, + last_updated = LU, + organization = Org, + contact_info = CI, + description = Desc, + revisions = Revs0, + name_assign = {Parent, SubIndex}}, + Line}|T], + Deprecated) -> + ?vlog2("defloop -> module-identity: " + "~n NewVarName: ~p" + "~n LU: ~p" + "~n Org: ~p" + "~n CI: ~p" + "~n Desc: ~p" + "~n Revs0: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [NewVarName, LU, Org, CI, Desc, Revs0, Parent, SubIndex], Line), + ensure_macro_imported('MODULE-IDENTITY', Line), + snmpc_lib:register_oid(Line, NewVarName, Parent, SubIndex), + Revs = [{R,D}||#mc_revision{revision = R,description = D} <- Revs0], + MI = #module_identity{last_updated = LU, + organization = Org, + contact_info = CI, + description = Desc, + revisions = Revs}, + CDATA = get(cdata), + put(cdata, CDATA#cdata{module_identity = MI}), + snmpc_lib:add_cdata( + #cdata.mes, + [snmpc_lib:makeInternalNode2(false, NewVarName)]), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_internal{name = NewVarName, + macro = Macro, + parent = Parent, + sub_index = SubIndex},Line}|T], + Deprecated) -> + ?vlog2("defloop -> internal:" + "~n NewVarName: ~p" + "~n Macro: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [NewVarName, Macro, Parent, SubIndex], Line), + ensure_macro_imported(Macro, Line), + snmpc_lib:register_oid(Line, NewVarName, Parent, SubIndex), + snmpc_lib:add_cdata( + #cdata.mes, + [snmpc_lib:makeInternalNode2(false, NewVarName)]), + definitions_loop(T, Deprecated); + +%% A trap message +definitions_loop([{#mc_trap{name = TrapName, + enterprise = EnterPrise, + vars = Variables, + description = Desc1, + num = SpecificCode}, Line}|T], + Deprecated) -> + ?vlog2("defloop -> trap:" + "~n TrapName: ~p" + "~n EnterPrise: ~p" + "~n Variables: ~p" + "~n SpecificCode: ~p", + [TrapName, EnterPrise, Variables, SpecificCode], Line), + update_status(TrapName, undefined), + CDATA = get(cdata), + snmpc_lib:check_trap_name(EnterPrise, Line, CDATA#cdata.mes), + Descriptions = make_description(Desc1), + Trap = #trap{trapname = TrapName, + enterpriseoid = EnterPrise, + specificcode = SpecificCode, + %% oidobjects: Store Variables temporary here. + %% This will be replaced later in the + %% get_final_mib function by a call to + %% the update_trap_objects function. + oidobjects = Variables, + description = Descriptions}, + lists:foreach(fun(Trap2) -> snmpc_lib:check_trap(Trap2, Trap, Line) end, + CDATA#cdata.traps), + snmpc_lib:add_cdata(#cdata.traps, [Trap]), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_object_type{name = NameOfEntry, + syntax = Type, + max_access = Eaccess, + kind = {table_entry, Index}, + status = Estatus, + name_assign = SubIndex},Eline}|T], + Deprecated) -> + ?vlog("defloop -> object_type (table_entry):" + "~n NameOfEntry: ~p" + "~n Type: ~p" + "~n Eaccess: ~p" + "~n Index: ~p" + "~n Estatus: ~p" + "~n SubIndex: ~p" + "~n SubIndex: ~p" + "~n Eline: ~p", + [NameOfEntry, Type, Eaccess, Index, Estatus, SubIndex, Eline]), + update_status(NameOfEntry, Estatus), + snmpc_lib:print_error("Misplaced TableEntry definition (~w)", + [NameOfEntry], Eline), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_notification{name = TrapName, + status = deprecated}, Line}|T], + false) -> + ?vinfo2("defloop -> notification ~w is deprecated => ignored", + [TrapName], Line), + update_status(TrapName, deprecated), + ensure_macro_imported('NOTIFICATION-TYPE', Line), + definitions_loop(T, false); + +definitions_loop([{#mc_notification{name = TrapName, + status = obsolete}, Line}|T], + Deprecated) -> + ?vlog2("defloop -> notification ~w is obsolete => ignored", + [TrapName], Line), + update_status(TrapName, obsolete), + ensure_macro_imported('NOTIFICATION-TYPE', Line), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_notification{name = TrapName, + vars = Variables, + status = Status, + description = Desc, + name_assign = {Parent, SubIndex}},Line}|T], + Deprecated) -> + ?vlog2("defloop -> notification:" + "~n TrapName: ~p" + "~n Variables: ~p" + "~n Status: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [TrapName, Variables, Status, Parent, SubIndex], Line), + update_status(TrapName, Status), + ensure_macro_imported('NOTIFICATION-TYPE', Line), + CDATA = get(cdata), + snmpc_lib:register_oid(Line, TrapName, Parent, SubIndex), + Descriptions = make_description(Desc), + Notif = #notification{trapname = TrapName, + description = Descriptions, + %% oidobjects: Store Variables temporary here. + %% This will be replaced later in the + %% get_final_mib function by a call to + %% the update_trap_objects function. + oidobjects = Variables}, + snmpc_lib:check_notification(Notif, Line, CDATA#cdata.traps), + snmpc_lib:add_cdata(#cdata.traps, [Notif]), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_module_compliance{name = Name},Line}|T], Deprecated) -> + ?vlog2("defloop -> module_compliance:" + "~n Name: ~p", [Name], Line), + ensure_macro_imported('MODULE-COMPLIANCE', Line), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_object_group{name = Name, + objects = GroupObjects, + status = Status, + description = Desc, + reference = Ref, + name_assign = {Parent,SubIndex}}, Line}|T], + Deprecated) -> + ?vlog2("defloop -> object_group ~p:" + "~n GroupObjects: ~p" + "~n Status: ~p" + "~n Desc: ~p" + "~n Ref: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [Name, GroupObjects, Status, Desc, Ref, Parent, SubIndex], Line), + ensure_macro_imported('OBJECT-GROUP', Line), + GroupBool = get_group_check(get(options)), + case GroupBool of + true -> + snmpc_lib:add_cdata(#cdata.objectgroups, + [{Name,GroupObjects,Line}]), + %% Check that the group members has been defined + %% and that they have the correct status + snmpc_lib:check_object_group(Name, GroupObjects, + Line, Status); + _ -> + ok + end, + + update_status(Name, Status), + snmpc_lib:test_father(Parent, Name, SubIndex, Line), + snmpc_lib:register_oid(Line, Name, Parent, SubIndex), + Description = make_description(Desc), + NewME = #me{aliasname = Name, + entrytype = group, + access = 'not-accessible', + description = Description, + assocList = [{kind, object}, + {objects, GroupObjects}]}, + snmpc_lib:add_cdata(#cdata.mes, [NewME]), + + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_notification_group{name = Name, + objects = GroupObjects, + status = Status, + description = Desc, + reference = Ref, + name_assign = {Parent,SubIndex}}, + Line} + |T], Deprecated) -> + ?vlog2("defloop -> notification_group ~p:" + "~n GroupObjects: ~p" + "~n Status: ~p" + "~n Desc: ~p" + "~n Ref: ~p" + "~n Parent: ~p" + "~n SubIndex: ~p", + [Name, GroupObjects, Status, Desc, Ref, Parent, SubIndex], Line), + ensure_macro_imported('NOTIFICATION-GROUP', Line), + GroupBool = get_group_check(get(options)), + case GroupBool of + true -> + snmpc_lib:add_cdata(#cdata.notificationgroups, + [{Name,GroupObjects,Line}]), + + %% Check that the group members has been defined + %% and that they have the correct status + snmpc_lib:check_notification_group(Name, GroupObjects, + Line, Status); + _ -> + ok + end, + + update_status(Name, Status), + snmpc_lib:test_father(Parent, Name, SubIndex, Line), + snmpc_lib:register_oid(Line, Name, Parent, SubIndex), + Description = make_description(Desc), + NewME = #me{aliasname = Name, + entrytype = group, + access = 'not-accessible', + description = Description, + assocList = [{kind, notification}, + {objects, GroupObjects}]}, + snmpc_lib:add_cdata(#cdata.mes, [NewME]), + + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_object_type{name = NameOfTable, + syntax = {{sequence_of, SeqName},_}, + status = Tstatus},Tline}, + Entry, Seq|T], + Deprecated) -> + ?vlog("defloop -> object_type (sequence_of): " + "~n NameOfTable: ~p" + "~n SeqName: ~p" + "~n Tline: ~p" + "~n Entry: ~p" + "~n Seq: ~p", + [NameOfTable, SeqName, Tline, Entry, Seq]), + update_status(NameOfTable, Tstatus), + case Entry of + {#mc_object_type{syntax = {{type, SeqName},_line}, + max_access = 'not-accessible', + kind = {table_entry, _IndexingInfo}, + name_assign = {_NameOfTable,[1]}}, _Eline} -> + case Seq of + {#mc_sequence{name = SeqName}, Sline} -> + snmpc_lib:error("Internal error. Correct incorrect " + "table (~p,~w).",[SeqName,Sline], + Tline); + _ -> + ?vinfo("defloop -> Invalid sequence: ~p", [Seq]), + snmpc_lib:print_error( + "Invalid SEQUENCE OF '~p'.", + [safe_elem(1,safe_elem(2,Seq))],Tline) + end; + Else -> + ?vinfo("defloop -> Invalid table entry: " + "~n ~p", [Else]), + snmpc_lib:print_error( + "Invalid TableEntry '~p' (check STATUS, Sequence name, Oid)", + [safe_elem(1,safe_elem(2,Entry))],Tline) + end, + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_object_type{name = NameOfTable, + syntax = {{sequence_of, SeqName},_}, + status = Tstatus},Tline}|T], + Deprecated) -> + ?vlog("defloop -> object_type (sequence_of):" + "~n object_type: ~p" + "~n sequence_of: ~p" + "~n Tline: ~p", [NameOfTable, SeqName, Tline]), + update_status(NameOfTable, Tstatus), + snmpc_lib:print_error("Invalid statements following table ~p.", + [NameOfTable],Tline), + definitions_loop(T, Deprecated); + +definitions_loop([{#mc_sequence{name = SeqName, + fields = _FieldList},Line}|T], + Deprecated) -> + ?vwarning2("Unexpected SEQUENCE ~w => ignoring", [SeqName], Line), + definitions_loop(T, Deprecated); + +definitions_loop([{Obj,Line}|T], Deprecated) -> + ?vinfo2("defloop -> unknown error" + "~n Obj: ~p", [Obj], Line), + snmpc_lib:print_error("Unknown Error in MIB. " + "Can't describe the error better than this: ~999p ignored." + " Please send a trouble report to [email protected].", + [Obj], Line), + definitions_loop(T, Deprecated); + +definitions_loop([], _Deprecated) -> + ?vlog("defloop -> done", []), + ok. + +safe_elem(N,T) -> + case catch(element(N,T)) of + {'EXIT',_} -> + "no more information available"; + X -> X + end. + +%% A correct column +define_cols([{#mc_object_type{name = NameOfCol, + syntax = Type1, + max_access = Access, + kind = {variable,Defval}, + status = Status, + description = Desc, + units = Units, + name_assign = {NameOfEntry,[SubIndex]}}, + Oline}|Rest], + SubIndex, + [{NameOfCol,Type2}|Fields], NameOfEntry, TableName, ColMEs) -> + ?vlog("defcols -> object_type (variable):" + "~n NameOfCol: ~p" + "~n Type1: ~p" + "~n Access: ~p" + "~n Defval: ~p" + "~n Status ~p" + "~n Units ~p" + "~n NameOfEntry ~p" + "~n Oline: ~p", + [NameOfCol, Type1, Access, Defval, Status, Units, + NameOfEntry, Oline]), + update_status(NameOfCol, Status), + Deprecated = get_deprecated(get(options)), + ASN1type = snmpc_lib:make_ASN1type(Type1), + case (snmpc_lib:make_ASN1type(Type2))#asn1_type.bertype of + T2 when T2 == ASN1type#asn1_type.bertype -> ok; + _Else -> + snmpc_lib:error( + "Types for ~p differs from the SEQUENCE definition. ", + [NameOfCol],Oline) + end, + NewAccess = % a simple way to get the obsolete behaviour + if + Status =:= obsolete -> + %% Be quiet and don't implement + 'not-accessible'; + (Status =:= deprecated) andalso (Deprecated =:= false) -> + %% The compiler chooses not to implement the column. + ?vinfo2("object_type ~w is deprecated => ignored", + [NameOfCol], Oline), + 'not-accessible'; + true -> Access + end, + snmpc_lib:register_oid(Oline,NameOfCol,NameOfEntry,[SubIndex]), + Description = make_description(Desc), + ColumnME = snmpc_lib:resolve_defval( + #me{oid = SubIndex, + aliasname = NameOfCol, + asn1_type = ASN1type, + entrytype = table_column, + access = NewAccess, + description = Description, + units = Units, %% Propably not usefull + assocList = [{table_name,TableName} | Defval]}), + define_cols(Rest,SubIndex+1,Fields,NameOfEntry,TableName, + [ColumnME|ColMEs]); + +%% A "hole" (non-consecutive columns) in the table. +%% Implemented as a not-accessible column so Col always is index in +%% row tuple. +define_cols([{#mc_object_type{name = NameOfCol, + syntax = Type1, + max_access = Access, + kind = Kind, + status = Status, + name_assign = {NameOfEntry,[SubIndex]}}, + Oline}|Rest], + ExpectedSubIndex, Fields, NameOfEntry, TableName, ColMEs) + when SubIndex > ExpectedSubIndex -> + ?vlog("defcols -> object_type (non consecutive cols):" + "~n NameOfCol: ~p" + "~n Type1: ~p" + "~n Access: ~p" + "~n Status ~p" + "~n NameOfEntry ~p" + "~n Oline: ~p", + [NameOfCol, Type1, Access, Status, NameOfEntry, Oline]), + update_status(NameOfCol, Status), + Int = {{type, 'INTEGER'},Oline}, + GeneratedColumn = + %% be sure to use an invalid column name here! + {#mc_object_type{name = '$no_name$', + syntax = Int, + max_access = 'not-accessible', + kind = {variable, [{defval,0}]}, + status = current, + description = undefined, + name_assign = {NameOfEntry, [ExpectedSubIndex]}}, + Oline}, + define_cols([GeneratedColumn, + {#mc_object_type{name = NameOfCol, + syntax = Type1, + max_access = Access, + kind = Kind, + status = Status, + description = undefined, + name_assign = {NameOfEntry,[SubIndex]}}, + Oline}|Rest], ExpectedSubIndex, + [{'$no_name$', Int}|Fields], NameOfEntry, TableName,ColMEs) ; + +%% Ok. done. All fields are eaten. +define_cols(Rest, _SubIndex, [], _NameOfEntry, _TableName, ColMEs) -> + {ColMEs, Rest}; + + +%% Error Handling + +%% The name of the field and object is the same +define_cols([{#mc_object_type{name = NameOfCol, + kind = Kind, + name_assign = SubIndex}, Oline}|Rest], + SubIndex2, [{NameOfCol, _Type2}|Fields], + NameOfEntry, TableName, ColMEs) -> + ?vlog("defcols -> object_type (name of field and object is the same):" + "~n NameOfCol: ~p" + "~n Kind: ~p" + "~n SubIndex: ~p" + "~n Oline: ~p" + "~n SubIndex2: ~p" + "~n NameOfEntry ~p" + "~n TableName ~p", + [NameOfCol,Kind,SubIndex,Oline,SubIndex2,NameOfEntry,TableName]), + SIok = case SubIndex of + {Parent,[_SI]} when Parent =/= NameOfEntry -> + snmpc_lib:print_error( + "Invalid parent ~p for table column ~p (should be ~p).", + [Parent,NameOfCol,NameOfEntry],Oline), + error; + {NameOfEntry,[SubIndex2]} -> + ok; + {NameOfEntry,[SI]} -> + snmpc_lib:print_error( + "Invalid column number ~p for column ~p.", + [SI, NameOfCol], Oline), + error; + _Q -> + snmpc_lib:print_error( + "Invalid parent for column ~p.",[NameOfCol],Oline), + error + end, + Kok = case Kind of + {variable,_} -> + ok; + _Q2 -> + snmpc_lib:print_error( + "Expected a table column.",[],Oline), + error + end, + case {SIok, Kok} of + {ok, ok} -> + snmpc_lib:print_error("Invalid table column definition for" + " ~p.",[NameOfCol],Oline); + _Q4 -> + done % already reported + end, + define_cols(Rest,SubIndex2+1,Fields,NameOfEntry,TableName,ColMEs); + +%% It's an object-type but everything else is wrong +define_cols([{#mc_object_type{name = NameOfCol},Oline}|Rest],SubIndex2,Fields, + NameOfEntry,TableName,ColMEs) -> + snmpc_lib:print_error( + "Number of columns differs from SEQUENCE definition (object:~p).", + [NameOfCol],Oline), + define_cols(Rest,SubIndex2+1,Fields,NameOfEntry,TableName,ColMEs); + +define_cols([{Obj,Line}|Tl], _SubIndex,_,_,_,ColMEs) -> + snmpc_lib:print_error("Corrupt table definition.",[],Line), + {ColMEs,[{Obj,Line}|Tl]}; +define_cols(Rest, _SubIndex,_,_,_,ColMEs) -> + snmpc_lib:print_error("Corrupt table definition.",[]), + {ColMEs,Rest}. + +ensure_macro_imported(dummy, _Line) -> ok; +ensure_macro_imported(Macro, Line) -> + Macros = (get(cdata))#cdata.imported_macros, + case lists:member(Macro, Macros) of + true -> ok; + false -> + snmpc_lib:print_error("Macro ~p not imported.", [Macro], + Line) + end. + +test_table(NameOfTable, Taccess, Kind, _Tindex, Tline) -> + if + Taccess =/= 'not-accessible' -> + snmpc_lib:print_error( + "Table ~w must have STATUS not-accessible", + [NameOfTable],Tline), + error; + Kind =/= {variable,[]} -> + snmpc_lib:print_error( + "Bad table definition (~w).", + [NameOfTable],Tline), + error; + true -> + ok + end. + +save(Filename, MibName, Options) -> + R = filename:rootname(Filename), + File1 = filename:basename(R), + File3 = snmpc_misc:to_upper(File1), + case snmpc_misc:to_upper(atom_to_list(MibName)) of + File3 -> + {value, OutDirr} = snmpc_misc:assq(outdir, Options), + OutDir = snmpc_misc:ensure_trailing_dir_delimiter(OutDirr), + File2 = (OutDir ++ File1) ++ ".bin", + {ok, MIB} = snmpc_lib:get_final_mib(File1, Options), + case get(errors) of + undefined -> + case file:write_file(File2, term_to_binary(MIB)) of + ok -> + {ok, File2}; + _Err -> + snmpc_lib:error( + "Couldn't write file \"~s\".",[File2]) + end; + E -> + ?vlog("save failed: " + "~n ~p", [E]), + {'EXIT',error} + end; + MibNameL -> + snmpc_lib:error("Mibname (~s) differs from filename (~s).", + [MibNameL, File1]) + end. + +%% parse takes a text file as a input and the output is a list of tokens. +%% Input: FileName (file of mibs) +%% Output: {ok, Mib} where MIB is a tuple of Tokens. +%% {error, {LineNbr, Mod, Msg} an error on line number LineNb. + + +parse(FileName) -> + case snmpc_tok:start_link(reserved_words(), + [{file, FileName ++ ".mib"}, + {forget_stringdata, true}]) of + {error,ReasonStr} -> + snmpc_lib:error(lists:flatten(ReasonStr),[]); + {ok, TokPid} -> + Toks = snmpc_tok:get_all_tokens(TokPid), + set_version(Toks), + %% io:format("parse -> lexical analysis: ~n~p~n", [Toks]), + %% t("parse -> lexical analysis: ~n~p", [Toks]), + CDataArg = + case lists:keysearch(module, 1, get(options)) of + {value, {module, M}} -> {module, M}; + _ -> {file, FileName ++ ".funcs"} + end, + put(cdata,snmpc_lib:make_cdata(CDataArg)), + snmpc_tok:stop(TokPid), + Res = if + is_list(Toks) -> + snmpc_mib_gram:parse(Toks); + true -> + Toks + end, + %% t("parse -> parsed: ~n~p", [Res]), + case Res of + {ok, PData} -> + {ok, PData}; + {error, {LineNbr, Mod, Msg}} -> + case catch format_yecc_error(LineNbr, Msg) of + {Line, Format, Data} -> + snmpc_lib:error(Format,Data,Line); + _Q -> % sorry, have to use ugly yecc printouts + Str = apply(Mod, format_error, [Msg]), + snmpc_lib:error("~s",[Str],LineNbr) + end + end + end. + +set_version(Toks) when is_list(Toks) -> +%% MODULE-IDENTITY _must_ be invoked in SNMPv2 according to RFC1908 + case lists:keymember('MODULE-IDENTITY',1,Toks) of + true -> + put(snmp_version,2); + false -> + put(snmp_version,1) + end; +set_version(_) -> + put(snmp_version,1). + + +%% YeccGeneratedFile:format_error/1 is bad. +format_yecc_error(Line, [ErrMsg, [${,Category, $,, _LineStr,$,, Value, $}]]) -> + {Line, "~s \"~s\" (~s).", [ErrMsg, Value, Category]}. + +%% The same as the (quoted) Terminals in the snmpc_mib_gram.yrl +reserved_words() -> + [ + 'ACCESS', + 'BEGIN', + 'BIT', + 'CONTACT-INFO', + 'Counter', + 'DEFINITIONS', + 'DEFVAL', + 'DESCRIPTION', + 'DISPLAY-HINT', + 'END', + 'ENTERPRISE', + 'FROM', + 'Gauge', + 'IDENTIFIER', + 'IDENTIFIER', + 'IMPORTS', + 'INDEX', + 'INTEGER', + 'IpAddress', + 'LAST-UPDATED', + 'NetworkAddress', + 'OBJECT', + 'OBJECT', + 'OBJECT-TYPE', + 'OCTET', + 'OF', + 'Opaque', + 'REFERENCE', + 'SEQUENCE', + 'SIZE', + 'STATUS', + 'STRING', + 'SYNTAX', + 'TRAP-TYPE', + 'TimeTicks', + 'VARIABLES', + + %% v2 + 'LAST-UPDATED', + 'ORGANIZATION', + 'CONTACT-INFO', + 'MODULE-IDENTITY', + 'NOTIFICATION-TYPE', + 'MODULE-COMPLIANCE', + 'OBJECT-GROUP', + 'NOTIFICATION-GROUP', + 'REVISION', + 'OBJECT-IDENTITY', + 'MAX-ACCESS', + 'UNITS', + 'AUGMENTS', + 'IMPLIED', + 'OBJECTS', + 'TEXTUAL-CONVENTION', + 'OBJECT-GROUP', + 'NOTIFICATION-GROUP', + 'NOTIFICATIONS', + 'MODULE-COMPLIANCE', + 'MODULE', + 'MANDATORY-GROUPS', + 'GROUP', + 'WRITE-SYNTAX', + 'MIN-ACCESS', + 'BITS' + ] +. diff --git a/lib/snmp/src/compile/snmpc.hrl b/lib/snmp/src/compile/snmpc.hrl new file mode 100644 index 0000000000..eb896cde6b --- /dev/null +++ b/lib/snmp/src/compile/snmpc.hrl @@ -0,0 +1,153 @@ +%% +%% %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% +%% + +%% Parser output +-record(pdata, {mib_version, + mib_name, + imports, + defs}). + +%% compilation information record +-record(cdata, {module_identity, + asn1_types = [], + mes = [], + traps = [], + mibfuncs, + sequences = [], + imported_macros = [], + objectgroups = [], + notificationgroups = [], + imports, + oid_ets, + status_ets}). + + +-record(mc_module_identity, + {name, + last_updated, + organization, + contact_info, + description, + revisions = [], %% A list of mc_revision + name_assign + } + ). + +-record(mc_revision, + {revision, + description + } + ). + +-record(mc_object_type, + {name, + syntax, + units, + max_access, + status, + description, + reference, + kind, + name_assign + } + ). + + +-record(mc_new_type, + {name, + macro, + status, + description, + reference, + display_hint, + syntax + } + ). + + +-record(mc_trap, + {name, + enterprise, + vars, + description, + reference, + num + } + ). + + +-record(mc_notification, + {name, + vars, + status, + description, + reference, + name_assign + } + ). + + +-record(mc_module_compliance, + {name, + status, + description, + reference, + module, + name_assign + } + ). + + +-record(mc_object_group, + {name, + objects, + status, + description, + reference, + name_assign + } + ). + + +-record(mc_notification_group, + {name, + objects, + status, + description, + reference, + name_assign + } + ). + + +-record(mc_sequence, + {name, + fields + } + ). + + +-record(mc_internal, + {name, + macro, + parent, + sub_index + } + ). + diff --git a/lib/snmp/src/compile/snmpc_lib.erl b/lib/snmp/src/compile/snmpc_lib.erl new file mode 100644 index 0000000000..b7e84e7d6b --- /dev/null +++ b/lib/snmp/src/compile/snmpc_lib.erl @@ -0,0 +1,1819 @@ +%% +%% %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(snmpc_lib). + +%% API +-export([test_father/4, make_ASN1type/1, import/1, makeInternalNode2/2, + is_consistent/1, resolve_defval/1, make_variable_info/1, + check_trap_name/3, make_table_info/4, get_final_mib/2, set_dir/2, + look_at/1, add_cdata/2, + check_object_group/4, check_notification_group/4, + check_notification/3, + register_oid/4, + error/2, error/3, + %% warning/2, warning/3, + print_error/2, print_error/3, + make_cdata/1, + key1search/2, key1search/3]). + +%% internal exports +-export([check_of/1, check_trap/2, check_trap/3, get_elem/2]). + +%% debug exports +-export([vvalidate/1, vprint/6]). + + +-include("snmp_types.hrl"). +-include("snmpc.hrl"). +-include("snmpc_lib.hrl"). + + +%%---------------------------------------------------------------------------- +%% Some basic types +%%---------------------------------------------------------------------------- + +-type severity() :: 'silence' | 'warning' | 'info' | 'log' | 'debug' | 'trace'. + + +test_father(FatherName, NewVarName, SubIndex, Line) -> + CDATA = get(cdata), + case lists:keysearch(FatherName, #me.aliasname, CDATA#cdata.mes) of + {value, #me{entrytype = table, aliasname = TableName}} -> + print_error("Variable '~w' (sub-index '~w') cannot " + "be defined under table '~w'.", + [NewVarName, SubIndex, TableName],Line); + {value, #me{entrytype = table_entry, aliasname = TableName}} -> + print_error("Variable '~w' (sub-index '~w') cannot " + "be defined under table entry '~w'.", + [NewVarName, SubIndex, TableName], Line); + + _X -> %% internal or variable + case lists:last(SubIndex) of + 0 -> + print_error("'~w'. A zero-valued final subidentifier is reserved for future use. (RFC1902, 7.10)",[NewVarName],Line); + _ -> ok + end + end. + +make_ASN1type({{type,Type},Line}) -> + case lookup_vartype(Type) of + {value, ASN1type} -> + ASN1type; + false -> + print_error("Undefined type '~w'",[Type],Line), + guess_integer_type() + end; +make_ASN1type({{type_with_size,Type,{range,Lo,Hi}},Line}) -> + case lookup_vartype(Type) of + {value,ASN1type} -> + case allow_size_rfc1902(BaseType = ASN1type#asn1_type.bertype) of + true -> + ok; + false -> + print_error( + "Size refinement is not allowed for subclass from ~w.", + [BaseType],Line) + end, + ASN1type#asn1_type{lo = Lo, hi = Hi}; + false -> + print_error("Undefined type '~w'",[Type],Line), + guess_string_type() + end; +make_ASN1type({{integer_with_enum,Type,Enums},Line}) -> + case lookup_vartype(Type) of + {value,ASN1type} -> ASN1type#asn1_type{assocList = [{enums, Enums}]}; + false -> + print_error("Undefined type '~w'",[Type],Line), + guess_integer_type() + end; +make_ASN1type({{bits,Kibbles},Line}) -> + case get(snmp_version) of + 2 -> + {value,Bits} = lookup_vartype('BITS'), + Kibbles2 = test_kibbles(Kibbles, Line), + Bits#asn1_type{assocList = [{kibbles, Kibbles2}]}; + _ -> + guess_integer_type() + end; +make_ASN1type({{sequence_of, _Type},Line}) -> + print_error("Use of SEQUENCE OF in non-table context.",[],Line), + guess_integer_type(). + +test_kibbles([], Line) -> + print_error("No kibbles found.",[],Line), + []; +test_kibbles(Kibbles,Line) -> + test_kibbles2(R = lists:keysort(2,Kibbles),0,Line), + R. + +test_kibbles2([],_,_) -> + ok; +test_kibbles2([{_KibbleName,BitNo}|Ks],BitNo,Line) -> + test_kibbles2(Ks,BitNo+1,Line); +test_kibbles2([{_KibbleName,BitNo}|_Ks],ExpectBitNo,Line) -> + print_error("Expected kibble no ~p but got ~p.",[ExpectBitNo,BitNo],Line). + + +allow_size_rfc1902('INTEGER') -> true; +allow_size_rfc1902('Integer32') -> true; +allow_size_rfc1902('Unsigned32') -> true; +allow_size_rfc1902('OCTET STRING') -> true; +allow_size_rfc1902('Gauge32') -> true; +allow_size_rfc1902(_) -> false. + +guess_integer_type() -> + {value,ASN1int} = lookup_vartype('INTEGER'), + ASN1int. + +guess_string_type() -> + {value,ASN1str} = lookup_vartype('OCTET STRING'), + ASN1str. + +lookup_vartype(Type) -> + CDATA = get(cdata), + lists:keysearch(Type, #asn1_type.aliasname, CDATA#cdata.asn1_types). + + + + +%%-------------------------------------------------- +%% Reads the oid-function files. +%% Out: A list of {oid, entry}. +%% oid is here either a Oid with integers, or +%% with symbolic names. +%% entry is {M,F,A}. +%%-------------------------------------------------- +read_funcs(FileName) -> + case snmpc_misc:read_noexit(FileName, fun check_of/1) of + {ok, Res} -> Res; + {error, LineNo, Reason} -> + print_error("~p: ~w: Syntax error: ~p", + [FileName, LineNo, Reason]), + []; + {error, open_file} -> [] + end. + +check_of({module, M}) when is_atom(M) -> + {ok, {module, M}}; +check_of({Oid, {M, F, A}}) when is_atom(M) andalso is_atom(F) andalso is_list(A) -> + {ok, {Oid, {M, F, A}}}; +check_of({_Oid, {M, F, A}}) -> + {invalid_argument, {M, F, A}}; +check_of(X) -> + {invalid_func, X}. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for IMPORT implementation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +import(ImportList) -> + %% Register some well known top-nodes (directly under the root) + WellKnownNodes = [ makeInternalNode(ccitt, [0]), + makeInternalNode(iso, [1]), + makeInternalNode('joint-iso-ccitt', [2]) ], + lists:foreach( + fun(#me{aliasname = AliasName, oid = Oid}) -> + register_oid(undef, AliasName, root, Oid) + end, + WellKnownNodes), + lists:foreach(fun import_mib/1, ImportList). + + +%%---------------------------------------------------------------------- +%% Returns: <nothing> only side effect stuff. +%%---------------------------------------------------------------------- +import_mib({{'SNMPv2-SMI', ImportsFromMib},Line}) -> + Nodes = [makeInternalNode(internet, [1,3,6,1]), + makeInternalNode(directory, [1,3,6,1,1]), + makeInternalNode(mgmt, [1,3,6,1,2]), + makeInternalNode('mib-2', [1,3,6,1,2,1]), + makeInternalNode(transmission, [1,3,6,1,2,1,10]), + makeInternalNode(experimental, [1,3,6,1,3]), + makeInternalNode(private, [1,3,6,1,4]), + makeInternalNode(enterprises, [1,3,6,1,4,1]), + makeInternalNode(zeroDotZero, [0,0]), + makeInternalNode(security, [1,3,6,1,5]), + makeInternalNode(snmpV2, [1,3,6,1,6]), + makeInternalNode(snmpDomains, [1,3,6,1,6,1]), + makeInternalNode(snmpProxys, [1,3,6,1,6,2]), + makeInternalNode(snmpModules, [1,3,6,1,6,3])], + Types = [#asn1_type{bertype = 'Integer32', + aliasname = 'Integer32', + lo = -2147483648, hi = 2147483647}, + #asn1_type{bertype = 'IpAddress', + aliasname = 'IpAddress', + lo = 4, hi = 4}, + #asn1_type{bertype = 'Counter32', + aliasname = 'Counter32', + lo = 0, hi = 4294967295}, + #asn1_type{bertype = 'Gauge32', + aliasname = 'Gauge32', + lo = 0, hi = 4294967295}, + #asn1_type{bertype = 'Unsigned32', + aliasname = 'Unsigned32', + lo = 0, hi = 4294967295}, + #asn1_type{bertype = 'TimeTicks', + aliasname = 'TimeTicks', + lo = 0, hi=4294967295}, + #asn1_type{bertype = 'Opaque', + aliasname = 'Opaque'}, + #asn1_type{bertype = 'Counter64', + aliasname = 'Counter64', + lo = 0, hi = 18446744073709551615}], + Macros = ['MODULE-IDENTITY','OBJECT-IDENTITY','OBJECT-TYPE', + 'NOTIFICATION-TYPE'], + import_built_in_loop(ImportsFromMib,Nodes,Types,Macros,'SNMPv2-SMI',Line); +import_mib({{'RFC-1215', ImportsFromMib},Line}) -> + Macros = ['TRAP-TYPE'], + import_built_in_loop(ImportsFromMib, [],[],Macros,'RFC-1215', Line); +import_mib({{'RFC-1212', ImportsFromMib},Line}) -> + Macros = ['OBJECT-TYPE'], + import_built_in_loop(ImportsFromMib, [],[],Macros,'RFC-1212', Line); +import_mib({{'SNMPv2-TC', ImportsFromMib},Line}) -> + Nodes = [], + Types = [#asn1_type{aliasname = 'DisplayString', + bertype = 'OCTET STRING', + lo = 0, hi = 255}, + #asn1_type{aliasname = 'PhysAddress', + bertype = 'OCTET STRING'}, + #asn1_type{aliasname = 'MacAddress', + bertype = 'OCTET STRING', + lo = 6, hi = 6}, + #asn1_type{aliasname = 'TruthValue', + bertype = 'INTEGER', + assocList = [{enums,[{false,2},{true,1}]}]}, + #asn1_type{aliasname = 'TestAndIncr', + bertype = 'INTEGER', + lo = 0, hi = 2147483647}, + #asn1_type{aliasname = 'AutonomousType', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'InstancePointer', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'VariablePointer', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'RowPointer', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'RowStatus', + bertype = 'INTEGER', + assocList = [{enums,[{destroy, 6}, + {createAndWait, 5}, + {createAndGo, 4}, + {notReady, 3}, + {notInService, 2}, + {active, 1}]}]}, + #asn1_type{aliasname = 'TimeStamp', + bertype = 'TimeTicks'}, + #asn1_type{aliasname = 'TimeInterval', + bertype = 'INTEGER', + lo = 0, hi = 2147483647}, + #asn1_type{aliasname = 'DateAndTime', + bertype = 'OCTET STRING', + lo = 8, hi = 11}, %% Actually 8 | 11 + #asn1_type{aliasname = 'StorageType', + bertype = 'INTEGER', + assocList = [{enums,[{readOnly, 5}, + {permanent, 4}, + {nonVolatile, 3}, + {volatile, 2}, + {other, 1}]}]}, + #asn1_type{aliasname = 'TDomain', + bertype = 'OBJECT IDENTIFIER'}, + #asn1_type{aliasname = 'TAddress', + bertype = 'OCTET STRING', + lo = 1, hi = 255} + ], + Macros = ['TEXTUAL-CONVENTION'], + import_built_in_loop(ImportsFromMib,Nodes,Types,Macros,'SNMPv2-TC',Line); +import_mib({{'SNMPv2-CONF', ImportsFromMib},Line}) -> + Macros = ['OBJECT-GROUP','NOTIFICATION-GROUP','MODULE-COMPLIANCE'], + import_built_in_loop(ImportsFromMib,[],[],Macros,'SNMPv2-CONF',Line); +import_mib({{'RFC1155-SMI', ImportsFromMib},Line}) -> + Nodes = [makeInternalNode(internet, [1,3,6,1]), + makeInternalNode(directory, [1,3,6,1,1]), + makeInternalNode(mgmt, [1,3,6,1,2]), + makeInternalNode(experimental, [1,3,6,1,3]), + makeInternalNode(private, [1,3,6,1,4]), + makeInternalNode(enterprises, [1,3,6,1,4,1])], + Types = [#asn1_type{bertype = 'NetworkAddress', + aliasname = 'NetworkAddress', lo = 4, hi = 4}, + #asn1_type{bertype='Counter',aliasname='Counter', + lo=0,hi=4294967295}, + #asn1_type{bertype='Gauge',aliasname='Gauge', + lo = 0, hi = 4294967295}, + #asn1_type{bertype='IpAddress',aliasname='IpAddress',lo=4,hi=4}, + #asn1_type{bertype = 'TimeTicks', aliasname = 'TimeTicks', + lo = 0, hi=4294967295}, + #asn1_type{bertype = 'Opaque', aliasname = 'Opaque'}], + Macros = ['OBJECT-TYPE'], + import_built_in_loop(ImportsFromMib,Nodes,Types,Macros,'RFC1155-SMI',Line); +import_mib({{MibName, ImportsFromMib},Line}) -> + import_from_file({{MibName, ImportsFromMib},Line}). + +import_built_in_loop(Objs, Nodes, Types, Macros, MibName, Line) -> + lists:foreach(fun (Obj) -> + import_built_in(Obj,Nodes,Types,Macros,MibName,Line) + end, Objs). + +import_from_file({{_MibName, []}, _Line}) -> + done; +import_from_file({{MibName, ImportsFromMib},Line}) -> + Filename = atom_to_list(MibName) ++ ".bin", + {value, Path} = snmpc_misc:assq(i, get(options)), + {value, LibPath} = snmpc_misc:assq(il,get(options)), + LibPath2 = include_lib(LibPath), + Path2 = Path++LibPath2++[filename:join(code:priv_dir(snmp),"mibs"), + "./"], + ImportedMib = case read_mib(Line,Filename, Path2) of + error -> + error("Could not import ~p from mib ~s. " + "File not found. " + "Check that the MIB to be IMPORTED " + "is compiled and present in the import path.", + [ImportsFromMib, Filename], Line); + Mib -> + Mib + end, + lists:foreach(fun (ImpObj) -> import(ImpObj,ImportedMib) end, + ImportsFromMib). + +import_built_in({_tag,Obj}, Nodes, Types, Macros, MibName, Line) -> + case lookup(Obj, Nodes) of + {value, ME} -> + register_oid(undef, ME#me.aliasname, root, ME#me.oid), + add_cdata(#cdata.mes, [ME#me{imported = true, oid = undefined}]); + false -> + case lists:keysearch(Obj, #asn1_type.aliasname, Types) of + {value, ASN1Type} -> + add_cdata(#cdata.asn1_types, + [ASN1Type#asn1_type{imported=true}]); + false -> + case lists:member(Obj, Macros) of + true -> + add_cdata(#cdata.imported_macros,[Obj]); + false -> + print_error("Cannot find '~w' in mib '~s'.", + [Obj, MibName], Line) + end + end + end. + +include_lib([]) -> []; +include_lib([Dir|Dirs]) -> + [Appl|Path] = filename:split(Dir), + case code:lib_dir(list_to_atom(Appl)) of + {error, _Reason} -> + include_lib(Dirs); + DirPath -> + [filename:join(DirPath,filename:join(Path))|include_lib(Dirs)] + end. + + +%%---------------------------------------------------------------------- +%% Returns: #mib +%%---------------------------------------------------------------------- +read_mib(_Line, _Filename, []) -> + error; +read_mib(Line, Filename, [Dir|Path]) -> + Dir2 = snmpc_misc:ensure_trailing_dir_delimiter(Dir), + case snmpc_misc:read_mib(AbsFile=lists:append(Dir2, Filename)) of + {ok, MIB} -> MIB; + {error, enoent} -> + read_mib(Line, Filename, Path); + {error, Reason} -> + ?vwarning("~s found but not imported: " + "~n Reason: ~p", [AbsFile,Reason]), + read_mib(Line, Filename, Path) + end. + + +%%---------------------------------------------------------------------- +%% imports ME or Type from other Mib into current compilation data. +%%---------------------------------------------------------------------- +import({node, NodeName}, #mib{mes = IMES, name = MibName}) -> + case lookup(NodeName, IMES) of + {value, ME} when ME#me.imported == false -> + register_oid(undef, ME#me.aliasname, root, ME#me.oid), + add_cdata(#cdata.mes, [ME#me{imported = true}]); + _ -> + print_error("Cannot find '~w' among the objects in the mib '~s'.", + [NodeName, MibName]) + end; +import({type, TypeName}, #mib{asn1_types = Types, name = MibName}) -> + case lists:keysearch(TypeName, #asn1_type.aliasname, Types) of + {value, ASN1Type} when is_record(ASN1Type, asn1_type) andalso + (ASN1Type#asn1_type.imported =:= false) -> + add_cdata(#cdata.asn1_types, [ASN1Type#asn1_type{imported=true, + aliasname=TypeName}]); + _X -> + print_error("Cannot find '~w' among the types in the mib '~s'.", + [TypeName, MibName]) + end; +import({builtin, Obj}, #mib{}) -> + print_error("~p should be imported from a standard mib.",[Obj]). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for initialisation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Types defined in rfc1155 (SMI) are hard coded. +init_types() -> + VerDep = case get(snmp_version) of + 1 -> []; + 2 -> + [#asn1_type{imported=true,bertype='BITS',aliasname='BITS'}] + end, + [#asn1_type{imported = true, bertype = 'INTEGER', aliasname = 'INTEGER'}, + #asn1_type{imported=true,bertype='OCTET STRING',aliasname='OCTET STRING'}, + #asn1_type{imported=true,bertype='BIT STRING',aliasname='BIT STRING'}, + #asn1_type{imported = true, bertype = 'OBJECT IDENTIFIER', + aliasname = 'OBJECT IDENTIFIER'} | VerDep]. + +makeInternalNode(Name, Oid) -> + makeInternalNode3(false, Name, Oid). + +makeInternalNode2(Imported, Name) -> + #me{imported = Imported, aliasname = Name, entrytype = internal}. + +makeInternalNode3(Imported, Name, Oid) -> + #me{imported = Imported, oid = Oid, aliasname = Name, entrytype = internal}. + +make_cdata(CDataArg) -> + MibFuncs = + case CDataArg of + {module, _Mod} -> [CDataArg]; + {file, MibFuncsFile} -> read_funcs(MibFuncsFile) + end, + #cdata{mibfuncs = MibFuncs, + asn1_types = init_types(), + oid_ets = ets:new(oid_ets, [set, private]), + status_ets = ets:new(status_ets, [set, private])}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for Intermib consistency checking +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +is_consistent(Filenames) -> + case catch check_all_consistency(Filenames) of + ok -> + ok; + {undef, Format, Data} -> + ok = io:format(Format, Data), + io:format("~n"), + {error, inconsistent} + end. + +check_all_consistency(Filenames) -> + MIBs = [load_mib(Filename) || Filename <- Filenames], + check_oid_conflicts(MIBs), + check_trap_conflicts(MIBs), + ok. + +check_oid_conflicts(MIBs) -> + MEs = lists:append( [get_elem(MIB, #mib.mes) || MIB <- MIBs] ), + SortedMEs = lists:keysort(#me.oid, MEs), + search_for_dublettes2(#me{aliasname=dummy_init}, SortedMEs). + +check_trap_conflicts(MIBs) -> + Traps = lists:append( [get_elem(MIB, #mib.traps) || MIB <- MIBs] ), + [check_trap(Trap, Traps) || Trap <- Traps]. + +check_trap(Trap, Traps) -> + %% check_trap/3 -> error | ok + Checked = [check_trap(T, Trap, undef) || T <- lists:delete(Trap, Traps)], + case lists:member(error, Checked) of + true -> + throw({undef,"",[]}); + false -> + ok + end. + + +%%---------------------------------------------------------------------- +%% Returns: {Oid, ASN1Type} +%%---------------------------------------------------------------------- +trap_variable_info(Variable, Type, MEs) -> + case lookup(Variable, MEs) of + false -> + error("Error in ~s definition. Cannot find object '~w'.", + [Type, Variable]); + {value, ME} when ME#me.entrytype == variable -> + {{variable, ME#me.aliasname}, ME#me.asn1_type}; + {value, ME} -> + {{column, ME#me.aliasname}, ME#me.asn1_type} + end. + +get_elem(MIB, Idx) -> + element(Idx, MIB). + +load_mib(Filename) -> + F1 = snmpc_misc:strip_extension_from_filename(Filename, ".mib"), + F2 = lists:append(F1, ".bin"), + case snmpc_misc:read_mib(F2) of + {error, Reason} -> + throw({undef, "Error reading file: ~w. Reason:~w", [F1, Reason]}); + {ok, Mib} -> + Mib + end. + +search_for_dublettes2(_PrevME, []) -> ok; +search_for_dublettes2(PrevME, [ME|MEs]) + when ME#me.imported==true -> + search_for_dublettes2(PrevME, MEs); +search_for_dublettes2(PrevME, [ME|MEs]) + when PrevME#me.oid == ME#me.oid -> + if PrevME#me.entrytype == internal, ME#me.entrytype == internal, + PrevME#me.aliasname == ME#me.aliasname -> + search_for_dublettes2(ME, MEs); + true -> + throw({undef,"Multiple used object with OBJECT IDENTIFIER '~w'" + " Used by '~w' and '~w' ", [PrevME#me.oid, + PrevME#me.aliasname, + ME#me.aliasname]}) + end; +search_for_dublettes2(_PrevME, [ME|MEs]) -> + search_for_dublettes2(ME, MEs). + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for handling of default value resolving +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +resolve_defval(ME) -> + case has_complex_defval(ME) of + true -> + CDATA = get(cdata), + resolve_complex_defval(ME, CDATA#cdata.mes); + false -> ME + end. + +has_complex_defval(#me{aliasname = N, + assocList = AssocList, + asn1_type = #asn1_type{bertype = BT}}) + when is_list(AssocList) -> + case snmpc_misc:assq(defval, AssocList) of + {value, Int} when is_integer(Int) -> + false; + {value, Val} when is_atom(Val) andalso (BT =:= 'OBJECT IDENTIFIER') -> + false; % resolved in update_me_oids + {value, Val} when is_atom(Val) andalso (BT =:= 'INTEGER') -> + true; + {value, Bits} when is_list(Bits) andalso (BT =:= 'BITS') -> + true; + {value, Str} when is_list(Str) andalso (BT =:= 'OCTET STRING') -> + false; % but ok + {value, Str} when is_list(Str) andalso (BT =:= 'Opaque') -> + false; % but ok + {value, Str} when is_list(Str) andalso + (length(Str) =:= 4) andalso + (BT =:= 'IpAddress') -> + false; % but ok + {value, Shit} -> + print_error("Bad default value for ~p: ~p [~p]",[N,Shit,BT]), + false; + false -> %% no defval (or strings nyi) + false + end; +has_complex_defval(_) -> false. + +resolve_complex_defval(ME, _AllMEs) + when (ME#me.asn1_type)#asn1_type.bertype == 'INTEGER' -> + #me{aliasname = MEName, assocList = AssocList} = ME, + {value, DefVal} = snmpc_misc:assq(defval, AssocList), + #asn1_type{bertype = TypeName, + assocList = AssocListForASN1Type} = ME#me.asn1_type, + case snmpc_misc:assq(enums, AssocListForASN1Type) of + false -> + print_error("Type '~w' has no defined enums. " + "Used in DEFVAL for '~w'.", [TypeName, MEName]), + ME; + {value, Enums} -> + case snmpc_misc:assq(DefVal, Enums) of + false -> + print_error("Enum '~w' not found. " + "Used in DEFVAL for '~w'.", [DefVal, MEName]), + ME; + {value, IntVal} when is_integer(IntVal) -> + ME#me{assocList = lists:keyreplace(defval, 1, AssocList, + {defval, IntVal})} + end + end; + +resolve_complex_defval(ME, _AllMEs) + when (ME#me.asn1_type)#asn1_type.bertype =:= 'BITS' -> + #me{aliasname = MEName, assocList = AssocList} = ME, + {value, DefVal} = snmpc_misc:assq(defval, AssocList), + #asn1_type{assocList = AssocListForASN1Type} = ME#me.asn1_type, + {value, Kibbles} = snmpc_misc:assq(kibbles, AssocListForASN1Type), + case snmpc_misc:bits_to_int(DefVal,Kibbles) of + error-> + print_error("Invalid default value ~w for ~w.",[DefVal, MEName]), + ME; + IntVal when is_integer(IntVal) -> + ME#me{assocList = lists:keyreplace(defval, 1, AssocList, + {defval, IntVal})} + end. + + +make_variable_info(#me{asn1_type = Asn1Type, assocList = Alist}) -> + Defval = + case snmpc_misc:assq(defval, Alist) of + {value, Val} -> + Val; + _ -> + get_def(Asn1Type) + end, + #variable_info{defval = Defval}. + +get_def(#asn1_type{bertype = BT, lo = LO, assocList = AL}) -> + ?vtrace("get_def -> entry with" + "~n BT: ~p" + "~n LO: ~p" + "~n AL: ~p", [BT, LO, AL]), + get_def(BT, LO, AL). + +get_def('INTEGER', Lo, _) when is_integer(Lo) -> Lo; +get_def('INTEGER', _, AL) -> + case snmpc_misc:assq(enums, AL) of + {value, Enums} -> + case lists:keysort(2, Enums) of + [{_, Val}|_] -> + Val; + _ -> + 0 + end; + _ -> + 0 + end; +get_def('Counter', _, _) -> 0; +get_def('Gauge', _, _) -> 0; +get_def('TimeTicks', _, _) -> 0; +get_def('OCTET STRING', _, _) -> ""; +get_def('IpAddress', _, _) -> [0,0,0,0]; +get_def('NetworkAddress', _, _) -> [0,0,0,0]; +get_def('OBJECT IDENTIFIER', _, _) -> [0, 0]; +get_def('Opaque', _, _) -> ""; +%v2 +get_def('Integer32',Lo, _) when is_integer(Lo) -> Lo; +get_def('Integer32',_, _) -> 0; +get_def('Counter32',_, _) -> 0; +get_def('Gauge32',_, _) -> 0; +get_def('Unsigned32',_, _) -> 0; +get_def('BITS',_, _) -> 0; +get_def('Counter64',_, _) -> 0. + +check_trap_name(EnterpriseName, Line, MEs) -> + case lists:keysearch(EnterpriseName, #me.aliasname, MEs) of + false -> + error("Error in trap definition. Cannot find object '~w'.", + [EnterpriseName],Line); + {value, _} -> + true + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for table functions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%---------------------------------------------------------------------- +%% This information is needed to be able to create default instrumentation +%% functions for tables. +%%---------------------------------------------------------------------- +make_table_info(Line, _TableName, {augments,SrcTableEntry}, ColumnMEs) -> + ColMEs = lists:keysort(#me.oid, ColumnMEs), + %% Nbr_of_Cols = length(ColMEs), + MEs = ColMEs ++ (get(cdata))#cdata.mes, + Aug = case lookup(SrcTableEntry,MEs) of + false -> + print_error("Cannot AUGMENT the non-existing table entry ~p", + [SrcTableEntry],Line), + {augments, error}; + {value,ME} -> + {augments, {SrcTableEntry,translate_type(ME#me.asn1_type)}} + end, + #table_info{index_types = Aug}; +make_table_info(Line, TableName, {indexes,[]}, _ColumnMEs) -> + print_error("Table ~w lacks indexes.", [TableName],Line), + #table_info{}; +make_table_info(Line, TableName, {indexes,Indexes}, ColumnMEs) -> + ColMEs = lists:keysort(#me.oid, ColumnMEs), + NonImpliedIndexes = lists:map(fun non_implied_name/1, Indexes), + test_read_create_access(ColMEs, Line, dummy), + NonIndexCol = test_index_positions(Line, NonImpliedIndexes, ColMEs), + Nbr_of_Cols = length(ColMEs), + ASN1Indexes = find_asn1_types_for_indexes(Indexes, ColMEs, Line), + FA = first_accessible(TableName, ColMEs), + StatCol = find_status_col(Line, TableName, ColMEs), + NoAccs = list_not_accessible(NonIndexCol,ColMEs), + case lists:member(StatCol,NoAccs) of + true -> + print_error("Status column cannot be not-accessible. In table ~p.", + [TableName],Line); + false -> ok + end, + #table_info{nbr_of_cols = Nbr_of_Cols, + first_own_index = find_first_own_index(NonImpliedIndexes, + ColMEs, 1), + status_col = StatCol, + first_accessible = FA, + not_accessible = NoAccs, + index_types = ASN1Indexes}. + +%% Perkins p110 +test_read_create_access([#me{aliasname = N, access = 'read-create'}|_ColMEs], + Line, 'read-write') -> + print_error("Column ~p cannot be read-create when another is read-write.", + [N], Line); +test_read_create_access([#me{aliasname = N, access = 'read-write'}|_ColMEs], + Line, 'read-create') -> + print_error("Column ~p cannot be read-write when another is read-create.", + [N], Line); +test_read_create_access([#me{access = 'read-write'}|ColMEs], + Line, _OtherStat) -> + test_read_create_access(ColMEs,Line,'read-write'); +test_read_create_access([#me{access = 'read-create'}|ColMEs], + Line, _OtherStat) -> + test_read_create_access(ColMEs,Line,'read-create'); +test_read_create_access([_ME|ColMEs],Line,OtherStat) -> + test_read_create_access(ColMEs,Line,OtherStat); +test_read_create_access([], _Line, _) -> + ok. + +find_status_col(_Line, _TableName, []) -> + undefined; +find_status_col(_Line, _TableName, + [#me{asn1_type=#asn1_type{aliasname='RowStatus'}}|_]) -> + 1; +find_status_col(Line, TableName, [_ShitME | MEs]) -> + case find_status_col(Line, TableName, MEs) of + undefined -> undefined; + N -> 1+N + end. + +list_not_accessible(none,_) -> []; +list_not_accessible(NonIndexCol, ColMEs) when is_integer(NonIndexCol) -> + list_not_accessible(lists:nthtail(NonIndexCol - 1,ColMEs)). + +list_not_accessible([#me{access='not-accessible', oid=Col}|ColMEs]) -> + [Col | list_not_accessible(ColMEs)]; +list_not_accessible([_ColME|ColMEs]) -> + list_not_accessible(ColMEs); +list_not_accessible([]) -> + []. + +%%---------------------------------------------------------------------- +%% See definition of first_own_index in the table_info record definition. +%%---------------------------------------------------------------------- +find_first_own_index([], _ColMEs, _FOI) -> 0; +find_first_own_index([NameOfIndex | Indexes], ColMEs, FOI) -> + case lists:keysearch(NameOfIndex, #me.aliasname, ColMEs) of + {value, _ME} -> + FOI; + false -> + find_first_own_index(Indexes, ColMEs, FOI + 1) + end. + +first_accessible(TableName, []) -> + error("Table '~w' must have at least one accessible column.",[TableName]); +first_accessible(TableName, [#me{access = 'not-accessible'} | T]) -> + first_accessible(TableName, T); +first_accessible(_TableName, [#me{oid = Col} | _]) -> + Col. + +get_defvals(ColMEs) -> + lists:keysort(1, + lists:filter(fun drop_undefined/1, + lists:map(fun column_and_defval/1, ColMEs))). + +find_asn1_types_for_indexes(Indexes, ColMEs,Line) -> + MEs = ColMEs ++ (get(cdata))#cdata.mes, + test_implied(Indexes, Line), + lists:map(fun (ColumnName) -> + translate_type(get_asn1_type(ColumnName, MEs,Line)) + end, + Indexes). + +test_implied([],_) -> ok; +test_implied([{implied, _Type}, _OtherIndexElem|_], Line) -> + print_error("Implied must be last.", [], Line); +test_implied([{implied, _Type}], _Line) -> + ok; +test_implied([_H|T], Line) -> + test_implied(T, Line). + +drop_undefined({_X, undefined}) -> false; +drop_undefined({_X, _Y}) -> true; +drop_undefined(undefined) -> false; +drop_undefined(_X) -> true. + +%% returns: {ColumnNo, Defval} +column_and_defval(#me{oid = Oid, assocList = AssocList}) -> + ColumnNo = lists:last(Oid), + case snmpc_misc:assq(defval, AssocList) of + false -> {ColumnNo, undefined}; + {value, DefVal} -> {ColumnNo, DefVal} + end. + +%% returns: an asn1_type if ColME is an indexfield, otherwise undefined. +get_asn1_type({implied,ColumnName}, MEs, Line) -> + case lookup(ColumnName, MEs) of + {value,#me{asn1_type=A}} when A#asn1_type.bertype =:= + 'OCTET STRING' -> + A#asn1_type{implied = true}; + {value,#me{asn1_type=A}} when A#asn1_type.bertype =:= + 'OBJECT IDENTIFIER' -> + A#asn1_type{implied = true}; + Shit -> + print_error("Only OCTET STRINGs and OIDs can be IMPLIED.(~w)", + [Shit], Line) + end; +get_asn1_type(ColumnName, MEs, Line) -> + case lookup(ColumnName, MEs) of + {value,ME} -> ME#me.asn1_type; + false -> error("Can't find object ~p. Used as INDEX in table.", + [ColumnName],Line) + end. + +test_index_positions(Line, Indexes, ColMEs) -> + TLI = lists:filter(fun (IndexName) -> + is_table_local_index(IndexName,ColMEs) end, + Indexes), + test_index_positions_impl(Line, TLI, ColMEs). + +%% Returns the first non-index column | none +test_index_positions_impl(_Line, [], []) -> none; +test_index_positions_impl(_Line, [], [#me{oid=Col}|_ColMEs]) -> + Col; +test_index_positions_impl(Line, Indexes, + [#me{aliasname = Name, + asn1_type = Asn1} | ColMEs]) -> + case lists:member(Name, Indexes) of + true -> + if + Asn1#asn1_type.bertype =:= 'BITS' -> + print_error("Invalid data type 'BITS' for index '~w'.", + [Name],Line); + true -> true + end, + test_index_positions_impl(Line, + lists:delete(Name, Indexes), ColMEs); + false -> + ?vwarning2("Index columns must be first for " + "the default functions to work properly. " + "~w is no index column.", [Name], Line), + none + end. + +is_table_local_index(IndexName, ColMEs) -> + case lists:keysearch(IndexName, #me.aliasname, ColMEs) of + false -> false; + _Q -> true + end. + +non_implied_name({implied, IndexColumnName}) -> IndexColumnName; +non_implied_name(IndexColumnName) -> IndexColumnName. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for generationg the final mib +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% returns: {ok, +% {snmp_mib, MEs, traps, list of {TrapOid, list of oids (objects)}}} +get_final_mib(Name, Options) -> + ?vdebug("get_final_mib -> entry", []), + CDATA = get(cdata), + #cdata{mes = MEs, + mibfuncs = MibFuncs, + asn1_types = Types, + traps = Traps0, + oid_ets = OidEts} = CDATA, + + ?vdebug("get_final_mib -> resolve oids", []), + resolve_oids(OidEts), + %% Reverse so that we get report on objects earlier in the file + %% before later objects. + UMEs = update_me_oids(lists:reverse(MEs), OidEts, []), + ?vtrace("get_final_mib -> " + "~n UMEs: ~p", [UMEs]), + + Traps1 = update_trap_objects(Traps0, MEs, []), + Traps2 = update_trap_oids(Traps1, OidEts, []), + ?vtrace("get_final_mib -> " + "~n Traps2: ~p", [Traps2]), + + SortedMEs = lists:keysort(#me.oid,UMEs), + ?vdebug("get_final_mib -> search for dublettes", []), + search_for_dublettes(#me{aliasname=dummy_init}, SortedMEs), + + ?vdebug("get_final_mib -> search for oid conflicts", []), + search_for_oid_conflicts(Traps2, SortedMEs), + + ?vdebug("get_final_mib -> resolve oid", []), + %% FIXME: use list comprehension instead + MibFs = lists:keysort(1, + lists:zf(fun({module, _Mod}) -> false; + (MF) -> {true, resolve_oid(MF,SortedMEs)} + end, MibFuncs)), + ?vtrace("get_final_mib -> " + "~n MibFs: ~p", [MibFs]), + {value, DBName} = snmpc_misc:assq(db, Options), + Module = key1search(module, MibFuncs, undefined), + MEsWithMFA = insert_mfa(MibFs, SortedMEs, DBName, Module), + Misc = [{snmp_version,get(snmp_version)} + | case lists:member(no_symbolic_info,Options) of + true -> [no_symbolic_info]; + false -> [] + end], + {value, GroupBool} = snmpc_misc:assq(group_check, Options), + case GroupBool of + true -> + case get(snmp_version) =:= 2 of + true -> + ?vdebug("get_final_mib -> check object groups:" + "~n ~p", [CDATA#cdata.objectgroups]), + check_group(CDATA#cdata.mes, + CDATA#cdata.objectgroups), + ?vdebug("get_final_mib -> check notifications group:" + "~n ~p", [CDATA#cdata.notificationgroups]), + check_notification(Traps2, + CDATA#cdata.notificationgroups); + false -> + ok + end; + false -> + ok + end, + MI = module_identity(CDATA), + Mib = #mib{name = Name, + misc = Misc, + module_identity = MI, + mes = lists:map(fun(ME) -> translate_me_type(ME) end, + MEsWithMFA), + variable_infos = extract_variable_infos(MEsWithMFA), + table_infos = extract_table_infos(MEsWithMFA), + traps = lists:map(fun(T) -> translate_trap_type(T) end, + Traps2), + asn1_types = lists:map(fun(T) -> translate_type(T) end, + Types), + imports = CDATA#cdata.imports}, + ?vdebug("get_final_mib -> done", []), + {ok, Mib}. + + +module_identity(#cdata{module_identity = MI}) -> + case lists:member(module_identity, get(options)) of + true -> + MI; + false -> + undefined + end. + + +update_trap_objects([], _MEs, Acc) -> + ?vtrace("update_trap_objects -> done", []), + lists:reverse(Acc); +update_trap_objects([#trap{trapname = Name, + oidobjects = Variables} = Trap|Traps], MEs, Acc) -> + ?vtrace("update_trap_objects -> update objects for trap ~p:" + "~n ~p", [Name, Variables]), + OidObjects = + [trap_variable_info(Var, "trap", MEs) || Var <- Variables], + UpdTrap = Trap#trap{oidobjects = OidObjects}, + update_trap_objects(Traps, MEs, [UpdTrap|Acc]); +update_trap_objects([#notification{trapname = Name, + oidobjects = Variables} = Notif|Traps], + MEs, Acc) -> + ?vtrace("update_trap_objects -> update objects for notification ~p:" + "~n ~p", [Name, Variables]), + OidObjects = + [trap_variable_info(Var, "notification", MEs) || Var <- Variables], + UpdNotif = Notif#notification{oidobjects = OidObjects}, + update_trap_objects(Traps, MEs, [UpdNotif|Acc]); +update_trap_objects([_|Traps], MEs, Acc) -> + update_trap_objects(Traps, MEs, Acc). + + +%% We don't want a zillion aliases for INTEGER (etc), +%% and since they are encoded with the same tag we can treat them as +%% equivalent. +%% The reason for having them at compile time is for example that +%% Unsigned32 is allowed as INDEX but not Gauge. +%% The compiler might want to ensure this and more... +translate_me_type(ME) -> + ME#me{asn1_type = translate_type(ME#me.asn1_type)}. + +translate_trap_type(Trap) when is_record(Trap, notification) -> + translate_trap_type_notif(Trap); +translate_trap_type(Trap) when is_record(Trap, trap) -> + translate_trap_type_trap(Trap). + +translate_trap_type_notif(Trap)-> + NewOidobjects = + lists:map(fun({Oid,ASN1type}) ->{Oid,translate_type(ASN1type)} end, + Trap#notification.oidobjects), + Trap#notification{oidobjects=NewOidobjects}. + +translate_trap_type_trap(Trap)-> + NewOidobjects = + lists:map(fun({Oid,ASN1type}) -> + {Oid, translate_type(ASN1type)} + end, + Trap#trap.oidobjects), + Trap#trap{oidobjects = NewOidobjects}. + +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'NetworkAddress' -> + ASN1type#asn1_type{bertype = 'IpAddress'}; +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Integer32' -> + ASN1type#asn1_type{bertype = 'INTEGER'}; +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Counter' -> + ASN1type#asn1_type{bertype = 'Counter32'}; +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Gauge' -> + ASN1type#asn1_type{bertype = 'Unsigned32'}; +translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Gauge32' -> + ASN1type#asn1_type{bertype = 'Unsigned32'}; +translate_type(ASN1type) -> ASN1type. + +%% Check for both NOTIFICATION-GROUP and OBJECT-GROUP + +check_notification_group(Name, GroupObjects, Line, Status) -> + #cdata{traps = Traps, status_ets = Ets} = get(cdata), + Objects = get_notification_names(Traps), + check_def(notification, Name, Line, Status, GroupObjects, Objects, Ets). + +get_notification_names(Traps) when is_list(Traps) -> + [Name || #notification{trapname = Name} <- Traps]. + +check_object_group(Name, GroupObjects, Line, Status) -> + #cdata{mes = MEs, status_ets = Ets} = get(cdata), + Objects = get_object_names(MEs), + check_def(object, Name, Line, Status, GroupObjects, Objects, Ets). + +get_object_names([])->[]; +get_object_names([#me{access=A, entrytype=T, aliasname=N}|MEs]) + when (A =/= 'not-accessible') andalso (T =/= 'internal') -> + [N|get_object_names(MEs)]; +get_object_names([_ME|Rest]) -> + get_object_names(Rest). + +%% Strictly we should not need to check more then the status +%% table, but since error do happen... +check_def(Type, Name, Line, Status, [GroupObject|GroupObjects], Objects, Ets) -> + ?vdebug2("check definition of ~p [~p]: presumed member of ~p [~p]", + [GroupObject, Type, Name, Status], Line), + case lists:member(GroupObject, Objects) of + true -> + ?vtrace("~p is a member of ~p", [GroupObject, Name]), + %% Lucky so far, now lets check that the status is valid + case ets:lookup(Ets, GroupObject) of + [{GroupObject, ObjectStatus}] -> + ?vtrace("check that the object status (~p) is valid", + [ObjectStatus]), + check_group_member_status(Name, Status, + GroupObject, ObjectStatus); + _ -> + print_error("group (~w) member ~w not found" + " in status table - status check failed", + [Name, GroupObject]) + end; + false -> + %% Ok, this could be because the status is obsolete or + %% deprecated (with the deprecated flag = false) + ?vtrace("~p is not a member of ~p " + "[object status could be obsolete]", + [GroupObject, Name]), + case ets:lookup(Ets, GroupObject) of + [{GroupObject, ObjectStatus}] -> + ?vtrace("check that the object status (~p) is valid", + [ObjectStatus]), + check_group_member_status(Name, Status, + GroupObject, ObjectStatus); + _ -> + group_member_error(Type, GroupObject, Line) + end + end, + check_def(Type, Name, Line, Status, GroupObjects, Objects, Ets); +check_def(_, _, _, _, [], _, _) -> + ok. + +group_member_error(object, Name, Line) -> + print_error("OBJECT-TYPE definition missing or " + "'not-accessible' for '~w'", [Name],Line); +group_member_error(notification, Name, Line) -> + print_error("NOTIFICATION-TYPE definition missing for '~w'", + [Name], Line). + + +check_group_member_status(_GroupName, _GroupStatus, _Member, undefined) -> + ok; +check_group_member_status(_GroupName, current, _Member, current) -> + ok; +check_group_member_status(GroupName, current, Member, MemberStatus) -> + group_member_status_error(GroupName, current, Member, MemberStatus, + "current"); +check_group_member_status(_GroupName, deprecated, _Member, MemberStatus) + when (MemberStatus =:= deprecated) orelse (MemberStatus =:= current) -> + ok; +check_group_member_status(GroupName, deprecated, Member, MemberStatus) -> + group_member_status_error(GroupName, deprecated, Member, MemberStatus, + "deprecated or current"); +check_group_member_status(_GroupName, obsolete, _Member, MemberStatus) + when (MemberStatus =:= obsolete) orelse + (MemberStatus =:= deprecated) orelse + (MemberStatus =:= current) -> + ok; +check_group_member_status(GroupName, obsolete, Member, MemberStatus) -> + group_member_status_error(GroupName, obsolete, Member, MemberStatus, + "obsolete, deprecated or current"); +check_group_member_status(_GroupName, _GroupStatus, _Member, _MemberStatus) -> + ok. + +group_member_status_error(Name, Status, Member, MemberStatus, Expected) -> + snmpc_lib:print_error("Invalid status of group member ~p " + "in group ~p. " + "Group status is ~p " + "and member status is ~p " + "(should be ~s)", + [Member, Name, Status, MemberStatus, Expected]). + + + +% check_def(Objects,[GroupObject|GroupObjects],Line)-> +% case lists:member(GroupObject,Objects) of +% false -> +% print_error("OBJECT-TYPE definition missing or " +% "'not-accessible' for '~w'", [GroupObject],Line), +% check_def(Objects,GroupObjects,Line); +% true -> +% check_def(Objects,GroupObjects,Line) +% end; +% check_def(_Objects,[],_Line) -> +% ok. + +%% Checking the definition of OBJECT-GROUP + +%%----------------------------- +check_group([#me{imported = true} | T],GroupObjects)-> + ?vtrace("check_group(imported) -> skip", []), + check_group(T,GroupObjects); +check_group([],_GroupObjects) -> + ?vtrace("check_group -> done", []), + ok; +check_group([#me{access = A, + entrytype = T, + aliasname = N}|MEs], GroupObjects) + when ((A =/= 'not-accessible') andalso + (T =/= 'internal') andalso + (T =/= group)) -> + ?vtrace("check_group -> " + "~n access: ~p" + "~n entrytype: ~p" + "~n aliasname: ~p", [A, T, N]), + check_member_group(N, GroupObjects), + check_group(MEs,GroupObjects); +check_group([_|MEs],GroupObjects) -> + check_group(MEs,GroupObjects). + +check_member_group(Aliasname, [])-> + print_error("'~w' missing in OBJECT-GROUP",[Aliasname]); +check_member_group(Aliasname, [{Name,GroupObject,Line}|Tl])-> + ?vtrace2("check_member_group -> entry with" + "~n Aliasname: ~p" + "~n Name: ~p" + "~n GroupObject: ~p", [Aliasname, Name, GroupObject], Line), + case lists:member(Aliasname,GroupObject) of + true -> + ok; + false -> + check_member_group(Aliasname,Tl) + end. + + +%% Checking definition in NOTIFICATION-GROUP + +%%-------------------------- +check_notification([],_NotificationObjects) -> + ok; +check_notification([#notification{trapname=Aliasname}|Traps], + NotificationObjects) -> + check_member_notification(Aliasname, NotificationObjects), + check_notification(Traps,NotificationObjects); +check_notification([_|Traps],NotificationObjects) -> + check_notification(Traps,NotificationObjects). + +check_member_notification(Aliasname,[])-> + print_error("'~w' missing in NOTIFICATION-GROUP",[Aliasname]); +check_member_notification(Aliasname,[{_Name,NotificationObject,_Line}|Tl]) -> + case lists:member(Aliasname, NotificationObject) of + true -> + ok; + false -> + check_member_notification(Aliasname,Tl) + end. + + +%%---------------------------------------------------------------------- +%% Purpose: Resolves oids for aliasnames used in .funcs file. +%% Returns: {Oid, X} +%%---------------------------------------------------------------------- +resolve_oid({NameOrOid, X}, MEs) -> + case lookup(NameOrOid, MEs) of + {value, #me{oid=Oid,entrytype=variable}} -> {Oid, X}; + {value, #me{oid=Oid,entrytype=table}} -> {Oid, X}; + {value, #me{entrytype=table_entry}} -> + error("Cannot associate an instrumentation function with a " + "Table Entry: ~w (must be table or variable)", + [NameOrOid]); + {value, #me{entrytype=table_column}} -> + error("Cannot associate an instrumentation function with a " + "Table Column: ~w (must be table or variable)", + [NameOrOid]); + _Q -> + error("Cannot find OBJECT-TYPE definition for '~w'.", + [NameOrOid]) + end. + +%%---------------------------------------------------------------------- +%% Fs is list of {Oid, {M,F,A}} +%% returns: MEs with access-functions. +%% Pre: Fs, MEs are sorted (on Oid) (then we can traverse mib efficiently) +%%---------------------------------------------------------------------- +insert_mfa(Fs, [ME | MEs], DBName, Mod) + when ME#me.imported =:= true -> + [ME | insert_mfa(Fs, MEs, DBName, Mod)]; + +insert_mfa(Fs, [ME | MEs], DBName, Mod) + when ME#me.entrytype =:= internal -> + [ME | insert_mfa(Fs, MEs, DBName, Mod)]; + +insert_mfa(Fs, [ME|MEs], DBName, Mod) + when ME#me.entrytype =:= group -> + [ME | insert_mfa(Fs, MEs, DBName, Mod)]; + +insert_mfa([X | Fs], [ME | MEs], DBName, Mod) + when ME#me.entrytype =:= variable -> + {Oid, {M,F,A}} = X, + case ME#me.oid of + Oid -> + [ME#me{mfa = {M,F,A}} | insert_mfa(Fs, MEs, DBName, Mod)]; + _Q -> + [insert_default_mfa(ME, DBName, Mod) | + insert_mfa([X | Fs], MEs, DBName, Mod)] + end; + +insert_mfa([X | Fs], [TableME | MEs], DBName, Mod) + when TableME#me.entrytype =:= table -> + {Oid, {M,F,A}} = X, + {TableMEs, RestMEs} = collect_mes_for_table(TableME, [TableME | MEs]), + [TableEntryME | ColMEs] = tl(TableMEs), + DefVals = get_defvals(ColMEs), + {value,TableInfo} = snmpc_misc:assq(table_info,TableME#me.assocList), + NAssocList = [{table_info, TableInfo#table_info{defvals = DefVals}} | + lists:keydelete(table_info, 1, TableME#me.assocList)], + NTableME = TableME#me{assocList = NAssocList}, + case is_same_table(Oid, NTableME#me.oid) of + true -> % use mfa from .funcs + lists:append([NTableME, + TableEntryME#me{mfa = {M, F, A}} + | ColMEs], + insert_mfa(Fs, RestMEs, DBName, Mod)); + false -> + lists:append(insert_default_mfa([NTableME | tl(TableMEs)], + DBName, Mod), + insert_mfa([X|Fs], RestMEs, DBName, Mod)) + end; + +insert_mfa([], [ME|MEs], DBName, Mod) + when ME#me.entrytype =:= variable -> + [insert_default_mfa(ME, DBName, Mod) | insert_mfa([], MEs, DBName, Mod)]; + +insert_mfa([], [ME|MEs], DBName, Mod) + when ME#me.entrytype =:= table -> + {TableMEs, RestMEs} = collect_mes_for_table(ME, [ME|MEs]), + [TableME, _TableEntryME | ColMEs] = TableMEs, + DefVals = get_defvals(ColMEs), + {value,TableInfo} = snmpc_misc:assq(table_info,TableME#me.assocList), + NAssocList = [{table_info, TableInfo#table_info{defvals = DefVals}} | + lists:keydelete(table_info, 1, TableME#me.assocList)], + NTableME = TableME#me{assocList = NAssocList}, + NewTableMEs = insert_default_mfa([NTableME | tl(TableMEs)], DBName, Mod), + lists:append(NewTableMEs, insert_mfa([], RestMEs, DBName, Mod)); + +insert_mfa([], [], _DBName, _Mod) -> + []; +insert_mfa([], [ME|_MEs], _DBName, _Mod) -> + error("Missing access-functions for '~w'.",[ME#me.aliasname]). + +%%---------------------------------------------------------------------- +%% Returns: {[TableME, TableEntryME | ColumnMEs], RestMEs} +%%---------------------------------------------------------------------- +collect_mes_for_table(_TableME, []) -> + {[], []}; + +collect_mes_for_table(TableME, [ME|MEs]) -> + case is_same_table(TableME#me.oid, ME#me.oid) of + true -> + {TableMEs, RestMEs} = collect_mes_for_table(TableME, MEs), + {[ME | TableMEs], RestMEs}; + false -> + {[], [ME | MEs]} + end. + +%% returns: MibEntry with access-functions. +insert_default_mfa(ME, DBName, undefined) when is_record(ME, me)-> + case lists:member(no_defs, get(options)) of + true -> + error("Missing access function for ~s", [ME#me.aliasname]); + false -> + ?vinfo("No accessfunction for '~w' => using default", + [ME#me.aliasname]), + set_default_function(ME, DBName) + end; + +insert_default_mfa(ME, _DBName, Mod) when is_record(ME, me)-> + ME#me{mfa = {Mod, ME#me.aliasname, []}}; + +insert_default_mfa([TableME, EntryME | Columns], DBName, undefined) -> + case lists:member(no_defs, get(options)) of + true -> + error("Missing access function for ~s", [TableME#me.aliasname]); + false -> + ?vinfo("No accessfunction for '~w' => using default", + [TableME#me.aliasname]), + set_default_function([TableME, EntryME | Columns], DBName) + end; + +insert_default_mfa([TableME, EntryME | Columns], _DBName, Mod) -> + [TableME, + EntryME#me{mfa = {Mod, TableME#me.aliasname, []}} | + Columns]. + + +%% returns boolean. +is_same_table(Oid, TableOid) -> + lists:prefix(Oid, TableOid). + +%% returns false | {value, ME} +lookup(UniqName, MEs) when is_atom(UniqName) -> + lists:keysearch(UniqName, #me.aliasname, MEs); +lookup(Oid, MEs) when is_list(Oid) -> + lists:keysearch(Oid, #me.oid, MEs). + +search_for_dublettes(PrevME, [ME|_MEs]) + when PrevME#me.oid =:= ME#me.oid -> + error("Multiple used object with OBJECT IDENTIFIER '~w'. " + "Used in '~w' and '~w'.", [PrevME#me.oid, + PrevME#me.aliasname, + ME#me.aliasname]); +search_for_dublettes(PrevME, [ME|MEs]) + when ((PrevME#me.entrytype =:= variable) andalso + (ME#me.entrytype =:= variable)) -> + case lists:prefix(PrevME#me.oid, ME#me.oid) of + true -> + error("Variable '~w' (~w) defined below other " + "variable '~w' (~w). ", + [ME#me.aliasname, ME#me.oid, + PrevME#me.aliasname, PrevME#me.oid]); + false -> + search_for_dublettes(ME, MEs) + end; +search_for_dublettes(_PrevME, [ME|MEs]) -> + search_for_dublettes(ME, MEs); +search_for_dublettes(_PrevME, []) -> + ok. + + +search_for_oid_conflicts([Rec|Traps],MEs) when is_record(Rec,notification) -> + #notification{oid = Oid, trapname = Name} = Rec, + case search_for_oid_conflicts1(Oid,MEs) of + {error,ME} -> + error("Notification with OBJECT IDENTIFIER '~w'. " + "Used in '~w' and '~w'.", [Oid,Name,ME#me.aliasname]); + ok -> + search_for_oid_conflicts(Traps,MEs) + end; +search_for_oid_conflicts([_Trap|Traps],MEs) -> + search_for_oid_conflicts(Traps,MEs); +search_for_oid_conflicts([],_MEs) -> + ok. + +search_for_oid_conflicts1(_Oid,[]) -> + ok; +search_for_oid_conflicts1(Oid,[ME|_MEs]) when Oid == ME#me.oid -> + {error,ME}; +search_for_oid_conflicts1(Oid,[_ME|MEs]) -> + search_for_oid_conflicts1(Oid,MEs). + +set_default_function([TableMe, EntryMe | ColMes], DBName) -> + #me{aliasname = Name} = TableMe, + check_rowstatus(TableMe), + [TableMe, + EntryMe#me{mfa = {snmp_generic, table_func, [{Name, DBName}]}} | + ColMes]; + +set_default_function(MibEntry,DBName) when MibEntry#me.entrytype == variable -> + #me{aliasname = Aname} = MibEntry, + MibEntry#me{mfa = {snmp_generic, variable_func, [{Aname, DBName}]}}. + +check_rowstatus(TableME) -> + {value,TableInfo} = snmpc_misc:assq(table_info,TableME#me.assocList), + case TableInfo#table_info.status_col of + undefined -> + ?vwarning("No RowStatus column in table ~w => " + "The default functions won't work properly", + [TableME#me.aliasname]); + _Q -> ok + end. + +check_trap(#trap{trapname=N1, specificcode=C, enterpriseoid=E}, + #trap{trapname=N2, specificcode=C, enterpriseoid=E},Line) -> + print_error("Trap code collision. Enterprise: ~w. Trapcode: ~w, " + "Name of traps: ~w, ~w.", [E, C, N1, N2],Line), + error; +check_trap(#trap{trapname=N, specificcode=C1, enterpriseoid=E1}, + #trap{trapname=N, specificcode=C2, enterpriseoid=E2},Line) -> + print_error("Trap name collision. Name: ~w Enterprises: ~w, ~w. " + "Trapcodes: ~w, ~w", [N, E1, E2, C1, C2],Line), + error; +check_trap(_OldTrap, _ThisTrap, _Line) -> + ok. + +check_notification(Notif, Line, Notifs) -> + lists:map(fun (OtherNotif) -> + check_notification1(Notifs,OtherNotif,Line) + end, lists:delete(Notif,Notifs)). + +check_notification1(#notification{trapname=N},#notification{trapname=N},Line)-> + print_error("Trap name collision for '~w.",[N],Line); +check_notification1(#notification{oid=Oid},#notification{oid=Oid},Line)-> + print_error("Trap oid collision for '~w.",[Oid],Line); +check_notification1(_T1, _T2, _L) -> + ok. + +%%---------------------------------------------------------------------- +%% Returns: list of {VariableName, variable_info-record} +%%---------------------------------------------------------------------- +extract_variable_infos([]) -> []; +extract_variable_infos([#me{entrytype = variable, + assocList = AL, + aliasname = Name} | T]) -> + {value, VI} = snmpc_misc:assq(variable_info, AL), + [{Name, VI} | extract_variable_infos(T)]; +extract_variable_infos([_ME | T]) -> + extract_variable_infos(T). + +%%---------------------------------------------------------------------- +%% Returns: list of {TableName, table_info-record} +%%---------------------------------------------------------------------- +extract_table_infos([]) -> []; +extract_table_infos([#me{entrytype = table, + assocList = AL, + aliasname = Name} | T]) -> + {value, VI} = snmpc_misc:assq(table_info, AL), + [{Name, VI} | extract_table_infos(T)]; +extract_table_infos([_ME | T]) -> + extract_table_infos(T). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for misc useful functions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% m(M) -> {?MODULE, M}. + +set_dir(File, NewDir) -> + case string:chr(File, $/) of + 0 -> lists:append(NewDir, File); + N -> set_dir(lists:nthtail(N,File), NewDir) + end. + +%% Look up a key in a list, and if found returns the value +%% or if not found returns the default value +key1search(Key, List) -> + key1search(Key, List, undefined). + +key1search(Key, List, Default) -> + case lists:keysearch(Key, 1, List) of + {value, {Key, Val}} -> Val; + _ -> Default + end. + + +%% print the compiled mib +look_at(FileName) -> + case file:read_file(FileName) of + {ok,Bin} -> + binary_to_term(Bin); + {error,Reason} -> + {error,Reason} + end. + + +%% Data is appended to compiler information +add_cdata(OffsetInRecord, ListOfData) -> + CDATA = get(cdata), + OldData = element(OffsetInRecord, CDATA), + put(cdata, setelement(OffsetInRecord, CDATA, lists:append(ListOfData, + OldData))), + undefined. + +check_sub_ids([H | _T], Name, Line) when H < 0 -> + error("OBJECT IDENTIFIER must have all sub " + "indexes > 0. Name: '~w'. Illegal sub index: ~w.", + [Name, H], Line); +check_sub_ids([H | _T], Name, Line) when H > 4294967295 -> + error("OBJECT IDENTIFIER must have all sub " + "indexes < 4294967295. Name: '~w'. Illegal sub index: ~w.", + [Name, H], Line); +check_sub_ids([_H | T], Name, Line) -> + check_sub_ids(T, Name, Line); +check_sub_ids([], _Name, _Line) -> + ok. + + +%%----------------------------------------------------------------- +%% Handle forward references: +%% This code handles OIDs that are defined in terms of a +%% parent OID that is defined later in the file. Ex: +%% x OBJECT IDENTIFIER ::= {y 1} +%% y OBJECT IDENTIFIER ::= {enterprises 1} +%% The following alg is used to handle this: +%% Datastructure: +%% An ets table, with one entry for each object in the mib: +%% {Name, FatherName, Line, SubIndex, Children} +%% Name : aliasname +%% FatherName : aliasname of parent object +%% SubIndex : list of subindexes from parent object +%% Children : list of aliasnames for all objects registered +%% under this one +%% FatherName == 'root' => top-level object +%% 1) When an OID is found in the mib, it is registered using +%% register_oid/4. This function updates the parent entry, +%% by adding the new name to its Children list. It also +%% updates the entry for the object defined. +%% 2) When all objects are registered, the ets table contains +%% a directed graph of all objects. +%% 3) resolve_oids/1 is called. This function traverses the +%% graph, starting at 'root', and changes each entry to +%% {Name, Line, Oid}, where Oid is a list of integers. +%% 4) The list of MibEntries is traversed. Each object is +%% looked up in the ets table, and the correspsonding oid +%% is updated. The functions for this is update_me_oids/2 +%% and update_trap_oids/2. +%%----------------------------------------------------------------- +register_oid(Line, Name, ParentName, SubIndex) when Name =/= '$no_name$' -> + ?vtrace2("register_oid -> entry with" + "~n Name: ~p" + "~n ParentName: ~p" + "~n SubIndex: ~p", [Name, ParentName, SubIndex], Line), + check_sub_ids(SubIndex, Name, Line), + OidEts = (get(cdata))#cdata.oid_ets, + %% Lookup Parent - if it doesn't already exist, create it + {_ParentName, ItsParentName, ItsLine, ItsSubIndex, Children} = + case ets:lookup(OidEts, ParentName) of + [Found] -> + ?vtrace("register_oid -> parent found: " + "~n ~p", [Found]), + Found; + [] -> + ?vtrace("register_oid -> father not found: " + "~n ~p", [ets:tab2list(OidEts)]), + {ParentName, undef, undef, [], []} + end, + %% Update Father with a pointer to us + NChildren = case lists:member(Name, Children) of + true -> Children; + false -> [Name | Children] + end, + NParent = {ParentName, ItsParentName, ItsLine, ItsSubIndex, NChildren}, + ?vtrace("register_oid -> NParent: ~n~p", [NParent]), + ets:insert(OidEts, NParent), + %% Lookup ourselves - if we don't exist, create us + MyChildren = + case ets:lookup(OidEts, Name) of + [Found2] -> + ?vtrace("register_oid -> children found: " + "~n ~p", [Found2]), + element(5, Found2); + [] -> + ?vtrace("register_oid -> no children found", []), + [] + end, + %% Update ourselves + NSelf = {Name, ParentName, Line, SubIndex, MyChildren}, + ?vtrace("register_oid -> NSelf: " + "~n ~p", [NSelf]), + ets:insert(OidEts, NSelf); +register_oid(_Line, _Name, _ParentName, _SubIndex) -> + ok. + + +resolve_oids(OidEts) -> + case ets:lookup(OidEts, root) of + [{_, _, _, _, RootChildren}] -> + resolve_oids(RootChildren, [], OidEts); + [] -> + ok + end. + +resolve_oids([Name | T], FatherOid, OidEts) -> + {MyOid, MyChildren, MyLine} = + case ets:lookup(OidEts, Name) of + [{Name, Oid, Line}] -> + print_error("Circular OBJECT IDENTIFIER definitions " + "involving ~w\n", [Name], Line), + {Oid, [], Line}; + [{Name, _Father, Line, SubIndex, Children}] -> + {FatherOid ++ SubIndex, Children, Line} + end, + ets:insert(OidEts, {Name, MyOid, MyLine}), + resolve_oids(T, FatherOid, OidEts), + resolve_oids(MyChildren, MyOid, OidEts); +resolve_oids([], _, _) -> + ok. + + + +update_me_oids([], _OidEts, Acc) -> + lists:reverse(Acc); +update_me_oids([#me{aliasname = '$no_name$'} | Mes], OidEts, Acc) -> + update_me_oids(Mes, OidEts, Acc); +update_me_oids([Me | Mes], OidEts, Acc) -> + ?vtrace("update_me_oids -> entry with" + "~n Me: ~p", [Me]), + Oid = tr_oid(Me#me.aliasname, OidEts), + NMe = resolve_oid_defval(Me, OidEts), + update_me_oids(Mes, OidEts, [NMe#me{oid = Oid} | Acc]). + +update_trap_oids([], _OidEts, Acc) -> + lists:reverse(Acc); +update_trap_oids([#trap{enterpriseoid = EOid, + oidobjects = OidObjs} = Trap | Traps], + OidEts, Acc) -> + ?vtrace("update_trap_oids -> entry with" + "~n EOid: ~p", [EOid]), + NEnter = tr_oid(EOid, OidEts), + NOidObjs = tr_oid_objs(OidObjs, OidEts), + NTrap = Trap#trap{enterpriseoid = NEnter, + oidobjects = NOidObjs}, + update_trap_oids(Traps, OidEts, [NTrap|Acc]); +update_trap_oids([#notification{trapname = Name, + oidobjects = OidObjs} = Notif | Traps], + OidEts, Acc) -> + ?vtrace("update_trap_oids -> entry with" + "~n Name: ~p", [Name]), + Oid = tr_oid(Name, OidEts), + NOidObjs = tr_oid_objs(OidObjs, OidEts), + NNotif = Notif#notification{oid = Oid, oidobjects = NOidObjs}, + update_trap_oids(Traps, OidEts, [NNotif|Acc]). + +tr_oid(Name, OidEts) -> + ?vtrace("tr_oid -> entry with" + "~n Name: ~p", [Name]), + case ets:lookup(OidEts, Name) of + [{Name, MyOid, _MyLine}] -> + MyOid; + [{_Natrap, Parent, Line, SubIndex, _Children}] -> + print_error("OBJECT IDENTIFIER [~w] defined in terms " + "of undefined parent object. Parent: '~w'." + "(Sub-indexes: ~w.)", + [Name, Parent, SubIndex], Line), + ?vtrace("tr_oid -> ets:tab2list(~w): " + "~n ~p", [OidEts, ets:tab2list(OidEts)]), + rnd_oid() + end. + +tr_oid_objs([{{variable, Name}, Type} | T], OidEts) -> + ?vtrace("tr_oid_objs(variable) -> entry with" + "~n Name: ~p", [Name]), + Oid = tr_oid(Name, OidEts) ++ [0], + [{Oid, Type} | tr_oid_objs(T, OidEts)]; +tr_oid_objs([{{column, Name}, Type} | T], OidEts) -> + ?vtrace("tr_oid_objs(column) -> entry with" + "~n Name: ~p", [Name]), + Oid = tr_oid(Name, OidEts), + [{Oid, Type} | tr_oid_objs(T, OidEts)]; +tr_oid_objs([], _OidEts) -> + []. + + +resolve_oid_defval(ME, OidEts) + when (ME#me.asn1_type)#asn1_type.bertype == 'OBJECT IDENTIFIER' -> + #me{aliasname = MEName, assocList = AssocList} = ME, + case snmpc_misc:assq(defval, AssocList) of + {value, DefVal} when is_atom(DefVal) -> + case ets:lookup(OidEts, DefVal) of + [{_, Oid, _}] -> + ME#me{assocList = lists:keyreplace(defval, 1, AssocList, + {defval, Oid})}; + _ -> + print_error("Can not find OBJECT-TYPE definition for '~w' " + "Used in DEFVAL for '~w'.", [DefVal, MEName]), + ME + end; + _ -> + ME + end; +resolve_oid_defval(ME, _OidEts) -> + ME. + +rnd_oid() -> + [99,99]. %% '99' means "stop computer" in Y2Kish... + +error(FormatStr, Data) when is_list(FormatStr) -> + print_error(FormatStr,Data), + exit(error). + +error(FormatStr, Data, Line) when is_list(FormatStr) -> + print_error(FormatStr,Data,Line), + exit(error). + +print_error(FormatStr, Data) when is_list(FormatStr) -> + ok = io:format("~s: Error: " ++ FormatStr,[get(filename)|Data]), + put(errors,yes), + io:format("~n"). + +print_error(FormatStr, Data,Line) when is_list(FormatStr) -> + ok = io:format("~s: ~w: Error: " ++ FormatStr,[get(filename), Line |Data]), + put(errors,yes), + io:format("~n"). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Section for debug functions +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +vprint(Severity, Mod, Line, MibLine, F, A) -> + case printable(Severity) of + standard when is_integer(MibLine) -> + io:format("[~s:~w][~s]: " ++ F ++ "~n", + [get(filename), MibLine, image_of_severity(Severity)|A]); + standard -> + io:format("[~s][~s]: " ++ F ++ "~n", + [get(filename), image_of_severity(Severity)|A]); + extended when is_integer(MibLine) -> + io:format("[~s:~w][~w:~w][~s]: " ++ F ++ "~n", + [get(filename), MibLine, Mod, Line, + image_of_severity(Severity)|A]); + extended -> + io:format("[~s][~w:~w][~s]: " ++ F ++ "~n", + [get(filename), Mod, Line, + image_of_severity(Severity)|A]); + _ -> + ok + end. + +printable(Severity) -> + printable(get(verbosity), Severity). + +printable(silence, _) -> none; +printable(warning, warning) -> standard; +printable(info, info) -> standard; +printable(info, warning) -> standard; +printable(log, warning) -> standard; +printable(log, info) -> standard; +printable(log, log) -> standard; +printable(debug, warning) -> standard; +printable(debug, info) -> standard; +printable(debug, log) -> standard; +printable(debug, debug) -> standard; +printable(trace, _Sev) -> extended; +printable(_Ver, _Sev) -> none. + +-spec image_of_severity(Sev :: severity()) -> string(). +image_of_severity(warning) -> "WAR"; +image_of_severity(info) -> "INF"; +image_of_severity(log) -> "LOG"; +image_of_severity(debug) -> "DBG"; +image_of_severity(trace) -> "TRC"; +image_of_severity(_) -> " - ". + + +vvalidate(silence) -> ok; +vvalidate(warning) -> ok; +vvalidate(info) -> ok; +vvalidate(log) -> ok; +vvalidate(debug) -> ok; +vvalidate(trace) -> ok; +vvalidate(V) -> exit({invalid_verbosity,V}). + + diff --git a/lib/snmp/src/compile/snmpc_lib.hrl b/lib/snmp/src/compile/snmpc_lib.hrl new file mode 100644 index 0000000000..000486e728 --- /dev/null +++ b/lib/snmp/src/compile/snmpc_lib.hrl @@ -0,0 +1,37 @@ +%% +%% %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% +%% + +-ifndef(snmpc_lib). +-define(snmpc_lib, true). + +-define(vwarning(F, A), ?verbosity(warning, F, A, ignore)). +-define(vwarning2(F, A, MibLine), ?verbosity(warning, F, A, MibLine)). +-define(vinfo(F, A), ?verbosity(info, F, A, ignore)). +-define(vinfo2(F, A, MibLine), ?verbosity(info, F, A, MibLine)). +-define(vlog(F, A), ?verbosity(log, F, A, ignore)). +-define(vlog2(F, A, MibLine), ?verbosity(log, F, A, MibLine)). +-define(vdebug(F, A), ?verbosity(debug, F, A, ignore)). +-define(vdebug2(F, A, MibLine), ?verbosity(debug, F, A, MibLine)). +-define(vtrace(F, A), ?verbosity(trace, F, A, ignore)). +-define(vtrace2(F, A, MibLine), ?verbosity(trace, F, A, MibLine)). + +-define(verbosity(Severity, F, A, MibLine), + snmpc_lib:vprint(Severity, ?MODULE, ?LINE, MibLine, F, A)). + +-endif. % -ifndef(snmpc_lib). diff --git a/lib/snmp/src/compile/snmpc_mib_gram.yrl b/lib/snmp/src/compile/snmpc_mib_gram.yrl new file mode 100644 index 0000000000..1957f52936 --- /dev/null +++ b/lib/snmp/src/compile/snmpc_mib_gram.yrl @@ -0,0 +1,973 @@ +%% +%% %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% +%% + +%%---------------------------------------------------------------------- +%% Number of expected shift/reduce warnings +%% This is ugly but... +%%---------------------------------------------------------------------- + +Expect 2. + + +%% ---------------------------------------------------------------------- +Nonterminals +%% ---------------------------------------------------------------------- +accessv1 +definition +defvalpart +description +descriptionfield +displaypart +entry +namedbits +fatherobjectname +fieldname +fields +implies +import +import_stuff +imports +imports_from_one_mib +index +indexpartv1 +indextypev1 +indextypesv1 +parentintegers +listofdefinitions +listofimports +mib +mibname +nameassign +newtype +newtypename +objectidentifier +objectname +objecttypev1 +range_num +referpart +size +sizedescr +statusv1 +syntax +tableentrydefinition +traptype +type +usertype +variables +varpart + +%v2 +moduleidentity +revisionpart +revisions +listofdefinitionsv2 +mibid +last_updated +oranization +contact_info +revision +revision_string +revision_desc +v1orv2 +objectidentity +objecttypev2 +unitspart +indexpartv2 +indextypesv2 +indextypev2 +statusv2 +accessv2 +notification +objectspart +objects +definitionv2 +textualconvention +objectgroup +notificationgroup +modulecompliance +modulepart +modules +module +modulenamepart +mandatorypart +compliancepart +compliances +compliance +compliancegroup +object +syntaxpart +writesyntaxpart +accesspart +fsyntax +defbitsvalue +defbitsnames +. +%% ---------------------------------------------------------------------- +Terminals +%% ---------------------------------------------------------------------- +integer variable atom string quote '{' '}' '::=' ':' '=' ',' '.' '(' ')' ';' '|' +'ACCESS' +'BEGIN' +'BIT' +'Counter' +'DEFINITIONS' +'DEFVAL' +'DESCRIPTION' +'DISPLAY-HINT' +'END' +'ENTERPRISE' +'FROM' +'Gauge' +'IDENTIFIER' +'IMPORTS' +'INDEX' +'INTEGER' +'IpAddress' +'NetworkAddress' +'OBJECT' +'OBJECT-TYPE' +'OCTET' +'OF' +'Opaque' +'REFERENCE' +'SEQUENCE' +'SIZE' +'STATUS' +'STRING' +'SYNTAX' +'TRAP-TYPE' +'TimeTicks' +'VARIABLES' + +%v2 +'LAST-UPDATED' +'ORGANIZATION' +'CONTACT-INFO' +'MODULE-IDENTITY' +'NOTIFICATION-TYPE' +'MODULE-COMPLIANCE' +'OBJECT-GROUP' +'NOTIFICATION-GROUP' +'REVISION' +'OBJECT-IDENTITY' +'MAX-ACCESS' +'UNITS' +'AUGMENTS' +'IMPLIED' +'OBJECTS' +'TEXTUAL-CONVENTION' +'NOTIFICATIONS' +'MODULE' +'MANDATORY-GROUPS' +'GROUP' +'WRITE-SYNTAX' +'MIN-ACCESS' +'BITS' +'DisplayString' +'PhysAddress' +'MacAddress' +'TruthValue' +'TestAndIncr' +'AutonomousType' +'InstancePointer' +'VariablePointer' +'RowPointer' +'RowStatus' +'TimeStamp' +'TimeInterval' +'DateAndTime' +'StorageType' +'TDomain' +'TAddress' +. + + +Rootsymbol mib. +Endsymbol '$end'. + +% ********************************************************************** + +mib -> mibname 'DEFINITIONS' implies 'BEGIN' + import v1orv2 'END' + : {Version, Defs} = '$6', + #pdata{mib_version = Version, + mib_name = '$1', + imports = '$5', + defs = Defs}. + +v1orv2 -> moduleidentity listofdefinitionsv2 : + {v2_mib, ['$1'|lists:reverse('$2')]}. +v1orv2 -> listofdefinitions : {v1_mib, lists:reverse('$1')}. + +definition -> objectidentifier : '$1'. +definition -> objecttypev1 : '$1'. +definition -> newtype : '$1'. +definition -> tableentrydefinition : '$1'. +definition -> traptype : '$1'. + +listofdefinitions -> definition : ['$1'] . +listofdefinitions -> listofdefinitions definition : ['$2' | '$1']. + +import -> '$empty' : []. +import -> 'IMPORTS' imports ';' : '$2'. + +imports -> imports_from_one_mib : ['$1']. +imports -> imports_from_one_mib imports : ['$1' | '$2']. + +imports_from_one_mib -> listofimports 'FROM' variable : + {{val('$3'), lists:reverse('$1')}, line_of('$2')}. + +listofimports -> import_stuff : ['$1']. +listofimports -> listofimports ',' import_stuff : ['$3' | '$1']. + +import_stuff -> 'OBJECT-TYPE' : {builtin, 'OBJECT-TYPE'}. +import_stuff -> 'TRAP-TYPE' : {builtin, 'TRAP-TYPE'}. +import_stuff -> 'NetworkAddress' : {builtin, 'NetworkAddress'}. +import_stuff -> 'TimeTicks' : {builtin, 'TimeTicks'}. +import_stuff -> 'IpAddress' : {builtin, 'IpAddress'}. +import_stuff -> 'Counter' : {builtin, 'Counter'}. +import_stuff -> 'Gauge' : {builtin, 'Gauge'}. +import_stuff -> 'Opaque' : {builtin, 'Opaque'}. +import_stuff -> variable : filter_v2imports(get(snmp_version), val('$1')). +import_stuff -> atom : {node, val('$1')}. +%%v2 +import_stuff -> 'MODULE-IDENTITY' + : ensure_ver(2,'$1'), {builtin, 'MODULE-IDENTITY'}. +import_stuff -> 'NOTIFICATION-TYPE' + : ensure_ver(2,'$1'), {builtin, 'NOTIFICATION-TYPE'}. +import_stuff -> 'MODULE-COMPLIANCE' + : ensure_ver(2,'$1'), {builtin, 'MODULE-COMPLIANCE'}. +import_stuff -> 'NOTIFICATION-GROUP' + : ensure_ver(2,'$1'), {builtin, 'NOTIFICATION-GROUP'}. +import_stuff -> 'OBJECT-GROUP' + : ensure_ver(2,'$1'), {builtin, 'OBJECT-GROUP'}. +import_stuff -> 'OBJECT-IDENTITY' + : ensure_ver(2,'$1'), {builtin, 'OBJECT-IDENTITY'}. +import_stuff -> 'TEXTUAL-CONVENTION' + : ensure_ver(2,'$1'), {builtin, 'TEXTUAL-CONVENTION'}. +import_stuff -> 'DisplayString' + : ensure_ver(2,'$1'), {builtin, 'DisplayString'}. +import_stuff -> 'PhysAddress' + : ensure_ver(2,'$1'), {builtin, 'PhysAddress'}. +import_stuff -> 'MacAddress' + : ensure_ver(2,'$1'), {builtin, 'MacAddress'}. +import_stuff -> 'TruthValue' + : ensure_ver(2,'$1'), {builtin, 'TruthValue'}. +import_stuff -> 'TestAndIncr' + : ensure_ver(2,'$1'), {builtin, 'TestAndIncr'}. +import_stuff -> 'AutonomousType' + : ensure_ver(2,'$1'), {builtin, 'AutonomousType'}. +import_stuff -> 'InstancePointer' + : ensure_ver(2,'$1'), {builtin, 'InstancePointer'}. +import_stuff -> 'VariablePointer' + : ensure_ver(2,'$1'), {builtin, 'VariablePointer'}. +import_stuff -> 'RowPointer' + : ensure_ver(2,'$1'), {builtin, 'RowPointer'}. +import_stuff -> 'RowStatus' + : ensure_ver(2,'$1'), {builtin, 'RowStatus'}. +import_stuff -> 'TimeStamp' + : ensure_ver(2,'$1'), {builtin, 'TimeStamp'}. +import_stuff -> 'TimeInterval' + : ensure_ver(2,'$1'), {builtin, 'TimeInterval'}. +import_stuff -> 'DateAndTime' + : ensure_ver(2,'$1'), {builtin, 'DateAndTime'}. +import_stuff -> 'StorageType' + : ensure_ver(2,'$1'), {builtin, 'StorageType'}. +import_stuff -> 'TDomain' + : ensure_ver(2,'$1'), {builtin, 'TDomain'}. +import_stuff -> 'TAddress' + : ensure_ver(2,'$1'), {builtin, 'TAddress'}. + +traptype -> objectname 'TRAP-TYPE' 'ENTERPRISE' objectname varpart + description referpart implies integer : + Trap = make_trap('$1', '$4', lists:reverse('$5'), + '$6', '$7', val('$9')), + {Trap, line_of('$2')}. + +% defines a name to an internal node. +objectidentifier -> objectname 'OBJECT' 'IDENTIFIER' nameassign : + {Parent, SubIndex} = '$4', + Int = make_internal('$1', dummy, Parent, SubIndex), + {Int, line_of('$2')}. + +% defines name, access and type for a variable. +objecttypev1 -> objectname 'OBJECT-TYPE' + 'SYNTAX' syntax + 'ACCESS' accessv1 + 'STATUS' statusv1 + 'DESCRIPTION' descriptionfield + referpart indexpartv1 defvalpart + nameassign : + Kind = kind('$13', '$12'), + OT = make_object_type('$1', '$4', '$6', '$8', '$10', + '$11', Kind, '$14'), + {OT, line_of('$2')}. + +newtype -> newtypename implies syntax : + NT = make_new_type('$1', dummy, '$3'), + {NT, line_of('$2')}. + +tableentrydefinition -> newtypename implies 'SEQUENCE' '{' fields '}' : + Seq = make_sequence('$1', lists:reverse('$5')), + {Seq, line_of('$3')}. + +% returns: list of {<fieldname>, <asn1_type>} +fields -> fieldname fsyntax : + [{val('$1'), '$2'}]. + +fields -> fields ',' fieldname fsyntax : [{val('$3'), '$4'} | '$1']. + +fsyntax -> 'BITS' : {{bits,[{dummy,0}]},line_of('$1')}. +fsyntax -> syntax : '$1'. + +fieldname -> atom : '$1'. + +syntax -> usertype : {{type, val('$1')}, line_of('$1')}. +syntax -> type : {{type, cat('$1')},line_of('$1')}. +syntax -> type size : {{type_with_size, cat('$1'), '$2'},line_of('$1')}. +syntax -> usertype size : {{type_with_size,val('$1'), '$2'},line_of('$1')}. +syntax -> 'INTEGER' '{' namedbits '}' : + {{integer_with_enum, 'INTEGER', '$3'}, line_of('$1')}. +syntax -> 'BITS' '{' namedbits '}' : + ensure_ver(2,'$1'), + {{bits, '$3'}, line_of('$1')}. +syntax -> 'SEQUENCE' 'OF' usertype : + {{sequence_of,val('$3')},line_of('$1')}. + +size -> '(' sizedescr ')' : make_range('$2'). +size -> '(' 'SIZE' '(' sizedescr ')' ')' : make_range('$4'). + +%% Returns a list of integers describing a range. +sizedescr -> range_num '.' '.' range_num : ['$1', '$4']. +sizedescr -> range_num '.' '.' range_num sizedescr :['$1', '$4' |'$5']. +sizedescr -> range_num : ['$1']. +sizedescr -> sizedescr '|' sizedescr : ['$1', '$3']. + +range_num -> integer : val('$1') . +range_num -> quote atom : make_range_integer(val('$1'), val('$2')) . +range_num -> quote variable : make_range_integer(val('$1'), val('$2')) . + +namedbits -> atom '(' integer ')' : [{val('$1'), val('$3')}]. +namedbits -> namedbits ',' atom '(' integer ')' : + [{val('$3'), val('$5')} | '$1']. + +usertype -> variable : '$1'. + +type -> 'OCTET' 'STRING' : {'OCTET STRING', line_of('$1')}. +type -> 'BIT' 'STRING' : {'BIT STRING', line_of('$1')}. +type -> 'OBJECT' 'IDENTIFIER' : {'OBJECT IDENTIFIER', line_of('$1')}. +type -> 'INTEGER' : '$1'. +type -> 'NetworkAddress' : '$1'. +type -> 'IpAddress' : '$1'. +type -> 'Counter' : ensure_ver(1,'$1'),'$1'. +type -> 'Gauge' : ensure_ver(1,'$1'),'$1'. +type -> 'TimeTicks' : '$1'. +type -> 'Opaque' : '$1'. +type -> 'DisplayString' : ensure_ver(2,'$1'), '$1'. +type -> 'PhysAddress' : ensure_ver(2,'$1'), '$1'. +type -> 'MacAddress' : ensure_ver(2,'$1'), '$1'. +type -> 'TruthValue' : ensure_ver(2,'$1'), '$1'. +type -> 'TestAndIncr' : ensure_ver(2,'$1'), '$1'. +type -> 'AutonomousType' : ensure_ver(2,'$1'), '$1'. +type -> 'InstancePointer' : ensure_ver(2,'$1'), '$1'. +type -> 'VariablePointer' : ensure_ver(2,'$1'), '$1'. +type -> 'RowPointer' : ensure_ver(2,'$1'), '$1'. +type -> 'RowStatus' : ensure_ver(2,'$1'), '$1'. +type -> 'TimeStamp' : ensure_ver(2,'$1'), '$1'. +type -> 'TimeInterval' : ensure_ver(2,'$1'), '$1'. +type -> 'DateAndTime' : ensure_ver(2,'$1'), '$1'. +type -> 'StorageType' : ensure_ver(2,'$1'), '$1'. +type -> 'TDomain' : ensure_ver(2,'$1'), '$1'. +type -> 'TAddress' : ensure_ver(2,'$1'), '$1'. + +% Returns: {FatherName, SubIndex} (the parent) +nameassign -> implies '{' fatherobjectname parentintegers '}' : {'$3', '$4' }. +nameassign -> implies '{' parentintegers '}' : { root, '$3'}. + + +varpart -> '$empty' : []. +varpart -> 'VARIABLES' '{' variables '}' : '$3'. +variables -> objectname : ['$1']. +variables -> variables ',' objectname : ['$3' | '$1']. + +implies -> '::=' : '$1'. +implies -> ':' ':' '=' : w("Sloppy asignment on line ~p", [line_of('$1')]), '$1'. +descriptionfield -> string : lists:reverse(val('$1')). +descriptionfield -> '$empty' : undefined. +description -> 'DESCRIPTION' string : lists:reverse(val('$2')). +description -> '$empty' : undefined. + +displaypart -> 'DISPLAY-HINT' string : display_hint('$2') . +displaypart -> '$empty' : undefined . + +% returns: {indexes, undefined} +% | {indexes, IndexList} where IndexList is a list of aliasnames. +indexpartv1 -> 'INDEX' '{' indextypesv1 '}' : {indexes, lists:reverse('$3')}. +indexpartv1 -> '$empty' : {indexes, undefined}. + +indextypesv1 -> indextypev1 : ['$1']. +indextypesv1 -> indextypesv1 ',' indextypev1 : ['$3' | '$1']. + +indextypev1 -> index : '$1'. + +index -> objectname : '$1'. + +parentintegers -> integer : [val('$1')]. +parentintegers -> atom '(' integer ')' : [val('$3')]. +parentintegers -> integer parentintegers : [val('$1') | '$2']. +parentintegers -> atom '(' integer ')' parentintegers : [val('$3') | '$5']. + +defvalpart -> 'DEFVAL' '{' integer '}' : {defval, val('$3')}. +defvalpart -> 'DEFVAL' '{' atom '}' : {defval, val('$3')}. +defvalpart -> 'DEFVAL' '{' '{' defbitsvalue '}' '}' : {defval, '$4'}. +defvalpart -> 'DEFVAL' '{' quote atom '}' + : {defval, make_defval_for_string(line_of('$1'), lists:reverse(val('$3')), + val('$4'))}. +defvalpart -> 'DEFVAL' '{' quote variable '}' + : {defval, make_defval_for_string(line_of('$1'), lists:reverse(val('$3')), + val('$4'))}. +defvalpart -> 'DEFVAL' '{' string '}' + : {defval, lists:reverse(val('$3'))}. +defvalpart -> '$empty' : undefined. + +defbitsvalue -> defbitsnames : '$1'. +defbitsvalue -> '$empty' : []. + +defbitsnames -> atom : [val('$1')]. +defbitsnames -> defbitsnames ',' atom : [val('$3') | '$1']. + +objectname -> atom : val('$1'). +mibname -> variable : val('$1'). +fatherobjectname -> objectname : '$1'. +newtypename -> variable : val('$1'). + +accessv1 -> atom: accessv1('$1'). + +statusv1 -> atom : statusv1('$1'). + +referpart -> 'REFERENCE' string : lists:reverse(val('$2')). +referpart -> '$empty' : undefined. + + +%%---------------------------------------------------------------------- +%% SNMPv2 grammatics +%%v2 +%%---------------------------------------------------------------------- +moduleidentity -> mibid 'MODULE-IDENTITY' + 'LAST-UPDATED' last_updated + 'ORGANIZATION' oranization + 'CONTACT-INFO' contact_info + 'DESCRIPTION' descriptionfield + revisionpart nameassign : + MI = make_module_identity('$1', '$4', '$6', '$8', + '$10', '$11', '$12'), + {MI, line_of('$2')}. + +mibid -> atom : val('$1'). +last_updated -> string : lists:reverse(val('$1')) . +oranization -> string : lists:reverse(val('$1')) . +contact_info -> string : lists:reverse(val('$1')) . + +revisionpart -> '$empty' : [] . +revisionpart -> revisions : lists:reverse('$1') . + +revisions -> revision : ['$1'] . +revisions -> revisions revision : ['$2' | '$1'] . +revision -> 'REVISION' revision_string 'DESCRIPTION' revision_desc : + make_revision('$2', '$4') . + +revision_string -> string : lists:reverse(val('$1')) . +revision_desc -> string : lists:reverse(val('$1')) . + +definitionv2 -> objectidentifier : '$1'. +definitionv2 -> objecttypev2 : '$1'. +definitionv2 -> textualconvention : '$1'. +definitionv2 -> objectidentity : '$1'. +definitionv2 -> newtype : '$1'. +definitionv2 -> tableentrydefinition : '$1'. +definitionv2 -> notification : '$1'. +definitionv2 -> objectgroup : '$1'. +definitionv2 -> notificationgroup : '$1'. +definitionv2 -> modulecompliance : '$1'. + +listofdefinitionsv2 -> '$empty' : [] . +listofdefinitionsv2 -> listofdefinitionsv2 definitionv2 : ['$2' | '$1']. + +textualconvention -> newtypename implies 'TEXTUAL-CONVENTION' displaypart + 'STATUS' statusv2 description referpart 'SYNTAX' syntax : + NT = make_new_type('$1', 'TEXTUAL-CONVENTION', '$4', + '$6', '$7', '$8', '$10'), + {NT, line_of('$3')}. + +objectidentity -> objectname 'OBJECT-IDENTITY' 'STATUS' statusv2 + 'DESCRIPTION' string referpart nameassign : + {Parent, SubIndex} = '$8', + Int = make_internal('$1', 'OBJECT-IDENTITY', + Parent, SubIndex), + {Int, line_of('$2')}. + +objectgroup -> objectname 'OBJECT-GROUP' objectspart + 'STATUS' statusv2 description referpart nameassign : + OG = make_object_group('$1', '$3', '$5', '$6', '$7', '$8'), + {OG, line_of('$2')}. + +notificationgroup -> objectname 'NOTIFICATION-GROUP' 'NOTIFICATIONS' '{' + objects '}' 'STATUS' statusv2 description referpart + nameassign : + NG = make_notification_group('$1', '$5', '$8', '$9', + '$10', '$11'), + {NG, line_of('$2')}. + +modulecompliance -> objectname 'MODULE-COMPLIANCE' 'STATUS' statusv2 + description referpart modulepart nameassign : + MC = make_module_compliance('$1', '$4', '$5', '$6', + '$7', '$8'), + {MC, line_of('$2')}. + +modulepart -> '$empty'. +modulepart -> modules. + +modules -> module. +modules -> modules module. + +module -> 'MODULE' modulenamepart mandatorypart compliancepart. + +modulenamepart -> mibname. +modulenamepart -> '$empty'. + +mandatorypart -> 'MANDATORY-GROUPS' '{' objects '}'. +mandatorypart -> '$empty'. + +compliancepart -> compliances. +compliancepart -> '$empty'. + +compliances -> compliance. +compliances -> compliances compliance. + +compliance -> compliancegroup. +compliance -> object. + +compliancegroup -> 'GROUP' objectname description. + +object -> 'OBJECT' objectname syntaxpart writesyntaxpart accesspart description. + +syntaxpart -> 'SYNTAX' syntax. +syntaxpart -> '$empty'. + +writesyntaxpart -> 'WRITE-SYNTAX' syntax. +writesyntaxpart -> '$empty'. + +accesspart -> 'MIN-ACCESS' accessv2. +accesspart -> '$empty'. + +objecttypev2 -> objectname 'OBJECT-TYPE' + 'SYNTAX' syntax + unitspart + 'MAX-ACCESS' accessv2 + 'STATUS' statusv2 + 'DESCRIPTION' descriptionfield + referpart indexpartv2 defvalpart + nameassign : + Kind = kind('$14', '$13'), + OT = make_object_type('$1', '$4', '$5', '$7', '$9', + '$11', '$12', Kind, '$15'), + {OT, line_of('$2')}. + +indexpartv2 -> 'INDEX' '{' indextypesv2 '}' : {indexes, lists:reverse('$3')}. +indexpartv2 -> 'AUGMENTS' '{' entry '}' : {augments, '$3'}. +indexpartv2 -> '$empty' : {indexes, undefined}. + +indextypesv2 -> indextypev2 : ['$1']. +indextypesv2 -> indextypesv2 ',' indextypev2 : ['$3' | '$1']. + +indextypev2 -> 'IMPLIED' index : {implied,'$2'}. +indextypev2 -> index : '$1'. + +entry -> objectname : '$1'. + +unitspart -> '$empty' : undefined. +unitspart -> 'UNITS' string : units('$2') . + +statusv2 -> atom : statusv2('$1'). + +accessv2 -> atom: accessv2('$1'). + +notification -> objectname 'NOTIFICATION-TYPE' objectspart + 'STATUS' statusv2 'DESCRIPTION' descriptionfield referpart + nameassign : + Not = make_notification('$1','$3','$5', '$7', '$8', '$9'), + {Not, line_of('$2')}. + +objectspart -> 'OBJECTS' '{' objects '}' : lists:reverse('$3'). +objectspart -> '$empty' : []. + +objects -> objectname : ['$1']. +objects -> objects ',' objectname : ['$3'|'$1']. + +%%---------------------------------------------------------------------- +Erlang code. +%%---------------------------------------------------------------------- + +-include("snmp_types.hrl"). +-include("snmpc_lib.hrl"). +-include("snmpc.hrl"). + +% value +val(Token) -> element(3, Token). + +line_of(Token) -> element(2, Token). + +%% category +cat(Token) -> element(1, Token). + +statusv1(Tok) -> + case val(Tok) of + mandatory -> mandatory; + optional -> optional; + obsolete -> obsolete; + deprecated -> deprecated; + Else -> return_error(line_of(Tok), + "syntax error before: " ++ atom_to_list(Else)) + end. + +statusv2(Tok) -> + case val(Tok) of + current -> current; + deprecated -> deprecated; + obsolete -> obsolete; + Else -> return_error(line_of(Tok), + "syntax error before: " ++ atom_to_list(Else)) + end. + +accessv1(Tok) -> + case val(Tok) of + 'read-only' -> 'read-only'; + 'read-write' -> 'read-write'; + 'write-only' -> 'write-only'; + 'not-accessible' -> 'not-accessible'; + Else -> return_error(line_of(Tok), + "syntax error before: " ++ atom_to_list(Else)) + end. + +accessv2(Tok) -> + case val(Tok) of + 'not-accessible' -> 'not-accessible'; + 'accessible-for-notify' -> 'accessible-for-notify'; + 'read-only' -> 'read-only'; + 'read-write' -> 'read-write'; + 'read-create' -> 'read-create'; + Else -> return_error(line_of(Tok), + "syntax error before: " ++ atom_to_list(Else)) + end. + +%% --------------------------------------------------------------------- +%% Various basic record build functions +%% --------------------------------------------------------------------- + +make_module_identity(Name, LU, Org, CI, Desc, Revs, NA) -> + #mc_module_identity{name = Name, + last_updated = LU, + organization = Org, + contact_info = CI, + description = Desc, + revisions = Revs, + name_assign = NA}. + +make_revision(Rev, Desc) -> + #mc_revision{revision = Rev, + description = Desc}. + +make_object_type(Name, Syntax, MaxAcc, Status, Desc, Ref, Kind, NA) -> + #mc_object_type{name = Name, + syntax = Syntax, + max_access = MaxAcc, + status = Status, + description = Desc, + reference = Ref, + kind = Kind, + name_assign = NA}. + +make_object_type(Name, Syntax, Units, MaxAcc, Status, Desc, Ref, Kind, NA) -> + #mc_object_type{name = Name, + syntax = Syntax, + units = Units, + max_access = MaxAcc, + status = Status, + description = Desc, + reference = Ref, + kind = Kind, + name_assign = NA}. + +make_new_type(Name, Macro, Syntax) -> + #mc_new_type{name = Name, + macro = Macro, + syntax = Syntax}. + +make_new_type(Name, Macro, DisplayHint, Status, Desc, Ref, Syntax) -> + #mc_new_type{name = Name, + macro = Macro, + status = Status, + description = Desc, + reference = Ref, + display_hint = DisplayHint, + syntax = Syntax}. + +make_trap(Name, Ent, Vars, Desc, Ref, Num) -> + #mc_trap{name = Name, + enterprise = Ent, + vars = Vars, + description = Desc, + reference = Ref, + num = Num}. + +make_notification(Name, Vars, Status, Desc, Ref, NA) -> + #mc_notification{name = Name, + vars = Vars, + status = Status, + description = Desc, + reference = Ref, + name_assign = NA}. + +make_module_compliance(Name, Status, Desc, Ref, Mod, NA) -> + #mc_module_compliance{name = Name, + status = Status, + description = Desc, + reference = Ref, + module = Mod, + name_assign = NA}. + +make_object_group(Name, Objs, Status, Desc, Ref, NA) -> + #mc_object_group{name = Name, + objects = Objs, + status = Status, + description = Desc, + reference = Ref, + name_assign = NA}. + +make_notification_group(Name, Objs, Status, Desc, Ref, NA) -> + #mc_notification_group{name = Name, + objects = Objs, + status = Status, + description = Desc, + reference = Ref, + name_assign = NA}. + +make_sequence(Name, Fields) -> + #mc_sequence{name = Name, + fields = Fields}. + +make_internal(Name, Macro, Parent, SubIdx) -> + #mc_internal{name = Name, + macro = Macro, + parent = Parent, + sub_index = SubIdx}. + + + +%% --------------------------------------------------------------------- + + +%%---------------------------------------------------------------------- +%% Purpose: Find how much room needs to be allocated for the data type +%% (when sending it in a PDU (the maximum difference will be +%% the size allocated)). +%% This is applicable for OCTET STRINGs and OBJECT IDENTIFIERs. +%% +%% Or : Find the range of integers in the integer list. +%% This is applicable for INTEGERs +%% +%% Arg: A list of integers. +%%---------------------------------------------------------------------- + +make_range_integer(RevHexStr, h) -> + erlang:list_to_integer(lists:reverse(RevHexStr), 16); +make_range_integer(RevHexStr, 'H') -> + erlang:list_to_integer(lists:reverse(RevHexStr), 16); +make_range_integer(RevBitStr, b) -> + erlang:list_to_integer(lists:reverse(RevBitStr), 2); +make_range_integer(RevBitStr, 'B') -> + erlang:list_to_integer(lists:reverse(RevBitStr), 2); +make_range_integer(RevStr, Base) -> + throw({error, {invalid_base, Base, lists:reverse(RevStr)}}). + +make_range(XIntList) -> + IntList = lists:flatten(XIntList), + {range, lists:min(IntList), lists:max(IntList)}. + +make_defval_for_string(Line, Str, Atom) -> + case lists:member(Atom, [h, 'H', b, 'B']) of + true -> + case catch make_defval_for_string2(Str, Atom) of + Defval when is_list(Defval) -> + Defval; + {error, ErrStr} -> + snmpc_lib:print_error("Bad DEFVAL ~w string ~p - ~s", + [Atom, Str, ErrStr], + Line), + ""; + _Else -> + snmpc_lib:print_error("Bad DEFVAL ~w string ~p", + [Atom, Str], + Line), + "" + end; + false -> + snmpc_lib:print_error("Bad DEFVAL string type ~w for ~p", + [Atom, Str], + Line), + "" + end. + + +make_defval_for_string2([], h) -> []; +make_defval_for_string2([X16,X|HexString], h) -> + lists:append(hex_to_bytes(snmpc_misc:to_upper([X16,X])), + make_defval_for_string2(HexString, h)); +make_defval_for_string2([_Odd], h) -> + throw({error, "odd number of bytes in hex string"}); +make_defval_for_string2(HexString, 'H') -> + make_defval_for_string2(HexString,h); + +make_defval_for_string2(BitString, 'B') -> + bits_to_bytes(BitString); +make_defval_for_string2(BitString, b) -> + make_defval_for_string2(BitString, 'B'). + +bits_to_bytes(BitStr) -> + lists:reverse(bits_to_bytes(lists:reverse(BitStr), 1, 0)). + +bits_to_bytes([], 1, _Byte) -> % empty bitstring + []; +bits_to_bytes([], 256, _Byte) -> % correct; multiple of 8 + []; +% If we are to support arbitrary length of bitstrings. This migth +% be needed in the new SMI. +%bits_to_bytes([], N, Byte) -> +% [Byte]; +bits_to_bytes([], _N, _Byte) -> + throw({error, "not a multiple of eight bits in bitstring"}); +bits_to_bytes(Rest, 256, Byte) -> + [Byte | bits_to_bytes(Rest, 1, 0)]; +bits_to_bytes([$1 | T], N, Byte) -> + bits_to_bytes(T, N*2, N + Byte); +bits_to_bytes([$0 | T], N, Byte) -> + bits_to_bytes(T, N*2, Byte); +bits_to_bytes([_BadChar | _T], _N, _Byte) -> + throw({error, "bad character in bit string"}). + +%%---------------------------------------------------------------------- +%% These HEX conversion routines are stolen from module asn1_bits by +%% I didn't want to ship the entire asn1-compiler so I used cut-and-paste. +%%---------------------------------------------------------------------- + +%% hex_to_bytes(HexNumber) when is_atom(HexNumber) -> +%% hex_to_bytes(atom_to_list(HexNumber)); + +hex_to_bytes(HexNumber) -> + case length(HexNumber) rem 2 of + 1 -> %% Odd + hex_to_bytes(lists:append(HexNumber,[$0]),[]); + 0 -> %% even + hex_to_bytes(HexNumber,[]) + end. + +hex_to_bytes([],R) -> + lists:reverse(R); +hex_to_bytes([Hi,Lo|Rest],Res) -> + hex_to_bytes(Rest,[hex_to_byte(Hi,Lo)|Res]). + +hex_to_four_bits(Hex) -> + if + Hex == $0 -> 0; + Hex == $1 -> 1; + Hex == $2 -> 2; + Hex == $3 -> 3; + Hex == $4 -> 4; + Hex == $5 -> 5; + Hex == $6 -> 6; + Hex == $7 -> 7; + Hex == $8 -> 8; + Hex == $9 -> 9; + Hex == $A -> 10; + Hex == $B -> 11; + Hex == $C -> 12; + Hex == $D -> 13; + Hex == $E -> 14; + Hex == $F -> 15; + true -> throw({error, "bad hex character"}) + end. + +hex_to_byte(Hi,Lo) -> + (hex_to_four_bits(Hi) bsl 4) bor hex_to_four_bits(Lo). + +kind(DefValPart,IndexPart) -> + case DefValPart of + undefined -> + case IndexPart of + {indexes, undefined} -> {variable, []}; + {indexes, Indexes} -> + {table_entry, {indexes, Indexes}}; + {augments,Table} -> + {table_entry,{augments,Table}} + end; + {defval, DefVal} -> {variable, [{defval, DefVal}]} + end. + +display_hint(Val) -> + case val(Val) of + Str when is_list(Str) -> + lists:reverse(Str); + _ -> + throw({error, {invalid_display_hint, Val}}) + end. + +units(Val) -> + case val(Val) of + Str when is_list(Str) -> + lists:reverse(Str); + _ -> + throw({error, {invalid_units, Val}}) + end. + +ensure_ver(Ver, Line, What) -> + case get(snmp_version) of + Ver -> ok; + _Other -> + snmpc_lib:print_error( + "~s is only allowed in SNMPv~p.",[What,Ver],Line) + end. + + +ensure_ver(Ver,Token) -> + ensure_ver(Ver,line_of(Token), atom_to_list(cat(Token))). + +filter_v2imports(2,'Integer32') -> {builtin, 'Integer32'}; +filter_v2imports(2,'Counter32') -> {builtin, 'Counter32'}; +filter_v2imports(2,'Gauge32') -> {builtin, 'Gauge32'}; +filter_v2imports(2,'Unsigned32') -> {builtin, 'Unsigned32'}; +filter_v2imports(2,'Counter64') -> {builtin, 'Counter64'}; +filter_v2imports(_,Type) -> {type, Type}. + +w(F, A) -> + ?vwarning(F, A). + +%i(F, A) -> +% io:format("~w:" ++ F ++ "~n", [?MODULE|A]). + diff --git a/lib/snmp/src/compile/snmpc_mib_to_hrl.erl b/lib/snmp/src/compile/snmpc_mib_to_hrl.erl new file mode 100644 index 0000000000..07bd29231b --- /dev/null +++ b/lib/snmp/src/compile/snmpc_mib_to_hrl.erl @@ -0,0 +1,391 @@ +%% +%% %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(snmpc_mib_to_hrl). + +-include_lib("stdlib/include/erl_compile.hrl"). +-include("snmp_types.hrl"). +-include("snmpc_lib.hrl"). + +%% External exports +-export([convert/1, compile/3]). + +%%----------------------------------------------------------------- +%% Func: convert/1 +%% Args: MibName = string() without extension. +%% Purpose: Produce a .hrl file with oid for tables and variables, +%% column numbers for columns and values for enums. +%% Writes only the first occurence of a name. Prints a +%% warning if a duplicate name is found. +%% Returns: ok | {error, Reason} +%% Note: The Mib must be compiled. +%%----------------------------------------------------------------- +convert(MibName) -> + MibFile = MibName ++ ".bin", + HrlFile = MibName ++ ".hrl", + put(verbosity, trace), + convert(MibFile, HrlFile, MibName). + +convert(MibFile, HrlFile, MibName) -> + ?vtrace("convert -> entry with" + "~n MibFile: ~s" + "~n HrlFile: ~s" + "~n MibName: ~s", [MibFile, HrlFile, MibName]), + case snmpc_misc:read_mib(MibFile) of + {ok, #mib{asn1_types = Types, mes = MEs, traps = Traps}} -> + ?vdebug("mib successfully read", []), + resolve(Types, MEs, Traps, HrlFile, + filename:basename(MibName)), + ok; + {error, Reason} -> + ?vinfo("failed reading mib: " + "~n Reason: ~p", [Reason]), + {error, Reason} + end. + +resolve(Types, MEs, Traps, HrlFile, MibName) -> + ?vtrace("resolve -> entry", []), + case file:open(HrlFile, [write]) of + {ok, Fd} -> + insert_header(Fd), + insert_begin(Fd, MibName), + insert_notifs(Traps, Fd), + insert_oids(MEs, Fd), + insert_range(MEs, Fd), + insert_enums(Types, MEs, Fd), + insert_defvals(MEs, Fd), + insert_end(Fd), + file:close(Fd), + ?vlog("~s written", [HrlFile]); + {error, Reason} -> + ?vinfo("failed opening output file: " + "~n Reason: ~p", [Reason]), + {error, Reason} + end. + +insert_header(Fd) -> + ?vdebug("insert file header", []), + io:format(Fd, "%%% This file was automatically generated by " + "snmpc_mib_to_hrl version ~s~n", [?version]), + {Y,Mo,D} = date(), + {H,Mi,S} = time(), + io:format(Fd, "%%% Date: ~2.2.0w-~s-~w::~2.2.0w:~2.2.0w:~2.2.0w~n", + [D,month(Mo),Y,H,Mi,S]). + +insert_begin(Fd, MibName) -> + ?vdebug("insert file begin", []), + io:format(Fd, + "-ifndef('~s').~n" + "-define('~s', true).~n", [MibName, MibName]). + +insert_end(Fd) -> + ?vdebug("insert file end", []), + io:format(Fd, "-endif.~n", []). + +insert_oids(MEs, Fd) -> + ?vdebug("insert oids", []), + io:format(Fd, "~n%% Oids~n", []), + insert_oids2(MEs, Fd), + io:format(Fd, "~n", []). + +insert_oids2([#me{imported = true} | T], Fd) -> + insert_oids2(T, Fd); +insert_oids2([#me{entrytype = table_column, oid = Oid, aliasname = Name} | T], + Fd) -> + ?vtrace("insert oid [table column]: ~p - ~w", [Name, Oid]), + io:format(Fd, "-define(~w, ~w).~n", [Name, lists:last(Oid)]), + insert_oids2(T, Fd); +insert_oids2([#me{entrytype = variable, oid = Oid, aliasname = Name} | T], + Fd) -> + ?vtrace("insert oid [variable]: ~p - ~w", [Name, Oid]), + io:format(Fd, "-define(~w, ~w).~n", [Name, Oid]), + io:format(Fd, "-define(~w, ~w).~n", [merge_atoms(Name, instance), + Oid ++ [0]]), + insert_oids2(T, Fd); +insert_oids2([#me{oid = Oid, aliasname = Name} | T], Fd) -> + ?vtrace("insert oid: ~p - ~w", [Name, Oid]), + io:format(Fd, "~n-define(~w, ~w).~n", [Name, Oid]), + insert_oids2(T, Fd); +insert_oids2([], _Fd) -> + ok. + + +insert_notifs(Traps, Fd) -> + ?vdebug("insert notifications", []), + Notifs = [Notif || Notif <- Traps, is_record(Notif, notification)], + case Notifs of + [] -> + ok; + _ -> + io:format(Fd, "~n%% Notifications~n", []), + insert_notifs2(Notifs, Fd) + end. + +insert_notifs2([], _Fd) -> + ok; +insert_notifs2([#notification{trapname = Name, oid = Oid}|T], Fd) -> + ?vtrace("insert notification ~p - ~w", [Name, Oid]), + io:format(Fd, "-define(~w, ~w).~n", [Name, Oid]), + insert_notifs2(T, Fd). + + +%%----------------------------------------------------------------- +%% There's nothing strange with this function! Enums can be +%% defined in types and in mibentries; therefore, we first call +%% ins_types and then ins_mes to insert enums from different places. +%%----------------------------------------------------------------- +insert_enums(Types, MEs, Fd) -> + ?vdebug("insert enums", []), + T = ins_types(Types, Fd, []), + ins_mes(MEs, T, Fd). + +%% Insert all types, but not the imported. Ret the names of inserted +%% types. +ins_types([#asn1_type{aliasname = Name, + assocList = Alist, + imported = false} | T], + Fd, Res) + when is_list(Alist) -> + case lists:keysearch(enums, 1, Alist) of + {value, {enums, Enums}} when Enums =/= [] -> + case Enums of + [] -> ins_types(T, Fd, Res); + NewEnums -> + io:format(Fd, "~n%% Definitions from ~w~n", [Name]), + ins_enums(NewEnums, Name, Fd), + ins_types(T, Fd, [Name | Res]) + end; + _ -> ins_types(T, Fd, Res) + end; +ins_types([_ | T], Fd, Res) -> + ins_types(T, Fd, Res); +ins_types([], _Fd, Res) -> Res. + +ins_mes([#me{entrytype = internal} | T], Types, Fd) -> + ins_mes(T, Types, Fd); +ins_mes([#me{entrytype = table} | T], Types, Fd) -> + ins_mes(T, Types, Fd); +ins_mes([#me{aliasname = Name, + asn1_type = #asn1_type{assocList = Alist, + aliasname = Aname}, + imported = false} | T], + Types, Fd) + when is_list(Alist) -> + case lists:keysearch(enums, 1, Alist) of + {value, {enums, Enums}} when Enums =/= [] -> + case Enums of + [] -> ins_mes(T, Types, Fd); + NewEnums -> + %% Now, check if the type is already inserted + %% (by ins_types). + case lists:member(Aname, Types) of + false -> + io:format(Fd, "~n%% Enum definitions from ~w~n", + [Name]), + ins_enums(NewEnums, Name, Fd), + ins_mes(T, Types, Fd); + _ -> ins_mes(T, Types, Fd) + end + end; + _ -> ins_mes(T, Types, Fd) + end; +ins_mes([_ | T], Types, Fd) -> + ins_mes(T, Types, Fd); +ins_mes([], _Types, _Fd) -> ok. + +ins_enums([{Name, Val} | T], Origin, Fd) -> + EnumName = merge_atoms(Origin, Name), + io:format(Fd, "-define(~w, ~w).~n", [EnumName, Val]), + ins_enums(T, Origin, Fd); +ins_enums([], _Origin, _Fd) -> + ok. + +%%---------------------------------------------------------------------- +%% Solves the problem with placing '' around some atoms. +%% You can't write two atoms using ~w_~w. +%%---------------------------------------------------------------------- +merge_atoms(TypeOrigin, Name) -> + list_to_atom(lists:append([atom_to_list(TypeOrigin), "_", + atom_to_list(Name)])). + +insert_defvals(Mes, Fd) -> + ?vdebug("insert default values", []), + io:format(Fd, "~n%% Default values~n", []), + insert_defvals2(Mes, Fd), + io:format(Fd, "~n", []). + +insert_defvals2([#me{imported = true} | T], Fd) -> + insert_defvals2(T, Fd); +insert_defvals2([#me{entrytype = table_column, assocList = Alist, + aliasname = Name} | T], + Fd) -> + case snmpc_misc:assq(defval, Alist) of + {value, Val} -> + Atom = merge_atoms('default', Name), + io:format(Fd, "-define(~w, ~w).~n", [Atom, Val]); + _ -> ok + end, + insert_defvals2(T, Fd); +insert_defvals2([#me{entrytype = variable, assocList = Alist, aliasname = Name} + | T], + Fd) -> + case snmpc_misc:assq(variable_info, Alist) of + {value, VarInfo} -> + case VarInfo#variable_info.defval of + undefined -> ok; + Val -> + Atom = merge_atoms('default', Name), + io:format(Fd, "-define(~w, ~w).~n", [Atom, Val]) + end; + _ -> ok + end, + insert_defvals2(T, Fd); +insert_defvals2([_ | T], Fd) -> + insert_defvals2(T, Fd); +insert_defvals2([], _Fd) -> ok. + +insert_range(Mes, Fd) -> + ?vdebug("insert range", []), + io:format(Fd, "~n%% Range values~n", []), + insert_range2(Mes, Fd), + io:format(Fd, "~n", []). + +insert_range2([#me{imported = true} | T], Fd)-> + insert_range2(T,Fd); +insert_range2([#me{asn1_type=#asn1_type{bertype='OCTET STRING',lo=Low,hi=High},aliasname=Name}|T],Fd)-> + case Low =:= undefined of + true-> + insert_range2(T,Fd); + false-> + AtomLow = merge_atoms('low', Name), + AtomHigh = merge_atoms('high', Name), + io:format(Fd,"-define(~w, ~w).~n",[AtomLow,Low]), + io:format(Fd,"-define(~w, ~w).~n",[AtomHigh,High]), + insert_range2(T,Fd) + end; +insert_range2([#me{asn1_type=#asn1_type{bertype='Unsigned32',lo=Low,hi=High},aliasname=Name}|T],Fd)-> + AtomLow = merge_atoms('low', Name), + AtomHigh = merge_atoms('high', Name), + io:format(Fd,"-define(~w, ~w).~n",[AtomLow,Low]), + io:format(Fd,"-define(~w, ~w).~n",[AtomHigh,High]), + insert_range2(T,Fd); +insert_range2([#me{asn1_type=#asn1_type{bertype='Counter32',lo=Low,hi=High},aliasname=Name}|T],Fd)-> + AtomLow = merge_atoms('low', Name), + AtomHigh = merge_atoms('high', Name), + io:format(Fd,"-define(~w, ~w).~n",[AtomLow,Low]), + io:format(Fd,"-define(~w, ~w).~n",[AtomHigh,High]), + insert_range2(T,Fd); +insert_range2([#me{asn1_type=#asn1_type{bertype='INTEGER',lo=Low,hi=High},aliasname=Name}|T],Fd)-> + case Low =:= undefined of + true-> + insert_range2(T,Fd); + false-> + AtomLow = merge_atoms('low', Name), + AtomHigh = merge_atoms('high', Name), + io:format(Fd,"-define(~w, ~w).~n",[AtomLow,Low]), + io:format(Fd,"-define(~w, ~w).~n",[AtomHigh,High]), + insert_range2(T,Fd) + end; +insert_range2([_|T],Fd) -> + insert_range2(T,Fd); +insert_range2([],_Fd) -> + ok. + +month(1) -> "Jan"; +month(2) -> "Feb"; +month(3) -> "Mar"; +month(4) -> "Apr"; +month(5) -> "May"; +month(6) -> "Jun"; +month(7) -> "Jul"; +month(8) -> "Aug"; +month(9) -> "Sep"; +month(10) -> "Oct"; +month(11) -> "Nov"; +month(12) -> "Dec". + +%%%----------------------------------------------------------------- +%%% Interface for erl_compile. +%%%----------------------------------------------------------------- + +%% Opts#options.specific +compile(Input, Output, Opts) -> + set_verbosity(Opts), + set_filename(Input), + ?vtrace("compile -> entry with" + "~n Input: ~s" + "~n Output: ~s" + "~n Opts: ~p", [Input, Output, Opts]), + case convert(Input++".bin", Output++".hrl", Input) of + ok -> + ok; + {error, Reason} -> + io:format("~p", [Reason]), + error + end. + +set_verbosity(#options{verbose = Verbose, specific = Spec}) -> + set_verbosity(Verbose, Spec). + +set_verbosity(Verbose, Spec) -> + Verbosity = + case lists:keysearch(verbosity, 1, Spec) of + {value, {verbosity, V}} -> + case (catch snmpc_lib:vvalidate(V)) of + ok -> + case Verbose of + true -> + case V of + silence -> + log; + info -> + log; + _ -> + V + end; + _ -> + V + end; + _ -> + case Verbose of + true -> + log; + false -> + silence + end + end; + false -> + case Verbose of + true -> + log; + false -> + silence + end + end, + put(verbosity, Verbosity). + + +set_filename(Filename) -> + Rootname = filename:rootname(Filename), + Basename = filename:basename(Rootname ++ ".mib"), + put(filename, Basename). + + + + diff --git a/lib/snmp/src/compile/snmpc_misc.erl b/lib/snmp/src/compile/snmpc_misc.erl new file mode 100644 index 0000000000..557f3e0f6b --- /dev/null +++ b/lib/snmp/src/compile/snmpc_misc.erl @@ -0,0 +1,173 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-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(snmpc_misc). + +%% need definition of mib record +-include("snmp_types.hrl"). +-include("snmpc_misc.hrl"). + + +-export([assq/2, + bits_to_int/2, + ensure_trailing_dir_delimiter/1, + foreach/3, + is_string/1, + read_mib/1, + read_noexit/2, + strip_extension_from_filename/2, + to_upper/1]). + + +%%-------------------------------------------------- +%% Not a real assq, but what the heck, it's useful. +%%-------------------------------------------------- +assq(Key, List) -> + case lists:keysearch(Key, 1, List) of + {value, {Key, Val}} -> {value, Val}; + _ -> false + end. + + +%%---------------------------------------------------------------------- +%% Converts a list of named bits to the integer value. +%% Returns: integer()|error +%%---------------------------------------------------------------------- +bits_to_int(Val,Kibbles) -> + bits_to_int(Val,Kibbles,0). + +bits_to_int([],_Kibbles,Res) -> Res; +bits_to_int([Kibble|Ks],Kibbles,Res) -> + case snmp_misc:assq(Kibble,Kibbles) of + {value,V} -> + bits_to_int(Ks,Kibbles,Res + round(math:pow(2,V))); + _ -> + error + end. + + +ensure_trailing_dir_delimiter([]) -> "/"; +ensure_trailing_dir_delimiter(DirSuggestion) -> + case lists:last(DirSuggestion) of + $/ -> DirSuggestion; + _ -> lists:append(DirSuggestion,"/") + end. + + +strip_extension_from_filename(FileName, Ext) when is_atom(FileName) -> + strip_extension_from_filename(atom_to_list(FileName), Ext); + +strip_extension_from_filename(FileName, Ext) when is_list(FileName) -> + case lists:suffix(Ext, FileName) of + true -> lists:sublist(FileName, 1, length(FileName) - length(Ext)); + false -> FileName + end. + + +to_upper([C|Cs]) when (C >= $a) andalso (C =< $z) -> [C-($a-$A)|to_upper(Cs)]; +to_upper([C|Cs]) -> [C|to_upper(Cs)]; +to_upper([]) -> []. + + +is_string([]) -> true; +is_string([Tkn | Str]) + when is_integer(Tkn) andalso (Tkn >= 0) andalso (Tkn =< 255) -> + is_string(Str); +is_string(_) -> false. + + +foreach(Function, ExtraArgs, [H | T]) -> + apply(Function, [H | ExtraArgs]), + foreach(Function, ExtraArgs, T); +foreach(_Function, _ExtraArgs, []) -> + true. + + + +%%---------------------------------------------------------------------- +%% Returns: {ok, Mib}|{error, Reason} +%% The reason for having the function if this module is: +%% The compiler package and the agent package are separated, this is +%% the only common module. +%%---------------------------------------------------------------------- +read_mib(FileName) -> + (catch do_read_mib(FileName)). + +do_read_mib(FileName) -> + ?read_mib(FileName). + + +%% Ret. {ok, Res} | {error, Line, Error} | {error, open_file} +read_noexit(File, CheckFunc) -> + case file:open(File, [read]) of + {ok, Fd} -> + case loop(Fd, [], CheckFunc, 1, File) of + {error, Line, R} -> + file:close(Fd), + {error, Line, R}; + Res -> + file:close(Fd), + {ok, Res} + end; + _Error -> + {error, open_file} + end. + + +%%----------------------------------------------------------------- +%% Ret: {error, Line, Reason} | Row +%%----------------------------------------------------------------- +loop(Fd, Res, Func, StartLine, File) -> + case read(Fd, "", StartLine) of + {ok, Row, EndLine} -> + case (catch apply(Func, [Row])) of + {ok, NewRow} -> + loop(Fd, [NewRow | Res], Func, EndLine, File); + true -> + loop(Fd, [Row | Res], Func, EndLine, File); + Error -> + {error, EndLine, Error} + end; + {error, EndLine, Error} -> + {error, EndLine, Error}; + eof -> + Res + end. + + +%%----------------------------------------------------------------- +%% io:read modified to give us line numbers. +%%----------------------------------------------------------------- +read(Io, Prompt, StartLine) -> + case io:request(Io, {get_until, Prompt, erl_scan, tokens, [StartLine]}) of + {ok, Toks, EndLine} -> + case erl_parse:parse_term(Toks) of + {ok, Term} -> + {ok, Term, EndLine}; + {error, {Line, erl_parse, Error}} -> + {error, Line, {parse_error, Error}} + end; + {error, E, EndLine} -> + {error, EndLine, E}; + {eof, _EndLine} -> + eof; + Other -> + Other + end. + diff --git a/lib/snmp/src/compile/snmpc_misc.hrl b/lib/snmp/src/compile/snmpc_misc.hrl new file mode 100644 index 0000000000..c29f2eb9e5 --- /dev/null +++ b/lib/snmp/src/compile/snmpc_misc.hrl @@ -0,0 +1,74 @@ +%% +%% %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(verify_format_version(VFV_Ver1,VFV_Ver2), + fun(VFV_V,VFV_V) -> + ok; + (VFV_V1,VFV_V2) when is_list(VFV_V1) andalso is_list(VFV_V2) -> + Toks1 = string:tokens(VFV_V1, [$.]), + [Major1|_] = case (catch [list_to_integer(I) || I <- Toks1]) of + Nums when is_list(Nums) -> + Nums; + _ -> + {error, {invalid_version_format, VFV_V1}} + end, + Toks2 = string:tokens(VFV_V2, [$.]), + case (catch [list_to_integer(I) || I <- Toks2]) of + [Major1|_] -> + ok; + [_Major2|_] -> + {error, wrong_version}; + _ -> + {error, {invalid_version_format, VFV_V2}} + end; + (VFV_V1,VFV_V2) -> + {error, {invalid_format, VFV_V1, VFV_V2}} + end(VFV_Ver1,VFV_Ver2)). + +-define(read_mib(RM_FN), + RM_Bin = case file:read_file(RM_FN) of + {ok, RM_B} -> + RM_B; + RM_Error -> + throw(RM_Error) + end, + RM_Mib = case (catch binary_to_term(RM_Bin)) of + RM_M when is_record(RM_M, mib) -> + RM_M; + _ -> + throw({error, bad_mib_format}) + end, + #mib{mib_format_version = RM_V1} = #mib{}, + case RM_Mib of + #mib{mib_format_version = RM_V2, + misc = RM_X} when is_integer(RM_X) -> + case (catch ?verify_format_version(RM_V1, RM_V2)) of + ok -> + {ok, RM_Mib#mib{misc = []}}; + _ -> + throw({error, {wrong_mib_format_version_tag, RM_V2}}) + end; + #mib{mib_format_version = RM_V2} -> + case (catch ?verify_format_version(RM_V1, RM_V2)) of + ok -> + {ok, RM_Mib#mib{misc = []}}; + _ -> + throw({error, {wrong_mib_format_version_tag, RM_V2}}) + end + end). diff --git a/lib/snmp/src/compile/snmpc_tok.erl b/lib/snmp/src/compile/snmpc_tok.erl new file mode 100644 index 0000000000..6b99e7ae43 --- /dev/null +++ b/lib/snmp/src/compile/snmpc_tok.erl @@ -0,0 +1,357 @@ +%% +%% %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(snmpc_tok). + +%% c(snmpc_tok). + +%%---------------------------------------------------------------------- +%% Generic (?) Tokenizer. +%%---------------------------------------------------------------------- +%% Token: {Category, Line, Value}|{Category, Line} +%% (Category == <KeyWord> ==> 2-tuple (otherwise 3-tuple) +%% Category: integer | quote | string +%% | variable | atom | <keyword> | <single_char> +%%---------------------------------------------------------------------- + +%% API +-export([start_link/2, stop/1, get_token/1, get_all_tokens/1, tokenize/2]). + +%% Internal exports +-export([null_get_line/0, format_error/1, terminate/2, handle_call/3, init/1, + test/0]). + + +%%---------------------------------------------------------------------- +%% Reserved_words: list of KeyWords. Example: ['IF', 'BEGIN', ..., 'GOTO'] +%% Options: list of Option +%% Option: {file, <filename>}, +%% or: {get_line_function, {Mod, Func, Arg} (default io, get_line, [Fid])}, +%% get_line_function shall behave as io:get_line. +%% {print_lineno, L} +%% Returns: {ok, Pid} | {error, Reason} +%%---------------------------------------------------------------------- +start_link(Reserved_words, Options) -> + case lists:keysearch(file, 1, Options) of + {value, {file, Filename}} -> + case file:open(Filename, [read]) of + {ok, Fid} -> + gen_server:start_link(?MODULE, + {Reserved_words, Options, + {io, get_line, [Fid, prompt]}}, []); + Error -> + Str = format_error({"Cannot open file '~s' (~800p).~n", + [Filename, Error]}), + {error,Str} + end; + false -> + MFA = case lists:keysearch(get_line_function, 1, Options) of + {value, {get_line_function, {M, F, A}}} -> + {M,F,A}; + false -> {?MODULE, null_get_line, []} + end, + gen_server:start_link(?MODULE,{Reserved_words,Options,MFA}, []) + end. + +%%-------------------------------------------------- +%% Returns: +%% {ok, [Token], LineNo} | {eof, LineNo} | {error, Error_description, Endline} +%% For more information, see manual page for yecc (and its requirements on a +%% tokenizer). +%%-------------------------------------------------- +get_token(TokPid) -> + V = gen_server:call(TokPid, get_token, infinity), + %% io:format("tok:~w~n", [V]), + V. + +get_all_tokens(TokPid) -> + V = gen_server:call(TokPid, get_all_tokens, infinity), + %% io:format("tok:~w~n", [V]), + V. + + + +%%-------------------------------------------------- +%% Returns: {ok, Tokens, EndLine} | {error, Error_description, Endline} +%% Comment: Tokeniser must be started since all options reside in +%% the process dictionary of the tokeniser process. +%%-------------------------------------------------- +tokenize(TokPid, String) -> + gen_server:call(TokPid, {tokenize, String}, infinity). + +stop(TokPid) -> + gen_server:call(TokPid,stop, infinity). + +%%---------------------------------------------------------------------- +%% Implementation +%%---------------------------------------------------------------------- +insert_keywords_into_ets(_DB, []) -> done; +insert_keywords_into_ets(DB, [Word | T]) -> + ets:insert(DB, {Word, reserved_word}), + insert_keywords_into_ets(DB, T). + +reserved_word(X) -> + case ets:lookup(get(db), X) of + [{X, reserved_word}] -> + true; + _ -> + false + end. + +%% If you only need to tokenize strings +null_get_line() -> eof. + +format_error({Format, Data}) -> + io_lib:format(lists:append("Tokeniser error: ", Format), Data). + +test() -> + start_link(['AUGMENTS','BEGIN','CONTACT-INFO','DEFINITIONS','DEFVAL', + 'DESCRIPTION','DISPLAY-HINT','END','IDENTIFIER','IMPLIED', + 'INDEX','INTEGER','LAST-UPDATED','MAX-ACCESS','MODULE-IDENTITY', + 'NOTIFICATION-TYPE','OBJECT','OBJECT-IDENTITY','OBJECT-TYPE', + 'OBJECTS','ORGANIZATION','REFERENCE','REVISION', + 'SIZE','STATUS','SYNTAX','TEXTUAL-CONVENTION','UNITS', + 'current','deprecated','not-accessible','obsolete', + 'read-create','read-only','read-write', 'IMPORTS', 'FROM', + 'MODULE-COMPLIANCE', + 'DisplayString', + 'PhysAddress', + 'MacAddress', + 'TruthValue', + 'TestAndIncr', + 'AutonomousType', + 'InstancePointer', + 'VariablePointer', + 'RowPointer', + 'RowStatus', + 'TimeStamp', + 'TimeInterval', + 'DateAndTime', + 'StorageType', + 'TDomain', + 'TAddress'], + [{file, "modemmib.mib"}]). + +init({Reserved_words, Options, GetLineMFA}) -> + put(get_line_function,GetLineMFA), + put(print_lineno, + case lists:keysearch(print_lineno, 1, Options) of + {value, {print_lineno, L}} -> L; + false -> undefined + end), + DB = ets:new(reserved_words, [set, private]), + insert_keywords_into_ets(DB, Reserved_words), + put(db, DB), + put(line, 0), + {ok, ""}. + +getLine() -> + OldLine = put(line, 1 + get(line)), + {M,F,A} = get(get_line_function), + case get(print_lineno) of + undefined -> true; + X -> case OldLine rem X of + 0 -> io:format('~w..',[OldLine]); + _ -> true + end + end, + apply(M,F,A). + +%% You can only do this when no file is open. +handle_call({tokenizeString, String}, _From, "") -> + {reply, safe_tokenize_whole_string(String), ""}; + +handle_call(get_token, _From, String) -> + {ReplyToken, RestChars} = safe_tokenise(String), + {reply, ReplyToken, RestChars}; + +handle_call(get_all_tokens, _From, String) -> + Toks = get_all_tokens(String,[]), + {reply, Toks, []}; + + +handle_call(stop, _From, String) -> + {stop, normal, ok, String}. + +terminate(_Reason, _State) -> + ok. + +%% ErrorInfo = {ErrorLine, Module, ErrorDescriptor} +%% will be used as +%% apply(Module, format_error, [ErrorDescriptor]). shall return a string. + +%% Returns a reply +tokenize_whole_string(eof) -> []; +tokenize_whole_string(String) -> + {Token, RestChars} = tokenise(String), + [Token | tokenize_whole_string(RestChars)]. + +safe_tokenize_whole_string(String) -> + case catch tokenize_whole_string(String) of + {error, ErrorInfo} -> {error, ErrorInfo, get(line)}; + Tokens -> {ok, Tokens, get(line)} + end. + +%% throw({error, {get(line), ?MODULE, "Unexpected eof~n"}}). + +%% Returns: {ReplyToken, NewState} +safe_tokenise(eof) -> {{eof, get(line)}, eof}; +safe_tokenise(Chars) when is_list(Chars) -> + case catch tokenise(Chars) of + {error, ErrorInfo} -> {{error, ErrorInfo, get(line)}, {[], eof}}; + {Token, RestChars} when is_tuple(Token) -> + {{ok, [Token], get(line)}, RestChars} + end. + +get_all_tokens(eof,Toks) -> + lists:reverse(Toks); +get_all_tokens(Str,Toks) -> + case catch tokenise(Str) of + {error, ErrorInfo} -> {error, ErrorInfo}; + {Token, RestChars} when is_tuple(Token) -> + get_all_tokens(RestChars, [Token|Toks]) + end. + + + +%%-------------------------------------------------- +%% Returns: {Token, Rest} +%%-------------------------------------------------- +tokenise([H|T]) when ($a =< H) andalso (H =< $z) -> + get_name(atom, [H], T); + +tokenise([H|T]) when ($A =< H) andalso (H =< $Z) -> + get_name(variable, [H], T); + +tokenise([$:,$:,$=|T]) -> + {{'::=', get(line)}, T}; + +tokenise([$-,$-|T]) -> + tokenise(skip_comment(T)); + +tokenise([$-,H|T]) when ($0 =< H ) andalso (H =< $9) -> + {Val, Rest} = get_integer(T, [H]), + {{integer, get(line), -1 * Val}, Rest}; + +tokenise([H|T]) when ($0 =< H) andalso (H =< $9) -> + {Val, Rest} = get_integer(T, [H]), + {{integer, get(line), Val}, Rest}; + +tokenise([$"|T]) -> + collect_string($", T, []); + +tokenise([$'|T]) -> + collect_string($', T, []); + +%% Read away white spaces +tokenise([9| T]) -> tokenise(T); +tokenise([10| T]) -> tokenise(T); +tokenise([13| T]) -> tokenise(T); +tokenise([32| T]) -> tokenise(T); + +%% Handle singe characters like { } [ ] + = ... +tokenise([Ch | T]) -> + Atm = list_to_atom([Ch]), + {{Atm, get(line)}, T}; + +tokenise([]) -> + tokenise(getLine()); + +tokenise(eof) -> + {{'$end', get(line)}, eof}. + +collect_string($", [$"|T],BackwardsStr) -> + {{string, get(line), BackwardsStr}, T}; + +collect_string($', [$'|T],BackwardsStr) -> + {{quote, get(line), BackwardsStr}, T}; + +collect_string(StopChar, [Ch|T], Str) -> + collect_string(StopChar,T,[Ch|Str]); + +collect_string(StopChar, [],Str) -> + collect_string(StopChar, getLine(), Str); + +collect_string(StopChar, eof, Str) -> + throw({error, {get(line), ?MODULE, + {"Missing ~s in string:~n \"~s\"~n", + [[StopChar], lists:reverse(Str)]}}}). + +get_name(Category, Name, [Char|T]) -> + case isInName(Char) of + true -> + get_name(Category, [Char|Name], T); + false -> + makeNameRespons(Category, Name, [Char | T]) + end; +get_name(Category, Name, []) -> + makeNameRespons(Category, Name, []). + +makeNameRespons(Category, Name, RestChars) -> + Atm = list_to_atom(lists:reverse(Name)), + case reserved_word(Atm) of + true -> {{Atm, get(line)}, RestChars}; + false -> {{Category, get(line), Atm}, RestChars} + end. + +isInName($-) -> true; +isInName(Ch) -> isalnum(Ch). + +isalnum(H) when ($A =< H) andalso (H =< $Z) -> + true; +isalnum(H) when ($a =< H) andalso (H =< $z) -> + true; +isalnum(H) when ($0 =< H) andalso (H =< $9) -> + true; +isalnum(_) -> + false. + +isdigit(H) when ($0 =< H) andalso (H =< $9) -> + true; +isdigit(_) -> + false. + +get_integer([H|T], "0") -> + case isdigit(H) of + true -> + throw({error, {get(line), ?MODULE, + {"Unexpected ~w~n", + [list_to_atom([H])]}}}); + false -> + {0, [H|T]} + end; +get_integer([H|T], L) -> + case isdigit(H) of + true -> + get_integer(T, [H|L]); + false -> + {list_to_integer(lists:reverse(L)), [H|T]} + end; +get_integer([], L) -> + {list_to_integer(lists:reverse(L)), []}. + +%%-------------------------------------------------- +%% ASN.1 type of comments. "--" is comment to eoln or next "--" +%%-------------------------------------------------- +skip_comment([]) -> + []; +skip_comment([$-,$-|T]) -> + T; +skip_comment([_|T]) -> + skip_comment(T). diff --git a/lib/snmp/src/manager/Makefile b/lib/snmp/src/manager/Makefile new file mode 100644 index 0000000000..c1d5703300 --- /dev/null +++ b/lib/snmp/src/manager/Makefile @@ -0,0 +1,124 @@ +#-*-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% + +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),d) + SNMP_FLAGS += -Dsnmp_debug +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 "ERL_FILES: $(ERL_FILES)" + @echo "HRL_FILES: $(HRL_FILES)" + @echo "TARGET_FILES: $(TARGET_FILES)" + @echo "" + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/manager + $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)/src/manager + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(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/manager/depend.mk b/lib/snmp/src/manager/depend.mk new file mode 100644 index 0000000000..0e7e9e3df7 --- /dev/null +++ b/lib/snmp/src/manager/depend.mk @@ -0,0 +1,75 @@ +#-*-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)/snmpm_user.$(EMULATOR): \ + snmpm_user.erl + +$(EBIN)/snmpm_network_interface.$(EMULATOR): \ + snmpm_network_interface.erl + +$(EBIN)/snmpm.$(EMULATOR): \ + snmpm.erl + +$(EBIN)/snmpm_config.$(EMULATOR): \ + snmpm_config.erl \ + ../../include/snmp_types.hrl \ + ../misc/snmp_verbosity.hrl + +$(EBIN)/snmpm_mpd.$(EMULATOR): \ + snmpm_mpd.erl \ + ../../include/snmp_types.hrl \ + ../../include/SNMP-MPD-MIB.hrl \ + ../../include/SNMPv2-TM.hrl \ + ../misc/snmp_verbosity.hrl + +$(EBIN)/snmpm_misc_sup.$(EMULATOR): \ + snmpm_misc_sup.erl \ + ../misc/snmp_debug.hrl + +$(EBIN)/snmpm_net_if.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + ../misc/snmp_debug.hrl \ + ../misc/snmp_verbosity.hrl \ + snmpm_net_if.erl \ + snmpm_network_interface.erl + +$(EBIN)/snmpm_server.$(EMULATOR): \ + ../../include/snmp_types.hrl \ + ../../include/STANDARD-MIB.hrl \ + ../misc/snmp_verbosity.hrl \ + snmpm_server.erl + +$(EBIN)/snmpm_server_sup.$(EMULATOR): \ + snmpm_server_sup.erl + +$(EBIN)/snmpm_supervisor.$(EMULATOR): \ + snmpm_supervisor.erl + +$(EBIN)/snmpm_user_default.$(EMULATOR): \ + snmpm_user_default.erl \ + snmpm_user.erl + +$(EBIN)/snmpm_usm.$(EMULATOR): \ + snmpm_usm.erl \ + snmpm_usm.hrl \ + ../../include/snmp_types.hrl \ + ../../include/SNMP-USER-BASED-SM-MIB.hrl \ + ../misc/snmp_verbosity.hrl + + diff --git a/lib/snmp/src/manager/modules.mk b/lib/snmp/src/manager/modules.mk new file mode 100644 index 0000000000..79f3dd65d9 --- /dev/null +++ b/lib/snmp/src/manager/modules.mk @@ -0,0 +1,45 @@ +#-*-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 = \ + snmpm_user \ + snmpm_user_old \ + snmpm_network_interface \ + snmpm_network_interface_filter + +MODULES = \ + $(BEHAVIOUR_MODULES) \ + snmpm \ + snmpm_conf \ + snmpm_config \ + snmpm_mpd \ + snmpm_misc_sup \ + snmpm_net_if \ + snmpm_net_if_filter \ + snmpm_server \ + snmpm_server_sup \ + snmpm_supervisor \ + snmpm_user_default \ + snmpm_usm + +INTERNAL_HRL_FILES = \ + snmpm_usm \ + snmpm_atl \ + snmpm_internal + diff --git a/lib/snmp/src/manager/snmpm.erl b/lib/snmp/src/manager/snmpm.erl new file mode 100644 index 0000000000..141addf440 --- /dev/null +++ b/lib/snmp/src/manager/snmpm.erl @@ -0,0 +1,1528 @@ +%% +%% %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(snmpm). + +%%---------------------------------------------------------------------- +%% This module implements a simple SNMP manager for Erlang. +%%---------------------------------------------------------------------- + +%% User interface +-export([ + %% + %% Management API + start/0, start/1, + start_link/0, start_link/1, + stop/0, + + monitor/0, demonitor/1, + notify_started/1, cancel_notify_started/1, + + backup/1, + + load_mib/1, unload_mib/1, + which_mibs/0, + name_to_oid/1, oid_to_name/1, oid_to_type/1, + + register_user/3, register_user/4, + register_user_monitor/3, register_user_monitor/4, + unregister_user/1, + which_users/0, + + register_agent/2, register_agent/3, register_agent/4, + unregister_agent/2, unregister_agent/3, + which_agents/0, which_agents/1, + agent_info/2, update_agent_info/4, + + register_usm_user/3, unregister_usm_user/2, + which_usm_users/0, which_usm_users/1, + usm_user_info/3, update_usm_user_info/4, + + %% + %% Basic SNMP API + sync_get/3, sync_get/4, sync_get/5, sync_get/6, + async_get/3, async_get/4, async_get/5, async_get/6, + sync_get_next/3, sync_get_next/4, sync_get_next/5, sync_get_next/6, + async_get_next/3, async_get_next/4, async_get_next/5, async_get_next/6, + sync_set/3, sync_set/4, sync_set/5, sync_set/6, + async_set/3, async_set/4, async_set/5, async_set/6, + sync_get_bulk/5, sync_get_bulk/6, sync_get_bulk/7, sync_get_bulk/8, + async_get_bulk/5, async_get_bulk/6, async_get_bulk/7, async_get_bulk/8, + cancel_async_request/2, + + %% + %% Extended SNMP API + %% discovery/2, discovery/3, discovery/4, discovery/5, discovery/6, + + %% + %% Logging + 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, + set_log_type/1, + + reconfigure/0, + + system_start_time/0, + sys_up_time/0, + + info/0, + verbosity/2 + ]). + +-export([format_reason/1, format_reason/2]). + +%% Backward compatibillity exports +-export([ + agent_info/3, update_agent_info/5, + g/3, g/4, g/5, g/6, g/7, + ag/3, ag/4, ag/5, ag/6, ag/7, + gn/3, gn/4, gn/5, gn/6, gn/7, + agn/3, agn/4, agn/5, agn/6, agn/7, + gb/5, gb/6, gb/7, gb/8, gb/9, + agb/5, agb/6, agb/7, agb/8, agb/9, + s/3, s/4, s/5, s/6, s/7, + as/3, as/4, as/5, as/6, as/7 + ]). + +%% Application internal export +-export([start_link/3, snmpm_start_verify/2, snmpm_start_verify/3]). + + +-include("snmp_debug.hrl"). +-include("snmpm_atl.hrl"). +-include("snmp_types.hrl"). + +-define(DEFAULT_AGENT_PORT, 161). +-define(DEFAULT_CONTEXT, ""). + + +%% This function is called when the snmp application +%% starts. +start_link(Opts, normal, []) -> + start_link(Opts). + + +simple_conf() -> + Vsns = [v1, v2, v3], + {ok, Cwd} = file:get_cwd(), + %% Check if the manager config file exist, if not create it + MgrConf = filename:join(Cwd, "manager.conf"), + case file:read_file_info(MgrConf) of + {ok, _} -> + ok; + _ -> + ok = snmp_config:write_manager_config(Cwd, "", + [{port, 5000}, + {engine_id, "mgrEngine"}, + {max_message_size, 484}]) + end, + Conf = [{dir, Cwd}, {db_dir, Cwd}], + [{versions, Vsns}, {config, Conf}]. + +%% Simple start. Start a manager with default values. +start_link() -> + start_link(simple_conf()). + +%% This function is normally not used. Instead the manager is +%% started as a consequence of a call to application:start(snmp) +%% when {snmp, [{manager, Options}]} is present in the +%% node config file. +start_link(Opts) -> + %% This start the manager top supervisor, which in turn + %% starts the other processes. + {ok, _} = snmpm_supervisor:start_link(normal, Opts), + ok. + +%% Simple start. Start a manager with default values. +start() -> + start(simple_conf()). + +start(Opts) -> + %% This start the manager top supervisor, which in turn + %% starts the other processes. + {ok, Pid} = snmpm_supervisor:start_link(normal, Opts), + unlink(Pid), + ok. + +stop() -> + snmpm_supervisor:stop(). + + +monitor() -> + erlang:monitor(process, snmpm_supervisor). + +demonitor(Ref) -> + erlang:demonitor(Ref). + + +-define(NOTIFY_START_TICK_TIME, 500). + +notify_started(To) when is_integer(To) andalso (To > 0) -> + spawn_link(?MODULE, snmpm_start_verify, [self(), To]). + +cancel_notify_started(Pid) -> + Pid ! {cancel, self()}, + ok. + +snmpm_start_verify(Parent, To) -> + ?d("starting", []), + snmpm_start_verify(Parent, monitor(), To). + +snmpm_start_verify(Parent, _Ref, To) when (To =< 0) -> + ?d("timeout", []), + unlink(Parent), + Parent ! {snmpm_start_timeout, self()}; +snmpm_start_verify(Parent, Ref, To) -> + T0 = t(), + receive + {cancel, Parent} -> + ?d("cancel", []), + demonitor(Ref), + unlink(Parent), + exit(normal); + {'EXIT', Parent, _} -> + exit(normal); + {'DOWN', Ref, process, _Object, _Info} -> + ?d("down", []), + sleep(?NOTIFY_START_TICK_TIME), + ?MODULE:snmpm_start_verify(Parent, monitor(), t(T0, To)) + after ?NOTIFY_START_TICK_TIME -> + ?d("down timeout", []), + demonitor(Ref), + case snmpm_server:is_started() of + true -> + unlink(Parent), + Parent ! {snmpm_started, self()}; + _ -> + ?MODULE:snmpm_start_verify(Parent, monitor(), t(T0, To)) + end + end. + +t(T0, T) -> T - (t() - T0). +t() -> snmp_misc:now(ms). +sleep(To) -> snmp_misc:sleep(To). + + +%% -- Misc -- + +backup(BackupDir) -> + snmpm_config:backup(BackupDir). + + +%% -- Mibs -- + +%% Load a mib into the manager +load_mib(MibFile) -> + snmpm_server:load_mib(MibFile). + +%% Unload a mib from the manager +unload_mib(Mib) -> + snmpm_server:unload_mib(Mib). + +%% Which mib's are loaded +which_mibs() -> + snmpm_config:which_mibs(). + +%% Get all the possible oid's for the aliasname +name_to_oid(Name) -> + snmpm_config:name_to_oid(Name). + +%% Get the aliasname for an oid +oid_to_name(Oid) -> + snmpm_config:oid_to_name(Oid). + +%% Get the type for an oid +oid_to_type(Oid) -> + snmpm_config:oid_to_type(Oid). + + +%% -- Info -- + +info() -> + snmpm_server:info(). + + +%% -- Verbosity -- + +%% Change the verbosity of a process in the manager +verbosity(config, V) -> + snmpm_config:verbosity(V); +verbosity(server, V) -> + snmpm_server:verbosity(V); +verbosity(net_if, V) -> + snmpm_server:verbosity(net_if, V); +verbosity(note_store, V) -> + snmpm_server:verbosity(note_store, V); +verbosity(all, V) -> + snmpm_config:verbosity(V), + snmpm_server:verbosity(V), + snmpm_server:verbosity(net_if, V), + snmpm_server:verbosity(note_store, V). + + +%% -- Users -- + +%% Register the 'user'. +%% The manager entity responsible for a specific agent. +%% Module is the callback module (snmpm_user behaviour) which +%% will be called whenever something happens (detected +%% agent, incomming reply or incomming trap/notification). +%% Note that this could have already been done as a +%% consequence of the node config. +register_user(Id, Module, Data) -> + register_user(Id, Module, Data, []). + +%% Default config for agents registered by this user +register_user(Id, Module, Data, DefaultAgentConfig) -> + snmpm_server:register_user(Id, Module, Data, DefaultAgentConfig). + +register_user_monitor(Id, Module, Data) -> + register_user_monitor(Id, Module, Data, []). + +register_user_monitor(Id, Module, Data, DefaultAgentConfig) -> + snmpm_server:register_user_monitor(Id, Module, Data, DefaultAgentConfig). + +unregister_user(Id) -> + snmpm_server:unregister_user(Id). + +which_users() -> + snmpm_config:which_users(). + + +%% -- Agents -- + +%% Explicitly instruct the manager to handle this agent. +%% Called to instruct the manager that this agent +%% shall be handled. These functions is used when +%% the user know's in advance which agents the +%% manager shall handle. +%% Note that there is an alternate way to do the same thing: +%% Add the agent to the manager config files. +%% +%% UserId -> Id of the user responsible for this agent: term() +%% TargetName -> Unique name for the agent: (string()) +%% Config -> Agent configuration: [config()] + +do_register_agent(UserId, TargetName, Config) -> + snmpm_config:register_agent(UserId, TargetName, Config). + +register_agent(UserId, TargetName, Config) + when (is_list(TargetName) andalso + (length(TargetName) > 0) andalso + is_list(Config)) -> + do_register_agent(UserId, TargetName, [{reg_type, target_name} | Config]); + +%% Backward compatibillity +%% Note that the agent engine id is a mandatory config option, +%% so this function *will* fail! +register_agent(UserId, Addr, Port) when is_integer(Port) -> + register_agent(UserId, Addr, Port, []); + +%% Backward compatibillity +register_agent(UserId, Addr, Config) when is_list(Config) -> + register_agent(UserId, Addr, ?DEFAULT_AGENT_PORT, Config). + +%% Backward compatibillity +%% Note that the agent engine id is a mandatory config option, +%% so this function *will* fail! +register_agent(UserId, Addr) -> + register_agent(UserId, Addr, ?DEFAULT_AGENT_PORT, []). + +%% Backward compatibillity +register_agent(UserId, Addr, Port, Config0) -> + case lists:keymember(target_name, 1, Config0) of + false -> + TargetName = mk_target_name(Addr, Port, Config0), + Config = [{reg_type, addr_port}, + {address, Addr}, {port, Port} | Config0], + do_register_agent(UserId, TargetName, ensure_engine_id(Config)); + true -> + {value, {_, TargetName}} = + lists:keysearch(target_name, 1, Config0), + Config1 = lists:keydelete(target_name, 1, Config0), + Config2 = [{reg_type, addr_port}, + {address, Addr}, {port, Port} | Config1], + register_agent(UserId, TargetName, ensure_engine_id(Config2)) + end. + +unregister_agent(UserId, TargetName) when is_list(TargetName) -> + snmpm_config:unregister_agent(UserId, TargetName); + +%% Backward compatibillity functions +unregister_agent(UserId, Addr) -> + unregister_agent(UserId, Addr, ?DEFAULT_AGENT_PORT). + +unregister_agent(UserId, Addr, Port) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + unregister_agent(UserId, TargetName); + Error -> + Error + end. + +agent_info(TargetName, Item) -> + snmpm_config:agent_info(TargetName, Item). + +%% Backward compatibillity +agent_info(Addr, Port, Item) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + agent_info(TargetName, Item); + Error -> + Error + end. + +update_agent_info(UserId, TargetName, Item, Val) -> +%% p("update_agent_info -> entry with" +%% "~n UserId: ~p" +%% "~n TargetName: ~p" +%% "~n Item: ~p" +%% "~n Val: ~p", [UserId, TargetName, Item, Val]), + snmpm_config:update_agent_info(UserId, TargetName, Item, Val). + +%% Backward compatibillity functions +update_agent_info(UserId, Addr, Port, Item, Val) -> +%% p("update_agent_info -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n Port: ~p" +%% "~n Item: ~p" +%% "~n Val: ~p", [UserId, Addr, Port, Item, Val]), + case target_name(Addr, Port) of + {ok, TargetName} -> +%% p("update_agent_info -> TargetName: ~p", [TargetName]), + update_agent_info(UserId, TargetName, Item, Val); + Error -> + Error + end. + +which_agents() -> + snmpm_config:which_agents(). + +which_agents(UserId) -> + snmpm_config:which_agents(UserId). + + +%% -- USM users -- + +register_usm_user(EngineID, UserName, Conf) + when is_list(EngineID) andalso is_list(UserName) andalso is_list(Conf) -> + snmpm_config:register_usm_user(EngineID, UserName, Conf). + +unregister_usm_user(EngineID, UserName) + when is_list(EngineID) andalso is_list(UserName) -> + snmpm_config:unregister_usm_user(EngineID, UserName). + +usm_user_info(EngineID, UserName, Item) + when is_list(EngineID) andalso is_list(UserName) andalso is_atom(Item) -> + snmpm_config:usm_user_info(EngineID, UserName, Item). + +update_usm_user_info(EngineID, UserName, Item, Val) + when is_list(EngineID) andalso is_list(UserName) andalso is_atom(Item) -> + snmpm_config:update_usm_user_info(EngineID, UserName, Item, Val). + +which_usm_users() -> + snmpm_config:which_usm_users(). + +which_usm_users(EngineID) when is_list(EngineID) -> + snmpm_config:which_usm_users(EngineID). + + +%% -- Discovery -- + +%% Start a discovery process +%% discovery(UserId, BAddr) -> +%% snmpm_server:discovery(UserId, BAddr). + +%% discovery(UserId, BAddr, ExpireOrConfig) -> +%% snmpm_server:discovery(UserId, BAddr, ExpireOrConfig). + +%% discovery(UserId, BAddr, Config, Expire) -> +%% snmpm_server:discovery(UserId, BAddr, Config, Expire). + +%% discovery(UserId, BAddr, Port, Config, Expire) -> +%% snmpm_server:discovery(UserId, BAddr, Port, Config, Expire). + +%% discovery(UserId, BAddr, Port, Config, Expire, ExtraInfo) -> +%% snmpm_server:discovery(UserId, BAddr, Port, Config, Expire, ExtraInfo). + + +%% -- Requests -- + +%% --- synchroneous get-request --- +%% + +sync_get(UserId, TargetName, Oids) -> +%% p("sync_get -> entry with" +%% "~n UserId: ~p" +%% "~n TargetName: ~p" +%% "~n Oids: ~p", [UserId, TargetName, Oids]), + sync_get(UserId, TargetName, ?DEFAULT_CONTEXT, Oids). + +sync_get(UserId, TargetName, Context, Oids) when is_list(Oids) -> +%% p("sync_get -> entry with" +%% "~n UserId: ~p" +%% "~n TargetName: ~p" +%% "~n Context: ~p" +%% "~n Oids: ~p", [UserId, TargetName, Context, Oids]), + snmpm_server:sync_get(UserId, TargetName, Context, Oids); + +sync_get(UserId, TargetName, Oids, Timeout) when is_integer(Timeout) -> +%% p("sync_get -> entry with" +%% "~n UserId: ~p" +%% "~n TargetName: ~p" +%% "~n Oids: ~p" +%% "~n Timeout: ~p", [UserId, TargetName, Oids, Timeout]), + sync_get(UserId, TargetName, ?DEFAULT_CONTEXT, Oids, Timeout). + +sync_get(UserId, TargetName, Context, Oids, Timeout) -> +%% p("sync_get -> entry with" +%% "~n UserId: ~p" +%% "~n TargetName: ~p" +%% "~n Context: ~p" +%% "~n Oids: ~p" +%% "~n Timeout: ~p", [UserId, TargetName, Context, Oids, Timeout]), + snmpm_server:sync_get(UserId, TargetName, Context, Oids, Timeout). + +sync_get(UserId, TargetName, Context, Oids, Timeout, ExtraInfo) -> +%% p("sync_get -> entry with" +%% "~n UserId: ~p" +%% "~n TargetName: ~p" +%% "~n Context: ~p" +%% "~n Oids: ~p" +%% "~n Timeout: ~p" +%% "~n ExtraInfo: ~p", +%% [UserId, TargetName, Context, Oids, Timeout, ExtraInfo]), + snmpm_server:sync_get(UserId, TargetName, Context, Oids, Timeout, + ExtraInfo). + + +g(UserId, Addr, Oids) -> +%% p("g -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n Oids: ~p", [UserId, Addr, Oids]), + g(UserId, Addr, ?DEFAULT_AGENT_PORT, Oids). + +g(UserId, Addr, CtxName, Oids) when is_list(CtxName) andalso is_list(Oids) -> +%% p("g -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n CtxName: ~p" +%% "~n Oids: ~p", [UserId, Addr, CtxName, Oids]), + g(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, Oids); + +g(UserId, Addr, Port, Oids) when is_integer(Port) andalso is_list(Oids) -> +%% p("g -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n Port: ~p" +%% "~n Oids: ~p", [UserId, Addr, Port, Oids]), + g(UserId, Addr, Port, ?DEFAULT_CONTEXT, Oids); + +g(UserId, Addr, Oids, Timeout) + when is_list(Oids) andalso is_integer(Timeout) -> +%% p("g -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n Oids: ~p" +%% "~n Timeout: ~p", [UserId, Addr, Oids, Timeout]), + g(UserId, Addr, ?DEFAULT_AGENT_PORT, Oids, Timeout). + +g(UserId, Addr, Port, CtxName, Oids) + when is_integer(Port) andalso is_list(CtxName) andalso is_list(Oids) -> +%% p("g -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n Port: ~p" +%% "~n Context: ~p" +%% "~n Oids: ~p", [UserId, Addr, Port, CtxName, Oids]), + case target_name(Addr, Port) of + {ok, TargetName} -> +%% p("g -> TargetName: ~p", [TargetName]), + sync_get(UserId, TargetName, CtxName, Oids); + Error -> + Error + end; + +g(UserId, Addr, Port, Oids, Timeout) + when is_integer(Port) andalso is_list(Oids) andalso is_integer(Timeout) -> +%% p("g -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n Oids: ~p" +%% "~n Timeout: ~p", +%% [UserId, Addr, Oids, Timeout]), + g(UserId, Addr, Port, ?DEFAULT_CONTEXT, Oids, Timeout); + +g(UserId, Addr, CtxName, Oids, Timeout) + when is_list(CtxName) andalso is_list(Oids) andalso is_integer(Timeout) -> +%% p("g -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n CtxName: ~p" +%% "~n Oids: ~p" +%% "~n Timeout: ~p", +%% [UserId, Addr, CtxName, Oids, Timeout]), + g(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, Oids, Timeout). + +g(UserId, Addr, Port, CtxName, Oids, Timeout) -> +%% p("g -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n Port: ~p" +%% "~n CtxName: ~p" +%% "~n Oids: ~p" +%% "~n Timeout: ~p", +%% [UserId, Addr, Port, CtxName, Oids, Timeout]), + case target_name(Addr, Port) of + {ok, TargetName} -> +%% p("g -> TargetName: ~p", [TargetName]), + sync_get(UserId, TargetName, CtxName, Oids, Timeout); + Error -> + Error + end. + +g(UserId, Addr, Port, CtxName, Oids, Timeout, ExtraInfo) -> +%% p("g -> entry with" +%% "~n UserId: ~p" +%% "~n Addr: ~p" +%% "~n Port: ~p" +%% "~n CtxName: ~p" +%% "~n Oids: ~p" +%% "~n Timeout: ~p" +%% "~n ExtraInfo: ~p", +%% [UserId, Addr, Port, CtxName, Oids, Timeout, ExtraInfo]), + case target_name(Addr, Port) of + {ok, TargetName} -> +%% p("g -> TargetName: ~p", [TargetName]), + sync_get(UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo); + Error -> + Error + end. + + + +%% --- asynchroneous get-request --- +%% +%% The reply will be delivered to the user +%% through a call to handle_pdu/5 +%% + +async_get(UserId, TargetName, Oids) -> + async_get(UserId, TargetName, ?DEFAULT_CONTEXT, Oids). + +async_get(UserId, TargetName, Context, Oids) when is_list(Oids) -> + snmpm_server:async_get(UserId, TargetName, Context, Oids); + +async_get(UserId, TargetName, Oids, Expire) when is_integer(Expire) -> + async_get(UserId, TargetName, ?DEFAULT_CONTEXT, Oids, Expire). + +async_get(UserId, TargetName, Context, Oids, Expire) -> + snmpm_server:async_get(UserId, TargetName, Context, Oids, Expire). + +async_get(UserId, TargetName, Context, Oids, Expire, ExtraInfo) -> + snmpm_server:async_get(UserId, TargetName, Context, Oids, Expire, + ExtraInfo). + + +ag(UserId, Addr, Oids) -> + ag(UserId, Addr, ?DEFAULT_AGENT_PORT, Oids). + +ag(UserId, Addr, Port, Oids) when is_integer(Port) andalso is_list(Oids) -> + ag(UserId, Addr, Port, ?DEFAULT_CONTEXT, Oids); + +ag(UserId, Addr, CtxName, Oids) when is_list(CtxName) andalso is_list(Oids) -> + ag(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, Oids); + +ag(UserId, Addr, Oids, Expire) when is_list(Oids) andalso is_integer(Expire) -> + ag(UserId, Addr, ?DEFAULT_AGENT_PORT, ?DEFAULT_CONTEXT, Oids, Expire). + +ag(UserId, Addr, Port, CtxName, Oids) + when is_integer(Port) andalso is_list(CtxName) andalso is_list(Oids) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_get(UserId, TargetName, CtxName, Oids); + Error -> + Error + end; + +ag(UserId, Addr, Port, Oids, Expire) + when is_integer(Port) andalso is_list(Oids) andalso is_integer(Expire) -> + ag(UserId, Addr, Port, ?DEFAULT_CONTEXT, Oids, Expire); + +ag(UserId, Addr, CtxName, Oids, Expire) + when is_list(CtxName) andalso is_list(Oids) andalso is_integer(Expire) -> + ag(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, Oids, Expire). + +ag(UserId, Addr, Port, CtxName, Oids, Expire) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_get(UserId, TargetName, CtxName, Oids, Expire); + Error -> + Error + end. + +ag(UserId, Addr, Port, CtxName, Oids, Expire, ExtraInfo) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_get(UserId, TargetName, CtxName, Oids, Expire, ExtraInfo); + Error -> + Error + end. + + + +%% --- synchroneous get_next-request --- +%% + +sync_get_next(UserId, TargetName, Oids) -> + sync_get_next(UserId, TargetName, ?DEFAULT_CONTEXT, Oids). + +sync_get_next(UserId, TargetName, Context, Oids) + when is_list(Context) andalso is_list(Oids) -> + snmpm_server:sync_get_next(UserId, TargetName, Context, Oids); + +sync_get_next(UserId, TargetName, Oids, Timeout) + when is_list(Oids) andalso is_integer(Timeout) -> + sync_get_next(UserId, TargetName, ?DEFAULT_CONTEXT, Oids, Timeout). + +sync_get_next(UserId, TargetName, Context, Oids, Timeout) -> + snmpm_server:sync_get_next(UserId, TargetName, Context, Oids, Timeout). + +sync_get_next(UserId, TargetName, Context, Oids, Timeout, ExtraInfo) -> + snmpm_server:sync_get_next(UserId, TargetName, Context, Oids, Timeout, + ExtraInfo). + + +gn(UserId, Addr, Oids) -> + gn(UserId, Addr, ?DEFAULT_AGENT_PORT, Oids). + +gn(UserId, Addr, CtxName, Oids) when is_list(CtxName) andalso is_list(Oids) -> + gn(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, Oids); + +gn(UserId, Addr, Port, Oids) when is_integer(Port) andalso is_list(Oids) -> + gn(UserId, Addr, Port, ?DEFAULT_CONTEXT, Oids); + +gn(UserId, Addr, Oids, Timeout) + when is_list(Oids) andalso is_integer(Timeout) -> + gn(UserId, Addr, ?DEFAULT_AGENT_PORT, Oids, Timeout). + +gn(UserId, Addr, Port, CtxName, Oids) + when is_integer(Port) andalso is_list(CtxName) andalso is_list(Oids) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + sync_get_next(UserId, TargetName, CtxName, Oids); + Error -> + Error + end; + +gn(UserId, Addr, Port, Oids, Timeout) + when is_integer(Port) andalso is_list(Oids) andalso is_integer(Timeout) -> + gn(UserId, Addr, Port, ?DEFAULT_CONTEXT, Oids, Timeout); +gn(UserId, Addr, CtxName, Oids, Timeout) + when is_list(CtxName) andalso is_list(Oids) andalso is_integer(Timeout) -> + gn(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, Oids, Timeout). + +gn(UserId, Addr, Port, CtxName, Oids, Timeout) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + sync_get_next(UserId, TargetName, CtxName, Oids, Timeout); + Error -> + Error + end. + +gn(UserId, Addr, Port, CtxName, Oids, Timeout, ExtraInfo) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + sync_get_next(UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo); + Error -> + Error + end. + + + +%% --- asynchroneous get_next-request --- +%% + +async_get_next(UserId, TargetName, Oids) -> + async_get_next(UserId, TargetName, ?DEFAULT_CONTEXT, Oids). + +async_get_next(UserId, TargetName, Context, Oids) + when is_list(Context) andalso is_list(Oids) -> + snmpm_server:async_get_next(UserId, TargetName, Context, Oids); + +async_get_next(UserId, TargetName, Oids, Timeout) + when is_list(Oids) andalso is_integer(Timeout) -> + async_get_next(UserId, TargetName, ?DEFAULT_CONTEXT, Oids, Timeout). + +async_get_next(UserId, TargetName, Context, Oids, Timeout) -> + snmpm_server:async_get_next(UserId, TargetName, Context, Oids, Timeout). + +async_get_next(UserId, TargetName, Context, Oids, Timeout, ExtraInfo) -> + snmpm_server:async_get_next(UserId, TargetName, Context, Oids, Timeout, + ExtraInfo). + +agn(UserId, Addr, Oids) -> + agn(UserId, Addr, ?DEFAULT_AGENT_PORT, Oids). + +agn(UserId, Addr, CtxName, Oids) when is_list(CtxName) andalso is_list(Oids) -> + agn(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, Oids); + +agn(UserId, Addr, Port, Oids) when is_integer(Port) andalso is_list(Oids) -> + agn(UserId, Addr, Port, ?DEFAULT_CONTEXT, Oids); + +agn(UserId, Addr, Oids, Expire) + when is_list(Oids) andalso is_integer(Expire) -> + agn(UserId, Addr, ?DEFAULT_AGENT_PORT, Oids, Expire). + +agn(UserId, Addr, Port, CtxName, Oids) + when is_integer(Port) andalso is_list(CtxName) andalso is_list(Oids) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_get_next(UserId, TargetName, CtxName, Oids); + Error -> + Error + end; + +agn(UserId, Addr, Port, Oids, Expire) + when is_integer(Port) andalso is_list(Oids) andalso is_integer(Expire) -> + agn(UserId, Addr, Port, ?DEFAULT_CONTEXT, Oids, Expire); +agn(UserId, Addr, CtxName, Oids, Expire) + when is_list(CtxName) andalso is_list(CtxName) andalso is_integer(Expire) -> + agn(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, Oids, Expire). + +agn(UserId, Addr, Port, CtxName, Oids, Expire) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_get_next(UserId, TargetName, CtxName, Oids, Expire); + Error -> + Error + end. + +agn(UserId, Addr, Port, CtxName, Oids, Expire, ExtraInfo) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_get_next(UserId, TargetName, CtxName, Oids, Expire, + ExtraInfo); + Error -> + Error + end. + + + +%% --- synchroneous set-request --- +%% + +sync_set(UserId, TargetName, VarsAndVals) -> + sync_set(UserId, TargetName, ?DEFAULT_CONTEXT, VarsAndVals). + +sync_set(UserId, TargetName, Context, VarsAndVals) + when is_list(Context) andalso is_list(VarsAndVals) -> + snmpm_server:sync_set(UserId, TargetName, Context, VarsAndVals); + +sync_set(UserId, TargetName, VarsAndVals, Timeout) + when is_list(VarsAndVals) andalso is_integer(Timeout) -> + sync_set(UserId, TargetName, ?DEFAULT_CONTEXT, VarsAndVals, Timeout). + +sync_set(UserId, TargetName, Context, VarsAndVals, Timeout) -> + snmpm_server:sync_set(UserId, TargetName, Context, VarsAndVals, Timeout). + +sync_set(UserId, TargetName, Context, VarsAndVals, Timeout, ExtraInfo) -> + snmpm_server:sync_set(UserId, TargetName, Context, VarsAndVals, Timeout, + ExtraInfo). + + +s(UserId, Addr, VarsAndVals) -> + s(UserId, Addr, ?DEFAULT_AGENT_PORT, VarsAndVals). + +s(UserId, Addr, Port, VarsAndVals) + when is_integer(Port) andalso is_list(VarsAndVals) -> + s(UserId, Addr, Port, ?DEFAULT_CONTEXT, VarsAndVals); + +s(UserId, Addr, CtxName, VarsAndVals) + when is_list(CtxName) andalso is_list(VarsAndVals) -> + s(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, VarsAndVals); + +s(UserId, Addr, VarsAndVals, Timeout) + when is_list(VarsAndVals) andalso is_integer(Timeout) -> + s(UserId, Addr, ?DEFAULT_AGENT_PORT, VarsAndVals, Timeout). + +s(UserId, Addr, Port, CtxName, VarsAndVals) + when is_integer(Port) andalso + is_list(CtxName) andalso + is_list(VarsAndVals) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + sync_set(UserId, TargetName, CtxName, VarsAndVals); + Error -> + Error + end; + +s(UserId, Addr, Port, VarsAndVals, Timeout) + when is_integer(Port) andalso + is_list(VarsAndVals) andalso + is_integer(Timeout) -> + s(UserId, Addr, Port, ?DEFAULT_CONTEXT, VarsAndVals, Timeout); + +s(UserId, Addr, CtxName, VarsAndVals, Timeout) + when is_list(CtxName) andalso + is_list(VarsAndVals) andalso + is_integer(Timeout) -> + s(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, VarsAndVals, Timeout). + +s(UserId, Addr, Port, CtxName, VarsAndVals, Timeout) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + sync_set(UserId, TargetName, CtxName, VarsAndVals, Timeout); + Error -> + Error + end. + +s(UserId, Addr, Port, CtxName, VarsAndVals, Timeout, ExtraInfo) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + sync_set(UserId, TargetName, CtxName, VarsAndVals, Timeout, ExtraInfo); + Error -> + Error + end. + + + +%% --- asynchroneous set-request --- +%% + +async_set(UserId, TargetName, VarsAndVals) -> + async_set(UserId, TargetName, ?DEFAULT_CONTEXT, VarsAndVals). + +async_set(UserId, TargetName, Context, VarsAndVals) + when is_list(Context) andalso is_list(VarsAndVals) -> + snmpm_server:async_set(UserId, TargetName, Context, VarsAndVals); + +async_set(UserId, TargetName, VarsAndVals, Expire) + when is_list(VarsAndVals) andalso is_integer(Expire) -> + async_set(UserId, TargetName, ?DEFAULT_CONTEXT, VarsAndVals, Expire). + +async_set(UserId, TargetName, Context, VarsAndVals, Expire) -> + snmpm_server:async_set(UserId, TargetName, Context, VarsAndVals, Expire). + +async_set(UserId, TargetName, Context, VarsAndVals, Expire, ExtraInfo) -> + snmpm_server:async_set(UserId, TargetName, Context, VarsAndVals, Expire, + ExtraInfo). + + +as(UserId, Addr, VarsAndVals) -> + as(UserId, Addr, ?DEFAULT_AGENT_PORT, VarsAndVals). + +as(UserId, Addr, Port, VarsAndVals) + when is_integer(Port) andalso is_list(VarsAndVals) -> + as(UserId, Addr, Port, ?DEFAULT_CONTEXT, VarsAndVals); + +as(UserId, Addr, CtxName, VarsAndVals) + when is_list(CtxName) andalso is_list(VarsAndVals) -> + as(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, VarsAndVals); + +as(UserId, Addr, VarsAndVals, Expire) + when is_list(VarsAndVals) andalso is_integer(Expire) -> + as(UserId, Addr, ?DEFAULT_AGENT_PORT, VarsAndVals, Expire). + +as(UserId, Addr, Port, CtxName, VarsAndVals) + when is_integer(Port) andalso + is_list(CtxName) andalso + is_list(VarsAndVals) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_set(UserId, TargetName, CtxName, VarsAndVals); + Error -> + Error + end; + +as(UserId, Addr, Port, VarsAndVals, Expire) + when is_integer(Port) andalso + is_list(VarsAndVals) andalso + is_integer(Expire) -> + as(UserId, Addr, Port, ?DEFAULT_CONTEXT, VarsAndVals, Expire); + +as(UserId, Addr, CtxName, VarsAndVals, Expire) + when is_list(CtxName) andalso + is_list(VarsAndVals) andalso + is_integer(Expire) -> + as(UserId, Addr, ?DEFAULT_AGENT_PORT, CtxName, VarsAndVals, Expire). + +as(UserId, Addr, Port, CtxName, VarsAndVals, Expire) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_set(UserId, TargetName, CtxName, VarsAndVals, Expire); + Error -> + Error + end. + +as(UserId, Addr, Port, CtxName, VarsAndVals, Expire, ExtraInfo) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_set(UserId, TargetName, CtxName, VarsAndVals, Expire, ExtraInfo); + Error -> + Error + end. + + + + +%% --- synchroneous get-bulk --- +%% + +sync_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) -> + sync_get_bulk(UserId, TargetName, NonRep, MaxRep, ?DEFAULT_CONTEXT, Oids). + +sync_get_bulk(UserId, TargetName, NonRep, MaxRep, Context, Oids) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Context) andalso + is_list(Oids) -> + snmpm_server:sync_get_bulk(UserId, TargetName, + NonRep, MaxRep, + Context, Oids); + +sync_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, Timeout) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Oids) andalso + is_integer(Timeout) -> + sync_get_bulk(UserId, TargetName, NonRep, MaxRep, + ?DEFAULT_CONTEXT, Oids, Timeout). + +sync_get_bulk(UserId, TargetName, NonRep, MaxRep, Context, Oids, Timeout) -> + snmpm_server:sync_get_bulk(UserId, TargetName, NonRep, MaxRep, + Context, Oids, Timeout). + +sync_get_bulk(UserId, TargetName, NonRep, MaxRep, Context, Oids, Timeout, + ExtraInfo) -> + snmpm_server:sync_get_bulk(UserId, TargetName, NonRep, MaxRep, + Context, Oids, Timeout, ExtraInfo). + + +gb(UserId, Addr, NonRep, MaxRep, Oids) -> + gb(UserId, Addr, ?DEFAULT_AGENT_PORT, NonRep, MaxRep, Oids). + +gb(UserId, Addr, Port, NonRep, MaxRep, Oids) + when is_integer(Port) andalso + is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Oids) -> + gb(UserId, Addr, Port, NonRep, MaxRep, ?DEFAULT_CONTEXT, Oids); + +gb(UserId, Addr, NonRep, MaxRep, CtxName, Oids) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(CtxName) andalso + is_list(Oids) -> + gb(UserId, Addr, ?DEFAULT_AGENT_PORT, NonRep, MaxRep, CtxName, Oids); + +gb(UserId, Addr, NonRep, MaxRep, Oids, Timeout) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Oids) andalso + is_integer(Timeout) -> + gb(UserId, Addr, ?DEFAULT_AGENT_PORT, NonRep, MaxRep, Oids, Timeout). + +gb(UserId, Addr, Port, NonRep, MaxRep, CtxName, Oids) + when is_integer(Port) andalso + is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(CtxName) andalso + is_list(Oids) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids); + Error -> + Error + end; + +gb(UserId, Addr, Port, NonRep, MaxRep, Oids, Timeout) + when is_integer(Port) andalso + is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Oids) andalso + is_integer(Timeout) -> + gb(UserId, Addr, Port, NonRep, MaxRep, ?DEFAULT_CONTEXT, Oids, Timeout); + +gb(UserId, Addr, NonRep, MaxRep, CtxName, Oids, Timeout) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(CtxName) andalso + is_list(Oids) andalso + is_integer(Timeout) -> + gb(UserId, Addr, ?DEFAULT_AGENT_PORT, NonRep, MaxRep, CtxName, Oids, + Timeout). + +gb(UserId, Addr, Port, NonRep, MaxRep, CtxName, Oids, Timeout) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + sync_get_bulk(UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, Timeout); + Error -> + Error + end. + +gb(UserId, Addr, Port, NonRep, MaxRep, CtxName, Oids, Timeout, ExtraInfo) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + sync_get_bulk(UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, Timeout, ExtraInfo); + Error -> + Error + end. + + + +%% --- asynchroneous get-bulk --- +%% + +async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) -> + async_get_bulk(UserId, TargetName, NonRep, MaxRep, ?DEFAULT_CONTEXT, Oids). + +async_get_bulk(UserId, TargetName, NonRep, MaxRep, Context, Oids) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Context) andalso + is_list(Oids) -> + snmpm_server:async_get_bulk(UserId, TargetName, + NonRep, MaxRep, Context, Oids); + +async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, Expire) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Oids) andalso + is_integer(Expire) -> + async_get_bulk(UserId, TargetName, + NonRep, MaxRep, ?DEFAULT_CONTEXT, Oids, Expire). + +async_get_bulk(UserId, TargetName, NonRep, MaxRep, Context, Oids, Expire) -> + snmpm_server:async_get_bulk(UserId, TargetName, + NonRep, MaxRep, Context, Oids, Expire). + +async_get_bulk(UserId, TargetName, NonRep, MaxRep, Context, Oids, Expire, + ExtraInfo) -> + snmpm_server:async_get_bulk(UserId, TargetName, + NonRep, MaxRep, + Context, Oids, Expire, ExtraInfo). + + +agb(UserId, Addr, NonRep, MaxRep, Oids) -> + agb(UserId, Addr, ?DEFAULT_AGENT_PORT, NonRep, MaxRep, Oids). + +agb(UserId, Addr, Port, NonRep, MaxRep, Oids) + when is_integer(Port) andalso + is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Oids) -> + agb(UserId, Addr, Port, NonRep, MaxRep, ?DEFAULT_CONTEXT, Oids); + +agb(UserId, Addr, NonRep, MaxRep, CtxName, Oids) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(CtxName) andalso + is_list(Oids) -> + agb(UserId, Addr, ?DEFAULT_AGENT_PORT, NonRep, MaxRep, CtxName, Oids); + +agb(UserId, Addr, NonRep, MaxRep, Oids, Expire) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Oids) andalso + is_integer(Expire) -> + agb(UserId, Addr, ?DEFAULT_AGENT_PORT, NonRep, MaxRep, Oids, Expire). + +agb(UserId, Addr, Port, NonRep, MaxRep, CtxName, Oids) + when is_integer(Port) andalso + is_integer(NonRep) andalso + is_integer(MaxRep), + is_list(CtxName) andalso + is_list(Oids) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_get_bulk(UserId, TargetName, + NonRep, MaxRep, CtxName, Oids); + Error -> + Error + end; + +agb(UserId, Addr, Port, NonRep, MaxRep, Oids, Expire) + when is_integer(Port) andalso + is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(Oids) andalso + is_integer(Expire) -> + agb(UserId, Addr, Port, NonRep, MaxRep, ?DEFAULT_CONTEXT, Oids, Expire); + +agb(UserId, Addr, NonRep, MaxRep, CtxName, Oids, Expire) + when is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(CtxName) andalso + is_list(Oids) andalso + is_integer(Expire) -> + agb(UserId, Addr, ?DEFAULT_AGENT_PORT, NonRep, MaxRep, CtxName, Oids). + +agb(UserId, Addr, Port, NonRep, MaxRep, CtxName, Oids, Expire) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_get_bulk(UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, Expire); + Error -> + Error + end. + +agb(UserId, Addr, Port, NonRep, MaxRep, CtxName, Oids, Expire, ExtraInfo) -> + case target_name(Addr, Port) of + {ok, TargetName} -> + async_get_bulk(UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, Expire, + ExtraInfo); + Error -> + Error + end. + + +cancel_async_request(UserId, ReqId) -> + snmpm_server:cancel_async_request(UserId, ReqId). + + +%%%----------------------------------------------------------------- +%%% Audit Trail Log functions (for backward compatibillity) +%%%----------------------------------------------------------------- +log_to_txt(LogDir, Mibs) -> + OutFile = "snmpm_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, + snmp:change_log_size(LogName, NewSize). + + +get_log_type() -> + snmpm_server:get_log_type(). + +%% NewType -> atl_type() +set_log_type(NewType) -> + snmpm_server:set_log_type(NewType). + + +reconfigure() -> + snmpm_server:reconfigure(). + + +%%%----------------------------------------------------------------- + +system_start_time() -> + {ok, Time} = snmpm_config: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). + + +%%%----------------------------------------------------------------- +%%% This is just some simple utility functions to create a pretty- +%%% printable string of the error reason received from either: +%%% +%%% * If any of the sync/async get/get-next/set/get-bulk +%%% returnes {error, Reason} +%%% * The Reason parameter in the handle_error user callback +%%% function +%%% +%%%----------------------------------------------------------------- + +format_reason(Reason) -> + format_reason("", Reason). + +format_reason(Prefix, Reason) when is_integer(Prefix) and (Prefix >= 0) -> + format_reason(lists:duplicate(Prefix, $ ), Reason); +format_reason(Prefix, Reason) when is_list(Prefix) -> + case (catch do_format_reason(Prefix, Reason)) of + FL when is_list(FL) -> + FL; + _ -> + %% Crap, try it without any fancy formatting + case (catch io_lib:format("~sInternal manager error: ~n" + "~s ~p~n", + [Prefix, Prefix, Reason])) of + L1 when is_list(L1) -> + lists:flatten(L1); + _ -> + %% Really crap, try it without the prefix + case (catch io_lib:format("Internal manager error: ~n" + " ~p~n", + [Reason])) of + L2 when is_list(L2) -> + lists:flatten(L2); + _ -> + %% Ok, I give up + "Illegal input. Unable to format error reason" + end + end + end. + + +do_format_reason(Prefix, {failed_generating_response, {RePdu, Reason}}) -> + FmtPdu = format_pdu(Prefix ++ " ", RePdu), + lists:flatten(io_lib:format("~sFailed generating response: ~n" + "~s" + "~s ~p~n", + [Prefix, FmtPdu, Prefix, Reason])); +do_format_reason(Prefix, {failed_processing_message, Reason}) -> + lists:flatten(io_lib:format("~sFailed processing message: ~n" + "~s ~p~n", + [Prefix, Prefix, Reason])); +do_format_reason(Prefix, {unexpected_pdu, SnmpInfo}) -> + FmtSnmpInfo = format_snmp_info(Prefix ++ " ", SnmpInfo), + lists:flatten(io_lib:format("~sUnexpected PDU: ~n~s", + [Prefix, FmtSnmpInfo])); +do_format_reason(Prefix, {send_failed, ReqId, Reason}) -> + lists:flatten(io_lib:format("~sSend failed: ~n" + "~s Request id: ~w~n" + "~s Reason: ~p~n", + [Prefix, Prefix, ReqId, Prefix, Reason])); +do_format_reason(Prefix, {invalid_sec_info, SecInfo, SnmpInfo}) -> + FmtSecInfo = format_sec_info(Prefix ++ " ", SecInfo), + FmtSnmpInfo = format_snmp_info(Prefix ++ " ", SnmpInfo), + lists:flatten(io_lib:format("~sInvalid security info: ~n" + "~s" + "~s", + [Prefix, FmtSecInfo, FmtSnmpInfo])); +do_format_reason(Prefix, Reason) -> + lists:flatten(io_lib:format("~sInternal manager error: ~n" + "~s ~p~n", [Prefix, Prefix, Reason])). + +format_pdu(Prefix, #pdu{type = Type, + request_id = ReqId, + error_status = ES, + error_index = EI, + varbinds = VBs}) -> + FmtPdyType = format_pdu_type(Type), + FmtErrStatus = format_error_status(ES), + FmtErrIdx = format_error_index(EI), + FmtVBs = format_varbinds(Prefix ++ " ", VBs), + lists:flatten(io_lib:format("~s~s: ~n" + "~s Request-id: ~w~n" + "~s Error-status: ~s~n" + "~s Error-index: ~s~n" + "~s", + [Prefix, FmtPdyType, + Prefix, ReqId, + Prefix, FmtErrStatus, + Prefix, FmtErrIdx, + FmtVBs])); +format_pdu(Prefix, #trappdu{enterprise = E, + agent_addr = AA, + generic_trap = GT, + specific_trap = ST, + time_stamp = TS, + varbinds = VBs}) -> + FmtVBs = format_varbinds(Prefix ++ " ", VBs), + lists:flatten(io_lib:format("~sTrap PDU: ~n" + "~s Enterprise: ~p~n" + "~s Agent address: ~p~n" + "~s Generic trap: ~p~n" + "~s Specific trap: ~p~n" + "~s Time stamp: ~p~n" + "~s", + [Prefix, + Prefix, E, + Prefix, AA, + Prefix, GT, + Prefix, ST, + Prefix, TS, + FmtVBs])); +format_pdu(Prefix, PDU) -> + lists:flatten(io_lib:format("~s~p~n", [Prefix, PDU])). + +format_pdu_type('get-request') -> + "GetRequest-PDU"; +format_pdu_type('get-next-request') -> + "GetNextRequest-PDU"; +format_pdu_type('get-response') -> + "Response-PDU"; +format_pdu_type('set-request') -> + "SetRequest-PDU"; +format_pdu_type('get-bulk-request') -> + "GetBulkRequest-PDU"; +format_pdu_type('inform-request') -> + "InformRequest-PDU"; +format_pdu_type('snmpv2-trap') -> + "SNMPv2-Trap-PDU"; +format_pdu_type(report) -> + "Report-PDU"; +format_pdu_type(T) -> + lists:flatten(io_lib:format("~p", [T])). + +format_snmp_info(Prefix, {ES, EI, VBs}) -> + lists:flatten(io_lib:format("~sSNMP info: ~n" + "~s Error-status: ~s~n" + "~s Error-index: ~s~n" + "~s", + [Prefix, + Prefix, format_error_status(ES), + Prefix, format_error_index(EI), + format_varbinds(Prefix ++ " ", VBs)])); +format_snmp_info(Prefix, JunkSnmpInfo) -> + lists:flatten(io_lib:format("~sJunk SNMP info: ~n" + "~s ~p~n", + [Prefix, Prefix, JunkSnmpInfo])). + +format_error_status(ES) -> + lists:flatten(io_lib:format("~p", [ES])). + +format_error_index(EI) -> + lists:flatten(io_lib:format("~p", [EI])). + +format_sec_info(Prefix, Info) -> + FmtSecInfo = do_format_sec_info(Prefix ++ " ", Info), + lists:flatten(io_lib:format("~sSecurity info: ~n~s", + [Prefix, FmtSecInfo])). + +do_format_sec_info(_Prefix, []) -> + ""; +do_format_sec_info(Prefix, [{Tag, ExpVal, Val}|T]) -> + format_sec_info(Prefix, Tag, ExpVal, Val) ++ + do_format_sec_info(Prefix, T). + + +format_sec_info(_Prefix, _Tag, Val, Val) -> + ""; +format_sec_info(Prefix, Tag, ExpVal, Val) -> + lists:flatten(io_lib:format("~s~s:~n" + "~s Expected value: ~p~n" + "~s Actual value: ~p~n", + [Prefix, format_sec_info_tag(Tag), + Prefix, ExpVal, + Prefix, Val])). + +format_sec_info_tag(sec_engine_id) -> + "Sec engine id"; +format_sec_info_tag(msg_sec_model) -> + "Msg sec model"; +format_sec_info_tag(sec_name) -> + "Sec name"; +format_sec_info_tag(sec_level) -> + "Sec level"; +format_sec_info_tag(ctx_engine_id) -> + "Context engine id"; +format_sec_info_tag(ctx_name) -> + "Context name"; +format_sec_info_tag(request_id) -> + "Request id"; +format_sec_info_tag(T) -> + lists:flatten(io_lib:format("~p", [T])). + +format_varbinds(Prefix, []) -> + lists:flatten(io_lib:format("~sVarbinds: []~n", [Prefix])); +format_varbinds(Prefix, VBs) when is_list(VBs) -> + lists:flatten(io_lib:format("~sVarbinds: ~n~s", + [Prefix, format_vbs(Prefix ++ " ", VBs)])); +format_varbinds(Prefix, VBs) -> + lists:flatten(io_lib:format("~sInvalid varbinds: ~n" + "~s ~p~n", + [Prefix, Prefix, VBs])). + +format_vbs(_Prefix, []) -> + ""; +format_vbs(Prefix, [VB|VBs]) -> + format_vb(Prefix, VB) ++ format_vbs(Prefix, VBs). + +format_vb(Prefix, #varbind{oid = Oid0, + variabletype = Type, + value = Val, + org_index = Idx}) -> + Oid = + case snmpm:oid_to_name(Oid0) of + {ok, O} -> + O; + _ -> + Oid0 + end, + FmtVT = format_vb_variabletype(Prefix ++ " ", Type), + FmtVal = format_vb_value(Prefix ++ " ", Type, Val), + lists:flatten(io_lib:format("~s~w:~n" + "~s" + "~s" + "~s Org-index: ~p~n", + [Prefix, Oid, + FmtVT, + FmtVal, + Prefix, Idx])); +format_vb(Prefix, JunkVB) -> + lists:flatten(io_lib:format("~sJunk varbind:~n" + "~s ~p~n", [Prefix, Prefix, JunkVB])). + +format_vb_variabletype(Prefix, Type) when is_atom(Type) -> + lists:flatten(io_lib:format("~sVariable-type: ~s~n", + [Prefix, atom_to_list(Type)])); +format_vb_variabletype(Prefix, Type) -> + lists:flatten(io_lib:format("~sVariable-type: ~p~n", [Prefix, Type])). + +format_vb_value(Prefix, _Type, Val) -> + lists:flatten(io_lib:format("~sValue: ~p~n", [Prefix, Val])). + + +%% --------------------------------------------------------------------------- +%% +%% --- Internal utility functions --- +%% + +target_name(Addr, Port) -> + snmpm_config:agent_info(Addr, Port, target_name). + +mk_target_name(Addr, Port, Config) -> + snmpm_config:mk_target_name(Addr, Port, Config). + +ensure_engine_id(Config) -> + case lists:keymember(engine_id, 1, Config) of + true -> + Config; + false -> + DefaultEngineId = "agentEngine-default", + [{engine_id, DefaultEngineId} | Config] + end. + + + +%% p(F) -> +%% p(F, []). + +%% p(F, A) -> +%% io:format("~w:" ++ F ++ "~n", [?MODULE | A]). diff --git a/lib/snmp/src/manager/snmpm_atl.hrl b/lib/snmp/src/manager/snmpm_atl.hrl new file mode 100644 index 0000000000..d99ee05ae6 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_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, "snmpm_log"). +-define(audit_trail_log_file, "snmpm.log"). diff --git a/lib/snmp/src/manager/snmpm_conf.erl b/lib/snmp/src/manager/snmpm_conf.erl new file mode 100644 index 0000000000..75f9c09477 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_conf.erl @@ -0,0 +1,396 @@ +%% +%% %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(snmpm_conf). + +-include_lib("kernel/include/file.hrl"). + +-export([ + %% manager.conf + manager_entry/2, + write_manager_config/2, write_manager_config/3, + append_manager_config/2, + read_manager_config/1, + + %% users.conf + users_entry/1, users_entry/2, users_entry/3, + write_users_config/2, write_users_config/3, + append_users_config/2, + read_users_config/1, + + %% agents.conf + agents_entry/12, + write_agents_config/2, write_agents_config/3, + append_agents_config/2, + read_agents_config/1, + + %% usm.conf + usm_entry/6, usm_entry/7, + write_usm_config/2, write_usm_config/3, + append_usm_config/2, + read_usm_config/1 + ]). + + + +-define(MANAGER_CONF_FILE, "manager.conf"). +-define(USERS_CONF_FILE, "users.conf"). +-define(AGENTS_CONF_FILE, "agents.conf"). +-define(USM_USERS_CONF_FILE, "usm.conf"). + +%% +%% ------ manager.conf ------ +%% + +manager_entry(Tag, Val) -> + {Tag, Val}. + + +write_manager_config(Dir, Conf) -> + Comment = +"%% This file defines the Manager local configuration info\n" +"%% Each row is a 2-tuple:\n" +"%% {Variable, Value}.\n" +"%% For example\n" +"%% {port, 5000}.\n" +"%% {address, [127,42,17,5]}.\n" +"%% {engine_id, \"managerEngine\"}.\n" +"%% {max_message_size, 484}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_manager_config(Dir, Hdr, Conf). + +write_manager_config(Dir, Hdr, Conf) + when is_list(Dir) andalso is_list(Hdr) andalso is_list(Conf) -> + Verify = fun() -> verify_manager_conf(Conf) end, + Write = fun(Fid) -> write_manager_conf(Fid, Hdr, Conf) end, + write_config_file(Dir, ?MANAGER_CONF_FILE, Verify, Write). + + +append_manager_config(Dir, Conf) + when is_list(Dir) andalso is_list(Conf) -> + Verify = fun() -> verify_manager_conf(Conf) end, + Write = fun(Fid) -> write_manager_conf(Fid, Conf) end, + append_config_file(Dir, ?MANAGER_CONF_FILE, Verify, Write). + + +read_manager_config(Dir) -> + Verify = fun(Entry) -> verify_manager_conf_entry(Entry) end, + read_config_file(Dir, ?MANAGER_CONF_FILE, Verify). + + +verify_manager_conf([]) -> + ok; +verify_manager_conf([H|T]) -> + verify_manager_conf_entry(H), + verify_manager_conf(T); +verify_manager_conf(X) -> + error({bad_manager_config, X}). + +verify_manager_conf_entry(Entry) -> + case snmpm_config:check_manager_config(Entry) of + ok -> + ok; +%% {ok, _} -> +%% ok; + Error -> + throw(Error) + end. + +write_manager_conf(Fd, "", Conf) -> + write_manager_conf(Fd, Conf); +write_manager_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_manager_conf(Fd, Conf). + +write_manager_conf(_Fd, []) -> + ok; +write_manager_conf(Fd, [H|T]) -> + do_write_manager_conf(Fd, H), + write_manager_conf(Fd, T). + +do_write_manager_conf(Fd, {address = Tag, Val}) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_manager_conf(Fd, {port = Tag, Val} ) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_manager_conf(Fd, {engine_id = Tag, Val} ) -> + io:format(Fd, "{~w, \"~s\"}.~n", [Tag, Val]); +do_write_manager_conf(Fd, {max_message_size = Tag, Val} ) -> + io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); +do_write_manager_conf(_Fd, Crap) -> + error({bad_manager_config, Crap}). + + +%% +%% ------ users.conf ------ +%% + +users_entry(UserId) -> + users_entry(UserId, snmpm_user_default). + +users_entry(UserId, UserMod) -> + users_entry(UserId, UserMod, undefined). + +users_entry(UserId, UserMod, UserData) -> + users_entry(UserId, UserMod, UserData, []). + +users_entry(UserId, UserMod, UserData, DefaultAgentConfig) -> + {UserId, UserMod, UserData, DefaultAgentConfig}. + + +write_users_config(Dir, Conf) -> + Comment = +"%% This file defines the users the manager handles\n" +"%% Each row is a 4-tuple:\n" +"%% {UserId, UserMod, UserData, DefaultAgentConfig}.\n" +"%% For example\n" +"%% {kalle, kalle_callback_user_mod, \"dummy\", []}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_users_config(Dir, Hdr, Conf). + +write_users_config(Dir, Hdr, Conf) + when is_list(Dir) andalso is_list(Hdr) andalso is_list(Conf) -> + Verify = fun() -> verify_users_conf(Conf) end, + Write = fun(Fd) -> write_users_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, ?USERS_CONF_FILE, Verify, Write). + + +append_users_config(Dir, Conf) + when is_list(Dir) andalso is_list(Conf) -> + Verify = fun() -> verify_users_conf(Conf) end, + Write = fun(Fd) -> write_users_conf(Fd, Conf) end, + append_config_file(Dir, ?USERS_CONF_FILE, Verify, Write). + + +read_users_config(Dir) when is_list(Dir) -> + Verify = fun(Entry) -> verify_users_conf_entry(Entry) end, + read_config_file(Dir, ?USERS_CONF_FILE, Verify). + + +verify_users_conf([]) -> + ok; +verify_users_conf([H|T]) -> + verify_users_conf_entry(H), + verify_users_conf(T); +verify_users_conf(X) -> + error({bad_users_conf, X}). + +verify_users_conf_entry(Entry) -> + {ok, _} = snmpm_config:check_user_config(Entry), + ok. + +write_users_conf(Fd, "", Conf) -> + write_users_conf(Fd, Conf); +write_users_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_users_conf(Fd, Conf). + +write_users_conf(_Fd, []) -> + ok; +write_users_conf(Fd, [H|T]) -> + do_write_users_conf(Fd, H), + write_users_conf(Fd, T). + +do_write_users_conf(Fd, {Id, Mod, Data}) -> + do_write_users_conf(Fd, {Id, Mod, Data, []}); +do_write_users_conf(Fd, {Id, Mod, Data, DefaultAgentConfig}) -> + io:format(Fd, "{~w, ~w, ~w, ~w}.~n", [Id, Mod, Data, DefaultAgentConfig]); +do_write_users_conf(_Fd, Crap) -> + error({bad_users_config, Crap}). + + +%% +%% ------ agents.conf ------ +%% + +agents_entry(UserId, TargetName, Comm, Ip, Port, EngineID, Timeout, + MaxMessageSize, Version, SecModel, SecName, SecLevel) -> + {UserId, TargetName, Comm, Ip, Port, EngineID, Timeout, + MaxMessageSize, Version, SecModel, SecName, SecLevel}. + + +write_agents_config(Dir, Conf) -> + Comment = +"%% This file defines the agents the manager handles\n" +"%% Each row is a 12-tuple:\n" +"%% {UserId, \n" +"%% TargetName, Comm, Ip, Port, EngineID, Timeout, \n" +"%% MaxMessageSize, Version, SecModel, SecName, SecLevel}\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_agents_config(Dir, Hdr, Conf). + +write_agents_config(Dir, Hdr, Conf) + when is_list(Dir) andalso is_list(Hdr) andalso is_list(Conf) -> + Verify = fun() -> verify_agents_conf(Conf) end, + Write = fun(Fd) -> write_agents_conf(Fd, Hdr, Conf) end, + write_config_file(Dir, ?AGENTS_CONF_FILE, Verify, Write). + + +append_agents_config(Dir, Conf) + when is_list(Dir) andalso is_list(Conf) -> + Verify = fun() -> verify_agents_conf(Conf) end, + Write = fun(Fd) -> write_agents_conf(Fd, Conf) end, + append_config_file(Dir, ?AGENTS_CONF_FILE, Verify, Write). + + +read_agents_config(Dir) -> + Verify = fun(Entry) -> verify_agents_conf_entry(Entry) end, + read_config_file(Dir, ?AGENTS_CONF_FILE, Verify). + + +verify_agents_conf([]) -> + ok; +verify_agents_conf([H|T]) -> + verify_agents_conf_entry(H), + verify_agents_conf(T); +verify_agents_conf(X) -> + error({bad_agents_config, X}). + +verify_agents_conf_entry(Entry) -> + {ok, _} = snmpm_config:check_agent_config(Entry), + ok. + +write_agents_conf(Fd, "", Conf) -> + write_agents_conf(Fd, Conf); +write_agents_conf(Fd, Hdr, Conf) -> + io:format(Fd, "~s~n", [Hdr]), + write_agents_conf(Fd, Conf). + +write_agents_conf(_Fd, []) -> + ok; +write_agents_conf(Fd, [H|T]) -> + do_write_agents_conf(Fd, H), + write_agents_conf(Fd, T). + +do_write_agents_conf(Fd, + {UserId, + TargetName, Comm, Ip, Port, EngineID, + Timeout, MaxMessageSize, Version, + SecModel, SecName, SecLevel} = _A) -> + io:format(Fd, + "{~w, \"~s\", \"~s\", ~w, ~w, \"~s\", ~w, ~w, ~w, ~w, \"~s\", ~w}.~n", [UserId, TargetName, Comm, Ip, Port, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel]); +do_write_agents_conf(_Fd, Crap) -> + error({bad_agents_config, Crap}). + + +%% +%% ------ usm.conf ----- +%% + +usm_entry(EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey) -> + {EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey}. + +usm_entry(EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey) -> + {EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey}. + + +write_usm_config(Dir, Conf) -> + Comment = +"%% This file defines the usm users the manager handles\n" +"%% Each row is a 6 or 7-tuple:\n" +"%% {EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey}\n" +"%% {EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey}\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_usm_config(Dir, Hdr, Conf). + +write_usm_config(Dir, Hdr, Conf) + when is_list(Dir) andalso is_list(Hdr) andalso 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_USERS_CONF_FILE, Verify, Write). + + +append_usm_config(Dir, Conf) + when is_list(Dir) andalso is_list(Conf) -> + Verify = fun() -> verify_usm_conf(Conf) end, + Write = fun(Fd) -> write_usm_conf(Fd, Conf) end, + append_config_file(Dir, ?USM_USERS_CONF_FILE, Verify, Write). + + +read_usm_config(Dir) + when is_list(Dir) -> + Verify = fun(Entry) -> verify_usm_conf_entry(Entry) end, + read_config_file(Dir, ?USM_USERS_CONF_FILE, 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, _} = snmpm_config:check_usm_user_config(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, []) -> + ok; +write_usm_conf(Fd, [H|T]) -> + do_write_usm_conf(Fd, H), + write_usm_conf(Fd, T). + +do_write_usm_conf(Fd, + {EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey}) -> + io:format(Fd, "{\"~s\", \"~s\", ~w, ~w, ~w, ~w}.~n", + [EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey]); +do_write_usm_conf(Fd, + {EngineID, UserName, SecName, + AuthP, AuthKey, PrivP, PrivKey}) -> + io:format(Fd, "{\"~s\", \"~s\", \"~s\", �~w, ~w, ~w, ~w}.~n", + [EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey]); +do_write_usm_conf(_Fd, Crap) -> + error({bad_usm_conf, 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/manager/snmpm_config.erl b/lib/snmp/src/manager/snmpm_config.erl new file mode 100644 index 0000000000..1a5400bf8e --- /dev/null +++ b/lib/snmp/src/manager/snmpm_config.erl @@ -0,0 +1,3116 @@ +%% +%% %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% +%% +%% ------------------------------------------------------------------------- +%% +%% Some of the stuff stored here should really be persistent!! +%% (e.g. snmp-engine-boot) +%% +%% ------------------------------------------------------------------------- + +-module(snmpm_config). + +-behaviour(gen_server). + +%% External exports +-export([start_link/1, stop/0, is_started/0]). +-export([register_user/4, unregister_user/1, + which_users/0, + user_info/0, user_info/1, user_info/2, + + register_agent/3, unregister_agent/2, + agent_info/0, agent_info/2, agent_info/3, update_agent_info/4, + which_agents/0, which_agents/1, + + is_known_engine_id/2, + get_agent_engine_id/1, + get_agent_engine_max_message_size/1, + get_agent_version/1, + get_agent_mp_model/1, + get_agent_user_id/1, get_agent_user_id/2, + get_agent_user_info/2, + + system_info/0, system_info/1, + %% update_system_info/2, + get_engine_id/0, get_engine_max_message_size/0, + + register_usm_user/3, unregister_usm_user/2, + which_usm_users/0, which_usm_users/1, + usm_user_info/3, update_usm_user_info/4, + get_usm_user/2, get_usm_user_from_sec_name/2, + is_usm_engine_id_known/1, + get_engine_boots/0, get_engine_time/0, + set_engine_boots/1, set_engine_time/1, + get_usm_eboots/1, get_usm_etime/1, get_usm_eltime/1, + set_usm_eboots/2, set_usm_etime/2, set_usm_eltime/2, + reset_usm_cache/1, + + + cre_counter/2, + incr_counter/2, + + cre_stats_counter/2, + maybe_cre_stats_counter/2, + incr_stats_counter/2, + reset_stats_counter/1, + get_stats_counters/0, get_stats_counter/1, + + load_mib/1, unload_mib/1, which_mibs/0, + make_mini_mib/0, + name_to_oid/1, oid_to_name/1, oid_to_type/1, + + system_start_time/0, + + info/0, + verbosity/1, + + backup/1, + + mk_target_name/3 + + ]). + +%% Backward compatibillity exports +-export([ + register_user/3, + unregister_agent/3, + update_agent_info/5, + is_known_engine_id/3, + get_agent_engine_id/2, + get_agent_engine_max_message_size/2, + get_agent_version/2, + get_agent_mp_model/2 + ]). + +-export([check_manager_config/1, + check_user_config/1, + check_agent_config/1, + check_usm_user_config/1]). + + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). + + +%% Includes: +-include_lib("kernel/include/file.hrl"). +-include("snmp_types.hrl"). +-include("snmpm_internal.hrl"). +-include("snmpm_usm.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). + + +%% Types: +-record(user, {id, mod, data, default_agent_config}). + +-record(state, {backup}). + + +%% Macros and Constants: +-define(SERVER, ?MODULE). +-define(BACKUP_DB, snmpm_config_backup). +-define(CONFIG_DB, snmpm_config_db). + +-define(DEFAULT_USER, default_user). + +-define(DEFAULT_AGENT_PORT, 161). + +-define(IRB_DEFAULT, auto). +%% -define(IRB_DEFAULT, {user, timer:seconds(15)}). + +-define(USER_MOD_DEFAULT, snmpm_user_default). +-define(USER_DATA_DEFAULT, undefined). + +%% -define(DEF_ADDR_TAG, default_addr_tag). +-define(DEFAULT_TARGETNAME, default_agent). +-define(DEF_PORT_TAG, default_port_tag). + +-ifdef(snmp_debug). +-define(GS_START_LINK(Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, [Opts], + [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Opts), + gen_server:start_link({local, ?SERVER}, ?MODULE, [Opts], [])). +-endif. + + + +%%%------------------------------------------------------------------- +%%% API +%%%------------------------------------------------------------------- +start_link(Opts) -> + ?d("start_link -> entry with" + "~n Opts: ~p", [Opts]), + ?GS_START_LINK(Opts). + +stop() -> + call(stop). + +is_started() -> + call(is_started, 1000). + +backup(BackupDir) when is_list(BackupDir) -> + call({backup, BackupDir}). + +%% Backward compatibillity +register_user(UserId, UserMod, UserData) -> + register_user(UserId, UserMod, UserData, []). + +register_user(UserId, UserMod, UserData, DefaultAgentConfig) + when (UserId =/= ?DEFAULT_USER) andalso is_list(DefaultAgentConfig) -> + case (catch verify_user_behaviour(UserMod)) of + ok -> + Config = default_agent_config(DefaultAgentConfig), + call({register_user, UserId, UserMod, UserData, Config}); + Error -> + Error + end; +register_user(UserId, _UserMod, _UserData, DefaultAgentConfig) + when (UserId =/= ?DEFAULT_USER) -> + {error, {bad_default_agent_config, DefaultAgentConfig}}; +register_user(UserId, _, _, _) -> + {error, {bad_user_id, UserId}}. + +default_agent_config(DefaultAgentConfig) -> + {ok, SystemDefaultAgentConfig} = agent_info(), + default_agent_config(SystemDefaultAgentConfig, DefaultAgentConfig). + +default_agent_config([], DefaultAgentConfig) -> + DefaultAgentConfig; +default_agent_config([{Key, _} = Entry|T], DefaultAgentConfig) -> + case lists:keysearch(Key, 1, DefaultAgentConfig) of + {value, _} -> + default_agent_config(T, DefaultAgentConfig); + false -> + default_agent_config(T, [Entry|DefaultAgentConfig]) + end. + + +verify_user_behaviour(UserMod) -> + case snmp_misc:verify_behaviour(snmpm_user, UserMod) of + ok -> + ok; + Error -> + %% This user may implement the old behaviour, check it + case snmp_misc:verify_behaviour(snmpm_user_old, UserMod) of + ok -> + ok; + _ -> + throw(Error) + end + end. + + +unregister_user(UserId) when UserId =/= ?DEFAULT_USER -> + call({unregister_user, UserId}); +unregister_user(BadUserId) -> + {error, {bad_user_id, BadUserId}}. + + +which_users() -> + Pattern = #user{id = '$1', _ = '_'}, + Match = ets:match(snmpm_user_table, Pattern), + [UserId || [UserId] <- Match, UserId =/= ?DEFAULT_USER]. + + +user_info() -> + UserId = ?DEFAULT_USER, + case user_info(UserId) of + {ok, Mod, Data} -> + {ok, UserId, Mod, Data}; + Error -> + Error + end. + +user_info(UserId) -> + case ets:lookup(snmpm_user_table, UserId) of + [#user{mod = UserMod, data = UserData}] -> + {ok, UserMod, UserData}; + _ -> + {error, not_found} + end. + +user_info(UserId, Item) -> + case (catch do_user_info(UserId, Item)) of + {'EXIT', _} -> + {error, {not_found, Item}}; + Val -> + {ok, Val} + end. + +do_user_info(UserId, module) -> + ets:lookup_element(snmpm_user_table, UserId, #user.mod); +do_user_info(UserId, data) -> + ets:lookup_element(snmpm_user_table, UserId, #user.data); +do_user_info(UserId, default_agent_config) -> + ets:lookup_element(snmpm_user_table, UserId, #user.default_agent_config); +do_user_info(_UserId, BadItem) -> + error({not_found, BadItem}). + + +%% A target-name constructed in this way is a string with the following +%% <IP-address>:<Port>-<Version> +%% +mk_target_name(Addr0, Port, Config) when is_list(Config) -> + Version = + case lists:keysearch(version, 1, Config) of + {value, {_, V}} -> + V; + false -> + select_lowest_supported_version() + end, +%% p("mk_target_name -> Version: ~p", [Version]), + case normalize_address(Addr0) of + {A, B, C, D} -> + lists:flatten( + io_lib:format("~w.~w.~w.~w:~w-~w", [A, B, C, D, Port, Version])); + [A, B, C, D] -> + lists:flatten( + io_lib:format("~w.~w.~w.~w:~w-~w", [A, B, C, D, Port, Version])); + _ -> + lists:flatten( + io_lib:format("~p:~w-~w", [Addr0, Port, Version])) + end. + +select_lowest_supported_version() -> + {ok, Versions} = system_info(versions), + select_lowest_supported_version([v1, v2, v3], Versions). + +select_lowest_supported_version([], Versions) -> + error({bad_versions, Versions}); +select_lowest_supported_version([H|T], Versions) -> + case lists:member(H, Versions) of + true -> + H; + false -> + select_lowest_supported_version(T, Versions) + end. + + +register_agent(UserId, _TargetName, _Config) when (UserId =:= user_id) -> + {error, {bad_user_id, UserId}}; +register_agent(UserId, TargetName, Config) + when (is_list(TargetName) andalso + (length(TargetName) > 0) andalso + is_list(Config)) -> + +%% p("register_agent -> entry with" +%% "~n UserId: ~p" +%% "~n TargetName: ~p" +%% "~n Config: ~p", [UserId, TargetName, Config]), + + %% Check: + %% 1) That the mandatory configs are present + %% 2) That the illegal config user_id (used internally) is + %% not present + %% 3) Check that there are no invalid or erroneous configs + %% 4) Chack that the manager is capable to use the selected version + case verify_agent_config(Config) of + ok -> + call({register_agent, UserId, TargetName, Config}); + Error -> + Error + end. + + +verify_agent_config(Conf) -> + ok = verify_mandatory(Conf, [engine_id, address, reg_type]), + case verify_invalid(Conf, [user_id]) of + ok -> + case verify_agent_config2(Conf) of + ok -> + {ok, Vsns} = system_info(versions), + Vsn = + case lists:keysearch(version, 1, Conf) of + {value, {version, V}} -> + V; + false -> + v1 + end, + case lists:member(Vsn, Vsns) of + true -> + ok; + false -> + {error, {version_not_supported_by_manager, Vsn, Vsns}} + end + end; + Error -> + Error + end. + +verify_agent_config2(Conf) -> + verify_agent2(Conf). + + +unregister_agent(UserId, TargetName) -> + call({unregister_agent, UserId, TargetName}). + +unregister_agent(UserId, Addr0, Port) -> + Addr = normalize_address(Addr0), + case do_agent_info(Addr, Port, target_name) of + {ok, TargetName} -> + unregister_agent(UserId, TargetName); + Error -> + Error + end. + +agent_info() -> + agent_info(?DEFAULT_TARGETNAME, all). + +agent_info(TargetName, all) -> + case ets:match_object(snmpm_agent_table, {{TargetName, '_'}, '_'}) of + [] -> + {error, not_found}; + All -> + {ok, [{Item, Val} || {{_, Item}, Val} <- All]} + end; +agent_info(TargetName, Item) -> + case ets:lookup(snmpm_agent_table, {TargetName, Item}) of + [{_, Val}] -> + {ok, Val}; + [] -> + {error, not_found} + end. + +agent_info(Addr0, Port, Item) -> + Addr = normalize_address(Addr0), + do_agent_info(Addr, Port, Item). + +do_agent_info(Addr, Port, target_name = Item) -> + case ets:lookup(snmpm_agent_table, {Addr, Port, Item}) of + [{_, Val}] -> + {ok, Val}; + [] -> + {error, not_found} + end; +do_agent_info(Addr, Port, Item) -> + case do_agent_info(Addr, Port, target_name) of + {ok, TargetName} -> + agent_info(TargetName, Item); + Error -> + Error + end. + + +which_agents() -> + which_agents('_'). + +which_agents(UserId) -> + Pat = {{'$1', user_id}, UserId}, + Agents = ets:match(snmpm_agent_table, Pat), + [TargetName || [TargetName] <- Agents]. + + +update_agent_info(UserId, TargetName, Item, Val0) + when (Item =/= user_id) -> + case (catch verify_val(Item, Val0)) of + {ok, Val} -> + call({update_agent_info, UserId, TargetName, Item, Val}); + Error -> + Error + end. + +%% Backward compatibillity +update_agent_info(UserId, Addr, Port, Item, Val) -> + case agent_info(Addr, Port, target_name) of + {ok, TargetName} -> + update_agent_info(UserId, TargetName, Item, Val); + Error -> + Error + end. + +is_known_engine_id(EngineID, TargetName) -> + case agent_info(TargetName, engine_id) of + {ok, EngineID} -> + true; + {ok, _OtherEngineID} -> + false; + _ -> + false + end. + +%% Backward compatibillity +is_known_engine_id(EngineID, Addr, Port) -> + case agent_info(Addr, Port, target_name) of + {ok, TargetName} -> + is_known_engine_id(EngineID, TargetName); + _ -> + false + end. + +get_agent_engine_id(TargetName) -> + agent_info(TargetName, engine_id). + +%% Backward compatibillity +get_agent_engine_id(Addr, Port) -> + agent_info(Addr, Port, engine_id). + +get_agent_engine_max_message_size(TargetName) -> + agent_info(TargetName, max_message_size). + +%% Backward compatibillity +get_agent_engine_max_message_size(Addr, Port) -> + agent_info(Addr, Port, max_message_size). + +get_agent_version(TargetName) -> + agent_info(TargetName, version). + +%% Backward compatibillity +get_agent_version(Addr, Port) -> + agent_info(Addr, Port, version). + +get_agent_mp_model(TargetName) -> + case agent_info(TargetName, version) of + {ok, v2} -> + {ok, v2c}; + {ok, V} -> + {ok, V}; + Err -> + Err + end. + +%% Backward compatibillity +get_agent_mp_model(Addr, Port) -> + case agent_info(Addr, Port, target_name) of + {ok, TargetName} -> + get_agent_mp_model(TargetName); + Error -> + Error + end. + +get_agent_user_id(TargetName) -> + agent_info(TargetName, user_id). + +get_agent_user_id(Addr, Port) -> + agent_info(Addr, Port, user_id). + +get_agent_user_info(Addr, Port) -> + case agent_info(Addr, Port, target_name) of + {ok, Target} -> + case agent_info(Target, reg_type) of + {ok, RegType} -> + case agent_info(Target, user_id) of + {ok, UserId} -> + {ok, UserId, Target, RegType}; + {error, not_found} -> + {error, {user_id_not_found, Target}} + end; + {error, not_found} -> + {error, {reg_type_not_found, Target}} + end; + {error, not_found} -> + {error, {target_name_not_found, Addr, Port}} + end. + + + +system_info() -> + system_info(all). + +system_info(all) -> + lists:sort(ets:tab2list(snmpm_config_table)); +system_info(Key) when is_atom(Key) -> + case ets:lookup(snmpm_config_table, Key) of + [{_, Val}] -> + {ok, Val}; + _ -> + {error, not_found} + end. + +%% update_system_info(Key, Val) -> +%% call({update_system_info, Key, Val}). + +system_start_time() -> + system_info(system_start_time). + +get_engine_id() -> + system_info(engine_id). + +get_engine_max_message_size() -> + system_info(max_message_size). + +get_engine_boots() -> + case dets:lookup(?CONFIG_DB, snmp_engine_boots) of + [{_, Boots}] -> + {ok, Boots}; + _ -> + {error, not_found} + end. + +set_engine_boots(Boots) -> + case (whereis(?SERVER) =:= self()) of + false -> + call({set_engine_boots, Boots}); + true -> + dets:insert(?CONFIG_DB, {snmp_engine_boots, Boots}), + ok + end. + + +get_engine_time() -> + case system_info(snmp_engine_base) of + {ok, EngineBase} -> + {ok, snmp_misc:now(sec) - EngineBase}; + Error -> + Error + end. + +get_usm_eboots(SnmpEngineID) -> + Key = {eboots, SnmpEngineID}, + case get_usm_cache(Key) of + {ok, Boots} -> + {ok, Boots}; + _ -> + {ok, 0} + end. + +get_usm_etime(SnmpEngineID) -> + Key = {etime, SnmpEngineID}, + case get_usm_cache(Key) of + {ok, Diff} -> + {ok, snmp_misc:now(sec) - Diff}; + _ -> + {ok, 0} + end. + +get_usm_eltime(SnmpEngineID) -> + Key = {eltime, SnmpEngineID}, + case get_usm_cache(Key) of + {ok, Time} -> + {ok, Time}; + _ -> + {ok, 0} + end. + +get_usm_cache(Key) -> + case ets:lookup(snmpm_usm_table, {usm_cache, Key}) of + [{_, Val}] -> + {ok, Val}; + _ -> + {error, not_found} + end. + +set_usm_eboots(SnmpEngineID, EngineBoots) -> + set_usm_cache({eboots, SnmpEngineID}, EngineBoots). + +set_usm_etime(SnmpEngineID, Diff) -> + set_usm_cache({etime, SnmpEngineID}, Diff). + +set_usm_eltime(SnmpEngineID, Time) -> + set_usm_cache({eltime, SnmpEngineID}, Time). + +set_usm_cache(Key, Val) -> + call({set_usm_cache, Key, Val}). + +reset_usm_cache(SnmpEngineID) -> + case (whereis(?SERVER) =:= self()) of + false -> + call({reset_usm_cache, SnmpEngineID}); + true -> + Pat = {{usm_cache, {'_', SnmpEngineID}}, '_'}, + ets:match_delete(snmpm_usm_table, Pat), + ok + end. + +set_engine_time(Time) -> + call({set_engine_time, Time}). + +register_usm_user(EngineID, Name, Config) + when is_list(EngineID) andalso is_list(Name) -> + case verify_usm_user_config(EngineID, Name, Config) of + {ok, User} -> + call({register_usm_user, User}); + Error -> + Error + end. + +unregister_usm_user(EngineID, Name) + when is_list(EngineID) andalso is_list(Name) -> + call({unregister_usm_user, EngineID, Name}). + +verify_usm_user_config(EngineID, Name, Config) -> + %% case verify_mandatory(Config, []) of + %% ok -> + %% case verify_invalid(Config, [engine_id, name]) of + %% ok -> + %% verify_usm_user_config2(EngineID, Name, Config); + %% Error -> + %% Error + %% end; + %% Error -> + %% Error + %% end. + ok = verify_mandatory(Config, []), + case verify_invalid(Config, [engine_id, name]) of + ok -> + verify_usm_user_config2(EngineID, Name, Config); + Error -> + Error + end. + +verify_usm_user_config2(EngineID, Name, Config) -> + SecName = verify_usm_user_get(sec_name, Name, Config), + Auth = verify_usm_user_get(auth, usmNoAuthProtocol, Config), + AuthKey = verify_usm_user_get(auth_key, [], Config), + Priv = verify_usm_user_get(priv, usmNoPrivProtocol, Config), + PrivKey = verify_usm_user_get(priv_key, [], Config), + User = {EngineID, Name, SecName, Auth, AuthKey, Priv, PrivKey}, + verify_usm_user(User). + +verify_usm_user_get(Item, Default, Config) -> + case lists:keysearch(Item, 1, Config) of + {value, {_, Val}} -> + Val; + false -> + Default + end. + +which_usm_users() -> + Pattern = {usm_key('$1', '$2'), '_'}, + Match = ets:match(snmpm_usm_table, Pattern), + [{EngineID, UserName} || [EngineID, UserName] <- Match]. + +which_usm_users(EngineID) -> + Pattern = {usm_key(EngineID, '$1'), '_'}, + Match = ets:match(snmpm_usm_table, Pattern), + [UserName || [UserName] <- Match]. + +usm_user_info(EngineID, UserName, Item) -> + case ets:lookup(snmpm_usm_table, usm_key(EngineID, UserName)) of + [] -> + {error, not_found}; + [{_Key, UsmUser}] -> + do_usm_user_info(UsmUser, Item) + end. + +do_usm_user_info(#usm_user{sec_name = SecName}, sec_name) -> + {ok, SecName}; +do_usm_user_info(#usm_user{auth = AuthP}, auth) -> + {ok, AuthP}; +do_usm_user_info(#usm_user{auth_key = AuthKey}, auth_key) -> + {ok, AuthKey}; +do_usm_user_info(#usm_user{priv = PrivP}, priv) -> + {ok, PrivP}; +do_usm_user_info(#usm_user{priv_key = PrivKey}, priv_key) -> + {ok, PrivKey}; +do_usm_user_info(#usm_user{engine_id = EngineID}, engine_id) -> + {ok, EngineID}; +do_usm_user_info(#usm_user{name = Name}, name) -> + {ok, Name}; +do_usm_user_info(_, Item) -> + {error, {bad_iten, Item}}. + +update_usm_user_info(EngineID, UserName, Item, Val) + when (Item =/= engine_id) andalso (Item =/= name) -> + call({update_usm_user_info, EngineID, UserName, Item, Val}). + +get_usm_user(EngineID, UserName) -> + Key = usm_key(EngineID, UserName), + case ets:lookup(snmpm_usm_table, Key) of + [{_, User}] -> + {ok, User}; + _ -> + {error, not_found} + end. + +is_usm_engine_id_known(EngineID) -> + Pattern = {usm_key(EngineID, '$1'), '_'}, + case ets:match(snmpm_usm_table, Pattern) of + [] -> + false; + _ -> + true + end. + +get_usm_user_from_sec_name(EngineID, SecName) -> + %% Since the normal mapping between UserName and SecName is the + %% identity-function, 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 = usm_key(EngineID, SecName), + case ets:lookup(snmpm_usm_table, Key) of + [{Key, #usm_user{sec_name = SecName} = User}] -> + {ok, User}; + _ -> + %% That did not work, so we have to search + Pattern = {usm_key(EngineID, '_'), + #usm_user{sec_name = SecName, _ = '_'}}, + case ets:match_object(snmpm_usm_table, Pattern) of + [{_, User}|_] -> + {ok, User}; + _ -> + {error, not_found} + end + end. + + +%% Wrap-counters (wrapping at 2147483647 or 4294967295) +cre_counter(Counter, Initial) -> + case (whereis(?SERVER) =:= self()) of + false -> + call({cre_counter, Counter, Initial}); + true -> + ets:insert(snmpm_counter_table, {Counter, Initial}), + Initial + end. + +incr_counter(usm_salt, Incr) -> % Backward compatibillity (upgrade) + incr_counter(usm_des_salt, Incr); % Backward compatibillity (upgrade) +incr_counter(usm_des_salt, Incr) -> + incr_counter(usm_des_salt, Incr, 4294967295); +incr_counter(usm_aes_salt, Incr) -> + incr_counter(usm_aes_salt, Incr, 36893488147419103231); +incr_counter(Counter, Incr) -> + incr_counter(Counter, Incr, 2147483647). + +incr_counter(Counter, Incr, Wrap) -> + case (catch ets:update_counter(snmpm_counter_table, Counter, Incr)) of + {'EXIT', _} -> + cre_counter(Counter, Incr); + NewVal when NewVal =< Wrap -> + NewVal; + N -> + cre_counter(Counter, N - Wrap) + end. + + +maybe_cre_stats_counter(Counter, Initial) -> + case ets:lookup(snmpm_stats_table, Counter) of + [_] -> + ok; + _ -> + cre_stats_counter(Counter, Initial) + end. + +cre_stats_counter(Counter, Initial) -> + case (whereis(?SERVER) =:= self()) of + false -> + call({cre_stats_counter, Counter, Initial}); + true -> + ets:insert(snmpm_stats_table, {Counter, Initial}), + Initial + end. + +incr_stats_counter(Counter, Incr) -> + case (catch ets:update_counter(snmpm_stats_table, Counter, Incr)) of + {'EXIT', _} -> + cre_counter(Counter, Incr); + NewVal -> + NewVal + end. + +reset_stats_counter(Counter) -> + case (whereis(?SERVER) =:= self()) of + false -> + call({reset_stats_counter, Counter}); + true -> + ets:insert(snmpm_stats_table, {Counter, 0}) + end, + ok. + +get_stats_counter(Counter) -> + case ets:lookup(snmpm_stats_table, Counter) of + [{Counter, Value}] -> + {ok, Value}; + _ -> + {error, not_found} + end. + +get_stats_counters() -> + ets:tab2list(snmpm_stats_table). + +load_mib(Mib) when is_list(Mib) -> + call({load_mib, Mib}). + +unload_mib(Mib) when is_list(Mib) -> + call({unload_mib, Mib}). + +which_mibs() -> + Pattern = {{mib, '_'}, '$1', '$2'}, + Mibs = ets:match(snmpm_mib_table, Pattern), + [list_to_tuple(X) || X <- Mibs]. + +name_to_oid(Name) -> + Pat = {{mini_mib, '$1'}, Name, '_', '_'}, + case ets:match(snmpm_mib_table, Pat) of + [] -> + {error, not_found}; + X -> + Oids = [Oid || [Oid] <- X], + {ok, Oids} + end. + +oid_to_name(Oid) -> + case ets:lookup(snmpm_mib_table, {mini_mib, Oid}) of + [{_, Name, _, _}] -> + {ok, Name}; + [] -> + {error, not_found} + end. + +oid_to_type(Oid) -> + case ets:lookup(snmpm_mib_table, {mini_mib, Oid}) of + [{_, _, Type, _}] -> + {ok, Type}; + [] -> + {error, not_found} + end. + +make_mini_mib() -> + Pat = {{mini_mib, '$1'}, '$2', '$3', '_'}, + MiniElems = ets:match(snmpm_mib_table, Pat), + lists:keysort(1, [list_to_tuple(MiniElem) || MiniElem <- MiniElems]). + + +info() -> + call(info). + +verbosity(Verbosity) -> + case ?vvalidate(Verbosity) of + Verbosity -> + call({verbosity, Verbosity}); + _ -> + {error, {invalid_verbosity, Verbosity}} + end. + + +%%%------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%-------------------------------------------------------------------- +init([Opts]) -> +% put(sname, mconf), +% put(verbosity, trace), + ?d("init -> entry with" + "~n Opts: ~p", [Opts]), + case (catch do_init(Opts)) of + ok -> + {ok, #state{}}; + {error, Reason} -> + error_msg("init error: ~p", [Reason]), + {stop, Reason}; + {'EXIT', Reason} -> + error_msg("init exit: ~p", [Reason]), + {stop, Reason}; + Error -> + error_msg("init failed: ~p", [Error]), + {stop, Error} + end. + +do_init(Opts) -> + process_flag(trap_exit, true), + %% Mandatory = [versions, {config, [dir]}], + Mandatory = [{config, [dir, db_dir]}], + verify_options(Opts, Mandatory), + + ets:new(snmpm_counter_table, [set, public, named_table, {keypos, 1}]), + ets:new(snmpm_stats_table, [set, public, named_table, {keypos, 1}]), + ets:new(snmpm_mib_table, [set, protected, named_table, {keypos, 1}]), + ets:new(snmpm_config_table, [set, protected, named_table, {keypos, 1}]), + ets:new(snmpm_agent_table, [set, protected, named_table, {keypos, 1}]), + ets:new(snmpm_user_table, [set, protected, named_table, {keypos, 2}]), + ets:new(snmpm_usm_table, [set, protected, named_table, {keypos, 1}]), + + %% -- System start time -- + ets:insert(snmpm_config_table, {system_start_time, snmp_misc:now(cs)}), + + %% --- Own options (dir and db_dir mandatory) --- + ConfOpts = get_opt(config, Opts, []), + ConfVerb = get_opt(verbosity, ConfOpts, silence), + ConfDir = get_opt(dir, ConfOpts), + ConfDbDir = get_opt(db_dir, ConfOpts), + ConfDbInitErr = get_opt(db_init_error, ConfOpts, terminate), + ConfRep = get_opt(repair, ConfOpts, true), + ConfAs = get_opt(auto_save, ConfOpts, 5000), + ets:insert(snmpm_config_table, {config_verbosity, ConfVerb}), + ets:insert(snmpm_config_table, {config_dir, ConfDir}), + ets:insert(snmpm_config_table, {config_db_dir, ConfDbDir}), + ets:insert(snmpm_config_table, {config_db_init_error, ConfDbInitErr}), + ets:insert(snmpm_config_table, {config_repair, ConfRep}), + ets:insert(snmpm_config_table, {config_auto_save, ConfAs}), + put(sname, mconf), + put(verbosity, ConfVerb), + ?vlog("starting", []), + + %% -- Create dets file used for storing persistent data -- + dets_open(ConfDbDir, ConfDbInitErr, ConfRep, ConfAs), + + %% -- Prio (optional) -- + Prio = get_opt(priority, Opts, normal), + ets:insert(snmpm_config_table, {prio, Prio}), + process_flag(priority, Prio), + + %% -- Server (optional) -- + ServerOpts = get_opt(server, Opts, []), + ServerVerb = get_opt(verbosity, ServerOpts, silence), + ServerGct = get_opt(timeout, ServerOpts, 30000), + ServerMt = get_opt(multi_threaded, ServerOpts, true), + ets:insert(snmpm_config_table, {server_verbosity, ServerVerb}), + ets:insert(snmpm_config_table, {server_timeout, ServerGct}), + ets:insert(snmpm_config_table, {server_multi_threaded, ServerMt}), + + %% -- Mibs (optional) -- + ?vdebug("initiate mini mib", []), + Mibs = get_opt(mibs, Opts, []), + ets:insert(snmpm_config_table, {mibs, Mibs}), + init_mini_mib(Mibs), + + %% -- Net-if (optional) -- + ?vdebug("net_if options", []), + NetIfIrb = + case get_opt(inform_request_behaviour, Opts, ?IRB_DEFAULT) of + user -> + {user, timer:seconds(15)}; + Irb -> + Irb + end, + NetIfOpts = get_opt(net_if, Opts, []), + NetIfMod = get_opt(module, NetIfOpts, snmpm_net_if), + NetIfVerb = get_opt(verbosity, NetIfOpts, silence), + NetIfOptions = get_opt(options, NetIfOpts, []), + ets:insert(snmpm_config_table, {net_if_module, NetIfMod}), + ets:insert(snmpm_config_table, {net_if_verbosity, NetIfVerb}), + ets:insert(snmpm_config_table, {net_if_irb, NetIfIrb}), + ets:insert(snmpm_config_table, {net_if_options, NetIfOptions}), + + %% -- Versions (optional) -- + %% -- Versions (mandatory) ???????????? -- + ?vdebug("versions", []), + Vsns = get_opt(versions, Opts, [v1, v2, v3]), + ets:insert(snmpm_config_table, {versions, Vsns}), + + %% -- Audit trail log (optional) -- + ?vdebug("audit trail log", []), + case get_opt(audit_trail_log, Opts, []) of + [] -> + ?vtrace("no ATL", []), + ets:insert(snmpm_config_table, {audit_trail_log, false}); + AuditTrailLogOpts -> + ?vtrace("ATL options: ~p", [AuditTrailLogOpts]), + ets:insert(snmpm_config_table, {audit_trail_log, true}), + LogDir = get_atl_dir(AuditTrailLogOpts), + LogType = get_atl_type(AuditTrailLogOpts), + LogSize = get_atl_size(AuditTrailLogOpts), + LogRep = get_atl_repair(AuditTrailLogOpts), + ets:insert(snmpm_config_table, {audit_trail_log_dir, LogDir}), + ets:insert(snmpm_config_table, {audit_trail_log_type, LogType}), + ets:insert(snmpm_config_table, {audit_trail_log_size, LogSize}), + ets:insert(snmpm_config_table, {audit_trail_log_repair, LogRep}) + end, + + %% -- System default agent config -- + ?vdebug("system default agent config", []), + init_agent_default(), + + %% -- User (optional) -- + ?vdebug("default user", []), + DefUserMod = get_opt(def_user_mod, Opts, ?USER_MOD_DEFAULT), + DefUserData = get_opt(def_user_data, Opts, ?USER_DATA_DEFAULT), + ets:insert(snmpm_config_table, {def_user_mod, DefUserMod}), + ets:insert(snmpm_config_table, {def_user_data, DefUserData}), + + {ok, SystemDefaultAgentConfig} = agent_info(), + DefUser = #user{id = ?DEFAULT_USER, + mod = DefUserMod, + data = DefUserData, + default_agent_config = SystemDefaultAgentConfig}, + ok = handle_register_user(DefUser), + + %% -- Note store -- + ?vdebug("note store", []), + NoteStoreOpts = get_opt(note_store, Opts, []), + NoteStoreVerb = get_opt(verbosity, NoteStoreOpts, silence), + NoteStoreTimeout = get_opt(timeout, NoteStoreOpts, 30000), + ets:insert(snmpm_config_table, {note_store_verbosity, NoteStoreVerb}), + ets:insert(snmpm_config_table, {note_store_timeout, NoteStoreTimeout}), + + %% -- Manager SNMP config -- + ?vdebug("manager snmp config", []), + MgrConf = read_manager_config_file(ConfDir), + init_manager_config(MgrConf), + + %% -- User config -- + ?vdebug("users config", []), + Users = read_users_config_file(ConfDir), + init_users_config(Users), + + %% -- Agents config -- + ?vdebug("agents config", []), + Agents = read_agents_config_file(ConfDir), + init_agents_config(Agents), + + %% -- USM config -- + UsmUsers = read_usm_config_file(ConfDir), + init_usm_users_config(UsmUsers), + + %% -- snmp engine init -- + init_engine(), + + ?vlog("started", []), + ok. + + +dets_open(Dir, DbInitError, Repair, AutoSave) -> + Name = ?CONFIG_DB, + Filename = dets_filename(Name, Dir), + case file:read_file_info(Filename) of + {ok, _} -> + %% File exists + case do_dets_open(Name, Filename, Repair, AutoSave) of + {ok, _Dets} -> + ok; + {error, Reason1} -> + info_msg("Corrupt local database: ~p", [Filename]), + case DbInitError of + terminate -> + error({failed_reopen_dets, Filename, Reason1}); + _ -> + Saved = Filename ++ ".saved", + file:rename(Filename, Saved), + case do_dets_open(Name, Filename, + Repair, AutoSave) of + {ok, _Dets} -> + ok; + {error, Reason2} -> + error({failed_open_dets, Filename, + Reason1, Reason2}) + end + end + end; + _ -> + case do_dets_open(Name, Filename, Repair, AutoSave) of + {ok, _Dets} -> + ok; + {error, Reason} -> + error({failed_open_dets, Filename, Reason}) + end + end. + +do_dets_open(Name, Filename, Repair, AutoSave) -> + Opts = [{repair, Repair}, + {auto_save, AutoSave}, + {file, Filename}], + dets:open_file(Name, Opts). + + +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. + + +%% ------------------------------------------------------------------------ + +init_engine() -> + case get_engine_boots() of + {ok, Val} when Val < 2147483647 -> + set_engine_boots(Val + 1); + {ok, _} -> + ok; + _ -> + set_engine_boots(1) + end, + reset_engine_base(). + +reset_engine_base() -> + ets:insert(snmpm_config_table, {snmp_engine_base, snmp_misc:now(sec)}). + + +%% ------------------------------------------------------------------------ + +verify_options(Opts, Mandatory) -> + ?d("verify_options -> entry with" + "~n Opts: ~p" + "~n Mandatory: ~p", [Opts, Mandatory]), + verify_mandatory_options(Opts, Mandatory), + verify_options(Opts). + +%% mandatory() -> [mand()] +%% mand() -> atom() | {atom, [atom()]} +verify_mandatory_options(_Opts, []) -> + ok; +verify_mandatory_options(Opts, [Mand|Mands]) -> + verify_mandatory_option(Opts, Mand), + verify_mandatory_options(Opts, Mands). + +verify_mandatory_option(Opts, {Mand, MandSubOpts}) -> + ?d("verify_mandatory_option -> entry with" + "~n Mand: ~p" + "~n MandSubObjs: ~p", [Mand, MandSubOpts]), + case lists:keysearch(Mand, 1, Opts) of + {value, {Mand, SubOpts}} -> + verify_mandatory_options(SubOpts, MandSubOpts); + false -> + ?d("missing mandatory option: ~w [~p]", [Mand, MandSubOpts]), + error({missing_mandatory, Mand, MandSubOpts}) + end; +verify_mandatory_option(Opts, Mand) -> + ?d("verify_mandatory_option -> entry with" + "~n Mand: ~p", [Mand]), + case lists:keymember(Mand, 1, Opts) of + true -> + ok; + false -> + ?d("missing mandatory option: ~w", [Mand]), + error({missing_mandatory, Mand}) + end. + +verify_options([]) -> + ?d("verify_options -> done", []), + ok; +verify_options([Opt|Opts]) -> + ?d("verify_options -> entry with" + "~n Opt: ~p", [Opt]), + verify_option(Opt), + verify_options(Opts). + +verify_option({prio, Prio}) -> + verify_prio(Prio); +verify_option({mibs, Mibs}) -> + verify_mibs(Mibs); +verify_option({inform_request_behaviour, IRB}) -> + verify_irb(IRB); +verify_option({net_if, NetIfOpts}) -> + verify_net_if_opts(NetIfOpts); +verify_option({server, ServerOpts}) -> + verify_server_opts(ServerOpts); +verify_option({note_store, NoteStoreOpts}) -> + verify_note_store_opts(NoteStoreOpts); +verify_option({config, ConfOpts}) -> + verify_config_opts(ConfOpts); +verify_option({versions, Vsns}) -> + verify_versions(Vsns); +verify_option({audit_trail_log, LogOpts}) -> + Mandatory = [dir, size], + case (catch verify_mandatory_options(LogOpts, Mandatory)) of + ok -> + verify_audit_trail_log_opts(LogOpts); + {error, {missing_mandatory, LogOpt}} -> + error({missing_mandatory, audit_trail_log, LogOpt}) + end; +verify_option({def_user_mod, Mod}) -> + verify_module(def_user_mod, Mod); +verify_option({def_user_data, _Data}) -> + ok; +verify_option(Opt) -> + {error, {invalid_option, Opt}}. + +verify_prio(Prio) when is_atom(Prio) -> + ok; +verify_prio(Prio) -> + error({invalid_prio, Prio}). + +verify_irb(auto) -> + ok; +verify_irb(user) -> + ok; +verify_irb({user, To}) when is_integer(To) andalso (To > 0) -> + ok; +verify_irb(IRB) -> + error({invalid_irb, IRB}). + +verify_mibs([]) -> + ok; +verify_mibs([Mib|Mibs]) when is_list(Mib) -> + verify_mibs(Mibs); +verify_mibs(Mibs) -> + error({invalid_mibs, Mibs}). + +verify_config_opts([]) -> + ok; +verify_config_opts([{verbosity, Verbosity}|Opts]) -> + verify_verbosity(Verbosity), + verify_config_opts(Opts); +verify_config_opts([{dir, Dir}|Opts]) -> + verify_conf_dir(Dir), + verify_config_opts(Opts); +verify_config_opts([{db_dir, Dir}|Opts]) -> + verify_conf_db_dir(Dir), + verify_config_opts(Opts); +verify_config_opts([{db_init_error, DbInitErr}|Opts]) -> + verify_conf_db_init_error(DbInitErr), + verify_config_opts(Opts); +verify_config_opts([{repair, Repair}|Opts]) -> + verify_conf_repair(Repair), + verify_config_opts(Opts); +verify_config_opts([{auto_save, AutoSave}|Opts]) -> + verify_conf_auto_save(AutoSave), + verify_config_opts(Opts); +verify_config_opts([Opt|_]) -> + error({invalid_config_option, Opt}). + +verify_server_opts([]) -> + ok; +verify_server_opts([{verbosity, Verbosity}|Opts]) -> + verify_verbosity(Verbosity), + verify_server_opts(Opts); +verify_server_opts([{timeout, Timeout}|Opts]) -> + verify_server_timeout(Timeout), + verify_server_opts(Opts); +verify_server_opts([Opt|_]) -> + error({invalid_server_option, Opt}). + +verify_server_timeout(T) when is_integer(T) andalso (T > 0) -> + ok; +verify_server_timeout(T) -> + error({invalid_server_timeout, T}). + +verify_net_if_opts([]) -> + ok; +verify_net_if_opts([{module, Mod}|Opts]) -> + verify_network_interface_behaviour(Mod), + verify_net_if_opts(Opts); +verify_net_if_opts([{verbosity, Verbosity}|Opts]) -> + verify_verbosity(Verbosity), + verify_net_if_opts(Opts); +verify_net_if_opts([{options, Options}|Opts]) when is_list(Options) -> + verify_net_if_opts(Opts); +verify_net_if_opts([Opt|_]) -> + error({invalid_net_if_option, Opt}). + +verify_network_interface_behaviour(Mod) -> + case snmp_misc:verify_behaviour(snmpm_network_interface, Mod) of + ok -> + ok; + Error -> + throw(Error) + end. + + +verify_note_store_opts([]) -> + ok; +verify_note_store_opts([{verbosity, Verbosity}|Opts]) -> + verify_verbosity(Verbosity), + verify_note_store_opts(Opts); +verify_note_store_opts([{timeout, Timeout}|Opts]) -> + verify_note_store_timeout(Timeout), + verify_note_store_opts(Opts); +verify_note_store_opts([Opt|_]) -> + error({invalid_note_store_option, Opt}). + +verify_note_store_timeout(T) when is_integer(T) andalso (T > 0) -> + ok; +verify_note_store_timeout(T) -> + error({invalid_note_store_timeout, T}). + +verify_conf_dir(Dir) -> + case (catch verify_dir(Dir)) of + ok -> + ok; + {error, Reason} -> + error({invalid_conf_dir, Dir, Reason}); + _ -> + error({invalid_conf_dir, Dir}) + end. + +verify_conf_db_dir(Dir) -> + case (catch verify_dir(Dir)) of + ok -> + ok; + {error, Reason} -> + error({invalid_conf_db_dir, Dir, Reason}); + _ -> + error({invalid_conf_db_dir, Dir}) + end. + + +verify_conf_db_init_error(terminate) -> + ok; +verify_conf_db_init_error(create) -> + ok; +verify_conf_db_init_error(InvalidDbInitError) -> + error({invalid_conf_db_init_error, InvalidDbInitError}). + + +verify_conf_repair(true) -> + ok; +verify_conf_repair(false) -> + ok; +verify_conf_repair(force) -> + ok; +verify_conf_repair(InvalidRepair) -> + error({invalid_conf_db_repair, InvalidRepair}). + + +verify_conf_auto_save(infinity) -> + ok; +verify_conf_auto_save(AutoSave) + when is_integer(AutoSave) andalso (AutoSave > 0) -> + ok; +verify_conf_auto_save(InvalidAutoSave) -> + error({invalid_conf_db_auto_save, InvalidAutoSave}). + + +verify_versions([]) -> + ok; +verify_versions([Vsn|Vsns]) -> + verify_version(Vsn), + verify_versions(Vsns). + +verify_version(v1) -> + ok; +verify_version(v2) -> + ok; +verify_version(v3) -> + ok; +verify_version(Vsn) -> + error({invalid_version, Vsn}). + +verify_audit_trail_log_opts([]) -> + ok; +verify_audit_trail_log_opts([{dir, Dir}|Opts]) -> + verify_log_dir(Dir), + verify_audit_trail_log_opts(Opts); +verify_audit_trail_log_opts([{type, Type}|Opts]) -> + verify_log_type(Type), + verify_audit_trail_log_opts(Opts); +verify_audit_trail_log_opts([{size, Size}|Opts]) -> + verify_log_size(Size), + verify_audit_trail_log_opts(Opts); +verify_audit_trail_log_opts([{repair, Repair}|Opts]) -> + verify_log_repair(Repair), + verify_audit_trail_log_opts(Opts); +verify_audit_trail_log_opts([Opt|_Opts]) -> + error({invalid_audit_trail_log_option, Opt}). + +verify_log_type(read) -> + ok; +verify_log_type(write) -> + ok; +verify_log_type(read_write) -> + ok; +verify_log_type(Type) -> + error({invalid_audit_trail_log_type, Type}). + +verify_log_dir(Dir) -> + case (catch verify_dir(Dir)) of + ok -> + ok; + {error, Reason} -> + error({invalid_audit_trail_log_dir, Dir, Reason}); + _ -> + error({invalid_audit_trail_log_dir, Dir}) + end. + +verify_log_size(Sz) when is_integer(Sz) andalso (Sz > 0) -> + ok; +verify_log_size(infinity) -> + ok; +verify_log_size({MaxNoBytes, MaxNoFiles}) + when (is_integer(MaxNoBytes) andalso + (MaxNoBytes > 0) andalso + is_integer(MaxNoFiles) andalso + (MaxNoFiles > 0) andalso + (MaxNoFiles < 65000)) -> + ok; +verify_log_size(Sz) -> + error({invalid_audit_trail_log_size, Sz}). + +verify_log_repair(true) -> ok; +verify_log_repair(false) -> ok; +verify_log_repair(truncate) -> ok; +verify_log_repair(Repair) -> + error({invalid_audit_trail_log_repair, Repair}). + + +verify_module(_, Mod) when is_atom(Mod) -> + ok; +verify_module(ReasonTag, Mod) -> + error({invalid_module, ReasonTag, Mod}). + +% verify_bool(_, true) -> +% ok; +% verify_bool(_, false) -> +% ok; +% verify_bool(ReasonTag, Bool) -> +% error({invalid_bool, ReasonTag, Bool}). + +verify_dir(Dir) when is_list(Dir) -> + case file:read_file_info(Dir) of + {ok, #file_info{type = directory}} -> + ok; + {ok, _} -> + {error, not_directory}; + {error, _Reason} -> + {error, not_found} + end; +verify_dir(Dir) -> + {error, {invalid_log_dir, Dir}}. + + +verify_verbosity(Verbosity) -> + case snmp_verbosity:validate(Verbosity) of + Verbosity -> + ok; + _ -> + error({invalid_verbosity, Verbosity}) + end. + +%% ------------------------------------------------------------------------ + +init_manager_config([]) -> + ok; +init_manager_config([{Key, Val}|Confs]) -> + ets:insert(snmpm_config_table, {Key, Val}), + init_manager_config(Confs). + + + +init_agent_default() -> + %% The purpose of the default_agent is only to have a place + %% to store system wide default values related to agents. + %% + + %% Port + init_agent_default(port, ?DEFAULT_AGENT_PORT), + + %% Timeout + init_agent_default(timeout, 10000), + + %% Max message (packet) size + init_agent_default(max_message_size, 484), + + %% MPModel + init_agent_default(version, v2), + + %% SecModel + init_agent_default(sec_model, v2c), + + %% SecName + init_agent_default(sec_name, "initial"), + + %% SecLevel + init_agent_default(sec_level, noAuthNoPriv), + + %% Community + init_agent_default(community, "all-rights"), + ok. + + +init_agent_default(Item, Val) when Item =/= user_id -> + case do_update_agent_info(default_agent, Item, Val) of + ok -> + ok; + {error, Reason} -> + error(Reason) + end. + + +read_agents_config_file(Dir) -> + Check = fun(C) -> check_agent_config2(C) end, + case read_file(Dir, "agents.conf", Check, []) of + {ok, Conf} -> + Conf; + Error -> + ?vlog("agent config error: ~p", [Error]), + throw(Error) + end. + +check_agent_config2(Agent) -> + case (catch check_agent_config(Agent)) of + {ok, {UserId, TargetName, Conf, Version}} -> + {ok, Vsns} = system_info(versions), + case lists:member(Version, Vsns) of + true -> + {ok, {UserId, TargetName, Conf}}; + false -> + error({version_not_supported_by_manager, + Version, Vsns}) + end; + Err -> + throw(Err) + end. + +check_agent_config({UserId, + TargetName, + Community, + Ip, Port, + EngineId, + Timeout, MaxMessageSize, + Version, SecModel, SecName, SecLevel}) -> + ?vtrace("check_agent_config -> entry with" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n Community: ~p" + "~n Ip: ~p" + "~n Port: ~p" + "~n EngineId: ~p" + "~n Timeout: ~p" + "~n MaxMessageSize: ~p" + "~n Version: ~p" + "~n SecModel: ~p" + "~n SecName: ~p" + "~n SecLevel: ~p", + [UserId, TargetName, Community, Ip, Port, + EngineId, Timeout, MaxMessageSize, + Version, SecModel, SecName, SecLevel]), + Addr = normalize_address(Ip), + ?vtrace("check_agent_config -> Addr: ~p", [Addr]), + Agent = {UserId, + TargetName, + Community, + Addr, Port, + EngineId, + Timeout, MaxMessageSize, + Version, SecModel, SecName, SecLevel}, + {ok, verify_agent(Agent)}; +check_agent_config(Agent) -> + error({bad_agent_config, Agent}). + + +init_agents_config([]) -> + ok; +init_agents_config([Agent|Agents]) -> + init_agent_config(Agent), + init_agents_config(Agents). + +init_agent_config({_UserId, ?DEFAULT_TARGETNAME = TargetName, _Config}) -> + throw({error, {invalid_target_name, TargetName}}); +init_agent_config({UserId, TargetName, Config}) -> + case handle_register_agent(UserId, TargetName, Config) of + ok -> + ok; + Error -> + throw(Error) + end. + + +verify_agent({UserId, + TargetName, + Comm, + Ip, Port, + EngineId, + Timeout, MMS, + Version, SecModel, SecName, SecLevel}) -> + ?vtrace("verify_agent -> entry with" + "~n UserId: ~p" + "~n TargetName: ~p", [UserId, TargetName]), + snmp_conf:check_string(TargetName, {gt, 0}), + case verify_val(address, Ip) of + {ok, Addr} -> + snmp_conf:check_integer(Port, {gt, 0}), + Conf = + [{address, Addr}, + {port, Port}, + {community, Comm}, + {engine_id, EngineId}, + {timeout, Timeout}, + {max_message_size, MMS}, + {version, Version}, + {sec_model, SecModel}, + {sec_name, SecName}, + {sec_level, SecLevel} + ], + case verify_agent2(Conf) of + ok -> + {UserId, TargetName, Conf, Version}; + Err -> + throw(Err) + end; + + Error -> + ?vlog("verify_agent -> failed: ~n ~p", [Error]), + throw(Error) + end. + +verify_agent2([]) -> + ok; +verify_agent2([{Item, Val}|Items]) -> + case verify_val(Item, Val) of + {ok, _Val} -> + verify_agent2(Items); + Err -> + Err + end; +verify_agent2([Bad|_]) -> + {error, {bad_agent_config, Bad}}. + + +read_users_config_file(Dir) -> + Check = fun(C) -> check_user_config(C) end, + case read_file(Dir, "users.conf", Check, []) of + {ok, Conf} -> + Conf; + Error -> + ?vlog("failure reading users config file: ~n ~p", [Error]), + throw(Error) + end. + + +check_user_config({Id, Mod, Data}) -> + check_user_config({Id, Mod, Data, []}); +check_user_config({Id, Mod, _Data, DefaultAgentConfig} = User) + when (Id =/= ?DEFAULT_USER) andalso is_list(DefaultAgentConfig) -> + case (catch verify_user_behaviour(Mod)) of + ok -> + case verify_user_agent_config(DefaultAgentConfig) of + ok -> + {ok, User}; + {error, Reason} -> + error({bad_default_agent_config, Reason}) + end; + Error -> + throw(Error) + end; +check_user_config({Id, _Mod, _Data, DefaultAgentConfig}) + when (Id =/= ?DEFAULT_USER) -> + {error, {bad_default_agent_config, DefaultAgentConfig}}; +check_user_config({Id, _Mod, _Data, _DefaultAgentConfig}) -> + error({bad_user_id, Id}); +check_user_config(User) -> + error({bad_user_config, User}). + +init_users_config([]) -> + ok; +init_users_config([User|Users]) -> + init_user_config(User), + init_users_config(Users). + +init_user_config(User) -> + case (catch verify_user(User)) of + {ok, UserRec} -> + case handle_register_user(UserRec) of + ok -> + ok; + {error, Reason} -> + error_msg("failed register user: " + "~n~w~n~w", [User, Reason]) + end; + {error, Reason} -> + error_msg("user config check failed: " + "~n~w~n~w", [User, Reason]) + end. + +verify_user({Id, UserMod, UserData}) -> + verify_user({Id, UserMod, UserData, []}); +verify_user({Id, UserMod, UserData, DefaultAgentConfig}) + when (Id =/= ?DEFAULT_USER) andalso is_list(DefaultAgentConfig) -> + ?d("verify_user -> entry with" + "~n Id: ~p" + "~n UserMod: ~p" + "~n UserData: ~p" + "~n DefaultAgentConfig: ~p", + [Id, UserMod, UserData, DefaultAgentConfig]), + case (catch verify_user_behaviour(UserMod)) of + ok -> + case verify_user_agent_config(DefaultAgentConfig) of + ok -> + Config = default_agent_config(DefaultAgentConfig), + {ok, #user{id = Id, + mod = UserMod, + data = UserData, + default_agent_config = Config}}; + {error, Reason} -> + error({bad_default_agent_config, Reason}) + end; + Error -> + throw(Error) + end; +verify_user({Id, _UserMod, _UserData, DefaultAgentConfig}) + when (Id =/= ?DEFAULT_USER) -> + {error, {bad_default_agent_config, DefaultAgentConfig}}; +verify_user({Id, _, _, _}) -> + {error, {bad_user_id, Id}}. + +verify_user_agent_config(Conf) -> + case verify_invalid(Conf, [user_id, engine_id, address]) of + ok -> + verify_agent_config2(Conf); + Error -> + Error + end. + +read_usm_config_file(Dir) -> + Check = fun(C) -> check_usm_user_config(C) end, + case read_file(Dir, "usm.conf", Check, []) of + {ok, Conf} -> + Conf; + Error -> + throw(Error) + end. + +%% Identity-function +check_usm_user_config({EngineId, Name, + AuthP, AuthKey, + PrivP, PrivKey}) -> + User = {EngineId, Name, Name, AuthP, AuthKey, PrivP, PrivKey}, + verify_usm_user(User); +check_usm_user_config({_EngineId, _Name, _SecName, + _AuthP, _AuthKey, + _PrivP, _PrivKey} = User) -> + verify_usm_user(User); +check_usm_user_config(User) -> + error({bad_usm_config, User}). + +init_usm_users_config([]) -> + ok; +init_usm_users_config([User|Users]) -> + init_usm_user_config(User), + init_usm_users_config(Users). + +init_usm_user_config(User) when is_record(User, usm_user) -> + case handle_register_usm_user(User) of + ok -> + ok; + Error -> + throw(Error) + end; +init_usm_user_config(BadUser) -> + error({bad_usm_user, BadUser}). + + +verify_usm_user({EngineID, Name, SecName, AuthP, AuthKey, PrivP, PrivKey}) -> + ?d("verify_usm_user -> entry with" + "~n EngineID: ~p" + "~n Name: ~p" + "~n SecName: ~p" + "~n AuthP: ~p" + "~n AuthKey: ~p" + "~n PrivP: ~p" + "~n PrivKey: ~p", + [EngineID, Name, SecName, AuthP, AuthKey, PrivP, PrivKey]), + verify_usm_user_engine_id(EngineID), + verify_usm_user_name(Name), + verify_usm_user_sec_name(SecName), + verify_usm_user(AuthP, AuthKey, PrivP, PrivKey), + User = #usm_user{engine_id = EngineID, + name = Name, + sec_name = SecName, + auth = AuthP, + auth_key = AuthKey, + priv = PrivP, + priv_key = PrivKey}, + {ok, User}. + +verify_usm_user_engine_id(EngineID) -> + case (catch snmp_conf:check_string(EngineID, {gt, 0})) of + ok -> + ok; + _ -> + error({bad_usm_engine_id, EngineID}) + end. + +verify_usm_user_name(Name) -> + case (catch snmp_conf:check_string(Name, {gt, 0})) of + ok -> + ok; + _ -> + error({bad_usm_user_name, Name}) + end. + +verify_usm_user_sec_name(Name) -> + case (catch snmp_conf:check_string(Name, {gt, 0})) of + ok -> + ok; + _ -> + error({bad_usm_sec_name, Name}) + end. + +verify_usm_user(AuthP, AuthKey, PrivP, PrivKey) -> + verify_usm_user_auth(AuthP, AuthKey), + verify_usm_user_priv(PrivP, PrivKey), + ok. + +verify_usm_user_auth(usmNoAuthProtocol, AuthKey) -> + case (catch snmp_conf:check_string(AuthKey, any)) of + ok -> + ok; + _ -> + error({invalid_auth_key, usmNoAuthProtocol}) + end; +verify_usm_user_auth(usmHMACMD5AuthProtocol, AuthKey) + when is_list(AuthKey) andalso (length(AuthKey) =:= 16) -> + case is_crypto_supported(md5_mac_96) of + true -> + case snmp_conf:all_integer(AuthKey) of + true -> + ok; + _ -> + error({invalid_auth_key, usmHMACMD5AuthProtocol}) + end; + false -> + error({unsupported_crypto, md5_mac_96}) + end; +verify_usm_user_auth(usmHMACMD5AuthProtocol, AuthKey) when is_list(AuthKey) -> + Len = length(AuthKey), + error({invalid_auth_key, usmHMACMD5AuthProtocol, Len}); +verify_usm_user_auth(usmHMACMD5AuthProtocol, _AuthKey) -> + error({invalid_auth_key, usmHMACMD5AuthProtocol}); +verify_usm_user_auth(usmHMACSHAAuthProtocol, AuthKey) + when is_list(AuthKey) andalso (length(AuthKey) =:= 20) -> + case is_crypto_supported(sha_mac_96) of + true -> + case snmp_conf:all_integer(AuthKey) of + true -> + ok; + _ -> + error({invalid_auth_key, usmHMACSHAAuthProtocol}) + end; + false -> + error({unsupported_crypto, sha_mac_96}) + end; +verify_usm_user_auth(usmHMACSHAAuthProtocol, AuthKey) when is_list(AuthKey) -> + Len = length(AuthKey), + error({invalid_auth_key, usmHMACSHAAuthProtocol, Len}); +verify_usm_user_auth(usmHMACSHAAuthProtocol, _AuthKey) -> + error({invalid_auth_key, usmHMACSHAAuthProtocol}); +verify_usm_user_auth(AuthP, _AuthKey) -> + error({invalid_auth_protocol, AuthP}). + +verify_usm_user_priv(usmNoPrivProtocol, PrivKey) -> + case (catch snmp_conf:check_string(PrivKey, any)) of + ok -> + ok; + _ -> + error({invalid_priv_key, usmNoPrivProtocol}) + end; +verify_usm_user_priv(usmDESPrivProtocol, PrivKey) + when (length(PrivKey) =:= 16) -> + case is_crypto_supported(des_cbc_decrypt) of + true -> + case snmp_conf:all_integer(PrivKey) of + true -> + ok; + _ -> + error({invalid_priv_key, usmDESPrivProtocol}) + end; + false -> + error({unsupported_crypto, des_cbc_decrypt}) + end; +verify_usm_user_priv(usmDESPrivProtocol, PrivKey) when is_list(PrivKey) -> + Len = length(PrivKey), + error({invalid_priv_key, usmDESPrivProtocol, Len}); +verify_usm_user_priv(usmDESPrivProtocol, _PrivKey) -> + error({invalid_priv_key, usmDESPrivProtocol}); +verify_usm_user_priv(usmAesCfb128Protocol, PrivKey) + when (length(PrivKey) =:= 16) -> + case is_crypto_supported(aes_cfb_128_decrypt) of + true -> + case snmp_conf:all_integer(PrivKey) of + true -> + ok; + _ -> + error({invalid_priv_key, usmAesCfb128Protocol}) + end; + false -> + error({unsupported_crypto, aes_cfb_128_decrypt}) + end; +verify_usm_user_priv(usmAesCfb128Protocol, PrivKey) when is_list(PrivKey) -> + Len = length(PrivKey), + error({invalid_priv_key, usmAesCfb128Protocol, Len}); +verify_usm_user_priv(usmAesCfb128Protocol, _PrivKey) -> + error({invalid_priv_key, usmAesCfb128Protocol}); +verify_usm_user_priv(PrivP, _PrivKey) -> + error({invalid_priv_protocol, PrivP}). + +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. + + +read_manager_config_file(Dir) -> + Check = fun(Conf) -> check_manager_config(Conf) end, + case read_file(Dir, "manager.conf", Check) of + {ok, Conf} -> + ?d("read_manager_config_file -> ok: " + "~n Conf: ~p", [Conf]), + %% If the address is not specified, then we assume + %% it should be the local host. + %% If the address is not possible to determine + %% that way, then we give up... + check_mandatory_manager_config(Conf), + ensure_manager_config(Conf); + Error -> + throw(Error) + end. + +default_manager_config() -> + {ok, HostName} = inet:gethostname(), + case inet:getaddr(HostName, inet) of + {ok, A} -> + [{address, tuple_to_list(A)}]; + {error, _Reason} -> + ?d("default_manager_config -> failed getting address: " + "~n _Reason: ~p", [_Reason]), + [] + end. + +check_manager_config({address, Addr}) -> + snmp_conf:check_ip(Addr); +check_manager_config({port, Port}) -> + snmp_conf:check_integer(Port, {gt, 0}); +check_manager_config({engine_id, EngineID}) -> + snmp_conf:check_string(EngineID); +check_manager_config({max_message_size, Max}) -> + snmp_conf:check_integer(Max, {gte, 484}); +check_manager_config(Conf) -> + {error, {unknown_config, Conf}}. + + +check_mandatory_manager_config(Conf) -> + Mand = [port, engine_id, max_message_size], + check_mandatory_manager_config(Mand, Conf). + +check_mandatory_manager_config([], _Conf) -> + ok; +check_mandatory_manager_config([Item|Mand], Conf) -> + case lists:keysearch(Item, 1, Conf) of + false -> + error({missing_mandatory_manager_config, Item}); + _ -> + check_mandatory_manager_config(Mand, Conf) + end. + + +ensure_manager_config(Confs) -> + ensure_manager_config(Confs, default_manager_config()). + +ensure_manager_config(Confs, []) -> + Confs; +ensure_manager_config(Confs, [{Key,_} = DefKeyVal|Defs]) -> + case lists:keysearch(Key, 1, Confs) of + false -> + ensure_manager_config([DefKeyVal|Confs], Defs); + {value, _Conf} -> + ensure_manager_config(Confs, Defs) + end. + +% ensure_manager_config([], Defs, Confs) -> +% Confs ++ Defs; +% ensure_manager_config(Confs0, [{Key, DefVal}|Defs], Acc) -> +% case lists:keysearch(Key, 1, Confs0) of +% false -> +% ensure_manager_config(Confs0, Defs, [{Key, DefVal}|Acc]); +% {value, Conf} -> +% Confs = lists:keydelete(Key, 1, Confs0), +% ensure_manager_config(Confs, Defs, [Conf|Acc]) +% end. + + + +read_file(Dir, FileName, Check, Default) -> + File = filename:join(Dir, FileName), + case file:read_file_info(File) of + {ok, _} -> + case (catch do_read(File, Check)) of + {ok, Conf} -> + {ok, Conf}; + Error -> + ?vtrace("read_file -> read failed:" + "~n Error: ~p", [Error]), + Error + end; + {error, Reason} -> + ?vlog("failed reading config from ~s: ~p", [FileName, Reason]), + {ok, Default} + end. + +read_file(Dir, FileName, Check) -> + File = filename:join(Dir, FileName), + case file:read_file_info(File) of + {ok, _} -> + case (catch do_read(File, Check)) of + {ok, Conf} -> + ?vtrace("read_file -> read ok" + "~n Conf: ~p", [Conf]), + {ok, Conf}; + Error -> + ?vtrace("read_file -> read failed:" + "~n Error: ~p", [Error]), + Error + end; + {error, Reason} -> + error_msg("failed reading config from ~s: ~p", [FileName, Reason]), + {error, {failed_reading, FileName, Reason}} + end. + +do_read(File, Check) -> + {ok, snmp_conf:read(File, Check)}. + + +%%-------------------------------------------------------------------- +%% 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) +%%-------------------------------------------------------------------- +handle_call({register_user, UserId, UserMod, UserData, DefaultAgentConfig}, + _From, State) -> + ?vlog("received register_user request: " + "~n UserId: ~p" + "~n UserMod: ~p" + "~n UserData: ~p" + "~n DefaultAgentConfig: ~p", + [UserId, UserMod, UserData, DefaultAgentConfig]), + User = #user{id = UserId, + mod = UserMod, + data = UserData, + default_agent_config = DefaultAgentConfig}, + Reply = handle_register_user(User), + {reply, Reply, State}; + +handle_call({unregister_user, UserId}, _From, State) -> + ?vlog("received unregister_user request: " + "~n UserId: ~p", [UserId]), + Reply = handle_unregister_user(UserId), + {reply, Reply, State}; + +handle_call({register_agent, UserId, TargetName, Config}, _From, State) -> + ?vlog("received register_agent request: " + "~n UserId: ~p" + "~n TargetName: ~p" + "~n Config: ~p", [UserId, TargetName, Config]), + Reply = handle_register_agent(UserId, TargetName, Config), + {reply, Reply, State}; + +handle_call({unregister_agent, UserId, TargetName}, _From, State) -> + ?vlog("received unregister_agent request: " + "~n UserId: ~p" + "~n TargetName: ~p", [UserId, TargetName]), + Reply = handle_unregister_agent(UserId, TargetName), + {reply, Reply, State}; + +handle_call({update_agent_info, UserId, TargetName, Item, Val}, + _From, State) -> + ?vlog("received update_agent_info request: " + "~n UserId: ~p" + "~n TargetName: ~p" + "~n Item: ~p" + "~n Val: ~p", [UserId, TargetName, Item, Val]), + Reply = handle_update_agent_info(UserId, TargetName, Item, Val), + {reply, Reply, State}; + +handle_call({register_usm_user, User}, _From, State) -> + ?vlog("received register_usm_user request: " + "~n User: ~p", [User]), + Reply = handle_register_usm_user(User), + {reply, Reply, State}; + +handle_call({unregister_usm_user, EngineID, Name}, _From, State) -> + ?vlog("received register_usm_user request: " + "~n EngineID: ~p" + "~n Name: ~p", [EngineID, Name]), + Reply = handle_unregister_usm_user(EngineID, Name), + {reply, Reply, State}; + +handle_call({update_usm_user_info, EngineID, UserName, Item, Val}, + _From, State) -> + ?vlog("received update_usm_user_info request: " + "~n EngineID: ~p" + "~n UserName: ~p" + "~n Item: ~p" + "~n Val: ~p", [EngineID, UserName, Item, Val]), + Reply = handle_update_usm_user_info(EngineID, UserName, Item, Val), + {reply, Reply, State}; + +handle_call({cre_counter, Counter, Initial}, _From, State) -> + ?vlog("received cre_counter ~p -> ~w", [Counter, Initial]), + Reply = cre_counter(Counter, Initial), + {reply, Reply, State}; + +handle_call({cre_stats_counter, Counter, Initial}, _From, State) -> + ?vlog("received cre_stats_counter ~p -> ~w", [Counter, Initial]), + Reply = cre_stats_counter(Counter, Initial), + {reply, Reply, State}; + +handle_call({reset_stats_counter, Counter}, _From, State) -> + ?vlog("received reset_stats_counter ~p", [Counter]), + Reply = reset_stats_counter(Counter), + {reply, Reply, State}; + +handle_call({load_mib, Mib}, _From, State) -> + ?vlog("received load_mib ~p", [Mib]), + case handle_load_mib(Mib) of + ok -> + {reply, ok, State}; + Error -> + {reply, Error, State} + end; + + +handle_call({unload_mib, Mib}, _From, State) -> + ?vlog("received unload_mib ~p", [Mib]), + case handle_unload_mib(Mib) of + ok -> + {reply, ok, State}; + Error -> + {reply, Error, State} + end; + + +handle_call({set_engine_boots, Boots}, _From, State) -> + ?vlog("received set_engine_boots ~p", [Boots]), + set_engine_boots(Boots), + {reply, ok, State}; + +handle_call({set_engine_time, Time}, _From, State) -> + ?vlog("received set_engine_time ~p", [Time]), + Base = snmp_misc:now(sec) - Time, + ets:insert(snmpm_config_table, {snmp_engine_base, Base}), + {reply, ok, State}; + +handle_call({set_usm_cache, Key, Val}, _From, State) -> + ?vlog("received set_usm_cache: ~w -> ~p", [Key, Val]), + ets:insert(snmpm_usm_table, {{usm_cache, Key}, Val}), + {reply, ok, State}; + +handle_call({reset_usm_cache, EngineID}, _From, State) -> + ?vlog("received reset_usm_cache: ~p", [EngineID]), + reset_usm_cache(EngineID), + {reply, ok, State}; + +handle_call({verbosity, Verbosity}, _From, State) -> + ?vlog("received verbosity request", []), + put(verbosity, Verbosity), + {reply, ok, State}; + +handle_call(info, _From, State) -> + ?vlog("received info request", []), + Reply = get_info(), + {reply, Reply, State}; + +handle_call({backup, BackupDir}, From, State) -> + ?vlog("backup 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, mcbs), + put(verbosity, V), + Dir = filename:join([BackupDir]), + Reply = handle_backup(?CONFIG_DB, 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({update_system_info, Key, Val}, _From, State) -> +%% ?vlog("received update_system_info: ~p -> ~p", [Key, Val]), +%% Reply = handle_update_system_info(Key, Val), +%% {reply, Reply, State}; + + +handle_call(is_started, _From, State) -> + ?vlog("received is_started request", []), + {reply, true, State}; + + +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; + + +handle_call(Req, _From, State) -> + warning_msg("received unknown request: ~n~p", [Req]), + {reply, {error, unknown_request}, State}. + + +%%-------------------------------------------------------------------- +%% Func: handle_cast/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- +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) +%%-------------------------------------------------------------------- +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 server 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}. + + +%%-------------------------------------------------------------------- +%% Func: terminate/2 +%% Purpose: Shutdown the server +%% Returns: any (ignored by gen_server) +%%-------------------------------------------------------------------- +terminate(Reason, _State) -> + ?vdebug("terminate: ~p",[Reason]), + ok. + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- + +%% downgrade +%% +code_change({down, _Vsn}, S1, downgrade_to_pre_4_7) -> + #state{backup = B} = S1, + stop_backup_server(B), + S2 = {state}, + {ok, S2}; + +%% upgrade +%% +code_change(_Vsn, _S1, upgrade_from_pre_4_7) -> + %% {state} = S1, + S2 = #state{}, + {ok, S2}; + +code_change(_Vsn, State, _Extra) -> + {ok, State}. + + +stop_backup_server(undefined) -> + ok; +stop_backup_server({Pid, _}) when is_pid(Pid) -> + exit(Pid, kill). + + + +%%---------------------------------------------------------- +%% Update system info +%%---------------------------------------------------------- + +%% handle_update_system_info(audit_trail_log_type = Key, Val) -> +%% case snmpm_config:system_info(audit_trail_log) of +%% {ok, true} -> +%% Value = +%% case Val of +%% read -> +%% {ok, [read]}; +%% write -> +%% {ok, [write]}; +%% read_write -> +%% {ok, [read,write]}; +%% _ -> +%% {error, {bad_value, Key, Val}} +%% end, +%% case Value of +%% {ok, NewValue} -> +%% ets:insert(snmpm_config_table, {Key, NewValue}), +%% ok; +%% false -> +%% Value +%% end; +%% _ -> +%% {error, audit_trail_log_not_enabled} +%% end; +%% handle_update_system_info(BadKey, Val) -> +%% {error, {unsupported_update, BadKey, Val}}. + + +%%---------------------------------------------------------- +%% 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_DB, Opts) of + {ok, B} -> + ?vtrace("dets_backup -> create fun", []), + F = fun(Arg) -> + dets_backup(Arg, start, D, B) + end, + dets:safe_fixtable(D, true), + Res = dets:init_table(?BACKUP_DB, F, [{format, bchunk}]), + 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. + + +%%%------------------------------------------------------------------- +%%% Internal functions +%%%------------------------------------------------------------------- + +handle_register_user(#user{id = Id} = User) -> + ?vdebug("handle_register_user -> entry with" + "~n User: ~p", [User]), + case ets:lookup(snmpm_user_table, Id) of + [] -> + ets:insert(snmpm_user_table, User), + ok; + _ -> + {error, {already_registered, User}} + end. + +handle_unregister_user(UserId) -> + ?vdebug("handle_unregister_user -> entry with" + "~n UserId: ~p", [UserId]), + ets:delete(snmpm_user_table, UserId), + ok. + + +handle_register_agent(UserId, TargetName, Config) -> + ?vdebug("handle_register_agent -> entry with" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n Config: ~p", [UserId, TargetName, Config]), + case (catch agent_info(TargetName, user_id)) of + {error, _} -> + case ets:lookup(snmpm_user_table, UserId) of + [#user{default_agent_config = DefConfig}] -> + do_handle_register_agent(TargetName, DefConfig), + do_handle_register_agent(TargetName, + [{user_id, UserId}|Config]), + %% <DIRTY-BACKWARD-COMPATIBILLITY> + %% And now for some (backward compatibillity) + %% dirty crossref stuff + {ok, Addr} = agent_info(TargetName, address), + {ok, Port} = agent_info(TargetName, port), + ets:insert(snmpm_agent_table, + {{Addr, Port, target_name}, TargetName}), + %% </DIRTY-BACKWARD-COMPATIBILLITY> + ok; + _ -> + {error, {not_found, UserId}} + end; + {ok, UserId} -> + ?vinfo("[~w] Agent (~p) already registered" + "~nwhen" + "~n Agents: ~p", + [UserId, TargetName, which_agents()]), + {error, {already_registered, TargetName}}; + {ok, OtherUserId} -> + ?vinfo("[~w] Agent (~p) already registered to ~p" + "~nwhen" + "~n Agents: ~p", + [UserId, TargetName, OtherUserId, which_agents()]), + {error, {already_registered, TargetName, OtherUserId}} + end. + +do_handle_register_agent(_TargetName, []) -> + ok; +do_handle_register_agent(TargetName, [{Item, Val}|Rest]) -> + case (catch do_update_agent_info(TargetName, Item, Val)) of + ok -> + do_handle_register_agent(TargetName, Rest); + {error, Reason} -> + ets:match_delete(snmpm_agent_table, {TargetName, '_'}), + {error, Reason} + end; +do_handle_register_agent(TargetName, BadConfig) -> + error_msg("error during agent registration - bad config: ~n~p", + [BadConfig]), + ets:match_delete(snmpm_agent_table, {TargetName, '_'}), + {error, {bad_agent_config, TargetName, BadConfig}}. + + +handle_unregister_agent(UserId, TargetName) -> + ?vdebug("handle_unregister_agent -> entry with" + "~n UserId: ~p" + "~n TargetName: ~p", [UserId, TargetName]), + case (catch agent_info(TargetName, user_id)) of + {ok, UserId} -> + {ok, EngineID} = agent_info(TargetName, engine_id), + reset_usm_cache(EngineID), + %% <DIRTY-BACKWARD-COMPATIBILLITY> + %% And now for some (backward compatibillity) + %% dirty crossref stuff + {ok, Addr} = agent_info(TargetName, address), + {ok, Port} = agent_info(TargetName, port), + ets:delete(snmpm_agent_table, {Addr, Port, target_name}), + %% </DIRTY-BACKWARD-COMPATIBILLITY> + ets:match_delete(snmpm_agent_table, {{TargetName, '_'}, '_'}), + ok; + {ok, OtherUserId} -> + {error, {not_owner, OtherUserId}}; + Error -> + Error + end. + + +handle_update_agent_info(UserId, TargetName, Item, Val) -> + ?vdebug("handle_update_agent_info -> entry with" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n Item: ~p" + "~n Val: ~p", [UserId, TargetName, Item, Val]), + case (catch agent_info(TargetName, user_id)) of + {ok, UserId} -> + do_update_agent_info(TargetName, Item, Val); + {ok, OtherUserId} -> + {error, {not_owner, OtherUserId}}; + Error -> + Error + end. + +do_update_agent_info(TargetName, Item, Val0) -> +%% p("do_update_agent_info -> entry with" +%% "~n TargetName: ~p" +%% "~n Item: ~p" +%% "~n Val0: ~p", [TargetName, Item, Val0]), + case verify_val(Item, Val0) of + {ok, Val} -> +%% p("do_update_agent_info -> verified value" +%% "~n Val: ~p", [Val]), + ets:insert(snmpm_agent_table, {{TargetName, Item}, Val}), + ok; + Error -> + ?vlog("do_update_agent_info -> verify value failed: " + "~n TargetName: ~p" + "~n Item: ~p" + "~n Val0: ~p" + "~n Error: ~p", [TargetName, Item, Val0, Error]), + {error, {bad_agent_val, TargetName, Item, Val0}} + end. + + +handle_register_usm_user(#usm_user{engine_id = EngineID, + name = Name} = User) -> + ?vdebug("handle_register_usm_user -> entry with" + "~n User: ~p", [User]), + Key = usm_key(EngineID, Name), + case ets:lookup(snmpm_usm_table, Key) of + [] -> + do_update_usm_user_info(Key, User); + _ -> + {error, {already_registered, EngineID, Name}} + end; +handle_register_usm_user(BadUsmUser) -> + {error, {bad_usm_user, BadUsmUser}}. + +handle_unregister_usm_user(EngineID, Name) -> + ?vdebug("handle_unregister_usm_user -> entry with" + "~n EngineID: ~p" + "~n Name: ~p", [EngineID, Name]), + Key = usm_key(EngineID, Name), + ets:delete(snmpm_usm_table, Key), + ok. + + +handle_update_usm_user_info(EngineID, Name, Item, Val) -> + ?vdebug("handle_update_usm_user_info -> entry with" + "~n EngineID: ~p" + "~n Name: ~p" + "~n Item: ~p" + "~n Val: ~p", [EngineID, Name, Item, Val]), + Key = usm_key(EngineID, Name), + case ets:lookup(snmpm_usm_table, Key) of + [] -> + {error, not_found}; + [{_Key, User}] -> + do_update_usm_user_info(Key, User, Item, Val) + end. + +do_update_usm_user_info(Key, User, sec_name, Val) -> + %% case verify_usm_user_sec_name(Val) of + %% ok -> + %% do_update_usm_user_info(Key, User#usm_user{sec_name = Val}); + %% _ -> + %% {error, {invalid_usm_sec_name, Val}} + %% end; + ok = verify_usm_user_sec_name(Val), + do_update_usm_user_info(Key, User#usm_user{sec_name = Val}); +do_update_usm_user_info(Key, User, auth, Val) + when (Val =:= usmNoAuthProtocol) orelse + (Val =:= usmHMACMD5AuthProtocol) orelse + (Val =:= usmHMACSHAAuthProtocol) -> + do_update_usm_user_info(Key, User#usm_user{auth = Val}); +do_update_usm_user_info(_Key, _User, auth, Val) -> + {error, {invalid_auth_protocol, Val}}; +do_update_usm_user_info(Key, + #usm_user{auth = usmNoAuthProtocol} = User, + auth_key, Val) -> + case (catch snmp_conf:check_string(Val, any)) of + ok -> + do_update_usm_user_info(Key, User#usm_user{auth_key = Val}); + _ -> + {error, {invalid_auth_key, Val}} + end; +do_update_usm_user_info(Key, + #usm_user{auth = usmHMACMD5AuthProtocol} = User, + auth_key, Val) + when length(Val) =:= 16 -> + case is_crypto_supported(md5_mac_96) of + true -> + do_update_usm_user_info(Key, User#usm_user{auth_key = Val}); + false -> + {error, {unsupported_crypto, md5_mac_96}} + end; +do_update_usm_user_info(_Key, + #usm_user{auth = usmHMACMD5AuthProtocol}, + auth_key, Val) when is_list(Val) -> + Len = length(Val), + {error, {invalid_auth_key_length, usmHMACMD5AuthProtocol, Len}}; +do_update_usm_user_info(_Key, + #usm_user{auth = usmHMACMD5AuthProtocol}, + auth_key, Val) -> + {error, {invalid_auth_key, usmHMACMD5AuthProtocol, Val}}; +do_update_usm_user_info(Key, + #usm_user{auth = usmHMACSHAAuthProtocol} = User, + auth_key, Val) + when length(Val) =:= 20 -> + case is_crypto_supported(sha_mac_96) of + true -> + do_update_usm_user_info(Key, User#usm_user{auth_key = Val}); + false -> + {error, {unsupported_crypto, sha_mac_96}} + end; +do_update_usm_user_info(_Key, + #usm_user{auth = usmHMACSHAAuthProtocol}, + auth_key, Val) when is_list(Val) -> + Len = length(Val), + {error, {invalid_auth_key_length, usmHMACSHAAuthProtocol, Len}}; +do_update_usm_user_info(_Key, + #usm_user{auth = usmHMACSHAAuthProtocol}, + auth_key, Val) -> + {error, {invalid_auth_key, usmHMACSHAAuthProtocol, Val}}; +do_update_usm_user_info(Key, User, priv, Val) + when (Val =:= usmNoPrivProtocol) orelse + (Val =:= usmDESPrivProtocol) orelse + (Val =:= usmAesCfb128Protocol) -> + do_update_usm_user_info(Key, User#usm_user{priv = Val}); +do_update_usm_user_info(_Key, _User, priv, Val) -> + {error, {invalid_priv_protocol, Val}}; +do_update_usm_user_info(Key, + #usm_user{priv = usmNoPrivProtocol} = User, + priv_key, Val) -> + case (catch snmp_conf:check_string(Val, any)) of + ok -> + do_update_usm_user_info(Key, User#usm_user{priv_key = Val}); + _ -> + {error, {invalid_priv_key, Val}} + end; +do_update_usm_user_info(Key, + #usm_user{priv = usmDESPrivProtocol} = User, + priv_key, Val) + when length(Val) =:= 16 -> + case is_crypto_supported(des_cbc_decrypt) of + true -> + do_update_usm_user_info(Key, User#usm_user{priv_key = Val}); + false -> + {error, {unsupported_crypto, des_cbc_decrypt}} + end; +do_update_usm_user_info(Key, + #usm_user{priv = usmAesCfb128Protocoll} = User, + priv_key, Val) + when length(Val) =:= 16 -> + case is_crypto_supported(aes_cfb_128_decrypt) of + true -> + do_update_usm_user_info(Key, User#usm_user{priv_key = Val}); + false -> + {error, {unsupported_crypto, aes_cfb_128_decrypt}} + end; +do_update_usm_user_info(_Key, + #usm_user{auth = usmHMACSHAAuthProtocol}, + priv_key, Val) when is_list(Val) -> + Len = length(Val), + {error, {invalid_priv_key_length, usmHMACSHAAuthProtocol, Len}}; +do_update_usm_user_info(_Key, + #usm_user{auth = usmHMACSHAAuthProtocol}, + priv_key, Val) -> + {error, {invalid_priv_key, usmHMACSHAAuthProtocol, Val}}; +do_update_usm_user_info(_Key, _User, Item, Val) -> + {error, {bad_item, Item, Val}}. + +do_update_usm_user_info(Key, User) -> + ets:insert(snmpm_usm_table, {Key, User}), + ok. + + +usm_key(EngineId, Name) -> + {usmUserTable, EngineId, Name}. + + +%% --------------------------------------------------------------------- + +verify_mandatory(_, []) -> + ok; +verify_mandatory(Conf, [Mand|Mands]) -> + case lists:keymember(Mand, 1, Conf) of + true -> + verify_mandatory(Conf, Mands); + false -> + {error, {missing_mandatory_config, Mand}} + end. + +verify_invalid(_, []) -> + ok; +verify_invalid(Conf, [Inv|Invs]) -> + case lists:member(Inv, Conf) of + false -> + verify_invalid(Conf, Invs); + true -> + {error, {illegal_config, Inv}} + end. + + +verify_val(user_id, UserId) -> + {ok, UserId}; +verify_val(reg_type, RegType) + when (RegType =:= addr_port) orelse (RegType =:= target_name) -> + {ok, RegType}; +verify_val(address, Addr0) -> + case normalize_address(Addr0) of + {_A1, _A2, _A3, _A4} = Addr -> + {ok, Addr}; + _ when is_list(Addr0) -> + case (catch snmp_conf:check_ip(Addr0)) of + ok -> + {ok, list_to_tuple(Addr0)}; + Err -> + Err + end; + _ -> + error({bad_address, Addr0}) + end; +verify_val(port, Port) -> + case (catch snmp_conf:check_integer(Port, {gt, 0})) of + ok -> + {ok, Port}; + Err -> + Err + end; +verify_val(community, Comm) -> + case (catch snmp_conf:check_string(Comm)) of + ok -> + {ok, Comm}; + Err -> + Err + end; +verify_val(engine_id, discovery = EngineId) -> + {ok, EngineId}; +verify_val(engine_id, EngineId) -> + case (catch snmp_conf:check_string(EngineId)) of + ok -> + {ok, EngineId}; + Err -> + Err + end; +verify_val(timeout, Timeout) -> + (catch snmp_conf:check_timer(Timeout)); +verify_val(max_message_size, MMS) -> + case (catch snmp_conf:check_packet_size(MMS)) of + ok -> + {ok, MMS}; + Err -> + Err + end; +verify_val(version, V) + when (V =:= v1) orelse (V =:= v2) orelse (V =:= v3) -> + {ok, V}; +verify_val(version, BadVersion) -> + error({bad_version, BadVersion}); +verify_val(sec_model, Model) -> + (catch snmp_conf:check_sec_model(Model)); +verify_val(sec_name, Name) when is_list(Name) -> + case (catch snmp_conf:check_string(Name)) of + ok -> + {ok, Name}; + Err -> + Err + end; +verify_val(sec_name, BadName) -> + error({bad_sec_name, BadName}); +verify_val(sec_level, Level) -> + (catch snmp_conf:check_sec_level(Level)); +verify_val(Item, _) -> + {error, {no_such_item, Item}}. + + +%%%------------------------------------------------------------------- +%%% +%%% Mini MIB stuff +%%% +%%%------------------------------------------------------------------- + +init_mini_mib(MibFiles) -> + MiniMibs = lists:flatten([do_load_mib(MibFile) || MibFile <- MibFiles]), + MiniMIB = remove_duplicates(lists:keysort(1, MiniMibs), []), + init_mini_mib2(MiniMIB). + +remove_duplicates([], Res) -> + Res; +remove_duplicates([X,X|T], Res) -> + remove_duplicates([X|T], Res); +remove_duplicates([{Oid, Name, Type, _} = X, {Oid, Name, Type, _}|T], Res) -> + remove_duplicates([X|T], Res); +remove_duplicates([X|T], Res) -> + remove_duplicates(T, [X|Res]). + +init_mini_mib2([]) -> + ok; +init_mini_mib2([{Oid, Name, Type, MibName}|MiniMib]) -> + ?vtrace("init mini mib -> ~w: ~w [~w] from ~s", + [Name, Oid, Type,MibName ]), + ets:insert(snmpm_mib_table, {{mini_mib, Oid}, Name, Type, MibName}), + init_mini_mib2(MiniMib). + + +handle_load_mib(Mib) -> + [{mibs, Mibs0}] = ets:lookup(snmpm_config_table, mibs), + case lists:member(Mib, Mibs0) of + true -> + {error, already_loaded}; + false -> + Mibs = [Mib|Mibs0], + case (catch do_load_mib(Mib)) of + MiniElems when is_list(MiniElems) -> + ets:insert(snmpm_config_table, {mibs, Mibs}), + update_mini_mib(MiniElems), + ok; + Error -> + Error + end + end. + +update_mini_mib([]) -> + ok; +update_mini_mib([{Oid, Name, Type, MibName}|Elems]) -> + Key = {mini_mib, Oid}, + case ets:lookup(snmpm_mib_table, Key) of + [{Key, _Name, _Type, _AnotherMibName}] -> + %% Already loaded from another mib + update_mini_mib(Elems); + [] -> + %% Not yet loaded + ?vtrace("update mini mib -> ~w: ~w [~w] from ~s", + [Name, Oid, Type, MibName]), + ets:insert(snmpm_mib_table, {Key, Name, Type, MibName}), + update_mini_mib(Elems) + end. + + +handle_unload_mib(Mib) -> + Key = {mib, Mib}, + case ets:lookup(snmpm_mib_table, Key) of + [{Key, MibName, _MibFile}] -> + do_unload_mib(MibName), + [{mibs, Mibs0}] = ets:lookup(snmpm_config_table, mibs), + Mibs = lists:delete(Mib, Mibs0), + ets:insert(snmpm_config_table, {mibs, Mibs}), + ets:delete(snmpm_mib_table, Key), + ok; + _ -> + {error, not_loaded} + end. + +do_unload_mib(MibName) -> + Pat = {{mini_mib, '$1'}, '_', '_', MibName}, + Oids = ets:match(snmpm_mib_table, Pat), + F = fun([Oid]) -> ets:delete(snmpm_mib_table, {mini_mib, Oid}) end, + lists:foreach(F, Oids). + + +do_load_mib(MibFile) -> + ?vtrace("load mib ~s", [MibFile]), + F1 = snmp_misc:strip_extension_from_filename(MibFile, ".bin"), + ActualFileName = lists:append(F1, ".bin"), + case snmp_misc:read_mib(ActualFileName) of + {ok, #mib{name = Name, mes = MEs, traps = Traps}} -> + %% Check that the mib was not loaded or loaded + %% with a different filename: + %% e.g. /tmp/MYMIB.bin and /tmp/mibs/MYMIB.bin + Name1 = mib_name(Name), + Pattern = {{mib, '_'}, Name1, '$1'}, + case ets:match(snmpm_mib_table, Pattern) of + [] -> + + Rec = {{mib, MibFile}, Name1, ActualFileName}, + ets:insert(snmpm_mib_table, Rec), + init_mini_mib_elems(Name1, MEs++Traps, []); + + %% This means that the mib has already been loaded + [[ActualFileName]] -> + []; + + %% This means that the mib was loaded before, + %% but under another filename + [[OtherMibFile]] -> + error({already_loaded, MibFile, OtherMibFile}) + end; + + {error, Reason} -> + error({failed_reading_mib, MibFile, Reason}) + end. + +mib_name(N) when is_list(N) -> + list_to_atom(N); +mib_name(N) -> + N. + +init_mini_mib_elems(_, [], Res) -> + Res; +init_mini_mib_elems(MibName, + [#me{aliasname = N, + oid = Oid, + entrytype = variable, + asn1_type = #asn1_type{bertype = Type}} | T], Res) -> + init_mini_mib_elems(MibName, T, [{Oid, N, Type, MibName}|Res]); + +init_mini_mib_elems(MibName, + [#me{aliasname = N, + oid = Oid, + entrytype = table_column, + asn1_type = #asn1_type{bertype = Type}}|T], Res) -> + init_mini_mib_elems(MibName, T, [{Oid, N, Type, MibName}|Res]); + +init_mini_mib_elems(MibName, + [#me{aliasname = N, + oid = Oid, + asn1_type = undefined}|T], Res) -> + init_mini_mib_elems(MibName, T, [{Oid, N, undefined, MibName}|Res]); + +init_mini_mib_elems(MibName, + [#notification{trapname = N, + oid = Oid}|T], Res) -> + init_mini_mib_elems(MibName, T, [{Oid, N, undefined, MibName}|Res]); + +init_mini_mib_elems(MibName, [_|T], Res) -> + init_mini_mib_elems(MibName, T, Res). + + + +%%---------------------------------------------------------------------- + +normalize_address(Addr) -> + case inet:getaddr(Addr, inet) of + {ok, Addr2} -> + Addr2; + _ when is_list(Addr) -> + case (catch snmp_conf:check_ip(Addr)) of + ok -> + list_to_tuple(Addr); + _ -> + Addr + end; + _ -> + Addr + end. + + +%%---------------------------------------------------------------------- + +call(Req) -> + call(Req, infinity). + +call(Req, To) -> + gen_server:call(?SERVER, Req, To). + +% cast(Msg) -> +% gen_server:cast(snmpm_server, Msg). + + +%%------------------------------------------------------------------- + +get_atl_dir(Opts) -> + get_opt(dir, Opts). + +get_atl_type(Opts) -> + case get_opt(type, Opts, read_write) of + read_write -> + [read,write]; + read -> + [read]; + write -> + [write] + end. + +get_atl_size(Opts) -> + get_opt(size, Opts). + +get_atl_repair(Opts) -> + get_opt(repair, Opts, truncate). + + +%%---------------------------------------------------------------------- + +get_opt(Key, Opts) -> + ?d("get option ~w from ~p", [Key, Opts]), + snmp_misc:get_option(Key, Opts). + +get_opt(Key, Opts, Def) -> + ?d("get option ~w with default ~p from ~p", [Key, Def, Opts]), + snmp_misc:get_option(Key, Opts, Def). + + +%%---------------------------------------------------------------------- + +get_info() -> + ProcSize = proc_mem(self()), + CntSz = tab_size(snmpm_counter_table), + StatsSz = tab_size(snmpm_stats_table), + MibSz = tab_size(snmpm_mib_table), + ConfSz = tab_size(snmpm_config_table), + AgentSz = tab_size(snmpm_agent_table), + UserSz = tab_size(snmpm_user_table), + UsmSz = tab_size(snmpm_usm_table), + [{process_memory, ProcSize}, + {db_memory, [{counter, CntSz}, + {stats, StatsSz}, + {mib, MibSz}, + {config, ConfSz}, + {agent, AgentSz}, + {user, UserSz}, + {usm, UsmSz}]}]. + +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(T) -> + case (catch ets:info(T, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + + +%%---------------------------------------------------------------------- + +error(Reason) -> + throw({error, Reason}). + + +%%---------------------------------------------------------------------- + +info_msg(F, A) -> + ?snmpm_info("Config server: " ++ F, A). + +warning_msg(F, A) -> + ?snmpm_warning("Config server: " ++ F, A). + +error_msg(F, A) -> + ?snmpm_error("Config server: " ++ F, A). + +%% p(F) -> +%% p(F, []). + +%% p(F, A) -> +%% io:format("~w:" ++ F ++ "~n", [?MODULE | A]). + diff --git a/lib/snmp/src/manager/snmpm_internal.hrl b/lib/snmp/src/manager/snmpm_internal.hrl new file mode 100644 index 0000000000..5d9b32e3f6 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_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(snmpm_internal). +-define(snmpm_internal, true). + +-include_lib("snmp/src/app/snmp_internal.hrl"). + +-define(snmpm_info(F, A), ?snmp_info("manager", F, A)). +-define(snmpm_warning(F, A), ?snmp_warning("manager", F, A)). +-define(snmpm_error(F, A), ?snmp_error("manager", F, A)). + +-endif. % -ifdef(snmpm_internal). + + + diff --git a/lib/snmp/src/manager/snmpm_misc_sup.erl b/lib/snmp/src/manager/snmpm_misc_sup.erl new file mode 100644 index 0000000000..1a5d7676df --- /dev/null +++ b/lib/snmp/src/manager/snmpm_misc_sup.erl @@ -0,0 +1,112 @@ +%% +%% %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(snmpm_misc_sup). + +-include("snmp_debug.hrl"). + +-behaviour(supervisor). + +%% External exports +-export([ + start_link/0, + start_net_if/2, stop_net_if/0, + start_note_store/2, stop_note_store/0 + ]). + +%% Internal exports +-export([init/1]). + +-define(SUP, ?MODULE). + + +%%%----------------------------------------------------------------- +%%% This is a supervisor for the mib and net_ifprocesses. +%%% Each agent has one mib process. +%%%----------------------------------------------------------------- + +start_link() -> + ?d("start_link -> entry", []), + SupName = {local,?SUP}, + supervisor:start_link(SupName, ?MODULE, []). + + +start_net_if(Mod, NoteStore) -> + ?d("start_net_if -> entry with" + "~n Mod: ~p" + "~n NoteStore: ~p", [Mod, NoteStore]), + SupName = ?SUP, + Name = net_if, + Args = [self(), NoteStore], + start_sup_child(SupName, Name, Mod, Args). + +stop_net_if() -> + stop_sup_child(?SUP, net_if). + + +%% The note store is a common code component, so we must +%% pass all the arguments in a way that works both for +%% the agent and the manager entities. I.e. as arguments +%% to the start_link function. +start_note_store(Prio, Opts) -> + ?d("start_note_store -> entry with" + "~n Prio: ~p" + "~n Opts: ~p", [Prio, Opts]), + SupName = ?MODULE, + Name = note_store, + Mod = snmp_note_store, + Args = [Prio, snmpm, Opts], + start_sup_child(SupName, Name, Mod, Args). + +stop_note_store() -> + stop_sup_child(?SUP, note_store). + + +%% --------------------------------------------------------------- + +start_sup_child(SupName, Name, Mod, Args) -> + %% make sure we start from scratch... + Children = supervisor:which_children(SupName), + case lists:keysearch(Name, 1, Children) of + {value, {_, _Pid, _, _}} -> + stop_sup_child(SupName, Name); + _ -> + ok + end, + Spec = {Name, + {Mod, start_link, Args}, + temporary, 2000, worker, [Mod]}, + supervisor:start_child(SupName, Spec). + +stop_sup_child(SupName, Name) -> + case whereis(SupName) of + undefined -> + ok; + _ -> + supervisor:terminate_child(SupName, Name), + supervisor:delete_child(SupName, Name) + end. + + + +init([]) -> + ?d("init -> entry", []), + SupFlags = {one_for_all, 0, 3600}, + {ok, {SupFlags, []}}. + diff --git a/lib/snmp/src/manager/snmpm_mpd.erl b/lib/snmp/src/manager/snmpm_mpd.erl new file mode 100644 index 0000000000..d76ad20051 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_mpd.erl @@ -0,0 +1,1024 @@ +%% +%% %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(snmpm_mpd). + +-export([init/1, + + process_msg/7, + generate_msg/5, generate_response_msg/4, + + next_msg_id/0, + next_req_id/0, + + reset/1, + inc/1]). + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). +-include("snmpm_internal.hrl"). +-include("SNMP-MPD-MIB.hrl"). +-include("SNMPv2-TM.hrl"). + +-define(VMODULE,"MPD"). +-include("snmp_verbosity.hrl"). + +-define(empty_msg_size, 24). + +-record(state, {v1 = false, v2c = false, v3 = false}). + + +%%%----------------------------------------------------------------- +%%% 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) -> + ?vdebug("init -> entry with ~p", [Vsns]), + {A,B,C} = erlang:now(), + random:seed(A,B,C), + snmpm_config:cre_counter(msg_id, random:uniform(2147483647)), + snmpm_config:cre_counter(req_id, random:uniform(2147483647)), + init_counters(), + State = init_versions(Vsns, #state{}), + init_usm(State#state.v3), + ?vtrace("init -> done when ~p", [State]), + State. + +reset(#state{v3 = V3}) -> + reset_counters(), + reset_usm(V3). + + +%%----------------------------------------------------------------- +%% Func: process_msg(Packet, TDomain, TAddress, State) -> +%% {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_msg(Msg, TDomain, Addr, Port, State, NoteStore, Logger) -> + + inc(snmpInPkts), + + case (catch snmp_pdus:dec_message_only(binary_to_list(Msg))) of + + %% Version 1 + #message{version = 'version-1', vsn_hdr = Community, data = Data} + when State#state.v1 =:= true -> + HS = ?empty_msg_size + length(Community), + process_v1_v2c_msg('version-1', NoteStore, Msg, TDomain, + Addr, Port, + Community, Data, HS, Logger); + + %% Version 2 + #message{version = 'version-2', vsn_hdr = Community, data = Data} + when State#state.v2c =:= true -> + HS = ?empty_msg_size + length(Community), + process_v1_v2c_msg('version-2', NoteStore, Msg, TDomain, + Addr, Port, + Community, Data, HS, Logger); + + %% Version 3 + #message{version = 'version-3', vsn_hdr = H, data = Data} + when State#state.v3 =:= true -> + ?vlog("v3:" + "~n msgID: ~p" + "~n msgFlags: ~p" + "~n msgSecModel: ~p", + [H#v3_hdr.msgID,H#v3_hdr.msgFlags,H#v3_hdr.msgSecurityModel]), + process_v3_msg(NoteStore, Msg, H, Data, Addr, Port, Logger); + + %% Crap + {'EXIT', {bad_version, Vsn}} -> + ?vinfo("exit: bad version: ~p",[Vsn]), + inc(snmpInBadVersions), + {discarded, snmpInBadVersions}; + + %% More crap + {'EXIT', Reason} -> + ?vinfo("exit: ~p",[Reason]), + inc(snmpInASNParseErrs), + {discarded, Reason}; + + %% Really crap + Crap -> + ?vinfo("unknown message: " + "~n ~p",[Crap]), + inc(snmpInBadVersions), + {discarded, snmpInBadVersions} + end. + + +%%----------------------------------------------------------------- +%% Handles a Community based message (v1 or v2c). +%%----------------------------------------------------------------- +process_v1_v2c_msg(Vsn, _NoteStore, Msg, snmpUDPDomain, + Addr, Port, + Community, Data, HS, Log) -> + + ?vdebug("process_v1_v2c_msg -> entry with" + "~n Vsn: ~p" + "~n Addr: ~p" + "~n Port: ~p" + "~n Community: ~p" + "~n HS: ~p", [Vsn, Addr, Port, Community, HS]), + + Max = get_max_message_size(), + AgentMax = get_agent_max_message_size(Addr, Port), + PduMS = pdu_ms(Max, AgentMax, HS), + + ?vtrace("process_v1_v2c_msg -> PduMS: ~p", [PduMS]), + + case (catch snmp_pdus:dec_pdu(Data)) of + Pdu when is_record(Pdu, pdu) -> + ?vtrace("process_v1_v2c_msg -> was a pdu", []), + Log(Msg), + inc_snmp_in(Pdu), + MsgData = {Community, sec_model(Vsn)}, + {ok, Vsn, Pdu, PduMS, MsgData}; + + Trap when is_record(Trap, trappdu) -> + ?vtrace("process_v1_v2c_msg -> was a trap", []), + Log(Msg), + inc_snmp_in(Trap), + MsgData = {Community, sec_model(Vsn)}, + {ok, Vsn, Trap, PduMS, MsgData}; + + {'EXIT', Reason} -> + ?vlog("process_v1_v2c_msg -> failed decoding PDU: " + "~n Reason: ~p", [Reason]), + inc(snmpInASNParseErrs), + {discarded, Reason} + end; +process_v1_v2c_msg(_Vsn, _NoteStore, _Msg, TDomain, + _Addr, _Port, + _Comm, _HS, _Data, _Log) -> + {discarded, {badarg, TDomain}}. + +pdu_ms(MgrMMS, AgentMMS, HS) when AgentMMS < MgrMMS -> + AgentMMS - HS; +pdu_ms(MgrMMS, _AgentMMS, HS) -> + MgrMMS - HS. + +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 +%%----------------------------------------------------------------- +process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) -> + + ?vdebug("process_v3_msg -> entry with" + "~n Hdr: ~p" + "~n Addr: ~p" + "~n Port: ~p", [Hdr, Addr, Port]), + + %% 7.2.3 + #v3_hdr{msgID = MsgID, + msgMaxSize = MMS, + msgFlags = MsgFlags, + msgSecurityModel = MsgSecModel, + msgSecurityParameters = SecParams, + hdr_size = HdrSize} = Hdr, + + %% 7.2.4 + SecModule = get_security_module(MsgSecModel), + ?vtrace("process_v3_msg -> 7.2.4: " + "~n SecModule: ~p", [SecModule]), + + %% 7.2.5 + SecLevel = check_sec_level(MsgFlags), + IsReportable = is_reportable(MsgFlags), + ?vtrace("process_v3_msg -> 7.2.5: " + "~n SecLevel: ~p" + "~n IsReportable: ~p", [SecLevel, IsReportable]), + + %% 7.2.6 + SecRes = (catch SecModule:process_incoming_msg(Msg, Data, + SecParams, SecLevel)), + ?vtrace("process_v3_msg -> 7.2.6 - message processing result: " + "~n ~p",[SecRes]), + {SecEngineID, SecName, ScopedPDUBytes, SecData} = + check_sec_module_result(SecRes, Hdr, Data, IsReportable, Log), + ?vtrace("process_v3_msg -> 7.2.6 - checked module result: " + "~n SecEngineID: ~p" + "~n SecName: ~p",[SecEngineID, SecName]), + + %% 7.2.7 + #scopedPdu{contextEngineID = CtxEngineID, + contextName = CtxName, + data = PDU} = + case (catch snmp_pdus:dec_scoped_pdu(ScopedPDUBytes)) of + ScopedPDU when is_record(ScopedPDU, scopedPdu) -> + ScopedPDU; + {'EXIT', Reason} -> + ?vlog("failed decoding scoped pdu: " + "~n ~p",[Reason]), + inc(snmpInASNParseErrs), + discard(Reason) + end, + + ?vlog("7.2.7" + "~n ContextEngineID: \"~s\" " + "~n context: \"~s\" ", + [CtxEngineID, CtxName]), + if + SecLevel == 3 -> % encrypted message - log decrypted pdu + Log({Hdr, ScopedPDUBytes}); + true -> % otherwise, log binary + Log(Msg) + end, + + %% Make sure a get_bulk doesn't get too big. + MgrMMS = get_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 = tot_mms(MgrMMS, MMS), + TotalLenOctets = snmp_pdus:get_encoded_length(TotMMS - 1), + PduMMS = TotMMS - TotalLenOctets - 10 - HdrSize - + length(CtxName) - length(CtxEngineID), + ?vtrace("process_v3_msg -> PduMMS = ~p", [PduMMS]), + Type = PDU#pdu.type, + ?vdebug("process_v3_msg -> PDU type: ~p",[Type]), + case Type of + report -> + %% 7.2.10 & 11 + %% BMK BMK BMK: discovery? + Note = snmp_note_store:get_note(NoteStore, MsgID), + case Note of + {SecEngineID, MsgSecModel, SecName, SecLevel, + CtxEngineID, CtxName, _ReqId} -> + ?vtrace("process_v3_msg -> 7.2.11b: ok", []), + %% BMK BMK: Should we discard the cached info + %% BMK BMK: or do we let the gc deal with it? + {ok, 'version-3', PDU, PduMMS, ok}; + _ when is_tuple(Note) -> + ?vlog("process_v3_msg -> 7.2.11b: error" + "~n Note: ~p", [Note]), + Recv = {SecEngineID, MsgSecModel, SecName, SecLevel, + CtxEngineID, CtxName, PDU#pdu.request_id}, + Err = sec_error(Note, Recv), + ACM = {invalid_sec_info, Err}, + ReqId = element(size(Note), Note), + {ok, 'version-3', PDU, PduMMS, {error, ReqId, ACM}}; + _NoFound -> + ?vtrace("process_v3_msg -> _NoFound: " + "~p", [_NoFound]), + inc(snmpUnknownPDUHandlers), + discard({no_outstanding_req, MsgID}) + end; + + 'get-response' -> + %% 7.2.10 & 12 + case snmp_note_store:get_note(NoteStore, MsgID) of + {SecEngineID, MsgSecModel, SecName, SecLevel, + CtxEngineID, CtxName, _} -> + %% 7.2.12.d + {ok, 'version-3', PDU, PduMMS, undefined}; + _ -> + %% 7.2.12.b + %% BMK BMK: Should we not discard the cached info?? + inc(snmpUnknownPDUHandlers), + discard({no_outstanding_req, MsgID}) + end; + + 'snmpv2-trap' -> + %% 7.2.14 + {ok, 'version-3', PDU, PduMMS, undefined}; + + 'inform-request' -> + %% 7.2.13 + SnmpEngineID = get_engine_id(), + case SecEngineID of + SnmpEngineID -> % 7.2.13.b + ?vtrace("valid securityEngineID: ~p", [SecEngineID]), + %% 4.2.2.1.1 - we don't handle proxys yet => we only + %% handle CtxEngineID to ourselves + %% Check that we actually know of an agent with this + %% CtxEngineID and Addr/Port + case is_known_engine_id(CtxEngineID, Addr, Port) of + true -> + ?vtrace("and the agent EngineID (~p) " + "is know to us", [CtxEngineID]), + %% Uses ACMData that snmpm_acm knows of. + %% BUGBUG BUGBUG + ACMData = + {MsgID, MsgSecModel, SecName, SecLevel, + CtxEngineID, CtxName, SecData}, + {ok, 'version-3', PDU, PduMMS, ACMData}; + _ -> + %% 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, CtxEngineID}, + {contextName, CtxName}]}, + case generate_v3_report_msg(MsgID, + MsgSecModel, + Data, + ErrorInfo, + Log) of + {ok, Report} when NIsReportable =:= true -> + discard(snmpUnknownPDUHandlers, Report); + _ -> + discard(snmpUnknownPDUHandlers) + end + end; + _ -> % 7.2.13.a + ?vinfo("invalid securityEngineID: ~p",[SecEngineID]), + discard({badSecurityEngineID, SecEngineID}) + end; + + _ -> + %% 7.2.13 - This would be the requests which we should not + %% receive since we are a manager, barring possible + %% proxy... + discard(Type) + end. + + +sec_error(T1, T2) + when is_tuple(T1) andalso is_tuple(T2) andalso (size(T1) =:= size(T2)) -> + Tags = {sec_engine_id, msg_sec_model, sec_name, sec_level, + ctx_engine_id, ctx_name, request_id}, + sec_error(size(T1), T1, T2, Tags, []); +sec_error(T1, T2) -> + [{internal_error, T1, T2}]. + +sec_error(0, _T1, _T2, _Tags, Acc) -> + Acc; +sec_error(Idx, T1, T2, Tags, Acc) -> + case element(Idx, T1) =:= element(Idx, T2) of + true -> + sec_error(Idx - 1, T1, T2, Tags, Acc); + false -> + Elem = {element(Idx, Tags), element(Idx, T1), element(Idx, T2)}, + sec_error(Idx - 1, T1, T2, Tags, [Elem|Acc]) + end. + +tot_mms(MgrMMS, AgentMMS) when MgrMMS > AgentMMS -> AgentMMS; +tot_mms(MgrMMS, _AgentMMS) -> MgrMMS. + +get_security_module(?SEC_USM) -> + snmpm_usm; +get_security_module(_) -> + inc(snmpUnknownSecurityModels), + discard(snmpUnknownSecurityModels). + +check_sec_level([MsgFlag]) -> + SecLevel = MsgFlag band 3, + if + SecLevel == 2 -> + inc(snmpInvalidMsgs), + discard(snmpInvalidMsgs); + true -> + SecLevel + end; +check_sec_level(_Unknown) -> + inc(snmpInvalidMsgs), + discard(snmpInvalidMsgs). + +is_reportable([MsgFlag]) -> + 4 == (MsgFlag band 4). + + +check_sec_module_result({ok, X}, _, _, _, _) -> + X; +check_sec_module_result({error, Reason, Info}, _, _, _, _) + when is_list(Info) -> + %% case 7.2.6 b + discard({securityError, Reason, Info}); +check_sec_module_result({error, Reason, ErrorInfo}, V3Hdr, Data, true, Log) -> + %% case 7.2.6 a + ?vtrace("security module result:" + "~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} -> + discard({securityError, Reason}, Report); + {discarded, _SomeOtherReason} -> + discard({securityError, Reason}) + end; +check_sec_module_result({error, Reason, _ErrorInfo}, _, _, _, _) -> + ?vtrace("security module result:" + "~n Reason: ~p" + "~n _ErrorInfo: ~p", [Reason, _ErrorInfo]), + discard({securityError, Reason}); +check_sec_module_result(Res, _, _, _, _) -> + ?vtrace("security module result:" + "~n Res: ~p", [Res]), + discard({securityError, Res}). + +get_scoped_pdu(D) when is_list(D) -> + (catch snmp_pdus:dec_scoped_pdu(D)); +get_scoped_pdu(D) -> + D. + + +%%----------------------------------------------------------------- +%% Generate a message +%%----------------------------------------------------------------- +generate_msg('version-3', NoteStore, Pdu, + {SecModel, SecName, SecLevel, CtxEngineID, CtxName, + TargetName}, Log) -> + generate_v3_msg(NoteStore, Pdu, + SecModel, SecName, SecLevel, CtxEngineID, CtxName, + TargetName, Log); +generate_msg(Vsn, _NoteStore, Pdu, {Community, _SecModel}, Log) -> + generate_v1_v2c_msg(Vsn, Pdu, Community, Log). + + +generate_v3_msg(NoteStore, Pdu, + SecModel, SecName, SecLevel, CtxEngineID, CtxName, + TargetName, Log) -> + %% rfc2272: 7.1.6 + ?vdebug("generate_v3_msg -> 7.1.6", []), + ScopedPDU = #scopedPdu{contextEngineID = CtxEngineID, + contextName = CtxName, + data = Pdu}, + case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of + {'EXIT', Reason} -> + user_err("failed encoding scoped pdu " + "~n pdu: ~w" + "~n contextName: ~w" + "~n reason: ~w", [Pdu, CtxName, Reason]), + {discarded, Reason}; + ScopedPDUBytes -> + {ok, generate_v3_msg(NoteStore, Pdu, ScopedPDUBytes, + SecModel, SecName, SecLevel, + CtxEngineID, CtxName, TargetName, Log)} + end. + +generate_v3_msg(NoteStore, + #pdu{type = Type} = Pdu, ScopedPduBytes, + SecModel, SecName, SecLevel, CtxEngineID, CtxName, + TargetName, Log) -> + %% 7.1.7 + ?vdebug("generate_v3_msg -> 7.1.7", []), + MsgID = next_msg_id(), + MsgFlags = snmp_misc:mk_msg_flags(Type, 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 = sec_module(SecModel), + + %% 7.1.9a + ?vdebug("generate_v3_msg -> 7.1.9a", []), + SecEngineID = sec_engine_id(TargetName), + ?vtrace("SecEngineID: ~p", [SecEngineID]), + %% 7.1.9b + ?vdebug("generate_v3_msg -> 7.1.9b", []), + case generate_v3_outgoing_msg(Message, SecModule, SecEngineID, + SecName, [], SecLevel) of + {ok, Packet} -> + %% 7.1.9c + %% Store in cache for 150 sec. + ?vdebug("generate_v3_msg -> 7.1.9c", []), + %% The request id is just in the case when we receive a + %% report with incorrect securityModel and/or securityLevel + CacheVal = {SecEngineID, SecModel, SecName, SecLevel, + CtxEngineID, CtxName, Pdu#pdu.request_id}, + snmp_note_store:set_note(NoteStore, 1500, MsgID, CacheVal), + Log(Packet), + inc_snmp_out(Pdu), + ?vdebug("generate_v3_msg -> done", []), + Packet; + + Error -> + throw(Error) + end. + + +sec_module(?SEC_USM) -> + snmpm_usm. + +%% 9) If the PDU is a GetRequest-PDU, GetNextRequest-PDU, +%% GetBulkRequest-PDU, SetRequest-PDU, InformRequest-PDU, or or +%% SNMPv2-Trap-PDU, then +%% +%% a) If the PDU is an SNMPv2-Trap-PDU, then securityEngineID is set +%% to the value of this entity's snmpEngineID. +%% +%% Otherwise, the snmpEngineID of the target entity is determined, +%% in an implementation-dependent manner, possibly using +%% transportDomain and transportAddress. The value of +%% securityEngineID is set to the value of the target entity's +%% snmpEngineID. +%% +%% As we never send traps, the SecEngineID is allways the +%% snmpEngineID of the target entity! +sec_engine_id(TargetName) -> + case get_agent_engine_id(TargetName) of + {ok, EngineId} -> + EngineId; + _ -> + config_err("Can't find engineID for " + "snmpTargetAddrName ~p", [TargetName]), + %% this will trigger error in secmodule + "" + end. + + +%% BMK BMK BMK +%% Denna verkar v�ldigt lik generate_v1_v2c_response_msg! +%% Gemensam? Borde det finnas olikheter? +%% +generate_v1_v2c_msg(Vsn, Pdu, Community, Log) -> + ?vdebug("generate_v1_v2c_msg -> encode pdu", []), + case (catch snmp_pdus:enc_pdu(Pdu)) of + {'EXIT', Reason} -> + user_err("failed encoding pdu: " + "(pdu: ~w, community: ~w): ~n~w", + [Pdu, Community, Reason]), + {discarded, Reason}; + PduBytes -> + MMS = get_max_message_size(), + Message = #message{version = Vsn, + vsn_hdr = Community, + data = PduBytes}, + case generate_v1_v2c_outgoing_msg(Message) of + {error, Reason} -> + user_err("failed encoding message " + "(pdu: ~w, community: ~w): ~n~w", + [Pdu, Community, Reason]), + {discarded, Reason}; + {ok, Packet} when size(Packet) =< MMS -> + Log(Packet), + inc_snmp_out(Pdu), + {ok, Packet}; + {ok, Packet} -> + ?vlog("packet max size exceeded: " + "~n MMS: ~p" + "~n Len: ~p", + [MMS, size(Packet)]), + {discarded, tooBig} + end + end. + + + +%% ----------------------------------------------------------------------- + +generate_response_msg('version-3', Pdu, + {MsgID, SecModel, SecName, SecLevel, + CtxEngineID, CtxName, SecData}, Log) -> + generate_v3_response_msg(Pdu, MsgID, SecModel, SecName, SecLevel, + CtxEngineID, CtxName, SecData, Log); +generate_response_msg(Vsn, Pdu, {Comm, _SecModel}, Log) -> + generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log). + + +generate_v3_response_msg(#pdu{type = Type} = Pdu, MsgID, + SecModel, SecName, SecLevel, + CtxEngineID, CtxName, SecData, Log) -> + %% rfc2272: 7.1 steps 6-8 + ScopedPdu = #scopedPdu{contextEngineID = CtxEngineID, + contextName = CtxName, + data = Pdu}, + case (catch snmp_pdus:enc_scoped_pdu(ScopedPdu)) of + {'EXIT', Reason} -> + user_err("failed encoded scoped pdu " + "(pdu: ~w, contextName: ~w): ~n~w", + [Pdu, CtxName, Reason]), + {discarded, Reason}; + ScopedPduBytes -> + MMS = get_max_message_size(), + MsgFlags = snmp_misc:mk_msg_flags(Type, SecLevel), + V3Hdr = #v3_hdr{msgID = MsgID, + msgMaxSize = MMS, + msgFlags = MsgFlags, + msgSecurityModel = SecModel}, + Message = #message{version = 'version-3', + vsn_hdr = V3Hdr, + data = ScopedPduBytes}, + %% We know that the security model is valid when we + %% generate a response. + SecModule = sec_module(SecModel), + SecEngineID = get_engine_id(), + case generate_v3_outgoing_msg(Message, SecModule, SecEngineID, + SecName, SecData, SecLevel) of + %% Check the packet size. Send the msg even + %% if it's larger than the agent can handle - + %% it will be dropped. Just check against the + %% internal size. + {ok, Packet} when size(Packet) =< MMS -> + if + SecLevel == 3 -> + %% encrypted - log decrypted pdu + Log({V3Hdr, ScopedPduBytes}); + true -> + %% otherwise log the entire msg + Log(Packet) + end, + inc_snmp_out(Pdu), + {ok, Packet}; + + {ok, _Packet} when Pdu#pdu.error_status =:= tooBig -> + ?vlog("packet max size exceeded (tooBog): " + "~n MMS: ~p", [MMS]), + inc(snmpSilentDrops), + {discarded, tooBig}; + {ok, _Packet} -> + ?vlog("packet max size exceeded: " + "~n MMS: ~p", [MMS]), + TooBigPdu = Pdu#pdu{error_status = tooBig, + error_index = 0, + varbinds = []}, + generate_v3_response_msg(TooBigPdu, MsgID, + SecModel, SecName, SecLevel, + CtxEngineID, + CtxName, + SecData, Log); + Error -> + Error + end + end. + + +generate_v3_outgoing_msg(Message, + SecModule, SecEngineID, SecName, SecData, SecLevel) -> + 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}; + 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. + + +generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log) -> + case (catch snmp_pdus:enc_pdu(Pdu)) of + {'EXIT', Reason} -> + user_err("failed encoding pdu: " + "(pdu: ~w, community: ~w): ~n~w", + [Pdu, Comm, Reason]), + {discarded, Reason}; + PduBytes -> + MMS = get_max_message_size(), + Message = #message{version = Vsn, + vsn_hdr = Comm, + data = PduBytes}, + case generate_v1_v2c_outgoing_msg(Message) of + {error, Reason} -> + user_err("failed encoding message only " + "(pdu: ~w, community: ~w): ~n~w", + [Pdu, Comm, Reason]), + {discarded, Reason}; + + {ok, Packet} when size(Packet) =< MMS -> + Log(Packet), + inc_snmp_out(Pdu), + {ok, Packet}; + + {ok, Packet} -> %% Too big + too_big(Vsn, Pdu, Comm, MMS, size(Packet), Log) + end + end. + + +too_big('version-1' = Vsn, #pdu{type = 'get-response'} = Pdu, + Comm, _MMS, _Len, Log) -> + %% 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), + TooBigPdu = Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = V}, + too_big(Vsn, TooBigPdu, Comm, Log); +too_big('version-2' = Vsn, #pdu{type = 'get-response'} = Pdu, + Comm, _MMS, _Len, Log) -> + %% In v2, varbinds should be empty (reasonable!) + TooBigPdu = Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = []}, + too_big(Vsn, TooBigPdu, Comm, Log); +too_big(_Vsn, Pdu, _Comm, _Log, MMS, Len) -> + user_err("encoded pdu, ~p bytes, exceeded " + "max message size of ~p bytes. Pdu: ~n~w", + [Len, MMS, Pdu]), + {discarded, tooBig}. + + +too_big(Vsn, Pdu, Comm, Log) -> + case (catch snmp_pdus:enc_pdu(Pdu)) of + {'EXIT', Reason} -> + user_err("failed encoding pdu " + "(pdu: ~w, community: ~w): ~n~w", + [Pdu, Comm, Reason]), + {discarded, Reason}; + PduBytes -> + Message = #message{version = Vsn, + vsn_hdr = Comm, + data = PduBytes}, + case generate_v1_v2c_outgoing_msg(Message) of + {error, Reason} -> + user_err("failed encoding message only" + "(pdu: ~w, community: ~w): ~n~w", + [Pdu, Comm, Reason]), + {discarded, Reason}; + {ok, Bin} -> + Log(Bin), + inc_snmp_out(Pdu), + {ok, Bin} + end + end. + +set_vb_null(Vbs) -> + [Vb#varbind{variabletype = 'NULL', value = 'NULL'} || Vb <- Vbs]. + + +generate_v1_v2c_outgoing_msg(Message) -> + ?vdebug("generate_v1_v2c_outgoing_msg -> encode message", []), + case (catch snmp_pdus:enc_message_only(Message)) of + {'EXIT', Reason} -> + {error, Reason}; + Bin when is_binary(Bin) -> + {ok, Bin}; + Packet when is_list(Packet) -> + case (catch list_to_binary(Packet)) of + Bin when is_binary(Bin) -> + {ok, Bin}; + {'EXIT', Reason} -> + {error, Reason} + end + end. + + + +generate_v3_report_msg(MsgID, SecModel, ScopedPdu, ErrInfo, Log) + when is_record(ScopedPdu, scopedPdu) -> + ReqID = (ScopedPdu#scopedPdu.data)#pdu.request_id, + generate_v3_report_msg2(MsgID, ReqID, SecModel, ErrInfo, Log); +generate_v3_report_msg(MsgID, SecModel, _, ErrInfo, Log) -> + %% RFC2572, 7.1.3.c.4 + generate_v3_report_msg2(MsgID, 0, SecModel, ErrInfo, Log). + + +generate_v3_report_msg2(MsgID, ReqID, SecModel, ErrInfo, Log) -> + {Varbind, SecName, Opts} = ErrInfo, + Pdu = #pdu{type = report, + request_id = ReqID, + error_status = noError, + error_index = 0, + varbinds = [Varbind]}, + SecLevel = snmp_misc:get_option(securityLevel, Opts, 0), + CtxEngineID = snmp_misc:get_option(contextEngineID, Opts, get_engine_id()), + CtxName = snmp_misc:get_option(contextName, Opts, ""), + SecData = snmp_misc:get_option(sec_data, Opts, []), + generate_v3_response_msg(Pdu, + MsgID, SecModel, SecName, SecLevel, + CtxEngineID, CtxName, SecData, Log). + + +%%----------------------------------------------------------------- + +%% Get "our" (manager) MMS +get_max_message_size() -> + case snmpm_config:get_engine_max_message_size() of + {ok, MMS} -> + MMS; + E -> + user_err("failed retreiving engine max message size: ~w", [E]), + 484 + end. + +%% The the MMS of the agent +get_agent_max_message_size(Addr, Port) -> + case snmpm_config:get_agent_engine_max_message_size(Addr, Port) of + {ok, MMS} -> + MMS; + _Error -> + ?vlog("unknown agent: ~w:~w", [Addr, Port]), + get_max_message_size() + end. + +%% Get "our" (manager) engine id +get_engine_id() -> + case snmpm_config:get_engine_id() of + {ok, Id} -> + Id; + _Error -> + "" + end. + +%% The engine id of the agent +get_agent_engine_id(Name) -> + snmpm_config:get_agent_engine_id(Name). + +is_known_engine_id(EngineID, Addr, Port) -> + snmpm_config:is_known_engine_id(EngineID, Addr, Port). + +% get_agent_engine_id(Addr, Port) -> +% case snmpm_config:get_agent_engine_id(Addr, Port) of +% {ok, Id} -> +% Id; +% _Error -> +% "" +% end. + + +%%----------------------------------------------------------------- +%% Sequence number (msg-id & req-id) functions +%%----------------------------------------------------------------- +next_msg_id() -> + next_id(msg_id). + +next_req_id() -> + next_id(req_id). + +next_id(Id) -> + snmpm_config:incr_counter(Id, 1). + + +%%----------------------------------------------------------------- +%% 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}). + +init_usm(true) -> + snmpm_usm:init(); +init_usm(_) -> + ok. + + +%%----------------------------------------------------------------- +%% Counter functions +%%----------------------------------------------------------------- +init_counters() -> + F = fun(Counter) -> maybe_create_counter(Counter) end, + lists:map(F, counters()). + +reset_counters() -> + F = fun(Counter) -> snmpm_config:reset_stats_counter(Counter) end, + lists:map(F, counters()). + +reset_usm(true) -> + snmpm_usm:reset(); +reset_usm(_) -> + ok. + +maybe_create_counter(Counter) -> + snmpm_config:maybe_cre_stats_counter(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) -> inc(Name, 1). +inc(Name, N) -> snmpm_config:incr_stats_counter(Name, N). + +inc_snmp_in(#pdu{type = Type}) -> + inc_in_type(Type); +inc_snmp_in(TrapPdu) when is_record(TrapPdu, trappdu) -> + inc(snmpInPkts), + inc(snmpInTraps). + +inc_snmp_out(#pdu{type = Type, + error_status = ErrorStatus}) -> + inc(snmpOutPkts), + inc_out_err(ErrorStatus), + inc_out_type(Type). + +inc_out_type('get-request') -> inc(snmpOutGetRequests); +inc_out_type('get-next-request') -> inc(snmpOutGetNexts); +inc_out_type('set-request') -> inc(snmpOutSetRequests); +inc_out_type(_) -> 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); +inc_out_err(_) -> ok. + +inc_in_type('get-response') -> inc(snmpInGetResponses); +inc_in_type(_) -> ok. + + +%%----------------------------------------------------------------- + +discard(Reason) -> + throw({discarded, Reason}). + +discard(Reason, Report) -> + throw({discarded, Reason, Report}). + +user_err(F, A) -> + error_msg("USER ERROR: " ++ F ++ "~n", A). + +config_err(F, A) -> + error_msg("CONFIG ERROR: " ++ F ++ "~n", A). + +error_msg(F, A) -> + ?snmpm_error("MPD: " ++ F, A). diff --git a/lib/snmp/src/manager/snmpm_net_if.erl b/lib/snmp/src/manager/snmpm_net_if.erl new file mode 100644 index 0000000000..14d39933dc --- /dev/null +++ b/lib/snmp/src/manager/snmpm_net_if.erl @@ -0,0 +1,1125 @@ +%% +%% %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(snmpm_net_if). + +-behaviour(gen_server). +-behaviour(snmpm_network_interface). + + +%% Network Interface callback functions +-export([ + start_link/2, + stop/1, + send_pdu/6, % Backward compatibillity + send_pdu/7, + + inform_response/4, + + note_store/2, + + info/1, + verbosity/2, + %% system_info_updated/2, + get_log_type/1, set_log_type/2, + filter_reset/1 + ]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). +-include("snmpm_internal.hrl"). +-include("snmpm_atl.hrl"). +-include("snmp_debug.hrl"). + +%% -define(VMODULE,"NET_IF"). +-include("snmp_verbosity.hrl"). + +-record(state, + { + server, + note_store, + sock, + mpd_state, + log, + irb = auto, % auto | {user, integer()} + irgc, + filter + }). + + +-define(DEFAULT_FILTER_MODULE, snmpm_net_if_filter). +-define(DEFAULT_FILTER_OPTS, [{module, ?DEFAULT_FILTER_MODULE}]). + +-ifdef(snmp_debug). +-define(GS_START_LINK(Args), + gen_server:start_link(?MODULE, Args, [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Args), + gen_server:start_link(?MODULE, Args, [])). +-endif. + + +-define(IRGC_TIMEOUT, timer:minutes(5)). + + +%%%------------------------------------------------------------------- +%%% API +%%%------------------------------------------------------------------- +start_link(Server, NoteStore) -> + ?d("start_link -> entry with" + "~n Server: ~p" + "~n NoteStore: ~p", [Server, NoteStore]), + Args = [Server, NoteStore], + ?GS_START_LINK(Args). + +stop(Pid) -> + call(Pid, stop). + +send_pdu(Pid, Pdu, Vsn, MsgData, Addr, Port) -> + send_pdu(Pid, Pdu, Vsn, MsgData, Addr, Port, undefined). + +send_pdu(Pid, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo) + when is_record(Pdu, pdu) -> + ?d("send_pdu -> entry with" + "~n Pid: ~p" + "~n Pdu: ~p" + "~n Vsn: ~p" + "~n MsgData: ~p" + "~n Addr: ~p" + "~n Port: ~p", [Pid, Pdu, Vsn, MsgData, Addr, Port]), + cast(Pid, {send_pdu, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo}). + +note_store(Pid, NoteStore) -> + call(Pid, {note_store, NoteStore}). + +inform_response(Pid, Ref, Addr, Port) -> + cast(Pid, {inform_response, Ref, Addr, Port}). + +info(Pid) -> + call(Pid, info). + +verbosity(Pid, V) -> + call(Pid, {verbosity, V}). + +%% system_info_updated(Pid, What) -> +%% call(Pid, {system_info_updated, What}). + +get_log_type(Pid) -> + call(Pid, get_log_type). + +set_log_type(Pid, NewType) -> + call(Pid, {set_log_type, NewType}). + +filter_reset(Pid) -> + cast(Pid, filter_reset). + + +%%%------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%-------------------------------------------------------------------- +init([Server, NoteStore]) -> + ?d("init -> entry with" + "~n Server: ~p" + "~n NoteStore: ~p", [Server, NoteStore]), + case (catch do_init(Server, NoteStore)) of + {error, Reason} -> + {stop, Reason}; + {ok, State} -> + {ok, State} + end. + +do_init(Server, NoteStore) -> + process_flag(trap_exit, true), + + %% -- Prio -- + {ok, Prio} = snmpm_config:system_info(prio), + process_flag(priority, Prio), + + %% -- Create inform request table -- + ets:new(snmpm_inform_request_table, + [set, protected, named_table, {keypos, 1}]), + + %% -- Verbosity -- + {ok, Verbosity} = snmpm_config:system_info(net_if_verbosity), + put(sname,mnif), + put(verbosity,Verbosity), + ?vlog("starting", []), + + %% -- MPD -- + {ok, Vsns} = snmpm_config:system_info(versions), + MpdState = snmpm_mpd:init(Vsns), + + %% -- Module dependent options -- + {ok, Opts} = snmpm_config:system_info(net_if_options), + + %% -- Inform response behaviour -- + {ok, IRB} = snmpm_config:system_info(net_if_irb), + IrGcRef = irgc_start(IRB), + + %% -- Socket -- + SndBuf = get_opt(Opts, sndbuf, default), + RecBuf = get_opt(Opts, recbuf, default), + BindTo = get_opt(Opts, bind_to, false), + NoReuse = get_opt(Opts, no_reuse, false), + {ok, Port} = snmpm_config:system_info(port), + {ok, Sock} = do_open_port(Port, SndBuf, RecBuf, BindTo, NoReuse), + + %% Flow control -- + FilterOpts = get_opt(Opts, filter, []), + FilterMod = create_filter(FilterOpts), + ?vdebug("FilterMod: ~w", [FilterMod]), + + %% -- Audit trail log --- + {ok, ATL} = snmpm_config:system_info(audit_trail_log), + Log = do_init_log(ATL), + + %% -- Initiate counters --- + init_counters(), + + %% -- We are done --- + State = #state{server = Server, + note_store = NoteStore, + mpd_state = MpdState, + sock = Sock, + log = Log, + irb = IRB, + irgc = IrGcRef, + filter = FilterMod}, + ?vdebug("started", []), + {ok, State}. + + +%% Open port +do_open_port(Port, SendSz, RecvSz, BindTo, NoReuse) -> + ?vtrace("do_open_port -> entry with" + "~n Port: ~p" + "~n SendSz: ~p" + "~n RecvSz: ~p" + "~n BindTo: ~p" + "~n NoReuse: ~p", [Port, SendSz, RecvSz, BindTo, NoReuse]), + IpOpts1 = bind_to(BindTo), + IpOpts2 = no_reuse(NoReuse), + IpOpts3 = recbuf(RecvSz), + IpOpts4 = sndbuf(SendSz), + IpOpts = [binary | IpOpts1 ++ IpOpts2 ++ IpOpts3 ++ IpOpts4], + OpenRes = + case init:get_argument(snmpm_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + gen_udp:open(0, [{fd, Fd}|IpOpts]); + error -> + gen_udp:open(Port, IpOpts) + end, + case OpenRes of + {error, _} = Error -> + throw(Error); + OK -> + OK + end. + +bind_to(true) -> + case snmpm_config:system_info(address) of + {ok, Addr} when is_list(Addr) -> + [{ip, list_to_tuple(Addr)}]; + {ok, Addr} -> + [{ip, Addr}]; + _ -> + [] + end; +bind_to(_) -> + []. + +no_reuse(false) -> + [{reuseaddr, true}]; +no_reuse(_) -> + []. + +recbuf(default) -> + []; +recbuf(Sz) -> + [{recbuf, Sz}]. + +sndbuf(default) -> + []; +sndbuf(Sz) -> + [{sndbuf, Sz}]. + + +create_filter(Opts) when is_list(Opts) -> + case get_opt(Opts, module, ?DEFAULT_FILTER_MODULE) of + ?DEFAULT_FILTER_MODULE = Mod -> + Mod; + Module -> + snmpm_network_interface_filter:verify(Module), + Module + end; +create_filter(BadOpts) -> + throw({error, {bad_filter_opts, BadOpts}}). + + +%% Open log +do_init_log(false) -> + ?vtrace("do_init_log(false) -> entry", []), + undefined; +do_init_log(true) -> + ?vtrace("do_init_log(true) -> entry", []), + {ok, Type} = snmpm_config:system_info(audit_trail_log_type), + {ok, Dir} = snmpm_config:system_info(audit_trail_log_dir), + {ok, Size} = snmpm_config:system_info(audit_trail_log_size), + {ok, Repair} = snmpm_config:system_info(audit_trail_log_repair), + 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} -> + {Log, Type}; + {error, Reason} -> + throw({error, {failed_create_audit_log, Reason}}) + end. + + +%%-------------------------------------------------------------------- +%% 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) +%%-------------------------------------------------------------------- +handle_call({verbosity, Verbosity}, _From, State) -> + ?vlog("received verbosity request", []), + put(verbosity, Verbosity), + {reply, ok, State}; + +%% handle_call({system_info_updated, What}, _From, State) -> +%% ?vlog("received system_info_updated request with What = ~p", [What]), +%% {NewState, Reply} = handle_system_info_updated(State, What), +%% {reply, Reply, NewState}; + +handle_call(get_log_type, _From, State) -> + ?vlog("received get-log-type request", []), + Reply = (catch handle_get_log_type(State)), + {reply, Reply, State}; + +handle_call({set_log_type, NewType}, _From, State) -> + ?vlog("received set-log-type request with NewType = ~p", [NewType]), + {NewState, Reply} = (catch handle_set_log_type(State, NewType)), + {reply, Reply, NewState}; + +handle_call({note_store, Pid}, _From, State) -> + ?vlog("received new note_store: ~w", [Pid]), + {reply, ok, State#state{note_store = Pid}}; + +handle_call(stop, _From, State) -> + ?vlog("received stop request", []), + Reply = ok, + {stop, normal, Reply, State}; + +handle_call(info, _From, State) -> + ?vlog("received info request", []), + Reply = get_info(State), + {reply, Reply, State}; + +handle_call(Req, From, State) -> + warning_msg("received unknown request (from ~p): ~n~p", [Req, From]), + {reply, {error, {invalid_request, Req}}, State}. + + +%%-------------------------------------------------------------------- +%% Func: handle_cast/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- +handle_cast({send_pdu, Pdu, Vsn, MsgData, Addr, Port, _ExtraInfo}, State) -> + ?vlog("received send_pdu message with" + "~n Pdu: ~p" + "~n Vsn: ~p" + "~n MsgData: ~p" + "~n Addr: ~p" + "~n Port: ~p", [Pdu, Vsn, MsgData, Addr, Port]), + maybe_handle_send_pdu(Pdu, Vsn, MsgData, Addr, Port, State), + {noreply, State}; + +handle_cast({inform_response, Ref, Addr, Port}, State) -> + ?vlog("received inform_response message with" + "~n Ref: ~p" + "~n Addr: ~p" + "~n Port: ~p", [Ref, Addr, Port]), + handle_inform_response(Ref, Addr, Port, State), + {noreply, State}; + +handle_cast(filter_reset, State) -> + ?vlog("received filter_reset message", []), + reset_counters(), + {noreply, State}; + +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) +%%-------------------------------------------------------------------- +handle_info({udp, Sock, Ip, Port, Bytes}, #state{sock = Sock} = State) -> + ?vlog("received ~w bytes from ~p:~p [~w]", [size(Bytes), Ip, Port, Sock]), + maybe_handle_recv_msg(Ip, Port, Bytes, State), + {noreply, State}; + +handle_info(inform_response_gc, State) -> + ?vlog("received inform_response_gc message", []), + State2 = handle_inform_response_gc(State), + {noreply, State2}; + +handle_info({disk_log, _Node, Log, Info}, State) -> + ?vlog("received disk_log message: " + "~n Info: ~p", [Info]), + State2 = handle_disk_log(Log, Info, State), + {noreply, State2}; + +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{log = Log, irgc = IrGcRef}) -> + ?vdebug("terminate: ~p",[Reason]), + irgc_stop(IrGcRef), + %% Close logs + do_close_log(Log), + ok. + + +do_close_log({Log, _Type}) -> + (catch snmp_log:sync(Log)), + (catch snmp_log:close(Log)), + ok; +do_close_log(_) -> + ok. + + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- + +code_change({down, _Vsn}, OldState, downgrade_to_pre45) -> + ?d("code_change(down) -> entry", []), + #state{server = Server, + note_store = NoteStore, + sock = Sock, + mpd_state = MpdState, + log = Log, + irgc = IrGcRef} = OldState, + irgc_stop(IrGcRef), + (catch ets:delete(snmpm_inform_request_table)), + State = {state, Server, NoteStore, Sock, MpdState, Log}, + {ok, State}; + +% upgrade +code_change(_Vsn, OldState, upgrade_from_pre45) -> + ?d("code_change(up) -> entry", []), + {state, Server, NoteStore, Sock, MpdState, Log} = OldState, + State = #state{server = Server, + note_store = NoteStore, + sock = Sock, + mpd_state = MpdState, + log = Log, + irb = auto, + irgc = undefined}, + ets:new(snmpm_inform_request_table, + [set, protected, named_table, {keypos, 1}]), + {ok, State}; + +code_change(_Vsn, State, _Extra) -> + {ok, State}. + + +%%%------------------------------------------------------------------- +%%% Internal functions +%%%------------------------------------------------------------------- + +maybe_handle_recv_msg(Addr, Port, Bytes, #state{filter = FilterMod} = State) -> + case (catch FilterMod:accept_recv(Addr, Port)) of + false -> + %% Drop the received packet + inc(netIfMsgInDrops), + ok; + _ -> + handle_recv_msg(Addr, Port, Bytes, State) + end. + + +handle_recv_msg(Addr, Port, Bytes, #state{server = Pid}) + when is_binary(Bytes) andalso (size(Bytes) =:= 0) -> + Pid ! {snmp_error, {empty_message, Addr, Port}, Addr, Port}, + ok; + +handle_recv_msg(Addr, Port, Bytes, + #state{server = Pid, + note_store = NoteStore, + mpd_state = MpdState, + sock = Sock, + log = Log} = State) -> + Logger = logger(Log, read, Addr, Port), + case (catch snmpm_mpd:process_msg(Bytes, snmpUDPDomain, Addr, Port, + MpdState, NoteStore, Logger)) of + + {ok, Vsn, Pdu, MS, ACM} -> + maybe_handle_recv_pdu(Addr, Port, Vsn, Pdu, MS, ACM, + Logger, State); + + {discarded, Reason, Report} -> + ?vdebug("discarded: ~p", [Reason]), + ErrorInfo = {failed_processing_message, Reason}, + Pid ! {snmp_error, ErrorInfo, Addr, Port}, + maybe_udp_send(State#state.filter, Sock, Addr, Port, Report), + ok; + + {discarded, Reason} -> + ?vdebug("discarded: ~p", [Reason]), + ErrorInfo = {failed_processing_message, Reason}, + Pid ! {snmp_error, ErrorInfo, Addr, Port}, + ok; + + Error -> + error_msg("processing of received message failed: " + "~n ~p", [Error]), + ok + end. + + +maybe_handle_recv_pdu(Addr, Port, + Vsn, #pdu{type = Type} = Pdu, PduMS, ACM, + Logger, + #state{filter = FilterMod} = State) -> + case (catch FilterMod:accept_recv_pdu(Addr, Port, Type)) of + false -> + inc(netIfPduInDrops), + ok; + _ -> + handle_recv_pdu(Addr, Port, Vsn, Pdu, PduMS, ACM, Logger, State) + end; +maybe_handle_recv_pdu(Addr, Port, Vsn, Trap, PduMS, ACM, Logger, + #state{filter = FilterMod} = State) + when is_record(Trap, trappdu) -> + case (catch FilterMod:accept_recv_pdu(Addr, Port, trappdu)) of + false -> + inc(netIfPduInDrops), + ok; + _ -> + handle_recv_pdu(Addr, Port, Vsn, Trap, PduMS, ACM, Logger, State) + end; +maybe_handle_recv_pdu(Addr, Port, Vsn, Pdu, PduMS, ACM, Logger, State) -> + handle_recv_pdu(Addr, Port, Vsn, Pdu, PduMS, ACM, Logger, State). + + +handle_recv_pdu(Addr, Port, + Vsn, #pdu{type = 'inform-request'} = Pdu, _PduMS, ACM, + Logger, #state{server = Pid, irb = IRB} = State) -> + handle_inform_request(IRB, Pid, Vsn, Pdu, ACM, + Addr, Port, Logger, State); +handle_recv_pdu(Addr, Port, + _Vsn, #pdu{type = report} = Pdu, _PduMS, ok, + _Logger, + #state{server = Pid} = _State) -> + ?vtrace("received report - ok", []), + Pid ! {snmp_report, {ok, Pdu}, Addr, Port}; +handle_recv_pdu(Addr, Port, + _Vsn, #pdu{type = report} = Pdu, _PduMS, + {error, ReqId, Reason}, + _Logger, + #state{server = Pid} = _State) -> + ?vtrace("received report - error", []), + Pid ! {snmp_report, {error, ReqId, Reason, Pdu}, Addr, Port}; +handle_recv_pdu(Addr, Port, + _Vsn, #pdu{type = 'snmpv2-trap'} = Pdu, _PduMS, _ACM, + _Logger, + #state{server = Pid} = _State) -> + ?vtrace("received snmpv2-trap", []), + Pid ! {snmp_trap, Pdu, Addr, Port}; +handle_recv_pdu(Addr, Port, + _Vsn, Trap, _PduMS, _ACM, + _Logger, + #state{server = Pid} = _State) when is_record(Trap, trappdu) -> + ?vtrace("received trappdu", []), + Pid ! {snmp_trap, Trap, Addr, Port}; +handle_recv_pdu(Addr, Port, + _Vsn, Pdu, _PduMS, _ACM, + _Logger, + #state{server = Pid} = _State) when is_record(Pdu, pdu) -> + ?vtrace("received pdu", []), + Pid ! {snmp_pdu, Pdu, Addr, Port}; +handle_recv_pdu(_Addr, _Port, _Vsn, Pdu, _PduMS, ACM, _Logger, _State) -> + ?vlog("received unexpected pdu: " + "~n Pdu: ~p" + "~n ACM: ~p", [Pdu, ACM]). + + +handle_inform_request(auto, Pid, Vsn, Pdu, ACM, Addr, Port, Logger, State) -> + ?vtrace("received inform-request (true)", []), + Pid ! {snmp_inform, ignore, Pdu, Addr, Port}, + RePdu = make_response_pdu(Pdu), + maybe_send_inform_response(RePdu, Vsn, ACM, Addr, Port, Logger, State); +handle_inform_request({user, To}, Pid, Vsn, #pdu{request_id = ReqId} = Pdu, + ACM, Addr, Port, _Logger, _State) -> + ?vtrace("received inform-request (false)", []), + + Pid ! {snmp_inform, ReqId, Pdu, Addr, Port}, + + %% Before we go any further, we need to check that we have not + %% already received this message (possible resend). + + Key = {ReqId, Addr, Port}, + case ets:lookup(snmpm_inform_request_table, Key) of + [_] -> + %% OK, we already know about this. We assume this + %% is a resend. Either the agent is really eager or + %% the user has not answered yet. Bad user! + ok; + [] -> + RePdu = make_response_pdu(Pdu), + Expire = t() + To, + Rec = {Key, Expire, {Vsn, ACM, RePdu}}, + ets:insert(snmpm_inform_request_table, Rec) + end. + +handle_inform_response(Ref, Addr, Port, State) -> + Key = {Ref, Addr, Port}, + case ets:lookup(snmpm_inform_request_table, Key) of + [{Key, _, {Vsn, ACM, RePdu}}] -> + Logger = logger(State#state.log, read, Addr, Port), + ets:delete(snmpm_inform_request_table, Key), + maybe_send_inform_response(RePdu, Vsn, ACM, Addr, Port, + Logger, State); + [] -> + %% Already acknowledged, or the user was to slow to reply... + ok + end, + ok. + +maybe_send_inform_response(RePdu, Vsn, ACM, Addr, Port, Logger, + #state{server = Pid, + sock = Sock, + filter = FilterMod}) -> + case (catch FilterMod:accept_send_pdu(Addr, Port, pdu_type_of(RePdu))) of + false -> + inc(netIfPduOutDrops), + ok; + _ -> + case snmpm_mpd:generate_response_msg(Vsn, RePdu, ACM, Logger) of + {ok, Msg} -> + maybe_udp_send(FilterMod, Sock, Addr, Port, Msg); + {discarded, Reason} -> + ?vlog("failed generating response message:" + "~n Reason: ~p", [Reason]), + ReqId = RePdu#pdu.request_id, + ErrorInfo = {failed_generating_response, {RePdu, Reason}}, + Pid ! {snmp_error, ReqId, ErrorInfo, Addr, Port}, + ok + end + end. + +handle_inform_response_gc(#state{irb = IRB} = State) -> + ets:safe_fixtable(snmpm_inform_request_table, true), + do_irgc(ets:first(snmpm_inform_request_table), t()), + ets:safe_fixtable(snmpm_inform_request_table, false), + State#state{irgc = irgc_start(IRB)}. + +%% We are deleting at the same time as we are traversing the table!!! +do_irgc('$end_of_table', _) -> + ok; +do_irgc(Key, Now) -> + Next = ets:next(snmpm_inform_request_table, Key), + case ets:lookup(snmpm_inform_request_table, Key) of + [{Key, BestBefore, _}] when BestBefore < Now -> + ets:delete(snmpm_inform_request_table, Key); + _ -> + ok + end, + do_irgc(Next, Now). + +irgc_start(auto) -> + undefined; +irgc_start(_) -> + erlang:send_after(?IRGC_TIMEOUT, self(), inform_response_gc). + +irgc_stop(undefined) -> + ok; +irgc_stop(Ref) -> + (catch erlang:cancel_timer(Ref)). + + +maybe_handle_send_pdu(Pdu, Vsn, MsgData, Addr, Port, + #state{filter = FilterMod} = State) -> + case (catch FilterMod:accept_send_pdu(Addr, Port, pdu_type_of(Pdu))) of + false -> + inc(netIfPduOutDrops), + ok; + _ -> + handle_send_pdu(Pdu, Vsn, MsgData, Addr, Port, State) + end. + +handle_send_pdu(Pdu, Vsn, MsgData, Addr, Port, + #state{server = Pid, + note_store = NoteStore, + sock = Sock, + log = Log, + filter = FilterMod}) -> + Logger = logger(Log, write, Addr, Port), + case (catch snmpm_mpd:generate_msg(Vsn, NoteStore, + Pdu, MsgData, Logger)) of + {ok, Msg} -> + ?vtrace("handle_send_pdu -> message generated", []), + maybe_udp_send(FilterMod, Sock, Addr, Port, Msg); + {discarded, Reason} -> + ?vlog("PDU not sent: " + "~n PDU: ~p" + "~n Reason: ~p", [Pdu, Reason]), + Pid ! {snmp_error, Pdu, Reason}, + ok + end. + + +maybe_udp_send(FilterMod, Sock, Addr, Port, Msg) -> + case (catch FilterMod:accept_send(Addr, Port)) of + false -> + inc(netIfMsgOutDrops), + ok; + _ -> + udp_send(Sock, Addr, Port, Msg) + end. + + +udp_send(Sock, Addr, Port, Msg) -> + case (catch gen_udp:send(Sock, Addr, Port, Msg)) of + ok -> + ?vdebug("sent ~w bytes to ~w:~w [~w]", + [sz(Msg), Addr, Port, Sock]), + ok; + {error, Reason} -> + error_msg("failed sending message to ~p:~p: " + "~n ~p",[Addr, Port, Reason]); + Error -> + error_msg("failed sending message to ~p:~p: " + "~n ~p",[Addr, Port, Error]) + end. + +sz(B) when is_binary(B) -> + size(B); +sz(L) when is_list(L) -> + length(L); +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("Failed 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. + + +%% mk_discovery_msg('version-3', Pdu, _VsnHdr, UserName) -> +%% ScopedPDU = #scopedPdu{contextEngineID = "", +%% contextName = "", +%% data = Pdu}, +%% Bytes = snmp_pdus:enc_scoped_pdu(ScopedPDU), +%% MsgID = get(msg_id), +%% put(msg_id,MsgID+1), +%% UsmSecParams = +%% #usmSecurityParameters{msgAuthoritativeEngineID = "", +%% msgAuthoritativeEngineBoots = 0, +%% msgAuthoritativeEngineTime = 0, +%% msgUserName = UserName, +%% msgPrivacyParameters = "", +%% msgAuthenticationParameters = ""}, +%% SecBytes = snmp_pdus:enc_usm_security_parameters(UsmSecParams), +%% PduType = Pdu#pdu.type, +%% Hdr = #v3_hdr{msgID = MsgID, +%% msgMaxSize = 1000, +%% msgFlags = snmp_misc:mk_msg_flags(PduType, 0), +%% msgSecurityModel = ?SEC_USM, +%% msgSecurityParameters = SecBytes}, +%% Msg = #message{version = 'version-3', vsn_hdr = Hdr, data = Bytes}, +%% case (catch snmp_pdus:enc_message_only(Msg)) of +%% {'EXIT', Reason} -> +%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), +%% error; +%% L when list(L) -> +%% {Msg, L} +%% end; +%% mk_discovery_msg(Version, Pdu, {Com, _, _, _, _}, UserName) -> +%% Msg = #message{version = Version, vsn_hdr = Com, data = Pdu}, +%% case catch snmp_pdus:enc_message(Msg) of +%% {'EXIT', Reason} -> +%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), +%% error; +%% L when list(L) -> +%% {Msg, L} +%% end. + + +%% mk_msg('version-3', Pdu, {Context, User, EngineID, CtxEngineId, SecLevel}, +%% MsgData) -> +%% %% Code copied from snmp_mpd.erl +%% {MsgId, SecName, SecData} = +%% if +%% tuple(MsgData), Pdu#pdu.type == 'get-response' -> +%% MsgData; +%% true -> +%% Md = get(msg_id), +%% put(msg_id, Md + 1), +%% {Md, User, []} +%% end, +%% ScopedPDU = #scopedPdu{contextEngineID = CtxEngineId, +%% contextName = Context, +%% data = Pdu}, +%% ScopedPDUBytes = snmp_pdus:enc_scoped_pdu(ScopedPDU), + +%% PduType = Pdu#pdu.type, +%% V3Hdr = #v3_hdr{msgID = MsgId, +%% msgMaxSize = 1000, +%% msgFlags = snmp_misc:mk_msg_flags(PduType, SecLevel), +%% msgSecurityModel = ?SEC_USM}, +%% Message = #message{version = 'version-3', vsn_hdr = V3Hdr, +%% data = ScopedPDUBytes}, +%% SecEngineID = case PduType of +%% 'get-response' -> snmp_framework_mib:get_engine_id(); +%% _ -> EngineID +%% end, +%% case catch snmp_usm:generate_outgoing_msg(Message, SecEngineID, +%% SecName, SecData, SecLevel) of +%% {'EXIT', Reason} -> +%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), +%% error; +%% {error, Reason} -> +%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), +%% error; +%% Packet -> +%% Packet +%% end; +%% mk_msg(Version, Pdu, {Com, _User, _EngineID, _Ctx, _SecLevel}, _SecData) -> +%% Msg = #message{version = Version, vsn_hdr = Com, data = Pdu}, +%% case catch snmp_pdus:enc_message(Msg) of +%% {'EXIT', Reason} -> +%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]), +%% error; +%% B when list(B) -> +%% B +%% end. + + +%% handle_system_info_updated(#state{log = {Log, _OldType}} = State, +%% audit_trail_log_type = _What) -> +%% %% Just to make sure, check that ATL is actually enabled +%% case snmpm_config:system_info(audit_trail_log) of +%% {ok, true} -> +%% {ok, Type} = snmpm_config:system_info(audit_trail_log_type), +%% NewState = State#state{log = {Log, Type}}, +%% {NewState, ok}; +%% _ -> +%% {State, {error, {adt_not_enabled}}} +%% end; +%% handle_system_info_updated(_State, _What) -> +%% ok. + +handle_get_log_type(#state{log = {_Log, Value}} = State) -> + %% Just to make sure, check that ATL is actually enabled + case snmpm_config:system_info(audit_trail_log) of + {ok, true} -> + Type = + case {lists:member(read, Value), lists:member(write, Value)} of + {true, true} -> + read_write; + {true, false} -> + read; + {false, true} -> + write; + {false, false} -> + throw({State, {error, {bad_atl_type, Value}}}) + end, + {ok, Type}; + _ -> + {error, not_enabled} + end; +handle_get_log_type(_State) -> + {error, not_enabled}. + +handle_set_log_type(#state{log = {Log, OldValue}} = State, NewType) -> + %% Just to make sure, check that ATL is actually enabled + case snmpm_config:system_info(audit_trail_log) of + {ok, true} -> + 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}}; + _ -> + {State, {error, not_enabled}} + end; +handle_set_log_type(State, _NewType) -> + {State, {error, not_enabled}}. + + +%% ------------------------------------------------------------------- + +make_response_pdu(#pdu{request_id = ReqId, varbinds = Vbs}) -> + #pdu{type = 'get-response', + request_id = ReqId, + error_status = noError, + error_index = 0, + varbinds = Vbs}. + + +%% ---------------------------------------------------------------- + +pdu_type_of(#pdu{type = Type}) -> + Type; +pdu_type_of(TrapPdu) when is_record(TrapPdu, trappdu) -> + trap. + + +%% ------------------------------------------------------------------- + +t() -> + {A,B,C} = erlang:now(), + A*1000000000+B*1000+(C div 1000). + + +%% ------------------------------------------------------------------- + +logger(undefined, _Type, _Addr, _Port) -> + fun(_) -> + ok + end; +logger({Log, Types}, Type, Addr, Port) -> + case lists:member(Type, Types) of + true -> + fun(Msg) -> + snmp_log:log(Log, Msg, Addr, Port) + end; + false -> + fun(_) -> + ok + end + end. + + +%% ------------------------------------------------------------------- + +%% info_msg(F, A) -> +%% ?snmpm_info("NET-IF server: " ++ F, A). + +warning_msg(F, A) -> + ?snmpm_warning("NET-IF server: " ++ F, A). + +error_msg(F, A) -> + ?snmpm_error("NET-IF server: " ++ F, A). + + + +%%%------------------------------------------------------------------- + +% get_opt(Key, Opts) -> +% ?vtrace("get option ~w", [Key]), +% snmp_misc:get_option(Key, Opts). + +get_opt(Opts, Key, Def) -> + ?vtrace("get option ~w with default ~p", [Key, Def]), + snmp_misc:get_option(Key, Opts, Def). + + +%% ------------------------------------------------------------------- + +get_info(#state{sock = Id}) -> + ProcSize = proc_mem(self()), + PortInfo = get_port_info(Id), + [{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, buffer])) of + {ok, Sz} -> + [{buffer_size, Sz}]; + _ -> + [] + end, + [{socket, Id}] ++ + IfList ++ + PortStats ++ + PortInfo ++ + PortStatus ++ + PortAct ++ + BufSz. + + +%%----------------------------------------------------------------- +%% Counter functions +%%----------------------------------------------------------------- +init_counters() -> + F = fun(Counter) -> maybe_create_counter(Counter) end, + lists:map(F, counters()). + +reset_counters() -> + F = fun(Counter) -> snmpm_config:reset_stats_counter(Counter) end, + lists:map(F, counters()). + +maybe_create_counter(Counter) -> + snmpm_config:maybe_cre_stats_counter(Counter, 0). + +counters() -> + [ + netIfMsgOutDrops, + netIfMsgInDrops, + netIfPduOutDrops, + netIfPduInDrops + ]. + +inc(Name) -> inc(Name, 1). +inc(Name, N) -> snmpm_config:incr_stats_counter(Name, N). + +%% get_counters() -> +%% Counters = counters(), +%% get_counters(Counters, []). + +%% get_counters([], Acc) -> +%% lists:reverse(Acc); +%% get_counters([Counter|Counters], Acc) -> +%% case snmpm_config:get_stats_counter(Counter) of +%% {ok, CounterVal} -> +%% get_counters(Counters, [{Counter, CounterVal}|Acc]); +%% _ -> +%% get_counters(Counters, Acc) +%% end. + + +%% ---------------------------------------------------------------- + +call(Pid, Req) -> + call(Pid, Req, infinity). + +call(Pid, Req, Timeout) -> + gen_server:call(Pid, Req, Timeout). + +cast(Pid, Msg) -> + gen_server:cast(Pid, Msg). + diff --git a/lib/snmp/src/manager/snmpm_net_if_filter.erl b/lib/snmp/src/manager/snmpm_net_if_filter.erl new file mode 100644 index 0000000000..eb0c6efb11 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_net_if_filter.erl @@ -0,0 +1,53 @@ +%% +%% %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(snmpm_net_if_filter). + +-export([accept_recv/2, + accept_send/2, + accept_recv_pdu/3, + accept_send_pdu/3]). + +-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(_Addr, _Port, _PduType) -> + ?d("accept_send_pdu -> entry with" + "~n Addr: ~p" + "~n Port: ~p" + "~n PduType: ~p", [_Addr, _Port, _PduType]), + true. + diff --git a/lib/snmp/src/manager/snmpm_network_interface.erl b/lib/snmp/src/manager/snmpm_network_interface.erl new file mode 100644 index 0000000000..b830d45ce7 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_network_interface.erl @@ -0,0 +1,37 @@ +%% +%% %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(snmpm_network_interface). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{start_link, 2}, + {stop, 1}, + {send_pdu, 7}, + {inform_response, 4}, + {note_store, 2}, + {info, 1}, + {verbosity, 2}, + %% {system_info_updated, 2}, + {get_log_type, 1}, + {set_log_type, 2}]; +behaviour_info(_) -> + undefined. + diff --git a/lib/snmp/src/manager/snmpm_network_interface_filter.erl b/lib/snmp/src/manager/snmpm_network_interface_filter.erl new file mode 100644 index 0000000000..ae8b7cfce1 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_network_interface_filter.erl @@ -0,0 +1,54 @@ +%% +%% %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(snmpm_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, 3}]; +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(Addr, Port, pdu_type()) -> boolean() +%% 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/manager/snmpm_server.erl b/lib/snmp/src/manager/snmpm_server.erl new file mode 100644 index 0000000000..30aacc0ec3 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_server.erl @@ -0,0 +1,3117 @@ +%% +%% %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(snmpm_server). + +%%---------------------------------------------------------------------- +%% This module implements a simple SNMP manager for Erlang. +%% +%%---------------------------------------------------------------------- + +%% User interface +-export([start_link/0, stop/0, + is_started/0, + + load_mib/1, unload_mib/1, + + register_user/4, register_user_monitor/4, unregister_user/1, + + sync_get/4, sync_get/5, sync_get/6, + async_get/4, async_get/5, async_get/6, + sync_get_next/4, sync_get_next/5, sync_get_next/6, + async_get_next/4, async_get_next/5, async_get_next/6, + sync_get_bulk/6, sync_get_bulk/7, sync_get_bulk/8, + async_get_bulk/6, async_get_bulk/7, async_get_bulk/8, + sync_set/4, sync_set/5, sync_set/6, + async_set/4, async_set/5, async_set/6, + cancel_async_request/2, + + %% discovery/2, discovery/3, discovery/4, discovery/5, discovery/6, + + %% system_info_updated/2, + get_log_type/0, set_log_type/1, + + reconfigure/0, + + info/0, + verbosity/1, verbosity/2 + + ]). + + +%% Internal exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + code_change/3, terminate/2]). + +%% GCT exports +-export([gct_init/1, gct/2]). + + +-include("snmpm_internal.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_types.hrl"). +-include("STANDARD-MIB.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). +-include("snmp_verbosity.hrl"). + + +%%---------------------------------------------------------------------- + +-define(SERVER, ?MODULE). + +-define(SYNC_GET_TIMEOUT, 5000). +-define(SYNC_SET_TIMEOUT, 5000). +-define(DEFAULT_ASYNC_EXPIRE, 5000). +-define(EXTRA_INFO, undefined). + +-define(SNMP_AGENT_PORT, 161). + + +-ifdef(snmp_debug). +-define(GS_START_LINK(Args), + gen_server:start_link({local, ?SERVER}, ?MODULE, Args, + [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Args), + gen_server:start_link({local, ?SERVER}, ?MODULE, Args, [])). +-endif. + + + +%%---------------------------------------------------------------------- + +-record(state, + {parent, + gct, + note_store, + note_store_ref, + net_if, + net_if_mod, + net_if_ref, + req, %% ???? Last request id in outgoing message + oid, %% ???? Last oid in request outgoing message + mini_mib + } + ). + +%% The active state is to ensure that nothing unpleasant happens +%% during (after) a code_change. At the initial start of the +%% application, this process (GCT) will make one run and then +%% deactivate (unless some async request has been issued in the +%% meantime). +-record(gct, {parent, state = active, timeout}). + +-record(request, + {id, + user_id, + reg_type, + target, + addr, + port, + type, + data, + ref, + mon, + from, + discovery = false, + expire = infinity % When shall the request expire (time in ms) + } + ). + +-record(monitor, + {id, + mon, + proc + } + ). + + +%%%------------------------------------------------------------------- +%%% API +%%%------------------------------------------------------------------- + +start_link() -> + ?d("start_link -> entry", []), + Args = [], + ?GS_START_LINK(Args). + +stop() -> + call(stop). + +is_started() -> + case (catch call(is_started, 1000)) of + Bool when ((Bool =:= true) orelse (Bool =:= false)) -> + Bool; + _ -> + false + end. + +load_mib(MibFile) when is_list(MibFile) -> + call({load_mib, MibFile}). + +unload_mib(Mib) when is_list(Mib) -> + call({unload_mib, Mib}). + + +register_user(UserId, UserMod, UserData, DefaultAgentConfig) -> + snmpm_config:register_user(UserId, UserMod, UserData, DefaultAgentConfig). + +register_user_monitor(Id, Module, Data, DefaultAgentConfig) -> + case register_user(Id, Module, Data, DefaultAgentConfig) of + ok -> + case call({monitor_user, Id, self()}) of + ok -> + ok; + Error -> + unregister_user(Id), + Error + end; + Error -> + Error + end. + +unregister_user(UserId) -> + call({unregister_user, UserId}). + + +%% -- [sync] get -- + +sync_get(UserId, TargetName, CtxName, Oids) -> + sync_get(UserId, TargetName, CtxName, Oids, + ?SYNC_GET_TIMEOUT). + +sync_get(UserId, TargetName, CtxName, Oids, Timeout) -> + sync_get(UserId, TargetName, CtxName, Oids, Timeout, ?EXTRA_INFO). + +sync_get(UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo) + when is_list(TargetName) andalso + is_list(CtxName) andalso + is_list(Oids) andalso + is_integer(Timeout) -> + call({sync_get, self(), UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo}). + +%% -- [async] get -- + +async_get(UserId, TargetName, CtxName, Oids) -> + async_get(UserId, TargetName, CtxName, Oids, + ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO). + +async_get(UserId, TargetName, CtxName, Oids, Expire) -> + async_get(UserId, TargetName, CtxName, Oids, Expire, ?EXTRA_INFO). + +async_get(UserId, TargetName, CtxName, Oids, Expire, ExtraInfo) + when (is_list(TargetName) andalso + is_list(CtxName) andalso + is_list(Oids) andalso + is_integer(Expire) andalso (Expire >= 0)) -> + call({async_get, self(), UserId, TargetName, CtxName, Oids, Expire, + ExtraInfo}). + +%% -- [sync] get-next -- + +sync_get_next(UserId, TargetName, CtxName, Oids) -> + sync_get_next(UserId, TargetName, CtxName, Oids, ?SYNC_GET_TIMEOUT, + ?EXTRA_INFO). + +sync_get_next(UserId, TargetName, CtxName, Oids, Timeout) -> + sync_get_next(UserId, TargetName, CtxName, Oids, Timeout, ?EXTRA_INFO). + +sync_get_next(UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo) + when is_list(TargetName) andalso + is_list(CtxName) andalso + is_list(Oids) andalso + is_integer(Timeout) -> + call({sync_get_next, self(), UserId, TargetName, CtxName, Oids, Timeout, + ExtraInfo}). + +%% -- [async] get-next -- + +async_get_next(UserId, TargetName, CtxName, Oids) -> + async_get_next(UserId, TargetName, CtxName, Oids, + ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO). + +async_get_next(UserId, TargetName, CtxName, Oids, Expire) -> + async_get_next(UserId, TargetName, CtxName, Oids, Expire, ?EXTRA_INFO). + +async_get_next(UserId, TargetName, CtxName, Oids, Expire, ExtraInfo) + when (is_list(TargetName) andalso + is_list(CtxName) andalso + is_list(Oids) andalso + is_integer(Expire) andalso (Expire >= 0)) -> + call({async_get_next, self(), UserId, TargetName, CtxName, Oids, + Expire, ExtraInfo}). + +%% -- [sync] get-bulk -- + +sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids) -> + sync_get_bulk(UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, + ?SYNC_GET_TIMEOUT, ?EXTRA_INFO). + +sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Timeout) -> + sync_get_bulk(UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, + Timeout, ?EXTRA_INFO). + +sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Timeout, + ExtraInfo) + when is_list(TargetName) andalso + is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(CtxName) andalso + is_list(Oids) andalso + is_integer(Timeout) -> + call({sync_get_bulk, self(), UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, Timeout, ExtraInfo}). + +%% -- [async] get-bulk -- + +async_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids) -> + async_get_bulk(UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, + ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO). + +async_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Expire) -> + async_get_bulk(UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, + Expire, ?EXTRA_INFO). + +async_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Expire, + ExtraInfo) + when is_list(TargetName) andalso + is_integer(NonRep) andalso + is_integer(MaxRep) andalso + is_list(CtxName) andalso + is_list(Oids) andalso + is_integer(Expire) -> + call({async_get_bulk, self(), UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, Expire, ExtraInfo}). + +%% -- [sync] set -- + +%% VarsAndValues is: {PlainOid, o|s|i, Value} (unknown mibs) | {Oid, Value} +sync_set(UserId, TargetName, CtxName, VarsAndVals) -> + sync_set(UserId, TargetName, CtxName, VarsAndVals, + ?SYNC_SET_TIMEOUT, ?EXTRA_INFO). + +sync_set(UserId, TargetName, CtxName, VarsAndVals, Timeout) -> + sync_set(UserId, TargetName, CtxName, VarsAndVals, + Timeout, ?EXTRA_INFO). + +sync_set(UserId, TargetName, CtxName, VarsAndVals, Timeout, ExtraInfo) + when is_list(TargetName) andalso + is_list(CtxName) andalso + is_list(VarsAndVals) andalso + is_integer(Timeout) -> + call({sync_set, self(), UserId, TargetName, + CtxName, VarsAndVals, Timeout, ExtraInfo}). + +%% -- [async] set -- + +async_set(UserId, TargetName, CtxName, VarsAndVals) -> + async_set(UserId, TargetName, CtxName, VarsAndVals, + ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO). + +async_set(UserId, TargetName, CtxName, VarsAndVals, Expire) -> + async_set(UserId, TargetName, CtxName, VarsAndVals, + Expire, ?EXTRA_INFO). + +async_set(UserId, TargetName, CtxName, VarsAndVals, Expire, ExtraInfo) + when (is_list(TargetName) andalso + is_list(CtxName) andalso + is_list(VarsAndVals) andalso + is_integer(Expire) andalso (Expire >= 0)) -> + call({async_set, self(), UserId, TargetName, + CtxName, VarsAndVals, Expire, ExtraInfo}). + + +cancel_async_request(UserId, ReqId) -> + call({cancel_async_request, UserId, ReqId}). + + +%% discovery(UserId, BAddr) -> +%% discovery(UserId, BAddr, ?SNMP_AGENT_PORT, [], +%% ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO). + +%% discovery(UserId, BAddr, Config) when is_list(Config) -> +%% discovery(UserId, BAddr, ?SNMP_AGENT_PORT, Config, +%% ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO); + +%% discovery(UserId, BAddr, Expire) when is_integer(Expire) -> +%% discovery(UserId, BAddr, ?SNMP_AGENT_PORT, [], Expire, ?EXTRA_INFO). + +%% discovery(UserId, BAddr, Config, Expire) -> +%% discovery(UserId, BAddr, ?SNMP_AGENT_PORT, Config, Expire, ?EXTRA_INFO). + +%% discovery(UserId, BAddr, Port, Config, Expire) -> +%% discovery(UserId, BAddr, Port, Config, Expire, ?EXTRA_INFO). + +%% discovery(UserId, BAddr, Port, Config, Expire, ExtraInfo) -> +%% call({discovery, self(), UserId, BAddr, Port, Config, Expire, ExtraInfo}). + + +verbosity(Verbosity) -> + case ?vvalidate(Verbosity) of + Verbosity -> + call({verbosity, Verbosity}); + _ -> + {error, {invalid_verbosity, Verbosity}} + end. + +info() -> + call(info). + +verbosity(net_if = Ref, Verbosity) -> + verbosity2(Ref, Verbosity); +verbosity(note_store = Ref, Verbosity) -> + verbosity2(Ref, Verbosity). + +verbosity2(Ref, Verbosity) -> + case ?vvalidate(Verbosity) of + Verbosity -> + call({verbosity, Ref, Verbosity}); + _ -> + {error, {invalid_verbosity, Verbosity}} + end. + +%% Target -> all | server | net_if +%% system_info_updated(Target, What) -> +%% call({system_info_updated, Target, What}). + +get_log_type() -> + call(get_log_type). + +set_log_type(NewType) -> + call({set_log_type, NewType}). + +reconfigure() -> + call(reconfigure). + + +%%---------------------------------------------------------------------- +%% Options: List of +%% {community, String ("public" is default} +%% {mibs, List of Filenames} +%% {trap_udp, integer() (default 5000)} +%% {conf_dir, string()} +%% {log_dir, string()} +%% {db_dir, string()} +%% {db_repair, true | false} +%%---------------------------------------------------------------------- +init(_) -> + ?d("init -> entry", []), + case (catch do_init()) of + {ok, State} -> + {ok, State}; + {error, Reason} -> + {stop, Reason} + end. + + +%% Put all config stuff in a snmpm_config module/process. +%% Tables should be protected so that it is cheap to +%% read. Writing has to go through the interface... + +do_init() -> + process_flag(trap_exit, true), + {ok, Prio} = snmpm_config:system_info(prio), + process_flag(priority, Prio), + + {ok, Verbosity} = snmpm_config:system_info(server_verbosity), + put(sname, mse), + put(verbosity, Verbosity), + ?vlog("starting", []), + + %% Start the garbage collector timer process + {ok, Timeout} = snmpm_config:system_info(server_timeout), + {ok, GCT} = gct_start(Timeout), + + %% -- Create request table -- + ets:new(snmpm_request_table, + [set, protected, named_table, {keypos, #request.id}]), + + %% -- Create monitor table -- + ets:new(snmpm_monitor_table, + [set, protected, named_table, {keypos, #monitor.id}]), + + %% -- Start the note-store and net-if processes -- + {NoteStore, NoteStoreRef} = do_init_note_store(Prio), + {NetIf, NetIfModule, NetIfRef} = do_init_net_if(NoteStore), + + MiniMIB = snmpm_config:make_mini_mib(), + State = #state{mini_mib = MiniMIB, + gct = GCT, + note_store = NoteStore, + note_store_ref = NoteStoreRef, + net_if = NetIf, + net_if_mod = NetIfModule, + net_if_ref = NetIfRef}, + ?vlog("started", []), + {ok, State}. + + +do_init_note_store(Prio) -> + ?vdebug("try start note store", []), + {ok, Verbosity} = snmpm_config:system_info(note_store_verbosity), + {ok, Timeout} = snmpm_config:system_info(note_store_timeout), + Opts = [{sname, mns}, + {verbosity, Verbosity}, + {timeout, Timeout}], + case snmpm_misc_sup:start_note_store(Prio, Opts) of + {ok, Pid} -> + ?vtrace("do_init_note_store -> Pid: ~p", [Pid]), + Ref = erlang:monitor(process, Pid), + {Pid, Ref}; + {error, Reason} -> + ?vlog("failed starting note-store - Reason: " + "~n Reason: ~p" + "~n", [Reason]), + throw({error, {failed_starting_note_store, Reason}}) + end. + +do_init_net_if(NoteStore) -> + ?vdebug("try start net if", []), + {ok, NetIfModule} = snmpm_config:system_info(net_if_module), + case snmpm_misc_sup:start_net_if(NetIfModule, NoteStore) of + {ok, Pid} -> + ?vtrace("do_init_net_if -> Pid: ~p", [Pid]), + Ref = erlang:monitor(process, Pid), + {Pid, NetIfModule, Ref}; + {error, Reason} -> + ?vlog("failed starting net-if - Reason: " + "~n Reason: ~p" + "~n", [Reason]), + throw({error, {failed_starting_net_if, Reason}}) + end. + +%% --------------------------------------------------------------------- +%% --------------------------------------------------------------------- + +handle_call({monitor_user, Id, Pid}, _From, State) when is_pid(Pid) -> + ?vlog("received monitor_user request for ~w [~w]", [Id, Pid]), + Reply = + case ets:lookup(snmpm_monitor_table, Id) of + [#monitor{proc = Pid}] -> + ?vdebug("already monitored", []), + ok; + + [#monitor{proc = OtherPid}] -> + ?vinfo("already registered to ~w", [OtherPid]), + {error, {already_monitored, OtherPid}}; + + [] -> + Ref = erlang:monitor(process, Pid), + ?vtrace("monitor ref: ~w", [Ref]), + Mon = #monitor{id = Id, mon = Ref, proc = Pid}, + ets:insert(snmpm_monitor_table, Mon), + ok + end, + {reply, Reply, State}; + +handle_call({unregister_user, UserId}, _From, State) -> + ?vlog("received request to unregister user ~p", [UserId]), + + %% 1) If this user is monitored, then demonitor + ?vtrace("handle_call(unregister_user) -> maybe demonitor", []), + case ets:lookup(snmpm_monitor_table, UserId) of + [] -> + ok; + [#monitor{mon = M}] -> + maybe_demonitor(M), % This is really overkill (meybe_), but... + ok + end, + + %% 2) Delete all outstanding requests from this user + ?vtrace("handle_call(unregister_user) -> " + "delete all outstanding requests for user", []), + Pat = #request{user_id = UserId, + id = '$1', ref = '$2', mon = '$3', _ = '_'}, + Match = ets:match(snmpm_request_table, Pat), + ?vtrace("handle_call(unregister_user) -> Match: ~p", [Match]), + F1 = fun([ReqId, Ref, MonRef]) -> + ets:delete(snmpm_request_table, ReqId), + cancel_timer(Ref), + maybe_demonitor(MonRef), + ok + end, + lists:foreach(F1, Match), + + %% 3) Unregister all agents registered by this user + ?vdebug("handle_call(unregister_user) -> " + "unregister all agents registered by user", []), + Agents = snmpm_config:which_agents(UserId), + ?vtrace("handle_call(unregister_user) -> Agents: ~p", [Agents]), + F2 = fun(TargetName) -> + snmpm_config:unregister_agent(UserId, TargetName) + end, + lists:foreach(F2, Agents), + + %% 4) Unregister the user + ?vdebug("handle_call(unregister_user) -> unregister user", []), + Reply = snmpm_config:unregister_user(UserId), + ?vtrace("handle_call(unregister_user) -> Reply: ~p", [Reply]), + {reply, Reply, State}; + + +%% We will reply to this request later, when the reply comes in from the +%% agent, or when the timeout hits (unless we get an error now). +handle_call({sync_get, Pid, UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo}, + From, State) -> + ?vlog("received sync_get [~p] request", [CtxName]), + case (catch handle_sync_get(Pid, + UserId, TargetName, CtxName, Oids, + Timeout, ExtraInfo, From, State)) of + ok -> + {noreply, State}; + Error -> + {reply, Error, State} + end; + + +handle_call({sync_get_next, Pid, UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo}, From, State) -> + ?vlog("received sync_get_next [~p] request", [CtxName]), + case (catch handle_sync_get_next(Pid, + UserId, TargetName, CtxName, Oids, + Timeout, ExtraInfo, From, State)) of + ok -> + {noreply, State}; + Error -> + {reply, Error, State} + end; + + +%% Check agent version? This op not in v1 +handle_call({sync_get_bulk, Pid, UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, Timeout, ExtraInfo}, + From, State) -> + ?vlog("received sync_get_bulk [~p] request", [CtxName]), + case (catch handle_sync_get_bulk(Pid, + UserId, TargetName, CtxName, + NonRep, MaxRep, Oids, + Timeout, ExtraInfo, From, State)) of + ok -> + {noreply, State}; + Error -> + {reply, Error, State} + end; + + +handle_call({sync_set, Pid, UserId, TargetName, + CtxName, VarsAndVals, Timeout, ExtraInfo}, + From, State) -> + ?vlog("received sync_set [~p] request", [CtxName]), + case (catch handle_sync_set(Pid, + UserId, TargetName, CtxName, VarsAndVals, + Timeout, ExtraInfo, From, State)) of + ok -> + {noreply, State}; + Error -> + {reply, Error, State} + end; + + +handle_call({async_get, Pid, UserId, TargetName, + CtxName, Oids, Expire, ExtraInfo}, + _From, State) -> + ?vlog("received async_get [~p] request", [CtxName]), + Reply = (catch handle_async_get(Pid, UserId, TargetName, CtxName, Oids, + Expire, ExtraInfo, State)), + {reply, Reply, State}; + + +handle_call({async_get_next, Pid, UserId, TargetName, + CtxName, Oids, Expire, ExtraInfo}, + _From, State) -> + ?vlog("received async_get_next [~p] request", [CtxName]), + Reply = (catch handle_async_get_next(Pid, UserId, TargetName, CtxName, + Oids, Expire, ExtraInfo, State)), + {reply, Reply, State}; + + +%% Check agent version? This op not in v1 +handle_call({async_get_bulk, Pid, UserId, TargetName, + NonRep, MaxRep, CtxName, Oids, Expire, ExtraInfo}, + _From, State) -> + ?vlog("received async_get_bulk [~p] request", [CtxName]), + Reply = (catch handle_async_get_bulk(Pid, + UserId, TargetName, CtxName, + NonRep, MaxRep, Oids, + Expire, ExtraInfo, State)), + {reply, Reply, State}; + + +handle_call({async_set, Pid, UserId, TargetName, + CtxName, VarsAndVals, Expire, ExtraInfo}, + _From, State) -> + ?vlog("received async_set [~p] request", [CtxName]), + Reply = (catch handle_async_set(Pid, UserId, TargetName, CtxName, + VarsAndVals, Expire, ExtraInfo, State)), + {reply, Reply, State}; + + +handle_call({cancel_async_request, UserId, ReqId}, _From, State) -> + ?vlog("received cancel_async_request request", []), + Reply = (catch handle_cancel_async_request(UserId, ReqId, State)), + {reply, Reply, State}; + + +%% handle_call({discovery, Pid, UserId, BAddr, Port, Config, Expire, ExtraInfo}, +%% _From, State) -> +%% ?vlog("received discovery request", []), +%% Reply = (catch handle_discovery(Pid, UserId, BAddr, Port, Config, +%% Expire, ExtraInfo, State)), +%% {reply, Reply, State}; + + +handle_call({load_mib, Mib}, _From, State) -> + ?vlog("received load_mib request", []), + case snmpm_config:load_mib(Mib) of + ok -> + MiniMIB = snmpm_config:make_mini_mib(), + {reply, ok, State#state{mini_mib = MiniMIB}}; + Error -> + {reply, Error, State} + end; + + +handle_call({unload_mib, Mib}, _From, State) -> + ?vlog("received unload_mib request", []), + case snmpm_config:unload_mib(Mib) of + ok -> + MiniMIB = snmpm_config:make_mini_mib(), + {reply, ok, State#state{mini_mib = MiniMIB}}; + Error -> + {reply, Error, State} + end; + +handle_call({verbosity, Verbosity}, _From, State) -> + ?vlog("received verbosity request", []), + put(verbosity, Verbosity), + {reply, ok, State}; + +handle_call({verbosity, net_if, Verbosity}, _From, + #state{net_if = Pid, net_if_mod = Mod} = State) -> + ?vlog("received net_if verbosity request", []), + Mod:verbosity(Pid, Verbosity), + {reply, ok, State}; + +handle_call({verbosity, note_store, Verbosity}, _From, + #state{note_store = Pid} = State) -> + ?vlog("received note_store verbosity request", []), + snmp_note_store:verbosity(Pid, Verbosity), + {reply, ok, State}; + +handle_call(reconfigure, _From, State) -> + ?vlog("received reconfigure request", []), + Reply = {error, not_implemented}, + {reply, Reply, State}; + +handle_call(info, _From, State) -> + ?vlog("received info request", []), + Reply = get_info(State), + {reply, Reply, State}; + +handle_call(is_started, _From, State) -> + ?vlog("received is_started request", []), + IsStarted = is_started(State), + {reply, IsStarted, State}; + +%% handle_call({system_info_updated, Target, What}, _From, State) -> +%% ?vlog("received system_info_updated request: " +%% "~n Target: ~p" +%% "~n What: ~p", [Target, What]), +%% Reply = handle_system_info_updated(State, Target, What), +%% {reply, Reply, State}; + +handle_call(get_log_type, _From, State) -> + ?vlog("received get_log_type request", []), + Reply = handle_get_log_type(State), + {reply, Reply, State}; + +handle_call({set_log_type, NewType}, _From, State) -> + ?vlog("received set_log_type request: " + "~n NewType: ~p", [NewType]), + Reply = handle_set_log_type(State, NewType), + {reply, Reply, State}; + +handle_call(stop, _From, State) -> + ?vlog("received stop request", []), + {stop, normal, ok, State}; + + +handle_call(Req, _From, State) -> + warning_msg("received unknown request: ~n~p", [Req]), + {reply, {error, unknown_request}, State}. + + +handle_cast(Msg, State) -> + warning_msg("received unknown message: ~n~p", [Msg]), + {noreply, State}. + + +handle_info({sync_timeout, ReqId, From}, State) -> + ?vlog("received sync_timeout [~w] message", [ReqId]), + handle_sync_timeout(ReqId, From, State), + {noreply, State}; + + +handle_info({snmp_error, Pdu, Reason}, State) -> + ?vlog("received snmp_error message", []), + handle_snmp_error(Pdu, Reason, State), + {noreply, State}; + +handle_info({snmp_error, Reason, Addr, Port}, State) -> + ?vlog("received snmp_error message", []), + handle_snmp_error(Addr, Port, -1, Reason, State), + {noreply, State}; + +handle_info({snmp_error, ReqId, Reason, Addr, Port}, State) -> + ?vlog("received snmp_error message", []), + handle_snmp_error(Addr, Port, ReqId, Reason, State), + {noreply, State}; + +%% handle_info({snmp_error, ReqId, Pdu, Reason, Addr, Port}, State) -> +%% ?vlog("received snmp_error message", []), +%% handle_snmp_error(Pdu, ReqId, Reason, Addr, Port, State), +%% {noreply, State}; + + +handle_info({snmp_pdu, Pdu, Addr, Port}, State) -> + ?vlog("received snmp_pdu message", []), + handle_snmp_pdu(Pdu, Addr, Port, State), + {noreply, State}; + + +handle_info({snmp_trap, Trap, Addr, Port}, State) -> + ?vlog("received snmp_trap message", []), + handle_snmp_trap(Trap, Addr, Port, State), + {noreply, State}; + + +handle_info({snmp_inform, Ref, Pdu, Addr, Port}, State) -> + ?vlog("received snmp_inform message", []), + handle_snmp_inform(Ref, Pdu, Addr, Port, State), + {noreply, State}; + + +handle_info({snmp_report, {ok, Pdu}, Addr, Port}, State) -> + handle_snmp_report(Pdu, Addr, Port, State), + {noreply, State}; + +handle_info({snmp_report, {error, ReqId, Info, Pdu}, Addr, Port}, State) -> + handle_snmp_report(ReqId, Pdu, Info, Addr, Port, State), + {noreply, State}; + + +handle_info(gc_timeout, #state{gct = GCT} = State) -> + ?vlog("received gc_timeout message", []), + handle_gc(GCT), + {noreply, State}; + + +handle_info({'DOWN', _MonRef, process, Pid, _Reason}, + #state{note_store = NoteStore, + net_if = Pid} = State) -> + ?vlog("received 'DOWN' message regarding net_if", []), + {NetIf, _, Ref} = do_init_net_if(NoteStore), + {noreply, State#state{net_if = NetIf, net_if_ref = Ref}}; + + +handle_info({'DOWN', _MonRef, process, Pid, _Reason}, + #state{note_store = Pid, + net_if = NetIf, + net_if_mod = Mod} = State) -> + ?vlog("received 'DOWN' message regarding note_store", []), + {ok, Prio} = snmpm_config:system_info(prio), + {NoteStore, Ref} = do_init_note_store(Prio), + Mod:note_store(NetIf, NoteStore), + {noreply, State#state{note_store = NoteStore, note_store_ref = Ref}}; + + +handle_info({'DOWN', MonRef, process, Pid, Reason}, State) -> + ?vlog("received 'DOWN' message (~w) from ~w " + "~n Reason: ~p", [MonRef, Pid, Reason]), + handle_down(MonRef), + {noreply, State}; + + +handle_info({'EXIT', Pid, Reason}, #state{gct = Pid} = State) -> + ?vlog("received 'EXIT' message from the GCT (~w) process: " + "~n ~p", [Pid, Reason]), + {ok, Timeout} = snmpm_config:system_info(server_timeout), + {ok, GCT} = gct_start(Timeout), + {noreply, State#state{gct = GCT}}; + + +handle_info(Info, State) -> + warning_msg("received unknown info: ~n~p", [Info]), + {noreply, State}. + + +%%---------------------------------------------------------- +%% Code change +%%---------------------------------------------------------- + +% downgrade +code_change({down, _Vsn}, #state{gct = Pid} = State, _Extra) -> + ?d("code_change(down) -> entry", []), + gct_code_change(Pid), + {ok, State}; + +% upgrade +code_change(_Vsn, #state{gct = Pid} = State0, _Extra) -> + ?d("code_change(up) -> entry", []), + gct_code_change(Pid), + MiniMIB = snmpm_config:make_mini_mib(), + State = State0#state{mini_mib = MiniMIB}, + {ok, State}. + + +%%---------------------------------------------------------- +%% Terminate +%%---------------------------------------------------------- + +terminate(Reason, #state{gct = GCT}) -> + ?vdebug("terminate: ~p",[Reason]), + gct_stop(GCT), + snmpm_misc_sup:stop_note_store(), + snmpm_misc_sup:stop_net_if(), + ok. + + +%%---------------------------------------------------------------------- +%% +%%---------------------------------------------------------------------- + +handle_sync_get(Pid, UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo, + From, State) -> + ?vtrace("handle_sync_get -> entry with" + "~n Pid: ~p" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n CtxName: ~p" + "~n Oids: ~p" + "~n Timeout: ~p" + "~n From: ~p", + [Pid, UserId, TargetName, CtxName, Oids, Timeout, From]), + case agent_data(TargetName, CtxName) of + {ok, RegType, Addr, Port, Vsn, MsgData} -> + ?vtrace("handle_sync_get -> send a ~p message", [Vsn]), + ReqId = send_get_request(Oids, Vsn, MsgData, Addr, Port, + ExtraInfo, State), + ?vdebug("handle_sync_get -> ReqId: ~p", [ReqId]), + Msg = {sync_timeout, ReqId, From}, + Ref = erlang:send_after(Timeout, self(), Msg), + MonRef = erlang:monitor(process, Pid), + ?vtrace("handle_sync_get -> MonRef: ~p", [MonRef]), + Req = #request{id = ReqId, + user_id = UserId, + reg_type = RegType, + target = TargetName, + addr = Addr, + port = Port, + type = get, + data = MsgData, + ref = Ref, + mon = MonRef, + from = From}, + ets:insert(snmpm_request_table, Req), + ok; + Error -> + ?vinfo("failed retrieving agent data for get:" + "~n TargetName: ~p" + "~n Error: ~p", [TargetName, Error]), + Error + end. + + +handle_sync_get_next(Pid, UserId, TargetName, CtxName, Oids, Timeout, + ExtraInfo, From, State) -> + ?vtrace("handle_sync_get_next -> entry with" + "~n Pid: ~p" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n CtxName: ~p" + "~n Oids: ~p" + "~n Timeout: ~p" + "~n From: ~p", + [Pid, UserId, TargetName, CtxName, Oids, Timeout, From]), + case agent_data(TargetName, CtxName) of + {ok, RegType, Addr, Port, Vsn, MsgData} -> + ?vtrace("handle_sync_get_next -> send a ~p message", [Vsn]), + ReqId = send_get_next_request(Oids, Vsn, MsgData, + Addr, Port, ExtraInfo, State), + ?vdebug("handle_sync_get_next -> ReqId: ~p", [ReqId]), + Msg = {sync_timeout, ReqId, From}, + Ref = erlang:send_after(Timeout, self(), Msg), + MonRef = erlang:monitor(process, Pid), + ?vtrace("handle_sync_get_next -> MonRef: ~p", [MonRef]), + Req = #request{id = ReqId, + user_id = UserId, + reg_type = RegType, + target = TargetName, + addr = Addr, + port = Port, + type = get_next, + data = MsgData, + ref = Ref, + mon = MonRef, + from = From}, + ets:insert(snmpm_request_table, Req), + ok; + + Error -> + ?vinfo("failed retrieving agent data for get-next:" + "~n TargetName: ~p" + "~n Error: ~p", [TargetName, Error]), + Error + end. + + +handle_sync_get_bulk(Pid, UserId, TargetName, CtxName, + NonRep, MaxRep, Oids, Timeout, + ExtraInfo, From, State) -> + ?vtrace("handle_sync_get_bulk -> entry with" + "~n Pid: ~p" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n CtxName: ~p" + "~n NonRep: ~p" + "~n MaxRep: ~p" + "~n Oids: ~p" + "~n Timeout: ~p" + "~n From: ~p", + [Pid, UserId, TargetName, CtxName, NonRep, MaxRep, Oids, + Timeout, From]), + case agent_data(TargetName, CtxName) of + {ok, RegType, Addr, Port, Vsn, MsgData} -> + ?vtrace("handle_sync_get_bulk -> send a ~p message", [Vsn]), + ReqId = send_get_bulk_request(Oids, Vsn, MsgData, Addr, Port, + NonRep, MaxRep, ExtraInfo, State), + ?vdebug("handle_sync_get_bulk -> ReqId: ~p", [ReqId]), + Msg = {sync_timeout, ReqId, From}, + Ref = erlang:send_after(Timeout, self(), Msg), + MonRef = erlang:monitor(process, Pid), + ?vtrace("handle_sync_get_bulk -> MonRef: ~p", [MonRef]), + Req = #request{id = ReqId, + user_id = UserId, + reg_type = RegType, + target = TargetName, + addr = Addr, + port = Port, + type = get_bulk, + data = MsgData, + ref = Ref, + mon = MonRef, + from = From}, + ets:insert(snmpm_request_table, Req), + ok; + + Error -> + ?vinfo("failed retrieving agent data for get-bulk:" + "~n TargetName: ~p" + "~n Error: ~p", [TargetName, Error]), + Error + end. + + +handle_sync_set(Pid, UserId, TargetName, CtxName, VarsAndVals, Timeout, + ExtraInfo, From, State) -> + ?vtrace("handle_sync_set -> entry with" + "~n Pid: ~p" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n CtxName: ~p" + "~n VarsAndVals: ~p" + "~n Timeout: ~p" + "~n From: ~p", + [Pid, UserId, TargetName, CtxName, VarsAndVals, Timeout, From]), + case agent_data(TargetName, CtxName) of + {ok, RegType, Addr, Port, Vsn, MsgData} -> + ?vtrace("handle_sync_set -> send a ~p message", [Vsn]), + ReqId = send_set_request(VarsAndVals, Vsn, MsgData, + Addr, Port, ExtraInfo, State), + ?vdebug("handle_sync_set -> ReqId: ~p", [ReqId]), + Msg = {sync_timeout, ReqId, From}, + Ref = erlang:send_after(Timeout, self(), Msg), + MonRef = erlang:monitor(process, Pid), + ?vtrace("handle_sync_set -> MonRef: ~p", [MonRef]), + Req = #request{id = ReqId, + user_id = UserId, + reg_type = RegType, + target = TargetName, + addr = Addr, + port = Port, + type = set, + data = MsgData, + ref = Ref, + mon = MonRef, + from = From}, + ets:insert(snmpm_request_table, Req), + ok; + + Error -> + ?vinfo("failed retrieving agent data for set:" + "~n TargetName: ~p" + "~n Error: ~p", [TargetName, Error]), + Error + end. + + +handle_async_get(Pid, UserId, TargetName, CtxName, Oids, Expire, ExtraInfo, + State) -> + ?vtrace("handle_async_get -> entry with" + "~n Pid: ~p" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n CtxName: ~p" + "~n Oids: ~p" + "~n Expire: ~p", + [Pid, UserId, TargetName, CtxName, Oids, Expire]), + case agent_data(TargetName, CtxName) of + {ok, RegType, Addr, Port, Vsn, MsgData} -> + ?vtrace("handle_async_get -> send a ~p message", [Vsn]), + ReqId = send_get_request(Oids, Vsn, MsgData, Addr, Port, + ExtraInfo, State), + ?vdebug("handle_async_get -> ReqId: ~p", [ReqId]), + Req = #request{id = ReqId, + user_id = UserId, + reg_type = RegType, + target = TargetName, + addr = Addr, + port = Port, + type = get, + data = MsgData, + expire = t() + Expire}, + + ets:insert(snmpm_request_table, Req), + gct_activate(State#state.gct), + {ok, ReqId}; + + Error -> + ?vinfo("failed retrieving agent data for get:" + "~n TargetName: ~p" + "~n Error: ~p", [TargetName, Error]), + Error + end. + + +handle_async_get_next(Pid, UserId, TargetName, CtxName, Oids, Expire, + ExtraInfo, State) -> + ?vtrace("handle_async_get_next -> entry with" + "~n Pid: ~p" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n CtxName: ~p" + "~n Oids: ~p" + "~n Expire: ~p", + [Pid, UserId, TargetName, CtxName, Oids, Expire]), + case agent_data(TargetName, CtxName) of + {ok, RegType, Addr, Port, Vsn, MsgData} -> + ?vtrace("handle_async_get_next -> send a ~p message", [Vsn]), + ReqId = send_get_next_request(Oids, Vsn, MsgData, + Addr, Port, ExtraInfo, State), + ?vdebug("handle_async_get_next -> ReqId: ~p", [ReqId]), + Req = #request{id = ReqId, + user_id = UserId, + reg_type = RegType, + target = TargetName, + addr = Addr, + port = Port, + type = get_next, + data = MsgData, + expire = t() + Expire}, + + ets:insert(snmpm_request_table, Req), + gct_activate(State#state.gct), + {ok, ReqId}; + + Error -> + ?vinfo("failed retrieving agent data for get-next:" + "~n TargetName: ~p" + "~n Error: ~p", [TargetName, Error]), + Error + end. + + +handle_async_get_bulk(Pid, UserId, TargetName, CtxName, + NonRep, MaxRep, Oids, Expire, + ExtraInfo, State) -> + ?vtrace("handle_async_get_bulk -> entry with" + "~n Pid: ~p" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n CtxName: ~p" + "~n NonRep: ~p" + "~n MaxRep: ~p" + "~n Oids: ~p" + "~n Expire: ~p", + [Pid, UserId, TargetName, CtxName, NonRep, MaxRep, Oids, Expire]), + case agent_data(TargetName, CtxName) of + {ok, RegType, Addr, Port, Vsn, MsgData} -> + ?vtrace("handle_async_get_bulk -> send a ~p message", [Vsn]), + ReqId = send_get_bulk_request(Oids, Vsn, MsgData, Addr, Port, + NonRep, MaxRep, ExtraInfo, State), + ?vdebug("handle_async_get_bulk -> ReqId: ~p", [ReqId]), + Req = #request{id = ReqId, + user_id = UserId, + reg_type = RegType, + target = TargetName, + addr = Addr, + port = Port, + type = get_bulk, + data = MsgData, + expire = t() + Expire}, + ets:insert(snmpm_request_table, Req), + gct_activate(State#state.gct), + {ok, ReqId}; + + Error -> + ?vinfo("failed retrieving agent data for get-bulk:" + "~n TargetName: ~p" + "~n Error: ~p", [TargetName, Error]), + Error + end. + + +handle_async_set(Pid, UserId, TargetName, CtxName, VarsAndVals, Expire, + ExtraInfo, State) -> + ?vtrace("handle_async_set -> entry with" + "~n Pid: ~p" + "~n UserId: ~p" + "~n TargetName: ~p" + "~n CtxName: ~p" + "~n VarsAndVals: ~p" + "~n Expire: ~p", + [Pid, UserId, TargetName, CtxName, VarsAndVals, Expire]), + case agent_data(TargetName, CtxName) of + {ok, RegType, Addr, Port, Vsn, MsgData} -> + ?vtrace("handle_async_set -> send a ~p message", [Vsn]), + ReqId = send_set_request(VarsAndVals, Vsn, MsgData, + Addr, Port, ExtraInfo, State), + ?vdebug("handle_async_set -> ReqId: ~p", [ReqId]), + Req = #request{id = ReqId, + user_id = UserId, + reg_type = RegType, + target = TargetName, + addr = Addr, + port = Port, + type = set, + data = MsgData, + expire = t() + Expire}, + + ets:insert(snmpm_request_table, Req), + gct_activate(State#state.gct), + {ok, ReqId}; + + Error -> + ?vinfo("failed retrieving agent data for set:" + "~n TargetName: ~p" + "~n Error: ~p", [TargetName, Error]), + Error + end. + + +handle_cancel_async_request(UserId, ReqId, _State) -> + ?vtrace("handle_cancel_async_request -> entry with" + "~n UserId: ~p" + "~n ReqId: ~p", [UserId, ReqId]), + case ets:lookup(snmpm_request_table, ReqId) of + [#request{user_id = UserId, + ref = Ref}] -> + ?vdebug("handle_cancel_async_request -> demonitor and cancel timer" + "~n Ref: ~p", [Ref]), + cancel_timer(Ref), + ets:delete(snmpm_request_table, ReqId), + ok; + + [#request{user_id = OtherUserId}] -> + ?vinfo("handle_cancel_async_request -> Not request owner" + "~n OtherUserId: ~p", [OtherUserId]), + {error, {not_owner, OtherUserId}}; + + [] -> + ?vlog("handle_cancel_async_request -> not found", []), + {error, not_found} + end. + + +%% handle_system_info_updated(#state{net_if = Pid, net_if_mod = Mod} = _State, +%% net_if = _Target, What) -> +%% case (catch Mod:system_info_updated(Pid, What)) of +%% {'EXIT', _} -> +%% {error, not_supported}; +%% Else -> +%% Else +%% end; +%% handle_system_info_updated(_State, Target, What) -> +%% {error, {bad_target, Target, What}}. + +handle_get_log_type(#state{net_if = Pid, net_if_mod = Mod}) -> + case (catch Mod:get_log_type(Pid)) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end. + +handle_set_log_type(#state{net_if = Pid, net_if_mod = Mod}, NewType) -> + case (catch Mod:set_log_type(Pid, NewType)) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end. + + +%% handle_discovery(Pid, UserId, BAddr, Port, Config, Expire, ExtraInfo, State) -> +%% ?vtrace("handle_discovery -> entry with" +%% "~n Pid: ~p" +%% "~n UserId: ~p" +%% "~n BAddr: ~p" +%% "~n Port: ~p" +%% "~n Config: ~p" +%% "~n Expire: ~p", +%% [Pid, UserId, BAddr, Port, Config, Expire]), +%% case agent_data(default, default, "", Config) of +%% {ok, Addr, Port, Vsn, MsgData} -> +%% ?vtrace("handle_discovery -> send a ~p disco message", [Vsn]), +%% ReqId = send_discovery(Vsn, MsgData, BAddr, Port, ExtraInfo, +%% State), +%% ?vdebug("handle_discovery -> ReqId: ~p", [ReqId]), +%% MonRef = erlang:monitor(process, Pid), +%% ?vtrace("handle_discovery -> MonRef: ~p", [MonRef]), +%% Req = #request{id = ReqId, +%% user_id = UserId, +%% target = TargetName, +%% addr = BAddr, +%% port = Port, +%% type = get, +%% data = MsgData, +%% mon = MonRef, +%% discovery = true, +%% expire = t() + Expire}, +%% ets:insert(snmpm_request_table, Req), +%% gct_activate(State#state.gct), +%% {ok, ReqId}; + +%% Error -> +%% ?vinfo("failed retrieving agent data for discovery (get):" +%% "~n BAddr: ~p" +%% "~n Port: ~p" +%% "~n Error: ~p", [BAddr, Port, Error]), +%% Error +%% end. + + +handle_sync_timeout(ReqId, From, State) -> + ?vtrace("handle_sync_timeout -> entry with" + "~n ReqId: ~p" + "~n From: ~p", [ReqId, From]), + case ets:lookup(snmpm_request_table, ReqId) of + [#request{mon = MonRef, from = From} = Req0] -> + ?vdebug("handle_sync_timeout -> " + "deliver reply (timeout) and demonitor: " + "~n Monref: ~p" + "~n From: ~p", [MonRef, From]), + gen_server:reply(From, {error, {timeout, ReqId}}), + maybe_demonitor(MonRef), + + %% + %% Instead of deleting the request record now, + %% we leave it to the gc. But for that to work + %% we must update the expire value (which for + %% sync requests is infinity). + %% + + Req = Req0#request{ref = undefined, + mon = undefined, + from = undefined, + expire = t()}, + ets:insert(snmpm_request_table, Req), + gct_activate(State#state.gct), + ok; + _ -> + ok + end. + + +handle_snmp_error(#pdu{request_id = ReqId} = Pdu, Reason, State) -> + + ?vtrace("handle_snmp_error -> entry with" + "~n Reason: ~p" + "~n Pdu: ~p", [Reason, Pdu]), + + case ets:lookup(snmpm_request_table, ReqId) of + + %% Failed async request + [#request{user_id = UserId, + from = undefined, + ref = undefined, + mon = MonRef, + discovery = Disco}] -> + + ?vdebug("handle_snmp_error -> " + "found corresponding request: " + "~n failed async request" + "~n UserId: ~p" + "~n ModRef: ~p" + "~n Disco: ~p", [UserId, MonRef, Disco]), + + maybe_demonitor(MonRef), + case snmpm_config:user_info(UserId) of + {ok, UserMod, UserData} -> + handle_error(UserId, UserMod, Reason, ReqId, + UserData, State), + maybe_delete(Disco, ReqId); + _ -> + %% reply to outstanding request, for which there is no + %% longer any owner (the user has unregistered). + %% Therefor send it to the default user + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_error(DefUserId, DefMod, Reason, ReqId, + DefData, State), + maybe_delete(Disco, ReqId); + _ -> + error_msg("failed retreiving the default user " + "info handling error [~w]: " + "~n~w", [ReqId, Reason]) + end + end; + + + %% Failed sync request + %% + [#request{ref = Ref, mon = MonRef, from = From}] -> + + ?vdebug("handle_snmp_error -> " + "found corresponding request: " + "~n failed sync request" + "~n Ref: ~p" + "~n ModRef: ~p" + "~n From: ~p", [Ref, MonRef, From]), + + Remaining = + case (catch cancel_timer(Ref)) of + Rem when is_integer(Rem) -> + Rem; + _ -> + 0 + end, + + ?vtrace("handle_snmp_error -> Remaining: ~p", [Remaining]), + + maybe_demonitor(MonRef), + Reply = {error, {send_failed, ReqId, Reason}}, + ?vtrace("handle_snmp_error -> deliver (error-) reply",[]), + gen_server:reply(From, Reply), + ets:delete(snmpm_request_table, ReqId), + ok; + + + %% A very old reply, see if this agent is handled by + %% a user. In that case send it there, else to the + %% default user. + _ -> + + ?vdebug("handle_snmp_error -> no request?", []), + + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_error(DefUserId, DefMod, Reason, + ReqId, DefData, State); + _ -> + error_msg("failed retreiving the default " + "user info handling error [~w]: " + "~n~w",[ReqId, Reason]) + end + end; + +handle_snmp_error(CrapError, Reason, _State) -> + error_msg("received crap (snmp) error =>" + "~n~p~n~p", [CrapError, Reason]), + ok. + +handle_snmp_error(Addr, Port, ReqId, Reason, State) -> + + ?vtrace("handle_snmp_error -> entry with" + "~n Addr: ~p" + "~n Port: ~p" + "~n ReqId: ~p" + "~n Reason: ~p", [Addr, Port, ReqId, Reason]), + + case snmpm_config:get_agent_user_id(Addr, Port) of + {ok, UserId} -> + case snmpm_config:user_info(UserId) of + {ok, UserMod, UserData} -> + handle_error(UserId, UserMod, Reason, ReqId, + UserData, State); + _Error -> + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_error(DefUserId, DefMod, Reason, + ReqId, DefData, State); + _Error -> + error_msg("failed retreiving the default user " + "info handling snmp error " + "<~p,~p>: ~n~w~n~w", + [Addr, Port, ReqId, Reason]) + end + end; + _Error -> + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_error(DefUserId, DefMod, Reason, + ReqId, DefData, State); + _Error -> + error_msg("failed retreiving the default user " + "info handling snmp error " + "<~p,~p>: ~n~w~n~w", + [Addr, Port, ReqId, Reason]) + end + end. + + +handle_error(_UserId, Mod, Reason, ReqId, Data, _State) -> + ?vtrace("handle_error -> entry when" + "~n Mod: ~p", [Mod]), + F = fun() -> (catch Mod:handle_error(ReqId, Reason, Data)) end, + handle_callback(F), + ok. + + +handle_snmp_pdu(#pdu{type = 'get-response', request_id = ReqId} = Pdu, + Addr, Port, State) -> + + ?vtrace("handle_snmp_pdu(get-response) -> entry with" + "~n Addr: ~p" + "~n Port: ~p" + "~n Pdu: ~p", [Addr, Port, Pdu]), + + case ets:lookup(snmpm_request_table, ReqId) of + + %% Reply to a async request or + %% possibly a late reply to a sync request + %% (ref is also undefined) + [#request{user_id = UserId, + reg_type = RegType, + target = Target, + from = undefined, + ref = undefined, + mon = MonRef, + discovery = Disco}] -> + + ?vdebug("handle_snmp_pdu(get-response) -> " + "found corresponding request: " + "~n reply to async request or late reply to sync request" + "~n UserId: ~p" + "~n ModRef: ~p" + "~n Disco: ~p", [UserId, MonRef, Disco]), + + maybe_demonitor(MonRef), + #pdu{error_status = EStatus, + error_index = EIndex, + varbinds = Varbinds} = Pdu, + Varbinds2 = fix_vbs_BITS(Varbinds), + SnmpResponse = {EStatus, EIndex, Varbinds2}, + case snmpm_config:user_info(UserId) of + {ok, UserMod, UserData} -> + handle_pdu(UserId, UserMod, + RegType, Target, Addr, Port, + ReqId, SnmpResponse, UserData, State), + maybe_delete(Disco, ReqId); + _Error -> + %% reply to outstanding request, for which there is no + %% longer any owner (the user has unregistered). + %% Therefor send it to the default user + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_pdu(DefUserId, DefMod, + RegType, Target, Addr, Port, + ReqId, SnmpResponse, DefData, State), + maybe_delete(Disco, ReqId); + Error -> + error_msg("failed retreiving the default user " + "info handling pdu from " + "~p <~p,~p>: ~n~w~n~w", + [Target, Addr, Port, Error, Pdu]) + end + end; + + + %% Reply to a sync request + %% + [#request{ref = Ref, mon = MonRef, from = From}] -> + + ?vdebug("handle_snmp_pdu(get-response) -> " + "found corresponding request: " + "~n reply to sync request" + "~n Ref: ~p" + "~n ModRef: ~p" + "~n From: ~p", [Ref, MonRef, From]), + + Remaining = + case (catch cancel_timer(Ref)) of + Rem when is_integer(Rem) -> + Rem; + _ -> + 0 + end, + + ?vtrace("handle_snmp_pdu(get-response) -> Remaining: ~p", + [Remaining]), + + maybe_demonitor(MonRef), + #pdu{error_status = EStatus, + error_index = EIndex, + varbinds = Varbinds} = Pdu, + Varbinds2 = fix_vbs_BITS(Varbinds), + SnmpReply = {EStatus, EIndex, Varbinds2}, + Reply = {ok, SnmpReply, Remaining}, + ?vtrace("handle_snmp_pdu(get-response) -> deliver reply",[]), + gen_server:reply(From, Reply), + ets:delete(snmpm_request_table, ReqId), + ok; + + + %% A very old reply, see if this agent is handled by + %% a user. In that case send it there, else to the + %% default user. + _ -> + + ?vdebug("handle_snmp_pdu(get-response) -> " + "no corresponding request: " + "~n a very old reply", []), + + #pdu{error_status = EStatus, + error_index = EIndex, + varbinds = Varbinds} = Pdu, + Varbinds2 = fix_vbs_BITS(Varbinds), + SnmpInfo = {EStatus, EIndex, Varbinds2}, + case snmpm_config:get_agent_user_id(Addr, Port) of + {ok, UserId} -> + %% A very late reply or a reply to a request + %% that has been cancelled. + %% + ?vtrace("handle_snmp_pdu(get-response) -> " + "a very late reply:" + "~n UserId: ~p",[UserId]), + case snmpm_config:user_info(UserId) of + {ok, UserMod, UserData} -> + Reason = {unexpected_pdu, SnmpInfo}, + handle_error(UserId, UserMod, Reason, ReqId, + UserData, State); + _Error -> + %% Ouch, found an agent but not it's user!! + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + Reason = {unexpected_pdu, SnmpInfo}, + handle_error(DefUserId, DefMod, Reason, + ReqId, DefData, State); + Error -> + error_msg("failed retreiving the default " + "user info handling (old) " + "pdu from " + "<~p,~p>: ~n~w~n~w", + [Addr, Port, Error, Pdu]) + end + end; + + {error, _} -> + %% No agent, so either this agent has been + %% unregistered, or this is a very late reply + %% to a request (possibly a discovery), which + %% has since been cancelled (either because of + %% a timeout or that the user has unregistered + %% itself (and with it all it's requests)). + %% No way to know which. + %% + ?vtrace("handle_snmp_pdu(get-response) -> " + "no agent info found", []), + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_agent(DefUserId, DefMod, + Addr, Port, + pdu, ignore, + SnmpInfo, DefData, State); + Error -> + error_msg("failed retreiving the default user " + "info handling (old) pdu when no user " + "found from " + "<~p,~p>: ~n~w~n~w", + [Addr, Port, Error, Pdu]) + end + end + end; + +handle_snmp_pdu(CrapPdu, Addr, Port, _State) -> + error_msg("received crap (snmp) Pdu from ~w:~w =>" + "~p", [Addr, Port, CrapPdu]), + ok. + + +handle_pdu(_UserId, Mod, target_name = _RegType, TargetName, _Addr, _Port, + ReqId, SnmpResponse, Data, _State) -> + ?vtrace("handle_pdu(target_name) -> entry when" + "~n Mod: ~p", [Mod]), + F = fun() -> + (catch Mod:handle_pdu(TargetName, ReqId, SnmpResponse, Data)) + end, + handle_callback(F), + ok; +handle_pdu(_UserId, Mod, addr_port = _RegType, _TargetName, Addr, Port, + ReqId, SnmpResponse, Data, _State) -> + ?vtrace("handle_pdu(addr_port) -> entry when" + "~n Mod: ~p", [Mod]), + F = fun() -> + (catch Mod:handle_pdu(Addr, Port, ReqId, SnmpResponse, Data)) + end, + handle_callback(F), + ok. + + +handle_agent(UserId, Mod, Addr, Port, Type, Ref, SnmpInfo, Data, State) -> + ?vtrace("handle_agent -> entry when" + "~n UserId: ~p" + "~n Type: ~p" + "~n Mod: ~p", [UserId, Type, Mod]), + F = fun() -> + do_handle_agent(UserId, Mod, Addr, Port, + Type, Ref, SnmpInfo, Data, State) + end, + handle_callback(F), + ok. + +do_handle_agent(DefUserId, DefMod, + Addr, Port, + Type, Ref, + SnmpInfo, DefData, State) -> + ?vdebug("do_handle_agent -> entry when" + "~n DefUserId: ~p", [DefUserId]), + case (catch DefMod:handle_agent(Addr, Port, Type, SnmpInfo, DefData)) of + {'EXIT', {undef, _}} when Type =:= pdu -> + %% Maybe, still on the old API + ?vdebug("do_handle_agent -> maybe still on the old api", []), + case (catch DefMod:handle_agent(Addr, Port, SnmpInfo, DefData)) of + {register, UserId2, Config} -> + ?vtrace("do_handle_agent -> register: " + "~n UserId2: ~p" + "~n Config: ~p", [UserId2, Config]), + TargetName = mk_target_name(Addr, Port, Config), + Config2 = [{reg_type, addr_port}, + {address, Addr}, + {port, Port} | Config], + case snmpm_config:register_agent(UserId2, + TargetName, Config2) of + ok -> + ok; + {error, Reason} -> + error_msg("failed registering agent - " + "handling agent " + "~p <~p,~p>: ~n~w", + [TargetName, Addr, Port, Reason]), + ok + end; + {register, UserId2, TargetName, Config} -> + ?vtrace("do_handle_agent -> register: " + "~n UserId2: ~p" + "~n TargetName: ~p" + "~n Config: ~p", + [UserId2, TargetName, Config]), + Config2 = ensure_present([{address, Addr}, {port, Port}], + Config), + Config3 = [{reg_type, target_name} | Config2], + case snmpm_config:register_agent(UserId2, + TargetName, Config3) of + ok -> + ok; + {error, Reason} -> + error_msg("failed registering agent - " + "handling agent " + "~p <~p,~p>: ~n~w", + [TargetName, Addr, Port, Reason]), + ok + end; + _Ignore -> + ?vdebug("do_handle_agent -> ignore", []), + ok + end; + + {'EXIT', {undef, _}} -> + %% If the user does not implement the new API (but the + %% old), then this clause catches all non-pdu handle_agent + %% calls. These calls was previously never made,so we make + %% a best-effert call (using reg-type target_name) to the + %% various callback functions, and leave it to the user to + %% figure out + + %% Backward compatibillity crap + RegType = target_name, + Target = mk_target_name(Addr, Port, default_agent_config()), + case Type of + report -> + SnmpInform = SnmpInfo, + handle_report(DefUserId, DefMod, + RegType, Target, Addr, Port, + SnmpInform, DefData, State); + + inform -> + SnmpInform = SnmpInfo, + handle_inform(DefUserId, DefMod, Ref, + RegType, Target, Addr, Port, + SnmpInform, DefData, State); + + trap -> + SnmpTrapInfo = SnmpInfo, + handle_trap(DefUserId, DefMod, + RegType, Target, Addr, Port, + SnmpTrapInfo, DefData, State); + + _ -> + error_msg("failed delivering ~w info to default user - " + "regarding agent " + "<~p,~p>: ~n~w", [Type, Addr, Port, SnmpInfo]) + end; + + {register, UserId2, TargetName, Config} -> + ?vtrace("do_handle_agent -> register: " + "~n UserId2: ~p" + "~n TargetName: ~p" + "~n Config: ~p", + [UserId2, TargetName, Config]), + Config2 = ensure_present([{address, Addr}, {port, Port}], Config), + Config3 = [{reg_type, target_name} | Config2], + case snmpm_config:register_agent(UserId2, + TargetName, Config3) of + ok -> + ok; + {error, Reason} -> + error_msg("failed registering agent - " + "handling agent " + "~p <~p,~p>: ~n~w", + [TargetName, Addr, Port, Reason]), + ok + end; + + _Ignore -> + ?vdebug("do_handle_agent -> ignore", []), + ok + + end. + +ensure_present([], Config) -> + Config; +ensure_present([{Key, _Val} = Elem|Ensure], Config) -> + case lists:keymember(Key, 1, Config) of + false -> + ensure_present(Ensure, [Elem|Config]); + true -> + ensure_present(Ensure, Config) + end. + + +%% Retrieve user info for this agent. +%% If this is an unknown agent, then use the default user +handle_snmp_trap(#trappdu{enterprise = Enteprise, + generic_trap = Generic, + specific_trap = Spec, + time_stamp = Timestamp, + varbinds = Varbinds} = Trap, + Addr, Port, State) -> + + ?vtrace("handle_snmp_trap [trappdu] -> entry with" + "~n Addr: ~p" + "~n Port: ~p" + "~n Trap: ~p", [Addr, Port, Trap]), + + Varbinds2 = fix_vbs_BITS(Varbinds), + SnmpTrapInfo = {Enteprise, Generic, Spec, Timestamp, Varbinds2}, + do_handle_snmp_trap(SnmpTrapInfo, Addr, Port, State); + +handle_snmp_trap(#pdu{error_status = EStatus, + error_index = EIndex, + varbinds = Varbinds} = Trap, + Addr, Port, State) -> + + ?vtrace("handle_snmp_trap [pdu] -> entry with" + "~n Addr: ~p" + "~n Port: ~p" + "~n Trap: ~p", [Addr, Port, Trap]), + + Varbinds2 = fix_vbs_BITS(Varbinds), + SnmpTrapInfo = {EStatus, EIndex, Varbinds2}, + do_handle_snmp_trap(SnmpTrapInfo, Addr, Port, State); + +handle_snmp_trap(CrapTrap, Addr, Port, _State) -> + error_msg("received crap (snmp) trap from ~w:~w =>" + "~p", [Addr, Port, CrapTrap]), + ok. + +do_handle_snmp_trap(SnmpTrapInfo, Addr, Port, State) -> + case snmpm_config:get_agent_user_info(Addr, Port) of + {ok, UserId, Target, RegType} -> + ?vtrace("handle_snmp_trap -> found user: ~p", [UserId]), + case snmpm_config:user_info(UserId) of + {ok, Mod, Data} -> + handle_trap(UserId, Mod, + RegType, Target, Addr, Port, + SnmpTrapInfo, Data, State); + + Error1 -> + %% User no longer exists, unregister agent + ?vlog("[trap] failed retreiving user info for " + "user ~p: " + "~n ~p", [UserId, Error1]), + case snmpm_config:unregister_agent(UserId, Target) of + ok -> + %% Try use the default user + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_agent(DefUserId, DefMod, + Addr, Port, + trap, ignore, + SnmpTrapInfo, DefData, State); + Error2 -> + error_msg("failed retreiving the default " + "user info handling report from " + "~p <~p,~p>: ~n~w~n~w", + [Target, Addr, Port, + Error2, SnmpTrapInfo]) + end; + Error3 -> + %% Failed unregister agent, + %% now its getting messy... + warning_msg("failed unregister agent ~p <~p,~p> " + "belonging to non-existing " + "user ~p, handling trap: " + "~n Error: ~w" + "~n Trap info: ~w", + [Target, Addr, Port, UserId, + Error3, SnmpTrapInfo]) + end + end; + + Error4 -> + %% Unknown agent, pass it on to the default user + ?vlog("[trap] failed retreiving user id for agent <~p,~p>: " + "~n ~p", [Addr, Port, Error4]), + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_agent(DefUserId, DefMod, + Addr, Port, + trap, ignore, + SnmpTrapInfo, DefData, State); + Error5 -> + error_msg("failed retreiving " + "the default user info handling trap from " + "<~p,~p>: ~n~w~n~w", + [Addr, Port, Error5, SnmpTrapInfo]) + end + end, + ok. + + +handle_trap(UserId, Mod, + RegType, Target, Addr, Port, SnmpTrapInfo, Data, State) -> + ?vtrace("handle_trap -> entry with" + "~n UserId: ~p" + "~n Mod: ~p", [UserId, Mod]), + F = fun() -> + do_handle_trap(UserId, Mod, + RegType, Target, Addr, Port, + SnmpTrapInfo, Data, State) + end, + handle_callback(F), + ok. + + +do_handle_trap(UserId, Mod, + RegType, Target, Addr, Port, SnmpTrapInfo, Data, _State) -> + ?vdebug("do_handle_trap -> entry with" + "~n UserId: ~p", [UserId]), + HandleTrap = + case RegType of + target_name -> + fun() -> Mod:handle_trap(Target, SnmpTrapInfo, Data) end; + addr_port -> + fun() -> Mod:handle_trap(Addr, Port, SnmpTrapInfo, Data) end + end, + + case (catch HandleTrap()) of + {register, UserId2, Config} -> + ?vtrace("do_handle_trap -> register: " + "~n UserId2: ~p" + "~n Config: ~p", [UserId2, Config]), + Target2 = mk_target_name(Addr, Port, Config), + Config2 = [{reg_type, target_name}, + {address, Addr}, {port, Port} | Config], + case snmpm_config:register_agent(UserId2, Target2, Config2) of + ok -> + ok; + {error, Reason} -> + error_msg("failed registering agent " + "handling trap " + "<~p,~p>: ~n~w", + [Addr, Port, Reason]), + ok + end; + {register, UserId2, Target2, Config} -> + ?vtrace("do_handle_trap -> register: " + "~n UserId2: ~p" + "~n Target2: ~p" + "~n Config: ~p", [UserId2, Target2, Config]), + %% The only user which would do this is the + %% default user + Config2 = [{reg_type, target_name} | Config], + case snmpm_config:register_agent(UserId2, Target2, Config2) of + ok -> + reply; + {error, Reason} -> + error_msg("failed registering agent " + "handling trap " + "~p <~p,~p>: ~n~w", + [Target2, Addr, Port, Reason]), + reply + end; + unregister -> + ?vtrace("do_handle_trap -> unregister", []), + case snmpm_config:unregister_agent(UserId, + Addr, Port) of + ok -> + ok; + {error, Reason} -> + error_msg("failed unregistering agent " + "handling trap " + "<~p,~p>: ~n~w", + [Addr, Port, Reason]), + ok + end; + _Ignore -> + ?vtrace("do_handle_trap -> ignore", []), + ok + end. + + +handle_snmp_inform(Ref, + #pdu{error_status = EStatus, + error_index = EIndex, + varbinds = Varbinds} = Pdu, Addr, Port, State) -> + + ?vtrace("handle_snmp_inform -> entry with" + "~n Addr: ~p" + "~n Port: ~p" + "~n Pdu: ~p", [Addr, Port, Pdu]), + + Varbinds2 = fix_vbs_BITS(Varbinds), + SnmpInform = {EStatus, EIndex, Varbinds2}, + case snmpm_config:get_agent_user_info(Addr, Port) of + {ok, UserId, Target, RegType} -> + case snmpm_config:user_info(UserId) of + {ok, Mod, Data} -> + ?vdebug("[inform] callback handle_inform with: " + "~n UserId: ~p" + "~n Mod: ~p", [UserId, Mod]), + handle_inform(UserId, Mod, Ref, + RegType, Target, Addr, Port, + SnmpInform, Data, State); + Error1 -> + %% User no longer exists, unregister agent + case snmpm_config:unregister_agent(UserId, Target) of + ok -> + %% Try use the default user + ?vlog("[inform] failed retreiving user " + "info for user ~p:" + "~n ~p", [UserId, Error1]), + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_agent(DefUserId, DefMod, + Addr, Port, + inform, Ref, + SnmpInform, DefData, State); + Error2 -> + error_msg("failed retreiving the default " + "user info handling inform from " + "~p <~p,~p>: ~n~w~n~w", + [Target, Addr, Port, + Error2, Pdu]) + end; + Error3 -> + %% Failed unregister agent, + %% now its getting messy... + warning_msg("failed unregister agent ~p <~p,~p> " + "~n belonging to non-existing " + "user ~p, handling inform: " + "~n Error: ~w" + "~n Pdu: ~w", + [Target, Addr, Port, UserId, + Error3, Pdu]) + end + end; + + Error4 -> + %% Unknown agent, pass it on to the default user + ?vlog("[inform] failed retreiving user id for agent <~p,~p>: " + "~n ~p", [Addr, Port, Error4]), + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_agent(DefUserId, DefMod, + Addr, Port, + inform, Ref, + SnmpInform, DefData, State); + Error5 -> + error_msg("failed retreiving " + "the default user info handling inform from " + "<~p,~p>: ~n~w~n~w", + [Addr, Port, Error5, Pdu]) + end + end, + ok; + +handle_snmp_inform(_Ref, CrapInform, Addr, Port, _State) -> + error_msg("received crap (snmp) inform from ~w:~w =>" + "~p", [Addr, Port, CrapInform]), + ok. + +handle_inform(UserId, Mod, Ref, + RegType, Target, Addr, Port, SnmpInform, Data, State) -> + ?vtrace("handle_inform -> entry with" + "~n UserId: ~p" + "~n Mod: ~p", [UserId, Mod]), + F = fun() -> + do_handle_inform(UserId, Mod, Ref, + RegType, Target, Addr, Port, SnmpInform, + Data, State) + end, + handle_callback(F), + ok. + +do_handle_inform(UserId, Mod, Ref, + RegType, Target, Addr, Port, SnmpInform, Data, State) -> + ?vdebug("do_handle_inform -> entry with" + "~n UserId: ~p", [UserId]), + HandleInform = + case RegType of + target_name -> + fun() -> Mod:handle_inform(Target, SnmpInform, Data) end; + addr_port -> + fun() -> Mod:handle_inform(Addr, Port, SnmpInform, Data) end + end, + + Rep = + case (catch HandleInform()) of + {register, UserId2, Config} -> + ?vtrace("do_handle_inform -> register: " + "~n UserId2: ~p" + "~n Config: ~p", [UserId2, Config]), + %% The only user which would do this is the + %% default user + Target2 = mk_target_name(Addr, Port, Config), + Config2 = [{reg_type, target_name}, + {address, Addr}, {port, Port} | Config], + case snmpm_config:register_agent(UserId2, Target2, Config2) of + ok -> + reply; + {error, Reason} -> + error_msg("failed registering agent " + "handling inform " + "~p <~p,~p>: ~n~w", + [Target2, Addr, Port, Reason]), + reply + end; + {register, UserId2, Target2, Config} -> + ?vtrace("do_handle_inform -> register: " + "~n UserId2: ~p" + "~n Target2: ~p" + "~n Config: ~p", [UserId2, Target2, Config]), + %% The only user which would do this is the + %% default user + Config2 = [{reg_type, target_name} | Config], + case snmpm_config:register_agent(UserId2, Target2, Config2) of + ok -> + reply; + {error, Reason} -> + error_msg("failed registering agent " + "handling inform " + "~p <~p,~p>: ~n~w", + [Target2, Addr, Port, Reason]), + reply + end; + unregister -> + ?vtrace("do_handle_inform -> unregister", []), + case snmpm_config:unregister_agent(UserId, + Addr, Port) of + ok -> + reply; + {error, Reason} -> + error_msg("failed unregistering agent " + "handling inform " + "<~p,~p>: ~n~w", + [Addr, Port, Reason]), + reply + end; + no_reply -> + ?vtrace("do_handle_inform -> no_reply", []), + no_reply; + _Ignore -> + ?vtrace("do_handle_inform -> ignore", []), + reply + end, + handle_inform_response(Rep, Ref, Addr, Port, State), + ok. + + +handle_inform_response(_, ignore, _Addr, _Port, _State) -> + ignore; +handle_inform_response(no_reply, _Ref, _Addr, _Port, _State) -> + no_reply; +handle_inform_response(_, Ref, Addr, Port, + #state{net_if = Pid, net_if_mod = Mod}) -> + ?vdebug("handle_inform -> response", []), + (catch Mod:inform_response(Pid, Ref, Addr, Port)). + +handle_snmp_report(#pdu{error_status = EStatus, + error_index = EIndex, + varbinds = Varbinds} = Pdu, Addr, Port, State) -> + + ?vtrace("handle_snmp_report -> entry with" + "~n Addr: ~p" + "~n Port: ~p" + "~n Pdu: ~p", [Addr, Port, Pdu]), + + Varbinds2 = fix_vbs_BITS(Varbinds), + SnmpReport = {EStatus, EIndex, Varbinds2}, + case snmpm_config:get_agent_user_info(Addr, Port) of + {ok, UserId, Target, RegType} -> + case snmpm_config:user_info(UserId) of + {ok, Mod, Data} -> + ?vdebug("[report] callback handle_report with: " + "~n ~p" + "~n ~p" + "~n ~p" + "~n ~p", [UserId, Mod, Target, SnmpReport]), + handle_report(UserId, Mod, + RegType, Target, Addr, Port, + SnmpReport, Data, State); + Error1 -> + %% User no longer exists, unregister agent + ?vlog("[report] failed retreiving user info " + "for user ~p:" + " ~n ~p", [UserId, Error1]), + case snmpm_config:unregister_agent(UserId, Target) of + ok -> + %% Try use the default user + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_agent(DefUserId, DefMod, + Addr, Port, + report, ignore, + SnmpReport, DefData, State); + + Error2 -> + error_msg("failed retreiving the default " + "user info handling report from " + "~p <~p,~p>: ~n~w~n~w", + [Target, Addr, Port, + Error2, Pdu]) + end; + Error3 -> + %% Failed unregister agent, + %% now its getting messy... + warning_msg("failed unregister agent ~p <~p,~p> " + "belonging to non-existing " + "user ~p, handling report: " + "~n Error: ~w" + "~n Report: ~w", + [Target, Addr, Port, UserId, + Error3, Pdu]) + end + end; + + Error4 -> + %% Unknown agent, pass it on to the default user + ?vlog("[report] failed retreiving user id for agent <~p,~p>: " + "~n ~p", [Addr, Port, Error4]), + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_agent(DefUserId, DefMod, + Addr, Port, + report, ignore, + SnmpReport, DefData, State); + Error5 -> + error_msg("failed retreiving " + "the default user info handling report from " + "<~p,~p>: ~n~w~n~w", + [Addr, Port, Error5, Pdu]) + end + end, + ok; + +handle_snmp_report(CrapReport, Addr, Port, _State) -> + error_msg("received crap (snmp) report from ~w:~w =>" + "~p", [Addr, Port, CrapReport]), + ok. + +%% This could be from a failed get-request, so we might have a user +%% waiting for a reply here. If there is, we handle this as an failed +%% get-response (except for tha data which is different). Otherwise, +%% we handle it as an error (reported via the handle_error callback +%% function). +handle_snmp_report(ReqId, + #pdu{error_status = EStatus, + error_index = EIndex, + varbinds = Varbinds} = Pdu, + {ReportReason, Info} = Rep, + Addr, Port, State) + when is_integer(ReqId) -> + + ?vtrace("handle_snmp_report -> entry with" + "~n Addr: ~p" + "~n Port: ~p" + "~n ReqId: ~p" + "~n Rep: ~p" + "~n Pdu: ~p", [Addr, Port, ReqId, Rep, Pdu]), + + Varbinds2 = fix_vbs_BITS(Varbinds), + SnmpReport = {EStatus, EIndex, Varbinds2}, + Reason = {ReportReason, Info, SnmpReport}, + + %% Check if there is someone waiting for this request + + case ets:lookup(snmpm_request_table, ReqId) of + + [#request{from = From, + ref = Ref, + mon = MonRef}] when (From =/= undefined) andalso + (Ref =/= undefined) -> + + ?vdebug("handle_snmp_report -> " + "found corresponding request: " + "~n reply to sync request" + "~n Ref: ~p" + "~n ModRef: ~p" + "~n From: ~p", [Ref, MonRef, From]), + + Remaining = + case (catch cancel_timer(Ref)) of + Rem when is_integer(Rem) -> + Rem; + _ -> + 0 + end, + + ?vtrace("handle_snmp_pdu(get-response) -> Remaining: ~p", + [Remaining]), + + maybe_demonitor(MonRef), + + Reply = {error, Reason}, + ?vtrace("handle_snmp_report -> deliver reply",[]), + gen_server:reply(From, Reply), + ets:delete(snmpm_request_table, ReqId), + ok; + + _ -> + %% Either not a sync request or no such request. Either + %% way, this is error info, so handle it as such. + + case snmpm_config:get_agent_user_id(Addr, Port) of + {ok, UserId} -> + case snmpm_config:user_info(UserId) of + {ok, Mod, Data} -> + ?vdebug("[report] callback handle_error with: " + "~n ~p" + "~n ~p" + "~n ~p", [UserId, Mod, Reason]), + handle_error(UserId, Mod, Reason, ReqId, + Data, State); + Error -> + %% Oh crap, use the default user + ?vlog("[report] failed retreiving user info for " + "user ~p:" + " ~n ~p", [UserId, Error]), + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_error(DefUserId, DefMod, Reason, + ReqId, DefData, State); + Error -> + error_msg("failed retreiving the " + "default user " + "info handling report from " + "<~p,~p>: ~n~w~n~w~n~w", + [Addr, Port, Error, + ReqId, Reason]) + end + end; + Error -> + %% Unknown agent, pass it on to the default user + ?vlog("[report] failed retreiving user id for " + "agent <~p,~p>: " + "~n ~p", [Addr, Port, Error]), + case snmpm_config:user_info() of + {ok, DefUserId, DefMod, DefData} -> + handle_error(DefUserId, DefMod, Reason, ReqId, + DefData, State); + Error -> + error_msg("failed retreiving " + "the default user info handling " + "report from " + "<~p,~p>: ~n~w~n~w~n~w", + [Addr, Port, Error, ReqId, Reason]) + end + end + end, + ok; + +handle_snmp_report(CrapReqId, CrapReport, CrapInfo, Addr, Port, _State) -> + error_msg("received crap (snmp) report from ~w:~w =>" + "~n~p~n~p~n~p", [Addr, Port, CrapReqId, CrapReport, CrapInfo]), + ok. + + +handle_report(UserId, Mod, RegType, Target, Addr, Port, + SnmpReport, Data, State) -> + ?vtrace("handle_report -> entry with" + "~n UserId: ~p" + "~n Mod: ~p", [UserId, Mod]), + F = fun() -> + do_handle_report(UserId, Mod, RegType, Target, Addr, Port, + SnmpReport, Data, State) + end, + handle_callback(F), + ok. + +do_handle_report(UserId, Mod, + RegType, Target, Addr, Port, SnmpReport, Data, _State) -> + ?vdebug("do_handle_report -> entry with" + "~n UserId: ~p", [UserId]), + HandleReport = + case RegType of + target_name -> + fun() -> Mod:handle_report(Target, SnmpReport, Data) end; + addr_port -> + fun() -> Mod:handle_report(Addr, Port, SnmpReport, Data) end + end, + + case (catch HandleReport()) of + {register, UserId2, Config} -> + ?vtrace("do_handle_report -> register: " + "~n UserId2: ~p" + "~n Config: ~p", [UserId2, Config]), + %% The only user which would do this is the + %% default user + Target2 = mk_target_name(Addr, Port, Config), + Config2 = [{reg_type, target_name}, + {address, Addr}, {port, Port} | Config], + case snmpm_config:register_agent(UserId2, Target2, Config2) of + ok -> + ok; + {error, Reason} -> + error_msg("failed registering agent " + "handling report " + "<~p,~p>: ~n~w", + [Addr, Port, Reason]), + ok + end; + {register, UserId2, Target2, Config} -> + ?vtrace("do_handle_report -> register: " + "~n UserId2: ~p" + "~n Target2: ~p" + "~n Config: ~p", [UserId2, Target2, Config]), + %% The only user which would do this is the + %% default user + Config2 = [{reg_type, target_name} | Config], + case snmpm_config:register_agent(UserId2, Target2, Config2) of + ok -> + reply; + {error, Reason} -> + error_msg("failed registering agent " + "handling report " + "~p <~p,~p>: ~n~w", + [Target2, Addr, Port, Reason]), + reply + end; + unregister -> + ?vtrace("do_handle_trap -> unregister", []), + case snmpm_config:unregister_agent(UserId, + Addr, Port) of + ok -> + ok; + {error, Reason} -> + error_msg("failed unregistering agent " + "handling report " + "<~p,~p>: ~n~w", + [Addr, Port, Reason]), + ok + end; + _Ignore -> + ?vtrace("do_handle_report -> ignore", []), + ok + end. + + +handle_callback(F) -> + V = get(verbosity), + erlang:spawn( + fun() -> + put(sname, msew), + put(verbosity, V), + F() + end). + + +handle_down(MonRef) -> + (catch do_handle_down(MonRef)). + +do_handle_down(MonRef) -> + %% Clear out all requests from this client + handle_down_requests_cleanup(MonRef), + + %% + %% Check also if this was a monitored user, and if so + %% unregister all agents registered by this user, and + %% finally unregister the user itself + %% + handle_down_user_cleanup(MonRef), + + ok. + + +handle_down_requests_cleanup(MonRef) -> + Pat = #request{id = '$1', ref = '$2', mon = MonRef, _ = '_'}, + Match = ets:match(snmpm_request_table, Pat), + Fun = fun([Id, Ref]) -> + ?vtrace("delete request: ~p", [Id]), + ets:delete(snmpm_request_table, Id), + cancel_timer(Ref), + ok + end, + lists:foreach(Fun, Match). + +handle_down_user_cleanup(MonRef) -> + Pat = #monitor{id = '$1', mon = MonRef, _ = '_'}, + Match = ets:match(snmpm_monitor_table, Pat), + Fun = fun([Id]) -> + Agents = snmpm_config:which_agents(Id), + lists:foreach( + fun({Addr,Port}) -> + %% ** Previous format ** + %% Just in case this happens during code upgrade + ?vtrace("unregister agent of monitored user " + "~w: <~w,~w>", [Id,Addr,Port]), + snmpm_config:unregister_agent(Id, Addr, Port); + (TargetName) -> + ?vtrace("unregister agent of monitored user " + "~w: ~p", [Id,TargetName]), + snmpm_config:unregister_agent(Id, TargetName) + end, + Agents), + ?vtrace("unregister monitored user: ~w", [Id]), + ets:delete(snmpm_monitor_table, Id), + snmpm_config:unregister_user(Id), + ok + end, + lists:foreach(Fun, Match). + +cancel_timer(undefined) -> + ok; +cancel_timer(Ref) -> + (catch erlang:cancel_timer(Ref)). + +handle_gc(GCT) -> + ets:safe_fixtable(snmpm_request_table, true), + case do_gc(ets:first(snmpm_request_table), t()) of + 0 -> + gct_deactivate(GCT); + _ -> + ok + end, + ets:safe_fixtable(snmpm_request_table, false). + + + +%% We are deleting at the same time as we are traversing the table!!! +do_gc('$end_of_table', _) -> + ets:info(snmpm_request_table, size); +do_gc(Key, Now) -> + Next = ets:next(snmpm_request_table, Key), + case ets:lookup(snmpm_request_table, Key) of + [#request{expire = BestBefore}] when (BestBefore < Now) -> + ets:delete(snmpm_request_table, Key); + _ -> + ok + end, + do_gc(Next, Now). + + + +%%---------------------------------------------------------------------- +%% +%%---------------------------------------------------------------------- + +send_get_request(Oids, Vsn, MsgData, Addr, Port, ExtraInfo, + #state{net_if = NetIf, + net_if_mod = Mod, + mini_mib = MiniMIB}) -> + Pdu = make_pdu(get, Oids, MiniMIB), + ?vtrace("send_get_request -> send get-request:" + "~n Mod: ~p" + "~n NetIf: ~p" + "~n Pdu: ~p" + "~n Vsn: ~p" + "~n MsgData: ~p" + "~n Addr: ~p" + "~n Port: ~p", [Mod, NetIf, Pdu, Vsn, MsgData, Addr, Port]), + (catch Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo)), + Pdu#pdu.request_id. + +send_get_next_request(Oids, Vsn, MsgData, Addr, Port, ExtraInfo, + #state{mini_mib = MiniMIB, + net_if = NetIf, + net_if_mod = Mod}) -> + Pdu = make_pdu(get_next, Oids, MiniMIB), + Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo), + Pdu#pdu.request_id. + +send_get_bulk_request(Oids, Vsn, MsgData, Addr, Port, + NonRep, MaxRep, ExtraInfo, + #state{mini_mib = MiniMIB, + net_if = NetIf, + net_if_mod = Mod}) -> + Pdu = make_pdu(bulk, {NonRep, MaxRep, Oids}, MiniMIB), + Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo), + Pdu#pdu.request_id. + +send_set_request(VarsAndVals, Vsn, MsgData, Addr, Port, ExtraInfo, + #state{mini_mib = MiniMIB, + net_if = NetIf, + net_if_mod = Mod}) -> + Pdu = make_pdu(set, VarsAndVals, MiniMIB), + Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo), + Pdu#pdu.request_id. + +%% send_discovery(Vsn, MsgData, Addr, Port, ExtraInfo, +%% #state{net_if = NetIf, +%% net_if_mod = Mod}) -> +%% Pdu = make_discovery_pdu(), +%% Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo), +%% Pdu#pdu.request_id. + + + +%%---------------------------------------------------------------------- +%% +%%---------------------------------------------------------------------- + +%% make_discovery_pdu() -> +%% Oids = [?sysObjectID_instance, ?sysDescr_instance, ?sysUpTime_instance], +%% make_pdu_impl(get, Oids). + +make_pdu(set, VarsAndVals, MiniMIB) -> + VBs = [var_and_value_to_varbind(VAV, MiniMIB) || VAV <- VarsAndVals], + make_pdu_impl(set, VBs); + +make_pdu(bulk, {NonRepeaters, MaxRepetitions, Oids}, MiniMIB) -> + Foids = [flatten_oid(Oid, MiniMIB) || Oid <- Oids], + #pdu{type = 'get-bulk-request', + request_id = request_id(), + error_status = NonRepeaters, + error_index = MaxRepetitions, + varbinds = [make_vb(Foid) || Foid <- Foids]}; + +make_pdu(Op, Oids, MiniMIB) -> + Foids = [flatten_oid(Oid, MiniMIB) || Oid <- Oids], + make_pdu_impl(Op, Foids). + + +make_pdu_impl(get, Oids) -> + #pdu{type = 'get-request', + request_id = request_id(), + error_status = noError, + error_index = 0, + varbinds = [make_vb(Oid) || Oid <- Oids]}; + +make_pdu_impl(get_next, Oids) -> + #pdu{type = 'get-next-request', + request_id = request_id(), + error_status = noError, + error_index = 0, + varbinds = [make_vb(Oid) || Oid <- Oids]}; + +make_pdu_impl(set, Varbinds) -> + #pdu{type = 'set-request', + request_id = request_id(), + error_status = noError, + error_index = 0, + varbinds = Varbinds}. + + +fix_vbs_BITS(Varbinds) -> + [fix_vb_BITS(Varbind) || Varbind <- Varbinds]. + +fix_vb_BITS(#varbind{oid = Oid, + variabletype = 'OCTET STRING' = _Type, + value = Value} = Varbind) -> + %% BITS are encoded as OCTET STRING, so this could be a BITS + %% check with the MiniMIB + case type_of_oid(Oid) of + {error, _} -> + Varbind; + {ok, NewType = 'BITS'} -> + NewValue = snmp_pdus:octet_str_to_bits(Value), + Varbind#varbind{variabletype = NewType, + value = NewValue}; + _ -> + Varbind + end; +fix_vb_BITS(Vb) -> + Vb. + +type_of_oid(Oid) -> + Oid2 = case lists:reverse(Oid) of + [0|T] -> + lists:reverse(T); + _ -> + Oid + end, + snmpm_config:oid_to_type(Oid2). + + +%%---------------------------------------------------------------------- +%% Purpose: Unnesting of oids like [myTable, 3, 4, "hej", 45] to +%% [1,2,3,3,4,104,101,106,45] +%%---------------------------------------------------------------------- + +flatten_oid([A|T], MiniMIB) when is_atom(A) -> + Oid = [alias2oid(A, MiniMIB)|T], + check_is_pure_oid(lists:flatten(Oid)); +flatten_oid(Oid, _) when is_list(Oid) -> + check_is_pure_oid(lists:flatten(Oid)); +flatten_oid(Shit, _) -> + throw({error, {invalid_oid, Shit}}). + +check_is_pure_oid([]) -> []; +check_is_pure_oid([X | T]) when is_integer(X) andalso (X >= 0) -> + [X | check_is_pure_oid(T)]; +check_is_pure_oid([X | _T]) -> + throw({error, {invalid_oid, X}}). + + +var_and_value_to_varbind({Oid, Type, Value}, MiniMIB) -> + Oid2 = flatten_oid(Oid, MiniMIB), + #varbind{oid = Oid2, + variabletype = char_to_type(Type), + value = Value}; +var_and_value_to_varbind({Oid, Value}, MiniMIB) -> + Oid2 = flatten_oid(Oid, MiniMIB), + #varbind{oid = Oid2, + variabletype = oid2type(Oid2, MiniMIB), + value = Value}. + +char_to_type(i) -> + 'INTEGER'; +char_to_type(u) -> + 'Unsigned32'; +char_to_type(g) -> % Gauge, Gauge32 + 'Unsigned32'; +char_to_type(b) -> + 'BITS'; +char_to_type(ip) -> + 'IpAddress'; +char_to_type(ia) -> + 'IpAddress'; +char_to_type(op) -> + 'Opaque'; +char_to_type(c32) -> + 'Counter32'; +char_to_type(c64) -> + 'Counter64'; +char_to_type(tt) -> + 'TimeTicks'; +char_to_type(o) -> + 'OBJECT IDENTIFIER'; +char_to_type(s) -> + 'OCTET STRING'; +char_to_type(C) -> + throw({error, {invalid_value_type, C}}). + + +alias2oid(AliasName, MiniMIB) when is_atom(AliasName) -> + case lists:keysearch(AliasName, 2, MiniMIB) of + {value, {Oid, _Aliasname, _Type}} -> + Oid; + false -> + throw({error, {unknown_aliasname, AliasName}}) + end. + +oid2type(Oid, MiniMIB) -> + Oid2 = case lists:reverse(Oid) of + [0|T] -> + lists:reverse(T); + _ -> + Oid + end, + oid2type(Oid2, MiniMIB, utter_nonsense). + +oid2type(_Oid, [], utter_nonsense) -> + throw({error, no_type}); +oid2type(_Oid, [], Type) -> + Type; +oid2type(Oid, [{Oid2, _, Type}|MiniMIB], Res) when (Oid2 =< Oid) -> + case lists:prefix(Oid2, Oid) of + true -> + oid2type(Oid, MiniMIB, Type); % A better guess + false -> + oid2type(Oid, MiniMIB, Res) + end; +oid2type(_Oid, _MiniMIB, utter_nonsense) -> + throw({error, no_type}); +oid2type(_Oid, _MiniMIB, Type) -> + Type. + + +make_vb(Oid) -> + #varbind{oid = Oid, variabletype = 'NULL', value = 'NULL'}. + + +%%---------------------------------------------------------------------- + +request_id() -> + snmpm_mpd:next_req_id(). + + +%%---------------------------------------------------------------------- + +agent_data(TargetName, CtxName) -> + agent_data(TargetName, CtxName, []). + +agent_data(TargetName, CtxName, Config) -> + case snmpm_config:agent_info(TargetName, all) of + {ok, Info} -> + {value, {_, Version}} = lists:keysearch(version, 1, Info), + MsgData = + case Version of + v3 -> + DefSecModel = agent_data_item(sec_model, Info), + DefSecName = agent_data_item(sec_name, Info), + DefSecLevel = agent_data_item(sec_level, Info), + + EngineId = agent_data_item(engine_id, Info), + + SecModel = agent_data_item(sec_model, + Config, + DefSecModel), + SecName = agent_data_item(sec_name, + Config, + DefSecName), + SecLevel = agent_data_item(sec_level, + Config, + DefSecLevel), + + {SecModel, SecName, mk_sec_level_flag(SecLevel), + EngineId, CtxName, TargetName}; + _ -> + DefComm = agent_data_item(community, Info), + DefSecModel = agent_data_item(sec_model, Info), + + Comm = agent_data_item(community, + Config, + DefComm), + SecModel = agent_data_item(sec_model, + Config, + DefSecModel), + + {Comm, SecModel} + end, + Addr = agent_data_item(address, Info), + Port = agent_data_item(port, Info), + RegType = agent_data_item(reg_type, Info), + {ok, RegType, Addr, Port, version(Version), MsgData}; + Error -> + Error + end. + +agent_data_item(Item, Info) -> + {value, {_, Val}} = lists:keysearch(Item, 1, Info), + Val. + +agent_data_item(Item, Info, Default) -> + case lists:keysearch(Item, 1, Info) of + {value, {_, Val}} -> + Val; + false -> + Default + end. + + +version(v1) -> + 'version-1'; +version(v2) -> + 'version-2'; +version(v3) -> + 'version-3'. + + +%%----------------------------------------------------------------- +%% Convert the SecurityLevel into a flag value used by snmpm_mpd +%%----------------------------------------------------------------- +mk_sec_level_flag(?'SnmpSecurityLevel_noAuthNoPriv') -> 0; +mk_sec_level_flag(?'SnmpSecurityLevel_authNoPriv') -> 1; +mk_sec_level_flag(?'SnmpSecurityLevel_authPriv') -> 3. + + +%%---------------------------------------------------------------------- +%% Request Garbage Collector timer +%%---------------------------------------------------------------------- + +gct_start(Timeout) -> + ?vdebug("start gc timer process (~p)", [Timeout]), + State = #gct{parent = self(), timeout = Timeout}, + proc_lib:start_link(?MODULE, gct_init, [State]). + +gct_stop(GCT) -> + GCT ! {stop, self()}. + +gct_activate(GCT) -> + GCT ! {activate, self()}. + +gct_deactivate(GCT) -> + GCT ! {deactivate, self()}. + +gct_code_change(GCT) -> + GCT ! {code_change, self()}. + +gct_init(#gct{parent = Parent, timeout = Timeout} = State) -> + proc_lib:init_ack(Parent, {ok, self()}), + gct(State, Timeout). + +gct(#gct{parent = Parent, state = active} = State, Timeout) -> + T = t(), + receive + {stop, Parent} -> + ok; + + %% This happens when a new request is received. + {activate, Parent} -> + ?MODULE:gct(State, new_timeout(Timeout, T)); + + {deactivate, Parent} -> + %% Timeout is of no consequence in the idle state, + %% but just to be sure + NewTimeout = State#gct.timeout, + ?MODULE:gct(State#gct{state = idle}, NewTimeout); + + {code_change, Parent} -> + %% Let the server take care of this + exit(normal); + + {'EXIT', Parent, _Reason} -> + ok; + + _ -> % Crap + ?MODULE:gct(State, Timeout) + + after Timeout -> + Parent ! gc_timeout, + NewTimeout = State#gct.timeout, + ?MODULE:gct(State, NewTimeout) + end; + +gct(#gct{parent = Parent, state = idle} = State, Timeout) -> + receive + {stop, Parent} -> + ok; + + {deactivate, Parent} -> + ?MODULE:gct(State, Timeout); + + {activate, Parent} -> + NewTimeout = State#gct.timeout, + ?MODULE:gct(State#gct{state = active}, NewTimeout); + + {code_change, Parent} -> + %% Let the server take care of this + exit(normal); + + {'EXIT', Parent, _Reason} -> + ok; + + _ -> % Crap + ?MODULE:gct(State, Timeout) + + after Timeout -> + ?MODULE:gct(State, Timeout) + end. + +new_timeout(T1, T2) -> + case T1 - (t() - T2) of + T when (T > 0) -> + T; + _ -> + 0 + end. + + +%%---------------------------------------------------------------------- + +maybe_delete(false, ReqId) -> + ets:delete(snmpm_request_table, ReqId); +maybe_delete(true, _) -> + ok. + +maybe_demonitor(undefined) -> + ok; +maybe_demonitor(MonRef) -> + erlang:demonitor(MonRef). + +%% Time in milli seconds +t() -> + {A,B,C} = erlang:now(), + A*1000000000+B*1000+(C div 1000). + +mk_target_name(Addr, Port, Config) -> + snmpm_config:mk_target_name(Addr, Port, Config). + +default_agent_config() -> + case snmpm_config:agent_info() of + {ok, Config} -> + Config; + _ -> + [] + end. + + +%%---------------------------------------------------------------------- + +is_started(#state{net_if = _Pid, net_if_mod = _Mod}) -> + %% Mod:is_started(Pid) and snmpm_config:is_started(). + case snmpm_config:is_started() of + true -> + true; + _ -> + false + end. + + +%%---------------------------------------------------------------------- + +call(Req) -> + call(Req, infinity). + +call(Req, To) -> + gen_server:call(?SERVER, Req, To). + +%% cast(Msg) -> +%% gen_server:cast(?SERVER, Msg). + +%% info_msg(F, A) -> +%% ?snmpm_info("Server: " ++ F, A). + +warning_msg(F, A) -> + ?snmpm_warning("Server: " ++ F, A). + +error_msg(F, A) -> + ?snmpm_error("Server: " ++ F, A). + + +%%---------------------------------------------------------------------- + +get_info(#state{gct = GCT, + net_if = NI, net_if_mod = NIMod, + note_store = NS}) -> + Info = [{server, server_info(GCT)}, + {config, config_info()}, + {net_if, net_if_info(NI, NIMod)}, + {note_store, note_store_info(NS)}, + {stats_counters, get_stats_counters()}], + Info. + +server_info(GCT) -> + ProcSize = proc_mem(self()), + GCTSz = proc_mem(GCT), + RTSz = tab_size(snmpm_request_table), + MTSz = tab_size(snmpm_monitor_table), + [{process_memory, [{server, ProcSize}, {gct, GCTSz}]}, + {db_memory, [{request, RTSz}, {monitor, MTSz}]}]. + +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(T) -> + case (catch ets:info(T, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + +config_info() -> + case (catch snmpm_config:info()) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +get_stats_counters() -> + lists:sort(snmpm_config:get_stats_counters()). + + +net_if_info(Pid, Mod) -> + case (catch Mod:info(Pid)) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +note_store_info(Pid) -> + case (catch snmp_note_store:info(Pid)) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + + +%%---------------------------------------------------------------------- + + +%%---------------------------------------------------------------------- +%% Debug +%%---------------------------------------------------------------------- + +% sz(L) when is_list(L) -> +% length(lists:flatten(L)); +% sz(B) when is_binary(B) -> +% size(B). + +%% p(F) -> +%% p(F, []). + +%% p(F, A) -> +%% io:format("~w:" ++ F ++ "~n", [?MODULE | A]). + diff --git a/lib/snmp/src/manager/snmpm_server_sup.erl b/lib/snmp/src/manager/snmpm_server_sup.erl new file mode 100644 index 0000000000..16238e4aaf --- /dev/null +++ b/lib/snmp/src/manager/snmpm_server_sup.erl @@ -0,0 +1,110 @@ +%% +%% %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(snmpm_server_sup). + +-behaviour(supervisor). + + +%% External exports +-export([start_link/2, stop/0]). + +%% supervisor callbacks +-export([init/1]). + + +-define(SERVER, ?MODULE). + +-include("snmp_debug.hrl"). + + +%%%------------------------------------------------------------------- +%%% API +%%%------------------------------------------------------------------- +start_link(_Type, Opts) -> + ?d("start_link -> entry with" + "~n Opts: ~p", [Opts]), + SupName = {local, ?MODULE}, + supervisor:start_link(SupName, ?MODULE, [Opts]). + +stop() -> + ?d("stop -> entry", []), + case whereis(?SERVER) of + Pid when is_pid(Pid) -> + ?d("stop -> Pid: ~p", [Pid]), + exit(Pid, shutdown), + ?d("stop -> stopped", []), + ok; + _ -> + ?d("stop -> not running", []), + not_running + end. + + +%%%------------------------------------------------------------------- +%%% Callback functions from supervisor +%%%------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, {SupFlags, [ChildSpec]}} | +%% ignore | +%% {error, Reason} +%%-------------------------------------------------------------------- +init([Opts]) -> + ?d("init -> entry with" + "~n Opts: ~p", [Opts]), + Restart = get_restart(Opts), + Flags = {one_for_all, 5, 500}, + Server = worker_spec(snmpm_server, [], Restart, [gen_server]), + Sups = [Server], + {ok, {Flags, Sups}}. + + +%%%------------------------------------------------------------------- +%%% Internal functions +%%%------------------------------------------------------------------- + +get_restart(Opts) -> + get_opt(Opts, restart_type, transient). + +get_opt(Opts, Key, Def) -> + snmp_misc:get_option(Key, Opts, Def). + +%% sup_spec(Name, Args, Restart) -> +%% ?d("sup_spec -> entry with" +%% "~n Name: ~p" +%% "~n Args: ~p" +%% "~n Restart: ~p", [Name, Args, Restart]), +%% {Name, +%% {Name, start_link, Args}, +%% Restart, 2000, supervisor, [Name,supervisor]}. + +worker_spec(Name, Args, Restart, Modules) -> + ?d("worker_spec -> entry with" + "~n Name: ~p" + "~n Args: ~p" + "~n Restart: ~p" + "~n Modules: ~p", [Name, Args, Restart, Modules]), + {Name, + {Name, start_link, Args}, + Restart, 2000, worker, [Name] ++ Modules}. + + + diff --git a/lib/snmp/src/manager/snmpm_supervisor.erl b/lib/snmp/src/manager/snmpm_supervisor.erl new file mode 100644 index 0000000000..8f43310c14 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_supervisor.erl @@ -0,0 +1,115 @@ +%% +%% %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(snmpm_supervisor). + +-behaviour(supervisor). + + +%% External exports +-export([start_link/2, stop/0]). + +%% supervisor callbacks +-export([init/1]). + + +-define(SERVER, ?MODULE). + +-include("snmp_debug.hrl"). + + +%%%------------------------------------------------------------------- +%%% API +%%%------------------------------------------------------------------- +start_link(Type, Opts) -> + ?d("start_link -> entry with" + "~n Opts: ~p", [Opts]), + SupName = {local, ?MODULE}, + supervisor:start_link(SupName, ?MODULE, [Type, Opts]). + +stop() -> + ?d("stop -> entry", []), + case whereis(?SERVER) of + Pid when is_pid(Pid) -> + ?d("stop -> Pid: ~p", [Pid]), + exit(Pid, shutdown), + ?d("stop -> stopped", []), + ok; + _ -> + ?d("stop -> not running", []), + not_running + end. + + +%%%------------------------------------------------------------------- +%%% Callback functions from supervisor +%%%------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, {SupFlags, [ChildSpec]}} | +%% ignore | +%% {error, Reason} +%%-------------------------------------------------------------------- +init([Opts]) when is_list(Opts) -> %% OTP-5963: Due to the addition + init([normal, Opts]); %% OTP-5963: of server_sup +init([Type, Opts]) -> + ?d("init -> entry with" + "~n Type: ~p" + "~n Opts: ~p", [Type, Opts]), + Restart = get_restart(Opts), + Flags = {one_for_all, 0, 3600}, + Config = worker_spec(snmpm_config, [Opts], Restart, [gen_server]), + MiscSup = sup_spec(snmpm_misc_sup, [], Restart), + ServerSup = sup_spec(snmpm_server_sup, [Type, Opts], Restart), + Sups = [Config, MiscSup, ServerSup], + {ok, {Flags, Sups}}. + + +%%%------------------------------------------------------------------- +%%% Internal functions +%%%------------------------------------------------------------------- + +get_restart(Opts) -> + get_opt(Opts, restart_type, transient). + +get_opt(Opts, Key, Def) -> + snmp_misc:get_option(Key, Opts, Def). + +sup_spec(Name, Args, Restart) -> + ?d("sup_spec -> entry with" + "~n Name: ~p" + "~n Args: ~p" + "~n Restart: ~p", [Name, Args, Restart]), + {Name, + {Name, start_link, Args}, + Restart, 2000, supervisor, [Name,supervisor]}. + +worker_spec(Name, Args, Restart, Modules) -> + ?d("worker_spec -> entry with" + "~n Name: ~p" + "~n Args: ~p" + "~n Restart: ~p" + "~n Modules: ~p", [Name, Args, Restart, Modules]), + {Name, + {Name, start_link, Args}, + Restart, 2000, worker, [Name] ++ Modules}. + + + diff --git a/lib/snmp/src/manager/snmpm_user.erl b/lib/snmp/src/manager/snmpm_user.erl new file mode 100644 index 0000000000..78aa560b2e --- /dev/null +++ b/lib/snmp/src/manager/snmpm_user.erl @@ -0,0 +1,97 @@ +%% +%% %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(snmpm_user). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{handle_error, 3}, + {handle_agent, 5}, + {handle_pdu, 4}, + {handle_trap, 3}, + {handle_inform, 3}, + {handle_report, 3}]; +behaviour_info(_) -> + undefined. + + +%% handle_error(ReqId, Reason, UserData) -> Reply +%% ReqId -> integer() +%% Reason -> term() +%% UserData -> term() (supplied when the user register) +%% Reply -> ignore + +%% handle_agent(Addr, Port, Type, SnmpInfo, UserData) -> Reply +%% Addr -> term() +%% Port -> integer() +%% Type -> pdu | trap | inform | report +%% SnmpInfo -> {ErrorStatus, ErrorIndex, Varbinds} +%% UserId -> term() +%% ErrorStatus -> atom() +%% ErrorIndex -> integer() +%% Varbinds -> [varbind()] +%% UserData -> term() (supplied when the user register) +%% Reply -> ignore | {register, UserId, agent_info()} +%% agent_info() -> [{agent_info_item(), agent_info_value()}] +%% This is the same info as in update_agent_info/4 + +%% handle_pdu(TargetName, ReqId, SnmpResponse, UserData) -> Reply +%% TargetName -> target_name() +%% ReqId -> term() (returned when calling ag(...), ...) +%% SnmpResponse -> {ErrorStatus, ErrorIndex, Varbinds} +%% ErrorStatus -> atom() +%% ErrorIndex -> integer() +%% Varbinds -> [varbind()] +%% UserData -> term() (supplied when the user register) +%% Reply -> ignore + +%% handle_trap(TargetName, SnmpTrapInfo, UserData) -> Reply +%% TargetName -> target_name() +%% SnmpTrapInfo -> {Enteprise, Generic, Spec, Timestamp, Varbinds} | +%% {ErrorStatus, ErrorIndex, Varbinds} +%% Enteprise -> oid() +%% Generic -> integer() +%% Spec -> integer() +%% Timestamp -> integer() +%% ErrorStatus -> atom() +%% ErrorIndex -> integer() +%% Varbinds -> [varbind()] +%% UserData -> term() (supplied when the user register) +%% Reply -> ignore | unregister | {register, UserId, agent_info()} + +%% handle_inform(TargetName, SnmpInform, UserData) -> Reply +%% TargetName -> target_name() +%% SnmpInform -> {ErrorStatus, ErrorIndex, Varbinds} +%% ErrorStatus -> atom() +%% ErrorIndex -> integer() +%% Varbinds -> [varbind()] +%% UserData -> term() (supplied when the user register) +%% Reply -> ignore | unregister | {register, UserId, agent_info()} +%% + +%% handle_report(TargetName, SnmpReport, UserData) -> Reply +%% TargetName -> target_name() +%% SnmpReport -> {ErrorStatus, ErrorIndex, Varbinds} +%% ErrorStatus -> integer() +%% ErrorIndex -> integer() +%% Varbinds -> [varbind()] +%% UserData -> term() (supplied when the user register) +%% Reply -> ignore | unregister | {register, UserId, agent_info()} + diff --git a/lib/snmp/src/manager/snmpm_user_default.erl b/lib/snmp/src/manager/snmpm_user_default.erl new file mode 100644 index 0000000000..d90fc3f258 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_user_default.erl @@ -0,0 +1,87 @@ +%% +%% %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(snmpm_user_default). + +-behaviour(snmpm_user). + +-export([handle_error/3, + handle_agent/5, + handle_pdu/4, + handle_trap/3, + handle_inform/3, + handle_report/3]). + +handle_error(ReqId, Reason, UserData) -> + info("received handle_error:" + "~n ReqId: ~p" + "~n Reason: ~p" + "~n UserData: ~p", [ReqId, Reason, UserData]), + ignore. + + +handle_agent(Addr, Port, Type, SnmpInfo, UserData) -> + info("received handle_agent:" + "~n Addr: ~p" + "~n Port: ~p" + "~n Type: ~p" + "~n SnmpInfo: ~p" + "~n UserData: ~p", [Addr, Port, Type, SnmpInfo, UserData]), + ignore. + + +handle_pdu(TargetName, ReqId, SnmpResponse, UserData) -> + info("received handle_pdu:" + "~n TargetName: ~p" + "~n ReqId: ~p" + "~n SnmpResponse: ~p" + "~n UserData: ~p", + [TargetName, ReqId, SnmpResponse, UserData]), + ignore. + + +handle_trap(TargetName, SnmpTrap, UserData) -> + info("received handle_trap:" + "~n TargetName: ~p" + "~n SnmpTrap: ~p" + "~n UserData: ~p", + [TargetName, SnmpTrap, UserData]), + ok. + + +handle_inform(TargetName, SnmpInform, UserData) -> + info("received handle_inform:" + "~n TargetName: ~p" + "~n SnmpInform: ~p" + "~n UserData: ~p", + [TargetName, SnmpInform, UserData]), + no_reply. + + +handle_report(TargetName, SnmpReport, UserData) -> + info("received handle_inform:" + "~n TargetName: ~p" + "~n SnmpReport: ~p" + "~n UserData: ~p", + [TargetName, SnmpReport, UserData]), + ok. + + +info(F, A) -> + error_logger:info_msg("SNMPM default user callback " ++ F ++ "~n", A). diff --git a/lib/snmp/src/manager/snmpm_user_old.erl b/lib/snmp/src/manager/snmpm_user_old.erl new file mode 100644 index 0000000000..69b1470790 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_user_old.erl @@ -0,0 +1,33 @@ +%% +%% %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(snmpm_user_old). + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{handle_error, 3}, + {handle_agent, 4}, + {handle_pdu, 5}, + {handle_trap, 4}, + {handle_inform, 4}, + {handle_report, 4}]; +behaviour_info(_) -> + undefined. + diff --git a/lib/snmp/src/manager/snmpm_usm.erl b/lib/snmp/src/manager/snmpm_usm.erl new file mode 100644 index 0000000000..8cb3062d4b --- /dev/null +++ b/lib/snmp/src/manager/snmpm_usm.erl @@ -0,0 +1,512 @@ +%% +%% %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% +%% +%%----------------------------------------------------------------- +%% This module implements the User Based Security Model for SNMP, +%% as defined in rfc2274. +%%----------------------------------------------------------------- + +-module(snmpm_usm). + +-export([init/0, + reset/0, + process_incoming_msg/4, generate_outgoing_msg/5]). + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). +-include("snmpm_usm.hrl"). +-include("SNMP-USER-BASED-SM-MIB.hrl"). +-include("SNMP-USM-AES-MIB.hrl"). +% -include("SNMPv2-TC.hrl"). + +-define(VMODULE,"M-USM"). +-include("snmp_verbosity.hrl"). + + +%%----------------------------------------------------------------- + +-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). + + +init() -> + init_counters(). + + +%%----------------------------------------------------------------- +%% 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) -> + %% 3.2.1 + ?vtrace("process_incoming_msg -> [3.2.1] check security parms",[]), + UsmSecParams = + case (catch snmp_pdus:dec_usm_security_parameters(SecParams)) of + {'EXIT', Reason} -> + inc(snmpInASNParseErrs), + error({parseError, Reason}, []); + Res -> + Res + end, + + %% Part of 3.2.2 + #usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID, + msgUserName = MsgUserName} = UsmSecParams, + ?vlog("process_incoming_msg -> [3.2.2]" + "~n authEngineID: ~p" + "~n userName: ~p", [MsgAuthEngineID, MsgUserName]), + + %% 3.2.3 (b) + ?vtrace("process_incoming_msg -> [3.2.3-b] check engine id",[]), + case snmpm_config:is_usm_engine_id_known(MsgAuthEngineID) of + true -> + ok; + false -> + SecData1 = [MsgUserName], + error(usmStatsUnknownEngineIDs, + ?usmStatsUnknownEngineIDs_instance, + undefined, [{sec_data, SecData1}]) + end, + + %% 3.2.4 + ?vtrace("process_incoming_msg -> [3.2.4] retrieve usm user",[]), + SecUser = + case snmpm_config:get_usm_user(MsgAuthEngineID, MsgUserName) of + {ok, User} -> + User; + _ -> % undefined user + SecData2 = [MsgUserName], + error(usmStatsUnknownUserNames, + ?usmStatsUnknownUserNames_instance, %% OTP-3542 + undefined, [{sec_data, SecData2}]) + end, + + %% 3.2.5 - implicit in following checks + %% 3.2.6 - 3.2.7 + ?vtrace("process_incoming_msg -> " + "[3.2.5 - 3.2.7] authenticate incoming",[]), + authenticate_incoming(Packet, UsmSecParams, SecUser, SecLevel), + + %% 3.2.8 + ?vtrace("process_incoming_msg -> [3.2.8] decrypt scoped data",[]), + ScopedPDUBytes = decrypt(Data, SecUser, UsmSecParams, SecLevel), + + %% 3.2.9 + %% Means that if AuthKey/PrivKey are changed; the old values + %% will be used. + CachedSecData = {MsgUserName, + SecUser#usm_user.auth, + SecUser#usm_user.auth_key, + SecUser#usm_user.priv, + SecUser#usm_user.priv_key}, + SecName = SecUser#usm_user.sec_name, + {ok, {MsgAuthEngineID, SecName, ScopedPDUBytes, CachedSecData}}. + + +authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel) -> + %% 3.2.6 + ?vtrace("authenticate incoming: 3.2.6",[]), + #usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID, + msgAuthoritativeEngineBoots = MsgAuthEngineBoots, + msgAuthoritativeEngineTime = MsgAuthEngineTime, + msgAuthenticationParameters = MsgAuthParams} = + UsmSecParams, + case snmp_misc:is_auth(SecLevel) of + true -> + SecName = UsmUser#usm_user.sec_name, + case is_auth(UsmUser#usm_user.auth, + UsmUser#usm_user.auth_key, + MsgAuthParams, + Packet, + SecName, + MsgAuthEngineID, + MsgAuthEngineBoots, + MsgAuthEngineTime) of + true -> + ok; + false -> + error(usmStatsWrongDigests, + ?usmStatsWrongDigests_instance, SecName) + end; + false -> % noAuth + ok + end. + + + +is_auth(usmNoAuthProtocol, _, _, _, SecName, _, _, _) -> % 3.2.5 + error(usmStatsUnsupportedSecLevels, + ?usmStatsUnsupportedSecLevels_instance, SecName); +is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName, + MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime) -> + case auth_in(AuthProtocol, AuthKey, AuthParams, Packet) of + true -> + %% 3.2.7 + ?vtrace("retrieve EngineBoots and EngineTime: 3.2.7",[]), + SnmpEngineID = get_engine_id(), + ?vtrace("SnmpEngineID: ~p",[SnmpEngineID]), + case MsgAuthEngineID of + SnmpEngineID -> %% 3.2.7a + ?vtrace("we are authoritative: 3.2.7a",[]), + SnmpEngineBoots = get_engine_boots(), + ?vtrace("SnmpEngineBoots: ~p",[SnmpEngineBoots]), + SnmpEngineTime = get_engine_time(), + ?vtrace("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; + %% OTP-4090 (OTP-3542) + false -> + error(usmStatsNotInTimeWindows, + ?usmStatsNotInTimeWindows_instance, + SecName, + [{securityLevel, 1}]) % authNoPriv + end; + _ -> %% 3.2.7b - we're non-authoritative + ?vtrace("we are non-authoritative: 3.2.7b",[]), + SnmpEngineBoots = get_engine_boots(MsgAuthEngineID), + ?vtrace("SnmpEngineBoots: ~p",[SnmpEngineBoots]), + SnmpEngineTime = get_engine_time(MsgAuthEngineID), + ?vtrace("SnmpEngineTime: ~p",[SnmpEngineTime]), + LatestRecvTime = get_engine_latest_time(MsgAuthEngineID), + ?vtrace("LatestRecvTime: ~p",[LatestRecvTime]), + UpdateLCD = + if + MsgAuthEngineBoots > SnmpEngineBoots -> true; + MsgAuthEngineBoots == SnmpEngineBoots, + MsgAuthEngineTime > LatestRecvTime -> true; + true -> false + end, + case UpdateLCD of + true -> %% 3.2.7b1 + ?vtrace("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("check if message is outside time window: 3.2.7b2", + []), + InTimeWindow = + if + SnmpEngineBoots == 2147483647 -> + {false, [{engine, SnmpEngineID}, + {boots, at_max}]}; + MsgAuthEngineBoots < SnmpEngineBoots -> + {false, [{engine, MsgAuthEngineID}, + {boots, MsgAuthEngineBoots}]}; + MsgAuthEngineBoots == SnmpEngineBoots, + MsgAuthEngineTime < (SnmpEngineTime - 150) -> + {false, [{engine, MsgAuthEngineID}, + {time, MsgAuthEngineTime}]}; + true -> true + end, + case InTimeWindow of + {false, Reason} -> + ?vinfo("not in time window[3.2.7b2]: ~p", + [Reason]), + error(notInTimeWindow, Reason); + true -> + ok + end, + true + 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, #usm_user{sec_name = SecName, + priv = PrivP, + priv_key = PrivKey}, + #usmSecurityParameters{msgPrivacyParameters = PrivParms}) -> + EncryptedPDU = snmp_pdus:dec_scoped_pdu_data(Data), + try_decrypt(PrivP, PrivKey, PrivParms, EncryptedPDU, SecName). + +try_decrypt(usmNoPrivProtocol, _, _, _, SecName) -> % 3.2.5 + error(usmStatsUnsupportedSecLevels, + ?usmStatsUnsupportedSecLevels_instance, SecName); +try_decrypt(usmDESPrivProtocol, + PrivKey, MsgPrivParams, EncryptedPDU, SecName) -> + case (catch des_decrypt(PrivKey, MsgPrivParams, EncryptedPDU)) of + {ok, DecryptedData} -> + DecryptedData; + _ -> + error(usmStatsDecryptionErrors, + ?usmStatsDecryptionErrors, SecName) + end; +try_decrypt(usmAesCfb128Protocol, + PrivKey, UsmSecParams, EncryptedPDU, SecName) -> + case (catch aes_decrypt(PrivKey, UsmSecParams, EncryptedPDU)) of + {ok, DecryptedData} -> + DecryptedData; + _ -> + error(usmStatsDecryptionErrors, + ?usmStatsDecryptionErrors, SecName) + end. + + +%%----------------------------------------------------------------- +%% Func: process_outgoing_msg(Message, SecEngineID, SecName, +%% SecData, SecLevel) -> +%% {ok, {SecEngineID, SecName, ScopedPDUBytes, SecData}} | +%% {error, Reason} | {error, Reason, ErrorInfo} +%% Return value may be throwed. +%% Types: Reason -> term() +%% Purpose: +%%----------------------------------------------------------------- +generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) -> + %% 3.1.1 + ?vtrace("generate_outgoing_msg -> entry (3.1.1)",[]), + {UserName, AuthProtocol, AuthKey, PrivProtocol, PrivKey} = + case SecData of + [] -> % 3.1.1b + %% Not a response - read from LCD + case snmpm_config:get_usm_user_from_sec_name(SecEngineID, + SecName) of + {ok, User} -> + {User#usm_user.name, + User#usm_user.auth, + User#usm_user.auth_key, + User#usm_user.priv, + User#usm_user.priv_key}; + _ -> + 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)",[]), + ScopedPduBytes = Message#message.data, + {ScopedPduData, MsgPrivParams} = + encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel), + SnmpEngineID = get_engine_id(), + ?vtrace("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}; + true when SecEngineID /= SnmpEngineID -> + {get_engine_boots(SecEngineID), get_engine_time(SecEngineID)}; + _ -> + {get_engine_boots(), 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). + + +%% 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 + {Data, []}; + true -> % 3.1.4a + case (catch try_encrypt(PrivProtocol, PrivKey, Data)) of + {ok, ScopedPduData, MsgPrivParams} -> + {snmp_pdus:enc_oct_str_tag(ScopedPduData), MsgPrivParams}; + {error, Reason} -> + error(Reason); + _ -> + 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, + 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, MsgPrivParams, EncData) -> + snmp_usm:des_decrypt(PrivKey, MsgPrivParams, EncData). + +get_des_salt() -> + SaltInt = snmpm_config:incr_counter(usm_des_salt, 1), + EngineBoots = 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 = MsgPrivParams, + msgAuthoritativeEngineTime = EngineTime, + msgAuthoritativeEngineBoots = EngineBoots} = + UsmSecParams, + snmp_usm:aes_decrypt(PrivKey, MsgPrivParams, EncData, + EngineBoots, EngineTime). + +get_aes_salt() -> + SaltInt = snmpm_config:incr_counter(usm_aes_salt, 1), + [?i64(SaltInt)]. + +%%----------------------------------------------------------------- + +get_engine_id() -> + {ok, EngineID} = snmpm_config:get_engine_id(), + EngineID. + +get_engine_boots() -> + {ok, Boots} = snmpm_config:get_engine_boots(), + Boots. + +get_engine_time() -> + {ok, Diff} = snmpm_config:get_engine_time(), + Diff. + + +%%----------------------------------------------------------------- +%% We cache the local values of all non-auth engines we know. +%% See section 2.3 (Time Synchronization) of the RFC. +%%----------------------------------------------------------------- +get_engine_boots(SnmpEngineID) -> + {ok, Boots} = snmpm_config:get_usm_eboots(SnmpEngineID), + Boots. + +get_engine_time(SnmpEngineID) -> + {ok, Diff} = snmpm_config:get_usm_etime(SnmpEngineID), + Diff. + +get_engine_latest_time(SnmpEngineID) -> + {ok, Time} = snmpm_config:get_usm_eltime(SnmpEngineID), + Time. + + +set_engine_boots(SnmpEngineID, EngineBoots) -> + snmpm_config:set_usm_eboots(SnmpEngineID, EngineBoots). + +set_engine_time(SnmpEngineID, EngineTime) -> + Diff = snmp_misc:now(sec) - EngineTime, + snmpm_config:set_usm_etime(SnmpEngineID, Diff). + +set_engine_latest_time(SnmpEngineID, EngineTime) -> + snmpm_config:set_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}). + + +%%----------------------------------------------------------------- + +init_counters() -> + F = fun(Counter) -> snmpm_config:maybe_cre_stats_counter(Counter, 0) end, + lists:map(F, counters()). + +reset() -> + F = fun(Counter) -> snmpm_config:reset_stats_counter(Counter) end, + lists:map(F, counters()). + +counters() -> + [usmStatsUnsupportedSecLevels, + usmStatsNotInTimeWindows, + usmStatsUnknownUserNames, + usmStatsUnknownEngineIDs, + usmStatsWrongDigests, + usmStatsDecryptionErrors]. + +inc(Name) -> snmpm_config:incr_stats_counter(Name, 1). + diff --git a/lib/snmp/src/manager/snmpm_usm.hrl b/lib/snmp/src/manager/snmpm_usm.hrl new file mode 100644 index 0000000000..1939ad6ed9 --- /dev/null +++ b/lib/snmp/src/manager/snmpm_usm.hrl @@ -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% +%% + +-record(usm_user, {engine_id, + name, + sec_name, % Default value is name + auth = usmNoAuthProtocol, + auth_key = [], + priv = usmNoPrivProtocol, + priv_key = []}). + diff --git a/lib/snmp/src/misc/Makefile b/lib/snmp/src/misc/Makefile new file mode 100644 index 0000000000..48d76bdbed --- /dev/null +++ b/lib/snmp/src/misc/Makefile @@ -0,0 +1,122 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2003-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 = $(HRLS:%=%.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 + +# SNMP_DEBUG=d +ifeq ($(SNMP_DEBUG),d) + SNMP_FLAGS += -Dsnmp_debug +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../compile \ + -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 "TARGET_FILES: $(TARGET_FILES)" + @echo "" + + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/src/misc + $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)/src/misc + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(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/misc/depend.mk b/lib/snmp/src/misc/depend.mk new file mode 100644 index 0000000000..dea0f75048 --- /dev/null +++ b/lib/snmp/src/misc/depend.mk @@ -0,0 +1,58 @@ +#-*-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)/snmp_conf.$(EMULATOR): \ + snmp_conf.erl \ + snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmp_config.$(EMULATOR): \ + snmp_config.erl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmp_log.$(EMULATOR): \ + snmp_log.erl \ + snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmp_misc.$(EMULATOR): \ + snmp_misc.erl \ + snmp_verbosity.hrl \ + ../compile/snmpc_misc.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmp_note_store.$(EMULATOR): \ + snmp_note_store.erl \ + ../misc/snmp_debug.hrl \ + ../misc/snmp_verbosity.hrl + +$(EBIN)/snmp_pdu.$(EMULATOR): \ + snmp_pdu.erl \ + snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmp_usm.$(EMULATOR): \ + snmp_usm.erl \ + snmp_verbosity.hrl \ + ../../include/snmp_types.hrl + +$(EBIN)/snmp_verbosity.$(EMULATOR): \ + snmp_verbosity.erl + + diff --git a/lib/snmp/src/misc/modules.mk b/lib/snmp/src/misc/modules.mk new file mode 100644 index 0000000000..32092aaae2 --- /dev/null +++ b/lib/snmp/src/misc/modules.mk @@ -0,0 +1,33 @@ +#-*-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% + +MODULES = \ + snmp_conf \ + snmp_config \ + snmp_log \ + snmp_mini_mib \ + snmp_misc \ + snmp_note_store \ + snmp_pdus \ + snmp_usm \ + snmp_verbosity + +HRLS = snmp_verbosity snmp_debug + + diff --git a/lib/snmp/src/misc/snmp_conf.erl b/lib/snmp/src/misc/snmp_conf.erl new file mode 100644 index 0000000000..63762ac17b --- /dev/null +++ b/lib/snmp/src/misc/snmp_conf.erl @@ -0,0 +1,421 @@ +%% +%% %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% +%% +%%---------------------------------------------------------------------- +%% Purpose: Basic module for reading and verifying config files +%%---------------------------------------------------------------------- +-module(snmp_conf). + + +%% External exports +-export([read_files/2, read/2]). + +%% Basic (type) check functions +-export([check_mandatory/2, + check_integer/1, check_integer/2, + + check_string/1, check_string/2, + + check_atom/2, + + check_timer/1, + + check_ip/1, check_taddress/1, + + check_packet_size/1, + + check_oid/1, + + check_mp_model/1, + check_sec_model/1, check_sec_model/2, check_sec_model/3, + check_sec_level/1, + + all_integer/1 + ]). + + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). + +-define(VMODULE,"CONF"). +-include("snmp_verbosity.hrl"). + + +%%----------------------------------------------------------------- + +read_files(Dir, Files) when is_list(Dir) andalso is_list(Files) -> + read_files(Dir, Files, []). + +read_files(_Dir, [], Res) -> + lists:reverse(Res); +read_files(Dir, [{Gen, Filter, Check, FileName}|Files], Res) + when is_function(Filter) andalso + is_function(Check) andalso + is_list(FileName) -> + ?vdebug("read_files -> entry with" + "~n FileName: ~p", [FileName]), + File = filename:join(Dir, FileName), + case file:read_file_info(File) of + {ok, _} -> + Confs = read(File, Check), + read_files(Dir, Files, [Filter(Confs)|Res]); + {error, R} -> + ?vlog("failed reading file info for ~s: " + "~n ~p", [FileName, R]), + Gen(Dir), + read_files(Dir, Files, [Filter([])|Res]) + end. + + +%% Ret. Res | exit(Reason) +read(File, Check) when is_function(Check) -> + ?vdebug("read -> entry with" + "~n File: ~p", [File]), + Fd = open_file(File), + + case loop(Fd, [], Check, 1, File) of + {error, Reason} -> + file:close(Fd), + error(Reason); + {ok, Res} -> + file:close(Fd), + Res + end. + +open_file(File) -> + case file:open(File, [read]) of + {ok, Fd} -> + Fd; + {error, Reason} -> + error({failed_open, File, Reason}) + end. + +loop(Fd, Res, Check, StartLine, File) -> + case do_read(Fd, "", StartLine) of + {ok, Row, EndLine} -> + ?vtrace("loop -> " + "~n Row: ~p" + "~n EndLine: ~p", [Row, EndLine]), + case (catch Check(Row)) of + ok -> + ?vtrace("loop -> ok", []), + loop(Fd, [Row | Res], Check, EndLine, File); + {ok, NewRow} -> + ?vtrace("loop -> ok: " + "~n NewRow: ~p", [NewRow]), + loop(Fd, [NewRow | Res], Check, EndLine, File); + {error, Reason} -> + ?vtrace("loop -> check error: " + "~n Reason: ~p", [Reason]), + {error, {failed_check, File, StartLine, EndLine, Reason}}; + Error -> + ?vtrace("loop -> check failure: " + "~n Error: ~p", [Error]), + {error, {failed_check, File, StartLine, EndLine, Error}} + end; + {error, EndLine, Error} -> + ?vtrace("loop -> read failure: " + "~n Error: ~p", [Error]), + {error, {failed_reading, File, StartLine, EndLine, Error}}; + eof -> + {ok, Res} + end. + + +do_read(Io, Prompt, StartLine) -> + case io:request(Io, {get_until,Prompt,erl_scan,tokens,[StartLine]}) of + {ok, Toks, EndLine} -> + case erl_parse:parse_term(Toks) of + {ok, Term} -> + {ok, Term, EndLine}; + {error, {Line, erl_parse, Error}} -> + {error, Line, {parse_error, Error}} + end; + {error,E,EndLine} -> + {error, EndLine, E}; + {eof, _EndLine} -> + eof; + Other -> + Other + end. + + +%%----------------------------------------------------------------- + + +check_mandatory(L, [{Key, Value}|T]) -> + case lists:keymember(Key, 1, L) of + true -> + check_mandatory(L, T); + false when Value == mandatory -> + error({missing_mandatory, Key}); + false -> + {value, V} = Value, + check_mandatory([{Key, V} | L], T) + end; +check_mandatory(L, []) -> + {ok, L}. + + +%% --------- + +check_integer(I) -> check_integer(I, any). + +check_integer(I, any) when is_integer(I) -> ok; +check_integer(I, pos) when is_integer(I), I > 0 -> ok; +check_integer(I, neg) when is_integer(I), I < 0 -> ok; +check_integer(I1, {gt, I2}) + when is_integer(I1) andalso is_integer(I2) andalso (I1 > I2) -> ok; +check_integer(I1, {gte, I2}) + when is_integer(I1) andalso is_integer(I2) andalso (I1 >= I2) -> ok; +check_integer(I1, {lt, I2}) + when is_integer(I1) andalso is_integer(I2) andalso (I1 < I2) -> ok; +check_integer(I1, {lte, I2}) + when is_integer(I1) andalso is_integer(I2) andalso (I1 =< I2) -> ok; +check_integer(I1, {eq, I1}) + when is_integer(I1) -> ok; +check_integer(I, {range, L, U}) + when (is_integer(I) andalso + is_integer(L) andalso + is_integer(U) andalso + (I >= L) andalso (I =< U)) -> ok; +check_integer(I, _) -> error({invalid_integer, I}). + +check_packet_size(S) -> + case (catch check_integer(S, {range, 484, 2147483647})) of + ok -> + ok; + {error, _} -> + error({invalid_packet_size, S}) + end. + +%% --------- + +check_string(X) when is_list(X) -> ok; +check_string(X) -> error({invalid_string, X}). + +check_string(X, any) + when is_list(X) -> ok; +check_string(X, {gt, Len}) + when is_list(X) andalso (length(X) > Len) -> ok; +check_string(X, {gt, _Len}) + when is_list(X) -> error({invalid_length, X}); +check_string(X, {gte, Len}) + when is_list(X) andalso (length(X) >= Len) -> ok; +check_string(X, {gte, _Len}) + when is_list(X) -> error({invalid_length, X}); +check_string(X, {lt, Len}) + when is_list(X) andalso (length(X) < Len) -> ok; +check_string(X, {lt, _Len}) + when is_list(X) -> error({invalid_length, X}); +check_string(X, {lte, Len}) + when is_list(X) andalso (length(X) =< Len) -> ok; +check_string(X, {lte, _Len}) + when is_list(X) -> error({invalid_length, X}); +check_string(X, Len) + when is_list(X) andalso is_integer(Len) andalso (length(X) =:= Len) -> ok; +check_string(X, _Len) when is_list(X) -> error({invalid_length, X}); +check_string(X, _Len) -> error({invalid_string, X}). + + +check_atom(X, Atoms) -> + case lists:keysearch(X, 1, Atoms) of + {value, {X, Val}} -> + {ok, Val}; + _ -> + error({invalid_atom, X, Atoms}) + end. + + +%% --------- + +check_mp_model(MPModel) when is_atom(MPModel) -> + All = [{v1, ?MP_V1}, {v2c, ?MP_V2C}, {v3, ?MP_V3}], + check_atom(MPModel, All); +check_mp_model(?MP_V1) -> + {ok, ?MP_V1}; +check_mp_model(?MP_V2C) -> + {ok, ?MP_V2C}; +check_mp_model(?MP_V3) -> + {ok, ?MP_V3}; +check_mp_model(BadMpModel) -> + error({invalid_mp_model, BadMpModel}). + + +%% --------- + +check_sec_model(SecModel) when is_atom(SecModel) -> + check_sec_model(SecModel, []); +check_sec_model(?SEC_ANY) -> + {ok, ?SEC_ANY}; +check_sec_model(?SEC_V1) -> + {ok, ?SEC_V1}; +check_sec_model(?SEC_V2C) -> + {ok, ?SEC_V2C}; +check_sec_model(?SEC_USM) -> + {ok, ?SEC_USM}; +check_sec_model(BadSecModel) -> + error({invalid_sec_model, BadSecModel}). + +check_sec_model(SecModel, Exclude) when is_atom(SecModel) -> + All = [{any, ?SEC_ANY}, + {v1, ?SEC_V1}, + {v2c, ?SEC_V2C}, + {usm, ?SEC_USM}], + Alt = [{X, Y} || {X, Y} <- All, not lists:member(X, Exclude)], + case (catch check_atom(SecModel, Alt) ) of + {error, _} -> + error({invalid_sec_model, SecModel}); + OK -> + OK + end; +check_sec_model(BadSecModel, _Exclude) -> + error({invalid_sec_model, BadSecModel}). + +check_sec_model(v1, v1, Exclude) -> + check_sec_model2(v1, ?SEC_V1, Exclude); +check_sec_model(v1, SecModel, _Exclude) -> + error({invalid_sec_model, v1, SecModel}); +check_sec_model(v2c, v2c, Exclude) -> + check_sec_model2(v2c, ?SEC_V2C, Exclude); +check_sec_model(v2c, SecModel, _Exclude) -> + error({invalid_sec_model, v2c, SecModel}); +check_sec_model(v3, usm, Exclude) -> + check_sec_model2(v3, ?SEC_USM, Exclude); +check_sec_model(v3, SecModel, _Exclude) -> + error({invalid_sec_model, v3, SecModel}); +check_sec_model(M1, M2, _Exclude) -> + error({invalid_sec_model, M1, M2}). + +check_sec_model2(SecModel, SM, Exclude) -> + case lists:member(SecModel, Exclude) of + false -> + {ok, SM}; + true -> + error({invalid_sec_model, SecModel}) + end. + + +%% --------- + +check_sec_level(SecLevel) when is_atom(SecLevel) -> + All = [{noAuthNoPriv, ?'SnmpSecurityLevel_noAuthNoPriv'}, + {authNoPriv, ?'SnmpSecurityLevel_authNoPriv'}, + {authPriv, ?'SnmpSecurityLevel_authPriv'}], + case (catch check_atom(SecLevel, All)) of + {error, _} -> + error({invalid_sec_level, SecLevel}); + OK -> + OK + end; +check_sec_level(?'SnmpSecurityLevel_noAuthNoPriv' = SL) -> + {ok, SL}; +check_sec_level(?'SnmpSecurityLevel_authNoPriv' = SL) -> + {ok, SL}; +check_sec_level(?'SnmpSecurityLevel_authPriv' = SL) -> + {ok, SL}; +check_sec_level(BadSecLevel) -> + error({invalid_sec_level, BadSecLevel}). + + +%% --------- + +check_taddress(X) when is_list(X) andalso (length(X) =:= 6) -> + case (catch all_integer(X)) of + true -> + ok; + false -> + error({invalid_taddress, X}) + end; +check_taddress(X) -> + error({invalid_taddress, X}). + + +%% --------- + +check_timer(infinity) -> + {ok, infinity}; +check_timer(T) when is_record(T, snmp_incr_timer) -> + {ok, T}; +check_timer({WaitFor, Factor, Incr, Retry} = T) -> + case (catch do_check_timer(WaitFor, Factor, Incr, Retry)) of + ok -> + {ok, #snmp_incr_timer{wait_for = WaitFor, + factor = Factor, + incr = Incr, + max_retries = Retry}}; + _Err -> + error({invalid_timer, T}) + end; +check_timer(Timeout) -> + case (catch check_integer(Timeout, {gt, 0})) of + ok -> + {ok, #snmp_incr_timer{wait_for = Timeout, + factor = 1, + incr = 0, + max_retries = 0}}; + _Err -> + error({invalid_timer, Timeout}) + end. + +do_check_timer(WaitFor, Factor, Incr, Retry) -> + check_integer(WaitFor, {gt, 0}), + check_integer(Factor, {gt, 0}), + check_integer(Incr, {gte, 0}), + check_integer(Retry, {gte, 0}), + ok. + +%% --------- + +check_ip(X) when is_list(X) andalso (length(X) =:= 4) -> + case (catch all_integer(X)) of + true -> + ok; + false -> + error({invalid_ip_address, X}) + end; +check_ip(X) -> + error({invalid_ip_address, X}). + + +%% --------- + +check_oid([E1,E2|_] = X) when E1 * 40 + E2 =< 255 -> + case all_integer(X) of + true -> + ok; + _ -> + error({invalid_object_identifier, X}) + end; +check_oid(X) -> + error({invalid_object_identifier, X}). + + +%% --------- + +all_integer([H|T]) when is_integer(H) -> all_integer(T); +all_integer([_H|_T]) -> false; +all_integer([]) -> true. + + +%% --------- + +error(Reason) -> + throw({error, Reason}). + diff --git a/lib/snmp/src/misc/snmp_config.erl b/lib/snmp/src/misc/snmp_config.erl new file mode 100644 index 0000000000..ad41eaf160 --- /dev/null +++ b/lib/snmp/src/misc/snmp_config.erl @@ -0,0 +1,2348 @@ +%% +%% %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_config). + +-include_lib("kernel/include/file.hrl"). +-include("snmp_types.hrl"). + +-export([config/0]). + +-export([write_config_file/4, append_config_file/4, read_config_file/3]). + +-export([write_agent_snmp_files/7, write_agent_snmp_files/12, + + write_agent_snmp_conf/5, + write_agent_snmp_context_conf/1, + write_agent_snmp_community_conf/1, + write_agent_snmp_standard_conf/2, + write_agent_snmp_target_addr_conf/4, + write_agent_snmp_target_addr_conf/6, + write_agent_snmp_target_params_conf/2, + write_agent_snmp_notify_conf/2, + write_agent_snmp_usm_conf/5, + write_agent_snmp_vacm_conf/3, + + write_manager_snmp_files/8, + write_manager_snmp_conf/5, + write_manager_snmp_users_conf/2, + write_manager_snmp_agents_conf/2, + write_manager_snmp_usm_conf/2 + + ]). + +-export([write_agent_config/3, + update_agent_config/2, + + write_agent_context_config/3, + update_agent_context_config/2, + + write_agent_community_config/3, + update_agent_community_config/2, + + write_agent_standard_config/3, + update_agent_standard_config/2, + + write_agent_target_addr_config/3, + update_agent_target_addr_config/2, + + write_agent_target_params_config/3, + update_agent_target_params_config/2, + + write_agent_notify_config/3, + update_agent_notify_config/2, + + write_agent_vacm_config/3, + update_agent_vacm_config/2, + + write_agent_usm_config/3, + update_agent_usm_config/2, + + write_manager_config/3, + update_manager_config/2, + + write_manager_users_config/3, + update_manager_users_config/2, + + write_manager_agents_config/3, + update_manager_agents_config/2, + + write_manager_usm_config/3, + update_manager_usm_config/2 + ]). + + +%%---------------------------------------------------------------------- +%% Handy SNMP configuration +%%---------------------------------------------------------------------- + +config() -> + case (catch config2()) of + ok -> + ok; + {error, Reason} -> + {error, Reason}; + E -> + {error, {failed, E}} + end. + + +config2() -> + intro(), + SysAgentConfig = + case config_agent() of + [] -> + []; + SAC -> + [{agent, SAC}] + end, + SysMgrConfig = + case config_manager() of + [] -> + []; + SMC -> + [{manager, SMC}] + end, + config_sys(SysAgentConfig ++ SysMgrConfig), + ok. + + +intro() -> + i("~nSimple SNMP configuration tool (version ~s)", [?version]), + i("------------------------------------------------"), + i("Note: Non-trivial configurations still has to be"), + i(" done manually. IP addresses may be entered "), + i(" as dront.ericsson.se (UNIX only) or"), + i(" 123.12.13.23"), + i("------------------------------------------------"), + ok. + + +config_agent() -> + case (catch snmp_agent2()) of + ok -> + []; + {ok, SysConf} -> + SysConf; + {error, Reason} -> + error(Reason); + {'EXIT', Reason} -> + error(Reason); + E -> + error({agent_config_failed, E}) + end. + +snmp_agent2() -> + case ask("~nConfigure an agent (y/n)?", "y", fun verify_yes_or_no/1) of + yes -> + {Vsns, ConfigDir, SysConf} = config_agent_sys(), + config_agent_snmp(ConfigDir, Vsns), + {ok, SysConf}; + no -> + ok + end. + + +config_manager() -> + case (catch config_manager2()) of + ok -> + []; + {ok, SysConf} -> + SysConf; + {error, Reason} -> + error(Reason); + {'EXIT', Reason} -> + error(Reason); + E -> + error({manager_config_failed, E}) + end. + +config_manager2() -> + case ask("~nConfigure a manager (y/n)?", "y", fun verify_yes_or_no/1) of + yes -> + {Vsns, ConfigDir, SysConf} = config_manager_sys(), + config_manager_snmp(ConfigDir, Vsns), + {ok, SysConf}; + no -> + ok + end. + + +config_sys(SysConfig) -> + i("~n--------------------"), + {ok, DefDir} = file:get_cwd(), + ConfigDir = ask("Configuration directory for system file (absolute path)?", + DefDir, fun verify_dir/1), + write_sys_config_file(ConfigDir, SysConfig). + + +%% ------------------- + +config_agent_sys() -> + i("~nAgent system config: " + "~n--------------------"), + Prio = ask("1. Agent process priority (low/normal/high)", + "normal", fun verify_prio/1), + Vsns = ask("2. What SNMP version(s) should be used " + "(1,2,3,1&2,1&2&3,2&3)?", "3", fun verify_versions/1), + %% d("Vsns: ~p", [Vsns]), + {ok, DefDir} = file:get_cwd(), + {DefConfDir, Warning} = default_agent_dir(DefDir), + ConfQ = + if + Warning == "" -> + "3. Configuration directory (absolute path)?"; + true -> + lists:flatten( + io_lib:format("3. Configuration directory (absolute path)?" + "~n ~s", [Warning])) + end, + ConfigDir = ask(ConfQ, DefConfDir, fun verify_dir/1), + ConfigVerb = ask("4. Config verbosity " + "(silence/info/log/debug/trace)?", + "silence", + fun verify_verbosity/1), + DbDir = ask("5. Database directory (absolute path)?", DefDir, + fun verify_dir/1), + MibStorageType = ask("6. Mib storage type (ets/dets/mnesia)?", "ets", + fun verify_mib_storage_type/1), + MibStorage = + case MibStorageType of + ets -> + ets; + dets -> + DetsDir = ask("6b. Mib storage directory (absolute path)?", + DbDir, fun verify_dir/1), + DetsAction = ask("6c. Mib storage [dets] database start " + "action " + "(default/clear/keep)?", + "default", fun verify_mib_storage_action/1), + case DetsAction of + default -> + {dets, DetsDir}; + _ -> + {dets, DetsDir, DetsAction} + end; + mnesia -> +% Nodes = ask("Mib storage nodes?", "none", +% fun verify_mib_storage_nodes/1), + Nodes = [], + MnesiaAction = ask("6b. Mib storage [mnesia] database start " + "action " + "(default/clear/keep)?", + "default", fun verify_mib_storage_action/1), + case MnesiaAction of + default -> + {mnesia, Nodes}; + _ -> + {mnesia, Nodes, MnesiaAction} + end + end, + TargetCacheVerb = ask("7. Target cache verbosity " + "(silence/info/log/debug/trace)?", "silence", + fun verify_verbosity/1), + SymStoreVerb = ask("8. Symbolic store verbosity " + "(silence/info/log/debug/trace)?", "silence", + fun verify_verbosity/1), + LocalDbVerb = ask("9. Local DB verbosity " + "(silence/info/log/debug/trace)?", "silence", + fun verify_verbosity/1), + LocalDbRepair = ask("10. Local DB repair (true/false/force)?", "true", + fun verify_dets_repair/1), + LocalDbAutoSave = ask("11. Local DB auto save (infinity/milli seconds)?", + "5000", fun verify_dets_auto_save/1), + ErrorMod = ask("12. Error report module?", "snmpa_error_logger", fun verify_module/1), + Type = ask("13. Agent type (master/sub)?", "master", + fun verify_agent_type/1), + AgentConfig = + case Type of + master -> + MasterAgentVerb = ask("14. Master-agent verbosity " + "(silence/info/log/debug/trace)?", + "silence", + fun verify_verbosity/1), + ForceLoad = ask("15. Shall the agent re-read the " + "configuration files during startup ~n" + " (and ignore the configuration " + "database) (true/false)?", "true", + fun verify_bool/1), + MultiThreaded = ask("16. Multi threaded agent (true/false)?", + "false", + fun verify_bool/1), + MeOverride = ask("17. Check for duplicate mib entries when " + "installing a mib (true/false)?", "false", + fun verify_bool/1), + TrapOverride = ask("18. Check for duplicate trap names when " + "installing a mib (true/false)?", "false", + fun verify_bool/1), + MibServerVerb = ask("19. Mib server verbosity " + "(silence/info/log/debug/trace)?", + "silence", + fun verify_verbosity/1), + MibServerCache = ask("20. Mib server cache " + "(true/false)?", + "true", + fun verify_bool/1), + NoteStoreVerb = ask("21. Note store verbosity " + "(silence/info/log/debug/trace)?", + "silence", + fun verify_verbosity/1), + NoteStoreTimeout = ask("22. Note store GC timeout?", "30000", + fun verify_timeout/1), + ATL = + case ask("23. Shall the agent use an audit trail log " + "(y/n)?", + "n", fun verify_yes_or_no/1) of + yes -> + ATLType = ask("23b. Audit trail log type " + "(write/read_write)?", + "read_write", fun verify_atl_type/1), + ATLDir = ask("23c. Where to store the " + "audit trail log?", + DefDir, fun verify_dir/1), + ATLMaxFiles = ask("23d. Max number of files?", + "10", + fun verify_pos_integer/1), + ATLMaxBytes = ask("23e. Max size (in bytes) " + "of each file?", + "10240", + fun verify_pos_integer/1), + ATLSize = {ATLMaxBytes, ATLMaxFiles}, + ATLRepair = ask("23f. Audit trail log repair " + "(true/false/truncate/snmp_repair)?", "true", + fun verify_atl_repair/1), + [{audit_trail_log, [{type, ATLType}, + {dir, ATLDir}, + {size, ATLSize}, + {repair, ATLRepair}]}]; + no -> + [] + end, + NetIfVerb = ask("24. Network interface verbosity " + "(silence/info/log/debug/trace)?", + "silence", + fun verify_verbosity/1), + NetIfMod = ask("25. Which network interface module shall be used?", + "snmpa_net_if", fun verify_module/1), + NetIfOpts = + case NetIfMod of + snmpa_net_if -> + NetIfBindTo = + ask("25a. Bind the agent IP address " + "(true/false)?", + "false", fun verify_bool/1), + NetIfNoReuse = + ask("25b. Shall the agents " + "IP address " + "and port be not reusable " + "(true/false)?", + "false", fun verify_bool/1), + NetIfReqLimit = + ask("25c. Agent request limit " + "(used for flow control) " + "(infinity/pos integer)?", + "infinity", + fun verify_netif_req_limit/1), + NetIfRecbuf = + case ask("25d. Receive buffer size of the " + "agent (in bytes) " + "(default/pos integer)?", + "default", + fun verify_netif_recbuf/1) of + default -> + []; + RecBufSz -> + [{recbuf, RecBufSz}] + end, + NetIfSndbuf = + case ask("25e. Send buffer size of the agent " + "(in bytes) (default/pos integer)?", + "default", + fun verify_netif_sndbuf/1) of + default -> + []; + SndBufSz -> + [{sndbuf, SndBufSz}] + end, + NetIfFilter = + case ask("25f. Do you wish to specify a " + "network interface filter module " + "(or use default)", + "default", fun verify_module/1) of + default -> + []; + NetIfFilterMod -> + [{filter, [{module, NetIfFilterMod}]}] + end, + [{bind_to, NetIfBindTo}, + {no_reuse, NetIfNoReuse}, + {req_limit, NetIfReqLimit}] ++ + NetIfRecbuf ++ NetIfSndbuf ++ NetIfFilter; + _ -> + [] + end, + NetIf = [{module, NetIfMod}, + {verbosity, NetIfVerb}, + {options, NetIfOpts}], + [{agent_type, master}, + {agent_verbosity, MasterAgentVerb}, + {config, [{dir, ConfigDir}, + {force_load, ForceLoad}, + {verbosity, ConfigVerb}]}, + {multi_threaded, MultiThreaded}, + {mib_server, [{mibentry_override, MeOverride}, + {trapentry_override, TrapOverride}, + {verbosity, MibServerVerb}, + {cache, MibServerCache}]}, + {note_store, [{timeout, NoteStoreTimeout}, + {verbosity, NoteStoreVerb}]}, + {net_if, NetIf}] ++ ATL; + sub -> + SubAgentVerb = ask("14. Sub-agent verbosity " + "(silence/info/log/debug/trace)?", + "silence", + fun verify_verbosity/1), + [{agent_type, sub}, + {agent_verbosity, SubAgentVerb}, + {config, [{dir, ConfigDir}]}] + end, + SysConfig = + [{priority, Prio}, + {versions, Vsns}, + {db_dir, DbDir}, + {mib_storage, MibStorage}, + {target_cache, [{verbosity, TargetCacheVerb}]}, + {symbolic_store, [{verbosity, SymStoreVerb}]}, + {local_db, [{repair, LocalDbRepair}, + {auto_save, LocalDbAutoSave}, + {verbosity, LocalDbVerb}]}, + {error_report_module, ErrorMod}] ++ AgentConfig, + {Vsns, ConfigDir, SysConfig}. + + +config_agent_snmp(Dir, Vsns) -> + i("~nAgent snmp config: " + "~n------------------"), + AgentName = guess_agent_name(), + EngineName = guess_engine_name(), + SysName = ask("1. System name (sysName standard variable)", + AgentName, fun verify_system_name/1), + EngineID = ask("2. Engine ID (snmpEngineID standard variable)", + EngineName, fun verify_engine_id/1), + MMS = ask("3. Max message size?", "484", + fun verify_max_message_size/1), + AgentUDP = ask("4. The UDP port the agent listens to. " + "(standard 161)", + "4000", fun verify_port_number/1), + Host = host(), + AgentIP = ask("5. IP address for the agent (only used as id ~n" + " when sending traps)", Host, fun verify_address/1), + ManagerIP = ask("6. IP address for the manager (only this manager ~n" + " will have access to the agent, traps are sent ~n" + " to this one)", Host, fun verify_address/1), + TrapUdp = ask("7. To what UDP port at the manager should traps ~n" + " be sent (standard 162)?", "5000", + fun verify_port_number/1), + SecType = ask("8. Do you want a none- minimum- or semi-secure" + " configuration? ~n" + " Note that if you chose v1 or v2, you won't get any" + " security for these~n" + " requests (none, minimum, semi_des, semi_aes)", + "minimum", + fun verify_sec_type/1), + Passwd = + case lists:member(v3, Vsns) and (SecType /= none) of + true -> + ensure_crypto_started(), + ask("8b. Give a password of at least length 8. It is used to " + "generate ~n" + " private keys for the configuration: ", + mandatory, fun verify_passwd/1); + false -> + "" + end, + NotifType = + case lists:member(v1, Vsns) of + true -> + Overwrite = ask("9. Current configuration files will " + "now be overwritten. " + "Ok (y/n)?", "y", fun verify_yes_or_no/1), + case Overwrite of + no -> + error(overwrite_not_allowed); + yes -> + ok + end, + trap; + false -> + NT = ask("9. Should notifications be sent as traps or informs " + "(trap/inform)?", "trap", fun verify_notif_type/1), + Overwrite = ask("10. Current configuration files will " + "now be overwritten. " + "Ok (y/n)?", "y", fun verify_yes_or_no/1), + case Overwrite of + no -> + error(overwrite_not_allowed); + yes -> + ok + end, + NT + end, + case (catch write_agent_snmp_files(Dir, + Vsns, ManagerIP, TrapUdp, + AgentIP, AgentUDP, + SysName, NotifType, SecType, + Passwd, EngineID, MMS)) of + ok -> + i("~n- - - - - - - - - - - - -"), + i("Info: 1. SecurityName \"initial\" has noAuthNoPriv read access~n" + " and authenticated write access to the \"restricted\"~n" + " subtree."), + i(" 2. SecurityName \"all-rights\" has noAuthNoPriv " + "read/write~n" + " access to the \"internet\" subtree."), + i(" 3. Standard traps are sent to the manager."), + case lists:member(v1, Vsns) or lists:member(v2, Vsns) of + true -> + i(" 4. Community \"public\" is mapped to security name" + " \"initial\"."), + i(" 5. Community \"all-rights\" is mapped to security" + " name \"all-rights\"."); + false -> + ok + end, + i("The following agent files were written: agent.conf, " + "community.conf,~n" + "standard.conf, target_addr.conf, " + "target_params.conf, ~n" + "notify.conf" ++ + case lists:member(v3, Vsns) of + true -> ", vacm.conf and usm.conf"; + false -> " and vacm.conf" + end), + i("- - - - - - - - - - - - -"), + ok; + E -> + error({failed_writing_files, E}) + end. + + +%% ------------------- + +config_manager_sys() -> + i("~nManager system config: " + "~n----------------------"), + Prio = ask("1. Manager process priority (low/normal/high)", + "normal", fun verify_prio/1), + Vsns = ask("2. What SNMP version(s) should be used " + "(1,2,3,1&2,1&2&3,2&3)?", "3", fun verify_versions/1), + {ok, DefDir} = file:get_cwd(), + {DefConfDir, Warning} = default_manager_dir(DefDir), + ConfQ = + if + Warning == "" -> + "3. Configuration directory (absolute path)?"; + true -> + lists:flatten( + io_lib:format("3. Configuration directory (absolute path)?" + "~n ~s", [Warning])) + end, + ConfigDir = ask(ConfQ, DefConfDir, fun verify_dir/1), + ConfigVerb = ask("4. Config verbosity " + "(silence/info/log/debug/trace)?", + "silence", + fun verify_verbosity/1), + ConfigDbDir = ask("5. Database directory (absolute path)?", + DefDir, fun verify_dir/1), + ConfigDbRepair = ask("6. Database repair " + "(true/false/force)?", "true", + fun verify_dets_repair/1), + ConfigDbAutoSave = ask("7. Database auto save " + "(infinity/milli seconds)?", + "5000", fun verify_dets_auto_save/1), + IRB = + case ask("8. Inform request behaviour (auto/user)?", + "auto", fun verify_irb/1) of + auto -> + auto; + user -> + case ask("8b. Use default GC timeout" + "(default/seconds)?", + "default", fun verify_irb_user/1) of + default -> + user; + IrbGcTo -> + {user, IrbGcTo} + end + end, + ServerVerb = ask("9. Server verbosity " + "(silence/info/log/debug/trace)?", + "silence", + fun verify_verbosity/1), + ServerTimeout = ask("10. Server GC timeout?", "30000", + fun verify_timeout/1), + NoteStoreVerb = ask("11. Note store verbosity " + "(silence/info/log/debug/trace)?", + "silence", + fun verify_verbosity/1), + NoteStoreTimeout = ask("12. Note store GC timeout?", "30000", + fun verify_timeout/1), + NetIfMod = ask("13. Which network interface module shall be used?", + "snmpm_net_if", fun verify_module/1), + NetIfVerb = ask("14. Network interface verbosity " + "(silence/info/log/debug/trace)?", "silence", + fun verify_verbosity/1), + NetIfBindTo = ask("15. Bind the manager IP address " + "(true/false)?", + "false", fun verify_bool/1), + NetIfNoReuse = ask("16. Shall the manager IP address and port " + "be not reusable (true/false)?", + "false", fun verify_bool/1), + NetIfRecbuf = + case ask("17. Receive buffer size of the manager (in bytes) " + "(default/pos integer)?", "default", + fun verify_netif_recbuf/1) of + default -> + []; + RecBufSz -> + [{recbuf, RecBufSz}] + end, + NetIfSndbuf = + case ask("18. Send buffer size of the manager (in bytes) " + "(default/pos integer)?", "default", + fun verify_netif_sndbuf/1) of + default -> + []; + SndBufSz -> + [{sndbuf, SndBufSz}] + end, + NetIfOpts = + [{bind_to, NetIfBindTo}, + {no_reuse, NetIfNoReuse}] ++ NetIfRecbuf ++ NetIfSndbuf, + NetIf = + [{module, NetIfMod}, + {verbosity, NetIfVerb}, + {options, NetIfOpts}], + ATL = + case ask("19. Shall the manager use an audit trail log " + "(y/n)?", + "n", fun verify_yes_or_no/1) of + yes -> + ATLDir = ask("19b. Where to store the " + "audit trail log?", + DefDir, fun verify_dir/1), + ATLMaxFiles = ask("19c. Max number of files?", + "10", + fun verify_pos_integer/1), + ATLMaxBytes = ask("19d. Max size (in bytes) " + "of each file?", + "10240", + fun verify_pos_integer/1), + ATLSize = {ATLMaxBytes, ATLMaxFiles}, + ATLRepair = ask("19e. Audit trail log repair " + "(true/false/truncate/snmp_repair)?", "true", + fun verify_atl_repair/1), + [{audit_trail_log, [{dir, ATLDir}, + {size, ATLSize}, + {repair, ATLRepair}]}]; + no -> + [] + end, + DefUser = + case ask("20. Do you wish to assign a default user [yes] or use~n" + " the default settings [no] (y/n)?", "n", + fun verify_yes_or_no/1) of + yes -> + DefUserMod = ask("20b. Default user module?", + "snmpm_user_default", + fun verify_module/1), + DefUserData = ask("20c. Default user data?", "undefined", + fun verify_user_data/1), + [{def_user_mod, DefUserMod}, + {def_user_data, DefUserData}]; + no -> + [] + end, + SysConfig = + [{priority, Prio}, + {versions, Vsns}, + {config, [{dir, ConfigDir}, + {verbosity, ConfigVerb}, + {db_dir, ConfigDbDir}, + {repair, ConfigDbRepair}, + {auto_save, ConfigDbAutoSave}]}, + {inform_request_behaviour, IRB}, + {mibs, []}, + {server, [{timeout, ServerTimeout}, + {verbosity, ServerVerb}]}, + {note_store, [{timeout, NoteStoreTimeout}, + {verbosity, NoteStoreVerb}]}, + {net_if, NetIf}] ++ ATL ++ DefUser, + {Vsns, ConfigDir, SysConfig}. + + +config_manager_snmp(Dir, Vsns) -> + i("~nManager snmp config: " + "~n--------------------"), + EngineName = guess_engine_name(), + EngineID = ask("1. Engine ID (snmpEngineID standard variable)", + EngineName, fun verify_engine_id/1), + MMS = ask("2. Max message size?", "484", + fun verify_max_message_size/1), + Host = host(), + IP = ask("3. IP address for the manager (only used as id ~n" + " when sending requests)", + Host, fun verify_address/1), + Port = ask("4. Port number (standard 162)?", "5000", + fun verify_port_number/1), + Users = config_manager_snmp_users([]), + Agents = config_manager_snmp_agents([]), + Usms = config_manager_snmp_usms([]), + Overwrite = ask("8. Current configuration files will now be overwritten. " + "Ok (y/n)?", "y", fun verify_yes_or_no/1), + case Overwrite of + no -> + error(overwrite_not_allowed); + yes -> + ok + end, + case (catch write_manager_snmp_files(Dir, + IP, Port, MMS, EngineID, + Users, Agents, Usms)) of + ok -> + i("~n- - - - - - - - - - - - -"), + i("The following manager files were written: " + "manager.conf, agents.conf " ++ + case lists:member(v3, Vsns) of + true -> + ", users.conf and usm.conf"; + false -> + " and users.conf" + end), + i("- - - - - - - - - - - - -"), + ok; + E -> + error({failed_writing_files, E}) + end. + + +config_manager_snmp_users(Users) -> + case ask("5. Configure a user of this manager (y/n)?", + "y", fun verify_yes_or_no/1) of + yes -> + User = config_manager_snmp_user(), + config_manager_snmp_users([User|Users]); + no -> + lists:reverse(Users) + end. + +config_manager_snmp_user() -> + UserId = ask("5b. User id?", mandatory, + fun verify_user_id/1), + UserMod = ask("5c. User callback module?", mandatory, + fun verify_module/1), + UserData = ask("5d. User (callback) data?", "undefined", + fun verify_user_data/1), + {UserId, UserMod, UserData}. + + +config_manager_snmp_agents(Agents) -> + case ask("6. Configure an agent handled by this manager (y/n)?", + "y", fun verify_yes_or_no/1) of + yes -> + Agent = config_manager_snmp_agent(), + config_manager_snmp_agents([Agent|Agents]); + no -> + lists:reverse(Agents) + end. + +config_manager_snmp_agent() -> + UserId = ask("6b. User id?", mandatory, + fun verify_user_id/1), + TargetName = ask("6c. Target name?", guess_agent_name(), + fun verify_system_name/1), + Version = ask("6d. Version (1/2/3)?", "1", + fun verify_version/1), + Comm = ask("6e. Community string ?", "public", + fun verify_community/1), + EngineID = ask("6f. Engine ID (snmpEngineID standard variable)", + guess_engine_name(), fun verify_engine_id/1), + IP = ask("6g. IP address for the agent", host(), + fun verify_address/1), + Port = ask("6h. The UDP port the agent listens to. " + "(standard 161)", "4000", fun verify_port_number/1), + Timeout = ask("6i. Retransmission timeout (infinity/pos integer)?", + "infinity", fun verify_retransmission_timeout/1), + MMS = ask("6j. Max message size?", "484", + fun verify_max_message_size/1), + SecModel = ask("6k. Security model (any/v1/v2c/usm)?", "any", + fun verify_sec_model/1), + SecName = ask("6l. Security name?", "\"initial\"", + fun verify_sec_name/1), + SecLevel = ask("6m. Security level (noAuthNoPriv/authNoPriv/authPriv)?", + "noAuthNoPriv", fun verify_sec_level/1), + {UserId, + TargetName, Comm, IP, Port, EngineID, Timeout, MMS, + Version, SecModel, SecName, SecLevel}. + + +config_manager_snmp_usms(Usms) -> + case ask("7. Configure an usm user handled by this manager (y/n)?", + "y", fun verify_yes_or_no/1) of + yes -> + Usm = config_manager_snmp_usm(), + config_manager_snmp_usms([Usm|Usms]); + no -> + lists:reverse(Usms) + end. + +config_manager_snmp_usm() -> + EngineID = ask("7a. Engine ID", guess_engine_name(), + fun verify_engine_id/1), + UserName = ask("7b. User name?", mandatory, fun verify_usm_name/1), + SecName = ask("7c. Security name?", UserName, + fun verify_usm_sec_name/1), + AuthP = ask("7d. Authentication protocol (no/sha/md5)?", "no", + fun verify_usm_auth_protocol/1), + AuthKey = ask_auth_key("7e", AuthP), + PrivP = ask("7e. Priv protocol (no/des/aes)?", "no", + fun verify_usm_priv_protocol/1), + PrivKey = ask_priv_key("7f", PrivP), + {EngineID, UserName, + SecName, AuthP, AuthKey, PrivP, PrivKey}. + + +%% ------------------------------------------------------------------ + +is_members([], _Files) -> + true; +is_members([H|T], Files) -> + lists:member(H, Files) andalso is_members(T, Files). + +default_agent_dir(DefDir) -> + default_dir("agent", DefDir). + +default_manager_dir(DefDir) -> + default_dir("manager", DefDir). + +default_dir(Component, DefDir) -> + %% Look for the component dir, if found use that as default + {ok, Files} = file:list_dir(DefDir), + case lists:member(Component, Files) of + true -> + {filename:join(DefDir, Component), ""}; + false -> + %% No luck, + %% so check if cwd contains either agent and/or + %% manager config files. If it does, issue a warning + + %% Check for presence of agent config files + %% If all the agent config files are present, + %% issue a warning + AgentConfs = + [ + "agent.conf", + "context.conf", + "community.conf", + "notify.conf", + "standard.conf", + "target_params.conf", + "target_addr.conf", + "usm.conf", + "vacm.conf" + ], + IsAgentDir = is_members(AgentConfs, Files), + + %% Check for presence of manager config files + %% If all the manager config files are present, + %% issue a warning + ManagerConfs = + [ + "agents.conf", + "manager.conf", + "users.conf", + "usm.conf" + ], + IsManagerDir = is_members(ManagerConfs, Files), + Warning = + if + IsAgentDir and IsManagerDir -> + "Note that the default directory contains both agent and manager config files"; + IsAgentDir -> + "Note that the default directory contains agent config files"; + IsManagerDir -> + "Note that the default directory contains manager config files"; + true -> + "" + end, + {DefDir, Warning} + end. + + +%% ------------------------------------------------------------------ + +ask_auth_key(_Prefix, usmNoAuthProtocol) -> + ""; +ask_auth_key(Prefix, usmHMACSHAAuthProtocol) -> + ask(Prefix ++ " Authentication [sha] key (length 0 or 20)?", "\"\"", + fun verify_usm_auth_sha_key/1); +ask_auth_key(Prefix, usmHMACMD5AuthProtocol) -> + ask(Prefix ++ " Authentication [md5] key (length 0 or 16)?", "\"\"", + fun verify_usm_auth_md5_key/1). + +ask_priv_key(_Prefix, usmNoPrivProtocol) -> + ""; +ask_priv_key(Prefix, usmDESPrivProtocol) -> + ask(Prefix ++ " Priv [des] key (length 0 or 16)?", "\"\"", + fun verify_usm_priv_des_key/1); +ask_priv_key(Prefix, usmAesCfb128Protocol) -> + ask(Prefix ++ " Priv [aes] key (length 0 or 16)?", "\"\"", + fun verify_usm_priv_aes_key/1). + + +%% ------------------------------------------------------------------ + +verify_yes_or_no("y") -> + {ok, yes}; +verify_yes_or_no("yes") -> + {ok, yes}; +verify_yes_or_no("n") -> + {ok, no}; +verify_yes_or_no("no") -> + {ok, no}; +verify_yes_or_no(YON) -> + {error, "invalid yes or no: " ++ YON}. + + +verify_prio("low") -> + {ok, low}; +verify_prio("normal") -> + {ok, normal}; +verify_prio("high") -> + {ok, high}; +verify_prio(Prio) -> + {error, "invalid process priority: " ++ Prio}. + + +verify_system_name(Name) -> {ok, Name}. + + +verify_engine_id(Name) -> {ok, Name}. + + +verify_max_message_size(MMS) -> + case (catch list_to_integer(MMS)) of + I when is_integer(I) andalso (I >= 484) -> + {ok, I}; + I when is_integer(I) -> + {error, "invalid max message size (must be atleast 484): " ++ MMS}; + _ -> + {error, "invalid max message size: " ++ MMS} + end. + + +verify_port_number(P) -> + case (catch list_to_integer(P)) of + N when is_integer(N) andalso (N > 0) -> + {ok, N}; + _ -> + {error, "invalid port number: " ++ P} + end. + + +verify_versions("1") -> {ok, [v1]}; +verify_versions("2") -> {ok, [v2]}; +verify_versions("3") -> {ok, [v3]}; +verify_versions("1&2") -> {ok, [v1,v2]}; +verify_versions("1&3") -> {ok, [v1,v3]}; +verify_versions("2&3") -> {ok, [v2,v3]}; +verify_versions("1&2&3") -> {ok, [v1,v2,v3]}; +verify_versions(V) -> {error, "incorrect version(s): " ++ V}. + +verify_version("1") -> {ok, v1}; +verify_version("2") -> {ok, v2}; +verify_version("3") -> {ok, v3}; +verify_version(V) -> {error, "incorrect version: " ++ V}. + + +verify_passwd(Passwd) when length(Passwd) >= 8 -> + {ok, Passwd}; +verify_passwd(_P) -> + {error, "invalid password"}. + + +verify_dir(Dir) -> + case filename:pathtype(Dir) of + absolute -> + case file:read_file_info(Dir) of + {ok, #file_info{type = directory}} -> + {ok, snmp_misc:ensure_trailing_dir_delimiter(Dir)}; + {ok, _FileInfo} -> + {error, Dir ++ " is not a directory"}; + _ -> + {error, "invalid directory: " ++ Dir} + end; + _E -> + {error, "invalid directory (not absolute): " ++ Dir} + end. + + +verify_notif_type("trap") -> {ok, trap}; +verify_notif_type("inform") -> {ok, inform}; +verify_notif_type(NT) -> {error, "invalid notifcation type: " ++ NT}. + + +verify_sec_type("none") -> {ok, none}; +verify_sec_type("minimum") -> {ok, minimum}; +verify_sec_type("semi_des") -> {ok, {semi, des}}; +verify_sec_type("semi_aes") -> {ok, {semi, aes}}; +verify_sec_type(ST) -> {error, "invalid security type: " ++ ST}. + + +verify_address(A) -> + case (catch snmp_misc:ip(A)) of + {ok, IP} -> + {ok, tuple_to_list(IP)}; + {error, _} -> + {error, "invalid address: " ++ A}; + _E -> + {error, "invalid address: " ++ A} + end. + + +verify_mib_storage_type("m") -> + {ok, mnesia}; +verify_mib_storage_type("mnesia") -> + {ok, mnesia}; +verify_mib_storage_type("d") -> + {ok, dets}; +verify_mib_storage_type("dets") -> + {ok, dets}; +verify_mib_storage_type("e") -> + {ok, ets}; +verify_mib_storage_type("ets") -> + {ok, ets}; +verify_mib_storage_type(T) -> + {error, "invalid mib storage type: " ++ T}. + +verify_mib_storage_action("default") -> + {ok, default}; +verify_mib_storage_action("clear") -> + {ok, clear}; +verify_mib_storage_action("keep") -> + {ok, keep}; +verify_mib_storage_action(A) -> + {error, "invalid mib storage action: " ++ A}. + + +verify_verbosity("silence") -> + {ok, silence}; +verify_verbosity("info") -> + {ok, info}; +verify_verbosity("log") -> + {ok, log}; +verify_verbosity("debug") -> + {ok, debug}; +verify_verbosity("trace") -> + {ok, trace}; +verify_verbosity(V) -> + {error, "invalid verbosity: " ++ V}. + + +verify_dets_repair("true") -> + {ok, true}; +verify_dets_repair("false") -> + {ok, false}; +verify_dets_repair("force") -> + {ok, force}; +verify_dets_repair(R) -> + {error, "invalid repair: " ++ R}. + +verify_dets_auto_save("infinity") -> + {ok, infinity}; +verify_dets_auto_save(I0) -> + case (catch list_to_integer(I0)) of + I when is_integer(I) andalso (I > 0) -> + {ok, I}; + _ -> + {error, "invalid auto save timeout time: " ++ I0} + end. + + +%% I know that this is a little of the edge, but... +verify_module(M0) -> + case (catch list_to_atom(M0)) of + M when is_atom(M) -> + {ok, M}; + _ -> + {error, "invalid module: " ++ M0} + end. + + +verify_agent_type("master") -> + {ok, master}; +verify_agent_type("sub") -> + {ok, sub}; +verify_agent_type(AT) -> + {error, "invalid agent type: " ++ AT}. + + +verify_bool("true") -> + {ok, true}; +verify_bool("false") -> + {ok, false}; +verify_bool(B) -> + {error, "invalid boolean: " ++ B}. + + +verify_timeout(T0) -> + case (catch list_to_integer(T0)) of + T when is_integer(T) andalso (T > 0) -> + {ok, T}; + _ -> + {error, "invalid timeout time: '" ++ T0 ++ "'"} + end. + + +verify_retransmission_timeout("infinity") -> + {ok, infinity}; +verify_retransmission_timeout([${|R] = Timer) -> + case lists:reverse(R) of + [$}|R2] -> + case string:tokens(lists:reverse(R2), ", ") of + [WaitForStr, FactorStr, IncrStr, RetryStr] -> + WaitFor = incr_timer_value(WaitForStr, 1), + Factor = incr_timer_value(FactorStr, 1), + Incr = incr_timer_value(IncrStr, 0), + Retry = incr_timer_value(RetryStr, 0), + {ok, {WaitFor, Factor, Incr, Retry}}; + _ -> + {error, "invalid retransmission timer: '" ++ Timer ++ "'"} + end; + _ -> + {error, "invalid retransmission timer: '" ++ Timer ++ "'"} + end; +verify_retransmission_timeout(T0) -> + case (catch list_to_integer(T0)) of + T when is_integer(T) andalso (T > 0) -> + {ok, T}; + _ -> + {error, "invalid timeout time: '" ++ T0 ++ "'"} + end. + +incr_timer_value(Str, Min) -> + case (catch list_to_integer(Str)) of + I when is_integer(I) andalso (I >= Min) -> + I; + I when is_integer(I) -> + E = lists:flatten(io_lib:format("invalid incremental timer value " + "(min value is ~w): " ++ Str, + [Min])), + error(E); + _ -> + error("invalid incremental timer value: " ++ Str) + end. + + +%% verify_atl_type("read") -> +%% {ok, read}; +verify_atl_type("write") -> + {ok, write}; +verify_atl_type("read_write") -> + {ok, read_write}; +verify_atl_type(T) -> + {error, "invalid log type: " ++ T}. + +verify_atl_repair("true") -> + {ok, true}; +verify_atl_repair("false") -> + {ok, false}; +verify_atl_repair("truncate") -> + {ok, truncate}; +verify_atl_repair("snmp_repair") -> + {ok, snmp_repair}; +verify_atl_repair(R) -> + {error, "invalid audit trail log repair: " ++ R}. + + +verify_pos_integer(I0) -> + case (catch list_to_integer(I0)) of + I when is_integer(I) andalso (I > 0) -> + {ok, I}; + _ -> + {error, "invalid integer value: " ++ I0} + end. + + +verify_netif_req_limit("infinity") -> + {ok, infinity}; +verify_netif_req_limit(I0) -> + case (catch list_to_integer(I0)) of + I when is_integer(I) andalso (I > 0) -> + {ok, I}; + _ -> + {error, "invalid network interface request limit: " ++ I0} + end. + +verify_netif_recbuf(Val) -> + verify_netif_recbuf_or_sndbuf(Val, "recbuf"). + +verify_netif_sndbuf(Val) -> + verify_netif_recbuf_or_sndbuf(Val, "sndbuf"). + +verify_netif_recbuf_or_sndbuf("default", _) -> + {ok, default}; +verify_netif_recbuf_or_sndbuf(I0, Buf) -> + case (catch list_to_integer(I0)) of + I when is_integer(I) andalso (I > 0) -> + {ok, I}; + _ -> + {error, "invalid network interface " ++ Buf ++ " size: " ++ I0} + end. + + +verify_irb("auto") -> + {ok, auto}; +verify_irb("user") -> + {ok, user}; +verify_irb(IRB) -> + E = lists:flatten(io_lib:format("invalid irb: ~p", [IRB])), + {error, E}. + + +verify_irb_user("default") -> + {ok, default}; +verify_irb_user(TO) -> + case (catch list_to_integer(TO)) of + I when is_integer(I) andalso (I > 0) -> + {ok, I*1000}; % Time is given in seconds + _ -> + {error, "invalid IRB GC time: " ++ TO} + end. + + + +verify_user_id(UserId) when is_list(UserId) -> + case (catch list_to_atom(UserId)) of + A when is_atom(A) -> + {ok, A}; + _ -> + {error, "invalid user id: " ++ UserId} + end; +verify_user_id(UserId) when is_atom(UserId) -> + {ok, UserId}; +verify_user_id(UserId) -> + E = lists:flatten(io_lib:format("invalid user id: ~p", [UserId])), + {error, E}. + +verify_user_data("undefined") -> + {ok, undefined}; +verify_user_data(UserData) -> + {ok, UserData}. + + +verify_community("\"\"") -> + {ok, ""}; +verify_community(Comm) -> + {ok, Comm}. + + +% verify_context_name("\"\"") -> +% {ok, ""}; +% verify_context_name(Ctx) -> +% {ok, Ctx}. + + +% verify_mp_model("v1") -> +% {ok, v1}; +% verify_mp_model("v2c") -> +% {ok, v2c}; +% verify_mp_model("v3") -> +% {ok, v3}; +% verify_mp_model(M) -> +% {error, "invalid mp model: " ++ M}. + + +verify_sec_model("any") -> + {ok, any}; +verify_sec_model("v1") -> + {ok, v1}; +verify_sec_model("v2c") -> + {ok, v2c}; +verify_sec_model("usm") -> + {ok, usm}; +verify_sec_model(M) -> + {error, "invalid sec model: " ++ M}. + +verify_sec_name("\"initial\"") -> + {ok, "initial"}; +verify_sec_name(N) -> + {ok, N}. + + +verify_sec_level("noAuthNoPriv") -> + {ok, noAuthNoPriv}; +verify_sec_level("authNoPriv") -> + {ok, authNoPriv}; +verify_sec_level("authPriv") -> + {ok, authPriv}; +verify_sec_level(L) -> + {error, "invalid sec level: " ++ L}. + + +verify_usm_name(Name) -> + {ok, Name}. + +verify_usm_sec_name(Name) -> + {ok, Name}. + + +verify_usm_auth_protocol("no") -> + {ok, usmNoAuthProtocol}; +verify_usm_auth_protocol("sha") -> + {ok, usmHMACSHAAuthProtocol}; +verify_usm_auth_protocol("md5") -> + {ok, usmHMACMD5AuthProtocol}; +verify_usm_auth_protocol(AuthP) -> + {error, "invalid auth protocol: " ++ AuthP}. + +verify_usm_auth_sha_key(Key) -> + verify_usm_key("auth sha", Key, 20). + +verify_usm_auth_md5_key(Key) -> + verify_usm_key("auth md5", Key, 16). + +verify_usm_priv_protocol("no") -> + {ok, usmNoPrivProtocol}; +verify_usm_priv_protocol("des") -> + {ok, usmDESPrivProtocol}; +verify_usm_priv_protocol("aes") -> + {ok, usmAesCfb128Protocol}; +verify_usm_priv_protocol(AuthP) -> + {error, "invalid priv protocol: " ++ AuthP}. + +verify_usm_priv_des_key(Key) -> + verify_usm_key("priv des", Key, 16). + +verify_usm_priv_aes_key(Key) -> + verify_usm_key("priv aes", Key, 16). + +verify_usm_key(_What, "\"\"", _ExpectLength) -> + {ok, ""}; +verify_usm_key(_What, Key, ExpectLength) when length(Key) =:= ExpectLength -> + {ok, Key}; +verify_usm_key(What, [$[|RestKey] = Key0, ExpectLength) -> + case lists:reverse(RestKey) of + [$]|RevRestKey] -> + Key1 = lists:reverse(RevRestKey), + verify_usm_key2(What, Key1, ExpectLength); + _ -> + %% Its not a list ([...]) and its not the correct length, ... + {error, "invalid " ++ What ++ " key length: " ++ Key0} + end; +verify_usm_key(What, Key, ExpectLength) -> + verify_usm_key2(What, Key, ExpectLength). + +verify_usm_key2(What, Key0, ExpectLength) -> + case string:tokens(Key0, [$,]) of + Key when length(Key) =:= ExpectLength -> + convert_usm_key(Key, []); + _ -> + {error, "invalid " ++ What ++ " key length: " ++ Key0} + end. + +convert_usm_key([], Acc) -> + {ok, lists:reverse(Acc)}; +convert_usm_key([I|Is], Acc) -> + case (catch list_to_integer(I)) of + Int when is_integer(Int) -> + convert_usm_key(Is, [Int|Acc]); + _Err -> + {error, "invalid key number: " ++ I} + end. + + +% ip(Host) -> +% case catch snmp_misc:ip(Host) of +% {ok, IPtuple} -> tuple_to_list(IPtuple); +% {error, Reason} -> throw({error, Reason}); +% _Q -> throw({error, {"ip conversion failed", Host}}) +% end. + +% make_ip(Str) -> +% case catch snmp_misc:ip(Str) of +% {ok, IPtuple} -> tuple_to_list(IPtuple); +% _Q -> ip(Str) +% end. + + +print_q(Q, mandatory) -> + io:format(Q ++ " ",[]); +print_q(Q, Default) when is_list(Default) -> + io:format(Q ++ " [~s] ",[Default]). + +%% Defval = string() | mandatory +ask(Q, Default, Verify) when is_list(Q) andalso is_function(Verify) -> + print_q(Q, Default), + PrelAnsw = io:get_line(''), + Answer = + case remove_newline(PrelAnsw) of + "" when Default =/= mandatory -> Default; + "" -> ask(Q, Default, Verify); + A -> A + end, + case (catch Verify(Answer)) of + {ok, Answer2} -> + Answer2; + {error, ReasonStr} -> + i("ERROR: " ++ ReasonStr), + ask(Q, Default, Verify) + end. + + +host() -> + case (catch inet:gethostname()) of + {ok, Name} -> + case (catch inet:getaddr(Name, inet)) of + {ok, Addr} when is_tuple(Addr) -> + lists:flatten( + io_lib:format("~w.~w.~w.~w", tuple_to_list(Addr))); + _ -> + "127.0.0.1" + end; + _ -> + "127.0.0.1" + end. + +guess_agent_name() -> + case os:type() of + {unix, _} -> + lists:append(remove_newline(os:cmd("echo $USER")), "'s agent"); + {_,_} -> "my agent" + end. + +guess_engine_name() -> + case os:type() of + {unix, _} -> + lists:append(remove_newline(os:cmd("echo $USER")), "'s engine"); + {_,_} -> "my engine" + end. + +% guess_user_id() -> +% case os:type() of +% {unix, _} -> +% lists:append(remove_newline(os:cmd("echo $USER")), "'s user"); +% {_,_} -> "user_id" +% end. + + +remove_newline(Str) -> + lists:delete($\n, Str). + + +%%====================================================================== +%% File generation +%%====================================================================== + +%%---------------------------------------------------------------------- +%% Dir: string() (ex: "../conf/") +%% ManagerIP, AgentIP: [int(),int(),int(),int()] +%% TrapUdp, AgentUDP: integer() +%% SysName: string() +%%---------------------------------------------------------------------- +write_agent_snmp_files(Dir, Vsns, ManagerIP, TrapUdp, + AgentIP, AgentUDP, SysName) + when is_list(Dir) andalso + is_list(Vsns) andalso + is_list(ManagerIP) andalso + is_integer(TrapUdp) andalso + is_list(AgentIP) andalso + is_integer(AgentUDP) andalso + is_list(SysName) -> + write_agent_snmp_files(Dir, Vsns, ManagerIP, TrapUdp, AgentIP, AgentUDP, + SysName, "trap", none, "", "agentEngine", 484). + +%% +%% ----- Agent config files generator functions ----- +%% + +write_agent_snmp_files(Dir, Vsns, ManagerIP, TrapUdp, AgentIP, AgentUDP, + SysName, NotifType, SecType, Passwd, EngineID, MMS) -> + write_agent_snmp_conf(Dir, AgentIP, AgentUDP, EngineID, MMS), + write_agent_snmp_context_conf(Dir), + write_agent_snmp_community_conf(Dir), + write_agent_snmp_standard_conf(Dir, SysName), + write_agent_snmp_target_addr_conf(Dir, ManagerIP, TrapUdp, Vsns), + write_agent_snmp_target_params_conf(Dir, Vsns), + write_agent_snmp_notify_conf(Dir, NotifType), + write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd), + write_agent_snmp_vacm_conf(Dir, Vsns, SecType), + ok. + + +%% +%% ------ [agent] agent.conf ------ +%% + +write_agent_snmp_conf(Dir, AgentIP, AgentUDP, EngineID, MMS) -> + 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, + Conf = [{intAgentUDPPort, AgentUDP}, + {intAgentIpAddress, AgentIP}, + {snmpEngineID, EngineID}, + {snmpEngineMaxMessageSize, MMS}], + write_agent_config(Dir, Hdr, Conf). + +write_agent_config(Dir, Hdr, Conf) -> + snmpa_conf:write_agent_config(Dir, Hdr, Conf). + +update_agent_config(Dir, Conf) -> + snmpa_conf:append_agent_config(Dir, Conf). + + +%% +%% ------ [agent] context.conf ------ +%% + +write_agent_snmp_context_conf(Dir) -> + 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, + Conf = [""], + write_agent_context_config(Dir, Hdr, Conf). + +write_agent_context_config(Dir, Hdr, Conf) -> + snmpa_conf:write_context_config(Dir, Hdr, Conf). + +update_agent_context_config(Dir, Conf) -> + snmpa_conf:append_context_config(Dir, Conf). + + +%% +%% ------ community.conf ------ +%% + +write_agent_snmp_community_conf(Dir) -> + 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, + Conf = [{"public", "public", "initial", "", ""}, + {"all-rights", "all-rights", "all-rights", "", ""}, + {"standard trap", "standard trap", "initial", "", ""}], + write_agent_community_config(Dir, Hdr, Conf). + +write_agent_community_config(Dir, Hdr, Conf) -> + snmpa_conf:write_community_config(Dir, Hdr, Conf). + +update_agent_community_config(Dir, Conf) -> + snmpa_conf:append_community_config(Dir, Conf). + + +%% +%% ------ standard.conf ------ +%% + +write_agent_snmp_standard_conf(Dir, SysName) -> + 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, + Conf = [{sysDescr, "Erlang SNMP agent"}, + {sysObjectID, [1,2,3]}, + {sysContact, "{mbj,eklas}@erlang.ericsson.se"}, + {sysLocation, "erlang"}, + {sysServices, 72}, + {snmpEnableAuthenTraps, enabled}, + {sysName, SysName}], + write_agent_standard_config(Dir, Hdr, Conf). + +write_agent_standard_config(Dir, Hdr, Conf) -> + snmpa_conf:write_standard_config(Dir, Hdr, Conf). + +update_agent_standard_config(Dir, Conf) -> + snmpa_conf:append_standard_config(Dir, Conf). + + +%% +%% ------ target_addr.conf ------ +%% + +write_agent_snmp_target_addr_conf(Dir, ManagerIp, UDP, Vsns) -> + Timeout = 1500, + RetryCount = 3, + write_agent_snmp_target_addr_conf(Dir, ManagerIp, UDP, + Timeout, RetryCount, + Vsns). + +write_agent_snmp_target_addr_conf(Dir, ManagerIp, UDP, + Timeout, RetryCount, + Vsns) -> + 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, + F = fun(v1 = Vsn, Acc) -> + [{mk_ip(ManagerIp, Vsn), + ManagerIp, UDP, Timeout, RetryCount, + "std_trap", mk_param(Vsn), "", [], 2048}| Acc]; + (v2 = Vsn, Acc) -> + [{mk_ip(ManagerIp, Vsn), + ManagerIp, UDP, Timeout, RetryCount, + "std_trap", mk_param(Vsn), "", [], 2048}, + {lists:flatten(io_lib:format("~s.2",[mk_ip(ManagerIp, Vsn)])), + ManagerIp, UDP, Timeout, RetryCount, + "std_inform", mk_param(Vsn), "", [], 2048}| Acc]; + (v3 = Vsn, Acc) -> + [{mk_ip(ManagerIp, Vsn), + ManagerIp, UDP, Timeout, RetryCount, + "std_trap", mk_param(Vsn), "", [], 2048}, + {lists:flatten(io_lib:format("~s.3",[mk_ip(ManagerIp, Vsn)])), + ManagerIp, UDP, Timeout, RetryCount, + "std_inform", mk_param(Vsn), "mgrEngine", [], 2048}| Acc] + end, + Conf = lists:foldl(F, [], Vsns), + write_agent_target_addr_config(Dir, Hdr, Conf). + +mk_param(Vsn) -> + lists:flatten(io_lib:format("target_~w", [Vsn])). + +mk_ip([A,B,C,D], Vsn) -> + lists:flatten(io_lib:format("~w.~w.~w.~w ~w", [A,B,C,D,Vsn])). + +write_agent_target_addr_config(Dir, Hdr, Conf) -> + snmpa_conf:write_target_addr_config(Dir, Hdr, Conf). + +update_agent_target_addr_config(Dir, Conf) -> + snmpa_conf:append_target_addr_config(Dir, Conf). + + +%% +%% ------ target_params.conf ------ +%% + +write_agent_snmp_target_params_conf(Dir, Vsns) -> + 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, + Conf = [fun(V) -> + MP = if V == v1 -> v1; + V == v2 -> v2c; + V == v3 -> v3 + end, + SM = if V == v1 -> v1; + V == v2 -> v2c; + V == v3 -> usm + end, + Name = lists:flatten( + io_lib:format("target_~w", [V])), + {Name, MP, SM, "initial", noAuthNoPriv} + end(Vsn) || Vsn <- Vsns], + write_agent_target_params_config(Dir, Hdr, Conf). + +write_agent_target_params_config(Dir, Hdr, Conf) -> + snmpa_conf:write_target_params_config(Dir, Hdr, Conf). + +update_agent_target_params_config(Dir, Conf) -> + snmpa_conf:append_target_params_config(Dir, Conf). + + +%% +%% ------ notify.conf ------ +%% + +write_agent_snmp_notify_conf(Dir, NotifyType) -> + 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, + Conf = [{"stadard_trap", "std_trap", NotifyType}], + write_agent_notify_config(Dir, Hdr, Conf). + +write_agent_notify_config(Dir, Hdr, Conf) -> + snmpa_conf:write_notify_config(Dir, Hdr, Conf). + +update_agent_notify_config(Dir, Conf) -> + snmpa_conf:append_notify_config(Dir, Conf). + + +%% +%% ------ usm.conf ------ +%% + +write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd) -> + case lists:member(v3, Vsns) of + false -> ok; + true -> write_agent_snmp_usm_conf(Dir, EngineID, SecType, Passwd) + end. + +write_agent_snmp_usm_conf(Dir, EngineID, SecType, Passwd) -> + 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 14-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, + Conf = write_agent_snmp_usm_conf2(EngineID, SecType, Passwd), + write_agent_usm_config(Dir, Hdr, Conf). + +write_agent_snmp_usm_conf2(EngineID, none, _Passwd) -> + [{EngineID, "initial", "initial", zeroDotZero, + usmNoAuthProtocol, "", "", + usmNoPrivProtocol, "", "", + "", "", ""}]; +write_agent_snmp_usm_conf2(EngineID, SecType, Passwd) -> + Secret16 = agent_snmp_mk_secret(md5, Passwd, EngineID), + Secret20 = agent_snmp_mk_secret(sha, Passwd, EngineID), + {PrivProt, PrivSecret} = + case SecType of + minimum -> + {usmNoPrivProtocol, ""}; + {semi, des} -> + {usmDESPrivProtocol, Secret16}; + {semi, aes} -> + {usmAesCfb128Protocol, Secret16} + end, + [{EngineID, "initial", "initial", zeroDotZero, + usmHMACMD5AuthProtocol, "", "", + PrivProt, "", "", + "", Secret16, PrivSecret}, + + {EngineID, "templateMD5", "templateMD5", zeroDotZero, + usmHMACMD5AuthProtocol, "", "", + PrivProt, "", "", + "", Secret16, PrivSecret}, + + {EngineID, "templateSHA", "templateSHA", zeroDotZero, + usmHMACSHAAuthProtocol, "", "", + PrivProt, "", "", + "", Secret20, PrivSecret}]. + +write_agent_usm_config(Dir, Hdr, Conf) -> + snmpa_conf:write_usm_config(Dir, Hdr, Conf). + +update_agent_usm_config(Dir, Conf) -> + snmpa_conf:append_usm_config(Dir, Conf). + + +%% +%% ------ vacm.conf ------ +%% + +write_agent_snmp_vacm_conf(Dir, Vsns, SecType) -> + 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 = lists:flatten(header()) ++ Comment, + Groups = + lists:foldl( + fun(V, Acc) -> + [{vacmSecurityToGroup, vacm_ver(V), + "initial", "initial"}, + {vacmSecurityToGroup, vacm_ver(V), + "all-rights", "all-rights"}| + Acc] + end, [], Vsns), + Acc = + [{vacmAccess, "initial", "", any, noAuthNoPriv, exact, + "restricted", "", "restricted"}, + {vacmAccess, "initial", "", usm, authNoPriv, exact, + "internet", "internet", "internet"}, + {vacmAccess, "initial", "", usm, authPriv, exact, + "internet", "internet", "internet"}, + {vacmAccess, "all-rights", "", any, noAuthNoPriv, exact, + "internet", "internet", "internet"}], + VTF0 = + case SecType of + none -> + [{vacmViewTreeFamily, + "restricted", [1,3,6,1], included, null}]; + minimum -> + [{vacmViewTreeFamily, + "restricted", [1,3,6,1], included, null}]; + {semi, _} -> + [{vacmViewTreeFamily, + "restricted", [1,3,6,1,2,1,1], included, null}, + {vacmViewTreeFamily, + "restricted", [1,3,6,1,2,1,11], included, null}, + {vacmViewTreeFamily, + "restricted", [1,3,6,1,6,3,10,2,1], included, null}, + {vacmViewTreeFamily, + "restricted", [1,3,6,1,6,3,11,2,1], included, null}, + {vacmViewTreeFamily, + "restricted", [1,3,6,1,6,3,15,1,1], included, null}] + end, + VTF = VTF0 ++ [{vacmViewTreeFamily,"internet",[1,3,6,1],included,null}], + write_agent_vacm_config(Dir, Hdr, Groups ++ Acc ++ VTF). + +vacm_ver(v1) -> v1; +vacm_ver(v2) -> v2c; +vacm_ver(v3) -> usm. + +write_agent_vacm_config(Dir, Hdr, Conf) -> + snmpa_conf:write_vacm_config(Dir, Hdr, Conf). + +update_agent_vacm_config(Dir, Conf) -> + snmpa_conf:append_vacm_config(Dir, Conf). + + +%% +%% ----- Manager config files generator functions ----- +%% + +write_manager_snmp_files(Dir, IP, Port, MMS, EngineID, + Users, Agents, Usms) -> + write_manager_snmp_conf(Dir, IP, Port, MMS, EngineID), + write_manager_snmp_users_conf(Dir, Users), + write_manager_snmp_agents_conf(Dir, Agents), + write_manager_snmp_usm_conf(Dir, Usms), + ok. + + +%% +%% ------ manager.conf ------ +%% + +write_manager_snmp_conf(Dir, IP, Port, MMS, EngineID) -> + Comment = +"%% This file defines the Manager local configuration info\n" +"%% Each row is a 2-tuple:\n" +"%% {Variable, Value}.\n" +"%% For example\n" +"%% {port, 5000}.\n" +"%% {address, [127,42,17,5]}.\n" +"%% {engine_id, \"managerEngine\"}.\n" +"%% {max_message_size, 484}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + Conf = [{port, Port}, + {address, IP}, + {engine_id, EngineID}, + {max_message_size, MMS}], + write_manager_config(Dir, Hdr, Conf). + +write_manager_config(Dir, Hdr, Conf) -> + snmpm_conf:write_manager_config(Dir, Hdr, Conf). + +update_manager_config(Dir, Conf) -> + snmpm_conf:append_manager_config(Dir, Conf). + + +%% +%% ------ users.conf ------ +%% + +write_manager_snmp_users_conf(Dir, Users) -> + Comment = +"%% This file defines the users the manager handles\n" +"%% Each row is a 3-tuple:\n" +"%% {UserId, UserMod, UserData}.\n" +"%% For example\n" +"%% {kalle, kalle_callback_user_mod, \"dummy\"}.\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_manager_users_config(Dir, Hdr, Users). + +write_manager_users_config(Dir, Hdr, Users) -> + snmpm_conf:write_users_config(Dir, Hdr, Users). + +update_manager_users_config(Dir, Users) -> + snmpm_conf:append_users_config(Dir, Users). + + +%% +%% ------ agents.conf ------ +%% + +write_manager_snmp_agents_conf(Dir, Agents) -> + Comment = +"%% This file defines the agents the manager handles\n" +"%% Each row is a 12-tuple:\n" +"%% {UserId, \n" +"%% TargetName, Comm, Ip, Port, EngineID, Timeout, \n" +"%% MaxMessageSize, Version, SecModel, SecName, SecLevel}\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_manager_agents_config(Dir, Hdr, Agents). + +write_manager_agents_config(Dir, Hdr, Agents) -> + snmpm_conf:write_agents_config(Dir, Hdr, Agents). + +update_manager_agents_config(Dir, Agents) -> + snmpm_conf:append_agents_config(Dir, Agents). + + +%% +%% ------ usm.conf ----- +%% + +write_manager_snmp_usm_conf(Dir, Usms) -> + Comment = +"%% This file defines the usm users the manager handles\n" +"%% Each row is a 6 or 7-tuple:\n" +"%% {EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey}\n" +"%% {EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey}\n" +"%%\n\n", + Hdr = header() ++ Comment, + write_manager_usm_config(Dir, Hdr, Usms). + +write_manager_usm_config(Dir, Hdr, Usms) -> + snmpm_conf:write_usm_config(Dir, Hdr, Usms). + +update_manager_usm_config(Dir, Usms) -> + snmpm_conf:append_usm_config(Dir, Usms). + + +%% +%% ------------------------------------------------------------------------- +%% + +write_sys_config_file(Dir, Services) -> + {ok, Fid} = file:open(filename:join(Dir,"sys.config"), [write]), + ok = io:format(Fid, "~s", [header()]), + ok = io:format(Fid, "[{snmp, ~n", []), + ok = io:format(Fid, " [~n", []), + write_sys_config_file_services(Fid, Services), + ok = io:format(Fid, " ]~n", []), + ok = io:format(Fid, " }~n", []), + ok = io:format(Fid, "].~n", []), + ok. + +write_sys_config_file_services(Fid, [Service]) -> + write_sys_config_file_service(Fid, Service), + ok = io:format(Fid, "~n", []), + ok; +write_sys_config_file_services(Fid, [Service|Services]) -> + write_sys_config_file_service(Fid, Service), + ok = io:format(Fid, ", ~n", []), + write_sys_config_file_services(Fid, Services). + +write_sys_config_file_service(Fid, {Service, Opts}) -> + ok = io:format(Fid, " {~w,~n", [Service]), + ok = io:format(Fid, " [~n", []), + write_sys_config_file_service_opts(Fid, Service, Opts), + ok = io:format(Fid, " ]~n", []), + ok = io:format(Fid, " }", []), + true. + +write_sys_config_file_service_opts(Fid, agent, Opts) -> + write_sys_config_file_agent_opts(Fid, Opts); +write_sys_config_file_service_opts(Fid, manager, Opts) -> + write_sys_config_file_manager_opts(Fid, Opts). + + +write_sys_config_file_agent_opts(Fid, [Opt]) -> + write_sys_config_file_agent_opt(Fid, Opt), + ok = io:format(Fid, "~n", []), + ok; +write_sys_config_file_agent_opts(Fid, [Opt|Opts]) -> + write_sys_config_file_agent_opt(Fid, Opt), + ok = io:format(Fid, ", ~n", []), + write_sys_config_file_agent_opts(Fid, Opts). + + +write_sys_config_file_agent_opt(Fid, {mibs, []}) -> + ok = io:format(Fid, " {mibs, []}", []); +write_sys_config_file_agent_opt(Fid, {priority, Prio}) -> + ok = io:format(Fid, " {priority, ~w}", [Prio]); +write_sys_config_file_agent_opt(Fid, {error_report_mod, Mod}) -> + ok = io:format(Fid, " {error_report_mod, ~w}", [Mod]); +write_sys_config_file_agent_opt(Fid, {versions, Vsns}) -> + ok = io:format(Fid, " {versions, ~w}", [Vsns]); +write_sys_config_file_agent_opt(Fid, {multi_threaded, B}) -> + ok = io:format(Fid, " {multi_threaded, ~w}", [B]); +write_sys_config_file_agent_opt(Fid, {config, Opts}) -> + ok = io:format(Fid, " {config, [", []), + write_sys_config_file_agent_config_opts(Fid, Opts), + ok = io:format(Fid, "}", []); +write_sys_config_file_agent_opt(Fid, {db_dir, Dir}) -> + ok = io:format(Fid, " {db_dir, \"~s\"}", [Dir]); +write_sys_config_file_agent_opt(Fid, {mib_storage, ets}) -> + ok = io:format(Fid, " {mib_storage, ets}", []); +write_sys_config_file_agent_opt(Fid, {mib_storage, {dets, Dir}}) -> + ok = io:format(Fid, " {mib_storage, {dets, \"~s\"}}", [Dir]); +write_sys_config_file_agent_opt(Fid, {mib_storage, {dets, Dir, Act}}) -> + ok = io:format(Fid, " {mib_storage, {dets, \"~s\", ~w}}", + [Dir, Act]); +write_sys_config_file_agent_opt(Fid, {mib_storage, {mnesia, Nodes}}) -> + ok = io:format(Fid, " {mib_storage, {mnesia, ~w}}", [Nodes]); +write_sys_config_file_agent_opt(Fid, {mib_storage, {mnesia, Nodes, Act}}) -> + ok = io:format(Fid, " {mib_storage, {mnesia, ~w, ~w}}", + [Nodes, Act]); +write_sys_config_file_agent_opt(Fid, {target_cache, Opts}) -> + ok = io:format(Fid, " {target_cache, ~w}", [Opts]); +write_sys_config_file_agent_opt(Fid, {local_db, Opts}) -> + ok = io:format(Fid, " {local_db, ~w}", [Opts]); +write_sys_config_file_agent_opt(Fid, {note_store, Opts}) -> + ok = io:format(Fid, " {note_store, ~w}", [Opts]); +write_sys_config_file_agent_opt(Fid, {symbolic_store, Opts}) -> + ok = io:format(Fid, " {symbolic_store, ~w}", [Opts]); +write_sys_config_file_agent_opt(Fid, {agent_type, Type}) -> + ok = io:format(Fid, " {agent_type, ~w}", [Type]); +write_sys_config_file_agent_opt(Fid, {agent_verbosity, Verb}) -> + ok = io:format(Fid, " {agent_verbosity, ~w}", [Verb]); +write_sys_config_file_agent_opt(Fid, {audit_trail_log, Opts}) -> + ok = io:format(Fid, " {audit_trail_log, [", []), + write_sys_config_file_agent_atl_opts(Fid, Opts), + ok = io:format(Fid, "}", []); +write_sys_config_file_agent_opt(Fid, {net_if, Opts}) -> + ok = io:format(Fid, " {net_if, ~w}", [Opts]); +write_sys_config_file_agent_opt(Fid, {mib_server, Opts}) -> + ok = io:format(Fid, " {mib_server, ~w}", [Opts]); +write_sys_config_file_agent_opt(Fid, {Key, Val}) -> + ok = io:format(Fid, " {~w, ~w}", [Key, Val]). + + +%% Mandatory option dir, means that this is never empty: +write_sys_config_file_agent_config_opts(Fid, [Opt]) -> + write_sys_config_file_agent_config_opt(Fid, Opt), + ok = io:format(Fid, "]", []), + ok; +write_sys_config_file_agent_config_opts(Fid, [Opt|Opts]) -> + write_sys_config_file_agent_config_opt(Fid, Opt), + ok = io:format(Fid, ", ", []), + write_sys_config_file_agent_config_opts(Fid, Opts). + +write_sys_config_file_agent_config_opt(Fid, {dir, Dir}) -> + ok = io:format(Fid, "{dir, \"~s\"}", [Dir]); +write_sys_config_file_agent_config_opt(Fid, {force_load, Bool}) -> + ok = io:format(Fid, "{force_load, ~w}", [Bool]); +write_sys_config_file_agent_config_opt(Fid, {verbosity, Verb}) -> + ok = io:format(Fid, "{verbosity, ~w}", [Verb]). + + +%% This is only present if there is atleast one option +write_sys_config_file_agent_atl_opts(Fid, [Opt]) -> + write_sys_config_file_agent_atl_opt(Fid, Opt), + ok = io:format(Fid, "]", []), + ok; +write_sys_config_file_agent_atl_opts(Fid, [Opt|Opts]) -> + write_sys_config_file_agent_atl_opt(Fid, Opt), + ok = io:format(Fid, ", ", []), + write_sys_config_file_agent_atl_opts(Fid, Opts). + +write_sys_config_file_agent_atl_opt(Fid, {dir, Dir}) -> + ok = io:format(Fid, "{dir, \"~s\"}", [Dir]); +write_sys_config_file_agent_atl_opt(Fid, {type, Type}) -> + ok = io:format(Fid, "{type, ~w}", [Type]); +write_sys_config_file_agent_atl_opt(Fid, {size, Size}) -> + ok = io:format(Fid, "{size, ~w}", [Size]); +write_sys_config_file_agent_atl_opt(Fid, {repair, Rep}) -> + ok = io:format(Fid, "{repair, ~w}", [Rep]). + + +write_sys_config_file_manager_opts(Fid, [Opt]) -> + write_sys_config_file_manager_opt(Fid, Opt), + ok = io:format(Fid, "~n", []), + ok; +write_sys_config_file_manager_opts(Fid, [Opt|Opts]) -> + write_sys_config_file_manager_opt(Fid, Opt), + ok = io:format(Fid, ", ~n", []), + write_sys_config_file_manager_opts(Fid, Opts). + + +write_sys_config_file_manager_opt(Fid, {mibs, []}) -> + ok = io:format(Fid, " {mibs, []}", []); +write_sys_config_file_manager_opt(Fid, {priority, Prio}) -> + ok = io:format(Fid, " {priority, ~w}", [Prio]); +write_sys_config_file_manager_opt(Fid, {versions, Vsns}) -> + ok = io:format(Fid, " {versions, ~w}", [Vsns]); +write_sys_config_file_manager_opt(Fid, {config, Opts}) -> + ok = io:format(Fid, " {config, [", []), + write_sys_config_file_manager_config_opts(Fid, Opts), + ok = io:format(Fid, "}", []); +write_sys_config_file_manager_opt(Fid, {server, Opts}) -> + ok = io:format(Fid, " {server, ~w}", [Opts]); +write_sys_config_file_manager_opt(Fid, {note_store, Opts}) -> + ok = io:format(Fid, " {note_store, ~w}", [Opts]); +write_sys_config_file_manager_opt(Fid, {audit_trail_log, Opts}) -> + ok = io:format(Fid, " {audit_trail_log, [", []), + write_sys_config_file_manager_atl_opts(Fid, Opts), + ok = io:format(Fid, "}", []); +write_sys_config_file_manager_opt(Fid, {net_if, Opts}) -> + ok = io:format(Fid, " {net_if, ~w}", [Opts]); +write_sys_config_file_manager_opt(Fid, {Key, Val}) -> + ok = io:format(Fid, " {~w, ~w}", [Key, Val]). + +%% Mandatory option dir, means that this is never empty: +write_sys_config_file_manager_config_opts(Fid, [Opt]) -> + write_sys_config_file_manager_config_opt(Fid, Opt), + ok = io:format(Fid, "]", []), + ok; +write_sys_config_file_manager_config_opts(Fid, [Opt|Opts]) -> + write_sys_config_file_manager_config_opt(Fid, Opt), + ok = io:format(Fid, ", ", []), + write_sys_config_file_manager_config_opts(Fid, Opts). + +write_sys_config_file_manager_config_opt(Fid, {dir, Dir}) -> + ok = io:format(Fid, "{dir, \"~s\"}", [Dir]); +write_sys_config_file_manager_config_opt(Fid, {db_dir, Dir}) -> + ok = io:format(Fid, "{db_dir, \"~s\"}", [Dir]); +write_sys_config_file_manager_config_opt(Fid, {repair, Rep}) -> + ok = io:format(Fid, "{repair, ~w}", [Rep]); +write_sys_config_file_manager_config_opt(Fid, {auto_save, As}) -> + ok = io:format(Fid, "{auto_save, ~w}", [As]); +write_sys_config_file_manager_config_opt(Fid, {verbosity, Verb}) -> + ok = io:format(Fid, "{verbosity, ~w}", [Verb]). + + +%% This is only present if there is atleast one option +write_sys_config_file_manager_atl_opts(Fid, [Opt]) -> + write_sys_config_file_manager_atl_opt(Fid, Opt), + ok = io:format(Fid, "]", []), + ok; +write_sys_config_file_manager_atl_opts(Fid, [Opt|Opts]) -> + write_sys_config_file_manager_atl_opt(Fid, Opt), + ok = io:format(Fid, ", ", []), + write_sys_config_file_manager_atl_opts(Fid, Opts). + +write_sys_config_file_manager_atl_opt(Fid, {dir, Dir}) -> + ok = io:format(Fid, "{dir, \"~s\"}", [Dir]); +write_sys_config_file_manager_atl_opt(Fid, {type, Type}) -> + ok = io:format(Fid, "{type, ~w}", [Type]); +write_sys_config_file_manager_atl_opt(Fid, {size, Size}) -> + ok = io:format(Fid, "{size, ~w}", [Size]); +write_sys_config_file_manager_atl_opt(Fid, {repair, Rep}) -> + ok = io:format(Fid, "{repair, ~w}", [Rep]). + + +header() -> + {Y,Mo,D} = date(), + {H,Mi,S} = time(), + io_lib:format("%% This file was generated by " + "snmp_config (version-~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]). + + +write_config_file(Dir, FileName, Verify, Write) + when (is_list(Dir) andalso + is_list(FileName) andalso + is_function(Verify) andalso + is_function(Write)) -> + (catch do_write_config_file(Dir, FileName, Verify, Write)). + +do_write_config_file(Dir, FileName, Verify, Write) -> + Verify(), + case file:open(filename:join(Dir, FileName), [write]) of + {ok, Fd} -> + (catch Write(Fd)), + file:close(Fd), + ok; + Error -> + Error + end. + + +append_config_file(Dir, FileName, Verify, Write) + when (is_list(Dir) andalso + is_list(FileName) andalso + is_function(Verify) andalso + is_function(Write)) -> + (catch do_append_config_file(Dir, FileName, Verify, Write)). + +do_append_config_file(Dir, FileName, Verify, Write) -> + Verify(), + case file:open(filename:join(Dir, FileName), [read, write]) of + {ok, Fd} -> + file:position(Fd, eof), + (catch Write(Fd)), + file:close(Fd), + ok; + Error -> + Error + end. + + +read_config_file(Dir, FileName, Verify) + when is_list(Dir) andalso is_list(FileName) andalso is_function(Verify) -> + (catch do_read_config_file(Dir, FileName, Verify)). + +do_read_config_file(Dir, FileName, Verify) -> + case file:open(filename:join(Dir, FileName), [read]) of + {ok, Fd} -> + Result = read_loop(Fd, [], Verify, 1), + file:close(Fd), + Result; + {error, Reason} -> + {error, {Reason, FileName}} + end. + +read_loop(Fd, Acc, Check, StartLine) -> + case read_term(Fd, StartLine) of + {ok, Term, EndLine} -> + case (catch Check(Term)) of + ok -> + read_loop(Fd, [Term | Acc], Check, EndLine); + {error, Reason} -> + {error, {failed_check, StartLine, EndLine, Reason}}; + Error -> + {error, {failed_check, StartLine, EndLine, Error}} + end; + {error, EndLine, Error} -> + {error, {failed_reading, StartLine, EndLine, Error}}; + eof -> + {ok, lists:reverse(Acc)} + end. + +read_term(Fd, StartLine) -> + case io:request(Fd, {get_until, "", erl_scan, tokens, [StartLine]}) of + {ok, Tokens, EndLine} -> + case erl_parse:parse_term(Tokens) of + {ok, Term} -> + {ok, Term, EndLine}; + {error, {Line, erl_parse, Error}} -> + {error, Line, {parse_error, Error}} + end; + {error, E, EndLine} -> + {error, EndLine, E}; + {eof, _EndLine} -> + eof; + Other -> + Other + end. + + +agent_snmp_mk_secret(Alg, Passwd, EngineID) -> + snmp_usm:passwd2localized_key(Alg, Passwd, EngineID). + + +ensure_crypto_started() -> + i("making sure crypto server is started..."), + ensure_started(crypto). + +ensure_started(App) -> + case (catch App:start()) of + ok -> + ok; + {error, {already_started, App}} -> + ok; + E -> + error({failed_starting, App, E}) + end. + + +%% ------------------------------------------------------------------------- + +% d(F, A) -> +% i("DBG: " ++ F, A). + +i(F) -> + i(F, []). + +i(F, A) -> + io:format(F ++ "~n", A). + +error(R) -> + throw({error, R}). diff --git a/lib/snmp/src/misc/snmp_debug.hrl b/lib/snmp/src/misc/snmp_debug.hrl new file mode 100644 index 0000000000..dc916ac96a --- /dev/null +++ b/lib/snmp/src/misc/snmp_debug.hrl @@ -0,0 +1,38 @@ +%% +%% %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% +%% + +-ifdef(snmp_debug). +-define(d(F,A), + io:format("~p:~p:~p:" ++ F ++ "~n",[self(),?MODULE,?LINE]++A)). + +%% Same as 'd' but without the ending newline ('~n'). +-define(d_b(F,A), + io:format("~p:~p:~p:" ++ F,[self(),?MODULE,?LINE]++A)). +%% To be used together with 'd_b'. Note: NO ending newline ('~n').. +-define(d_e(F,A), + io:format(F,A)). +-else. +-define(d(F,A),ok). +-define(d_b(F,A),ok). +-define(d_e(F,A),ok). +-endif. + + + + diff --git a/lib/snmp/src/misc/snmp_log.erl b/lib/snmp/src/misc/snmp_log.erl new file mode 100644 index 0000000000..c3932ccc08 --- /dev/null +++ b/lib/snmp/src/misc/snmp_log.erl @@ -0,0 +1,584 @@ +%% +%% %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_log). + + +-export([ + create/4, create/5, + change_size/2, close/1, sync/1, info/1, + log/4, + log_to_txt/5, log_to_txt/6, log_to_txt/7, + log_to_io/4, log_to_io/5, log_to_io/6 + ]). + + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). + +-define(VMODULE,"LOG"). +-include("snmp_verbosity.hrl"). + +-define(LOG_FORMAT, internal). +-define(LOG_TYPE, wrap). + + +%% -------------------------------------------------------------------- +%% Exported functions +%% -------------------------------------------------------------------- + + +%% -- create --- + +create(Name, File, Size, Repair) -> + create(Name, File, Size, Repair, false). + +create(Name, File, Size, Repair, Notify) -> + ?vtrace("create -> entry with" + "~n Name: ~p" + "~n File: ~p" + "~n Size: ~p" + "~n Repair: ~p" + "~n Notify: ~p", [Name, File, Size, Repair, Notify]), + log_open(Name, File, Size, Repair, Notify). + + +%% -- close --- + +close(Log) -> + ?vtrace("close -> entry with" + "~n Log: ~p", [Log]), + disk_log:close(Log). + + +%% -- close --- + +sync(Log) -> + ?vtrace("sync -> entry with" + "~n Log: ~p", [Log]), + disk_log:sync(Log). + +%% -- info --- + +info(Log) -> + case disk_log:info(Log) of + Info when is_list(Info) -> + Items = [no_current_bytes, no_current_items, + current_file, no_overflows], + info_filter(Items, Info, []); + Else -> + Else + end. + +info_filter([], _Info, Acc) -> + {ok, Acc}; +info_filter([Item|Items], Info, Acc) -> + case lists:keysearch(Item, 1, Info) of + {value, New} -> + info_filter(Items, Info, [New|Acc]); + false -> + info_filter(Items, Info, Acc) + end. + + +%% -- log --- + +%%----------------------------------------------------------------- +%% For efficiency reasons, we want to log the packet as a binary. +%% This is only possible for messages that are not encrypted. +%% Therefore, Packet can be either a binary (encoded message), or +%% a tuple {V3Hdr, ScopedPduBytes} +%% +%% log(Log, Packet, Addr, Port) +%%----------------------------------------------------------------- + + +log(Log, Packet, Addr, Port) -> + ?vtrace("log -> entry with" + "~n Log: ~p" + "~n Addr: ~p" + "~n Port: ~p", [Log, Addr, Port]), + Entry = {timestamp(), Packet, Addr, Port}, + disk_log:alog(Log, Entry). + + + +%% -- change_size --- + +change_size(Log, NewSize) -> + ?vtrace("change_size -> entry with" + "~n Log: ~p" + "~n NewSize: ~p", [Log, NewSize]), + disk_log:change_size(Log, NewSize). + + +%% -- log_to_txt --- + +log_to_txt(Log, FileName, Dir, Mibs, TextFile) -> + log_to_txt(Log, FileName, Dir, Mibs, TextFile, null, null). + +log_to_txt(Log, FileName, Dir, Mibs, TextFile, Start) -> + log_to_txt(Log, FileName, Dir, Mibs, TextFile, Start, null). + +log_to_txt(Log, FileName, Dir, Mibs, TextFile, Start, Stop) + when is_list(Mibs) andalso is_list(TextFile) -> + ?vtrace("log_to_txt -> entry with" + "~n Log: ~p" + "~n FileName: ~p" + "~n Dir: ~p" + "~n Mibs: ~p" + "~n TextFile: ~p" + "~n Start: ~p" + "~n Stop: ~p", + [Log, FileName, Dir, Mibs, TextFile, Start, Stop]), + File = filename:join(Dir, FileName), + Converter = fun(L) -> + do_log_to_file(L, TextFile, Mibs, Start, Stop) + end, + log_convert(Log, File, Converter). + + +%% -- log_to_io --- + +log_to_io(Log, FileName, Dir, Mibs) -> + log_to_io(Log, FileName, Dir, Mibs, null, null). + +log_to_io(Log, FileName, Dir, Mibs, Start) -> + log_to_io(Log, FileName, Dir, Mibs, Start, null). + +log_to_io(Log, FileName, Dir, Mibs, Start, Stop) + when is_list(Mibs) -> + File = filename:join(Dir, FileName), + Converter = fun(L) -> + do_log_to_io(L, Mibs, Start, Stop) + end, + log_convert(Log, File, Converter). + + +%% -------------------------------------------------------------------- +%% Internal functions +%% -------------------------------------------------------------------- + + +%% -- log_convert --- + +log_convert(Log, File, Converter) -> + %% First check if the caller process has already opened the + %% log, because if we close an already open log we will cause + %% a runtime error. + case is_owner(Log) of + true -> + Converter(Log); + false -> + %% Not yet member of the ruling party, apply for membership... + %% If a log is opened as read_write it is not possible to + %% open it as read_only. So, to get around this we open + %% it under a different name... + Log2 = convert_name(Log), + case log_open(Log2, File) of + {ok, _} -> + Res = Converter(Log2), + disk_log:close(Log2), + Res; + {error, {name_already_open, _}} -> + Converter(Log2); + {error, Reason} -> + {error, {Log, Reason}} + end + end. + +convert_name(Name) when is_list(Name) -> + Name ++ "_tmp"; +convert_name(Name) when is_atom(Name) -> + list_to_atom(atom_to_list(Name) ++ "_tmp"); +convert_name(Name) -> + lists:flatten(io_lib:format("~w_tmp", [Name])). + + +%% -- do_log_to_text --- + +do_log_to_file(Log, TextFile, Mibs, Start, Stop) -> + case file:open(TextFile, [write]) of + {ok, Fd} -> + MiniMib = snmp_mini_mib:create(Mibs), + Write = fun(X) -> + case format_msg(X, MiniMib, Start, Stop) of + {ok, S} -> + io:format(Fd, "~s", [S]); + _ -> + ok + end + end, + Res = (catch loop(disk_log:chunk(Log, start), Log, Write)), + snmp_mini_mib:delete(MiniMib), + file:close(Fd), + Res; + {error, Reason} -> + {error, {TextFile, Reason}} + end. + + +do_log_to_io(Log, Mibs, Start, Stop) -> + MiniMib = snmp_mini_mib:create(Mibs), + Write = fun(X) -> + case format_msg(X, MiniMib, Start, Stop) of + {ok, S} -> + io:format("~s", [S]); + _ -> + ok + end + end, + (catch loop(disk_log:chunk(Log, start), Log, Write)), + snmp_mini_mib:delete(MiniMib), + ok. + + +loop(eof, _Log, _Write) -> + ok; +loop({error, _} = Error, _Log, _Write) -> + Error; +loop({corrupt_log_file, _} = Reason, _Log, _Write) -> + {error, Reason}; +loop({Cont, Terms}, Log, Write) -> + case (catch lists:foreach(Write, Terms)) of + {'EXIT', Reason} -> + {error, Reason}; + _ -> + loop(disk_log:chunk(Log, Cont), Log, Write) + end; +loop({Cont, Terms, BadBytes}, Log, Write) -> + error_logger:error_msg("Skipping ~w bytes while converting ~p~n~n", + [BadBytes, Log]), + case (catch lists:foreach(Write, Terms)) of + {'EXIT', Reason} -> + {error, Reason}; + _ -> + loop(disk_log:chunk(Log, Cont), Log, Write) + end; +loop(Error, _Log, _Write) -> + Error. + +format_msg({TimeStamp, {V3Hdr, ScopedPdu}, {Addr, Port}}, + Mib, Start, Stop) -> + format_msg({TimeStamp, {V3Hdr, ScopedPdu}, Addr, Port}, + Mib, Start, Stop); +format_msg({TimeStamp, {V3Hdr, ScopedPdu}, Addr, Port}, + Mib, Start, Stop) -> +% io:format("format_msg -> entry with" +% "~n TimeStamp: ~p" +% "~n Start: ~p" +% "~n Stop: ~p", [TimeStamp, Start, Stop]), + case timestamp_filter(TimeStamp, Start, Stop) of + true -> + case (catch snmp_pdus:dec_scoped_pdu(ScopedPdu)) of + ScopedPDU when is_record(ScopedPDU, scopedPdu) -> + Msg = #message{version = 'version-3', + vsn_hdr = V3Hdr, + data = ScopedPDU}, + f(ts2str(TimeStamp), Msg, Addr, Port, Mib); + {'EXIT', Reason} -> + format_tab("** error in log file at ~s from ~p:~w ~p\n\n", + [ts2str(TimeStamp), ip(Addr), Port, Reason]) + end; + false -> + ignore + end; +format_msg({TimeStamp, Packet, {Addr, Port}}, Mib, Start, Stop) -> + format_msg({TimeStamp, Packet, Addr, Port}, Mib, Start, Stop); +format_msg({TimeStamp, Packet, Addr, Port}, Mib, Start, Stop) -> + case timestamp_filter(TimeStamp, Start, Stop) of + true -> + case (catch snmp_pdus:dec_message(binary_to_list(Packet))) of + Msg when is_record(Msg, message) -> + f(ts2str(TimeStamp), Msg, Addr, Port, Mib); + {'EXIT', Reason} -> + format_tab("** error in log file ~p\n\n", [Reason]) + end; + false -> + ignore + end; +format_msg(_, _Mib, _Start, _Stop) -> + format_tab("** unknown entry in log file\n\n", []). + +f(TimeStamp, #message{version = Vsn, vsn_hdr = VsnHdr, data = Data}, + Addr, Port, Mib) -> + Str = format_pdu(Data, Mib), + HdrStr = format_header(Vsn, VsnHdr), + case get_type(Data) of + trappdu -> + f_trap(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + 'snmpv2-trap' -> + f_trap(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + 'inform-request' -> + f_inform(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + 'get-response' -> + f_response(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + report -> + f_report(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + _ -> + f_request(TimeStamp, Vsn, HdrStr, Str, Addr, Port) + end. + +f_request(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("request ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + +f_response(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("response ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + +f_report(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("report ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + +f_trap(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("trap ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + +f_inform(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("inform ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + + +%% Convert a timestamp 2-tupple to a printable string +%% +ts2str({Local,Universal}) -> + dat2str(Local) ++ " , " ++ dat2str(Universal); +ts2str(_) -> + "". + +%% Convert a datetime 2-tupple to a printable string +%% +dat2str({{Y,M,D},{H,Min,S}}) -> + io_lib:format("~w-~w-~w,~w:~w:~w",[Y,M,D,H,Min,S]). + + +timestamp_filter({Local,Universal},Start,Stop) -> + tsf_ge(Local,Universal,Start) and tsf_le(Local,Universal,Stop); +timestamp_filter(_,_Start,_Stop) -> + true. + +tsf_ge(_Local,_Universal,null) -> + true; +tsf_ge(Local,_Universal,{local_time,DateTime}) -> + tsf_ge(Local,DateTime); +tsf_ge(_Local,Universal,{universal_time,DateTime}) -> + tsf_ge(Universal,DateTime); +tsf_ge(Local,_Universal,DateTime) -> + tsf_ge(Local,DateTime). + +tsf_ge(TimeStamp,DateTime) -> + T1 = calendar:datetime_to_gregorian_seconds(TimeStamp), + T2 = calendar:datetime_to_gregorian_seconds(DateTime), + T1 >= T2. + +tsf_le(_Local,_Universal,null) -> + true; +tsf_le(Local,_Universal,{local_time,DateTime}) -> + tsf_le(Local,DateTime); +tsf_le(_Local,Universal,{universal_time,DateTime}) -> + tsf_le(Universal,DateTime); +tsf_le(Local,_Universal,DateTime) -> + tsf_le(Local,DateTime). + +tsf_le(TimeStamp,DateTime) -> + T1 = calendar:datetime_to_gregorian_seconds(TimeStamp), + T2 = calendar:datetime_to_gregorian_seconds(DateTime), + T1 =< T2. + + +%% In the output replace TAB by ESC TAB, and add a single trailing TAB. +%% +format_tab(Format, Args) -> + Str = lists:flatten(io_lib:format(Format, Args)), + DStr = lists:map(fun($\t) -> "\e\t"; (C) -> C end, Str), + {ok, io_lib:format("~s\t", [DStr])}. + + +format_header('version-1', CommunityStr) -> + CommunityStr; +format_header('version-2', CommunityStr) -> + CommunityStr; +format_header('version-3', #v3_hdr{msgFlags = MsgFlags, + msgSecurityModel = SecModel, + msgSecurityParameters = SecParams}) -> + SecLevel = snmp_misc:get_sec_level(MsgFlags), + case SecModel of + ?SEC_USM -> + case catch snmp_pdus:dec_usm_security_parameters(SecParams) of + #usmSecurityParameters{msgAuthoritativeEngineID = AuthEngineID, + msgUserName = UserName} -> + io_lib:format("~w:\"~s\":\"~s\"", + [SecLevel, AuthEngineID, UserName]); + _ -> + "-" + end; + _ -> + "\"unknown security model\"" + end. + + +format_pdu(#scopedPdu{contextName = Context, data = Pdu}, Mib) -> + io_lib:format("Context: \"~s\"\n~s", + [Context, snmp_misc:format_pdu(Pdu, Mib)]); +format_pdu(Pdu, Mib) -> + snmp_misc:format_pdu(Pdu, Mib). + +get_type(#scopedPdu{data = Pdu}) -> + get_type(Pdu); +get_type(Pdu) when is_record(Pdu, trappdu) -> + trappdu; +get_type(#pdu{type = Type}) -> + Type. + + +ip({A,B,C,D}) -> + io_lib:format("~w.~w.~w.~w", [A,B,C,D]). + + + +%% ------------------------------------------------------------------- +%% Various utility functions +%% ------------------------------------------------------------------- + +log_open(Name, File, Size, Repair, Notify) -> + case do_log_open(Name, File, Size, Repair, Notify) of + {ok, Log} -> + {ok, Log}; + {repaired, Log, Rec, Bad} -> + ?vlog("log_open -> repaired: " + "~n Rec: ~p" + "~n Bad: ~p", [Rec, Bad]), + {ok, Log}; + Error -> + Error + end. + +%% We need to make sure we do not end up in an infinit loop +%% Take the number of files of the wrap log and add 2 (for +%% the index and size files). +do_log_open(Name, File, {_, N} = Size, snmp_repair = _Repair, Notify) -> + do_snmp_log_open(Name, File, Size, N+2, Notify); + +do_log_open(Name, File, Size, snmp_repair = _Repair, Notify) -> + do_snmp_log_open(Name, File, Size, 1, Notify); + +do_log_open(Name, File, Size, Repair, Notify) -> + do_std_log_open(Name, File, Size, Repair, Notify). + + +do_snmp_log_open(Name, File, Size, N, Notify) when N =< 0 -> + do_std_log_open(Name, File, Size, true, Notify); +do_snmp_log_open(Name, File, Size, N, Notify) -> + case do_std_log_open(Name, File, Size, true, Notify) of + {error, {not_a_log_file, XFile}} -> + case file:rename(XFile, lists:append([XFile, ".MOVED"])) of + ok -> + ?vinfo("Failed open log file (even with repair) - " + "not a logfile:" + "~n Attempting to move file aside (.MOVED)" + "~n ~s", [XFile]), + do_snmp_log_open(Name, File, Size, N-1, Notify); + Error -> + {error, {rename_failed, Error}} + end; + {error, Reason} -> + ?vinfo("Failed open log file (even with repair) - " + "~n Attempting to move old log file aside (.MOVED)" + "~n~p", [Reason]), + move_log(File), + do_std_log_open(Name, File, Size, true, Notify); + Else -> + Else + end. + + +%% First try to open the log without the size-spec. This will +%% succeed if the log has already been created. In that case, +%% we'll use whatever size the log had at the time it was closed. +do_std_log_open(Name, File, Size, Repair, Notify) -> + Opts = [{name, Name}, + {file, File}, + {type, ?LOG_TYPE}, + {format, ?LOG_FORMAT}, + {mode, read_write}, + {notify, Notify}, + {repair, Repair}], + case disk_log:open(Opts) of + {error, {badarg, size}} -> + %% The log didn't exist, try with the size-spec + disk_log:open([{size, Size} | Opts]); + Else -> + Else + end. + + +log_open(Name, File) -> + Opts = [{name, Name}, + {file, File}, + {type, ?LOG_TYPE}, + {format, ?LOG_FORMAT}, + {mode, read_only}], + case disk_log:open(Opts) of + {error, {badarg, size}} -> + {error, no_such_log}; + Else -> + Else + end. + + +move_log(File) -> + Dir = filename:dirname(File), + FileName = filename:basename(File), + case file:list_dir(Dir) of + {ok, Files0} -> + Files = [F || F <- Files0, lists:prefix(FileName, F)], + F = fun(XFile) -> + file:rename(XFile, lists:append([XFile, ".MOVED"])) + end, + lists:foreach(F, Files); + _ -> + ok + end. + + +is_owner(Log) -> + lists:member(self(), log_owners(Log)). + +log_owners(Log) -> + Info = log_info(Log), + case lists:keysearch(owners, 1, Info) of + {value, {_, Pids}} -> + [P || {P, _} <- Pids]; + _ -> + [] + end. + +log_info(Log) -> + case disk_log:info(Log) of + Info when is_list(Info) -> + Info; + _ -> + [] + end. + + +timestamp() -> + {calendar:local_time(), calendar:universal_time()}. + diff --git a/lib/snmp/src/misc/snmp_mini_mib.erl b/lib/snmp/src/misc/snmp_mini_mib.erl new file mode 100644 index 0000000000..d80270d5c2 --- /dev/null +++ b/lib/snmp/src/misc/snmp_mini_mib.erl @@ -0,0 +1,151 @@ +%% +%% %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(snmp_mini_mib). + +%% need definition of mib record +-include("snmp_types.hrl"). + +-export([ + create/1, + delete/1, + aliasname/2, + oid/2, + type/2 + ]). + + +-record(mini_mib, {cache, db = []}). + + +%%%-------------------------------------------------- +%%% The Mini MIB representation +%%%-------------------------------------------------- + +%% Returns a Mini MIB +create(Mibs) -> + Loaded = lists:append([load_mib(Mib) || Mib <- Mibs]), + Sorted = lists:keysort(1, Loaded), + Db = remove_dubbletts(Sorted), + Cache = ets:new(snmp_mini_mib_cache, [set, {keypos, 1}]), + #mini_mib{cache = Cache, + db = Db}. + +delete(#mini_mib{cache = Cache}) -> + ets:delete(Cache), + ok. + + +%%---------------------------------------------------------------------- +%% Returns: A list of {Oid, Aliasname, Type} +%%---------------------------------------------------------------------- +load_mib(MIB) -> + F1 = snmp_misc:strip_extension_from_filename(MIB, ".bin"), + ActualFileName = lists:append(F1, ".bin"), + case snmp_misc:read_mib(ActualFileName) of + {ok, #mib{mes = MEs, traps = Traps}} -> + make_mini_mib_elem(MEs++Traps); + {error, Reason} -> + exit({error, {MIB, Reason}}) + end. + + +%%---------------------------------------------------------------------- +%% Pre: List is sorted (dublettes are list neighbours) +%%---------------------------------------------------------------------- +remove_dubbletts([]) -> []; +remove_dubbletts([X]) -> [X]; +remove_dubbletts([X,X|T]) -> remove_dubbletts([X|T]); +remove_dubbletts([X|T]) -> [X|remove_dubbletts(T)]. + + +%%---------------------------------------------------------------------- +%% Args: A list if Mes +%% Returns: a list of {Oid, Aliasname, Type} +%%---------------------------------------------------------------------- +make_mini_mib_elem([]) -> []; +make_mini_mib_elem([#me{aliasname = N, + oid = Oid, + entrytype = variable, + asn1_type = #asn1_type{bertype = Type}} | T]) -> + [{Oid, N, Type} | make_mini_mib_elem(T)]; +make_mini_mib_elem([#me{aliasname = N, + oid = Oid, + entrytype = table_column, + asn1_type = ASN1}|T]) + when is_record(ASN1, asn1_type)-> + [{Oid, N, ASN1#asn1_type.bertype} | make_mini_mib_elem(T)]; +make_mini_mib_elem([#me{aliasname = N, + oid = Oid, + asn1_type = undefined}|T]) -> + [{Oid, N, undefined} | make_mini_mib_elem(T)]; +make_mini_mib_elem([#notification{trapname = N, + oid = Oid}|T]) -> + [{Oid, N, undefined} | make_mini_mib_elem(T)]; +make_mini_mib_elem([_|T]) -> + make_mini_mib_elem(T). + + +%%---------------------------------------------------------------------- +%% Returns: false | {Oid, Aliasname, Type} +%%---------------------------------------------------------------------- + +aliasname(#mini_mib{cache = Cache, db = Db}, Oid) -> + Key = {Oid, aliasname}, + case ets:lookup(Cache, Key) of + [{_, Value}] -> + Value; + _ -> + Value = aliasname(Db, Oid, false), + ets:insert(Cache, {Key, Value}), + Value + end. + +aliasname([], _Oid, Res) -> + Res; +aliasname([{Oid, _Aliasname, _Type} = OidData|T], OidX, Res) + when Oid =< OidX -> + case lists:prefix(Oid, OidX) of + true -> + aliasname(T, OidX, OidData); + false -> + aliasname(T, OidX, Res) + end; +aliasname([{_Oid, _Aliasname, _Type}|_T], _OidX, Res) -> + Res. + + +oid(#mini_mib{db = Db}, AliasName) -> + case lists:keysearch(AliasName, 2, Db) of + {value, {Oid, _Aliasname, _Type}} -> + Oid; + false -> + false + end. + + +type(MiniMIB, Oid) -> + case aliasname(MiniMIB, Oid) of + {_Oid, _AliasName, Type} -> + Type; + Else -> + Else + end. + + diff --git a/lib/snmp/src/misc/snmp_misc.erl b/lib/snmp/src/misc/snmp_misc.erl new file mode 100644 index 0000000000..1b535743a4 --- /dev/null +++ b/lib/snmp/src/misc/snmp_misc.erl @@ -0,0 +1,465 @@ +%% +%% %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_misc). + +%% need definition of mib record +-include("snmp_types.hrl"). +-include("snmpc_misc.hrl"). + +-define(VMODULE,"MISC"). +-include("snmp_verbosity.hrl"). + + +-export([assq/2, + bits_to_int/2, + diff/2, + ensure_trailing_dir_delimiter/1, + foreach/3, + format_pdu/2, + format_val/4, + format_vb/2, + format_vbs/2, + format/3, + get_option/2, + get_option/3, + get_sec_level/1, + ip/1, + is_auth/1, + is_BitString/1, + is_oid/1, + is_priv/1, + is_reportable/1, + is_reportable_pdu/1, + is_string/1, + is_tag_member/2, + is_tmask_match/3, + keyreplaceadd/4, + mem_size/1, + mk_msg_flags/2, + multi_map/2, + %% now/0, + now/1, + read_mib/1, + set_option/3, + sleep/1, + strip_extension_from_filename/2, + str_xor/2, + time/3, + + verify_behaviour/2 + ]). + + +verify_behaviour(Behaviour, UserMod) + when is_atom(Behaviour) andalso is_atom(UserMod) -> + case (catch UserMod:module_info(exports)) of + Exps when is_list(Exps) -> + Callbacks = Behaviour:behaviour_info(callbacks), + (catch verify_behaviour2(Callbacks, Exps)); + _ -> + {error, {bad_module, UserMod}} + end; +verify_behaviour(_, BadModule) -> + {error, {bad_module, BadModule}}. + +verify_behaviour2([], _) -> + ok; +verify_behaviour2([{Func, Arity} = FuncArity|Callbacks], Exps) -> + case lists:member(FuncArity, Exps) of + true -> + verify_behaviour2(Callbacks, Exps); + false -> + throw({error, {bad_module, {function, Func, Arity}}}) + end. + + +sleep(Time) -> + receive + after Time -> + true + end. + + +%% Returns time in ms = sec/1000 +% now() -> now(ms). +now(ms) -> + Now = erlang:now(), + element(1,Now)*1000000000+ + element(2,Now)*1000+ + (element(3,Now) div 1000); +%% Returns time in cs = sec/100 +now(cs) -> + Now = erlang:now(), + element(1,Now)*100000000+ + element(2,Now)*100+ + (element(3,Now) div 10000); +now(sec) -> + Now = erlang:now(), + element(1,Now)*1000000+ + element(2,Now)+ + (element(3,Now) div 1000000). + + +is_string([]) -> true; +is_string([Tkn | Str]) + when is_integer(Tkn) andalso (Tkn >= 0) andalso (Tkn =< 255) -> + is_string(Str); +is_string(_) -> false. + + +is_oid([E1, E2| Rest]) + when (length(Rest) =< 126) andalso (E1 *40 + E2 =< 255) -> + is_oid2(Rest); +is_oid([E1]) when E1 =< 2 -> + true; +is_oid(_) -> false. + +is_oid2([]) -> true; +is_oid2([Nbr | RestOid]) + when is_integer(Nbr) andalso (0 =< Nbr) andalso (Nbr =< 2147483647) -> + is_oid2(RestOid); +is_oid2(_) -> false. + +is_BitString([]) -> true; +is_BitString([Nbr | RestBitstring]) + when is_integer(Nbr) andalso (Nbr >= 0) andalso (Nbr =< 1) -> + is_BitString(RestBitstring); +is_BitString(_) -> false. + + +%% Check if a Tag is a member in a TagList. Tags and TagLists are defined +%% in SNMP-TARGET-MIB +is_tag_member(Tag, TagList) -> + check_tag_list(TagList, [], lists:reverse(Tag)). + +check_tag_list([32 | T], Res, Gat) -> + tag_delimiter_found(Res, Gat, T); +check_tag_list([9 | T], Res, Gat) -> + tag_delimiter_found(Res, Gat, T); +check_tag_list([13 | T], Res, Gat) -> + tag_delimiter_found(Res, Gat, T); +check_tag_list([11 | T], Res, Gat) -> + tag_delimiter_found(Res, Gat, T); +check_tag_list([Char | T], Res, Gat) -> + check_tag_list(T, [Char | Res], Gat); +check_tag_list([], Res, Gat) -> + tag_delimiter_found(Res, Gat, []). + +tag_delimiter_found(Gat, Gat, _T) -> + true; +tag_delimiter_found(_Res, _Gat, []) -> + false; +tag_delimiter_found(_Res, Gat, T) -> + check_tag_list(T, [], Gat). + + +%% Pre: length(TAddr1) == length(TAddr2) +%% length(TMask) == 0 | length(TAddr1) +is_tmask_match(_TAddr1, _TAddr2, []) -> + true; +is_tmask_match([H1 | T1], [H2 | T2], [M1 | M2]) -> + if + (H1 band M1) == (H2 band M1) -> + is_tmask_match(T1, T2, M2); + true -> + false + end. + + +%%-------------------------------------------------- +%% Not a real assq, but what the heck, it's useful. +%%-------------------------------------------------- +assq(Key, List) -> + case lists:keysearch(Key, 1, List) of + {value, {Key, Val}} -> {value, Val}; + _ -> false + end. + +get_option(Key, Options) -> + case lists:keysearch(Key, 1, Options) of + {value, {_Key, Value}} -> + Value; + _ -> + throw({error, {not_found, Key}}) + end. + +get_option(Key, Options, Default) -> + case lists:keysearch(Key, 1, Options) of + {value, {_Key, Value}} -> + Value; + _ -> + Default + end. + +set_option(Key, Val, Opts) -> + keyreplaceadd(Key, 1, Opts, {Key, Val}). + +keyreplaceadd(Key, Pos, List, New) -> + case lists:keysearch(Key, Pos, List) of + {value, _} -> lists:keyreplace(Key, Pos, List, New); + _ -> [New | List] + end. + +is_auth(SecLevel) -> + 1 == (SecLevel band 1). + +is_priv(SecLevel) -> + 2 == (SecLevel band 2). + +is_reportable([MsgFlag]) -> + 4 == (MsgFlag band 4). + +%% [OTP-3416] +%% [RFC 2571] Confirmed Class: GetRequest-PDU, GetNextRequest-PDU, +%% GetBulkRequest-PDU, SetRequest-PDU, and InformRequest-PDU. +%% Unconfirmed Class: Report-PDU, Trapv2-PDU, and GetResponse-PDU. +%% [RFC 2572] The reportableFlag MUST always be zero when the message +%% contains a PDU from the Unconfirmed Class; it MUST always be one +%% for a PDU from the Confirmed Class, +%% +is_reportable_pdu('get-request') -> true; +is_reportable_pdu('get-next-request') -> true; +is_reportable_pdu('get-bulk-request') -> true; +is_reportable_pdu('set-request') -> true; +is_reportable_pdu('inform-request') -> true; +is_reportable_pdu(_) -> false. + +mk_msg_flags(PduType, SecLevel) -> + Flags1 = case is_reportable_pdu(PduType) of + true -> 4; + false -> 0 + end, + [Flags1 bor SecLevel]. + +get_sec_level([Flag]) -> + SecLevel = Flag band 3, + case {is_auth(SecLevel), is_priv(SecLevel)} of + {false, false} -> noAuthNoPriv; + {true, false} -> authNoPriv; + {true, true} -> authPriv + end. + + +%% diff(L1, L2) -> L1 - L2. +%% Ex. [1, 2, 3, 4] - [1, 3, 4] = [2, 3, 4] +diff(L1, []) -> L1; +diff([H | T1], [H | T2]) -> diff(T1, T2); +diff(L1, _) -> L1. + +foreach(Function, ExtraArgs, [H | T]) -> + apply(Function, [H | ExtraArgs]), + foreach(Function, ExtraArgs, T); +foreach(_Function, _ExtraArgs, []) -> true. + +str_xor([H1|T1], [H2|T2]) -> + [H1 bxor H2 | str_xor(T1, T2)]; +str_xor([], []) -> + []. + + +%%----------------------------------------------------------------- +%% Pre: ListOfLists is a list of N lists, each of length M. +%% Func is a function of arity N. +%% Returns: A list of length M where element Y is the result of +%% applying Func on [Elem(Y, List1), ..., Elem(Y, ListN)]. +%%----------------------------------------------------------------- +multi_map(_Func, [[] | _ListOfLists]) -> + []; +multi_map(Func, ListOfLists) -> + HD = [hd(L) || L <- ListOfLists], + TL = [tl(L) || L <- ListOfLists], +%% io:format("multi_map -> " +%% "~n HD: ~p" +%% "~n TL: ~p", [HD, TL]), + [ + apply(Func, HD) | multi_map(Func, TL) + ]. + +%% Primitive performance analysis. +time(M,F,A) -> + statistics(runtime), + R = apply(M, F, A), + {R, statistics(runtime)}. + +%% How much memory is allocated for X? At least some kind of upper estimation... +mem_size(X) -> + E = ets:new(tmp, [set, protected]), + M1 = ets:info(E, memory), + ets:insert(E, {make_ref(), X}), + M2 = ets:info(E, memory), + ets:delete(E), + M2 - M1. + + +strip_extension_from_filename(FileName, Ext) when is_atom(FileName) -> + strip_extension_from_filename(atom_to_list(FileName), Ext); + +strip_extension_from_filename(FileName, Ext) when is_list(FileName) -> + case lists:suffix(Ext, FileName) of + true -> lists:sublist(FileName, 1, length(FileName) - length(Ext)); + false -> FileName + end. + + +%%---------------------------------------------------------------------- +%% Returns: {ok, Mib}|{error, Reason} +%% +%%---------------------------------------------------------------------- +read_mib(FileName) -> + (catch do_read_mib(FileName)). + +do_read_mib(FileName) -> + ?read_mib(FileName). + + +%%---------------------------------------------------------------------- +%% Converts a list of named bits to the integer value. +%% Returns: integer()|error +%%---------------------------------------------------------------------- +bits_to_int(Val,Kibbles) -> + bits_to_int(Val,Kibbles,0). + +bits_to_int([],_Kibbles,Res) -> Res; +bits_to_int([Kibble|Ks],Kibbles,Res) -> + case snmp_misc:assq(Kibble,Kibbles) of + {value,V} -> + bits_to_int(Ks,Kibbles,Res + round(math:pow(2,V))); + _ -> + error + end. + + +%%---------------------------------------------------------------------- +%% Returns: {ok, {int(),int(),int(),int()}} | {error, Reason} +%%---------------------------------------------------------------------- +ip(Host) -> + inet:getaddr(Host, inet). + +ensure_trailing_dir_delimiter([]) -> "/"; +ensure_trailing_dir_delimiter(DirSuggestion) -> + case lists:last(DirSuggestion) of + $/ -> DirSuggestion; + _ -> lists:append(DirSuggestion,"/") + end. + + +format_pdu(PDU, MiniMib) when is_record(PDU, pdu) -> + #pdu{type = T, + error_status = ES, + error_index = EI, + request_id = RID, + varbinds = VBs} = PDU, + Txt1 = if + (ES =:= noError) andalso (EI =:= 0) -> + ""; + (T =:= 'get-bulk-request') -> + ""; + true -> + io_lib:format("*!*!* An error occured. *!*!* ~n" + "Error status = ~w, index = ~w.~n", + [ES, EI]) + end, + Txt2 = if T =:= 'snmpv2-trap' -> + io_lib:format("v2 Trap, Request Id:~w~n", [RID]); + T =:= 'get-request' -> + io_lib:format("Get request, Request Id:~w~n", [RID]); + T =:= 'get-next-request' -> + io_lib:format("Get-Next request, Request Id:~w~n", [RID]); + T =:= 'get-bulk-request' -> + io_lib:format("Get-Bulk request, Request Id:~w~n" + " Non-repeaters = ~w~n" + " Max-repetitions = ~w~n", [RID, ES, EI]); + T =:= 'set-request' -> + io_lib:format("Set request, Request Id:~w~n", [RID]); + T =:= 'get-response' -> + io_lib:format("Response, Request Id:~w~n", [RID]); + T =:= 'inform-request' -> + io_lib:format("Inform Request Request Id:~w~n", [RID]); + T =:= report -> + io_lib:format("Report Request Id:~w~n", [RID]); + true -> + "" + end, + [Txt1, Txt2, format_vbs(VBs, MiniMib)|"\n"]; + +format_pdu(#trappdu{enterprise = Enterprise, + agent_addr = AgentAddr, + generic_trap = GenericTrap, + specific_trap = SpecificTrap, + time_stamp = TimeStamp, + varbinds = VBs}, MiniMib) -> + [io_lib:format("v1 Trap~n" + " Generic: ~w~n" + " Enterprise: ~w~n" + " Specific: ~w~n" + " Agent addr: ~w~n" + " TimeStamp: ~w~n", + [GenericTrap, + element(1,symbolify_oid(MiniMib,Enterprise)),SpecificTrap, + AgentAddr, TimeStamp]), + format_vbs(VBs, MiniMib) | "\n"]. + +format_vbs(Vbs, MiniMib) -> + [format_vb(VB, MiniMib) || VB <- Vbs]. + +format_vb(#varbind{oid = Oid, + variabletype = Type, + value = Value}, MiniMib) -> + {Soid, Mtype} = symbolify_oid(MiniMib, Oid), + [io_lib:format(" ~w = ", [Soid]), + format_val(Type, Mtype, Value, MiniMib) | "\n"]. + +format(Max, F, A) when is_integer(Max) -> + case lists:flatten(io_lib:format(F,A)) of + S when length(S) > Max -> + case lists:suffix("\n", S) of + true -> + lists:concat([lists:sublist(S,Max), "...\n"]); + false -> + lists:concat([lists:sublist(S,Max), "..."]) + end; + S -> + S + end. + + +%%---------------------------------------------------------------------- +%% Returns: (a nested) symbolified oid. +%%---------------------------------------------------------------------- +symbolify_oid(MiniMib, Oid) -> + case snmp_mini_mib:aliasname(MiniMib, Oid) of + false -> + {Oid, unknown}; + {FoundOid, Aliasname, Type} -> + Rest = snmp_misc:diff(Oid, FoundOid), + {[Aliasname| Rest], Type} + end. + +format_val('OCTET STRING', 'BITS', Val, _MiniMib) -> + io_lib:format("~w", [snmp_pdus:octet_str_to_bits(Val)]); +format_val('OBJECT IDENTIFIER', _, Val, MiniMib) -> + {NVal, _} = symbolify_oid(MiniMib, Val), + io_lib:format("~w", [NVal]); +format_val(_, _, Val, _MiniMib) -> + io_lib:format("~p", [Val]). + + + + diff --git a/lib/snmp/src/misc/snmp_note_store.erl b/lib/snmp/src/misc/snmp_note_store.erl new file mode 100644 index 0000000000..a21a6209f1 --- /dev/null +++ b/lib/snmp/src/misc/snmp_note_store.erl @@ -0,0 +1,450 @@ +%% +%% %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_note_store). + +-behaviour(gen_server). + +-include_lib("snmp/src/app/snmp_internal.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). + +%% External exports +-export([start_link/3, stop/1, + get_note/2, + set_note/3, set_note/4, + info/1, verbosity/2]). + +%% Internal exports +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3]). + +-export([timer/3]). + +-define(timeout, 30000). % Perform gc twice in a minute. + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + +-ifdef(snmp_debug). +-define(GS_START_LINK(Args), + gen_server:start_link(?MODULE, Args, [{debug,[trace]}])). +-else. +-define(GS_START_LINK(Args), + gen_server:start_link(?MODULE, Args, [])). +-endif. + + +-record(state, {mod, notes, timer, timeout, active = false}). + + +%%%----------------------------------------------------------------- +%%% Implements a database for notes with a lifetime. Once in a +%%% while, the database will be gc:ed, to get rid of old notes. +%%% This database will not contain much data. +%%% Options is a list of Option, where Option is +%%% {verbosity, silence|log|debug|trace} % undocumented feature +%%%----------------------------------------------------------------- +start_link(Prio, Mod, Opts) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Mod: ~p" + "~n Opts: ~p", [Prio, Mod, Opts]), + ?GS_START_LINK([Prio, Mod, Opts]). + + +%%----------------------------------------------------------------- +%% Interface functions. +%%----------------------------------------------------------------- + +stop(Pid) -> + call(Pid, stop). + +get_note(Pid, Key) -> + call(Pid, {get_note, Key}). + +%% Lifetime is in 1/10 sec or infinity +set_note(Pid, Key, Value) -> + set_note(Pid, infinity, Key, Value). +set_note(Pid, Lifetime, Key, Value) -> + call(Pid, {set_note, Lifetime, Key, Value}). + +info(Pid) -> + call(Pid, info). + +verbosity(Pid, Verbosity) -> + cast(Pid, {verbosity, Verbosity}). + + +init([Prio, Mod, Opts]) -> + ?d("init -> entry with" + "~n Prio: ~p" + "~n Mod: ~p" + "~n Opts: ~p", [Prio, Mod, Opts]), + case (catch do_init(Prio, Mod, Opts)) of + {ok, State} -> + {ok, State}; + E -> + error_msg("failed starting note-store: ~n~p", [E]), + {stop, E} + end. + +do_init(Prio, Mod, Opts) -> + process_flag(trap_exit, true), + process_flag(priority, Prio), + put(sname, get_sname(Opts)), + put(verbosity, get_verbosity(Opts)), + put(snmp_component, get_component(Mod)), + ?vlog("starting",[]), + Notes = ets:new(snmp_note_store, [set, protected]), + Timeout = get_timeout(Opts), + State = #state{mod = Mod, + notes = Notes, + timeout = Timeout, + timer = start_timer(Timeout)}, + ?vdebug("started",[]), + {ok, State}. + + +%%----------------------------------------------------------------- +%% A note is any internal information that has to be +%% stored for some time (the Lifetime). +%% A note is stored in ets as {Key, {BestBefore, Value}}, +%% where BestBefore is currentTime + Lifetime. +%% A GC-op can destroy any notes with CurTime > BestBore. +%% Lifetime is in centiseconds or infinity, in which case +%% the note is eternal. +%%----------------------------------------------------------------- +handle_call({set_note, Lifetime, Key, Value}, _From, + #state{mod = Mod, notes = Notes} = State) + when is_integer(Lifetime) -> + ?vlog("set note <~p,~p> with life time ~p", [Key,Value,Lifetime]), + case (catch Mod:system_start_time()) of + SysStartTime when is_integer(SysStartTime) -> + ?vtrace("handle_call(set_note) -> SysStartTime: ~p", + [SysStartTime]), + Now = snmp_misc:now(cs), + ?vtrace("handle_call(set_note) -> Now: ~p", [Now]), + RealUpTime = Now - SysStartTime, + ?vtrace("handle_call(set_note) -> RealUpTime: ~p", [RealUpTime]), + BestBefore = RealUpTime + Lifetime, + ?vtrace("handle_call(set_note) -> BestBefore: ~p", [BestBefore]), + Val = ets:insert(Notes, {Key, {BestBefore, Value}}), + NState = activate_timer(State), + {reply, Val, NState}; + _Crap -> + ?vinfo("handle_call(set_note) -> " + "failed retreiving system start time from ~w: " + "~n ~p", [Mod, _Crap]), + {reply, {error, failed_retreive_system_start_time}, State} + end; + +handle_call({set_note, infinity, Key, Value}, _From, + #state{notes = Notes} = State) -> + ?vlog("set note <~p,~p>",[Key,Value]), + Val = ets:insert(Notes, {Key, {infinity, Value}}), + ?vdebug("set note; old value: ~p",[Val]), + {reply, Val, State}; + +handle_call({get_note, Key}, _From, + #state{mod = Mod, notes = Notes} = State) -> + ?vlog("get note ~p",[Key]), + Val = handle_get_note(Notes, Mod, Key), + ?vdebug("get note: ~p",[Val]), + {reply, Val, State}; + +handle_call(info, _From, #state{timer = Pid, notes = Notes} = State) -> + ?vlog("info",[]), + Info = get_info(Pid, Notes), + {reply, Info, State}; + +handle_call(stop, _From, State) -> + ?vlog("stop",[]), + {stop, normal, ok, State}; + +handle_call(Req, From, State) -> + warning_msg("received unexpected request from ~p: ~n~p",[From, Req]), + {reply, {error, {unknown_request, Req}}, 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 unexpected message: ~n~p",[Msg]), + {noreply, State}. + + +%%----------------------------------------------------------------- +%% If there are no possible garbage left, we don't +%% have to wait for timeout, and perform another +%% gc, because we won't do anything. So +%% we switch the timeout off in that case. +%% It will be switched on as soon as we get some +%% other message. +%%----------------------------------------------------------------- +handle_info(timeout, State) -> + ?vdebug("timeout",[]), + case gc(State) of + nothing_left -> + NState = deactivate_timer(State), + {noreply, NState}; + work_to_do -> + NState = activate_timer(State), + {noreply, NState} + end; + +handle_info({'EXIT', Pid, Reason}, + #state{timer = Pid, timeout = Timeout} = State) -> + ?vinfo("exit message from the timer process ~p for reason ~p", + [Pid, Reason]), + set_state(State#state{timer = start_timer(Timeout)}); + +handle_info({'EXIT',Pid,Reason}, State) -> + ?vlog("exit message from ~p for reason ~p",[Pid,Reason]), + {noreply, State}; + +handle_info(Info, State) -> + warning_msg("received unexpected info: ~n~p",[Info]), + {noreply, State}. + + +set_state(S) -> + case gc(S) of + nothing_left -> + NState = deactivate_timer(S), + {noreply, NState}; + work_to_do -> + NState = activate_timer(S), + {noreply, NState} + end. + + +terminate(Reason, _State) -> + ?vdebug("terminate: ~p",[Reason]), + ok. + + +%%---------------------------------------------------------- +%% Code change +%%---------------------------------------------------------- + +% downgrade +code_change({down, _Vsn}, State, _Extra) -> + NState = activate_timer(deactivate_timer(State)), + {ok, NState}; + +% upgrade +code_change(_Vsn, State, _Extra) -> + process_flag(trap_exit, true), + NState = restart_timer(State), + {ok, NState}. + + +%%---------------------------------------------------------- +%% Timer +%%---------------------------------------------------------- + +activate_timer(#state{timer = Pid, active = false} = State) -> + Pid ! activate, + receive + activated -> ok + end, + State#state{active = true}; +activate_timer(State) -> + State. + +deactivate_timer(#state{timer = Pid, active = true} = State) -> + Pid ! deactivate, + receive + deactivated -> ok + end, + State#state{timeout = false}; +deactivate_timer(State) -> + State. + +start_timer(Timeout) -> + spawn_link(?MODULE, timer, [self(), passive, Timeout]). + +%% Kill, restart and activate timer. +restart_timer(#state{timer = Pid, timeout = Timeout} = State) -> + ?d("restart_timer -> kill current timer process ~p",[Pid]), + exit(Pid, kill), + ?d("restart_timer -> await acknowledgement",[]), + receive + {'EXIT', Pid, _Reason} -> + ok + end, + ?d("restart_timer -> start a new timer process",[]), + activate_timer(State#state{timer = start_timer(Timeout), active = false}). + +timer(Pid, passive, Timeout) -> + receive + deactivate -> + ?d("timer(passive) -> deactivate request, just send ack",[]), + Pid ! deactivated, + ?MODULE:timer(Pid, passive, Timeout); + + activate -> + ?d("timer(deactive) -> activate request, send ack",[]), + Pid ! activated, + ?d("timer(deactive) -> activate",[]), + ?MODULE:timer(Pid, active, Timeout) % code replacement + after + Timeout -> + ?d("timer(deactive) -> timeout",[]), + ?MODULE:timer(Pid, passive, Timeout) + end; +timer(Pid, active, Timeout) -> + receive + activate -> + ?d("timer(active) -> activate request, just send ack",[]), + Pid ! activated, + ?MODULE:timer(Pid, active, Timeout); + + deactivate -> + ?d("timer(active) -> deactivate request, send ack",[]), + Pid ! deactivated, + ?d("timer(active) -> deactivate",[]), + ?MODULE:timer(Pid, passive, Timeout) + after + Timeout -> + ?d("timer(active) -> timeout",[]), + Pid ! timeout, + ?MODULE:timer(Pid, active, Timeout) + end. + + +handle_get_note(Notes, Mod, Key) -> + case ets:lookup(Notes, Key) of + [{Key, {infinity, Val}}] -> + Val; + [{Key, {BestBefore, Val}}] -> + ?vtrace("get note -> BestBefore: ~w", [BestBefore]), + StartTime = Mod:system_start_time(), + ?vtrace("get note -> StartTime: ~w", [StartTime]), + Now = snmp_misc:now(cs), + ?vtrace("get note -> Now: ~w", [Now]), + case (Now - StartTime) of + Diff when BestBefore >= Diff -> + ?vtrace("get note -> Diff: ~w", [Diff]), + Val; + OldDiff -> + ?vtrace("get note -> note to old [~w] - delete", [OldDiff]), + ets:delete(Notes, Key), + undefined + end; + [] -> + undefined + end. + + +%%----------------------------------------------------------------- +%% Clean up all old notes in the database. +%%----------------------------------------------------------------- +gc(#state{mod = Mod, notes = Notes}) -> + RealUpTime = snmp_misc:now(cs) - Mod:system_start_time(), + gc(nothing_left, ets:tab2list(Notes), Notes, RealUpTime). + +gc(Flag, [{_Key, {infinity, _}} | T], Tab, Now) -> gc(Flag, T, Tab, Now); +gc(Flag, [{Key, {BestBefore, _}} | T], Tab, Now) + when is_integer(BestBefore) andalso (BestBefore < Now) -> + ets:delete(Tab, Key), + gc(Flag, T, Tab, Now); +gc(_Flag, [_ | T], Tab, Now) -> gc(work_to_do, T, Tab, Now); +gc(Flag, [], _Tab, _Now) -> Flag. + + +%%----------------------------------------------------------------- + +get_info(Tmr, Notes) -> + ProcSize = proc_mem(self()), + TMRSz = proc_mem(Tmr), + NotesSz = tab_size(Notes), + [{process_memory, [{notes, ProcSize}, {timer, TMRSz}]}, + {db_memory, [{notes, NotesSz}]}]. + +proc_mem(P) when is_pid(P) -> + case (catch erlang:process_info(P, memory)) of + {memory, Sz} -> + Sz; + _ -> + undefined + end; +proc_mem(_) -> + undefined. + +tab_size(T) -> + case (catch ets:info(T, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + + +%%----------------------------------------------------------------- + +call(Pid, Req) -> + call(Pid, Req, infinity). + +call(Pid, Req, Timeout) -> + gen_server:call(Pid, Req, Timeout). + +cast(Pid, Msg) -> + gen_server:cast(Pid, Msg). + + +%%----------------------------------------------------------------- + +%% info_msg(F, A) -> +%% ?snmp_info(get(snmp_component), "Note store server " ++ F, A). + +warning_msg(F, A) -> + ?snmp_warning(get(snmp_component), "Note store server " ++ F, A). + +error_msg(F, A) -> + ?snmp_error(get(snmp_component), "Note store server " ++ F, A). + + +%%----------------------------------------------------------------- + +get_verbosity(Opts) -> + snmp_misc:get_option(verbosity, Opts, ?default_verbosity). + +get_sname(Opts) -> + snmp_misc:get_option(sname, Opts, ns). + +get_timeout(Opts) -> + snmp_misc:get_option(timeout, Opts, ?timeout). + +get_component(snmpm) -> + "manager"; +get_component(snmpa) -> + "agent"; +get_component(_) -> + "". + diff --git a/lib/snmp/src/misc/snmp_pdus.erl b/lib/snmp/src/misc/snmp_pdus.erl new file mode 100644 index 0000000000..6c80fc3876 --- /dev/null +++ b/lib/snmp/src/misc/snmp_pdus.erl @@ -0,0 +1,770 @@ +%% +%% %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_pdus). + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). + +-define(VMODULE,"PDUS"). +-include("snmp_verbosity.hrl"). + +%% See RFC1155, RFC1157, RFC1901, RFC1902, RFC1905, RFC2272 + + +%% API +-export([enc_message/1, enc_message_only/1, enc_pdu/1, + enc_varbind/1, + enc_oct_str_tag/1, enc_scoped_pdu/1, + enc_usm_security_parameters/1, + dec_message/1, dec_message_only/1, dec_pdu/1, + dec_scoped_pdu_data/1, dec_scoped_pdu/1, + dec_usm_security_parameters/1, + strip_encrypted_scoped_pdu_data/1, + octet_str_to_bits/1, bits_to_str/1, + get_encoded_length/1]). + +%% Returns the number of octets required to encode Length. +get_encoded_length(Length) -> + length(elength(Length)). + +dec_message([48 | Bytes]) -> + Bytes2 = get_data_bytes(Bytes), + case dec_snmp_version(Bytes2) of + {'version-3', Rest} -> + dec_rest_v3_msg(Rest); + {Vsn, Rest} -> % 1 or 2 + dec_rest_v1_v2_msg(Vsn, Rest) + end. + +dec_message_only([48 | Bytes]) -> + Bytes2 = get_data_bytes(Bytes), + case dec_snmp_version(Bytes2) of + {'version-3', Rest} -> + dec_rest_v3_msg_only(Rest); + {Vsn, Rest} -> % 1 or 2 + dec_rest_v1_v2_msg_only(Vsn, Rest) + end. + +dec_snmp_version(Bytes) -> + case (catch dec_int_tag(Bytes, 10)) of + {error, {bad_integer, BadInt}} -> + exit({bad_version, BadInt}); + {SNMPversion, Rest} when is_integer(SNMPversion) andalso is_list(Rest) -> + {dec_snmp_ver(SNMPversion), Rest}; + {'EXIT', Reason} -> + exit(Reason) + end. + + +dec_snmp_ver(0) -> + 'version-1'; +dec_snmp_ver(1) -> + 'version-2'; +dec_snmp_ver(3) -> + 'version-3'; +dec_snmp_ver(Vsn) -> + exit({bad_version, Vsn}). + +dec_rest_v1_v2_msg(Vsn, Rest1) -> + {Community, Rest2} = dec_oct_str_tag(Rest1), + PDU = dec_pdu(Rest2), + #message{version = Vsn, vsn_hdr = Community, data = PDU}. + +dec_rest_v1_v2_msg_only(Vsn, Rest1) -> + {Community, Rest2} = dec_oct_str_tag(Rest1), + #message{version = Vsn, vsn_hdr = Community, data = Rest2}. + +dec_rest_v3_msg_only([48 | Bytes]) -> % starts with header data sequence + {Size, Tail} = dec_len(Bytes), + {HBytes, Bytes1} = split_at(Tail, Size, []), + %% Decode HeaderData + {MsgID, HBytes1} = dec_int_tag(HBytes), + chk_msg_id(MsgID), + {MsgMaxSize, HBytes2} = dec_int_tag(HBytes1), + chk_msg_max_size(MsgMaxSize), + {MsgFlags, HBytes3} = dec_oct_str_tag(HBytes2), + {MsgSecurityModel, []} = dec_int_tag(HBytes3), + chk_msg_sec_model(MsgSecurityModel), + %% Continue with Message +% {MsgSecurityParameters, Bytes2} = dec_oct_str_tag(Bytes1), + + [4 | Bytes1a] = Bytes1, + {Size1a, Tail1a} = dec_len(Bytes1a), + {MsgSecurityParameters, Bytes2} = split_at(Tail1a, Size1a, []), + + %% [48 , HdrDataLen, HdrData, 4, MsgSecLen, MsgSec, ...] + %% NOTE: HdrDataLen is always so small that one octet is enough to + %% encode its length. + %% MsgSecLen is worse... but for USM, it is small enough for + %% one octet. USM is currently the only secmodel. + %% 1 + 1 + Size + 1 + 1 + Size1a + HdrSize = Size + Size1a + 4, + V3Hdr = #v3_hdr{msgID = MsgID, + msgMaxSize = MsgMaxSize, + msgFlags = MsgFlags, %dec_msg_flags(MsgFlags), + msgSecurityModel = MsgSecurityModel, + msgSecurityParameters = MsgSecurityParameters, + hdr_size = HdrSize}, + #message{version = 'version-3', vsn_hdr = V3Hdr, data = Bytes2}. + +dec_rest_v3_msg(Bytes) -> + Message = dec_rest_v3_msg_only(Bytes), + Data = Message#message.data, + Message#message{data = dec_scoped_pdu_data(Data)}. + +dec_scoped_pdu_data([48 | Bytes]) -> % plaintext + {ScopedPdu, []} = dec_scoped_pdu_notag(Bytes), + ScopedPdu; +dec_scoped_pdu_data([4 | Bytes]) -> % encryptedPDU + {EncryptedPDU, []} = dec_oct_str_notag(Bytes), + EncryptedPDU. + + +dec_scoped_pdu([48 | Bytes]) -> + element(1, dec_scoped_pdu_notag(Bytes)). + +dec_scoped_pdu_notag(Bytes) -> + Bytes1 = get_data_bytes(Bytes), + {ContextEngineID, Bytes2} = dec_oct_str_tag(Bytes1), + {ContextName, Bytes3} = dec_oct_str_tag(Bytes2), + Pdu = dec_pdu(Bytes3), + {#scopedPdu{contextEngineID = ContextEngineID, + contextName = ContextName, + data = Pdu}, + []}. + +dec_pdu_tag(160) -> + 'get-request'; +dec_pdu_tag(161) -> + 'get-next-request'; +dec_pdu_tag(162) -> + 'get-response'; +dec_pdu_tag(163) -> + 'set-request'; +%% 164 SNMPv1 Trap +%% 165 Bulk +dec_pdu_tag(166) -> + 'inform-request'; +dec_pdu_tag(167) -> + 'snmpv2-trap'; +dec_pdu_tag(168) -> + report. + + +dec_pdu([164 | Bytes]) -> % It's a trap + Bytes2 = get_data_bytes(Bytes), + {Enterprise, Rest1} = dec_oid_tag(Bytes2), + {{'IpAddress', AgentAddr}, Rest2} = dec_value(Rest1), + {GenericTrap, Rest3} = dec_int_tag(Rest2), + {SpecificTrap, Rest4} = dec_int_tag(Rest3), + {{'TimeTicks', TimeStamp}, VBBytes} = dec_value(Rest4), + VBs = dec_VBs(VBBytes), + #trappdu{enterprise = Enterprise, agent_addr = AgentAddr, + generic_trap = GenericTrap, specific_trap = SpecificTrap, + time_stamp = TimeStamp, varbinds = VBs}; + +dec_pdu([165 | Bytes]) -> % Bulk + Bytes2 = get_data_bytes(Bytes), + {RequestID, Rest1} = dec_int_tag(Bytes2), + {NonRepeaters, Rest2} = dec_int_tag(Rest1), + {MaxRepetitions,VBbytes} = dec_int_tag(Rest2), + VBs = dec_VBs(VBbytes), + #pdu{type = 'get-bulk-request', request_id = RequestID, + error_status = NonRepeaters, error_index = MaxRepetitions, + varbinds = VBs}; + +dec_pdu([PduTag | Bytes]) -> + Type = dec_pdu_tag(PduTag), + Bytes2 = get_data_bytes(Bytes), + {RequestID, Rest1} = dec_int_tag(Bytes2), + {ErrStat, Rest2} = dec_int_tag(Rest1), + ErrStatus = case lists:keysearch(ErrStat, 2, errMsgs()) of + {value, {ErrStatName, _ErrStat}} -> + ErrStatName; + false -> + ErrStat + end, + {ErrIndex, VarbindsBytes} = dec_int_tag(Rest2), + VBs = dec_VBs(VarbindsBytes), + #pdu{type = Type, request_id = RequestID, error_status = ErrStatus, + error_index = ErrIndex, varbinds = VBs}. + +dec_VBs([48 | Bytes]) -> + Bytes1 = get_data_bytes(Bytes), + dec_individual_VBs(Bytes1, 1, []). + +dec_individual_VBs([], _No, VBs) -> + lists:reverse(VBs); +dec_individual_VBs([48 | Bytes], OrgIndex, AccVBs) -> + {_SizeOfThisVB, Bytes2} = dec_len(Bytes), + {Oid, Rest} = dec_oid_tag(Bytes2), + {{Type, Value}, Rest2} = dec_value(Rest), + % perhaps we should check that we have eaten SizeOfThisVB bytes, but we + % don't consider ourselves to have time for such list traversing stuff. + dec_individual_VBs(Rest2, OrgIndex + 1, [#varbind{oid = Oid, + variabletype = Type, + value = Value, + org_index = OrgIndex} + | AccVBs]). + +dec_usm_security_parameters([48 | Bytes1]) -> + {_Len, Bytes2} = dec_len(Bytes1), + {MsgAuthEngineID, Bytes3} = dec_oct_str_tag(Bytes2), + {MsgAuthEngineBoots, Bytes4} = dec_int_tag(Bytes3), + {MsgAuthEngineTime, Bytes5} = dec_int_tag(Bytes4), + {MsgUserName, Bytes6} = dec_oct_str_tag(Bytes5), + {MsgAuthParams, Bytes7} = dec_oct_str_tag(Bytes6), + {MsgPrivParams, []} = dec_oct_str_tag(Bytes7), + #usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID, + msgAuthoritativeEngineBoots = MsgAuthEngineBoots, + msgAuthoritativeEngineTime = MsgAuthEngineTime, + msgUserName = MsgUserName, + msgAuthenticationParameters = MsgAuthParams, + msgPrivacyParameters = MsgPrivParams}. + +strip_encrypted_scoped_pdu_data([48 | Bytes]) -> + {Size, Tail} = dec_len(Bytes), + [48 | elength(Size)] ++ strip(Size, Tail). + +strip(N, [H|T]) when N > 0 -> [H | strip(N-1, T)]; +strip(0, _Tail) -> + []. + + +%%---------------------------------------------------------------------- +%% Returns:{Type, Value} +%%---------------------------------------------------------------------- +dec_value([6 | Bytes]) -> + {Value, Rest} = dec_oid_notag(Bytes), + {{'OBJECT IDENTIFIER', Value}, Rest}; +dec_value([5,0 | T]) -> + {{'NULL', 'NULL'}, T}; +dec_value([2 | Bytes]) -> + {Value, Rest} = dec_integer_notag(Bytes), + {{'INTEGER', Value}, Rest}; +dec_value([4 | Bytes]) -> + {Value, Rest} = dec_oct_str_notag(Bytes), + {{'OCTET STRING', Value}, Rest}; +dec_value([64 | Bytes]) -> + {Value, Rest} = dec_oct_str_notag(Bytes), + {{'IpAddress', Value}, Rest}; +dec_value([65 | Bytes]) -> + {Value, Rest} = dec_integer_notag(Bytes), + if Value >= 0, Value =< 4294967295 -> + {{'Counter32', Value}, Rest}; + true -> + exit({error, {bad_counter32, Value}}) + end; +dec_value([66 | Bytes]) -> + {Value, Rest} = dec_integer_notag(Bytes), + if Value >= 0, Value =< 4294967295 -> + {{'Unsigned32', Value}, Rest}; + true -> + exit({error, {bad_unsigned32, Value}}) + end; +dec_value([67 | Bytes]) -> + {Value, Rest} = dec_integer_notag(Bytes), + if Value >= 0, Value =< 4294967295 -> + {{'TimeTicks', Value}, Rest}; + true -> + exit({error, {bad_timeticks, Value}}) + end; +dec_value([68 | Bytes]) -> + {Value, Rest} = dec_oct_str_notag(Bytes), + {{'Opaque', Value}, Rest}; +dec_value([70 | Bytes]) -> + {Value, Rest} = dec_integer_notag(Bytes), + if Value >= 0, Value =< 18446744073709551615 -> + {{'Counter64', Value}, Rest}; + true -> + exit({error, {bad_counter64, Value}}) + end; +dec_value([128,0|T]) -> + {{'NULL', noSuchObject}, T}; +dec_value([129,0|T]) -> + {{'NULL', noSuchInstance}, T}; +dec_value([130,0|T]) -> + {{'NULL', endOfMibView}, T}. + + +%%---------------------------------------------------------------------- +%% Purpose: Uses the beginning length bytes to return the actual data. +%% If data has the wrong length, the program is exited. +%% Pre: Tag is removed. +%%---------------------------------------------------------------------- +get_data_bytes(Bytes) -> + {Size, Tail} = dec_len(Bytes), + if + length(Tail) =:= Size -> + Tail; + true -> + exit({error, {wrong_length, Bytes}}) + end. + +split_at(L, 0, Acc) -> + {lists:reverse(Acc), L}; +split_at([H|T], N, Acc) -> + split_at(T, N-1, [H|Acc]). + +%%---------------------------------------------------------------------- +%% All decoding routines return: {Data, RestBytes} +%%---------------------------------------------------------------------- + +dec_int_tag([2 | Bytes]) -> + dec_integer_notag(Bytes). +dec_int_tag([2 | Bytes], SizeLimit) -> + dec_integer_notag(Bytes, SizeLimit). + +dec_integer_notag(Ints) -> + dec_integer_notag(Ints, infinity). +dec_integer_notag(Ints, SizeLimit) -> + case dec_len(Ints) of + {Size, Ints2} when SizeLimit =:= infinity -> + do_dec_integer_notag(Size, Ints2); + {Size, Ints2} when (is_integer(SizeLimit) andalso + (Size =< SizeLimit)) -> + do_dec_integer_notag(Size, Ints2); + {BadSize, _BadInts2} -> + throw({error, {bad_integer, {BadSize, SizeLimit}}}) + end. + +do_dec_integer_notag(Size, Ints) -> + if hd(Ints) band 128 == 0 -> %% Positive number + dec_pos_int(Ints, Size, 8 * (Size - 1)); + true -> %% Negative + dec_neg_int(Ints, Size, 8 * (Size - 1)) + end. + + +dec_pos_int(T, 0, _) -> {0, T}; +dec_pos_int([Byte|Tail], Size, Shift) -> + {Int, Rest} = dec_pos_int(Tail, Size - 1, Shift - 8), + {(Byte bsl Shift) bor Int, Rest}. + +dec_neg_int(T, 0, _) -> {0, T}; +dec_neg_int([Byte|Tail], Size, Shift) -> + {Int, Rest} = dec_pos_int(Tail, Size - 1, Shift-8), + {(-128 + (Byte band 127) bsl Shift) bor Int, Rest}. + +dec_oct_str_tag([4 | Bytes]) -> + dec_oct_str_notag(Bytes). + +dec_oct_str_notag(Bytes) -> + {Size, Tail} = dec_len(Bytes), + split_at(Tail, Size, []). + +dec_oid_tag([6 | Bytes]) -> + dec_oid_notag(Bytes). + +dec_oid_notag(Bytes) -> + {Size, [H | Tail]} = dec_len(Bytes), + {Oid, Rest} = dec_oid_elements(Tail, Size - 1, []), + {[H div 40, H rem 40 | Oid], Rest}. + +dec_oid_elements(L, 0, Acc) -> + {lists:reverse(Acc), L}; +dec_oid_elements([Dig|Tail], Size, Acc) when Dig < 128 -> + dec_oid_elements(Tail, Size - 1, [Dig | Acc]); +dec_oid_elements([Dig|Tail], Size, Acc) -> + {Num, Neaten, Tl} = dec_oid_element(Tail,1,Dig band 127), + dec_oid_elements(Tl, Size - Neaten, [Num|Acc]). + +dec_oid_element([Dig|Tail], Neaten, Num) when Dig < 128 -> + {Num*128+Dig,Neaten+1,Tail}; +dec_oid_element([Dig|Tail],Neaten, Num) -> + dec_oid_element(Tail, Neaten+1, Num*128 + (Dig band 127)). + +chk_msg_id(MsgId) when (MsgId >= 0) andalso (MsgId =< 2147483647) -> ok; +chk_msg_id(MsgId) -> exit({bad_msg_id, MsgId}). + +chk_msg_max_size(MMS) when (MMS >= 484) andalso (MMS =< 2147483647) -> ok; +chk_msg_max_size(MMS) -> exit({bad_msg_max_size, MMS}). + +chk_msg_sec_model(MsgSecurityModel) when MsgSecurityModel >= 0, + MsgSecurityModel =< 2147483647 -> ok; +chk_msg_sec_model(MsgSecurityModel) -> + exit({bad_msg_sec_model, MsgSecurityModel}). + +%%---------------------------------------------------------------------- +%% Code copied from the original ASN.1 compiler written by +%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Returns: {Len, Tail} +%%---------------------------------------------------------------------- +dec_len([128|_Tail]) -> + %% indefinite form - not allowed in SNMP + exit({asn1_error, indefinite_length}); + +dec_len([Hd|Tl]) when Hd >= 0 -> + %% definite form + if + Hd < 128 -> % 8th bit is cleared + %% Short form (actually, we can remove this test, since snmp_pdus + %% performs this test _before_ calling this function) + {Hd,Tl}; + true -> + %% Long form + No = Hd band 127, % clear 8th bit + {DigList, Rest} = head(No, Tl), + Size = dec_integer_len(DigList), + {Size, Rest} + end. + +dec_integer_len([D]) -> + D; +dec_integer_len([A,B]) -> + (A bsl 8) bor B; +dec_integer_len([A,B,C]) -> + (A bsl 16) bor (B bsl 8) bor C; +%% More than 3 elements for length => either *very* long packet +%% (which we don't handle), or the length is encoded with more octets +%% than necessary (in which case the first octet must be 0). +dec_integer_len([0 | T]) -> + dec_integer_len(T). + +%%----------------------------------------------------------------- +%% head(N, List) -> {List1, List2} +%% List == List1 ++ List2 +%% length(List1) == N +%%----------------------------------------------------------------- +head(L,List) -> + head(L,List,[]). + +head(0,L,Res) -> + {lists:reverse(Res),L}; + +head(Int,[H|Tail],Res) -> + head(Int-1,Tail,[H|Res]); +head(Int, [], _Res) -> + exit({asn1_error, {bad_length, Int}}). + +%%%---------------------------------------------------------------------- +%%% ENCODING ENCODING ENCODING ENCODING ENCODING ENCODING ENCODING ENCODING +%%%---------------------------------------------------------------------- + +enc_message(#message{version = Ver, vsn_hdr = VsnHdr, data = Data}) -> + VerBytes = enc_version(Ver), + Bytes = + case Ver of + 'version-3' -> + V3HeaderBytes = enc_v3_header(VsnHdr), + DataBytes = enc_scoped_pdu(Data), + V3HeaderBytes ++ DataBytes; + _ -> + ComBytes = enc_community(VsnHdr), + DataBytes = enc_pdu(Data), + ComBytes ++ DataBytes + end, + Bytes2 = VerBytes ++ Bytes, + Len = elength(length(Bytes2)), + [48 | Len] ++ Bytes2. + +enc_message_only(#message{version = Ver, vsn_hdr = VsnHdr, data = DataBytes}) -> + VerBytes = enc_version(Ver), + Bytes = + case Ver of + 'version-3' -> + V3HeaderBytes = enc_v3_header(VsnHdr), + V3HeaderBytes ++ DataBytes; + _ -> + ComBytes = enc_community(VsnHdr), + ComBytes ++ DataBytes + end, + Bytes2 = VerBytes ++ Bytes, + Len = elength(length(Bytes2)), + [48 | Len] ++ Bytes2. + +enc_version('version-1') -> + [2,1,0]; +enc_version('version-2') -> + [2,1,1]; +enc_version('version-3') -> + [2,1,3]. + +enc_community(Com) -> + enc_oct_str_tag(Com). + +enc_v3_header(#v3_hdr{msgID = MsgID, + msgMaxSize = MsgMaxSize, + msgFlags = MsgFlags, + msgSecurityModel = MsgSecurityModel, + msgSecurityParameters = MsgSecurityParameters}) -> + Bytes = lists:append([enc_integer_tag(MsgID), + enc_integer_tag(MsgMaxSize), + enc_oct_str_tag(MsgFlags), + enc_integer_tag(MsgSecurityModel)]), + Len = elength(length(Bytes)), + lists:append([[48 | Len], Bytes, enc_oct_str_tag(MsgSecurityParameters)]). + +enc_scoped_pdu(#scopedPdu{contextEngineID = ContextEngineID, + contextName = ContextName, + data = Data}) -> + Bytes = lists:append([enc_oct_str_tag(ContextEngineID), + enc_oct_str_tag(ContextName), + enc_pdu(Data)]), + Len = elength(length(Bytes)), + [48 | Len] ++ Bytes. + + +enc_pdu(PDU) when PDU#pdu.type =:= 'get-request' -> + enc_pdu(160, PDU); +enc_pdu(PDU) when PDU#pdu.type =:= 'get-next-request' -> + enc_pdu(161, PDU); +enc_pdu(PDU) when PDU#pdu.type =:= 'get-response' -> + enc_pdu(162, PDU); +enc_pdu(PDU) when PDU#pdu.type =:= 'set-request' -> + enc_pdu(163, PDU); +enc_pdu(PDU) when PDU#pdu.type =:= 'get-bulk-request' -> + enc_pdu(165, PDU); +enc_pdu(PDU) when PDU#pdu.type =:= 'inform-request' -> + enc_pdu(166, PDU); +enc_pdu(PDU) when PDU#pdu.type =:= 'snmpv2-trap' -> + enc_pdu(167, PDU); +enc_pdu(PDU) when PDU#pdu.type =:= report -> + enc_pdu(168, PDU); +enc_pdu(TrapPDU) when is_record(TrapPDU, trappdu) -> + enc_Trap(TrapPDU). + + +enc_pdu(Tag,PDU) -> + Bytes2 = enc_pdu2(PDU), + Len2 = elength(length(Bytes2)), + lists:append([Tag | Len2], Bytes2). + +enc_pdu2(#pdu{type = Type, request_id = ReqId, error_index = ErrIndex, + error_status = ErrStat, varbinds = VBs}) -> + ReqBytes = enc_integer_tag(ReqId), + Val = err_val(ErrStat,Type), + ErrStatBytes = enc_integer_tag(Val), + ErrIndexBytes = enc_integer_tag(ErrIndex), + VBsBytes = enc_VarBindList(VBs), + lists:append([ReqBytes, ErrStatBytes, ErrIndexBytes, VBsBytes]). + +enc_usm_security_parameters( + #usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID, + msgAuthoritativeEngineBoots = MsgAuthEngineBoots, + msgAuthoritativeEngineTime = MsgAuthEngineTime, + msgUserName = MsgUserName, + msgAuthenticationParameters = MsgAuthParams, + msgPrivacyParameters = MsgPrivParams}) -> + Bytes1 = enc_oct_str_tag(MsgAuthEngineID), + Bytes2 = enc_integer_tag(MsgAuthEngineBoots), + Bytes3 = enc_integer_tag(MsgAuthEngineTime), + Bytes4 = enc_oct_str_tag(MsgUserName), + Bytes5 = enc_oct_str_tag(MsgAuthParams), + Bytes6 = enc_oct_str_tag(MsgPrivParams), + Bytes7 = lists:append([Bytes1, Bytes2, Bytes3, Bytes4, Bytes5, Bytes6]), + Len = elength(length(Bytes7)), + [48 | Len] ++ Bytes7. + +err_val(Int,'get-bulk-request') when is_integer(Int) -> Int; +err_val(ErrStat, _) -> + {value, {_ErrStat, Val}} = lists:keysearch(ErrStat, 1, errMsgs()), + Val. + +errMsgs() -> + [{noError,0},{tooBig,1},{noSuchName,2}, + {badValue,3},{readOnly,4},{genErr,5}, + %% v2 + {noAccess,6},{wrongType,7},{wrongLength,8},{wrongEncoding,9}, + {wrongValue,10},{noCreation,11},{inconsistentValue,12}, + {resourceUnavailable,13},{commitFailed,14},{undoFailed,15}, + {authorizationError,16},{notWritable,17},{inconsistentName,18}]. + +enc_VarBindList(EncodedVBs) when is_integer(hd(EncodedVBs)) -> + Len1 = elength(length(EncodedVBs)), + lists:append([48 | Len1],EncodedVBs); +enc_VarBindList(VBs) -> + Bytes1 = lists:append(lists:map(fun enc_varbind/1, VBs)), + Len1 = elength(length(Bytes1)), + lists:append([48 | Len1],Bytes1). + +enc_varbind(Varbind) -> + Bytes1 = enc_VarBind_attributes(Varbind), + Len1 = elength(length(Bytes1)), + lists:append([48 | Len1],Bytes1). + + +enc_VarBind_attributes(#varbind{oid = Oid, variabletype = Type,value = Val}) -> + OidBytes = enc_oid_tag(Oid), + ValueBytes = enc_value(Type, Val), + lists:append(OidBytes, ValueBytes). + +enc_value('INTEGER',Val) -> + enc_integer_tag(Val); +enc_value('OCTET STRING', Val) -> + enc_oct_str_tag(Val); +enc_value('BITS', Val) -> + enc_oct_str_tag(bits_to_str(Val)); +enc_value('OBJECT IDENTIFIER', Val) -> + enc_oid_tag(Val); +enc_value('IpAddress',Val) -> + Bytes2 = enc_oct_str_notag(Val), + Len2 = elength(length(Bytes2)), + lists:append([64 | Len2],Bytes2); +enc_value('Opaque', Val) -> + Bytes2 = enc_oct_str_notag(Val), + Len2 = elength(length(Bytes2)), + lists:append([68 | Len2],Bytes2); +enc_value(_Type, noSuchObject) -> + [128,0]; +enc_value(_Type, noSuchInstance) -> + [129,0]; +enc_value(_Type, endOfMibView) -> + [130,0]; +enc_value('NULL', _Val) -> + [5,0]; +enc_value(Type, Val) -> + Bytes2 = enc_integer_notag(Val), + Len2 = elength(length(Bytes2)), + lists:append([enc_val_tag(Type,Val) | Len2],Bytes2). + +enc_val_tag('Counter32',Val) when (Val >= 0) andalso (Val =< 4294967295) -> + 65; +enc_val_tag('Unsigned32', Val) when (Val >= 0) andalso (Val =< 4294967295) -> + 66; +enc_val_tag('TimeTicks', Val) when (Val >= 0) andalso (Val =< 4294967295) -> + 67; +enc_val_tag('Counter64', Val) when ((Val >= 0) andalso + (Val =< 18446744073709551615)) -> + 70. + + +%%---------------------------------------------------------------------- +%% Impl according to RFC1906, section 8 +%% For example: the number 1010 0000 (=160) 0100 0001 (=65) is represented as +%% the octet string: 1000 0010, 0000 0101 (=[130,5]) +%%---------------------------------------------------------------------- +bits_to_str(0) -> ""; +bits_to_str(Int) -> + [rev_int8(Int band 255) | bits_to_str(Int div 256)]. + +rev_int8(Val) -> + rev_int(Val,0,1,128). + +rev_int(_Val,Res,256,0) -> Res; +rev_int(Val,Res,OldBit,NewBit) when Val band OldBit =/= 0 -> + rev_int(Val,Res+NewBit,OldBit*2,NewBit div 2); +rev_int(Val,Res,OldBit,NewBit) -> + rev_int(Val,Res,OldBit*2,NewBit div 2). + +octet_str_to_bits(Str) -> + octet_str_to_bits(Str,1). + +octet_str_to_bits("",_) -> 0; +octet_str_to_bits([Byte|Bytes],Mul) -> + Mul*rev_int8(Byte)+octet_str_to_bits(Bytes,Mul*256). + + +enc_Trap(TrapPdu) when is_record(TrapPdu, trappdu) -> + Bytes1 = enc_trap_data(TrapPdu), + Len1 = elength(length(Bytes1)), + lists:append([164 | Len1],Bytes1). + + +enc_trap_data(#trappdu{enterprise = Enterprise, + agent_addr = AgentAddr, + generic_trap = GenericTrap, + specific_trap = SpecificTrap, + time_stamp = TimeStamp, + varbinds = VBs}) -> + L1 = enc_oid_tag(Enterprise), + L2 = enc_value('IpAddress', AgentAddr), + L3 = enc_integer_tag(GenericTrap), + L4 = enc_integer_tag(SpecificTrap), + L5 = enc_value('TimeTicks', TimeStamp), + L6 = enc_VarBindList(VBs), + lists:append([L1,L2,L3,L4,L5,L6]). + +enc_oid_tag([E1,E2|RestOid]) when E1 * 40 + E2 =< 255 -> + Head = 40*E1 + E2, % weird + Res = e_object_elements(RestOid, []), + lists:append([6 | elength(length(Res) + 1)],[Head|Res]). + +e_object_elements([Num | T], Res) -> + e_object_elements(T, lists:append(e_object_element(Num),Res)); + +e_object_elements([], Res) -> lists:reverse(Res). + +%%---------------------------------------------------------------------- +%% The reversed encoding for an oid-element +%%---------------------------------------------------------------------- +e_object_element(Num) when Num > 0 -> + [Last|T] = e_object_element2(Num), + [Last-128|T]; +e_object_element(0) -> [0]. + +e_object_element2(Num) when Num > 0 -> + Byte = (Num rem 128), + [128+Byte | e_object_element2((Num-Byte) div 128)]; +e_object_element2(0) -> []. + +enc_integer_tag(Val) when Val >= 0 -> %% stdcase positive ints + Bytes = eint(Val,[]), + [2 | elength(length(Bytes))] ++ Bytes; + +enc_integer_tag(Val) -> %% It's a negative number + Bytes = enint(Val,[]), + [2 | elength(length(Bytes))] ++ Bytes. + +enc_integer_notag(Val) when Val >= 0 -> %% stdcase positive ints + eint(Val,[]); + +enc_integer_notag(Val) -> %% It's a negative number + enint(Val,[]). + +eint(0, [B|Acc]) when B < 128 -> + [B|Acc]; +eint(N, Acc) -> + eint(N bsr 8, [N band 16#ff| Acc]). + +enint(-1, [B1|T]) when B1 > 127 -> + [B1|T]; +enint(N, Acc) -> + enint(N bsr 8, [N band 16#ff|Acc]). + +enc_oct_str_tag(OStr) when is_list(OStr) -> + lists:append([4|elength(length(OStr))],OStr); +enc_oct_str_tag(OBin) -> + [4 | elength(size(OBin))] ++ binary_to_list(OBin). + + +enc_oct_str_notag(OStr) -> OStr. + +%%----------------------------------------------------------------- +%% Always use definite form +%%----------------------------------------------------------------- +%% Short +elength(L) when L < 127 -> + [L]; + +%% 3 cases of long form +elength(L) when L =< 16#FF -> + [2#10000001,L]; + +elength(L) when L =< 16#FFFF -> + [2#10000010,(L bsr 8),(L band 16#FF)]; + +elength(L) when L =< 16#7FFFFF -> + [2#10000011,(L bsr 16),((L band 16#FF00) bsr 8), (L band 16#FF)]. + + diff --git a/lib/snmp/src/misc/snmp_usm.erl b/lib/snmp/src/misc/snmp_usm.erl new file mode 100644 index 0000000000..6d216e65d6 --- /dev/null +++ b/lib/snmp/src/misc/snmp_usm.erl @@ -0,0 +1,367 @@ +%% +%% %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(snmp_usm). + +-export([passwd2localized_key/3, localize_key/3]). +-export([auth_in/4, auth_out/4, set_msg_auth_params/3]). +-export([des_encrypt/3, des_decrypt/3]). +-export([aes_encrypt/3, aes_decrypt/5]). + + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). +-include("SNMP-USER-BASED-SM-MIB.hrl"). +-include("SNMP-USM-AES-MIB.hrl"). + +-define(VMODULE,"USM"). +-include("snmp_verbosity.hrl"). + + +%%----------------------------------------------------------------- + +-define(twelwe_zeros, [0,0,0,0,0,0,0,0,0,0,0,0]). + +-define(i32(Int), (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255). + + +%%----------------------------------------------------------------- +%% Func: passwd2localized_key/3 +%% Types: Alg = md5 | sha +%% Passwd = string() +%% EngineID = string() +%% Purpose: Generates a key that can be used as an authentication +%% or privacy key using MD5 och SHA. The key is +%% localized for EngineID. +%% The algorithm is described in appendix A.1 2) of +%% rfc2274. +%%----------------------------------------------------------------- +passwd2localized_key(Alg, Passwd, EngineID) when length(Passwd) > 0 -> + Key = mk_digest(Alg, Passwd), + localize_key(Alg, Key, EngineID). + + +%%----------------------------------------------------------------- +%% Func: localize_key/3 +%% Types: Alg = md5 | sha +%% Passwd = string() +%% EngineID = string() +%% Purpose: Localizes an unlocalized key for EngineID. See rfc2274 +%% section 2.6 for a definition of localized keys. +%%----------------------------------------------------------------- +localize_key(Alg, Key, EngineID) -> + Str = [Key, EngineID, Key], + binary_to_list(crypto:Alg(Str)). + + +mk_digest(md5, Passwd) -> + mk_md5_digest(Passwd); +mk_digest(sha, Passwd) -> + mk_sha_digest(Passwd). + +mk_md5_digest(Passwd) -> + Ctx = crypto:md5_init(), + Ctx2 = md5_loop(0, [], Ctx, Passwd, length(Passwd)), + crypto:md5_final(Ctx2). + +md5_loop(Count, Buf, Ctx, Passwd, PasswdLen) when Count < 1048576 -> + {Buf64, NBuf} = mk_buf64(length(Buf), Buf, Passwd, PasswdLen), + NCtx = crypto:md5_update(Ctx, Buf64), + md5_loop(Count+64, NBuf, NCtx, Passwd, PasswdLen); +md5_loop(_Count, _Buf, Ctx, _Passwd, _PasswdLen) -> + Ctx. + +mk_sha_digest(Passwd) -> + Ctx = crypto:sha_init(), + Ctx2 = sha_loop(0, [], Ctx, Passwd, length(Passwd)), + crypto:sha_final(Ctx2). + +sha_loop(Count, Buf, Ctx, Passwd, PasswdLen) when Count < 1048576 -> + {Buf64, NBuf} = mk_buf64(length(Buf), Buf, Passwd, PasswdLen), + NCtx = crypto:sha_update(Ctx, Buf64), + sha_loop(Count+64, NBuf, NCtx, Passwd, PasswdLen); +sha_loop(_Count, _Buf, Ctx, _Passwd, _PasswdLen) -> + Ctx. + +%% Create a 64 bytes long string, by repeating Passwd as many times +%% as necessary. Output is the 64 byte string, and the rest of the +%% last repetition of the Passwd. This is used as input in the next +%% invocation. +mk_buf64(BufLen, Buf, Passwd, PasswdLen) -> + case BufLen + PasswdLen of + TotLen when TotLen > 64 -> + {[Buf, lists:sublist(Passwd, 64-BufLen)], + lists:sublist(Passwd, 65-BufLen, PasswdLen)}; + TotLen -> + mk_buf64(TotLen, [Buf, Passwd], Passwd, PasswdLen) + end. + + +%%----------------------------------------------------------------- +%% Auth and priv algorithms +%%----------------------------------------------------------------- + +auth_in(usmHMACMD5AuthProtocol, AuthKey, AuthParams, Packet) -> + md5_auth_in(AuthKey, AuthParams, Packet); +auth_in(?usmHMACMD5AuthProtocol, AuthKey, AuthParams, Packet) -> + md5_auth_in(AuthKey, AuthParams, Packet); +auth_in(usmHMACSHAAuthProtocol, AuthKey, AuthParams, Packet) -> + sha_auth_in(AuthKey, AuthParams, Packet); +auth_in(?usmHMACSHAAuthProtocol, AuthKey, AuthParams, Packet) -> + sha_auth_in(AuthKey, AuthParams, Packet). + +auth_out(usmNoAuthProtocol, _AuthKey, _Message, _UsmSecParams) -> % 3.1.3 + error(unSupportedSecurityLevel); +auth_out(?usmNoAuthProtocol, _AuthKey, _Message, _UsmSecParams) -> % 3.1.3 + error(unSupportedSecurityLevel); +auth_out(usmHMACMD5AuthProtocol, AuthKey, Message, UsmSecParams) -> + md5_auth_out(AuthKey, Message, UsmSecParams); +auth_out(?usmHMACMD5AuthProtocol, AuthKey, Message, UsmSecParams) -> + md5_auth_out(AuthKey, Message, UsmSecParams); +auth_out(usmHMACSHAAuthProtocol, AuthKey, Message, UsmSecParams) -> + sha_auth_out(AuthKey, Message, UsmSecParams); +auth_out(?usmHMACSHAAuthProtocol, AuthKey, Message, UsmSecParams) -> + sha_auth_out(AuthKey, Message, UsmSecParams). + +md5_auth_out(AuthKey, Message, UsmSecParams) -> + %% 6.3.1.1 + Message2 = set_msg_auth_params(Message, UsmSecParams, ?twelwe_zeros), + Packet = snmp_pdus:enc_message_only(Message2), + %% 6.3.1.2-4 is done by the crypto function + %% 6.3.1.4 + MAC = binary_to_list(crypto:md5_mac_96(AuthKey, Packet)), + %% 6.3.1.5 + set_msg_auth_params(Message, UsmSecParams, MAC). + +md5_auth_in(AuthKey, AuthParams, Packet) when length(AuthParams) == 12 -> + %% 6.3.2.3 + Packet2 = patch_packet(binary_to_list(Packet)), + %% 6.3.2.5 + MAC = binary_to_list(crypto:md5_mac_96(AuthKey, Packet2)), + %% 6.3.2.6 +%% ?vtrace("md5_auth_in -> entry with" +%% "~n Packet2: ~w" +%% "~n AuthKey: ~w" +%% "~n AuthParams: ~w" +%% "~n MAC: ~w", [Packet2, AuthKey, AuthParams, MAC]), + MAC == AuthParams; +md5_auth_in(_AuthKey, _AuthParams, _Packet) -> + %% 6.3.2.1 + ?vtrace("md5_auth_in -> entry with" + "~n _AuthKey: ~p" + "~n _AuthParams: ~p", [_AuthKey, _AuthParams]), + false. + + +sha_auth_out(AuthKey, Message, UsmSecParams) -> + %% 7.3.1.1 + Message2 = set_msg_auth_params(Message, UsmSecParams, ?twelwe_zeros), + Packet = snmp_pdus:enc_message_only(Message2), + %% 7.3.1.2-4 is done by the crypto function + %% 7.3.1.4 + MAC = binary_to_list(crypto:sha_mac_96(AuthKey, Packet)), + %% 7.3.1.5 + set_msg_auth_params(Message, UsmSecParams, MAC). + +sha_auth_in(AuthKey, AuthParams, Packet) when length(AuthParams) =:= 12 -> + %% 7.3.2.3 + Packet2 = patch_packet(binary_to_list(Packet)), + %% 7.3.2.5 + MAC = binary_to_list(crypto:sha_mac_96(AuthKey, Packet2)), + %% 7.3.2.6 + MAC == AuthParams; +sha_auth_in(_AuthKey, _AuthParams, _Packet) -> + %% 7.3.2.1 + ?vtrace("sha_auth_in -> entry with" + "~n _AuthKey: ~p" + "~n _AuthParams: ~p", [_AuthKey, _AuthParams]), + false. + + +des_encrypt(PrivKey, Data, SaltFun) -> + [A,B,C,D,E,F,G,H | PreIV] = PrivKey, + DesKey = [A,B,C,D,E,F,G,H], + Salt = SaltFun(), + IV = snmp_misc:str_xor(PreIV, Salt), + TailLen = (8 - (length(Data) rem 8)) rem 8, + Tail = mk_tail(TailLen), + EncData = crypto:des_cbc_encrypt(DesKey, IV, [Data,Tail]), + {ok, binary_to_list(EncData), Salt}. + +des_decrypt(PrivKey, MsgPrivParams, EncData) + when length(MsgPrivParams) =:= 8 -> + [A,B,C,D,E,F,G,H | PreIV] = PrivKey, + DesKey = [A,B,C,D,E,F,G,H], + Salt = MsgPrivParams, + IV = snmp_misc:str_xor(PreIV, Salt), + %% Whatabout errors here??? E.g. not a mulitple of 8! + Data = binary_to_list(crypto:des_cbc_decrypt(DesKey, IV, EncData)), + Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data), + {ok, Data2}. + +aes_encrypt(PrivKey, Data, SaltFun) -> + AesKey = PrivKey, + Salt = SaltFun(), + EngineBoots = snmp_framework_mib:get_engine_boots(), + EngineTime = snmp_framework_mib:get_engine_time(), + IV = [?i32(EngineBoots), ?i32(EngineTime) | Salt], + EncData = crypto:aes_cfb_128_encrypt(AesKey, IV, Data), + {ok, binary_to_list(EncData), Salt}. + +aes_decrypt(PrivKey, MsgPrivParams, EncData, EngineBoots, EngineTime) + when length(MsgPrivParams) == 8 -> + AesKey = PrivKey, + Salt = MsgPrivParams, + IV = [?i32(EngineBoots), ?i32(EngineTime) | Salt], + %% Whatabout errors here??? E.g. not a mulitple of 8! + Data = binary_to_list(crypto:aes_cfb_128_decrypt(AesKey, IV, EncData)), + Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data), + {ok, Data2}. + + +%%----------------------------------------------------------------- +%% Utility functions +%%----------------------------------------------------------------- +mk_tail(N) when N > 0 -> + [0 | mk_tail(N-1)]; +mk_tail(0) -> + []. + +set_msg_auth_params(Message, UsmSecParams, AuthParams) -> + NUsmSecParams = + UsmSecParams#usmSecurityParameters{msgAuthenticationParameters = + AuthParams}, + SecBytes = snmp_pdus:enc_usm_security_parameters(NUsmSecParams), + VsnHdr = Message#message.vsn_hdr, + NVsnHdr = VsnHdr#v3_hdr{msgSecurityParameters = SecBytes}, + Message#message{vsn_hdr = NVsnHdr}. + + +%% Not very nice... +%% This function patches the asn.1 encoded message. It changes the +%% AuthenticationParameters to 12 zeros. +%% NOTE: returns a deep list of bytes +patch_packet([48 | T]) -> + %% Length for whole packet - 2 is tag for version + {Len1, [2 | T1]} = split_len(T), + %% Length for version - 48 is tag for header data + {Len2, [Vsn,48|T2]} = split_len(T1), + %% Length for header data + {Len3, T3} = split_len(T2), + [48,Len1,2,Len2,Vsn,48,Len3|pp2(dec_len(Len3),T3)]. + +%% Skip HeaderData - 4 is tag for SecurityParameters +pp2(0,[4|T]) -> + %% 48 is tag for UsmSecParams + {Len1,[48|T1]} = split_len(T), + %% 4 is tag for EngineID + {Len2,[4|T2]} = split_len(T1), + %% Len 3 is length for EngineID + {Len3,T3} = split_len(T2), + [4,Len1,48,Len2,4,Len3|pp3(dec_len(Len3),T3)]; +pp2(N,[H|T]) -> + [H|pp2(N-1,T)]. + +%% Skip EngineID - 2 is tag for EngineBoots +pp3(0,[2|T]) -> + {Len1,T1} = split_len(T), + [2,Len1|pp4(dec_len(Len1),T1)]; +pp3(N,[H|T]) -> + [H|pp3(N-1,T)]. + +%% Skip EngineBoots - 2 is tag for EngineTime +pp4(0,[2|T]) -> + {Len1,T1} = split_len(T), + [2,Len1|pp5(dec_len(Len1),T1)]; +pp4(N,[H|T]) -> + [H|pp4(N-1,T)]. + +%% Skip EngineTime - 4 is tag for UserName +pp5(0,[4|T]) -> + {Len1,T1} = split_len(T), + [4,Len1|pp6(dec_len(Len1),T1)]; +pp5(N,[H|T]) -> + [H|pp5(N-1,T)]. + +%% Skip UserName - 4 is tag for AuthenticationParameters +%% This is what we're looking for! +pp6(0,[4|T]) -> + {Len1,[_,_,_,_,_,_,_,_,_,_,_,_|T1]} = split_len(T), + 12 = dec_len(Len1), + [4,Len1,?twelwe_zeros|T1]; +pp6(N,[H|T]) -> + [H|pp6(N-1,T)]. + + +%% Returns {LengthOctets, Rest} +split_len([Hd|Tl]) -> + %% definite form + case is8set(Hd) of + 0 -> % Short form + {Hd,Tl}; + 1 -> % Long form - at least one more octet + No = clear(Hd, 8), + {DigList,Rest} = head(No,Tl), + {[Hd | DigList], Rest} + end. + +dec_len(D) when is_integer(D) -> + D; +dec_len([_LongOctet|T]) -> + dl(T). +dl([D]) -> + D; +dl([A,B]) -> + (A bsl 8) bor B; +dl([A,B,C]) -> + (A bsl 16) bor (B bsl 8) bor C; +dl([0 | T]) -> + dl(T). + +head(L,List) when length(List) == L -> {List,[]}; +head(L,List) -> + head(L,List,[]). + +head(0,L,Res) -> + {lists:reverse(Res),L}; + +head(Int,[H|Tail],Res) -> + head(Int-1,Tail,[H|Res]). + +clear(Byte, 8) -> + Byte band 127. +%% clear(Byte,Pos) when Pos < 9 -> +%% Mask = bnot bset(0,Pos), +%% Mask band Byte. + +%% bset(Byte, 8) -> +%% Byte bor 2#10000000; +%% bset(Byte, Pos) when (Pos < 9) -> +%% Mask = 1 bsl (Pos-1), +%% Byte bor Mask. + +is8set(Byte) -> + if + Byte > 127 -> 1; + true -> 0 + end. + +error(Reason) -> + throw({error, Reason}). + diff --git a/lib/snmp/src/misc/snmp_verbosity.erl b/lib/snmp/src/misc/snmp_verbosity.erl new file mode 100644 index 0000000000..85037ba2ae --- /dev/null +++ b/lib/snmp/src/misc/snmp_verbosity.erl @@ -0,0 +1,161 @@ +%% +%% %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(snmp_verbosity). + +-include_lib("stdlib/include/erl_compile.hrl"). + +-export([print/4,print/5,printc/4,validate/1]). + +-export([process_args/2]). + +print(silence,_Severity,_Format,_Arguments) -> + ok; +print(Verbosity,Severity,Format,Arguments) -> + print1(printable(Verbosity,Severity),Format,Arguments). + + +print(silence,_Severity,_Module,_Format,_Arguments) -> + ok; +print(Verbosity,Severity,Module,Format,Arguments) -> + print1(printable(Verbosity,Severity),Module,Format,Arguments). + + +printc(silence,_Severity,_Format,_Arguments) -> + ok; +printc(Verbosity,Severity,Format,Arguments) -> + print2(printable(Verbosity,Severity),Format,Arguments). + + +print1(false,_Format,_Arguments) -> ok; +print1(Verbosity,Format,Arguments) -> + V = image_of_verbosity(Verbosity), + S = image_of_sname(get(sname)), + A = process_args(Arguments, []), + (catch io:format("*** [~s] SNMP ~s ~s *** ~n" + " " ++ Format ++ "~n", + [timestamp(), S, V | A])). + +print1(false,_Module,_Format,_Arguments) -> ok; +print1(Verbosity,Module,Format,Arguments) -> + V = image_of_verbosity(Verbosity), + S = image_of_sname(get(sname)), + A = process_args(Arguments, []), + (catch io:format("*** [~s] SNMP ~s ~s ~s *** ~n" + " " ++ Format ++ "~n", + [timestamp(), S, Module, V | A])). + + +print2(false,_Format,_Arguments) -> ok; +print2(_Verbosity,Format,Arguments) -> + A = process_args(Arguments, []), + (catch io:format(Format ++ "~n",A)). + + +timestamp() -> + format_timestamp(now()). + +format_timestamp({_N1, _N2, N3} = Now) -> + {Date, Time} = calendar:now_to_datetime(Now), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", + [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), + lists:flatten(FormatDate). + +process_args([], Acc) -> + lists:reverse(Acc); +process_args([{vapply, {M,F,A}}|T], Acc) + when is_atom(M) andalso is_atom(F) andalso is_list(A) -> + process_args(T, [(catch apply(M,F,A))|Acc]); +process_args([H|T], Acc) -> + process_args(T, [H|Acc]). + + +%% printable(Verbosity,Severity) +printable(info,info) -> info; +printable(log,info) -> info; +printable(log,log) -> log; +printable(debug,info) -> info; +printable(debug,log) -> log; +printable(debug,debug) -> debug; +printable(trace,V) -> V; +printable(_Verb,_Sev) -> false. + + +image_of_verbosity(info) -> "INFO"; +image_of_verbosity(log) -> "LOG"; +image_of_verbosity(debug) -> "DEBUG"; +image_of_verbosity(trace) -> "TRACE"; +image_of_verbosity(_) -> "". + +%% ShortName +image_of_sname(ma) -> "MASTER-AGENT"; +image_of_sname(maw) -> io_lib:format("MASTER-AGENT-worker(~p)",[self()]); +image_of_sname(madis) -> io_lib:format("MASTER-AGENT-discovery_inform_sender(~p)", + [self()]); +image_of_sname(mais) -> io_lib:format("MASTER-AGENT-inform_sender(~p)", + [self()]); +image_of_sname(mats) -> io_lib:format("MASTER-AGENT-trap_sender(~p)", + [self()]); +image_of_sname(maph) -> io_lib:format("MASTER-AGENT-pdu_handler(~p)", + [self()]); +image_of_sname(sa) -> "SUB-AGENT"; +image_of_sname(saw) -> io_lib:format("SUB-AGENT-worker(~p)",[self()]); +image_of_sname(sais) -> io_lib:format("SUB-AGENT-inform_sender(~p)", + [self()]); +image_of_sname(sats) -> io_lib:format("SUB-AGENT-trap_sender(~p)", + [self()]); +image_of_sname(saph) -> io_lib:format("SUB-AGENT-pdu_handler(~p)", + [self()]); +image_of_sname(nif) -> "A-NET-IF"; +image_of_sname(ldb) -> "A-LOCAL-DB"; +image_of_sname(ns) -> "A-NOTE-STORE"; +image_of_sname(ss) -> "A-SYMBOLIC-STORE"; +image_of_sname(asup) -> "A-SUPERVISOR"; +image_of_sname(ms) -> "A-MIB-SERVER"; +image_of_sname(tcs) -> "A-TARGET-CACHE-SERVER"; +image_of_sname(conf) -> "A-CONF"; + +image_of_sname(abs) -> "A-BKP"; +image_of_sname(albs) -> "A-LDB-BKP"; +image_of_sname(ambs) -> "A-MS-BKP"; +image_of_sname(asbs) -> "A-SS-BKP"; +image_of_sname(mcbs) -> "M-C-BKP"; + +image_of_sname(mse) -> "M-SERVER"; +image_of_sname(msew) -> io_lib:format("M-SERVER-worker(~p)", [self()]); +image_of_sname(mns) -> "M-NOTE-STORE"; +image_of_sname(mnif) -> "M-NET-IF"; +image_of_sname(mconf) -> "M-CONF"; + +image_of_sname(mgr) -> "MGR"; +image_of_sname(mgr_misc) -> "MGR_MISC"; + +image_of_sname(undefined) -> ""; +image_of_sname(V) -> lists:flatten(io_lib:format("~p",[V])). + + +validate(info) -> info; +validate(log) -> log; +validate(debug) -> debug; +validate(trace) -> trace; +validate(_) -> silence. + diff --git a/lib/snmp/src/misc/snmp_verbosity.hrl b/lib/snmp/src/misc/snmp_verbosity.hrl new file mode 100644 index 0000000000..934d32831f --- /dev/null +++ b/lib/snmp/src/misc/snmp_verbosity.hrl @@ -0,0 +1,64 @@ +%% +%% %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% +%% + +-ifndef(dont_use_verbosity). + +-define(vapply(M,F,A),{vapply, {M,F,A}}). + +-ifdef(VMODULE). + +-define(vinfo(F,A), snmp_verbosity:print(get(verbosity),info, ?VMODULE,F,A)). +-define(vlog(F,A), snmp_verbosity:print(get(verbosity),log, ?VMODULE,F,A)). +-define(vdebug(F,A),snmp_verbosity:print(get(verbosity),debug,?VMODULE,F,A)). +-define(vtrace(F,A),snmp_verbosity:print(get(verbosity),trace,?VMODULE,F,A)). + +-else. + +-define(vinfo(F,A), snmp_verbosity:print(get(verbosity),info, F,A)). +-define(vlog(F,A), snmp_verbosity:print(get(verbosity),log, F,A)). +-define(vdebug(F,A),snmp_verbosity:print(get(verbosity),debug,F,A)). +-define(vtrace(F,A),snmp_verbosity:print(get(verbosity),trace,F,A)). + +-endif. + +-define(vvalidate(V), snmp_verbosity:validate(V)). + +-define(vinfoc(F,A), snmp_verbosity:printc(get(verbosity),info, F,A)). +-define(vlogc(F,A), snmp_verbosity:printc(get(verbosity),log, F,A)). +-define(vdebugc(F,A),snmp_verbosity:printc(get(verbosity),debug,F,A)). +-define(vtracec(F,A),snmp_verbosity:printc(get(verbosity),trace,F,A)). + +-else. + +-define(vvalidate(V),ok). + +-define(vinfo(F,A),ok). +-define(vlog(F,A),ok). +-define(vdebug(F,A),ok). +-define(vtrace(F,A),ok). + +-define(vinfoc(F,A),ok). +-define(vlogc(F,A),ok). +-define(vdebugc(F,A),ok). +-define(vtracec(F,A),ok). + +-endif. + + + diff --git a/lib/snmp/src/subdirs.mk b/lib/snmp/src/subdirs.mk new file mode 100644 index 0000000000..1cbcd04b54 --- /dev/null +++ b/lib/snmp/src/subdirs.mk @@ -0,0 +1,22 @@ +#-*-makefile-*- ; force emacs to enter makefile-mode + +# %CopyrightBegin% +# +# Copyright Ericsson AB 2003-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% + +SUB_DIRECTORIES = compile app misc agent manager +# SUB_DIRECTORIES = compile app misc agent manager proxy + |