ACC SHELL

Path : /usr/share/YaST2/modules/
File Upload :
Current File : //usr/share/YaST2/modules/NtpClient.ycp

/**
 * File:	modules/NtpClient.ycp
 * Package:	Configuration of ntp-client
 * Summary:	Data for configuration of ntp-client, input and output functions.
 * Authors:	Jiri Srain <jsrain@suse.cz>
 *
 * $Id: NtpClient.ycp 61641 2010-04-09 16:07:40Z varkoly $
 *
 * Representation of the configuration of ntp-client.
 * Input and output routines.
 */

{

module "NtpClient";
textdomain "ntp-client";

import "Directory";
import "FileUtils";
import "Language";
import "Message";
import "Mode";
import "NetworkInterfaces";
import "PackageSystem";
import "Popup";
import "Progress";
import "Report";
import "Service";
import "SLPAPI";
import "Stage";
import "String";
import "Summary";
import "SuSEFirewall";
import "FileChanges";


/**
 * Abort function
 * return boolean return true if abort
 */
global boolean() AbortFunction = nil;

/**
 * Data was modified?
 */
global boolean modified = false;

/**
 * Write only, used during autoinstallation.
 * Don't run services and SuSEconfig, it's all done at one place.
 */
global boolean write_only = false;

/**
 * Read all ntp-client settings
 * @return true on success
 */
global list<map<string, any> > ntp_records = [];

global map <string, map <string,any> > restrict_map = $[];

/**
  * Should the daemon be started when system boots?
  */
global boolean run_service = true;

/**
  * Should the time synchronized periodicaly?
  */
global boolean synchronize_time = false;

/**
  * The interval of synchronization in minutes.
  */
global integer sync_interval = 5;

/**
  * The cron file name for the synchronization.
  */
global string cron_file = "/etc/cron.d/novell.ntp-synchronize";

/**
 * Service name of the NTP daemon
 */
global string service_name = "ntp";

/**
  * Should the daemon be started in chroot environment?
  */
global boolean run_chroot = false;


/**
 * Netconfig policy: for merging and prioritizing static and DHCP config.
 * FIXME get a public URL
 * https://svn.suse.de/svn/sysconfig/branches/mt/dhcp6-netconfig/netconfig/doc/README
 */
global string ntp_policy = "auto";


/**
  * Index of the currently sellected item
  */
global integer selected_index = -1;

/**
  * The currently sellected item
  */
global map<string,any> selected_record = $[];

/**
  * Active Directory controller
  */
global string ad_controller = "";

/**
  * Should the firewall settings be changed?
  */
global boolean change_firewall = false;

/**
  * Required packages
  */
global list required_packages = ["ntp"];

/**
 * ports in firewall to open
 */
global list<string> firewall_services = ["service:ntp"];


/**
 * List of known NTP servers
 * server address -> information
 *  address: the key repeated
 *  country: CC (uppercase)
 *  location: for displaying
 *  ...: (others are unused)
 */
map<string,map<string,string> > ntp_servers = nil;

/**
 * Mapping between country codes and country names ("CZ" -> "Czech Republic")
 */
map<string,string> country_names = nil;

global boolean simple_dialog = false;

global boolean config_has_been_read = false;

global boolean ntp_selected = false;


global boolean PolicyIsAuto() {
    return ntp_policy == "auto" || ntp_policy == "STATIC *";
}

global boolean PolicyIsNomodify() {
    return ntp_policy == "";
}

global boolean PolicyIsNonstatic() {
    return ntp_policy != "" && ntp_policy != "STATIC";
}

/**
 * Abort function
 * @return blah blah lahjk
 */
define boolean Abort() ``{
    if(NtpClient::AbortFunction != nil)
	return NtpClient::AbortFunction() == true;
    return false;
}

// for lazy loading
boolean countries_already_read = false;
map <string, string> known_countries = $[];
/**
 * Reads and returns all known countries with their country codes
 *
 * @return map <string, string> of known contries
 * @struct $[
 *   "CL" : "Chile",
 *   "FR" : "France",
 *   ...
 * ]
 */
global define map <string, string> GetAllKnownCountries () {
    //first point of dependence on yast2-country-data
    if (!countries_already_read) {
	known_countries = (map <string, string>) eval (SCR::Read (.target.ycp, Directory::datadir + "/country.ycp"));
	countries_already_read = true;
	if (known_countries == nil) known_countries = $[];
    }

    //workaround bug #241054: servers in United Kingdom are in domain .uk
    // domain .gb does not exist - add UK to the list of known countries
    if (haskey(known_countries, "GB")){
	known_countries["UK"] = known_countries["GB"]:"";
	known_countries = remove(known_countries,"GB");
    }

    return known_countries;
}

/**
 * Read current language (RC_LANG from sysconfig)
 * @return two-letter language code (cs_CZ.UTF-8 -> CZ)
 */
global define string GetCurrentLanguageCode (  ) {
    string lang = (string)SCR::Read (.sysconfig.language.RC_LANG);

    //second point of dependence on yast2-country-data
    return Language::GetGivenLanguageCountry( lang );
}

map<string, string> MakePoolRecord (string CC, string location) {
    return $[
	     "address" : tolower(CC) + ".pool.ntp.org",
	     "country" : CC,
	     "location": location,
	     ];
}

/**
 * Get the list of known NTP servers
 * @return a list of known NTP servers
 */
global map<string, map<string,string> > GetNtpServers () {
    if (ntp_servers == nil)
    {
	ntp_servers = $[];
	list<map<string,string> > servers = (list<map<string,string> >)
	    SCR::Read (.target.ycp, Directory::datadir + "/ntp_servers.ycp");
	if (servers == nil)
	{
	    y2error ("Failed to read the list of NTP servers");
	}
	else
	{
	    y2milestone ("%1 known NTP servers read", size (servers));
	    ntp_servers = listmap (map<string,string> s, servers, {
		string server = s["address"]:"";
		return $[ server : s ];
	    });
	}
	foreach (string short_country, string country_name, GetAllKnownCountries(), {
	    // just refactored existing code
	    map<string, string> p = MakePoolRecord(short_country, country_name);
	    ntp_servers[p["address"]:""] = p;
	});
    }

    return ntp_servers;
}

/**
 * Get the mapping between country codea and names ("CZ" -> "Czech Republic")
 * @return a map the country codes and names mapping
 */
global map<string, string> GetCountryNames () {
    if (country_names == nil)
    {
	country_names = (map<string,string>)
	    eval(SCR::Read(.target.yast2, "country.ycp"));
    }
    if (country_names == nil)
    {
	y2error ("Failed to read country names");
	country_names = $[];
    }
    return country_names;
}

/**
 * Get list of public NTP servers for a country
 * @param country two-letter country code
 * @param terse_output display additional data (location etc.)
 * @return list of servers (usable as combo-box items)
 */
global list GetNtpServersByCountry ( string country, boolean terse_output ) {
    map<string,string> country_names = $[];
    map<string,map<string,string> > servers = GetNtpServers ();
    if (country != "")
    {
        servers = filter (string s, map<string,string> o, servers, {
            return o["country"]:"" == country;
        });
	// bnc#458917 add country, in case data/country.ycp does not have it
	map<string,string> p = MakePoolRecord (country, "");
	servers[p["address"]:""] = p;
    }
    else
    {
        country_names = GetCountryNames ();
    }

    list items = maplist (string s, map<string,string> o, servers, {
	string label = o["location"]:"";
        string l_country = o["country"]:"";
        if (country != "")
            l_country = "";
        else
            l_country = country_names[l_country]:l_country;
        if (label != "" && l_country != "")
            label = sformat ("%1 (%2, %3)", s, label, l_country);
        else if (label == "" && l_country == "")
            label = s;
        else
            label = sformat ("%1 (%2%3)", s, label, l_country);

	if (terse_output)
	    return `item (`id(s), s);
	else
            return `item (`id (s), label);
    });

    return items;

}

