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
21-module(snmp_config).
22
23-include_lib("kernel/include/file.hrl").
24-include("snmp_types.hrl").
25
26%% Avoid warning for local function error/1 clashing with autoimported BIF.
27-compile({no_auto_import,[error/1]}).
28-export([config/0]).
29
30%%-export([write_config_file/4, append_config_file/4, read_config_file/4]).
31
32-export([write_config_file/6, append_config_file/6, read_config_file/4]).
33
34-export([write_agent_snmp_files/7, write_agent_snmp_files/12,
35	 write_agent_snmp_files/6, write_agent_snmp_files/11,
36
37	 write_agent_snmp_conf/4, write_agent_snmp_conf/5,
38	 write_agent_snmp_context_conf/1,
39	 write_agent_snmp_community_conf/1,
40	 write_agent_snmp_standard_conf/2,
41	 write_agent_snmp_target_addr_conf/3,
42	 write_agent_snmp_target_addr_conf/4,
43	 write_agent_snmp_target_addr_conf/5,
44	 write_agent_snmp_target_addr_conf/6,
45	 write_agent_snmp_target_params_conf/2,
46	 write_agent_snmp_notify_conf/2,
47	 write_agent_snmp_usm_conf/5,
48	 write_agent_snmp_vacm_conf/3,
49
50	 write_manager_snmp_files/4, write_manager_snmp_files/5,
51         write_manager_snmp_files/7, write_manager_snmp_files/8,
52	 write_manager_snmp_conf/4, write_manager_snmp_conf/5,
53	 write_manager_snmp_users_conf/2,
54	 write_manager_snmp_agents_conf/2,
55	 write_manager_snmp_usm_conf/2
56
57	]).
58
59-export([write_agent_config/3,
60	 update_agent_config/2,
61
62	 write_agent_context_config/3,
63	 update_agent_context_config/2,
64
65	 write_agent_community_config/3,
66	 update_agent_community_config/2,
67
68	 write_agent_standard_config/3,
69	 update_agent_standard_config/2,
70
71	 write_agent_target_addr_config/3,
72	 update_agent_target_addr_config/2,
73
74	 write_agent_target_params_config/3,
75	 update_agent_target_params_config/2,
76
77	 write_agent_notify_config/3,
78	 update_agent_notify_config/2,
79
80	 write_agent_vacm_config/3,
81	 update_agent_vacm_config/2,
82
83	 write_agent_usm_config/3,
84	 update_agent_usm_config/2,
85
86	 write_manager_config/3,
87	 update_manager_config/2,
88
89	 write_manager_users_config/3,
90	 update_manager_users_config/2,
91
92	 write_manager_agents_config/3,
93	 update_manager_agents_config/2,
94
95	 write_manager_usm_config/3,
96	 update_manager_usm_config/2
97	]).
98
99
100-export_type([
101              order_config_entry_function/0,
102	      check_config_entry_function/0,
103	      write_config_function/0
104             ]).
105
106
107%%----------------------------------------------------------------------
108
109%%----------------------------------------------------------------------
110%% Handy SNMP configuration
111%%----------------------------------------------------------------------
112
113config() ->
114    case (catch config2()) of
115	ok ->
116	    ok;
117	{error, Reason} ->
118	    {error, Reason};
119	E ->
120	    {error, {failed, E}}
121    end.
122
123
124config2() ->
125    intro(),
126    SysAgentConfig =
127	case config_agent() of
128	    [] ->
129		[];
130	    SAC ->
131		[{agent, SAC}]
132	end,
133    SysMgrConfig   =
134	case config_manager() of
135	    [] ->
136		[];
137	    SMC ->
138		[{manager, SMC}]
139	end,
140    config_sys(SysAgentConfig ++ SysMgrConfig),
141    ok.
142
143
144intro() ->
145    i("~nSimple SNMP configuration tool (version ~s)", [?version]),
146    i("------------------------------------------------"),
147    i("Note: Non-trivial configurations still has to be"),
148    i("      done manually. IP addresses may be entered "),
149    i("      as dront.ericsson.se (UNIX only) or"),
150    i("      123.12.13.23"),
151    i("------------------------------------------------"),
152    ok.
153
154
155config_agent() ->
156    case (catch snmp_agent2()) of
157	ok ->
158	    [];
159	{ok, SysConf} ->
160	    SysConf;
161	{error, Reason} ->
162	    error(Reason);
163	{'EXIT', Reason} ->
164	    error(Reason);
165	E ->
166	    error({agent_config_failed, E})
167    end.
168
169snmp_agent2() ->
170    case ask("~nConfigure an agent (y/n)?", "y", fun verify_yes_or_no/1) of
171	yes ->
172	    {Vsns, ConfigDir, SysConf} = config_agent_sys(),
173	    config_agent_snmp(ConfigDir, Vsns),
174	    {ok, SysConf};
175	no ->
176	    ok
177    end.
178
179
180config_manager() ->
181    case (catch config_manager2()) of
182	ok ->
183	    [];
184	{ok, SysConf} ->
185	    SysConf;
186	{error, Reason} ->
187	    error(Reason);
188	{'EXIT', Reason} ->
189	    error(Reason);
190	E ->
191	    error({manager_config_failed, E})
192    end.
193
194config_manager2() ->
195    case ask("~nConfigure a manager (y/n)?", "y", fun verify_yes_or_no/1) of
196	yes ->
197	    {Vsns, ConfigDir, SysConf} = config_manager_sys(),
198 	    config_manager_snmp(ConfigDir, Vsns),
199	    {ok, SysConf};
200	no ->
201	    ok
202    end.
203
204
205config_sys(SysConfig) ->
206    i("~n--------------------"),
207    {ok, DefDir} = file:get_cwd(),
208    ConfigDir = ask("Configuration directory for system file (absolute path)?",
209		    DefDir, fun verify_dir/1),
210    write_sys_config_file(ConfigDir, SysConfig).
211
212
213%% -------------------
214
215config_agent_sys() ->
216    i("~nAgent system config: "
217      "~n--------------------"),
218    Prio = ask("1. Agent process priority (low/normal/high)",
219	       "normal", fun verify_prio/1),
220    Vsns = ask("2. What SNMP version(s) should be used "
221	       "(1,2,3,1&2,1&2&3,2&3)?", "3", fun verify_versions/1),
222    %% d("Vsns: ~p", [Vsns]),
223    {ok, DefDir} = file:get_cwd(),
224    {DefConfDir, Warning} = default_agent_dir(DefDir),
225    ConfQ =
226	if
227	    Warning == "" ->
228		"3. Configuration directory (absolute path)?";
229	    true ->
230		lists:flatten(
231		  io_lib:format("3. Configuration directory (absolute path)?"
232				"~n   ~s", [Warning]))
233	end,
234    ConfigDir = ask(ConfQ, DefConfDir, fun verify_dir/1),
235    ConfigVerb = ask("4. Config verbosity "
236		     "(silence/info/log/debug/trace)?",
237		     "silence",
238		     fun verify_verbosity/1),
239    DbDir     = ask("5. Database directory (absolute path)?", DefDir,
240		    fun verify_dir/1),
241    DbInitError = ask("6. How to handle DB init error?",
242		      "terminate", fun verify_db_init_error/1),
243    MibStorageType = ask("7. Mib storage type (ets/dets/mnesia)?", "ets",
244			 fun verify_mib_storage_type/1),
245    MibStorage =
246	case MibStorageType of
247	    ets ->
248		[{module, snmpa_mib_storage_ets}];
249	    dets ->
250		DetsDir = ask("7b. Mib storage directory (absolute path)?",
251			      DbDir, fun verify_dir/1),
252		DetsAction = ask("7c. Mib storage [dets] database start "
253				 "action "
254				 "(default/clear/keep)?",
255				 "default", fun verify_mib_storage_action/1),
256		case DetsAction of
257		    default ->
258			[{module, snmpa_mib_storage_dets},
259			 {options, [{dir,    DetsDir}]}];
260		    _ ->
261			[{module, snmpa_mib_storage_dets},
262			 {options, [{dir,    DetsDir},
263				    {action, DetsAction}]}]
264		end;
265	    mnesia ->
266		Nodes = [],
267		MnesiaAction = ask("7b. Mib storage [mnesia] database start "
268				   "action "
269				   "(default/clear/keep)?",
270				   "default", fun verify_mib_storage_action/1),
271		case MnesiaAction of
272		    default ->
273			[{module, snmpa_mib_storage_mnesia},
274			 {options, [{nodes, Nodes}]}];
275		    _ ->
276			[{module, snmpa_mib_storage_mnesia},
277			 {options, [{nodes,  Nodes},
278				    {action, MnesiaAction}]}]
279		end
280	end,
281
282    %% Here we should ask about mib-server data module,
283    %% but as we only have one at the moment...
284
285    TargetCacheVerb = ask("8. Target cache verbosity "
286			  "(silence/info/log/debug/trace)?", "silence",
287			  fun verify_verbosity/1),
288    SymStoreVerb = ask("9. Symbolic store verbosity "
289		       "(silence/info/log/debug/trace)?", "silence",
290		       fun verify_verbosity/1),
291    LocalDbVerb = ask("10. Local DB verbosity "
292		       "(silence/info/log/debug/trace)?", "silence",
293		       fun verify_verbosity/1),
294    LocalDbRepair = ask("11. Local DB repair (true/false/force)?", "true",
295			fun verify_dets_repair/1),
296    LocalDbAutoSave = ask("12. Local DB auto save (infinity/milli seconds)?",
297			  "5000", fun verify_dets_auto_save/1),
298    ErrorMod = ask("13. Error report module?", "snmpa_error_logger", fun verify_module/1),
299    Type = ask("14. Agent type (master/sub)?", "master",
300	       fun verify_agent_type/1),
301    AgentConfig =
302	case Type of
303	    master ->
304		MasterAgentVerb = ask("15. Master-agent verbosity "
305				      "(silence/info/log/debug/trace)?",
306				      "silence",
307				      fun verify_verbosity/1),
308		ForceLoad = ask("16. Shall the agent re-read the "
309				"configuration files during startup ~n"
310				"    (and ignore the configuration "
311				"database) (true/false)?", "true",
312				fun verify_bool/1),
313		MultiThreaded = ask("17. Multi threaded agent (true/false)?",
314				    "false",
315				    fun verify_bool/1),
316		MeOverride = ask("18. Check for duplicate mib entries when "
317				 "installing a mib (true/false)?", "false",
318				 fun verify_bool/1),
319		TrapOverride = ask("19. Check for duplicate trap names when "
320				   "installing a mib (true/false)?", "false",
321				   fun verify_bool/1),
322		MibServerVerb = ask("20. Mib server verbosity "
323				    "(silence/info/log/debug/trace)?",
324				    "silence",
325				    fun verify_verbosity/1),
326		MibServerCache = ask("21. Mib server cache "
327				    "(true/false)?",
328				    "true",
329				    fun verify_bool/1),
330		NoteStoreVerb = ask("22. Note store verbosity "
331				    "(silence/info/log/debug/trace)?",
332				    "silence",
333				    fun verify_verbosity/1),
334		NoteStoreTimeout = ask("23. Note store GC timeout?", "30000",
335				       fun verify_timeout/1),
336		ATL =
337		    case ask("24. Shall the agent use an audit trail log "
338			     "(y/n)?",
339			     "n", fun verify_yes_or_no/1) of
340			yes ->
341			    ATLType = ask("24b. Audit trail log type "
342					  "(write/read_write)?",
343					  "read_write", fun verify_atl_type/1),
344			    ATLDir = ask("24c. Where to store the "
345					 "audit trail log?",
346					 DefDir, fun verify_dir/1),
347			    ATLMaxFiles = ask("24d. Max number of files?",
348					      "10",
349					      fun verify_pos_integer/1),
350			    ATLMaxBytes = ask("24e. Max size (in bytes) "
351					      "of each file?",
352					      "10240",
353					      fun verify_pos_integer/1),
354			    ATLSize = {ATLMaxBytes, ATLMaxFiles},
355			    ATLRepair = ask("24f. Audit trail log repair "
356					    "(true/false/truncate/snmp_repair)?", "true",
357					    fun verify_atl_repair/1),
358			    ATLSeqNo = ask("24g. Audit trail log "
359					   "sequence-numbering (true/false)?",
360					   "false",
361					   fun verify_atl_seqno/1),
362			    [{audit_trail_log, [{type,   ATLType},
363						{dir,    ATLDir},
364						{size,   ATLSize},
365						{repair, ATLRepair},
366						{seqno,  ATLSeqNo}]}];
367			no ->
368			    []
369		    end,
370		NetIfVerb = ask("25. Network interface verbosity "
371				"(silence/info/log/debug/trace)?",
372				"silence",
373				fun verify_verbosity/1),
374		NetIfMod = ask("26. Which network interface module shall be used?",
375			       "snmpa_net_if", fun verify_module/1),
376		NetIfOpts =
377		    case NetIfMod of
378			snmpa_net_if ->
379			    NetIfBindTo =
380				ask("26a. Bind the agent IP address "
381				    "(true/false)?",
382				    "false", fun verify_bool/1),
383			    NetIfNoReuse =
384				ask("26b. Shall the agents "
385				    "IP address "
386				    "and port be not reusable "
387				    "(true/false)?",
388				    "false", fun verify_bool/1),
389			    NetIfReqLimit =
390				ask("26c. Agent request limit "
391				    "(used for flow control) "
392				    "(infinity/pos integer)?",
393				    "infinity",
394				    fun verify_netif_req_limit/1),
395			    NetIfRecbuf =
396				case ask("26d. Receive buffer size of the "
397					 "agent (in bytes) "
398					 "(default/pos integer)?",
399					 "default",
400					 fun verify_netif_recbuf/1) of
401				    default ->
402					[];
403				    RecBufSz ->
404					[{recbuf, RecBufSz}]
405				end,
406			    NetIfSndbuf =
407				case ask("26e. Send buffer size of the agent "
408					 "(in bytes) (default/pos integer)?",
409					 "default",
410					 fun verify_netif_sndbuf/1) of
411				    default ->
412					[];
413				    SndBufSz ->
414					[{sndbuf, SndBufSz}]
415				end,
416			    NetIfFilter =
417				case ask("26f. Do you wish to specify a "
418					 "network interface filter module "
419					 "(or use default)",
420					 "default", fun verify_module/1) of
421				    default ->
422					[];
423				    NetIfFilterMod ->
424					[{filter, [{module, NetIfFilterMod}]}]
425				end,
426			    [{bind_to,   NetIfBindTo},
427			     {no_reuse,  NetIfNoReuse},
428			     {req_limit, NetIfReqLimit}] ++
429				NetIfRecbuf ++ NetIfSndbuf ++ NetIfFilter;
430			_ ->
431			    []
432		    end,
433		NetIf = [{module,    NetIfMod},
434			 {verbosity, NetIfVerb},
435			 {options,   NetIfOpts}],
436		TermDiscoEnable = ask("27. Allow terminating discovery "
437				      "(true/false)?", "true",
438				      fun verify_bool/1),
439		TermDiscoConf =
440		    case TermDiscoEnable of
441			true ->
442			    TermDiscoStage2 =
443				ask("27a. Second stage behaviour "
444				    "(discovery/plain)?", "discovery",
445				    fun verify_term_disco_behaviour/1),
446			    TermDiscoTrigger =
447				ask("27b. Trigger username "
448				    "(default/a string)?", "default",
449				    fun verify_term_disco_trigger_username/1),
450			    [{enable, TermDiscoEnable},
451			     {stage2, TermDiscoStage2},
452			     {trigger_username, TermDiscoTrigger}];
453			false ->
454			    [{enable, TermDiscoEnable},
455			     {stage2, discovery},
456			     {trigger_username, ""}]
457		    end,
458		OrigDiscoEnable = ask("28. Allow originating discovery "
459				      "(true/false)?", "true",
460				      fun verify_bool/1),
461		OrigDiscoConf =
462		    [{enable, OrigDiscoEnable}],
463		DiscoveryConfig =
464		    [{terminating, TermDiscoConf},
465		     {originating, OrigDiscoConf}],
466		[{agent_type,      master},
467		 {agent_verbosity, MasterAgentVerb},
468		 {discovery,       DiscoveryConfig},
469		 {config,          [{dir,        ConfigDir},
470				    {force_load, ForceLoad},
471				    {verbosity,  ConfigVerb}]},
472		 {multi_threaded,  MultiThreaded},
473		 {mib_server,      [{mibentry_override,  MeOverride},
474				    {trapentry_override, TrapOverride},
475				    {verbosity,          MibServerVerb},
476				    {cache,              MibServerCache}]},
477		 {note_store,      [{timeout,   NoteStoreTimeout},
478				    {verbosity, NoteStoreVerb}]},
479		 {net_if, NetIf}] ++ ATL;
480	    sub ->
481		SubAgentVerb = ask("15. Sub-agent verbosity "
482				   "(silence/info/log/debug/trace)?",
483				   "silence",
484				   fun verify_verbosity/1),
485		[{agent_type,      sub},
486		 {agent_verbosity, SubAgentVerb},
487		 {config,          [{dir, ConfigDir}]}]
488	end,
489    SysConfig =
490	[{priority,       Prio},
491	 {versions,       Vsns},
492	 {db_dir,         DbDir},
493	 {db_init_error,  DbInitError},
494	 {mib_storage,    MibStorage},
495	 {target_cache,   [{verbosity, TargetCacheVerb}]},
496	 {symbolic_store, [{verbosity, SymStoreVerb}]},
497	 {local_db, [{repair,    LocalDbRepair},
498		     {auto_save, LocalDbAutoSave},
499		     {verbosity, LocalDbVerb}]},
500	 {error_report_module, ErrorMod}] ++ AgentConfig,
501    {Vsns, ConfigDir, SysConfig}.
502
503
504config_agent_transports(ID) ->
505    config_agent_transports(ID, []).
506
507config_agent_transports(ID, []) ->
508    i(ID ++ ". Configure atleast one transport: "),
509    T = config_agent_transport(ID),
510    config_agent_transports(ID, [T]);
511config_agent_transports(ID, Acc) ->
512    case ask(ID ++ ". Configure another transport (yes/no)?",
513             "yes", fun verify_yes_or_no/1) of
514        yes ->
515            T = config_agent_transport(ID),
516            config_agent_transports(ID, [T|Acc]);
517        no ->
518            lists:reverse(Acc)
519    end.
520
521config_agent_transport(ID) ->
522    TDomain  = ask(ID ++ "a. Transport Domain "
523                   "(UDP IPv4 (u4) or UDP IPv6 (u6))",
524                   "u4", fun verify_transport_domain/1),
525    Host     = host(TDomain),
526    Address  = ask(ID ++ "b. Address of transport",
527                   Host, fun(A) -> verify_transport_address(TDomain, A) end),
528    PortInfo = ask(ID ++ "c. Port number info (how we shall choose a port)"
529                   "~n    Note that we do not allow all variants here! "
530                   "~n    Edit manually for more variants (range/ranges)."
531                   "~n    default(d)/system(s)/pos-integer",
532                   "4000", fun verify_port_number_info/1),
533    TAddress = {Address, PortInfo},
534    Kind = ask(ID ++ "d. Kind of transport "
535               "(all(a)/request-responder(rr)/trap-sender(ts))",
536               "a", fun verify_transport_kind/1),
537    i("*** We do not ask about the transport options here ***~n"
538      "*** the user must manually edit the config files!  ***"),
539    case Kind of
540        all ->
541            {TDomain, TAddress, []};
542        _ when (Kind =:= req_responder) orelse (Kind =:= trap_sender) ->
543            {TDomain, TAddress, Kind, []}
544    end.
545
546config_agent_snmp(Dir, Vsns) ->
547    i("~nAgent snmp config: "
548      "~n------------------"),
549    AgentName  = guess_agent_name(),
550    EngineName = guess_engine_name(),
551    SysName    = ask("1. System name (sysName standard variable)",
552		     AgentName, fun verify_system_name/1),
553    EngineID   = ask("2. Engine ID (snmpEngineID standard variable)",
554		      EngineName, fun verify_engine_id/1),
555    MMS        = ask("3. Max message size?", "484",
556		     fun verify_max_message_size/1),
557    ATransports = config_agent_transports("4"),
558    %% AgentUDP   = ask("4. The UDP port the agent listens to. "
559    %%     	     "(standard 161)",
560    %%     	     "4000", fun verify_port_number/1),
561    %% AgentIP    = ask("5. IP address for the agent (only used as id ~n"
562    %%     	     "   when sending traps)", Host, fun verify_address/1),
563
564    ManagerTDomain  = ask("5. Manager Transport Domain"
565                          "(UDP IPv4 (u4) or UDP IPv6 (u6))",
566                          "u4", fun verify_transport_domain/1),
567    Host       = host(ManagerTDomain),
568    ManagerIP  = ask("6. IP address for the manager (only this manager ~n"
569		     "   will have access to the agent, traps are sent ~n"
570		     "   to this one)", Host,
571                     fun(A) -> verify_transport_address(ManagerTDomain, A) end),
572    ManagerUdp = ask("7. To what UDP port at the manager should traps ~n"
573		     "   be sent (standard 162)?", "5000",
574		     fun verify_port_number/1),
575
576    SecType    = ask("8. Do you want a none- minimum- or semi-secure"
577		     " configuration? ~n"
578		     "   Note that if you chose v1 or v2, you won't get any"
579		     " security for these~n"
580		     "   requests (none, minimum, semi_des, semi_aes)",
581		     "minimum",
582		    fun verify_sec_type/1),
583    Passwd =
584	case lists:member(v3, Vsns) and (SecType /= none) of
585	    true ->
586		ensure_crypto_started(),
587		ask("8b. Give a password of at least length 8. It is used to "
588		    "generate ~n"
589		    "    private keys for the configuration: ",
590		    mandatory, fun verify_passwd/1);
591	    false ->
592		""
593	end,
594    NotifType  =
595	case lists:member(v1, Vsns) of
596	    true ->
597		Overwrite = ask("9. Current configuration files will "
598				"now be overwritten. "
599				"Ok (y/n)?", "y", fun verify_yes_or_no/1),
600		case Overwrite of
601		    no ->
602			error(overwrite_not_allowed);
603		    yes ->
604			ok
605		end,
606		trap;
607	    false ->
608		NT = ask("9. Should notifications be sent as traps or informs "
609			 "(trap/inform)?", "trap", fun verify_notif_type/1),
610		Overwrite = ask("10. Current configuration files will "
611				"now be overwritten. "
612				"Ok (y/n)?", "y", fun verify_yes_or_no/1),
613		case Overwrite of
614		    no ->
615			error(overwrite_not_allowed);
616		    yes ->
617			ok
618		end,
619		NT
620	end,
621
622
623
624    case (catch write_agent_snmp_files(
625		  Dir, Vsns,
626                  ManagerTDomain, ManagerIP, ManagerUdp,
627                  ATransports, SysName,
628		  NotifType, SecType, Passwd, EngineID, MMS)) of
629	ok ->
630	   i("~n- - - - - - - - - - - - -"),
631	   i("Info: 1. SecurityName \"initial\" has noAuthNoPriv read access~n"
632	     "         and authenticated write access to the \"restricted\"~n"
633	     "         subtree."),
634	   i("      2. SecurityName \"all-rights\" has noAuthNoPriv "
635	     "read/write~n"
636	     "         access to the \"internet\" subtree."),
637	   i("      3. Standard traps are sent to the manager."),
638	   case lists:member(v1, Vsns) or lists:member(v2, Vsns) of
639	       true ->
640		   i("      4. Community \"public\" is mapped to security name"
641		     " \"initial\"."),
642		   i("      5. Community \"all-rights\" is mapped to security"
643		     " name \"all-rights\".");
644	       false ->
645		   ok
646	   end,
647	   i("The following agent files where written: agent.conf, "
648	     "community.conf,~n"
649	     "standard.conf, target_addr.conf, "
650	     "target_params.conf, ~n"
651	     "notify.conf" ++
652	     case lists:member(v3, Vsns) of
653		 true -> ", vacm.conf and usm.conf";
654		 false -> " and vacm.conf"
655	     end),
656	   i("- - - - - - - - - - - - -"),
657	   ok;
658	E ->
659	    error({failed_writing_files, E})
660    end.
661
662
663%% -------------------
664
665config_manager_sys() ->
666    i("~nManager system config: "
667      "~n----------------------"),
668    Prio = ask("1. Manager process priority (low/normal/high)",
669	       "normal", fun verify_prio/1),
670    Vsns = ask("2. What SNMP version(s) should be used "
671	       "(1,2,3,1&2,1&2&3,2&3)?", "3", fun verify_versions/1),
672    {ok, DefDir} = file:get_cwd(),
673    {DefConfDir, Warning} = default_manager_dir(DefDir),
674    ConfQ =
675	if
676	    Warning == "" ->
677		"3. Configuration directory (absolute path)?";
678	    true ->
679		lists:flatten(
680		  io_lib:format("3. Configuration directory (absolute path)?"
681				"~n   ~s", [Warning]))
682	end,
683    ConfigDir = ask(ConfQ, DefConfDir, fun verify_dir/1),
684    ConfigVerb = ask("4. Config verbosity "
685			"(silence/info/log/debug/trace)?",
686			"silence",
687			fun verify_verbosity/1),
688    ConfigDbDir = ask("5. Database directory (absolute path)?",
689		      DefDir, fun verify_dir/1),
690    ConfigDbInitError = ask("6. How to handle DB init error?",
691			    "terminate", fun verify_db_init_error/1),
692    ConfigDbRepair = ask("7. Database repair "
693			 "(true/false/force)?", "true",
694			 fun verify_dets_repair/1),
695    ConfigDbAutoSave = ask("8. Database auto save "
696			   "(infinity/milli seconds)?",
697			   "5000", fun verify_dets_auto_save/1),
698    IRB =
699	case ask("9. Inform request behaviour (auto/user)?",
700		 "auto", fun verify_irb/1) of
701	    auto ->
702		auto;
703	    user ->
704		case ask("9b. Use default GC timeout"
705			 "(default/seconds)?",
706			 "default", fun verify_irb_user/1) of
707		    default ->
708			user;
709		    IrbGcTo ->
710			{user, IrbGcTo}
711		end
712	end,
713    ServerVerb = ask("10. Server verbosity "
714			"(silence/info/log/debug/trace)?",
715			"silence",
716			fun verify_verbosity/1),
717    ServerTimeout = ask("11. Server GC timeout?", "30000",
718			   fun verify_timeout/1),
719    NoteStoreVerb = ask("12. Note store verbosity "
720			"(silence/info/log/debug/trace)?",
721			"silence",
722			fun verify_verbosity/1),
723    NoteStoreTimeout = ask("13. Note store GC timeout?", "30000",
724			   fun verify_timeout/1),
725    NetIfMod = ask("14. Which network interface module shall be used?",
726		   "snmpm_net_if", fun verify_module/1),
727    NetIfVerb = ask("15. Network interface verbosity "
728		    "(silence/info/log/debug/trace)?", "silence",
729		    fun verify_verbosity/1),
730    NetIfBindTo = ask("16. Bind the manager IP address "
731		      "(true/false)?",
732		      "false", fun verify_bool/1),
733    NetIfNoReuse = ask("17. Shall the manager IP address and port "
734		       "be *not* reusable (true/false)?",
735		       "false", fun verify_bool/1),
736    NetIfRecbuf =
737	case ask("18. Receive buffer size of the manager (in bytes) "
738		 "(default/pos integer)?", "default",
739		 fun verify_netif_recbuf/1) of
740	    default ->
741		[];
742	    RecBufSz ->
743		[{recbuf, RecBufSz}]
744	end,
745    NetIfSndbuf =
746	case ask("19. Send buffer size of the manager (in bytes) "
747		 "(default/pos integer)?", "default",
748		 fun verify_netif_sndbuf/1) of
749	    default ->
750		[];
751	    SndBufSz ->
752		[{sndbuf, SndBufSz}]
753	end,
754    NetIfOpts =
755	[{bind_to,   NetIfBindTo},
756	 {no_reuse,  NetIfNoReuse}] ++ NetIfRecbuf ++ NetIfSndbuf,
757    NetIf =
758	[{module,    NetIfMod},
759	 {verbosity, NetIfVerb},
760	 {options,   NetIfOpts}],
761    ATL =
762	case ask("20. Shall the manager use an audit trail log "
763		 "(y/n)?",
764		 "n", fun verify_yes_or_no/1) of
765	    yes ->
766		ATLType = ask("20b. Audit trail log type "
767			      "(write/read_write)?",
768			      "read_write", fun verify_atl_type/1),
769		ATLDir = ask("20c. Where to store the "
770			     "audit trail log?",
771			     DefDir, fun verify_dir/1),
772		ATLMaxFiles = ask("20d. Max number of files?",
773				  "10",
774				  fun verify_pos_integer/1),
775		ATLMaxBytes = ask("20e. Max size (in bytes) "
776				  "of each file?",
777				  "10240",
778				  fun verify_pos_integer/1),
779		ATLSize = {ATLMaxBytes, ATLMaxFiles},
780		ATLRepair = ask("20f. Audit trail log repair "
781				"(true/false/truncate/snmp_repair)?", "true",
782				fun verify_atl_repair/1),
783		ATLSeqNo = ask("20g. Audit trail log sequence-numbering "
784			       "(true/false)?", "false",
785			       fun verify_atl_seqno/1),
786		[{audit_trail_log, [{type,   ATLType},
787				    {dir,    ATLDir},
788				    {size,   ATLSize},
789				    {repair, ATLRepair},
790				    {seqno,  ATLSeqNo}]}];
791	    no ->
792		[]
793	end,
794    DefUser =
795	case ask("21. Do you wish to assign a default user [yes] or use~n"
796		 "    the default settings [no] (y/n)?", "n",
797		 fun verify_yes_or_no/1) of
798	    yes ->
799		DefUserMod = ask("21b. Default user module?",
800				 "snmpm_user_default",
801				 fun verify_module/1),
802		DefUserData = ask("21c. Default user data?", "undefined",
803				  fun verify_user_data/1),
804		[{def_user_mod,  DefUserMod},
805		 {def_user_data, DefUserData}];
806	    no ->
807		[]
808	end,
809    SysConfig =
810	[{priority,   Prio},
811	 {versions,   Vsns},
812	 {config,     [{dir,           ConfigDir},
813		       {db_dir,        ConfigDbDir},
814		       {db_init_error, ConfigDbInitError},
815		       {repair,        ConfigDbRepair},
816		       {auto_save,     ConfigDbAutoSave},
817		       {verbosity,     ConfigVerb}]},
818	 {inform_request_behaviour, IRB},
819	 {mibs,       []},
820	 {server,     [{timeout,   ServerTimeout},
821		       {verbosity, ServerVerb}]},
822	 {note_store, [{timeout,   NoteStoreTimeout},
823		       {verbosity, NoteStoreVerb}]},
824	 {net_if,     NetIf}] ++ ATL ++ DefUser,
825    {Vsns, ConfigDir, SysConfig}.
826
827
828config_manager_snmp(Dir, Vsns) ->
829    i("~nManager snmp config: "
830      "~n--------------------"),
831    EngineName = guess_engine_name(),
832    EngineID   = ask("1. Engine ID (snmpEngineID standard variable)",
833		      EngineName, fun verify_engine_id/1),
834    MMS        = ask("2. Max message size?", "484",
835		     fun verify_max_message_size/1),
836    Host       = host(),
837    IP         = ask("3. IP address for the manager (only used as id ~n"
838		     "   when sending requests)",
839		     Host, fun verify_address/1),
840    Port       = ask("4. Port number (standard 162)?", "5000",
841		     fun verify_port_number/1),
842    Users      = config_manager_snmp_users([]),
843    Agents     = config_manager_snmp_agents([]),
844    Usms       = config_manager_snmp_usms([]),
845    Overwrite = ask("8. Current configuration files will now be overwritten. "
846		    "Ok (y/n)?", "y", fun verify_yes_or_no/1),
847    case Overwrite of
848	no ->
849	    error(overwrite_not_allowed);
850	yes ->
851	    ok
852    end,
853    case (catch write_manager_snmp_files(Dir,
854					 IP, Port, MMS, EngineID,
855					 Users, Agents, Usms)) of
856	ok ->
857	   i("~n- - - - - - - - - - - - -"),
858	   i("The following manager files where written: "
859	     "manager.conf, agents.conf " ++
860	     case lists:member(v3, Vsns) of
861		 true ->
862		     ", users.conf and usm.conf";
863		 false ->
864		     " and users.conf"
865	     end),
866	   i("- - - - - - - - - - - - -"),
867	    ok;
868	E ->
869	    error({failed_writing_files, E})
870    end.
871
872
873config_manager_snmp_users(Users) ->
874    case ask("5. Configure a user of this manager (y/n)?",
875	     "y", fun verify_yes_or_no/1) of
876	yes ->
877	    User = config_manager_snmp_user(),
878	    config_manager_snmp_users([User|Users]);
879	no ->
880	    lists:reverse(Users)
881    end.
882
883config_manager_snmp_user() ->
884    UserId   = ask("5b. User id?", mandatory,
885		   fun verify_user_id/1),
886    UserMod  = ask("5c. User callback module?", mandatory,
887		   fun verify_module/1),
888    UserData = ask("5d. User (callback) data?", "undefined",
889		   fun verify_user_data/1),
890    {UserId, UserMod, UserData}.
891
892
893config_manager_snmp_agents(Agents) ->
894    case ask("6. Configure an agent handled by this manager (y/n)?",
895	     "y", fun verify_yes_or_no/1) of
896	yes ->
897	    Agent = config_manager_snmp_agent(),
898	    config_manager_snmp_agents([Agent|Agents]);
899	no ->
900	    lists:reverse(Agents)
901    end.
902
903config_manager_snmp_agent() ->
904    UserId     = ask("6b. User id?", mandatory,
905		     fun verify_user_id/1),
906    TargetName = ask("6c. Target name?", guess_agent_name(),
907		     fun verify_system_name/1),
908    Version    = ask("6d. Version (1/2/3)?", "1",
909	             fun verify_version/1),
910    Comm       = ask("6e. Community string ?", "public",
911	             fun verify_community/1),
912    EngineID   = ask("6f. Engine ID (snmpEngineID standard variable)",
913	             guess_engine_name(), fun verify_engine_id/1),
914    IP         = ask("6g. IP address for the agent", host(),
915	             fun verify_address/1),
916    Port       = ask("6h. The UDP port the agent listens to. "
917	             "(standard 161)", "4000", fun verify_port_number/1),
918    Timeout    = ask("6i. Retransmission timeout (infinity/pos integer)?",
919	             "infinity", fun verify_retransmission_timeout/1),
920    MMS        = ask("6j. Max message size?", "484",
921	             fun verify_max_message_size/1),
922    SecModel   = ask("6k. Security model (any/v1/v2c/usm)?", "any",
923	             fun verify_sec_model/1),
924    SecName    = ask("6l. Security name?", "\"initial\"",
925	             fun verify_sec_name/1),
926    SecLevel   = ask("6m. Security level (noAuthNoPriv/authNoPriv/authPriv)?",
927	             "noAuthNoPriv", fun verify_sec_level/1),
928    {UserId,
929     TargetName, Comm, IP, Port, EngineID, Timeout, MMS,
930     Version, SecModel, SecName, SecLevel}.
931
932
933config_manager_snmp_usms(Usms) ->
934    case ask("7. Configure an usm user handled by this manager (y/n)?",
935	     "y", fun verify_yes_or_no/1) of
936	yes ->
937	    Usm = config_manager_snmp_usm(),
938	    config_manager_snmp_usms([Usm|Usms]);
939	no ->
940	    lists:reverse(Usms)
941    end.
942
943config_manager_snmp_usm() ->
944    EngineID = ask("7a. Engine ID", guess_engine_name(),
945		   fun verify_engine_id/1),
946    UserName = ask("7b. User name?", mandatory, fun verify_usm_name/1),
947    SecName  = ask("7c. Security name?", UserName,
948		   fun verify_usm_sec_name/1),
949    AuthP    = ask("7d. Authentication protocol (no/sha/md5)?", "no",
950		   fun verify_usm_auth_protocol/1),
951    AuthKey  = ask_auth_key("7e", AuthP),
952    PrivP    = ask("7e. Priv protocol (no/des/aes)?", "no",
953		   fun verify_usm_priv_protocol/1),
954    PrivKey  = ask_priv_key("7f", PrivP),
955    {EngineID, UserName,
956     SecName, AuthP, AuthKey, PrivP, PrivKey}.
957
958
959%% ------------------------------------------------------------------
960
961is_members([], _Files) ->
962    true;
963is_members([H|T], Files) ->
964    lists:member(H, Files) andalso is_members(T, Files).
965
966default_agent_dir(DefDir) ->
967    default_dir("agent", DefDir).
968
969default_manager_dir(DefDir) ->
970    default_dir("manager", DefDir).
971
972default_dir(Component, DefDir) ->
973    %% Look for the component dir, if found use that as default
974    {ok, Files} = file:list_dir(DefDir),
975    case lists:member(Component, Files) of
976	true ->
977	    {filename:join(DefDir, Component), ""};
978	false ->
979	    %% No luck,
980	    %% so check if cwd contains either agent and/or
981	    %% manager config files. If it does, issue a warning
982
983	    %% Check for presence of agent config files
984	    %% If all the agent config files are present,
985	    %% issue a warning
986	    AgentConfs =
987		[
988		 "agent.conf",
989		 "context.conf",
990		 "community.conf",
991		 "notify.conf",
992		 "standard.conf",
993		 "target_params.conf",
994		 "target_addr.conf",
995		 "usm.conf",
996		 "vacm.conf"
997		],
998	    IsAgentDir = is_members(AgentConfs, Files),
999
1000	    %% Check for presence of manager config files
1001	    %% If all the manager config files are present,
1002	    %% issue a warning
1003	    ManagerConfs =
1004		[
1005		 "agents.conf",
1006		 "manager.conf",
1007		 "users.conf",
1008		 "usm.conf"
1009		],
1010	    IsManagerDir = is_members(ManagerConfs, Files),
1011	    Warning =
1012		if
1013		    IsAgentDir and IsManagerDir ->
1014			"Note that the default directory contains both agent and manager config files";
1015		    IsAgentDir ->
1016			"Note that the default directory contains agent config files";
1017		    IsManagerDir ->
1018			"Note that the default directory contains manager config files";
1019		    true ->
1020			""
1021		end,
1022	    {DefDir, Warning}
1023    end.
1024
1025
1026%% ------------------------------------------------------------------
1027
1028ask_auth_key(_Prefix, usmNoAuthProtocol) ->
1029    "";
1030ask_auth_key(Prefix, usmHMACSHAAuthProtocol) ->
1031    ask(Prefix ++ "  Authentication [sha] key (length 0 or 20)?", "\"\"",
1032	fun verify_usm_auth_sha_key/1);
1033ask_auth_key(Prefix, usmHMACMD5AuthProtocol) ->
1034    ask(Prefix ++ "  Authentication [md5] key (length 0 or 16)?", "\"\"",
1035	fun verify_usm_auth_md5_key/1).
1036
1037ask_priv_key(_Prefix, usmNoPrivProtocol) ->
1038    "";
1039ask_priv_key(Prefix, usmDESPrivProtocol) ->
1040    ask(Prefix ++ "  Priv [des] key (length 0 or 16)?", "\"\"",
1041	fun verify_usm_priv_des_key/1);
1042ask_priv_key(Prefix, usmAesCfb128Protocol) ->
1043    ask(Prefix ++ "  Priv [aes] key (length 0 or 16)?", "\"\"",
1044	fun verify_usm_priv_aes_key/1).
1045
1046
1047%% ------------------------------------------------------------------
1048
1049verify_yes_or_no("y") ->
1050    {ok, yes};
1051verify_yes_or_no("yes") ->
1052    {ok, yes};
1053verify_yes_or_no("n") ->
1054    {ok, no};
1055verify_yes_or_no("no") ->
1056    {ok, no};
1057verify_yes_or_no(YON) ->
1058    {error, "invalid yes or no: " ++ YON}.
1059
1060
1061verify_prio("low") ->
1062    {ok, low};
1063verify_prio("normal") ->
1064    {ok, normal};
1065verify_prio("high") ->
1066    {ok, high};
1067verify_prio(Prio) ->
1068    {error, "invalid process priority: " ++ Prio}.
1069
1070
1071verify_system_name(Name) -> {ok, Name}.
1072
1073
1074verify_engine_id(Name) -> {ok, Name}.
1075
1076
1077verify_max_message_size(MMS) ->
1078    case (catch list_to_integer(MMS)) of
1079	I when is_integer(I) andalso (I >= 484) ->
1080	    {ok, I};
1081	I when is_integer(I) ->
1082	    {error, "invalid max message size (must be atleast 484): " ++ MMS};
1083	_ ->
1084	    {error, "invalid max message size: " ++ MMS}
1085    end.
1086
1087
1088verify_port_number(P) ->
1089    case (catch list_to_integer(P)) of
1090	N when is_integer(N) andalso (N > 0) ->
1091	    {ok, N};
1092	_ ->
1093	    {error, "invalid port number: " ++ P}
1094    end.
1095
1096
1097verify_versions("1")     -> {ok, [v1]};
1098verify_versions("2")     -> {ok, [v2]};
1099verify_versions("3")     -> {ok, [v3]};
1100verify_versions("1&2")   -> {ok, [v1,v2]};
1101verify_versions("1&3")   -> {ok, [v1,v3]};
1102verify_versions("2&3")   -> {ok, [v2,v3]};
1103verify_versions("1&2&3") -> {ok, [v1,v2,v3]};
1104verify_versions(V)       -> {error, "incorrect version(s): " ++ V}.
1105
1106verify_version("1")     -> {ok, v1};
1107verify_version("2")     -> {ok, v2};
1108verify_version("3")     -> {ok, v3};
1109verify_version(V)       -> {error, "incorrect version: " ++ V}.
1110
1111
1112verify_passwd(Passwd) when length(Passwd) >= 8 ->
1113    {ok, Passwd};
1114verify_passwd(_P) ->
1115    {error, "invalid password"}.
1116
1117
1118verify_dir(Dir) ->
1119    case filename:pathtype(Dir) of
1120	absolute ->
1121	    case file:read_file_info(Dir) of
1122		{ok, #file_info{type = directory}} ->
1123		    {ok, snmp_misc:ensure_trailing_dir_delimiter(Dir)};
1124		{ok, _FileInfo} ->
1125		    {error, Dir ++ " is not a directory"};
1126		_ ->
1127		    {error, "invalid directory: " ++ Dir}
1128	    end;
1129	_E ->
1130	    {error, "invalid directory (not absolute): " ++ Dir}
1131    end.
1132
1133
1134verify_db_init_error("terminate") ->
1135    {ok, true};
1136verify_db_init_error("create") ->
1137    {ok, create};
1138verify_db_init_error("create_db_and_dir") ->
1139    {ok, create_db_and_dir};
1140verify_db_init_error(R) ->
1141    {error, "invalid DB init error: " ++ R}.
1142
1143
1144verify_notif_type("trap")   -> {ok, trap};
1145verify_notif_type("inform") -> {ok, inform};
1146verify_notif_type(NT)       -> {error, "invalid notifcation type: " ++ NT}.
1147
1148
1149verify_sec_type("none")     -> {ok, none};
1150verify_sec_type("minimum")  -> {ok, minimum};
1151verify_sec_type("semi_des") -> {ok, {semi, des}};
1152verify_sec_type("semi_aes") -> {ok, {semi, aes}};
1153verify_sec_type(ST)         -> {error, "invalid security type: " ++ ST}.
1154
1155
1156verify_address(A) ->
1157    verify_address(A, snmpUDPDomain).
1158
1159-dialyzer({nowarn_function, verify_address/2}). % Future compat
1160verify_address(A, snmpUDPDomain = _Domain) ->
1161    do_verify_address(A, inet);
1162verify_address(A, transportDomainUdpIpv4 = _Domain) ->
1163    do_verify_address(A, inet);
1164verify_address(A, transportDomainUdpIpv6 = _Domain) ->
1165    do_verify_address(A, inet6).
1166
1167do_verify_address(A, Family) ->
1168    do_verify_address(A, Family, list).
1169
1170do_verify_address(A, Family, Form)
1171  when (Form =:= list) orelse (Form =:= tuple) ->
1172    case (catch snmp_misc:ip(A, Family)) of
1173	{ok, IP} when (Form =:= tuple) ->
1174	    {ok, IP};
1175	{ok, IP} when (Form =:= list) ->
1176	    {ok, tuple_to_list(IP)};
1177	{error, _} ->
1178	    {error, "invalid address: " ++ A};
1179	_E ->
1180	    {error, "invalid address: " ++ A}
1181    end.
1182
1183
1184verify_mib_storage_type("m") ->
1185    {ok, mnesia};
1186verify_mib_storage_type("mnesia") ->
1187    {ok, mnesia};
1188verify_mib_storage_type("d") ->
1189    {ok, dets};
1190verify_mib_storage_type("dets") ->
1191    {ok, dets};
1192verify_mib_storage_type("e") ->
1193    {ok, ets};
1194verify_mib_storage_type("ets") ->
1195    {ok, ets};
1196verify_mib_storage_type(T) ->
1197    {error, "invalid mib storage type: " ++ T}.
1198
1199verify_mib_storage_action("default") ->
1200    {ok, default};
1201verify_mib_storage_action("clear") ->
1202    {ok, clear};
1203verify_mib_storage_action("keep") ->
1204    {ok, keep};
1205verify_mib_storage_action(A) ->
1206    {error, "invalid mib storage action: " ++ A}.
1207
1208
1209verify_verbosity("silence") ->
1210    {ok, silence};
1211verify_verbosity("info") ->
1212    {ok, info};
1213verify_verbosity("log") ->
1214    {ok, log};
1215verify_verbosity("debug") ->
1216    {ok, debug};
1217verify_verbosity("trace") ->
1218    {ok, trace};
1219verify_verbosity(V) ->
1220    {error, "invalid verbosity: " ++ V}.
1221
1222
1223verify_dets_repair("true") ->
1224    {ok, true};
1225verify_dets_repair("false") ->
1226    {ok, false};
1227verify_dets_repair("force") ->
1228    {ok, force};
1229verify_dets_repair(R) ->
1230    {error, "invalid repair: " ++ R}.
1231
1232verify_dets_auto_save("infinity") ->
1233    {ok, infinity};
1234verify_dets_auto_save(I0) ->
1235    case (catch list_to_integer(I0)) of
1236	I when is_integer(I) andalso (I > 0) ->
1237	    {ok, I};
1238	_ ->
1239	    {error, "invalid auto save timeout time: " ++ I0}
1240    end.
1241
1242
1243%% I know that this is a little of the edge, but...
1244verify_module(M) when is_atom(M) ->
1245    {ok, M};
1246verify_module(M0) when is_list(M0) ->
1247    {ok, list_to_atom(M0)};
1248verify_module(M0) ->
1249    {error, lists:flatten(io_lib:format("invalid module: ~p", [M0]))}.
1250
1251%% verify_module(M0) ->
1252%%     case (catch list_to_atom(M0)) of
1253%% 	M when is_atom(M) ->
1254%% 	    {ok, M};
1255%% 	_ ->
1256%% 	    {error, "invalid module: " ++ M0}
1257%%     end.
1258
1259
1260verify_agent_type("master") ->
1261    {ok, master};
1262verify_agent_type("sub") ->
1263    {ok, sub};
1264verify_agent_type(AT) ->
1265    {error, "invalid agent type: " ++ AT}.
1266
1267
1268verify_bool("true") ->
1269    {ok, true};
1270verify_bool("false") ->
1271    {ok, false};
1272verify_bool(B) ->
1273    {error, "invalid boolean: " ++ B}.
1274
1275
1276verify_timeout(T0) ->
1277    case (catch list_to_integer(T0)) of
1278	T when is_integer(T) andalso (T > 0) ->
1279	    {ok, T};
1280	_ ->
1281	    {error, "invalid timeout time: '" ++ T0 ++ "'"}
1282    end.
1283
1284
1285verify_retransmission_timeout("infinity") ->
1286    {ok, infinity};
1287verify_retransmission_timeout([${|R] = Timer) ->
1288    case lists:reverse(R) of
1289	[$}|R2] ->
1290	    case string:tokens(lists:reverse(R2), ", ") of
1291		[WaitForStr, FactorStr, IncrStr, RetryStr] ->
1292		    WaitFor = incr_timer_value(WaitForStr, 1),
1293		    Factor  = incr_timer_value(FactorStr,  1),
1294		    Incr    = incr_timer_value(IncrStr,    0),
1295		    Retry   = incr_timer_value(RetryStr,   0),
1296		    {ok, {WaitFor, Factor, Incr, Retry}};
1297		_ ->
1298		    {error, "invalid retransmission timer: '" ++ Timer ++ "'"}
1299	    end;
1300	_ ->
1301	    {error, "invalid retransmission timer: '" ++ Timer ++ "'"}
1302    end;
1303verify_retransmission_timeout(T0) ->
1304    case (catch list_to_integer(T0)) of
1305	T when is_integer(T) andalso (T > 0) ->
1306	    {ok, T};
1307	_ ->
1308	    {error, "invalid timeout time: '" ++ T0 ++ "'"}
1309    end.
1310
1311incr_timer_value(Str, Min) ->
1312    case (catch list_to_integer(Str)) of
1313	I when is_integer(I) andalso (I >= Min) ->
1314	    I;
1315	I when is_integer(I) ->
1316	    E = lists:flatten(io_lib:format("invalid incremental timer value "
1317					    "(min value is ~w): " ++ Str,
1318					    [Min])),
1319	    error(E);
1320	_ ->
1321	    error("invalid incremental timer value: " ++ Str)
1322    end.
1323
1324
1325%% verify_atl_type("read") ->
1326%%     {ok, read};
1327verify_atl_type("write") ->
1328    {ok, write};
1329verify_atl_type("read_write") ->
1330    {ok, read_write};
1331verify_atl_type(T) ->
1332    {error, "invalid log type: " ++ T}.
1333
1334verify_atl_repair("true") ->
1335    {ok, true};
1336verify_atl_repair("false") ->
1337    {ok, false};
1338verify_atl_repair("truncate") ->
1339    {ok, truncate};
1340verify_atl_repair("snmp_repair") ->
1341    {ok, snmp_repair};
1342verify_atl_repair(R) ->
1343    {error, "invalid audit trail log repair: " ++ R}.
1344
1345verify_atl_seqno("true") ->
1346    {ok, true};
1347verify_atl_seqno("false") ->
1348    {ok, false};
1349verify_atl_seqno(SN) ->
1350    {error, "invalid audit trail log seqno: " ++ SN}.
1351
1352
1353verify_pos_integer(I0) ->
1354    case (catch list_to_integer(I0)) of
1355	I when is_integer(I) andalso (I > 0) ->
1356	    {ok, I};
1357	_ ->
1358	    {error, "invalid integer value: " ++ I0}
1359    end.
1360
1361
1362verify_netif_req_limit("infinity") ->
1363    {ok, infinity};
1364verify_netif_req_limit(I0) ->
1365    case (catch list_to_integer(I0)) of
1366	I when is_integer(I) andalso (I > 0) ->
1367	    {ok, I};
1368	_ ->
1369	    {error, "invalid network interface request limit: " ++ I0}
1370    end.
1371
1372verify_netif_recbuf(Val) ->
1373    verify_netif_recbuf_or_sndbuf(Val, "recbuf").
1374
1375verify_netif_sndbuf(Val) ->
1376    verify_netif_recbuf_or_sndbuf(Val, "sndbuf").
1377
1378verify_netif_recbuf_or_sndbuf("default", _) ->
1379    {ok, default};
1380verify_netif_recbuf_or_sndbuf(I0, Buf) ->
1381    case (catch list_to_integer(I0)) of
1382	I when is_integer(I) andalso (I > 0) ->
1383	    {ok, I};
1384	_ ->
1385	    {error, "invalid network interface " ++ Buf ++ " size: " ++ I0}
1386    end.
1387
1388
1389verify_irb("auto") ->
1390    {ok, auto};
1391verify_irb("user") ->
1392    {ok, user};
1393verify_irb(IRB) ->
1394    E = lists:flatten(io_lib:format("invalid irb: ~p", [IRB])),
1395    {error, E}.
1396
1397
1398verify_irb_user("default") ->
1399    {ok, default};
1400verify_irb_user(TO) ->
1401    case (catch list_to_integer(TO)) of
1402	I when is_integer(I) andalso (I > 0) ->
1403	    {ok, I*1000}; % Time is given in seconds
1404	_ ->
1405	    {error, "invalid IRB GC time: " ++ TO}
1406    end.
1407
1408
1409verify_transport_domain("u4") ->
1410    {ok, transportDomainUdpIpv4};
1411verify_transport_domain("udp4") ->
1412    {ok, transportDomainUdpIpv4};
1413verify_transport_domain("transportDomainUdpIpv4") ->
1414    {ok, transportDomainUdpIpv4};
1415verify_transport_domain("u6") ->
1416    {ok, transportDomainUdpIpv6};
1417verify_transport_domain("udp6") ->
1418    {ok, transportDomainUdpIpv6};
1419verify_transport_domain("transportDomainUdpIpv6") ->
1420    {ok, transportDomainUdpIpv6};
1421verify_transport_domain(TS) ->
1422    {error, "invalid transport domain: " ++ TS}.
1423
1424
1425verify_transport_address(transportDomainUdpIpv4 = _Domain, A) ->
1426    do_verify_address(A, inet, tuple);
1427verify_transport_address(transportDomainUdpIpv6 = _Domain, A) ->
1428    do_verify_address(A, inet6, tuple).
1429
1430
1431verify_transport_kind("a") ->
1432    {ok, all};
1433verify_transport_kind("rr") ->
1434    {ok, req_responder};
1435verify_transport_kind("ts") ->
1436    {ok, trap_sender};
1437verify_transport_kind(K) ->
1438    {error, "invalid transport kind: " ++ K}.
1439
1440
1441verify_port_number_info("d") ->
1442    {ok, 0};
1443verify_port_number_info("default") ->
1444    {ok, 0};
1445verify_port_number_info("s") ->
1446    {ok, system};
1447verify_port_number_info("system") ->
1448    {ok, system};
1449verify_port_number_info(P) ->
1450    case (catch list_to_integer(P)) of
1451	N when is_integer(N) andalso (N > 0) ->
1452	    {ok, N};
1453	_ ->
1454	    {error, "invalid port number: " ++ P}
1455    end.
1456
1457
1458verify_term_disco_behaviour("discovery") ->
1459    {ok, discovery};
1460verify_term_disco_behaviour("plain") ->
1461    {ok, plain};
1462verify_term_disco_behaviour(B) ->
1463    {error, "invalid terminating discovery behaviour: " ++ B}.
1464
1465verify_term_disco_trigger_username("default") ->
1466    {ok, ""};
1467verify_term_disco_trigger_username(Trigger) ->
1468    {ok, Trigger}.
1469
1470
1471verify_user_id(UserId) when is_list(UserId) ->
1472    case (catch list_to_atom(UserId)) of
1473	A when is_atom(A) ->
1474	    {ok, A};
1475	_ ->
1476	    {error, "invalid user id: " ++ UserId}
1477    end;
1478verify_user_id(UserId) when is_atom(UserId) ->
1479    {ok, UserId};
1480verify_user_id(UserId) ->
1481    E = lists:flatten(io_lib:format("invalid user id: ~p", [UserId])),
1482    {error, E}.
1483
1484verify_user_data("undefined") ->
1485    {ok, undefined};
1486verify_user_data(UserData) ->
1487    {ok, UserData}.
1488
1489
1490verify_community("\"\"") ->
1491    {ok, ""};
1492verify_community(Comm) ->
1493    {ok, Comm}.
1494
1495
1496% verify_context_name("\"\"") ->
1497%     {ok, ""};
1498% verify_context_name(Ctx) ->
1499%     {ok, Ctx}.
1500
1501
1502% verify_mp_model("v1") ->
1503%     {ok, v1};
1504% verify_mp_model("v2c") ->
1505%     {ok, v2c};
1506% verify_mp_model("v3") ->
1507%     {ok, v3};
1508% verify_mp_model(M) ->
1509%     {error, "invalid mp model: " ++ M}.
1510
1511
1512verify_sec_model("any") ->
1513    {ok, any};
1514verify_sec_model("v1") ->
1515    {ok, v1};
1516verify_sec_model("v2c") ->
1517    {ok, v2c};
1518verify_sec_model("usm") ->
1519    {ok, usm};
1520verify_sec_model(M) ->
1521    {error, "invalid sec model: " ++ M}.
1522
1523verify_sec_name("\"initial\"") ->
1524    {ok, "initial"};
1525verify_sec_name(N) ->
1526    {ok, N}.
1527
1528
1529verify_sec_level("noAuthNoPriv") ->
1530    {ok, noAuthNoPriv};
1531verify_sec_level("authNoPriv") ->
1532    {ok, authNoPriv};
1533verify_sec_level("authPriv") ->
1534    {ok, authPriv};
1535verify_sec_level(L) ->
1536    {error, "invalid sec level: " ++ L}.
1537
1538
1539verify_usm_name(Name) ->
1540    {ok, Name}.
1541
1542verify_usm_sec_name(Name) ->
1543    {ok, Name}.
1544
1545
1546verify_usm_auth_protocol("no") ->
1547    {ok, usmNoAuthProtocol};
1548verify_usm_auth_protocol("sha") ->
1549    {ok, usmHMACSHAAuthProtocol};
1550verify_usm_auth_protocol("md5") ->
1551    {ok, usmHMACMD5AuthProtocol};
1552verify_usm_auth_protocol(AuthP) ->
1553    {error, "invalid auth protocol: " ++ AuthP}.
1554
1555verify_usm_auth_sha_key(Key) ->
1556    verify_usm_key("auth sha", Key, 20).
1557
1558verify_usm_auth_md5_key(Key) ->
1559    verify_usm_key("auth md5", Key, 16).
1560
1561verify_usm_priv_protocol("no") ->
1562    {ok, usmNoPrivProtocol};
1563verify_usm_priv_protocol("des") ->
1564    {ok, usmDESPrivProtocol};
1565verify_usm_priv_protocol("aes") ->
1566    {ok, usmAesCfb128Protocol};
1567verify_usm_priv_protocol(AuthP) ->
1568    {error, "invalid priv protocol: " ++ AuthP}.
1569
1570verify_usm_priv_des_key(Key) ->
1571    verify_usm_key("priv des", Key, 16).
1572
1573verify_usm_priv_aes_key(Key) ->
1574    verify_usm_key("priv aes", Key, 16).
1575
1576verify_usm_key(_What, "\"\"", _ExpectLength) ->
1577    {ok, ""};
1578verify_usm_key(_What, Key, ExpectLength) when length(Key) =:= ExpectLength ->
1579    {ok, Key};
1580verify_usm_key(What, [$[|RestKey] = Key0, ExpectLength) ->
1581    case lists:reverse(RestKey) of
1582	[$]|RevRestKey] ->
1583	    Key1 = lists:reverse(RevRestKey),
1584	    verify_usm_key2(What, Key1, ExpectLength);
1585	_ ->
1586	    %% Its not a list ([...]) and its not the correct length, ...
1587	    {error, "invalid " ++ What ++ " key length: " ++ Key0}
1588    end;
1589verify_usm_key(What, Key, ExpectLength) ->
1590    verify_usm_key2(What, Key, ExpectLength).
1591
1592verify_usm_key2(What, Key0, ExpectLength) ->
1593    case string:tokens(Key0, [$,]) of
1594	Key when length(Key) =:= ExpectLength ->
1595	    convert_usm_key(Key, []);
1596	_ ->
1597	    {error, "invalid " ++ What ++ " key length: " ++ Key0}
1598    end.
1599
1600convert_usm_key([], Acc) ->
1601    {ok, lists:reverse(Acc)};
1602convert_usm_key([I|Is], Acc) ->
1603    case (catch list_to_integer(I)) of
1604	Int when is_integer(Int) ->
1605	    convert_usm_key(Is, [Int|Acc]);
1606	_Err ->
1607	    {error, "invalid key number: " ++ I}
1608    end.
1609
1610
1611% ip(Host) ->
1612%     case catch snmp_misc:ip(Host) of
1613% 	{ok, IPtuple} -> tuple_to_list(IPtuple);
1614% 	{error, Reason} -> throw({error, Reason});
1615% 	_Q -> throw({error, {"ip conversion failed", Host}})
1616%     end.
1617
1618% make_ip(Str) ->
1619%     case catch snmp_misc:ip(Str) of
1620% 	{ok, IPtuple} -> tuple_to_list(IPtuple);
1621% 	_Q -> ip(Str)
1622%     end.
1623
1624
1625print_q(Q, mandatory) ->
1626    io:format(Q ++ " ",[]);
1627print_q(Q, Default) when is_list(Default) ->
1628    io:format(Q ++ " [~s] ",[Default]).
1629
1630%% Defval = string() | mandatory
1631ask(Q, Default, Verify) when is_list(Q) andalso is_function(Verify) ->
1632    print_q(Q, Default),
1633    PrelAnsw = io:get_line(''),
1634    Answer =
1635	case remove_newline(PrelAnsw) of
1636	    "" when Default =/= mandatory -> Default;
1637	    "" -> ask(Q, Default, Verify);
1638	    A -> A
1639    end,
1640    case (catch Verify(Answer)) of
1641	{ok, Answer2} ->
1642	    Answer2;
1643	{error, ReasonStr} ->
1644	    i("ERROR: " ++ ReasonStr),
1645	    ask(Q, Default, Verify)
1646    end.
1647
1648
1649host() ->
1650    do_host(inet).
1651
1652host(transportDomainUdpIpv4) ->
1653    do_host(inet);
1654host(transportDomainUdpIpv6) ->
1655    do_host(inet6).
1656
1657do_host(Fam) ->
1658    case (catch inet:gethostname()) of
1659	{ok, Name} ->
1660	    case (catch inet:getaddr(Name, Fam)) of
1661		{ok, Addr} when is_tuple(Addr) ->
1662		    lists:flatten(
1663		      io_lib:format("~w.~w.~w.~w", tuple_to_list(Addr)));
1664		_ when (Fam =:= inet) ->
1665		    "127.0.0.1";
1666                _ when (Fam =:= inet6)  ->
1667                    "::1"
1668	    end;
1669        _ when (Fam =:= inet) ->
1670            "127.0.0.1";
1671        _ when (Fam =:= inet6)  ->
1672            "::1"
1673    end.
1674
1675guess_agent_name() ->
1676    case os:type() of
1677	{unix, _} ->
1678	    lists:append(remove_newline(os:cmd("echo $USER")), "'s agent");
1679	{_,_} -> "my agent"
1680    end.
1681
1682guess_engine_name() ->
1683    case os:type() of
1684	{unix, _} ->
1685	    lists:append(remove_newline(os:cmd("echo $USER")), "'s engine");
1686	{_,_} -> "my engine"
1687    end.
1688
1689% guess_user_id() ->
1690%     case os:type() of
1691% 	{unix, _} ->
1692% 	    lists:append(remove_newline(os:cmd("echo $USER")), "'s user");
1693% 	{_,_} -> "user_id"
1694%     end.
1695
1696
1697remove_newline(Str) ->
1698    lists:delete($\n, Str).
1699
1700
1701%%======================================================================
1702%% File generation
1703%%======================================================================
1704
1705write_agent_snmp_files(
1706  Dir, Vsns, Domain, ManagerAddr, AgentAddr, SysName)
1707  when is_list(Dir),
1708       is_list(Vsns),
1709       is_atom(Domain),
1710       is_list(SysName) ->
1711    write_agent_snmp_files(
1712      Dir, Vsns, Domain, ManagerAddr, AgentAddr, SysName,
1713      trap, none, "", "agentEngine", 484).
1714
1715%%----------------------------------------------------------------------
1716%% Dir: string()  (ex: "../conf/")
1717%% ManagerIP, AgentIP: [int(),int(),int(),int()]
1718%% TrapUdp, AgentUDP: integer()
1719%% SysName: string()
1720%%----------------------------------------------------------------------
1721write_agent_snmp_files(
1722  Dir, Vsns, ManagerIP, TrapUDP, AgentIP, AgentUDP, SysName)
1723  when is_list(Dir) andalso
1724       is_list(Vsns) andalso
1725       is_list(ManagerIP) andalso
1726       is_integer(TrapUDP) andalso
1727       is_list(AgentIP) andalso
1728       is_integer(AgentUDP) andalso
1729       is_list(SysName) ->
1730    write_agent_snmp_files(
1731      Dir, Vsns, ManagerIP, TrapUDP, AgentIP, AgentUDP, SysName,
1732      trap, none, "", "agentEngine", 484).
1733
1734%%
1735%% ----- Agent config files generator functions -----
1736%%
1737
1738%% This function has no documentation, so for "possible" users
1739%% (other then our test suite), we have this spec...
1740
1741-type agent_pre_transport() :: #{addr := tuple(),
1742                                 kind := req_responder | trap_sender,
1743                                 opts := list()} |
1744                               #{addr := tuple(),
1745                                 kind := req_responder | trap_sender} |
1746                               #{addr := tuple()}.
1747
1748-spec write_agent_snmp_files(Dir, Vsns,
1749                             TransportDomain, ManagerAddr, AgentPreTransports,
1750                             SysName,
1751                             NotifyType, SecType, Passwd, EngineID, MMS) ->
1752          ok when
1753      Dir                :: file:filename(),
1754      Vsns               :: [snmp:version()],
1755      TransportDomain    :: snmp:tdomain(),
1756      ManagerAddr        :: {inet:ip_address(), inet:port_number()},
1757      AgentPreTransports :: [agent_pre_transport()],
1758      SysName            :: string(),
1759      NotifyType         :: trap | inform,
1760      SecType            :: none | minimum | {semi, des | aes},
1761      Passwd             :: list(),
1762      EngineID           :: snmp:engine_id(),
1763      MMS                :: snmp:mms();
1764
1765                            (Dir, Vsns,
1766                             TransportDomain, ManagerAddr, AgentAddr,
1767                             SysName,
1768                             NotifyType, SecType, Passwd, EngineID, MMS) ->
1769          ok when
1770      Dir                :: file:filename(),
1771      Vsns               :: [snmp:version()],
1772      TransportDomain    :: snmp:tdomain(),
1773      ManagerAddr        :: {inet:ip_address(), inet:port_number()},
1774      AgentAddr          :: {inet:ip_address(), inet:port_number()},
1775      SysName            :: string(),
1776      NotifyType         :: trap | inform,
1777      SecType            :: none | minimum | {semi, des | aes},
1778      Passwd             :: list(),
1779      EngineID           :: snmp:engine_id(),
1780      MMS                :: snmp:mms().
1781
1782write_agent_snmp_files(
1783  Dir, Vsns, TransportDomain, ManagerAddr, AgentPreTransports, SysName,
1784  NotifType, SecType, Passwd, EngineID, MMS) when is_list(AgentPreTransports) ->
1785    F = fun(#{addr := Addr, kind := Kind, opts := Opts})
1786              when is_tuple(Addr) andalso
1787                   is_atom(Kind) andalso
1788                   is_list(Opts) ->
1789                {TransportDomain, Addr, Kind, Opts};
1790           (#{addr := Addr, kind := Kind})
1791              when is_tuple(Addr) andalso
1792                   is_atom(Kind) ->
1793                {TransportDomain, Addr, Kind, []};
1794           (#{addr := Addr})
1795              when is_tuple(Addr) ->
1796                {TransportDomain, Addr}
1797        end,
1798    AgentTransports = lists:map(F, AgentPreTransports),
1799    write_agent_snmp_conf(Dir, AgentTransports, EngineID, MMS),
1800    write_agent_snmp_context_conf(Dir),
1801    write_agent_snmp_community_conf(Dir),
1802    write_agent_snmp_standard_conf(Dir, SysName),
1803    write_agent_snmp_target_addr_conf(Dir, TransportDomain, ManagerAddr, Vsns),
1804    write_agent_snmp_target_params_conf(Dir, Vsns),
1805    write_agent_snmp_notify_conf(Dir, NotifType),
1806    write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
1807    write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
1808    ok;
1809write_agent_snmp_files(
1810  Dir, Vsns, Domain, ManagerAddr, AgentAddr, SysName,
1811  NotifType, SecType, Passwd, EngineID, MMS)
1812  when is_list(Dir) andalso
1813       is_list(Vsns) andalso
1814       is_atom(Domain) andalso
1815       is_tuple(ManagerAddr) andalso
1816       is_tuple(ManagerAddr) andalso
1817       is_list(SysName) andalso
1818       is_atom(NotifType) ->
1819    write_agent_snmp_conf(Dir, [{Domain, AgentAddr}], EngineID, MMS),
1820    write_agent_snmp_context_conf(Dir),
1821    write_agent_snmp_community_conf(Dir),
1822    write_agent_snmp_standard_conf(Dir, SysName),
1823    write_agent_snmp_target_addr_conf(Dir, Domain, ManagerAddr, Vsns),
1824    write_agent_snmp_target_params_conf(Dir, Vsns),
1825    write_agent_snmp_notify_conf(Dir, NotifType),
1826    write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
1827    write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
1828    ok.
1829
1830write_agent_snmp_files(
1831  Dir, Vsns, ManagerIP, TrapUDP, AgentIP, AgentUDP, SysName,
1832  NotifType, SecType, Passwd, EngineID, MMS)
1833  when is_list(Dir) andalso
1834       is_list(Vsns) andalso
1835       is_list(ManagerIP) andalso
1836       is_integer(TrapUDP) andalso
1837       is_list(AgentIP) andalso
1838       is_integer(AgentUDP) andalso
1839       is_list(SysName) andalso
1840       is_atom(NotifType) ->
1841    Domain = snmp_target_mib:default_domain(),
1842    ManagerAddr = {ManagerIP, TrapUDP},
1843    write_agent_snmp_conf(Dir, AgentIP, AgentUDP, EngineID, MMS),
1844    write_agent_snmp_context_conf(Dir),
1845    write_agent_snmp_community_conf(Dir),
1846    write_agent_snmp_standard_conf(Dir, SysName),
1847    write_agent_snmp_target_addr_conf(Dir, Domain, ManagerAddr, Vsns),
1848    write_agent_snmp_target_params_conf(Dir, Vsns),
1849    write_agent_snmp_notify_conf(Dir, NotifType),
1850    write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
1851    write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
1852    ok;
1853write_agent_snmp_files(
1854  Dir, Vsns,
1855  ManagerTDomain, ManagerIP, ManagerTrapUDP,
1856  AgentTransports, SysName,
1857  NotifType, SecType, Passwd, EngineID, MMS)
1858  when is_list(Dir) andalso
1859       is_list(Vsns) andalso
1860       is_atom(ManagerTDomain) andalso
1861       is_tuple(ManagerIP) andalso
1862       is_integer(ManagerTrapUDP) andalso
1863       is_list(AgentTransports) andalso
1864       is_list(SysName) andalso
1865       is_atom(NotifType) ->
1866    ManagerAddr = {ManagerIP, ManagerTrapUDP},
1867    write_agent_snmp_conf(Dir, AgentTransports, EngineID, MMS),
1868    write_agent_snmp_context_conf(Dir),
1869    write_agent_snmp_community_conf(Dir),
1870    write_agent_snmp_standard_conf(Dir, SysName),
1871    write_agent_snmp_target_addr_conf(Dir, ManagerTDomain, ManagerAddr, Vsns),
1872    write_agent_snmp_target_params_conf(Dir, Vsns),
1873    write_agent_snmp_notify_conf(Dir, NotifType),
1874    write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd),
1875    write_agent_snmp_vacm_conf(Dir, Vsns, SecType),
1876    ok.
1877
1878
1879
1880%%
1881%% ------ [agent] agent.conf ------
1882%%
1883
1884write_agent_snmp_conf(Dir, Transports, EngineID, MMS) ->
1885    Conf =
1886	[{intAgentTransports,       Transports},
1887	 {snmpEngineID,             EngineID},
1888	 {snmpEngineMaxMessageSize, MMS}],
1889    do_write_agent_snmp_conf(Dir, Conf).
1890
1891write_agent_snmp_conf(Dir, Domain, AgentAddr, EngineID, MMS)
1892  when is_atom(Domain) ->
1893    {AgentIP, AgentUDP} = AgentAddr,
1894    Conf =
1895	[{intAgentTransportDomain,  Domain},
1896	 {intAgentUDPPort,          AgentUDP},
1897	 {intAgentIpAddress,        AgentIP},
1898	 {snmpEngineID,             EngineID},
1899	 {snmpEngineMaxMessageSize, MMS}],
1900    do_write_agent_snmp_conf(Dir, Conf);
1901write_agent_snmp_conf(Dir, AgentIP, AgentUDP, EngineID, MMS)
1902  when is_integer(AgentUDP) ->
1903    Conf =
1904	[{intAgentUDPPort,          AgentUDP},
1905	 {intAgentIpAddress,        AgentIP},
1906	 {snmpEngineID,             EngineID},
1907	 {snmpEngineMaxMessageSize, MMS}],
1908    do_write_agent_snmp_conf(Dir, Conf);
1909write_agent_snmp_conf(_Dir, Domain, AgentAddr, _EngineID, _MMS) ->
1910    error({bad_address, {Domain, AgentAddr}}).
1911
1912do_write_agent_snmp_conf(Dir, Conf) ->
1913    Comment =
1914"%% This file defines the Agent local configuration info\n"
1915"%% The data is inserted into the snmpEngine* variables defined\n"
1916"%% in SNMP-FRAMEWORK-MIB, and the intAgent* variables defined\n"
1917"%% in OTP-SNMPEA-MIB.\n"
1918"%% Each row is a 2-tuple:\n"
1919"%% {AgentVariable, Value}.\n"
1920"%% For example\n"
1921"%% {intAgentUDPPort, 4000}.\n"
1922"%% The ip address for the agent is sent as id in traps.\n"
1923"%% {intAgentIpAddress, [127,42,17,5]}.\n"
1924"%% {snmpEngineID, \"agentEngine\"}.\n"
1925"%% {snmpEngineMaxMessageSize, 484}.\n"
1926"%%\n\n",
1927    Hdr = header() ++ Comment,
1928    write_agent_config(Dir, Hdr, Conf).
1929
1930write_agent_config(Dir, Hdr, Conf) ->
1931    snmpa_conf:write_agent_config(Dir, Hdr, Conf).
1932
1933update_agent_config(Dir, Conf) ->
1934    snmpa_conf:append_agent_config(Dir, Conf).
1935
1936
1937%%
1938%% ------ [agent] context.conf ------
1939%%
1940
1941write_agent_snmp_context_conf(Dir) ->
1942    Comment =
1943"%% This file defines the contexts known to the agent.\n"
1944"%% The data is inserted into the vacmContextTable defined\n"
1945"%% in SNMP-VIEW-BASED-ACM-MIB.\n"
1946"%% Each row is a string:\n"
1947"%% ContextName.\n"
1948"%%\n"
1949"%% The empty string is the default context.\n"
1950"%% For example\n"
1951"%% \"bridge1\".\n"
1952"%% \"bridge2\".\n"
1953"%%\n\n",
1954    Hdr = header() ++ Comment,
1955    Conf = [""],
1956    write_agent_context_config(Dir, Hdr, Conf).
1957
1958write_agent_context_config(Dir, Hdr, Conf) ->
1959    snmpa_conf:write_context_config(Dir, Hdr, Conf).
1960
1961update_agent_context_config(Dir, Conf) ->
1962    snmpa_conf:append_context_config(Dir, Conf).
1963
1964
1965%%
1966%% ------ community.conf ------
1967%%
1968
1969write_agent_snmp_community_conf(Dir) ->
1970    Comment =
1971"%% This file defines the community info which maps to VACM parameters.\n"
1972"%% The data is inserted into the snmpCommunityTable defined\n"
1973"%% in SNMP-COMMUNITY-MIB.\n"
1974"%% Each row is a 5-tuple:\n"
1975"%% {CommunityIndex, CommunityName, SecurityName, ContextName, TransportTag}.\n"
1976"%% For example\n"
1977"%% {\"1\", \"public\", \"initial\", \"\", \"\"}.\n"
1978"%% {\"2\", \"secret\", \"secret_name\", \"\", \"tag\"}.\n"
1979"%% {\"3\", \"bridge1\", \"initial\", \"bridge1\", \"\"}.\n"
1980"%%\n\n",
1981    Hdr = header() ++ Comment,
1982    Conf = [{"public", "public", "initial", "", ""},
1983	    {"all-rights", "all-rights", "all-rights", "", ""},
1984	    {"standard trap", "standard trap", "initial", "", ""}],
1985    write_agent_community_config(Dir, Hdr, Conf).
1986
1987write_agent_community_config(Dir, Hdr, Conf) ->
1988    snmpa_conf:write_community_config(Dir, Hdr, Conf).
1989
1990update_agent_community_config(Dir, Conf) ->
1991    snmpa_conf:append_community_config(Dir, Conf).
1992
1993
1994%%
1995%% ------ standard.conf ------
1996%%
1997
1998write_agent_snmp_standard_conf(Dir, SysName) ->
1999    Comment =
2000"%% This file defines the STANDARD-MIB info.\n"
2001"%% Each row is a 2-tuple:\n"
2002"%% {StandardVariable, Value}.\n"
2003"%% For example\n"
2004"%% {sysDescr, \"Erlang SNMP agent\"}.\n"
2005"%% {sysObjectID, [1,2,3]}.\n"
2006"%% {sysContact, \"{mbj,eklas}@erlang.ericsson.se\"}.\n"
2007"%% {sysName, \"test\"}.\n"
2008"%% {sysLocation, \"erlang\"}.\n"
2009"%% {sysServices, 72}.\n"
2010"%% {snmpEnableAuthenTraps, enabled}.\n"
2011"%%\n\n",
2012    Hdr = header() ++ Comment,
2013    Conf = [{sysDescr,              "Erlang SNMP agent"},
2014	    {sysObjectID,           [1,2,3]},
2015	    {sysContact,            "{mbj,eklas}@erlang.ericsson.se"},
2016	    {sysLocation,           "erlang"},
2017	    {sysServices,           72},
2018	    {snmpEnableAuthenTraps, enabled},
2019	    {sysName,               SysName}],
2020    write_agent_standard_config(Dir, Hdr, Conf).
2021
2022write_agent_standard_config(Dir, Hdr, Conf) ->
2023    snmpa_conf:write_standard_config(Dir, Hdr, Conf).
2024
2025update_agent_standard_config(Dir, Conf) ->
2026    snmpa_conf:append_standard_config(Dir, Conf).
2027
2028
2029%%
2030%% ------ target_addr.conf ------
2031%%
2032
2033write_agent_snmp_target_addr_conf(Dir, Addresses, Vsns) ->
2034    Timeout    = 1500,
2035    RetryCount = 3,
2036    write_agent_snmp_target_addr_conf(
2037      Dir, Addresses, Timeout, RetryCount, Vsns).
2038
2039write_agent_snmp_target_addr_conf(Dir, Domain_or_Ip, Addr_or_Port, Vsns) ->
2040    Addresses = [{Domain_or_Ip, Addr_or_Port}],
2041    write_agent_snmp_target_addr_conf(Dir, Addresses, Vsns).
2042
2043write_agent_snmp_target_addr_conf(
2044  Dir, Addresses, Timeout, RetryCount, Vsns) ->
2045    Comment =
2046"%% This file defines the target address parameters.\n"
2047"%% The data is inserted into the snmpTargetAddrTable defined\n"
2048"%% in SNMP-TARGET-MIB, and in the snmpTargetAddrExtTable defined\n"
2049"%% in SNMP-COMMUNITY-MIB.\n"
2050"%% Each row is a 10-tuple:\n"
2051"%% {Name, Ip, Udp, Timeout, RetryCount, TagList, ParamsName, EngineId,\n"
2052"%%        TMask, MaxMessageSize}.\n"
2053"%% The EngineId value is only used if Inform-Requests are sent to this\n"
2054"%% target.  If Informs are not sent, this value is ignored, and can be\n"
2055"%% e.g. an empty string.  However, if Informs are sent, it is essential\n"
2056"%% that the value of EngineId matches the value of the target's\n"
2057"%% actual snmpEngineID.\n"
2058"%% For example\n"
2059"%% {\"1.2.3.4 v1\", [1,2,3,4], 162, \n"
2060"%%  1500, 3, \"std_inform\", \"otp_v2\", \"\",\n"
2061"%%  [127,0,0,0],  2048}.\n"
2062"%%\n\n",
2063    Hdr = header() ++ Comment,
2064    Conf =
2065	lists:foldl(
2066	  fun ({Domain_or_Ip, Addr_or_Port} = Address, OuterAcc) ->
2067		  lists:foldl(
2068		    fun(v1 = Vsn, Acc) ->
2069			    [{mk_name(Address, Vsn),
2070			      Domain_or_Ip, Addr_or_Port,
2071			      Timeout, RetryCount,
2072			      "std_trap", mk_param(Vsn), "",
2073			      [], 2048}| Acc];
2074		       (v2 = Vsn, Acc) ->
2075			    [{mk_name(Address, Vsn),
2076			      Domain_or_Ip, Addr_or_Port,
2077			      Timeout, RetryCount,
2078			      "std_trap", mk_param(Vsn), "",
2079			      [], 2048},
2080			       {lists:flatten(
2081				  io_lib:format(
2082				    "~s.2", [mk_name(Address, Vsn)])),
2083				Domain_or_Ip, Addr_or_Port,
2084				Timeout, RetryCount,
2085				"std_inform", mk_param(Vsn), "",
2086				[], 2048}| Acc];
2087		       (v3 = Vsn, Acc) ->
2088			    [{mk_name(Address, Vsn),
2089			      Domain_or_Ip, Addr_or_Port,
2090			      Timeout, RetryCount,
2091			      "std_trap", mk_param(Vsn), "",
2092			      [], 2048},
2093			     {lists:flatten(
2094				io_lib:format(
2095				  "~s.3", [mk_name(Address, Vsn)])),
2096			      Domain_or_Ip, Addr_or_Port,
2097			      Timeout, RetryCount,
2098			      "std_inform", mk_param(Vsn), "mgrEngine",
2099			      [], 2048} | Acc]
2100		    end, OuterAcc, Vsns)
2101	  end, [], Addresses),
2102    write_agent_target_addr_config(Dir, Hdr, Conf).
2103
2104write_agent_snmp_target_addr_conf(
2105  Dir, Domain_or_Ip, Addr_or_Port, Timeout, RetryCount, Vsns) ->
2106    Addresses = [{Domain_or_Ip, Addr_or_Port}],
2107    write_agent_snmp_target_addr_conf(
2108      Dir, Addresses, Timeout, RetryCount, Vsns).
2109
2110mk_param(Vsn) ->
2111    lists:flatten(io_lib:format("target_~w", [Vsn])).
2112
2113mk_name(Address, Vsn) ->
2114    lists:flatten(
2115      io_lib:format(
2116	"~s ~w", [snmp_conf:mk_addr_string(Address), Vsn])).
2117
2118write_agent_target_addr_config(Dir, Hdr, Conf) ->
2119    snmpa_conf:write_target_addr_config(Dir, Hdr, Conf).
2120
2121update_agent_target_addr_config(Dir, Conf) ->
2122    snmpa_conf:append_target_addr_config(Dir, Conf).
2123
2124
2125%%
2126%% ------ target_params.conf ------
2127%%
2128
2129write_agent_snmp_target_params_conf(Dir, Vsns) ->
2130    Comment =
2131"%% This file defines the target parameters.\n"
2132"%% The data is inserted into the snmpTargetParamsTable defined\n"
2133"%% in SNMP-TARGET-MIB.\n"
2134"%% Each row is a 5-tuple:\n"
2135"%% {Name, MPModel, SecurityModel, SecurityName, SecurityLevel}.\n"
2136"%% For example\n"
2137"%% {\"target_v3\", v3, usm, \"\", noAuthNoPriv}.\n"
2138"%%\n\n",
2139    Hdr = header() ++ Comment,
2140    Conf = [fun(V) ->
2141		    MP = if V == v1 -> v1;
2142			    V == v2 -> v2c;
2143			    V == v3 -> v3
2144			 end,
2145		    SM = if V == v1 -> v1;
2146			    V == v2 -> v2c;
2147			    V == v3 -> usm
2148			 end,
2149		    Name = lists:flatten(
2150			     io_lib:format("target_~w", [V])),
2151		    {Name, MP, SM, "initial", noAuthNoPriv}
2152	    end(Vsn) || Vsn <- Vsns],
2153    write_agent_target_params_config(Dir, Hdr, Conf).
2154
2155write_agent_target_params_config(Dir, Hdr, Conf) ->
2156    snmpa_conf:write_target_params_config(Dir, Hdr, Conf).
2157
2158update_agent_target_params_config(Dir, Conf) ->
2159    snmpa_conf:append_target_params_config(Dir, Conf).
2160
2161
2162%%
2163%% ------ notify.conf ------
2164%%
2165
2166write_agent_snmp_notify_conf(Dir, NotifyType) ->
2167    Comment =
2168"%% This file defines the notification parameters.\n"
2169"%% The data is inserted into the snmpNotifyTable defined\n"
2170"%% in SNMP-NOTIFICATION-MIB.\n"
2171"%% The Name is used as CommunityString for v1 and v2c.\n"
2172"%% Each row is a 3-tuple:\n"
2173"%% {Name, Tag, Type}.\n"
2174"%% For example\n"
2175"%% {\"standard trap\", \"std_trap\", trap}.\n"
2176"%% {\"standard inform\", \"std_inform\", inform}.\n"
2177"%%\n\n",
2178    Hdr = header() ++ Comment,
2179    Conf = [{"standard trap", "std_trap", NotifyType}],
2180    write_agent_notify_config(Dir, Hdr, Conf).
2181
2182write_agent_notify_config(Dir, Hdr, Conf) ->
2183    snmpa_conf:write_notify_config(Dir, Hdr, Conf).
2184
2185update_agent_notify_config(Dir, Conf) ->
2186    snmpa_conf:append_notify_config(Dir, Conf).
2187
2188
2189%%
2190%% ------ usm.conf ------
2191%%
2192
2193write_agent_snmp_usm_conf(Dir, Vsns, EngineID, SecType, Passwd) ->
2194    case lists:member(v3, Vsns) of
2195	false -> ok;
2196	true -> write_agent_snmp_usm_conf(Dir, EngineID, SecType, Passwd)
2197    end.
2198
2199write_agent_snmp_usm_conf(Dir, EngineID, SecType, Passwd) ->
2200    Comment =
2201"%% This file defines the security parameters for the user-based\n"
2202"%% security model.\n"
2203"%% The data is inserted into the usmUserTable defined\n"
2204"%% in SNMP-USER-BASED-SM-MIB.\n"
2205"%% Each row is a 14-tuple:\n"
2206"%% {EngineID, UserName, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC,\n"
2207"%%  PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey}.\n"
2208"%% For example\n"
2209"%% {\"agentEngine\", \"initial\", \"initial\", zeroDotZero,\n"
2210"%%  usmNoAuthProtocol, \"\", \"\", usmNoPrivProtocol, \"\", \"\", \"\",\n"
2211"%%  \"\", \"\"}.\n"
2212"%%\n\n",
2213    Hdr = header() ++ Comment,
2214    Conf = write_agent_snmp_usm_conf2(EngineID, SecType, Passwd),
2215    write_agent_usm_config(Dir, Hdr, Conf).
2216
2217write_agent_snmp_usm_conf2(EngineID, none, _Passwd) ->
2218    [{EngineID, "initial", "initial", zeroDotZero,
2219      usmNoAuthProtocol, "", "",
2220      usmNoPrivProtocol, "", "",
2221      "", "", ""}];
2222write_agent_snmp_usm_conf2(EngineID, SecType, Passwd) ->
2223    Secret16 = agent_snmp_mk_secret(md5, Passwd, EngineID),
2224    Secret20 = agent_snmp_mk_secret(sha, Passwd, EngineID),
2225    {PrivProt, PrivSecret} =
2226	case SecType of
2227	    minimum ->
2228		{usmNoPrivProtocol,    ""};
2229	    {semi, des} ->
2230		{usmDESPrivProtocol,   Secret16};
2231	    {semi, aes} ->
2232		{usmAesCfb128Protocol, Secret16}
2233	end,
2234    [{EngineID, "initial", "initial", zeroDotZero,
2235      usmHMACMD5AuthProtocol, "", "",
2236      PrivProt, "", "",
2237      "", Secret16, PrivSecret},
2238
2239     {EngineID, "templateMD5", "templateMD5", zeroDotZero,
2240      usmHMACMD5AuthProtocol, "", "",
2241      PrivProt, "", "",
2242      "", Secret16, PrivSecret},
2243
2244     {EngineID, "templateSHA", "templateSHA", zeroDotZero,
2245      usmHMACSHAAuthProtocol, "", "",
2246      PrivProt, "", "",
2247      "", Secret20, PrivSecret}].
2248
2249write_agent_usm_config(Dir, Hdr, Conf) ->
2250    snmpa_conf:write_usm_config(Dir, Hdr, Conf).
2251
2252update_agent_usm_config(Dir, Conf) ->
2253    snmpa_conf:append_usm_config(Dir, Conf).
2254
2255
2256%%
2257%% ------ vacm.conf ------
2258%%
2259
2260write_agent_snmp_vacm_conf(Dir, Vsns, SecType) ->
2261    Comment =
2262"%% This file defines the Mib Views.\n"
2263"%% The data is inserted into the vacm* tables defined\n"
2264"%% in SNMP-VIEW-BASED-ACM-MIB.\n"
2265"%% Each row is one of 3 tuples; one for each table in the MIB:\n"
2266"%% {vacmSecurityToGroup, SecModel, SecName, GroupName}.\n"
2267"%% {vacmAccess, GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV}.\n"
2268"%% {vacmViewTreeFamily, ViewIndex, ViewSubtree, ViewStatus, ViewMask}.\n"
2269"%% For example\n"
2270"%% {vacmSecurityToGroup, v2c, \"initial\", \"initial\"}.\n"
2271"%% {vacmSecurityToGroup, usm, \"initial\", \"initial\"}.\n"
2272"%%  read/notify access to system\n"
2273"%% {vacmAccess, \"initial\", \"\", any, noAuthNoPriv, exact,\n"
2274"%%              \"system\", \"\", \"system\"}.\n"
2275"%% {vacmViewTreeFamily, \"system\", [1,3,6,1,2,1,1], included, null}.\n"
2276"%% {vacmViewTreeFamily, \"exmib\", [1,3,6,1,3], included, null}."
2277" % for EX1-MIB\n"
2278"%% {vacmViewTreeFamily, \"internet\", [1,3,6,1], included, null}.\n"
2279"%%\n\n",
2280    Hdr = lists:flatten(header()) ++ Comment,
2281    Groups =
2282	lists:foldl(
2283	  fun(V, Acc) ->
2284		  [{vacmSecurityToGroup, vacm_ver(V),
2285		    "initial",    "initial"},
2286		   {vacmSecurityToGroup, vacm_ver(V),
2287		    "all-rights", "all-rights"}|
2288		   Acc]
2289	  end, [], Vsns),
2290    Acc =
2291	[{vacmAccess, "initial", "", any, noAuthNoPriv, exact,
2292	  "restricted", "", "restricted"},
2293	 {vacmAccess, "initial", "", usm, authNoPriv, exact,
2294	  "internet", "internet", "internet"},
2295	 {vacmAccess, "initial", "", usm, authPriv, exact,
2296	  "internet", "internet", "internet"},
2297	 {vacmAccess, "all-rights", "", any, noAuthNoPriv, exact,
2298	  "internet", "internet", "internet"}],
2299    VTF0 =
2300	case SecType of
2301	    none ->
2302		[{vacmViewTreeFamily,
2303		  "restricted", [1,3,6,1], included, null}];
2304	    minimum ->
2305		[{vacmViewTreeFamily,
2306		  "restricted", [1,3,6,1], included, null}];
2307	    {semi, _} ->
2308		[{vacmViewTreeFamily,
2309		  "restricted", [1,3,6,1,2,1,1], included, null},
2310		 {vacmViewTreeFamily,
2311		  "restricted", [1,3,6,1,2,1,11], included, null},
2312		 {vacmViewTreeFamily,
2313		  "restricted", [1,3,6,1,6,3,10,2,1], included, null},
2314		 {vacmViewTreeFamily,
2315		  "restricted", [1,3,6,1,6,3,11,2,1], included, null},
2316		 {vacmViewTreeFamily,
2317		  "restricted", [1,3,6,1,6,3,15,1,1], included, null}]
2318	end,
2319    VTF = VTF0 ++ [{vacmViewTreeFamily,"internet",[1,3,6,1],included,null}],
2320    write_agent_vacm_config(Dir, Hdr, Groups ++ Acc ++ VTF).
2321
2322vacm_ver(v1) -> v1;
2323vacm_ver(v2) -> v2c;
2324vacm_ver(v3) -> usm.
2325
2326write_agent_vacm_config(Dir, Hdr, Conf) ->
2327    snmpa_conf:write_vacm_config(Dir, Hdr, Conf).
2328
2329update_agent_vacm_config(Dir, Conf) ->
2330    snmpa_conf:append_vacm_config(Dir, Conf).
2331
2332
2333%%
2334%% ----- Manager config files generator functions -----
2335%%
2336
2337write_manager_snmp_files(Dir, Transports, MMS, EngineID) ->
2338    write_manager_snmp_files(Dir, Transports, MMS, EngineID,
2339                             [], [], []).
2340
2341write_manager_snmp_files(Dir, IP, Port, MMS, EngineID) ->
2342    write_manager_snmp_files(Dir, IP, Port, MMS, EngineID,
2343                             [], [], []).
2344
2345write_manager_snmp_files(Dir, Transports, MMS, EngineID,
2346			 Users, Agents, Usms) ->
2347    write_manager_snmp_conf(Dir, Transports, MMS, EngineID),
2348    write_manager_snmp_users_conf(Dir, Users),
2349    write_manager_snmp_agents_conf(Dir, Agents),
2350    write_manager_snmp_usm_conf(Dir, Usms),
2351    ok.
2352
2353write_manager_snmp_files(Dir, IP, Port, MMS, EngineID,
2354			 Users, Agents, Usms) ->
2355    write_manager_snmp_conf(Dir, IP, Port, MMS, EngineID),
2356    write_manager_snmp_users_conf(Dir, Users),
2357    write_manager_snmp_agents_conf(Dir, Agents),
2358    write_manager_snmp_usm_conf(Dir, Usms),
2359    ok.
2360
2361
2362%%
2363%% ------ manager.conf ------
2364%%
2365
2366write_manager_snmp_conf(Dir, Transports, MMS, EngineID) ->
2367    Comment =
2368"%% This file defines the Manager local configuration info\n"
2369"%% Each row is a 2-tuple:\n"
2370"%% {Variable, Value}.\n"
2371"%% For example\n"
2372"%% {transports,       [{transportDomainUdpIpv4, {{127,42,17,5}, 5000}}]}.\n"
2373"%% {engine_id,        \"managerEngine\"}.\n"
2374"%% {max_message_size, 484}.\n"
2375"%%\n\n",
2376    Hdr = header() ++ Comment,
2377    Conf =
2378	[{transports,       Transports},
2379	 {engine_id,        EngineID},
2380	 {max_message_size, MMS}],
2381    write_manager_config(Dir, Hdr, Conf).
2382
2383write_manager_snmp_conf(Dir, Domain_or_IP, Addr_or_Port, MMS, EngineID) ->
2384    Comment =
2385"%% This file defines the Manager local configuration info\n"
2386"%% Each row is a 2-tuple:\n"
2387"%% {Variable, Value}.\n"
2388"%% For example\n"
2389"%% {port,             5000}.\n"
2390"%% {address,          [127,42,17,5]}.\n"
2391"%% {engine_id,        \"managerEngine\"}.\n"
2392"%% {max_message_size, 484}.\n"
2393"%%\n\n",
2394    Hdr = header() ++ Comment,
2395    Conf =
2396	case Addr_or_Port of
2397	    {IP, Port} when is_integer(Port), is_atom(Domain_or_IP) ->
2398		[{domain,  Domain_or_IP},
2399		 {port,    Port},
2400		 {address, IP}];
2401	    _ when is_integer(Addr_or_Port) ->
2402		[{port,    Addr_or_Port},
2403		 {address, Domain_or_IP}];
2404	    _ ->
2405		error({bad_address, {Domain_or_IP, Addr_or_Port}})
2406	end ++
2407	[{engine_id,        EngineID},
2408	 {max_message_size, MMS}],
2409    write_manager_config(Dir, Hdr, Conf).
2410
2411write_manager_config(Dir, Hdr, Conf) ->
2412    snmpm_conf:write_manager_config(Dir, Hdr, Conf).
2413
2414update_manager_config(Dir, Conf) ->
2415    snmpm_conf:append_manager_config(Dir, Conf).
2416
2417
2418%%
2419%% ------ users.conf ------
2420%%
2421
2422write_manager_snmp_users_conf(Dir, Users) ->
2423    Comment =
2424"%% This file defines the users the manager handles\n"
2425"%% Each row is a 3-tuple:\n"
2426"%% {UserId, UserMod, UserData}.\n"
2427"%% For example\n"
2428"%% {kalle, kalle_callback_user_mod, \"dummy\"}.\n"
2429"%%\n\n",
2430    Hdr = header() ++ Comment,
2431    write_manager_users_config(Dir, Hdr, Users).
2432
2433write_manager_users_config(Dir, Hdr, Users) ->
2434    snmpm_conf:write_users_config(Dir, Hdr, Users).
2435
2436update_manager_users_config(Dir, Users) ->
2437    snmpm_conf:append_users_config(Dir, Users).
2438
2439
2440%%
2441%% ------ agents.conf ------
2442%%
2443
2444write_manager_snmp_agents_conf(Dir, Agents) ->
2445    Comment =
2446"%% This file defines the agents the manager handles\n"
2447"%% Each row is a 12-tuple:\n"
2448"%% {UserId, \n"
2449"%%  TargetName, Comm, Ip, Port, EngineID, Timeout, \n"
2450"%%  MaxMessageSize, Version, SecModel, SecName, SecLevel}\n"
2451"%%\n\n",
2452    Hdr = header() ++ Comment,
2453    write_manager_agents_config(Dir, Hdr, Agents).
2454
2455write_manager_agents_config(Dir, Hdr, Agents) ->
2456    snmpm_conf:write_agents_config(Dir, Hdr, Agents).
2457
2458update_manager_agents_config(Dir, Agents) ->
2459    snmpm_conf:append_agents_config(Dir, Agents).
2460
2461
2462%%
2463%% ------ usm.conf -----
2464%%
2465
2466write_manager_snmp_usm_conf(Dir, Usms) ->
2467    Comment =
2468"%% This file defines the usm users the manager handles\n"
2469"%% Each row is a 6 or 7-tuple:\n"
2470"%% {EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey}\n"
2471"%% {EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey}\n"
2472"%%\n\n",
2473    Hdr = header() ++ Comment,
2474    write_manager_usm_config(Dir, Hdr, Usms).
2475
2476write_manager_usm_config(Dir, Hdr, Usms) ->
2477    snmpm_conf:write_usm_config(Dir, Hdr, Usms).
2478
2479update_manager_usm_config(Dir, Usms) ->
2480    snmpm_conf:append_usm_config(Dir, Usms).
2481
2482
2483%%
2484%% -------------------------------------------------------------------------
2485%%
2486
2487write_sys_config_file(Dir, Services) ->
2488    {ok, Fid} = file:open(filename:join(Dir,"sys.config"), [write]),
2489    ok = io:format(Fid, "~s", [header()]),
2490    ok = io:format(Fid, "[{snmp, ~n", []),
2491    ok = io:format(Fid, "  [~n", []),
2492    write_sys_config_file_services(Fid, Services),
2493    ok = io:format(Fid, "  ]~n", []),
2494    ok = io:format(Fid, " }~n", []),
2495    ok = io:format(Fid, "].~n", []),
2496    ok.
2497
2498write_sys_config_file_services(Fid, [Service]) ->
2499    write_sys_config_file_service(Fid, Service),
2500    ok = io:format(Fid, "~n", []),
2501    ok;
2502write_sys_config_file_services(Fid, [Service|Services]) ->
2503    write_sys_config_file_service(Fid, Service),
2504    ok = io:format(Fid, ", ~n", []),
2505    write_sys_config_file_services(Fid, Services).
2506
2507write_sys_config_file_service(Fid, {Service, Opts}) ->
2508    ok = io:format(Fid, "   {~w,~n", [Service]),
2509    ok = io:format(Fid, "    [~n", []),
2510    write_sys_config_file_service_opts(Fid, Service, Opts),
2511    ok = io:format(Fid, "    ]~n", []),
2512    ok = io:format(Fid, "   }", []),
2513    true.
2514
2515write_sys_config_file_service_opts(Fid, agent, Opts) ->
2516    write_sys_config_file_agent_opts(Fid, Opts);
2517write_sys_config_file_service_opts(Fid, manager, Opts) ->
2518    write_sys_config_file_manager_opts(Fid, Opts).
2519
2520
2521write_sys_config_file_agent_opts(Fid, [Opt]) ->
2522    write_sys_config_file_agent_opt(Fid, Opt),
2523    ok = io:format(Fid, "~n", []),
2524    ok;
2525write_sys_config_file_agent_opts(Fid, [Opt|Opts]) ->
2526    write_sys_config_file_agent_opt(Fid, Opt),
2527    ok = io:format(Fid, ", ~n", []),
2528    write_sys_config_file_agent_opts(Fid, Opts).
2529
2530
2531write_sys_config_file_agent_opt(Fid, {mibs, []}) ->
2532    ok = io:format(Fid, "     {mibs, []}", []);
2533write_sys_config_file_agent_opt(Fid, {priority, Prio}) ->
2534    ok = io:format(Fid, "     {priority, ~w}", [Prio]);
2535write_sys_config_file_agent_opt(Fid, {error_report_mod, Mod}) ->
2536    ok = io:format(Fid, "     {error_report_mod, ~w}", [Mod]);
2537write_sys_config_file_agent_opt(Fid, {versions, Vsns}) ->
2538    ok = io:format(Fid, "     {versions, ~w}", [Vsns]);
2539write_sys_config_file_agent_opt(Fid, {multi_threaded, B}) ->
2540    ok = io:format(Fid, "     {multi_threaded, ~w}", [B]);
2541write_sys_config_file_agent_opt(Fid, {config, Opts}) ->
2542    ok = io:format(Fid, "     {config, [", []),
2543    write_sys_config_file_agent_config_opts(Fid, Opts),
2544    ok = io:format(Fid, "}", []);
2545write_sys_config_file_agent_opt(Fid, {db_dir, Dir}) ->
2546    ok = io:format(Fid, "     {db_dir, \"~s\"}", [Dir]);
2547write_sys_config_file_agent_opt(Fid, {db_init_error, Action}) ->
2548    ok = io:format(Fid, "     {db_init_error, ~w}", [Action]);
2549write_sys_config_file_agent_opt(Fid, {mib_storage, ets}) ->
2550    ok = io:format(Fid, "     {mib_storage, ets}", []);
2551write_sys_config_file_agent_opt(Fid, {mib_storage, {dets, Dir}}) ->
2552    ok = io:format(Fid, "     {mib_storage, {dets, \"~s\"}}", [Dir]);
2553write_sys_config_file_agent_opt(Fid, {mib_storage, {dets, Dir, Act}}) ->
2554    ok = io:format(Fid, "     {mib_storage, {dets, \"~s\", ~w}}",
2555		   [Dir, Act]);
2556write_sys_config_file_agent_opt(Fid, {mib_storage, {mnesia, Nodes}}) ->
2557    ok = io:format(Fid, "     {mib_storage, {mnesia, ~w}}", [Nodes]);
2558write_sys_config_file_agent_opt(Fid, {mib_storage, {mnesia, Nodes, Act}}) ->
2559    ok = io:format(Fid, "     {mib_storage, {mnesia, ~w, ~w}}",
2560		   [Nodes, Act]);
2561write_sys_config_file_agent_opt(Fid, {target_cache, Opts}) ->
2562    ok = io:format(Fid, "     {target_cache, ~w}", [Opts]);
2563write_sys_config_file_agent_opt(Fid, {local_db, Opts}) ->
2564    ok = io:format(Fid, "     {local_db, ~w}", [Opts]);
2565write_sys_config_file_agent_opt(Fid, {note_store, Opts}) ->
2566    ok = io:format(Fid, "     {note_store, ~w}", [Opts]);
2567write_sys_config_file_agent_opt(Fid, {symbolic_store, Opts}) ->
2568    ok = io:format(Fid, "     {symbolic_store, ~w}", [Opts]);
2569write_sys_config_file_agent_opt(Fid, {agent_type, Type}) ->
2570    ok = io:format(Fid, "     {agent_type, ~w}", [Type]);
2571write_sys_config_file_agent_opt(Fid, {agent_verbosity, Verb}) ->
2572    ok = io:format(Fid, "     {agent_verbosity, ~w}", [Verb]);
2573write_sys_config_file_agent_opt(Fid, {audit_trail_log, Opts}) ->
2574    ok = io:format(Fid, "     {audit_trail_log, [", []),
2575    write_sys_config_file_agent_atl_opts(Fid, Opts),
2576    ok = io:format(Fid, "}", []);
2577write_sys_config_file_agent_opt(Fid, {discovery, Opts}) ->
2578    ok = io:format(Fid, "     {discovery, [", []),
2579    write_sys_config_file_agent_disco_opts(Fid, Opts),
2580    ok = io:format(Fid, "}", []);
2581write_sys_config_file_agent_opt(Fid, {net_if, Opts}) ->
2582    ok = io:format(Fid, "     {net_if, ~w}", [Opts]);
2583write_sys_config_file_agent_opt(Fid, {mib_server, Opts}) ->
2584    ok = io:format(Fid, "     {mib_server, ~w}", [Opts]);
2585write_sys_config_file_agent_opt(Fid, {Key, Val}) ->
2586    ok = io:format(Fid, "     {~w, ~w}", [Key, Val]).
2587
2588
2589%% Mandatory option dir, means that this is never empty:
2590write_sys_config_file_agent_config_opts(Fid, [Opt]) ->
2591    write_sys_config_file_agent_config_opt(Fid, Opt),
2592    ok = io:format(Fid, "]", []),
2593    ok;
2594write_sys_config_file_agent_config_opts(Fid, [Opt|Opts]) ->
2595    write_sys_config_file_agent_config_opt(Fid, Opt),
2596    ok = io:format(Fid, ", ", []),
2597    write_sys_config_file_agent_config_opts(Fid, Opts).
2598
2599write_sys_config_file_agent_config_opt(Fid, {dir, Dir}) ->
2600    ok = io:format(Fid, "{dir, \"~s\"}", [Dir]);
2601write_sys_config_file_agent_config_opt(Fid, {force_load, Bool}) ->
2602    ok = io:format(Fid, "{force_load, ~w}", [Bool]);
2603write_sys_config_file_agent_config_opt(Fid, {verbosity, Verb}) ->
2604    ok = io:format(Fid, "{verbosity, ~w}", [Verb]).
2605
2606
2607%% This is only present if there is atleast one option
2608write_sys_config_file_agent_atl_opts(Fid, [Opt]) ->
2609    write_sys_config_file_agent_atl_opt(Fid, Opt),
2610    ok = io:format(Fid, "]", []),
2611    ok;
2612write_sys_config_file_agent_atl_opts(Fid, [Opt|Opts]) ->
2613    write_sys_config_file_agent_atl_opt(Fid, Opt),
2614    ok = io:format(Fid, ", ", []),
2615    write_sys_config_file_agent_atl_opts(Fid, Opts).
2616
2617write_sys_config_file_agent_atl_opt(Fid, {dir, Dir}) ->
2618    ok = io:format(Fid, "{dir, \"~s\"}", [Dir]);
2619write_sys_config_file_agent_atl_opt(Fid, {type, Type}) ->
2620    ok = io:format(Fid, "{type, ~w}", [Type]);
2621write_sys_config_file_agent_atl_opt(Fid, {size, Size}) ->
2622    ok = io:format(Fid, "{size, ~w}", [Size]);
2623write_sys_config_file_agent_atl_opt(Fid, {repair, Rep}) ->
2624    ok = io:format(Fid, "{repair, ~w}", [Rep]);
2625write_sys_config_file_agent_atl_opt(Fid, {seqno, SeqNo}) ->
2626    ok = io:format(Fid, "{seqno, ~w}", [SeqNo]).
2627
2628
2629%% These options are allways there
2630write_sys_config_file_agent_disco_opts(Fid, [Opt]) ->
2631    write_sys_config_file_agent_disco_opt(Fid, Opt),
2632    ok = io:format(Fid, "]", []),
2633    ok;
2634write_sys_config_file_agent_disco_opts(Fid, [Opt|Opts]) ->
2635    write_sys_config_file_agent_disco_opt(Fid, Opt),
2636    ok = io:format(Fid, ", ", []),
2637    write_sys_config_file_agent_disco_opts(Fid, Opts).
2638
2639write_sys_config_file_agent_disco_opt(Fid, {terminating, Opts}) ->
2640    ok = io:format(Fid, "{terminating, [", []),
2641    write_sys_config_file_agent_term_disco_opts(Fid, Opts),
2642    ok = io:format(Fid, "}", []);
2643write_sys_config_file_agent_disco_opt(Fid, {originating, Opts}) ->
2644    ok = io:format(Fid, "{originating, [", []),
2645    write_sys_config_file_agent_orig_disco_opts(Fid, Opts),
2646    ok = io:format(Fid, "}", []).
2647
2648write_sys_config_file_agent_term_disco_opts(Fid, [Opt]) ->
2649    write_sys_config_file_agent_term_disco_opt(Fid, Opt),
2650    ok = io:format(Fid, "]", []),
2651    ok;
2652write_sys_config_file_agent_term_disco_opts(Fid, [Opt|Opts]) ->
2653    write_sys_config_file_agent_term_disco_opt(Fid, Opt),
2654    ok = io:format(Fid, ", ", []),
2655    write_sys_config_file_agent_term_disco_opts(Fid, Opts).
2656
2657write_sys_config_file_agent_term_disco_opt(Fid, {enable, Enable}) ->
2658    ok = io:format(Fid, "{enable, ~w}", [Enable]);
2659write_sys_config_file_agent_term_disco_opt(Fid, {stage2, Stage2}) ->
2660    ok = io:format(Fid, "{stage2, ~w}", [Stage2]);
2661write_sys_config_file_agent_term_disco_opt(Fid, {trigger_username, Trigger}) ->
2662    ok = io:format(Fid, "{trigger_username, \"~s\"}", [Trigger]).
2663
2664write_sys_config_file_agent_orig_disco_opts(Fid, [Opt]) ->
2665    write_sys_config_file_agent_orig_disco_opt(Fid, Opt),
2666    ok = io:format(Fid, "]", []),
2667    ok;
2668write_sys_config_file_agent_orig_disco_opts(Fid, [Opt|Opts]) ->
2669    write_sys_config_file_agent_orig_disco_opt(Fid, Opt),
2670    ok = io:format(Fid, ", ", []),
2671    write_sys_config_file_agent_orig_disco_opts(Fid, Opts).
2672
2673write_sys_config_file_agent_orig_disco_opt(Fid, {enable, Enable}) ->
2674    ok = io:format(Fid, "{enable, ~w}", [Enable]).
2675
2676
2677
2678write_sys_config_file_manager_opts(Fid, [Opt]) ->
2679    write_sys_config_file_manager_opt(Fid, Opt),
2680    ok = io:format(Fid, "~n", []),
2681    ok;
2682write_sys_config_file_manager_opts(Fid, [Opt|Opts]) ->
2683    write_sys_config_file_manager_opt(Fid, Opt),
2684    ok = io:format(Fid, ", ~n", []),
2685    write_sys_config_file_manager_opts(Fid, Opts).
2686
2687
2688write_sys_config_file_manager_opt(Fid, {mibs, []}) ->
2689    ok = io:format(Fid, "     {mibs, []}", []);
2690write_sys_config_file_manager_opt(Fid, {priority, Prio}) ->
2691    ok = io:format(Fid, "     {priority, ~w}", [Prio]);
2692write_sys_config_file_manager_opt(Fid, {versions, Vsns}) ->
2693    ok = io:format(Fid, "     {versions, ~w}", [Vsns]);
2694write_sys_config_file_manager_opt(Fid, {config, Opts}) ->
2695    ok = io:format(Fid, "     {config, [", []),
2696    write_sys_config_file_manager_config_opts(Fid, Opts),
2697    ok = io:format(Fid, "}", []);
2698write_sys_config_file_manager_opt(Fid, {server, Opts}) ->
2699    ok = io:format(Fid, "     {server, ~w}", [Opts]);
2700write_sys_config_file_manager_opt(Fid, {note_store, Opts}) ->
2701    ok = io:format(Fid, "     {note_store, ~w}", [Opts]);
2702write_sys_config_file_manager_opt(Fid, {audit_trail_log, Opts}) ->
2703    ok = io:format(Fid, "     {audit_trail_log, [", []),
2704    write_sys_config_file_manager_atl_opts(Fid, Opts),
2705    ok = io:format(Fid, "}", []);
2706write_sys_config_file_manager_opt(Fid, {net_if, Opts}) ->
2707    ok = io:format(Fid, "     {net_if, ~w}", [Opts]);
2708write_sys_config_file_manager_opt(Fid, {Key, Val}) ->
2709    ok = io:format(Fid, "     {~w, ~w}", [Key, Val]).
2710
2711%% Mandatory option dir, means that this is never empty:
2712write_sys_config_file_manager_config_opts(Fid, [Opt]) ->
2713    write_sys_config_file_manager_config_opt(Fid, Opt),
2714    ok = io:format(Fid, "]", []),
2715    ok;
2716write_sys_config_file_manager_config_opts(Fid, [Opt|Opts]) ->
2717    write_sys_config_file_manager_config_opt(Fid, Opt),
2718    ok = io:format(Fid, ", ", []),
2719    write_sys_config_file_manager_config_opts(Fid, Opts).
2720
2721write_sys_config_file_manager_config_opt(Fid, {dir, Dir}) ->
2722    ok = io:format(Fid, "{dir, \"~s\"}", [Dir]);
2723write_sys_config_file_manager_config_opt(Fid, {db_dir, Dir}) ->
2724    ok = io:format(Fid, "{db_dir, \"~s\"}", [Dir]);
2725write_sys_config_file_manager_config_opt(Fid, {db_init_error, Action}) ->
2726    ok = io:format(Fid, "{db_init_error, ~w}", [Action]);
2727write_sys_config_file_manager_config_opt(Fid, {repair, Rep}) ->
2728    ok = io:format(Fid, "{repair, ~w}", [Rep]);
2729write_sys_config_file_manager_config_opt(Fid, {auto_save, As}) ->
2730    ok = io:format(Fid, "{auto_save, ~w}", [As]);
2731write_sys_config_file_manager_config_opt(Fid, {verbosity, Verb}) ->
2732    ok = io:format(Fid, "{verbosity, ~w}", [Verb]).
2733
2734
2735%% This is only present if there is atleast one option
2736write_sys_config_file_manager_atl_opts(Fid, [Opt]) ->
2737    write_sys_config_file_manager_atl_opt(Fid, Opt),
2738    ok = io:format(Fid, "]", []),
2739    ok;
2740write_sys_config_file_manager_atl_opts(Fid, [Opt|Opts]) ->
2741    write_sys_config_file_manager_atl_opt(Fid, Opt),
2742    ok = io:format(Fid, ", ", []),
2743    write_sys_config_file_manager_atl_opts(Fid, Opts).
2744
2745write_sys_config_file_manager_atl_opt(Fid, {dir, Dir}) ->
2746    ok = io:format(Fid, "{dir, \"~s\"}", [Dir]);
2747write_sys_config_file_manager_atl_opt(Fid, {type, Type}) ->
2748    ok = io:format(Fid, "{type, ~w}", [Type]);
2749write_sys_config_file_manager_atl_opt(Fid, {size, Size}) ->
2750    ok = io:format(Fid, "{size, ~w}", [Size]);
2751write_sys_config_file_manager_atl_opt(Fid, {repair, Rep}) ->
2752    ok = io:format(Fid, "{repair, ~w}", [Rep]);
2753write_sys_config_file_manager_atl_opt(Fid, {seqno, SeqNo}) ->
2754    ok = io:format(Fid, "{seqno, ~w}", [SeqNo]).
2755
2756
2757header() ->
2758    {Y, Mo, D} = date(),
2759    {H, Mi, S} = time(),
2760    io_lib:format("%% This file was generated by "
2761		  "~w (version-~s) ~w-~2.2.0w-~2.2.0w "
2762		  "~2.2.0w:~2.2.0w:~2.2.0w\n",
2763		  [?MODULE, ?version, Y, Mo, D, H, Mi, S]).
2764
2765
2766%% *If* these functions are successfull, they successfully return
2767%% (value is ignored), but they fail preferably with
2768%% throw({error, Reason}).  Other exceptions are also handled.
2769
2770%% Sorting order for config entries (see lists:sort/2)
2771-type(order_config_entry_function() ::
2772	fun((term(), term()) -> boolean())).
2773
2774%% Check of config entries. Initial State is 'undefined'
2775-type(check_config_entry_function() ::
2776	fun((Entry :: term(), State :: undefined | term()) ->
2777		    {ok | {ok, NewEntry :: term()}, NewState :: term()})).
2778
2779%% Write configuration entries to file descriptor Fd
2780-type(write_config_function() ::
2781	fun((Fd :: file:io_device(), [Entry :: term()]) -> ok)).
2782
2783-spec write_config_file(
2784	Dir :: string(),
2785	FileName :: string(),
2786	Order :: order_config_entry_function(),
2787	Check :: check_config_entry_function(),
2788	Write :: write_config_function(),
2789	Entries :: [term()]) ->
2790			       ok | {error, term()}.
2791
2792write_config_file(Dir, FileName, Order, Check, Write, Entries)
2793  when is_list(Dir), is_list(FileName),
2794       is_function(Order), is_function(Check), is_function(Write),
2795       is_list(Entries) ->
2796    try
2797	SortedEntries = lists:sort(Order, Entries),
2798	_ =
2799	    lists:foldl(
2800	      fun (Entry, State) ->
2801		      case Check(Entry, State) of
2802			  {Ok, NewState} when is_list(Ok) ->
2803			      NewState;
2804			  {ok, NewState} ->
2805			      NewState;
2806			  {{ok, _}, NewState} ->
2807			      NewState
2808		      end
2809	      end, undefined, SortedEntries),
2810	ok
2811    of
2812	_ ->
2813	    case file:open(filename:join(Dir, FileName), [write]) of
2814		{ok, Fd} ->
2815		    write_config_file(Dir, FileName, Write, Entries, Fd);
2816		Error ->
2817		    Error
2818	    end
2819    catch
2820	throw:E:S ->
2821	    d("File write of ~s throwed: "
2822              "~n   ~p"
2823              "~n   ~p"
2824              "~n", [FileName, E, S]),
2825	    E;
2826	C:E:S ->
2827	    d("File write of ~s exception: "
2828              "~n   ~p:~p"
2829              "~n   ~p"
2830              "~n", [FileName, C, E, S]),
2831	    {error, {failed_write, Dir, FileName, {C, E, S}}}
2832    end.
2833
2834write_config_file(Dir, FileName, Write, Entries, Fd) ->
2835    try	Write(Fd, Entries) of
2836	ok ->
2837	    close_config_file(Dir, FileName, Fd)
2838    catch
2839	throw:E:S ->
2840	    d("File write of ~s throwed: "
2841              "~n   ~p"
2842              "~n   ~p"
2843              "~n", [FileName, E, S]),
2844	    close_config_file(Dir, FileName, Fd),
2845	    E;
2846	C:E:S ->
2847	    d("File write of ~s exception: "
2848              "~n   ~p:~p"
2849              "~n   ~p"
2850              "~n", [FileName, C, E, S]),
2851	    close_config_file(Dir, FileName, Fd),
2852	    {error, {failed_write, Dir, FileName, {C, E, S}}}
2853    end.
2854
2855close_config_file(Dir, FileName, Fd) ->
2856    case file:sync(Fd) of
2857	ok ->
2858	    case file:close(Fd) of
2859		ok ->
2860		    ok;
2861		{error, Reason} ->
2862		    {error, {failed_closing, Dir, FileName, Reason}}
2863	    end;
2864	{error, Reason} ->
2865	    _ = file:close(Fd),
2866	    {error, {failed_syncing, Dir, FileName, Reason}}
2867    end.
2868
2869
2870
2871-spec append_config_file(
2872	Dir :: string(),
2873	FileName :: string(),
2874	Order :: order_config_entry_function(),
2875	Check :: check_config_entry_function(),
2876	Write :: write_config_function(),
2877	Entries :: [term()]) ->
2878			       ok | {error, term()}.
2879
2880append_config_file(Dir, FileName, Order, Check, Write, Entries)
2881  when is_list(Dir), is_list(FileName),
2882       is_function(Order), is_function(Check), is_function(Write),
2883       is_list(Entries) ->
2884    case file:open(filename:join(Dir, FileName), [read, write]) of
2885	{ok, Fd} ->
2886	    append_config_file(
2887	      Dir, FileName, Order, Check, Write, Entries, Fd);
2888	Error ->
2889	    Error
2890    end.
2891
2892append_config_file(Dir, FileName, Order, Check, Write, Entries, Fd) ->
2893    try
2894	%% Verify the entries together with the file content
2895	LinesInFileR = read_lines(Fd, [], 1),
2896	StartLine =
2897	    case LinesInFileR of
2898		[] ->
2899		    1;
2900		[{_, _, EndLine} | _] ->
2901		    EndLine
2902	    end,
2903	LinesR = prepend_lines(LinesInFileR, Entries, StartLine),
2904	SortedLines = sort_lines(lists:reverse(LinesR), Order),
2905	_ = verify_lines(SortedLines, Check, undefined, []),
2906	%% Append to the file
2907	Write(Fd, Entries)
2908    of
2909	ok ->
2910	    close_config_file(Dir, FileName, Fd)
2911    catch
2912	throw:E:S ->
2913	    d("File append of ~s throwed: "
2914              "~n   ~p"
2915              "~n   ~p"
2916              "~n", [FileName, E, S]),
2917	    close_config_file(Dir, FileName, Fd),
2918	    E;
2919	C:E:S ->
2920	    d("File append of ~s exception: "
2921              "~n   ~p:~p"
2922              "~n   ~p"
2923              "~n", [FileName, C, E, S]),
2924	    close_config_file(Dir, FileName, Fd),
2925	    {error, {failed_append, Dir, FileName, {C, E, S}}}
2926    end.
2927
2928%% Fake line numbers, one per entry
2929prepend_lines(Lines, [], _) ->
2930    Lines;
2931prepend_lines(Lines, [Entry | Entries], StartLine) ->
2932    EndLine = StartLine + 1,
2933    prepend_lines([{StartLine, Entry, EndLine} | Lines], Entries, EndLine).
2934
2935
2936
2937-spec read_config_file(
2938	Dir :: string(),
2939	FileName :: string(),
2940	Order :: order_config_entry_function(),
2941	Check :: check_config_entry_function()) ->
2942				{ok, Config :: [Entry :: term()]} |
2943				{error, Reason :: term()}.
2944
2945read_config_file(Dir, FileName, Order, Check)
2946  when is_list(Dir), is_list(FileName),
2947       is_function(Order), is_function(Check) ->
2948    case file:open(filename:join(Dir, FileName), [read]) of
2949	{ok, Fd} ->
2950	    try
2951		Lines = lists:reverse(read_lines(Fd, [], 1)),
2952		SortedLines = sort_lines(Lines, Order),
2953		{ok, verify_lines(SortedLines, Check, undefined, [])}
2954	    catch
2955		throw:E:S ->
2956		    d("File read of ~s throwed: "
2957                      "~n   ~p"
2958                      "~n   ~p"
2959                      "~n", [FileName, E, S]),
2960		    {error, E};
2961		C:E:S ->
2962		    d("File read of ~s exception: "
2963                      "~n   ~p:~p"
2964                      "~n    ~p"
2965                      "~n", [FileName, C, E, S]),
2966		    {error, {failed_read, Dir, FileName, {C, E, S}}}
2967	    after
2968		file:close(Fd)
2969	    end;
2970	{error, Reason} ->
2971	    {error, {Reason, FileName}}
2972    end.
2973
2974read_lines(Fd, Acc, StartLine) ->
2975    case read_and_parse_term(Fd, StartLine) of
2976	{ok, Term, EndLine} ->
2977	    read_lines(Fd, [{StartLine, Term, EndLine}|Acc], EndLine);
2978	{error, Error, EndLine} ->
2979            throw({failed_reading, StartLine, EndLine, Error});
2980        {eof, _EndLine} ->
2981	    Acc
2982    end.
2983
2984read_and_parse_term(Fd, StartLine) ->
2985    Enc = latin1,
2986    case io:request(Fd, {get_until, Enc, "", erl_scan, tokens, [StartLine]}) of
2987	{ok, Tokens, EndLine} ->
2988	    case erl_parse:parse_term(Tokens) of
2989                {ok, Term} ->
2990                    {ok, Term, EndLine};
2991                {error, {Line, erl_parse, Error}} ->
2992                    {error, {parse_error, Error}, Line}
2993            end;
2994        Other ->
2995            Other
2996    end.
2997
2998sort_lines(Lines, Order) ->
2999    lists:sort(
3000      fun ({_, T1, _}, {_, T2, _}) ->
3001	      Order(T1, T2)
3002      end, Lines).
3003
3004verify_lines([], _, _, Acc) ->
3005    lists:reverse(Acc);
3006verify_lines(
3007  [{StartLine, Term, EndLine}|Lines], Check, State, Acc) ->
3008    try Check(Term, State) of
3009	{Terms, NewState} when is_list(Terms) ->
3010	    verify_lines(Lines, Check, NewState, Terms ++ Acc);
3011	{ok, NewState} ->
3012	    verify_lines(Lines, Check, NewState, [Term|Acc]);
3013	{{ok, NewTerm}, NewState} ->
3014	    verify_lines(Lines, Check, NewState, [NewTerm|Acc])
3015    catch
3016	throw:{error, Reason}:_ ->
3017	    throw({failed_check, StartLine, EndLine, Reason});
3018	C:E:S ->
3019	    throw({failed_check, StartLine, EndLine, {C, E, S}})
3020    end.
3021
3022
3023agent_snmp_mk_secret(Alg, Passwd, EngineID) ->
3024    snmp_usm:passwd2localized_key(Alg, Passwd, EngineID).
3025
3026
3027ensure_crypto_started() ->
3028    i("making sure crypto server is started..."),
3029    ensure_started(crypto).
3030
3031ensure_started(App) ->
3032    case (catch App:start()) of
3033	ok ->
3034	    ok;
3035	{error, {already_started, App}} ->
3036	    ok;
3037	E ->
3038	    error({failed_starting, App, E})
3039    end.
3040
3041
3042%% -------------------------------------------------------------------------
3043
3044d(F, A) ->
3045    i("DBG: " ++ F, A).
3046
3047i(F) ->
3048    i(F, []).
3049
3050i(F, A) ->
3051    io:format(F ++ "~n", A).
3052
3053error(R) ->
3054    throw({error, R}).
3055