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
| | # -*- 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
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
buf = @ssl_blocked = buf.dup
case rv = @ssl.write_nonblock(buf, exception: false)
when :wait_readable, :wait_writable
return rv # do not clear ssl_blocked
when Integer
rv = len == rv ? nil : buf.byteslice(rv, len - rv)
end
@ssl_blocked = nil
rv
end
def kgio_syssend(buf, flags)
kgio_trywrite(buf)
end
def read_nonblock(len, buf = nil, exception: true)
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: exception)
end
def trysendio(io, offset, count)
return 0 if count == 0
unless buf = @ssl_blocked
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
end
# call write_nonblock directly since kgio_trywrite allocates
# an unnecessary string
case rv = @ssl.write_nonblock(buf, exception: false)
when :wait_readable, :wait_writable
return rv # do not clear ssl_blocked
end
@ssl_blocked = nil
rv
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
|