/**
 * Read and parse /etc.ntp.conf
 * @return true on success
 */
global define boolean ProcessNtpConf() ``{

    if (config_has_been_read)
    {
	y2milestone("Configuration has been read already, skipping.");
	return false;
    }

    map conf = nil;
    if (FileUtils::Exists("/etc/ntp.conf"))
        conf = (map)SCR::Read (.etc.ntp_conf.all);

    if (conf == nil )
    {
	y2error("Failed to read /etc/ntp.conf, either it doesn't exist or contains no data");
	return false;
    }

    config_has_been_read = true;
    list<map<string, any> > value = conf["value"]:[];
    integer index = -1;
    ntp_records = maplist (map<string, any> m, value, ``{
	index = index + 1;
	string type = m["name"]:"";
	string address = m["value"]:"";
	string options = "";
	if (contains ([
	    "server", "peer", "broadcast", "broadcastclient", "manycast",
	    "manycastclient", "fudge", "restrict" ],
	    type))
	{
	    list<string> l = splitstring (address, " \t");
	    l = (list<string>)filter (string s, l, ``(s != ""));
	    address = l[0]:"";
	    l[0] = "";
	    options = mergestring (l, " ");
	}
	map<string,any> entry = $[
	    "type" : type,
	    "address" : address,
	    "options" : options,
	    "comment" : m["comment"]:"",
	];
	return entry;
    });
    list<map<string, any> > fudge_records = filter (map<string,any> m, ntp_records, ``(
	m["type"]:"" == "fudge"
    ));
    map <string, map<string, any> > fudge_map = (map <string, map<string, any> >)listmap (map m, fudge_records,
    ``{
	string key = m["address"]:"";
	return $[key: m];
    });

    list<map<string, any> > restrict_records = filter (map<string,any> m, ntp_records, ``(
	m["type"]:"" == "restrict"
    ));

    restrict_map = (map <string, map <string,any> > )listmap (map m, restrict_records,
    {
	string key = m["address"]:"";
	map value = $[];

	list <string> opts = splitstring( String::CutBlanks( m["options"]:"" ), " \t");
	if ( opts[0]:""  == "mask") {
	    value["mask"] = opts[1]:"";
	    opts[0] = ""; opts[1] = "";
	}
	else {
	    value["mask"] = "";
	}

	value["options"] = String::CutBlanks( mergestring(opts, " "));
	value["comment"] = m["comment"]:"";

	return $[key: value];
    });

    ntp_records = filter (map<string,any> m, ntp_records, ``(
	m["type"]:"" != "fudge"
    ));

    ntp_records = filter (map<string,any> m, ntp_records, ``(
	m["type"]:"" != "restrict"
    ));

    ntp_records = (list<map<string,any> >)maplist (map m, ntp_records, ``{
	if (haskey (fudge_map, m["address"]:""))
	{
	    m["fudge_options"] = fudge_map[m["address"]:"", "options"]:"";
	    m["fudge_comment"] = fudge_map[m["address"]:"", "comment"]:"";
	}
	return m;
    });

    // mark local clock to be local clock and not real servers
    ntp_records = maplist (map<string,any> p, ntp_records, ``{
	if (p["type"]:"" == "server"
	    && regexpmatch (p["address"]:"", "^127\.127\.[0-9]+.[0-9]+$"))
	{
	    p["type"] = "__clock";
	}
	return p;
    });

    return true;

}

