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