diff options
Diffstat (limited to 'lib/snmp/src/agent/snmpa_agent.erl')
-rw-r--r-- | lib/snmp/src/agent/snmpa_agent.erl | 3964 |
1 files changed, 3964 insertions, 0 deletions
diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl new file mode 100644 index 0000000000..508a1da514 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_agent.erl @@ -0,0 +1,3964 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(snmpa_agent). + +-include_lib("kernel/include/file.hrl"). +-include("snmpa_internal.hrl"). +-include("snmp_types.hrl"). +-include("snmp_debug.hrl"). +-include("snmp_verbosity.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). + +%% External exports +-export([start_link/4, start_link/5, stop/1]). +-export([subagent_set/2, + load_mibs/2, unload_mibs/2, which_mibs/1, whereis_mib/2, info/1, + register_subagent/3, unregister_subagent/2, + send_trap/6, + register_notification_filter/5, + unregister_notification_filter/2, + which_notification_filter/1, + get_net_if/1]). +-export([ + discovery/6, + is_originating_discovery_enabled/0, + is_terminating_discovery_enabled/0, + terminating_discovery_stage2/0, + terminating_trigger_username/0 + ]). +-export([verbosity/2, dump_mibs/1, dump_mibs/2]). +-export([validate_err/3, make_value_a_correct_value/3, + do_get/3, do_get/4, + get/2, get/3, get_next/2, get_next/3]). +-export([mib_of/1, mib_of/2, me_of/1, me_of/2, + invalidate_mibs_cache/1, + enable_mibs_cache/1, disable_mibs_cache/1, + gc_mibs_cache/1, gc_mibs_cache/2, gc_mibs_cache/3, + enable_mibs_cache_autogc/1, disable_mibs_cache_autogc/1, + update_mibs_cache_age/2, + update_mibs_cache_gclimit/2]). +-export([get_agent_mib_storage/0, db/1, + backup/2]). +-export([get_log_type/1, set_log_type/2]). +-export([get_request_limit/1, set_request_limit/2]). +-export([invalidate_ca_cache/0]). +-export([restart_worker/1, restart_set_worker/1]). + +%% Internal exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3, tr_var/2, tr_varbind/1, + handle_pdu/7, worker/2, worker_loop/1, do_send_trap/6]). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + +-define(empty_pdu_size, 21). + +-ifdef(snmp_extended_verbosity). +-define(vt(F,A), ?vtrace(F, A)). +-else. +-define(vt(_F, _A), ok). +-endif. + +-define(DISCO_TERMINATING_TRIGGER_USERNAME, ""). + + +-ifdef(snmp_debug). +-define(GS_START_LINK3(Prio, Parent, Ref, Opts), + gen_server:start_link(?MODULE, [Prio, Parent, Ref, Opts], + [{debug,[trace]}])). +-define(GS_START_LINK4(Prio, Name, Parent, Ref, Opts), + gen_server:start_link({local, Name}, ?MODULE, + [Prio, Parent, Ref, Opts], + [{debug,[trace]}])). +-else. +-define(GS_START_LINK3(Prio, Parent, Ref, Opts), + gen_server:start_link(?MODULE, [Prio, Parent, Ref, Opts],[])). +-define(GS_START_LINK4(Prio, Name, Parent, Ref, Opts), + gen_server:start_link({local, Name}, ?MODULE, + [Prio, Parent, Ref, Opts],[])). +-endif. + + +-record(notification_filter, {id, mod, data}). +-record(disco, + {from, rec, sender, target, engine_id, + sec_level, ctx, ivbs, stage, handler, extra}). + + +%%----------------------------------------------------------------- +%% The agent is multi-threaded, i.e. each request is handled +%% by a separate process. However, in the normal case, there +%% is just one request handled at the time. In order to improve +%% performance, there is always two worker processes alive. They are +%% created at initialization time. There is always one worker +%% dedicated to SET-handling. When a get*-request is received, +%% it is sent to the worker, and the worker is marked as busy. +%% If a request is received when the worker is busy, a new temporary +%% worker is spawned. +%% Code change +%% =========== +%% Note that the worker(s) execute the same module as the master +%% agent. For code change we have two options - ignore the workers, +%% or send them a code change message. +%%----------------------------------------------------------------- +-record(state, {type, + parent, + worker, + worker_state = ready, + set_worker, + multi_threaded, + ref, + vsns, + nfilters = [], + note_store, + mib_server, %% Currently unused + net_if, %% Currently unused + net_if_mod, + backup, + disco, + mibs_cache_request}). + + +%%%----------------------------------------------------------------- +%%% This module implements the agent machinery; both for the master +%%% agent and the subagents. +%%%----------------------------------------------------------------- +%%% Table of contents +%%% ================= +%%% 1. Interface +%%% 2. Main loop +%%% 3. GET REQUEST +%%% 4. GET-NEXT REQUEST +%%% 5. GET-BULK REQUEST +%%% 6. SET REQUEST +%%% 7. Misc functions +%%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Parent is a Pid (of master_agent) or none +%% Options is a list of Option, where Option is +%% {mibs, Mibs} +%% {net_if, NetIfModule} +%% {priority, Prio} +%% {verbosity, Verbosity} +%% {multi_threaded, Bool} true means that SETs are serialized, +%% while GETs are concurrent, even with a SET. +%% {set_mechanism, SetModule} % undocumented feature +%% +%% The following options are now removed - they are not needed +%% anymore when VACM is standard for authentication, and works +%% with all versions, and trap sending is standardized too. +%% {authentication_service, AuthModule} % undocumented feature +%% {trap_mechanism, TrapModule} % undocumented feature +%% Note: authentication_service is reintroduced for AXD301 (OTP-3324). +%%----------------------------------------------------------------- +start_link(Prio, Parent, Ref, Options) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p", [Prio, Parent, Ref, Options]), + %% gen_server:start_link(?MODULE, [Prio, Parent, Ref, Options], []). + ?GS_START_LINK3(Prio, Parent, Ref, Options). + +start_link(Prio, Name, Parent, Ref, Options) -> + ?d("start_link -> entry with" + "~n Prio: ~p" + "~n Name: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p", [Prio, Name, Parent, Ref, Options]), +% gen_server:start_link({local, Name}, ?MODULE, +% [Prio, Parent, Ref, Options], []). + ?GS_START_LINK4(Prio, Name, Parent, Ref, Options). + +stop(Agent) -> call(Agent, stop). + +restart_worker(Agent) -> + call(Agent, restart_worker). + +restart_set_worker(Agent) -> + call(Agent, restart_set_worker). + +get_log_type(Agent) -> + call(Agent, get_log_type). + +set_log_type(Agent, NewType) -> + call(Agent, {set_log_type, NewType}). + +get_request_limit(Agent) -> + call(Agent, get_request_limit). + +set_request_limit(Agent, NewLimit) -> + call(Agent, {set_request_limit, NewLimit}). + +mib_of(Oid) when is_list(Oid) -> + mib_of(snmp_master_agent, Oid). + +mib_of(Agent, Oid) when is_list(Oid) -> + call(Agent, {mib_of, Oid}). + +me_of(Oid) when is_list(Oid) -> + me_of(snmp_master_agent, Oid). + +me_of(Agent, Oid) when is_list(Oid) -> + call(Agent, {me_of, Oid}). + + +invalidate_mibs_cache(Agent) -> + call(Agent, {mibs_cache_request, invalidate_cache}). + + +gc_mibs_cache(Agent) -> + call(Agent, {mibs_cache_request, gc_cache}). + +gc_mibs_cache(Agent, Age) -> + call(Agent, {mibs_cache_request, {gc_cache, Age}}). + +gc_mibs_cache(Agent, Age, GcLimit) -> + call(Agent, {mibs_cache_request, {gc_cache, Age, GcLimit}}). + + +enable_mibs_cache(Agent) -> + call(Agent, {mibs_cache_request, enable_cache}). + +disable_mibs_cache(Agent) -> + call(Agent, {mibs_cache_request, disable_cache}). + + +enable_mibs_cache_autogc(Agent) -> + call(Agent, {mibs_cache_request, enable_autogc}). + +disable_mibs_cache_autogc(Agent) -> + call(Agent, {mibs_cache_request, disable_autogc}). + + +update_mibs_cache_gclimit(Agent, GcLimit) -> + call(Agent, {mibs_cache_request, {update_gclimit, GcLimit}}). + + +update_mibs_cache_age(Agent, Age) -> + call(Agent, {mibs_cache_request, {update_age, Age}}). + + +init([Prio, Parent, Ref, Options]) -> + ?d("init -> entry with" + "~n Prio: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p", [Prio, Parent, Ref, Options]), + case (catch do_init(Prio, Parent, Ref, Options)) of + {ok, State} -> + ?vdebug("started",[]), + {ok, State}; + {error, Reason} -> + config_err("failed starting agent: ~n~p", [Reason]), + {stop, Reason} + end. + +do_init(Prio, Parent, Ref, Options) -> + process_flag(priority, Prio), + put(sname,short_name(Parent)), + put(verbosity,get_verbosity(Options)), + ?vlog("starting with: " + "~n Prio: ~p" + "~n Parent: ~p" + "~n Ref: ~p" + "~n Options: ~p",[Prio, Parent, Ref, Options]), + + Mibs = get_mibs(Options), + SetModule = get_set_mechanism(Options), + put(set_module, SetModule), + + %% OTP-3324. For AXD301. + AuthModule = get_authentication_service(Options), + put(auth_module, AuthModule), + + MultiT = get_multi_threaded(Options), + Vsns = get_versions(Options), + + NS = start_note_store(Prio, Ref, Options), + {Type, NetIfPid, NetIfMod} = + start_net_if(Parent, Prio, Ref, Vsns, NS, Options), + + MibPid = start_mib_server(Prio, Ref, Mibs, Options), + + put(net_if, NetIfPid), + put(mibserver, MibPid), + process_flag(trap_exit, true), + {Worker, SetWorker} = workers_start(MultiT), + {ok, #state{type = Type, + parent = Parent, + worker = Worker, + set_worker = SetWorker, + multi_threaded = MultiT, + ref = Ref, + vsns = Vsns, + note_store = NS, + net_if_mod = NetIfMod}}. + + +start_note_store(Prio, Ref, Options) -> + ?vdebug("start_note_store -> with Prio: ~p", [Prio]), + NsOpts = get_note_store_opt(Options), + + ?vtrace("start_note_store -> NsOpts: ~p", [NsOpts]), + + case (catch snmpa_misc_sup:start_note_store(Prio, Ref, NsOpts)) of + {ok, Pid} -> + ?vdebug("start_note_store -> Pid: ~p", [Pid]), + Pid; + {error, Reason} -> + ?vinfo("error starting note store: ~n~p",[Reason]), + throw({error, {note_store_error, Reason}}); + {'EXIT', Reason} -> + ?vinfo("exit starting note store: ~n~p",[Reason]), + throw({error, {note_store_exit, Reason}}); + Error -> + ?vinfo("failed starting note store: ~n~p",[Error]), + throw({error, {note_store_failed, Error}}) + end. + + +start_net_if(none, Prio, Ref, Vsns, NoteStore, Options) -> + ?vdebug("start_net_if(none) -> with Prio: ~p", [Prio]), + NetIfOpts = get_net_if_opt(Options), + Verbosity = get_net_if_verbosity(NetIfOpts), + Mod = get_net_if_module(NetIfOpts), + NiOptions = get_net_if_options(NetIfOpts), + NiOpts = [{versions, Vsns}, + {verbosity, Verbosity} | NiOptions], + + ?vtrace("start_net_if -> " + "~n Mod: ~p" + "~n NiOpts: ~p",[Mod, NiOpts]), + + case (catch snmpa_misc_sup:start_net_if(Prio, NoteStore, Ref, self(), + Mod, NiOpts)) of + {ok, Pid} -> + ?vdebug("start_net_if -> Pid: ~p", [Pid]), + {master_agent, Pid, Mod}; + {error, Reason} -> + ?vinfo("error starting net if: ~n~p",[Reason]), + throw({error, {net_if_error, Reason}}); + {'EXIT', Reason} -> + ?vinfo("exit starting net if: ~n~p",[Reason]), + throw({error, {net_if_exit, Reason}}); + Error -> + ?vinfo("failed starting net if: ~n~p",[Error]), + throw({error, {net_if_failed, Error}}) + end; +start_net_if(Parent, _Prio, _Ref, _Vsns, _NoteStore, _Options) + when is_pid(Parent) -> + ?vdebug("start_net_if(~p) -> subagent => ignore", [Parent]), + {subagent, undefined, undefined}. + + +start_mib_server(Prio, Ref, Mibs, Options) -> + ?vdebug("start_mib_server -> with Prio: ~p", [Prio]), + MibStorage = get_mib_storage(Options), + MibsOpts = [{mib_storage, MibStorage}|get_option(mib_server, Options, [])], + + ?vtrace("start_mib_server -> " + "~n Mibs: ~p" + "~n MibsOpts: ~p", [Mibs, MibsOpts]), + + case (catch snmpa_misc_sup:start_mib_server(Prio, Ref, Mibs, MibsOpts)) of + {ok, Pid} -> + ?vdebug("start_mib_server -> Pid: ~p", [Pid]), + Pid; + {error, Reason} -> + ?vinfo("error starting mib server: ~n~p",[Reason]), + throw({error, {mib_server_error, Reason}}); + {'EXIT', Reason} -> + ?vinfo("exit starting mib server: ~n~p",[Reason]), + throw({error, {mib_server_exit, Reason}}); + Error -> + ?vinfo("failed starting mib server: ~n~p",[Error]), + throw({error, {mib_server_failed, Error}}) + end. + + +%%----------------------------------------------------------------- +%% Purpose: We must calculate the length of an empty Pdu. This +%% length is used to calculate the max pdu size allowed +%% for each get-bulk-request. This size is +%% dependent on the varbinds. It is calculated +%% as EmptySize + 8. 8 comes from the fact that the +%% maximum pdu size needs 31 bits which needs 5 * 7 bits to be +%% expressed. One 7bit octet is already present in the +%% empty pdu, leaving 4 more 7bit octets. The length is +%% repeated twice, once for the varbinds, and once for the +%% entire pdu; 2 * 4 = 8. +%% Actually, this function is not used, we use a constant instead. +%%----------------------------------------------------------------- +%% Ret: 21 +%% empty_pdu() -> +%% Pdu = #pdu{type = 'get-response', +%% request_id = 1, +%% error_status = noError, +%% error_index = 0, +%% varbinds = []}, +%% length(snmp_pdus:enc_pdu(Pdu)) + 8. + + +%%%-------------------------------------------------- +%%% 1. Interface +%%%-------------------------------------------------- +%% Called by administrator (not subagent; deadlock could occur) +register_subagent(Agent, SubTreeOid, SubagentPid) -> + call(Agent, {register_subagent, SubTreeOid, SubagentPid}). + +%% Called by administrator (not subagent; deadlock could occur) +unregister_subagent(Agent, SubagentOidOrPid) -> + call(Agent, {unregister_subagent, SubagentOidOrPid}). + + +%%----------------------------------------------------------------- +%% These subagent_ functions either return a value, or exits +%% with {nodedown, Node} | Reason. +%%----------------------------------------------------------------- +subagent_get(SubAgent, Varbinds, IsNotification) -> + PduData = get_pdu_data(), + call(SubAgent, {subagent_get, Varbinds, PduData, IsNotification}). + +subagent_get_next(SubAgent, MibView, Varbinds) -> + PduData = get_pdu_data(), + call(SubAgent, {subagent_get_next, MibView, Varbinds, PduData}). + +subagent_set(SubAgent, Arguments) -> + PduData = get_pdu_data(), + call(SubAgent, {subagent_set, Arguments, PduData}). + + +%% Called by administrator (not agent; deadlock would occur) +load_mibs(Agent, Mibs) -> + call(Agent, {load_mibs, Mibs}). + +%% Called by administrator (not agent; deadlock would occur) +unload_mibs(Agent, Mibs) -> + call(Agent, {unload_mibs, Mibs}). + +which_mibs(Agent) -> + call(Agent, which_mibs). + +whereis_mib(Agent, Mib) -> + call(Agent, {whereis_mib, Mib}). + +info(Agent) -> + call(Agent, info). + + +get_net_if(Agent) -> + call(Agent, get_net_if). + + +register_notification_filter(Agent, Id, Mod, Args, Where) + when (Where =:= first) orelse (Where =:= last) -> + case (catch verify_notification_filter_behaviour(Mod)) of + ok -> + call(Agent, {register_notification_filter, Id, Mod, Args, Where}); + Error -> + Error + end; +register_notification_filter(Agent, Id, Mod, Args, {Loc, _Id} = Where) + when (Loc =:= insert_before) orelse (Loc =:= insert_after) -> + case (catch verify_notification_filter_behaviour(Mod)) of + ok -> + call(Agent, {register_notification_filter, Id, Mod, Args, Where}); + Error -> + Error + end. + +verify_notification_filter_behaviour(Mod) -> + snmp_misc:verify_behaviour(snmpa_notification_filter, Mod). + +unregister_notification_filter(Agent, Id) -> + call(Agent, {unregister_notification_filter, Id}). + +which_notification_filter(Agent) -> + call(Agent, which_notification_filter). + + +send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds) -> + ?d("send_trap -> entry with" + "~n self(): ~p" + "~n Agent: ~p [~p]" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n CtxName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [self(), Agent, wis(Agent), Trap, NotifyName, CtxName, Recv, Varbinds]), + Msg = {send_trap, Trap, NotifyName, CtxName, Recv, Varbinds}, + case (wis(Agent) =:= self()) of + false -> + call(Agent, Msg); + true -> + Agent ! Msg + end. + + +%% -- Discovery functions -- + +disco_opts() -> + case ets:lookup(snmp_agent_table, discovery) of + [] -> + []; + [{discovery, DiscoOptions}] -> + DiscoOptions + end. + +originating_disco_opts() -> + DiscoOpts = disco_opts(), + case lists:keysearch(originating, 1, DiscoOpts) of + {value, {originating, OrigDisco}} -> + OrigDisco; + _ -> + [] + end. + +is_originating_discovery_enabled() -> + OrigDisco = originating_disco_opts(), + case lists:keysearch(enable, 1, OrigDisco) of + {value, {enable, false}} -> + false; + _ -> + true + end. + +terminating_disco_opts() -> + DiscoOpts = disco_opts(), + case lists:keysearch(terminating, 1, DiscoOpts) of + {value, {terminating, TermDisco}} -> + TermDisco; + _ -> + [] + end. + +is_terminating_discovery_enabled() -> + TermDisco = terminating_disco_opts(), + case lists:keysearch(enable, 1, TermDisco) of + {value, {enable, false}} -> + false; + _ -> + true + end. + +terminating_discovery_stage2() -> + Default = discovery, + TermDisco = terminating_disco_opts(), + case lists:keysearch(stage2, 1, TermDisco) of + {value, {stage2, Stage2}} when ((Stage2 =:= discovery) orelse (Stage2 =:= plain)) -> + Stage2; + _ -> + Default + end. + +terminating_trigger_username() -> + Default = ?DISCO_TERMINATING_TRIGGER_USERNAME, + TermDisco = terminating_disco_opts(), + case lists:keysearch(trigger_username, 1, TermDisco) of + {value, {trigger_username, Trigger}} when is_list(Trigger) -> + Trigger; + _ -> + Default + end. + + +discovery(TargetName, Notification, ContextName, Varbinds, + DiscoHandler, ExtraInfo) -> + case is_originating_discovery_enabled() of + true -> + Agent = snmp_master_agent, + call(Agent, + {discovery, + TargetName, Notification, ContextName, Varbinds, + DiscoHandler, ExtraInfo}); + false -> + {error, not_enabled} + end. + +wis(Pid) when is_pid(Pid) -> + Pid; +wis(Atom) when is_atom(Atom) -> + whereis(Atom). + +forward_trap(Agent, TrapRecord, NotifyName, CtxName, Recv, Varbinds) -> + Agent ! {forward_trap, TrapRecord, NotifyName, CtxName, Recv, Varbinds}. + + +%%----------------------------------------------------------------- +%% Args: Vars = [Oid] +%% Returns: [Value] +%% Called from a program to get variables. Don't call this from +%% an instrumentation function; deadlock can occur! +%%----------------------------------------------------------------- +get(Agent, Vars) -> + call(Agent, {get, Vars, ""}). + +get(Agent, Vars, Context) -> + call(Agent, {get, Vars, Context}). + +get_next(Agent, Vars) -> + call(Agent, {get_next, Vars, ""}). + +get_next(Agent, Vars, Context) -> + call(Agent, {get_next, Vars, Context}). + + +%%----------------------------------------------------------------- + +backup(Agent, BackupDir) when is_list(BackupDir) -> + call(Agent, {backup, BackupDir}). + + +%%----------------------------------------------------------------- +%% Runtime debug support. +%%----------------------------------------------------------------- +dump_mibs(Agent) -> + call(Agent, dump_mibs). +dump_mibs(Agent, File) when is_list(File) -> + call(Agent, {dump_mibs, File}). + + +%%----------------------------------------------------------------- +%% Runtime debug (verbosity) support. +%%----------------------------------------------------------------- +verbosity(net_if,Verbosity) -> + cast(snmp_master_agent,{net_if_verbosity,Verbosity}); +verbosity(mib_server,Verbosity) -> + cast(snmp_master_agent,{mib_server_verbosity,Verbosity}); +verbosity(note_store,Verbosity) -> + cast(snmp_master_agent,{note_store_verbosity,Verbosity}); +verbosity(sub_agents,Verbosity) -> + cast(snmp_master_agent,{sub_agents_verbosity,Verbosity}); +verbosity(master_agent,Verbosity) -> + cast(snmp_master_agent,{verbosity,Verbosity}); +verbosity(Agent,{sub_agents,Verbosity}) -> + cast(Agent,{sub_agents_verbosity,Verbosity}); +verbosity(Agent,Verbosity) -> + cast(Agent,{verbosity,Verbosity}). + + +%%%-------------------------------------------------- + +get_agent_mib_storage() -> + ets:lookup_element(snmp_agent_table, agent_mib_storage, 2). + +db(Tab) -> + {Tab, get_agent_mib_storage()}. + + +%%%-------------------------------------------------- +%%% 2. Main loop +%%%-------------------------------------------------- +%% gen_server:reply(From, Reply) +handle_info({discovery_response, Response}, S) -> + ?vdebug("handle_info(discovery_response) -> entry with" + "~n Response: ~p", [Response]), + NewS = handle_discovery_response(S, Response), + {noreply, NewS}; + +handle_info({snmp_pdu, Vsn, Pdu, PduMS, ACMData, Address, Extra}, S) -> + ?vdebug("handle_info(snmp_pdu) -> entry with" + "~n Vsn: ~p" + "~n Pdu: ~p" + "~n Address: ~p", [Vsn, Pdu, Address]), + + NewS = handle_snmp_pdu(is_valid_pdu_type(Pdu#pdu.type), + Vsn, Pdu, PduMS, ACMData, Address, Extra, S), + + {noreply, NewS}; + +handle_info(worker_available, S) -> + ?vdebug("worker available",[]), + {noreply, S#state{worker_state = ready}}; + +handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, S) -> + ?vlog("[handle_info] send trap request:" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [Trap,NotifyName,ContextName,Recv,Varbinds]), + case catch handle_send_trap(S, Trap, NotifyName, ContextName, + Recv, Varbinds) of + {ok, NewS} -> + {noreply, NewS}; + {'EXIT', R} -> + ?vinfo("Trap not sent:~n ~p", [R]), + {noreply, S}; + _ -> + {noreply, S} + end; + +handle_info({forward_trap, TrapRecord, NotifyName, ContextName, + Recv, Varbinds},S) -> + ?vlog("[handle_info] forward trap request:" + "~n TrapRecord: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [TrapRecord,NotifyName,ContextName,Recv,Varbinds]), + case (catch maybe_send_trap(S, TrapRecord, NotifyName, ContextName, + Recv, Varbinds)) of + {ok, NewS} -> + {noreply, NewS}; + {'EXIT', R} -> + ?vinfo("Trap not sent:~n ~p", [R]), + {noreply, S}; + _ -> + {noreply, S} + end; + +handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) -> + ?vlog("[handle_info] backup done:" + "~n Reply: ~p", [Reply]), + gen_server:reply(From, Reply), + {noreply, S#state{backup = undefined}}; + + +handle_info(invalidate_ca_cache, S) -> + invalidate_ca_cache(), + {noreply, S}; + + +%%----------------------------------------------------------------- +%% If a process crashes, we first check to see if it was the mib, +%% net-if or note-store. +%% Otherwise, we check to see if it was a subagent. In this case +%% we unregister the sa, and unlink us from the sa. +%%----------------------------------------------------------------- +handle_info({'EXIT', Pid, Reason}, #state{note_store = Pid} = S) -> + ?vlog("note store (~p) exited for reason ~n~p", [Pid, Reason]), + error_msg("note-store exited: ~n~p", [Reason]), + {stop, {note_store_exit, Reason}, S#state{note_store = undefined}}; +handle_info({'EXIT', Pid, Reason}, #state{worker = Pid} = S) -> + ?vlog("worker (~p) exited -> create new ~n ~p", [Pid, Reason]), + NewWorker = worker_start(), + {noreply, S#state{worker = NewWorker}}; +handle_info({'EXIT', Pid, Reason}, #state{set_worker = Pid} = S) -> + ?vlog("set-worker (~p) exited -> create new ~n ~p", [Pid,Reason]), + NewWorker = set_worker_start(), + {noreply, S#state{set_worker = NewWorker}}; +handle_info({'EXIT', Pid, Reason}, #state{parent = Pid} = S) -> + ?vlog("parent (~p) exited for reason ~n~p", [Pid,Reason]), + {stop, {parent_died, Reason}, S}; +handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) -> + ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]), + case Reason of + normal -> + {noreply, S}; + _ -> + gen_server:reply(From, {error, Reason}), + {noreply, S#state{backup = undefined}} + end; +handle_info({'EXIT', Pid, Reason}, S) -> + ?vlog("~p exited for reason ~p", [Pid, Reason]), + Mib = get(mibserver), + NetIf = get(net_if), + case Pid of + Mib -> + error_msg("mib-server exited: ~n~p", [Reason]), + {stop, {mib_server_exit, Reason}, S}; + NetIf -> + error_msg("net-if exited: ~n~p", [Reason]), + {stop, {net_if_exit, Reason}, S}; + _ -> + %% Could be a sub-agent + SAs = snmpa_mib:info(Mib, subagents), + case lists:keysearch(Pid, 1, SAs) of + {value, _} -> + ?vlog("subagent exit", []), + snmpa_mib:unregister_subagent(Mib, Pid), + unlink(Pid); + _ -> + %% Otherwise it was probably a worker thread - ignore + ok + end, + {noreply, S} + end; +handle_info({'DOWN', Ref, process, Pid, {mibs_cache_reply, Reply}}, + #state{mibs_cache_request = {Pid, Ref, From}} = S) -> + ?vlog("reply from the mibs cache request handler (~p): ~n~p", + [Pid, Reply]), + gen_server:reply(From, Reply), + {noreply, S#state{mibs_cache_request = undefined}}; + +handle_info(Info, S) -> + warning_msg("received unexpected info: ~n~p", [Info]), + {noreply, S}. + +handle_call(restart_worker, _From, #state{worker = Pid} = S) -> + if + is_pid(Pid) -> + ?vlog("[handle_call] restart worker ~p", [Pid]), + exit(Pid, kill); + true -> + ?vlog("[handle_call] not multi-threaded => " + "ignoring restart request", []), + ok + end, + {reply, ok, S}; +handle_call(restart_set_worker, _From, #state{set_worker = Pid} = S) -> + if + is_pid(Pid) -> + ?vlog("[handle_call] restart set worker: ~p", [Pid]), + exit(Pid, kill); + true -> + ?vlog("[handle_call] not multi-threaded => " + "ignoring restart request", []), + ok + end, + {reply, ok, S}; +handle_call({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, + _From, S) -> + ?vlog("[handle_call] send trap request:" + "~n Trap: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p" + "~n Recv: ~p" + "~n Varbinds: ~p", + [Trap,NotifyName,ContextName,Recv,Varbinds]), + case (catch handle_send_trap(S, Trap, NotifyName, ContextName, + Recv, Varbinds)) of + {ok, NewS} -> + {reply, ok, NewS}; + {'EXIT', Reason} -> + ?vinfo("Trap not sent:~n ~p", [Reason]), + {reply, {error, {send_failed, Reason}}, S}; + _ -> + ?vinfo("Trap not sent", []), + {reply, {error, send_failed}, S} + end; +handle_call({discovery, + TargetName, Notification, ContextName, Vbs, DiscoHandler, ExtraInfo}, + From, + #state{disco = undefined} = S) -> + ?vlog("[handle_call] initiate discovery process:" + "~n TargetName: ~p" + "~n Notification: ~p" + "~n ContextName: ~p" + "~n Vbs: ~p" + "~n DiscoHandler: ~p" + "~n ExtraInfo: ~p", + [TargetName, Notification, ContextName, Vbs, + DiscoHandler, ExtraInfo]), + case handle_discovery(S, From, TargetName, + Notification, ContextName, Vbs, DiscoHandler, + ExtraInfo) of + {ok, NewS} -> + ?vtrace("[handle_call] first stage of discovery process initiated", + []), + {noreply, NewS}; + {error, _} = Error -> + {reply, Error, S} + end; +handle_call({discovery, _TargetName, _Notification, _ContextName, _Vbs, _DiscoHandler, _ExtraInfo}, _From, + #state{disco = DiscoData} = S) -> + Reply = {error, {discovery_in_progress, DiscoData}}, + {reply, Reply, S}; +handle_call({subagent_get, Varbinds, PduData, IsNotification}, _From, S) -> + ?vlog("[handle_call] subagent get:" + "~n Varbinds: ~p" + "~n PduData: ~p", + [Varbinds,PduData]), + put_pdu_data(PduData), + {reply, do_get(Varbinds, IsNotification), S}; +handle_call({subagent_get_next, MibView, Varbinds, PduData}, _From, S) -> + ?vlog("[handle_call] subagent get-next:" + "~n MibView: ~p" + "~n Varbinds: ~p" + "~n PduData: ~p", + [MibView,Varbinds,PduData]), + put_pdu_data(PduData), + {reply, do_get_next(MibView, Varbinds), S}; +handle_call({subagent_set, Arguments, PduData}, _From, S) -> + ?vlog("[handle_call] subagent set:" + "~n Arguments: ~p" + "~n PduData: ~p", + [Arguments,PduData]), + put_pdu_data(PduData), + {reply, do_subagent_set(Arguments), S}; + +handle_call({get, Vars, Context}, _From, S) -> + ?vlog("[handle_call] get:" + "~n Vars: ~p" + "~n Context: ~p", [Vars, Context]), + put_pdu_data({undefined, undefined, undefined, undefined, Context}), + case catch mapfoldl({?MODULE, tr_var}, [], 1, Vars) of + {error, Reason} -> {reply, {error, Reason}, S}; + {_, Varbinds} -> + ?vdebug("Varbinds: ~p",[Varbinds]), + Reply = + case do_get(Varbinds, false) of + {noError, 0, NewVarbinds} -> + Vbs = lists:keysort(#varbind.org_index, NewVarbinds), + [Value || #varbind{value = Value} <- Vbs]; + {ErrorStatus, ErrIndex, _} -> + N = lists:nth(ErrIndex, Vars), + {error, {ErrorStatus, N}} + end, + {reply, Reply, S} + end; + +handle_call({get_next, Vars, Context}, _From, S) -> + ?vlog("[handle_call] get_next:" + "~n Vars: ~p" + "~n Context: ~p",[Vars, Context]), + put_pdu_data({undefined, undefined, undefined, undefined, Context}), + case catch mapfoldl({?MODULE, tr_var}, [], 1, Vars) of + {error, Reason} -> {reply, {error, Reason}, S}; + {_, Varbinds} -> + ?vdebug("Varbinds: ~p",[Varbinds]), + MibView = snmpa_acm:get_root_mib_view(), + Reply = + case do_get_next(MibView, Varbinds) of + {noError, 0, NewVarbinds} -> + Vbs = lists:keysort(#varbind.org_index, NewVarbinds), + [{Oid,Val} || #varbind{oid = Oid, value = Val} <- Vbs]; + {ErrorStatus, ErrIndex, _} -> + N = lists:nth(ErrIndex, Vars), + {error, {ErrorStatus, N}} + end, + {reply, Reply, S} + end; + +handle_call({do_get, MibView, UnsortedVarbinds, IsNotification, PduData}, + _From, S) -> + ?vlog("[handle_call] do_get:" + "~n MibView: ~p" + "~n UnsortedVarbinds: ~p" + "~n IsNotification: ~p" + "~n PduData: ~p", + [MibView, UnsortedVarbinds, IsNotification, PduData]), + put_pdu_data(PduData), + Reply = do_get(MibView, UnsortedVarbinds, IsNotification), + {reply, Reply, S}; + +handle_call({register_subagent, SubTreeOid, SubagentPid}, _From, S) -> + Reply = + case snmpa_mib:register_subagent(get(mibserver), + SubTreeOid, SubagentPid) of + ok -> link(SubagentPid), ok; + Error -> Error + end, + {reply, Reply, S}; + +handle_call({unregister_subagent, SubagentPid}, _From, S) + when is_pid(SubagentPid) -> + ?vlog("[handle_call] unregister subagent ~p", [SubagentPid]), + Reply = snmpa_mib:unregister_subagent(get(mibserver), SubagentPid), + unlink(SubagentPid), + {reply, Reply, S}; + +handle_call({unregister_subagent, SubTreeOid}, _From, S) -> + ?vlog("[handle_call] unregister subagent ~p", [SubTreeOid]), + Reply = + case snmpa_mib:unregister_subagent(get(mibserver), SubTreeOid) of + {ok, DeletedSubagentPid} -> + SAs = snmpa_mib:info(get(mibserver), subagents), + case lists:keysearch(DeletedSubagentPid, 1, SAs) of + {value, _} -> ok; + _ -> unlink(DeletedSubagentPid) + end, + ok; + Error -> + Error + end, + {reply, Reply, S}; + +handle_call({load_mibs, Mibs}, _From, S) -> + ?vlog("load mibs ~p", [Mibs]), + {reply, snmpa_mib:load_mibs(get(mibserver), Mibs), S}; + +handle_call({unload_mibs, Mibs}, _From, S) -> + ?vlog("unload mibs ~p", [Mibs]), + {reply, snmpa_mib:unload_mibs(get(mibserver), Mibs), S}; + +handle_call(which_mibs, _From, S) -> + ?vlog("which mibs", []), + {reply, snmpa_mib:which_mibs(get(mibserver)), S}; + +handle_call({whereis_mib, Mib}, _From, S) -> + ?vlog("whereis mib ~p", [Mib]), + {reply, snmpa_mib:whereis_mib(get(mibserver), Mib), S}; + +handle_call({mibs_cache_request, MibsCacheReq}, From, S) -> + ?vlog("mibs_cache_request: ~p", [MibsCacheReq]), + {MibsCacheWorker, Ref} = + handle_mibs_cache_request(get(mibserver), MibsCacheReq), + NewS = S#state{mibs_cache_request = {MibsCacheWorker, Ref, From}}, + {noreply, NewS}; + +handle_call(info, _From, S) -> + ?vlog("info", []), + Vsns = S#state.vsns, + Stats = get_stats_counters(), + AI = agent_info(S), + NI = net_if_info(S), + NS = note_store_info(S), + SS = symbolic_store_info(), + LD = local_db_info(), + MS = mib_server_info(), + Info = [{vsns, Vsns}, + {stats_counters, Stats}, + {agent, AI}, + {net_if, NI}, + {note_store, NS}, + {symbolic_store, SS}, + {local_db, LD}, + {mib_server, MS}], + {reply, Info, S}; + +handle_call(get_net_if, _From, S) -> + {reply, get(net_if), S}; + +handle_call({backup, BackupDir}, From, S) -> + ?vlog("backup: ~p", [BackupDir]), + Pid = self(), + V = get(verbosity), + MS = get(mibserver), + BackupServer = + erlang:spawn_link( + fun() -> + put(sname, abs), + put(verbosity, V), + Dir = filename:join([BackupDir]), + Reply = handle_backup(Dir, MS), + Pid ! {backup_done, Reply}, + unlink(Pid) + end), + ?vtrace("backup server: ~p", [BackupServer]), + {noreply, S#state{backup = {BackupServer, From}}}; + +handle_call(dump_mibs, _From, S) -> + Reply = snmpa_mib:dump(get(mibserver)), + {reply, Reply, S}; + +handle_call({dump_mibs,File}, _From, S) -> + Reply = snmpa_mib:dump(get(mibserver),File), + {reply, Reply, S}; + +handle_call({register_notification_filter, Id, Mod, Data, Where}, _From, + #state{nfilters = NFs} = S) -> + ?vlog("register_notification_filter -> " + "~n Id: ~p" + "~n Mod: ~p" + "~n Where: ~p", [Id, Mod, Where]), + case lists:keymember(Id, 2, NFs) of + true -> + {reply, {error, {already_registered, Id}}, S}; + false -> + NF = #notification_filter{id = Id, mod = Mod, data = Data}, + {Reply, NewNFs} = add_notification_filter(Where, NF, NFs), + {reply, Reply, S#state{nfilters = NewNFs}} + end; + +handle_call({unregister_notification_filter, Id}, _From, + #state{nfilters = NFs} = S) -> + ?vlog("unregister_notification_filter -> " + "~n Id: ~p", [Id]), + case lists:keydelete(Id, 2, NFs) of + NFs -> + {reply, {error, {not_found, Id}}, S}; + NFs2 -> + {reply, ok, S#state{nfilters = NFs2}} + end; + +handle_call(which_notification_filter, _From, + #state{nfilters = NFs} = S) -> + ?vlog("which_notification_filter", []), + {reply, [Id || #notification_filter{id = Id} <- NFs], S}; + +handle_call({mib_of, Oid}, _From, S) -> + Reply = handle_mib_of(get(mibserver), Oid), + {reply, Reply, S}; + +handle_call({me_of, Oid}, _From, S) -> + Reply = handle_me_of(get(mibserver), Oid), + {reply, Reply, S}; + +handle_call(get_log_type, _From, S) -> + ?vlog("get_log_type", []), + Reply = handle_get_log_type(S), + {reply, Reply, S}; + +handle_call({set_log_type, NewType}, _From, S) -> + ?vlog("set_log_type -> " + "~n NewType: ~p", [NewType]), + Reply = handle_set_log_type(S, NewType), + {reply, Reply, S}; + +handle_call(get_request_limit, _From, S) -> + ?vlog("get_request_limit", []), + Reply = handle_get_request_limit(S), + {reply, Reply, S}; + +handle_call({set_request_limit, NewLimit}, _From, S) -> + ?vlog("set_request_limit -> " + "~n NewLimit: ~p", [NewLimit]), + Reply = handle_set_request_limit(S, NewLimit), + {reply, Reply, S}; + +handle_call(stop, _From, S) -> + {stop, normal, ok, S}; + +handle_call(Req, _From, S) -> + warning_msg("received unknown request: ~n~p", [Req]), + Reply = {error, {unknown, Req}}, + {reply, Reply, S}. + +handle_cast({verbosity,Verbosity}, S) -> + ?vlog("verbosity: ~p -> ~p",[get(verbosity),Verbosity]), + put(verbosity,snmp_verbosity:validate(Verbosity)), + case S#state.worker of + Pid when is_pid(Pid) -> Pid ! {verbosity,Verbosity}; + _ -> ok + end, + case S#state.set_worker of + Pid2 when is_pid(Pid2) -> Pid2 ! {verbosity,Verbosity}; + _ -> ok + end, + {noreply, S}; + +handle_cast({sub_agents_verbosity,Verbosity}, S) -> + ?vlog("sub_agents verbosity: ~p",[Verbosity]), + subagents_verbosity(Verbosity), + {noreply, S}; + +%% This should only happen if we are a master_agent +handle_cast({net_if_verbosity, Verbosity}, S) -> + net_if_verbosity(get(net_if), Verbosity), + {noreply, S}; + +handle_cast({mib_server_verbosity, Verbosity}, S) -> + mib_server_verbosity(get(mibserver),Verbosity), + {noreply, S}; + +handle_cast({note_store_verbosity, Verbosity}, #state{note_store = Pid} = S) -> + note_store_verbosity(Pid,Verbosity), + {noreply, S}; + +handle_cast(Msg, S) -> + warning_msg("received unknown message: ~n~p", [Msg]), + {noreply, S}. + + +terminate(shutdown, #state{worker = Worker, + set_worker = SetWorker, + backup = Backup, + ref = Ref}) -> + %% Ordered shutdown - stop misc-workers, net_if, mib-server and note-store. + backup_server_stop(Backup), + worker_stop(Worker, 100), + worker_stop(SetWorker, 100), + snmpa_misc_sup:stop_net_if(Ref), + snmpa_misc_sup:stop_mib_server(Ref); +terminate(_Reason, _S) -> + %% We crashed! We will reuse net_if and mib if we get restarted. + ok. + + +handle_mibs_cache_request(MibServer, Req) -> + {MibsCacheWorker, MibsCacheRef} = + spawn_monitor( + fun() -> + Reply = + case Req of + invalidate_cache -> + snmpa_mib:invalidate_cache(MibServer); + gc_cache -> + snmpa_mib:gc_cache(MibServer); + {gc_cache, Age} -> + snmpa_mib:gc_cache(MibServer, Age); + {gc_cache, Age, GcLimit} -> + snmpa_mib:gc_cache(MibServer, Age, GcLimit); + enable_cache -> + snmpa_mib:enable_cache(MibServer); + disable_cache -> + snmpa_mib:disable_cache(MibServer); + enable_autogc -> + snmpa_mib:enable_cache_autogc(MibServer); + disable_autogc -> + snmpa_mib:disable_cache_autogc(MibServer); + {update_gclimit, GcLimit} -> + snmpa_mib:update_cache_gclimit(MibServer, + GcLimit); + {update_age, Age} -> + snmpa_mib:update_cache_age(MibServer, Age); + _ -> + {error, {unknown_mibs_cache_request, Req}} + end, + exit({mibs_cache_reply, Reply}) + end), + {MibsCacheWorker, MibsCacheRef}. + + +%%----------------------------------------------------------------- +%% Code replacement +%% +%%----------------------------------------------------------------- + +%% Downgrade +%% +code_change({down, _Vsn}, S, downgrade_to_pre_4_13) -> + S1 = workers_restart(S), + case S1#state.disco of + undefined -> + ok; + #disco{from = From, + sender = Sender, + stage = Stage} -> + gen_server:reply(From, {error, {upgrade, Stage, Sender}}), + exit(Sender, kill) + end, + S2 = {state, + S1#state.type, + S1#state.parent, + S1#state.worker, + S1#state.worker_state, + S1#state.set_worker, + S1#state.multi_threaded, + S1#state.ref, + S1#state.vsns, + S1#state.nfilters, + S1#state.note_store, + S1#state.mib_server, + S1#state.net_if, + S1#state.net_if_mod, + S1#state.backup, + S1#state.disco}, + {ok, S2}; + +%% Upgrade +%% +code_change(_Vsn, S, upgrade_from_pre_4_13) -> + {state, + Type, + Parent, + Worker, + WorkerState, + SetWorker, + MultiThreaded, + Ref, + Vsns, + NFilters = [], + NoteStore, + MibServer, %% Currently unused + NetIf, %% Currently unused + NetIfMod, + Backup} = S, + S1 = #state{type = Type, + parent = Parent, + worker = Worker, + worker_state = WorkerState, + set_worker = SetWorker, + multi_threaded = MultiThreaded, + ref = Ref, + vsns = Vsns, + nfilters = NFilters, + note_store = NoteStore, + mib_server = MibServer, + net_if = NetIf, + net_if_mod = NetIfMod, + backup = Backup}, + S2 = workers_restart(S1), + {ok, S2}; + +code_change(_Vsn, S, _Extra) -> + {ok, S}. + + +workers_restart(#state{worker = W, set_worker = SW} = S) -> + Worker = worker_restart(W), + SetWorker = set_worker_restart(SW), + S#state{worker = Worker, + set_worker = SetWorker}. + + +%%----------------------------------------------------------------- + +backup_server_stop({Pid, _}) when is_pid(Pid) -> + exit(Pid, kill); +backup_server_stop(_) -> + ok. + + +workers_start(true) -> + ?vdebug("start worker and set-worker",[]), + {worker_start(), set_worker_start()}; +workers_start(_) -> + {undefined, undefined}. + +worker_start() -> + worker_start(get()). + +set_worker_start() -> + worker_start([{master, self()} | get()]). + +worker_start(Dict) -> + proc_lib:spawn_link(?MODULE, worker, [self(), Dict]). + +worker_stop(Pid) -> + worker_stop(Pid, infinity). + +worker_stop(Pid, Timeout) when is_pid(Pid) -> + Pid ! terminate, + receive + {'EXIT', Pid, normal} -> + ok + after Timeout -> + (catch exit(Pid, kill)), + ok + end; +worker_stop(_, _) -> + ok. + +set_worker_restart(Pid) -> + worker_restart(Pid, [{master, self()} | get()]). + +worker_restart(Pid) -> + worker_restart(Pid, get()). + +worker_restart(Pid, Dict) when is_pid(Pid) -> + worker_stop(Pid), + worker_start(Dict); +worker_restart(Any, _Dict) -> + Any. + + +%%----------------------------------------------------------------- + +handle_backup(BackupDir, MibServer) -> + ?vlog("handle_backup -> entry with" + "~n BackupDir: ~p", [BackupDir]), + case ets:lookup(snmp_agent_table, db_dir) of + [{db_dir, BackupDir}] -> + ?vinfo("handle_backup -> backup dir and db dir the same", []), + {error, db_dir}; + _ -> + case file:read_file_info(BackupDir) of + {ok, #file_info{type = directory}} -> + ?vdebug("handle_backup -> backup dir ok", []), + + VacmRes = (catch snmpa_vacm:backup(BackupDir)), + ?vtrace("handle_backup -> " + "~n VacmRes: ~p", [VacmRes]), + + LdbRes = (catch snmpa_local_db:backup(BackupDir)), + ?vtrace("handle_backup -> " + "~n LdbRes: ~p", [LdbRes]), + + MsRes = (catch snmpa_mib:backup(MibServer, BackupDir)), + ?vtrace("handle_backup -> " + "~n MsRes: ~p", [MsRes]), + + SsRes = (catch snmpa_symbolic_store:backup(BackupDir)), + ?vtrace("handle_backup -> " + "~n SsRes: ~p", [SsRes]), + handle_backup_res([{vacm, VacmRes}, + {local_db, LdbRes}, + {mib_server, MsRes}, + {symbolic_store, SsRes}]); + {ok, _} -> + ?vinfo("handle_backup -> backup dir not a dir", []), + {error, not_a_directory}; + Error -> + ?vinfo("handle_backup -> Error: ~p", [Error]), + Error + end + end. + + +handle_backup_res(Results) -> + handle_backup_res(Results, []). + +handle_backup_res([], []) -> + ok; +handle_backup_res([], Acc) -> + {error, lists:reverse(Acc)}; +handle_backup_res([{_, ok}|Results], Acc) -> + handle_backup_res(Results, Acc); +handle_backup_res([{Who, {error, Reason}}|Results], Acc) -> + handle_backup_res(Results, [{Who, Reason}|Acc]); +handle_backup_res([{Who, Crap}|Results], Acc) -> + handle_backup_res(Results, [{Who, Crap}|Acc]). + + +%%----------------------------------------------------------------- +%% We must cheat to get the community string out of the ACM data, +%% because we (for some reason) support the function +%% snmpa:current_community(). +%%----------------------------------------------------------------- +cheat({community, _SecModel, Community, _IpUdp}, Address, ContextName) -> + {Community, Address, ContextName}; +cheat(_, Address, ContextName) -> + {"", Address, ContextName}. + + +%% This function will either be in the context of the: +%% 1) master-agent +%% 2) set-worker +%% 3) user code +%% ( calling e.g. snmp_community_mib:snmpCommunityTable(set, ...) ) +invalidate_ca_cache() -> + case get(master) of + undefined -> % 1 or 3 + case get(auth_module) of + undefined -> % 3 + %% Ouch, we are not running in the context of the + %% master agent either. Check if we are on the + %% master-agent node. If so, sent it there, + case whereis(snmp_master_agent) of + MasterAgent when is_pid(MasterAgent) -> + case node(MasterAgent) =:= node() of + true -> + %% Ok, we are on the node running the + %% master_agent process, so sent it there + MasterAgent ! invalidate_ca_cache; + false -> + %% This is running on a sub-agent node, + %% so sent skip it + ok + end; + _ -> % Not on this node + ok + end; + AuthMod -> % 1 + AuthMod:invalidate_ca_cache() + end; + Pid -> % 2 + Pid ! invalidate_ca_cache + end. + + +%%----------------------------------------------------------------- +%% Threads and workers +%% +%%----------------------------------------------------------------- + +spawn_thread(Vsn, Pdu, PduMS, ACMData, Address, Extra) -> + Dict = get(), + Args = [Vsn, Pdu, PduMS, ACMData, Address, Extra, Dict], + proc_lib:spawn_link(?MODULE, handle_pdu, Args). + +spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, V) -> + Dict = get(), + proc_lib:spawn_link(?MODULE, do_send_trap, + [TrapRec, NotifyName, ContextName, Recv, V, Dict]). + +do_send_trap(TrapRec, NotifyName, ContextName, Recv, V, Dict) -> + lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), + put(sname,trap_sender_short_name(get(sname))), + ?vlog("starting",[]), + snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, V, + get(net_if)). + +worker(Master, Dict) -> + lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), + put(sname, worker_short_name(get(sname))), + ?vlog("starting",[]), + worker_loop(Master). + +worker_loop(Master) -> + receive + {Vsn, Pdu, PduMS, ACMData, Address, Extra} -> + ?vtrace("worker_loop -> received request", []), + handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra), + Master ! worker_available; + + %% Old style message + {MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra} -> + ?vtrace("worker_loop -> received (old) request", []), + do_handle_pdu(MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra), + Master ! worker_available; + + {TrapRec, NotifyName, ContextName, Recv, V} -> % We don't trap exits! + ?vtrace("worker_loop -> send trap:" + "~n ~p", [TrapRec]), + snmpa_trap:send_trap(TrapRec, NotifyName, + ContextName, Recv, V, get(net_if)), + Master ! worker_available; + + {verbosity, Verbosity} -> + put(verbosity,snmp_verbosity:validate(Verbosity)); + + terminate -> + exit(normal); + + _X -> + %% ignore + ok + + after 30000 -> + %% This is to assure that the worker process leaves a + %% possibly old version of this module. + ok + end, + ?MODULE:worker_loop(Master). + + +%%----------------------------------------------------------------- +%%----------------------------------------------------------------- + +handle_snmp_pdu(true, Vsn, Pdu, PduMS, ACMData, Address, Extra, + #state{multi_threaded = false} = S) -> + ?vtrace("handle_snmp_pdu -> single-thread agent",[]), + handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra), + S; +handle_snmp_pdu(true, Vsn, #pdu{type = 'set-request'} = Pdu, PduMS, + ACMData, Address, Extra, + #state{set_worker = Worker} = S) -> + ?vtrace("handle_snmp_pdu -> multi-thread agent: " + "send set-request to main worker",[]), + Worker ! {Vsn, Pdu, PduMS, ACMData, Address, Extra}, + S#state{worker_state = busy}; +handle_snmp_pdu(true, Vsn, Pdu, PduMS, + ACMData, Address, Extra, + #state{worker_state = busy} = S) -> + ?vtrace("handle_snmp_pdu -> multi-thread agent: " + "main worker busy - create new worker",[]), + spawn_thread(Vsn, Pdu, PduMS, ACMData, Address, Extra), + S; +handle_snmp_pdu(true, Vsn, Pdu, PduMS, ACMData, Address, Extra, + #state{worker = Worker} = S) -> + ?vtrace("handle_snmp_pdu -> multi-thread agent: " + "send to main worker",[]), + Worker ! {Vsn, Pdu, PduMS, ACMData, Address, Extra}, + S#state{worker_state = busy}; +handle_snmp_pdu(_, _Vsn, _Pdu, _PduMS, _ACMData, _Address, _Extra, S) -> + S. + + +%% Called via the spawn_thread function +handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra, Dict) -> + lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), + put(sname, pdu_handler_short_name(get(sname))), + ?vlog("new worker starting",[]), + handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra). + +handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra) -> + %% OTP-3324 + AuthMod = get(auth_module), + case AuthMod:init_check_access(Pdu, ACMData) of + {ok, MibView, ContextName} -> + ?vlog("handle_pdu -> ok:" + "~n MibView: ~p" + "~n ContextName: ~p", [MibView, ContextName]), + AgentData = cheat(ACMData, Address, ContextName), + do_handle_pdu(MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra); + {error, Reason} -> + ?vlog("handle_pdu -> error:" + "~n Reason: ~p", [Reason]), + handle_acm_error(Vsn, Reason, Pdu, ACMData, Address, Extra); + {discarded, Variable, Reason} -> + ?vlog("handle_pdu -> discarded:" + "~n Variable: ~p" + "~n Reason: ~p", [Variable, Reason]), + get(net_if) ! {discarded_pdu, Vsn, Pdu#pdu.request_id, + ACMData, Variable, Extra} + end. + +do_handle_pdu(MibView, Vsn, Pdu, PduMS, + ACMData, {Community, Address, ContextName}, Extra) -> + + put(net_if_data, Extra), + RePdu = process_msg(MibView, Vsn, Pdu, PduMS, Community, + Address, ContextName), + + ?vtrace("do_handle_pdu -> processed:" + "~n RePdu: ~p", [RePdu]), + get(net_if) ! {snmp_response, Vsn, RePdu, + RePdu#pdu.type, ACMData, Address, Extra}. + + +handle_acm_error(Vsn, Reason, Pdu, ACMData, Address, Extra) -> + #pdu{type = Type, request_id = ReqId, varbinds = Vbs} = Pdu, + RawErrStatus = snmpa_acm:error2status(Reason), + case is_valid_pdu_type(Type) of + true -> + %% RawErrStatus can be authorizationError or genErr. If it is + %% authorizationError, we'll have to do different things, + %% depending on which SNMP version is used. + %% v1 - noSuchName error + %% v2 - GET: all variables 'noSuchObject' + %% NEXT/BULK: all variables 'endOfMibView' + %% SET: noAccess error + %% v3 - authorizationError error + %% + %% NOTE: this procedure is not yet defined in the coex document! + ?vdebug("~n Raw error status: ~w",[RawErrStatus]), + Idx = case Vbs of + [] -> 0; + _ -> 1 + end, + RePdu = + if + Vsn =:= 'version-1' -> + ErrStatus = v2err_to_v1err(RawErrStatus), + make_response_pdu(ReqId, ErrStatus, Idx, Vbs, Vbs); + Vsn =:= 'version-3' -> + make_response_pdu(ReqId, RawErrStatus, Idx, Vbs, Vbs); + Type =:= 'get-request' -> % this is v2 + ReVbs = lists:map( + fun(Vb) -> + Vb#varbind{value=noSuchObject} + end, + Vbs), + make_response_pdu(ReqId, noError, 0, Vbs, ReVbs); + Type =:= 'set-request' -> + make_response_pdu(ReqId, noAccess, Idx, Vbs, Vbs); + true -> % next or bulk + ReVbs = lists:map( + fun(Vb) -> + Vb#varbind{value=endOfMibView} + end, + Vbs), + make_response_pdu(ReqId, noError, 0, Vbs, ReVbs) + end, + get(net_if) ! {snmp_response, Vsn, RePdu, + 'get-response', ACMData, Address, Extra}; + false -> + ?vdebug("~n Raw error status: ~w" + "~n invalid pdu type: ~w", + [RawErrStatus,Type]), + ok + end. + + +handle_send_trap(S, TrapName, NotifyName, ContextName, Recv, Varbinds) -> + ?vtrace("handle_send_trap -> entry with" + "~n S#state.type: ~p" + "~n TrapName: ~p" + "~n NotifyName: ~p" + "~n ContextName: ~p", + [S#state.type, TrapName, NotifyName, ContextName]), + case snmpa_trap:construct_trap(TrapName, Varbinds) of + {ok, TrapRecord, VarList} -> + ?vtrace("handle_send_trap -> construction complete: " + "~n TrapRecord: ~p" + "~n VarList: ~p", + [TrapRecord, VarList]), + case S#state.type of + subagent -> + ?vtrace("handle_send_trap -> [sub] forward trap",[]), + maybe_forward_trap(S, TrapRecord, NotifyName, + ContextName, Recv, VarList), + {ok, S}; + master_agent -> + ?vtrace("handle_send_trap -> " + "[master] handle send trap",[]), + maybe_send_trap(S, TrapRecord, NotifyName, + ContextName, Recv, VarList) + end; + error -> + error + end. + + +maybe_forward_trap(#state{parent = Parent, nfilters = NFs} = S, + TrapRec, NotifyName, ContextName, Recv, V) -> + ?vtrace("maybe_forward_trap -> entry with" + "~n NFs: ~p", [NFs]), + case filter_notification(NFs, [], TrapRec) of + {dont_send, [], Id} -> + ?vdebug("trap not forwarded [filter ~p]", [Id]), + {ok, S}; + + {dont_send, Removed, Id} -> + ?vdebug("trap not forwarded [filter ~p]", [Id]), + NFs2 = del_notification_filter(Removed, NFs), + {ok, S#state{nfilters = NFs2}}; + + {send, [], TrapRec2} -> + ?vtrace("maybe_forward_trap -> forward trap:" + "~n ~p", [TrapRec2]), + forward_trap(Parent, TrapRec2, NotifyName, ContextName, Recv, V), + {ok, S}; + + {send, Removed, TrapRec2} -> + ?vtrace("maybe_forward_trap -> forward trap:" + "~n ~p", [TrapRec2]), + forward_trap(Parent, TrapRec2, NotifyName, ContextName, Recv, V), + NFs2 = del_notification_filter(Removed, NFs), + {ok, S#state{nfilters = NFs2}} + end. + + +maybe_send_trap(#state{nfilters = NFs} = S, + TrapRec, NotifyName, ContextName, Recv, Varbinds) -> + ?vtrace("maybe_send_trap -> entry with" + "~n NFs: ~p", [NFs]), + case filter_notification(NFs, [], TrapRec) of + {dont_send, [], Id} -> + ?vdebug("trap not sent [filter ~p]",[Id]), + {ok, S}; + + {dont_send, Removed, Id} -> + ?vdebug("trap not sent [filter ~p]",[Id]), + NFs2 = del_notification_filter(Removed, NFs), + {ok, S#state{nfilters = NFs2}}; + + {send, [], TrapRec2} -> + ?vtrace("maybe_send_trap -> send trap:" + "~n ~p", [TrapRec2]), + do_handle_send_trap(S, TrapRec2, + NotifyName, ContextName, Recv, Varbinds); + + {send, Removed, TrapRec2} -> + ?vtrace("maybe_send_trap -> send trap:" + "~n ~p", [TrapRec2]), + NFs2 = del_notification_filter(Removed, NFs), + do_handle_send_trap(S#state{nfilters = NFs2}, TrapRec2, + NotifyName, ContextName, Recv, Varbinds) + end. + +do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds) -> + V = snmpa_trap:try_initialise_vars(get(mibserver), Varbinds), + case S#state.type of + subagent -> + forward_trap(S#state.parent, TrapRec, NotifyName, ContextName, + Recv, V), + {ok, S}; + master_agent when S#state.multi_threaded =:= false -> + ?vtrace("do_handle_send_trap -> send trap:" + "~n ~p", [TrapRec]), + snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, + Recv, V, get(net_if)), + {ok, S}; + master_agent when S#state.worker_state =:= busy -> + %% Main worker busy => create new worker + ?vtrace("do_handle_send_trap -> main worker busy: " + "spawn a trap sender", []), + spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, V), + {ok, S}; + master_agent -> + %% Send to main worker + ?vtrace("do_handle_send_trap -> send to main worker",[]), + S#state.worker ! {TrapRec, NotifyName, ContextName, Recv, V}, + {ok, S#state{worker_state = busy}} + end. + + +filter_notification([], RemoveNFs, Notif) -> + ?vtrace("filter_notification -> done when" + "~n RemoveNFs: ~p" + "~n Notif: ~p", [RemoveNFs, Notif]), + {send, RemoveNFs, Notif}; +filter_notification([NF|NFs], RemoveNFs, Notif0) -> + ?vtrace("filter_notification -> entry with" + "~n NF: ~p" + "~n RemoveNFs: ~p" + "~n Notif0: ~p", [NF, RemoveNFs, Notif0]), + case do_filter_notification(NF, Notif0) of + {dont_send, Id} -> + {dont_send, RemoveNFs, Id}; + {send, Notif} -> + filter_notification(NFs, RemoveNFs, Notif); + {error, Id} -> + filter_notification(NFs, [Id|RemoveNFs], Notif0) + end. + +do_filter_notification(#notification_filter{id = Id, mod = Mod, data = Data}, + Notif) -> + case (catch Mod:handle_notification(Notif, Data)) of + dont_send -> + {dont_send, Id}; + send -> + {send, Notif}; + {send, NewNotif} -> + {send, NewNotif}; + Else -> + user_err("notification filter ~p removed: ~n~p", [Id, Else]), + {error, Id} + end. + + +add_notification_filter(first, NewNF, NFs) -> + {ok, [NewNF | NFs]}; +add_notification_filter(last, NewNF, NFs) -> + {ok, lists:append(NFs, [NewNF])}; +add_notification_filter(Where, NewNF, NFs) -> + case add_nf(Where, NewNF, NFs, []) of + {ok, NFs2} -> + {ok, NFs2}; + Error -> + {Error, NFs} + end. + +add_nf({_Loc, Id}, _NewNF, [], _Acc) -> + {error, {not_found, Id}}; +add_nf({insert_before, Id}, NewNF, [NF|NFs], Acc) + when Id =:= NF#notification_filter.id -> + {ok, lists:reverse(Acc) ++ [NewNF,NF|NFs]}; +add_nf({insert_after, Id}, NewNF, [NF|NFs], Acc) + when Id =:= NF#notification_filter.id -> + {ok, lists:reverse(Acc) ++ [NF,NewNF|NFs]}; +add_nf(Where, NewNF, [NF|NFs], Acc) -> + add_nf(Where, NewNF, NFs, [NF|Acc]). + + +del_notification_filter(IDs, NFs) -> + Fun = fun(Id, NFilters) -> lists:keydelete(Id, 2, NFilters) end, + lists:foldl(Fun, NFs, IDs). + + +handle_discovery(#state{type = master_agent} = S, From, + TargetName, Notification, ContextName, Varbinds, + DiscoHandler, ExtraInfo) -> + ?vtrace("handle_discovery -> entry with" + "~n TargetName: ~p" + "~n Notification: ~p" + "~n ContextName: ~p" + "~n Varbinds: ~p", + [TargetName, Notification, ContextName, Varbinds]), + case snmpa_trap:construct_trap(Notification, Varbinds) of + {ok, Record, InitVars} -> + ?vtrace("handle_discovery -> trap construction complete: " + "~n Record: ~p" + "~n InitVars: ~p", [Record, InitVars]), + send_discovery(S, From, TargetName, + Record, ContextName, InitVars, + DiscoHandler, ExtraInfo); + error -> + {error, failed_constructing_notification} + end; +handle_discovery(_S, _From, + _TargetName, _Notification, _ContextName, _Varbinds, + _DiscoHandler, _ExtraInfo) -> + {error, only_master_discovery}. + +%% We ignore if the master agent is multi-threaded or not. +%% +send_discovery(S, From, + TargetName, Record, ContextName, InitVars, + DiscoHandler, ExtraInfo) -> + case snmpa_trap:send_discovery(TargetName, Record, ContextName, + InitVars, get(net_if)) of + {ok, Sender, SecLevel} -> + Disco = #disco{from = From, + rec = Record, + sender = Sender, + target = TargetName, + sec_level = SecLevel, + ctx = ContextName, + ivbs = InitVars, + stage = 1, + handler = DiscoHandler, + extra = ExtraInfo}, + {ok, S#state{disco = Disco}}; + Error -> + ?vlog("send_discovery -> failed sending discovery: " + "~n Error: ~p", [Error]), + Error + end. + + +handle_discovery_response(#state{disco = #disco{from = From}} = S, + {error, _} = Error) -> + ?vlog("handle_discovery_response -> entry with" + "~n From: ~p" + "~n Error: ~p", [From, Error]), + gen_server:reply(From, Error), + S#state{disco = undefined}; + +handle_discovery_response(#state{disco = #disco{target = TargetName, + stage = 1, + extra = ExtraInfo} = Disco} = S, + {ok, _Pdu, ManagerEngineId}) + when is_record(Disco, disco) -> + ?vlog("handle_discovery_response(1) -> entry with" + "~n TargetName: ~p" + "~n ManagerEngineId: ~p", [TargetName, ManagerEngineId]), + %% This is end of stage 1. + %% So, first we need to update the database with the EngineId of the + %% manager and then deside if we should continue with stage 2. E.g. + %% establish authenticated communication. + case snmp_target_mib:set_target_engine_id(TargetName, ManagerEngineId) of + true when Disco#disco.sec_level =:= ?'SnmpSecurityLevel_noAuthNoPriv' -> + %% Ok, we are done + From = Disco#disco.from, + Handler = Disco#disco.handler, + Reply = + case handle_discovery_stage1_finish(Handler, + TargetName, + ManagerEngineId, + ExtraInfo) of + {ok, _} -> + {ok, ManagerEngineId}; + Error -> + Error + end, + gen_server:reply(From, Reply), + S#state{disco = undefined}; + + true when Disco#disco.sec_level =/= ?'SnmpSecurityLevel_noAuthNoPriv' -> + %% Ok, time for stage 2 + %% Send the same inform again, + %% this time we have the proper EngineId + + From = Disco#disco.from, + Handler = Disco#disco.handler, + + case handle_discovery_stage1_finish(Handler, + TargetName, + ManagerEngineId, + ExtraInfo) of + {ok, NewExtraInfo} -> + ?vdebug("handle_discovery_response(1) -> " + "we are done with stage 1 - " + "continue with stage 2", []), + #disco{rec = Record, + ctx = ContextName, + ivbs = InitVars} = Disco, + case snmpa_trap:send_discovery(TargetName, Record, + ContextName, + InitVars, get(net_if)) of + {ok, Sender, _SecLevel} -> + ?vdebug("handle_discovery_response(1) -> " + "stage 2 trap sent", []), + Disco2 = Disco#disco{sender = Sender, + engine_id = ManagerEngineId, + stage = 2, + extra = NewExtraInfo}, + S#state{disco = Disco2}; + Error -> + ?vlog("handle_discovery_response(1) -> " + "failed sending stage 2 trap: " + "~n ~p", [Error]), + error_msg("failed sending second " + "discovery message: " + "~n ~p", [Error]), + Reply = {error, {second_send_failed, Error}}, + gen_server:reply(From, Reply), + S#state{disco = undefined} + end; + {error, Reason} = Error -> + ?vlog("handle_discovery_response(1) -> " + "stage 1 finish failed: " + "~n ~p", [Reason]), + gen_server:reply(From, Error), + S#state{disco = undefined} + end; + false -> + ?vinfo("handle_discovery_response(1) -> " + "failed setting doscovered engine-id - " + "inform the user", []), + From = Disco#disco.from, + Reason = {failed_setting_engine_id, TargetName, ManagerEngineId}, + gen_server:reply(From, {error, Reason}), + S#state{disco = undefined} + + end; + +handle_discovery_response(#state{disco = #disco{from = From, + engine_id = EngineID, + stage = 2}} = S, + {ok, _Pdu}) -> + ?vlog("handle_discovery_response(2) -> entry with" + "~n From: ~p", [From]), + gen_server:reply(From, {ok, EngineID}), + S#state{disco = undefined}; + +handle_discovery_response(#state{disco = #disco{from = From}} = S, Crap) -> + Reason = {invalid_response, Crap}, + gen_server:reply(From, {error, Reason}), + S#state{disco = undefined}; + +handle_discovery_response(S, Crap) -> + warning_msg("Received unexpected discovery response: ~p", [Crap]), + S. + +handle_discovery_stage1_finish(Handler, TargetName, ManagerEngineID, + ExtraInfo) -> + case (catch Handler:stage1_finish(TargetName, ManagerEngineID, + ExtraInfo)) of + ignore -> + ?vtrace("handle_discovery_stage1_finish -> " + "we are done - [ignore] inform the user", []), + {ok, ExtraInfo}; + + {ok, UsmEntry} when is_tuple(UsmEntry) -> + ?vtrace("handle_discovery_stage1_finish -> " + "received usm entry - attempt to add it", []), + case add_usm_users([UsmEntry]) of + ok -> + {ok, ExtraInfo}; + Error -> + Error + end; + + {ok, UsmEntry, NewExtraInfo} when is_tuple(UsmEntry) -> + ?vtrace("handle_discovery_stage1_finish -> " + "received usm entry - attempt to add it", []), + case add_usm_users([UsmEntry]) of + ok -> + {ok, NewExtraInfo}; + Error -> + Error + end; + + {ok, UsmEntries} when is_list(UsmEntries) -> + ?vtrace("handle_discovery_stage1_finish -> " + "received usm entries - attempt to add them", []), + case add_usm_users(UsmEntries) of + ok -> + {ok, ExtraInfo}; + Error -> + Error + end; + + {ok, UsmEntries, NewExtraInfo} when is_list(UsmEntries) -> + ?vtrace("handle_discovery_stage1_finish -> " + "received usm entries - attempt to add them", []), + case add_usm_users(UsmEntries) of + ok -> + {ok, NewExtraInfo}; + Error -> + Error + end; + + {'EXIT', Reason} -> + ?vlog("handle_discovery_stage1_finish -> stage 1 function exited: " + "~n ~p", [Reason]), + {error, {finish_exit, Reason, ManagerEngineID}}; + + {error, Reason} -> + ?vlog("handle_discovery_stage1_finish -> stage 1 function error: " + "~n ~p", [Reason]), + {error, {finish_error, Reason, ManagerEngineID}}; + + Unknown -> + ?vlog("handle_discovery_stage1_finish -> stage 1 function unknown: " + "~n ~p", [Unknown]), + {error, {finish_failed, Unknown, ManagerEngineID}} + end. + +add_usm_users([]) -> + ok; +add_usm_users([UsmEntry|UsmEntries]) when is_tuple(UsmEntry) -> + ?vtrace("add_usm_users -> attempt to add entry (~w)", + [element(1, UsmEntry)]), + case snmp_user_based_sm_mib:add_user(UsmEntry) of + {ok, _} -> + add_usm_users(UsmEntries); + {error, Reason} -> + ?vlog("add_usm_users -> failed adding usm entry: " + "~n ~p", [Reason]), + {error, {failed_adding_entry, Reason, UsmEntry}} + end. + + +handle_me_of(MibServer, Oid) -> + case snmpa_mib:lookup(MibServer, Oid) of + {variable, ME} -> + {ok, ME}; + {table_column, ME, _TableEntryOid} -> + {ok, ME}; + {subagent, SubAgentPid, _SANextOid} -> + {error, {subagent, SubAgentPid}}; + {false, Reason} -> + {error, Reason}; + Else -> + {error, Else} + end. + + +handle_mib_of(MibServer, Oid) -> + snmpa_mib:which_mib(MibServer, Oid). + + +%%----------------------------------------------------------------- +%% Func: process_msg/7 +%% Returns: RePdu +%%----------------------------------------------------------------- +process_msg(MibView, Vsn, Pdu, PduMS, Community, {Ip, Udp}, ContextName) -> + #pdu{request_id = ReqId} = Pdu, + put(snmp_address, {tuple_to_list(Ip), Udp}), + put(snmp_request_id, ReqId), + put(snmp_community, Community), + put(snmp_context, ContextName), + ?vtrace("process ~p",[Pdu#pdu.type]), + process_pdu(Pdu, PduMS, Vsn, MibView). + +process_pdu(#pdu{type='get-request', request_id = ReqId, varbinds=Vbs}, + _PduMS, Vsn, MibView) -> + ?vtrace("get ~p",[ReqId]), + Res = get_err(do_get(MibView, Vbs, false)), + ?vtrace("get result: " + "~n ~p",[Res]), + {ErrStatus, ErrIndex, ResVarbinds} = + if + Vsn =:= 'version-1' -> validate_get_v1(Res); + true -> Res + end, + ?vtrace("get final result: " + "~n Error status: ~p" + "~n Error index: ~p" + "~n Varbinds: ~p", + [ErrStatus,ErrIndex,ResVarbinds]), + ResponseVarbinds = lists:keysort(#varbind.org_index, ResVarbinds), + ?vtrace("response varbinds: " + "~n ~p",[ResponseVarbinds]), + make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds); + +process_pdu(#pdu{type = 'get-next-request', request_id = ReqId, varbinds = Vbs}, + _PduMS, Vsn, MibView) -> + ?vtrace("process get-next-request -> entry with" + "~n ReqId: ~p" + "~n Vbs: ~p" + "~n MibView: ~p",[ReqId, Vbs, MibView]), + Res = get_err(do_get_next(MibView, Vbs)), + ?vtrace("get-next result: " + "~n ~p",[Res]), + {ErrStatus, ErrIndex, ResVarbinds} = + if + Vsn =:= 'version-1' -> validate_next_v1(Res, MibView); + true -> Res + end, + ?vtrace("get-next final result -> validation result:" + "~n Error status: ~p" + "~n Error index: ~p" + "~n Varbinds: ~p",[ErrStatus,ErrIndex,ResVarbinds]), + ResponseVarbinds = lists:keysort(#varbind.org_index, ResVarbinds), + ?vtrace("get-next final result -> response varbinds: " + "~n ~p",[ResponseVarbinds]), + make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds); + +process_pdu(#pdu{type = 'get-bulk-request',request_id = ReqId,varbinds = Vbs, + error_status = NonRepeaters, error_index = MaxRepetitions}, + PduMS, _Vsn, MibView)-> + {ErrStatus, ErrIndex, ResponseVarbinds} = + get_err(do_get_bulk(MibView,NonRepeaters,MaxRepetitions,PduMS,Vbs)), + ?vtrace("get-bulk final result: " + "~n Error status: ~p" + "~n Error index: ~p" + "~n Respons varbinds: ~p", + [ErrStatus,ErrIndex,ResponseVarbinds]), + make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds); + +process_pdu(#pdu{type = 'set-request', request_id = ReqId, varbinds = Vbs}, + _PduMS, Vsn, MibView)-> + Res = do_set(MibView, Vbs), + ?vtrace("set result: " + "~n ~p",[Res]), + {ErrStatus, ErrIndex} = + if + Vsn =:= 'version-1' -> validate_err(v2_to_v1, Res); + true -> Res + end, + ?vtrace("set final result: " + "~n Error status: ~p" + "~n Error index: ~p",[ErrStatus,ErrIndex]), + make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, Vbs). + +%%----------------------------------------------------------------- +%% Transform a value == noSuchInstance | noSuchObject or a +%% Counter64 type to a noSuchName error for the whole pdu. +%% Args: {Error, Index, Vbs} +%% Returns: {NewError, NewIndex, NewVbs} +%%----------------------------------------------------------------- +validate_get_v1({noError, _, ResponseVarbinds}) -> + case validate_get_v1_2(ResponseVarbinds) of + true -> {noError, 0, ResponseVarbinds}; + {Error, Index} -> {Error, Index, []} % dummy vbs + end; +validate_get_v1({ErrStatus, ErrIndex, ResponseVarbinds}) -> + {v2err_to_v1err(ErrStatus), ErrIndex, ResponseVarbinds}. + +validate_get_v1_2([Vb | Vbs]) + when ((Vb#varbind.value =/= noSuchInstance) andalso + (Vb#varbind.value =/= noSuchObject) andalso + (Vb#varbind.variabletype =/= 'Counter64')) -> + validate_get_v1_2(Vbs); +validate_get_v1_2([Vb | _Vbs]) -> + {noSuchName, Vb#varbind.org_index}; +validate_get_v1_2([]) -> + true. + +%%----------------------------------------------------------------- +%% Transform a value == endOfMibView to a noSuchName for the +%% whole pdu, and do another get-next for any Counter64 value. +%% Args: {Error, Index, Vbs} +%% Returns: {NewError, NewIndex, NewVbs} +%%----------------------------------------------------------------- +validate_next_v1({noError, _, ResponseVarbinds}, MibView) -> + case validate_next_v1_2(ResponseVarbinds, MibView, []) of + {true, NVbs} -> {noError, 0, NVbs}; + {Error, Index} -> {Error, Index, []} % dummy vbs + end; +validate_next_v1({ErrStatus, ErrIndex, ResponseVarbinds}, _MibView) -> + {v2err_to_v1err(ErrStatus), ErrIndex, ResponseVarbinds}. + +validate_next_v1_2([Vb | _Vbs], _MibView, _Res) + when Vb#varbind.value =:= endOfMibView -> + {noSuchName, Vb#varbind.org_index}; +validate_next_v1_2([Vb | Vbs], MibView, Res) + when Vb#varbind.variabletype =:= 'Counter64' -> + case validate_next_v1(do_get_next(MibView, [mk_next_oid(Vb)]), MibView) of + {noError, 0, [NVb]} -> + validate_next_v1_2(Vbs, MibView, [NVb | Res]); + {Error, Index, _OrgVb} -> + {Error, Index} + end; +validate_next_v1_2([Vb | Vbs], MibView, Res) -> + validate_next_v1_2(Vbs, MibView, [Vb | Res]); +validate_next_v1_2([], _MibView, Res) -> + {true, Res}. + +%%----------------------------------------------------------------- +%% Optimization. When we get to a Counter64 object that is a table +%% column, we'll try to find the next instance. This will be the +%% next row in the table, which is a Counter64 value as well. This +%% means that we will loop through the entire table, until we find +%% a column that isn't a Counter64 column. We can optimze this by +%% adding 1 to the column-no in the oid of this instance. +%% If the table is implemented by a subagent this does not help, +%% we'll call that subagent many times. But it shouldn't be any +%% problems. +%%----------------------------------------------------------------- +mk_next_oid(Vb) -> + case snmpa_mib:lookup(get(mibserver), Oid = Vb#varbind.oid) of + {table_column, _MibEntry, TableEntryOid} -> + [Col | _] = Oid -- TableEntryOid, + Vb#varbind{oid = TableEntryOid ++ [Col+1]}; + _ -> + Vb + end. + +%%%----------------------------------------------------------------- +%%% 3. GET REQUEST +%%% -------------- +%%% According to RFC1157, section 4.1.2 and RFC1905, section 4.2.1. +%%% In rfc1157:4.1.2 it isn't specified if noSuchName should be +%%% returned even if some other varbind generates a genErr. +%%% In rfc1905:4.2.1 this is not a problem since exceptions are +%%% used, and thus a genErr will be returned anyway. +%%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Func: do_get/3 +%% Purpose: do_get handles "getRequests". +%% Pre: incoming varbinds have type == 'NULL', value == unSpecified +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- + +%% If this function is called from a worker-process, we *may* +%% need to tunnel into the master-agent and let it do the +%% work + +do_get(MibView, UnsortedVarbinds, IsNotification) -> + do_get(MibView, UnsortedVarbinds, IsNotification, false). + +do_get(MibView, UnsortedVarbinds, IsNotification, ForceMaster) -> + ?vtrace("do_get -> entry with" + "~n MibView: ~p" + "~n UnsortedVarbinds: ~p" + "~n IsNotification: ~p", + [MibView, UnsortedVarbinds, IsNotification]), + case (whereis(snmp_master_agent) =:= self()) of + false when (ForceMaster =:= true) -> + %% I am a lowly worker process, handoff to the master agent + PduData = get_pdu_data(), + call(snmp_master_agent, + {do_get, MibView, UnsortedVarbinds, IsNotification, PduData}); + + _ -> + %% This is me, the master, so go ahead + {OutSideView, InSideView} = + split_vbs_view(UnsortedVarbinds, MibView), + {Error, Index, NewVbs} = + do_get(InSideView, IsNotification), + {Error, Index, NewVbs ++ OutSideView} + + end. + + +split_vbs_view(Vbs, MibView) -> + ?vtrace("split the varbinds view", []), + split_vbs_view(Vbs, MibView, [], []). + +split_vbs_view([Vb | Vbs], MibView, Out, In) -> + case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of + true -> split_vbs_view(Vbs, MibView, Out, [Vb | In]); + false -> split_vbs_view(Vbs, MibView, + [Vb#varbind{value = noSuchObject} | Out], In) + end; +split_vbs_view([], _MibView, Out, In) -> + {Out, In}. + +do_get(UnsortedVarbinds, IsNotification) -> + {MyVarbinds, SubagentVarbinds} = sort_varbindlist(UnsortedVarbinds), + case do_get_local(MyVarbinds, [], IsNotification) of + {noError, 0, NewMyVarbinds} -> + case do_get_subagents(SubagentVarbinds, IsNotification) of + {noError, 0, NewSubagentVarbinds} -> + {noError, 0, NewMyVarbinds ++ NewSubagentVarbinds}; + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex, []} + end; + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex, []} + end. + +%%----------------------------------------------------------------- +%% Func: do_get_local/3 +%% Purpose: Loop the variablebindings list. We know that each varbind +%% in that list belongs to us. +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- +do_get_local([Vb | Vbs], Res, IsNotification) -> + case try_get(Vb, IsNotification) of + NewVb when is_record(NewVb, varbind) -> + do_get_local(Vbs, [NewVb | Res], IsNotification); + ListOfNewVb when is_list(ListOfNewVb) -> + do_get_local(Vbs, lists:append(ListOfNewVb, Res), IsNotification); + {error, Error, OrgIndex} -> + {Error, OrgIndex, []} + end; +do_get_local([], Res, _IsNotification) -> + {noError, 0, Res}. + +%%----------------------------------------------------------------- +%% Func: do_get_subagents/2 +%% Purpose: Loop the list of varbinds for different subagents. +%% For each of them, call sub_agent_get to retreive +%% the values for them. +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%%----------------------------------------------------------------- +do_get_subagents(SubagentVarbinds, IsNotification) -> + do_get_subagents(SubagentVarbinds, [], IsNotification). +do_get_subagents([{SubAgentPid, SAVbs} | Tail], Res, IsNotification) -> + {_SAOids, Vbs} = sa_split(SAVbs), + case catch subagent_get(SubAgentPid, Vbs, IsNotification) of + {noError, 0, NewVbs} -> + do_get_subagents(Tail, lists:append(NewVbs, Res), IsNotification); + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex, []}; + {'EXIT', Reason} -> + user_err("Lost contact with subagent (get) ~w. Using genErr", + [Reason]), + {genErr, 0, []} + end; +do_get_subagents([], Res, _IsNotification) -> + {noError, 0, Res}. + + +%%----------------------------------------------------------------- +%% Func: try_get/2 +%% Returns: {error, ErrorStatus, OrgIndex} | +%% #varbind | +%% List of #varbind +%%----------------------------------------------------------------- +try_get(IVb, IsNotification) when is_record(IVb, ivarbind) -> + ?vtrace("try_get(ivarbind) -> entry with" + "~n IVb: ~p", [IVb]), + get_var_value_from_ivb(IVb, IsNotification); +try_get({TableOid, TableVbs}, IsNotification) -> + ?vtrace("try_get(table) -> entry with" + "~n TableOid: ~p" + "~n TableVbs: ~p", [TableOid, TableVbs]), + [#ivarbind{mibentry = MibEntry}|_] = TableVbs, + {NoAccessVbs, AccessVbs} = + check_all_table_vbs(TableVbs, IsNotification, [], []), + case get_tab_value_from_mib(MibEntry, TableOid, AccessVbs) of + {error, ErrorStatus, OrgIndex} -> + {error, ErrorStatus, OrgIndex}; + NVbs -> + NVbs ++ NoAccessVbs + end. + +%%----------------------------------------------------------------- +%% Make sure all requested columns are accessible. +%%----------------------------------------------------------------- +check_all_table_vbs([IVb| IVbs], IsNotification, NoA, A) -> + #ivarbind{mibentry = Me, varbind = Vb} = IVb, + case Me#me.access of + 'not-accessible' -> + NNoA = [Vb#varbind{value = noSuchInstance} | NoA], + check_all_table_vbs(IVbs, IsNotification, NNoA, A); + 'accessible-for-notify' when IsNotification =:= false -> + NNoA = [Vb#varbind{value = noSuchInstance} | NoA], + check_all_table_vbs(IVbs, IsNotification, NNoA, A); + 'write-only' -> + NNoA = [Vb#varbind{value = noSuchInstance} | NoA], + check_all_table_vbs(IVbs, IsNotification, NNoA, A); + _ -> + check_all_table_vbs(IVbs, IsNotification, NoA, [IVb | A]) + end; +check_all_table_vbs([], _IsNotification, NoA, A) -> {NoA, A}. + +%%----------------------------------------------------------------- +%% Returns: {error, ErrorStatus, OrgIndex} | +%% #varbind +%%----------------------------------------------------------------- +get_var_value_from_ivb(IVb, IsNotification) + when IVb#ivarbind.status =:= noError -> + ?vtrace("get_var_value_from_ivb(noError) -> entry", []), + #ivarbind{mibentry = Me, varbind = Vb} = IVb, + #varbind{org_index = OrgIndex, oid = Oid} = Vb, + case Me#me.access of + 'not-accessible' -> + Vb#varbind{value = noSuchInstance}; + 'accessible-for-notify' when IsNotification =:= false -> + Vb#varbind{value = noSuchInstance}; + 'write-only' -> + Vb#varbind{value = noSuchInstance}; + _ -> + case get_var_value_from_mib(Me, Oid) of + {value, Type, Value} -> + Vb#varbind{variabletype = Type, value = Value}; + {error, ErrorStatus} -> + {error, ErrorStatus, OrgIndex} + end + end; +get_var_value_from_ivb(#ivarbind{status = Status, varbind = Vb}, _) -> + ?vtrace("get_var_value_from_ivb(~p) -> entry", [Status]), + Vb#varbind{value = Status}. + +%%----------------------------------------------------------------- +%% Func: get_var_value_from_mib/1 +%% Purpose: +%% Returns: {error, ErrorStatus} | +%% {value, Type, Value} +%%----------------------------------------------------------------- +%% Pre: Oid is a correct instance Oid (lookup checked that). +%% Returns: A correct return value (see make_value_a_correct_value) +get_var_value_from_mib(#me{entrytype = variable, + asn1_type = ASN1Type, + mfa = {Mod, Func, Args}}, + _Oid) -> + ?vtrace("get_var_value_from_mib(variable) -> entry when" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p", [Mod, Func, Args]), + Result = (catch dbg_apply(Mod, Func, [get | Args])), + % mib shall return {value, <a-nice-value-within-range>} | + % {noValue, noSuchName} (v1) | + % {noValue, noSuchObject | noSuchInstance} (v2, v1) + % everything else (including 'genErr') will generate 'genErr'. + make_value_a_correct_value(Result, ASN1Type, {Mod, Func, Args}); + +get_var_value_from_mib(#me{entrytype = table_column, + oid = MeOid, + asn1_type = ASN1Type, + mfa = {Mod, Func, Args}}, + Oid) -> + ?vtrace("get_var_value_from_mib(table_column) -> entry when" + "~n MeOid: ~p" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p" + "~n Oid: ~p", [MeOid, Mod, Func, Args, Oid]), + Col = lists:last(MeOid), + Indexes = snmp_misc:diff(Oid, MeOid), + [Result] = (catch dbg_apply(Mod, Func, [get, Indexes, [Col] | Args])), + make_value_a_correct_value(Result, ASN1Type, + {Mod, Func, Args, Indexes, Col}). + + +%% For table operations we need to pass RestOid down to the table-function. +%% Its up to the table-function to check for noSuchInstance (ex: a +%% non-existing row). +%% Returns: {error, ErrorStatus, OrgIndex} | +%% {value, Type, Value} +get_tab_value_from_mib(#me{mfa = {Mod, Func, Args}}, TableOid, TableVbs) -> + ?vtrace("get_tab_value_from_mib -> entry when" + "~n Mod: ~p" + "~n Func: ~p" + "~n Args: ~p", [Mod, Func, Args]), + TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), + SortedVBsRows = snmpa_svbl:sort_varbinds_rows(TableOpsWithShortOids), + case get_value_all_rows(SortedVBsRows, Mod, Func, Args, []) of + {Error, Index} -> + #ivarbind{varbind = Vb} = lists:nth(Index, TableVbs), + {error, Error, Vb#varbind.org_index}; + ListOfValues -> + merge_varbinds_and_value(TableVbs, ListOfValues) + end. + +%%----------------------------------------------------------------- +%% Values is a scrambled list of {CorrectValue, Index}, where Index +%% is index into the #ivarbind list. So for each Value, we must +%% find the corresponding #ivarbind, and merge them into a new +%% #varbind. +%% The Values list comes from validate_tab_res. +%%----------------------------------------------------------------- +merge_varbinds_and_value(IVbs, [{{value, Type, Value}, Index} | Values]) -> + #ivarbind{varbind = Vb} = lists:nth(Index, IVbs), + [Vb#varbind{variabletype = Type, value = Value} | + merge_varbinds_and_value(IVbs, Values)]; +merge_varbinds_and_value(_, []) -> []. + +get_value_all_rows([{[], OrgCols} | Rows], Mod, Func, Args, Res) -> + ?vtrace("get_value_all_rows -> entry when" + "~n OrgCols: ~p", [OrgCols]), + Cols = [{{value, noValue, noSuchInstance}, Index} || + {_Col, _ASN1Type, Index} <- OrgCols], + NewRes = lists:append(Cols, Res), + get_value_all_rows(Rows, Mod, Func, Args, NewRes); +get_value_all_rows([{RowIndex, OrgCols} | Rows], Mod, Func, Args, Res) -> + ?vtrace("get_value_all_rows -> entry when" + "~n RowIndex: ~p" + "~n OrgCols: ~p", [RowIndex, OrgCols]), + {DOrgCols, Dup} = remove_duplicates(OrgCols), + Cols = delete_index(DOrgCols), + Result = (catch dbg_apply(Mod, Func, [get, RowIndex, Cols | Args])), + case validate_tab_res(Result, DOrgCols, {Mod, Func, Args}) of + Values when is_list(Values) -> + NVals = restore_duplicates(Dup, Values), + NewRes = lists:append(NVals, Res), + get_value_all_rows(Rows, Mod, Func, Args, NewRes); + {error, ErrorStatus, Index} -> + validate_err(row_set, {ErrorStatus, Index}, {Mod, Func, Args}) + end; +get_value_all_rows([], _Mod, _Func, _Args, Res) -> + ?vtrace("get_value_all_rows -> entry when done" + "~n Res: ~p", [Res]), + Res. + +%%----------------------------------------------------------------- +%% Returns: list of {ShortOid, ASN1TYpe} +%%----------------------------------------------------------------- +deletePrefixes(Prefix, [#ivarbind{varbind = Varbind, mibentry = ME} | Vbs]) -> + #varbind{oid = Oid} = Varbind, + [{snmp_misc:diff(Oid, Prefix), ME#me.asn1_type} | + deletePrefixes(Prefix, Vbs)]; +deletePrefixes(_Prefix, []) -> []. + +%%----------------------------------------------------------------- +%% Args: {RowIndex, list of {ShortOid, ASN1Type}} +%% Returns: list of Col +%%----------------------------------------------------------------- +delete_index([{Col, _Val, _OrgIndex} | T]) -> + [Col | delete_index(T)]; +delete_index([]) -> []. + +%%----------------------------------------------------------------- +%% This function is called before 'get' on a table, and removes +%% any duplicate columns. It returns {Cols, DupInfo}. The Cols +%% are the unique columns. The instrumentation function is +%% called to get the values. These values, together with the +%% DupInfo, is later passed to restore_duplicates, which uses +%% the retrieved values to reconstruct the original column list, +%% but with the retrieved value for each column. +%%----------------------------------------------------------------- +remove_duplicates(Cols) -> + remove_duplicates(Cols, [], []). + + +remove_duplicates([{Col, V1, OrgIdx1}, {Col, V2, OrgIdx2} | T], NCols, Dup) -> + remove_duplicates([{Col, V1, OrgIdx1} | T], NCols, + [{Col, V2, OrgIdx2} | Dup]); +remove_duplicates([Col | T], NCols, Dup) -> + remove_duplicates(T, [Col | NCols], Dup); +remove_duplicates([], NCols, Dup) -> + {lists:reverse(NCols), lists:reverse(Dup)}. + +restore_duplicates([], Cols) -> + [{Val, OrgIndex} || {_Col, Val, OrgIndex} <- Cols]; +restore_duplicates([{Col, _Val2, OrgIndex2} | Dup], + [{Col, NVal, OrgIndex1} | Cols]) -> + [{NVal, OrgIndex2} | + restore_duplicates(Dup, [{Col, NVal, OrgIndex1} | Cols])]; +restore_duplicates(Dup, [{_Col, Val, OrgIndex} | T]) -> + [{Val, OrgIndex} | restore_duplicates(Dup, T)]. + +%% Maps the column number to Index. +% col_to_index(0, _) -> 0; +% col_to_index(Col, [{Col, _, Index}|_]) -> +% Index; +% col_to_index(Col, [_|Cols]) -> +% col_to_index(Col, Cols). + +%%----------------------------------------------------------------- +%% Three cases: +%% 1) All values ok +%% 2) table_func returned {Error, ...} +%% 3) Some value in Values list is erroneous. +%% Args: Value is a list of values from table_func(get..) +%% OrgCols is a list with {Col, ASN1Type, OrgIndex} +%% each element in Values and OrgCols correspond to each +%% other. +%%----------------------------------------------------------------- +validate_tab_res(Values, OrgCols, Mfa) when is_list(Values) -> + {_Col, _ASN1Type, OneIdx} = hd(OrgCols), + validate_tab_res(Values, OrgCols, Mfa, [], OneIdx); +validate_tab_res({noValue, Error}, OrgCols, Mfa) -> + Values = lists:duplicate(length(OrgCols), {noValue, Error}), + validate_tab_res(Values, OrgCols, Mfa); +validate_tab_res({genErr, Col}, OrgCols, Mfa) -> + case lists:keysearch(Col, 1, OrgCols) of + {value, {_Col, _ASN1Type, Index}} -> + {error, genErr, Index}; + _ -> + user_err("Invalid column in {genErr, ~w} from ~w (get)", + [Col, Mfa]), + [{_Col, _ASN1Type, Index} | _] = OrgCols, + {error, genErr, Index} + end; +validate_tab_res(genErr, [{_Col, __ASN1Type, Index} | _OrgCols], _Mfa) -> + {error, genErr, Index}; +validate_tab_res(Error, [{_Col, _ASN1Type, Index} | _OrgCols], Mfa) -> + user_err("Invalid return value ~w from ~w (get)",[Error, Mfa]), + {error, genErr, Index}. + +validate_tab_res([Value | Values], + [{Col, ASN1Type, Index} | OrgCols], + Mfa, Res, I) -> + %% This one makes it possible to return a list of genErr, which + %% is not allowed according to the manual. But that's ok, as + %% everything else will generate a genErr! (the only problem is + %% that it won't generate a user_error). + case make_value_a_correct_value(Value, ASN1Type, Mfa) of + {error, ErrorStatus} -> + {error, ErrorStatus, Index}; + CorrectValue -> + NewRes = [{Col, CorrectValue, Index} | Res], + validate_tab_res(Values, OrgCols, Mfa, NewRes, I) + end; +validate_tab_res([], [], _Mfa, Res, _I) -> + lists:reverse(Res); +validate_tab_res([], [{_Col, _ASN1Type, Index}|_], Mfa, _Res, _I) -> + user_err("Too few values returned from ~w (get)", [Mfa]), + {error, genErr, Index}; +validate_tab_res(_TooMany, [], Mfa, _Res, I) -> + user_err("Too many values returned from ~w (get)", [Mfa]), + {error, genErr, I}. + + +%%%----------------------------------------------------------------- +%%% 4. GET-NEXT REQUEST +%%% -------------- +%%% According to RFC1157, section 4.1.3 and RFC1905, section 4.2.2. +%%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% Func: do_get_next/2 +%% Purpose: do_get_next handles "getNextRequests". +%% Note: Even if it is SNMPv1, a varbind's value can be +%% endOfMibView. This is converted to noSuchName in process_pdu. +%% Returns: {noError, 0, ListOfNewVarbinds} | +%% {ErrorStatus, ErrorIndex, []} +%% Note2: ListOfNewVarbinds is not sorted in any order!!! +%% Alg: First, the variables are sorted in OID order. +%% +%% Second, next in the MIB is performed for each OID, and +%% the result is collected as: if next oid is a variable, +%% perform a get to retrieve its value; if next oid is in a +%% table, save this value and continue until we get an oid +%% outside this table. Then perform get_next on the table, +%% and continue with all endOfTables and the oid outside the +%% table; if next oid is an subagent, save this value and +%% continue as in the table case. +%% +%% Third, each response is checked for endOfMibView, or (for +%% subagents) that the Oid returned has the correct prefix. +%% (This is necessary since an SA can be registered under many +%% separated subtrees, and if the last variable in the first +%% subtree is requested in a next, the SA will return the first +%% variable in the second subtree. This might be working, since +%% there may be a variable in between these subtrees.) For each +%% of these, a new get-next is performed, one at a time. +%% This alg. might be optimised in several ways. The most +%% striking one is that the same SA might be called several +%% times, when one time should be enough. But it isn't clear +%% that this really matters, since many nexts across the same +%% subagent must be considered to be very rare. +%%----------------------------------------------------------------- +do_get_next(MibView, UnsortedVarbinds) -> + SortedVarbinds = oid_sort_varbindlist(UnsortedVarbinds), + next_loop_varbinds([], SortedVarbinds, MibView, [], []). + +oid_sort_varbindlist(Vbs) -> + lists:keysort(#varbind.oid, Vbs). + +%% LAVb is Last Accessible Vb +next_loop_varbinds([], [Vb | Vbs], MibView, Res, LAVb) -> + ?vt("next_loop_varbinds -> entry when" + "~n Vb: ~p" + "~n MibView: ~p", [Vb, MibView]), + case varbind_next(Vb, MibView) of + endOfMibView -> + RVb = if LAVb =:= [] -> Vb; + true -> LAVb + end, + NewVb = RVb#varbind{variabletype = 'NULL', value = endOfMibView}, + next_loop_varbinds([], Vbs, MibView, [NewVb | Res], []); + {variable, ME, VarOid} when ((ME#me.access =/= 'not-accessible') andalso + (ME#me.access =/= 'write-only') andalso + (ME#me.access =/= 'accessible-for-notify')) -> + case try_get_instance(Vb, ME) of + {value, noValue, _NoSuchSomething} -> + %% Try next one + NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, + next_loop_varbinds([], [NewVb | Vbs], MibView, Res, []); + {value, Type, Value} -> + NewVb = Vb#varbind{oid = VarOid, variabletype = Type, + value = Value}, + next_loop_varbinds([], Vbs, MibView, [NewVb | Res], []); + {error, ErrorStatus} -> + ?vdebug("next loop varbinds:" + "~n ErrorStatus: ~p",[ErrorStatus]), + {ErrorStatus, Vb#varbind.org_index, []} + end; + {variable, _ME, VarOid} -> + RVb = if LAVb =:= [] -> Vb; + true -> LAVb + end, + NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, + next_loop_varbinds([], [NewVb | Vbs], MibView, Res, RVb); + {table, TableOid, TableRestOid, ME} -> + next_loop_varbinds({table, TableOid, ME, + [{tab_oid(TableRestOid), Vb}]}, + Vbs, MibView, Res, []); + {subagent, SubAgentPid, SAOid} -> + NewVb = Vb#varbind{variabletype = 'NULL', value = 'NULL'}, + next_loop_varbinds({subagent, SubAgentPid, SAOid, [NewVb]}, + Vbs, MibView, Res, []) + end; +next_loop_varbinds({table, TableOid, ME, TabOids}, + [Vb | Vbs], MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds(table) -> entry with" + "~n TableOid: ~p" + "~n Vb: ~p", [TableOid, Vb]), + case varbind_next(Vb, MibView) of + {table, TableOid, TableRestOid, _ME} -> + next_loop_varbinds({table, TableOid, ME, + [{tab_oid(TableRestOid), Vb} | TabOids]}, + Vbs, MibView, Res, []); + _ -> + case get_next_table(ME, TableOid, TabOids, MibView) of + {ok, TabRes, TabEndOfTabVbs} -> + NewVbs = lists:append(TabEndOfTabVbs, [Vb | Vbs]), + NewRes = lists:append(TabRes, Res), + next_loop_varbinds([], NewVbs, MibView, NewRes, []); + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next varbind" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end + end; +next_loop_varbinds({table, TableOid, ME, TabOids}, + [], MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds(table) -> entry with" + "~n TableOid: ~p", [TableOid]), + case get_next_table(ME, TableOid, TabOids, MibView) of + {ok, TabRes, TabEndOfTabVbs} -> + ?vt("next_loop_varbinds(table) -> get_next_table result:" + "~n TabRes: ~p" + "~n TabEndOfTabVbs: ~p", [TabRes, TabEndOfTabVbs]), + NewRes = lists:append(TabRes, Res), + next_loop_varbinds([], TabEndOfTabVbs, MibView, NewRes, []); + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next table" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end; +next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, + [Vb | Vbs], MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds(subagent) -> entry with" + "~n SAPid: ~p" + "~n SAOid: ~p" + "~n Vb: ~p", [SAPid, SAOid, Vb]), + case varbind_next(Vb, MibView) of + {subagent, _SubAgentPid, SAOid} -> + next_loop_varbinds({subagent, SAPid, SAOid, + [Vb | SAVbs]}, + Vbs, MibView, Res, []); + _ -> + case get_next_sa(SAPid, SAOid, SAVbs, MibView) of + {ok, SARes, SAEndOfMibViewVbs} -> + NewVbs = lists:append(SAEndOfMibViewVbs, [Vb | Vbs]), + NewRes = lists:append(SARes, Res), + next_loop_varbinds([], NewVbs, MibView, NewRes, []); + {noSuchName, OrgIndex} -> + %% v1 reply, treat this Vb as endOfMibView, and try again + %% for the others. + case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of + {value, EVb} -> + NextOid = next_oid(SAOid), + EndOfVb = + EVb#varbind{oid = NextOid, + value = {endOfMibView, NextOid}}, + case lists:delete(EVb, SAVbs) of + [] -> + next_loop_varbinds([], [EndOfVb, Vb | Vbs], + MibView, Res, []); + TryAgainVbs -> + next_loop_varbinds({subagent, SAPid, SAOid, + TryAgainVbs}, + [EndOfVb, Vb | Vbs], + MibView, Res, []) + end; + false -> + %% bad index from subagent + {genErr, (hd(SAVbs))#varbind.org_index, []} + end; + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next subagent" + "~n Vb: ~p" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [Vb,ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end + end; +next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, + [], MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds(subagent) -> entry with" + "~n SAPid: ~p" + "~n SAOid: ~p", [SAPid, SAOid]), + case get_next_sa(SAPid, SAOid, SAVbs, MibView) of + {ok, SARes, SAEndOfMibViewVbs} -> + NewRes = lists:append(SARes, Res), + next_loop_varbinds([], SAEndOfMibViewVbs, MibView, NewRes, []); + {noSuchName, OrgIndex} -> + %% v1 reply, treat this Vb as endOfMibView, and try again for + %% the others. + case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of + {value, EVb} -> + NextOid = next_oid(SAOid), + EndOfVb = EVb#varbind{oid = NextOid, + value = {endOfMibView, NextOid}}, + case lists:delete(EVb, SAVbs) of + [] -> + next_loop_varbinds([], [EndOfVb], MibView, Res, []); + TryAgainVbs -> + next_loop_varbinds({subagent, SAPid, SAOid, + TryAgainVbs}, + [EndOfVb], MibView, Res, []) + end; + false -> + %% bad index from subagent + {genErr, (hd(SAVbs))#varbind.org_index, []} + end; + {ErrorStatus, OrgIndex} -> + ?vdebug("next loop varbinds: next subagent" + "~n ErrorStatus: ~p" + "~n OrgIndex: ~p", + [ErrorStatus,OrgIndex]), + {ErrorStatus, OrgIndex, []} + end; +next_loop_varbinds([], [], _MibView, Res, _LAVb) -> + ?vt("next_loop_varbinds -> entry when done", []), + {noError, 0, Res}. + +try_get_instance(_Vb, #me{mfa = {M, F, A}, asn1_type = ASN1Type}) -> + ?vtrace("try get instance from <~p,~p,~p>",[M,F,A]), + Result = (catch dbg_apply(M, F, [get | A])), + % mib shall return {value, <a-nice-value-within-range>} | + % {noValue, noSuchName} (v1) | + % {noValue, noSuchObject | noSuchInstance} (v2, v1) + % everything else (including 'genErr') will generate 'genErr'. + make_value_a_correct_value(Result, ASN1Type, {M, F, A}). + +tab_oid([]) -> [0]; +tab_oid(X) -> X. + +%%----------------------------------------------------------------- +%% Perform a next, using the varbinds Oid if value is simple +%% value. If value is {endOf<something>, NextOid}, use NextOid. +%% This case happens when a table has returned endOfTable, or +%% a subagent has returned endOfMibView. +%%----------------------------------------------------------------- +varbind_next(#varbind{value = Value, oid = Oid}, MibView) -> + ?vt("varbind_next -> entry with" + "~n Value: ~p" + "~n Oid: ~p" + "~n MibView: ~p", [Value, Oid, MibView]), + case Value of + {endOfTable, NextOid} -> + snmpa_mib:next(get(mibserver), NextOid, MibView); + {endOfMibView, NextOid} -> + snmpa_mib:next(get(mibserver), NextOid, MibView); + _ -> + snmpa_mib:next(get(mibserver), Oid, MibView) + end. + +get_next_table(#me{mfa = {M, F, A}}, TableOid, TableOids, MibView) -> + % We know that all TableOids have at least a column number as oid + ?vt("get_next_table -> entry with" + "~n M: ~p" + "~n F: ~p" + "~n A: ~p" + "~n TableOid: ~p" + "~n TableOids: ~p" + "~n MibView: ~p", [M, F, A, TableOid, TableOids, MibView]), + Sorted = snmpa_svbl:sort_varbinds_rows(TableOids), + case get_next_values_all_rows(Sorted, M,F,A, [], TableOid) of + NewVbs when is_list(NewVbs) -> + ?vt("get_next_table -> " + "~n NewVbs: ~p", [NewVbs]), + % We must now check each Vb for endOfTable and that it is + % in the MibView. If not, it becomes a endOfTable. We + % collect all of these together. + transform_tab_next_result(NewVbs, {[], []}, MibView); + {ErrorStatus, OrgIndex} -> + {ErrorStatus, OrgIndex} + end. + +get_next_values_all_rows([Row | Rows], M, F, A, Res, TabOid) -> + {RowIndex, TableOids} = Row, + Cols = delete_index(TableOids), + ?vt("get_next_values_all_rows -> " + "~n Cols: ~p", [Cols]), + Result = (catch dbg_apply(M, F, [get_next, RowIndex, Cols | A])), + ?vt("get_next_values_all_rows -> " + "~n Result: ~p", [Result]), + case validate_tab_next_res(Result, TableOids, {M, F, A}, TabOid) of + Values when is_list(Values) -> + ?vt("get_next_values_all_rows -> " + "~n Values: ~p", [Values]), + NewRes = lists:append(Values, Res), + get_next_values_all_rows(Rows, M, F, A, NewRes, TabOid); + {ErrorStatus, OrgIndex} -> + {ErrorStatus, OrgIndex} + end; +get_next_values_all_rows([], _M, _F, _A, Res, _TabOid) -> + Res. + +transform_tab_next_result([Vb | Vbs], {Res, EndOfs}, MibView) -> + case Vb#varbind.value of + {endOfTable, _} -> +%% ?vtrace("transform_tab_next_result -> endOfTable: " +%% "split varbinds",[]), +%% R = split_varbinds(Vbs, Res, [Vb | EndOfs]), +%% ?vtrace("transform_tab_next_result -> " +%% "~n R: ~p", [R]), +%% R; + split_varbinds(Vbs, Res, [Vb | EndOfs]); + _ -> + case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of + true -> + transform_tab_next_result(Vbs, {[Vb|Res], EndOfs},MibView); + _ -> + Oid = Vb#varbind.oid, + NewEndOf = Vb#varbind{value = {endOfTable, Oid}}, + transform_tab_next_result(Vbs, {Res, [NewEndOf | EndOfs]}, + MibView) + end + end; +transform_tab_next_result([], {Res, EndOfs}, _MibView) -> + ?vt("transform_tab_next_result -> entry with: " + "~n Res: ~p" + "~n EndIfs: ~p",[Res, EndOfs]), + {ok, Res, EndOfs}. + +%%----------------------------------------------------------------- +%% Three cases: +%% 1) All values ok +%% 2) table_func returned {Error, ...} +%% 3) Some value in Values list is erroneous. +%% Args: Value is a list of values from table_func(get_next, ...) +%% TableOids is a list of {TabRestOid, OrgVb} +%% each element in Values and TableOids correspond to each +%% other. +%% Returns: List of NewVarbinds | +%% {ErrorStatus, OrgIndex} +%% (In the NewVarbinds list, the value may be endOfTable) +%%----------------------------------------------------------------- +validate_tab_next_res(Values, TableOids, Mfa, TabOid) -> + ?vt("validate_tab_next_res -> entry with: " + "~n Values: ~p" + "~n TableOids: ~p" + "~n Mfa: ~p" + "~n TabOid: ~p", [Values, TableOids, Mfa, TabOid]), + {_Col, _ASN1Type, OneIdx} = hd(TableOids), + validate_tab_next_res(Values, TableOids, Mfa, [], TabOid, + next_oid(TabOid), OneIdx). +validate_tab_next_res([{NextOid, Value} | Values], + [{_ColNo, OrgVb, _Index} | TableOids], + Mfa, Res, TabOid, TabNextOid, I) -> + ?vt("validate_tab_next_res -> entry with: " + "~n NextOid: ~p" + "~n Value: ~p" + "~n Values: ~p" + "~n TableOids: ~p" + "~n Mfa: ~p" + "~n TabOid: ~p", + [NextOid, Value, Values, TableOids, Mfa, TabOid]), + #varbind{org_index = OrgIndex} = OrgVb, + ?vt("validate_tab_next_res -> OrgIndex: ~p", [OrgIndex]), + NextCompleteOid = lists:append(TabOid, NextOid), + case snmpa_mib:lookup(get(mibserver), NextCompleteOid) of + {table_column, #me{asn1_type = ASN1Type}, _TableEntryOid} -> + ?vt("validate_tab_next_res -> ASN1Type: ~p", [ASN1Type]), + case make_value_a_correct_value({value, Value}, ASN1Type, Mfa) of + {error, ErrorStatus} -> + ?vt("validate_tab_next_res -> " + "~n ErrorStatus: ~p", [ErrorStatus]), + {ErrorStatus, OrgIndex}; + {value, Type, NValue} -> + ?vt("validate_tab_next_res -> " + "~n Type: ~p" + "~n NValue: ~p", [Type, NValue]), + NewVb = OrgVb#varbind{oid = NextCompleteOid, + variabletype = Type, value = NValue}, + validate_tab_next_res(Values, TableOids, Mfa, + [NewVb | Res], TabOid, TabNextOid, I) + end; + Error -> + user_err("Invalid oid ~w from ~w (get_next). Using genErr => ~p", + [NextOid, Mfa, Error]), + {genErr, OrgIndex} + end; +validate_tab_next_res([endOfTable | Values], + [{_ColNo, OrgVb, _Index} | TableOids], + Mfa, Res, TabOid, TabNextOid, I) -> + ?vt("validate_tab_next_res(endOfTable) -> entry with: " + "~n Values: ~p" + "~n OrgVb: ~p" + "~n TableOids: ~p" + "~n Mfa: ~p" + "~n Res: ~p" + "~n TabOid: ~p" + "~n TabNextOid: ~p" + "~n I: ~p", + [Values, OrgVb, TableOids, Mfa, Res, TabOid, TabNextOid, I]), + NewVb = OrgVb#varbind{value = {endOfTable, TabNextOid}}, + validate_tab_next_res(Values, TableOids, Mfa, [NewVb | Res], + TabOid, TabNextOid, I); +validate_tab_next_res([], [], _Mfa, Res, _TabOid, _TabNextOid, _I) -> + Res; +validate_tab_next_res([], [{_Col, _OrgVb, Index}|_], Mfa, _Res, _, _, _I) -> + user_err("Too few values returned from ~w (get_next)", [Mfa]), + {genErr, Index}; +validate_tab_next_res({genErr, ColNumber}, OrgCols, + Mfa, _Res, _TabOid, _TabNextOid, _I) -> + OrgIndex = snmpa_svbl:col_to_orgindex(ColNumber, OrgCols), + validate_err(table_next, {genErr, OrgIndex}, Mfa); +validate_tab_next_res({error, Reason}, [{_ColNo, OrgVb, _Index} | _TableOids], + Mfa, _Res, _TabOid, _TabNextOid, _I) -> + #varbind{org_index = OrgIndex} = OrgVb, + user_err("Erroneous return value ~w from ~w (get_next)", + [Reason, Mfa]), + {genErr, OrgIndex}; +validate_tab_next_res(Error, [{_ColNo, OrgVb, _Index} | _TableOids], + Mfa, _Res, _TabOid, _TabNextOid, _I) -> + #varbind{org_index = OrgIndex} = OrgVb, + user_err("Invalid return value ~w from ~w (get_next)", + [Error, Mfa]), + {genErr, OrgIndex}; +validate_tab_next_res(TooMany, [], Mfa, _Res, _, _, I) -> + user_err("Too many values ~w returned from ~w (get_next)", + [TooMany, Mfa]), + {genErr, I}. + +%%----------------------------------------------------------------- +%% Func: get_next_sa/4 +%% Purpose: Loop the list of varbinds for the subagent. +%% Call subagent_get_next to retreive +%% the next varbinds. +%% Returns: {ok, ListOfNewVbs, ListOfEndOfMibViewsVbs} | +%% {ErrorStatus, ErrorIndex} +%%----------------------------------------------------------------- +get_next_sa(SAPid, SAOid, SAVbs, MibView) -> + case catch subagent_get_next(SAPid, MibView, SAVbs) of + {noError, 0, NewVbs} -> + NewerVbs = transform_sa_next_result(NewVbs,SAOid,next_oid(SAOid)), + split_varbinds(NewerVbs, [], []); + {ErrorStatus, ErrorIndex, _} -> + {ErrorStatus, ErrorIndex}; + {'EXIT', Reason} -> + user_err("Lost contact with subagent (next) ~w. Using genErr", + [Reason]), + {genErr, 0} + end. + +%%----------------------------------------------------------------- +%% Check for wrong prefix returned or endOfMibView, and convert +%% into {endOfMibView, SANextOid}. +%%----------------------------------------------------------------- +transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) + when Vb#varbind.value =:= endOfMibView -> + [Vb#varbind{value = {endOfMibView, SANextOid}} | + transform_sa_next_result(Vbs, SAOid, SANextOid)]; +transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) -> + case lists:prefix(SAOid, Vb#varbind.oid) of + true -> + [Vb | transform_sa_next_result(Vbs, SAOid, SANextOid)]; + _ -> + [Vb#varbind{oid = SANextOid, value = {endOfMibView, SANextOid}} | + transform_sa_next_result(Vbs, SAOid, SANextOid)] + end; +transform_sa_next_result([], _SAOid, _SANextOid) -> + []. + +split_varbinds([Vb | Vbs], Res, EndOfs) -> + case Vb#varbind.value of + {endOfMibView, _} -> split_varbinds(Vbs, Res, [Vb | EndOfs]); + {endOfTable, _} -> split_varbinds(Vbs, Res, [Vb | EndOfs]); + _ -> split_varbinds(Vbs, [Vb | Res], EndOfs) + end; +split_varbinds([], Res, EndOfs) -> {ok, Res, EndOfs}. + +next_oid(Oid) -> + case lists:reverse(Oid) of + [H | T] -> lists:reverse([H+1 | T]); + [] -> [] + end. + + +%%%----------------------------------------------------------------- +%%% 5. GET-BULK REQUEST +%%%----------------------------------------------------------------- +do_get_bulk(MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds) -> + ?vtrace("do get bulk: start with" + "~n MibView: ~p" + "~n NonRepeaters: ~p" + "~n MaxRepetitions: ~p" + "~n PduMS: ~p" + "~n Varbinds: ~p", + [MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds]), + {NonRepVbs, RestVbs} = split_vbs(NonRepeaters, Varbinds, []), + ?vt("do get bulk -> split: " + "~n NonRepVbs: ~p" + "~n RestVbs: ~p", [NonRepVbs, RestVbs]), + case do_get_next(MibView, NonRepVbs) of + {noError, 0, UResNonRepVbs} -> + ?vt("do get bulk -> next: " + "~n UResNonRepVbs: ~p", [UResNonRepVbs]), + ResNonRepVbs = lists:keysort(#varbind.org_index, UResNonRepVbs), + %% Decode the first varbinds, produce a reversed list of + %% listOfBytes. + case (catch enc_vbs(PduMS - ?empty_pdu_size, ResNonRepVbs)) of + {error, Idx, Reason} -> + user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), + {genErr, Idx, []}; + {SizeLeft, Res} when is_integer(SizeLeft) and is_list(Res) -> + ?vtrace("do get bulk -> encoded: " + "~n SizeLeft: ~p" + "~n Res: ~w", [SizeLeft, Res]), + case (catch do_get_rep(SizeLeft, MibView, MaxRepetitions, + RestVbs, Res)) of + {error, Idx, Reason} -> + user_err("failed encoding varbind ~w:~n~p", + [Idx, Reason]), + {genErr, Idx, []}; + Res when is_list(Res) -> + ?vtrace("do get bulk -> Res: " + "~n ~w", [Res]), + {noError, 0, conv_res(Res)}; + Else -> + ?vtrace("do get bulk -> Else: " + "~n ~w", [Else]), + Else + end; + Res when is_list(Res) -> + {noError, 0, conv_res(Res)} + end; + {ErrorStatus, Index, _} -> + ?vdebug("do get bulk: " + "~n ErrorStatus: ~p" + "~n Index: ~p",[ErrorStatus, Index]), + {ErrorStatus, Index, []} + end. + +% sz(L) when list(L) -> length(L); +% sz(B) when binary(B) -> size(B); +% sz(_) -> unknown. + +split_vbs(N, Varbinds, Res) when N =< 0 -> {Res, Varbinds}; +split_vbs(N, [H | T], Res) -> split_vbs(N-1, T, [H | Res]); +split_vbs(_N, [], Res) -> {Res, []}. + +enc_vbs(SizeLeft, Vbs) -> + ?vt("enc_vbs -> entry with" + "~n SizeLeft: ~w", [SizeLeft]), + Fun = fun(Vb, {Sz, Res}) when Sz > 0 -> + ?vt("enc_vbs -> (fun) entry with" + "~n Vb: ~p" + "~n Sz: ~p" + "~n Res: ~w", [Vb, Sz, Res]), + case (catch snmp_pdus:enc_varbind(Vb)) of + {'EXIT', Reason} -> + ?vtrace("enc_vbs -> encode failed: " + "~n Reason: ~p", [Reason]), + throw({error, Vb#varbind.org_index, Reason}); + X -> + ?vt("enc_vbs -> X: ~w", [X]), + Lx = length(X), + ?vt("enc_vbs -> Lx: ~w", [Lx]), + if + Lx < Sz -> + {Sz - length(X), [X | Res]}; + true -> + throw(Res) + end + end; + (_Vb, {_Sz, [_H | T]}) -> + ?vt("enc_vbs -> (fun) entry with" + "~n T: ~p", [T]), + throw(T); + (_Vb, {_Sz, []}) -> + ?vt("enc_vbs -> (fun) entry", []), + throw([]) + end, + lists:foldl(Fun, {SizeLeft, []}, Vbs). + +do_get_rep(Sz, MibView, MaxRepetitions, Varbinds, Res) + when MaxRepetitions >= 0 -> + do_get_rep(Sz, MibView, 0, MaxRepetitions, Varbinds, Res); +do_get_rep(Sz, MibView, _MaxRepetitions, Varbinds, Res) -> + do_get_rep(Sz, MibView, 0, 0, Varbinds, Res). + +conv_res(ResVarbinds) -> + conv_res(ResVarbinds, []). +conv_res([VbListOfBytes | T], Bytes) -> + conv_res(T, VbListOfBytes ++ Bytes); +conv_res([], Bytes) -> + Bytes. + +do_get_rep(_Sz, _MibView, Max, Max, _, Res) -> + ?vt("do_get_rep -> done when: " + "~n Res: ~p", [Res]), + {noError, 0, conv_res(Res)}; +do_get_rep(Sz, MibView, Count, Max, Varbinds, Res) -> + ?vt("do_get_rep -> entry when: " + "~n Sz: ~p" + "~n Count: ~p" + "~n Res: ~w", [Sz, Count, Res]), + case try_get_bulk(Sz, MibView, Varbinds) of + {noError, NextVarbinds, SizeLeft, Res2} -> + ?vt("do_get_rep -> noError: " + "~n SizeLeft: ~p" + "~n Res2: ~p", [SizeLeft, Res2]), + do_get_rep(SizeLeft, MibView, Count+1, Max, NextVarbinds, + Res2 ++ Res); + {endOfMibView, _NextVarbinds, _SizeLeft, Res2} -> + ?vt("do_get_rep -> endOfMibView: " + "~n Res2: ~p", [Res2]), + {noError, 0, conv_res(Res2 ++ Res)}; + {ErrorStatus, Index} -> + ?vtrace("do_get_rep -> done when error: " + "~n ErrorStatus: ~p" + "~n Index: ~p", [ErrorStatus, Index]), + {ErrorStatus, Index, []} + end. + +try_get_bulk(Sz, MibView, Varbinds) -> + ?vt("try_get_bulk -> entry with" + "~n Sz: ~w", [Sz]), + case do_get_next(MibView, Varbinds) of + {noError, 0, UNextVarbinds} -> + ?vt("try_get_bulk -> noError", []), + NextVarbinds = lists:keysort(#varbind.org_index, UNextVarbinds), + case (catch enc_vbs(Sz, NextVarbinds)) of + {error, Idx, Reason} -> + user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), + ?vtrace("try_get_bulk -> error: " + "~n Idx: ~p" + "~n Reason: ~p", [Idx, Reason]), + {genErr, Idx}; + {SizeLeft, Res} when is_integer(SizeLeft) andalso is_list(Res) -> + ?vt("try get bulk -> " + "~n SizeLeft: ~w" + "~n Res: ~w", [SizeLeft, Res]), + {check_end_of_mibview(NextVarbinds), + NextVarbinds, SizeLeft, Res}; + Res when is_list(Res) -> + ?vt("try get bulk -> Res: " + "~n ~w", [Res]), + {endOfMibView, [], 0, Res} + end; + {ErrorStatus, Index, _} -> + ?vt("try get bulk: " + "~n ErrorStatus: ~p" + "~n Index: ~p",[ErrorStatus, Index]), + {ErrorStatus, Index} + end. + +%% If all variables in this pass are endOfMibView, +%% there is no reason to continue. +check_end_of_mibview([#varbind{value = endOfMibView} | T]) -> + check_end_of_mibview(T); +check_end_of_mibview([]) -> endOfMibView; +check_end_of_mibview(_) -> noError. + + +%%%-------------------------------------------------- +%%% 6. SET REQUEST +%%%-------------------------------------------------- +%% return: {ErrStatus, ErrIndex} +%% where ErrIndex is an index in Varbinds list (not org_index (user-functions +%% doesn't see org_index)). +do_set(MibView, UnsortedVarbinds) -> + SetModule = get(set_module), + ?vtrace("set module: ~p",[SetModule]), + apply(SetModule, do_set, [MibView, UnsortedVarbinds]). + +do_subagent_set(Arguments) -> + SetModule = get(set_module), + apply(SetModule, do_subagent_set, [Arguments]). + +%%%----------------------------------------------------------------- +%%% 7. Misc functions +%%%----------------------------------------------------------------- +sort_varbindlist(Varbinds) -> + snmpa_svbl:sort_varbindlist(get(mibserver), Varbinds). + +sa_split(SubagentVarbinds) -> + snmpa_svbl:sa_split(SubagentVarbinds). + +make_response_pdu(ReqId, ErrStatus, ErrIndex, OrgVarbinds, _ResponseVarbinds) + when ErrIndex =/= 0 -> + #pdu{type = 'get-response', request_id = ReqId, error_status = ErrStatus, + error_index = ErrIndex, varbinds = OrgVarbinds}; +make_response_pdu(ReqId, ErrStatus, ErrIndex, _OrgVarbinds, ResponseVarbinds) -> + #pdu{type = 'get-response', request_id = ReqId, error_status = ErrStatus, + error_index = ErrIndex, varbinds = ResponseVarbinds}. + +%% Valid errormsgs for different operations. +validate_err(consistency_check, {'EXIT', _Reason}, _) -> + {genErr, 0}; +validate_err(consistency_check, X, _) -> + X; + +validate_err(is_set_ok, noError, _) -> noError; +validate_err(is_set_ok, noCreation, _) -> noCreation; +validate_err(is_set_ok, inconsistentValue, _) -> inconsistentValue; +validate_err(is_set_ok, resourceUnavailable, _) -> resourceUnavailable; +validate_err(is_set_ok, inconsistentName, _) -> inconsistentName; +validate_err(is_set_ok, badValue, _) -> badValue; +validate_err(is_set_ok, wrongValue, _) -> wrongValue; +validate_err(is_set_ok, noSuchName, _) -> noSuchName; +validate_err(is_set_ok, noAccess, _) -> noAccess; +validate_err(is_set_ok, notWritable, _) -> notWritable; +validate_err(is_set_ok, genErr, _) -> genErr; +validate_err(is_set_ok, X, Mfa) -> + user_err("~w with is_set_ok, returned: ~w. Using genErr.", + [Mfa, X]), + genErr; + +validate_err(set, commitFailed, _) -> commitFailed; +validate_err(set, undoFailed, _) -> undoFailed; +validate_err(set, noError, _) -> noError; +validate_err(set, genErr, _) -> genErr; +validate_err(set, X, Mfa) -> + user_err("~w with set, returned: ~w. Using genErr.", + [Mfa, X]), + genErr; + +validate_err(undo, undoFailed, _) -> undoFailed; +validate_err(undo, noError, _) -> noError; +validate_err(undo, genErr, _) -> genErr; +validate_err(undo, X, Mfa) -> + user_err("~w with undo, returned: ~w. Using genErr.", + [Mfa, X]), + genErr; + +validate_err(table_is_set_ok, {Err, Idx}, Mfa) when is_integer(Idx) -> + {validate_err(is_set_ok, Err, Mfa), Idx}; +validate_err(table_is_set_ok, X, Mfa) -> + user_err("~w with is_set_ok (table), returned: ~w. Using genErr.", + [Mfa, X]), + {genErr, 0}; + +validate_err(row_is_set_ok, {Err, Idx}, _) when is_integer(Idx) -> + {Err, Idx}; +validate_err(row_is_set_ok, {_Err, {false, BadCol}}, Mfa) -> + user_err("~w with is_set_ok (table), returned bad column: " + "~w. Using genErr.", [Mfa, BadCol]), + {genErr, 0}; + +validate_err(table_undo, {Err, Idx}, Mfa) when is_integer(Idx) -> + {validate_err(undo, Err, Mfa), Idx}; +validate_err(table_undo, X, Mfa) -> + user_err("~w with undo (table), returned: ~w. Using genErr.", + [Mfa, X]), + {genErr, 0}; + +validate_err(row_undo, {Err, Idx}, _) when is_integer(Idx) -> + {Err, Idx}; +validate_err(row_undo, {_Err, {false, BadCol}}, Mfa) -> + user_err("~w with undo (table), returned bad column: " + "~w. Using genErr.", [Mfa, BadCol]), + {genErr, 0}; + +validate_err(table_set, {Err, Idx}, Mfa) when is_integer(Idx) -> + {validate_err(set, Err, Mfa), Idx}; +validate_err(table_set, X, Mfa) -> + user_err("~w with set (table), returned: ~w. Using genErr.", + [Mfa, X]), + {genErr, 0}; + +validate_err(row_set, {Err, Idx}, _) when is_integer(Idx) -> + {Err, Idx}; +validate_err(row_set, {_Err, {false, BadCol}}, Mfa) -> + user_err("~w with set (table), returned bad column: " + "~w. Using genErr.", [Mfa, BadCol]), + {genErr, 0}; + +validate_err(table_next, {Err, Idx}, _Mfa) when is_integer(Idx) -> + {Err, Idx}; +validate_err(table_next, {_Err, {false, BadCol}}, Mfa) -> + user_err("~w with get_next, returned bad column: " + "~w. Using genErr.", [Mfa, BadCol]), + {genErr, 0}. + +validate_err(v2_to_v1, {V2Err, Index}) -> + {v2err_to_v1err(V2Err), Index}; +validate_err(v2_to_v1, _) -> + {genErr, 0}. + +get_err({ErrC, ErrI, Vbs}) -> + {get_err_i(ErrC), ErrI, Vbs}. + +get_err_i(noError) -> noError; +get_err_i(S) -> + ?vtrace("convert '~p' to 'genErr'",[S]), + genErr. + +v2err_to_v1err(noError) -> noError; +v2err_to_v1err(noAccess) -> noSuchName; +v2err_to_v1err(noCreation) -> noSuchName; +v2err_to_v1err(notWritable) -> noSuchName; +v2err_to_v1err(wrongLength) -> badValue; +v2err_to_v1err(wrongEncoding) -> badValue; +v2err_to_v1err(wrongType) -> badValue; +v2err_to_v1err(wrongValue) -> badValue; +v2err_to_v1err(inconsistentValue) -> badValue; +v2err_to_v1err(inconsistentName) -> noSuchName; +v2err_to_v1err(noSuchName) -> noSuchName; +v2err_to_v1err(badValue) -> badValue; +v2err_to_v1err(authorizationError) -> noSuchName; +%% genErr | resourceUnavailable | undoFailed | commitFailed -> genErr +v2err_to_v1err(_Error) -> genErr. + +%%----------------------------------------------------------------- +%% transforms a (hopefully correct) return value ((perhaps) from a +%% mib-function) to a typed and guaranteed correct return value. +%% An incorrect return value is transformed to {error, genErr}. +%% A correct return value is on the form: +%% {error, <error-msg>} | {value, <variable-type>, <value>} +%%----------------------------------------------------------------- +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'INTEGER' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'Counter32' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'Unsigned32' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'TimeTicks' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when Asn1#asn1_type.bertype =:= 'Counter64' -> + check_integer(Val, Asn1, Mfa); + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when (Asn1#asn1_type.bertype =:= 'BITS') andalso is_list(Val) -> + {value,Kibbles} = snmp_misc:assq(kibbles,Asn1#asn1_type.assocList), + case snmp_misc:bits_to_int(Val,Kibbles) of + error -> + wrongValue(Val, Mfa); + Int -> + make_value_a_correct_value({value,Int},Asn1,Mfa) + end; + +make_value_a_correct_value({value, Val}, Asn1, Mfa) + when (Asn1#asn1_type.bertype =:= 'BITS') andalso is_integer(Val) -> + {value,Kibbles} = snmp_misc:assq(kibbles,Asn1#asn1_type.assocList), + {_Kibble,BitNo} = lists:last(Kibbles), + case (1 bsl (BitNo+1)) of + X when Val < X -> + {value,'BITS',Val}; + _Big -> + wrongValue(Val, Mfa) + end; + +make_value_a_correct_value({value, String}, + #asn1_type{bertype = 'OCTET STRING', + hi = Hi, lo = Lo}, Mfa) -> + check_octet_string(String, Hi, Lo, Mfa, 'OCTET STRING'); + +make_value_a_correct_value({value, String}, + #asn1_type{bertype = 'IpAddress', + hi = Hi, lo = Lo}, Mfa) -> + check_octet_string(String, Hi, Lo, Mfa, 'IpAddress'); + +make_value_a_correct_value({value, Oid}, + #asn1_type{bertype = 'OBJECT IDENTIFIER'}, + _Mfa) -> + case snmp_misc:is_oid(Oid) of + true -> {value, 'OBJECT IDENTIFIER', Oid}; + _Else -> {error, wrongType} + end; + +make_value_a_correct_value({value, Val}, Asn1, _Mfa) + when Asn1#asn1_type.bertype =:= 'Opaque' -> + if is_list(Val) -> {value, 'Opaque', Val}; + true -> {error, wrongType} + end; + +make_value_a_correct_value({noValue, noSuchObject}, _ASN1Type, _Mfa) -> + {value, noValue, noSuchObject}; +make_value_a_correct_value({noValue, noSuchInstance}, _ASN1Type, _Mfa) -> + {value, noValue, noSuchInstance}; +make_value_a_correct_value({noValue, noSuchName}, _ASN1Type, _Mfa) -> + %% Transform this into a v2 value. It is converted to noSuchName + %% later if it was v1. If it was v2, we use noSuchInstance. + {value, noValue, noSuchInstance}; +%% For backwards compatibility only - we really shouldn't allow this; +%% it makes no sense to return unSpecified for a variable! But we did +%% allow it previously. -- We transform unSpecified to noSuchInstance +%% (OTP-3303). +make_value_a_correct_value({noValue, unSpecified}, _ASN1Type, _Mfa) -> + {value, noValue, noSuchInstance}; +make_value_a_correct_value(genErr, _ASN1Type, _MFA) -> + {error, genErr}; + +make_value_a_correct_value(_WrongVal, _ASN1Type, undef) -> + {error, genErr}; + +make_value_a_correct_value(WrongVal, ASN1Type, Mfa) -> + user_err("Got ~w from ~w. (~w) Using genErr", + [WrongVal, Mfa, ASN1Type]), + {error, genErr}. + +check_integer(Val, Asn1, Mfa) -> + case Asn1#asn1_type.assocList of + undefined -> check_size(Val, Asn1, Mfa); + Alist -> + case snmp_misc:assq(enums, Alist) of + {value, Enums} -> check_enums(Val, Asn1, Enums, Mfa); + false -> check_size(Val, Asn1, Mfa) + end + end. + +check_octet_string(String, Hi, Lo, Mfa, Type) -> + Len = (catch length(String)), % it might not be a list + case snmp_misc:is_string(String) of + true when Lo =:= undefined -> {value, Type, String}; + true when Len =< Hi, Len >= Lo -> + {value, Type, String}; + true -> + wrongLength(String, Mfa); + _Else -> + wrongType(String, Mfa) + end. + +check_size(Val, #asn1_type{lo = Lo, hi = Hi, bertype = Type}, Mfa) + when is_integer(Val) -> + ?vtrace("check size of integer: " + "~n Value: ~p" + "~n Upper limit: ~p" + "~n Lower limit: ~p" + "~n BER-type: ~p", + [Val,Hi,Lo,Type]), + if + (Lo =:= undefined) andalso (Hi =:= undefined) -> {value, Type, Val}; + (Lo =:= undefined) andalso is_integer(Hi) andalso (Val =< Hi) -> + {value, Type, Val}; + is_integer(Lo) andalso (Val >= Lo) andalso (Hi =:= undefined) -> + {value, Type, Val}; + is_integer(Lo) andalso is_integer(Hi) andalso (Val >= Lo) andalso (Val =< Hi) -> + {value, Type, Val}; + true -> + wrongValue(Val, Mfa) + end; +check_size(Val, _, Mfa) -> + wrongType(Val, Mfa). + +check_enums(Val, Asn1, Enums, Mfa) -> + Association = + if + is_integer(Val) -> lists:keysearch(Val, 2, Enums); + is_atom(Val) -> lists:keysearch(Val, 1, Enums); + true -> {error, wrongType} + end, + case Association of + {value, {_AliasIntName, Val2}} -> + {value, Asn1#asn1_type.bertype, Val2}; + false -> + wrongValue(Val, Mfa); + {error, wrongType} -> + wrongType(Val, Mfa) + end. + +wrongLength(Val, Mfa) -> + report_err(Val, Mfa, wrongLength). + +wrongValue(Val, Mfa) -> + report_err(Val, Mfa, wrongValue). + +wrongType(Val, Mfa) -> + report_err(Val, Mfa, wrongType). + +report_err(_Val, undef, Err) -> + {error, Err}; +report_err(Val, Mfa, Err) -> + user_err("Got ~p from ~w. Using ~w", [Val, Mfa, Err]), + {error, Err}. + +is_valid_pdu_type('get-request') -> true; +is_valid_pdu_type('get-next-request') -> true; +is_valid_pdu_type('get-bulk-request') -> true; +is_valid_pdu_type('set-request') -> true; +is_valid_pdu_type(_) -> false. + +get_pdu_data() -> + {get(net_if_data), + get(snmp_request_id), + get(snmp_address), + get(snmp_community), + get(snmp_context)}. + +put_pdu_data({Extra, ReqId, Address, Community, ContextName}) -> + {put(net_if_data, Extra), + put(snmp_address, Address), + put(snmp_request_id, ReqId), + put(snmp_community, Community), + put(snmp_context, ContextName)}. + +tr_var(Oid, Idx) -> + case snmp_misc:is_oid(Oid) of + true -> + {#varbind{oid = Oid, value = unSpecified, org_index = Idx}, + Idx+1}; + false -> throw({error, {bad_oid, Oid}}) + end. + +tr_varbind(#varbind{value = Value}) -> Value. + +mapfoldl(F, Eas, Accu0, [Hd|Tail]) -> + {R,Accu1} = apply(F, [Hd,Accu0|Eas]), + {Accu2,Rs} = mapfoldl(F, Eas, Accu1, Tail), + {Accu2,[R|Rs]}; +mapfoldl(_F, _Eas, Accu, []) -> {Accu,[]}. + +%%----------------------------------------------------------------- +%% Runtime debugging of the agent. +%%----------------------------------------------------------------- + +dbg_apply(M,F,A) -> + case get(verbosity) of + silence -> + apply(M,F,A); + _ -> + ?vlog("~n apply: ~w,~w,~p~n", [M,F,A]), + Res = (catch apply(M,F,A)), + case Res of + {'EXIT', Reason} -> + ?vinfo("Call to: " + "~n Module: ~p" + "~n Function: ~p" + "~n Args: ~p" + "~n" + "~nresulted in an exit" + "~n" + "~n ~p", [M, F, A, Reason]); + _ -> + ?vlog("~n returned: ~p", [Res]) + end, + Res + end. + + +short_name(none) -> ma; +short_name(_Pid) -> sa. + +worker_short_name(ma) -> maw; +worker_short_name(_) -> saw. + +trap_sender_short_name(ma) -> mats; +trap_sender_short_name(_) -> sats. + +pdu_handler_short_name(ma) -> maph; +pdu_handler_short_name(_) -> saph. + + +mib_server_verbosity(Pid,Verbosity) when is_pid(Pid) -> + snmpa_mib:verbosity(Pid,Verbosity); +mib_server_verbosity(_Pid,_Verbosity) -> + ok. + +note_store_verbosity(Pid,Verbosity) when is_pid(Pid) -> + snmp_note_store:verbosity(Pid,Verbosity); +note_store_verbosity(_Pid,_Verbosity) -> + ok. + +subagents_verbosity(V) -> + subagents_verbosity(catch snmpa_mib:info(get(mibserver),subagents),V). + +subagents_verbosity([],_V) -> + ok; +subagents_verbosity([{Pid,_Oid}|T],V) -> + catch verbosity(Pid,V), %% On the agent + catch verbosity(Pid,{subagents,V}), %% and it's subagents + subagents_verbosity(T,V); +subagents_verbosity(_,_V) -> + ok. + + +%% --------------------------------------------------------------------- + +handle_get_log_type(#state{net_if_mod = Mod}) + when Mod =/= undefined -> + case (catch Mod:get_log_type(get(net_if))) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end; +handle_get_log_type(_) -> + {error, not_supported}. + +handle_set_log_type(#state{net_if_mod = Mod}, NewType) + when Mod =/= undefined -> + case (catch Mod:set_log_type(get(net_if), NewType)) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end; +handle_set_log_type(_, _) -> + {error, not_supported}. + + +handle_get_request_limit(#state{net_if_mod = Mod}) + when Mod =/= undefined -> + case (catch Mod:get_request_limit(get(net_if))) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end; +handle_get_request_limit(_) -> + {error, not_supported}. + +handle_set_request_limit(#state{net_if_mod = Mod}, NewLimit) + when Mod =/= undefined -> + case (catch Mod:set_request_limit(get(net_if), NewLimit)) of + {'EXIT', _} -> + {error, not_supported}; + Else -> + Else + end; +handle_set_request_limit(_, _) -> + {error, not_supported}. + + +agent_info(#state{worker = W, set_worker = SW}) -> + case (catch get_agent_info(W, SW)) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +get_agent_info(W, SW) -> + MASz = proc_mem(self()), + WSz = proc_mem(W), + SWSz = proc_mem(SW), + ATSz = tab_mem(snmp_agent_table), + CCSz = tab_mem(snmp_community_cache), + VacmSz = tab_mem(snmpa_vacm), + [{process_memory, [{master_agent, MASz}, + {worker, WSz}, + {set_worker, SWSz}]}, + {db_memory, [{agent, ATSz}, + {community_cache, CCSz}, + {vacm, VacmSz}]}]. + +proc_mem(P) when is_pid(P) -> + case (catch erlang:process_info(P, memory)) of + {memory, Sz} when is_integer(Sz) -> + Sz; + _ -> + undefined + end; +proc_mem(_) -> + undefined. + +tab_mem(T) -> + case (catch ets:info(T, memory)) of + Sz when is_integer(Sz) -> + Sz; + _ -> + undefined + end. + +net_if_info(#state{net_if_mod = Mod}) when Mod =/= undefined -> + case (catch Mod:info(get(net_if))) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end; +net_if_info(_) -> + %% This could be a result of a code upgrade + %% Make best effert + [{process_memory, proc_mem(get(net_if))}]. + +note_store_info(#state{note_store = NS}) -> + case (catch snmp_note_store:info(NS)) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +symbolic_store_info() -> + case (catch snmpa_symbolic_store:info()) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +local_db_info() -> + case (catch snmpa_local_db:info()) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +mib_server_info() -> + case (catch snmpa_mib:info(get(mibserver))) of + Info when is_list(Info) -> + Info; + E -> + [{error, E}] + end. + +get_stats_counters() -> + Counters = snmpa_mpd:counters(), + get_stats_counters(Counters, []). + +get_stats_counters([], Acc) -> + lists:reverse(Acc); +get_stats_counters([Counter|Counters], Acc) -> + case ets:lookup(snmp_agent_table, Counter) of + [CounterVal] -> + get_stats_counters(Counters, [CounterVal|Acc]); + _ -> + get_stats_counters(Counters, Acc) + end. + + +%% --------------------------------------------------------------------- + +%% info_msg(F, A) -> +%% ?snmpa_info(F, A). + +warning_msg(F, A) -> + ?snmpa_warning(F, A). + +error_msg(F, A) -> + ?snmpa_error(F, A). + +%% --- + +config_err(F, A) -> + snmpa_error:config_err(F, A). + +user_err(F, A) -> + snmpa_error:user_err(F, A). + + +%% --------------------------------------------------------------------- + +call(Server, Req) -> + gen_server:call(Server, Req, infinity). + +cast(Server, Msg) -> + gen_server:cast(Server, Msg). + + +%% --------------------------------------------------------------------- + +get_verbosity(Opts) -> + get_option(verbosity, Opts, ?default_verbosity). + +get_mibs(Opts) -> + get_option(mibs, Opts, []). + +get_mib_storage(Opts) -> + get_option(mib_storage, Opts, ets). + +get_set_mechanism(Opts) -> + get_option(set_mechanism, Opts, snmpa_set). + +get_authentication_service(Opts) -> + get_option(authentication_service, Opts, snmpa_acm). + +get_multi_threaded(Opts) -> + get_option(multi_threaded, Opts, false). + +get_versions(Opts) -> + get_option(versions, Opts, [v1,v2,v3]). + +get_note_store_opt(Opts) -> + get_option(note_store, Opts, []). + +get_net_if_opt(Opts) -> + get_option(net_if, Opts, []). + +get_net_if_verbosity(Opts) -> + get_option(verbosity, Opts, silence). + +get_net_if_module(Opts) -> + get_option(module, Opts, snmpa_net_if). + +get_net_if_options(Opts) -> + get_option(options, Opts, []). + + +net_if_verbosity(Pid,Verbosity) when is_pid(Pid) -> + Pid ! {verbosity,Verbosity}; +net_if_verbosity(_Pid,_Verbosity) -> + ok. + + +get_option(Key, Opts, Default) -> + snmp_misc:get_option(Key, Opts, Default). + + +%% --------------------------------------------------------------------- + + +%% i(F) -> +%% i(F, []). + +%% i(F, A) -> +%% io:format("~p: " ++ F ++ "~n", [?MODULE|A]). + + + |