/**
 * Read all ntp-client settings
 * @return true on success
 */
global define boolean Read() ``{

    if (config_has_been_read)
	return true;

    /* NtpClient read dialog caption */
    string caption = _("Initializing NTP Client Configuration");

    integer steps = 2;
    integer sl = 500;

    boolean have_progress = (Mode::normal());

    // We do not set help text here, because it was set outside
    if ( have_progress )
    {
        Progress::New( caption, " ", steps, [
                // progress stage
                _("Read network configuration"),
                // progress stage
                _("Read NTP settings"),
            ], [
                // progress step
                _("Reading network configuration..."),
                // progress step
                _("Reading NTP settings..."),
                // progress step
                _("Finished")
            ],
            ""
        );
    }

    // read network configuration
    if(Abort()) return false;
    if (have_progress) Progress::NextStage();

    boolean progress_orig = Progress::set (false);
    NetworkInterfaces::Read ();
    Progress::set (progress_orig);

    //SCR::Read may return nil (no such value in sysconfig, file not there etc. )
    string policy = (string)SCR::Read (.sysconfig.network.config.NETCONFIG_NTP_POLICY);
    //set if not nil, otherwise use 'auto' as safe fallback (#449362)
    if (policy != nil)
	ntp_policy = policy;

    GetNtpServers ();
    GetCountryNames ();
    // read current settings
    if(Abort()) return false;
    if (have_progress) Progress::NextStage();

    boolean failed = false;

    if (! Mode::testsuite () && ! Mode::installation ()
	&& ! PackageSystem::CheckAndInstallPackagesInteractive (["ntp"])
    )
    {
	return false;
    }

    run_service = Service::Enabled (service_name);

    //Poke to /var/lib/YaST if there is Active Directory controller address dumped in .ycp file
    string ad_ntp_file = Directory::vardir + "/ad_ntp_data.ycp";
    if(FileUtils::Exists(ad_ntp_file)) {
	y2milestone("Reading %1", ad_ntp_file);
	map <string, string> ad_ntp_data = (map <string, string>) SCR::Read(.target.ycp, ad_ntp_file);
	ad_controller = ad_ntp_data["ads"]:"";
	if(ad_controller != "") {
	    y2milestone("Got %1 for ntp sync, deleting %2, since it is no longer needed", ad_controller, ad_ntp_file);
	    SCR::Execute(.target.remove, ad_ntp_file);
	}
    }

    // Stay away if the user may have made changes which we cannot parse.
    // But bnc#456553, no pop-ups for CLI.
    if (! Mode::commandline() && ! FileChanges::CheckFiles (["/etc/ntp.conf"]))
	failed = true;
    ProcessNtpConf();
    list crontab = (list)SCR::Read(.cron, cron_file, "");
    y2milestone("CRONTAB %1", crontab);
    string tmp       = (string) crontab[0,"events",0,"active"]:"0";
    synchronize_time = tmp == "1";
    tmp              = (string) crontab[0,"events",0,"minute"]:"*/5";
    y2milestone("MINUTE %1", tmp);
    list pos  = regexppos(tmp, "[0-9]+");
    string tmp2 = substring(tmp,pos[0]:0,pos[1]:0);
    sync_interval    = tointeger(tmp2);
    y2milestone("SYNC_INTERVAL %1", sync_interval);

    string run_chroot_s = (string) SCR::Read (.sysconfig.ntp.NTPD_RUN_CHROOTED);
    run_chroot = run_chroot_s == "yes";

    if (run_chroot_s == nil)
    {
	failed = true;
	y2error ("Failed reading .sysconfig.ntp.NTPD_RUN_CHROOTED");
    }

    if(failed)
    {
	// error report
	Report::Error (Message::CannotReadCurrentSettings ());
    }

    if (! Mode::testsuite ())
    {
	boolean progress_orig = Progress::set (false);
	SuSEFirewall::Read ();
	Progress::set (progress_orig);
    }

    if(Abort()) return false;
    if (have_progress)
    {
        Progress::NextStage ();
        Progress::Title (_("Finished"));
    }
    sleep(sl);

    if(Abort()) return false;
    modified = false;
    return true;
}

