sq3: `transacted` uses scoped delegate
[iv.d.git] / sslsocket.d
blobb7a86d291ad11e2dd605e5217ec004c5aaee1eff
1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // loosely based on opticron and Adam D. Ruppe work
18 module iv.sslsocket /*is aliced*/;
20 import iv.alice;
21 public import std.socket;
22 import iv.gnutls;
25 // ///////////////////////////////////////////////////////////////////////// //
26 shared static this () { gnutls_global_init(); }
27 shared static ~this () { gnutls_global_deinit(); }
30 // ///////////////////////////////////////////////////////////////////////// //
31 /// deprecated!
32 class SSLClientSocket : Socket {
33 gnutls_certificate_credentials_t xcred;
34 gnutls_session_t session;
35 private bool sslInitialized;
36 bool manualHandshake = false; // for non-blocking sockets this should be `true`
37 bool isblocking = false;
38 string certBaseName;
40 // take care of pre-connection TLS stuff
41 //FIXME: possible memory leak on exception? (sholdn't be, as `close()` will free the things)
42 private void sslInit (string acertbasename) {
43 if (sslInitialized) return;
45 // x509 stuff
46 gnutls_certificate_allocate_credentials(&xcred);
48 // sets the trusted certificate authority file (no need for us, as we aren't checking any certificate)
49 //gnutls_certificate_set_x509_trust_file(xcred, CAFILE, GNUTLS_X509_FMT_PEM);
50 if (acertbasename.length) {
51 certBaseName = acertbasename;
52 import std.internal.cstring : tempCString;
53 string cfname = certBaseName~".cer";
54 string kfname = certBaseName~".key";
55 int ret = gnutls_certificate_set_x509_key_file(xcred, cfname.tempCString, kfname.tempCString, GNUTLS_X509_FMT_PEM);
56 if (ret < 0) {
57 import std.string : fromStringz;
58 import std.conv : to;
59 string errstr = gnutls_strerror(ret).fromStringz.idup;
60 gnutls_certificate_free_credentials(xcred);
61 throw new Exception("TLS Error ("~errstr~"): cannot load certificate, err="~ret.to!string);
65 // initialize TLS session
66 gnutls_init(&session, GNUTLS_CLIENT|(certBaseName.length ? GNUTLS_FORCE_CLIENT_CERT : 0));
68 // use default priorities
70 const(char)* err;
71 auto ret = gnutls_priority_set_direct(session, "PERFORMANCE", &err);
72 if (ret < 0) {
73 import std.string : fromStringz;
74 import std.conv : to;
75 if (ret == GNUTLS_E_INVALID_REQUEST) throw new Exception("Syntax error at: "~err.fromStringz.idup);
76 string errstr = gnutls_strerror(ret).fromStringz.idup;
77 gnutls_deinit(session);
78 gnutls_certificate_free_credentials(xcred);
79 throw new Exception("TLS Error ("~errstr~"): returned with "~ret.to!string);
82 auto ret = gnutls_set_default_priority(session);
83 if (ret < 0) {
84 import std.string : fromStringz;
85 import std.conv : to;
86 //if (ret == GNUTLS_E_INVALID_REQUEST) throw new Exception("Syntax error at: "~err.fromStringz.idup);
87 string errstr = gnutls_strerror(ret).fromStringz.idup;
88 gnutls_deinit(session);
89 gnutls_certificate_free_credentials(xcred);
90 throw new Exception("TLS Error ("~errstr~"): returned with "~ret.to!string);
92 gnutls_session_enable_compatibility_mode(session);
94 // put the x509 credentials to the current session
95 gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
96 sslInitialized = true;
99 // this is required for new TLS (fuck)
100 // call this before connecting
101 public void sslhostname (const(char)[] hname) @trusted {
102 import std.internal.cstring : tempCString;
103 int res = gnutls_server_name_set(session, GNUTLS_NAME_DNS, hname.tempCString, hname.length);
104 if (res < 0) {
105 import std.string : fromStringz;
106 import std.conv : to;
107 string errstr = gnutls_strerror(res).fromStringz.idup;
108 //gnutls_deinit(session);
109 //gnutls_certificate_free_credentials(xcred);
110 throw new Exception("TLS Error ("~errstr~"): returned with "~res.to!string);
114 public void sslHandshake () {
115 // lob the socket handle off to gnutls
116 gnutls_transport_set_ptr(session, cast(gnutls_transport_ptr_t)handle);
117 // perform the TLS handshake
118 for (;;) {
119 auto ret = gnutls_handshake(session);
120 if (ret < 0 && !gnutls_error_is_fatal(ret)) continue;
121 if (ret < 0) {
122 import std.string : fromStringz;
123 throw new Exception("Handshake failed: "~gnutls_strerror(ret).fromStringz.idup);
125 break;
129 override @property void blocking (bool byes) @trusted {
130 super.blocking(byes);
131 isblocking = byes;
134 override void connect (Address to) @trusted {
135 super.connect(to);
136 if (!manualHandshake) sslHandshake();
139 // close the encrypted connection
140 override void close () @trusted {
141 scope(exit) sslInitialized = false;
142 if (sslInitialized) {
143 //{ import core.stdc.stdio : printf; printf("deiniting\n"); }
144 gnutls_bye(session, GNUTLS_SHUT_RDWR);
145 gnutls_deinit(session);
146 gnutls_certificate_free_credentials(xcred);
148 super.close();
151 override ptrdiff_t send (const(void)[] buf, SocketFlags flags) @trusted {
152 if (buf.length == 0) return 0;
153 for (;;) {
154 auto res = gnutls_record_send(session, buf.ptr, buf.length);
155 if (res >= 0 || !isblocking) return res;
156 if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) continue;
157 //if (gnutls_error_is_fatal(res)) return res;
158 return res;
162 override ptrdiff_t send (const(void)[] buf) {
163 import core.sys.posix.sys.socket;
164 static if (is(typeof(MSG_NOSIGNAL))) {
165 return send(buf, cast(SocketFlags)MSG_NOSIGNAL);
166 } else {
167 return send(buf, SocketFlags.NOSIGNAL);
171 override ptrdiff_t receive (void[] buf, SocketFlags flags) @trusted {
172 if (buf.length == 0) return 0;
173 for (;;) {
174 auto res = gnutls_record_recv(session, buf.ptr, buf.length);
175 if (res >= 0 || !isblocking) return res;
176 if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) continue;
177 //if (gnutls_error_is_fatal(res)) return res;
178 return res;
182 override ptrdiff_t receive (void[] buf) { return receive(buf, SocketFlags.NONE); }
184 this (AddressFamily af, SocketType type=SocketType.STREAM, string certbasename=null) {
185 sslInit(certbasename);
186 super(af, type);
189 this (socket_t sock, AddressFamily af, string certbasename=null) {
190 sslInit(certbasename);
191 super(sock, af);
196 // ///////////////////////////////////////////////////////////////////////// //
197 // this can be used as both client and server socket
198 // don't forget to set certificate file (and key file, if you have both) for server!
199 // `connect()` will do client mode, `accept()` will do server mode (and will return `SSLSocket` instance)
200 class SSLSocket : Socket {
201 gnutls_certificate_credentials_t xcred;
202 gnutls_session_t session;
203 private bool sslInitialized = false;
204 bool manualHandshake = false; // for non-blocking sockets this should be `true`
205 bool isblocking = false;
206 private bool thisIsServer = false;
207 // server
208 private string certfilez; // "cert.pem"
209 private string keyfilez; // "key.pem"
211 // both key and cert can be in one file
212 void setKeyCertFile (const(char)[] certname, const(char)[] keyname=null) {
213 if (certname.length == 0) { certname = keyname; keyname = null; }
214 if (certname.length == 0) {
215 certfilez = keyfilez = "";
216 } else {
217 auto buf = new char[](certname.length+1);
218 buf[] = 0;
219 buf[0..certname.length] = certname;
220 certfilez = cast(string)buf;
221 if (keyname.length != 0) {
222 buf = new char[](keyname.length+1);
223 buf[] = 0;
224 buf[0..keyname.length] = keyname;
226 keyfilez = cast(string)buf;
230 // take care of pre-connection TLS stuff
231 //FIXME: possible memory leak on exception? (sholdn't be, as `close()` will free the things)
232 private void sslInit () {
233 if (sslInitialized) return;
234 sslInitialized = true;
236 // x509 stuff
237 gnutls_certificate_allocate_credentials(&xcred);
239 // sets the trusted certificate authority file (no need for us, as we aren't checking any certificate)
240 //gnutls_certificate_set_x509_trust_file(xcred, CAFILE, GNUTLS_X509_FMT_PEM);
242 if (thisIsServer) {
243 // server
244 if (certfilez.length < 1) throw new SocketException("TLS Error: certificate file not set");
245 if (keyfilez.length < 1) throw new SocketException("TLS Error: key file not set");
246 auto res = gnutls_certificate_set_x509_key_file(xcred, certfilez.ptr, keyfilez.ptr, GNUTLS_X509_FMT_PEM);
247 if (res < 0) {
248 import std.conv : to;
249 throw new SocketException("TLS Error: returned with "~res.to!string);
251 gnutls_init(&session, GNUTLS_SERVER);
252 gnutls_certificate_server_set_request(session, GNUTLS_CERT_IGNORE);
253 gnutls_handshake_set_timeout(session, /*GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT*/2300);
254 } else {
255 // client
256 // initialize TLS session
257 gnutls_init(&session, GNUTLS_CLIENT);
260 // use default priorities
262 const(char)* err;
263 auto ret = gnutls_priority_set_direct(session, "PERFORMANCE", &err);
264 if (ret < 0) {
265 import std.string : fromStringz;
266 import std.conv : to;
267 if (ret == GNUTLS_E_INVALID_REQUEST) throw new SocketException("Syntax error at: "~err.fromStringz.idup);
268 throw new SocketException("TLS Error: returned with "~ret.to!string);
271 auto ret = gnutls_set_default_priority(session);
272 if (ret < 0) {
273 import std.string : fromStringz;
274 import std.conv : to;
275 //if (ret == GNUTLS_E_INVALID_REQUEST) throw new Exception("Syntax error at: "~err.fromStringz.idup);
276 string errstr = gnutls_strerror(ret).fromStringz.idup;
277 gnutls_deinit(session);
278 gnutls_certificate_free_credentials(xcred);
279 throw new Exception("TLS Error ("~errstr~"): returned with "~ret.to!string);
281 gnutls_session_enable_compatibility_mode(session);
283 // put the x509 credentials to the current session
284 gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred);
287 // this is required for new TLS (fuck)
288 // call this before connecting
289 public void sslhostname (const(char)[] hname) @trusted {
290 import std.internal.cstring : tempCString;
291 int res = gnutls_server_name_set(session, GNUTLS_NAME_DNS, hname.tempCString, hname.length);
292 if (res < 0) {
293 import std.string : fromStringz;
294 import std.conv : to;
295 string errstr = gnutls_strerror(res).fromStringz.idup;
296 //gnutls_deinit(session);
297 //gnutls_certificate_free_credentials(xcred);
298 throw new Exception("TLS Error ("~errstr~"): returned with "~res.to!string);
302 public void sslHandshake () {
303 sslInit();
304 // lob the socket handle off to gnutls
305 gnutls_transport_set_ptr(session, cast(gnutls_transport_ptr_t)handle);
306 // perform the TLS handshake
307 for (;;) {
308 auto ret = gnutls_handshake(session);
309 if (ret < 0 && !gnutls_error_is_fatal(ret)) continue;
310 if (ret < 0) {
311 import std.string : fromStringz;
312 throw new Exception("Handshake failed: "~gnutls_strerror(ret).fromStringz.idup);
314 break;
318 override @property void blocking (bool byes) @trusted {
319 super.blocking(byes);
320 manualHandshake = !byes;
321 isblocking = byes;
324 override void connect (Address to) @trusted {
325 if (sslInitialized && thisIsServer) throw new SocketException("wtf?!");
326 thisIsServer = false;
327 sslInit();
328 super.connect(to);
329 if (!manualHandshake) sslHandshake();
332 protected override Socket accepting () pure nothrow {
333 return new SSLSocket();
336 override Socket accept () @trusted {
337 auto sk = super.accept();
338 if (auto ssk = cast(SSLSocket)sk) {
339 ssk.keyfilez = keyfilez;
340 ssk.certfilez = certfilez;
341 ssk.manualHandshake = manualHandshake;
342 ssk.thisIsServer = true;
343 ssk.sslInit();
344 if (!ssk.manualHandshake) ssk.sslHandshake();
345 } else {
346 throw new SocketAcceptException("failed to create ssl socket");
348 return sk;
351 // close the encrypted connection
352 override void close () @trusted {
353 scope(exit) sslInitialized = false;
354 if (sslInitialized) {
355 //{ import core.stdc.stdio : printf; printf("deiniting\n"); }
356 gnutls_bye(session, GNUTLS_SHUT_RDWR);
357 gnutls_deinit(session);
358 gnutls_certificate_free_credentials(xcred);
360 super.close();
363 override ptrdiff_t send (const(void)[] buf, SocketFlags flags) @trusted {
364 if (session is null || !sslInitialized) throw new SocketException("not initialized");
365 //return gnutls_record_send(session, buf.ptr, buf.length);
366 if (buf.length == 0) return 0;
367 for (;;) {
368 auto res = gnutls_record_send(session, buf.ptr, buf.length);
369 if (res >= 0 || !isblocking) return res;
370 if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) continue;
371 //if (gnutls_error_is_fatal(res)) return res;
372 return res;
376 override ptrdiff_t send (const(void)[] buf) {
377 import core.sys.posix.sys.socket;
378 static if (is(typeof(MSG_NOSIGNAL))) {
379 return send(buf, cast(SocketFlags)MSG_NOSIGNAL);
380 } else {
381 return send(buf, SocketFlags.NOSIGNAL);
385 override ptrdiff_t receive (void[] buf, SocketFlags flags) @trusted {
386 if (session is null || !sslInitialized) throw new SocketException("not initialized");
387 //return gnutls_record_recv(session, buf.ptr, buf.length);
388 if (buf.length == 0) return 0;
389 for (;;) {
390 auto res = gnutls_record_recv(session, buf.ptr, buf.length);
391 if (res >= 0 || !isblocking) return res;
392 if (res == GNUTLS_E_INTERRUPTED || res == GNUTLS_E_AGAIN) continue;
393 //if (gnutls_error_is_fatal(res)) return res;
394 return res;
398 override ptrdiff_t receive (void[] buf) { return receive(buf, SocketFlags.NONE); }
400 private this () pure nothrow @safe {}
402 this (AddressFamily af, SocketType type=SocketType.STREAM) {
403 super(af, type);
406 this (socket_t sock, AddressFamily af) {
407 super(sock, af);