/* This file is part of libmicrohttpd Copyright (C) 2020 Christian Grothoff, Silvio Clecio (and other contributing authors) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /** * @file websocket_threaded_example.c * @brief example for how to provide a tiny threaded websocket server * @author Silvio Clecio (silvioprog) */ /* TODO: allow to send large messages. */ #include "platform.h" #include #include #define CHAT_PAGE \ "\n" \ "\n" \ "WebSocket chat\n" \ "\n" \ "\n" \ "\n" \ "\n" \ "

\n" \ "\n" \ "\n" \ "" */ #define BAD_REQUEST_PAGE \ "\n" \ "\n" \ "WebSocket chat\n" \ "\n" \ "\n" \ "Bad Request\n" \ "\n" \ "\n" #define UPGRADE_REQUIRED_PAGE \ "\n" \ "\n" \ "WebSocket chat\n" \ "\n" \ "\n" \ "Upgrade required\n" \ "\n" \ "\n" #define WS_SEC_WEBSOCKET_VERSION "13" #define WS_UPGRADE_VALUE "websocket" #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" #define WS_GUID_LEN 36 #define WS_KEY_LEN 24 #define WS_KEY_GUID_LEN ((WS_KEY_LEN) + (WS_GUID_LEN)) #define WS_FIN 128 #define WS_OPCODE_TEXT_FRAME 1 #define WS_OPCODE_CON_CLOSE_FRAME 8 #define MAX_CLIENTS 10 static MHD_socket CLIENT_SOCKS[MAX_CLIENTS]; static pthread_mutex_t MUTEX = PTHREAD_MUTEX_INITIALIZER; struct WsData { struct MHD_UpgradeResponseHandle *urh; MHD_socket sock; }; /********** begin SHA-1 **********/ #define SHA1HashSize 20 #define SHA1CircularShift(bits, word) \ (((word) << (bits)) | ((word) >> (32 - (bits)))) enum SHA1_RESULT { SHA1_RESULT_SUCCESS = 0, SHA1_RESULT_NULL = 1, SHA1_RESULT_STATE_ERROR = 2 }; struct SHA1Context { uint32_t intermediate_hash[SHA1HashSize / 4]; uint32_t length_low; uint32_t length_high; int_least16_t message_block_index; unsigned char message_block[64]; int computed; int corrupted; }; static void SHA1ProcessMessageBlock (struct SHA1Context *context) { const uint32_t K[] = { 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 }; int i; uint32_t temp; uint32_t W[80]; uint32_t A, B, C, D, E; for (i = 0; i < 16; i++) { W[i] = context->message_block[i * 4] << 24; W[i] |= context->message_block[i * 4 + 1] << 16; W[i] |= context->message_block[i * 4 + 2] << 8; W[i] |= context->message_block[i * 4 + 3]; } for (i = 16; i < 80; i++) { W[i] = SHA1CircularShift (1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]); } A = context->intermediate_hash[0]; B = context->intermediate_hash[1]; C = context->intermediate_hash[2]; D = context->intermediate_hash[3]; E = context->intermediate_hash[4]; for (i = 0; i < 20; i++) { temp = SHA1CircularShift (5, A) + ((B & C) | ((~B) & D)) + E + W[i] + K[0]; E = D; D = C; C = SHA1CircularShift (30, B); B = A; A = temp; } for (i = 20; i < 40; i++) { temp = SHA1CircularShift (5, A) + (B ^ C ^ D) + E + W[i] + K[1]; E = D; D = C; C = SHA1CircularShift (30, B); B = A; A = temp; } for (i = 40; i < 60; i++) { temp = SHA1CircularShift (5, A) + ((B & C) | (B & D) | (C & D)) + E + W[i] + K[2]; E = D; D = C; C = SHA1CircularShift (30, B); B = A; A = temp; } for (i = 60; i < 80; i++) { temp = SHA1CircularShift (5, A) + (B ^ C ^ D) + E + W[i] + K[3]; E = D; D = C; C = SHA1CircularShift (30, B); B = A; A = temp; } context->intermediate_hash[0] += A; context->intermediate_hash[1] += B; context->intermediate_hash[2] += C; context->intermediate_hash[3] += D; context->intermediate_hash[4] += E; context->message_block_index = 0; } static void SHA1PadMessage (struct SHA1Context *context) { if (context->message_block_index > 55) { context->message_block[context->message_block_index++] = 0x80; while (context->message_block_index < 64) { context->message_block[context->message_block_index++] = 0; } SHA1ProcessMessageBlock (context); while (context->message_block_index < 56) { context->message_block[context->message_block_index++] = 0; } } else { context->message_block[context->message_block_index++] = 0x80; while (context->message_block_index < 56) { context->message_block[context->message_block_index++] = 0; } } context->message_block[56] = context->length_high >> 24; context->message_block[57] = context->length_high >> 16; context->message_block[58] = context->length_high >> 8; context->message_block[59] = context->length_high; context->message_block[60] = context->length_low >> 24; context->message_block[61] = context->length_low >> 16; context->message_block[62] = context->length_low >> 8; context->message_block[63] = context->length_low; SHA1ProcessMessageBlock (context); } static enum SHA1_RESULT SHA1Reset (struct SHA1Context *context) { if (! context) { return SHA1_RESULT_NULL; } context->length_low = 0; context->length_high = 0; context->message_block_index = 0; context->intermediate_hash[0] = 0x67452301; context->intermediate_hash[1] = 0xEFCDAB89; context->intermediate_hash[2] = 0x98BADCFE; context->intermediate_hash[3] = 0x10325476; context->intermediate_hash[4] = 0xC3D2E1F0; context->computed = 0; context->corrupted = 0; return SHA1_RESULT_SUCCESS; } static enum SHA1_RESULT SHA1Result (struct SHA1Context *context, unsigned char Message_Digest[SHA1HashSize]) { int i; if (! context || ! Message_Digest) { return SHA1_RESULT_NULL; } if (context->corrupted) { return context->corrupted; } if (! context->computed) { SHA1PadMessage (context); for (i = 0; i < 64; ++i) { context->message_block[i] = 0; } context->length_low = 0; context->length_high = 0; context->computed = 1; } for (i = 0; i < SHA1HashSize; ++i) { Message_Digest[i] = context->intermediate_hash[i >> 2] >> 8 * (3 - (i & 0x03)); } return SHA1_RESULT_SUCCESS; } static enum SHA1_RESULT SHA1Input (struct SHA1Context *context, const unsigned char *message_array, unsigned length) { if (! length) { return SHA1_RESULT_SUCCESS; } if (! context || ! message_array) { return SHA1_RESULT_NULL; } if (context->computed) { context->corrupted = SHA1_RESULT_STATE_ERROR; return SHA1_RESULT_STATE_ERROR; } if (context->corrupted) { return context->corrupted; } while (length-- && ! context->corrupted) { context->message_block[context->message_block_index++] = (*message_array & 0xFF); context->length_low += 8; if (context->length_low == 0) { context->length_high++; if (context->length_high == 0) { context->corrupted = 1; } } if (context->message_block_index == 64) { SHA1ProcessMessageBlock (context); } message_array++; } return SHA1_RESULT_SUCCESS; } /********** end SHA-1 **********/ /********** begin Base64 **********/ ssize_t BASE64Encode (const void *in, size_t len, char **output) { #define FILLCHAR '=' const char *cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; const char *data = in; char *opt; ssize_t ret; size_t i; char c; ret = 0; opt = malloc (2 + (len * 4 / 3) + 8); if (NULL == opt) { return -1; } for (i = 0; i < len; ++i) { c = (data[i] >> 2) & 0x3F; opt[ret++] = cvt[(int) c]; c = (data[i] << 4) & 0x3F; if (++i < len) { c |= (data[i] >> 4) & 0x0F; } opt[ret++] = cvt[(int) c]; if (i < len) { c = (data[i] << 2) & 0x3F; if (++i < len) { c |= (data[i] >> 6) & 0x03; } opt[ret++] = cvt[(int) c]; } else { ++i; opt[ret++] = FILLCHAR; } if (i < len) { c = data[i] & 0x3F; opt[ret++] = cvt[(int) c]; } else { opt[ret++] = FILLCHAR; } } *output = opt; return ret; } /********** end Base64 **********/ static enum MHD_Result is_websocket_request (struct MHD_Connection *con, const char *upg_header, const char *con_header) { (void) con; /* Unused. Silent compiler warning. */ return (upg_header != NULL) && (con_header != NULL) && (0 == strcmp (upg_header, WS_UPGRADE_VALUE)) && (NULL != strstr (con_header, "Upgrade")) ? MHD_YES : MHD_NO; } static enum MHD_Result send_chat_page (struct MHD_Connection *con) { struct MHD_Response *res; enum MHD_Result ret; res = MHD_create_response_from_buffer (strlen (CHAT_PAGE), (void *) CHAT_PAGE, MHD_RESPMEM_PERSISTENT); ret = MHD_queue_response (con, MHD_HTTP_OK, res); MHD_destroy_response (res); return ret; } static enum MHD_Result send_bad_request (struct MHD_Connection *con) { struct MHD_Response *res; enum MHD_Result ret; res = MHD_create_response_from_buffer (strlen (BAD_REQUEST_PAGE), (void *) BAD_REQUEST_PAGE, MHD_RESPMEM_PERSISTENT); ret = MHD_queue_response (con, MHD_HTTP_BAD_REQUEST, res); MHD_destroy_response (res); return ret; } static enum MHD_Result send_upgrade_required (struct MHD_Connection *con) { struct MHD_Response *res; enum MHD_Result ret; res = MHD_create_response_from_buffer (strlen (UPGRADE_REQUIRED_PAGE), (void *) UPGRADE_REQUIRED_PAGE, MHD_RESPMEM_PERSISTENT); MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION, WS_SEC_WEBSOCKET_VERSION); ret = MHD_queue_response (con, MHD_HTTP_UPGRADE_REQUIRED, res); MHD_destroy_response (res); return ret; } static enum MHD_Result ws_get_accept_value (const char *key, char **val) { struct SHA1Context ctx; unsigned char hash[SHA1HashSize]; char *str; ssize_t len; if ( (NULL == key) || (WS_KEY_LEN != strlen (key))) { return MHD_NO; } str = malloc (WS_KEY_LEN + WS_GUID_LEN + 1); if (NULL == str) { return MHD_NO; } strncpy (str, key, (WS_KEY_LEN + 1)); strncpy (str + WS_KEY_LEN, WS_GUID, WS_GUID_LEN + 1); SHA1Reset (&ctx); SHA1Input (&ctx, (const unsigned char *) str, WS_KEY_GUID_LEN); if (SHA1_RESULT_SUCCESS != SHA1Result (&ctx, hash)) { free (str); return MHD_NO; } free (str); len = BASE64Encode (hash, SHA1HashSize, val); if (-1 == len) { return MHD_NO; } (*val)[len] = '\0'; return MHD_YES; } static void make_blocking (MHD_socket fd) { #if defined(MHD_POSIX_SOCKETS) int flags; flags = fcntl (fd, F_GETFL); if (-1 == flags) return; if ((flags & ~O_NONBLOCK) != flags) if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK)) abort (); #elif defined(MHD_WINSOCK_SOCKETS) unsigned long flags = 1; ioctlsocket (fd, (int) FIONBIO, &flags); #endif /* MHD_WINSOCK_SOCKETS */ } static size_t send_all (MHD_socket sock, const unsigned char *buf, size_t len) { ssize_t ret; size_t off; for (off = 0; off < len; off += ret) { ret = send (sock, (const void*) &buf[off], len - off, 0); if (0 > ret) { if (EAGAIN == errno) { ret = 0; continue; } break; } if (0 == ret) { break; } } return off; } static int ws_send_frame (MHD_socket sock, const char *msg, size_t length) { unsigned char *response; unsigned char frame[10]; unsigned char idx_first_rdata; int idx_response; int output; MHD_socket isock; size_t i; frame[0] = (WS_FIN | WS_OPCODE_TEXT_FRAME); if (length <= 125) { frame[1] = length & 0x7F; idx_first_rdata = 2; } else if ((length >= 126) && (length <= 0xFFFF)) { frame[1] = 126; frame[2] = (length >> 8) & 0xFF; frame[3] = length & 0xFF; idx_first_rdata = 4; } else { frame[1] = 127; frame[2] = (unsigned char) ((length >> 56) & 0xFF); frame[3] = (unsigned char) ((length >> 48) & 0xFF); frame[4] = (unsigned char) ((length >> 40) & 0xFF); frame[5] = (unsigned char) ((length >> 32) & 0xFF); frame[6] = (unsigned char) ((length >> 24) & 0xFF); frame[7] = (unsigned char) ((length >> 16) & 0xFF); frame[8] = (unsigned char) ((length >> 8) & 0xFF); frame[9] = (unsigned char) (length & 0xFF); idx_first_rdata = 10; } idx_response = 0; response = malloc (idx_first_rdata + length + 1); if (NULL == response) { return -1; } for (i = 0; i < idx_first_rdata; i++) { response[i] = frame[i]; idx_response++; } for (i = 0; i < length; i++) { response[idx_response] = msg[i]; idx_response++; } response[idx_response] = '\0'; output = 0; pthread_mutex_lock (&MUTEX); for (i = 0; i < MAX_CLIENTS; i++) { isock = CLIENT_SOCKS[i]; if ((isock != MHD_INVALID_SOCKET) && (isock != sock)) { output += send_all (isock, response, idx_response); } } pthread_mutex_unlock (&MUTEX); free (response); return output; } static unsigned char * ws_receive_frame (unsigned char *frame, ssize_t *length, int *type) { unsigned char masks[4]; unsigned char mask; unsigned char *msg; unsigned char flength; unsigned char idx_first_mask; unsigned char idx_first_data; ssize_t data_length; int i; int j; msg = NULL; if (frame[0] == (WS_FIN | WS_OPCODE_TEXT_FRAME)) { *type = WS_OPCODE_TEXT_FRAME; idx_first_mask = 2; mask = frame[1]; flength = mask & 0x7F; if (flength == 126) { idx_first_mask = 4; } else if (flength == 127) { idx_first_mask = 10; } idx_first_data = idx_first_mask + 4; data_length = *length - idx_first_data; masks[0] = frame[idx_first_mask + 0]; masks[1] = frame[idx_first_mask + 1]; masks[2] = frame[idx_first_mask + 2]; masks[3] = frame[idx_first_mask + 3]; msg = malloc (data_length + 1); if (NULL != msg) { for (i = idx_first_data, j = 0; i < *length; i++, j++) { msg[j] = frame[i] ^ masks[j % 4]; } *length = data_length; msg[j] = '\0'; } } else if (frame[0] == (WS_FIN | WS_OPCODE_CON_CLOSE_FRAME)) { *type = WS_OPCODE_CON_CLOSE_FRAME; } else { *type = frame[0] & 0x0F; } return msg; } static void * run_usock (void *cls) { struct WsData *ws = cls; struct MHD_UpgradeResponseHandle *urh = ws->urh; unsigned char buf[2048]; unsigned char *msg; char client[20]; char *text; ssize_t got; size_t size; int type; int sent; int i; make_blocking (ws->sock); while (1) { got = recv (ws->sock, (void*) buf, sizeof (buf), 0); if (0 >= got) { break; } msg = ws_receive_frame (buf, &got, &type); if (NULL == msg) { break; } if (type == WS_OPCODE_TEXT_FRAME) { size = sprintf (client, "User#%d: ", (int) ws->sock); size += got; text = malloc (size); if (NULL != text) { sprintf (text, "%s%s", client, msg); sent = ws_send_frame (ws->sock, text, size); free (text); } else { sent = -1; } free (msg); if (-1 == sent) { break; } } else { if (type == WS_OPCODE_CON_CLOSE_FRAME) { free (msg); break; } } } pthread_mutex_lock (&MUTEX); for (i = 0; i < MAX_CLIENTS; i++) { if (CLIENT_SOCKS[i] == ws->sock) { CLIENT_SOCKS[i] = MHD_INVALID_SOCKET; break; } } pthread_mutex_unlock (&MUTEX); free (ws); MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE); return NULL; } static void uh_cb (void *cls, struct MHD_Connection *con, void *con_cls, const char *extra_in, size_t extra_in_size, MHD_socket sock, struct MHD_UpgradeResponseHandle *urh) { struct WsData *ws; pthread_t pt; int sock_overflow; int i; (void) cls; /* Unused. Silent compiler warning. */ (void) con; /* Unused. Silent compiler warning. */ (void) con_cls; /* Unused. Silent compiler warning. */ (void) extra_in; /* Unused. Silent compiler warning. */ (void) extra_in_size; /* Unused. Silent compiler warning. */ ws = malloc (sizeof (struct WsData)); if (NULL == ws) abort (); memset (ws, 0, sizeof (struct WsData)); ws->sock = sock; ws->urh = urh; sock_overflow = MHD_YES; pthread_mutex_lock (&MUTEX); for (i = 0; i < MAX_CLIENTS; i++) { if (MHD_INVALID_SOCKET == CLIENT_SOCKS[i]) { CLIENT_SOCKS[i] = ws->sock; sock_overflow = MHD_NO; break; } } pthread_mutex_unlock (&MUTEX); if (sock_overflow) { free (ws); MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE); return; } if (0 != pthread_create (&pt, NULL, &run_usock, ws)) abort (); /* Note that by detaching like this we make it impossible to ensure a clean shutdown, as the we stop the daemon even if a worker thread is still running. Alas, this is a simple example... */ pthread_detach (pt); } static enum MHD_Result ahc_cb (void *cls, struct MHD_Connection *con, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr) { struct MHD_Response *res; const char *upg_header; const char *con_header; const char *ws_version_header; const char *ws_key_header; char *ws_ac_value; enum MHD_Result ret; size_t key_size; (void) cls; /* Unused. Silent compiler warning. */ (void) url; /* Unused. Silent compiler warning. */ (void) upload_data; /* Unused. Silent compiler warning. */ (void) upload_data_size; /* Unused. Silent compiler warning. */ if (NULL == *ptr) { *ptr = (void *) 1; return MHD_YES; } *ptr = NULL; upg_header = MHD_lookup_connection_value (con, MHD_HEADER_KIND, MHD_HTTP_HEADER_UPGRADE); con_header = MHD_lookup_connection_value (con, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONNECTION); if (MHD_NO == is_websocket_request (con, upg_header, con_header)) { return send_chat_page (con); } if ((0 != strcmp (method, MHD_HTTP_METHOD_GET)) || (0 != strcmp (version, MHD_HTTP_VERSION_1_1))) { return send_bad_request (con); } ws_version_header = MHD_lookup_connection_value ( con, MHD_HEADER_KIND, MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION); if ((NULL == ws_version_header) || (0 != strcmp (ws_version_header, WS_SEC_WEBSOCKET_VERSION))) { return send_upgrade_required (con); } ret = MHD_lookup_connection_value_n ( con, MHD_HEADER_KIND, MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY, strlen (MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY), &ws_key_header, &key_size); if ((MHD_NO == ret) || (key_size != WS_KEY_LEN)) { return send_bad_request (con); } ret = ws_get_accept_value (ws_key_header, &ws_ac_value); if (MHD_NO == ret) { return ret; } res = MHD_create_response_for_upgrade (&uh_cb, NULL); MHD_add_response_header (res, MHD_HTTP_HEADER_UPGRADE, WS_UPGRADE_VALUE); MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, ws_ac_value); free (ws_ac_value); ret = MHD_queue_response (con, MHD_HTTP_SWITCHING_PROTOCOLS, res); MHD_destroy_response (res); return ret; } int main (int argc, char *const *argv) { struct MHD_Daemon *d; uint16_t port; size_t i; if (argc != 2) { printf ("%s PORT\n", argv[0]); return 1; } port = atoi (argv[1]); d = MHD_start_daemon (MHD_ALLOW_UPGRADE | MHD_USE_AUTO_INTERNAL_THREAD | MHD_USE_ERROR_LOG, port, NULL, NULL, &ahc_cb, &port, MHD_OPTION_END); if (NULL == d) return 1; for (i = 0; i < sizeof(CLIENT_SOCKS) / sizeof(CLIENT_SOCKS[0]); ++i) CLIENT_SOCKS[i] = MHD_INVALID_SOCKET; (void) getc (stdin); MHD_stop_daemon (d); return 0; }