/**
 * Function returns list of NTP servers used in the configuration.
 *
 * @return list <string> of servers
 */
global list <string> GetUsedNtpServers () {
    list <string> used_servers = [];
    foreach (map <string, any> record, ntp_records, {
	if (record["type"]:"" == "server") used_servers = add (used_servers, record["address"]:"");
    });

    return used_servers;
}

/**
 * List of servers defined by the pool.ntp.org to get random ntp servers
 *
 * @see: http://www.pool.ntp.org/
 */
global list <string> random_pool_servers = ["0.pool.ntp.org", "1.pool.ntp.org", "2.pool.ntp.org"];

/**
 * Checks whether all servers listed in the random_pool_servers list
 * are used in the configuration.
 *
 * @return boolean true if enabled
 */
global boolean IsRandomServersServiceEnabled () {
    // all servers needed by pool.ntp.org service, before checking false == not used
    map <string, boolean> needed_servers = $[];
    foreach (string server_name, random_pool_servers, {
	needed_servers[server_name] = false;
    });

    foreach (string used_server, GetUsedNtpServers(), {
	// if server is needed by pool.ntp.org and matches
	if (needed_servers[used_server]:nil != nil) {
	    needed_servers[used_server] = true;
	}
    });

    boolean ret = true;
    foreach (string nserver_name, boolean ns_value, needed_servers, {
	if (ns_value != true) ret = false;
    });
    return ret;
}

