1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1997-2020. 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_mpd).
21
22-export([init/1, reset/0, inc/1, counters/0,
23	 discarded_pdu/1,
24	 process_packet/5, process_packet/6, process_packet/7,
25	 generate_response_msg/5, generate_response_msg/6,
26	 generate_msg/5, generate_msg/6,
27	 generate_discovery_msg/4,
28	 process_taddrs/1,
29	 generate_req_id/0]).
30
31-define(SNMP_USE_V3, true).
32-include("snmp_types.hrl").
33-include("SNMP-MPD-MIB.hrl").
34-include("SNMPv2-TM.hrl").
35-include("SNMP-FRAMEWORK-MIB.hrl").
36-include("TRANSPORT-ADDRESS-MIB.hrl").
37
38-define(VMODULE,"MPD").
39-include("snmp_verbosity.hrl").
40-include("snmpa_internal.hrl").
41
42-define(empty_msg_size, 24).
43
44-record(state, {v1 = false, v2c = false, v3 = false}).
45-record(note, {sec_engine_id,
46	       sec_model,
47	       sec_name,
48	       sec_level,
49	       ctx_engine_id,
50	       ctx_name,
51	       disco = false,
52	       req_id}).
53
54
55%%%-----------------------------------------------------------------
56%%% This module implemets the Message Processing and Dispatch part of
57%%% the multi-lingual SNMP agent.
58%%%
59%%% The MPD is responsible for:
60%%%   *) call the security module (auth/priv).
61%%%   *) decoding the message into a PDU.
62%%%   *) decide a suitable Access Control Model, and provide it with
63%%%      the data it needs.
64%%%   *) maintaining SNMP counters.
65%%%
66%%% In order to take care of the different versions of counters, it
67%%% implements and maintains the union of all SNMP counters (i.e. from
68%%% rfc1213 and from rfc1907).  It is up to the administrator of the
69%%% agent to load the correct MIB.  Note that this module implements
70%%% the counters only, it does not provide instrumentation functions
71%%% for the counters.
72%%%
73%%% With the terms defined in rfc2271, this module implememts part
74%%% of the Dispatcher and the Message Processing functionality.
75%%%-----------------------------------------------------------------
76init(Vsns) ->
77    ?vlog("init -> entry with"
78	"~n   Vsns: ~p", [Vsns]),
79    ?SNMP_RAND_SEED(),
80    ets:insert(snmp_agent_table, {msg_id, rand:uniform(2147483647)}),
81    ets:insert(snmp_agent_table, {req_id, rand:uniform(2147483647)}),
82    init_counters(),
83    init_versions(Vsns, #state{}).
84
85
86reset() ->
87    reset_counters(),
88    ok.
89
90
91%%-----------------------------------------------------------------
92%% Purpose: We must calculate the length of a
93%%          message with an empty Pdu, and zero-length community
94%%          string.  This length is used to calculate the max
95%%          pdu size allowed for each request. This size is
96%%          dependent on two dynamic fields, the community string
97%%          and the pdu (varbinds actually). It is calculated
98%%          as EmptySize + length(CommunityString) + 4.
99%%          We assume that the length of the CommunityString is
100%%          less than 128 (thus requiring just one octet for the
101%%          length field (the same as the zero-length community
102%%          string)). 4 comes from the fact that the maximum pdu
103%%          size needs 31 bits which needs 5 * 7 bits to be
104%%          expressed. One 7bit octet is already present in the
105%%          empty msg, leaving 4 more 7bit octets.
106%% Actually, this function is not used, we use a constant instead.
107%%-----------------------------------------------------------------
108%% Ret: 24
109%empty_msg() ->
110%    M = #message{version = 'version-1', community = "", data =
111%		 #pdu{type = 'get-response', request_id = 1,
112%		      error_status = noError, error_index = 0, varbinds = []}},
113%    length(snmp_pdus:enc_message(M)) + 4.
114
115%%-----------------------------------------------------------------
116%% Func: process_packet(Packet, Domain, Address, State, Log) ->
117%%       {ok, SnmpVsn, Pdu, PduMS, ACMData} | {discarded, Reason}
118%% Types: Packet = binary()
119%%        Domain = snmpUDPDomain | transportDomain()
120%%        Address = {Ip, Udp} (*but* depends on Domain)
121%%        State = #state
122%% Purpose: This is the main Message Dispatching function. (see
123%%          section 4.2.1 in rfc2272)
124%%-----------------------------------------------------------------
125process_packet(Packet, From, State, NoteStore, Log) ->
126    LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
127    process_packet(Packet, From, LocalEngineID, State, NoteStore, Log).
128
129process_packet(
130  Packet, Domain, Address, LocalEngineID, State, NoteStore, Log) ->
131    From = {Domain, Address},
132    process_packet(Packet, From, LocalEngineID, State, NoteStore, Log).
133
134process_packet(Packet, Domain, Address, State, NoteStore, Log)
135  when is_atom(Domain) ->
136    LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
137    From = {Domain, Address},
138    process_packet(Packet, From, LocalEngineID, State, NoteStore, Log);
139process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) ->
140    inc(snmpInPkts),
141    case catch snmp_pdus:dec_message_only(binary_to_list(Packet)) of
142
143	#message{version = 'version-1', vsn_hdr = Community, data = Data}
144	  when State#state.v1 =:= true ->
145	    ?vlog("v1, community: ~s", [Community]),
146	    HS = ?empty_msg_size + length(Community),
147	    v1_v2c_proc(
148	      'version-1', NoteStore, Community, From,
149	      LocalEngineID, Data, HS, Log, Packet);
150
151	#message{version = 'version-2', vsn_hdr = Community, data = Data}
152	  when State#state.v2c =:= true ->
153	    ?vlog("v2c, community: ~s", [Community]),
154	    HS = ?empty_msg_size + length(Community),
155	    v1_v2c_proc(
156	      'version-2', NoteStore, Community, From,
157	      LocalEngineID, Data, HS, Log, Packet);
158
159	#message{version = 'version-3', vsn_hdr = V3Hdr, data = Data}
160	  when State#state.v3 =:= true ->
161	    ?vlog("v3, msgID: ~p, msgFlags: ~p, msgSecModel: ~p",
162		  [V3Hdr#v3_hdr.msgID,
163		   V3Hdr#v3_hdr.msgFlags,
164		   V3Hdr#v3_hdr.msgSecurityModel]),
165	    v3_proc(
166	      NoteStore, Packet, From,
167	      LocalEngineID, V3Hdr, Data, Log);
168
169	#message{version = MsgVersion} ->
170	    ?vlog("Invalid Version: "
171                  "~n      Message Version: ~p"
172                  "~nwhen"
173                  "~n      Versions:"
174                  "~n         v1:  ~w"
175                  "~n         v2c: ~w"
176                  "~n         v3:  ~w",
177                  [MsgVersion,
178                   State#state.v1, State#state.v2c, State#state.v3]),
179	    inc(snmpInBadVersions),
180	    {discarded, snmpInBadVersions};
181
182	{'EXIT', {bad_version, Vsn}} ->
183	    ?vtrace("exit: bad version: ~p",[Vsn]),
184	    inc(snmpInBadVersions),
185	    {discarded, snmpInBadVersions};
186
187	{'EXIT', Reason} ->
188	    ?vtrace("exit: ~p", [Reason]),
189	    inc(snmpInASNParseErrs),
190	    {discarded, Reason};
191
192	UnknownMessage ->
193	    ?vdebug("Unknown message: "
194                    "~n      ~p"
195                    "~nwhen"
196                    "~n   State: "
197                    "~n      ~p", [UnknownMessage, State]),
198	    inc(snmpInBadVersions),
199	    {discarded, snmpInBadVersions}
200    end.
201
202discarded_pdu(false) -> ok;
203discarded_pdu(Variable) -> inc(Variable).
204
205
206%%-----------------------------------------------------------------
207%% Handles a Community based message (v1 or v2c).
208%%-----------------------------------------------------------------
209v1_v2c_proc(
210  Vsn, NoteStore, Community, From,
211  LocalEngineID, Data, HS, Log, Packet) ->
212    try
213	case From of
214	    {D, A} when is_atom(D) ->
215		{snmp_conf:mk_tdomain(D),
216		 snmp_conf:mk_taddress(D, A)};
217	    {_, P} = A when is_integer(P) ->
218		{snmp_conf:mk_tdomain(),
219		 snmp_conf:mk_taddress(A)}
220	end
221    of
222	{TDomain, TAddress} ->
223	    v1_v2c_proc_dec(
224	      Vsn, NoteStore, Community, TDomain, TAddress,
225	      LocalEngineID, Data, HS, Log, Packet)
226    catch
227	_ ->
228	    {discarded, {badarg, From}}
229    end.
230
231
232v1_v2c_proc_dec(
233  Vsn, NoteStore, Community, TDomain, TAddress,
234  LocalEngineID, Data, HS, Log, Packet) ->
235    AgentMS  = get_engine_max_message_size(LocalEngineID),
236    MgrMS    = snmp_community_mib:get_target_addr_ext_mms(TDomain, TAddress),
237    PduMS    = case MgrMS of
238		   {ok, MMS} when MMS < AgentMS -> MMS - HS;
239		   _ -> AgentMS - HS
240	       end,
241    case (catch snmp_pdus:dec_pdu(Data)) of
242	Pdu when is_record(Pdu, pdu) ->
243	    Log(Pdu#pdu.type, Packet),
244	    inc_snmp_in_vars(Pdu),
245	    #pdu{request_id = ReqId} = Pdu,
246
247	    %% <TDomain>
248	    %% We have added TDomain, what are the consequences?
249	    ACMData =
250		{community, sec_model(Vsn), Community, TDomain, TAddress},
251	    OkRes = {ok, Vsn, Pdu, PduMS, ACMData},
252	    %% </TDomain>
253
254	    %% Make sure that we don't process duplicate SET request
255	    %% twice.  We don't know what could happen in that case.
256	    %% The mgr does, so he has to generate a new SET request.
257	    ?vdebug("PDU type: ~p", [Pdu#pdu.type]),
258	    case Pdu#pdu.type of
259		'set-request' ->
260		    %% Check if this message has already been processed
261		    Key = {agent, {TDomain, TAddress}, ReqId},
262		    case snmp_note_store:get_note(NoteStore, Key) of
263			undefined ->
264			    %% Set the processed note _after_ pdu processing.
265			    %% This makes duplicated requests be ignored even
266			    %% if pdu processing took long time.
267			    snmp_note_store:set_note(NoteStore,
268						     100, Key, true),
269			    %% Uses ACMData that snmpa_acm knows of.
270			    OkRes;
271			true ->
272			    {discarded, duplicate_pdu}
273		    end;
274		_ ->
275		    OkRes
276	    end;
277	{'EXIT', Reason} ->
278	    ?vtrace("PDU decode exit: ~p",[Reason]),
279	    inc(snmpInASNParseErrs),
280	    {discarded, Reason};
281	_TrapPdu ->
282	    {discarded, trap_pdu}
283    end.
284
285sec_model('version-1') -> ?SEC_V1;
286sec_model('version-2') -> ?SEC_V2C.
287
288
289%%-----------------------------------------------------------------
290%% Handles a SNMPv3 Message, following the procedures in rfc2272,
291%% section 4.2 and 7.2
292%%-----------------------------------------------------------------
293v3_proc(NoteStore, Packet, _From, LocalEngineID, V3Hdr, Data, Log) ->
294    case (catch v3_proc(NoteStore, Packet, LocalEngineID, V3Hdr, Data, Log)) of
295	{'EXIT', Reason} ->
296	    exit(Reason);
297	Result ->
298	    Result
299    end.
300
301v3_proc(NoteStore, Packet, LocalEngineID, V3Hdr, Data, Log) ->
302    ?vtrace("v3_proc -> entry with"
303	    "~n   LocalEngineID: ~p",
304	    [LocalEngineID]),
305    %% 7.2.3
306    #v3_hdr{msgID                 = MsgID,
307	    msgMaxSize            = MMS,
308	    msgFlags              = MsgFlags,
309	    msgSecurityModel      = MsgSecurityModel,
310	    msgSecurityParameters = SecParams,
311	    hdr_size              = HdrSize} = V3Hdr,
312    ?vdebug("v3_proc -> version 3 message header [7.2.3]:"
313	    "~n   msgID                 = ~p"
314	    "~n   msgMaxSize            = ~p"
315	    "~n   msgFlags              = ~p"
316	    "~n   msgSecurityModel      = ~p"
317	    "~n   msgSecurityParameters = ~w",
318	    [MsgID, MMS, MsgFlags, MsgSecurityModel, SecParams]),
319    %% 7.2.4
320    SecModule    = get_security_module(MsgSecurityModel),
321    %% 7.2.5
322    SecLevel     = check_sec_level(MsgFlags),
323    IsReportable = snmp_misc:is_reportable(MsgFlags),
324    %% 7.2.6
325    ?vtrace("v3_proc -> [7.2.4-7.2.6]"
326	    "~n   SecModule    = ~p"
327	    "~n   SecLevel     = ~p"
328	    "~n   IsReportable = ~p",
329	    [SecModule, SecLevel, IsReportable]),
330    SecRes = (catch SecModule:process_incoming_msg(Packet, Data,
331						   SecParams, SecLevel,
332						   LocalEngineID)),
333    ?vtrace("v3_proc -> message processing result: "
334	    "~n   SecRes: ~p", [SecRes]),
335    {SecEngineID, SecName, ScopedPDUBytes, SecData, DiscoOrPlain} =
336	check_sec_module_result(SecRes, V3Hdr, Data,
337				LocalEngineID, IsReportable, Log),
338    ?vtrace("v3_proc -> "
339	    "~n   DiscoOrPlain: ~w"
340	    "~n   SecEngineID:  ~w"
341	    "~n   SecName:      ~p", [DiscoOrPlain, SecEngineID, SecName]),
342    %% 7.2.7
343    #scopedPdu{contextEngineID = ContextEngineID,
344	       contextName     = ContextName,
345	       data            = PDU} =
346	case (catch snmp_pdus:dec_scoped_pdu(ScopedPDUBytes)) of
347	    ScopedPDU when is_record(ScopedPDU, scopedPdu) ->
348		?vtrace("v3_proc -> message processing result: "
349			"~n   ScopedPDU: ~p", [ScopedPDU]),
350		ScopedPDU;
351	    {'EXIT', Reason} ->
352		inc(snmpInASNParseErrs),
353		throw({discarded, Reason})
354	end,
355    %% We'll have to take care of the unlikely case that we receive an
356    %% v1 trappdu in a v3 message explicitly...
357    if
358	is_record(PDU, trappdu) ->
359	    inc(snmpUnknownPDUHandlers),
360	    throw({discarded, received_v1_trap});
361	true ->
362	    ok
363    end,
364    ?vlog("7.2.7 result: "
365	  "~n   contextEngineID: ~w"
366	  "~n   ContextName:     \"~s\"", [ContextEngineID, ContextName]),
367    if
368	SecLevel =:= ?'SnmpSecurityLevel_authPriv' ->
369	    %% encrypted message - log decrypted pdu
370	    Log(PDU#pdu.type, {V3Hdr, ScopedPDUBytes});
371	true -> % otherwise, log binary
372	    Log(PDU#pdu.type, Packet)
373    end,
374    %% Make sure a get_bulk doesn't get too big.
375    AgentMS = get_engine_max_message_size(LocalEngineID),
376    %% PduMMS is supposed to be the maximum total length of the response
377    %% PDU we can send.  From the MMS, we need to subtract everything before
378    %% the PDU, i.e. Message and ScopedPDU.
379    %%   Message: [48, TotalLen, Vsn, [Tag, LH, Hdr], [Tag, LM, MsgSec], Data]
380    %%             1              3   <----------- HdrSize ----------->
381    %%   HdrSize = everything up to and including msgSecurityParameters.
382    %% ScopedPduData follows.  This is
383    %%   [Tag, Len, [Tag, L1, CtxName], [Tag, L2, CtxEID]]
384    %%   i.e. 6 + length(CtxName) + length(CtxEID)
385    %%
386    %% Total: 1 + TotalLenOctets + 3 + ScopedPduDataLen
387    TotMMS = if AgentMS > MMS -> MMS;
388		true -> AgentMS
389	     end,
390    TotalLenOctets = snmp_pdus:get_encoded_length(TotMMS - 1),
391    PduMMS = TotMMS - TotalLenOctets - 10 - HdrSize -
392	length(ContextName) - length(ContextEngineID),
393    ?vdebug("v3_proc -> PDU type: ~p", [PDU#pdu.type]),
394    case PDU#pdu.type of
395	report when DiscoOrPlain =:= discovery ->
396	    %% Discovery stage 1 response
397	    Key  = {agent, MsgID},
398	    Note = snmp_note_store:get_note(NoteStore, Key),
399	    case Note of
400                #note{sec_engine_id = "",
401                      sec_model     = _MsgSecModel,
402                      sec_name      = "",
403                      sec_level     = _SecLevel,
404                      ctx_engine_id = _CtxEngineID,
405                      ctx_name      = _CtxName,
406                      disco         = true,
407                      req_id        = _ReqId} ->
408                    %% This is part of the discovery process initiated by us.
409                    %% Response to the discovery stage 1 request
410                    ?vdebug("v3_proc -> discovery stage 1 response", []),
411                    {ok, 'version-3', PDU, PduMMS, {discovery, SecEngineID}};
412                #note{sec_engine_id = SecEngineID,
413                      sec_model     = _MsgSecModel,
414                      sec_name      = SecName,
415                      sec_level     = _SecLevel,   % OTP-16207
416                      ctx_engine_id = _CtxEngineID,
417                      ctx_name      = _CtxName,
418                      disco         = true,
419                      req_id        = _ReqId} ->
420                    %% This is part of the discovery process initiated by us.
421                    %% Response to the discovery stage 2 request
422                    ?vdebug("v3_proc -> discovery stage 2 response", []),
423                    {ok, 'version-3', PDU, PduMMS, discovery};
424		_ ->
425		    %% 7.2.11
426		    DiscardReason = {bad_disco_note, Key, Note},
427		    throw({discarded, DiscardReason})
428	    end;
429	report ->
430	    %% 7.2.11
431	    throw({discarded, report});
432	'get-response' -> %% As a result of a sent inform-request?
433	    %% 7.2.12
434	    Key  = {agent, MsgID},
435	    Note = snmp_note_store:get_note(NoteStore, Key),
436	    case Note of
437                #note{sec_engine_id = "",
438                      sec_model     = _MsgSecModel,
439                      sec_name      = "",
440                      sec_level     = _SecLevel,
441                      ctx_engine_id = _CtxEngineID,
442                      ctx_name      = _CtxName,
443                      disco         = true,
444                      req_id        = _ReqId} ->
445                    %% This is part of the discovery process initiated by us.
446                    %% Response to the discovery stage 1 request
447                    ?vdebug("v3_proc -> discovery stage 1 response", []),
448                    {ok, 'version-3', PDU, PduMMS, {discovery, SecEngineID}};
449                #note{sec_engine_id = SecEngineID,
450                      sec_model     = _MsgSecModel,
451                      sec_name      = SecName,
452                      sec_level     = SecLevel,
453                      ctx_engine_id = _CtxEngineID,
454                      ctx_name      = _CtxName,
455                      disco         = true,
456                      req_id        = _ReqId} ->
457                    %% This is part of the discovery process initiated by us.
458                    %% Response to the discovery stage 2 request
459                    ?vdebug("v3_proc -> discovery stage 2 response", []),
460                    {ok, 'version-3', PDU, PduMMS, discovery};
461		#note{sec_engine_id = SecEngineID,
462		      sec_model     = MsgSecurityModel,
463		      sec_name      = SecName,
464		      sec_level     = SecLevel,
465		      ctx_engine_id = ContextEngineID,
466		      ctx_name      = ContextName,
467		      disco         = false,
468		      req_id        = _ReqId} ->
469		    {ok, 'version-3', PDU, PduMMS, undefined};
470		_ ->
471		    inc(snmpUnknownPDUHandlers),
472		    throw({discarded, {no_outstanding_req, MsgID}})
473	    end;
474	'snmpv2-trap' ->
475	    inc(snmpUnknownPDUHandlers),
476	    throw({discarded, received_v2_trap});
477	Type ->
478	    %% 7.2.13
479	    SnmpEngineID = LocalEngineID,
480	    ?vtrace("v3_proc -> 7.2.13", []),
481	    case SecEngineID of
482		SnmpEngineID when (DiscoOrPlain =:= discovery) ->
483		    %% This is a discovery step 2 message!
484		    ?vtrace("v3_proc -> discovery stage 2", []),
485		    generate_discovery2_report_msg(MsgID,
486						   MsgSecurityModel,
487						   SecName,
488						   SecLevel,
489						   ContextEngineID,
490						   ContextName,
491						   SecData,
492						   PDU,
493						   LocalEngineID,
494						   Log);
495
496		SnmpEngineID when (DiscoOrPlain =:= plain) ->
497		    %% 4.2.2.1.1 - we don't handle proxys yet => we only
498		    %% handle ContextEngineID to ourselves
499		    case ContextEngineID of
500			SnmpEngineID ->
501			    %% Uses ACMData that snmpa_acm knows of.
502			    {ok, 'version-3', PDU, PduMMS,
503			     {v3, MsgID, MsgSecurityModel, SecName, SecLevel,
504			      ContextEngineID, ContextName, SecData}};
505			_ ->
506			    %% 4.2.2.1.2
507			    NIsReportable = snmp_misc:is_reportable_pdu(Type),
508			    ErrorInfo =
509				snmpUnknownPDUHandlers_ei(SecName, SecLevel,
510							  ContextEngineID,
511							  ContextName),
512			    case generate_v3_report_msg(MsgID,
513							MsgSecurityModel,
514							Data, LocalEngineID,
515							ErrorInfo, Log) of
516				{ok, Report} when NIsReportable =:= true ->
517				    {discarded, snmpUnknownPDUHandlers, Report};
518				_ ->
519				    {discarded, snmpUnknownPDUHandlers}
520			    end
521		    end;
522
523		"" ->
524		    %% This is a discovery step 1 message!!
525		    ?vtrace("v3_proc -> discovery step 1", []),
526		    generate_discovery1_report_msg(MsgID,
527						   MsgSecurityModel,
528						   SecName,
529						   SecLevel,
530						   ContextEngineID,
531						   ContextName,
532						   SecData,
533						   PDU,
534						   LocalEngineID,
535						   Log);
536
537		_ ->
538		    {discarded, {badSecurityEngineID, SecEngineID}}
539	    end
540    end.
541
542make_error_info(Variable, Oid, SecName, Opts) ->
543    Val = inc(Variable),
544    VB  = #varbind{oid          = Oid,
545		   variabletype = 'Counter32',
546		   value        = Val},
547    {VB, SecName, Opts}.
548
549snmpUnknownPDUHandlers_ei(SecName, SecLevel,
550			  ContextEngineID, ContextName) ->
551    Opts = [{securityLevel,   SecLevel},
552	    {contextEngineID, ContextEngineID},
553	    {contextName,     ContextName}],
554    make_error_info(snmpUnknownPDUHandlers,
555		    ?snmpUnknownPDUHandlers_instance,
556		    SecName, Opts).
557
558get_security_module(?SEC_USM) ->
559    snmpa_usm;
560get_security_module(_) ->
561    inc(snmpUnknownSecurityModels),
562    throw({discarded, snmpUnknownSecurityModels}).
563
564check_sec_level([MsgFlag]) ->
565    SecLevel = MsgFlag band 3,
566    if
567	SecLevel == 2 ->
568	    inc(snmpInvalidMsgs),
569	    throw({discarded, snmpInvalidMsgs});
570	true ->
571	    SecLevel
572    end;
573check_sec_level(Unknown) ->
574    ?vlog("invalid msgFlags: ~p",[Unknown]),
575    inc(snmpInvalidMsgs),
576    throw({discarded, snmpInvalidMsgs}).
577
578check_sec_module_result(Res, V3Hdr, Data, LocalEngineID, IsReportable, Log) ->
579    case Res of
580	{ok, X} ->
581	    X;
582	{error, Reason, []} ->         % case 7.2.6 b
583	    ?vdebug("security module result [7.2.6-b]:"
584		    "~n   Reason: ~p", [Reason]),
585	    throw({discarded, {securityError, Reason}});
586	{error, Reason, ErrorInfo} when IsReportable =:= true -> % case 7.2.6 a
587	    ?vdebug("security module result when reportable [7.2.6-a]:"
588		    "~n   Reason:    ~p"
589		    "~n   ErrorInfo: ~p", [Reason, ErrorInfo]),
590	    #v3_hdr{msgID = MsgID, msgSecurityModel = MsgSecModel} = V3Hdr,
591	    Pdu = get_scoped_pdu(Data),
592	    case generate_v3_report_msg(MsgID, MsgSecModel, Pdu,
593					LocalEngineID, ErrorInfo, Log) of
594		{ok, Report} ->
595		    throw({discarded, {securityError, Reason}, Report});
596		{discarded, _SomeOtherReason} ->
597		    throw({discarded, {securityError, Reason}})
598	    end;
599	{error, Reason, ErrorInfo} ->
600	    ?vdebug("security module result when not reportable:"
601		    "~n   Reason:    ~p"
602		    "~n   ErrorInfo: ~p", [Reason, ErrorInfo]),
603	    throw({discarded, {securityError, Reason}});
604	Else ->
605	    ?vdebug("security module result:"
606		    "~n   Else: ~p", [Else]),
607	    throw({discarded, {securityError, Else}})
608    end.
609
610get_scoped_pdu(D) when is_list(D) ->
611    (catch snmp_pdus:dec_scoped_pdu(D));
612get_scoped_pdu(D) ->
613    D.
614
615
616%%-----------------------------------------------------------------
617%% Executed when a response or report message is generated.
618%%-----------------------------------------------------------------
619generate_response_msg(Vsn, RePdu, Type, ACMData, Log) ->
620    generate_response_msg(Vsn, RePdu, Type, ACMData, Log, 1).
621
622generate_response_msg(Vsn, RePdu, Type, ACMData, Log, N) when is_integer(N) ->
623    LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
624    generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log, N);
625generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) ->
626    generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log, 1).
627
628generate_response_msg(Vsn, RePdu, Type,
629		      {community, _SecModel, Community, _TDomain, _TAddress},
630		      LocalEngineID,
631		      Log, _) ->
632	case catch snmp_pdus:enc_pdu(RePdu) of
633	    {'EXIT', Reason} ->
634		user_err("failed encoding pdu: "
635			 "(pdu: ~w, community: ~w): ~n~w",
636			 [RePdu, Community, Reason]),
637		{discarded, Reason};
638	    PduBytes ->
639		Message = #message{version = Vsn,
640				   vsn_hdr = Community,
641				   data    = PduBytes},
642		case catch list_to_binary(
643			     snmp_pdus:enc_message_only(Message)) of
644		    {'EXIT', Reason} ->
645			user_err("failed encoding message only "
646				 "(pdu: ~w, community: ~w): ~n~w",
647				 [RePdu, Community, Reason]),
648			{discarded, Reason};
649		    Packet ->
650			MMS = get_engine_max_message_size(LocalEngineID),
651			case size(Packet) of
652			    Len when Len =< MMS ->
653				Log(Type, Packet),
654				inc_snmp_cnt_vars(Type, RePdu),
655				inc_snmp_out_vars(RePdu),
656				{ok, Packet};
657			    Len ->
658				?vlog("pdu to big:"
659				      "~n   Max message size:     ~p"
660				      "~n   Encoded message size: ~p",
661				      [MMS,Len]),
662				too_big(Vsn, RePdu, Community, Log, MMS, Len)
663			end
664		end
665	end;
666generate_response_msg(Vsn, RePdu, Type,
667		      {v3, MsgID, MsgSecurityModel, SecName, SecLevel,
668		       ContextEngineID, ContextName, SecData},
669		      LocalEngineID,
670		      Log, N) ->
671    %% rfc2272: 7.1 steps 6-8
672    ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID,
673			   contextName     = ContextName,
674			   data            = RePdu},
675    case catch snmp_pdus:enc_scoped_pdu(ScopedPDU) of
676	{'EXIT', Reason} ->
677	    user_err("failed encoded scoped pdu "
678		     "(pdu: ~w, contextName: ~w): ~n~w",
679		     [RePdu, ContextName, Reason]),
680	    {discarded, Reason};
681	ScopedPDUBytes ->
682	    AgentMS = get_engine_max_message_size(LocalEngineID),
683	    V3Hdr = #v3_hdr{msgID      = MsgID,
684			    msgMaxSize = AgentMS,
685			    msgFlags = snmp_misc:mk_msg_flags(Type, SecLevel),
686			    msgSecurityModel = MsgSecurityModel},
687	    Message = #message{version = Vsn,
688			       vsn_hdr = V3Hdr,
689			       data    = ScopedPDUBytes},
690	    %% We know that the security model is valid when we
691	    %% generate a response.
692	    SecModule =
693		case MsgSecurityModel of
694		    ?SEC_USM ->
695			snmpa_usm
696		end,
697	    SecEngineID = LocalEngineID, % 3.1.1a
698	    ?vtrace("generate_response_msg -> SecEngineID: ~w", [SecEngineID]),
699	    case (catch SecModule:generate_outgoing_msg(Message,
700							SecEngineID,
701							SecName,
702							SecData,
703							SecLevel,
704							LocalEngineID)) of
705		{'EXIT', Reason} ->
706		    config_err("~p (message: ~p)", [Reason, Message]),
707		    {discarded, Reason};
708		{error, Reason} ->
709		    config_err("~p (message: ~p)", [Reason, Message]),
710		    {discarded, Reason};
711		OutMsg when is_list(OutMsg) ->
712		    %% Check the packet size.  Send the msg even
713		    %% if it's larger than the mgr can handle - it
714		    %% will be dropped.  Just check against the
715		    %% internal size.  For GET-BULk responses: we
716		    %% *know* that we're within the right limits,
717		    %% because of the calculation we do when we
718		    %% receive the bulk-request.
719		    Packet = list_to_binary(OutMsg),
720		    case size(Packet) of
721			Len when Len =< AgentMS ->
722			    if
723				SecLevel =:= 3 ->
724				    %% encrypted - log decrypted pdu
725				    Log(Type, {V3Hdr, ScopedPDUBytes});
726				true ->
727				    %% otherwise log the entire msg
728				    Log(Type, Packet)
729			    end,
730			    inc_snmp_cnt_vars(Type, RePdu),
731			    inc_snmp_out_vars(RePdu),
732			    {ok, Packet};
733			Len when N =:= 2 ->
734			    ?vlog("packet max size exceeded: "
735				  "~n   Max: ~p"
736				  "~n   Len: ~p",
737				  [AgentMS,Len]),
738			    inc(snmpSilentDrops),
739			    {discarded, tooBig};
740			Len ->
741			    ?vlog("packet max size exceeded: "
742				  "~n   N:   ~p"
743				  "~n   Max: ~p"
744				  "~n   Len: ~p",
745				  [N, AgentMS, Len]),
746			    TooBigPdu = RePdu#pdu{error_status = tooBig,
747						  error_index  = 0,
748						  varbinds     = []},
749			    generate_response_msg(Vsn, TooBigPdu, Type,
750						  {v3, MsgID,
751						   MsgSecurityModel,
752						   SecName, SecLevel,
753						   ContextEngineID,
754						   ContextName,
755						   SecData},
756						  LocalEngineID, Log, N+1)
757		    end
758	    end
759    end.
760
761generate_v3_report_msg(MsgID, MsgSecurityModel, Data, LocalEngineID,
762		       ErrorInfo, Log) ->
763    {Varbind, SecName, Opts} = ErrorInfo,
764    ReqId =
765	if
766	    is_record(Data, scopedPdu) ->
767		(Data#scopedPdu.data)#pdu.request_id;
768	   true ->
769		0 %% RFC2572, 7.1.3.c.4
770	end,
771    ?vtrace("Report ReqId: ~p",[ReqId]),
772    Pdu = #pdu{type         = report,
773	       request_id   = ReqId,
774	       error_status = noError,
775	       error_index  = 0,
776	       varbinds     = [Varbind]},
777    SecLevel        = snmp_misc:get_option(securityLevel, Opts, 0),
778    SnmpEngineID    = LocalEngineID,
779    ContextEngineID =
780	snmp_misc:get_option(contextEngineID, Opts, SnmpEngineID),
781    ContextName     = snmp_misc:get_option(contextName, Opts, ""),
782    SecData         = snmp_misc:get_option(sec_data,    Opts, []),
783
784    generate_response_msg('version-3', Pdu, report,
785			  {v3, MsgID, MsgSecurityModel, SecName, SecLevel,
786			   ContextEngineID, ContextName, SecData},
787			  LocalEngineID, Log).
788
789
790%% Response to stage 1 discovery message (terminating, i.e. from the manager)
791generate_discovery1_report_msg(MsgID, MsgSecurityModel,
792			       SecName, SecLevel,
793			       ContextEngineID, ContextName,
794			       {SecData, Oid, Value},
795			       #pdu{request_id = ReqId},
796			       LocalEngineID, Log) ->
797    ?vtrace("generate_discovery1_report_msg -> entry with"
798	    "~n   ReqId: ~p"
799	    "~n   Value: ~p", [ReqId, Value]),
800    Varbind = #varbind{oid          = Oid,
801		       variabletype = 'Counter32',
802		       value        = Value,
803		       org_index    = 1},
804    PduOut = #pdu{type         = report,
805		  request_id   = ReqId,
806		  error_status = noError,
807		  error_index  = 0,
808		  varbinds     = [Varbind]},
809    case generate_response_msg('version-3', PduOut, report,
810			       {v3, MsgID, MsgSecurityModel, SecName, SecLevel,
811				ContextEngineID, ContextName, SecData},
812			       LocalEngineID, Log) of
813	{ok, Packet} ->
814	    {discovery, Packet};
815	Error ->
816	    Error
817    end.
818
819%% Response to stage 2 discovery message (terminating, i.e. from the manager)
820generate_discovery2_report_msg(MsgID, MsgSecurityModel,
821			       SecName, SecLevel,
822			       ContextEngineID, ContextName,
823			       SecData, #pdu{request_id = ReqId},
824			       LocalEngineID, Log) ->
825    ?vtrace("generate_discovery2_report_msg -> entry with"
826	    "~n   ReqId: ~p", [ReqId]),
827    SecModule = get_security_module(MsgSecurityModel),
828    Vb = SecModule:current_statsNotInTimeWindows_vb(),
829    PduOut = #pdu{type         = report,
830		  request_id   = ReqId,
831		  error_status = noError,
832		  error_index  = 0,
833		  varbinds     = [Vb]},
834    case generate_response_msg('version-3', PduOut, report,
835			       {v3, MsgID, MsgSecurityModel, SecName, SecLevel,
836				ContextEngineID, ContextName, SecData},
837			       LocalEngineID, Log) of
838	{ok, Packet} ->
839	    {discovery, Packet};
840	Error ->
841	    Error
842    end.
843
844
845too_big(Vsn, Pdu, Community, Log, _MMS, _Len)
846  when Pdu#pdu.type =:= 'get-response' ->
847    ErrPdu =
848	if
849	    Vsn =:= 'version-1' ->
850		%% In v1, the varbinds should be identical to the incoming
851		%% request.  It isn't identical now!
852		%% Make acceptable (?) approximation.
853		V = set_vb_null(Pdu#pdu.varbinds),
854		Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = V};
855	    true ->
856		%% In v2, varbinds should be empty (reasonable!)
857		Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = []}
858	end,
859
860    case catch snmp_pdus:enc_pdu(ErrPdu) of
861	{'EXIT', Reason} ->
862	    user_err("failed encoding pdu (pdu: ~w, community: ~w): ~n~w",
863		     [ErrPdu, Community, Reason]),
864	    {discarded, Reason};
865	PduBytes ->
866	    Message = #message{version = Vsn, vsn_hdr = Community,
867			       data = PduBytes},
868	    case catch snmp_pdus:enc_message_only(Message) of
869		{'EXIT', Reason} ->
870		    user_err("failed encoding message only"
871			     "(pdu: ~w, community: ~w): ~n~w",
872			     [ErrPdu, Community, Reason]),
873		    {discarded, Reason};
874		Packet ->
875		    Bin = list_to_binary(Packet),
876		    Log(Pdu#pdu.type, Bin),
877		    inc_snmp_out_vars(ErrPdu),
878		    {ok, Bin}
879	    end
880    end;
881too_big(_Vsn, Pdu, _Community, _Log, MMS, Len) ->
882    user_err("encoded pdu, ~p bytes, exceeded "
883	     "max message size of ~p bytes. Pdu: ~n~w",
884	     [Len, MMS, Pdu]),
885    {discarded, tooBig}.
886
887set_vb_null([Vb | Vbs]) ->
888    [Vb#varbind{variabletype = 'NULL', value = 'NULL'} | set_vb_null(Vbs)];
889set_vb_null([]) ->
890    [].
891
892%%-----------------------------------------------------------------
893%% Executed when a message that isn't a response is generated, i.e.
894%% a trap or an inform.
895%%-----------------------------------------------------------------
896generate_msg(Vsn, NoteStore, Pdu, ACMData, To) ->
897    LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
898    generate_msg(Vsn, NoteStore, Pdu, ACMData, LocalEngineID, To).
899
900generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, LocalEngineID, To) ->
901    Message = #message{version = Vsn, vsn_hdr = Community, data = Pdu},
902    case catch list_to_binary(snmp_pdus:enc_message(Message)) of
903	{'EXIT', Reason} ->
904	    user_err("failed encoding message "
905		     "(pdu: ~w, community: ~w): ~n~w",
906		     [Pdu, Community, Reason]),
907	    {discarded, Reason};
908	Packet ->
909	    AgentMax = get_engine_max_message_size(LocalEngineID),
910	    case size(Packet) of
911		Len when Len =< AgentMax ->
912		    {ok, mk_v1_v2_packet_list(To, Packet, Len, Pdu)};
913		Len ->
914		    ?vlog("packet max size exceeded: "
915			  "~n   Max: ~p"
916			  "~n   Len: ~p",
917			  [AgentMax, Len]),
918		    {discarded, tooBig}
919	    end
920    end;
921generate_msg('version-3', NoteStore, Pdu,
922	     {v3, ContextEngineID, ContextName}, LocalEngineID, To) ->
923    %% rfc2272: 7.1 step 6
924    ScopedPDU = #scopedPdu{contextEngineID = LocalEngineID,
925			   contextName = ContextName,
926			   data = Pdu},
927    case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of
928	{'EXIT', Reason} ->
929	    user_err("failed encoding scoped pdu "
930		     "(pdu: ~w, contextName: ~w): ~n~w",
931		     [Pdu, ContextName, Reason]),
932	    {discarded, Reason};
933	ScopedPDUBytes ->
934	    {ok, mk_v3_packet_list(NoteStore, To, ScopedPDUBytes, Pdu,
935				   ContextEngineID, ContextName,
936				   LocalEngineID)}
937    end.
938
939
940generate_discovery_msg(NoteStore, Pdu, MsgData, To) ->
941    Timeout = 1500,
942    generate_discovery_msg(NoteStore, Pdu, MsgData, Timeout, To).
943
944generate_discovery_msg(NoteStore, Pdu, MsgData, Timeout, To) ->
945    {SecData, ContextEngineID, ContextName}       = MsgData,
946    {SecModel, SecName, SecLevelFlag, TargetName} = SecData,
947    {ManagerEngineId, InitialUserName} =
948	case get_target_engine_id(TargetName) of
949	    {ok, discovery} ->
950		{"", ""};             % Discovery stage 1
951	    {ok, {discovery, IUN}} ->
952		{"", IUN};            % Discovery stage 1
953	    {ok, TargetEngineId} ->
954		{TargetEngineId, ""}  % Discovery stage 2
955	end,
956    generate_discovery_msg(NoteStore, Pdu,
957			   ContextEngineID, ContextName,
958			   SecModel, SecName, SecLevelFlag,
959			   ManagerEngineId,
960			   InitialUserName,
961			   Timeout, To).
962
963generate_discovery_msg(NoteStore, Pdu,
964		       ContextEngineID, ContextName,
965		       SecModel, _SecName, _SecLevelFlag,
966		       "" = ManagerEngineID,
967		       InitialUserName,
968		       Timeout, To) ->
969    %% Discovery step 1 uses SecLevel = noAuthNoPriv
970    SecName      = "",
971    SecLevelFlag = 0, % ?'SnmpSecurityLevel_noAuthNoPriv',
972    generate_discovery_msg2(NoteStore, Pdu,
973			    ContextEngineID, ManagerEngineID,
974			    SecModel, SecName, SecLevelFlag,
975			    InitialUserName,
976			    ContextName, Timeout, To);
977generate_discovery_msg(NoteStore, Pdu,
978		       ContextEngineID, ContextName,
979		       SecModel, SecName, SecLevelFlag,
980		       ManagerEngineID,
981		       InitialUserName,
982		       Timeout, To) ->
983    %% SecLevelFlag = 1, % ?'SnmpSecurityLevel_authNoPriv',
984    generate_discovery_msg2(NoteStore, Pdu,
985			    ContextEngineID, ManagerEngineID,
986			    SecModel, SecName, SecLevelFlag,
987			    InitialUserName,
988			    ContextName, Timeout, To).
989
990generate_discovery_msg2(NoteStore, Pdu,
991			ContextEngineID, ManagerEngineID,
992			SecModel, SecName, SecLevelFlag,
993			InitialUserName,
994			ContextName, Timeout, To) ->
995    %% rfc2272: 7.1.6
996    ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID,
997			   contextName     = ContextName,
998			   data            = Pdu},
999    case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of
1000	{'EXIT', Reason} ->
1001	    user_err("failed encoding scoped pdu "
1002		     "(pdu: ~w, contextName: ~w): ~n~w",
1003		     [Pdu, ContextName, Reason]),
1004	    {discarded, Reason};
1005	ScopedPDUBytes ->
1006	    {ok, generate_discovery_msg(NoteStore, To,
1007					Pdu, ScopedPDUBytes,
1008					ContextEngineID, ManagerEngineID,
1009					SecModel, SecName, SecLevelFlag,
1010					InitialUserName,
1011					ContextName, Timeout)}
1012    end.
1013
1014%% Timeout is in msec but note timeout is in 1/10 seconds
1015discovery_note_timeout(Timeout) ->
1016    (Timeout div 100) + 1.
1017
1018generate_discovery_msg(NoteStore, {TDomain, TAddress},
1019		       Pdu, ScopedPduBytes,
1020		       ContextEngineID, ManagerEngineID,
1021		       SecModel, SecName, SecLevelFlag,
1022		       InitialUserName,
1023		       ContextName, Timeout) ->
1024
1025    {ok, {Domain, Address}} = transform_taddr(TDomain, TAddress),
1026
1027    %% 7.1.7
1028    ?vdebug("generate_discovery_msg -> 7.1.7 (~w)", [ManagerEngineID]),
1029    MsgID     = generate_msg_id(),
1030    PduType   = Pdu#pdu.type,
1031    MsgFlags  = mk_msg_flags(PduType, SecLevelFlag),
1032    V3Hdr     = #v3_hdr{msgID            = MsgID,
1033			msgMaxSize       = get_max_message_size(),
1034			msgFlags         = MsgFlags,
1035			msgSecurityModel = SecModel},
1036    Message   = #message{version = 'version-3',
1037			 vsn_hdr = V3Hdr,
1038			 data    = ScopedPduBytes},
1039    SecModule = sec_module(SecModel),
1040
1041    %% 7.1.9b
1042    ?vdebug("generate_discovery_msg -> 7.1.9b", []),
1043    case generate_sec_discovery_msg(Message, SecModule,
1044				    ManagerEngineID,
1045				    SecName, SecLevelFlag,
1046				    InitialUserName) of
1047	{ok, Packet} ->
1048	    %% 7.1.9c
1049	    %% Store in cache for Timeout msec.
1050	    NoteTimeout = discovery_note_timeout(Timeout),
1051	    ?vdebug("generate_discovery_msg -> 7.1.9c [~w]", [NoteTimeout]),
1052	    %% The request id is just in case when we receive a
1053	    %% report with incorrect securityModel and/or securityLevel
1054	    Key  = {agent, MsgID},
1055	    Note = #note{sec_engine_id = ManagerEngineID,
1056			 sec_model     = SecModel,
1057			 sec_name      = SecName,
1058			 sec_level     = SecLevelFlag,
1059			 ctx_engine_id = ContextEngineID,
1060			 ctx_name      = ContextName,
1061			 disco         = true,
1062			 req_id        = Pdu#pdu.request_id},
1063	    snmp_note_store:set_note(NoteStore, Timeout, Key, Note),
1064	    %% Log(Packet),
1065	    inc_snmp_out_vars(Pdu),
1066	    ?vdebug("generate_discovery_msg -> done", []),
1067	    {Domain, Address, Packet};
1068
1069	Error ->
1070	    throw(Error)
1071    end.
1072
1073generate_sec_discovery_msg(Message, SecModule,
1074			   SecEngineID, SecName, SecLevelFlag,
1075			   InitialUserName) ->
1076    case (catch SecModule:generate_discovery_msg(Message, SecEngineID,
1077						 SecName, SecLevelFlag,
1078						 InitialUserName)) of
1079	{'EXIT', Reason} ->
1080	    config_err("~p (message: ~p)", [Reason, Message]),
1081	    {discarded, Reason};
1082	{error, Reason} ->
1083	    config_err("~p (message: ~p)", [Reason, Message]),
1084	    {discarded, Reason};
1085	Bin when is_binary(Bin) ->
1086	    {ok, Bin};
1087	OutMsg when is_list(OutMsg) ->
1088	    case (catch list_to_binary(OutMsg)) of
1089		Bin when is_binary(Bin) ->
1090		    {ok, Bin};
1091		{'EXIT', Reason} ->
1092		    {error, Reason}
1093	    end
1094    end.
1095
1096
1097transform_taddr(?snmpUDPDomain, TAddress) ->
1098    transform_taddr(?transportDomainUdpIpv4, TAddress);
1099transform_taddr(?transportDomainUdpIpv4, [A, B, C, D, P1, P2]) ->
1100    Domain  = transportDomainUdpIpv4,
1101    Addr    = {A,B,C,D},
1102    Port    = P1 bsl 8 + P2,
1103    Address = {Addr, Port},
1104    {ok, {Domain, Address}};
1105transform_taddr(?transportDomainUdpIpv4, BadAddr) ->
1106    {error, {bad_transportDomainUdpIpv4_address, BadAddr}};
1107transform_taddr(
1108  ?transportDomainUdpIpv6,
1109  [A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16,
1110   P1, P2]) ->
1111    Domain = transportDomainUdpIpv6,
1112    Addr =
1113	{(A1 bsl 8) bor A2, (A3 bsl 8) bor A4,
1114	 (A5 bsl 8) bor A6, (A7 bsl 8) bor A8,
1115	 (A9 bsl 8) bor A10, (A11 bsl 8) bor A12,
1116	 (A13 bsl 8) bor A14, (A15 bsl 8) bor A16},
1117    Port = P1 bsl 8 + P2,
1118    Address = {Addr, Port},
1119    {ok, {Domain, Address}};
1120transform_taddr(
1121  ?transportDomainUdpIpv6,
1122  [A1, A2, A3, A4, A5, A6, A7, A8, P1, P2]) ->
1123    Domain  = transportDomainUdpIpv6,
1124    Addr    = {A1, A2, A3, A4, A5, A6, A7, A8},
1125    Port    = P1 bsl 8 + P2,
1126    Address = {Addr, Port},
1127    {ok, {Domain, Address}};
1128transform_taddr(?transportDomainUdpIpv6, BadAddr) ->
1129    {error, {bad_transportDomainUdpIpv6_address, BadAddr}};
1130transform_taddr(BadTDomain, TAddress) ->
1131    case lists:member(BadTDomain, snmp_conf:all_tdomains()) of
1132	true ->
1133	    {error, {unsupported_tdomain, BadTDomain, TAddress}};
1134	false ->
1135	    {error, {unknown_tdomain, BadTDomain, TAddress}}
1136    end.
1137
1138
1139process_taddrs(Dests) ->
1140    ?vtrace("process_taddrs -> entry with"
1141	    "~n   Dests: ~p", [Dests]),
1142    process_taddrs(Dests, []).
1143
1144process_taddrs([], Acc) ->
1145    ?vtrace("process_taddrs -> entry when done with"
1146	    "~n   Acc: ~p", [Acc]),
1147    lists:reverse(Acc);
1148
1149%% v3
1150process_taddrs([{{TDomain, TAddress}, SecData} | T], Acc) ->
1151    ?vtrace("process_taddrs -> entry when v3 with"
1152	    "~n   TDomain:  ~p"
1153	    "~n   TAddress: ~p"
1154	    "~n   SecData:  ~p", [TDomain, TAddress, SecData]),
1155    case transform_taddr(TDomain, TAddress) of
1156	{ok, DestAddr} ->
1157	    ?vtrace("process_taddrs -> transformed: "
1158		    "~n   DestAddr: ~p", [DestAddr]),
1159	    Entry = {DestAddr, SecData},
1160	    process_taddrs(T, [Entry | Acc]);
1161	{error, Reason} ->
1162	    ?vinfo("Failed transforming v3 domain and address"
1163		   "~n   Reason:  ~p", [Reason]),
1164	    user_err("Bad TDomain/TAddress: ~w/~w", [TDomain, TAddress]),
1165	    process_taddrs(T, Acc)
1166    end;
1167%% v1 & v2
1168process_taddrs([{TDomain, TAddress} | T], Acc) ->
1169    ?vtrace("process_taddrs -> entry when v1/v2 with"
1170	    "~n   TDomain:  ~p"
1171	    "~n   TAddress: ~p", [TDomain, TAddress]),
1172    case transform_taddr(TDomain, TAddress) of
1173	{ok, DestAddr} ->
1174	    ?vtrace("process_taddrs -> transformed: "
1175		    "~n   DestAddr: ~p", [DestAddr]),
1176	    Entry = DestAddr,
1177	    process_taddrs(T, [Entry | Acc]);
1178	{error, Reason} ->
1179	    ?vinfo("Failed transforming v1/v2 domain and address: "
1180		   "~n   Reason:  ~p", [Reason]),
1181	    user_err("Bad TDomain/TAddress: ~w/~w", [TDomain, TAddress]),
1182	    process_taddrs(T, Acc)
1183    end;
1184process_taddrs(Crap, Acc) ->
1185    throw({error, {bad_taddrs, Crap, Acc}}).
1186
1187
1188mk_v1_v2_packet_list(To, Packet, Len, Pdu) ->
1189    mk_v1_v2_packet_list(To, Packet, Len, Pdu, []).
1190
1191mk_v1_v2_packet_list([], _Packet, _Len, _Pdu, Acc) ->
1192    lists:reverse(Acc);
1193
1194%% This (old) clause is for backward compatibillity reasons
1195%% If this is called, then the filter function is not used
1196mk_v1_v2_packet_list([{?snmpUDPDomain, [A,B,C,D,U1,U2]} | T],
1197		     Packet, Len, Pdu, Acc) ->
1198    %% Sending from default UDP port
1199    inc_snmp_out_vars(Pdu),
1200    Entry = {snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}, Packet},
1201    mk_v1_v2_packet_list(T, Packet, Len, Pdu, [Entry | Acc]);
1202
1203%% This is the new clause
1204%% This is only called if the actual target was accepted
1205%% (by the filter module)
1206mk_v1_v2_packet_list([{Domain, Addr} | T],
1207		     Packet, Len, Pdu, Acc) ->
1208    %% Sending from default UDP port
1209    inc_snmp_out_vars(Pdu),
1210    Entry = {Domain, Addr, Packet},
1211    %% It would be cleaner to return {To, Packet} to not
1212    %% break the abstraction for an address on the
1213    %% {Domain, Address} format.
1214    mk_v1_v2_packet_list(T, Packet, Len, Pdu, [Entry | Acc]).
1215
1216
1217get_max_message_size() ->
1218    snmp_framework_mib:get_engine_max_message_size().
1219
1220mk_msg_flags(PduType, SecLevel) ->
1221    snmp_misc:mk_msg_flags(PduType, SecLevel).
1222
1223mk_v3_packet_entry(NoteStore, Domain, Addr,
1224		   {SecModel, SecName, SecLevel, TargetAddrName},
1225		   ScopedPDUBytes, Pdu, _ContextEngineID, ContextName,
1226		   LocalEngineID) ->
1227    %% rfc2272 7.1 step 77
1228    ?vtrace("mk_v3_packet_entry -> entry - RFC2272-7.1:7", []),
1229    MsgVersion  = 'version-3',                     % 7.1:7a
1230    MsgID       = generate_msg_id(),               % 7.1:7b
1231    MaxMsgSz    = get_max_message_size(),          % 7.1:7c
1232    PduType     = Pdu#pdu.type,
1233    MsgFlags    = mk_msg_flags(PduType, SecLevel), % 7.1:7d
1234    MsgSecModel = SecModel,                        % 7.1:7e
1235    V3Hdr     = #v3_hdr{msgID            = MsgID,
1236			msgMaxSize       = MaxMsgSz,
1237			msgFlags         = MsgFlags,
1238			msgSecurityModel = MsgSecModel},
1239    Message   = #message{version = MsgVersion,
1240			 vsn_hdr = V3Hdr,
1241			 data    = ScopedPDUBytes},
1242    SecModule =
1243	case SecModel of
1244	    ?SEC_USM ->
1245		snmpa_usm
1246	end,
1247
1248    %%
1249    %% 7.1:8 - If the PDU is from the Response Class or the Internal Class
1250    %%         securityEngineID = snmpEngineID (local/source)
1251    %% 7.1:9 - If the PDU is from the Unconfirmed Class
1252    %%         securityEngineID = snmpEngineID (local/source)
1253    %%         else
1254    %%         securityEngineID = targetEngineID (remote/destination)
1255    %%
1256
1257    %% 7.1.9a
1258    ?vtrace("mk_v3_packet_entry -> sec engine id - 7.1.9a", []),
1259    SecEngineID =
1260	case PduType of
1261	    'snmpv2-trap' ->
1262		LocalEngineID;
1263	    _ ->
1264		%% This is the implementation dependent target engine id
1265		%% procedure.
1266		case get_target_engine_id(TargetAddrName) of
1267		    {ok, discovery} ->
1268			config_err("Discovery has not yet been performed for "
1269				   "snmpTargetAddrName ~p~n",
1270				   [TargetAddrName]),
1271			throw({discarded, {discovery, TargetAddrName}});
1272		    {ok, TargetEngineId} ->
1273			?vtrace("TargetEngineId: ~p", [TargetEngineId]),
1274			TargetEngineId;
1275		    undefined ->
1276			config_err("Can't find engineID for "
1277				   "snmpTargetAddrName ~p~n",
1278				   [TargetAddrName]),
1279			"" % this will trigger error in secmodule
1280		end
1281	end,
1282
1283    ?vdebug("mk_v3_packet_entry -> secEngineID: ~p", [SecEngineID]),
1284    %% 7.1.9b
1285    case (catch SecModule:generate_outgoing_msg(Message, SecEngineID,
1286						SecName, [], SecLevel,
1287						LocalEngineID)) of
1288	{'EXIT', Reason} ->
1289	    config_err("~p (message: ~p)", [Reason, Message]),
1290	    skip;
1291	{error, Reason} ->
1292	    ?vlog("~n   ~w error ~p\n", [SecModule, Reason]),
1293	    skip;
1294	OutMsg when is_list(OutMsg) ->
1295	    %% 7.1.9c
1296	    %% Store in cache for 150 sec.
1297	    Packet = list_to_binary(OutMsg),
1298	    ?vdebug("mk_v3_packet_entry -> generated: ~w bytes",
1299		    [size(Packet)]),
1300	    Data =
1301		if
1302		    SecLevel =:= 3 ->
1303			%% encrypted - log decrypted pdu
1304			{Packet, {V3Hdr, ScopedPDUBytes}};
1305		    true ->
1306			%% otherwise log the entire msg
1307			Packet
1308		end,
1309	    CacheKey = {agent, MsgID},
1310	    CacheVal = #note{sec_engine_id = SecEngineID,
1311			     sec_model     = SecModel,
1312			     sec_name      = SecName,
1313			     sec_level     = SecLevel,
1314			     ctx_engine_id = LocalEngineID,
1315			     ctx_name      = ContextName,
1316			     disco         = false,
1317			     req_id        = Pdu#pdu.request_id},
1318	    snmp_note_store:set_note(NoteStore, 1500, CacheKey, CacheVal),
1319	    inc_snmp_out_vars(Pdu),
1320	    %% It would be cleaner to return {To, Packet} to not
1321	    %% break the abstraction for an address on the
1322	    %% {Domain, Address} format.
1323	    {ok, {Domain, Addr, Data}}
1324    end.
1325
1326
1327mk_v3_packet_list(NoteStore, To,
1328		  ScopedPDUBytes, Pdu, ContextEngineID, ContextName,
1329		  LocalEngineID) ->
1330    mk_v3_packet_list(NoteStore, To,
1331		      ScopedPDUBytes, Pdu,
1332		      ContextEngineID, ContextName, LocalEngineID, []).
1333
1334mk_v3_packet_list(_, [],
1335		  _ScopedPDUBytes, _Pdu,
1336		  _ContextEngineID, _ContextName,
1337		  _LocalEngineID, Acc) ->
1338    lists:reverse(Acc);
1339
1340%% This clause is for backward compatibillity reasons
1341%% If this is called the filter function is not used
1342mk_v3_packet_list(NoteStore,
1343		  [{{?snmpUDPDomain, [A,B,C,D,U1,U2]}, SecData} | T],
1344		  ScopedPDUBytes, Pdu, ContextEngineID, ContextName,
1345		  LocalEngineID, Acc) ->
1346    case mk_v3_packet_entry(NoteStore,
1347			    snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}, SecData,
1348			    ScopedPDUBytes, Pdu,
1349			    ContextEngineID, ContextName, LocalEngineID) of
1350	skip ->
1351	    mk_v3_packet_list(NoteStore, T,
1352			      ScopedPDUBytes, Pdu,
1353			      ContextEngineID, ContextName, LocalEngineID,
1354			      Acc);
1355	{ok, Entry} ->
1356	    mk_v3_packet_list(NoteStore, T,
1357			      ScopedPDUBytes, Pdu,
1358			      ContextEngineID, ContextName, LocalEngineID,
1359			      [Entry | Acc])
1360    end;
1361
1362%% This is the new clause
1363%% This is only called if the actual target was accepted
1364%% (by the filter module)
1365mk_v3_packet_list(NoteStore,
1366		  [{{Domain, Addr}, SecData} | T],
1367		  ScopedPDUBytes, Pdu, ContextEngineID, ContextName,
1368		  LocalEngineID, Acc) ->
1369    case mk_v3_packet_entry(NoteStore,
1370			    Domain, Addr, SecData,
1371			    ScopedPDUBytes, Pdu,
1372			    ContextEngineID, ContextName, LocalEngineID) of
1373	skip ->
1374	    mk_v3_packet_list(NoteStore, T,
1375			      ScopedPDUBytes, Pdu,
1376			      ContextEngineID, ContextName, Acc);
1377	{ok, Entry} ->
1378	    mk_v3_packet_list(NoteStore, T,
1379			      ScopedPDUBytes, Pdu,
1380			      ContextEngineID, ContextName,
1381			      LocalEngineID, [Entry | Acc])
1382    end.
1383
1384
1385generate_msg_id() ->
1386    gen(msg_id).
1387
1388generate_req_id() ->
1389    gen(req_id).
1390
1391gen(Id) ->
1392    case ets:update_counter(snmp_agent_table, Id, 1) of
1393	N when N =< 2147483647 ->
1394	    N;
1395	_N ->
1396	    ets:insert(snmp_agent_table, {Id, 0}),
1397	    0
1398    end.
1399
1400
1401get_target_engine_id(TargetAddrName) ->
1402    snmp_target_mib:get_target_engine_id(TargetAddrName).
1403
1404get_engine_max_message_size(_LocalEngineID) ->
1405    snmp_framework_mib:get_engine_max_message_size().
1406
1407sec_module(?SEC_USM) ->
1408    snmpa_usm.
1409
1410
1411%%-----------------------------------------------------------------
1412%% Version(s) functions
1413%%-----------------------------------------------------------------
1414init_versions([], S) ->
1415    S;
1416init_versions([v1|Vsns], S) ->
1417    init_versions(Vsns, S#state{v1 = true});
1418init_versions([v2|Vsns], S) ->
1419    init_versions(Vsns, S#state{v2c = true});
1420init_versions([v3|Vsns], S) ->
1421    init_versions(Vsns, S#state{v3 = true}).
1422
1423
1424%%-----------------------------------------------------------------
1425%% Counter functions
1426%%-----------------------------------------------------------------
1427init_counters() ->
1428    F = fun(Counter) -> maybe_create_counter(Counter) end,
1429    lists:map(F, counters()).
1430
1431reset_counters() ->
1432    F = fun(Counter) -> init_counter(Counter) end,
1433    lists:map(F, counters()).
1434
1435maybe_create_counter(Counter) ->
1436    case ets:lookup(snmp_agent_table, Counter) of
1437	[_] -> ok;
1438	_ -> init_counter(Counter)
1439    end.
1440
1441init_counter(Counter) ->
1442    ets:insert(snmp_agent_table, {Counter, 0}).
1443
1444counters() ->
1445    [
1446     snmpInPkts,
1447     snmpOutPkts,
1448     snmpInBadVersions,
1449     snmpInBadCommunityNames,
1450     snmpInBadCommunityUses,
1451     snmpInASNParseErrs,
1452     snmpInTooBigs,
1453     snmpInNoSuchNames,
1454     snmpInBadValues,
1455     snmpInReadOnlys,
1456     snmpInGenErrs,
1457     snmpInTotalReqVars,
1458     snmpInTotalSetVars,
1459     snmpInGetRequests,
1460     snmpInGetNexts,
1461     snmpInSetRequests,
1462     snmpInGetResponses,
1463     snmpInTraps,
1464     snmpOutTooBigs,
1465     snmpOutNoSuchNames,
1466     snmpOutBadValues,
1467     snmpOutGenErrs,
1468     snmpOutGetRequests,
1469     snmpOutGetNexts,
1470     snmpOutSetRequests,
1471     snmpOutGetResponses,
1472     snmpOutTraps,
1473     snmpSilentDrops,
1474     snmpProxyDrops,
1475     %% From SNMP-MPD-MIB
1476     snmpUnknownSecurityModels,
1477     snmpInvalidMsgs,
1478     snmpUnknownPDUHandlers
1479    ].
1480
1481
1482
1483%%-----------------------------------------------------------------
1484%%  inc(VariableName) increments the variable (Counter) in
1485%%  the local mib. (e.g. snmpInPkts)
1486%%-----------------------------------------------------------------
1487inc(Name)    -> ets:update_counter(snmp_agent_table, Name, 1).
1488inc(Name, N) -> ets:update_counter(snmp_agent_table, Name, N).
1489
1490inc_snmp_in_vars(#pdu{type = Type}) ->
1491    inc_in_type(Type).
1492
1493inc_snmp_cnt_vars(_, #pdu{error_status = ErrStat}) when ErrStat =/= noError ->
1494    ok;
1495inc_snmp_cnt_vars('get-request', #pdu{varbinds = Vbs}) ->
1496    inc(snmpInTotalReqVars, length(Vbs));
1497inc_snmp_cnt_vars('get-next-request', #pdu{varbinds = Vbs}) ->
1498    inc(snmpInTotalReqVars, length(Vbs));
1499inc_snmp_cnt_vars('set-request', #pdu{varbinds = Vbs}) ->
1500    inc(snmpInTotalSetVars, length(Vbs));
1501inc_snmp_cnt_vars(_, _) ->
1502    ok.
1503
1504inc_snmp_out_vars(#pdu{type         = Type,
1505		       error_status = ErrorStatus}) ->
1506    inc(snmpOutPkts),
1507    inc_out_err(ErrorStatus),
1508    inc_out_vars_2(Type);
1509inc_snmp_out_vars(TrapPdu) when is_record(TrapPdu, trappdu) ->
1510    inc(snmpOutPkts),
1511    inc(snmpOutTraps).
1512
1513inc_out_vars_2('get-response')     -> inc(snmpOutGetResponses);
1514inc_out_vars_2('get-request')      -> inc(snmpOutGetRequests);
1515inc_out_vars_2('get-next-request') -> inc(snmpOutGetNexts);
1516inc_out_vars_2('set-request')      -> inc(snmpOutSetRequests);
1517inc_out_vars_2(_)                  -> ok.
1518
1519inc_out_err(genErr)     -> inc(snmpOutGenErrs);
1520inc_out_err(tooBig)     -> inc(snmpOutTooBigs);
1521inc_out_err(noSuchName) -> inc(snmpOutNoSuchNames);
1522inc_out_err(badValue)   -> inc(snmpOutBadValues);
1523% snmpOutReadOnlys is not used any more (rfc1213)
1524%inc_out_err(readOnly) -> inc(snmpOutReadOnlys);
1525inc_out_err(_)          -> ok.
1526
1527inc_in_type('get-request')      -> inc(snmpInGetRequests);
1528inc_in_type('get-next-request') -> inc(snmpInGetNexts);
1529inc_in_type('set-request')      -> inc(snmpInSetRequests);
1530inc_in_type(_)                  -> ok.
1531
1532
1533user_err(F, A) ->
1534    snmpa_error:user_err(F, A).
1535
1536config_err(F, A) ->
1537    snmpa_error:config_err(F, A).
1538