# -*- coding:binary -*-
require 'spec_helper'

require 'stringio'

class MyStringIO < StringIO
  def put(data)
    write(data)
  end

  def get_once(length, timeout = 10)
    read(length)
  end
end

RSpec.describe Rex::Proto::Kerberos::Client do
  before :example do
    allow(Rex::Socket::Tcp).to receive(:create) do
      s = ''
      io = MyStringIO.new(s, 'w+b')
      io
    end
  end

  let(:rhost) { '127.0.0.1' }
  let(:rport) { 88 }
  let(:subscriber) { instance_double(Rex::Proto::Kerberos::KerberosSubscriber, on_request: nil, on_response: nil) }

  subject(:client) do
    opts = {
      host: rhost,
      port: rport,
      subscriber: subscriber
    }
    described_class.new(opts)
  end

  let(:sample_asn1_request) do
    "\x6a\x82\x01\x08\x30\x82\x01\x04\xa1\x03\x02\x01" +
    "\x05\xa2\x03\x02\x01\x0a\xa3\x5f\x30\x5d\x30\x48\xa1\x03\x02\x01" +
    "\x02\xa2\x41\x04\x3f\x30\x3d\xa0\x03\x02\x01\x17\xa2\x36\x04\x34" +
    "\x60\xae\x53\xa5\x0b\x56\x2e\x46\x61\xd9\xd6\x89\x98\xfc\x79\x9d" +
    "\x45\x73\x7d\x0d\x8a\x78\x84\x4d\xd7\x7c\xc6\x50\x08\x8d\xab\x22" +
    "\x79\xc3\x8d\xd3\xaf\x9f\x5e\xb7\xb8\x9b\x57\xc5\xc9\xc5\xea\x90" +
    "\x89\xc3\x63\x58\x30\x11\xa1\x04\x02\x02\x00\x80\xa2\x09\x04\x07" +
    "\x30\x05\xa0\x03\x01\x01\x00\xa4\x81\x96\x30\x81\x93\xa0\x07\x03" +
    "\x05\x00\x50\x80\x00\x00\xa1\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1" +
    "\x08\x30\x06\x1b\x04\x6a\x75\x61\x6e\xa2\x0c\x1b\x0a\x44\x45\x4d" +
    "\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x1f\x30\x1d\xa0\x03\x02\x01\x01" +
    "\xa1\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45" +
    "\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c\xa4\x11\x18\x0f\x31\x39\x37\x30" +
    "\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a\xa5\x11\x18\x0f\x31" +
    "\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30\x5a\xa6\x11" +
    "\x18\x0f\x31\x39\x37\x30\x30\x31\x30\x31\x30\x30\x30\x30\x30\x30" +
    "\x5a\xa7\x06\x02\x04\x18\xf4\x10\x2c\xa8\x05\x30\x03\x02\x01\x17"
  end

  let(:req_length) do
    272
  end

  let(:res_invalid) do
    'ABCDEF'
  end

  let(:res_valid) do
    "\x00\x00\x02\x57\x6b\x82\x02\x53\x30\x82\x02\x4f\xa0\x03\x02\x01" +
    "\x05\xa1\x03\x02\x01\x0b\xa3\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c" +
    "\x4f\x43\x41\x4c\xa4\x11\x30\x0f\xa0\x03\x02\x01\x01\xa1\x08\x30" +
    "\x06\x1b\x04\x6a\x75\x61\x6e\xa5\x82\x01\x10\x61\x82\x01\x0c\x30" +
    "\x82\x01\x08\xa0\x03\x02\x01\x05\xa1\x0c\x1b\x0a\x44\x45\x4d\x4f" +
    "\x2e\x4c\x4f\x43\x41\x4c\xa2\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1" +
    "\x16\x30\x14\x1b\x06\x6b\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d" +
    "\x4f\x2e\x4c\x4f\x43\x41\x4c\xa3\x81\xd1\x30\x81\xce\xa0\x03\x02" +
    "\x01\x17\xa1\x03\x02\x01\x02\xa2\x81\xc1\x04\x81\xbe\x78\xce\x09" +
    "\x63\x22\x20\x46\xe0\x86\x82\x2f\x2e\x02\x6e\xf8\x09\xd4\xd4\x87" +
    "\x82\xa6\x5d\xbe\x6e\x75\x65\xd4\xc7\x7e\x71\xaf\xc9\xb5\x00\x7c" +
    "\x57\x0a\xca\x9a\xb1\x3c\xda\x4e\x47\x24\x96\x11\xb6\x54\x86\x0f" +
    "\xf0\x85\x54\xf0\x47\x88\xa4\x31\xc9\xe4\x7c\x43\x7c\xe7\x54\x9b" +
    "\xf4\x06\xfe\x86\xd2\x5c\x1b\x6c\xe8\x30\xa6\x51\xb3\x2e\xf6\x45" +
    "\x54\x3c\x52\x70\xb8\xc6\x31\x4c\x49\x57\xb7\xd6\x16\x11\x70\x77" +
    "\x4f\x3d\x40\x96\xfc\xb9\x9c\x6a\x4b\x55\x94\x52\x99\x6a\xcb\xf3" +
    "\x85\x1d\xf8\xa7\x0b\xe4\x34\xa6\x35\x8b\x27\x9d\x70\x4d\xc6\xdf" +
    "\xca\x70\x65\x10\x84\x1d\x23\xf7\xea\xf7\xef\x67\x02\x68\xe4\xab" +
    "\x94\x22\x8f\x18\xab\x3b\x57\x85\x23\xa0\xdd\xc9\x60\x01\x24\x8f" +
    "\xf7\x3d\x82\xc8\x9b\xdd\x9e\xc8\xeb\xa7\xf8\xb0\xc4\x72\x93\x8c" +
    "\xed\xf1\xf6\x49\x96\xab\x61\x78\xcd\x75\x04\xa6\x82\x01\x0c\x30" +
    "\x82\x01\x08\xa0\x03\x02\x01\x17\xa1\x03\x02\x01\x01\xa2\x81\xfb" +
    "\x04\x81\xf8\x7c\x0f\x86\x51\xdb\x1b\x09\x86\xfa\x68\xe4\xea\x2f" +
    "\xcb\xfd\x92\x88\x76\x00\x0d\x47\x78\xcc\xfb\x1a\x37\x3a\x89\x54" +
    "\x08\x71\x5b\xdf\xe1\xe5\xac\xe0\xa7\xda\xeb\xe1\xbf\x67\x91\xa3" +
    "\xbc\xb5\x02\x53\xf7\xdb\x90\x5d\x9b\xb2\x28\xdb\x37\x25\xab\xc2" +
    "\x1f\x49\x71\xaf\x4c\x00\x8b\xda\x8d\x35\x26\x88\xc5\xa7\xe8\x79" +
    "\x3e\x10\xb7\xe0\xc9\x77\x71\x10\x74\x05\xc2\x85\x1a\x56\x05\xa0" +
    "\x22\x38\x7d\x6e\xeb\x3c\xa9\xc1\x4a\x50\x3f\x33\x12\x76\x28\x56" +
    "\x8e\xf8\x9e\x77\x62\x9c\xe5\xfe\x4b\xb2\x03\xdb\x6f\x44\x5f\x0e" +
    "\x2b\xa7\x20\x39\xd8\x5e\xb2\x41\xff\x5d\xe4\xa1\x8f\x7c\x47\x4b" +
    "\xae\x5a\x2f\xc2\x07\xdd\xbb\x12\xcf\xe3\xbd\x6c\xef\x49\x4a\xf7" +
    "\x25\x8a\x7b\xfa\xaf\x22\x33\x31\xbb\xc7\xdd\x17\x5c\xe3\x19\xc0" +
    "\xa0\x18\xba\xcf\xa9\xcf\xd0\x21\xd9\x68\xa8\x2f\x43\x63\x0c\x60" +
    "\x3b\x66\xbe\xe1\xa1\x9f\xba\xac\x05\xa3\xad\x28\xc5\xfc\x80\x8a" +
    "\x3d\x24\xa8\x2e\x8e\xc5\x06\x4b\xbc\x79\x1e\x41\x2c\x47\x6c\x3e" +
    "\x50\x59\x25\xc0\xe5\x94\xee\x38\xca\x09\x8c\xe1\x43\x87\xa7\x34" +
    "\xa6\x7c\x0f\xe4\xcb\x1c\xf6\xb4\xa3\xc7\x6e"
  end

  let(:res_error) do
    "\x00\x00\x00\x8f\x7e\x81\x8c\x30\x81\x89\xa0\x03\x02\x01\x05\xa1" +
    "\x03\x02\x01\x1e\xa4\x11\x18\x0f\x32\x30\x31\x34\x31\x32\x31\x39" +
    "\x31\x38\x30\x35\x30\x33\x5a\xa5\x04\x02\x02\x51\x89\xa6\x03\x02" +
    "\x01\x18\xa9\x0c\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41\x4c" +
    "\xaa\x1f\x30\x1d\xa0\x03\x02\x01\x01\xa1\x16\x30\x14\x1b\x06\x6b" +
    "\x72\x62\x74\x67\x74\x1b\x0a\x44\x45\x4d\x4f\x2e\x4c\x4f\x43\x41" +
    "\x4c\xac\x30\x04\x2e\x30\x2c\x30\x16\xa1\x03\x02\x01\x0b\xa2\x0f" +
    "\x04\x0d\x30\x0b\x30\x09\xa0\x03\x02\x01\x17\xa1\x02\x04\x00\x30" +
    "\x12\xa1\x03\x02\x01\x13\xa2\x0b\x04\x09\x30\x07\x30\x05\xa0\x03" +
    "\x02\x01\x17"
  end

  describe "#send_request" do
    context "when TCP connection" do
      it "returns the written data length" do
        request = Rex::Proto::Kerberos::Model::KdcRequest.decode(sample_asn1_request)
        expect(subscriber).to receive(:on_request).with(request)
        expect(subject.send_request(request)).to eq(req_length)
      end
    end
  end

  describe '#connect' do
    context 'when no host is specified' do

      subject do
        opts = {
          port: rport,
        }
        described_class.new(opts)
      end

      it 'should raise an argument error' do
        expect { subject.connect }.to raise_error(::ArgumentError)
      end
    end

    context 'when no port is specified' do
      subject do
        opts = {
          host: rhost,
        }
        described_class.new(opts)
      end
      it 'should raise an argument error' do
        expect { subject.connect }.not_to raise_error
      end
    end

    context 'when host and port are specified' do
      it 'should not raise an error' do
        expect { subject.connect }.not_to raise_error
      end
    end
  end

  describe "#recv_response" do
    context "when no connection" do
      it "raises RunitmeError" do
        expect { subject.recv_response }.to raise_error(::RuntimeError)
      end
    end

    context "when TCP connection" do
      context "when reads a kerberos error" do
        it "returns a Rex::Proto::Kerberos::Model::KrbError" do
          subject.connect
          subject.connection.write(res_error)
          subject.connection.seek(0)
          expect(subscriber).to receive(:on_response).with(instance_of(Rex::Proto::Kerberos::Model::KrbError))
          expect(subject.recv_response).to be_a(Rex::Proto::Kerberos::Model::KrbError)
        end
      end

      context "when reads a Kerberos response" do
        it "returns a Rex::Proto::Kerberos::Model::KdcResponse" do
          subject.connect
          subject.connection.write(res_valid)
          subject.connection.seek(0)
          expect(subscriber).to receive(:on_response).with(instance_of(Rex::Proto::Kerberos::Model::KdcResponse))
          expect(subject.recv_response).to be_a(Rex::Proto::Kerberos::Model::KdcResponse)
        end
      end

      context "when reads unexpected data" do
        it "raises an error" do
          subject.connect
          subject.connection.write(res_invalid)
          subject.connection.seek(0)
          expect { subject.recv_response }.to raise_error(::EOFError)
        end
      end
    end
  end
end
