# frozen_string_literal: true

require_relative 'server'

module Ftpd
  class TlsServer < Server

    # Whether or not to do TLS, and which flavor.
    #
    # One of:
    # * :off
    # * :explicit
    # * :implicit
    #
    # Notes:
    # * Defaults to :off
    # * Set this before calling #start.
    # * If other than :off, then #certfile_path must be set.
    #
    # @return [Symbol]

    attr_accessor :tls

    # The path of the SSL certificate to use for TLS.  Defaults to nil
    # (no SSL certificate).
    #
    # Set this before calling #start.
    #
    # @return [String]

    attr_accessor :certfile_path

    # Create a new TLS server.

    def initialize
      super
      @tls = :off
    end

    private

    def make_server_socket
      socket = super
      if tls_enabled?
        socket = OpenSSL::SSL::SSLServer.new(socket, ssl_context);
        socket.start_immediately = false
      end
      socket
    end

    def accept
      socket = @server_socket.accept
      if tls_enabled?
        add_missing_methods_to_socket socket
        add_tls_methods_to_socket socket
      end
      socket
    end

    def ssl_context
      unless @certfile_path
        raise ArgumentError, ":certfile required if tls enabled"
      end
      context = OpenSSL::SSL::SSLContext.new
      File.open(@certfile_path) do |certfile|
        context.cert = OpenSSL::X509::Certificate.new(certfile)
        certfile.rewind
        context.key = OpenSSL::PKey::RSA.new(certfile)
      end
      context
    end
    memoize :ssl_context

    # Add to the TLS socket some methods that regular socket has, but TLS socket is missing

    def add_missing_methods_to_socket(socket)

      def socket.local_address
        @io.local_address
      end

    end

    # Add to the TLS sockets some methods of our own invention that
    # make life a little easier.

    def add_tls_methods_to_socket(socket)
      context = @ssl_context
      class << socket

        def ssl_context
          context
        end

        def encrypted?
          !!cipher
        end

        def encrypt
          accept
        end

      end
    end

    private

    def tls_enabled?
      @tls != :off
    end

  end
end
