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-module(snmpa_vacm). 21 22-export([get_mib_view/5]). 23-export([init/1, init/2, backup/1]). 24-export([delete/1, get_row/1, get_next_row/1, insert/1, insert/2, 25 cleanup/0, dump_table/0]). 26 27-include("SNMPv2-TC.hrl"). 28-include("SNMP-VIEW-BASED-ACM-MIB.hrl"). 29-include("SNMP-FRAMEWORK-MIB.hrl"). 30-include("snmp_types.hrl"). 31-include("snmpa_vacm.hrl"). 32 33-define(VMODULE,"VACM"). 34-include("snmp_verbosity.hrl"). 35 36 37%%%----------------------------------------------------------------- 38%%% Access Control Module for VACM (see also snmpa_acm) 39%%% This module implements: 40%%% 1. access control functions for VACM 41%%% 2. vacmAccessTable as an ordered ets table 42%%% 43%%% This version of VACM handles v1, v2c and v3. 44%%%----------------------------------------------------------------- 45 46%%%----------------------------------------------------------------- 47%%% 1. access control functions for VACM 48%%%----------------------------------------------------------------- 49%%----------------------------------------------------------------- 50%% Func: get_mib_view/5 -> {ok, ViewName} | 51%% {discarded, Reason} 52%% Types: ViewType = read | write | notify 53%% SecModel = ?SEC_* (see snmp_types.hrl) 54%% SecName = string() 55%% SecLevel = ?'SnmpSecurityLevel_*' (see SNMP-FRAMEWORK-MIB.hrl) 56%% ContextName = string() 57%% Purpose: This function is used to map VACM parameters to a mib 58%% view. 59%%----------------------------------------------------------------- 60get_mib_view(ViewType, SecModel, SecName, SecLevel, ContextName) -> 61 check_auth(catch auth(ViewType, SecModel, SecName, SecLevel, ContextName)). 62 63 64%% Follows the procedure in rfc2275 65auth(ViewType, SecModel, SecName, SecLevel, ContextName) -> 66 ?vtrace("auth -> entry with" 67 "~n ViewType: ~p" 68 "~n SecModel: ~p" 69 "~n SecName: ~p" 70 "~n SecLevel: ~p" 71 "~n ContextName: ~p", 72 [ViewType, SecModel, SecName, SecLevel, ContextName]), 73 % 3.2.1 - Check that the context is known to us 74 ?vdebug("check that the context (~p) is known to us",[ContextName]), 75 case snmp_view_based_acm_mib:vacmContextTable(get, ContextName, 76 [?vacmContextName]) of 77 [_Found] -> 78 ok; 79 _ -> 80 snmpa_mpd:inc(snmpUnknownContexts), 81 throw({discarded, noSuchContext}) 82 end, 83 % 3.2.2 - Check that the SecModel and SecName is valid 84 ?vdebug("check that SecModel (~p) and SecName (~p) is valid", 85 [SecModel, SecName]), 86 GroupName = 87 case snmp_view_based_acm_mib:get(vacmSecurityToGroupTable, 88 [SecModel, length(SecName) | SecName], 89 [?vacmGroupName, ?vacmSecurityToGroupStatus]) of 90 [{value, GN}, {value, ?'RowStatus_active'}] -> 91 GN; 92 [{value, _GN}, {value, RowStatus}] -> 93 ?vlog("valid SecModel and SecName but wrong row status:" 94 "~n RowStatus: ~p", [RowStatus]), 95 throw({discarded, noGroupName}); 96 _ -> 97 throw({discarded, noGroupName}) 98 end, 99 % 3.2.3-4 - Find an access entry and its view name 100 ?vdebug("find an access entry and its view name",[]), 101 ViewName = 102 case get_view_name(ViewType, GroupName, ContextName, 103 SecModel, SecLevel) of 104 {ok, VN} -> VN; 105 Error -> throw(Error) 106 end, 107 % 3.2.5a - Find the corresponding mib view 108 ?vdebug("find the corresponding mib view (for ~p)",[ViewName]), 109 get_mib_view(ViewName). 110 111check_auth({'EXIT', Error}) -> exit(Error); 112check_auth({discarded, Reason}) -> {discarded, Reason}; 113check_auth(Res) -> {ok, Res}. 114 115%%----------------------------------------------------------------- 116%% Returns a list of {ViewSubtree, ViewMask, ViewType} 117%% The view table is index by ViewIndex, ViewSubtree, 118%% so a next on ViewIndex returns the first 119%% key in the table >= ViewIndex. 120%%----------------------------------------------------------------- 121get_mib_view(ViewName) -> 122 ?vtrace("get_mib_view -> entry with" 123 "~n ViewName: ~p", [ViewName]), 124 ViewKey = [length(ViewName) | ViewName], 125 case snmp_view_based_acm_mib:table_next(vacmViewTreeFamilyTable, 126 ViewKey) of 127 endOfTable -> 128 {discarded, noSuchView}; 129 Indexes -> 130 case split_prefix(ViewKey, Indexes) of 131 {ok, Subtree} -> 132 loop_mib_view(ViewKey, Subtree, Indexes, []); 133 false -> 134 {discarded, noSuchView} 135 end 136 end. 137 138split_prefix([H|T], [H|T2]) -> split_prefix(T,T2); 139split_prefix([], Rest) -> {ok, Rest}; 140split_prefix(_, _) -> false. 141 142 143%% ViewName is including length from now on 144loop_mib_view(ViewName, Subtree, Indexes, MibView) -> 145 [{value, Mask}, {value, Type}, {value, Status}] = 146 snmp_view_based_acm_mib:vacmViewTreeFamilyTable( 147 get, Indexes, 148 [?vacmViewTreeFamilyMask, 149 ?vacmViewTreeFamilyType, 150 ?vacmViewTreeFamilyStatus]), 151 NextMibView = 152 case Status of 153 ?'RowStatus_active' -> 154 [_Length | Tree] = Subtree, 155 [{Tree, Mask, Type} | MibView]; 156 _ -> 157 MibView 158 end, 159 case snmp_view_based_acm_mib:table_next(vacmViewTreeFamilyTable, 160 Indexes) of 161 endOfTable -> NextMibView; 162 NextIndexes -> 163 case split_prefix(ViewName, NextIndexes) of 164 {ok, NextSubTree} -> 165 loop_mib_view(ViewName, NextSubTree, NextIndexes, 166 NextMibView); 167 false -> 168 NextMibView 169 end 170 end. 171 172%%%----------------------------------------------------------------- 173%%% 1b. The ordered ets table that implements vacmAccessTable 174%%%----------------------------------------------------------------- 175 176init(Dir) -> 177 init(Dir, terminate). 178 179init(Dir, InitError) -> 180 FName = filename:join(Dir, "snmpa_vacm.db"), 181 case file:read_file_info(FName) of 182 {ok, _} -> 183 %% File exists - we must check this, since ets doesn't tell 184 %% us the reason in case of error... 185 case ets:file2tab(FName) of 186 {ok, _Tab} -> 187 gc_tab([]); 188 {error, Reason} -> 189 user_err("Corrupt VACM database ~p", [FName]), 190 case InitError of 191 terminate -> 192 throw({error, {file2tab, FName, Reason}}); 193 _ -> 194 %% Rename old file (for later analyzes) 195 Saved = FName ++ ".saved", 196 file:rename(FName, Saved), 197 ets:new(snmpa_vacm, 198 [public, ordered_set, named_table]) 199 end 200 end; 201 {error, _} -> 202 ets:new(snmpa_vacm, [public, ordered_set, named_table]) 203 end, 204 ets:insert(snmp_agent_table, {snmpa_vacm_file, FName}), 205 {ok, FName}. 206 207 208backup(BackupDir) -> 209 BackupFile = filename:join(BackupDir, "snmpa_vacm.db"), 210 ets:tab2file(snmpa_vacm, BackupFile). 211 212 213%% Ret: {ok, ViewName} | {error, Reason} 214get_view_name(ViewType, GroupName, ContextName, SecModel, SecLevel) -> 215 ?vtrace("get_view_name -> entry with" 216 "~n ViewType: ~p" 217 "~n GroupName: ~p" 218 "~n ContextName: ~p" 219 "~n SecModel: ~p" 220 "~n SecLevel: ~p", 221 [ViewType, GroupName, ContextName, SecModel, SecLevel]), 222 GroupKey = [length(GroupName) | GroupName], 223 case get_access_row(GroupKey, ContextName, SecModel, SecLevel) of 224 undefined -> 225 {discarded, noAccessEntry}; 226 Row -> 227 ?vtrace("get_view_name -> Row: ~n ~p", [Row]), 228 ViewName = 229 case ViewType of 230 read -> element(?vacmAReadViewName, Row); 231 write -> element(?vacmAWriteViewName, Row); 232 notify -> element(?vacmANotifyViewName, Row) 233 end, 234 case ViewName of 235 "" -> 236 ?vtrace("get_view_name -> not found when" 237 "~n ViewType: ~p" 238 "~n GroupName: ~p" 239 "~n ContextName: ~p" 240 "~n SecModel: ~p" 241 "~n SecLevel: ~p", [ViewType, GroupName, 242 ContextName, SecModel, 243 SecLevel]), 244 {discarded, noSuchView}; 245 _ -> {ok, ViewName} 246 end 247 end. 248 249 250get_row(Key) -> 251 case ets:lookup(snmpa_vacm, Key) of 252 [{_Key, Row}] -> {ok, Row}; 253 _ -> false 254 end. 255 256get_next_row(Key) -> 257 case ets:next(snmpa_vacm, Key) of 258 '$end_of_table' -> false; 259 NextKey -> 260 case ets:lookup(snmpa_vacm, NextKey) of 261 [Entry] -> Entry; 262 _ -> false 263 end 264 end. 265 266insert(Entries) -> insert(Entries, true). 267 268insert(Entries, Dump) -> 269 lists:foreach(fun(Entry) -> ets:insert(snmpa_vacm, Entry) end, Entries), 270 dump_table(Dump). 271 272delete(Key) -> 273 ets:delete(snmpa_vacm, Key), 274 dump_table(). 275 276 277cleanup() -> 278 ets:delete_all_objects(snmpa_vacm), 279 dump_table(). 280 281dump_table(true) -> 282 dump_table(); 283dump_table(_) -> 284 ok. 285 286 287dump_table() -> 288 [{_, FName}] = ets:lookup(snmp_agent_table, snmpa_vacm_file), 289 TmpName = unique_table_name(FName), 290 case ets:tab2file(snmpa_vacm, TmpName) of 291 ok -> 292 case file:rename(TmpName, FName) of 293 ok -> 294 ok; 295 Else -> % What is this? Undocumented return code... 296 user_err("Warning: could not move VACM db ~p" 297 " (~p)", [FName, Else]) 298 end; 299 {error, Reason} -> 300 user_err("Warning: could not save vacm db ~p (~p)", 301 [FName, Reason]) 302 end. 303 304%% This little thing is an attempt to create a "unique" filename 305%% in order to minimize the risk of two processes at the same 306%% time dumping the table. 307unique_table_name(Pre) -> 308 %% We want something that is guaranteed to be unique, 309 %% therefor we use erlang:timestamp() instead of os:timestamp() 310 unique_table_name(Pre, erlang:timestamp()). 311 312unique_table_name(Pre, {_A, _B, C} = Now) -> 313 {Date, Time} = calendar:now_to_datetime(Now), 314 {YYYY, MM, DD} = Date, 315 {Hour, Min, Sec} = Time, 316 FormatDate = 317 io_lib:format("~.4w~.2.0w~.2.0w_~.2.0w~.2.0w~.2.0w_~w", 318 [YYYY, MM, DD, Hour, Min, Sec, round(C/1000)]), 319 unique_table_name2(Pre, FormatDate). 320 321unique_table_name2(Pre, FormatedDate) -> 322 PidPart = unique_table_name_pid(), 323 lists:flatten(io_lib:format("~s.~s~s.tmp", [Pre, PidPart, FormatedDate])). 324 325unique_table_name_pid() -> 326 case string:tokens(pid_to_list(self()), [$<,$.,$>]) of 327 [A, B, C] -> 328 A ++ B ++ C ++ "."; 329 _ -> 330 "" 331 end. 332 333 334%%----------------------------------------------------------------- 335%% Alg. 336%% Procedure is defined in the descr. of vacmAccessTable. 337%% 338%% for (each entry with matching group name, context, secmodel and seclevel) 339%% { 340%% rate the entry; if it's score is > prev max score, keep it 341%% } 342%% 343%% Rating: The procedure says to keep entries in order 344%% 1. matching secmodel ('any'(0) or same(1) is ok) 345%% 2. matching contextprefix (exact(1) or prefix(0) is ok) 346%% 3. longest prefix (0..32) 347%% 4. highest secLevel (noAuthNoPriv(0) < authNoPriv(1) < authPriv(2)) 348%% We give each entry a single rating number according to this order. 349%% The number is chosen so that a higher number gives a better 350%% entry, according to the order above. 351%% The number is: 352%% secLevel + (3 * prefix_len) + (99 * match_prefix) + (198 * match_secmodel) 353%% 354%% Optimisation: Maybe the most common case is that there 355%% is just one matching entry, and it matches exact. We could do 356%% an exact lookup for this entry; if we find one, use it, otherwise 357%% perform this alg. 358%%----------------------------------------------------------------- 359get_access_row(GroupKey, ContextName, SecModel, SecLevel) -> 360 %% First, try the optimisation... 361 ExactKey = 362 GroupKey ++ [length(ContextName) | ContextName] ++ [SecModel,SecLevel], 363 case ets:lookup(snmpa_vacm, ExactKey) of 364 [{_Key, Row}] -> 365 Row; 366 _ -> % Otherwise, perform the alg 367 get_access_row(GroupKey, GroupKey, ContextName, 368 SecModel, SecLevel, 0, undefined) 369 end. 370 371get_access_row(Key, GroupKey, ContextName, SecModel, SecLevel, Score, Found) -> 372 case get_next_row(Key) of 373 {NextKey, Row} 374 when element(?vacmAStatus, Row) == ?'RowStatus_active'-> 375 case catch score(NextKey, GroupKey, ContextName, 376 element(?vacmAContextMatch, Row), 377 SecModel, SecLevel) of 378 {ok, NScore} when NScore > Score -> 379 get_access_row(NextKey, GroupKey, ContextName, 380 SecModel, SecLevel, NScore, Row); 381 {ok, _} -> % e.g. a throwed {ok, 0} 382 get_access_row(NextKey, GroupKey, ContextName, 383 SecModel, SecLevel, Score, Found); 384 false -> 385 Found 386 end; 387 {NextKey, _InvalidRow} -> 388 get_access_row(NextKey, GroupKey, ContextName, SecModel, 389 SecLevel, Score, Found); 390 false -> 391 Found 392 end. 393 394 395 396score(Key, GroupKey, ContextName, Match, SecModel, SecLevel) -> 397 [CtxLen | Rest1] = chop_off_group(GroupKey, Key), 398 {NPrefix, [VSecModel, VSecLevel]} = 399 chop_off_context(ContextName, Rest1, 0, CtxLen, Match), 400 %% Make sure the vacmSecModel is valid (any or matching) 401 NSecModel = case VSecModel of 402 SecModel -> 198; 403 ?SEC_ANY -> 0; 404 _ -> throw({ok, 0}) 405 end, 406 %% Make sure the vacmSecLevel is less than the requested 407 NSecLevel = if 408 VSecLevel =< SecLevel -> VSecLevel - 1; 409 true -> throw({ok, 0}) 410 end, 411 {ok, NSecLevel + 3*CtxLen + NPrefix + NSecModel}. 412 413 414 415chop_off_group([H|T], [H|T2]) -> chop_off_group(T, T2); 416chop_off_group([], Rest) -> Rest; 417chop_off_group(_, _) -> throw(false). 418 419chop_off_context([H|T], [H|T2], Cnt, Len, Match) when Cnt < Len -> 420 chop_off_context(T, T2, Cnt+1, Len, Match); 421chop_off_context([], Rest, Len, Len, _Match) -> 422 %% We have exact match; don't care about Match 423 {99, Rest}; 424chop_off_context(_, Rest, Len, Len, ?vacmAccessContextMatch_prefix) -> 425 %% We have a prefix match 426 {0, Rest}; 427chop_off_context(_Ctx, _Rest, _Cnt, _Len, _Match) -> 428 %% Otherwise, it didn't match! 429 throw({ok, 0}). 430 431 432gc_tab(Oid) -> 433 case get_next_row(Oid) of 434 {NextOid, Row} -> 435 case element(?vacmAStorageType, Row) of 436 ?'StorageType_volatile' -> 437 ets:delete(snmpa_vacm, NextOid), 438 gc_tab(NextOid); 439 _ -> 440 gc_tab(NextOid) 441 end; 442 false -> 443 ok 444 end. 445 446 447user_err(F, A) -> 448 snmpa_error:user_err(F, A). 449