$NetBSD: patch-dq,v 1.4 2016/03/24 16:30:11 taca Exp $

* r18172: suppress warnings.
* r20494: (ossl_ssl_read_nonblock): OpenSSL::SSL::SSLSocket should implement
          read_nonblock. a patch from Aaron Patterson in [ruby-core:20277].
          fix: #814 [ruby-core:20241]
* r21772: Test for Server Name Indication support.
* r23008: revert incomplete read_nonblock implemenatation.
* r26835: backport fixes in 1.9.
* r26838: backport the commit from trunk.
* Constify (some cases are depends on OpenSSL's version).
* Only enable SSLv3 methods if library provides support.

--- ext/openssl/ossl_ssl.c.orig	2012-02-08 06:09:40.000000000 +0000
+++ ext/openssl/ossl_ssl.c
@@ -26,6 +26,12 @@
 #  define TO_SOCKET(s) s
 #endif
 
+#if OPENSSL_VERSION_NUMBER >= 0x00909000L
+#define OSSL_CONST	const
+#else
+#define OSSL_CONST
+#endif
+
 VALUE mSSL;
 VALUE eSSLError;
 VALUE cSSLContext;
@@ -69,6 +75,9 @@ static const char *ossl_sslctx_attrs[] =
     "verify_callback", "options", "cert_store", "extra_chain_cert",
     "client_cert_cb", "tmp_dh_callback", "session_id_context",
     "session_get_cb", "session_new_cb", "session_remove_cb",
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+    "servername_cb",
+#endif
 };
 
 #define ossl_ssl_get_io(o)           rb_iv_get((o),"@io")
@@ -86,7 +95,12 @@ static const char *ossl_sslctx_attrs[] =
 #define ossl_ssl_set_tmp_dh(o,v)     rb_iv_set((o),"@tmp_dh",(v))
 
 static const char *ossl_ssl_attr_readers[] = { "io", "context", };
-static const char *ossl_ssl_attrs[] = { "sync_close", };
+static const char *ossl_ssl_attrs[] = {
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+    "hostname",
+#endif
+    "sync_close", 
+};
 
 ID ID_callback_state;
 
