/*********************************************************************** * * Copyright (C) 2006 Novell, Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2.1 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, Novell, Inc. * * To contact Novell about this file by physical or electronic mail, * you may find current contact information at www.novell.com. * ***********************************************************************/ /** * File: modules/CasaAts.ycp * Package: Configuration of casa-ats * Summary: CasaAts settings, input and output functions * Authors: Juan Carlos Luciani * Ryan Partridge * * $Id: CasaAts.ycp 27914 2006-02-13 14:32:08Z locilka $ * * Representation of the configuration of casa-ats. * Input and output routines. */ { module "CasaAts"; textdomain "casa-ats"; import "Progress"; import "Report"; import "Summary"; import "Message"; import "SuSEFirewall"; import "FileUtils"; import "Service"; /** * Configuration File and Command Paths. * */ string trustedServerConfigFile = "/tmp/trusted_ats.conf"; string svcSettingsFile = "/etc/CASA/authtoken/svc/svc.settings"; string svcSettingsEditor = "/usr/share/java/CASA/authtoken/bin/CasaSvcSettingsEditor.sh"; string authPolicyFile = "/etc/CASA/authtoken/svc/auth.policy"; string authPolicyEditor = "/usr/share/java/CASA/authtoken/bin/CasaAuthPolicyEditor.sh"; string iaRealmsFile = "/etc/CASA/authtoken/svc/iaRealms.xml"; string iaRealmsEditor = "/usr/share/java/CASA/authtoken/bin/CasaIaRealmsEditor.sh"; string trustedServerCertsFolder = "/etc/CASA/authtoken/keys/trustedATSCerts"; string tomcatConnectorEditor = "/usr/share/java/CASA/authtoken/bin/CasaTomcatConnectorEditor.sh"; string webServerIsAvailableChecker = "/usr/share/java/CASA/authtoken/bin/CasaIsWebServerAvailable.sh"; /** * Settings Map */ global map Settings = $[]; string port = "2645"; string service_name = "casa_atsd"; /** * Prototypes */ global boolean Modified(); /** * Data was modified? */ global boolean modified = false; /** */ global boolean proposal_valid = false; /** * Write only, used during autoinstallation. * Don't run services and SuSEconfig, it's all done at one place. */ global boolean write_only = false; /** * Abort function * return boolean return true if abort */ global boolean() AbortFunction = Modified; /** * Abort function * @return boolean return true if abort */ global define boolean Abort() ``{ if(AbortFunction != nil) { return AbortFunction () == true; } return false; } /** * Data was modified? * @return true if modified */ global boolean Modified() { y2debug("modified=%1",modified); return modified; } global boolean IsPortOpen() { return contains(SuSEFirewall::GetAdditionalServices("TCP", "EXT"), port); } boolean ModifyFirewallPort() { boolean retVal = false; list services = SuSEFirewall::GetAdditionalServices("TCP", "EXT"); if (!contains(services, port) && Settings["CONFIG_CASAATS_DIRECT_ACCESS"]:false) { services = add(services, port); retVal = true; } else if (contains(services, port) && !Settings["CONFIG_CASAATS_DIRECT_ACCESS"]:false) { services = filter(string service, services, { return (service != port); } ); retVal = true; } if (retVal) { SuSEFirewall::SetAdditionalServices("TCP", "EXT", services); } return retVal; } /** * Read all casa-ats settings * @return true on success */ global boolean Read() { y2milestone("Read Executing"); string cmd = ""; map ret = $[]; integer exit = -1; /* CasaAts read dialog caption */ string caption = _("Initializing CASA ATS Configuration"); // Read stages integer steps = 2; integer sl = 500; sleep(sl); // We do not set help text here, because it was set outside Progress::New( caption, " ", steps, [ /* Progress stage 1/2 */ _("Read the previous settings"), /* Progress stage 2/2 */ _("Read the firewall status") ], [ /* Progress step 1/2 */ _("Reading the settings file..."), /* Progress step 2/2 */ _("Reading the firewall status..."), /* Progress finished */ _("Finished") ], "" ); // Read settings if (Abort()) return false; Progress::NextStage(); // Set defaults Settings["CONFIG_CASAATS_ENABLE"] = false; Settings["CONFIG_CASAATS_DIRECT_ACCESS"] = true;; Settings["CONFIG_CASAATS_WEB_ACCESS"] = false; Settings["CONFIG_CASAATS_RECONFIG_INTERVAL"] = 60; if (FileUtils::Exists("/etc/sysconfig/casa-ats")) { Settings["CONFIG_CASAATS_ENABLE"] = tolower((string)SCR::Read(.sysconfig.casa-ats.CONFIG_CASAATS_ENABLE)) == "yes"; if ((Settings["CONFIG_CASAATS_ENABLE"]:false) == true) { cmd = svcSettingsEditor + " -get ReconfigureInterval -file " + svcSettingsFile; ret = (map) SCR::Execute(.target.bash_output, cmd); integer exit = ret["exit"]:-1; if (exit == 0) { string cmd_output = ret["stdout"]:""; list output_lines = splitstring(cmd_output, "\n"); list reconfigIntervalLineComponents = splitstring(output_lines[0]:"ReconfigureInterval=60", "="); Settings["CONFIG_CASAATS_RECONFIG_INTERVAL"] = tointeger(reconfigIntervalLineComponents[1]:"60"); } } } if (false) Report::Error(_("Cannot read settings file.")); sleep(sl); // Read the trusted server config y2milestone("Reading trusted server config"); list trustedServerList = []; if (SCR::Read(.target.size, trustedServerConfigFile) > 0) { string trustedServerListString = (string) SCR::Read(.target.string, trustedServerConfigFile); if (trustedServerListString != nil) { trustedServerList = splitstring(trustedServerListString, "\n"); } else { y2error("Failed to read from " + trustedServerConfigFile); } } Settings["CONFIG_CASAATS_TRUSTED"] = trustedServerList; // Check if we need to read the server configuration if ((Settings["CONFIG_CASAATS_ENABLE"]:false) == true) { // Read the auth.policy information cmd = sformat("%1 -list -file %2", authPolicyEditor, authPolicyFile); ret = (map) SCR::Execute(.target.bash_output, cmd); map authPolicy = $[]; exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to read realm info from " + iaRealmsFile); else { string cmd_output = ret["stdout"]:""; list authPolicyLines = splitstring(cmd_output, "\n"); string realmId = ""; string mechanism = ""; foreach(string line, authPolicyLines, { if (tolower(line) == "auth_source:") { realmId = ""; mechanism = ""; } else { list lineComponents = splitstring(line, "\t"); foreach(string component, lineComponents, { if (component != "") { list settingValue = splitstring(component, ":"); if (tolower(settingValue[0]:"") == "identity source") { realmId = settingValue[1]:""; } else if (tolower(settingValue[0]:"") == "authentication mechanism") { mechanism = settingValue[1]:""; } } }); } if (realmId != "" && mechanism != "") { map authMechs = (map) authPolicy[realmId]:$[]; authMechs[mechanism] = true; authPolicy[realmId] = authMechs; } }); } // Get a list of the configured realms in the iaRealms.xml file cmd = iaRealmsEditor + " -list -file " + iaRealmsFile; ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to read configured realms from " + iaRealmsFile); else { string cmd_output = ret["stdout"]:""; list realmIdList = splitstring(cmd_output, "\n"); list realms = []; // Read the information about each realm foreach(string realmId, realmIdList, { // Make sure that it is a valid id if (realmId != "") { // Read the realm info from the iaRealms.xml file cmd = sformat("%1 -get %2 -file %3", iaRealmsEditor, realmId, iaRealmsFile); ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to read realm info from " + iaRealmsFile); else { cmd_output = ret["stdout"]:""; list realmIdComponents = splitstring(cmd_output, "\n"); map realm = $[]; list ldapUrls = []; list searchRoots = []; string stage = ""; realm["REALM_ID"] = realmId; foreach(string component, realmIdComponents, { if (tolower(component) == "dirtype") { stage = "DirType"; } else if (tolower(component) == "searchroots") { stage = "SearchRoots"; } else if (tolower(component) == "ldapurls") { stage = "LdapUrls"; } else { if (stage == "DirType") { if (component == "\teDir") { y2milestone("%1 is eDir", realmId); realm["EDIR_TYPE"] = true; realm["AD_TYPE"] = false; } else { y2milestone("%1 is %2", realmId, component); realm["EDIR_TYPE"] = false; realm["AD_TYPE"] = true; } } else if (stage == "SearchRoots") { list ctxList = splitstring(component, "\t"); foreach(string value, ctxList, { if (value != "") { y2milestone("%1 ctx added", value); searchRoots = add(searchRoots, value); } }); } else if (stage == "LdapUrls") { list urlList = splitstring(component, "\t"); foreach(string value, urlList, { if (value != "") { y2milestone("%1 url added", value); ldapUrls = add(ldapUrls, value); } }); } else { y2error("Error reading realm information"); } } }); realm["LDAP_URL_LIST"] = ldapUrls; realm["SEARCH_ROOT_LIST"] = searchRoots; // Set the authentication mechanism information for the realm map authMechs = (map) authPolicy[realmId]:$[]; if (authMechs != nil) { if (authMechs["PwdAuthenticate"]:false == true) realm["PASSWD_MECH"] = true; else realm["PASSWD_MECH"] = false; if (authMechs["Krb5Authenticate"]:false == true) realm["KRB_MECH"] = true; else realm["KRB_MECH"] = false; } else { y2error("Missing auth.policy info for " + realmId); } realms = add(realms, realm); } } }); Settings["CONFIG_CASAATS_REALMS"] = realms; } // Get the Tomcat SSL connector statuses cmd = tomcatConnectorEditor + " -s ssl"; ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to read ssl connector status"); else { string cmd_output = ret["stdout"]:""; list lines = splitstring(cmd_output, "\n"); string statusLine = lines[2]:""; if (statusLine == "Connector enabled") { y2milestone("SSL connector enabled"); Settings["CONFIG_CASAATS_DIRECT_ACCESS"] = true; } else { y2milestone("SSL connector disabled"); Settings["CONFIG_CASAATS_DIRECT_ACCESS"] = false; } } // Get the Tomcat AJP connector statuses cmd = tomcatConnectorEditor + " -s ajp"; ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to read ajp connector status"); else { string cmd_output = ret["stdout"]:""; list lines = splitstring(cmd_output, "\n"); string statusLine = lines[2]:""; if (statusLine == "Connector enabled") { y2milestone("AJP connector enabled"); Settings["CONFIG_CASAATS_WEB_ACCESS"] = true; } else { y2milestone("AJP connector disabled"); Settings["CONFIG_CASAATS_WEB_ACCESS"] = false; } } // Get the Web Server status integer status = (integer) SCR::Execute(.target.bash, webServerIsAvailableChecker); if (status == 1) { y2milestone("Web server available"); Settings["WEB_SERVER_AVAILABLE"] = true; } else { y2milestone("Web server un-available"); Settings["WEB_SERVER_AVAILABLE"] = false; } } // read firewall settings if (Abort()) return false; Progress::NextStage(); Progress::set(false); SuSEFirewall::Read(); Progress::set(true); /* Error message */ if (false) Report::Error(_("Cannot read firewall status.")); sleep(sl); if (Abort()) return false; /* Progress finished */ Progress::NextStage(); Progress::Finish(); sleep(sl); if (Abort()) return false; modified = false; return true; } /** * Write all casa-ats settings * @return true on success */ global boolean Write() { y2milestone("Write Executing"); /* CasaAts read dialog caption */ string caption = _("Saving casa-ats Configuration"); integer sl = 500; sleep(sl); // Set the stages depending on whether we are configuring the // server or not. integer steps = 0; if ((Settings["CONFIG_CASAATS_ENABLE"]:false) == false) { steps = 3; Progress::New(caption, " ", steps, [ /* Progress stage 1/3 */ _("Write the trusted server settings"), /* Progress stage 2/3 */ _("Write the sysconfig settings"), /* Progress stage 3/3 */ _("Update runlevel settings"), ], [ /* Progress step 1/3 */ _("Writing the trusted server settings..."), /* Progress step 2/3 */ _("Writing the sysconfig settings..."), /* Progress step 3/3 */ _("Updating runlevel settings..."), /* Progress finished */ _("Finished") ], "" ); } else { steps = 4; Progress::New(caption, " ", steps, [ /* Progress stage 1/3 */ _("Write the trusted server settings"), /* Progress stage 2/3 */ _("Write the sysconfig settings"), /* Progress stage 4/4 */ _("Adjust firewall"), /* Progress stage 3/3 */ _("Update runlevel settings") ], [ /* Progress step 1/3 */ _("Writing the trusted server settings..."), /* Progress step 2/3 */ _("Writing the sysconfig settings..."), /* Progress step 4/4 */ _("Adjusting firewall..."), /* Progress step 3/3 */ _("Updating runlevel settings..."), /* Progress finished */ _("Finished") ], "" ); } // Write the trusted server list if (Abort()) return false; Progress::NextStage(); // Create trusted server config file if it does not exists, // otherwise backup. y2milestone("Writing trusted server config"); if (SCR::Read(.target.size, trustedServerConfigFile) < 0) SCR::Write(.target.string, trustedServerConfigFile, ""); else SCR::Execute(.target.bash, "/bin/cp "+trustedServerConfigFile+" "+trustedServerConfigFile+".YaST2save"); // Create a fresh folder to hold the Signing Certs of the trusted ATSs SCR::Execute(.target.bash, "/bin/rm -fr " + trustedServerCertsFolder); SCR::Execute(.target.bash, "/bin/mkdir " + trustedServerCertsFolder); // Update the trusted server config any anyRet = false; list trustedServerList = Settings["CONFIG_CASAATS_TRUSTED"]:[]; string trustedServerListString = ""; if (trustedServerList != []) { // Merge all of the addresses onto the string trustedServerListString = mergestring(trustedServerList, "\n"); // Import the Signing Certs from the trusted ATSs foreach(string trustedATS, trustedServerList, { if (trustedATS != "") { string cmd = sformat("curl -f --capath /etc/ssl/certs -o %1/%2 https://%3:443/CasaAuthTokenSvc/SigningCert", trustedServerCertsFolder, trustedATS, trustedATS); integer exit = (integer) SCR::Execute(.target.bash, cmd); if (exit != 0) { y2error("SigningCert import from " + trustedATS + "using port 443 failed with Curl error" + tostring(exit) + " trying port 2645"); cmd = sformat("curl -f --capath /etc/ssl/certs -o %1/%2 https://%3:2645/CasaAuthTokenSvc/SigningCert", trustedServerCertsFolder, trustedATS, trustedATS); exit = (integer) SCR::Execute(.target.bash, cmd); if (exit != 0) { y2error("SigningCert import from " + trustedATS + "using port 2645 failed with Curl error" + tostring(exit)); } } } }); } anyRet = SCR::Write(.target.string, trustedServerConfigFile, trustedServerListString); if (anyRet != true) y2error("Failed to write to " + trustedServerConfigFile); // Refresh the trusted ATS Keystore SCR::Execute(.target.bash, "/usr/share/java/CASA/authtoken/bin/refresh_trusted_ats_keystore.sh"); // Write the /etc/sysconfig/casa-ats settings if(Abort()) return false; Progress::NextStage(); SCR::Write(.sysconfig.casa-ats.CONFIG_CASAATS_ENABLE, Settings["CONFIG_CASAATS_ENABLE"]:false ? "yes" : "no"); if (false) Report::Error (_("Cannot sysconfig settings.")); sleep(sl); // Try to obtain the uid of casaatsd string uid = ""; map ret = (map) SCR::Execute(.target.bash_output, "id -u casaatsd"); integer exit = ret["exit"]:-1; if (exit == 0) { string cmd_output = ret["stdout"]:""; list uidComponents = splitstring(cmd_output, "\n"); uid = uidComponents[0]:""; y2milestone("casaatsd uid = " + uid); // Clear out the credentials that may have been saved in miCASA // for this user. /* Comment this out until we can read realm proxy credentials using CASAcli. string cmd = sformat("CASAcli -r -u %1", uid); ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to remove casaatsd credentials"); */ } // Check if we need to save the server configuration if ((Settings["CONFIG_CASAATS_ENABLE"]:false) == true) { // Create svc.settings file string cmd = "rm -f " + svcSettingsFile; ret = (map) SCR::Execute(.target.bash_output, cmd); cmd = svcSettingsEditor + " -create -file " + svcSettingsFile; ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to create " + svcSettingsFile); // Write the reconfigure interval value cmd = sformat("%1 -set ReconfigureInterval %2 -file %3", svcSettingsEditor, tostring(Settings["CONFIG_CASAATS_RECONFIG_INTERVAL"]:60), svcSettingsFile); ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to set reconfigure interval"); // Create the auth.policy file cmd = "rm -f " + authPolicyFile; ret = (map) SCR::Execute(.target.bash_output, cmd); cmd = authPolicyEditor + " -create -file " + authPolicyFile; ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to create " + authPolicyFile); // Create the iaRealms.xml file cmd = "rm -f " + iaRealmsFile; ret = (map) SCR::Execute(.target.bash_output, cmd); cmd = iaRealmsEditor + " -create -file " + iaRealmsFile; ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to create " + iaRealmsFile); // Add the real information to auth.policy and iaRealms.xml files list realms = (list) CasaAts::Settings["CONFIG_CASAATS_REALMS"]:[]; foreach (map realm, realms, { // Pull the realm parameters into local variables string realmId = realm["REALM_ID"]:""; boolean eDirType = realm["EDIR_TYPE"]:true; boolean adType = realm["AD_TYPE"]:false; boolean passwd_mech = realm["PASSWD_MECH"]:true; boolean krb_mech = realm["KRB_MECH"]:false; string proxy_username = realm["PROXY_USERNAME"]:""; string proxy_password = realm["PROXY_PASSWD"]:""; list ldapUrls = realm["LDAP_URL_LIST"]:[]; list searchRoots = realm["SEARCH_ROOT_LIST"]:[]; // Update the auth.policy if (krb_mech == true) { cmd = authPolicyEditor + " -append -entry " + realmId + ":Krb5Authenticate -file " + authPolicyFile; ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to add entry to " + authPolicyFile); } if (passwd_mech == true) { cmd = authPolicyEditor + " -append -entry " + realmId + ":PwdAuthenticate -file " + authPolicyFile; ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to add entry to " + authPolicyFile); } // Update the iaRealms.xml file if (eDirType) cmd = sformat("%1 -set \"%2\" -type eDir ", iaRealmsEditor, realmId); else cmd = sformat("%1 -set \"%2\" -type ActiveDirectory ", iaRealmsEditor, realmId); foreach (string url, ldapUrls, { cmd = sformat("%1 -url \"%2\"", cmd, url); }); foreach (string ctx, searchRoots, { cmd = sformat("%1 -sr \"%2\"", cmd, ctx); }); cmd = sformat("%1 -file %2", cmd, iaRealmsFile); ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to set realm " + realmId); // Try to save Proxy User Credentials in miCASA if we have the uid of casaatsd if (uid != "") { // Save the credentials if we have access to them if (proxy_username != "") { // Set the Proxy User Credentials in miCASA cmd = sformat("KEYVALUE=\"%1\" CASAcli -s -u %2 -n %3 -k CN", proxy_username, uid, realmId); ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to set Proxy Username in miCASA for realm " + realmId); cmd = sformat("KEYVALUE=\"%1\" CASAcli -s -u %2 -n \"%3\" -k Password", proxy_password, uid, realmId); ret = (map) SCR::Execute(.target.bash_output, cmd); exit = ret["exit"]:-1; if (exit != 0) y2error("Failed to set Proxy Password in miCASA for realm " + realmId); } } else { y2error("Not setting proxy credentials in miCASA due to blank uid"); } }); // Refresh the server Keystore SCR::Execute(.target.bash, "/usr/share/java/CASA/authtoken/bin/refresh_server_keystore.sh"); // Adjust the Tomcat connectors // // First disable them both and then re-enable as necessary cmd = tomcatConnectorEditor + " -d ssl"; exit = (integer) SCR::Execute(.target.bash, cmd); if (exit != 0) y2error("Failed to disable the SSL connector"); else { if ((Settings["CONFIG_CASAATS_DIRECT_ACCESS"]:false) == true) { cmd = tomcatConnectorEditor + " -e ssl"; exit = (integer) SCR::Execute(.target.bash, cmd); if (exit != 0) y2error("Failed to enable the SSL connector"); } } cmd = tomcatConnectorEditor + " -d ajp"; exit = (integer) SCR::Execute(.target.bash, cmd); if (exit != 0) y2error("Failed to disable the AJP connector"); else { if (Settings["CONFIG_CASAATS_WEB_ACCESS"]:false == true) { cmd = tomcatConnectorEditor + " -e ajp"; exit = (integer) SCR::Execute(.target.bash, cmd); if (exit != 0) y2error("Failed to enable the AJP connector"); } } // Adjust firewall as needed if (Abort()) return false; Progress::NextStage(); if (ModifyFirewallPort()) { // write settings Progress::set(false); SuSEFirewall::WriteOnly(); if (!write_only) { SuSEFirewall::ActivateConfiguration(); } Progress::set(true); } if (false) Report::Error (_("Error adjusting firewall.")); sleep(sl); } // Enable/disable and start/stop service as needed if (Abort()) return false; Progress::NextStage(); if (Settings["CONFIG_CASAATS_ENABLE"]:false) { if (!Service::Enabled(service_name)) { Service::Enable(service_name); } if (Service::Status(service_name) != 0) { Service::Start(service_name); } else { Service::Restart(service_name); } } else { if (Service::Enabled(service_name)) { Service::Disable(service_name); } if (Service::Status(service_name) == 0) { Service::Stop(service_name); } } if (false) Report::Error (_("Error updating runlevels.")); sleep(sl); if (Abort()) return false; /* Progress finished */ Progress::NextStage(); Progress::Finish(); sleep(sl); if (Abort()) return false; return true; } /** * Get all casa-ats settings from the first parameter * (For use by autoinstallation.) * @param settings The YCP structure to be imported. * @return boolean True on success */ global boolean Import (map settings) { // TODO FIXME: your code here (fill the above mentioned variables)... return true; } /** * Dump the casa-ats settings to a single map * (For use by autoinstallation.) * @return map Dumped settings (later acceptable by Import ()) */ global map Export () { // TODO FIXME: your code here (return the above mentioned variables)... return $[]; } /** * Create a textual summary and a list of unconfigured cards * @return summary of the current configuration */ global list Summary() { // TODO FIXME: your code here... /* Configuration summary text for autoyast */ return [ _("Configuration summary..."), [] ]; } /** * Create an overview table with all configured cards * @return table items */ global list Overview() { // TODO FIXME: your code here... return []; } /** * Return packages needed to be installed and removed during * Autoinstallation to insure module has all needed software * installed. * @return map with 2 lists. */ global map AutoPackages() { // TODO FIXME: your code here... return $[ "install":[], "remove":[] ]; } /* EOF */ }