diff options
author | Kazuki Yamaguchi <k@rhe.jp> | 2024-03-22 04:13:14 +0900 |
---|---|---|
committer | Kazuki Yamaguchi <k@rhe.jp> | 2024-03-24 02:44:55 +0900 |
commit | cd12c602b66122476585ff1682af67bf58e29f17 (patch) | |
tree | b913d0979b0330036272e202e7d7ad4ced9bee0e | |
parent | a8caa63729e66f8ad5cb503f0199e099042faac5 (diff) | |
download | ruby-openssl-cd12c602b66122476585ff1682af67bf58e29f17.tar.gz |
bio: add a BIO method that wraps IO-like object
Implement a minimum BIO_METHOD required for SSL/TLS. The underlying
IO-like object must implement the following methods:
- #read_nonblock(len, exception: false)
- #write_nonblock(str, exception: false)
- #flush
The IO-like object is also required to implement several other methods
to function in a later commit in this series.
-rw-r--r-- | ext/openssl/ossl.c | 1 | ||||
-rw-r--r-- | ext/openssl/ossl_bio.c | 187 | ||||
-rw-r--r-- | ext/openssl/ossl_bio.h | 5 |
3 files changed, 193 insertions, 0 deletions
diff --git a/ext/openssl/ossl.c b/ext/openssl/ossl.c index 00eded55..26cb1f96 100644 --- a/ext/openssl/ossl.c +++ b/ext/openssl/ossl.c @@ -1150,6 +1150,7 @@ Init_openssl(void) /* * Init components */ + Init_ossl_bio(); Init_ossl_bn(); Init_ossl_cipher(); Init_ossl_config(); diff --git a/ext/openssl/ossl_bio.c b/ext/openssl/ossl_bio.c index 42833d90..becdf0ef 100644 --- a/ext/openssl/ossl_bio.c +++ b/ext/openssl/ossl_bio.c @@ -40,3 +40,190 @@ ossl_membio2str(BIO *bio) return ret; } + +BIO_METHOD *ossl_bio_meth; +static int bio_state_idx, bio_errinfo_idx; + +static void +bio_save_error(BIO *bio, int state) +{ + VALUE errinfo = Qnil; + if (state) { + errinfo = rb_errinfo(); + if (rb_obj_is_kind_of(errinfo, rb_eException)) + rb_set_errinfo(Qnil); + else + errinfo = Qnil; + } + BIO_set_ex_data(bio, bio_state_idx, (void *)(uintptr_t)state); + BIO_set_ex_data(bio, bio_errinfo_idx, (void *)errinfo); +} + +int +ossl_bio_restore_error(BIO *bio) +{ + int state = (int)(uintptr_t)BIO_get_ex_data(bio, bio_state_idx); + if (!state) + return 0; + + VALUE errinfo = (VALUE)BIO_get_ex_data(bio, bio_errinfo_idx); + BIO_set_ex_data(bio, bio_state_idx, (void *)(uintptr_t)0); + BIO_set_ex_data(bio, bio_errinfo_idx, (void *)Qnil); + if (!NIL_P(errinfo)) + rb_set_errinfo(errinfo); + return state; +} + +struct bwrite_args { + BIO *bio; + const char *data; + size_t dlen; + size_t *written; +}; + +static VALUE +bio_bwrite0(VALUE args) +{ + struct bwrite_args *p = (void *)args; + VALUE io = (VALUE)BIO_get_data(p->bio); + BIO_clear_retry_flags(p->bio); + + VALUE str = rb_str_new_static(p->data, p->dlen); + VALUE kwargs = rb_hash_new(); + rb_hash_aset(kwargs, ID2SYM(rb_intern("exception")), Qfalse); + VALUE funcallargs[] = { str, kwargs }; + VALUE ret = rb_funcallv_public_kw(io, rb_intern("write_nonblock"), + 2, funcallargs, RB_PASS_KEYWORDS); + + if (RB_INTEGER_TYPE_P(ret)) { + *p->written = NUM2SIZET(ret); + return INT2FIX(1); + } + else if (ret == ID2SYM(rb_intern("wait_readable"))) { + BIO_set_retry_read(p->bio); + return INT2FIX(0); + } + else if (ret == ID2SYM(rb_intern("wait_writable"))) { + BIO_set_retry_write(p->bio); + return INT2FIX(0); + } + else { + rb_raise(rb_eTypeError, "write_nonblock must return an Integer, " + ":wait_readable, or :wait_writable"); + } +} + +static int +bio_bwrite(BIO *bio, const char *data, size_t dlen, size_t *written) +{ + struct bwrite_args args = { bio, data, dlen, written }; + int state; + + VALUE ret = rb_protect(bio_bwrite0, (VALUE)&args, &state); + bio_save_error(bio, state); + if (state) + return 0; + return FIX2INT(ret); +} + +struct bread_args { + BIO *bio; + char *data; + size_t dlen; + size_t *readbytes; +}; + +static VALUE +bio_bread0(VALUE args) +{ + struct bread_args *p = (void *)args; + VALUE io = (VALUE)BIO_get_data(p->bio); + BIO_clear_retry_flags(p->bio); + + VALUE kwargs = rb_hash_new(); + rb_hash_aset(kwargs, ID2SYM(rb_intern("exception")), Qfalse); + VALUE funcallargs[] = { SIZET2NUM(p->dlen), kwargs }; + VALUE ret = rb_funcallv_public_kw(io, rb_intern("read_nonblock"), + 2, funcallargs, RB_PASS_KEYWORDS); + + if (RB_TYPE_P(ret, T_STRING)) { + size_t len = (size_t)RSTRING_LEN(ret); + if (len > p->dlen) + rb_raise(rb_eTypeError, "read_nonblock returned too much data"); + memcpy(p->data, RSTRING_PTR(ret), len); + *p->readbytes = len; + return INT2FIX(1); + } + else if (NIL_P(ret)) { + BIO_set_flags(p->bio, BIO_FLAGS_IN_EOF); + return INT2FIX(0); + } + else if (ret == ID2SYM(rb_intern("wait_readable"))) { + BIO_set_retry_read(p->bio); + return INT2FIX(0); + } + else if (ret == ID2SYM(rb_intern("wait_writable"))) { + BIO_set_retry_write(p->bio); + return INT2FIX(0); + } + else { + rb_raise(rb_eTypeError, "write_nonblock must return an Integer, " + ":wait_readable, or :wait_writable"); + } +} + +static int +bio_bread(BIO *bio, char *data, size_t dlen, size_t *readbytes) +{ + struct bread_args args = { bio, data, dlen, readbytes }; + int state; + + VALUE ret = rb_protect(bio_bread0, (VALUE)&args, &state); + bio_save_error(bio, state); + if (state) + return 0; + return FIX2INT(ret); +} + +static VALUE +bio_flush0(VALUE vbio) +{ + VALUE io = (VALUE)BIO_get_data((BIO *)vbio); + return rb_funcallv_public(io, rb_intern("flush"), 0, NULL); +} + +static long +bio_ctrl(BIO *bio, int cmd, long larg, void *parg) +{ + int state; + + switch (cmd) { + case BIO_CTRL_EOF: + return BIO_test_flags(bio, BIO_FLAGS_IN_EOF); + case BIO_CTRL_FLUSH: + rb_protect(bio_flush0, (VALUE)bio, &state); + bio_save_error(bio, state); + return !state; + default: + return 0; + } +} + +void +Init_ossl_bio(void) +{ + if ((bio_state_idx = BIO_get_ex_new_index(0, NULL, NULL, NULL, NULL)) < 0 || + (bio_errinfo_idx = BIO_get_ex_new_index(0, NULL, NULL, NULL, NULL)) < 0) + ossl_raise(eOSSLError, "BIO_get_ex_new_index"); + + ossl_bio_meth = BIO_meth_new(BIO_TYPE_SOURCE_SINK, "Ruby IO-like object"); + if (!ossl_bio_meth) + ossl_raise(eOSSLError, "BIO_meth_new"); + if (!BIO_meth_set_write_ex(ossl_bio_meth, bio_bwrite) || + !BIO_meth_set_read_ex(ossl_bio_meth, bio_bread) || + !BIO_meth_set_ctrl(ossl_bio_meth, bio_ctrl)) { + BIO_meth_free(ossl_bio_meth); + ossl_bio_meth = NULL; + ossl_raise(eOSSLError, "BIO_meth_set_*"); + } +} diff --git a/ext/openssl/ossl_bio.h b/ext/openssl/ossl_bio.h index da68c5e5..e0ef24f5 100644 --- a/ext/openssl/ossl_bio.h +++ b/ext/openssl/ossl_bio.h @@ -13,4 +13,9 @@ BIO *ossl_obj2bio(volatile VALUE *); VALUE ossl_membio2str(BIO*); +extern BIO_METHOD *ossl_bio_meth; +int ossl_bio_restore_error(BIO *bio); + +void Init_ossl_bio(void); + #endif |