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