ACC SHELL

Path : /usr/share/YaST2/clients/
File Upload :
Current File : //usr/share/YaST2/clients/services_proposal.ycp

/**
 * File:	clients/services_proposal.ycp
 * Package:	Services configuration
 * Summary:	Services configuration proposal
 * Authors:	Lukas Ocilka <locilka@suse.cz>
 * See:		FATE #305583: Start CIMOM by default
 *
 * $Id$
 */

{

textdomain "runlevel";

import "RunlevelEd";
import "Progress";
import "ProductControl";
import "ProductFeatures";
import "Service";
import "Linuxrc";
import "Report";
import "Package";
import "SuSEFirewall";

/***
 *  <globals>
 *      <!-- Used in services proposal -->
 *      <services_proposal config:type="list">
 *          <!-- FATE #305583: Start CIMOM by default -->
 *          <service>
 *              <label_id>service_sfcb</label_id>
 *              <!-- space-separated names of services as found in /etc/init.d/ -->
 *              <service_names>sfcb</service_names>
 *              <!-- space-separated SuSEfirewall2 services as found in /etc/sysconfig/SuSEfirewall2.d/services/ -->
 *              <firewall_plugins>sblim-sfcb</firewall_plugins>
 *              <!-- Should be the service proposed as enabled by default? If not defined, false is used. -->
 *              <enabled_by_default config:type="boolean">true</enabled_by_default>
 *              <!-- list of packages to be installed before the services is enabled -->
 *              <packages>sblim-sfcb</packages>
 *          </service>
 *      </services_proposal>
 *  </globals>
 *
 *  <texts>
 *      <service_sfcb><label>CIM Service</label></service_sfcb>
 *  </texts>
 */

// Client operates differently in automatic configuration
boolean automatic_configuration = false;

boolean GetCurrentStatus (list <string> services) {
    if (services == nil || services == []) {
	y2error ("Services not defined");
	return false;
    }

    boolean ret = true;

    foreach (string service, services, {
	if (Service::Status (service) != 0 || Service::Enabled (service) != true) {
	    ret = false;
	    y2milestone ("Service %1 is not running or it is disabled", service);
	    break;
	}
    });

    return ret;
}

void ReadCurrentConfiguration (boolean force) {
  if (RunlevelEd::configuration_already_loaded != true || force == true) {
    y2milestone ("Loading the current runlevel configuration");
    boolean progress_orig = Progress::set (false);

    RunlevelEd::Read();
    RunlevelEd::configuration_already_loaded = true;

    SuSEFirewall::Read();

    list <map> tmp_services_settings = (list <map>) ProductFeatures::GetFeature("globals", "services_proposal");

    if (tmp_services_settings == nil) {
	y2error ("No globals->services_proposal defined");
    } else {
	RunlevelEd::services_proposal_settings = [];
	RunlevelEd::services_proposal_links = [];
	integer counter = -1;

	foreach (map one_service, tmp_services_settings, {
	    // service_names requiered
	    if (! haskey(one_service, "service_names") || one_service["service_names"]:"" == nil) {
		y2error ("Invalid service_names in %1, ignoring this service.", one_service);
		return;
	    }
	    list <string> services = splitstring (one_service["service_names"]:"", " ,");
	    if (services == nil || size(services) < 1) {
		y2error ("Invalid service_names in %1, ignoring this service.", one_service);
		return;
	    }

	    // firewall_plugins are not required but they must be correct anyway
	    if (haskey(one_service, "firewall_plugins") && one_service["firewall_plugins"]:"" == nil) {
		y2error ("Invalid firewall_plugins in %1, ignoring this service.", one_service);
		return;
	    }
	    list <string> firewall_plugins = splitstring (one_service["firewall_plugins"]:"", " ,");

	    // default is: disabled
	    boolean enabled_by_default = false;

	    // if defined, it must be boolean
	    if (haskey(one_service, "enabled_by_default")) {
		if (one_service["enabled_by_default"]:false == true || one_service["enabled_by_default"]:false == false) {
		    enabled_by_default = one_service["enabled_by_default"]:false;
		} else {
		    y2error ("Invalid enabled_by_default in %1, using: %2.", one_service, enabled_by_default);
		}
	    }

	    // fallback for label
	    string label = mergestring (services, ", ");

	    if (haskey(one_service, "label_id") && one_service["label_id"]:"" == nil || one_service["label_id"]:"" == "") {
		y2error ("Invalid label_id in %1, using %2", one_service, label);
	    } else {
		string tmp_label = ProductControl::GetTranslatedText (one_service["label_id"]:"");
		if (tmp_label == nil || tmp_label == "") {
		    label = one_service["label_id"]:"";
		    y2error ("Unable to translate %1", one_service["label_id"]:"");
		} else {
		    label = tmp_label;
		}
	    }

	    // packages are not required but they must be correct anyway
	    if (haskey(one_service, "packages") && one_service["packages"]:"" == nil) {
		y2error ("Invalid packages in %1, ignoring this service.", one_service);
		return;
	    }
	    list <string> packages = splitstring (one_service["packages"]:"", " ,");

	    map <string, any> one_service = $[
		"label" : label,
		"services" : services,
		"firewall_plugins" : firewall_plugins,
		// override the default status according to the current status
		"enabled" : (enabled_by_default == false ? GetCurrentStatus(services):enabled_by_default),
		"enabled_by_default" : enabled_by_default,
		"packages" : packages,
	    ];

	    RunlevelEd::services_proposal_settings = add (RunlevelEd::services_proposal_settings, one_service);

	    counter = counter + 1;
	    RunlevelEd::services_proposal_links = add (RunlevelEd::services_proposal_links, sformat ("toggle_service_%1", counter));
	});

	y2milestone ("Default settings loaded: %1", RunlevelEd::services_proposal_settings);
    }

    Progress::set (progress_orig);
  }
}

string GetProposalSummary () {
    string ret = "";

    boolean firewall_is_enabled = SuSEFirewall::IsEnabled();

    integer counter = -1;
    foreach (map <string, any> one_service, RunlevelEd::services_proposal_settings, {
	counter = counter + 1;
	string message = "";

	// There are some ports (services) required to be open and firewall is enabled
	if (size (one_service["firewall_plugins"]:[]) > 0 && firewall_is_enabled) {
	    if (one_service["enabled"]:false == true) {
		message = sformat (
		    _("Service <i>%1</i> will be <b>enabled</b> and ports in firewall will be open <a href=\"%2\">(disable)</a>"),
		    one_service["label"]:"",
		    sformat("toggle_service_%1", counter)
		);
	    } else {
		message = sformat (
		    _("Service <i>%1</i> will be <b>disabled</b> and ports in firewall will be open <a href=\"%2\">(enable)</a>"),
		    one_service["label"]:"",
		    sformat("toggle_service_%1", counter)
		);
	    }
	} else {
	    if (one_service["enabled"]:false == true) {
		message = sformat (
		    _("Service <i>%1</i> will be <b>enabled</b> <a href=\"%2\">(disable)</a>"),
		    one_service["label"]:"",
		    sformat("toggle_service_%1", counter)
		);
	    } else {
		message = sformat (
		    _("Service <i>%1</i> will be <b>disabled</b> <a href=\"%2\">(enable)</a>"),
		    one_service["label"]:"",
		    sformat("toggle_service_%1", counter)
		);
	    }
	}

	ret = ret + "<li>" + message + "</li>\n";
    });

    return "<ul>" + ret + "</ul>";
}

/**
 * Changes the proposed value.
 * Enables disabled service and vice versa.
 */
boolean ToggleService (string chosen_id) {
    if (! regexpmatch (chosen_id, "^toggle_service_[[:digit:]]+$")) {
	y2error ("Erroneous ID: %1", chosen_id);
	return false;
    }

    chosen_id = regexpsub (chosen_id, "^toggle_service_([[:digit:]])+$", "\\1");
    if (chosen_id == nil) {
	y2error ("Cannot get ID from: %1", chosen_id);
	return false;
    }

    integer ID = tointeger (chosen_id);
    if (RunlevelEd::services_proposal_settings[ID]:$[] == nil || RunlevelEd::services_proposal_settings[ID]:$[] == $[]) {
	y2error ("Cannot find service ID: %1 in %2", ID, RunlevelEd::services_proposal_settings);
	return false;
    }

    if (RunlevelEd::services_proposal_settings[ID,"enabled"]:false == nil) {
	y2error ("Service %1 is neither enabled not disabled", RunlevelEd::services_proposal_settings[ID]:$[]);
	return false;
    }

    RunlevelEd::services_proposal_settings[ID,"enabled"] = ! RunlevelEd::services_proposal_settings[ID,"enabled"]:false;
    y2milestone ("Service ID: %1 is enabled: %2", ID, RunlevelEd::services_proposal_settings[ID,"enabled"]:false);
    return true;
}

/**
 * Some services should not be stopped, in respect to the currently
 * used installation method.
 */
boolean CanCloseService (string service_name) {
    if (Linuxrc::vnc() && service_name == "xinetd") {
	return false;
    }
    if (Linuxrc::usessh() && service_name == "sshd") {
	return false;
    }

    return true;
}

void DisableAndStopServices (list <string> services) {
    foreach (string one_name, services, {
	if (CanCloseService (one_name) != true) {
	    y2warning ("Service %1 must no be closed now", one_name);
	}

	// Service is currently enabled or running
	if (Service::Status (one_name) == 0 || Service::Enabled (one_name)) {
	    y2milestone ("Stopping and disabling service: %1", one_name);
	    Service::RunInitScriptWithTimeOut (one_name, "stop");
	    Service::Disable (one_name);
	}
    });
}

/**
 * Enables and starts services with their dependencies.
 */
boolean EnableAndStartServices (list <string> services) {
    boolean ret = true;

    list <string> all_needed_services = [];

    // Find all the services dependencies first
    foreach (string service, services, {
	list <string> needed_services = RunlevelEd::ServiceDependencies (service, true);

	if (needed_services != nil && needed_services != []) {
	    y2milestone("Service dependencies for %1 are: %2", service, needed_services);
	    all_needed_services = (list <string>) union (all_needed_services, needed_services);
	}
    });

    // Prepend services dependencies ...
    all_needed_services = toset (all_needed_services);
    // ... to list of services to enable
    services = (list <string>) union (all_needed_services, services);

    foreach (string service, services, {
	if (Service::Status (service) == -1) {
	    y2error ("Service name %1 is unknown", service);
	    Report::Error (sformat (_("Unable to start and enable service %1.
Service is not installed."), service));
	    ret = false;
	} else {
	    // Check and enable service
	    if (Service::Enabled (service) != true && Service::Enable (service) != true) {
		y2error ("Unable to enable service %1", service);
		Report::Error (sformat(_("Cannot enable service %1"), service));
		ret = false;
	    }

	    // Check and start service
	    if (Service::RunInitScriptWithTimeOut (service, "status") != 0 && Service::RunInitScriptWithTimeOut (service, "start") != 0) {
		y2error ("Unable to start service %1", service);
		Report::Error (sformat(_("Cannot start service %1"), service));
		ret = false;
	    }
	}
    });

    return ret;
}

/**
 * Opens ports using firewall services got as parameter.
 * Services are actually in format "service_name", not
 * "service:service_name".
 */
boolean OpenPortInFirewall (list <string> firewall_plugins) {
    firewall_plugins = maplist (string one_plugin, firewall_plugins, {
	return sformat ("service:%1", one_plugin);
    });

    // All interfaces known to firewall
    list <string> interfaces = maplist (map one_interface, SuSEFirewall::GetAllKnownInterfaces(), {
	return one_interface["id"]:"";
    });

    if (interfaces == nil) interfaces = [];
    interfaces = filter (string one_interface, interfaces, {
	return (one_interface != nil && one_interface != "");
    });
    y2milestone ("All known fw interfaces: %1", interfaces);

    // Services will be open in these zones
    list <string> used_fw_zones = [];

    // All zones used by all interfaces (if any interface is known)
    if (size (interfaces) > 0) {
	used_fw_zones = SuSEFirewall::GetZonesOfInterfacesWithAnyFeatureSupported (interfaces);
    // Opening for all zones otherwise
    } else {
	used_fw_zones = SuSEFirewall::GetKnownFirewallZones();
    }

    y2milestone ("Firewall zones: %1 for enabling services: %2, ", used_fw_zones, firewall_plugins);
    return SuSEFirewall::SetServicesForZones (firewall_plugins, used_fw_zones, true);
}

/**
 * Checks which packages from those got as a list are not installed.
 * Returns list of not installed packages.
 * Eventually reports unavailable packages one by one.
 */
list <string> NotInstalledPackages (list <string> packages) {
    list <string> not_installed = [];

    foreach (string one_package, packages, {
	if (Package::Installed (one_package) != true) {
	    if (Package::Available (one_package) != true) {
		Report::Error (sformat (_("Required package %1 is not available for installation."), one_package));
	    }
	    not_installed = add (not_installed, one_package);
	}
    });

    y2milestone ("Not installed packages: %1", not_installed);
    return not_installed;
}

boolean WriteSettings () {
    boolean ret = true;

    boolean progress_orig = Progress::set (false);
    boolean firewall_is_enabled = SuSEFirewall::IsEnabled();

    integer counter = -1;
    foreach (map <string, any> one_service, RunlevelEd::services_proposal_settings, {
	counter = counter + 1;

	list <string> services		= one_service["services"]:[];
	list <string> firewall_plugins	= one_service["firewall_plugins"]:[];
	list <string> packages		= one_service["packages"]:[];

	// Service should be disabled
	if (one_service["enabled"]:false != true) {
	    y2milestone ("Service %1 should not be enabled", counter);
	    DisableAndStopServices (services);
	    // next service
	    return;
	}

	// Some packages have to be installed first
	if (size (packages) > 0) {
	    boolean packages_installed = nil;

	    // Returns which packages from list are not installed
	    packages = NotInstalledPackages (packages);

	    // All are installed
	    if (packages == []) {
		packages_installed = true;
	    // Install them
	    } else {
		packages_installed = Package::DoInstall (packages);
	    }

	    if (packages_installed == true) {
		y2milestone ("Required packages for %1 are installed", counter);
	    } else {
		Report::Error (_("Installation of required packages has failed,
enabling and starting the services may also fail."));
	    }
	}

	// Enabling services
	if (EnableAndStartServices (services) != true) {
	    ret = false;
	    // next service
	    return;
	}

	// Opening ports in firewall (only if firewall is enabled)
	if (firewall_is_enabled == true && size(firewall_plugins) > 0) {
	    OpenPortInFirewall (firewall_plugins);
	}
    });

    // Finally, write the firewall settings
    SuSEFirewall::Write();

    Progress::set (progress_orig);

    return ret;
}

string GetHelpText () {
    if (RunlevelEd::services_proposal_settings == nil || RunlevelEd::services_proposal_settings == []) {
	// a help text, not very helpful though
	return _("<p><big><b>Services</b></big><br>
The current setup does not provide any functionality now.</p>");
    }

    // a help text, part 1
    return _("<p><big><b>Services</b></big><br>
This installation proposal allows you to start and enable a service from list
of services.</p>") +
    // a help text, part 2
    _("<p>It may also open ports in firewall for a service if firewall is enabled
and a particular service requires opening them.</p>");
}

/* The main () */
y2milestone("----------------------------------------");
y2milestone("Services proposal started");
y2milestone("Arguments: %1", WFM::Args());

string func = (string) WFM::Args(0);
map param = (map) WFM::Args(1);
map ret = $[];

automatic_configuration = (param["AutomaticConfiguration"]:false == true);

/* create a textual proposal */
if(func == "MakeProposal") {
    boolean progress_orig = Progress::set (false);
    boolean force_reset = param["force_reset"]:false;

    ReadCurrentConfiguration (force_reset);

    ret = $[
	"preformatted_proposal" : GetProposalSummary(),
	"warning_level" : `warning,
	"warning" : nil,
	"links" : RunlevelEd::services_proposal_links,
	"help" : GetHelpText(),
    ];
    y2internal ("RETURNED %1", ret);

    Progress::set (progress_orig);
}
/* run the module */
else if(func == "AskUser") {
    any chosen_id = param["chosen_id"]:nil;
    y2milestone("Services Proposal wanted to change with id %1", chosen_id);

    /*
     * When user clicks on any clickable <a href> in services proposal,
     * one of these actions is called
     */
    if (is (chosen_id, string) && regexpmatch (tostring (chosen_id), "^toggle_service_[[:digit:]]+$")) {
	y2milestone ("Toggling service: %1", chosen_id);
	ToggleService (tostring (chosen_id));
	ret = $[ "workflow_sequence" : `next ];

    // Change the services settings in usual configuration dialogs
    } else {
	y2warning ("ID %1 is not handled", chosen_id);
	ret = $[ "workflow_sequence" : `next ];
    }
}

/* create titles */
else if(func == "Description") {
    ret = $[
	/* RichText label */
	"rich_text_title" : _("Services"),
	/* Menu label */
	"menu_title" : _("&Services"),
	"id" : "services",
    ];
}
/* write the proposal */
else if(func == "Write") {
    WriteSettings();
}
/* unknown function */
else {
    y2error("unknown function: %1", func);
}

/* Finish */
y2debug("ret=%1",ret);
y2milestone("Services proposal finished");
y2milestone("----------------------------------------");
return ret;

/* EOF */
}

ACC SHELL 2018