/**
 * Removes all servers contained in the random_pool_servers list
 * from the current configuration.
 */
global define void DeActivateRandomPoolServersFunction () {
    foreach (string random_pool_server, random_pool_servers, {
	ntp_records = filter (map <string, any> one_record, ntp_records, {
	    return (
		// do not filter out not-servers
		one_record["type"]:"" != "server"
		// do not filter out serces that are not random_pool_servers
		|| one_record["address"]:"" != random_pool_server
	    );
	});
    });
}

/**
 * Add servers needed for random_pool_servers function
 * into the current configuration.
 */
global define void ActivateRandomPoolServersFunction () {
    // leave the current configuration if any
    map <string, map <string, any> > store_current_options = $[];
    foreach (map <string, any> one_record, ntp_records, {
	if (one_record["type"]:"" == "server" && one_record["address"]:"" != "") {
	    string one_address					= one_record["address"]:"";
	    store_current_options[one_address]                  = $[];
	    store_current_options[one_address, "options"]       = one_record["options"]:"";
	}
    });

    // remove all old ones
    DeActivateRandomPoolServersFunction();

    ntp_records = filter (map <string, any> one_record, ntp_records, {
	return (
	    // filter out all servers
	    one_record["type"]:"" != "server"
	);
    });

    foreach (string one_server, random_pool_servers, {
	string  one_options      = "";
	
	if (haskey(store_current_options, one_server)) {
	    one_options      = store_current_options[one_server, "options"]:"";
	    y2milestone("Leaving current configuration for server '%1', options '%2'",
		one_server, one_options);
	}
	
	ntp_records = add (ntp_records, $[
	    "address":one_server,
	    "comment":"\n# Random pool server, see http://www.pool.ntp.org/ for more information\n",
	    "options":one_options,
	    "type":"server"
	]);
    });
}

/**
 * Write all ntp-client settings
 * @return true on success
 */
