/**
 * 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 22619 2005-03-16 14:54:21Z locilka $
 *
 * Representation of the configuration of ntp-client.
 * Input and output routines.
 */

{

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

import "Directory";
import "Message";
import "Mode";
import "NetworkDevices";
import "PackageSystem";
import "Progress";
import "Report";
import "Service";
import "SLP";
import "Summary";
import "SuSEFirewall";


/**
 * 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 = [];


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

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

/**
 * NTP servers will be configured via DHCP
 */
global boolean config_dhcp = false;

/**
 * NTP servers will be configured via DHCP - value got while reading
 */
global boolean original_config_dhcp = false;

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

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

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

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

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


/**
 * List of known NTP servers
 * server -> information
 */
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;

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

/**
 * 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 ];
	    });
	}
    }
    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;
}

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

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

    integer steps = 2;
    integer sl = 500;

    // We do not set help text here, because it was set outside
    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;
    Progress::NextStage();

    boolean progress_orig = Progress::set (false);
    NetworkDevices::Read ();
    Progress::set (progress_orig);
    config_dhcp = tolower ((string)SCR::Read (.sysconfig.network.dhcp.DHCLIENT_MODIFY_NTP_CONF)) == "yes";
    original_config_dhcp = config_dhcp;

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

    boolean failed = false;

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

    run_service = Service::Enabled ("xntpd");

    map conf = (map)SCR::Read (.etc.ntp_conf.all);
    if (conf == nil)
    {
	failed = true;
	conf = $[];
    }
    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" ],
	    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];
    });
    ntp_records = filter (map<string,any> m, ntp_records, ``(
	m["type"]:"" != "fudge"
    ));

    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;
    });

    run_chroot = SCR::Read (.sysconfig.xntp.XNTPD_RUN_CHROOTED) == "yes";

    string initial_sync = (string)SCR::Read (.sysconfig.xntp.XNTPD_INITIAL_NTPDATE);
    if (initial_sync == nil)
    {
	initial_sync = "";
	failed = true;
	y2error ("Failed reading .sysconfig.xntp.XNTPD_INITIAL_NTPDATE");
    }

    // 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;
    });

    // mark those servers and peers that are used for initial syncing
    if (initial_sync == "AUTO")
    {
	ntp_records = maplist (map<string,any> p, ntp_records, ``{
	    p["initial_sync"] = p["type"]:"" == "server";
	    return p;
	});
    }
    else if (regexpmatch (initial_sync, "AUTO-[0-9]+"))
    {
	integer count = tointeger (
	    regexpsub (initial_sync, "AUTO-([0-9]+)", "\\1"));
	ntp_records = maplist (map<string,any> p, ntp_records, ``{
	    if (p["type"]:"" == "server" || p["type"]:"" == "peer")
	    {
		count = count - 1;
		p["initial_sync"] = count >= 0;
	    }
	    else
		p["initial_sync"] = false;
	    return p;
	});
    }
    else
    {
	list listed = splitstring (initial_sync, " ");
	ntp_records = maplist (map<string,any> p, ntp_records, ``{
	    p["initial_sync"]
		= (p["type"]:"" == "server" || p["type"]:"" == "peer")
		    && contains (listed, p["address"]:"");
	    return p;
	});
    }

    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;
    Progress::NextStage ();
    Progress::Title (_("Finished"));
    sleep(sl);

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

/**
 * 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);

    // We do not set help text here, because it was set outside
    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;
    Progress::NextStage();

    if (update_dhcp)
    {
	SCR::Write (.sysconfig.network.dhcp.DHCLIENT_MODIFY_NTP_CONF,
	    config_dhcp ? "yes" : "no");
	SCR::Write (.sysconfig.network.dhcp, nil);
	// switch DHCP udpates off before sacing the settings
	if (! config_dhcp)
	{
	    Service::RunInitScript ("network", "restart-all-dhcp-clients");
	}
    }

    if (config_dhcp && ! update_dhcp)
    {
	y2milestone ("Configuration was and will be DHCP, not touching /etc/dhcps.conf");
    }
    else
    {
      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;
      }

      if (failed)
	// error report
	Report::Error (Message::CannotWriteSettingsTo ("/etc/ntp.conf"));
    }

    // if configuration via DHCP was turned on, restart DHCP clients now
    if (update_dhcp && config_dhcp)
    {
	Service::RunInitScript ("network", "restart-all-dhcp-clients");
    }

    list init = maplist (map<string,any> p, ntp_records, ``(
	(p["type"]:"" == "server" || p["type"]:"" == "peer")
	    && p["initial_sync"]:false
		? p["address"]:""
		: nil
    ));
    init = filter (any i, init, ``(i != nil));
    string init_str = mergestring ((list<string>)init, " ");

    // Don't remove the AUTO key, something should be present for initialization
    // if there is no NTP server set explicitly
    if (size (init) == 0)
    {
	string orig_init = (string)SCR::Read (.sysconfig.xntp.XNTPD_INITIAL_NTPDATE);
	if (orig_init == "AUTO" || substring (orig_init, 0, 5) == "AUTO-")
	{
	    init_str = orig_init;
	}
    }

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

    sleep(sl);

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

//    if (change_firewall)
//    {
	boolean progress_orig = Progress::set (false);
	SuSEFirewall::Write ();
	Progress::set (progress_orig);
//    }

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

    if (run_service && (! write_only)
	&& 0 != Service::RunInitScript("xntpd", "restart"))
    {
	// error report
	Report::Error (_("Cannot restart the NTP daemon."));
    }
    if (! run_service)
    {
	Service::RunInitScript("xntpd", "stop");
    }

    sleep(sl);

    if(Abort()) return false;
    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) ``{
    run_service = settings["start_at_boot"]:false;
    run_chroot = settings["start_in_chroot"]:true;
    config_dhcp = settings["configure_dhcp"]:false;
    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, "initial"))
		    p["initial_sync"] = p["initial"]:false;
		if (haskey (p, "param"))
		    p["options"] = p["param"]:"";
                return (p);
            } else {
                return (p);
            }
            });
    modified = true;
    original_config_dhcp = nil;
    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 $[
	"start_at_boot" : run_service,
	"start_in_chroot" : run_chroot,
	"configure_dhcp" : config_dhcp,
	"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"),
	// summary string, %1 is list of addresses
	"broadcastclient" : _("Accept broadcasted time information from"),
    ];
    if (config_dhcp)
    {
	summary = Summary::AddLine (summary,
	    // summary string
	    _("Configure NTP daemon via DHCP."));
	return summary;
    }
    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 silent boolean don't report the result of the detection
 * @return boolean true if NTP server answers properly
 */
global boolean TestNtpServer (string server, boolean silent) {
    integer status = Service::Status ("xntpd");
    if (status == 0)
	Service::Stop ("xntpd");
    y2milestone ("Testing reachability of server %1", server);
    integer ret = (integer)SCR::Execute (.target.bash, sformat (
	"/usr/sbin/ntpdate %1",
	server
    ));
    if (status == 0)
	Service::Start ("xntpd");
    boolean ok = ret == 0;
    if (! silent)
    {
	if (ok)
	{
	    // message report - result of test of connection to NTP server
	    Report::Message (_("Server is reachable and responds properly."));
	}
	else
	{
	    // message report - result of test of connection to NTP server
	    Report::Message (_("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)
    {
	list<map> servers = SLP::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;
}

/**
 * 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 */
}
