# Simple Man in the middle server
#
# server = MitmServer.new('localhost', 22)
# Net.SSH.start('localhost', ENV['USER'], port: server.port)
# server.run

class MitmServer < TCPServer
  attr_accessor :local_read_size
  attr_accessor :target_read_size

  def initialize(remote_host = 'localhost', remote_port = 22)
    @remote_host = remote_host
    @remote_port = remote_port
    @server = TCPServer.open(0)
    @local_read_size = 2048
    @target_read_size = 2048
  end

  def port
    @server.addr[1]
  end

  def host
    'localhost'
  end

  def run
    start(@server, @remote_host, @remote_port)
  end

  private

  def start_server(server, &block)
    server ||= TCPServer.open(0)
    Thread.start do
      loop do
        Thread.start(server.accept) do |client|
          yield(client)
        end
      end
    end
    return server
  end

  def dlog(message); end

  def start(server, remote_host, remote_port)
    err = nil
    server = start_server(server) do |local|
      remote = TCPSocket.new(remote_host, remote_port)
      loop do
        r, _w, _e = IO.select([local, remote], nil, nil)
        if r.include? local
          begin
            data = local.recv local_read_size
          rescue StandardError => e
            data = nil
            dlog "Local closed: #{e}"
            break
          end
          if data.empty?
            dlog "Local closed: #{data.inspect}"
            break
          end
          dlog "Forwarding: #{data.length} to remote"
          begin
            remote.write data
          rescue StandardError => e
            dlog "remote closed: #{e}"
            break
          end
        end
        if r.include? remote # rubocop:disable Style/Next
          begin
            data = remote.recv target_read_size
          rescue StandardError => e
            dlog "remote closed: #{e}"
            break
          end
          if data.nil? || data.empty?
            dlog "Remote closed: #{data.inspect} #{err.inspect}"
            break
          end
          dlog "Forwarding: #{data.length} to local"
          begin
            local.write data
          rescue StandardError => e
            dlog "local closed: #{e}"
            break
          end
        end
      end

      dlog "Closing..."
      local.close
      remote.close
    end
    @server = server
    return server
  end
end
