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 }