yast/yast-yast2

View on GitHub
library/network/src/lib/y2firewall/firewalld/api.rb

Summary

Maintainability
A
0 mins
Test Coverage
#
# ***************************************************************************
#
# Copyright (c) 2017 SUSE LLC.
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 or 3 of the GNU General
# Public License as published by the Free Software Foundation.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, contact SUSE LLC.
#
# To contact SUSE about this file by physical or electronic mail,
# you may find current contact information at www.suse.com
#
# ***************************************************************************

require "yast"
require "yast2/execute"
require "y2firewall/firewalld/api/services"
require "y2firewall/firewalld/api/zones"

Yast.import "Stage"
Yast.import "Service"
Yast.import "Package"

module Y2Firewall
  class Firewalld
    class Error < RuntimeError
    end

    # Firewalld command line API supporting two modes (:offline and :running)
    #
    # The :offline mode is useful in environments where the daemon is not running or
    # the DBUS API is not accesible, in other case the :running mode should be
    # used.
    class Api
      include Yast::Logger
      include Yast::I18n
      include Services
      include Zones
      extend Forwardable

      # Map firewalld modes with their command line tools
      COMMAND = { offline: "firewall-offline-cmd", running: "firewall-cmd" }.freeze
      # FIXME: Do not like to define twice
      PACKAGE = "firewalld".freeze
      # Modification commands were applied successfully
      SUCCESS = "success".freeze

      # Determines the mode in which firewalld is running and as consequence the
      # command to be used.
      attr_accessor :mode

      # Constructor
      #
      # @param mode [Symbol, nil] defines which cmdline should be used if the
      #   running or the offline one. Possible values are: :offline, :running
      # @param permanent [Boolean] whether the configuration should be written
      #   permanently or in runtime when firewalld is running.
      def initialize(mode: nil, permanent: true)
        @mode = mode || (running? ? :running : :offline)
        @permanent = !offline? && permanent
      end

      # Whether the mode is :offline or not
      #
      # @return [Boolean] true if current mode if :offline; false otherwise
      def offline?
        @mode == :offline
      end

      # Whether the command called to modify configuration should make the
      # changes permanent or not
      #
      # @return [Boolean]
      def permanent?
        return false if offline?

        @permanent
      end

      # Whether firewalld is running or not
      #
      # @return [Boolean] true if the state is running; false otherwise
      def running?
        return false if Yast::Stage.initial
        return false if !Yast::Package.Installed(PACKAGE, target: :system)

        state == "running"
      end

      # Enables the firewalld service
      def enable!
        offline? ? run_command("--enable") : Yast::Service.Enable("firewalld")
      end

      # Disables the firewalld service
      def disable!
        offline? ? run_command("--disable") : Yast::Service.Disable("firewalld")
      end

      # Return the current state of the firewalld service (running or not
      # running)
      #
      # @return [String] firewalld service state
      # @see http://www.firewalld.org/documentation/man-pages/firewall-cmd.html
      def state
        case Yast::Execute.on_target("firewall-cmd", "--state", allowed_exitstatus: [0, 252])
        when 0
          "running"
        when 252
          "not running"
        else
          "unknown"
        end
      end

      # Return the default zone

      # @return [String] default zone
      def default_zone
        string_command("--get-default-zone")
      end

      # Set the default zone
      #
      # @param zone [String] The firewall zone
      def modify_default_zone(zone)
        modify_command("--set-default-zone=#{zone}")
      end

      # Do a reload of the firewall if running. In offline mode just return
      # true as a reload is not needed to apply the changes.
      #
      # @return [Boolean] true if the firewall was reloaded successfully
      def reload
        return true if offline?

        modify_command("--reload")
      end

      # Do a complete reload of the firewall if running. In offline mode just
      # return true as a reload is not needed to apply the changes
      #
      # @return [Boolean] true if the firewall was reloaded completely with success
      def complete_reload
        return true if offline?

        modify_command("--complete-reload")
      end

      ### Logging ###

      # @param kind [String] Denied packets to log. Possible values are:
      #   all, unicast, broadcast, multicast and off
      # @return [Boolean] True if desired packet type is being logged when denied
      def log_denied_packets?(kind)
        string_command("--get-log-denied").strip == kind
      end

      # @param kind [String] Denied packets to log. Possible values are:
      #   all, unicast, broadcast, multicast and off
      def modify_log_denied_packets(kind)
        modify_command("--set-log-denied=#{kind}")
      end

      # @return [String] packet type which is being logged when denied
      def log_denied_packets
        string_command("--get-log-denied").strip
      end

    private

      # Command to be used depending on the current mode.
      # @return [String] command for the current mode.
      def command
        COMMAND[@mode]
      end

      # Executes the command for the current mode with the given arguments.
      #
      # @see #command
      # @see Yast::Execute
      # @param args [Array<String>] list of command optional arguments
      # @param permanent [Boolean] if true and firewalld is running it
      #   operates over the permanent configuration
      # @param allowed_exitstatus [Fixnum, .include?, nil] allowed exit codes
      #   which do not cause an exception.
      def run_command(*args, permanent: false, allowed_exitstatus: nil)
        arguments = (!offline? && permanent) ? ["--permanent"] : []
        arguments.concat(args)
        log.info("Executing #{command} with #{arguments.inspect}")

        Yast::Execute.on_target(
          command, *arguments, stdout: :capture, allowed_exitstatus: allowed_exitstatus
        )
      end

      # Convenience method that run the command for the current mode treating
      # the output as a string and chomping it
      #
      # @see #run_command
      # @param args [Array<String>] list of command optional arguments
      # @param permanent [Boolean] if true and firewalld is running it
      #   operates over the permanent configuration
      # @return [String] the chomped output of the run command
      def string_command(*args, permanent: false)
        run_command(*args, permanent: permanent).to_s.chomp
      end

      # Convenience method that run the given modification command for the
      # current mode returning whether it was applied successfully or not
      #
      #
      # @see #run_command
      # @param args [Array<String>] list of command optional arguments
      # @param permanent [Boolean] if true and firewalld is running it
      #   operates over the permanent configuration
      # @return [Boolean] true if the executed command returns succesfully
      def modify_command(*args, permanent: false)
        string_command(*args, permanent: permanent) == SUCCESS
      end

      # Convenience method which return true whether the run command for the
      # current mode return the exit status 0.
      #
      # @see #run_command
      # @param args [Array<String>] list of command optional arguments
      # @param permanent [Boolean] if true and firewalld is running it
      #   operates over the permanent configuration
      # @return [Boolean] true if the exit status of the executed command is 0
      def query_command(*args, permanent: false)
        _output, exit_status = run_command(*args, allowed_exitstatus: [0, 1], permanent: permanent)

        exit_status == 0
      end
    end
  end
end