module Msf::Exploit::Remote::SMB::Relay::NTLM
  # This class provides the SMB server core. Settings that are relevant server wide are managed by this object.
  # Currently, the server only supports negotiating and authenticating requests. No other server functionality is
  # available at this time. The negotiating and authentication is supported for SMB versions 1 through 3.1.1.
  class Server < ::RubySMB::Server

    # The supported server dialects. SMB 1 is allowed, so that it can be reported as a failure to the user
    # https://github.com/rapid7/metasploit-framework/issues/16261
    # Note there are similar supported dialects for both the server and the relay clients
    # {Msf::Exploit::Remote::SMB::Relay::NTLM::SUPPORTED_SERVER_DIALECTS} and
    # {Msf::Exploit::Remote::SMB::Relay::NTLM::SMBRelayTargetClient::SUPPORTED_CLIENT_DIALECTS}
    SUPPORTED_SERVER_DIALECTS = [
      RubySMB::Client::SMB1_DIALECT_SMB1_DEFAULT,

      RubySMB::Client::SMB2_DIALECT_0202,
      RubySMB::Client::SMB2_DIALECT_0210,
      RubySMB::Client::SMB2_DIALECT_0300,
      RubySMB::Client::SMB2_DIALECT_0302,
    ]

    def initialize(relay_timeout:, relay_targets:, listener:, thread_manager:, **kwargs)
      super(**kwargs)

      @dialects = SUPPORTED_SERVER_DIALECTS
      @relay_targets = relay_targets
      @relay_timeout = relay_timeout
      @listener = listener
      @thread_manager = thread_manager
      @closed = false
    end

    # Run the server and accept any connections. For each connection, the block will be executed if specified. When the
    # block returns false, the loop will exit and the server will no long accept new connections.
    def run(&block)
      until closed? do
        sock = @socket.accept
        return if closed?

        server_client = Msf::Exploit::Remote::SMB::Relay::NTLM::ServerClient.new(
          self,
          RubySMB::Dispatcher::Socket.new(sock),
          relay_targets: @relay_targets,
          relay_timeout: @relay_timeout,
          listener: @listener,
        )
        @connections << Connection.new(server_client, @thread_manager.spawn("SMBRelayServerClient for #{sock.peerinfo}", false, server_client) do |server_client|
          begin
            _port, ip_address = ::Socket::unpack_sockaddr_in(server_client.getpeername)
            logger.print_status("New request from #{ip_address}")
            logger.info("starting thread for connection")
            server_client.run
          rescue => e
            logger.print_error "#{e.message}"
            elog(e)
          end
          logger.info("ending thread for connection")
        end)

        break unless block.nil? || block.call(server_client)
      end
    end

    def closed?
      @closed
    end

    def close
      @closed = true
      @connections.each do |connection|
        begin
          connection.thread.kill
        rescue StandardError => e
          elog('Failed SMBRelayServerClient', error: e)
        end
      end
    end
  end
end