global define boolean Write() ``{

    //boolean update_dhcp = original_config_dhcp != config_dhcp;

    /* NtpClient read dialog caption */
    string caption = _("Saving NTP Client Configuration");

    integer steps = 2;

    integer sl = 0;
    sleep(sl);

    boolean have_progress = (Mode::normal());

    // We do not set help text here, because it was set outside
    if ( have_progress )
    {
        Progress::New(caption, " ", steps, [
                // progress stage
                _("Write NTP settings"),
                // progress stage
                _("Restart NTP daemon")
            ], [
                // progress step
                _("Writing the settings..."),
                // progress step
                _("Restarting NTP daemon..."),
                // progress step
                _("Finished")
            ],
            ""
        );
    }

    // write settings
    if(Abort()) return false;
    if ( have_progress ) Progress::NextStage();

    if (true) {
      foreach (string key, map <string, any> m, restrict_map, {
	map <string, any> ret = $[
	   "address" : key,
	   "comment" : m["comment"]:"",
	   "type" : "restrict",
	   "options": ((m["mask"]:"" != "") ? " mask " + m["mask"]:"" : "" ) + " " +
		      m["options"]:"",
        ];
	ntp_records = add(ntp_records, ret);
      });

      y2milestone ("Writing settings %1", ntp_records);

      list<map<string, any> > save2 = (list<map<string, any> >)flatten (maplist(
	map<string, any> r, ntp_records,
      {
	map<string,any> s1 = $[
	    "comment" : r["comment"]:"",
	    "kind" : "value",
	    "name" : r["type"]:"",
	    "type" : 0,
	    "value" : r["address"]:"" + " " + r["options"]:"",
	];
	map<string,any> s2 = nil;
	if (r["type"]:"" == "__clock")
	{
	    s2 = $[
		"comment" : r["fudge_comment"]:"",
		"kind" : "value",
		"name" : "fudge",
		"type" : 0,
		"value" : r["address"]:"" + " " + r["fudge_options"]:"",
	    ];
	    s1["name"] = "server";
	}
	return [ s1, s2 ];
      }));
      save2 = filter (map<string, any> m, save2, ``(m != nil));

      boolean failed = false;
      map conf = (map)SCR::Read (.etc.ntp_conf.all);
      if (conf == nil)
      {
        failed = true;
      }
      else
      {
	conf["value"] = save2;
	if (! SCR::Write (.etc.ntp_conf.all, conf))
	    failed = true;
	if (! SCR::Write (.etc.ntp_conf, nil))
	    failed = true;
      }

      FileChanges::StoreFileCheckSum ("/etc/ntp.conf");

      if (failed)
	  Report::Error (Message::CannotWriteSettingsTo ("/etc/ntp.conf"));
    }
    // write policy and run netconfig command
    SCR::Write(.sysconfig.network.config.NETCONFIG_NTP_POLICY, ntp_policy);
    SCR::Write(.sysconfig.network.config, nil);

    if (SCR::Execute(.target.bash, "/sbin/netconfig update") != 0) {
	// error message
	Report::Error(_("Cannot update the dynamic configuration policy."));
    }

    if (! (SCR::Write (.sysconfig.ntp.NTPD_RUN_CHROOTED, run_chroot
	    ? "yes"
	    : "no")
	   && SCR::Write (.sysconfig.ntp, nil)))
    {
	// error report
	Report::Error (_("Cannot write sysconfig variables."));
    }

    sleep(sl);

    // restart daemon
    if(Abort()) return false;
    if (have_progress) Progress::NextStage ();

    // SuSEFirewall::Write checks on its own whether there are pending
    // changes, so call it always. bnc#476951
    if (true)
    {
	boolean progress_orig = Progress::set (false);
	SuSEFirewall::Write ();
	Progress::set (progress_orig);
    }

    if (! Service::Adjust (service_name,
	run_service ? "enable" : "disable"))
    {
	// error report
	Report::Error (Message::CannotAdjustService ("NTP"));
    }

    if (run_service && (! write_only)
	&& 0 != Service::RunInitScript(service_name, "restart"))
    {
	// error report
	Report::Error (_("Cannot restart the NTP daemon."));
    }
    if (! run_service)
    {
	Service::RunInitScript(service_name, "stop");
    }
    if (synchronize_time)
    {
	SCR::Write (.target.string,cron_file,"-*/"+sync_interval+" * * * * root /etc/init.d/ntp ntptimeset &>/dev/null");
    }
    else
    {
	SCR::Execute (.target.bash,"test -e "+cron_file+"  && rm "+cron_file+";");
    }

    sleep(sl);

    if(Abort()) return false;
    if ( have_progress )
    {
        Progress::NextStage ();
        Progress::Title (_("Finished"));
    }
    sleep(sl);

    if(Abort()) return false;
    return true;
}

/**
 * Get all ntp-client settings from the first parameter
 * (For use by autoinstallation.)
 * @param settings The YCP structure to be imported.
 * @return boolean True on success
 */
global define boolean Import (map settings) ``{
    synchronize_time = settings["synchronize_time"]:false;
    sync_interval = settings["sync_interval"]:5;
    run_service = settings["start_at_boot"]:false;
    run_chroot = settings["start_in_chroot"]:true;
    // compatibility: configure_dhcp:true translates to ntp_policy:auto
    boolean config_dhcp = settings["configure_dhcp"]:false;
    ntp_policy = settings["ntp_policy"]:(config_dhcp? "auto": "");
    ntp_records = settings["peers"]:[];
    ntp_records = maplist(map<string, any> p, ntp_records, ``{
            if (haskey(p, "key") && haskey(p, "value") )
            {
                p["type"] = p["key"]:"";
                p["address"] = p["value"]:"";
		if (haskey (p, "param"))
		    p["options"] = p["param"]:"";
                return (p);
            } else {
                return (p);
            }
            });
    modified = true;
    return true;
}

/**
 * Dump the ntp-client settings to a single map
 * (For use by autoinstallation.)
 * @return map Dumped settings (later acceptable by Import ())
 */
