1 /// https://github.com/openssl/openssl/tree/master/demos/sslecho 2 module app; 3 4 import core.stdc.stdio; 5 import core.sys.posix.netinet.in_; 6 import core.sys.posix.stdio : getline; 7 import core.sys.posix.unistd; 8 9 import std.algorithm : startsWith; 10 import std.stdio : write, writeln, writefln; 11 12 import deimos.openssl.opensslv; 13 import deimos.openssl.err; 14 import deimos.openssl.ssl; 15 16 const ushort server_port = 4433; 17 18 int create_socket(bool isServer) 19 { 20 int optval = 1; 21 sockaddr_in addr; 22 23 int s = socket(AF_INET, SOCK_STREAM, 0); 24 if (s < 0) { 25 perror("Unable to create socket"); 26 return -1; 27 } 28 29 if (!isServer) 30 return s; 31 32 addr.sin_family = AF_INET; 33 addr.sin_port = htons(server_port); 34 addr.sin_addr.s_addr = INADDR_ANY; 35 36 /* Reuse the address; good for quick restarts */ 37 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &optval, optval.sizeof) < 0) { 38 perror("setsockopt(SO_REUSEADDR) failed"); 39 return -1; 40 } 41 42 if (bind(s, cast(sockaddr*) &addr, addr.sizeof) < 0) { 43 perror("Unable to bind"); 44 return -1; 45 } 46 47 if (listen(s, 1) < 0) { 48 perror("Unable to listen"); 49 return -1; 50 } 51 52 return s; 53 } 54 55 private int usage() 56 { 57 writeln("Usage: sslecho s"); 58 writeln(" --or--"); 59 writeln(" sslecho c ip"); 60 writeln(" c=client, s=server, ip=dotted ip of server"); 61 return 1; 62 } 63 64 int main(string[] args) 65 { 66 int result; 67 68 /* used by getline relying on realloc, can't be statically allocated */ 69 // char *txbuf = NULL; 70 // size_t txcap = 0; 71 // int txlen; 72 73 // struct sockaddr_in addr; 74 // unsigned int addr_len = sizeof(addr); 75 76 /* Splash */ 77 writefln("sslecho : Simple Echo Client/Server (OpenSSL %s): %s %s", 78 OpenSSLVersion.text, __DATE__, __TIME__); 79 80 /* Need to know if client or server */ 81 if (args.length < 2) 82 return usage(); 83 84 if (const isServer = args[1].startsWith('s')) 85 return runServer(); 86 87 /* If client get remote server address (should be localhost) */ 88 if (args.length != 3) 89 return usage(); 90 91 const remote_server_ip = args[2]; 92 return runClient(remote_server_ip); 93 } 94 95 private int runServer (ushort port = server_port) 96 { 97 char[256] buffer; 98 int result; 99 100 writeln("We are the server on port: ", port); 101 102 const SSL_METHOD* method = TLS_server_method(); 103 SSL_CTX* ctx = SSL_CTX_new(method); 104 if (ctx is null) 105 { 106 perror("Unable to create SSL context"); 107 ERR_print_errors_fp(stderr); 108 return 1; 109 } 110 scope (exit) SSL_CTX_free(ctx); 111 112 /* Configure server context with appropriate key files */ 113 if (SSL_CTX_use_certificate_chain_file(ctx, "cert.pem".ptr) <= 0) { 114 ERR_print_errors_fp(stderr); 115 return 1; 116 } 117 if (SSL_CTX_use_PrivateKey_file(ctx, "key.pem".ptr, SSL_FILETYPE_PEM) <= 0) { 118 ERR_print_errors_fp(stderr); 119 return 1; 120 } 121 122 /* Create server socket; will bind with server port and listen */ 123 int server_skt = create_socket(true); 124 if (server_skt < 0) return 1; 125 scope (exit) close(server_skt); 126 127 /* 128 * Loop to accept clients. 129 * Need to implement timeouts on TCP & SSL connect/read functions 130 * before we can catch a CTRL-C and kill the server. 131 */ 132 while (true) { 133 /* Wait for TCP connection from client */ 134 sockaddr_in addr; 135 socklen_t addr_len = sockaddr_in.sizeof; 136 int client_skt = accept(server_skt, cast(sockaddr*) &addr, &addr_len); 137 138 if (client_skt < 0) { 139 perror("Unable to accept"); 140 return 1; 141 } 142 143 writeln("Client TCP connection accepted"); 144 scope (exit) close(client_skt); 145 146 /* Create server SSL structure using newly accepted client socket */ 147 SSL* ssl = SSL_new(ctx); 148 SSL_set_fd(ssl, client_skt); 149 scope (exit) { 150 /* Cleanup for next client */ 151 SSL_shutdown(ssl); 152 SSL_free(ssl); 153 } 154 155 /* Wait for SSL connection from the client */ 156 if (SSL_accept(ssl) <= 0) { 157 ERR_print_errors_fp(stderr); 158 break; 159 } 160 161 writeln("Client SSL connection accepted"); 162 163 /* Echo loop */ 164 while (true) { 165 /* Get message from client; will fail if client closes connection */ 166 if ((result = SSL_read(ssl, buffer.ptr, buffer.length)) <= 0) { 167 if (result == 0) { 168 writeln("Client closed connection"); 169 } 170 ERR_print_errors_fp(stderr); 171 break; 172 } 173 174 const rcvd = buffer[0 .. result]; 175 /* Look for kill switch */ 176 if (rcvd == "kill\n") { 177 /* Terminate...with extreme prejudice */ 178 writeln("Server received 'kill' command"); 179 return 0; 180 } 181 /* Show received message */ 182 writefln("Received %s bytes:", rcvd.length); 183 write(rcvd); 184 /* Echo it back */ 185 if (SSL_write(ssl, rcvd.ptr, result) <= 0) { 186 ERR_print_errors_fp(stderr); 187 } 188 } 189 } 190 writeln("Server exiting..."); 191 return 0; 192 } 193 194 private int runClient (string remote) 195 { 196 char[256] buffer; 197 writeln("We are the client"); 198 199 const SSL_METHOD* method = TLS_client_method(); 200 SSL_CTX* ctx = SSL_CTX_new(method); 201 if (ctx is null) 202 { 203 perror("Unable to create SSL context"); 204 ERR_print_errors_fp(stderr); 205 return 1; 206 } 207 scope (exit) SSL_CTX_free(ctx); 208 209 /* 210 * Configure the client to abort the handshake if certificate verification 211 * fails 212 */ 213 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, null); 214 /* 215 * In a real application you would probably just use the default system certificate trust store and call: 216 * SSL_CTX_set_default_verify_paths(ctx); 217 * In this demo though we are using a self-signed certificate, so the client must trust it directly. 218 */ 219 if (!SSL_CTX_load_verify_locations(ctx, "cert.pem".ptr, null)) { 220 ERR_print_errors_fp(stderr); 221 return 1; 222 } 223 224 /* Create "bare" socket */ 225 int client_skt = create_socket(false); 226 if (client_skt < 0) return 1; 227 scope (exit) close(client_skt); 228 229 /* Set up connect address */ 230 sockaddr_in addr; 231 addr.sin_family = AF_INET; 232 // Note: The runtime ensures that `args` are `\0` terminated 233 inet_pton(AF_INET, remote.ptr, &addr.sin_addr.s_addr); 234 addr.sin_port = htons(server_port); 235 /* Do TCP connect with server */ 236 if (connect(client_skt, cast(sockaddr*) &addr, addr.sizeof) != 0) { 237 perror("Unable to TCP connect to server"); 238 return 1; 239 } 240 writeln("TCP connection to server successful"); 241 242 /* Create client SSL structure using dedicated client socket */ 243 SSL* ssl = SSL_new(ctx); 244 SSL_set_fd(ssl, client_skt); 245 scope (exit) 246 { 247 SSL_shutdown(ssl); 248 SSL_free(ssl); 249 } 250 251 /* Set host name for SNI */ 252 SSL_set_tlsext_host_name(ssl, remote.ptr); 253 /* Configure server hostname check */ 254 static if (OPENSSL_VERSION_AT_LEAST(1, 1, 0)) 255 SSL_set1_host(ssl, remote.ptr); 256 257 /* Now do SSL connect with server */ 258 if (SSL_connect(ssl) != 1) { 259 writeln("SSL connection to server failed"); 260 ERR_print_errors_fp(stderr); 261 return 1; 262 } 263 264 writeln("SSL connection to server successful"); 265 266 /* Loop to send input from keyboard */ 267 while (true) { 268 /* Get a line of input */ 269 auto len = buffer.length; 270 auto pptr = buffer.ptr; 271 ssize_t txlen = getline(&pptr, &len, stdin); 272 273 /* Exit loop on error */ 274 if (txlen < 1 || pptr is null) 275 break; 276 /* Exit loop if just a carriage return */ 277 if (buffer[0] == '\n') 278 break; 279 assert(txlen <= int.max); 280 281 /* Send it to the server */ 282 auto result = SSL_write(ssl, buffer.ptr, cast(int) txlen); 283 if (result <= 0) { 284 writeln("Server closed connection"); 285 ERR_print_errors_fp(stderr); 286 break; 287 } 288 289 /* Wait for the echo */ 290 auto rxlen = SSL_read(ssl, buffer.ptr, cast(int) buffer.length); 291 if (rxlen <= 0) { 292 writeln("Server closed connection"); 293 ERR_print_errors_fp(stderr); 294 break; 295 } 296 /* Show it */ 297 writefln("Received %s bytes (sent: %s bytes):", rxlen, txlen); 298 writeln(buffer[0 .. rxlen]); 299 } 300 writeln("Client exiting..."); 301 return 0; 302 }