1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1999-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%% AES: RFC 3826
21%%
22
23-module(snmpa_usm).
24
25%% Avoid warning for local function error/1 clashing with autoimported BIF.
26-compile({no_auto_import,[error/1]}).
27%% Avoid warning for local function error/2 clashing with autoimported BIF.
28-compile({no_auto_import,[error/2]}).
29-export([
30	 process_incoming_msg/4, process_incoming_msg/5,
31	 generate_outgoing_msg/5, generate_outgoing_msg/6,
32	 generate_discovery_msg/4, generate_discovery_msg/5,
33	 current_statsNotInTimeWindows_vb/0
34	]).
35
36-define(SNMP_USE_V3, true).
37-include("snmp_types.hrl").
38-include("SNMP-USER-BASED-SM-MIB.hrl").
39-include("SNMP-USM-AES-MIB.hrl").
40-include("SNMPv2-TC.hrl").
41
42-define(VMODULE,"A-USM").
43-include("snmp_verbosity.hrl").
44-include("snmpa_internal.hrl").
45
46
47%%-----------------------------------------------------------------
48%% This module implements the User Based Security Model for SNMP,
49%% as defined in rfc2274.
50%%-----------------------------------------------------------------
51
52%% Columns not accessible via SNMP
53-define(usmUserAuthKey, 14).
54-define(usmUserPrivKey, 15).
55
56-define(i32(Int), (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255).
57-define(i64(Int), (Int bsr 56) band 255, (Int bsr 48) band 255, (Int bsr 40) band 255, (Int bsr 32) band 255, (Int bsr 24) band 255, (Int bsr 16) band 255, (Int bsr 8) band 255, Int band 255).
58
59
60%%-----------------------------------------------------------------
61%% Func: process_incoming_msg(Packet, Data, SecParams, SecLevel) ->
62%%       {ok, {SecEngineID, SecName, ScopedPDUBytes, SecData}} |
63%%       {error, Reason} | {error, Reason, ErrorInfo}
64%%       Return value may be throwed.
65%% Types: Reason -> term()
66%% Purpose:
67%%-----------------------------------------------------------------
68
69process_incoming_msg(Packet, Data, SecParams, SecLevel) ->
70    LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
71    process_incoming_msg(Packet, Data, SecParams, SecLevel, LocalEngineID).
72
73process_incoming_msg(Packet, Data, SecParams, SecLevel, LocalEngineID) ->
74    TermDiscoEnabled    = is_terminating_discovery_enabled(),
75    TermTriggerUsername = terminating_trigger_username(),
76    %% 3.2.1
77    ?vtrace("process_incoming_msg -> check security parms: 3.2.1",[]),
78    UsmSecParams =
79	case catch snmp_pdus:dec_usm_security_parameters(SecParams) of
80	    {'EXIT', Reason} ->
81		inc(snmpInASNParseErrs),
82		error({parseError, Reason}, []);
83	    Res ->
84		Res
85	end,
86    case UsmSecParams of
87	#usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID,
88			       msgUserName              = TermTriggerUsername} when TermDiscoEnabled =:= true ->
89	    %% Step 1 discovery message
90	    ?vtrace("process_incoming_msg -> [~p] discovery step 1",
91		    [TermTriggerUsername]),
92	    process_discovery_msg(MsgAuthEngineID, Data, SecLevel);
93
94	#usmSecurityParameters{msgAuthoritativeEngineID = MsgAuthEngineID,
95			       msgUserName = MsgUserName} ->
96	    ?vlog("process_incoming_msg -> USM security parms: "
97		  "~n   msgAuthEngineID: ~w"
98		  "~n   userName:        ~p", [MsgAuthEngineID, MsgUserName]),
99	    %% 3.2.3
100	    ?vtrace("process_incoming_msg -> check engine id: 3.2.3",[]),
101	    case snmp_user_based_sm_mib:is_engine_id_known(MsgAuthEngineID) of
102		true ->
103		    ok;
104		false ->
105		    SecData1 = [MsgUserName],
106		    error(usmStatsUnknownEngineIDs,
107			  ?usmStatsUnknownEngineIDs_instance, %% OTP-3542
108			  undefined, [{sec_data, SecData1}])
109	    end,
110	    %% 3.2.4
111	    ?vtrace("process_incoming_msg -> retrieve usm user: 3.2.4",[]),
112	    UsmUser =
113		case snmp_user_based_sm_mib:get_user(MsgAuthEngineID,
114						     MsgUserName) of
115		    User when element(?usmUserStatus, User) =:= ?'RowStatus_active' ->
116			User;
117		    {_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} ->
118			?vdebug("process_incoming_msg -> "
119				"found user ~p with wrong row status: ~p",
120				[Name, RowStatus]),
121			SecData2 = [MsgUserName],
122			error(usmStatsUnknownUserNames,
123			      ?usmStatsUnknownUserNames_instance, %% OTP-3542
124			      undefined, [{sec_data, SecData2}]);
125		    _ -> % undefined or not active user
126			SecData2 = [MsgUserName],
127			error(usmStatsUnknownUserNames,
128			      ?usmStatsUnknownUserNames_instance, %% OTP-3542
129			      undefined, [{sec_data, SecData2}])
130		end,
131	    SecName = element(?usmUserSecurityName, UsmUser),
132	    ?vtrace("process_incoming_msg -> securityName: ~p",[SecName]),
133	    %% 3.2.5 - implicit in following checks
134	    %% 3.2.6 - 3.2.7
135	    ?vtrace("process_incoming_msg -> "
136		    "authenticate incoming: 3.2.5 - 3.2.7"
137		    "~n   ~p",[UsmUser]),
138	    DiscoOrPlain = authenticate_incoming(Packet,
139						 UsmSecParams, UsmUser,
140						 SecLevel, LocalEngineID),
141	    %% 3.2.8
142	    ?vtrace("process_incoming_msg -> "
143		    "decrypt scoped data: 3.2.8",[]),
144	    ScopedPDUBytes =
145		decrypt(Data, UsmUser, UsmSecParams, SecLevel),
146	    %% 3.2.9
147	    %% Means that if AuthKey/PrivKey are changed;
148	    %% the old values will be used.
149	    ?vtrace("process_incoming_msg -> "
150		    "AuthKey/PrivKey are changed - "
151		    "use old values: 3.2.9",[]),
152	    CachedSecData = {MsgUserName,
153			     element(?usmUserAuthProtocol, UsmUser),
154			     element(?usmUserPrivProtocol, UsmUser),
155			     element(?usmUserAuthKey, UsmUser),
156			     element(?usmUserPrivKey, UsmUser)},
157	    {ok, {MsgAuthEngineID, SecName, ScopedPDUBytes,
158		  CachedSecData, DiscoOrPlain}}
159    end.
160
161%% Process a step 1 discovery message
162process_discovery_msg(MsgAuthEngineID, Data, SecLevel) ->
163    ?vtrace("process_discovery_msg -> entry with"
164	    "~n   Data:     ~p"
165	    "~n   SecLevel: ~p", [Data, SecLevel]),
166    case (not snmp_misc:is_priv(SecLevel)) of
167	true -> % noAuthNoPriv
168	    ?vtrace("process_discovery_msg -> noAuthNoPriv", []),
169	    ScopedPDUBytes = Data,
170	    SecData = {"", usmNoAuthProtocol, "", usmNoPrivProtocol, ""},
171	    NewData = {SecData,
172		       ?usmStatsUnknownEngineIDs_instance,
173		       get_counter(usmStatsUnknownEngineIDs)},
174	    {ok, {MsgAuthEngineID, "", ScopedPDUBytes, NewData, discovery}};
175	false ->
176	    error(usmStatsUnknownEngineIDs,
177		  ?usmStatsUnknownEngineIDs_instance,
178		  undefined, [{sec_data, ""}])
179    end.
180
181
182authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel,
183		      LocalEngineID) ->
184    %% 3.2.6
185    ?vtrace("authenticate_incoming -> 3.2.6", []),
186    AuthProtocol = element(?usmUserAuthProtocol, UsmUser),
187    #usmSecurityParameters{msgAuthoritativeEngineID    = MsgAuthEngineID,
188			   msgAuthoritativeEngineBoots = MsgAuthEngineBoots,
189			   msgAuthoritativeEngineTime  = MsgAuthEngineTime,
190			   msgAuthenticationParameters = MsgAuthParams} =
191	UsmSecParams,
192    ?vtrace("authenticate_incoming -> Sec params: "
193	    "~n   MsgAuthEngineID:    ~w"
194	    "~n   MsgAuthEngineBoots: ~p"
195	    "~n   MsgAuthEngineTime:  ~p",
196	    [MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime]),
197    case snmp_misc:is_auth(SecLevel) of
198	true ->
199	    SecName = element(?usmUserSecurityName, UsmUser),
200	    case is_auth(AuthProtocol,
201			 element(?usmUserAuthKey, UsmUser),
202			 MsgAuthParams,
203			 Packet,
204			 SecName,
205			 MsgAuthEngineID,
206			 MsgAuthEngineBoots,
207			 MsgAuthEngineTime,
208			 LocalEngineID) of
209		discovery ->
210		    discovery;
211		true ->
212		    plain;
213		false ->
214		    error(usmStatsWrongDigests,
215			  ?usmStatsWrongDigests_instance, % OTP-5464
216			  SecName)
217	    end;
218
219	false ->  % noAuth
220	    plain
221    end.
222
223authoritative(SecName, MsgAuthEngineBoots, MsgAuthEngineTime, LocalEngineID) ->
224    ?vtrace("authoritative -> entry with"
225	    "~n   SecName:            ~p"
226	    "~n   MsgAuthEngineBoots: ~p"
227	    "~n   MsgAuthEngineTime:  ~p",
228	    [SecName, MsgAuthEngineBoots, MsgAuthEngineTime]),
229    SnmpEngineBoots = get_local_engine_boots(LocalEngineID),
230    ?vtrace("authoritative -> SnmpEngineBoots: ~p", [SnmpEngineBoots]),
231    SnmpEngineTime = get_local_engine_time(LocalEngineID),
232    ?vtrace("authoritative -> SnmpEngineTime: ~p", [SnmpEngineTime]),
233    InTimeWindow =
234	if
235	    SnmpEngineBoots =:= 2147483647 -> false;
236	    MsgAuthEngineBoots =/= SnmpEngineBoots -> false;
237	    MsgAuthEngineTime + 150 < SnmpEngineTime -> false;
238	    MsgAuthEngineTime - 150 > SnmpEngineTime -> false;
239	    true -> true
240	end,
241    case InTimeWindow of
242	true ->
243	    true;
244	false ->
245	    %% OTP-4090 (OTP-3542)
246	    ?vinfo("NOT in time window: "
247		   "~n   SecName:            ~p"
248		   "~n   SnmpEngineBoots:    ~p"
249		   "~n   MsgAuthEngineBoots: ~p"
250		   "~n   SnmpEngineTime:     ~p"
251		   "~n   MsgAuthEngineTime:  ~p",
252		   [SecName,
253		    SnmpEngineBoots, MsgAuthEngineBoots,
254		    SnmpEngineTime, MsgAuthEngineTime]),
255	    error(usmStatsNotInTimeWindows,
256		  ?usmStatsNotInTimeWindows_instance,
257		  SecName,
258		  [{securityLevel, 1}]) % authNoPriv
259    end.
260
261non_authoritative(SecName,
262		  MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime) ->
263    ?vtrace("non_authoritative -> entry with"
264	    "~n   SecName:            ~p"
265	    "~n   MsgAuthEngineID:    ~p"
266	    "~n   MsgAuthEngineBoots: ~p"
267	    "~n   MsgAuthEngineTime:  ~p",
268	    [SecName,
269	     MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime]),
270    SnmpEngineBoots = get_engine_boots(MsgAuthEngineID),
271    SnmpEngineTime  = get_engine_time(MsgAuthEngineID),
272    LatestRecvTime  = get_engine_latest_time(MsgAuthEngineID),
273    ?vtrace("non_authoritative -> "
274	    "~n   SnmpEngineBoots: ~p"
275	    "~n   SnmpEngineTime:  ~p"
276	    "~n   LatestRecvTime:  ~p",
277	    [SnmpEngineBoots, SnmpEngineTime, LatestRecvTime]),
278    UpdateLCD =
279	if
280	    MsgAuthEngineBoots > SnmpEngineBoots -> true;
281	    ((MsgAuthEngineBoots =:= SnmpEngineBoots) andalso
282	     (MsgAuthEngineTime > LatestRecvTime)) -> true;
283	    true -> false
284	end,
285    case UpdateLCD of
286	true -> %% 3.2.7b1
287	    ?vtrace("non_authoritative -> "
288		    "update msgAuthoritativeEngineID: 3.2.7b1",
289		    []),
290	    set_engine_boots(MsgAuthEngineID, MsgAuthEngineBoots),
291	    set_engine_time(MsgAuthEngineID, MsgAuthEngineTime),
292	    set_engine_latest_time(MsgAuthEngineID, MsgAuthEngineTime);
293	false ->
294	    ok
295    end,
296    %% 3.2.7.b2
297    ?vtrace("non_authoritative -> "
298	    "check if message is outside time window: 3.2.7b2", []),
299    InTimeWindow =
300	if
301	    SnmpEngineBoots =:= 2147483647 ->
302		false;
303	    MsgAuthEngineBoots < SnmpEngineBoots ->
304		false;
305	    ((MsgAuthEngineBoots =:= SnmpEngineBoots) andalso
306	     (MsgAuthEngineTime < (SnmpEngineTime - 150))) ->
307		false;
308	    true -> true
309	end,
310    case InTimeWindow of
311	false ->
312	    ?vinfo("NOT in time window: "
313		   "~n   SecName:            ~p"
314		   "~n   SnmpEngineBoots:    ~p"
315		   "~n   MsgAuthEngineBoots: ~p"
316		   "~n   SnmpEngineTime:     ~p"
317		   "~n   MsgAuthEngineTime:  ~p",
318		   [SecName,
319		    SnmpEngineBoots, MsgAuthEngineBoots,
320		    SnmpEngineTime, MsgAuthEngineTime]),
321	    error(notInTimeWindow, []);
322	true ->
323	    %% If the previous values where all zero's this is the
324	    %% second stage discovery message
325	    if
326		((SnmpEngineBoots =:= 0) andalso
327		 (SnmpEngineTime  =:= 0) andalso
328		 (LatestRecvTime  =:= 0)) ->
329		    ?vtrace("non_authoritative -> "
330			    "[maybe] originating discovery stage 2", []),
331		    discovery;
332		true ->
333		    true
334	    end
335    end.
336
337is_auth(?usmNoAuthProtocol, _, _, _, SecName, _, _, _, _) -> % 3.2.5
338    error(usmStatsUnsupportedSecLevels,
339	  ?usmStatsUnsupportedSecLevels_instance, SecName); % OTP-5464
340is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName,
341	MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime,
342	LocalEngineID) ->
343    TermDiscoEnabled = is_terminating_discovery_enabled(),
344    TermDiscoStage2  = terminating_discovery_stage2(),
345    IsAuth = auth_in(AuthProtocol, AuthKey, AuthParams, Packet),
346    ?vtrace("is_auth -> IsAuth: ~p", [IsAuth]),
347    case IsAuth of
348	true ->
349	    %% 3.2.7
350	    ?vtrace("is_auth -> "
351		    "retrieve EngineBoots and EngineTime: 3.2.7",[]),
352	    SnmpEngineID = LocalEngineID,
353	    ?vtrace("is_auth -> SnmpEngineID: ~p", [SnmpEngineID]),
354	    case MsgAuthEngineID of
355		SnmpEngineID when ((MsgAuthEngineBoots =:= 0) andalso
356				   (MsgAuthEngineTime =:= 0) andalso
357				   (TermDiscoEnabled =:= true) andalso
358				   (TermDiscoStage2 =:= discovery)) -> %% 3.2.7a
359		    ?vtrace("is_auth -> terminating discovery stage 2 - discovery",[]),
360		    discovery;
361		SnmpEngineID when ((MsgAuthEngineBoots =:= 0) andalso
362				   (MsgAuthEngineTime =:= 0) andalso
363				   (TermDiscoEnabled =:= true) andalso
364				   (TermDiscoStage2 =:= plain)) -> %% 3.2.7a
365		    ?vtrace("is_auth -> terminating discovery stage 2 - plain",[]),
366		    %% This will *always* result in the manager *not*
367		    %% beeing in timewindow
368		    authoritative(SecName,
369				  MsgAuthEngineBoots, MsgAuthEngineTime,
370				  LocalEngineID);
371
372		SnmpEngineID -> %% 3.2.7a
373		    ?vtrace("is_auth -> we are authoritative: 3.2.7a", []),
374		    authoritative(SecName,
375				  MsgAuthEngineBoots, MsgAuthEngineTime,
376				  LocalEngineID);
377
378		_ -> %% 3.2.7b - we're non-authoritative
379		    ?vtrace("is_auth -> we are non-authoritative: 3.2.7b",[]),
380		    non_authoritative(SecName,
381				      MsgAuthEngineID,
382				      MsgAuthEngineBoots, MsgAuthEngineTime)
383	    end;
384
385	false ->
386	    false
387    end.
388
389
390decrypt(Data, UsmUser, UsmSecParams, SecLevel) ->
391    case snmp_misc:is_priv(SecLevel) of
392	true ->
393	    do_decrypt(Data, UsmUser, UsmSecParams);
394	false ->
395	    Data
396    end.
397
398do_decrypt(Data, UsmUser, UsmSecParams) ->
399	    EncryptedPDU = snmp_pdus:dec_scoped_pdu_data(Data),
400	    SecName      = element(?usmUserSecurityName, UsmUser),
401	    PrivP        = element(?usmUserPrivProtocol, UsmUser),
402	    PrivKey      = element(?usmUserPrivKey,      UsmUser),
403    ?vtrace("do_decrypt -> try decrypt with: "
404	    "~n   SecName: ~p"
405	    "~n   PrivP:   ~p", [SecName, PrivP]),
406    try_decrypt(PrivP, PrivKey, UsmSecParams, EncryptedPDU, SecName).
407
408try_decrypt(?usmNoPrivProtocol, _, _, _, SecName) -> % 3.2.5
409    error(usmStatsUnsupportedSecLevels,
410	  ?usmStatsUnsupportedSecLevels_instance, SecName); % OTP-5464
411try_decrypt(?usmDESPrivProtocol,
412	    PrivKey, UsmSecParams, EncryptedPDU, SecName) ->
413    case (catch des_decrypt(PrivKey, UsmSecParams, EncryptedPDU)) of
414	{ok, DecryptedData} ->
415	    DecryptedData;
416	Error ->
417	    ?vlog("try_decrypt -> failed DES decrypt"
418		  "~n   Error: ~p", [Error]),
419	    error(usmStatsDecryptionErrors,
420		  ?usmStatsDecryptionErrors_instance, % OTP-5464
421		  SecName)
422    end;
423try_decrypt(?usmAesCfb128Protocol,
424	    PrivKey, UsmSecParams,  EncryptedPDU, SecName) ->
425    case (catch aes_decrypt(PrivKey, UsmSecParams, EncryptedPDU)) of
426	{ok, DecryptedData} ->
427	    DecryptedData;
428	Error ->
429	    ?vlog("try_decrypt -> failed AES decrypt"
430		  "~n   Error: ~p", [Error]),
431	    error(usmStatsDecryptionErrors,
432		  ?usmStatsDecryptionErrors_instance, % OTP-5464
433		  SecName)
434    end.
435
436
437generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) ->
438    LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
439    generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel,
440			  LocalEngineID).
441
442generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel,
443		      LocalEngineID) ->
444    %% 3.1.1
445    ?vtrace("generate_outgoing_msg -> [3.1.1] entry with"
446	    "~n   SecEngineID:   ~p"
447	    "~n   SecName:       ~p"
448	    "~n   SecLevel:      ~w"
449	    "~n   LocalEngineID: ~p",
450	    [SecEngineID, SecName, SecLevel, LocalEngineID]),
451    {UserName, AuthProtocol, PrivProtocol, AuthKey, PrivKey} =
452	case SecData of
453	    [] -> % 3.1.1b
454		%% Not a response - read from LCD
455		case snmp_user_based_sm_mib:get_user_from_security_name(
456		       SecEngineID, SecName) of
457		    User when element(?usmUserStatus, User) =:=
458			      ?'RowStatus_active' ->
459			{element(?usmUserName, User),
460			 element(?usmUserAuthProtocol, User),
461			 element(?usmUserPrivProtocol, User),
462			 element(?usmUserAuthKey, User),
463			 element(?usmUserPrivKey, User)};
464		    {_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} ->
465			?vdebug("generate_outgoing_msg -> "
466				"found not active user ~p: ~p",
467				[Name, RowStatus]),
468			error(unknownSecurityName);
469		    _ ->
470			error(unknownSecurityName)
471		end;
472	    [MsgUserName] ->
473		%% This means the user at the engine is unknown
474		{MsgUserName, ?usmNoAuthProtocol, ?usmNoPrivProtocol, "", ""};
475	    _ -> % 3.1.1a
476		SecData
477	end,
478    %% 3.1.6
479    SnmpEngineID = LocalEngineID,
480    ?vtrace("generate_outgoing_msg -> SnmpEngineID: ~p [3.1.6]",
481	    [SnmpEngineID]),
482    {MsgAuthEngineBoots, MsgAuthEngineTime} =
483	case snmp_misc:is_auth(SecLevel) of
484	    false when SecData =:= [] -> % not a response
485		{0, 0};
486	    false when UserName =:= "" -> % reply (report) to discovery step 1
487		{0, 0};
488	    true when SecEngineID =/= SnmpEngineID ->
489		{get_engine_boots(SecEngineID),
490		 get_engine_time(SecEngineID)};
491	    _ ->
492		{get_local_engine_boots(SnmpEngineID),
493		 get_local_engine_time(SnmpEngineID)}
494	end,
495    %% 3.1.4
496    ?vtrace("generate_outgoing_msg -> [3.1.4]"
497	    "~n   UserName:     ~p"
498	    "~n   AuthProtocol: ~p"
499	    "~n   PrivProtocol: ~p",
500	    [UserName, AuthProtocol, PrivProtocol]),
501    ScopedPduBytes = Message#message.data,
502    {ScopedPduData, MsgPrivParams} =
503	encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel,
504                MsgAuthEngineBoots, MsgAuthEngineTime),
505    %% 3.1.5 - 3.1.7
506    ?vtrace("generate_outgoing_msg -> [3.1.5 - 3.1.7]",[]),
507    UsmSecParams =
508	#usmSecurityParameters{msgAuthoritativeEngineID    = SecEngineID,
509			       msgAuthoritativeEngineBoots = MsgAuthEngineBoots,
510			       msgAuthoritativeEngineTime  = MsgAuthEngineTime,
511			       msgUserName                 = UserName,
512			       msgPrivacyParameters        = MsgPrivParams},
513    Message2 = Message#message{data = ScopedPduData},
514    %% 3.1.8
515    ?vtrace("generate_outgoing_msg -> [3.1.8]",[]),
516    authenticate_outgoing(Message2, UsmSecParams,
517			  AuthKey, AuthProtocol, SecLevel).
518
519
520generate_discovery_msg(Message, SecEngineID, SecName, SecLevel) ->
521    generate_discovery_msg(Message, SecEngineID, SecName, SecLevel, "").
522
523generate_discovery_msg(Message,
524		       SecEngineID, SecName, SecLevel,
525		       InitialUserName) ->
526   ?vtrace("generate_discovery_msg -> entry with"
527	    "~n   SecEngineID:     ~p"
528	    "~n   SecName:         ~p"
529	    "~n   SecLevel:        ~p"
530	    "~n   InitialUserName: ~p",
531	    [SecEngineID, SecName, SecLevel, InitialUserName]),
532    {UserName, AuthProtocol, AuthKey, PrivProtocol, PrivKey} =
533	case SecEngineID of
534	    "" ->
535		%% Discovery step 1
536		%% Nothing except the user name will be used in this
537		%% tuple in this step, but since we need some values,
538		%% we fill in proper ones just in case
539		%% {"initial", usmNoAuthProtocol, "", usmNoPrivProtocol, ""};
540		%% {"", usmNoAuthProtocol, "", usmNoPrivProtocol, ""};
541		{InitialUserName,
542		 usmNoAuthProtocol, "", usmNoPrivProtocol, ""};
543
544	    _ ->
545		%% Discovery step 2
546		case snmp_user_based_sm_mib:get_user_from_security_name(
547		       SecEngineID, SecName) of
548		    User when element(?usmUserStatus, User) =:=
549			      ?'RowStatus_active' ->
550			{element(?usmUserName, User),
551			 element(?usmUserAuthProtocol, User),
552			 element(?usmUserAuthKey, User),
553			 element(?usmUserPrivProtocol, User),
554			 element(?usmUserPrivKey, User)};
555		    {_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} ->
556			?vdebug("generate_discovery_msg -> "
557				"found user ~p with wrong row status: ~p",
558				[Name, RowStatus]),
559			error(unknownSecurityName);
560		    _ ->
561			error(unknownSecurityName)
562		end
563	end,
564    ScopedPduBytes = Message#message.data,
565    MsgAuthEngineBoots = 0,
566    MsgAuthEngineTime = 0,
567    {ScopedPduData, MsgPrivParams} =
568	encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel,
569               MsgAuthEngineBoots, MsgAuthEngineTime),
570    UsmSecParams =
571	#usmSecurityParameters{msgAuthoritativeEngineID    = SecEngineID,
572			       msgAuthoritativeEngineBoots = % Boots
573                                   MsgAuthEngineBoots,
574			       msgAuthoritativeEngineTime = % Time
575                                   MsgAuthEngineTime,
576			       msgUserName                 = UserName,
577			       msgPrivacyParameters        = MsgPrivParams},
578    Message2 = Message#message{data = ScopedPduData},
579    authenticate_outgoing(Message2, UsmSecParams,
580			  AuthKey, AuthProtocol, SecLevel).
581
582
583%% Ret: {ScopedPDU, MsgPrivParams} - both are already encoded as OCTET STRINGs
584encrypt(Data, PrivProtocol, PrivKey, SecLevel, EngineBoots, EngineTime) ->
585    case snmp_misc:is_priv(SecLevel) of
586	false -> % 3.1.4b
587	    ?vtrace("encrypt -> 3.1.4b",[]),
588	    {Data, []};
589	true -> % 3.1.4a
590	    ?vtrace("encrypt -> 3.1.4a",[]),
591	    case (catch try_encrypt(PrivProtocol, PrivKey, Data,
592                                    EngineBoots, EngineTime)) of
593		{ok, ScopedPduData, MsgPrivParams} ->
594		    ?vtrace("encrypt -> encrypted - now encode tag",[]),
595		    {snmp_pdus:enc_oct_str_tag(ScopedPduData), MsgPrivParams};
596                {error, Reason} ->
597		    ?vtrace("encrypt -> error: "
598			    "~n   Reason: ~p", [Reason]),
599                    error(Reason);
600 		Error ->
601		    ?vtrace("encrypt -> other: "
602			    "~n   Error: ~p", [Error]),
603		    error(encryptionError)
604	    end
605    end.
606
607try_encrypt(
608  ?usmNoPrivProtocol, _PrivKey, _Data, _EngineBoots, _EngineTime) -> % 3.1.2
609    error(unsupportedSecurityLevel);
610try_encrypt(?usmDESPrivProtocol, PrivKey, Data, _EngineBoots, _EngineTime) ->
611    des_encrypt(PrivKey, Data);
612try_encrypt(?usmAesCfb128Protocol, PrivKey, Data, EngineBoots, EngineTime) ->
613    aes_encrypt(PrivKey, Data, EngineBoots, EngineTime).
614
615
616authenticate_outgoing(Message, UsmSecParams,
617		      AuthKey, AuthProtocol, SecLevel) ->
618    Message2 =
619	case snmp_misc:is_auth(SecLevel) of
620	    true ->
621		auth_out(AuthProtocol, AuthKey, Message, UsmSecParams);
622	    false ->
623		set_msg_auth_params(Message, UsmSecParams)
624	end,
625    ?vtrace("authenticate_outgoing -> encode message only",[]),
626    snmp_pdus:enc_message_only(Message2).
627
628
629%%-----------------------------------------------------------------
630%% Auth and priv algorithms
631%%-----------------------------------------------------------------
632auth_in(AuthProtocol, AuthKey, AuthParams, Packet) ->
633    snmp_usm:auth_in(AuthProtocol, AuthKey, AuthParams, Packet).
634
635auth_out(AuthProtocol, AuthKey, Message, UsmSecParams) ->
636    snmp_usm:auth_out(AuthProtocol, AuthKey, Message, UsmSecParams).
637
638set_msg_auth_params(Message, UsmSecParams) ->
639    snmp_usm:set_msg_auth_params(Message, UsmSecParams, []).
640
641des_encrypt(PrivKey, Data) ->
642    snmp_usm:des_encrypt(PrivKey, Data, fun get_des_salt/0).
643
644des_decrypt(PrivKey, UsmSecParams, EncData) ->
645    #usmSecurityParameters{msgPrivacyParameters = PrivParms} = UsmSecParams,
646    snmp_usm:des_decrypt(PrivKey, PrivParms, EncData).
647
648get_des_salt() ->
649    SaltInt =
650	case catch ets:update_counter(snmp_agent_table, usm_des_salt, 1) of
651	    N when N =< 4294967295 ->
652		N;
653	    N when is_integer(N) -> % wrap
654		ets:insert(snmp_agent_table, {usm_des_salt, 0}),
655		0;
656	    _ -> % it doesn't exist, initialize
657                ?SNMP_RAND_SEED(),
658                %% rand:seed(exrop,
659                %%           {erlang:phash2([node()]),
660                %%            erlang:monotonic_time(),
661                %%            erlang:unique_integer()}),
662		R = rand:uniform(4294967295),
663		ets:insert(snmp_agent_table, {usm_des_salt, R}),
664		R
665	end,
666    EngineBoots = snmp_framework_mib:get_engine_boots(),
667    [?i32(EngineBoots), ?i32(SaltInt)].
668
669aes_encrypt(PrivKey, Data, EngineBoots, EngineTime) ->
670    snmp_usm:aes_encrypt(PrivKey, Data, fun get_aes_salt/0,
671                         EngineBoots, EngineTime).
672
673aes_decrypt(PrivKey, UsmSecParams, EncData) ->
674    #usmSecurityParameters{msgPrivacyParameters        = PrivParams,
675			   msgAuthoritativeEngineTime  = EngineTime,
676			   msgAuthoritativeEngineBoots = EngineBoots} =
677	UsmSecParams,
678    snmp_usm:aes_decrypt(PrivKey, PrivParams, EncData,
679			 EngineBoots, EngineTime).
680
681get_aes_salt() ->
682    SaltInt =
683	case catch ets:update_counter(snmp_agent_table, usm_aes_salt, 1) of
684	    N when N =< 36893488147419103231  ->
685		N;
686	    N when is_integer(N) -> % wrap
687		ets:insert(snmp_agent_table, {usm_aes_salt, 0}),
688		0;
689	    _ -> % it doesn't exist, initialize
690                ?SNMP_RAND_SEED(),
691                %% rand:seed(exrop,
692                %%           {erlang:phash2([node()]),
693                %%            erlang:monotonic_time(),
694                %%            erlang:unique_integer()}),
695		R = rand:uniform(36893488147419103231),
696		ets:insert(snmp_agent_table, {usm_aes_salt, R}),
697		R
698	end,
699    [?i64(SaltInt)].
700
701
702
703%%-----------------------------------------------------------------
704%% Discovery wrapper functions
705%%-----------------------------------------------------------------
706
707is_terminating_discovery_enabled() ->
708    snmpa_agent:is_terminating_discovery_enabled().
709
710terminating_discovery_stage2() ->
711    snmpa_agent:terminating_discovery_stage2().
712
713terminating_trigger_username() ->
714    snmpa_agent:terminating_trigger_username().
715
716current_statsNotInTimeWindows_vb() ->
717    #varbind{oid          = ?usmStatsNotInTimeWindows_instance,
718	     variabletype = 'Counter32',
719	     value        = get_counter(usmStatsNotInTimeWindows)}.
720
721
722
723%%-----------------------------------------------------------------
724%% Future profing...
725%%-----------------------------------------------------------------
726
727get_local_engine_boots(_LocalEngineID) ->
728    snmp_framework_mib:get_engine_boots().
729
730get_local_engine_time(_LocalEngineID) ->
731    snmp_framework_mib:get_engine_time().
732
733
734
735%%-----------------------------------------------------------------
736%% We cache the local values of all non-auth engines we know.
737%% Keep the values in the snmp_agent_table.
738%% See section 2.3 of the RFC.
739%%-----------------------------------------------------------------
740get_engine_boots(SnmpEngineID) ->
741    case ets:lookup(snmp_agent_table, {usm_eboots, SnmpEngineID}) of
742	[{_Key, Boots}] -> Boots;
743	_ -> 0
744    end.
745
746get_engine_time(SnmpEngineID) ->
747    case ets:lookup(snmp_agent_table, {usm_etime, SnmpEngineID}) of
748	[{_Key, Diff}] -> snmp_misc:now(sec) - Diff;
749	_ -> 0
750    end.
751
752get_engine_latest_time(SnmpEngineID) ->
753    case ets:lookup(snmp_agent_table, {usm_eltime, SnmpEngineID}) of
754	[{_Key, Time}] -> Time;
755	_ -> 0
756    end.
757
758
759set_engine_boots(SnmpEngineID, EngineBoots) ->
760    ets:insert(snmp_agent_table, {{usm_eboots, SnmpEngineID}, EngineBoots}).
761
762set_engine_time(SnmpEngineID, EngineTime) ->
763    Diff = snmp_misc:now(sec) - EngineTime,
764    ets:insert(snmp_agent_table, {{usm_etime, SnmpEngineID}, Diff}).
765
766set_engine_latest_time(SnmpEngineID, EngineTime) ->
767    ets:insert(snmp_agent_table, {{usm_eltime, SnmpEngineID}, EngineTime}).
768
769
770%%-----------------------------------------------------------------
771%% Utility functions
772%%-----------------------------------------------------------------
773-spec error(term()) -> no_return().
774error(Reason) ->
775    throw({error, Reason}).
776
777-spec error(term(), term()) -> no_return().
778error(Reason, ErrorInfo) ->
779    throw({error, Reason, ErrorInfo}).
780
781-spec error(term(), term(), term()) -> no_return().
782error(Variable, Oid, SecName) ->
783    error(Variable, Oid, SecName, []).
784
785-spec error(term(), term(), term(), [term()]) -> no_return().
786error(Variable, Oid, SecName, Opts) ->
787    Val = inc(Variable),
788    ErrorInfo = {#varbind{oid = Oid,
789			  variabletype = 'Counter32',
790			  value = Val},
791		 SecName,
792		 Opts},
793    throw({error, Variable, ErrorInfo}).
794
795inc(Name) -> ets:update_counter(snmp_agent_table, Name, 1).
796
797get_counter(Name) ->
798    case (catch ets:lookup(snmp_agent_table, Name)) of
799	[{_, Val}] ->
800	    Val;
801	_ ->
802	    0
803    end.
804