openssl-nonblock 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +9 -0
- data/ext/extconf.rb +19 -0
- data/ext/openssl_nonblock.c +330 -0
- data/lib/openssl/nonblock.rb +2 -0
- data/openssl-nonblock.gemspec +22 -0
- metadata +57 -0
data/Rakefile
ADDED
data/ext/extconf.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
dir_config("openssl_nonblock")
|
4
|
+
have_library("c", "main")
|
5
|
+
|
6
|
+
if have_header('openssl/ssl.h')
|
7
|
+
$LIBS << '-lssl -lcrypto'
|
8
|
+
else
|
9
|
+
STDERR.puts("*** Error: OpenSSL header files are required to build openssl-nonblock")
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
|
13
|
+
if have_func('rb_str_set_len')
|
14
|
+
$defs << '-DHAVE_RB_STR_SET_LEN'
|
15
|
+
end
|
16
|
+
|
17
|
+
$defs << "-DRUBY_VERSION_CODE=#{RUBY_VERSION.gsub(/\D/, '')}"
|
18
|
+
|
19
|
+
create_makefile("openssl_nonblock")
|
@@ -0,0 +1,330 @@
|
|
1
|
+
/*
|
2
|
+
* Ruby OpenSSL C extension with non-blocking I/O support
|
3
|
+
* Copyright (C) 2008-09 Tony Arcieri
|
4
|
+
*
|
5
|
+
* Includes portions from the 'OpenSSL for Ruby' project
|
6
|
+
* Copyright (C) 2000-2002 GOTOU Yuuzou <gotoyuzo@notwork.org>
|
7
|
+
* Copyright (C) 2001-2002 Michal Rokos <m.rokos@sh.cvut.cz>
|
8
|
+
* Copyright (C) 2001-2007 Technorama Ltd. <oss-ruby@technorama.net>
|
9
|
+
* You may redistribute this under the terms of the Ruby license.
|
10
|
+
* See LICENSE for details
|
11
|
+
*/
|
12
|
+
|
13
|
+
#include "ruby.h"
|
14
|
+
#include "rubyio.h"
|
15
|
+
|
16
|
+
#include <openssl/ssl.h>
|
17
|
+
|
18
|
+
static VALUE mOSSL = Qnil;
|
19
|
+
static VALUE eOSSLError = Qnil;
|
20
|
+
|
21
|
+
static VALUE mSSL = Qnil;
|
22
|
+
static VALUE cSSLSocket = Qnil;
|
23
|
+
static VALUE eSSLError = Qnil;
|
24
|
+
|
25
|
+
static VALUE eReadAgain = Qnil;
|
26
|
+
static VALUE eWriteAgain = Qnil;
|
27
|
+
|
28
|
+
static VALUE ossl_nonblock_connect_nonblock(VALUE self);
|
29
|
+
static VALUE ossl_nonblock_accept_nonblock(VALUE self);
|
30
|
+
static VALUE ossl_nonblock_ssl_setup(VALUE self);
|
31
|
+
static VALUE ossl_nonblock_ssl_setup_check(VALUE dummy, VALUE error_info);
|
32
|
+
static VALUE ossl_nonblock_start_ssl(VALUE self, int (*func)(), const char *funcname);
|
33
|
+
|
34
|
+
static VALUE ossl_nonblock_read_nonblock(int argc, VALUE *argv, VALUE self);
|
35
|
+
static VALUE ossl_nonblock_write_nonblock(VALUE self, VALUE str);
|
36
|
+
|
37
|
+
/*
|
38
|
+
* Time to monkey patch some C code!
|
39
|
+
*/
|
40
|
+
|
41
|
+
/* Ruby 1.8 leaves us no recourse but to commonly couple to the OpenSSL native
|
42
|
+
extension through externs. Ugh */
|
43
|
+
#if RUBY_VERSION_CODE < 190
|
44
|
+
|
45
|
+
/* Externs from Ruby's OpenSSL native extension , in ossl_ssl.c*/
|
46
|
+
extern int ossl_ssl_ex_vcb_idx;
|
47
|
+
extern int ossl_ssl_ex_store_p;
|
48
|
+
extern int ossl_ssl_ex_ptr_idx;
|
49
|
+
extern int ossl_ssl_ex_client_cert_cb_idx;
|
50
|
+
extern int ossl_ssl_ex_tmp_dh_callback_idx;
|
51
|
+
|
52
|
+
/* #defines shamelessly copied and pasted from ossl_ssl.c */
|
53
|
+
#define ossl_ssl_get_io(o) rb_iv_get((o),"@io")
|
54
|
+
#define ossl_ssl_get_ctx(o) rb_iv_get((o),"@context")
|
55
|
+
#define ossl_sslctx_get_verify_cb(o) rb_iv_get((o),"@verify_callback")
|
56
|
+
#define ossl_sslctx_get_client_cert_cb(o) rb_iv_get((o),"@client_cert_cb")
|
57
|
+
#define ossl_sslctx_get_tmp_dh_cb(o) rb_iv_get((o),"@tmp_dh_callback")
|
58
|
+
|
59
|
+
#ifdef _WIN32
|
60
|
+
# define TO_SOCKET(s) _get_osfhandle(s)
|
61
|
+
#else
|
62
|
+
# define TO_SOCKET(s) s
|
63
|
+
#endif
|
64
|
+
|
65
|
+
#endif /* RUBY_VERSION_CODE < 190 */
|
66
|
+
|
67
|
+
#ifndef HAVE_RB_STR_SET_LEN
|
68
|
+
static void rb_str_set_len(VALUE str, long len)
|
69
|
+
{
|
70
|
+
RSTRING(str)->len = len;
|
71
|
+
RSTRING(str)->ptr[len] = '\0';
|
72
|
+
}
|
73
|
+
#endif /* HAVE_RB_STR_SET_LEN */
|
74
|
+
|
75
|
+
void Init_openssl_nonblock()
|
76
|
+
{
|
77
|
+
mOSSL = rb_define_module("OpenSSL");
|
78
|
+
eOSSLError = rb_define_class_under(mOSSL, "OpenSSLError", rb_eStandardError);
|
79
|
+
|
80
|
+
mSSL = rb_define_module_under(mOSSL, "SSL");
|
81
|
+
cSSLSocket = rb_define_class_under(mSSL, "SSLSocket", rb_cObject);
|
82
|
+
eSSLError = rb_define_class_under(mSSL, "SSLError", eOSSLError);
|
83
|
+
|
84
|
+
eReadAgain = rb_define_class_under(mSSL, "ReadAgain", rb_eStandardError);
|
85
|
+
eWriteAgain = rb_define_class_under(mSSL, "WriteAgain", rb_eStandardError);
|
86
|
+
|
87
|
+
rb_define_method(cSSLSocket, "connect_nonblock", ossl_nonblock_connect_nonblock, 0);
|
88
|
+
rb_define_method(cSSLSocket, "accept_nonblock", ossl_nonblock_accept_nonblock, 0);
|
89
|
+
|
90
|
+
rb_define_method(cSSLSocket, "read_nonblock", ossl_nonblock_read_nonblock, -1);
|
91
|
+
rb_define_method(cSSLSocket, "write_nonblock", ossl_nonblock_write_nonblock, 1);
|
92
|
+
}
|
93
|
+
|
94
|
+
#if RUBY_VERSION_CODE < 190
|
95
|
+
/* SSL initialization for Ruby 1.8 */
|
96
|
+
static VALUE
|
97
|
+
ossl_nonblock_ssl_setup(VALUE self)
|
98
|
+
{
|
99
|
+
VALUE io, v_ctx, cb;
|
100
|
+
SSL_CTX *ctx;
|
101
|
+
SSL *ssl;
|
102
|
+
OpenFile *fptr;
|
103
|
+
|
104
|
+
Data_Get_Struct(self, SSL, ssl);
|
105
|
+
if(!ssl) {
|
106
|
+
v_ctx = ossl_ssl_get_ctx(self);
|
107
|
+
Data_Get_Struct(v_ctx, SSL_CTX, ctx);
|
108
|
+
|
109
|
+
ssl = SSL_new(ctx);
|
110
|
+
if (!ssl) {
|
111
|
+
ossl_raise(eSSLError, "SSL_new:");
|
112
|
+
}
|
113
|
+
DATA_PTR(self) = ssl;
|
114
|
+
|
115
|
+
io = ossl_ssl_get_io(self);
|
116
|
+
GetOpenFile(io, fptr);
|
117
|
+
rb_io_check_readable(fptr);
|
118
|
+
rb_io_check_writable(fptr);
|
119
|
+
SSL_set_fd(ssl, TO_SOCKET(fileno(fptr->f)));
|
120
|
+
SSL_set_ex_data(ssl, ossl_ssl_ex_ptr_idx, (void*)self);
|
121
|
+
cb = ossl_sslctx_get_verify_cb(v_ctx);
|
122
|
+
SSL_set_ex_data(ssl, ossl_ssl_ex_vcb_idx, (void*)cb);
|
123
|
+
cb = ossl_sslctx_get_client_cert_cb(v_ctx);
|
124
|
+
SSL_set_ex_data(ssl, ossl_ssl_ex_client_cert_cb_idx, (void*)cb);
|
125
|
+
cb = ossl_sslctx_get_tmp_dh_cb(v_ctx);
|
126
|
+
SSL_set_ex_data(ssl, ossl_ssl_ex_tmp_dh_callback_idx, (void*)cb);
|
127
|
+
}
|
128
|
+
|
129
|
+
return Qtrue;
|
130
|
+
}
|
131
|
+
#endif
|
132
|
+
|
133
|
+
#if RUBY_VERSION_CODE >= 190
|
134
|
+
/* Slightly less insane SSL setup for Ruby 1.9 */
|
135
|
+
static VALUE
|
136
|
+
ossl_nonblock_ssl_setup(VALUE self)
|
137
|
+
{
|
138
|
+
/*
|
139
|
+
* DANGER WILL ROBINSON! CRAZY HACKS AHEAD!
|
140
|
+
*
|
141
|
+
* Before we connect or accept we need to call the ossl_ssl_setup() function
|
142
|
+
* in ossl_ssl.c. For whatever reason this isn't called in
|
143
|
+
* SSLSocket#initialize but is instead called directly from #connect and
|
144
|
+
* #accept.
|
145
|
+
*
|
146
|
+
* To make things even more awesome, it's a static function, so we can't
|
147
|
+
* call it directly. However, we can call it indirectly...
|
148
|
+
*
|
149
|
+
* There's one other method within ossl_ssl.c which calls ossl_ssl_setup(),
|
150
|
+
* and that's #session=. I'm not sure why this calls it, but its author
|
151
|
+
* left this comment to help us figure out:
|
152
|
+
*
|
153
|
+
* "why is ossl_ssl_setup delayed?"
|
154
|
+
*
|
155
|
+
* Why indeed, guy... why indeed. Well, his function calls ossl_ssl_setup(),
|
156
|
+
* then typechecks its arguments, which means if we pass a bogus one it will
|
157
|
+
* happily setup SSL for us, then raise an exception. So we can catch
|
158
|
+
* that exception and be on our merry way.
|
159
|
+
*
|
160
|
+
* I don't even know what this method is supposed to do. It appears related
|
161
|
+
* to OpenSSL::SSL::Session, which is linked into the OpenSSL library but
|
162
|
+
* never initialized, probably because it's buggy. Nevertheless, the
|
163
|
+
* #session= method is still available to use for this hack. Awesome!
|
164
|
+
*/
|
165
|
+
rb_funcall(self, rb_intern("session="), 1, Qnil);
|
166
|
+
}
|
167
|
+
#endif
|
168
|
+
|
169
|
+
/* Ensure the error raised by calling #session= with a dummy argument is
|
170
|
+
* the one we were expecting */
|
171
|
+
static VALUE
|
172
|
+
ossl_nonblock_ssl_setup_check(VALUE dummy, VALUE err)
|
173
|
+
{
|
174
|
+
return Qnil;
|
175
|
+
}
|
176
|
+
|
177
|
+
/*
|
178
|
+
* call-seq:
|
179
|
+
* ssl.connect_nonblock => self
|
180
|
+
*/
|
181
|
+
static VALUE
|
182
|
+
ossl_nonblock_connect_nonblock(VALUE self)
|
183
|
+
{
|
184
|
+
#if RUBY_VERSION_CODE >= 190
|
185
|
+
rb_rescue(ossl_nonblock_ssl_setup, self, ossl_nonblock_ssl_setup_check, Qnil);
|
186
|
+
#else
|
187
|
+
ossl_nonblock_ssl_setup(self);
|
188
|
+
#endif
|
189
|
+
|
190
|
+
return ossl_nonblock_start_ssl(self, SSL_connect, "SSL_connect");
|
191
|
+
}
|
192
|
+
|
193
|
+
/*
|
194
|
+
* call-seq:
|
195
|
+
* ssl.accept_nonblock => self
|
196
|
+
*/
|
197
|
+
static VALUE
|
198
|
+
ossl_nonblock_accept_nonblock(VALUE self)
|
199
|
+
{
|
200
|
+
#if RUBY_VERSION_CODE >= 190
|
201
|
+
rb_rescue(ossl_nonblock_ssl_setup, self, 0, 0);
|
202
|
+
#else
|
203
|
+
ossl_nonblock_ssl_setup(self);
|
204
|
+
#endif
|
205
|
+
|
206
|
+
return ossl_nonblock_start_ssl(self, SSL_accept, "SSL_accept");
|
207
|
+
}
|
208
|
+
|
209
|
+
static VALUE
|
210
|
+
ossl_nonblock_start_ssl(VALUE self, int (*func)(), const char *funcname)
|
211
|
+
{
|
212
|
+
SSL *ssl;
|
213
|
+
int ret, ret2;
|
214
|
+
|
215
|
+
Data_Get_Struct(self, SSL, ssl);
|
216
|
+
if(!ssl)
|
217
|
+
rb_raise(rb_eRuntimeError, "SSL never initialized");
|
218
|
+
|
219
|
+
if((ret = func(ssl)) <= 0) {
|
220
|
+
switch((ret2 = SSL_get_error(ssl, ret))) {
|
221
|
+
case SSL_ERROR_WANT_WRITE:
|
222
|
+
rb_raise(eWriteAgain, "write again");
|
223
|
+
case SSL_ERROR_WANT_READ:
|
224
|
+
rb_raise(eReadAgain, "read again");
|
225
|
+
case SSL_ERROR_SYSCALL:
|
226
|
+
if (errno) rb_sys_fail(funcname);
|
227
|
+
rb_raise(eSSLError, "%s SYSCALL returned=%d errno=%d state=%s",
|
228
|
+
funcname, ret2, errno, SSL_state_string_long(ssl)
|
229
|
+
);
|
230
|
+
default:
|
231
|
+
rb_raise(eSSLError, "%s returned=%d errno=%d state=%s",
|
232
|
+
funcname, ret2, errno, SSL_state_string_long(ssl)
|
233
|
+
);
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
return self;
|
238
|
+
}
|
239
|
+
|
240
|
+
/*
|
241
|
+
* call-seq:
|
242
|
+
* ssl.read_nonblock(length) => string
|
243
|
+
* ssl.read_nonblock(length, buffer) => buffer
|
244
|
+
*
|
245
|
+
* === Parameters
|
246
|
+
* * +length+ is a positive integer.
|
247
|
+
* * +buffer+ is a string used to store the result.
|
248
|
+
*/
|
249
|
+
static VALUE
|
250
|
+
ossl_nonblock_read_nonblock(int argc, VALUE *argv, VALUE self)
|
251
|
+
{
|
252
|
+
SSL *ssl;
|
253
|
+
int ilen, nread = 0;
|
254
|
+
VALUE len, str;
|
255
|
+
|
256
|
+
rb_scan_args(argc, argv, "11", &len, &str);
|
257
|
+
ilen = NUM2INT(len);
|
258
|
+
|
259
|
+
if(NIL_P(str))
|
260
|
+
str = rb_str_new(0, ilen);
|
261
|
+
else {
|
262
|
+
StringValue(str);
|
263
|
+
rb_str_modify(str);
|
264
|
+
rb_str_resize(str, ilen);
|
265
|
+
}
|
266
|
+
|
267
|
+
if(ilen == 0) return str;
|
268
|
+
|
269
|
+
Data_Get_Struct(self, SSL, ssl);
|
270
|
+
|
271
|
+
if (ssl) {
|
272
|
+
nread = SSL_read(ssl, RSTRING_PTR(str), RSTRING_LEN(str));
|
273
|
+
switch(SSL_get_error(ssl, nread)){
|
274
|
+
case SSL_ERROR_NONE:
|
275
|
+
goto end;
|
276
|
+
case SSL_ERROR_ZERO_RETURN:
|
277
|
+
rb_eof_error();
|
278
|
+
case SSL_ERROR_WANT_WRITE:
|
279
|
+
rb_raise(eWriteAgain, "write again");
|
280
|
+
case SSL_ERROR_WANT_READ:
|
281
|
+
rb_raise(eReadAgain, "read again");
|
282
|
+
case SSL_ERROR_SYSCALL:
|
283
|
+
if(ERR_peek_error() == 0 && nread == 0) rb_eof_error();
|
284
|
+
rb_sys_fail(0);
|
285
|
+
default:
|
286
|
+
rb_raise(eSSLError, "SSL_read:");
|
287
|
+
}
|
288
|
+
} else
|
289
|
+
rb_raise(rb_eRuntimeError, "SSL session is not started yet.");
|
290
|
+
|
291
|
+
end:
|
292
|
+
rb_str_set_len(str, nread);
|
293
|
+
OBJ_TAINT(str);
|
294
|
+
|
295
|
+
return str;
|
296
|
+
}
|
297
|
+
|
298
|
+
/*
|
299
|
+
* call-seq:
|
300
|
+
* ssl.write_nonblock(string) => integer
|
301
|
+
*/
|
302
|
+
static VALUE
|
303
|
+
ossl_nonblock_write_nonblock(VALUE self, VALUE str)
|
304
|
+
{
|
305
|
+
SSL *ssl;
|
306
|
+
int nwrite = 0;
|
307
|
+
|
308
|
+
StringValue(str);
|
309
|
+
Data_Get_Struct(self, SSL, ssl);
|
310
|
+
|
311
|
+
if (ssl) {
|
312
|
+
nwrite = SSL_write(ssl, RSTRING_PTR(str), RSTRING_LEN(str));
|
313
|
+
switch(SSL_get_error(ssl, nwrite)){
|
314
|
+
case SSL_ERROR_NONE:
|
315
|
+
goto end;
|
316
|
+
case SSL_ERROR_WANT_WRITE:
|
317
|
+
rb_raise(eWriteAgain, "write again");
|
318
|
+
case SSL_ERROR_WANT_READ:
|
319
|
+
rb_raise(eReadAgain, "read again");
|
320
|
+
case SSL_ERROR_SYSCALL:
|
321
|
+
if (errno) rb_sys_fail(0);
|
322
|
+
default:
|
323
|
+
rb_raise(eSSLError, "SSL_write:");
|
324
|
+
}
|
325
|
+
} else
|
326
|
+
rb_raise(rb_eRuntimeError, "SSL session is not started yet.");
|
327
|
+
|
328
|
+
end:
|
329
|
+
return INT2NUM(nwrite);
|
330
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
GEMSPEC = Gem::Specification.new do |s|
|
4
|
+
s.name = "openssl-nonblock"
|
5
|
+
s.version = "0.1.0"
|
6
|
+
s.authors = "Tony Arcieri"
|
7
|
+
s.email = "tony@medioh.com"
|
8
|
+
s.date = "2009-02-06"
|
9
|
+
s.summary = "Non-blocking support for Ruby OpenSSL"
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.required_ruby_version = '>= 1.8.6'
|
12
|
+
|
13
|
+
# Gem contents
|
14
|
+
s.files = Dir.glob("{lib,ext}/**/*.{rb,c,h}") + ['Rakefile', 'openssl-nonblock.gemspec']
|
15
|
+
|
16
|
+
# RubyForge info
|
17
|
+
s.homepage = "http://rev.rubyforge.org"
|
18
|
+
s.rubyforge_project = "rev"
|
19
|
+
|
20
|
+
# Extensions
|
21
|
+
s.extensions = %w[ext/extconf.rb]
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: openssl-nonblock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tony Arcieri
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-02-06 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: tony@medioh.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions:
|
21
|
+
- ext/extconf.rb
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- lib/openssl/nonblock.rb
|
26
|
+
- ext/extconf.rb
|
27
|
+
- ext/openssl_nonblock.c
|
28
|
+
- Rakefile
|
29
|
+
- openssl-nonblock.gemspec
|
30
|
+
has_rdoc: false
|
31
|
+
homepage: http://rev.rubyforge.org
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 1.8.6
|
42
|
+
version:
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0"
|
48
|
+
version:
|
49
|
+
requirements: []
|
50
|
+
|
51
|
+
rubyforge_project: rev
|
52
|
+
rubygems_version: 1.3.1
|
53
|
+
signing_key:
|
54
|
+
specification_version: 2
|
55
|
+
summary: Non-blocking support for Ruby OpenSSL
|
56
|
+
test_files: []
|
57
|
+
|