ACC SHELL

Path : /usr/share/YaST2/include/firewall/
File Upload :
Current File : //usr/share/YaST2/include/firewall/uifunctions.ycp

/**
 * File:        firewall/uifunctions.ycp
 * Package:     Configuration YaST2 Firewall
 * Summary:     Configuration dialogs handling functions
 * Authors:     Lukas Ocilka <locilka@suse.cz>
 *
 * $Id: uifunctions.ycp 59814 2009-11-26 18:24:30Z kmachalkova $
 *
 * Configuration dialogs handling.
 * Both Expert and Simple.
 */
{
    textdomain "firewall";

    import "Confirm";
    import "SuSEFirewall";
    import "SuSEFirewallServices";
    import "SuSEFirewallExpertRules";
    import "PortAliases";
    import "Popup";
    import "Address";
    import "Wizard";
    import "Report";
    import "Label";
    import "Mode";
    import "IP";
    import "Netmask";
    import "PortRanges";

    include "firewall/generalfunctions.ycp";
    include "firewall/helps.ycp";
    include "firewall/subdialogs.ycp";

    // GLOBAL UI CONFIGURATION
    term all_popup_definition = `opt(`decorated,`centered);

    // EXAMPLE FUNCTIONS
/*
 *    void ExampleInit(string key) {
 *	y2milestone("Example Init");
 *    }
 *
 *    symbol ExampleHandle(string key, map event) {
 *	any ret = event["ID"]:nil;
 *	y2milestone("Example Handle");
 *	return nil;
 *    }
 *
 *    void ExampleStore(string key, map event) {
 *	any ret = event["ID"]:nil;
 *	y2milestone("Example Store");
 *    }
 *
 */

    /**
     * Checks the network entry which can be defined in several formats.
     *
     * @example
     *	CheckNetwork ("192.168.0.1") -> true
     *	CheckNetwork ("192.168.0.0/20") -> true
     *	CheckNetwork ("192.168.0.0/255.255.255.0") -> true
     *	CheckNetwork ("0/0") -> true
     */
    boolean CheckNetwork (string network) {
	boolean ret = nil;

	if (network == nil || network == "") {
	    ret = false;

	} else if (network == "0/0") {
	    ret = true;

	// 192.168.0.1, 0.8.55.999
	} else if (regexpmatch(network, "^[0123456789\.]+$")) {
	    ret = IP::Check4 (network);

	// 192.168.0.0/20, 0.8.55/158
	} else if (regexpmatch(network, "^[0123456789\.]+/[0123456789]+$")) {
	    string network_ip   = regexpsub (network, "^([0123456789\.]+)/[0123456789]+$", "\\1");
	    string network_mask = regexpsub (network, "^[0123456789\.]+/([0123456789]+)$", "\\1");
	    
	    ret = (IP::Check4 (network_ip) && Netmask::CheckPrefix4 (network_mask));

	// 192.168.0.0/255.255.255.0, 0.8.55/10.258.12
	} else if (regexpmatch(network, "^[0123456789\.]+/[0123456789\.]+$")) {
	    string network_ip   = regexpsub (network, "^([0123456789\.]+)/[0123456789\.]+$", "\\1");
	    string network_mask = regexpsub (network, "^[0123456789\.]+/([0123456789\.]+)$", "\\1");
	    
	    ret = (IP::Check4 (network_ip) && Netmask::Check4 (network_mask));
	}
	
	if (ret != true && ! Mode::testsuite()) {
	    // TRANSLATORS: error message, %1 represents the erroneous network definition
	    Report::Error (sformat (_("Invalid network definition '%1'.
Network can be defined as an IP or IP with slash and netmask.

For instance: 192.168.0.1
or 192.168.0.0/20
or 192.168.0.0/255.255.255.0
or 0/0"), network));
	}

	return ret;
    }

    // UI Functions

    /**
     * Sets the dialog icon.
     */
    void SetFirewallIcon () {
	Wizard::SetTitleIcon("yast-firewall");
    }

    /**
     * Function disables the back button.
     * Fake function for CWM Tree Widget.
     */
    void DisableBackButton (string key) {
	SetFirewallIcon();
	UI::ChangeWidget(`id(`back), `Enabled, false);
    }

    /**
     * Function saves configuration and restarts firewall
     */
    boolean SaveAndRestart () {
	Wizard::CreateDialog ();
	Wizard::RestoreHelp (HelpForDialog("saving_configuration"));
	boolean success = SuSEFirewall::SaveAndRestartService ();
	if (success) SuSEFirewall::SetStartService(true);
	sleep(500);
	UI::CloseDialog ();

	return success;
    }

    /**
     * Function starts Firewall services and sets firewall
     * to be started after exiting YaST
     */
    boolean StartNow() {
	UI::OpenDialog(`Label(_("Starting firewall...")));
	SuSEFirewall::SetStartService(true);
	boolean ret = SuSEFirewall::StartServices();
	UI::CloseDialog ();

	return ret;
    }
    
    /**
     * Function stops Firewall services and sets firewall
     * to be stopped after exiting YaST
     */
    boolean StopNow() {
	UI::OpenDialog(`Label(_("Stopping firewall...")));
	SuSEFirewall::SetStartService(false);
	boolean ret = SuSEFirewall::StopServices();
	UI::CloseDialog ();

	return ret;
    }

    /**
     * Function sets appropriate states for [Change] and [Custom] buttons
     */
    void SetFirewallInterfacesCustomAndChangeButtons(string current_item) {
	// if called from Init() function
	if (current_item == nil)
	    current_item = (string) UI::QueryWidget(
		`id ("table_firewall_interfaces"), `CurrentItem);
    
	// string is one of known network interfaces
	if (regexpmatch(current_item, "^known-.*")) {
	    UI::ChangeWidget(`id("change_firewall_interface"), `Enabled, true);
	    UI::ChangeWidget(`id("user_defined_firewall_interface"), `Enabled, true);
	// string is a custom string
	} else {
	    UI::ChangeWidget(`id("change_firewall_interface"), `Enabled, false);
	    UI::ChangeWidget(`id("user_defined_firewall_interface"), `Enabled, true);
	}
    }

    // maximum length of the string "Interface Name" (min. 3)
    integer max_length_intname = 35;

    /**
     * Function redraws Interfaces Table
     */
    void RedrawFirewallInterfaces() {
	list <term> table_items = [];

	// firstly listing all known interfaces
	foreach (map <string, string> interface, SuSEFirewall::GetAllKnownInterfaces(), {
	    // TRANSLATORS: table item, connected with firewall zone of interface
	    string zone_name = _("No zone assigned");
	    if (interface["zone"]:nil != nil)
		zone_name = SuSEFirewall::GetZoneFullName(interface["zone"]:nil);

	    // shortening the network card name
	    if (interface["name"]:nil != nil && size(interface["name"]:"")>max_length_intname) {
		interface["name"] = substring(interface["name"]:"", 0, (max_length_intname - 3)) + "...";
	    }

	    table_items = add (table_items, `item( `id("known-" + interface["id"]:nil),
		interface["name"]:nil,
		interface["id"]:nil,
		zone_name
	    ));
	});

	foreach (string zone, SuSEFirewall::GetKnownFirewallZones(), {
	    list <string> specials = SuSEFirewall::GetSpecialInterfacesInZone(zone);
	    string zone_name = SuSEFirewall::GetZoneFullName(zone);
	    string custom_string_text = "";
	    foreach (string special, specials, {
		// TRANSLATORS: table item, "User defined string" instead of Device_name
		custom_string_text = _("Custom string");

		table_items = add (table_items, `item( `id("special-" + special),
		    custom_string_text,
		    special,
		    zone_name
		));
	    });
	});

	UI::ChangeWidget ( `id("table_firewall_interfaces"), `Items, table_items);
	
	SetFirewallInterfacesCustomAndChangeButtons(nil);
    }

    // map of device names
    map <string, string> known_device_names = $[];

    /**
     * Function initializes Interfaces table and known_device_names
     */
    void InitFirewallInterfaces (string key) {
	SetFirewallIcon();

	// initializing names of interfaces
	known_device_names = $[];
	foreach (map <string, string> known_interface, SuSEFirewall::GetAllKnownInterfaces(), {
	    // shortening the network card name
	    if (size(known_interface["name"]:"")>max_length_intname) {
		known_interface["name"] = substring(known_interface["name"]:"", 0, (max_length_intname - 3)) + "...";
	    }
	    known_device_names[known_interface["id"]:""] = known_interface["name"]:"";
	});

	// known interfaces/string have ID: "known-" + interface
	// uknown strings have          ID: "special-" + string

	RedrawFirewallInterfaces();
    }

    /**
     * Function handles popup dialog witch setting Interface into Zone
     */
    void HandlePopupSetFirewallInterfaceIntoZone (string interface) {
	// interface could be unassigned
	// TRANSLATORAS: selection box item, connected with firewall zone of interface
	list <term> zones = [ `item(`id(""), _("No Zone Assigned")) ];
	// current zone of interface
	string current_zone = SuSEFirewall::GetZoneOfInterface (interface);
	foreach (string zone_shortname, SuSEFirewall::GetKnownFirewallZones(), {
	    zones = add (zones, `item(
		`id(zone_shortname), SuSEFirewall::GetZoneFullName(zone_shortname),
		(zone_shortname == current_zone ? true:false)
	    ));
	});

	// opening popup
	UI::OpenDialog(all_popup_definition, SetFirewallInterfaceIntoZone(
	    known_device_names[interface]:"",
	    interface,
	    zones
	));

	string ret = (string) UI::UserInput();

	boolean changed = false;
	if (ret == "ok") {
	    string new_zone = (string) UI::QueryWidget(`id("zone_for_interface"), `Value);
	    if (new_zone != current_zone) {
		changed = true;
		SuSEFirewall::AddInterfaceIntoZone(interface, new_zone);
	    }
	}

	UI::CloseDialog();

	if (changed) RedrawFirewallInterfaces();
    }

    /**
     * Function handles popup with additional settings in zones
     */
    void HandlePopupAdditionalSettingsForZones () {
	map <string, list <string> > starting_additionals = $[]; 
	map <string, map <string, string> > zones_additons = $[];

	foreach (string zone_shortname, SuSEFirewall::GetKnownFirewallZones(), {
	    list <string> specials = SuSEFirewall::GetSpecialInterfacesInZone(zone_shortname);
	    starting_additionals[zone_shortname] = specials;
	    zones_additons[zone_shortname] = $[
		"name" : SuSEFirewall::GetZoneFullName(zone_shortname),
		"items" : mergestring(specials, " "),
	    ];
	});

	UI::OpenDialog(all_popup_definition, AdditionalSettingsForZones(zones_additons));

	string ret = (string) UI::UserInput();

	boolean changed = false;
	if (ret == "ok") {
	    list <list <string> > events_remove = [];
	    list <list <string> > events_add    = [];
	    foreach (string zone_shortname, SuSEFirewall::GetKnownFirewallZones(), {
		list <string> new_additions = splitstring((string) UI::QueryWidget(`id("zone_additions_" + zone_shortname), `Value), " ");

		// checking for new additions
		foreach (string new_addition_item, new_additions, {
		    if (new_addition_item != "" && !contains(starting_additionals[zone_shortname]:[], new_addition_item)) {
			changed = true;
			events_add = add (events_add, [ new_addition_item, zone_shortname ]);
		    }
		});

		// checking for removed additions
		foreach (string old_addition_item, starting_additionals[zone_shortname]:[], {
		    if (old_addition_item != "" && !contains(new_additions, old_addition_item)) {
			changed = true;
			events_remove = add (events_remove, [ old_addition_item, zone_shortname ]);
		    }
		});

	    });
	    foreach (list <string> adding, events_add, {
		SuSEFirewall::AddSpecialInterfaceIntoZone(adding[0]:"", adding[1]:"");
	    });
	    foreach (list <string> removing, events_remove, {
		SuSEFirewall::RemoveSpecialInterfaceFromZone(removing[0]:"", removing[1]:"");
	    });
	}

	UI::CloseDialog();

	if (changed) RedrawFirewallInterfaces();
    }

    /**
     * Function handles whole firewall-interfaces dialg
     */
    symbol HandleFirewallInterfaces (string key, map event) {
	any ret = event["ID"]:nil;
	// "Activated" (double-click) or "SelectionChanged" (any other)
	string event_reason = event["EventReason"]:"SelectionChanged";

	string current_item = (string) UI::QueryWidget(
	    `id ("table_firewall_interfaces"), `CurrentItem);

	// double click on the table item
	if (ret == "table_firewall_interfaces" && event_reason == "Activated") {
	    // known iterface means -> as it was pressed [Change] button
	    if (regexpmatch(current_item, "^known-")) {
		ret = "change_firewall_interface";
	    // known iterface means -> as it was pressed [Custom] button
	    } else if (regexpmatch(current_item, "^special-")) {
		ret = "user_defined_firewall_interface";
	    }
	}

	// Double click on the item or some modification button has been pressed
	if (ret == "change_firewall_interface" || ret == "user_defined_firewall_interface") {
	    // "change" can handle both interfaces and special strings
	    if (ret == "change_firewall_interface") {
		// handling interfaces
		if (regexpmatch(current_item, "^known-")) {
		    HandlePopupSetFirewallInterfaceIntoZone(
			regexpsub(current_item, "^known-(.*)", "\\1")
		    );
		// handling special strings
		} else if (regexpmatch(current_item, "^special-")) {
		    HandlePopupAdditionalSettingsForZones();
		} else {
		    y2error("Uknown interfaces_item '%1'", current_item);
		}
	    // "user-defined" can only handle special strings
	    } else if (ret == "user_defined_firewall_interface") {
		HandlePopupAdditionalSettingsForZones();
	    }
	// single click (changed current item)
	} else if (ret == "table_firewall_interfaces" && event_reason == "SelectionChanged") {
	    SetFirewallInterfacesCustomAndChangeButtons(current_item);
	}

	return nil;
    }

    /**
     * Reports that the port definition is wrong. Either a single port
     * or a port range. Returns whether user accepts the wrong port definition
     * despite this warning.
     *
     * @param integer port_nr
     * @param string port_definition might be a single port or a port-range definition
     * @return boolean whether user accepts the port definition despite the warning.
     *
     * @example
     *	// maximum port number is 65535, port range
     *	boolean accepted = ReportWrongPortDefinition(99999, "5:99999");
     *	// dtto., single port
     *	boolean whattodo = ReportWrongPortDefinition(78910, "78910");
     */
    boolean ReportWrongPortDefinition (string port_nr, string port_definition) {
	string port_err = "";

	if (port_nr == port_definition) {
	    // TRANSLATORS: error message, %1 stands for the port number
	    port_err = sformat(_("Port number %1 is invalid."), port_nr);
	} else {
	    // TRANSLATORS: error message, %1 stands for the port number,
	    // %2 stands for, e.g., port range, where the wrong port definition %1 was found
	    port_err = sformat(
		_("Port number %1 in definition %2 is invalid."),
		port_nr, port_definition
	    );
	}

	return Popup::ContinueCancelHeadline (
	    // TRANSLATORS: popup headline
	    _("Invalid Port Definition"),
	    port_err + "\n\n" +
	    // TRANSLATORS: popup message, %1 stands for the maximal port number
	    // that is possible to use in port-range
	    sformat(_("The port number must be in the interval from 1 to %1 (inclusive)."), SuSEFirewall::max_port_number)
	);
    }

    boolean CheckPortNumberDefinition (integer port_nr, string port) {
	if (port_nr < 1 || port_nr > SuSEFirewall::max_port_number) {
	    return ReportWrongPortDefinition(tostring(port_nr), port);
	} else {
	    return true;
	}
    }

    boolean CheckPortNameDefinition (string port_name) {
	if (PortAliases::IsAllowedPortName(port_name)) {
	    return true;
	} else {
	    Report::Error (PortAliases::AllowedPortNameOrNumber());
	    return false;
	}
    }

    /**
     * Function checks list of ports if they exist (are known).
     *
     * @param string ui_id for the setfocus
     * @param list <string> of ports to be checked
     */
    boolean CheckIfTheyAreAllKnownPorts(string ui_id, list <string> ports) {
	boolean checked = true;

	// begin of for~each
	foreach (string port, ports, {
	    // previos port was wrong, break the loop
	    if (!checked) break;

	    // just a waste-space
	    if (port == "") return;

	    // common numeric port
	    if (regexpmatch(port, "^[0123456789]+$")) {
		integer port_nr = tointeger(port);
		if (CheckPortNumberDefinition(port_nr, port) && checked) checked = true;
		else checked = false;
		
		return;
	    }

	    // common port range
	    if (regexpmatch(port, "^[0123456789]+:[0123456789]+$")) {
		string port1 = regexpsub(port, "^([0123456789]+):[0123456789]+$", "\\1");
		string port2 = regexpsub(port, "^[0123456789]+:([0123456789]+)$", "\\1");
		
		integer port1i = tointeger(port1);
		integer port2i = tointeger(port2);

		if (! CheckPortNumberDefinition(port1i, port)) checked = false;

		if (! CheckPortNumberDefinition(port2i, port)) checked = false;
		
		// port range is defined as 'A:B' where A<B
		else if (port1i != nil && port2i != nil && port1i < port2i) return;
		else if (!Popup::ContinueCancelHeadline (
			// TRANSLATORS: popup headline
			_("Invalid Port Range Definition"),
			// TRANSLATORS: popup message, %1 is a port-range defined by user
			sformat(_("Port range %1 is invalid.
It must be defined as the min_port_number:max_port_number and
max_port_number must be bigger than min_port_number."), port)
		) && checked) {
		    checked = false;
		}
		
		return;
	    }

	    // port number
	    if (! PortAliases::IsKnownPortName(port)) {
		if (! Popup::ContinueCancelHeadline(
		    // TRANSLATORS: popup headline
		    _("Unknown Port Name"),
		    sformat(
			// TRANSLATORS: popup message, %1 is a port-name
			_("Port name %1 is unknown in your current system.
It probably would not work.
Really use this port?
"),
			port
		    )
		)) checked = false;
		
		return;
	    }
	// end of for~each
	});

	if (!checked) {
	    UI::SetFocus(`id(ui_id));
	}

	return checked;
    }

    /**
     * Checks the string (services definition) for syntax errors
     *
     * @param string services_definition
     * @return boolean whether everything was ok or whether user wants is despite the error
     */
    boolean CheckAdditionalServicesDefinition (string services_definition) {
	if (regexpmatch(services_definition, ",")) {
	    list <string> ports = splitstring(services_definition, ",");
	    return Popup::YesNoHeadline (
		// TRANSLATORS: popup headline
		_("Invalid Additional Service Definition"),
		// TRANSLATORS: popup message, %1 stands for the wrong settings (might be quite long)
		sformat(_("It appears that the additional service settings
%1
are wrong. Entries should be separated by spaces instead of commas,
which are not allowed.
Really use the current settings?"), services_definition)
	    );
	}
	
	return true;
    }

    boolean HandlePopupAdditionalServices (string zone) {
	string zone_name = SuSEFirewall::GetZoneFullName(zone);

	UI::OpenDialog(all_popup_definition, AdditionalServices(zone_name));

	// getting additional services
	list <string> additional_tcp = toset(SuSEFirewall::GetAdditionalServices("TCP", zone));
	list <string> additional_udp = toset(SuSEFirewall::GetAdditionalServices("UDP", zone));
	list <string> additional_rpc = toset(SuSEFirewall::GetAdditionalServices("RPC", zone));
	list <string> additional_ip  = toset(SuSEFirewall::GetAdditionalServices("IP",  zone));

	// filling up popup dialog
	UI::ChangeWidget(`id("additional_tcp"), `Value, mergestring(additional_tcp, " "));
	UI::ChangeWidget(`id("additional_udp"), `Value, mergestring(additional_udp, " "));
	UI::ChangeWidget(`id("additional_rpc"), `Value, mergestring(additional_rpc, " "));
	UI::ChangeWidget(`id("additional_ip"),  `Value, mergestring(additional_ip,  " "));
	
	// Filling up help
	UI::ChangeWidget(`help_text, `Value, HelpForDialog("additional-services"));

	any ret = nil;
	boolean ret_value = false;
	while (true) {
	    ret = UI::UserInput();

	    if (ret == "ok") {
		string s_additional_tcp = (string) UI::QueryWidget(`id("additional_tcp"), `Value);
		list <string> new_additional_tcp = toset(splitstring(s_additional_tcp, " "));

		string s_additional_udp = (string) UI::QueryWidget(`id("additional_udp"), `Value);
		list <string> new_additional_udp = toset(splitstring(s_additional_udp, " "));

		string s_additional_rpc = (string) UI::QueryWidget(`id("additional_rpc"), `Value);
		list <string> new_additional_rpc = toset(splitstring(s_additional_rpc, " "));

		string s_additional_ip = (string) UI::QueryWidget(`id("additional_ip"),  `Value);
		list <string> new_additional_ip  = toset(splitstring(s_additional_ip, " "));
		
		// Check the format
		if (!CheckAdditionalServicesDefinition(s_additional_tcp)) continue;
		if (!CheckAdditionalServicesDefinition(s_additional_udp)) continue;
		if (!CheckAdditionalServicesDefinition(s_additional_rpc)) continue;
		if (!CheckAdditionalServicesDefinition(s_additional_ip))  continue;

		// checking for known TCP and UDP port names
		if (!CheckIfTheyAreAllKnownPorts("additional_tcp", new_additional_tcp)) continue;
		if (!CheckIfTheyAreAllKnownPorts("additional_udp", new_additional_udp)) continue;

		SuSEFirewall::SetAdditionalServices("TCP", zone, new_additional_tcp);
		SuSEFirewall::SetAdditionalServices("UDP", zone, new_additional_udp);
		SuSEFirewall::SetAdditionalServices("RPC", zone, new_additional_rpc);
		SuSEFirewall::SetAdditionalServices("IP",  zone, new_additional_ip );
		
		ret_value = true;
		break;
	    } else if (ret == "cancel" || ret == `cancel) {
		ret_value = false;
		break;
	    }
	}

	UI::CloseDialog();
	return ret_value;
    }

    void RedrawAllowedServicesDialog (string current_zone) {
	if (SuSEFirewall::GetProtectFromInternalZone() == false && current_zone == "INT") {
	    UI::ChangeWidget(`id("allow_service_names"), `Enabled, false);
	    UI::ChangeWidget(`id("add_allowed_service"), `Enabled, false);
	    UI::ChangeWidget(`id("table_allowed_services"), `Enabled, false);
	    UI::ChangeWidget(`id("remove_allowed_service"), `Enabled, false);
	    UI::ChangeWidget(`id("advanced_allowed_service"), `Enabled, false);
	} else {
	    UI::ChangeWidget(`id("allow_service_names"), `Enabled, true);
	    UI::ChangeWidget(`id("add_allowed_service"), `Enabled, true);
	    UI::ChangeWidget(`id("table_allowed_services"), `Enabled, true);
	    UI::ChangeWidget(`id("remove_allowed_service"), `Enabled, true);
	    UI::ChangeWidget(`id("advanced_allowed_service"), `Enabled, true);
	}
    }

    void RedrawAllowedServices(string current_zone) {
	if (! contains(SuSEFirewall::GetKnownFirewallZones(), current_zone)) {
	    y2error("Unknown zone '%1'", current_zone);
	    return nil;
	}

	// FIXME: protect from internal, disabling table, etc...

	list <term> allowed_services = [];
	// sorted by translated service_name
	map <string, string> translations_to_service_ids = $[];

	foreach (string service_id, string service_name, SuSEFirewallServices::GetSupportedServices(), {
	    // a service with the very same name (translation) already defined
	    if (translations_to_service_ids[service_name]:nil != nil) {
		// service:apache2 -> apache2
		if (SuSEFirewallServices::ServiceDefinedByPackage (service_id)) {
		    service_name = sformat (
			"%1 (%2)",
			service_name, SuSEFirewallServices::GetFilenameFromServiceDefinedByPackage (service_id)
		    );
		} else {
		    service_name = sformat (
			"%1 (%2)",
			service_name, service_id
		    );
		}
	    }
	    translations_to_service_ids[service_name] = service_id;
	});

	list <term> all_known_services = GetDefinedServicesListedItems();
	list <term> not_allowed_services = [];

	// not protected, all services are allowed
	if (current_zone == "INT" && !SuSEFirewall::GetProtectFromInternalZone()) {
	    foreach (string service_name, string service_id, translations_to_service_ids, {
		allowed_services = add (allowed_services, `item(`id(service_id), service_name));
	    });
	// protected, only allowed services
	} else {
	    foreach (string service_name, string service_id, translations_to_service_ids, {
		if (SuSEFirewall::IsServiceSupportedInZone(service_id, current_zone)) {
		    allowed_services = add (allowed_services, `item(`id(service_id), service_name, SuSEFirewallServices::GetDescription(service_id)));
		} else {
		    not_allowed_services = add (not_allowed_services, `item(`id(service_id), service_name));
		}
	    });
	}

	// BNC #461790: A better sorting
	allowed_services = sort (term x, term y, allowed_services,
	    ``(tolower(x[1]:"a") <= tolower(y[1]:"b"))
	);
	not_allowed_services = sort (term x, term y, not_allowed_services,
	    ``(tolower(x[1]:"a") <= tolower(y[1]:"b"))
	);

	UI::ChangeWidget(`id("table_allowed_services"), `Items, allowed_services);
	UI::ReplaceWidget(`id("allow_service_names_replacepoint"),
	    // TRANSLATORS: select box
	    `ComboBox(`id("allow_service_names"), _("&Service to Allow"), not_allowed_services)
	);

	// disable or enable buttons, selectboxes, table
	RedrawAllowedServicesDialog(current_zone);

	if (size (allowed_services) == 0) {
	    UI::ChangeWidget(`id("remove_allowed_service"), `Enabled, false);
	}
    }

    void InitAllowedServices (string key) {
	SetFirewallIcon();

	if (SuSEFirewall::GetProtectFromInternalZone()) {
	    UI::ChangeWidget(`id("protect_from_internal"), `Value, true);
	} else {
	    UI::ChangeWidget(`id("protect_from_internal"), `Value, false);
	}

	// The default zone
	string init_zone = "EXT";

	// All zones
	list <string> all_currently_known_zones = SuSEFirewall::GetKnownFirewallZones();

	// The default zone must exist in configuration
	if (! contains(all_currently_known_zones, init_zone)) {
	    init_zone = all_currently_known_zones[0]:nil;
	}
	// Checking
	if (init_zone == nil) {
	    y2error("There are no zones defined!");
	    return;
	}

	RedrawAllowedServices(init_zone);
	UI::ChangeWidget(`id("allowed_services_zone"), `Value, init_zone);
    }

    symbol HandleAllowedServices (string key, map event) {
	any ret = event["ID"]:nil;

	string current_zone = (string) UI::QueryWidget(`id("allowed_services_zone"), `Value);

	// changing zone
	if (ret == "allowed_services_zone") {
	    RedrawAllowedServices (current_zone);
	} else if (ret == "protect_from_internal") {
	    boolean protect_from_internal = (boolean) UI::QueryWidget(`id("protect_from_internal"), `Value);
	    SuSEFirewall::SetProtectFromInternalZone(protect_from_internal);
	    RedrawAllowedServices (current_zone);
	} else if (ret == "add_allowed_service") {
	    string add_service = (string) UI::QueryWidget(`id("allow_service_names"), `Value);
	    SuSEFirewall::SetServicesForZones([add_service], [current_zone], true);
	    RedrawAllowedServices (current_zone);
	} else if (ret == "remove_allowed_service") {
	    if (Confirm::DeleteSelected()) {
		string remove_service = (string) UI::QueryWidget(`id("table_allowed_services"), `CurrentItem);
		SuSEFirewall::SetServicesForZones([remove_service], [current_zone], false);
		RedrawAllowedServices (current_zone);
	    }
	} else if (ret == "advanced_allowed_service") {
	    // redraw when "OK" button pressed
	    if (HandlePopupAdditionalServices(current_zone)) {
		RedrawAllowedServices (current_zone);
	    }
	}

	return nil;
    }

    /**
     * Function sets UI for Masquerade Table (and buttons) enabled or disabled
     *
     * @param	boolean enable
     */
    void SetMasqueradeTableUsable (boolean usable) {
	UI::ChangeWidget(`id("table_redirect_masq"), `Enabled, usable);
	UI::ChangeWidget(`id("add_redirect_to_masquerade"), `Enabled, usable);
	UI::ChangeWidget(`id("remove_redirect_to_masquerade"), `Enabled, usable);
    }

    /**
     * Function returns if masquerading is possible.
     * Masquerading needs at least two interfaces in two different firewall zones.
     * One of them has to be External.
     *
     * @return	boolean if possible.
     */
    boolean IsMasqueradingPossible () {
	// FIXME: for Expert configuration, there is possible to set the masqueraded zone
	//        it is EXT for Simple configuration as default
	boolean possible = false;

	// if (!IsThisExpertConfiguration()) {
	    // needs to have any external and any other interface
	    boolean has_external = false;
	    boolean has_other    = false;

	    foreach (string zone, SuSEFirewall::GetKnownFirewallZones(), {
		// no interfaces in zone
		if ( size( union(
		    SuSEFirewall::GetInterfacesInZone(zone),
		    SuSEFirewall::GetSpecialInterfacesInZone(zone)
		)) == 0) return;

		if (zone == "EXT") has_external = true;
		else		   has_other    = true;
	    });
	    
	    possible = (has_external && has_other);
	// } else {
	//    y2error("FIXME: missing functionality for expert configuration");
	//}

	return possible;
    }

    void InitMasquerading (string key) {
	SetFirewallIcon();
    
	boolean masquerade = SuSEFirewall::GetMasquerade();
	boolean masquerade_possible = IsMasqueradingPossible();

	// setting checkbox
	UI::ChangeWidget(`id("masquerade_networks"), `Value, masquerade);

	// enabling or disabling masquerade redirect table when masquerading is enabled
	// and also possible
	//if (!IsThisExpertConfiguration()) {
	    SetMasqueradeTableUsable(masquerade && masquerade_possible);
	//}

	// impossible masquerading, user gets information why
	if (!masquerade_possible) {
	    // disabling checkbox
	    UI::ChangeWidget(`id("masquerade_networks"), `Enabled, false);

	    UI::ReplaceWidget(`id("replacepoint_masquerade_information"),
		//(!IsThisExpertConfiguration() ?
		    // TRANSLATORS: informative label
		    `Left(`Label(_("Masquerading needs at least one external interface and one other interface.")))
		    //:
		    //`Left(`Label("FIXME: missing functionality for expert configuration"))
		//)
	    );
	}

	return nil;
    }

    boolean CheckExistency(string ui_id) {
	if (UI::QueryWidget(`id(ui_id), `Value) == "") {
	    UI::SetFocus(`id(ui_id));
	    // TRANSLATORS: popup message
	    Popup::Error(_("This entry must be completed."));
	    return false;
	}
	return true;
    }

    boolean CheckPort(string ui_id) {
	string port  = (string) UI::QueryWidget(`id(ui_id), `Value);
	
	// no port can be allowed
	if (port == "") return true;
	
	// checking for port-name rightness
	if (!PortAliases::IsAllowedPortName(port)) {
	    UI::SetFocus(`id(ui_id));
	    Popup::Error(
		// TRANSLATORS: popup message, right port definition is two lines below this message
		_("Wrong port definition.")
		+ "\n\n" +
		PortAliases::AllowedPortNameOrNumber()
	    );
	    return false;
	}

	// checking for known TCP and UDP port names
	if (!CheckIfTheyAreAllKnownPorts(ui_id, [port])) return false;

	return true;
    }

    /**
     * Function checks port number got as parameter.
     * If check fails SetFocus is called and an empty string is returned.
     */
    string CheckPortNumber (string port_to_be_checked, string widget_id) {
	string port_number = GetPortNumber(port_to_be_checked);
	// if port name wasn't found
	if (port_number=="") {
	    Popup::Error(
		// TRANSLATORS: popup error message
		_("Wrong port definition.
No port number found for this port name.
Use the port number instead of the port name.
")
	    );

	    // setfocus for GUI
	    if (widget_id!="" && widget_id!=nil) UI::SetFocus(`id(widget_id));
	}

	return port_number;
    }

    boolean CheckIP(string ui_id) {
	string ip = (string) UI::QueryWidget(`id(ui_id), `Value);
	if (!Address::Check4(ip)) {
	    UI::SetFocus(`id(ui_id));
	    Popup::Error(
		// TRANSLATORS: popup message, right definition is two lines belos this message
		_("Invalid IP definition.")
		+ "\n\n" +
		Address::Valid4()
	    );
	    return false;
	}
	return true;
    }

    string UserReadablePortName (string port, string protocol) {
	if (port == "") return "";
	if (port == nil) return nil;

	protocol = tolower (protocol);
	// Do not seek port number for RPC services
	if (protocol == "rpc" || protocol == "_rpc_") return port;

	// number
	if (regexpmatch (port, "^[0123456789]+$")) {
	    string port_name = GetPortName (port);
	    // port name must be known and not the same as defined yet
	    if (port_name != nil && port_name != port) {
		port = sformat("%1 (%2)", port_name, port);
	    }
	// not a port range
	} else if (! regexpmatch (port, "^[0123456789]+:[0123456789]+$")) {
	    string port_number = GetPortNumber (port);
	    if (port_number != nil && port_number != port) {
		port = sformat("%1 (%2)", port, port_number);
	    }
	}

	return port;
    }

    void RedrawRedirectToMasqueradedIPTable() {
	list <term> items = [];

	integer row_id = 0;
	foreach (map <string, string> rule, SuSEFirewall::GetListOfForwardsIntoMasquerade(), {
	    // redirect_to_port is the same as requested_port if not defined
	    if (rule["to_port"]:""=="") {
		rule["to_port"] = rule["req_port"]:"";
	    }

	    // printing port names rather then port numbers
	    foreach (string key, [ "req_port", "to_port" ], {
		rule[key] = UserReadablePortName (rule[key]:"", rule["protocol"]:"");
	    });

	    items = add (items,
		`item(`id(row_id),
		    rule["source_net"]:"",
		    rule["protocol"]:"",
		    rule["req_ip"]:"",
		    rule["req_port"]:"",
		    UI::Glyph(`BulletArrowRight),
		    rule["forward_to"]:"",
		    rule["to_port"]:""
		)
	    );
	    row_id = row_id + 1;
	});

	UI::ChangeWidget (`id("table_redirect_masq"), `Items, items);
    }


    void HandlePopupAddRedirectToMasqueradedIPRule () {
	UI::OpenDialog(all_popup_definition, AddRedirectToMasqueradedIPRule());
	UI::SetFocus (`id("add_source_network"));

	boolean ret_value = false;

	while (true) {
	    any ret = UI::UserInput();

	    if (ret == "cancel" || ret == `cancel) {
		break;
	    } else if (ret == "ok") {

		if (!CheckExistency("add_requested_port"))  continue;
		if (!CheckExistency("add_source_network"))  continue;
		if (!CheckExistency("add_redirectto_ip"))   continue;
		
		if (!CheckPort("add_requested_port"))	continue;
		if (!CheckPort("add_redirectto_port"))	continue;
		if (!CheckIP("add_redirectto_ip"))	continue;

		// FIXME: checking for spaces in sttrings
		//        removing space from start or end of the string
		
		string add_source_network  = (string) UI::QueryWidget(`id("add_source_network"),  `Value);
		string add_requested_ip    = (string) UI::QueryWidget(`id("add_requested_ip"),    `Value);
		string add_protocol        = (string) UI::QueryWidget(`id("add_protocol"),        `Value);
		string add_requested_port  = (string) UI::QueryWidget(`id("add_requested_port"),  `Value);
		string add_redirectto_ip   = (string) UI::QueryWidget(`id("add_redirectto_ip"),   `Value);
		string add_redirectto_port = (string) UI::QueryWidget(`id("add_redirectto_port"), `Value);
		
		// Ports must be port numbers, getting port numbers from port names
		if (add_requested_port!="" && add_requested_port!=nil) {
		    add_requested_port  = CheckPortNumber (add_requested_port,  "add_requested_port");
		    if (add_requested_port==nil)  continue;
		}
		if (add_redirectto_port!="" && add_redirectto_port!=nil) {
		    add_redirectto_port = CheckPortNumber (add_redirectto_port, "add_redirectto_port");
		    if (add_redirectto_port==nil) continue;
		}

		SuSEFirewall::AddForwardIntoMasqueradeRule (add_source_network, add_redirectto_ip, add_protocol,
		    add_requested_port, add_redirectto_port, add_requested_ip);

		ret_value = true;
		break;
	    }
	}

	UI::CloseDialog();

	if (ret_value) RedrawRedirectToMasqueradedIPTable();
    }

    symbol HandleMasquerading (string key, map event) {
	any ret = event["ID"]:nil;

	if (ret == "masquerade_networks") {
	    boolean masquerade = (boolean) UI::QueryWidget(`id("masquerade_networks"), `Value);
	    SuSEFirewall::SetMasquerade(masquerade);
	    // enabling or disabling masquerade redirect table when masquerade enabled
	    //if (!IsThisExpertConfiguration()) {
		SetMasqueradeTableUsable(masquerade);
	    //}
	}

	return nil;
    }

    symbol HandleRedirectToMasqueradedIP (string key, map event) {
	any ret = event["ID"]:nil;
	
	if (ret == "add_redirect_to_masquerade") {
	    HandlePopupAddRedirectToMasqueradedIPRule();
	} else if (ret == "remove_redirect_to_masquerade") {
	    integer current_item = (integer) UI::QueryWidget(`id("table_redirect_masq"), `CurrentItem);
	    if (Confirm::DeleteSelected()) {
		SuSEFirewall::RemoveForwardIntoMasqueradeRule(current_item);
		RedrawRedirectToMasqueradedIPTable();
	    }
	}
    }

    void InitRedirectToMasqueradedIP (string key) {
	SetFirewallIcon();
	
	RedrawRedirectToMasqueradedIPTable();
    }

    void HandlePopupIPsecTrustAsZone () {
	UI::OpenDialog(all_popup_definition, IPsecTrustAsZone());
	
	string default_value = SuSEFirewall::GetTrustIPsecAs();
	UI::ChangeWidget(`id("trust_ipsec_as"), `Value, default_value);
	
	any ret = UI::UserInput();

	if (ret == "ok") {
	    string new_value = (string) UI::QueryWidget(`id("trust_ipsec_as"), `Value);
	    SuSEFirewall::SetTrustIPsecAs(new_value);
	}

	UI::CloseDialog();
    }

    // IPsec support opens IPsec traffic from external zone
    void InitIPsecSupport (string key) {
	SetFirewallIcon();
	
	// FIXME: check whether such service exists
	boolean supported = SuSEFirewall::IsServiceSupportedInZone("service:ipsec", "EXT");

	if (supported == nil) {
	    y2error ("No such service 'service:ipsec'");
	    UI::ChangeWidget(`id("ispsec_support"), `Enabled, false);
	} else {
	    UI::ChangeWidget(`id("ispsec_support"), `Enabled, true);
	    UI::ChangeWidget(`id("ispsec_support"), `Value, supported);
	}
    }    

    symbol HandleIPsecSupport (string key, map event) {
	any ret = event["ID"]:nil;

	if (ret == "ipsec_details") {
	    HandlePopupIPsecTrustAsZone();
	}

	return nil;
    }

    void StoreIPsecSupport (string key, map event) {
	boolean to_support = (boolean) UI::QueryWidget(`id("ispsec_support"), `Value);
	SuSEFirewall::SetServicesForZones(["ipsec"], ["EXT"], to_support);
    }

    void InitLoggingLevel (string key) {
	SetFirewallIcon();
	
	UI::ChangeWidget(`id("logging_ACCEPT"), `Value, SuSEFirewall::GetLoggingSettings("ACCEPT"));
	UI::ChangeWidget(`id("logging_DROP"),   `Value, SuSEFirewall::GetLoggingSettings("DROP"));
    }

    void StoreLoggingLevel (string key, map event) {
	SuSEFirewall::SetLoggingSettings("ACCEPT",
	    (string) UI::QueryWidget(`id("logging_ACCEPT"), `Value));
	SuSEFirewall::SetLoggingSettings("DROP",
	    (string) UI::QueryWidget(`id("logging_DROP"),   `Value));
    }

    void InitBroadcastConfigurationSimple (string key) {
	SetFirewallIcon();
	
	term replace_dialog = `VBox();

	map <string, list <string> > allowed_bcast_ports = SuSEFirewall::GetBroadcastAllowedPorts ();

	foreach (string zone, SuSEFirewall::GetKnownFirewallZones(), {
	    string zone_name = SuSEFirewall::GetZoneFullName(zone);
	    string ports_for_zone = mergestring(allowed_bcast_ports[zone]:[], " ");
	    boolean log_packets = (SuSEFirewall::GetIgnoreLoggingBroadcast(zone) == "no");
	    replace_dialog = add(replace_dialog,
		`HBox(
		    `HWeight( 40,
			`InputField (`id("bcast_ports_" + zone), `opt (`hstretch), zone_name, ports_for_zone)
		    ),
		    `HWeight( 60,
			`VBox (
			    `Label(""),
			    // TRANSLATORS: check box
			    `CheckBox(`id("bcast_log_" + zone), _("&Log Not Accepted Broadcast Packets"), log_packets)
			)
		    )
		)
	    );
	});

	UI::ReplaceWidget(`id("replace_point_bcast"), replace_dialog);
    }

    // FIXME: should check for PortAliases::IsKnownPortName() in future
    void StoreBroadcastConfigurationSimple (string key, map event) {
	map <string, list <string> > allowed_bcast_ports = $[];

	foreach (string zone, SuSEFirewall::GetKnownFirewallZones(), {
	    list <string> allowed_ports = splitstring((string) UI::QueryWidget(`id("bcast_ports_" + zone), `Value), " ");
	    boolean log_packets = (boolean) UI::QueryWidget(`id("bcast_log_" + zone), `Value);

	    allowed_bcast_ports[zone] = allowed_ports;
	    SuSEFirewall::SetIgnoreLoggingBroadcast(zone, (log_packets ? "no":"yes"));
	});

	SuSEFirewall::SetBroadcastAllowedPorts(allowed_bcast_ports);
    }

    boolean firewall_enabled_st = nil;
    boolean firewall_started_st = nil;

    void InitServiceStartVsStartedStopped (string key) {
	firewall_enabled_st = SuSEFirewall::GetEnableService();
	firewall_started_st = SuSEFirewall::IsStarted();
    }

    void SetEnableFirewall (boolean new_state) {
	if (firewall_enabled_st == new_state) {
	    y2milestone ("Enable firewall status preserved (enable=%1)", firewall_enabled_st);
	    return;
	}

	boolean curr_running = SuSEFirewall::IsStarted();
	boolean new_running  = new_state;

	// disabling firewall
	if (new_state == false && curr_running == true) {
	    // TRANSLATORS: popup question
	    if (Popup::YesNo (_("Firewall automatic starting has been disabled
but firewall is currently running.

Stop the firewall after the new configuration is written?"))) {
		y2milestone ("User decided to stop the firewall after it is disabled");
		new_running = false;
	    } else {
		y2milestone ("User decided not to stop the firewall after it is disabled");
		new_running = true;
	    }
	}

	// Changes the default values - Enable and Start at once
	SuSEFirewall::SetEnableService (new_state);
	SuSEFirewall::SetStartService  (new_running);

	y2milestone("New Settings - Firewall Enabled: %1, Firewall Started: %2 (after Write())",
	    SuSEFirewall::GetEnableService(), SuSEFirewall::GetStartService()
	);
    }

    string customrules_current_zone = nil;

    void RedrawCustomRules (string current_zone) {
	if (current_zone == nil || ! contains (SuSEFirewall::GetKnownFirewallZones(), current_zone)) {
	    y2error("Unknown zone '%1'", current_zone);
	    return nil;
	}

	list <map <string, string> > rules = SuSEFirewallExpertRules::GetListOfAcceptRules (current_zone);

	// some rules are already defined
	if (size(rules) > 0) {
	    integer counter = -1;
	    list <term> items = maplist (map <string, string> one_rule, rules, {
		counter = counter + 1;
		return `item (
		    `id (counter),
		    one_rule["network"]:"",
		    SuSEFirewall::GetProtocolTranslatedName (one_rule["protocol"]:""),
		    UserReadablePortName (one_rule["dport"]:"", one_rule["protocol"]:""),
		    UserReadablePortName (one_rule["sport"]:"", "")
		);
	    });

	    items = sort (term aa, term bb, items, ``(aa[1]:"" < bb[1]:""));

	    UI::ChangeWidget (`id ("custom_rules_table"), `Items, items);
	    UI::ChangeWidget (`id ("remove_custom_rule"), `Enabled, true);

	// no rules defined
	} else {
	    UI::ChangeWidget (`id ("custom_rules_table"), `Items, []);
	    UI::ChangeWidget (`id ("remove_custom_rule"), `Enabled, false);
	}
    }

    void InitCustomRules (string key) {
	SetFirewallIcon();

	// set the default once, EXT is the first one
	if (customrules_current_zone == nil) {
	    foreach (string one_zone, (list <string>) union (SuSEFirewall::GetKnownFirewallZones(), ["EXT"]), {
		// at least one interface in the zone
		if (size (SuSEFirewall::GetInterfacesInZoneSupportingAnyFeature(one_zone)) > 0)
		    customrules_current_zone = one_zone;
	    });
	    // nothing found, set the default manually
	    if (customrules_current_zone == nil)
		customrules_current_zone = "EXT";
	}

	UI::ChangeWidget (`id ("custom_rules_firewall_zone"), `Value, customrules_current_zone);

	RedrawCustomRules (customrules_current_zone);
    }

    void DeleteSelectedCustomRule (string selected_zone, integer current_item) {
	if (SuSEFirewallExpertRules::DeleteRuleID (selected_zone, current_item)) {
	    RedrawCustomRules (selected_zone);
	    UI::ChangeWidget (`id("custom_rules_table"), `SelectedItem, 0);
	}
    }

    boolean CheckPortNameOrNumber (string port) {
	// port number
	if (regexpmatch(port, "^[0123456789]+$")) {
	    return CheckPortNumberDefinition (tointeger (port), port);
	// not a port range 
	} else if (! regexpmatch(port, "^[0123456789]+:[0123456789]+$")) {
	    return CheckPortNameDefinition (port);
	}
    }

    boolean HandlePopupAddCustomRule (string selected_zone) {
	UI::OpenDialog (all_popup_definition,
	    `HBox (
		`MinWidth (
		    30,
		    `RichText (HelpForDialog ("custom-rules-popup"))
		),
		AddCustomFirewallRule()
	    )
	);
	UI::SetFocus (`id("add_source_network"));

	boolean ret_value = false;

	while (true) {
	    any ret = UI::UserInput();

	    if (ret == "cancel" || ret == `cancel) {
		break;
	    } else if (ret == "ok") {
		if (!CheckExistency("add_source_network"))	continue;
		if (!CheckExistency("add_protocol"))		continue;

		string add_source_network   = (string) UI::QueryWidget(`id("add_source_network"),   `Value);
		string add_protocol	    = (string) UI::QueryWidget(`id("add_protocol"),	    `Value);
		string add_destination_port = (string) UI::QueryWidget(`id("add_destination_port"), `Value);
		string add_source_port      = (string) UI::QueryWidget(`id("add_source_port"),      `Value);

		// network is mandatory
		if (add_source_network == "" || ! CheckNetwork (add_source_network)) {
		    UI::SetFocus (`id ("add_source_network"));
		    Report::Error (sformat (_("Invalid network definition '%1'"), add_source_network) + "\n" + SuSEFirewallExpertRules::ValidNetwork());
		    continue;
		}

		// destination port is optional
		if (add_destination_port != "") {
		    if (PortRanges::IsPortRange (add_destination_port)) {
			if (! PortRanges::IsValidPortRange (add_destination_port)) {
			    UI::SetFocus (`id ("add_destination_port"));
			    Report::Error (sformat (_("Invalid port range '%1'"), add_destination_port));
			    continue;
			}
		    } else if (! CheckPortNameOrNumber (add_destination_port)) {
			UI::SetFocus (`id ("add_destination_port"));
			Report::Error (sformat (_("Invalid port name or number '%1'"), add_destination_port) + "\n" + PortAliases::AllowedPortNameOrNumber());
			continue;
		    }
		}

		// source port is optional
		if (add_source_port != "") {
		    if (PortRanges::IsPortRange (add_source_port)) {
			if (! PortRanges::IsValidPortRange (add_source_port)) {
			    UI::SetFocus (`id ("add_source_port"));
			    Report::Error (sformat (_("Invalid port range '%1'"), add_source_port));
			    continue;
			}
		    } else if (! CheckPortNameOrNumber (add_source_port)) {
			UI::SetFocus (`id ("add_source_port"));
			Report::Error (sformat (_("Invalid port name or number '%1'"), add_source_port) + "\n" + PortAliases::AllowedPortNameOrNumber());
			continue;
		    }
		}

		SuSEFirewallExpertRules::AddNewAcceptRule (
		    selected_zone,
		    $[
			"network"	: add_source_network,
			"protocol"	: add_protocol,
			"dport"	: add_destination_port,
			"sport"	: add_source_port
		    ]
		);
		
		ret_value = true;
		break;
	    }
	}

	UI::CloseDialog();

	return ret_value;
    }

    symbol HandleCustomRules (string key, map event) {
	any ret = event["ID"]:nil;
	
	string selected_zone = (string) UI::QueryWidget (`id ("custom_rules_firewall_zone"), `Value);

	if (ret == "custom_rules_firewall_zone") {
	    customrules_current_zone = selected_zone;
	    RedrawCustomRules (selected_zone);
	} else if (ret == "add_custom_rule") {
	    if (HandlePopupAddCustomRule (selected_zone)) {
		RedrawCustomRules (selected_zone);
	    }
	} else if (ret == "remove_custom_rule") {
	    integer current_item = (integer) UI::QueryWidget(`id("custom_rules_table"), `CurrentItem);

	    if (current_item != nil && Confirm::DeleteSelected()) {
		DeleteSelectedCustomRule (selected_zone, current_item);
	    }
	}

	return nil;
    }

    string GetBcastServiceName (string protocol, string sport) {
	if (protocol == "udp" && sport == "") {
	    return _("All services using UDP");
	} else if (protocol == "tcp" && sport == "") {
	    return _("All services using TCP");
	} else if (protocol == "udp" && PortAliases::GetPortNumber (sport) == 137) {
	    return _("Samba browsing");
	} else if (protocol == "udp" && PortAliases::GetPortNumber (sport) == 427) {
	    return _("SLP browsing");
	} else {
	    return sformat ("%1/%2", SuSEFirewall::GetProtocolTranslatedName (protocol), sport);
	}
    }

    string GetBcastNetworkName (string network) {
	if (network == "0/0") {
	    return _("All networks");
	} else {
	    return sformat (_("Subnet: %1"), network);
	}
    }

    void RedrawBroadcastReplyTable () {
	list <term> items = [];

	foreach (string zone, SuSEFirewall::GetKnownFirewallZones(), {
	    list <string> ruleset = SuSEFirewall::GetServicesAcceptRelated (zone);

	    integer rule_in_ruleset = -1;
	    foreach (string one_rule, ruleset, {
		rule_in_ruleset = rule_in_ruleset + 1;
		list <string> rulelist = splitstring (one_rule, ",");

		items = add (items, `item (
		    `id (sformat ("%1 %2", zone, rule_in_ruleset)),
		    SuSEFirewall::GetZoneFullName (zone),
		    sformat (GetBcastServiceName (rulelist[1]:"", rulelist[2]:"")),
		    GetBcastNetworkName (rulelist[0]:"0/0")
		));
	    });
	});

	if (UI::WidgetExists (`id ("table_broadcastreply")))
	    UI::ChangeWidget (`id ("table_broadcastreply"), `Items, items);

	if (UI::WidgetExists (`id (`delete_br)))
	    UI::ChangeWidget (`id (`delete_br), `Enabled, (size (items) != 0));
    }

    void InitBroadcastReply (string key) {
	RedrawBroadcastReplyTable();
    }

    map <string, string> service_to_protocol = $[
	"samba"		: "udp",
	"slp"		: "udp",
	"all-udp"	: "udp",
	"all-ycp"	: "udp",
    ];

    map <string, string> service_to_port = $[
	"samba"		: "137",
	"slp"		: "427",
	"all-udp"	: "",
	"all-tcp"	: "",
    ];

    string GetBcastServiceProtocol (string service) {
	return service_to_protocol[service]:"";
    }

    string GetBcastServicePort (string service) {
	return service_to_port[service]:"";
    }

    boolean ValidateBroadcastReplyRule (string zone, string network, string service, string protocol, string port) {
	if (service != "user-defined") {
	    return true;
	}

	if (! SuSEFirewallExpertRules::IsValidNetwork (network)) {
	    UI::SetFocus (`id (`network));
	    Report::Error (sformat (_("Invalid network definition '%1'"), network) + "\n" + SuSEFirewallExpertRules::ValidNetwork());
	    return false;
	}

	if (! PortAliases::IsAllowedPortName (port)) {
	    UI::SetFocus (`id (`port));
	    Report::Error (sformat (_("Invalid port name or number '%1'"), port) + "\n" + PortAliases::AllowedPortNameOrNumber());
	    return false;
	}

	return true;
    }

    boolean AddAcceptBroadcastReplyRule () {
	list <term> zones = [];
	foreach (string zone_shortname, SuSEFirewall::GetKnownFirewallZones(), {
	    zones = add (zones, `item(
		`id(zone_shortname), SuSEFirewall::GetZoneFullName(zone_shortname),
		// hard-coded default
		(zone_shortname == "EXT")
	    ));
	});

	UI::OpenDialog (
	    `VBox (
		`Left (`ComboBox (`id (`zone), _("&Zone"), zones)),
		`Left (`MinWidth (18, `ComboBox (`id (`network), `opt (`editable), _("&Network"), ["0/0"]))),
		`Left (`ComboBox (`id (`service), `opt (`notify), _("&Service"), [
		    `item (`id ("samba"), GetBcastServiceName ("udp", "137")),
		    `item (`id ("slp"), GetBcastServiceName ("udp", "427")),
		    `item (`id ("all-udp"), GetBcastServiceName ("udp", "")),
		    `item (`id ("all-tcp"), GetBcastServiceName ("tcp", "")),
		    `item (`id ("user-defined"), _("User-defined service"))
		])),
		`HSquash (`HBox (
		    `HWeight (1, `ComboBox (`id (`protocol), `opt (`disabled), _("&Protocol"), [
			`item (`id ("udp"), SuSEFirewall::GetProtocolTranslatedName ("udp"), true),
			`item (`id ("tcp"), SuSEFirewall::GetProtocolTranslatedName ("tcp"))
		    ])),
		    `HWeight (1, `InputField (`id (`port), `opt (`disabled), _("Po&rt"), ""))
		)),
		`VSpacing (1),
		`ButtonBox (
		    `PushButton (`id (`ok), `opt (`okButton, `default, `key_F10), Label::AddButton()),
		    `PushButton (`id (`cancel), `opt (`cancelButton, `key_F9), Label::CancelButton())
		)
	    )
	);

	boolean dialog_ret = false;
	while (true) {
	    any ret = UI::UserInput();

	    if (ret == `service) {
		boolean custom_service = (UI::QueryWidget (`id (`service), `Value) == "user-defined");
		UI::ChangeWidget (`id (`protocol), `Enabled, custom_service);
		UI::ChangeWidget (`id (`port), `Enabled, custom_service);
	    } else if (ret == `ok) {
		// read the current settings
		string zone = (string) UI::QueryWidget (`id (`zone), `Value);
		string network = (string) UI::QueryWidget (`id (`network), `Value);
		string service = (string) UI::QueryWidget (`id (`service), `Value);

		// use either pre-defined or user-defined
		string protocol = (service == "user-defined" ?
		    (string) UI::QueryWidget (`id (`protocol), `Value)
		    :
		    GetBcastServiceProtocol (service)
		);

		// use either pre-defined or user-defined
		string port = (service == "user-defined" ?
		    (string) UI::QueryWidget (`id (`port), `Value)
		    :
		    GetBcastServicePort (service)
		);

		if (! ValidateBroadcastReplyRule (zone, network, service, protocol, port))
		    continue;

		// Add the rule if validation went fine
		list <string> items = SuSEFirewall::GetServicesAcceptRelated (zone);
		string new_rule = sformat ("%1,%2", network, protocol);
		if (port != "") new_rule = sformat ("%1,%2", new_rule, port);
		items = add (items, new_rule);
		SuSEFirewall::SetServicesAcceptRelated (zone, items);

		// redraw table
		dialog_ret = true;
		break;
	    } else {
		break;
	    }
	}

	UI::CloseDialog();

	return dialog_ret;
    }

    symbol HandleBroadcastReply (string key, map event) {
	any ret = event["ID"]:nil;

	if (ret == `add_br) {
	    if (AddAcceptBroadcastReplyRule())
		RedrawBroadcastReplyTable();
	} else if (ret == `delete_br) {
	    string current_id = (string) UI::QueryWidget (`id ("table_broadcastreply"), `Value);
	    if (current_id != nil && current_id != "") {
		if (Confirm::DeleteSelected()) {
		    list item_to_delete = splitstring (current_id, " ");
		    list <string> items = SuSEFirewall::GetServicesAcceptRelated (item_to_delete[0]:"");
		    integer item_in_list = tointeger (item_to_delete[1]:"-1");
		    items[item_in_list] = nil;
		    items = filter (string one_rule, items, { return one_rule != nil; });
		    SuSEFirewall::SetServicesAcceptRelated (item_to_delete[0]:"", items);
		    RedrawBroadcastReplyTable();
		}
	    } else {
		Report::Error (_("Select an item to delete."));
	    }
	}

	return nil;
    }
}

ACC SHELL 2018