1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1999-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%% 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.4
479    ?vtrace("generate_outgoing_msg -> [3.1.4]"
480	    "~n   UserName:     ~p"
481	    "~n   AuthProtocol: ~p"
482	    "~n   PrivProtocol: ~p",
483	    [UserName, AuthProtocol, PrivProtocol]),
484    ScopedPduBytes = Message#message.data,
485    {ScopedPduData, MsgPrivParams} =
486	encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel),
487    SnmpEngineID = LocalEngineID,
488    ?vtrace("generate_outgoing_msg -> SnmpEngineID: ~p [3.1.6]",
489	    [SnmpEngineID]),
490    %% 3.1.6
491    {MsgAuthEngineBoots, MsgAuthEngineTime} =
492	case snmp_misc:is_auth(SecLevel) of
493	    false when SecData =:= [] -> % not a response
494		{0, 0};
495	    false when UserName =:= "" -> % reply (report) to discovery step 1
496		{0, 0};
497	    true when SecEngineID =/= SnmpEngineID ->
498		{get_engine_boots(SecEngineID),
499		 get_engine_time(SecEngineID)};
500	    _ ->
501		{get_local_engine_boots(SnmpEngineID),
502		 get_local_engine_time(SnmpEngineID)}
503	end,
504    %% 3.1.5 - 3.1.7
505    ?vtrace("generate_outgoing_msg -> [3.1.5 - 3.1.7]",[]),
506    UsmSecParams =
507	#usmSecurityParameters{msgAuthoritativeEngineID    = SecEngineID,
508			       msgAuthoritativeEngineBoots = MsgAuthEngineBoots,
509			       msgAuthoritativeEngineTime  = MsgAuthEngineTime,
510			       msgUserName                 = UserName,
511			       msgPrivacyParameters        = MsgPrivParams},
512    Message2 = Message#message{data = ScopedPduData},
513    %% 3.1.8
514    ?vtrace("generate_outgoing_msg -> [3.1.8]",[]),
515    authenticate_outgoing(Message2, UsmSecParams,
516			  AuthKey, AuthProtocol, SecLevel).
517
518
519generate_discovery_msg(Message, SecEngineID, SecName, SecLevel) ->
520    generate_discovery_msg(Message, SecEngineID, SecName, SecLevel, "").
521
522generate_discovery_msg(Message,
523		       SecEngineID, SecName, SecLevel,
524		       InitialUserName) ->
525   ?vtrace("generate_discovery_msg -> entry with"
526	    "~n   SecEngineID:     ~p"
527	    "~n   SecName:         ~p"
528	    "~n   SecLevel:        ~p"
529	    "~n   InitialUserName: ~p",
530	    [SecEngineID, SecName, SecLevel, InitialUserName]),
531    {UserName, AuthProtocol, AuthKey, PrivProtocol, PrivKey} =
532	case SecEngineID of
533	    "" ->
534		%% Discovery step 1
535		%% Nothing except the user name will be used in this
536		%% tuple in this step, but since we need some values,
537		%% we fill in proper ones just in case
538		%% {"initial", usmNoAuthProtocol, "", usmNoPrivProtocol, ""};
539		%% {"", usmNoAuthProtocol, "", usmNoPrivProtocol, ""};
540		{InitialUserName,
541		 usmNoAuthProtocol, "", usmNoPrivProtocol, ""};
542
543	    _ ->
544		%% Discovery step 2
545		case snmp_user_based_sm_mib:get_user_from_security_name(
546		       SecEngineID, SecName) of
547		    User when element(?usmUserStatus, User) =:=
548			      ?'RowStatus_active' ->
549			{element(?usmUserName, User),
550			 element(?usmUserAuthProtocol, User),
551			 element(?usmUserAuthKey, User),
552			 element(?usmUserPrivProtocol, User),
553			 element(?usmUserPrivKey, User)};
554		    {_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} ->
555			?vdebug("generate_discovery_msg -> "
556				"found user ~p with wrong row status: ~p",
557				[Name, RowStatus]),
558			error(unknownSecurityName);
559		    _ ->
560			error(unknownSecurityName)
561		end
562	end,
563    ScopedPduBytes = Message#message.data,
564    {ScopedPduData, MsgPrivParams} =
565	encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel),
566    UsmSecParams =
567	#usmSecurityParameters{msgAuthoritativeEngineID    = SecEngineID,
568			       msgAuthoritativeEngineBoots = 0, % Boots,
569			       msgAuthoritativeEngineTime  = 0, % Time,
570			       msgUserName                 = UserName,
571			       msgPrivacyParameters        = MsgPrivParams},
572    Message2 = Message#message{data = ScopedPduData},
573    authenticate_outgoing(Message2, UsmSecParams,
574			  AuthKey, AuthProtocol, SecLevel).
575
576
577%% Ret: {ScopedPDU, MsgPrivParams} - both are already encoded as OCTET STRINGs
578encrypt(Data, PrivProtocol, PrivKey, SecLevel) ->
579    case snmp_misc:is_priv(SecLevel) of
580	false -> % 3.1.4b
581	    ?vtrace("encrypt -> 3.1.4b",[]),
582	    {Data, []};
583	true -> % 3.1.4a
584	    ?vtrace("encrypt -> 3.1.4a",[]),
585	    case (catch try_encrypt(PrivProtocol, PrivKey, Data)) of
586		{ok, ScopedPduData, MsgPrivParams} ->
587		    ?vtrace("encrypt -> encrypted - now encode tag",[]),
588		    {snmp_pdus:enc_oct_str_tag(ScopedPduData), MsgPrivParams};
589                {error, Reason} ->
590		    ?vtrace("encrypt -> error: "
591			    "~n   Reason: ~p", [Reason]),
592                    error(Reason);
593 		Error ->
594		    ?vtrace("encrypt -> other: "
595			    "~n   Error: ~p", [Error]),
596		    error(encryptionError)
597	    end
598    end.
599
600try_encrypt(?usmNoPrivProtocol, _PrivKey, _Data) -> % 3.1.2
601    error(unsupportedSecurityLevel);
602try_encrypt(?usmDESPrivProtocol, PrivKey, Data) ->
603    des_encrypt(PrivKey, Data);
604try_encrypt(?usmAesCfb128Protocol, PrivKey, Data) ->
605    aes_encrypt(PrivKey, Data).
606
607
608authenticate_outgoing(Message, UsmSecParams,
609		      AuthKey, AuthProtocol, SecLevel) ->
610    Message2 =
611	case snmp_misc:is_auth(SecLevel) of
612	    true ->
613		auth_out(AuthProtocol, AuthKey, Message, UsmSecParams);
614	    false ->
615		set_msg_auth_params(Message, UsmSecParams)
616	end,
617    ?vtrace("authenticate_outgoing -> encode message only",[]),
618    snmp_pdus:enc_message_only(Message2).
619
620
621%%-----------------------------------------------------------------
622%% Auth and priv algorithms
623%%-----------------------------------------------------------------
624auth_in(AuthProtocol, AuthKey, AuthParams, Packet) ->
625    snmp_usm:auth_in(AuthProtocol, AuthKey, AuthParams, Packet).
626
627auth_out(AuthProtocol, AuthKey, Message, UsmSecParams) ->
628    snmp_usm:auth_out(AuthProtocol, AuthKey, Message, UsmSecParams).
629
630set_msg_auth_params(Message, UsmSecParams) ->
631    snmp_usm:set_msg_auth_params(Message, UsmSecParams, []).
632
633des_encrypt(PrivKey, Data) ->
634    snmp_usm:des_encrypt(PrivKey, Data, fun get_des_salt/0).
635
636des_decrypt(PrivKey, UsmSecParams, EncData) ->
637    #usmSecurityParameters{msgPrivacyParameters = PrivParms} = UsmSecParams,
638    snmp_usm:des_decrypt(PrivKey, PrivParms, EncData).
639
640get_des_salt() ->
641    SaltInt =
642	case catch ets:update_counter(snmp_agent_table, usm_des_salt, 1) of
643	    N when N =< 4294967295 ->
644		N;
645	    N when is_integer(N) -> % wrap
646		ets:insert(snmp_agent_table, {usm_des_salt, 0}),
647		0;
648	    _ -> % it doesn't exist, initialize
649                random:seed(erlang:phash2([node()]),
650                            erlang:monotonic_time(),
651                            erlang:unique_integer()),
652		R = random:uniform(4294967295),
653		ets:insert(snmp_agent_table, {usm_des_salt, R}),
654		R
655	end,
656    EngineBoots = snmp_framework_mib:get_engine_boots(),
657    [?i32(EngineBoots), ?i32(SaltInt)].
658
659aes_encrypt(PrivKey, Data) ->
660    EngineBoots = snmp_framework_mib:get_engine_boots(),
661    EngineTime  = snmp_framework_mib:get_engine_time(),
662    snmp_usm:aes_encrypt(PrivKey, Data, fun get_aes_salt/0,
663			 EngineBoots, EngineTime).
664
665aes_decrypt(PrivKey, UsmSecParams, EncData) ->
666    #usmSecurityParameters{msgPrivacyParameters        = PrivParams,
667			   msgAuthoritativeEngineTime  = EngineTime,
668			   msgAuthoritativeEngineBoots = EngineBoots} =
669	UsmSecParams,
670    snmp_usm:aes_decrypt(PrivKey, PrivParams, EncData,
671			 EngineBoots, EngineTime).
672
673get_aes_salt() ->
674    SaltInt =
675	case catch ets:update_counter(snmp_agent_table, usm_aes_salt, 1) of
676	    N when N =< 36893488147419103231  ->
677		N;
678	    N when is_integer(N) -> % wrap
679		ets:insert(snmp_agent_table, {usm_aes_salt, 0}),
680		0;
681	    _ -> % it doesn't exist, initialize
682                random:seed(erlang:phash2([node()]),
683                            erlang:monotonic_time(),
684                            erlang:unique_integer()),
685		R = random:uniform(36893488147419103231),
686		ets:insert(snmp_agent_table, {usm_aes_salt, R}),
687		R
688	end,
689    [?i64(SaltInt)].
690
691
692
693%%-----------------------------------------------------------------
694%% Discovery wrapper functions
695%%-----------------------------------------------------------------
696
697is_terminating_discovery_enabled() ->
698    snmpa_agent:is_terminating_discovery_enabled().
699
700terminating_discovery_stage2() ->
701    snmpa_agent:terminating_discovery_stage2().
702
703terminating_trigger_username() ->
704    snmpa_agent:terminating_trigger_username().
705
706current_statsNotInTimeWindows_vb() ->
707    #varbind{oid          = ?usmStatsNotInTimeWindows_instance,
708	     variabletype = 'Counter32',
709	     value        = get_counter(usmStatsNotInTimeWindows)}.
710
711
712
713%%-----------------------------------------------------------------
714%% Future profing...
715%%-----------------------------------------------------------------
716
717get_local_engine_boots(_LocalEngineID) ->
718    snmp_framework_mib:get_engine_boots().
719
720get_local_engine_time(_LocalEngineID) ->
721    snmp_framework_mib:get_engine_time().
722
723
724
725%%-----------------------------------------------------------------
726%% We cache the local values of all non-auth engines we know.
727%% Keep the values in the snmp_agent_table.
728%% See section 2.3 of the RFC.
729%%-----------------------------------------------------------------
730get_engine_boots(SnmpEngineID) ->
731    case ets:lookup(snmp_agent_table, {usm_eboots, SnmpEngineID}) of
732	[{_Key, Boots}] -> Boots;
733	_ -> 0
734    end.
735
736get_engine_time(SnmpEngineID) ->
737    case ets:lookup(snmp_agent_table, {usm_etime, SnmpEngineID}) of
738	[{_Key, Diff}] -> snmp_misc:now(sec) - Diff;
739	_ -> 0
740    end.
741
742get_engine_latest_time(SnmpEngineID) ->
743    case ets:lookup(snmp_agent_table, {usm_eltime, SnmpEngineID}) of
744	[{_Key, Time}] -> Time;
745	_ -> 0
746    end.
747
748
749set_engine_boots(SnmpEngineID, EngineBoots) ->
750    ets:insert(snmp_agent_table, {{usm_eboots, SnmpEngineID}, EngineBoots}).
751
752set_engine_time(SnmpEngineID, EngineTime) ->
753    Diff = snmp_misc:now(sec) - EngineTime,
754    ets:insert(snmp_agent_table, {{usm_etime, SnmpEngineID}, Diff}).
755
756set_engine_latest_time(SnmpEngineID, EngineTime) ->
757    ets:insert(snmp_agent_table, {{usm_eltime, SnmpEngineID}, EngineTime}).
758
759
760%%-----------------------------------------------------------------
761%% Utility functions
762%%-----------------------------------------------------------------
763-spec error(term()) -> no_return().
764error(Reason) ->
765    throw({error, Reason}).
766
767-spec error(term(), term()) -> no_return().
768error(Reason, ErrorInfo) ->
769    throw({error, Reason, ErrorInfo}).
770
771-spec error(term(), term(), term()) -> no_return().
772error(Variable, Oid, SecName) ->
773    error(Variable, Oid, SecName, []).
774
775-spec error(term(), term(), term(), [term()]) -> no_return().
776error(Variable, Oid, SecName, Opts) ->
777    Val = inc(Variable),
778    ErrorInfo = {#varbind{oid = Oid,
779			  variabletype = 'Counter32',
780			  value = Val},
781		 SecName,
782		 Opts},
783    throw({error, Variable, ErrorInfo}).
784
785inc(Name) -> ets:update_counter(snmp_agent_table, Name, 1).
786
787get_counter(Name) ->
788    case (catch ets:lookup(snmp_agent_table, Name)) of
789	[{_, Val}] ->
790	    Val;
791	_ ->
792	    0
793    end.
794