global define map Export () ``{
    return $[
	"synchronize_time" : synchronize_time,
	"sync_interval"    : sync_interval,
	"start_at_boot"    : run_service,
	"start_in_chroot"  : run_chroot,
	"ntp_policy"       : ntp_policy,
	"peers"            : ntp_records,
    ];
}

/**
 * Create a textual summary and a list of unconfigured cards
 * @return string summary of the current configuration
 */
global define string Summary() ``{
    string summary = "";
    if (run_service)
	// summary string
	summary = Summary::AddLine(summary, _("The NTP daemon starts when starting the system.") );
    else
	// summary string
	summary = Summary::AddLine(summary, _("The NTP daemon does not start automatically.") );

    map types = $[
	// summary string, %1 is list of addresses
	"server" : _("Servers: %1"),
	// summary string, %1 is list of addresses
	"__clock" : _("Radio Clocks: %1"),
	// summary string, %1 is list of addresses
	"peer" : _("Peers: %1"),
	// summary string, %1 is list of addresses
	"broadcast" : _("Broadcast time information to: %1"),
	// summary string, %1 is list of addresses
	"broadcastclient" : _("Accept broadcasted time information from: %1"),
    ];
	/*
	  if (config_dhcp)
	  {
	  summary = Summary::AddLine (summary,
	  // summary string
	  _("Configure NTP daemon via DHCP."));
	  return summary;
	  }
	*/
    // netconfig policy
    if (PolicyIsAuto ()) {
	// summary string, FIXME
	summary = Summary::AddLine(summary, _("Combine static and DHCP configuration."));
    } else if (PolicyIsNomodify ()) {
	// summary string, FIXME
	summary = Summary::AddLine(summary, _("Static configuration only."));
    }
    else {
	// summary string, FIXME
	summary = Summary::AddLine(summary, _("Custom configuration policy.")); // FIXME too generic!
    }

    foreach (string t, ["server", "__clock", "peer", "broadcast", "broadcastclient" ],
    ``{
	list<map<string,any> > l
	    = filter (map<string,any> p, ntp_records, ``(p["type"]:"" == t));
	list<string> names
	    = maplist (map<string,any> i, l, ``(i["address"]:""));
	names = filter (string n, names, ``(n != ""));
	if (size (names) > 0)
	{
	    summary = Summary::AddLine (summary, sformat (
		types[t]:"", mergestring ((list<string>)names, ", ")
	    ));
	}
    });
    return summary;
}

/**
 * Test if specified NTP server answers
 * @param server string host name or IP address of the NTP server
 * @param verbosity `no_ui: ..., `transient_popup: pop up while scanning,
 *                  `result_popup: also final pop up about the result
 * @return boolean true if NTP server answers properly
 */
