1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1996-2020. 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_set_lib).
21
22-include("snmp_types.hrl").
23
24-define(VMODULE,"SETLIB").
25-include("snmp_verbosity.hrl").
26
27
28%% External exports
29-export([is_varbinds_ok/1, consistency_check/1,
30	 undo_varbinds/1, try_set/1]).
31
32%%%-----------------------------------------------------------------
33%%% This module implements functions common to all different set
34%%% mechanisms.
35%%%-----------------------------------------------------------------
36%% Phase 1 of the set-operation. (see rfc1905:4.2.5)
37%% Input is a sorted Varbinds list.
38%% Output is either ok or {ErrIndex, ErrCode}.
39%%*  1) IF the variable is outside the mib view THEN noAccess.
40%%*  2a) IF the varaible doesn't exist THEN notWritable.
41%%   2b) IF mib isn't able to create instances of the variable (for
42%%       example in a table) THEN noCreation.
43%%*  3) IF the new value provided is of the wrong ASN.1 type THEN wrongType.
44%%*  4) IF then new value is of wrong length THEN wrongLength.
45%%#  5) IF value is incorrectly encoded THEN wrongEncoding. [nyi]
46%%*  6) IF value is outside the acceptable range THEN wrongValue.
47%%   7) IF variable does not exist and could not ever be created
48%%      THEN noCreation.
49%%   8) IF variable cannot be created now THEN inconsistentName.
50%%   9) IF value cannot be set now THEN inconsistentValue.
51%%*  9) IF instances of the variable cannot be modified THEN notWritable.
52%%  10) IF an unavailable resource is needed THEN resourceUnavailable.
53%%  11) IF any other error THEN genErr.
54%%  12) Otherwise ok!
55%% The Agent takes care of *-marked. (# should be done by the agent but is
56%% nyi.)
57%% The rest is taken care of in consistency_check that communicates with the mib
58%% (through the is_set_ok-function (for each variable)).
59%%
60%% SNMPv1  (see rfc1157:4.1.5)
61%%*  1) IF variable not available for set in the mibview THEN noSuchName.
62%%*  2) IF variable exists, but doesn't permit writeing THEN readOnly.
63%%*  3) IF provided value doesn't match ASN.1 type THEN badValue.
64%%   4) IF any other error THEN genErr.
65%%   5) Otherwise ok!
66%%
67%% Internally we use snmpv2 messages, and convert them to the appropriate
68%% v1 msg.
69%%-----------------------------------------------------------------
70%% Func: is_varbinds_ok/2
71%% Purpose: Call is_varbind_ok for each varbind
72%% Returns: {noError, 0} | {ErrorStatus, ErrorIndex}
73%%-----------------------------------------------------------------
74is_varbinds_ok([{_TableOid, TableVbs} | IVarbinds]) ->
75    is_varbinds_ok(lists:append(TableVbs, IVarbinds));
76is_varbinds_ok([IVarbind | IVarbinds]) ->
77    case catch is_varbind_ok(IVarbind) of
78	true -> is_varbinds_ok(IVarbinds);
79	ErrorStatus ->
80	    Varbind = IVarbind#ivarbind.varbind,
81	    ?vtrace("varbinds erroneous: ~p -> ~p",
82		    [Varbind#varbind.org_index,ErrorStatus]),
83	    {ErrorStatus, Varbind#varbind.org_index}
84    end;
85is_varbinds_ok([]) ->
86    ?vtrace("varbinds ok",[]),
87    {noError, 0}.
88
89%%-----------------------------------------------------------------
90%% Func: is_varbind_ok/1
91%% Purpose: Check everything we can check about the varbind against
92%%          the MIB. Here we don't call any instrumentation functions.
93%% Returns: true |
94%% Fails:   with an <error-atom>.
95%%-----------------------------------------------------------------
96is_varbind_ok(#ivarbind{status = Status,
97			mibentry = MibEntry,
98			varbind = Varbind}) ->
99    variableExists(Status, MibEntry, Varbind),
100    % If we get here, MibEntry /= false
101    variableIsWritable(MibEntry, Varbind),
102    checkASN1Type(MibEntry, Varbind),
103    checkValEncoding(MibEntry, Varbind),
104    true.
105
106variableExists(noError, false, _Varbind) -> throw(notWritable);
107variableExists(noError, _MibEntry, _Varbind) -> true;
108%% ErrorStatus == noSuchObject | noSuchInstance
109variableExists(noSuchObject, _MibEntry, _Varbind) -> throw(notWritable);
110variableExists(_ErrorStatus, _MibEntry, _Varbind) -> throw(noCreation).
111
112variableIsWritable(#me{access = 'read-write'}, _Varbind) -> true;
113variableIsWritable(#me{access = 'read-create'}, _Varbind) -> true;
114variableIsWritable(#me{access = 'write-only'}, _Varbind) -> true;
115variableIsWritable(_MibEntry, _Varbind) -> throw(notWritable).
116
117%% Uses make_value_a_correct_value to check type, length and range.
118%% Returns: true | <error-atom>
119checkASN1Type(#me{asn1_type = ASN1Type},
120	      #varbind{variabletype = Type, value = Value})
121     when ASN1Type#asn1_type.bertype =:= Type ->
122    case make_value_a_correct_value({value, Value}, ASN1Type,
123				    undef) of
124	{value, Type, Value} -> true;
125	{error, Error} when is_atom(Error) -> throw(Error)
126    end;
127
128checkASN1Type(_,_) -> throw(wrongType).
129
130
131%% tricky...
132checkValEncoding(_MibEntry, _Varbind) -> true.
133
134%%-----------------------------------------------------------------
135%% Func: consistency_check/1
136%% Purpose: Scans all vbs, and checks whether there is a is_set_ok
137%%          function or not. If it exists, it is called with the
138%%          vb, to check if the set-op is ok. If it doesn't exist,
139%%          it is considered ok to set the variable.
140%% Returns: {noError, 0} | {ErrorStatus, ErrorIndex]
141%% PRE: #ivarbind.status == noError for each varbind
142%%-----------------------------------------------------------------
143consistency_check(Varbinds) ->
144    consistency_check(Varbinds, []).
145consistency_check([{TableOid, TableVbs} | Varbinds], Done) ->
146    ?vtrace("consistency_check -> entry with"
147	    "~n   TableOid: ~p"
148	    "~n   TableVbs: ~p", [TableOid, TableVbs]),
149    TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs),
150    [#ivarbind{mibentry = MibEntry}|_] = TableVbs,
151    case is_set_ok_table(MibEntry, TableOpsWithShortOids) of
152	{noError, 0} ->
153	    consistency_check(Varbinds, [{TableOid, TableVbs} | Done]);
154	{Reason, Idx} ->
155	    case undo_varbinds(Done) of
156		{noError, 0} -> {Reason, find_org_index(Idx, TableVbs)};
157		Error -> Error
158	    end
159    end;
160consistency_check([IVarbind | Varbinds], Done) ->
161    ?vtrace("consistency_check -> entry with"
162	    "~n   IVarbind: ~p", [IVarbind]),
163    #ivarbind{varbind = Varbind, mibentry = MibEntry} = IVarbind,
164    #varbind{value = Value, org_index = OrgIndex} = Varbind,
165    case is_set_ok_variable(MibEntry, Value) of
166	noError -> consistency_check(Varbinds, [IVarbind | Done]);
167	Reason ->
168	    case undo_varbinds(Done) of
169		{noError, 0} -> {Reason, OrgIndex};
170		Error -> Error
171	    end
172    end;
173consistency_check([], _Done) ->
174    ?vtrace("consistency_check -> done",[]),
175    {noError, 0}.
176
177deletePrefixes(Prefix, [#ivarbind{varbind = Varbind} | Vbs]) ->
178    #varbind{oid = Oid, value = Value} = Varbind,
179    [{snmp_misc:diff(Oid, Prefix), Value} | deletePrefixes(Prefix, Vbs)];
180deletePrefixes(_Prefix, []) -> [].
181
182%% Val = <a-value>
183is_set_ok_variable(#me{mfa = {Module, Func, Args} = MFA}, Val) ->
184    ?vtrace("is_set_ok_variable -> entry with"
185	"~n   MFA: ~p"
186	"~n   Val: ~p",[MFA,Val]),
187    case dbg_apply(Module, Func, [is_set_ok, Val | Args]) of
188	{'EXIT', {hook_undef, _}} -> noError;
189	{'EXIT', {hook_function_clause, _}} -> noError;
190	Result ->
191	    ?vtrace("is_set_ok_variable -> Result: ~n   ~p", [Result]),
192	    validate_err(is_set_ok, Result, {Module, Func, Args})
193    end.
194
195%% ValueArg: <list-of-simple-tableops>
196is_set_ok_table(#me{mfa = {Module, Func, Args} = MFA}, ValueArg) ->
197    ?vtrace("is_set_ok_table -> entry with"
198	"~n   MFA:      ~p"
199	"~n   ValueArg: ~p",[MFA,ValueArg]),
200    is_set_ok_all_rows(Module, Func, Args, sort_varbinds_rows(ValueArg), []).
201
202%% Try one row at a time. Sort varbinds to table-format.
203is_set_ok_all_rows(Module, Func, Args, [Row | Rows], Done) ->
204    ?vtrace("is_set_ok_all_rows -> entry with"
205	"~n   MFA:  ~p"
206	"~n   Row:  ~p"
207	"~n   Done: ~p",[{Module,Func,Args},Row,Done]),
208    [{RowIndex, Cols}] = delete_org_index([Row]),
209    case dbg_apply(Module, Func, [is_set_ok, RowIndex, Cols | Args]) of
210	{'EXIT', {hook_undef, _}} ->
211	    is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]);
212	{'EXIT', {hook_function_clause, _}} ->
213	    is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]);
214	Result ->
215	    case validate_err(table_is_set_ok, Result, {Module, Func, Args}) of
216		{noError, 0} ->
217		    is_set_ok_all_rows(Module, Func, Args, Rows, [Row | Done]);
218		{ErrorStatus, ColNumber} ->
219		    case undo_all_rows(Module, Func, Args, Done) of
220			{noError, 0} ->
221			    {RowIndex, OrgCols} = Row,
222			    validate_err(row_is_set_ok,
223					 {ErrorStatus,
224					  col_to_orgindex(ColNumber,OrgCols)},
225					 {Module, Func, Args});
226			Error -> Error
227		    end
228	    end
229    end;
230is_set_ok_all_rows(_Module, _Func, _Args, [], _Done) ->
231    ?vtrace("is_set_ok_all_rows -> done",[]),
232    {noError, 0}.
233
234undo_varbinds([{TableOid, TableVbs} | Varbinds]) ->
235    TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs),
236    [#ivarbind{mibentry = MibEntry}|_] = TableVbs,
237    case undo_table(MibEntry, TableOpsWithShortOids) of
238	{noError, 0} ->
239	    undo_varbinds(Varbinds);
240	{Reason, Idx} ->
241	    undo_varbinds(Varbinds),
242	    {Reason, Idx}
243    end;
244undo_varbinds([IVarbind | Varbinds]) ->
245    #ivarbind{varbind = Varbind, mibentry = MibEntry} = IVarbind,
246    #varbind{value = Value, org_index = OrgIndex} = Varbind,
247    case undo_variable(MibEntry, Value) of
248	noError -> undo_varbinds(Varbinds);
249	Reason ->
250	    undo_varbinds(Varbinds),
251	    {Reason, OrgIndex}
252    end;
253undo_varbinds([]) -> {noError, 0}.
254
255%% Val = <a-value>
256undo_variable(#me{mfa = {Module, Func, Args}}, Val) ->
257    case dbg_apply(Module, Func, [undo, Val | Args]) of
258	{'EXIT', {hook_undef, _}} -> noError;
259	{'EXIT', {hook_function_clause, _}} -> noError;
260	Result -> validate_err(undo, Result, {Module, Func, Args})
261    end.
262
263%% ValueArg: <list-of-simple-tableops>
264undo_table(#me{mfa = {Module, Func, Args}}, ValueArg) ->
265    undo_all_rows(Module, Func, Args, sort_varbinds_rows(ValueArg)).
266
267undo_all_rows(Module, Func, Args, [Row | Rows]) ->
268    [{RowIndex, Cols}] = delete_org_index([Row]),
269    case dbg_apply(Module, Func, [undo, RowIndex, Cols | Args]) of
270	{'EXIT', {hook_undef, _}} ->
271	    undo_all_rows(Module, Func, Args, Rows);
272	{'EXIT', {hook_function_clause, _}} ->
273	    undo_all_rows(Module, Func, Args, Rows);
274	Result ->
275	    case validate_err(table_undo, Result, {Module, Func, Args}) of
276		{noError, 0} -> undo_all_rows(Module, Func, Args, Rows);
277		{ErrorStatus, ColNumber} ->
278		    {RowIndex, OrgCols} = Row,
279		    undo_all_rows(Module, Func, Args, Rows),
280		    OrgIdx = col_to_orgindex(ColNumber, OrgCols),
281		    validate_err(row_undo, {ErrorStatus, OrgIdx},
282				 {Module, Func, Args})
283	    end
284    end;
285undo_all_rows(_Module, _Func, _Args, []) ->
286    {noError, 0}.
287
288try_set([{TableOid, TableVbs} | Varbinds]) ->
289    TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs),
290    [#ivarbind{mibentry = MibEntry}|_] = TableVbs,
291    case set_value_to_tab_mibentry(MibEntry, TableOpsWithShortOids) of
292	{noError, 0} ->
293	    try_set(Varbinds);
294	{ErrorStatus, Index} ->
295	    undo_varbinds(Varbinds),
296	    {ErrorStatus, find_org_index(Index, TableVbs)}
297    end;
298try_set([#ivarbind{varbind = Varbind, mibentry = MibEntry} | Varbinds]) ->
299    #varbind{value = Value, org_index = Index} = Varbind,
300    case set_value_to_var_mibentry(MibEntry, Value) of
301	noError -> try_set(Varbinds);
302	Error ->
303	    undo_varbinds(Varbinds),
304	    {Error, Index}
305    end;
306try_set([]) -> {noError, 0}.
307
308
309%% returns: ErrMsg
310set_value_to_var_mibentry(#me{mfa = {Module, Func, Args}},
311			  SetArgs) ->
312    Result = dbg_apply(Module, Func, [set, SetArgs | Args]),
313    validate_err(set, Result, {Module, Func, Args}).
314
315%% returns: {ErrMsg, Idx}
316set_value_to_tab_mibentry(#me{mfa = {Module, Func, Args}},
317			  SetArgs) ->
318    set_value_all_rows(Module, Func, Args,
319		       sort_varbinds_rows(SetArgs)).
320
321
322set_value_all_rows(_Module, _Func, _Args, []) -> {noError, 0};
323set_value_all_rows(Module, Func, Args, [Row | Rows]) ->
324    [{RowIndex, Cols}] = delete_org_index([Row]),
325    Res = dbg_apply(Module, Func, [set, RowIndex, Cols | Args]),
326    case validate_err(table_set, Res, {Module, Func, Args}) of
327	{noError, 0} -> set_value_all_rows(Module, Func, Args, Rows);
328	{ErrCode, ColNumber} ->
329	    {RowIndex, OrgCols} = Row,
330	    OrgIndex = col_to_orgindex(ColNumber, OrgCols),
331	    validate_err(row_set, {ErrCode, OrgIndex}, {Module, Func, Args})
332    end.
333
334sort_varbinds_rows(Varbinds) ->
335    snmpa_svbl:sort_varbinds_rows(Varbinds).
336delete_org_index(Indexes) ->
337    snmpa_svbl:delete_org_index(Indexes).
338col_to_orgindex(ColNumber, OrgCols) ->
339    snmpa_svbl:col_to_orgindex(ColNumber, OrgCols).
340
341find_org_index(ExternIndex, _SortedVarbinds) when ExternIndex =:= 0 -> 0;
342find_org_index(ExternIndex, SortedVarbinds) ->
343    VBs = lists:flatten(SortedVarbinds),
344    case length(VBs) of
345	Len when (ExternIndex =< Len) andalso (ExternIndex >= 1) ->
346	    IVB = lists:nth(ExternIndex, VBs),
347	    VB = IVB#ivarbind.varbind,
348	    VB#varbind.org_index;
349	_Else ->
350	    0
351    end.
352
353validate_err(Type, Error, Mfa) ->
354    snmpa_agent:validate_err(Type, Error, Mfa).
355make_value_a_correct_value(Value, ASN1Type, Mfa) ->
356    snmpa_agent:make_value_a_correct_value(Value, ASN1Type, Mfa).
357
358%%-----------------------------------------------------------------
359%% Runtime debug support
360%%-----------------------------------------------------------------
361
362%% XYZ: This function match on the exakt return codes from EXIT
363%% messages. As of this writing it was not decided if this is
364%% the right way so don't blindly do things this way.
365%%
366%% We fake a real EXIT signal as the return value because the
367%% result is passed to the function snmpa_agent:validate_err()
368%% that expect it.
369
370dbg_apply(M,F,A) ->
371    case maybe_verbose_apply(M, F, A) of
372        %% <Future proofing>
373        %% As of R15 we get extra info containing,
374        %% among other things, line numbers.
375        {'EXIT', {undef, [{M, F, A, _} | _]}} ->
376            {'EXIT', {hook_undef, {M, F, A}}};
377        {'EXIT', {function_clause, [{M, F, A, _} | _]}} ->
378            {'EXIT', {hook_function_clause, {M, F, A}}};
379
380        %% This is really overkill, but just to be on the safe side...
381        {'EXIT', {undef, {M, F, A, _}}} ->
382            {'EXIT', {hook_undef, {M, F, A}}};
383        {'EXIT', {function_clause, {M, F, A, _}}} ->
384            {'EXIT', {hook_function_clause, {M, F, A}}};
385        %% </Future proofing>
386
387        %% Old format format for compatibility
388        {'EXIT', {undef, [{M, F, A} | _]}} ->
389            {'EXIT', {hook_undef, {M, F, A}}};
390        {'EXIT', {function_clause, [{M, F, A} | _]}} ->
391            {'EXIT', {hook_function_clause, {M, F, A}}};
392
393        %% XYZ: Older format for compatibility
394        {'EXIT', {undef, {M, F, A}}} ->
395            {'EXIT', {hook_undef, {M, F, A}}};
396        {'EXIT', {function_clause, {M, F, A}}} ->
397            {'EXIT', {hook_function_clause, {M, F, A}}};
398
399        Result ->
400            Result
401    end.
402
403maybe_verbose_apply(M, F, A) ->
404    case get(verbosity) of
405        false ->
406            (catch apply(M,F,A));
407        _ ->
408            ?vlog("~n   apply: ~w,~w,~p~n", [M,F,A]),
409            Res = (catch apply(M,F,A)),
410            ?vlog("~n   returned: ~p", [Res]),
411            Res
412    end.
413