@@ -95,21 +109,24 @@ ID ID_callback_state;
  */
 struct {
     const char *name;
-    SSL_METHOD *(*func)(void);
+    OSSL_CONST SSL_METHOD *(*func)(void);
 } ossl_ssl_method_tab[] = {
 #define OSSL_SSL_METHOD_ENTRY(name) { #name, name##_method }
     OSSL_SSL_METHOD_ENTRY(TLSv1),
     OSSL_SSL_METHOD_ENTRY(TLSv1_server),
     OSSL_SSL_METHOD_ENTRY(TLSv1_client),
-#if defined(HAVE_SSLV2_METHOD) && defined(HAVE_SSLV2_SERVER_METHOD) && \
+#if !defined(OPENSSL_NO_SSL2) && defined(HAVE_SSLV2_METHOD) && defined(HAVE_SSLV2_SERVER_METHOD) && \
         defined(HAVE_SSLV2_CLIENT_METHOD)	
     OSSL_SSL_METHOD_ENTRY(SSLv2),
     OSSL_SSL_METHOD_ENTRY(SSLv2_server),
     OSSL_SSL_METHOD_ENTRY(SSLv2_client),
 #endif
+#if defined(HAVE_SSLV3_METHOD) && defined(HAVE_SSLV3_SERVER_METHOD) && \
+	defined(HAVE_SSLV3_CLIENT_METHOD)
     OSSL_SSL_METHOD_ENTRY(SSLv3),
     OSSL_SSL_METHOD_ENTRY(SSLv3_server),
     OSSL_SSL_METHOD_ENTRY(SSLv3_client),
+#endif
     OSSL_SSL_METHOD_ENTRY(SSLv23),
     OSSL_SSL_METHOD_ENTRY(SSLv23_server),
     OSSL_SSL_METHOD_ENTRY(SSLv23_client),
@@ -146,7 +163,7 @@ ossl_sslctx_s_alloc(VALUE klass)
 static VALUE
 ossl_sslctx_set_ssl_version(VALUE self, VALUE ssl_method)
 {
-    SSL_METHOD *method = NULL;
+    OSSL_CONST SSL_METHOD *method = NULL;
     const char *s;
     int i;
 
@@ -299,7 +316,7 @@ ossl_ssl_verify_callback(int preverify_o
 static VALUE
 ossl_call_session_get_cb(VALUE ary)
 {
-    VALUE ssl_obj, sslctx_obj, cb, ret;
+    VALUE ssl_obj, sslctx_obj, cb;
     
     Check_Type(ary, T_ARRAY);
     ssl_obj = rb_ary_entry(ary, 0);
@@ -327,7 +344,7 @@ ossl_sslctx_session_get_cb(SSL *ssl, uns
     ssl_obj = (VALUE)ptr;
     ary = rb_ary_new2(2);
     rb_ary_push(ary, ssl_obj);
-    rb_ary_push(ary, rb_str_new(buf, len));
+    rb_ary_push(ary, rb_str_new((const char *)buf, len));
 
     ret_obj = rb_protect((VALUE(*)_((VALUE)))ossl_call_session_get_cb, ary, &state);
     if (state) {
@@ -346,7 +363,7 @@ ossl_sslctx_session_get_cb(SSL *ssl, uns
 static VALUE
 ossl_call_session_new_cb(VALUE ary)
 {
-    VALUE ssl_obj, sslctx_obj, cb, ret;
+    VALUE ssl_obj, sslctx_obj, cb;
     
     Check_Type(ary, T_ARRAY);
     ssl_obj = rb_ary_entry(ary, 0);
@@ -389,10 +406,11 @@ ossl_sslctx_session_new_cb(SSL *ssl, SSL
     return RTEST(ret_obj) ? 1 : 0;
 }
 
+#if 0				/* unused */
 static VALUE
 ossl_call_session_remove_cb(VALUE ary)
 {
-    VALUE sslctx_obj, cb, ret;
+    VALUE sslctx_obj, cb;
     
     Check_Type(ary, T_ARRAY);
     sslctx_obj = rb_ary_entry(ary, 0);
@@ -402,6 +420,7 @@ ossl_call_session_remove_cb(VALUE ary)
 
     return rb_funcall(cb, rb_intern("call"), 1, ary);
 }
+#endif
 
 static void
 ossl_sslctx_session_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess)
@@ -448,6 +467,66 @@ ossl_sslctx_add_extra_chain_cert_i(VALUE
     return i;
 }
 
+static VALUE ossl_sslctx_setup(VALUE self);
+
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+static VALUE
+ossl_call_servername_cb(VALUE ary)
+{
+    VALUE ssl_obj, sslctx_obj, cb, ret_obj;
+
+    Check_Type(ary, T_ARRAY);
+    ssl_obj = rb_ary_entry(ary, 0);
+
+    sslctx_obj = rb_iv_get(ssl_obj, "@context");
+    if (NIL_P(sslctx_obj)) return Qnil;
+    cb = rb_iv_get(sslctx_obj, "@servername_cb");
+    if (NIL_P(cb)) return Qnil;
+
+    ret_obj = rb_funcall(cb, rb_intern("call"), 1, ary);
+    if (rb_obj_is_kind_of(ret_obj, cSSLContext)) {
+        SSL *ssl;
+        SSL_CTX *ctx2;
+
+        ossl_sslctx_setup(ret_obj);
+        Data_Get_Struct(ssl_obj, SSL, ssl);
+        Data_Get_Struct(ret_obj, SSL_CTX, ctx2);
+        SSL_set_SSL_CTX(ssl, ctx2);
+    } else if (!NIL_P(ret_obj)) {
+            rb_raise(rb_eArgError, "servername_cb must return an OpenSSL::SSL::SSLContext object or nil");
+    }
+
+    return ret_obj;
+}
+
+static int
+ssl_servername_cb(SSL *ssl, int *ad, void *arg)
+{
+    VALUE ary, ssl_obj, ret_obj;
+    void *ptr;
+    int state = 0;
+    const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
+
+    if (!servername)
+        return SSL_TLSEXT_ERR_OK;
+
+    if ((ptr = SSL_get_ex_data(ssl, ossl_ssl_ex_ptr_idx)) == NULL)
+    	return SSL_TLSEXT_ERR_ALERT_FATAL;
+    ssl_obj = (VALUE)ptr;
+    ary = rb_ary_new2(2);
+    rb_ary_push(ary, ssl_obj);
+    rb_ary_push(ary, rb_str_new2(servername));
+
+    ret_obj = rb_protect((VALUE(*)_((VALUE)))ossl_call_servername_cb, ary, &state);
+    if (state) {
+        rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(state));
+        return SSL_TLSEXT_ERR_ALERT_FATAL;
+    }
+
+    return SSL_TLSEXT_ERR_OK;
+}
+#endif
+
 /*
  * call-seq:
  *    ctx.setup => Qtrue # first time
@@ -569,7 +648,7 @@ ossl_sslctx_setup(VALUE self)
     val = ossl_sslctx_get_sess_id_ctx(self);
     if (!NIL_P(val)){
 	StringValue(val);
-	if (!SSL_CTX_set_session_id_context(ctx, RSTRING_PTR(val),
+	if (!SSL_CTX_set_session_id_context(ctx, (unsigned char *)RSTRING_PTR(val),
 					    RSTRING_LEN(val))){
 	    ossl_raise(eSSLError, "SSL_CTX_set_session_id_context:");
 	}
@@ -587,11 +666,20 @@ ossl_sslctx_setup(VALUE self)
 	SSL_CTX_sess_set_remove_cb(ctx, ossl_sslctx_session_remove_cb);
 	OSSL_Debug("SSL SESSION remove callback added");
     }
+
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+    val = rb_iv_get(self, "@servername_cb");
+    if (!NIL_P(val)) {
+        SSL_CTX_set_tlsext_servername_callback(ctx, ssl_servername_cb);
+	OSSL_Debug("SSL TLSEXT servername callback added");
+    }
+#endif
+
     return Qtrue;
 }
 
 static VALUE
-ossl_ssl_cipher_to_ary(SSL_CIPHER *cipher)
+ossl_ssl_cipher_to_ary(OSSL_CONST SSL_CIPHER *cipher)
 {
     VALUE ary;
     int bits, alg_bits;
@@ -615,7 +703,7 @@ ossl_sslctx_get_ciphers(VALUE self)
 {
     SSL_CTX *ctx;
     STACK_OF(SSL_CIPHER) *ciphers;
-    SSL_CIPHER *cipher;
+    OSSL_CONST SSL_CIPHER *cipher;
     VALUE ary;
     int i, num;
 
@@ -629,10 +717,10 @@ ossl_sslctx_get_ciphers(VALUE self)
     if (!ciphers)
         return rb_ary_new();
 
-    num = sk_num((STACK*)ciphers);
+    num = sk_SSL_CIPHER_num(ciphers);
     ary = rb_ary_new2(num);
     for(i = 0; i < num; i++){
-        cipher = (SSL_CIPHER*)sk_value((STACK*)ciphers, i);
+        cipher = sk_SSL_CIPHER_value(ciphers, i);
         rb_ary_push(ary, ossl_ssl_cipher_to_ary(cipher));
     }
     return ary;
@@ -821,7 +909,6 @@ ossl_sslctx_flush_sessions(int argc, VAL
     VALUE arg1;
     SSL_CTX *ctx;
     time_t tm = 0;
-    int cb_state;
 
     rb_scan_args(argc, argv, "01", &arg1);
 
@@ -895,6 +982,8 @@ ossl_ssl_initialize(int argc, VALUE *arg
     ossl_sslctx_setup(ctx);
     rb_call_super(0, 0);
 
+    rb_iv_set(self, "@hostname", Qnil);
+
     return self;
 }
 
@@ -908,6 +997,10 @@ ossl_ssl_setup(VALUE self)
 
     Data_Get_Struct(self, SSL, ssl);
     if(!ssl){
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+	VALUE hostname = rb_iv_get(self, "@hostname");
+#endif
+
         v_ctx = ossl_ssl_get_ctx(self);
         Data_Get_Struct(v_ctx, SSL_CTX, ctx);
 
@@ -917,6 +1010,12 @@ ossl_ssl_setup(VALUE self)
         }
         DATA_PTR(self) = ssl;
 
+#ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME
+        if (!NIL_P(hostname)) {
+           if (SSL_set_tlsext_host_name(ssl, StringValuePtr(hostname)) != 1)
+               ossl_raise(eSSLError, "SSL_set_tlsext_host_name:");
+        }
+#endif
         io = ossl_ssl_get_io(self);
         GetOpenFile(io, fptr);
         rb_io_check_readable(fptr);
@@ -953,7 +1052,15 @@ ossl_start_ssl(VALUE self, int (*func)()
     Data_Get_Struct(self, SSL, ssl);
     GetOpenFile(ossl_ssl_get_io(self), fptr);
     for(;;){
-	if((ret = func(ssl)) > 0) break;
+	ret = func(ssl);
+
+	cb_state = rb_ivar_get(self, ID_callback_state);
+        if (!NIL_P(cb_state))
+            rb_jump_tag(NUM2INT(cb_state));
+
+	if (ret > 0)
+	    break;
+
 	switch((ret2 = ssl_get_error(ssl, ret))){
 	case SSL_ERROR_WANT_WRITE:
             rb_io_wait_writable(FPTR_TO_FD(fptr));
@@ -969,10 +1076,6 @@ ossl_start_ssl(VALUE self, int (*func)()
 	}
     }
 
-    cb_state = rb_ivar_get(self, ID_callback_state);
-    if (!NIL_P(cb_state))
-        rb_jump_tag(NUM2INT(cb_state));
-
     return self;
 }
 
@@ -1010,6 +1113,72 @@ ossl_ssl_accept(VALUE self)
 static VALUE
 ossl_ssl_read(int argc, VALUE *argv, VALUE self)
 {
+  SSL *ssl;
+  int ilen, nread = 0;
+  VALUE len, str;
+  rb_io_t *fptr;
+
+  rb_scan_args(argc, argv, "11", &len, &str);
+  ilen = NUM2INT(len);
+  if(NIL_P(str)) str = rb_str_new(0, ilen);
+  else{
+    StringValue(str);
+    rb_str_modify(str);
+    rb_str_resize(str, ilen);
+  }
+  if(ilen == 0) return str;
+
+  Data_Get_Struct(self, SSL, ssl);
+  GetOpenFile(ossl_ssl_get_io(self), fptr);
+  if (ssl) {
+    if(SSL_pending(ssl) <= 0)
+      rb_thread_wait_fd(FPTR_TO_FD(fptr));
+    for (;;){
+      nread = SSL_read(ssl, RSTRING_PTR(str), RSTRING_LEN(str));
+      switch(SSL_get_error(ssl, nread)){
+        case SSL_ERROR_NONE:
+          goto end;
+        case SSL_ERROR_ZERO_RETURN:
+          rb_eof_error();
+        case SSL_ERROR_WANT_WRITE:
+          rb_io_wait_writable(FPTR_TO_FD(fptr));
+          continue;
+        case SSL_ERROR_WANT_READ:
+	  rb_io_wait_readable(FPTR_TO_FD(fptr));
+          continue;
+        case SSL_ERROR_SYSCALL:
+          if(ERR_peek_error() == 0 && nread == 0) rb_eof_error();
+          rb_sys_fail(0);
+        default:
+          ossl_raise(eSSLError, "SSL_read:");
+      }
+    }
+  }
+  else {
+    ID id_sysread = rb_intern("sysread");
+    rb_warning("SSL session is not started yet.");
+    return rb_funcall(ossl_ssl_get_io(self), id_sysread, 2, len, str);
+  }
+
+end:
+  rb_str_set_len(str, nread);
+  OBJ_TAINT(str);
+
+  return str;
+}
+
+/*
+ * call-seq:
+ *    ssl.read_nonblock(length) => string
+ *    ssl.read_nonblock(length, buffer) => buffer
+ *
+ * === Parameters
+ * * +length+ is a positive integer.
+ * * +buffer+ is a string used to store the result.
+ */
+static VALUE
+ossl_ssl_read_nonblock(int argc, VALUE *argv, VALUE self)
+{
     SSL *ssl;
     int ilen, nread = 0;
     VALUE len, str;
@@ -1027,12 +1196,11 @@ ossl_ssl_read(int argc, VALUE *argv, VAL
 
     Data_Get_Struct(self, SSL, ssl);
     GetOpenFile(ossl_ssl_get_io(self), fptr);
+    rb_io_set_nonblock(fptr);
     if (ssl) {
-	if(SSL_pending(ssl) <= 0)
-	    rb_thread_wait_fd(FPTR_TO_FD(fptr));
 	for (;;){
 	    nread = SSL_read(ssl, RSTRING_PTR(str), RSTRING_LEN(str));
-	    switch(ssl_get_error(ssl, nread)){
+	    switch(SSL_get_error(ssl, nread)){
 	    case SSL_ERROR_NONE:
 		goto end;
 	    case SSL_ERROR_ZERO_RETURN:
@@ -1041,7 +1209,7 @@ ossl_ssl_read(int argc, VALUE *argv, VAL
                 rb_io_wait_writable(FPTR_TO_FD(fptr));
                 continue;
 	    case SSL_ERROR_WANT_READ:
-                rb_io_wait_readable(FPTR_TO_FD(fptr));
+		rb_sys_fail(fptr->path);
 		continue;
 	    case SSL_ERROR_SYSCALL:
 		if(ERR_peek_error() == 0 && nread == 0) rb_eof_error();
@@ -1052,9 +1220,8 @@ ossl_ssl_read(int argc, VALUE *argv, VAL
         }
     }
     else {
-        ID id_sysread = rb_intern("sysread");
         rb_warning("SSL session is not started yet.");
-        return rb_funcall(ossl_ssl_get_io(self), id_sysread, 2, len, str);
+	return rb_funcall(ossl_ssl_get_io(self), rb_intern("sysread"), 2, len, str);
     }
 
   end:
@@ -1227,7 +1394,7 @@ ossl_ssl_get_cipher(VALUE self)
         rb_warning("SSL session is not started yet.");
         return Qnil;
     }
-    cipher = SSL_get_current_cipher(ssl);
+    cipher = (SSL_CIPHER *)SSL_get_current_cipher(ssl);
 
     return ossl_ssl_cipher_to_ary(cipher);
 }
@@ -1350,13 +1517,13 @@ Init_ossl_ssl()
 
     ID_callback_state = rb_intern("@callback_state");
 
-    ossl_ssl_ex_vcb_idx = SSL_get_ex_new_index(0,"ossl_ssl_ex_vcb_idx",0,0,0);
-    ossl_ssl_ex_store_p = SSL_get_ex_new_index(0,"ossl_ssl_ex_store_p",0,0,0);
-    ossl_ssl_ex_ptr_idx = SSL_get_ex_new_index(0,"ossl_ssl_ex_ptr_idx",0,0,0);
+    ossl_ssl_ex_vcb_idx = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_vcb_idx",0,0,0);
+    ossl_ssl_ex_store_p = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_store_p",0,0,0);
+    ossl_ssl_ex_ptr_idx = SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_ptr_idx",0,0,0);
     ossl_ssl_ex_client_cert_cb_idx =
-	SSL_get_ex_new_index(0,"ossl_ssl_ex_client_cert_cb_idx",0,0,0);
+	SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_client_cert_cb_idx",0,0,0);
     ossl_ssl_ex_tmp_dh_callback_idx =
-	SSL_get_ex_new_index(0,"ossl_ssl_ex_tmp_dh_callback_idx",0,0,0);
+	SSL_get_ex_new_index(0,(void *)"ossl_ssl_ex_tmp_dh_callback_idx",0,0,0);
 
     mSSL = rb_define_module_under(mOSSL, "SSL");
     eSSLError = rb_define_class_under(mSSL, "SSLError", eOSSLError);
