yahns Ruby server user/dev discussion
 help / color / mirror / code / Atom feed
blob c090083952387e20fa4dbd06089c5032824844a8 4124 bytes (raw)
name: lib/yahns/openssl_client.rb 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
 
# -*- encoding: binary -*-
# Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
# License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
# frozen_string_literal: true

# this is to be included into a Kgio::Socket-derived class
# this requires Ruby 2.1 and later for "exception: false"
module Yahns::OpenSSLClient # :nodoc:
  def self.included(cls)
    # Forward these methods to OpenSSL::SSL::SSLSocket so hijackers
    # can rely on stdlib methods instead of ugly kgio stuff that
    # we hope to phase out.
    # This is a bit weird, since OpenSSL::SSL::SSLSocket wraps
    # our actual socket, too, so we must take care to not blindly
    # use method_missing and cause infinite recursion
    %w(sync= read write readpartial write_nonblock read_nonblock
       print printf puts gets readlines readline getc
       readchar ungetc eof eof? << flush
       sysread syswrite).map!(&:to_sym).each do |m|
      cls.__send__(:define_method, m) { |*a| @ssl.__send__(m, *a) }
    end

    # block captures, ugh, but nobody really uses them
    %w(each each_line each_byte).map!(&:to_sym).each do |m|
      cls.__send__(:define_method, m) { |*a, &b| @ssl.__send__(m, *a, &b) }
    end
  end

  # this is special, called during IO initialization in Ruby
  def sync
    defined?(@ssl) ? @ssl.sync : super
  end

  def yahns_init_ssl(ssl_ctx)
    @need_accept = true
    @ssl = OpenSSL::SSL::SSLSocket.new(self, ssl_ctx)
    @ssl_blocked = nil
  end

  def kgio_trywrite(buf)
    len = buf.bytesize
    return if len == 0

    case @ssl_blocked
    when nil # likely
      buf = @ssl_blocked = buf.dup
    when Exception
      raise @ssl_blocked
    when String
      if @ssl_blocked != buf
        pfx = object_id
        warn("#{pfx} BUG: ssl_blocked != buf\n" \
             "#{pfx} ssl_blocked=#{@ssl_blocked.inspect}\n" \
             "#{pfx} buf=#{buf.inspect}\n")
        raise 'BUG: ssl_blocked} != buf'
      end
    end

    case rv = @ssl.write_nonblock(buf, exception: false)
    when :wait_readable, :wait_writable
      rv # do not clear ssl_blocked
    when Integer
      @ssl_blocked = len == rv ? nil : buf.byteslice(rv, len - rv)
    end
  rescue SystemCallError => e # ECONNRESET/EPIPE
    e.set_backtrace([])
    raise(@ssl_blocked = e)
  end

  def kgio_trywritev(buf)
    kgio_trywrite(buf.join)
  end

  def kgio_syssend(buf, flags)
    kgio_trywrite(buf)
  end

  def kgio_tryread(len, buf)
    if @need_accept
      # most protocols require read before write, so we start the negotiation
      # process here:
      case rv = accept_nonblock(@ssl)
      when :wait_readable, :wait_writable, nil
        return rv
      end
      @need_accept = false
    end
    @ssl.read_nonblock(len, buf, exception: false)
  end

  def trysendio(io, offset, count)
    return 0 if count == 0

    case buf = @ssl_blocked
    when nil
      count = 0x4000 if count > 0x4000
      buf = Thread.current[:yahns_sfbuf] ||= ''.dup
      io.pos = offset
      buf = io.read(count, buf) or return # nil for EOF
      buf = @ssl_blocked = buf.dup
    when Exception
      raise buf
    # when String # just use it as-is
    end

    # call write_nonblock directly since kgio_trywrite allocates
    # an unnecessary string
    len = buf.size
    case rv = @ssl.write_nonblock(buf, exception: false)
    when :wait_readable, :wait_writable
      return rv # do not clear ssl_blocked
    when Integer
      @ssl_blocked = len == rv ? nil : buf.byteslice(rv, len - rv)
    end
    rv
  rescue SystemCallError => e # ECONNRESET/EPIPE
    e.set_backtrace([])
    raise(@ssl_blocked = e)
  end

  def shutdown # we never call this with a how=SHUT_* arg
    @ssl.sysclose
  end

  alias trysendfile trysendio

  def close
    @ssl.close # flushes SSLSocket
    super # IO#close
  end

  if RUBY_VERSION.to_f >= 2.3
    def accept_nonblock(ssl)
      ssl.accept_nonblock(exception: false)
    end
  else
    def accept_nonblock(ssl)
      ssl.accept_nonblock
    rescue IO::WaitReadable
      :wait_readable
    rescue IO::WaitWritable
      :wait_writable
    rescue OpenSSL::SSL::SSLError
      nil
    end
  end
end

debug log:

solving c090083 ...
found c090083 in https://yhbt.net/yahns.git/

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://yhbt.net/yahns.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).