1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 /**
21  * Provides a SSL BIO implementation wrapping a Thrift transport.
22  *
23  * This way, SSL I/O can be relayed over Thrift transport without introducing
24  * an additional layer of buffering, especially for the non-blocking
25  * transports.
26  *
27  * For the Thrift transport incarnations of the SSL entities, "tt" is used as
28  * prefix for clarity.
29  */
30 module thrift.internal.ssl_bio;
31 
32 import core.stdc.config;
33 import core.stdc.string : strlen;
34 import core.memory : GC;
35 import deimos.openssl.bio;
36 import deimos.openssl.err;
37 import thrift.base;
38 import thrift.internal.ssl;
39 import thrift.transport.base;
40 
41 /**
42  * Creates an SSL BIO object wrapping the given transport.
43  *
44  * Exceptions thrown by the transport are pushed onto the OpenSSL error stack,
45  * using the location/reason values from thrift.internal.ssl.ERR_*_D_EXCEPTION.
46  *
47  * The transport is assumed to be ready for reading and writing when the BIO
48  * functions are called, it is not opened by the implementation.
49  *
50  * Params:
51  *   transport = The transport to wrap.
52  *   closeTransport = Whether the close the transport when the SSL BIO is
53  *     closed.
54  */
55 BIO* createTTransportBIO(TTransport transport, bool closeTransport) {
56   auto result = BIO_new(cast(BIO_METHOD*)&ttBioMethod);
57   if (!result) return null;
58 
59   GC.addRoot(cast(void*)transport);
60   BIO_set_fd(result, closeTransport, cast(c_long)cast(void*)transport);
61 
62   return result;
63 }
64 
65 private {
66   // Helper to get the Thrift transport assigned with the given BIO.
67   TTransport trans(BIO* b) nothrow {
68     auto result = cast(TTransport)b.ptr;
69     assert(result);
70     return result;
71   }
72 
73   void setError(Exception e) nothrow {
74     ERR_put_error(ERR_LIB_D_EXCEPTION, ERR_F_D_EXCEPTION, ERR_R_D_EXCEPTION,
75       ERR_FILE_D_EXCEPTION, ERR_LINE_D_EXCEPTION);
76     try { GC.addRoot(cast(void*)e); } catch (Throwable) {}
77     ERR_set_error_data(cast(char*)e, ERR_FLAGS_D_EXCEPTION);
78   }
79 
80   extern(C) int ttWrite(BIO* b, const(char)* data, int length) nothrow {
81     assert(b);
82     if (!data || length <= 0) return 0;
83     try {
84       trans(b).write((cast(ubyte*)data)[0 .. length]);
85       return length;
86     } catch (Exception e) {
87       setError(e);
88       return -1;
89     }
90   }
91 
92   extern(C) int ttRead(BIO* b, char* data, int length) nothrow {
93     assert(b);
94     if (!data || length <= 0) return 0;
95     try {
96       return cast(int)trans(b).read((cast(ubyte*)data)[0 .. length]);
97     } catch (Exception e) {
98       setError(e);
99       return -1;
100     }
101   }
102 
103   extern(C) int ttPuts(BIO* b, const(char)* str) nothrow {
104     return ttWrite(b, str, cast(int)strlen(str));
105   }
106 
107   extern(C) c_long ttCtrl(BIO* b, int cmd, c_long num, void* ptr) nothrow {
108     assert(b);
109 
110     switch (cmd) {
111       case BIO_C_SET_FD:
112         // Note that close flag and "fd" are actually reversed here because we
113         // need 64 bit width for the pointer – should probably drop BIO_set_fd
114         // altogether.
115         ttDestroy(b);
116         b.ptr = cast(void*)num;
117         b.shutdown = cast(int)ptr;
118         b.init_ = 1;
119         return 1;
120       case BIO_C_GET_FD:
121         if (!b.init_) return -1;
122         *(cast(void**)ptr) = b.ptr;
123         return cast(c_long)b.ptr;
124       case BIO_CTRL_GET_CLOSE:
125         return b.shutdown;
126       case BIO_CTRL_SET_CLOSE:
127         b.shutdown = cast(int)num;
128         return 1;
129       case BIO_CTRL_FLUSH:
130         try {
131           trans(b).flush();
132           return 1;
133         } catch (Exception e) {
134           setError(e);
135           return -1;
136         }
137       case BIO_CTRL_DUP:
138         // Seems like we have nothing to do on duplication, but couldn't find
139         // any documentation if this actually ever happens during normal SSL
140         // usage.
141         return 1;
142       default:
143         return 0;
144     }
145   }
146 
147   extern(C) int ttCreate(BIO* b) nothrow {
148     assert(b);
149     b.init_ = 0;
150     b.num = 0; // User-defined number field, unused here.
151     b.ptr = null;
152     b.flags = 0;
153     return 1;
154   }
155 
156   extern(C) int ttDestroy(BIO* b) nothrow {
157     if (!b) return 0;
158 
159     int rc = 1;
160     if (b.shutdown) {
161       if (b.init_) {
162         try {
163           trans(b).close();
164           GC.removeRoot(cast(void*)trans(b));
165           b.ptr = null;
166         } catch (Exception e) {
167           setError(e);
168           rc = -1;
169         }
170       }
171       b.init_ = 0;
172       b.flags = 0;
173     }
174 
175     return rc;
176   }
177 
178   immutable BIO_METHOD ttBioMethod = {
179     BIO_TYPE_SOURCE_SINK,
180     "TTransport",
181     &ttWrite,
182     &ttRead,
183     &ttPuts,
184     null, // gets
185     &ttCtrl,
186     &ttCreate,
187     &ttDestroy,
188     null // callback_ctrl
189   };
190 }