1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2004-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
21-module(snmpm_mpd).
22
23-export([init/1,
24
25	 process_msg/7, process_msg/6,
26	 generate_msg/5, generate_response_msg/4,
27
28	 next_msg_id/0,
29	 next_req_id/0,
30
31	 reset/1,
32	 inc/1]).
33
34-define(SNMP_USE_V3, true).
35-include("snmp_types.hrl").
36-include("snmpm_internal.hrl").
37-include("SNMP-MPD-MIB.hrl").
38-include("SNMPv2-TM.hrl").
39
40-define(VMODULE,"MPD").
41-include("snmp_verbosity.hrl").
42
43-define(empty_msg_size, 24).
44
45-record(state, {v1 = false, v2c = false, v3 = false}).
46
47
48%%%-----------------------------------------------------------------
49%%% This module implemets the Message Processing and Dispatch part of
50%%% the multi-lingual SNMP agent.
51%%%
52%%% The MPD is responsible for:
53%%%   *) call the security module (auth/priv).
54%%%   *) decoding the message into a PDU.
55%%%   *) decide a suitable Access Control Model, and provide it with
56%%%      the data it needs.
57%%%   *) maintaining SNMP counters.
58%%%
59%%% In order to take care of the different versions of counters, it
60%%% implements and maintains the union of all SNMP counters (i.e. from
61%%% rfc1213 and from rfc1907).  It is up to the administrator of the
62%%% agent to load the correct MIB.  Note that this module implements
63%%% the counters only, it does not provide instrumentation functions
64%%% for the counters.
65%%%
66%%% With the terms defined in rfc2271, this module implememts part
67%%% of the Dispatcher and the Message Processing functionality.
68%%%-----------------------------------------------------------------
69init(Vsns) ->
70    ?vdebug("init -> entry with ~p", [Vsns]),
71    ?SNMP_RAND_SEED(),
72    %% rand:seed(exrop,
73    %%           {erlang:phash2([node()]),
74    %%            erlang:monotonic_time(),
75    %%            erlang:unique_integer()}),
76    snmpm_config:cre_counter(msg_id, rand:uniform(2147483647)),
77    snmpm_config:cre_counter(req_id, rand:uniform(2147483647)),
78    init_counters(),
79    State = init_versions(Vsns, #state{}),
80    init_usm(State#state.v3),
81    ?vtrace("init -> done when ~p", [State]),
82    State.
83
84reset(#state{v3 = V3}) ->
85    reset_counters(),
86    reset_usm(V3).
87
88
89%%-----------------------------------------------------------------
90%% Func: process_msg(Packet, TDomain, TAddress, State) ->
91%%       {ok, SnmpVsn, Pdu, PduMS, ACMData} | {discarded, Reason}
92%% Types: Packet = binary()
93%%        TDomain = snmpUDPDomain | atom()
94%%        TAddress = {Ip, Udp}
95%%        State = #state
96%% Purpose: This is the main Message Dispatching function. (see
97%%          section 4.2.1 in rfc2272)
98%%-----------------------------------------------------------------
99process_msg(Msg, Domain, Ip, Port, State, NoteStore, Logger) ->
100    process_msg(Msg, Domain, {Ip, Port}, State, NoteStore, Logger).
101
102process_msg(Msg, Domain, Addr, State, NoteStore, Logger) ->
103    inc(snmpInPkts),
104
105    case (catch snmp_pdus:dec_message_only(binary_to_list(Msg))) of
106
107	%% Version 1
108	#message{version = 'version-1', vsn_hdr = Community, data = Data}
109	  when State#state.v1 =:= true ->
110	    HS = ?empty_msg_size + length(Community),
111	    process_v1_v2c_msg(
112	      'version-1', NoteStore, Msg, Domain, Addr,
113	      Community, Data, HS, Logger);
114
115	%% Version 2
116	#message{version = 'version-2', vsn_hdr = Community, data = Data}
117	  when State#state.v2c =:= true ->
118	    HS = ?empty_msg_size + length(Community),
119	    process_v1_v2c_msg(
120	      'version-2', NoteStore, Msg, Domain, Addr,
121	      Community, Data, HS, Logger);
122
123	%% Version 3
124	#message{version = 'version-3', vsn_hdr = H, data = Data}
125	  when State#state.v3 =:= true ->
126	    ?vlog("v3:"
127		"~n   msgID:       ~p"
128		"~n   msgFlags:    ~p"
129		"~n   msgSecModel: ~p",
130		[H#v3_hdr.msgID,H#v3_hdr.msgFlags,H#v3_hdr.msgSecurityModel]),
131	    process_v3_msg(NoteStore, Msg, H, Data, Addr, Logger);
132
133	%% Crap
134	{'EXIT', {bad_version, Vsn}} ->
135	    ?vinfo("exit: bad version: ~p",[Vsn]),
136	    inc(snmpInBadVersions),
137	    {discarded, snmpInBadVersions};
138
139	%% More crap
140	{'EXIT', Reason} ->
141	    ?vinfo("exit: ~p",[Reason]),
142	    inc(snmpInASNParseErrs),
143	    {discarded, Reason};
144
145	%% Really crap
146	Crap ->
147	    ?vinfo("unknown message: "
148		   "~n   ~p",[Crap]),
149	    inc(snmpInBadVersions),
150	    {discarded, snmpInBadVersions}
151    end.
152
153
154%%-----------------------------------------------------------------
155%% Handles a Community based message (v1 or v2c).
156%%-----------------------------------------------------------------
157process_v1_v2c_msg(
158  Vsn, _NoteStore, Msg, Domain, Addr, Community, Data, HS, Log) ->
159
160    ?vdebug("process_v1_v2c_msg -> entry with"
161	    "~n   Vsn:       ~p"
162	    "~n   Domain:    ~p"
163	    "~n   Addr:      ~p"
164	    "~n   Community: ~p"
165	    "~n   HS:        ~p", [Vsn, Domain, Addr, Community, HS]),
166
167    {TDomain, TAddress} =
168	try
169	    {snmp_conf:mk_tdomain(Domain),
170	     snmp_conf:mk_taddress(Domain, Addr)}
171	catch
172	    throw:{error, TReason} ->
173		throw({discarded, {badarg, Domain, TReason}})
174	end,
175
176    Max      = get_max_message_size(),
177    AgentMax = get_agent_max_message_size(Domain, Addr),
178    PduMS    = pdu_ms(Max, AgentMax, HS),
179
180    ?vtrace("process_v1_v2c_msg -> PduMS: ~p", [PduMS]),
181
182    case (catch snmp_pdus:dec_pdu(Data)) of
183	Pdu when is_record(Pdu, pdu) ->
184	    ?vtrace("process_v1_v2c_msg -> was a pdu", []),
185	    Log(Msg),
186	    inc_snmp_in(Pdu),
187	    MsgData = {Community, sec_model(Vsn), TDomain, TAddress},
188	    {ok, Vsn, Pdu, PduMS, MsgData};
189
190	Trap when is_record(Trap, trappdu) ->
191	    ?vtrace("process_v1_v2c_msg -> was a trap", []),
192	    Log(Msg),
193	    inc_snmp_in(Trap),
194	    MsgData = {Community, sec_model(Vsn), TDomain, TAddress},
195	    {ok, Vsn, Trap, PduMS, MsgData};
196
197	{'EXIT', Reason} ->
198	    ?vlog("process_v1_v2c_msg -> failed decoding PDU: "
199		  "~n   Reason: ~p", [Reason]),
200	    inc(snmpInASNParseErrs),
201	    {discarded, Reason}
202    end.
203
204pdu_ms(MgrMMS, AgentMMS, HS) when AgentMMS < MgrMMS ->
205    AgentMMS - HS;
206pdu_ms(MgrMMS, _AgentMMS, HS) ->
207    MgrMMS - HS.
208
209sec_model('version-1') -> ?SEC_V1;
210sec_model('version-2') -> ?SEC_V2C.
211
212
213%%-----------------------------------------------------------------
214%% Handles a SNMPv3 Message, following the procedures in rfc2272,
215%% section 4.2 and 7.2
216%%-----------------------------------------------------------------
217process_v3_msg(NoteStore, Msg, Hdr, Data, Address, Log) ->
218    ?vdebug(
219       "process_v3_msg -> entry with~n"
220       "   Hdr:     ~p~n"
221       "   Address: ~p", [Hdr, Address]),
222
223    %% 7.2.3
224    #v3_hdr{msgID                 = MsgID,
225	    msgMaxSize            = MMS,
226	    msgFlags              = MsgFlags,
227	    msgSecurityModel      = MsgSecModel,
228	    msgSecurityParameters = SecParams,
229	    hdr_size              = HdrSize} = Hdr,
230
231    %% 7.2.4
232    SecModule    = get_security_module(MsgSecModel),
233    ?vtrace("process_v3_msg -> 7.2.4: "
234	    "~n   SecModule: ~p", [SecModule]),
235
236    %% 7.2.5
237    SecLevel     = check_sec_level(MsgFlags),
238    IsReportable = is_reportable(MsgFlags),
239    ?vtrace("process_v3_msg -> 7.2.5: "
240	    "~n   SecLevel:     ~p"
241	    "~n   IsReportable: ~p", [SecLevel, IsReportable]),
242
243    %% 7.2.6
244    SecRes = (catch SecModule:process_incoming_msg(Msg, Data,
245						   SecParams, SecLevel)),
246    ?vtrace("process_v3_msg -> 7.2.6 - message processing result: "
247	    "~n   ~p",[SecRes]),
248    {SecEngineID, SecName, ScopedPDUBytes, SecData} =
249	check_sec_module_result(SecRes, Hdr, Data, IsReportable, Log),
250    ?vtrace("process_v3_msg -> 7.2.6 - checked module result: "
251	    "~n   SecEngineID: ~p"
252	    "~n   SecName:     ~p",[SecEngineID, SecName]),
253
254    %% 7.2.7
255    #scopedPdu{contextEngineID = CtxEngineID,
256	       contextName     = CtxName,
257	       data            = PDU} =
258	case (catch snmp_pdus:dec_scoped_pdu(ScopedPDUBytes)) of
259	    ScopedPDU when is_record(ScopedPDU, scopedPdu) ->
260		ScopedPDU;
261	    {'EXIT', Reason} ->
262		?vlog("failed decoding scoped pdu: "
263		      "~n   ~p",[Reason]),
264		inc(snmpInASNParseErrs),
265		discard(Reason)
266	end,
267
268    ?vlog("7.2.7"
269	  "~n   ContextEngineID: ~p "
270	  "~n   context:         \"~s\" ",
271	  [CtxEngineID, CtxName]),
272    if
273	SecLevel =:= 3 -> % encrypted message - log decrypted pdu
274	    Log({Hdr, ScopedPDUBytes});
275	true -> % otherwise, log binary
276	    Log(Msg)
277    end,
278
279    %% Make sure a get_bulk doesn't get too big.
280    MgrMMS = get_max_message_size(),
281    %% PduMMS is supposed to be the maximum total length of the response
282    %% PDU we can send.  From the MMS, we need to subtract everything before
283    %% the PDU, i.e. Message and ScopedPDU.
284    %%   Message: [48, TotalLen, Vsn, [Tag, LH, Hdr], [Tag, LM, MsgSec], Data]
285    %%             1              3   <----------- HdrSize ----------->
286    %%   HdrSize = everything up to and including msgSecurityParameters.
287    %% ScopedPduData follows.  This is
288    %%   [Tag, Len, [Tag, L1, CtxName], [Tag, L2, CtxEID]]
289    %%   i.e. 6 + length(CtxName) + length(CtxEID)
290    %%
291    %% Total: 1 + TotalLenOctets + 3 + ScopedPduDataLen
292    TotMMS = tot_mms(MgrMMS, MMS),
293    TotalLenOctets = snmp_pdus:get_encoded_length(TotMMS - 1),
294    PduMMS = TotMMS - TotalLenOctets - 10 - HdrSize -
295	length(CtxName) - length(CtxEngineID),
296    ?vtrace("process_v3_msg -> PduMMS = ~p", [PduMMS]),
297    Type = PDU#pdu.type,
298    ?vdebug("process_v3_msg -> PDU type: ~p",[Type]),
299    case Type of
300	report ->
301	    %% 7.2.10 & 11
302	    %% BMK BMK BMK: discovery?
303	    Note = snmp_note_store:get_note(NoteStore, MsgID),
304	    case Note of
305		{SecEngineID, MsgSecModel, SecName, SecLevel,
306		 CtxEngineID, CtxName, _ReqId} ->
307		    ?vtrace("process_v3_msg -> 7.2.11b: ok", []),
308		    %% BMK BMK: Should we discard the cached info
309		    %% BMK BMK: or do we let the gc deal with it?
310 		    {ok, 'version-3', PDU, PduMMS, ok};
311 		_ when is_tuple(Note) ->
312 		    ?vlog("process_v3_msg -> 7.2.11b: error"
313 			  "~n   Note: ~p", [Note]),
314		    Recv  = {SecEngineID, MsgSecModel, SecName, SecLevel,
315			     CtxEngineID, CtxName, PDU#pdu.request_id},
316		    Err   = sec_error(Note, Recv),
317 		    ACM   = {invalid_sec_info, Err},
318		    ReqId = element(size(Note), Note),
319 		    {ok, 'version-3', PDU, PduMMS, {error, ReqId, ACM}};
320		_NoFound ->
321		    ?vtrace("process_v3_msg -> _NoFound: "
322			    "~p", [_NoFound]),
323		    inc(snmpUnknownPDUHandlers),
324		    discard({no_outstanding_req, MsgID})
325	    end;
326
327	'get-response' ->
328	    %% 7.2.10 & 12
329	    case snmp_note_store:get_note(NoteStore, MsgID) of
330		{SecEngineID, MsgSecModel, SecName, SecLevel,
331		 CtxEngineID, CtxName, _} ->
332		    %% 7.2.12.d
333		    {ok, 'version-3', PDU, PduMMS, undefined};
334		_ ->
335		    %% 7.2.12.b
336		    %% BMK BMK: Should we not discard the cached info??
337		    inc(snmpUnknownPDUHandlers),
338		    discard({no_outstanding_req, MsgID})
339	    end;
340
341	'snmpv2-trap' ->
342	    %% 7.2.14
343	    {ok, 'version-3', PDU, PduMMS, undefined};
344
345	'inform-request' ->
346	    %% 7.2.13
347	    SnmpEngineID = get_engine_id(),
348	    case SecEngineID of
349		SnmpEngineID -> % 7.2.13.b
350		    ?vtrace("7.2.13d - valid securityEngineID: ~p",
351			    [SecEngineID]),
352		    %% 4.2.2.1.1 - we don't handle proxys yet => we only
353		    %% handle CtxEngineID to ourselves
354		    %% Check that we actually know of an agent with this
355		    %% CtxEngineID and Address
356		    case is_known_engine_id(CtxEngineID, Address) of
357			true ->
358			    ?vtrace("and the agent EngineID (~p) "
359				    "is know to us", [CtxEngineID]),
360			    %% Uses ACMData that snmpm_acm knows of.
361			    %% BUGBUG BUGBUG
362			    ACMData =
363				{MsgID, MsgSecModel, SecName, SecLevel,
364				 CtxEngineID, CtxName, SecData},
365			    {ok, 'version-3', PDU, PduMMS, ACMData};
366			UnknownEngineID ->
367			    ?vtrace("4.2.2.1.2 - UnknownEngineId: ~p",
368				    [UnknownEngineID]),
369			    %% 4.2.2.1.2
370			    NIsReportable = snmp_misc:is_reportable_pdu(Type),
371			    Val = inc(snmpUnknownPDUHandlers),
372			    ErrorInfo =
373				{#varbind{oid = ?snmpUnknownPDUHandlers,
374					  variabletype = 'Counter32',
375					  value = Val},
376				 SecName,
377				 [{securityLevel,   SecLevel},
378				  {contextEngineID, CtxEngineID},
379				  {contextName,     CtxName}]},
380			    case generate_v3_report_msg(MsgID,
381							MsgSecModel,
382							Data,
383							ErrorInfo,
384							Log) of
385				{ok, Report} when NIsReportable =:= true ->
386				    discard(snmpUnknownPDUHandlers, Report);
387				_ ->
388				    discard(snmpUnknownPDUHandlers)
389			    end
390		    end;
391		_ -> % 7.2.13.a
392		    ?vinfo("7.2.13a - invalid securityEngineID: ~p",
393			   [SecEngineID]),
394		    discard({badSecurityEngineID, SecEngineID})
395	    end;
396
397	_ ->
398	    %% 7.2.13 - This would be the requests which we should not
399	    %%          receive since we are a manager, barring possible
400	    %%          proxy...
401	    discard(Type)
402    end.
403
404
405sec_error(T1, T2)
406  when is_tuple(T1) andalso is_tuple(T2) andalso (size(T1) =:= size(T2)) ->
407    Tags = {sec_engine_id, msg_sec_model, sec_name, sec_level,
408	    ctx_engine_id, ctx_name, request_id},
409    sec_error(size(T1), T1, T2, Tags, []);
410sec_error(T1, T2) ->
411    [{internal_error, T1, T2}].
412
413sec_error(0, _T1, _T2, _Tags, Acc) ->
414    Acc;
415sec_error(Idx, T1, T2, Tags, Acc) ->
416    case element(Idx, T1) =:= element(Idx, T2) of
417	true ->
418	    sec_error(Idx - 1, T1, T2, Tags, Acc);
419	false ->
420	    Elem = {element(Idx, Tags), element(Idx, T1), element(Idx, T2)},
421	    sec_error(Idx - 1, T1, T2, Tags, [Elem|Acc])
422    end.
423
424tot_mms(MgrMMS, AgentMMS) when MgrMMS > AgentMMS -> AgentMMS;
425tot_mms(MgrMMS, _AgentMMS) -> MgrMMS.
426
427get_security_module(?SEC_USM) ->
428    snmpm_usm;
429get_security_module(_) ->
430    inc(snmpUnknownSecurityModels),
431    discard(snmpUnknownSecurityModels).
432
433check_sec_level([MsgFlag]) ->
434    SecLevel = MsgFlag band 3,
435    if
436	SecLevel == 2 ->
437	    inc(snmpInvalidMsgs),
438	    discard(snmpInvalidMsgs);
439	true ->
440	    SecLevel
441    end;
442check_sec_level(_Unknown) ->
443    inc(snmpInvalidMsgs),
444    discard(snmpInvalidMsgs).
445
446is_reportable([MsgFlag]) ->
447    4 == (MsgFlag band 4).
448
449
450check_sec_module_result({ok, X}, _, _, _, _) ->
451    X;
452check_sec_module_result({error, Reason, Info}, _, _, _, _)
453  when is_list(Info) ->
454    %% case 7.2.6 b
455    discard({securityError, Reason, Info});
456check_sec_module_result({error, Reason, ErrorInfo}, V3Hdr, Data, true, Log) ->
457    %% case 7.2.6 a
458    ?vtrace("security module result:"
459	    "~n   Reason:    ~p"
460	    "~n   ErrorInfo: ~p", [Reason, ErrorInfo]),
461    #v3_hdr{msgID = MsgID, msgSecurityModel = MsgSecModel} = V3Hdr,
462    Pdu = get_scoped_pdu(Data),
463    case generate_v3_report_msg(MsgID, MsgSecModel, Pdu, ErrorInfo, Log) of
464	{ok, Report} ->
465	    discard({securityError, Reason}, Report);
466	{discarded, _SomeOtherReason} ->
467	    discard({securityError, Reason})
468    end;
469check_sec_module_result({error, Reason, _ErrorInfo}, _, _, _, _) ->
470    ?vtrace("security module result:"
471	    "~n   Reason:     ~p"
472	    "~n   _ErrorInfo: ~p", [Reason, _ErrorInfo]),
473    discard({securityError, Reason});
474check_sec_module_result(Res, _, _, _, _) ->
475    ?vtrace("security module result:"
476	    "~n   Res: ~p", [Res]),
477    discard({securityError, Res}).
478
479get_scoped_pdu(D) when is_list(D) ->
480    (catch snmp_pdus:dec_scoped_pdu(D));
481get_scoped_pdu(D) ->
482    D.
483
484
485%%-----------------------------------------------------------------
486%% Generate a message
487%%-----------------------------------------------------------------
488generate_msg('version-3', NoteStore, Pdu,
489	     {SecModel, SecName, SecLevel, CtxEngineID, CtxName,
490	      TargetName}, Log) ->
491    generate_v3_msg(NoteStore, Pdu,
492		    SecModel, SecName, SecLevel, CtxEngineID, CtxName,
493		    TargetName, Log);
494generate_msg(Vsn, _NoteStore, Pdu, {Comm, _SecModel}, Log) ->
495    generate_v1_v2c_msg(Vsn, Pdu, Comm, Log).
496
497
498generate_v3_msg(NoteStore, Pdu,
499		SecModel, SecName, SecLevel, CtxEngineID, CtxName,
500		TargetName, Log) ->
501    %% rfc2272: 7.1.6
502    ?vdebug("generate_v3_msg -> 7.1.6", []),
503    ScopedPDU = #scopedPdu{contextEngineID = CtxEngineID,
504			   contextName     = CtxName,
505			   data            = Pdu},
506    case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of
507	{'EXIT', Reason} ->
508	    user_err("failed encoding scoped pdu "
509		     "~n   pdu: ~w"
510		     "~n   contextName: ~w"
511		     "~n   reason: ~w", [Pdu, CtxName, Reason]),
512	    {discarded, Reason};
513	ScopedPDUBytes ->
514	    {ok, generate_v3_msg(NoteStore, Pdu, ScopedPDUBytes,
515				 SecModel, SecName, SecLevel,
516				 CtxEngineID, CtxName, TargetName, Log)}
517    end.
518
519generate_v3_msg(NoteStore,
520		#pdu{type = Type} = Pdu, ScopedPduBytes,
521		SecModel, SecName, SecLevel, CtxEngineID, CtxName,
522		TargetName, Log) ->
523    %% 7.1.7
524    ?vdebug("generate_v3_msg -> 7.1.7", []),
525    MsgID     = next_msg_id(),
526    MsgFlags  = snmp_misc:mk_msg_flags(Type, SecLevel),
527    V3Hdr     = #v3_hdr{msgID            = MsgID,
528			msgMaxSize       = get_max_message_size(),
529			msgFlags         = MsgFlags,
530			msgSecurityModel = SecModel},
531    Message   = #message{version = 'version-3',
532			 vsn_hdr = V3Hdr,
533			 data    = ScopedPduBytes},
534    SecModule = sec_module(SecModel),
535
536    %% 7.1.9a
537    ?vdebug("generate_v3_msg -> 7.1.9a", []),
538    SecEngineID = sec_engine_id(TargetName),
539    ?vtrace("SecEngineID: ~p", [SecEngineID]),
540    %% 7.1.9b
541    ?vdebug("generate_v3_msg -> 7.1.9b", []),
542    case generate_v3_outgoing_msg(Message, SecModule, SecEngineID,
543				  SecName, [], SecLevel) of
544	{ok, Packet} ->
545	    %% 7.1.9c
546	    %% Store in cache for 150 sec.
547	    ?vdebug("generate_v3_msg -> 7.1.9c", []),
548	    %% The request id is just in the case when we receive a
549	    %% report with incorrect securityModel and/or securityLevel
550	    CacheVal = {SecEngineID, SecModel, SecName, SecLevel,
551			CtxEngineID, CtxName, Pdu#pdu.request_id},
552	    snmp_note_store:set_note(NoteStore, 1500, MsgID, CacheVal),
553	    Log(Packet),
554	    inc_snmp_out(Pdu),
555	    ?vdebug("generate_v3_msg -> done", []),
556	    Packet;
557
558	Error ->
559	    throw(Error)
560    end.
561
562
563sec_module(?SEC_USM) ->
564    snmpm_usm.
565
566%% 9) If the PDU is a GetRequest-PDU, GetNextRequest-PDU,
567%%    GetBulkRequest-PDU, SetRequest-PDU, InformRequest-PDU, or or
568%%    SNMPv2-Trap-PDU, then
569%%
570%%    a) If the PDU is an SNMPv2-Trap-PDU, then securityEngineID is set
571%%       to the value of this entity's snmpEngineID.
572%%
573%%       Otherwise, the snmpEngineID of the target entity is determined,
574%%       in an implementation-dependent manner, possibly using
575%%       transportDomain and transportAddress.  The value of
576%%       securityEngineID is set to the value of the target entity's
577%%       snmpEngineID.
578%%
579%% As we never send traps, the SecEngineID is allways the
580%% snmpEngineID of the target entity!
581sec_engine_id(TargetName) ->
582    case get_agent_engine_id(TargetName) of
583	{ok, EngineId} ->
584	    EngineId;
585	_ ->
586	    config_err("Can't find engineID for "
587		       "snmpTargetAddrName ~p", [TargetName]),
588	    %% this will trigger error in secmodule
589	    ""
590	end.
591
592
593%% BMK BMK BMK
594%% This one looks very similar to lik generate_v1_v2c_response_msg!
595%% Common/shared? Should there be differences?
596%%
597generate_v1_v2c_msg(Vsn, Pdu, Community, Log) ->
598    ?vdebug("generate_v1_v2c_msg -> encode pdu", []),
599    case (catch snmp_pdus:enc_pdu(Pdu)) of
600	{'EXIT', Reason} ->
601	    user_err("failed encoding pdu: "
602		     "(pdu: ~w, community: ~w): ~n~w",
603		     [Pdu, Community, Reason]),
604	    {discarded, Reason};
605	PduBytes ->
606	    MMS     = get_max_message_size(),
607	    Message = #message{version = Vsn,
608			       vsn_hdr = Community,
609			       data    = PduBytes},
610	    case generate_v1_v2c_outgoing_msg(Message) of
611		{error, Reason} ->
612		    user_err("failed encoding message "
613			     "(pdu: ~w, community: ~w): ~n~w",
614			     [Pdu, Community, Reason]),
615		    {discarded, Reason};
616		{ok, Packet} when size(Packet) =< MMS ->
617		    Log(Packet),
618		    inc_snmp_out(Pdu),
619		    {ok, Packet};
620		{ok, Packet} ->
621		    ?vlog("packet max size exceeded: "
622			  "~n   MMS: ~p"
623			  "~n   Len: ~p",
624			  [MMS, size(Packet)]),
625		    {discarded, tooBig}
626	    end
627    end.
628
629
630
631%% -----------------------------------------------------------------------
632
633generate_response_msg('version-3', Pdu,
634		      {MsgID, SecModel, SecName, SecLevel,
635		       CtxEngineID, CtxName, SecData}, Log) ->
636    generate_v3_response_msg(Pdu, MsgID, SecModel, SecName, SecLevel,
637			     CtxEngineID, CtxName, SecData, Log);
638generate_response_msg(Vsn, Pdu, {Comm, _SecModel}, Log) ->
639    generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log);
640generate_response_msg(Vsn, Pdu, {Comm, _SecModel, _TDomain, _TAddress}, Log) ->
641    generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log).
642
643
644generate_v3_response_msg(#pdu{type = Type} = Pdu, MsgID,
645			 SecModel, SecName, SecLevel,
646			 CtxEngineID, CtxName, SecData, Log) ->
647    %% rfc2272: 7.1 steps 6-8
648    ScopedPdu = #scopedPdu{contextEngineID = CtxEngineID,
649			   contextName     = CtxName,
650			   data            = Pdu},
651    case (catch snmp_pdus:enc_scoped_pdu(ScopedPdu)) of
652	{'EXIT', Reason} ->
653	    user_err("failed encoded scoped pdu "
654		     "(pdu: ~w, contextName: ~w): ~n~w",
655		     [Pdu, CtxName, Reason]),
656	    {discarded, Reason};
657	ScopedPduBytes ->
658	    MMS      = get_max_message_size(),
659	    MsgFlags = snmp_misc:mk_msg_flags(Type, SecLevel),
660	    V3Hdr    = #v3_hdr{msgID            = MsgID,
661			       msgMaxSize       = MMS,
662			       msgFlags         = MsgFlags,
663			       msgSecurityModel = SecModel},
664	    Message  = #message{version = 'version-3',
665				vsn_hdr = V3Hdr,
666				data    = ScopedPduBytes},
667	    %% We know that the security model is valid when we
668	    %% generate a response.
669	    SecModule = sec_module(SecModel),
670	    SecEngineID = get_engine_id(),
671	    case generate_v3_outgoing_msg(Message, SecModule, SecEngineID,
672					  SecName, SecData, SecLevel) of
673		%% Check the packet size. Send the msg even
674		%% if it's larger than the agent can handle -
675		%% it will be dropped. Just check against the
676		%% internal size.
677		{ok, Packet} when size(Packet) =< MMS ->
678		    if
679			SecLevel == 3 ->
680			    %% encrypted - log decrypted pdu
681			    Log({V3Hdr, ScopedPduBytes});
682			true ->
683			    %% otherwise log the entire msg
684			    Log(Packet)
685		    end,
686		    inc_snmp_out(Pdu),
687		    {ok, Packet};
688
689		{ok, _Packet} when Pdu#pdu.error_status =:= tooBig ->
690		    ?vlog("packet max size exceeded (tooBog): "
691			  "~n   MMS: ~p", [MMS]),
692		    inc(snmpSilentDrops),
693		    {discarded, tooBig};
694		{ok, _Packet} ->
695		    ?vlog("packet max size exceeded: "
696			  "~n   MMS: ~p", [MMS]),
697		    TooBigPdu = Pdu#pdu{error_status = tooBig,
698					error_index  = 0,
699					varbinds     = []},
700		    generate_v3_response_msg(TooBigPdu, MsgID,
701					     SecModel, SecName, SecLevel,
702					     CtxEngineID,
703					     CtxName,
704					     SecData, Log);
705		Error ->
706		    Error
707	    end
708    end.
709
710
711generate_v3_outgoing_msg(Message,
712			 SecModule, SecEngineID, SecName, SecData, SecLevel) ->
713    case (catch SecModule:generate_outgoing_msg(Message,
714						SecEngineID,
715						SecName, SecData,
716						SecLevel)) of
717	{'EXIT', Reason} ->
718	    config_err("~p (message: ~p)", [Reason, Message]),
719	    {discarded, Reason};
720	{error, Reason} ->
721	    config_err("~p (message: ~p)", [Reason, Message]),
722	    {discarded, Reason};
723	Bin when is_binary(Bin) ->
724	    {ok, Bin};
725	OutMsg when is_list(OutMsg) ->
726	    case (catch list_to_binary(OutMsg)) of
727		Bin when is_binary(Bin) ->
728		    {ok, Bin};
729		{'EXIT', Reason} ->
730		    {error, Reason}
731	    end
732    end.
733
734
735generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log) ->
736    case (catch snmp_pdus:enc_pdu(Pdu)) of
737	{'EXIT', Reason} ->
738	    user_err("failed encoding pdu: "
739		     "(pdu: ~w, community: ~w): ~n~w",
740		     [Pdu, Comm, Reason]),
741	    {discarded, Reason};
742	PduBytes ->
743	    MMS = get_max_message_size(),
744	    Message = #message{version = Vsn,
745			       vsn_hdr = Comm,
746			       data    = PduBytes},
747	    case generate_v1_v2c_outgoing_msg(Message) of
748		{error, Reason} ->
749		    user_err("failed encoding message only "
750			     "(pdu: ~w, community: ~w): ~n~w",
751			     [Pdu, Comm, Reason]),
752		    {discarded, Reason};
753
754		{ok, Packet} when size(Packet) =< MMS ->
755		    Log(Packet),
756		    inc_snmp_out(Pdu),
757		    {ok, Packet};
758
759		{ok, Packet} ->  %% Too big
760		    too_big(Vsn, Pdu, Comm, MMS, size(Packet), Log)
761	    end
762    end.
763
764
765too_big('version-1' = Vsn, #pdu{type = 'get-response'} = Pdu,
766	Comm, _MMS, _Len, Log) ->
767    %% In v1, the varbinds should be identical to the incoming
768    %% request. It isn't identical now! Make acceptable (?)
769    %% approximation.
770    V = set_vb_null(Pdu#pdu.varbinds),
771    TooBigPdu = Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = V},
772    too_big(Vsn, TooBigPdu, Comm, Log);
773too_big('version-2' = Vsn, #pdu{type = 'get-response'} = Pdu,
774	Comm, _MMS, _Len, Log) ->
775    %% In v2, varbinds should be empty (reasonable!)
776    TooBigPdu = Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = []},
777    too_big(Vsn, TooBigPdu, Comm, Log);
778too_big(_Vsn, Pdu, _Comm, _Log, MMS, Len) ->
779    user_err("encoded pdu, ~p bytes, exceeded "
780	     "max message size of ~p bytes. Pdu: ~n~w",
781	     [Len, MMS, Pdu]),
782    {discarded, tooBig}.
783
784
785too_big(Vsn, Pdu, Comm, Log) ->
786    case (catch snmp_pdus:enc_pdu(Pdu)) of
787	{'EXIT', Reason} ->
788	    user_err("failed encoding pdu "
789		     "(pdu: ~w, community: ~w): ~n~w",
790		     [Pdu, Comm, Reason]),
791	    {discarded, Reason};
792	PduBytes ->
793	    Message = #message{version = Vsn,
794			       vsn_hdr = Comm,
795			       data    = PduBytes},
796	    case generate_v1_v2c_outgoing_msg(Message) of
797		{error, Reason} ->
798		    user_err("failed encoding message only"
799			     "(pdu: ~w, community: ~w): ~n~w",
800			     [Pdu, Comm, Reason]),
801		    {discarded, Reason};
802		{ok, Bin} ->
803		    Log(Bin),
804		    inc_snmp_out(Pdu),
805		    {ok, Bin}
806	    end
807    end.
808
809set_vb_null(Vbs) ->
810    [Vb#varbind{variabletype = 'NULL', value = 'NULL'} || Vb <- Vbs].
811
812
813generate_v1_v2c_outgoing_msg(Message) ->
814    ?vdebug("generate_v1_v2c_outgoing_msg -> encode message", []),
815    case (catch snmp_pdus:enc_message_only(Message)) of
816	{'EXIT', Reason} ->
817	    {error, Reason};
818	Bin when is_binary(Bin) ->
819	    {ok, Bin};
820	Packet when is_list(Packet) ->
821	    case (catch list_to_binary(Packet)) of
822		Bin when is_binary(Bin) ->
823		    {ok, Bin};
824		{'EXIT', Reason} ->
825		    {error, Reason}
826	    end
827    end.
828
829
830
831generate_v3_report_msg(MsgID, SecModel, ScopedPdu, ErrInfo, Log)
832  when is_record(ScopedPdu, scopedPdu) ->
833    ReqID = (ScopedPdu#scopedPdu.data)#pdu.request_id,
834    generate_v3_report_msg2(MsgID, ReqID, SecModel, ErrInfo, Log);
835generate_v3_report_msg(MsgID, SecModel, _, ErrInfo, Log) ->
836    %% RFC2572, 7.1.3.c.4
837    generate_v3_report_msg2(MsgID, 0, SecModel, ErrInfo, Log).
838
839
840generate_v3_report_msg2(MsgID, ReqID, SecModel, ErrInfo, Log) ->
841    {Varbind, SecName, Opts} = ErrInfo,
842    Pdu = #pdu{type         = report,
843	       request_id   = ReqID,
844	       error_status = noError,
845	       error_index  = 0,
846	       varbinds     = [Varbind]},
847    SecLevel    = snmp_misc:get_option(securityLevel, Opts, 0),
848    CtxEngineID = snmp_misc:get_option(contextEngineID, Opts, get_engine_id()),
849    CtxName     = snmp_misc:get_option(contextName, Opts, ""),
850    SecData     = snmp_misc:get_option(sec_data, Opts, []),
851    generate_v3_response_msg(Pdu,
852			     MsgID, SecModel, SecName, SecLevel,
853			     CtxEngineID, CtxName, SecData, Log).
854
855
856%%-----------------------------------------------------------------
857
858%% Get "our" (manager) MMS
859get_max_message_size() ->
860    case snmpm_config:get_engine_max_message_size() of
861	{ok, MMS} ->
862	    MMS;
863	E ->
864	    user_err("failed retreiving engine max message size: ~w", [E]),
865	    484
866    end.
867
868%% The the MMS of the agent
869get_agent_max_message_size(Domain, Addr) ->
870    case snmpm_config:get_agent_engine_max_message_size(Domain, Addr) of
871	{ok, MMS} ->
872	    MMS;
873	_Error ->
874            TAddr = fun(TN) ->
875                            case snmpm_config:agent_info(TN, taddress) of
876                                {ok, TA} ->
877                                    TA;
878                                {error, _} ->
879                                    undefined
880                            end
881                    end,
882            KnownAgents =
883                [{TargetName, TAddr(TargetName)} ||
884                    TargetName <- snmpm_config:which_agents()],
885	    ?vlog("[agent engine max msg size lookup] unknown agent: ~s"
886                  "~n      Known Agents: ~p",
887		  [snmp_conf:mk_addr_string({Domain, Addr}), KnownAgents]),
888	    get_max_message_size()
889    end.
890%% get_agent_max_message_size(Addr, Port) ->
891%%     case snmpm_config:get_agent_engine_max_message_size(Addr, Port) of
892%% 	{ok, MMS} ->
893%% 	    MMS;
894%% 	_Error ->
895%% 	    ?vlog("unknown agent: ~w:~w", [Addr, Port]),
896%% 	    get_max_message_size()
897%%     end.
898
899%% Get "our" (manager) engine id
900get_engine_id() ->
901    case snmpm_config:get_engine_id() of
902	{ok, Id} ->
903	    Id;
904	_Error ->
905	    ""
906    end.
907
908%% The engine id of the agent
909get_agent_engine_id(Name) ->
910    snmpm_config:get_agent_engine_id(Name).
911
912is_known_engine_id(EngineID, {Addr, Port}) ->
913    snmpm_config:is_known_engine_id(EngineID, Addr, Port).
914
915
916%%-----------------------------------------------------------------
917%% Sequence number (msg-id & req-id) functions
918%%-----------------------------------------------------------------
919next_msg_id() ->
920    next_id(msg_id).
921
922next_req_id() ->
923    next_id(req_id).
924
925next_id(Id) ->
926    snmpm_config:incr_counter(Id, 1).
927
928
929%%-----------------------------------------------------------------
930%% Version(s) functions
931%%-----------------------------------------------------------------
932init_versions([], S) ->
933    S;
934init_versions([v1|Vsns], S) ->
935    init_versions(Vsns, S#state{v1 = true});
936init_versions([v2|Vsns], S) ->
937    init_versions(Vsns, S#state{v2c = true});
938init_versions([v3|Vsns], S) ->
939    init_versions(Vsns, S#state{v3 = true}).
940
941init_usm(true) ->
942    snmpm_usm:init();
943init_usm(_) ->
944    ok.
945
946
947%%-----------------------------------------------------------------
948%% Counter functions
949%%-----------------------------------------------------------------
950init_counters() ->
951    F = fun(Counter) -> maybe_create_counter(Counter) end,
952    lists:map(F, counters()).
953
954reset_counters() ->
955    F = fun(Counter) -> snmpm_config:reset_stats_counter(Counter) end,
956    lists:map(F, counters()).
957
958reset_usm(true) ->
959    snmpm_usm:reset();
960reset_usm(_) ->
961    ok.
962
963maybe_create_counter(Counter) ->
964    snmpm_config:maybe_cre_stats_counter(Counter, 0).
965
966counters() ->
967    [snmpInPkts,
968     snmpOutPkts,
969     snmpInBadVersions,
970     snmpInBadCommunityNames,
971     snmpInBadCommunityUses,
972     snmpInASNParseErrs,
973     snmpInTooBigs,
974     snmpInNoSuchNames,
975     snmpInBadValues,
976     snmpInReadOnlys,
977     snmpInGenErrs,
978     snmpInTotalReqVars,
979     snmpInTotalSetVars,
980     snmpInGetRequests,
981     snmpInGetNexts,
982     snmpInSetRequests,
983     snmpInGetResponses,
984     snmpInTraps,
985     snmpOutTooBigs,
986     snmpOutNoSuchNames,
987     snmpOutBadValues,
988     snmpOutGenErrs,
989     snmpOutGetRequests,
990     snmpOutGetNexts,
991     snmpOutSetRequests,
992     snmpOutGetResponses,
993     snmpOutTraps,
994     snmpSilentDrops,
995     snmpProxyDrops,
996     %% From SNMP-MPD-MIB
997     snmpUnknownSecurityModels,
998     snmpInvalidMsgs,
999     snmpUnknownPDUHandlers
1000    ].
1001
1002
1003%%-----------------------------------------------------------------
1004%%  inc(VariableName) increments the variable (Counter) in
1005%%  the local mib. (e.g. snmpInPkts)
1006%%-----------------------------------------------------------------
1007inc(Name)    -> inc(Name, 1).
1008inc(Name, N) -> snmpm_config:incr_stats_counter(Name, N).
1009
1010inc_snmp_in(#pdu{type = Type}) ->
1011    inc_in_type(Type);
1012inc_snmp_in(TrapPdu) when is_record(TrapPdu, trappdu) ->
1013    inc(snmpInPkts),
1014    inc(snmpInTraps).
1015
1016inc_snmp_out(#pdu{type         = Type,
1017		  error_status = ErrorStatus}) ->
1018    inc(snmpOutPkts),
1019    inc_out_err(ErrorStatus),
1020    inc_out_type(Type).
1021
1022inc_out_type('get-request')      -> inc(snmpOutGetRequests);
1023inc_out_type('get-next-request') -> inc(snmpOutGetNexts);
1024inc_out_type('set-request')      -> inc(snmpOutSetRequests);
1025inc_out_type(_) -> ok.
1026
1027inc_out_err(genErr)     -> inc(snmpOutGenErrs);
1028inc_out_err(tooBig)     -> inc(snmpOutTooBigs);
1029inc_out_err(noSuchName) -> inc(snmpOutNoSuchNames);
1030inc_out_err(badValue)   -> inc(snmpOutBadValues);
1031inc_out_err(_)          -> ok.
1032
1033inc_in_type('get-response') -> inc(snmpInGetResponses);
1034inc_in_type(_)              -> ok.
1035
1036
1037%%-----------------------------------------------------------------
1038
1039discard(Reason) ->
1040    throw({discarded, Reason}).
1041
1042discard(Reason, Report) ->
1043    throw({discarded, Reason, Report}).
1044
1045user_err(F, A) ->
1046    error_msg("USER ERROR: " ++ F ++ "~n", A).
1047
1048config_err(F, A) ->
1049    error_msg("CONFIG ERROR: " ++ F ++ "~n", A).
1050
1051error_msg(F, A) ->
1052    ?snmpm_error("MPD: " ++ F, A).
1053