1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20-module(snmpa_trap).
21
22%%%-----------------------------------------------------------------
23%%% This module takes care of all trap(notification handling.
24%%%-----------------------------------------------------------------
25%% External exports
26-export([construct_trap/2,
27	 try_initialise_vars/2,
28	 send_trap/6, send_trap/7, send_trap/8]).
29-export([send_discovery/6]).
30
31%% Internal exports
32-export([init_v2_inform/9, init_v2_inform/10,
33	 init_v3_inform/9, init_v3_inform/10, init_v3_inform/11,
34	 send_inform/6]).
35-export([init_discovery_inform/13, send_discovery_inform/5]).
36
37%% <BACKWARD-COMPAT>
38-export([send_discovery/5,
39	 init_discovery_inform/12]).
40%% </BACKWARD-COMPAT>
41
42-include_lib("snmp/include/snmp_types.hrl").
43-include_lib("snmp/src/agent/snmpa_internal.hrl").
44-include_lib("snmp/include/SNMPv2-MIB.hrl").
45-include_lib("snmp/include/SNMPv2-TM.hrl").
46-include_lib("snmp/include/SNMPv2-TC.hrl").
47-include_lib("snmp/include/SNMP-FRAMEWORK-MIB.hrl").
48-include_lib("snmp/include/SNMP-TARGET-MIB.hrl").
49-include_lib("snmp/include/TRANSPORT-ADDRESS-MIB.hrl").
50-define(enterpriseSpecific, 6).
51
52
53-define(VMODULE,"TRAP").
54-include("snmp_verbosity.hrl").
55-include("snmpa_internal.hrl").
56
57
58%%-----------------------------------------------------------------
59%% Trap mechanism
60%% ==============
61%% Distributed subagent (dSA) case
62%%   The MIB with the TRAP-TYPE macro is loaded in dSA.  This means
63%%   that dSA has info on all variables defined in the TRAP-TYPE,
64%%   even though some variables may be located in other SA:s (or
65%%   in the MA). Other variables that may be sent in the trap,
66%%   must be known by either the dSA, or some of its parent agents
67%%   (e.g. the master agent), if the variable should be referred
68%%   to by symbolic name. It is however possible to send other
69%%   variables as well, but then the entire OID must be provided.
70%%   The dSA locates the asn1 type, oid and value for as many
71%%   variables as possible. This information, together with the
72%%   variables for which the type, value or oid isn't known, is
73%%   sent to the dSA's parent. This agent performs the same
74%%   operation, and so on, until eventually the MA will receive the
75%%   info. The MA then fills in the gaps, and at this point all
76%%   oids and types must be known, otherwise an error is signalled,
77%%   and the opertaion is aborted. For the unknown values for some
78%%   oids, a get-operation is performed by the MA. This will
79%%   retreive the missing values.
80%%   At this point, all oid, types and values are known, so the MA
81%%   can distribute the traps according to the information in the
82%%   internal tables.
83%%
84%% Local subagent (lSA) case
85%%   This case is similar to the case above.
86%%
87%% Master agent (MA) case
88%%   This case is similar to the case above.
89%%
90%% NOTE: All trap forwarding between agents is made asynchronously.
91%%
92%% dSA: Distributed SA  (the #trap is loaded here)
93%% nSA: [many] SAs between dSA and MA
94%% MA:  Master Agent. (all trap info (destiniations is here))
95%% 1) application decides to send a trap.
96%% 2) dSA calls send_trap which initialises vars
97%% 3) dSA sends all to nSA
98%% 4) nSA tries to map symbolic names to oids and find the types
99%%    of all variableoids with a value (and no type).
100%% 5) nSA sends all to (n-1)SA
101%% 6) MA tries to initialise vars
102%% 7) MA makes a trappdu, and sends it to all destination.
103%%
104%% Problems with this implementation
105%% =================================
106%% It's ok to send {Oid, Value} but not just Oid. (it should be for
107%%   any Oid)
108%% It's ok to send {Name, Value} but not just Name. (it should be
109%%   for Names in the hierarchy)
110%% This approach might be too flexible; will people use it?
111%% *NOTE*
112%% Therefore, in this version we *do not* allow extra variables
113%% in traps.
114%% *YES* In _this_ version we do.
115%%-----------------------------------------------------------------
116
117%%-----------------------------------------------------------------
118%% Func: construct_trap/2
119%% Args: Trap is an atom
120%%       Varbinds is a list of
121%%               {Variable, Value} | {SymbolicTableCol, RowIndex, Value}
122%%         where Variable is an atom or an OID,
123%%         where RowIndex is the indexes for the row.
124%%         We don't check the RowIndex.
125%% Purpose: This is the initially-called function. It is called
126%%          by the agent that found out that a trap should be
127%%          sent.
128%%          Initialize as many variables as possible.
129%% Returns: {ok, TrapRecord, <list of Var>} | error
130%%          where Var is returned from initiate_vars.
131%% NOTE: Executed at the inital SA
132%%-----------------------------------------------------------------
133construct_trap(Trap, Varbinds) ->
134    ?vdebug("construct_trap -> entry with"
135            "~n   Trap: ~p", [Trap]),
136    case snmpa_symbolic_store:get_notification(Trap) of
137	undefined ->
138	    user_err("construct_trap got undef Trap: ~w" , [Trap]),
139	    error;
140
141	{value, #trap{oidobjects = ListOfVars} = TRec} ->
142	    ?vdebug("construct_trap -> trap"
143		    "~n   ~p", [TRec]),
144	    OidVbs        = [alias_to_oid(Vb) || Vb <- Varbinds],
145	    LV            = initiate_vars(ListOfVars, OidVbs),
146	    InitiatedVars = try_initialise_vars(get(mibserver), LV),
147	    {ok, TRec, InitiatedVars};
148
149	{value, #notification{oidobjects = ListOfVars} = NRec} ->
150	    ?vdebug("construct_trap -> notification"
151		    "~n   ~p", [NRec]),
152	    OidVbs        = [alias_to_oid(Vb) || Vb <- Varbinds],
153	    LV            = initiate_vars(ListOfVars, OidVbs),
154	    InitiatedVars = try_initialise_vars(get(mibserver), LV),
155	    {ok, NRec, InitiatedVars}
156    end.
157
158%% Variable value (without oid processing)
159alias_to_oid({Alias, Val}) when is_atom(Alias) ->
160    case alias2oid(Alias) of
161	Alias ->
162            {{keep, Alias}, {value, Val}};
163	Oid ->
164            {{keep, Oid}, {value, Val}}
165    end;
166alias_to_oid({Oid, Val}) when is_list(Oid) ->
167    {{keep, Oid}, {value, Val}};
168
169%% Variable value (with oid processing)
170alias_to_oid({{Process, Alias}, Val}) when is_atom(Alias) ->
171    case alias2oid(Alias) of
172	Alias ->
173            {{Process, Alias}, {value, Val}};
174	Oid ->
175            {{Process, Oid}, {value, Val}}
176    end;
177alias_to_oid({{Process, Oid}, Val}) when is_list(Oid) ->
178    {{Process, Oid}, {value, Val}};
179
180%% Table Column value
181alias_to_oid({Alias, RowIndex, Val}) when is_atom(Alias) ->
182    case alias2oid(Alias, RowIndex) of
183        Alias ->
184            {Alias, RowIndex, {value, Val}};
185	Oid ->
186            {{keep, Oid}, {value, Val}}
187
188    end.
189
190alias2oid(Alias) ->
191    alias2oid(Alias, [0]).
192
193alias2oid(Alias, Append) ->
194    case snmpa_symbolic_store:aliasname_to_oid(Alias) of
195	{value, Oid} ->
196            lists:append(Oid, Append);
197	_ ->
198            Alias
199    end.
200
201
202%%-----------------------------------------------------------------
203%% Func: initiate_vars/2
204%% Args: ListOfVars is a list of {Oid, #asn1_type}
205%%       Varbinds is a list of
206%%          {{Process, VariableOid}, Value} |
207%%          {{Process, VariableAtom}, Value} |
208%%          {TableColAtom, RowIndex, Value}
209%% Purpose: For each variable specified in the TRAP-TYPE macro
210%%          (each in ListOfVars), check if it's got a value given
211%%          in the Varbinds list.
212%%          For each Oid:
213%%            1) It has corresponding VariableOid. Use Value.
214%%            2) No corresponding VariableOid. No value.
215%% Returns: A list of
216%%            {{Process, VariableOid}, #asn1_type, Value} |
217%%            {{Process, VariableOid}, #asn1_type} |
218%%            {{Process, VariableOid}, Value} |
219%%            {{Process, VariableAtom}, Value} |
220%%            {TableColAtom, RowIndex, Value}
221%% NOTE: Executed at the inital SA
222%%-----------------------------------------------------------------
223initiate_vars([{Oid, Asn1Type} | T], Varbinds) ->
224    case delete_oid_from_varbinds(Oid, Varbinds) of
225	{undefined, _, _} ->
226	    [{{keep, Oid}, Asn1Type} | initiate_vars(T, Varbinds)];
227        %% Skip this oid!
228	{{value, ?NOTIFICATION_IGNORE_VB_VALUE}, _VarOid, RestOfVarbinds} ->
229	    initiate_vars(T, RestOfVarbinds);
230	{Value, VarOid, RestOfVarbinds} ->
231	    [{VarOid, Asn1Type, Value} | initiate_vars(T, RestOfVarbinds)]
232    end;
233initiate_vars([], Varbinds) ->
234    Varbinds.
235
236delete_oid_from_varbinds(Oid, [{{_Process, VarOid} = VOid, Value} | T]) ->
237    case lists:prefix(Oid, VarOid) of
238	true ->
239	    {Value, VOid, T};
240	_ ->
241	    {Value2, VOid2, T2} = delete_oid_from_varbinds(Oid, T),
242	    {Value2, VOid2, [{VOid, Value} | T2]}
243    end;
244delete_oid_from_varbinds(Oid, [H | T]) ->
245    {Value, VOid, T2} = delete_oid_from_varbinds(Oid, T),
246    {Value, VOid, [H | T2]};
247delete_oid_from_varbinds(_Oid, []) ->
248    {undefined, undefined, []}.
249
250
251%%-----------------------------------------------------------------
252%% Func: try_initialise_vars(Mib, Varbinds)
253%% Args: Mib is the local mib process
254%%       Varbinds is a list returned from initiate_vars.
255%% Purpose: Try to initialise uninitialised vars.
256%% Returns: see initiate_vars
257%% NOTE: Executed at the intermediate SAs
258%%-----------------------------------------------------------------
259try_initialise_vars(Mib, Varbinds) ->
260    V = try_map_symbolic(Varbinds),
261    try_find_type(V, Mib).
262
263
264%%-----------------------------------------------------------------
265%% Func: try_map_symbolic/1
266%% Args: Varbinds is a list returned from initiate_vars.
267%% Purpose: Try to map symbolic name to oid for the
268%%          symbolic names left in the Varbinds list.
269%% Returns: see initiate_vars.
270%% NOTE: Executed at the intermediate SAs
271%%-----------------------------------------------------------------
272try_map_symbolic([Varbind | Varbinds]) ->
273    [localise_oid(Varbind) | try_map_symbolic(Varbinds)];
274try_map_symbolic([]) -> [].
275
276localise_oid({{_Process, Alias}, _Value} = VB) when is_atom(Alias) ->
277    alias_to_oid(VB);
278localise_oid({Alias, _RowIndex, _Value} = VB) when is_atom(Alias) ->
279    alias_to_oid(VB);
280localise_oid(X) ->
281    X.
282
283
284%%-----------------------------------------------------------------
285%% Func: try_find_type/2
286%% Args: Varbinds is a list returned from initiate_vars.
287%%       Mib is a ref to the Mib process corresponding to
288%%         this agent.
289%% Purpose: Try to find the type for each variableoid with a value
290%%          but no type.
291%% Returns: see initiate_vars.
292%% NOTE: Executed at the intermediate SAs
293%%-----------------------------------------------------------------
294try_find_type(Varbinds, Mib) ->
295    [localise_type(Varbind, Mib) || Varbind <- Varbinds].
296
297%% We add the 'process oid' value of 'keep' for all variables
298%% that does not already have a (process) value (and tables
299%% although those will never have any other value).
300localise_type({VariableOid, Type}, _Mib)
301  when is_list(VariableOid) andalso is_record(Type, asn1_type) ->
302    {{keep, VariableOid}, Type};
303localise_type({{_Process, VariableOid} = VOid, Type}, _Mib)
304  when is_list(VariableOid) andalso is_record(Type, asn1_type) ->
305    {VOid, Type};
306localise_type({VariableOid, Value}, Mib) when is_list(VariableOid) ->
307    case snmpa_mib:lookup(Mib, VariableOid) of
308	{variable, ME} ->
309	    {{keep, VariableOid}, ME#me.asn1_type, Value};
310	{table_column, ME, _} ->
311	    {{keep, VariableOid}, ME#me.asn1_type, Value};
312	_ ->
313	    {{keep, VariableOid}, Value}
314    end;
315localise_type({{_Process, VariableOid} = VOid, Value}, Mib)
316  when is_list(VariableOid) ->
317    case snmpa_mib:lookup(Mib, VariableOid) of
318	{variable, ME} ->
319	    {VOid, ME#me.asn1_type, Value};
320	{table_column, ME, _} ->
321	    {VOid, ME#me.asn1_type, Value};
322	_ ->
323	    {VOid, Value}
324    end;
325localise_type(X, _) ->
326    X.
327
328
329%%-----------------------------------------------------------------
330%% Func: make_v1_trap_pdu/5
331%% Args: Enterprise = oid()
332%%       Specific = integer()
333%%       Varbinds is as returned from initiate_vars
334%%         (but only {Oid, Type[, Value} permitted)
335%%       SysUpTime = integer()
336%%       AgentIp = {A, B, C, D}
337%% Purpose: Make a #trappdu
338%%          Checks the Varbinds to see that no symbolic names are
339%%          present, and that each var has a type. Performs a get
340%%          to find any missing value.
341%% Returns: {#trappdu, [byte()] | error
342%% Fails: yes
343%% NOTE: Executed at the MA
344%%-----------------------------------------------------------------
345make_v1_trap_pdu(Enterprise, Specific, VarbindList, SysUpTime, AgentIp) ->
346    {Enterp,Generic,Spec} =
347	case Enterprise of
348	    ?snmp ->
349		{sys_object_id(),Specific,0};
350	    _ ->
351		{Enterprise,?enterpriseSpecific,Specific}
352    end,
353    #trappdu{enterprise    = Enterp,
354	     agent_addr    = AgentIp,
355	     generic_trap  = Generic,
356	     specific_trap = Spec,
357	     time_stamp    = SysUpTime,
358	     varbinds      = VarbindList}.
359
360make_discovery_pdu(Vbs) ->
361    #pdu{type         = 'inform-request',
362	 request_id   = snmpa_mpd:generate_req_id(),
363	 error_status = noError,
364	 error_index  = 0,
365	 varbinds     = Vbs}.
366
367make_v2_notif_pdu(Vbs, Type) ->
368    #pdu{type         = Type,
369	 request_id   = snmpa_mpd:generate_req_id(),
370	 error_status = noError,
371	 error_index  = 0,
372	 varbinds     = Vbs}.
373
374make_varbind_list(Varbinds) ->
375    OVarbinds = order(Varbinds),
376    {VariablesWithValueAndType, VariablesWithType} = split_variables(OVarbinds),
377    V    = get_values(VariablesWithType),
378    Vars = lists:append([V, VariablesWithValueAndType]),
379    [make_varbind(Var) || Var <- unorder(lists:keysort(1, Vars))].
380
381
382%%-----------------------------------------------------------------
383%% Func: send_trap/6
384%% Args: TrapRec = #trap | #notification
385%%       NotifyName = string()
386%%       ContextName = string()
387%%       Recv = no_receiver | {Ref, Receiver}
388%%       Receiver = pid() | atom() | {M,F,A}
389%%       Vbs = [varbind()]
390%%       NetIf = pid()
391%% Purpose: Default trap sending function.
392%%          Sends the trap to the targets pointed out by NotifyName.
393%%          If NotifyName is ""; the normal procedure defined in
394%%          SNMP-NOTIFICATION-MIB is used, i.e. the trap is sent to
395%%          all managers.
396%%          Otherwise, the NotifyName is used to find an entry in the
397%%          SnmpNotifyTable which define how to send the notification
398%%          (as an Inform or a Trap), and to select targets from
399%%          SnmpTargetAddrTable (using the Tag).
400%%-----------------------------------------------------------------
401send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf) ->
402    ExtraInfo     = ?DEFAULT_NOTIF_EXTRA_INFO,
403    LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
404    send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
405	      LocalEngineID, ExtraInfo, NetIf).
406
407send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, ExtraInfo, NetIf) ->
408    LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
409    send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
410	      LocalEngineID, ExtraInfo, NetIf).
411
412%% The agent normally does not care about the result,
413%% but since it can be usefull when debugging, add
414%% some info when we fail to send the trap(s).
415send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID,
416	  ExtraInfo, NetIf) ->
417    try
418	begin
419	    do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
420			 LocalEngineID, ExtraInfo, NetIf)
421	end
422    catch
423	C:E:S ->
424	    Info = [{args,  [TrapRec, NotifyName, ContextName,
425                             Recv, Vbs, LocalEngineID, ExtraInfo, NetIf]},
426		    {class, C},
427		    {err,   E},
428		    {stacktrace, S}],
429	    ?vlog("snmpa_trap:send_trap exception: "
430                  "~n   ~p", [Info]),
431	    {error, {failed_sending_trap, Info}}
432    end.
433
434do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
435	     LocalEngineID, ExtraInfo, NetIf) ->
436    VarbindList = make_varbind_list(Vbs),
437    Dests       = find_dests(NotifyName),
438    send_trap_pdus(Dests, ContextName, {TrapRec, VarbindList}, [], [], [],
439		   Recv, LocalEngineID, ExtraInfo, NetIf).
440
441send_discovery(TargetName, Record, ContextName, Vbs, NetIf) ->
442    ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO,
443    send_discovery(TargetName, Record, ContextName, Vbs, NetIf, ExtraInfo).
444send_discovery(TargetName, Record, ContextName, Vbs, NetIf, ExtraInfo) ->
445    case find_dest(TargetName) of
446	{ok, Dest} ->
447            Vbs2 = make_varbind_list(Vbs), % OTP-16207
448	    send_discovery_pdu(Dest, Record, ContextName, Vbs2, NetIf,
449			       ExtraInfo);
450	Error ->
451	    Error
452    end.
453
454
455get_values(VariablesWithType) ->
456    {Order, Varbinds} = extract_order(VariablesWithType, 1),
457    ?vtrace("get_values -> "
458	    "~n   Order:    ~p"
459	    "~n   Varbinds: ~p", [Order, Varbinds]),
460    case snmpa_agent:do_get(snmpa_acm:get_root_mib_view(), Varbinds, true) of
461	{noError, _, NewVarbinds} ->
462	    ?vtrace("get_values -> values retrieved"
463		    "~n   NewVarbinds: ~p", [NewVarbinds]),
464	    %% NewVarbinds is the result of:
465	    %% first a reverse, then a sort on the oid field and finally
466	    %% a reverse during the get-processing so we need to re-sort
467	    %% on the org_index field again before contract-order
468 	    NewVarbinds1 = lists:keysort(#varbind.org_index, NewVarbinds),
469 	    contract_order(Order, NewVarbinds1);
470	{ErrorStatus, ErrorIndex, _} ->
471	    user_err("snmpa_trap: get operation failed: ~w"
472		     "~n    at ~w"
473		     "~n    in ~w",
474		     [ErrorStatus, ErrorIndex, Varbinds]),
475	    throw(error)
476    end.
477
478make_varbind({Process, #varbind{oid = Oid} = VB}) ->
479    VB#varbind{oid = process_oid(Process, Oid)};
480make_varbind({Process, {VarOid, ASN1Type, Value}}) ->
481    case snmpa_agent:make_value_a_correct_value(Value, ASN1Type, undef) of
482	{value, Type, Val} ->
483	    #varbind{oid          = process_oid(Process, VarOid),
484                     variabletype = Type,
485                     value        = Val};
486	{error, Reason} ->
487	    user_err("snmpa_trap: Invalid value: ~w"
488		     "~n   Oid:  ~w"
489		     "~n   Val:  ~w"
490		     "~n   Type: ~w",
491		     [Reason, VarOid, Value, ASN1Type]),
492	    throw(error)
493    end.
494
495process_oid(truncate, Oid) ->
496    case lists:reverse(Oid) of
497        [0 | RevRestOid] ->
498            lists:reverse(RevRestOid);
499        _ ->
500            Oid
501    end;
502process_oid(_, Oid) ->
503    Oid.
504
505
506
507%% Order does two things:
508%% 1) Add an index to each element indicating where in the
509%%    list it was found.
510%% 2) Extract the 'process oid' information (and add it to the index => ID)
511%% We can add whatever we want to the second element since the first,
512%% the integer (No) is unique.
513
514order(Varbinds) ->
515    order(Varbinds, 1).
516
517order([{{Process, OidOrAlias}, Type, Value} | T], No) ->
518    VB = {OidOrAlias, Type, Value},
519    ID = {No, Process},
520    [{ID, VB} | order(T, No + 1)];
521order([{{Process, OidOrAlias}, Type} | T], No) ->
522    VB = {OidOrAlias, Type},
523    ID = {No, Process},
524    [{ID, VB} | order(T, No + 1)];
525order([H | T], No) ->
526    ID = {No, keep},
527    [{ID, H} | order(T, No + 1)];
528order([], _) ->
529    [].
530
531
532unorder(OVbs) ->
533    [{Process, VB} || {{_No, Process}, VB} <- OVbs].
534
535
536extract_order([{No, {VarOid, _Type}} | T], Index) ->
537    {Order, V} = extract_order(T, Index+1),
538    {[No | Order], [#varbind{oid = VarOid, org_index = Index} | V]};
539extract_order([], _) ->
540    {[], []}.
541
542contract_order([No | Order], [Varbind | T]) ->
543    [{No, Varbind} | contract_order(Order, T)];
544contract_order([], []) ->
545    [].
546
547
548split_variables([{No, {VarOid, Type, Val}} | T]) when is_list(VarOid) ->
549    {A, B} = split_variables(T),
550    {[{No, {VarOid, Type, Val}} | A], B};
551split_variables([{No, {VarOid, Type}} | T])
552  when is_list(VarOid) andalso is_record(Type, asn1_type) ->
553    {A, B} = split_variables(T),
554    {A, [{No, {VarOid, Type}} | B]};
555
556split_variables([{_No, {VarName, Value}} | _T]) ->
557    user_err("snmpa_trap: Undefined variable ~w (~w)", [VarName, Value]),
558    throw(error);
559split_variables([{_No, {VarName, RowIndex, Value}} | _T]) ->
560    user_err("snmpa_trap: Undefined variable ~w ~w (~w)",
561	     [VarName, RowIndex, Value]),
562    throw(error);
563
564split_variables([]) ->
565    {[], []}.
566
567
568%%-----------------------------------------------------------------
569%% Func: find_dests(NotifyName) ->
570%%          [{DestAddr, TargetName, TargetParams, NotifyType}]
571%% Types: NotifyType = string()
572%%        DestAddr = {TDomain, TAddr}
573%%        TargetName = string()
574%%        TargetParams = {MpModel, SecModel, SecName, SecLevel}
575%%        NotifyType = trap | {inform, Timeout, Retry}
576%% Returns: A list of all Destination addresses for this community.
577%% NOTE: This function is executed in the master agent's context
578%%-----------------------------------------------------------------
579find_dests("") ->
580    ?vtrace("find destinations", []),
581    snmp_notification_mib:get_targets();
582find_dests(NotifyName) ->
583    ?vtrace("find destinations for ~p", [NotifyName]),
584    case snmp_notification_mib:get_targets(NotifyName) of
585	[] ->
586	    ?vlog("No dests found for NotifyName: ~p", [NotifyName]),
587	    [];
588	Dests ->
589	    Dests
590    end.
591
592find_dest(TargetName) ->
593    AddrCols = [?snmpTargetAddrTDomain,
594		?snmpTargetAddrTAddress,
595		?snmpTargetAddrTimeout,
596		?snmpTargetAddrRetryCount,
597		?snmpTargetAddrParams,
598		?snmpTargetAddrRowStatus],
599    case snmp_target_mib:snmpTargetAddrTable(get, TargetName, AddrCols) of
600	[{value, TDomain},
601	 {value, TAddress},
602	 {value, Timeout},
603	 {value, RetryCount},
604	 {value, Params},
605	 {value, ?'RowStatus_active'}] ->
606	    ?vtrace("find_dest -> found snmpTargetAddrTable info:"
607		    "~n   TDomain:    ~p"
608		    "~n   TAddress:   ~p"
609		    "~n   Timeout:    ~p"
610		    "~n   RetryCount: ~p"
611		    "~n   Params:     ~p",
612		    [TDomain, TAddress, Timeout, RetryCount, Params]),
613	    ParmCols = [?snmpTargetParamsMPModel,
614			?snmpTargetParamsSecurityModel,
615			?snmpTargetParamsSecurityName,
616			?snmpTargetParamsSecurityLevel,
617			?snmpTargetParamsRowStatus],
618	    case snmp_target_mib:snmpTargetParamsTable(get, Params, ParmCols) of
619		[{value, ?MP_V3},
620		 {value, SecModel},
621		 {value, SecName},
622		 {value, SecLevel},
623		 {value, ?'RowStatus_active'}] ->
624		    ?vtrace("find_dest -> found snmpTargetParamsTable info:"
625			    "~n   SecModel: ~p"
626			    "~n   SecName:  ~p"
627			    "~n   SecLevel: ~p",
628			    [SecModel, SecName, SecLevel]),
629		    DestAddr     = {TDomain, TAddress},
630		    TargetParams = {SecModel, SecName, SecLevel},
631		    Val = {DestAddr, TargetName, TargetParams, Timeout, RetryCount},
632		    {ok, Val};
633		[{value, ?MP_V3},
634		 {value, _SecModel},
635		 {value, _SecName},
636		 {value, _SecLevel},
637		 {value, RowStatus}] ->
638		    {error, {invalid_RowStatus, RowStatus, snmpTargetParamsTable}};
639		[{value, MpModel},
640		 {value, _SecModel},
641		 {value, _SecName},
642		 {value, _SecLevel},
643		 {value, ?'RowStatus_active'}] ->
644		    {error, {invalid_MpModel, MpModel, snmpTargetParamsTable}};
645		[{value, _MpModel},
646		 {value, _SecModel},
647		 {value, _SecName},
648		 {value, _SecLevel},
649		 {value, RowStatus}] ->
650		    {error, {invalid_RowStatus, RowStatus, snmpTargetParamsTable}};
651		Bad ->
652		    ?vlog("find_dest -> "
653			  "could not find snmpTargetParamsTable info: "
654			  "~n   Bad: ~p", [Bad]),
655		    {error, {not_found, snmpTargetParamsTable}}
656	    end;
657
658	[{value, _TDomain},
659	 {value, _TAddress},
660	 {value, _Timeout},
661	 {value, _RetryCount},
662	 {value, _Params},
663	 {value, RowStatus}] ->
664	    {error, {invalid_RowStatus, RowStatus, snmpTargetAddrTable}};
665	_ ->
666	    {error, {not_found, snmpTargetAddrTable}}
667    end.
668
669send_discovery_pdu({Dest, TargetName, {SecModel, SecName, SecLevel},
670		    Timeout, Retry},
671		   Record, ContextName, Vbs, NetIf,
672		   ExtraInfo) ->
673    ?vdebug("send_discovery_pdu -> entry with "
674	    "~n   Destination address: ~p"
675	    "~n   Target name:         ~p"
676	    "~n   Sec model:           ~p"
677	    "~n   Sec name:            ~p"
678	    "~n   Sec level:           ~p"
679	    "~n   Timeout:             ~p"
680	    "~n   Retry:               ~p"
681	    "~n   Record:              ~p"
682	    "~n   ContextName:         ~p"
683	    "~n   ExtraInfo:           ~p",
684	    [Dest, TargetName, SecModel, SecName, SecLevel,
685	     Timeout, Retry, Record, ContextName, ExtraInfo]),
686    case get_mib_view(SecModel, SecName, SecLevel, ContextName) of
687	{ok, MibView} ->
688	    case check_all_varbinds(Record, Vbs, MibView) of
689		true ->
690		    SysUpTime = snmp_standard_mib:sys_up_time(),
691		    send_discovery_pdu(Record, Dest, Vbs,
692				       SecModel, SecName, SecLevel,
693				       TargetName, ContextName,
694				       Timeout, Retry,
695				       SysUpTime, NetIf, ExtraInfo);
696		false ->
697		    {error, {mibview_validation_failed, Vbs, MibView}}
698	    end;
699	{discarded, Reason} ->
700	    {error, {failed_get_mibview, Reason}}
701    end.
702
703send_discovery_pdu(Record, Dest, Vbs,
704		   SecModel, SecName, SecLevel, TargetName,
705		   ContextName, Timeout, Retry, SysUpTime, NetIf, ExtraInfo) ->
706    {_Oid, IVbs} = mk_v2_trap(Record, Vbs, SysUpTime), % v2 refers to SMIv2;
707    Sender = proc_lib:spawn_link(?MODULE, init_discovery_inform,
708				 [self(),
709				  Dest,
710				  SecModel, SecName, SecLevel, TargetName,
711				  ContextName,
712				  Timeout, Retry,
713				  IVbs, NetIf,
714				  get(verbosity),
715				  ExtraInfo]),
716    {ok, Sender, SecLevel}.
717
718init_discovery_inform(Parent,
719		      Dest,
720		      SecModel, SecName, SecLevel, TargetName,
721		      ContextName, Timeout, Retry, Vbs, NetIf, Verbosity) ->
722    ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO,
723    init_discovery_inform(Parent,
724			  Dest,
725			  SecModel, SecName, SecLevel, TargetName,
726			  ContextName, Timeout, Retry, Vbs, NetIf,
727			  Verbosity, ExtraInfo).
728init_discovery_inform(Parent,
729		      Dest,
730		      SecModel, SecName, SecLevel, TargetName,
731		      ContextName, Timeout, Retry, Vbs, NetIf,
732		      Verbosity, ExtraInfo) ->
733    put(verbosity, Verbosity),
734    put(sname, madis),
735    Pdu = make_discovery_pdu(Vbs),
736    ContextEngineId = snmp_framework_mib:get_engine_id(),
737    SecLevelFlag = mk_flag(SecLevel),
738    SecData      = {SecModel, SecName, SecLevelFlag, TargetName},
739    MsgData      = {SecData, ContextEngineId, ContextName},
740    Msg          = {send_discovery, Pdu, MsgData, Dest, self(), ExtraInfo},
741    ?MODULE:send_discovery_inform(Parent, Timeout*10, Retry, Msg, NetIf).
742
743%% note_timeout(Timeout, Retry)
744%%   when ((is_integer(Timeout) andalso (Timeout > 0)) andalso
745%% 	(is_integer(Retry) andalso (Retry > 0)))
746%%     note_timeout(Timeout*10, Retry, 0);
747%% note_timeout(Timeout, Retry)
748%%   when (is_integer(Timeout) andalso (Timeout > 0)) ->
749%%     Timeout*10.
750
751%% note_timeout(_Timeout, -1, NoteTimeout) ->
752%%     NoteTimeout;
753%% note_timeout(Timeout, Retry, NoteTimeout) when ->
754%%     note_timeout(Timeout*2, Retry-1, NoteTimeout+Timeout).
755
756send_discovery_inform(Parent, _Timeout, -1, _Msg, _NetIf) ->
757    Parent ! {discovery_response, {error, timeout}};
758send_discovery_inform(Parent, Timeout, Retry, Msg, NetIf) ->
759    NetIf ! Msg,
760    receive
761	{snmp_discovery_response_received, Pdu, undefined} ->
762	    ?vtrace("received stage 2 discovery response: "
763		    "~n   Pdu: ~p", [Pdu]),
764	    Parent ! {discovery_response, {ok, Pdu}};
765	{snmp_discovery_response_received, Pdu, ManagerEngineId} ->
766	    ?vtrace("received stage 1 discovery response: "
767		    "~n   Pdu:             ~p"
768		    "~n   ManagerEngineId: ~p", [Pdu, ManagerEngineId]),
769	    Parent ! {discovery_response, {ok, Pdu, ManagerEngineId}}
770    after
771	Timeout ->
772	    ?MODULE:send_discovery_inform(Parent,
773					  Timeout*2, Retry-1, Msg, NetIf)
774    end.
775
776
777%%-----------------------------------------------------------------
778%% NOTE: This function is executed in the master agent's context
779%% For each target, check if it has access to the objects in the
780%% notification, determine which message version (v1, v2c or v3)
781%% should be used for the target, and determine the message
782%% specific parameters to be used.
783%%-----------------------------------------------------------------
784send_trap_pdus([{DestAddr, TargetName,
785		 {MpModel, SecModel, SecName, SecLevel}, Type} | T],
786	       ContextName,
787	       {TrapRec, Vbs}, V1Res, V2Res, V3Res, Recv,
788	       LocalEngineID, ExtraInfo, NetIf) ->
789    ?vdebug("send trap pdus: "
790	    "~n   Destination address: ~p"
791	    "~n   Target name:         ~p"
792	    "~n   MP model:            ~p"
793	    "~n   Type:                ~p"
794	    "~n   V1Res:               ~p"
795	    "~n   V2Res:               ~p"
796	    "~n   V3Res:               ~p",
797	    [DestAddr, TargetName, MpModel, Type, V1Res, V2Res, V3Res]),
798    case get_mib_view(SecModel, SecName, SecLevel, ContextName) of
799	{ok, MibView} ->
800	    case check_all_varbinds(TrapRec, Vbs, MibView) of
801		true when MpModel =:= ?MP_V1 ->
802		    ?vtrace("send_trap_pdus -> v1 mp model",[]),
803		    ContextEngineId = LocalEngineID,
804		    case snmp_community_mib:vacm2community({SecName,
805							    ContextEngineId,
806							    ContextName},
807							   DestAddr) of
808			{ok, Community} ->
809			    ?vdebug("community found  for v1 dest: ~p",
810				    [element(2, DestAddr)]),
811			    send_trap_pdus(T, ContextName, {TrapRec, Vbs},
812					   [{DestAddr, Community} | V1Res],
813					   V2Res, V3Res, Recv,
814					   LocalEngineID, ExtraInfo, NetIf);
815			undefined ->
816			    ?vdebug("No community found for v1 dest: ~p",
817				    [element(2, DestAddr)]),
818			    send_trap_pdus(T, ContextName, {TrapRec, Vbs},
819					   V1Res, V2Res, V3Res, Recv,
820					   LocalEngineID, ExtraInfo, NetIf)
821		    end;
822		true when MpModel =:= ?MP_V2C ->
823		    ?vtrace("send_trap_pdus -> v2c mp model",[]),
824		    ContextEngineId = LocalEngineID,
825		    case snmp_community_mib:vacm2community({SecName,
826							    ContextEngineId,
827							    ContextName},
828							   DestAddr) of
829			{ok, Community} ->
830			    ?vdebug("community found for v2c dest: ~p",
831				    [element(2, DestAddr)]),
832			    send_trap_pdus(T, ContextName, {TrapRec, Vbs},
833					   V1Res,
834					   [{DestAddr, Community, Type}|V2Res],
835					   V3Res, Recv,
836					   LocalEngineID, ExtraInfo, NetIf);
837			undefined ->
838			    ?vdebug("No community found for v2c dest: ~p",
839				    [element(2, DestAddr)]),
840			    send_trap_pdus(T, ContextName, {TrapRec, Vbs},
841					   V1Res, V2Res, V3Res, Recv,
842					   LocalEngineID, ExtraInfo, NetIf)
843		    end;
844		true when MpModel =:= ?MP_V3 ->
845		    ?vtrace("send_trap_pdus -> v3 mp model",[]),
846		    SecLevelF = mk_flag(SecLevel),
847		    MsgData = {SecModel, SecName, SecLevelF, TargetName},
848		    send_trap_pdus(T, ContextName, {TrapRec, Vbs},
849				   V1Res, V2Res,
850				   [{DestAddr, MsgData, Type} | V3Res],
851				   Recv, LocalEngineID, ExtraInfo, NetIf);
852		true ->
853		    ?vlog("bad MpModel ~p for dest ~p",
854			  [MpModel, element(2, DestAddr)]),
855		    send_trap_pdus(T, ContextName, {TrapRec, Vbs},
856				   V1Res, V2Res, V3Res, Recv,
857				   LocalEngineID, ExtraInfo, NetIf);
858		_ ->
859		    ?vlog("no access for dest: "
860			  "~n   ~p in target ~p",
861			  [element(2, DestAddr), TargetName]),
862		    send_trap_pdus(T, ContextName, {TrapRec, Vbs},
863				   V1Res, V2Res, V3Res, Recv,
864				   LocalEngineID, ExtraInfo, NetIf)
865	    end;
866	{discarded, Reason} ->
867	    ?vlog("mib view error ~p for"
868		  "~n   dest:    ~p"
869		  "~n   SecName: ~w",
870		  [Reason, element(2, DestAddr), SecName]),
871	    send_trap_pdus(T, ContextName, {TrapRec, Vbs},
872			   V1Res, V2Res, V3Res, Recv,
873			   LocalEngineID, ExtraInfo, NetIf)
874    end;
875send_trap_pdus([], ContextName, {TrapRec, Vbs},
876	       V1Res, V2Res, V3Res, Recv,
877	       LocalEngineID, ExtraInfo,
878	       NetIf) ->
879    SysUpTime = snmp_standard_mib:sys_up_time(),
880    ?vdebug("send trap pdus with sysUpTime ~p", [SysUpTime]),
881    InformRecvs   = get_inform_recvs(V2Res ++ V3Res),
882    InformTargets = [Addr || {Addr, _, _, _} <- InformRecvs],
883    deliver_recv(Recv, snmp_targets, InformTargets),
884    send_v1_trap(TrapRec, V1Res, Vbs, ExtraInfo, NetIf, SysUpTime),
885    send_v2_trap(TrapRec, V2Res, Vbs, Recv, ExtraInfo, NetIf, SysUpTime),
886    send_v3_trap(TrapRec, V3Res, Vbs, Recv, LocalEngineID, ExtraInfo, NetIf,
887		 SysUpTime, ContextName).
888
889send_v1_trap(_TrapRec, [], _Vbs, _ExtraInfo, _NetIf, _SysUpTime) ->
890    ok;
891send_v1_trap(
892  #trap{enterpriseoid = Enter, specificcode = Spec},
893  V1Res, Vbs, ExtraInfo, NetIf, SysUpTime) ->
894    ?vdebug("prepare to send v1 trap "
895	    "~n   '~p'"
896	    "~n   with"
897	    "~n   ~p"
898	    "~n   to"
899	    "~n   ~p", [Enter, Spec, V1Res]),
900    do_send_v1_trap(Enter, Spec, V1Res, Vbs, ExtraInfo, NetIf, SysUpTime);
901send_v1_trap(
902  #notification{oid = Oid},
903  V1Res, Vbs, ExtraInfo, NetIf, SysUpTime) ->
904    %% Use alg. in rfc2089 to map a v2 trap to a v1 trap
905    % delete Counter64 objects from vbs
906    ?vdebug("prepare to send v1 trap '~p'",[Oid]),
907    NVbs = [Vb || Vb <- Vbs, Vb#varbind.variabletype =/= 'Counter64'],
908    {Enter,Spec} =
909	case Oid of
910	    [1,3,6,1,6,3,1,1,5,Specific] ->
911		{?snmp,Specific - 1};
912	    _ ->
913		case lists:reverse(Oid) of
914		    [Last, 0 | First] ->
915			{lists:reverse(First),Last};
916		    [Last | First] ->
917			{lists:reverse(First),Last}
918		end
919	end,
920    do_send_v1_trap(Enter, Spec, V1Res, NVbs, ExtraInfo, NetIf, SysUpTime).
921
922do_send_v1_trap(Enter, Spec, V1Res, NVbs, ExtraInfo, NetIf, SysUpTime) ->
923    {value, Transports} = snmp_framework_mib:intAgentTransports(get),
924    {_Domain, {AgentIp, _AgentPort}} =
925	case lists:keyfind(snmpUDPDomain, 1, Transports) of
926	    false ->
927		case lists:keyfind(transportDomainUdpIpv4, 1, Transports) of
928		    false ->
929			?vtrace(
930			   "snmpa_trap: cannot send v1 trap "
931			   "without IPv4 domain: ~p",
932			   [Transports]),
933			user_err(
934			   "snmpa_trap: cannot send v1 trap "
935			   "without IPv4 domain: ~p",
936			   [Transports]);
937		    DomainAddr ->
938			DomainAddr
939		end;
940	    DomainAddr ->
941		DomainAddr
942	end,
943    TrapPdu = make_v1_trap_pdu(Enter, Spec, NVbs, SysUpTime, AgentIp),
944    AddrCommunities = mk_addr_communities(V1Res),
945    lists:foreach(
946      fun ({Community, Addrs}) ->
947	      ?vtrace("send v1 trap to ~p",[Addrs]),
948	      NetIf ! {send_pdu, 'version-1', TrapPdu,
949		       {community, Community}, Addrs, ExtraInfo}
950      end, AddrCommunities).
951
952send_v2_trap(_TrapRec, [], _Vbs, _Recv, _ExtraInfo, _NetIf, _SysUpTime) ->
953    ok;
954send_v2_trap(TrapRec, V2Res, Vbs, Recv, ExtraInfo, NetIf, SysUpTime) ->
955    ?vdebug("prepare to send v2 trap",[]),
956    {_Oid, IVbs} = mk_v2_trap(TrapRec, Vbs, SysUpTime),
957    TrapRecvs    = get_trap_recvs(V2Res),
958    InformRecvs  = get_inform_recvs(V2Res),
959    do_send_v2_trap(TrapRecvs, IVbs, ExtraInfo, NetIf),
960    do_send_v2_inform(InformRecvs, IVbs, Recv, ExtraInfo, NetIf).
961
962send_v3_trap(_TrapRec, [], _Vbs, _Recv, _LocalEngineID, _ExtraInfo,
963	     _NetIf, _SysUpTime, _ContextName) ->
964    ok;
965send_v3_trap(TrapRec, V3Res, Vbs, Recv, LocalEngineID, ExtraInfo,
966	     NetIf, SysUpTime, ContextName) ->
967    ?vdebug("prepare to send v3 trap",[]),
968    {_Oid, IVbs} = mk_v2_trap(TrapRec, Vbs, SysUpTime), % v2 refers to SMIv2;
969    TrapRecvs = get_trap_recvs(V3Res),                  % same SMI for v3
970    InformRecvs = get_inform_recvs(V3Res),
971    do_send_v3_trap(TrapRecvs, ContextName, IVbs, ExtraInfo, NetIf),
972    do_send_v3_inform(InformRecvs, ContextName, IVbs, Recv,
973		      LocalEngineID, ExtraInfo, NetIf).
974
975
976mk_v2_trap(#notification{oid = Oid}, Vbs, SysUpTime) ->
977    ?vtrace("make v2 notification '~p'",[Oid]),
978    mk_v2_notif(Oid, Vbs, SysUpTime);
979mk_v2_trap(#trap{enterpriseoid = Enter, specificcode = Spec},
980	   Vbs, SysUpTime) ->
981    %% Use alg. in rfc1908 to map a v1 trap to a v2 trap
982    ?vtrace("make v2 trap for '~p' with ~p",[Enter,Spec]),
983    {Oid,Enterp} =
984	case Enter of
985	    ?snmp ->
986		{?snmpTraps ++ [Spec + 1],sys_object_id()};
987	    _ ->
988		{Enter ++ [0, Spec],Enter}
989	end,
990    ExtraVb = #varbind{oid = ?snmpTrapEnterprise_instance,
991		       variabletype = 'OBJECT IDENTIFIER',
992		       value = Enterp},
993    mk_v2_notif(Oid, Vbs ++ [ExtraVb], SysUpTime).
994
995mk_v2_notif(Oid, Vbs, SysUpTime) ->
996    IVbs = [#varbind{oid = ?sysUpTime_instance,
997		     variabletype = 'TimeTicks',
998		     value = SysUpTime},
999	    #varbind{oid = ?snmpTrapOID_instance,
1000		     variabletype = 'OBJECT IDENTIFIER',
1001		     value = Oid} | Vbs],
1002    {Oid, IVbs}.
1003
1004get_trap_recvs(TrapRecvs) ->
1005    [{Addr, MsgData} || {Addr, MsgData, trap} <- TrapRecvs].
1006
1007get_inform_recvs(InformRecvs) ->
1008    [{Addr, MsgData, Timeout, Retry} ||
1009	{Addr, MsgData, {inform, Timeout, Retry}} <- InformRecvs].
1010
1011do_send_v2_trap([], _Vbs, _ExtraInfo, _NetIf) ->
1012    ok;
1013do_send_v2_trap(Recvs, Vbs, ExtraInfo, NetIf) ->
1014    TrapPdu = make_v2_notif_pdu(Vbs, 'snmpv2-trap'),
1015    AddrCommunities = mk_addr_communities(Recvs),
1016    lists:foreach(fun({Community, Addrs}) ->
1017			  ?vtrace("send v2 trap to ~p",[Addrs]),
1018			  NetIf ! {send_pdu, 'version-2', TrapPdu,
1019				   {community, Community}, Addrs, ExtraInfo}
1020		  end, AddrCommunities),
1021    ok.
1022
1023do_send_v2_inform([], _Vbs, _Recv, _ExtraInfo, _NetIf) ->
1024    ok;
1025do_send_v2_inform(Recvs, Vbs, Recv, ExtraInfo, NetIf) ->
1026    lists:foreach(
1027      fun({Addr, Community, Timeout, Retry}) ->
1028	      ?vtrace("~n   start inform sender to send v2 inform to ~p",
1029		      [Addr]),
1030	      proc_lib:spawn_link(?MODULE, init_v2_inform,
1031				  [Addr, Timeout, Retry, Vbs,
1032				   Recv, ExtraInfo, NetIf, Community,
1033				   get(verbosity), get(sname)])
1034      end,
1035      Recvs).
1036
1037do_send_v3_trap([], _ContextName, _Vbs, _ExtraInfo, _NetIf) ->
1038    ok;
1039do_send_v3_trap(Recvs, ContextName, Vbs, ExtraInfo, NetIf) ->
1040    TrapPdu = make_v2_notif_pdu(Vbs, 'snmpv2-trap'), % Yes, v2
1041    ContextEngineId = snmp_framework_mib:get_engine_id(),
1042    lists:foreach(fun(Recv) ->
1043			  ?vtrace("~n   send v3 notif to ~p",[Recv]),
1044			  NetIf ! {send_pdu, 'version-3', TrapPdu,
1045				   {v3, ContextEngineId, ContextName},
1046				   [Recv], ExtraInfo}
1047		  end, Recvs),
1048    ok.
1049
1050do_send_v3_inform([], _ContextName, _Vbs, _Recv,
1051		  _LocalEngineID, _ExtraInfo, _NetIf) ->
1052    ok;
1053do_send_v3_inform(Recvs, ContextName, Vbs, Recv,
1054		  LocalEngineID, ExtraInfo, NetIf) ->
1055    lists:foreach(
1056      fun({Addr, MsgData, Timeout, Retry}) ->
1057	      ?vtrace("~n   start inform sender to send v3 inform to ~p",
1058		      [Addr]),
1059	      proc_lib:spawn_link(?MODULE, init_v3_inform,
1060				  [{Addr, MsgData}, Timeout, Retry, Vbs,
1061				   Recv, LocalEngineID, ExtraInfo,
1062				   NetIf, ContextName,
1063				   get(verbosity), get(sname)])
1064      end,
1065      Recvs).
1066
1067%% New process
1068init_v2_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, Community, V, S) ->
1069    ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO,
1070    init_v2_inform(Addr, Timeout, Retry, Vbs, Recv, ExtraInfo, NetIf,
1071		   Community, V, S).
1072
1073init_v2_inform(Addr, Timeout, Retry, Vbs, Recv, ExtraInfo, NetIf,
1074	       Community, V, S) ->
1075    %% Make a new Inform for each recipient; they need unique
1076    %% request-ids!
1077    put(verbosity,V),
1078    put(sname,inform_sender_short_name(S)),
1079    ?vdebug("~n   starting with timeout = ~p and retry = ~p",
1080	    [Timeout,Retry]),
1081    InformPdu = make_v2_notif_pdu(Vbs, 'inform-request'),
1082    Msg = {send_pdu_req, 'version-2', InformPdu, {community, Community},
1083	   [Addr], self(), ExtraInfo},
1084    ?MODULE:send_inform(Addr, Timeout*10, Retry, Msg, Recv, NetIf).
1085
1086
1087%% New process
1088init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, ContextName, V, S) ->
1089    ExtraInfo     = ?DEFAULT_NOTIF_EXTRA_INFO,
1090    LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
1091    init_v3_inform(Addr, Timeout, Retry, Vbs, Recv,
1092		   LocalEngineID, ExtraInfo,
1093		   NetIf, ContextName, V, S).
1094
1095init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, LocalEngineID, NetIf,
1096	       ContextName, V, S) ->
1097    ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO,
1098    init_v3_inform(Addr, Timeout, Retry, Vbs, Recv,
1099		   LocalEngineID, ExtraInfo,
1100		   NetIf, ContextName, V, S).
1101
1102init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, LocalEngineID, ExtraInfo,
1103	       NetIf, ContextName, V, S) ->
1104    %% Make a new Inform for each recipient; they need unique
1105    %% request-ids!
1106    put(verbosity,V),
1107    put(sname,inform_sender_short_name(S)),
1108    ?vdebug("~n   starting with timeout = ~p and retry = ~p",
1109	    [Timeout,Retry]),
1110    InformPdu = make_v2_notif_pdu(Vbs, 'inform-request'), % Yes, v2
1111    ContextEngineId = LocalEngineID,
1112    Msg = {send_pdu_req, 'version-3', InformPdu,
1113	   {v3, ContextEngineId, ContextName}, [Addr], self(), ExtraInfo},
1114    ?MODULE:send_inform(Addr, Timeout*10, Retry, Msg, Recv, NetIf).
1115
1116send_inform(Addr, _Timeout, -1, _Msg,  Recv, _NetIf) ->
1117    ?vinfo("~n   Delivery of send-pdu-request to net-if failed: reply timeout",
1118	   []),
1119    deliver_recv(Recv, snmp_notification, {no_response, Addr});
1120send_inform(Addr, Timeout, Retry, Msg, Recv, NetIf) ->
1121    ?vtrace("deliver send-pdu-request to net-if when"
1122	    "~n   Timeout: ~p"
1123	    "~n   Retry:   ~p",[Timeout, Retry]),
1124    NetIf ! Msg,
1125    receive
1126	{snmp_response_received, _Vsn, _Pdu, _From} ->
1127	    ?vtrace("received response for ~p (when Retry = ~p)",
1128		    [Recv, Retry]),
1129	    deliver_recv(Recv, snmp_notification, {got_response, Addr})
1130    after
1131	Timeout ->
1132	    ?MODULE:send_inform(Addr, Timeout*2, Retry-1, Msg, Recv, NetIf)
1133    end.
1134
1135% A nasty bit of verbosity setup...
1136inform_sender_short_name(ma)   -> mais;
1137inform_sender_short_name(maw)  -> mais;
1138inform_sender_short_name(mats) -> mais;
1139inform_sender_short_name(_)    -> sais.
1140
1141deliver_recv(no_receiver, _MsgId, _Result) ->
1142    ?vtrace("deliver_recv -> no receiver", []),
1143    ok;
1144deliver_recv(#snmpa_notification_delivery_info{tag   = Tag,
1145					       mod   = Mod,
1146					       extra = Extra},
1147	     snmp_targets, TAddrs) when is_list(TAddrs) ->
1148    ?vtrace("deliver_recv(snmp_targets) -> entry with"
1149	"~n   Tag:    ~p"
1150	"~n   Mod:    ~p"
1151	"~n   Extra:  ~p"
1152	"~n   TAddrs: ~p"
1153	"", [Tag, Mod, Extra, TAddrs]),
1154    Addrs = transform_taddrs(TAddrs),
1155    (catch Mod:delivery_targets(Tag, Addrs, Extra));
1156deliver_recv(#snmpa_notification_delivery_info{tag   = Tag,
1157					       mod   = Mod,
1158					       extra = Extra},
1159	     snmp_notification, {DeliveryResult, TAddr}) ->
1160    ?vtrace("deliver_recv -> entry with"
1161	"~n   Tag:            ~p"
1162	"~n   Mod:            ~p"
1163	"~n   Extra:          ~p"
1164	"~n   DeliveryResult: ~p"
1165	"~n   TAddr:          ~p"
1166	"", [Tag, Mod, Extra, DeliveryResult, TAddr]),
1167    [Addr] = transform_taddrs([TAddr]),
1168    (catch Mod:delivery_info(Tag, Addr, DeliveryResult, Extra));
1169deliver_recv({Tag, Receiver}, MsgId, Result) ->
1170    ?vtrace("deliver_recv -> entry with"
1171	"~n   Tag:      ~p"
1172	"~n   Receiver: ~p"
1173	"~n   MsgId:    ~p"
1174	"~n   Result:   ~p"
1175	"", [Tag, Receiver, MsgId, Result]),
1176    Msg = {MsgId, Tag, Result},
1177    case Receiver of
1178	Pid when is_pid(Pid) ->
1179	    Pid ! Msg;
1180	Name when is_atom(Name) ->
1181	    catch Name ! Msg;
1182	{M, F, A} ->
1183	    catch M:F([Msg | A]);
1184	Else ->
1185	    ?vinfo("~n   Cannot deliver acknowledgment: bad receiver = '~p'",
1186		   [Else]),
1187	    user_err("snmpa: bad receiver, ~w\n", [Else])
1188    end;
1189deliver_recv(Else, _MsgId, _Result) ->
1190    ?vinfo("~n   Cannot deliver acknowledgment: bad receiver = '~p'",
1191	   [Else]),
1192    user_err("snmpa: bad receiver, ~w\n", [Else]).
1193
1194transform_taddrs(TAddrs) ->
1195    UseTDomain =
1196	case snmp_framework_mib:intAgentTransportDomain(get) of
1197	    {value,snmpUDPDomain} ->
1198		false;
1199	    {value,_} ->
1200		true;
1201	    genErr ->
1202		false
1203	end,
1204    DomAddrs = [transform_taddr(TAddr) || TAddr <- TAddrs],
1205    case UseTDomain of
1206	true ->
1207	    DomAddrs;
1208	false ->
1209	    [Addr || {_Domain, Addr} <- DomAddrs]
1210    end.
1211
1212%% v2
1213transform_taddr({?snmpUDPDomain, Addr}) ->
1214    transform_taddr(transportDomainUdpIpv4, Addr);
1215transform_taddr({?transportDomainUdpIpv4, Addr}) ->
1216    transform_taddr(transportDomainUdpIpv4, Addr);
1217transform_taddr({?transportDomainUdpIpv6, Addr}) ->
1218    transform_taddr(transportDomainUdpIpv6, Addr);
1219%% v3
1220transform_taddr({{?snmpUDPDomain, Addr}, _MsgData}) ->
1221    transform_taddr(transportDomainUdpIpv4, Addr);
1222transform_taddr({{?transportDomainUdpIpv4, Addr}, _MsgData}) ->
1223    transform_taddr(transportDomainUdpIpv4, Addr);
1224transform_taddr({{?transportDomainUdpIpv6, Addr}, _MsgData}) ->
1225    transform_taddr(transportDomainUdpIpv6, Addr).
1226
1227transform_taddr(
1228  transportDomainUdpIpv4 = Domain,
1229  [A1,A2,A3,A4,P1,P2]) ->
1230    Ip = {A1, A2, A3, A4},
1231    Port = P1 bsl 8 + P2,
1232    {Domain, {Ip, Port}};
1233transform_taddr(
1234  transportDomainUdpIpv6 = Domain,
1235  [A1, A2, A3, A4, A5, A6, A7, A8, P1, P2]) ->
1236    Ip = {A1, A2, A3, A4, A5, A6, A7, A8},
1237    Port = P1 bsl 8 + P2,
1238    {Domain, {Ip, Port}};
1239transform_taddr(
1240  transportDomainUdpIpv6 = Domain,
1241  [A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16,
1242   P1, P2]) ->
1243    Ip =
1244	{(A1 bsl 8) bor A2, (A3 bsl 8) bor A4,
1245	 (A5 bsl 8) bor A6, (A7 bsl 8) bor A8,
1246	 (A9 bsl 8) bor A10, (A11 bsl 8) bor A12,
1247	 (A13 bsl 8) bor A14, (A15 bsl 8) bor A16},
1248    Port = P1 bsl 8 + P2,
1249    {Domain, {Ip, Port}}.
1250
1251%% transform_taddr({?snmpUDPDomain, [A1, A2, A3, A4, P1, P2]}) -> % v2
1252%%     Addr = {A1, A2, A3, A4},
1253%%     Port = P1 bsl 8 + P2,
1254%%     {Addr, Port};
1255%% transform_taddr({?transportDomainUdpIpv4, [A1, A2, A3, A4, P1, P2]}) -> % v2
1256%%     Addr = {A1, A2, A3, A4},
1257%%     Port = P1 bsl 8 + P2,
1258%%     {Addr, Port};
1259%% transform_taddr({?transportDomainUdpIpv6,
1260%% 		 [A1, A2, A3, A4, A5, A6, A7, A8, P1, P2]}) -> % v2
1261%%     Addr = {A1, A2, A3, A4, A5, A6, A7, A8},
1262%%     Port = P1 bsl 8 + P2,
1263%%     {Addr, Port};
1264%% transform_taddr({{?snmpUDPDomain, [A1, A2, A3, A4, P1, P2]}, _MsgData}) -> % v3
1265%%     Addr = {A1, A2, A3, A4},
1266%%     Port = P1 bsl 8 + P2,
1267%%     {Addr, Port};
1268%% transform_taddr({{?transportDomainUdpIpv4, [A1, A2, A3, A4, P1, P2]}, _MsgData}) -> % v3
1269%%     Addr = {A1, A2, A3, A4},
1270%%     Port = P1 bsl 8 + P2,
1271%%     {Addr, Port};
1272%% transform_taddr({{?transportDomainUdpIpv6,
1273%% 		  [A1, A2, A3, A4, A5, A6, A7, A8, P1, P2]}, _MsgData}) -> % v3
1274%%     Addr = {A1, A2, A3, A4, A5, A6, A7, A8},
1275%%     Port = P1 bsl 8 + P2,
1276%%     {Addr, Port}.
1277
1278
1279
1280check_all_varbinds(#notification{oid = Oid}, Vbs, MibView) ->
1281    case snmpa_acm:validate_mib_view(Oid, MibView) of
1282	true  -> check_all_varbinds(Vbs, MibView);
1283	false -> false
1284    end;
1285check_all_varbinds(#trap{enterpriseoid = Enter, specificcode = Spec},
1286		   Vbs, MibView) ->
1287    %% Use alg. in rfc1908 to map a v1 trap to a v2 trap
1288    Oid = case Enter of
1289	      ?snmp -> ?snmpTraps ++ [Spec + 1];
1290	      _ -> Enter ++ [0, Spec]
1291	  end,
1292    case snmpa_acm:validate_mib_view(Oid, MibView) of
1293	true  -> check_all_varbinds(Vbs, MibView);
1294	false -> false
1295    end.
1296
1297check_all_varbinds([#varbind{oid = Oid} | Vbs], MibView) ->
1298    case snmpa_acm:validate_mib_view(Oid, MibView) of
1299	true -> check_all_varbinds(Vbs, MibView);
1300	false -> false
1301    end;
1302check_all_varbinds([], _MibView) ->
1303    true.
1304
1305
1306%%--------------------------------------------------
1307%% Functions to access the local mib.
1308%%--------------------------------------------------
1309sys_object_id() ->
1310    case snmpa_agent:do_get(snmpa_acm:get_root_mib_view(),
1311			    [#varbind{oid = ?sysObjectID_instance}],
1312			    true) of
1313	{noError, _, [#varbind{value = Value}]} ->
1314	    Value;
1315	X ->
1316	    user_err("sysObjectID bad return value ~w", [X])
1317    end.
1318
1319%% Collect all ADDRs for each community together.
1320%% In: [{Addr, Community}]
1321%% Out: [{Community, [Addr]}]
1322mk_addr_communities(Recvs) ->
1323    [{Addr, Comm} | T] = lists:keysort(2, Recvs),
1324    mic(T, Comm, [Addr], []).
1325
1326mic([{Addr, Comm} | T], CurComm, AddrList, Res) when Comm =:= CurComm ->
1327    mic(T, CurComm, [Addr | AddrList], Res);
1328mic([{Addr, Comm} | T], CurComm, AddrList, Res) ->
1329    mic(T, Comm, [Addr], [{CurComm, AddrList} | Res]);
1330mic([], CurComm, AddrList, Res) ->
1331    [{CurComm, AddrList} | Res].
1332
1333
1334%%-----------------------------------------------------------------
1335%% Convert the SecurityLevel into a flag value used by snmpa_mpd
1336%%-----------------------------------------------------------------
1337mk_flag(?'SnmpSecurityLevel_noAuthNoPriv') -> 0;
1338mk_flag(?'SnmpSecurityLevel_authNoPriv') -> 1;
1339mk_flag(?'SnmpSecurityLevel_authPriv') -> 3.
1340
1341
1342%%--------------------------------------------------
1343%% Mib view wrapper
1344%%--------------------------------------------------
1345get_mib_view(SecModel, SecName, SecLevel, ContextName) ->
1346    snmpa_vacm:get_mib_view(notify,
1347			    SecModel, SecName, SecLevel, ContextName).
1348
1349
1350user_err(F, A) ->
1351    snmpa_error:user_err(F, A).
1352