diff options
Diffstat (limited to 'lib/snmp/src/misc')
-rw-r--r-- | lib/snmp/src/misc/Makefile | 122 | ||||
-rw-r--r-- | lib/snmp/src/misc/depend.mk | 58 | ||||
-rw-r--r-- | lib/snmp/src/misc/modules.mk | 33 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_conf.erl | 421 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_config.erl | 2348 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_debug.hrl | 38 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_log.erl | 584 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_mini_mib.erl | 151 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_misc.erl | 465 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_note_store.erl | 450 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_pdus.erl | 770 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_usm.erl | 367 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_verbosity.erl | 161 | ||||
-rw-r--r-- | lib/snmp/src/misc/snmp_verbosity.hrl | 64 |
14 files changed, 6032 insertions, 0 deletions
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. + + + |