openssl-nonblock 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+