ACC SHELL
/**
* 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