/**
 * File:	modules/Kerberos.ycp
 * Package:	Configuration of kerberos-client
 * Summary:	Data for configuration of kerberos-client, i/o functions.
 * Authors:	Jiri Suchomel <jsuchome@suse.cz>
 *
 * $Id: Kerberos.ycp 22294 2005-03-04 15:10:58Z jsuchome $
 *
 * Representation of the configuration of kerberos-client.
 * Input and output routines.
 */

{

module "Kerberos";

textdomain "kerberos";

import "Arch";
import "Label";
import "Mode";
import "Package";
import "PamSettings";
import "Popup";
import "Progress";
import "Service";
import "Summary";

/**
 * Required packages for this module to operate
 */
global list<string> required_packages = ["pam_krb5", "krb5", "krb5-client"];

global boolean write_only = false;

// if pam_krb5 module is used for login
global boolean use_pam_krb = false;
// if pam_krb5afs module is used for login
global boolean use_afs = false;

// current pam modules called by pam_unix (value of call_modules=... in
// /etc/security/pam_unix2)
list<string> auth_modules = [];

// if pam_unix is in /etc/pam.d/login
boolean pam_unix_present = false;

// default realm and domain name
global string default_realm = "";
global string default_domain = "";

// adress of KDC (key distribution centre) server for default realm
global string kdc = "";
string admin_server = "";

global string clockskew = "300";

global boolean pam_modified = false;
global boolean afs_modified = false;
global boolean ssh_modified = false;
global boolean modified = false;

// afs cell name
global string afs_cell = "";
// afs server (IP adress)
global string afs_serverip = "";
global string afs_server = "";
// regenerate cell info (value in sysconfig/afs-client)
global string regenerate_ci = "yes";

// advanced krb5.conf settings (pam section)
global string ticket_lifetime = "1d";
global string renew_lifetime = "1d";
global string forwardable = "true";
global string proxiable = "false";
global string retain_after_close = "false";
global boolean ssh_support = false;
global string minimum_uid = "0";

// section in /etc/ssh/ssh_config file for storing krb support
string ssh_section = "*";

// packages to install (openssh, openafs, etc.)
global list<string> packages = [];

// packages to install for specific arhitectures (#49808)
global map arch_packages	= $[
    "krb5"	: $[
        "ia64"		: [ "krb5-x86" ],
        "ppc"		: [ "krb5-64bit" ],
        "ppc64"		: [ "krb5-32bit" ],
        "s390_64"	: [ "krb5-32bit" ],
        "x86_64"	: [ "krb5-32bit" ],
    ]
];


/**
 * Data was modified?
 * @return true if modified
 */
global define boolean Modified() ``{
    y2debug("modified=%1",modified);
    return (modified || pam_modified || afs_modified || ssh_modified);
};


/**
 * Get all the Kerberos configuration from a map.
 * @param settings imported map
 * @return	success
 */
global define boolean Import (map settings) ``{
    use_pam_krb = settings["pam_login", "use_kerberos"]:false;

	map client = settings["kerberos_client"]:$[];
    default_domain = client["default_domain"]:"";
    default_realm = client["default_realm"]:"";
    kdc = client["kdc_server"]:"";
    clockskew = client["clockskew"]:clockskew;

    afs_serverip = client["afs_serverip"]:"";
    afs_cell = client["afs_cell"]:"";
    if (size(afs_serverip) > 0)
    {
	use_afs = true;
	afs_modified = true;
    }
    else
	use_afs = false;

    ssh_support = client["ssh_support"]:false;
    ticket_lifetime = client["ticket_lifetime"]:"1d";
    renew_lifetime = client["renew_lifetime"]:"1d";
    boolean tmp_forwardable = client["forwardable"]:true;
    forwardable= (tmp_forwardable) ? "true": "false";
    boolean tmp_proxiable = client["proxiable"]:false;
    proxiable = (tmp_proxiable) ?   "true" :  "false";
    boolean tmp_retain_after_close = client["retain_after_close"]:false;
    retain_after_close = (tmp_retain_after_close) ? "true" : "false";
    minimum_uid = client["minimum_uid"]:"0";

    pam_modified = true;
    modified = true;
    return true;
}


/**
 * Dump the Kerberos settings to a map, for autoinstallation use.
 * @return map with settings
 */
global define map Export () ``{

    return $[
	"pam_login": $[
		"use_kerberos"		: use_pam_krb,
	],
	"kerberos_client": $[
		"default_domain"	: default_domain,
		"default_realm"		: default_realm,
		"kdc_server"		: kdc,
		"clockskew"		: clockskew,
		"afs_serverip"		: afs_serverip,
		"afs_cell"	        : afs_cell,
		"ssh_support"       : ssh_support,
		"ticket_lifetime"   : ticket_lifetime,
                "renew_lifetime"    : renew_lifetime,
		"forwardable"       : ( forwardable == "true" ) ? true : false,
                "proxiable"         : ( proxiable == "true") ? true: false,
                "retain_after_close": ( retain_after_close == "true") ? true: false ,
                "minimum_uid"       : minimum_uid

	]
    ];
}

/**
 * Reads the item value from the /etc/krb5.conf via ini agent
 * If the item doesn't exist, returns empty string (not nil)
 * @param path_to_value path for agent (.etc.krb5_conf)
 * @return string the value
 */
global define string ReadFile (path path_to_value, string def_value) ``{

    string value = (string) SCR::Read (path_to_value);
    if (value == nil)
        value = def_value;

    return value;
}

/**
 * Write the item value to /etc/krb5.conf
 * Do not write anything for empty and nil values
 * @param path_to_value path for agent (.etc.krb5_conf)
 * @param value
 * @return false when nothing was written, success of write otherwise
 */
global define boolean WriteFile (path path_to_value, string value) {

    if (value == nil || value == "")
	return false;

    return SCR::Write (path_to_value, value);
}

/**
 * Check for file existence
 * @param path_to_file the path
 * @return the answer
 */
global define boolean FileExists (string path_to_file) ``{

    return (SCR::Execute (.target.bash,
        sformat ("/usr/bin/test -e %1", path_to_file)) == 0);
}


/**
 * Reads Kerberos settings from the SCR
 * @return success
 */
global define boolean Read () ``{

    // check only "auth"?
    list<string> auth_settings = PamSettings::GetValues ("pam_unix2", "auth");
    boolean comment = false;
    foreach (string val, auth_settings, ``{
	if (substring (val, 0, 1) == "#")
	    comment = true;
	if (comment)
	    return;
	if (contains (auth_settings, "use_krb5"))
	    use_pam_krb = true;
	if (issubstring (val, "call_modules="))
	{
	    list line_split	= splitstring (val, "=");
	    auth_modules	= splitstring (line_split[1]:"",",");
	    if (contains (auth_modules, "krb5afs"))
	    {
		use_pam_krb = true;
		use_afs	= true;
	    }
	}
    });

    // now read the settings from /etc/krb5.conf
    if (FileExists ("/etc/krb5.conf"))
    {
        y2debug("krb5.conf sections: %1", SCR::Dir(.etc.krb5_conf.s));

        default_realm = ReadFile(.etc.krb5_conf.v.libdefaults.default_realm,"");

        clockskew = ReadFile (.etc.krb5_conf.v.libdefaults.clockskew, "300");

        kdc = ReadFile (add ( add (.etc.krb5_conf.v, default_realm), "kdc"),"");

	admin_server = ReadFile (add ( add (.etc.krb5_conf.v, default_realm), "admin_server"),"");
	if (admin_server == kdc)
	    admin_server = ""; // we could replace it in Write in this case...

        default_domain = ReadFile ( add ( add (.etc.krb5_conf.v,
            default_realm), "default_domain"), "");

	ticket_lifetime = ReadFile (.etc.krb5_conf.v.pam.ticket_lifetime, "1d");
	renew_lifetime = ReadFile (.etc.krb5_conf.v.pam.renew_lifetime, "1d");
	forwardable = ReadFile (.etc.krb5_conf.v.pam.forwardable, "true");
	proxiable = ReadFile (.etc.krb5_conf.v.pam.proxiable, "false");
	retain_after_close = ReadFile (.etc.krb5_conf.v.pam.retain_after_close,
	    "false");
	minimum_uid = ReadFile (.etc.krb5_conf.v.pam.minimum_uid, "0");
    }
    else
        SCR::Execute (.target.bash, sformat("/usr/bin/touch /etc/krb5.conf"));

    // propose some good values, if there are install defaults
    if (default_domain == "" && FileExists ("/bin/ypdomainname"))
    {
        map out = (map) SCR::Execute (.target.bash_output, "/bin/ypdomainname");
        default_domain = deletechars (out["stdout"]:"", "\n");
    }
    if (default_domain == "" && FileExists ("/bin/domainname"))
    {
        map out = (map) SCR::Execute (.target.bash_output, "/bin/domainname");
        default_domain = deletechars (out["stdout"]:"", "\n");
    }

    if ((default_realm == "" || default_realm == "MY.REALM" ||
	 default_realm == "EXAMPLE.COM") &&
        default_domain != "")
    {
        default_realm = toupper (default_domain);
    }
    if ((kdc == "" || kdc == "MY.COMPUTER" || kdc == "kerberos.example.com") &&
	FileExists ("/usr/bin/ypwhich"))
    {
        map out = (map) SCR::Execute (.target.bash_output, "/usr/bin/ypwhich");
        kdc = deletechars (out["stdout"]:"", "\n");
    }

    if (SCR::Read (.target.stat, "/etc/sysconfig/afs-client") != $[])
    {
	// read AFS related values
	afs_cell = (string) SCR::Read (.sysconfig.afs-client.THIS_CELL);
	afs_server = (string) SCR::Read (.sysconfig.afs-client.THIS_CELL_SERVER_NAME);
	afs_serverip  = (string) SCR::Read (.sysconfig.afs-client.THIS_CELL_SERVER);
	regenerate_ci = (string) SCR::Read (.sysconfig.afs-client.REGENERATE_CELL_INFO);
    }

    // propose AFS defaults
    if (afs_cell == nil || afs_cell == "")
	afs_cell = tolower (default_realm);
    if (afs_serverip == nil)
	afs_serverip = "";
    if (regenerate_ci == nil || regenerate_ci == "")
	regenerate_ci = "yes";

    // read ssh support
    y2debug ("ssh_config sections: %1", SCR::Dir (.etc.ssh.ssh_config.s));
    string hostname = "*";
    map out = (map) SCR::Execute (.target.bash_output, "LANG=C /bin/hostname");
    if (out["stderr"]:nil == "")
	hostname = deletechars(out["stdout"]:"", "\n");

    ssh_support = nil;
    foreach (string sec, SCR::Dir (.etc.ssh.ssh_config.s), ``{
	// according to ssh man page, first value is taken:
	if (ssh_support != nil)
	    return;

	list cont = SCR::Dir (add(.etc.ssh.ssh_config.v, sec));
	y2debug ("section %1 contains: %2", sec, cont);

	if ((sec == "*" || sec == hostname) &&
	   (contains (cont, "GSSAPIAuthentication") &&
	    contains (cont, "GSSAPIDelegateCredentials")))
	{
	    ssh_support =
		(SCR::Read (add (add (.etc.ssh.ssh_config.v, sec),
		    "GSSAPIAuthentication")) == "yes") &&
		(SCR::Read (add (add (.etc.ssh.ssh_config.v, sec),
		    "GSSAPIDelegateCredentials")) == "yes");
	    ssh_section = sec;
	}
    });
    if (ssh_support == nil)
	ssh_support = false;

    return true;
}


/**
 * Saves Kerberos configuration.
 * (No parameters because it is too short to abort)
 * @return true on success
 */
global define boolean Write () ``{

    boolean pam_installed = false;
    boolean ret = true;

    // dialog caption
    string caption = _("Saving Kerberos Client Configuration");

    integer no_stages = 4;
    list<string> stages =  [
	    // progress stage label
	    _("Write PAM settings"),
	    // progress stage label
	    _("Write Kerberos client settings"),
	    // progress stage label
	    _("Write openssh settings"),
	    // progress stage label
	    _("Write AFS settings")
    ];
    list steps = [
	    // progress step label
	    _("Writing PAM settings..."),
	    // progress step label
	    _("Writing Kerberos client settings..."),
	    // progress step label
	    _("Writing openssh settings..."),
	    // progress step label
	    _("Writing AFS settings..."),
	    // final progress step label
	    _("Finished")
    ];
    if (packages != [])
    {
	// progress stage label
	stages = prepend (stages, _("Install required packages"));
	// progress step label
	steps = prepend (steps, _("Installing required packages..."));
	no_stages = no_stages + 1;
    }

    Progress::New(caption, " ", no_stages, stages, steps, "");

    if (packages != [])
    {
	y2debug ("packages to install: %1", packages);
	Progress::NextStage ();
	list<string> to_install = [];
	// check if packages are avialable...
	foreach (string p, packages, ``{
	    if (Package::Available (p))
		to_install = add (to_install, p);
	});
	Package::DoInstallAndRemove (to_install, []);

	if (use_afs && !Package::InstalledAll (["openafs", "openafs-client"]))
	{
	    // error popup
	    Popup::Error (_("Packages for AFS were not successfully installed.
AFS will not be enabled."));
	    use_afs = false;
	    afs_modified = false;
	}
	packages = [];
    }

    Progress::NextStage ();

    // -- pam settings
    if (pam_modified || write_only)
    {
        if (use_pam_krb)
        {
	    // pam settigs: use only one call of pam module:
	    // either ususal pam_krb (via use_krb5 option) or krb5afs
	    if (!use_afs)
	    {
		auth_modules = filter (string m, auth_modules,``(m != "krb5afs"));
		foreach (string type, [ "account", "auth", "password" ], ``{
		    PamSettings::AddValue ("unix2", type, "use_krb5");
		    PamSettings::RemoveValue ("unix2", type,"call_modules");
		    if (auth_modules != [])
			PamSettings::AddValue ("unix2", type, sformat (
			    "call_modules=%1", mergestring (auth_modules,",")));
		});
	    }
	    else
	    {
		auth_modules = (list<string>) union (auth_modules, ["krb5afs"]);
		foreach (string type, [ "account", "auth", "password" ], ``{
		    PamSettings::RemoveValue ("unix2", type, "use_krb5");
		    PamSettings::RemoveValue ("unix2", type,"call_modules");
		    PamSettings::AddValue ("unix2", type, sformat (
			"call_modules=%1", mergestring (auth_modules,",")));
		});
	    }
        }
        else
        {
	    auth_modules = filter (string m, auth_modules,``(m != "krb5afs"));
	    foreach (string type, [ "account", "auth", "password" ], ``{
		PamSettings::RemoveValue ("unix2", type, "use_krb5");
		PamSettings::RemoveValue ("unix2", type, "call_modules");
		if (auth_modules != [])
		    PamSettings::AddValue ("unix2", type, sformat (
			"call_modules=%1", mergestring (auth_modules,",")));

	    });
        }
	// write the changes now
	PamSettings::Write (false);
    }

    // -- write to /etc/krb5.conf
    Progress::NextStage ();

    if (modified)
    {
	// change the default realm name
	WriteFile (.etc.krb5_conf.v.libdefaults.default_realm, default_realm);

	// write the mapping domain-realm
	if (default_domain != "")
	{
	    string domain = default_domain;
	    if ( findfirstof (domain, ".") != 0)
		domain = "." + domain;
	    WriteFile (add(.etc.krb5_conf.v.domain_realm,domain),default_realm);
	}

	WriteFile (.etc.krb5_conf.v.libdefaults.clockskew, clockskew);

	if (contains (SCR::Dir(.etc.krb5_conf.s), default_realm))
	{
	    // update the default realm settings
	    WriteFile (add (add (.etc.krb5_conf.v, default_realm),"kdc"),kdc);
	    WriteFile (add (add (.etc.krb5_conf.v, default_realm),
		"default_domain"), default_domain);
	    if (admin_server == "")
		// save only when the entry was mising or same as KDC
		WriteFile (add (add (.etc.krb5_conf.v, default_realm),
		    "admin_server"),kdc);
	}
	else if (default_realm != "")
	{
	    // specify the type of this subsection
	    SCR::Write( add (.etc.krb5_conf.st.realms, default_realm), 1);
	    // write the settings of the new default realm
	    WriteFile (
		add (add(.etc.krb5_conf.v.realms,default_realm),"kdc"), kdc);
	    WriteFile (add (add (.etc.krb5_conf.v.realms, default_realm),
		"default_domain"), default_domain);
	    if (admin_server == "")
		WriteFile(add (add (.etc.krb5_conf.v.realms, default_realm),
		    "admin_server"), kdc);
	}

	// write advanced settings
	path pam_sect = .etc.krb5_conf.v.pam;
	if (!contains (SCR::Dir(.etc.krb5_conf.s), "pam"))
	{
	    // specify the type of new subsection
	    SCR::Write(.etc.krb5_conf.st.appdefaults.pam, 1);
	    pam_sect = .etc.krb5_conf.v.appdefaults.pam;
	}

	WriteFile (add (pam_sect, "ticket_lifetime"), ticket_lifetime);
	WriteFile (add (pam_sect, "renew_lifetime"), renew_lifetime);
	WriteFile (add (pam_sect, "forwardable"), forwardable);
	WriteFile (add (pam_sect, "proxiable"), proxiable);
	WriteFile (add (pam_sect, "retain_after_close"), retain_after_close);
	WriteFile (add (pam_sect, "minimum_uid"), minimum_uid);

	if (afs_modified && afs_cell != "")
	    WriteFile (add (pam_sect, "afs_cells"), afs_cell);

	// write the changes now
	SCR::Write (.etc.krb5_conf, nil);

	// unmount agent; otherwise it won't be available to read new created
	// sections (it will treat DEFAULT_REALM as a subsection of [realms])
	SCR::UnmountAgent (.etc.krb5_conf);
    }

    // -- write AFS settings
    Progress::NextStage ();

    if (afs_modified)
    {
	// Adjust runlevels in which service runs
	if (use_afs)
	    Service::Enable ("afs-client");
	else
	    Service::Disable ("afs-client");

	SCR::Write (.sysconfig.afs-client.THIS_CELL, afs_cell);
	SCR::Write (.sysconfig.afs-client.THIS_CELL_SERVER, afs_serverip);
	if (use_afs)
	    regenerate_ci = "yes";
	SCR::Write (.sysconfig.afs-client.REGENERATE_CELL_INFO, regenerate_ci);
	afs_server = afs_cell;
	// get the name via 'getent hosts ip_addr' call
	if (use_afs && SCR::Read (.target.stat, "/usr/bin/getent") != $[])
	{
	    map out = (map) SCR::Execute (.target.bash_output,
		"LANG=C /usr/bin/getent hosts " + afs_serverip);
	    if (out["stderr"]:"" == "")
	    {
		string line = deletechars (out["stdout"]:"", "\n");
		if (line != "")
		{
		    list ll = splitstring (line, " \t");
		    if (ll[1]:"" != "")
			afs_server	= ll[1]:"";
		}
	    }
	    else
		y2error ("'getent hosts' returned error: %1", out["stderr"]:"");
	}
	SCR::Write (.sysconfig.afs-client.THIS_CELL_SERVER_NAME, afs_server);

	// force saving before starting service
	SCR::Write (.sysconfig.afs-client, nil);

	// stop service
	if (!Mode::config ())
	{
	    boolean running = Service::Status ("afs-client") == 0;
	    // start service for first time
	    if (use_afs && !running)
		Service::RunInitScript ("afs-client", "start");
	    else if (running)
	    {
		string command = "restart";
		// popup message: question, if service should be restarted
		// do not translate command ('rcafs-client ...')
		string message = _("The AFS client is currently running, but must be restarted to activate changes.
Restart it now?

Warning: Restarting the AFS client stops all processes using AFS. If your home directory is exported via AFS, entire X sessions could be killed.
You can restart the AFS client later using the 'rcafs-client restart' command.\n");

		if (!use_afs)
		{
		    command = "stop";
		    // popup message: question, if service should be stopped
		    // do not translate command ('rcafs-client ...')
		    message = _("The AFS client is currently running, but you have chosen to stop it.
Stop it now?

Warning: Stopping the AFS client will stop all processes using AFS. If the '/home' directory is exported via AFS, entire X sessions could be killed.
You can stop the AFS client later using the 'rcafs-client stop' command.
");
		}
		// popup header
		if (Popup::AnyQuestion (_("AFS Settings Changed"), message,
		    Label::YesButton(), Label::NoButton(), `focus_no))
		    Service::RunInitScript ("afs-client", command);
	    }
	}
    }

    // -- write openssh settings
    Progress::NextStage ();

    if (ssh_modified)
    {
	string write = ssh_support ? "yes": "no";
	SCR::Write (add (add (.etc.ssh.ssh_config.v, ssh_section),
	    "GSSAPIAuthentication"), write);
	SCR::Write (add (add (.etc.ssh.ssh_config.v, ssh_section),
	    "GSSAPIDelegateCredentials"), write);
	SCR::Write (.etc.ssh.ssh_config, nil);
	y2milestone ("/etc/ssh/ssh_config modified");
    }

    // final stage
    Progress::NextStage ();

    return ret;
}

/**
 * Create a textual summary
 * @return summary of the current configuration
 */
global define list Summary() ``{

    // TODO add AFS!

    string summary = "";
    string nc = Summary::NotConfigured ();
    // summary header
    summary = Summary::AddHeader(summary, _("PAM Login"));

    summary = Summary::AddLine(summary, (use_pam_krb) ? _("Use Kerberos") : _("Do Not Use Kerberos"));

    summary = Summary::AddHeader(summary, _("Default Realm"));
    summary = Summary::AddLine(summary, (default_realm != "") ? default_realm : nc);

    summary = Summary::AddHeader(summary, _("Default Domain"));
    summary = Summary::AddLine(summary, (default_domain != "") ? default_domain : nc);

    summary = Summary::AddHeader(summary, _("KDC Server Address"));
    summary = Summary::AddLine(summary, (kdc != "") ? kdc : nc);

    summary = Summary::AddHeader(summary, _("Clock Skew"));
    summary = Summary::AddLine(summary, (clockskew != "") ? clockskew : nc);

    return [ summary, [] ];
}

/**
 * Create a short textual summary
 * @return summary of the current configuration
 */
global define string ShortSummary() ``{

    string summary = "";
    string nc = Summary::NotConfigured ();
    // summary text
    summary = sformat(_("<b>KDC Server</b>: %1<br>"), (kdc != "") ? kdc : nc) +
    // summary text
    sformat (_("<b>Default Domain</b>: %1<br>"), (default_domain != "") ?
	default_domain : nc) +
    // summary text
    sformat (_("<b>Default Realm</b>: %1<br>"), (default_realm != "") ?
	default_realm : nc) +
    // summary text (yes/no follows)
    sformat(_("<b>Kerberos Authentication Enabled</b>: %1<br>"),
        use_pam_krb ? _("Yes") : _("No"));

    return summary;
}

/**
 * Return list of architecture specific packages (derived from package list
 * given as parameter) merged with the packages in parameter
 */
global define list<string> UpdatedArchPackages (list<string> packages) {

    list<string> ret	= packages;
    string arch		= Arch::architecture ();

    foreach (string package, packages, {
        ret	= (list<string>) union (ret, arch_packages[package,arch]:[]);
    });
    return ret;
}

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



/* EOF */
}
