ACC SHELL

Path : /usr/share/YaST2/modules/
File Upload :
Current File : //usr/share/YaST2/modules/PortRanges.ycp

/**
 * Copyright 2004, Novell, Inc.  All rights reserved.
 *
 * File:	modules/PortRanges.ycp
 * Package:	SuSEFirewall configuration
 * Summary:	Checking and manipulation with port ranges (iptables).
 * Authors:	Lukas Ocilka <locilka@suse.cz>
 *
 * $id$
 *
 * Module for handling port ranges.
 */

{
    module "PortRanges";
    textdomain "base";

    import "PortAliases";

    // Local Helper Functions -->

    /**
     * Variable for ReportOnlyOnce() function
     */
    list <string> report_only_once = [];

    /**
     * Report the error, warning, message only once.
     * Stores the error, warning, message in memory.
     * This is just a helper function that could avoid from filling y2log up with
     * a lot of the very same messages - 'foreach()' is a very powerful builtin.
     *
     * @param string error, warning or message
     * @return boolean whether the message should be reported or not
     *
     * @example
     *	string error = sformat("Port number %1 is invalid.", port_nr);
     *	if (ReportOnlyOnce(error)) y2error(error);
     */
    boolean ReportOnlyOnce (string what_to_report) {
	if (contains(report_only_once, what_to_report)) {
	    return false;
	} else {
	    report_only_once = add (report_only_once, what_to_report);
	    return true;
	}
    }

    // <-- Local Helper Functions

    /**
     * Maximal number of port number, they are in the interval 1-65535 included.
     * The very same value should appear in SuSEFirewall::max_port_number.
     */
    global integer max_port_number = 65535;

    // Port Ranges -->

    /**
     * Function returns where the string parameter is a port range.
     * Port ranges are defined by the syntax "min_port_number:max_port_number".
     * Port range means that these maximum and minimum ports define the range
     * of currency in Firewall. Ports defining the range are included in it.
     * This function doesn't check whether the port range is valid or not.
     *
     * @param string to be checked
     * @return boolean whether the checked string is a port range or not
     *
     * @see IsValidPortRange()
     *
     * @example
     *     IsPortRange("34:38")      -> true
     *     IsPortRange("0:38")       -> true
     *     IsPortRange("port-range") -> false
     *     IsPortRange("19-22")      -> false
     */
    global boolean IsPortRange (string check_this) {
	if (regexpmatch(check_this,"^[0123456789]+:[0123456789]+$")) {
	    return true;
	}
	return false;
    }

    /**
     * Checks whether the port range is valid.
     *
     * @param string port_range
     * @return boolean if it is valid
     *
     * @see IsPortRange()
     *
     * @example
     *     IsValidPortRange("54:135") -> true  // valid
     *     IsValidPortRange("135:54") -> false // reverse order
     *     IsValidPortRange("0:135")  -> false // cannot be from 0
     *     IsValidPortRange("135")    -> false // cannot be one number
     *     IsValidPortRange("54-135") -> false // wrong separator
     */
    global boolean IsValidPortRange (string port_range) {
	// not a port range
	if (! IsPortRange(port_range)) {
	    string warning = sformat ("Not a port-range %1", port_range);
	    if (ReportOnlyOnce(warning)) y2milestone(warning);

	    return false;
	}

	integer min_pr = tointeger(regexpsub(port_range,"^([0123456789]+):.*$", "\\1"));
	integer max_pr = tointeger(regexpsub(port_range,"^.*:([0123456789]+)$", "\\1"));
	
	// couldn't extract two integers
	if (min_pr == nil && max_pr == nil) {
	    string warning = sformat ("Wrong port-range: '%1':'%2'", min_pr, max_pr);
	    if (ReportOnlyOnce(warning)) y2warning(warning);

	    return false;
	}
	
	// Checking the minimal port number in the port-range
	// wrong range
	if (min_pr < 1 || min_pr > max_port_number) {
	    string warning = sformat ("Wrong port-range definition %1", port_range);
	    if (ReportOnlyOnce(warning)) y2warning(warning);

	    return false;
	}
	
	// Checking the maximal port number in the port-range
	// wrong range
	if (max_pr < 1 || max_pr > max_port_number) {
	    string warning = sformat ("Wrong port-range definition %1", port_range);
	    if (ReportOnlyOnce(warning)) y2warning(warning);

	    return false;
	}
	
	// wrong range
	if (min_pr >= max_pr) {
	    string warning = sformat ("Wrong port-range definition %1", port_range);
	    if (ReportOnlyOnce(warning)) y2warning(warning);

	    return false;
	}

	return true;
    }

    /**
     * Function returns where the port name or port number is included in the
     * list of port ranges. Port ranges must be defined as a string with format
     * "min_port_number:max_port_number".
     * 
     * @param string port_number_or_port_name
     * @param list <string> port_ranges
     *
     * @example
     *     PortIsInPortranges ("130",  ["100:150","10:30"]) -> true
     *     PortIsInPortranges ("30",   ["100:150","10:20"]) -> false
     *     PortIsInPortranges ("pop3", ["100:150","10:30"]) -> true
     *     PortIsInPortranges ("http", ["100:150","10:20"]) -> false
     */
    global boolean PortIsInPortranges (string port, list <string> port_ranges) {
	if (size(port_ranges) == 0) return false;

	boolean ret = false;

	integer port_number = PortAliases::GetPortNumber(port);

	if (port_number != nil) {
	    foreach (string port_range, port_ranges, {
		// is portrange really a port range?
		if (IsValidPortRange(port_range)) {
		    integer min_pr = tointeger(regexpsub(port_range,"^([0123456789]+):.*$", "\\1"));
		    integer max_pr = tointeger(regexpsub(port_range,"^.*:([0123456789]+)$", "\\1"));

		    // is the port inside?
		    if (min_pr <= max_pr && min_pr <= port_number && port_number <= max_pr) {
			ret = true;

			break;  // break the loop, match found
		    }
		}
	    });
	}

	return ret;
    }

    /**
     * Function divides list of ports to the map of ports and port ranges.
     * If with_aliases is 'true' it also returns ports wit their port aliases.
     * Port ranges are not affected with it.
     *
     * @struct Returns $[
     *    "ports" : [ list of ports ],
     *    "port_ranges" : [ list of port ranges ],
     * ]
     *
     * @param list <string> unsorted_ports
     * @param boolean with port aliases
     * @return <map <string, list <string> > > of divided ports
     */
    global map <string, list <string> > DividePortsAndPortRanges (list <string> unsorted_ports, boolean with_aliases) {
	map <string, list <string> > ret = $[];

	foreach (string port, unsorted_ports, {
	    // port range
	    if (IsPortRange(port)) {
		ret["port_ranges"] = add (ret["port_ranges"]:[], port);
	    // is a normal port
	    } else {
		// find also aliases
		if (with_aliases) {
		    ret["ports"] = (list<string>) union (
			ret["ports"]:[], PortAliases::GetListOfServiceAliases(port)
		    );
		// only add the port itself
		} else {
		    ret["ports"] = add (ret["ports"]:[], port);
		}
	    }
	});

	return ret;
    }

    /**
     * Function creates a port range from min and max params. Max must be bigger than min.
     * If something is wrong, it returns an empty string.
     *
     * @param integer min_port
     * @param integer max_port
     * @return string new port range
     */
    global string CreateNewPortRange (integer min_pr, integer max_pr) {
	if (min_pr == nil || min_pr == 0) {
	    y2error("Wrong definition of the starting port '%1', it must be between 1 and 65535", min_pr);
	    return "";
	} else if (max_pr == nil || max_pr == 0 || max_pr > 65535) {
	    y2error("Wrong definition of the ending port '%1', it must be between 1 and 65535", max_pr);
	    return "";
	}

	// max and min are the same, this is not a port range
	if (min_pr == max_pr) {
	    return tostring(min_pr);
	// right port range
	} else if (min_pr < max_pr) {
	    return tostring(min_pr) + ":" + tostring(max_pr);
	// min is bigger than max
	} else {
	    y2error("Starting port '%1' cannot be bigger than ending port '%2'", min_pr, max_pr);
	    return "";
	}
    }

    /**
     * Function removes port number from all port ranges. Port must be in its numeric
     * form.
     *
     * @see PortAliases::GetPortNumber()
     * @param integer port_number to be removed
     * @param list <string> of all current port_ranges
     * @return list <string> of filtered port_ranges
     *
     * @example
     *     RemovePortFromPortRanges(25, ["19-88", "152-160"]) -> ["19-24", "26-88", "152-160"]
     */
    global list <string> RemovePortFromPortRanges (integer port_number, list <string> port_ranges) {
	// Checking necessarity of filtering and params
	if (port_ranges == nil || port_ranges == []) return port_ranges;
	if (port_number == nil || port_number == 0) return port_ranges;

	y2milestone("Removing port %1 from port ranges %2", port_number, port_ranges);
	
	list <string> ret = [];
	// Checking every port range alone
	foreach (string port_range, port_ranges, {
	    // Port range might be now only "port"
	    if (!IsPortRange(port_range)) {
		// If the port doesn't match the ~port_range...
		if (tostring(port_number) != port_range)
		    ret = add (ret, port_range);
		// If matches, it isn't added (it is filtered)
	    // Modify the port range when the port is included
	    } else if (PortIsInPortranges(tostring(port_number), [port_range])) {
		integer min_pr = tointeger(regexpsub(port_range,"^([0123456789]+):.*$", "\\1"));
		integer max_pr = tointeger(regexpsub(port_range,"^.*:([0123456789]+)$", "\\1"));
		
		// Port matches the min. value of port range
		if (port_number == min_pr) {
		    ret = add (ret, CreateNewPortRange(port_number + 1, max_pr));
		// Port matches the max. value of port range
		} else if (port_number == max_pr) {
		    ret = add (ret, CreateNewPortRange(min_pr, port_number - 1));
		// Port is inside the port range, split it up
		} else {
		    ret = add (ret, CreateNewPortRange(port_number + 1, max_pr));
		    ret = add (ret, CreateNewPortRange(min_pr, port_number - 1));
		}
	    // Port isn't in the port range, adding the current port range
	    } else {
		ret = add (ret, port_range);
	    }
	});

	y2milestone("Result: %1", ret);
	
	return ret;
    }

    /**
     * Function tries to flatten services into the minimal list.
     * If ports are already mentioned inside port ranges, they are dropped.
     *
     * @param list <string> of services and port ranges
     * @return list <string> of flattened services and port ranges
     */
    global list <string> FlattenServices (list <string> old_list, string protocol) {
	if (! contains(["TCP", "UDP"], protocol)) {
	    string message = sformat("Protocol %1 doesn't support port ranges, skipping...", protocol);
	    if (ReportOnlyOnce(message)) y2milestone(message);
	    return old_list;
	}
    
	list <string> new_list = [];
	list <string> list_of_ports = [];
	list <string> list_of_ranges = [];
	// Using port number, we can remove ports mentioned in port ranges
	map <string, integer> ports_to_port_numbers = $[];
	// Using this we can remove ports mentioned more than once
	map <integer, list <string> > port_numbers_to_port_names = $[];
	
	foreach (string one_item, old_list, {
	    // Port range
	    if (IsPortRange(one_item)) list_of_ranges = add (list_of_ranges, one_item);
	    // Port number or name
	    else {
		integer port_number = PortAliases::GetPortNumber(one_item);
		// Cannot find port number for this port, it is en error of the configuration
		if (port_number == nil) {
		    y2warning("Unknown port %1 but leaving it in the configuration.", one_item);
		    new_list = add (new_list, one_item);
		    // skip the 'nil' port number
		    return;
		}
		ports_to_port_numbers[one_item] = port_number;
		port_numbers_to_port_names[port_number] = add (port_numbers_to_port_names[port_number]:[], one_item);
	    }
	});
	
	foreach (integer port_number, list <string> port_names, port_numbers_to_port_names, {
	    // Port is not in any defined port range
	    if (!PortIsInPortranges(tostring(port_number), list_of_ranges)) {
		// Port - 1 IS in some port range
		if (PortIsInPortranges(tostring(port_number - 1), list_of_ranges)) {
		    // Creating fake port range, to be joined with another one
		    list_of_ranges = add (list_of_ranges, CreateNewPortRange(port_number - 1, port_number));
		// Port + 1 IS in some port range
		} else if (PortIsInPortranges(tostring(port_number + 1), list_of_ranges)) {
		    // Creating fake port range, to be joined with another one
		    list_of_ranges = add (list_of_ranges, CreateNewPortRange(port_number, port_number + 1));
		// Port is not in any port range and also it cannot be joined with any one
		} else {
		    // Port names of this port
		    list <string> used_port_names = port_numbers_to_port_names[port_number]:[];
		    if (size(used_port_names)>0) {
			new_list = add (new_list, used_port_names[0]:"");
		    } else {
			y2milestone("No port name for port number %1. Adding %1...", port_number);
			// There are no port names (hmm?), adding port number
			new_list = add (new_list, tostring(port_number));
		    }
		}
	    // Port is in a port range
	    } else {
		y2milestone("Removing port %1 mentioned in port ranges %2", port_number, list_of_ranges);
	    }
	});

	list_of_ranges = toset(list_of_ranges);
	// maximal count of steps
	integer max_loops = 5000;

	// Joining port ranges together
	// this is a bit dangerous!
	y2milestone("Joining list of ranges %1", list_of_ranges);
	while (true && max_loops>0) {
	    // if something goes wrong
	    max_loops = max_loops - 1;
	    
	    boolean any_change_during_this_loop = false;

	    list <string> try_all_these_ranges = list_of_ranges;
	    foreach (string port_range, try_all_these_ranges, {
		if (! IsValidPortRange(port_range)) {
		    string warning = sformat("Wrong port-range definition %1, cannot join", port_range);
		    if (ReportOnlyOnce(warning)) y2warning(warning);
		    return;
		}

		integer min_pr = tointeger(regexpsub(port_range,"^([0123456789]+):.*$", "\\1"));
		integer max_pr = tointeger(regexpsub(port_range,"^.*:([0123456789]+)$", "\\1"));
		
		if (min_pr == nil || max_pr == nil) {
		    y2error("Not a port range %1", port_range);
		    return;
		}

		// try to join it with another port ranges
		// -->
		    foreach (string try_this_pr, try_all_these_ranges, {
			// Exact match means the same port range
			if (try_this_pr == port_range) return;
			
			string this_min = regexpsub(try_this_pr,"^([0123456789]+):.*$", "\\1");
			string this_max = regexpsub(try_this_pr,"^.*:([0123456789]+)$", "\\1");
			
			if (this_min == nil || this_max == nil) {
			    y2error("Wrong port range %1, %2 > %3", port_range, this_min, this_max);
			    // skip it
			    return;
			}
			
			integer this_min_pr = tointeger(this_min);
			integer this_max_pr = tointeger(this_max);
			
			// // wrong definition of the port range
			if (this_min_pr < 1 || this_max_pr > max_port_number) {
			    string warning = sformat("Wrong port-range definition %1, cannot join", port_range);
			    if (ReportOnlyOnce(warning)) y2warning(warning);
			    // skip it
			    return;
			}
		    
			// If new port range should be created
			integer new_min = nil;
			integer new_max = nil;

			// the second one is inside the first one
			if (min_pr <= this_min_pr && max_pr >= this_max_pr) {
			    // take min_pr & max_pr
			    any_change_during_this_loop = true;
			    new_min = min_pr;
			    new_max = max_pr;
			// the fist one is inside the second one
			} else if (min_pr >= this_min_pr && max_pr <= this_max_pr) {
			    // take this_min_pr & this_max_pr
			    any_change_during_this_loop = true;
			    new_min = this_min_pr;
			    new_max = this_max_pr;
			// the fist one partly covers the second one (by its right side)
			} else if (min_pr <= this_min_pr && max_pr >= this_min_pr) {
			    // take min_pr & this_max_pr
			    any_change_during_this_loop = true;
			    new_min = min_pr;
			    new_max = this_max_pr;
			// the second one partly covers the first one (by its left side)
			} else if (min_pr >= this_min_pr && max_pr <= this_max_pr) {
			    // take this_min_pr & max_pr
			    any_change_during_this_loop = true;
			    new_min = this_min_pr;
			    new_max = max_pr;
			// the first one has the second one just next on the right
			} else if ((max_pr + 1) == this_min_pr) {
			    // take min_pr & this_max_pr
			    any_change_during_this_loop = true;
			    new_min = min_pr;
			    new_max = this_max_pr;
			// the first one has the second one just next on the left side
			} else if ((min_pr - 1) == this_max_pr) {
			    // take this_min_pr & max_pr
			    any_change_during_this_loop = true;
			    new_min = this_min_pr;
			    new_max = max_pr;
			}
			
			if (any_change_during_this_loop && new_min != nil && new_max != nil) {
			    string new_port_range = CreateNewPortRange(new_min, new_max);
			    y2milestone("Joining %1 and %2 into %3", port_range, try_this_pr, new_port_range);
			    // Remove old port ranges
			    list_of_ranges = filter (string filter_pr, list_of_ranges, {
				return (filter_pr != port_range && filter_pr != try_this_pr);
			    });
			    // Create a new one
			    list_of_ranges = add (list_of_ranges, new_port_range);
			}
		    });
		// <--
		
		// renew list of current port ranges, they have changed
		if (any_change_during_this_loop) break;
	    });
	    
	    if (!any_change_during_this_loop) break;
	}
	y2milestone("Result of joining: %1", list_of_ranges);

	new_list = (list <string>) union (new_list, list_of_ranges);

	return new_list;
    }

    // <-- Port Ranges

/* EOF */
}

ACC SHELL 2018