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.
@@ -0,0 +1,9 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+
4
+ Dir['tasks/**/*.rake'].each { |task| load task }
5
+
6
+ task :default => :compile
7
+
8
+ CLEAN.include ["**/*.o", "**/*.log", "pkg"]
9
+ CLEAN.include ["ext/Makefile", "**/*nonblock*.#{Config::CONFIG['DLEXT']}"]
@@ -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,2 @@
1
+ require 'openssl'
2
+ require File.dirname(__FILE__) + '/../openssl_nonblock'
@@ -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
+