aboutsummaryrefslogtreecommitdiff
path: root/src/microhttpd/test_upgrade_ssl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/microhttpd/test_upgrade_ssl.c')
-rw-r--r--src/microhttpd/test_upgrade_ssl.c472
1 files changed, 472 insertions, 0 deletions
diff --git a/src/microhttpd/test_upgrade_ssl.c b/src/microhttpd/test_upgrade_ssl.c
new file mode 100644
index 00000000..5c663dd9
--- /dev/null
+++ b/src/microhttpd/test_upgrade_ssl.c
@@ -0,0 +1,472 @@
1/*
2 This file is part of libmicrohttpd
3 Copyright (C) 2016 Christian Grothoff
4
5 libmicrohttpd is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 2, or (at your
8 option) any later version.
9
10 libmicrohttpd is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with libmicrohttpd; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21/**
22 * @file test_upgrade_ssl.c
23 * @brief Testcase for libmicrohttpd upgrading a connection
24 * @author Christian Grothoff
25 */
26
27#include "platform.h"
28#include "microhttpd.h"
29#include <stdlib.h>
30#include <string.h>
31#include <stdio.h>
32
33#ifndef WINDOWS
34#include <unistd.h>
35#endif
36
37#include <sys/socket.h>
38#include <sys/wait.h>
39#include <netinet/in.h>
40#include <netinet/ip.h>
41#include <pthread.h>
42#include "mhd_sockets.h"
43
44#include "../testcurl/https/tls_test_keys.h"
45
46
47/**
48 * Thread we use to run the interaction with the upgraded socket.
49 */
50static pthread_t pt;
51
52/**
53 * Will be set to the upgraded socket.
54 */
55static MHD_socket usock;
56
57/**
58 * Fork child that connects via OpenSSL to our @a port. Allows us to
59 * talk to our port over a socket in @a sp without having to worry
60 * about TLS.
61 *
62 * @param location where the socket is returned
63 * @return -1 on error, otherwise PID of SSL child process
64 */
65static pid_t
66openssl_connect (int *sock,
67 uint16_t port)
68{
69 pid_t chld;
70 int sp[2];
71 char destination[30];
72
73 if (0 != socketpair (AF_UNIX,
74 SOCK_STREAM,
75 0,
76 sp))
77 return -1;
78 chld = fork ();
79 if (0 != chld)
80 {
81 *sock = sp[1];
82 MHD_socket_close_ (sp[0]);
83 return chld;
84 }
85 MHD_socket_close_ (sp[1]);
86 (void) close (0);
87 (void) close (1);
88 dup2 (sp[0], 0);
89 dup2 (sp[0], 1);
90 close (sp[0]);
91 sprintf (destination,
92 "localhost:%u",
93 (unsigned int) port);
94 execlp ("openssl",
95 "openssl",
96 "s_client",
97 "-connect",
98 destination,
99 "-verify",
100 "0",
101 // "-quiet",
102 (char *) NULL);
103 _exit (1);
104}
105
106
107/**
108 * Change itc FD options to be non-blocking.
109 *
110 * @param fd the FD to manipulate
111 * @return non-zero if succeeded, zero otherwise
112 */
113static void
114make_blocking (MHD_socket fd)
115{
116 int flags;
117
118 flags = fcntl (fd, F_GETFL);
119 if (-1 == flags)
120 return;
121 if ((flags & ~O_NONBLOCK) != flags)
122 fcntl (fd, F_SETFL, flags & ~O_NONBLOCK);
123}
124
125
126static void
127send_all (MHD_socket sock,
128 const char *text)
129{
130 size_t len = strlen (text);
131 ssize_t ret;
132
133 make_blocking (sock);
134 for (size_t off = 0; off < len; off += ret)
135 {
136 ret = write (sock,
137 &text[off],
138 len - off);
139 if (-1 == ret)
140 {
141 if (EAGAIN == errno)
142 {
143 ret = 0;
144 continue;
145 }
146 abort ();
147 }
148 }
149}
150
151
152/**
153 * Read character-by-character until we
154 * get '\r\n\r\n'.
155 */
156static void
157recv_hdr (MHD_socket sock)
158{
159 unsigned int i;
160 char next;
161 char c;
162 ssize_t ret;
163
164 make_blocking (sock);
165 next = '\r';
166 i = 0;
167 while (i < 4)
168 {
169 ret = read (sock,
170 &c,
171 1);
172 if (0 == ret)
173 abort (); /* this is fatal */
174 if (-1 == ret)
175 {
176 if (EAGAIN == errno)
177 {
178 ret = 0;
179 continue;
180 }
181 abort ();
182 }
183 if (0 == ret)
184 continue;
185 if (c == next)
186 {
187 i++;
188 if (next == '\r')
189 next = '\n';
190 else
191 next = '\r';
192 continue;
193 }
194 if (c == '\r')
195 {
196 i = 1;
197 next = '\n';
198 continue;
199 }
200 i = 0;
201 next = '\r';
202 }
203}
204
205
206static void
207recv_all (MHD_socket sock,
208 const char *text)
209{
210 size_t len = strlen (text);
211 char buf[len];
212 ssize_t ret;
213
214 make_blocking (sock);
215 for (size_t off = 0; off < len; off += ret)
216 {
217 ret = read (sock,
218 &buf[off],
219 len - off);
220 if (0 == ret)
221 abort (); /* this is fatal */
222 if (-1 == ret)
223 {
224 if (EAGAIN == errno)
225 {
226 ret = 0;
227 continue;
228 }
229 abort ();
230 }
231 }
232 if (0 != strncmp (text, buf, len))
233 abort();
234}
235
236
237/**
238 * Main function for the thread that runs the interaction with
239 * the upgraded socket.
240 *
241 * @param cls the handle for the upgrade
242 */
243static void *
244run_usock (void *cls)
245{
246 struct MHD_UpgradeResponseHandle *urh = cls;
247
248 fprintf (stderr,
249 "Sending `Hello'\n");
250 send_all (usock,
251 "Hello");
252 fprintf (stderr,
253 "Receiving `World'\n");
254 recv_all (usock,
255 "World");
256 fprintf (stderr,
257 "Sending `Finished'\n");
258 send_all (usock,
259 "Finished");
260 fprintf (stderr,
261 "Closing socket\n");
262 while (MHD_NO ==
263 MHD_upgrade_action (urh,
264 MHD_UPGRADE_ACTION_FLUSH))
265 usleep (1000);
266 MHD_upgrade_action (urh,
267 MHD_UPGRADE_ACTION_CLOSE);
268 fprintf (stderr,
269 "Thread terminating\n");
270 return NULL;
271}
272
273
274/**
275 * Function called after a protocol "upgrade" response was sent
276 * successfully and the socket should now be controlled by some
277 * protocol other than HTTP.
278 *
279 * Any data received on the socket will be made available in
280 * 'data_in'. The function should update 'data_in_size' to
281 * reflect the number of bytes consumed from 'data_in' (the remaining
282 * bytes will be made available in the next call to the handler).
283 *
284 * Any data that should be transmitted on the socket should be
285 * stored in 'data_out'. '*data_out_size' is initially set to
286 * the available buffer space in 'data_out'. It should be set to
287 * the number of bytes stored in 'data_out' (which can be zero).
288 *
289 * The return value is a BITMASK that indicates how the function
290 * intends to interact with the event loop. It can request to be
291 * notified for reading, writing, request to UNCORK the send buffer
292 * (which MHD is allowed to ignore, if it is not possible to uncork on
293 * the local platform), to wait for the 'external' select loop to
294 * trigger another round. It is also possible to specify "no events"
295 * to terminate the connection; in this case, the
296 * #MHD_RequestCompletedCallback will be called and all resources of
297 * the connection will be released.
298 *
299 * Except when in 'thread-per-connection' mode, implementations
300 * of this function should never block (as it will still be called
301 * from within the main event loop).
302 *
303 * @param cls closure, whatever was given to #MHD_create_response_for_upgrade().
304 * @param connection original HTTP connection handle,
305 * giving the function a last chance
306 * to inspect the original HTTP request
307 * @param con_cls last value left in `*con_cls` in the `MHD_AccessHandlerCallback`
308 * @param extra_in if we happened to have read bytes after the
309 * HTTP header already (because the client sent
310 * more than the HTTP header of the request before
311 * we sent the upgrade response),
312 * these are the extra bytes already read from @a sock
313 * by MHD. The application should treat these as if
314 * it had read them from @a sock.
315 * @param extra_in_size number of bytes in @a extra_in
316 * @param sock socket to use for bi-directional communication
317 * with the client. For HTTPS, this may not be a socket
318 * that is directly connected to the client and thus certain
319 * operations (TCP-specific setsockopt(), getsockopt(), etc.)
320 * may not work as expected (as the socket could be from a
321 * socketpair() or a TCP-loopback)
322 * @param urh argument for #MHD_upgrade_action()s on this @a connection.
323 * Applications must eventually use this function to perform the
324 * close() action on the @a sock.
325 */
326static void
327upgrade_cb (void *cls,
328 struct MHD_Connection *connection,
329 void *con_cls,
330 const char *extra_in,
331 size_t extra_in_size,
332 MHD_socket sock,
333 struct MHD_UpgradeResponseHandle *urh)
334{
335 usock = sock;
336 if (0 != extra_in_size)
337 abort ();
338 pthread_create (&pt,
339 NULL,
340 &run_usock,
341 urh);
342}
343
344
345/**
346 * A client has requested the given url using the given method
347 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
348 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
349 * must call MHD callbacks to provide content to give back to the
350 * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
351 * #MHD_HTTP_NOT_FOUND, etc.).
352 *
353 * @param cls argument given together with the function
354 * pointer when the handler was registered with MHD
355 * @param url the requested url
356 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
357 * #MHD_HTTP_METHOD_PUT, etc.)
358 * @param version the HTTP version string (i.e.
359 * #MHD_HTTP_VERSION_1_1)
360 * @param upload_data the data being uploaded (excluding HEADERS,
361 * for a POST that fits into memory and that is encoded
362 * with a supported encoding, the POST data will NOT be
363 * given in upload_data and is instead available as
364 * part of #MHD_get_connection_values; very large POST
365 * data *will* be made available incrementally in
366 * @a upload_data)
367 * @param upload_data_size set initially to the size of the
368 * @a upload_data provided; the method must update this
369 * value to the number of bytes NOT processed;
370 * @param con_cls pointer that the callback can set to some
371 * address and that will be preserved by MHD for future
372 * calls for this request; since the access handler may
373 * be called many times (i.e., for a PUT/POST operation
374 * with plenty of upload data) this allows the application
375 * to easily associate some request-specific state.
376 * If necessary, this state can be cleaned up in the
377 * global #MHD_RequestCompletedCallback (which
378 * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
379 * Initially, `*con_cls` will be NULL.
380 * @return #MHD_YES if the connection was handled successfully,
381 * #MHD_NO if the socket must be closed due to a serios
382 * error while handling the request
383 */
384static int
385ahc_upgrade (void *cls,
386 struct MHD_Connection *connection,
387 const char *url,
388 const char *method,
389 const char *version,
390 const char *upload_data,
391 size_t *upload_data_size,
392 void **con_cls)
393{
394 struct MHD_Response *resp;
395 int ret;
396
397 resp = MHD_create_response_for_upgrade (&upgrade_cb,
398 NULL);
399 MHD_add_response_header (resp,
400 MHD_HTTP_HEADER_UPGRADE,
401 "Hello World Protocol");
402 ret = MHD_queue_response (connection,
403 MHD_HTTP_SWITCHING_PROTOCOLS,
404 resp);
405 MHD_destroy_response (resp);
406 return ret;
407}
408
409
410static int
411test_upgrade_internal_select ()
412{
413 struct MHD_Daemon *d;
414 MHD_socket sock;
415 pid_t pid;
416
417 d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | MHD_USE_SUSPEND_RESUME | MHD_USE_TLS,
418 1080,
419 NULL, NULL,
420 &ahc_upgrade, NULL,
421 MHD_OPTION_HTTPS_MEM_KEY, srv_signed_key_pem,
422 MHD_OPTION_HTTPS_MEM_CERT, srv_signed_cert_pem,
423 MHD_OPTION_END);
424 if (NULL == d)
425 return 2;
426 if (-1 == (pid = openssl_connect (&sock, 1080)))
427 {
428 MHD_stop_daemon (d);
429 return 4;
430 }
431
432 send_all (sock,
433 "GET / HTTP/1.1\r\nConnection: Upgrade\r\n\r\n");
434 recv_hdr (sock);
435 recv_all (sock,
436 "Hello");
437 fprintf (stderr,
438 "Received `Hello'\n");
439 send_all (sock,
440 "World");
441 fprintf (stderr,
442 "Sent `World'\n");
443 recv_all (sock,
444 "Finished");
445 fprintf (stderr,
446 "Received `Finished'\n");
447 MHD_socket_close_ (sock);
448 pthread_join (pt,
449 NULL);
450 fprintf (stderr,
451 "Joined helper thread\n");
452 waitpid (pid, NULL, 0);
453 MHD_stop_daemon (d);
454 return 0;
455}
456
457
458int
459main (int argc,
460 char *const *argv)
461{
462 int errorCount = 0;
463
464 if (0 != system ("openssl version 1> /dev/null"))
465 return 77; /* openssl not available, can't run the test */
466 errorCount += test_upgrade_internal_select ();
467 if (errorCount != 0)
468 fprintf (stderr,
469 "Error (code: %u)\n",
470 errorCount);
471 return errorCount != 0; /* 0 == pass */
472}