global boolean TestNtpServer (string server, symbol verbosity) {
    if (verbosity != `no_ui)
      UI::OpenDialog(
	// An informative popup label diring the NTP server testings
	`Left(`Label(_("Testing the NTP server...")))
      );

    y2milestone ("Testing reachability of server %1", server);

    // testing the server using IPv4 and then using IPv6 protocol
    // bug #74076, Firewall could have been blocked IPv6
    // -c 1 -d 15: delay 15s, only one try (bnc#442287)
    integer ret_IPv4 = (integer) SCR::Execute (.target.bash, sformat (
	"/usr/sbin/sntp -4 -c 1 -d 15 %1",
	server
    ));
    integer ret_IPv6 = 0;
    if (ret_IPv4 != 0)
	ret_IPv6 = (integer) SCR::Execute (.target.bash, sformat (
	    "/usr/sbin/sntp -6 -c 1 -d 15 %1",
	    server
	));

    if (verbosity != `no_ui) {
	UI::CloseDialog();
    }

    boolean ok = (ret_IPv4 == 0 || ret_IPv6 == 0);
    if (verbosity == `result_popup)
    {
	if (ok)
	{
	    // message report - result of test of connection to NTP server
	    Popup::Notify (_("Server is reachable and responds properly.")); 
	}
	else
	{
	    // error message  - result of test of connection to NTP server
            // report error instead of simple message (#306018)
	    Report::Error (_("Server is unreachable or does not respond properly."));
	}
    }
    return ok;
}

/**
 * Detect NTP servers present in the local network
 * @param method symbol method of the detection (only `slp suported ATM)
 * @return a list of found NTP servers
 */
global list<string> DetectNtpServers (symbol method) {
    if (method == `slp)
    {
	string required_package = "yast2-slp";

	// if package is not installed (in the inst-sys, it is: bnc#399659)
	if ( !Stage::initial() && !PackageSystem::Installed(required_package) )
	{
	    if ( !PackageSystem::CheckAndInstallPackages( [required_package] ) )
	    {
		Report::Error( sformat(_("Cannot search for NTP server in local network
without having package %1 installed."), required_package) );
		y2warning("Not searching for local NTP servers via SLP");
		return [];
	    }
	    else
	    {
		SCR::RegisterAgent(.slp, `ag_slp(`SlpAgent()));
	    }
	}

	list<map> servers = SLPAPI::FindSrvs ("service:ntp", "");
	list<string> server_names = maplist (map m, servers, ``(
	    (string)(m["pcHost"]:"")
	));
	server_names = filter (string s, server_names, ``(s != ""));
	return server_names;
    }
    y2error ("Unknown detection method: %1", method);
    return [];
}

/**
  * Get the list of synchronization-related records
  * @return a list of maps with keys type (eg. "server"), address and index.
  */
global define list<map<string,any> >getSyncRecords () ``{
    integer index = -1;
    list<map<string,any> > ret = maplist (map m, ntp_records, ``{
	index = index + 1;
	string type = m["type"]:"";
	if (! contains (["server", "peer", "broadcast",
	    "broadcastclient", "__clock"], type))
	{
	    return nil;
	}
	return $[
	    "type" : type,
	    "index" : index,
	    "address" : m["address"]:"",
	    "device" : m["device"]:"",
	];
    });
    ret = filter (map<string,any> m, ret, ``(m != nil));
    return ret;
}

/**
  * Select synchronization record
  * @param index integer, -1 for creating a new record
  * @return boolean true on success
  */
global define boolean selectSyncRecord (integer index) ``{
    boolean ret = true;
    if (index >= size (ntp_records) || index < -1)
    {
	y2error ("Record with index %1 doesn't exist, creating new", index);
	index = -1;
	ret = false;
    }
    if (index == -1)
	selected_record = $[];
    else
	selected_record = ntp_records[index]:$[];
    selected_index = index;
    return ret;
}

/**
  * Find index of synchronization record
  * @param type string record type
  * @param address string address
  * @return integer index of the record if found, -1 otherwise
  */
global define integer findSyncRecord (string type, string address) ``{
    integer index = -1;
    integer ret = -1;
    foreach (map<string,any> m, ntp_records, ``{
	index = index + 1;
	if (type == m["type"]:"" && address == m["address"]:"")
	    ret = index;
    });
    return ret;
}

/**
  * Store currently sellected synchronization record
  * @return boolean true on success
  */
global define boolean storeSyncRecord () ``{
    if (selected_index == -1)
	ntp_records = add (ntp_records, selected_record);
    else
	ntp_records[selected_index] = selected_record;
    modified = true;
    return true;
}

/**
  * Delete specified synchronization record
  * @param index integer index of record to delete
  * @return boolean true on success
  */
global define boolean deleteSyncRecord (integer index) ``{
    if (index >= size (ntp_records) || index <= -1)
    {
	y2error ("Record with index %1 doesn't exist", index);
	return false;
    }
    ntp_records[index] = nil;
    ntp_records = filter (map<string,any> r, ntp_records, ``(r != nil));
    modified = true;
    return true;
}

/**
 * Ensure that selected_record["options"] contains the option.
 * (A set operation in a string)
 */
global void enableOptionInSyncRecord (string option) {
    // careful, "burst" != "iburst"
    string old = selected_record["options"]:"";
    list<string> old_l = splitstring (old, " \t");
    if (!contains (old_l, option)) {
	old_l = add (old_l, option);
    }
    selected_record["options"] = mergestring (old_l, " ");
}

/**
 * Return required packages for auto-installation
 * @return map of packages to be installed and to be removed
 */
global define map AutoPackages() ``{
	return ($["install": required_packages, "remove": []]);

}

/* EOF */
}

ACC SHELL 2018