├── AUTHORS ├── NEWS ├── .travis.yml ├── src ├── sha1.h ├── gwsocket.h ├── base64.h ├── xmalloc.h ├── gslist.h ├── log.h ├── base64.c ├── xmalloc.c ├── log.c ├── gslist.c ├── sha1.c ├── websocket.h ├── gwsocket.c └── websocket.c ├── .github └── FUNDING.yml ├── .gitignore ├── Makefile.am ├── COPYING ├── README ├── ChangeLog ├── configure.ac ├── README.md └── gwsocket.1 /AUTHORS: -------------------------------------------------------------------------------- 1 | gwsocket was designed and developed by Gerardo Orellana 2 | 3 | Special thanks to the following individuals for their great contributions: 4 | 5 | * AdamŠtevko 6 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 2 | Gerardo Orellana 3 | 4 | * Version history: 5 | - 0.3 [Friday, Aug 10, 2018] 6 | . gwsocket 0.3 Released. See ChangeLog for features. 7 | - 0.2 [Tuesday, Oct 11, 2016] 8 | . gwsocket 0.2 Released. See ChangeLog for features. 9 | - 0.1 [Monday, May 02, 2016] 10 | . gwsocket 0.1 Released. See ChangeLog for features. 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | compiler: 3 | - clang 4 | - gcc 5 | before_install: 6 | - sudo apt-get update -qq 7 | - sudo apt-get install -qq autoconf autotools-dev 8 | install: 9 | - autoreconf -fiv 10 | - ./configure 11 | - make 12 | - make distclean 13 | - autoreconf -fiv 14 | - ./configure --enable-debug 15 | - make 16 | - make distclean 17 | - autoreconf -fiv 18 | branches: 19 | only: 20 | - master 21 | script: "echo done" 22 | -------------------------------------------------------------------------------- /src/sha1.h: -------------------------------------------------------------------------------- 1 | #ifndef SHA1_H_INCLUDED 2 | #define SHA1_H_INCLUDED 3 | 4 | #include 5 | #include 6 | 7 | // From http://www.mirrors.wiretapped.net/security/cryptography/hashes/sha1/sha1.c 8 | 9 | typedef struct { 10 | uint32_t state[5]; 11 | uint32_t count[2]; 12 | uint8_t buffer[64]; 13 | } SHA1_CTX; 14 | 15 | extern void SHA1Init (SHA1_CTX * context); 16 | extern void SHA1Update (SHA1_CTX * context, uint8_t * data, uint32_t len); 17 | extern void SHA1Final (uint8_t digest[20], SHA1_CTX * context); 18 | 19 | #endif // for #ifndef SHA1_H 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: allinurl 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #custom section 2 | config.status 3 | config.guess 4 | config.sub 5 | /.deps 6 | /INSTALL 7 | /Makefile 8 | src/.dirstamp 9 | src/config.h 10 | src/config.h.in 11 | src/config.h.in~ 12 | src/config.log 13 | src/config.status 14 | src/.deps/ 15 | src/stamp-h1 16 | gwsocket 17 | #merged from https://raw.github.com/github/gitignore/master/Autotools.gitignore 18 | # http://www.gnu.org/software/automake 19 | 20 | Makefile.in 21 | 22 | # http://www.gnu.org/software/autoconf 23 | 24 | /autom4te.cache 25 | /aclocal.m4 26 | /compile 27 | /configure 28 | /depcomp 29 | /install-sh 30 | /missing 31 | 32 | # VIM 33 | *.*.swp 34 | *.log 35 | 36 | # Object files 37 | *.o 38 | *.ko 39 | *.obj 40 | *.elf 41 | 42 | # Precompiled Headers 43 | *.gch 44 | *.pch 45 | 46 | # Libraries 47 | *.lib 48 | *.a 49 | *.la 50 | *.lo 51 | 52 | # Shared objects (inc. Windows DLLs) 53 | *.dll 54 | *.so 55 | *.so.* 56 | *.dylib 57 | 58 | # Executables 59 | *.exe 60 | *.out 61 | *.app 62 | *.i*86 63 | *.x86_64 64 | *.hex 65 | 66 | # Vagrant 67 | .vagrant 68 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | #AUTOMAKE_OPTIONS = foreign 2 | bin_PROGRAMS = gwsocket 3 | AUTOMAKE_OPTIONS = subdir-objects 4 | 5 | gwsocket_SOURCES = \ 6 | src/base64.c \ 7 | src/base64.h \ 8 | src/gslist.c \ 9 | src/gslist.h \ 10 | src/gwsocket.c \ 11 | src/gwsocket.h \ 12 | src/log.c \ 13 | src/log.h \ 14 | src/sha1.c \ 15 | src/sha1.h \ 16 | src/websocket.c \ 17 | src/websocket.h \ 18 | src/xmalloc.c \ 19 | src/xmalloc.h 20 | 21 | if DEBUG 22 | AM_CFLAGS = -DDEBUG -O0 -g 23 | else 24 | AM_CFLAGS = -O2 25 | endif 26 | 27 | AM_LDFLAGS = 28 | if WITH_RDYNAMIC 29 | AM_LDFLAGS += -rdynamic 30 | endif 31 | 32 | if HAVE_OPENSSL 33 | AM_LDFLAGS += -rdynamic 34 | endif 35 | 36 | AM_CFLAGS += -Wno-long-long -Wall -W -Wnested-externs -Wformat=2 37 | AM_CFLAGS += -Wmissing-prototypes -Wstrict-prototypes -Wmissing-declarations 38 | AM_CFLAGS += -Wwrite-strings -Wshadow -Wpointer-arith -Wsign-compare 39 | AM_CFLAGS += -Wredundant-decls -Wbad-function-cast -Winline -Wcast-align -Wextra 40 | AM_CFLAGS += -Wdeclaration-after-statement -Wno-missing-field-initializers 41 | 42 | dist_man_MANS = gwsocket.1 43 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Gerardo Orellana 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | What is it? 2 | ----------- 3 | gwsocket is a standalone, simple, yet powerful rfc6455 compliant WebSocket 4 | Server, written in C. 5 | 6 | Why? 7 | ---- 8 | I needed a simple, fast, no-dependencies, RFC6455 compliant WebSocket Server 9 | written in C that I could use as a library for the upcoming version (v1.0) of 10 | GoAccess by simply piping data in and out. 11 | 12 | Features 13 | ------- 14 | * Message Fragmentation per section 5.4 15 | * UTF-8 Handling 16 | * Framing (Text & Binary messages) 17 | * Multiplexed non-blocking network I/O 18 | * Ability to pipe data in/out in two different modes (stdin/stdout & strict mode) 19 | * It passes the Autobahn Testsuite :) 20 | * and of course, Valgrind tested. 21 | * missing something?, please feel free to post it on Github. 22 | 23 | How it Works? 24 | ------------- 25 | Very simple, just pipe your data out of your application and let gwsocket do 26 | the rest. e.g.: tail -f /var/log/nginx/access.log > /tmp/wspipein.fifo 27 | 28 | By the way, you can also pipe the client's data into your application. 29 | 30 | Note: You can even send your favorite NCurses program's output. See 31 | https://github.com/allinurl/gwsocket. 32 | 33 | More Examples? 34 | ------------- 35 | Looking for more examples and details on how it works? Head to the man page for 36 | more details. Or visit http://gwsocket.io 37 | -------------------------------------------------------------------------------- /src/gwsocket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * _______ _______ __ __ 3 | * / ____/ | / / ___/____ _____/ /_____ / /_ 4 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 5 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 6 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 7 | * 8 | * The MIT License (MIT) 9 | * Copyright (c) 2009-2020 Gerardo Orellana 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | 30 | #ifndef WS_H_INCLUDED 31 | #define WS_H_INCLUDED 32 | 33 | #define GW_VERSION "0.4" 34 | 35 | #endif // for #ifndef WS_H 36 | -------------------------------------------------------------------------------- /src/base64.h: -------------------------------------------------------------------------------- 1 | /** 2 | * _______ _______ __ __ 3 | * / ____/ | / / ___/____ _____/ /_____ / /_ 4 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 5 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 6 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 7 | * 8 | * 9 | * The MIT License (MIT) 10 | * Copyright (c) 2009-2020 Gerardo Orellana 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | */ 30 | 31 | #ifndef BASE64_H_INCLUDED 32 | #define BASE64_H_INCLUDED 33 | 34 | #include 35 | 36 | char *base64_encode (const void *buf, size_t size); 37 | 38 | #endif // for #ifndef BASE64_H 39 | -------------------------------------------------------------------------------- /src/xmalloc.h: -------------------------------------------------------------------------------- 1 | /** 2 | * _______ _______ __ __ 3 | * / ____/ | / / ___/____ _____/ /_____ / /_ 4 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 5 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 6 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 7 | * 8 | * The MIT License (MIT) 9 | * Copyright (c) 2009-2020 Gerardo Orellana 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | 30 | #ifndef XMALLOC_H_INCLUDED 31 | #define XMALLOC_H_INCLUDED 32 | 33 | char *xstrdup (const char *s); 34 | void *xcalloc (size_t nmemb, size_t size); 35 | void *xmalloc (size_t size); 36 | void *xrealloc (void *oldptr, size_t size); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | Changes to GWSocket 0.4 - Friday, March 10 2023 2 | 3 | - Added missing '--addr' to the command long options. 4 | - Added support for 'stdin' / 'stdout'. 5 | - Added websocket server bind on unix-domain sockets via 6 | '--unix-socket='. 7 | - Ensure client is not accessed since it's closed and freed under 8 | handle_tcp_close(). 9 | - Ensure 'localtime_r' is use for thread-safe ops. 10 | - Ensure port is set upon passing '--port'. 11 | - Ensure we have a default path to WS_PIPEIN|WS_PIPEOUT. 12 | - Fixed build issue due to missing -lcrypto. 13 | - Fixed build issue with OpenSSL 1.1.0 or newer. 14 | - Fixed issue where an invalid client connection would stall data out to 15 | clients. 16 | - Fixed strict protocol invalid packet handling. 17 | - Fixed websocket issue returning a 400 due to request header size. 18 | - Replaced 'select(2)' with the more efficient 'poll(2)'. 19 | 20 | Changes to GWSocket 0.3 - Friday, August 10 2018 21 | 22 | - Added a 'build configuration' summary to configure.ac. 23 | - Added additional debug messages when starting WebSocket server. 24 | - Added support for openssl-1.1*. 25 | - Added support for TLS/SSL 26 | - Fixed clean up of invalid UTF-8 sequences in WebSocket server. 27 | - Fixed header parsing issue for certain hosts when trying to upgrade 28 | connection resulting in a 400 status code. 29 | - Removed FIPS_mode_set() from websocket.c to make it compatible with LibreSSL. 30 | 31 | Changes to GWSocket 0.2 - Tuesday, October 11, 2016 32 | 33 | - Added a throttle mechanism for slow clients. 34 | - Added byte-swapping functions for glibc < 2.9. 35 | - Added missing command long option for binding address. 36 | - Added the ability to display active connections on DEBUG mode. 37 | - Fixed byte swap functions for Sun Solaris. 38 | - Fixed memory leak on dangling client's referer header and queue on server 39 | close. 40 | - Fixed potential invalid memory read when queueing up fifo buffer. 41 | - Fixed uint types in sha1 files. 42 | - Refactored how all file descriptors are monitored under websocket.c. 43 | - Renamed --bind command line option to --addr. 44 | 45 | Changes to GWSocket 0.1 - Monday, May 02, 2016 46 | 47 | - Initial release 0.1 48 | -------------------------------------------------------------------------------- /src/gslist.h: -------------------------------------------------------------------------------- 1 | /** 2 | * _______ _______ __ __ 3 | * / ____/ | / / ___/____ _____/ /_____ / /_ 4 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 5 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 6 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 7 | * 8 | * 9 | * The MIT License (MIT) 10 | * Copyright (c) 2009-2020 Gerardo Orellana 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | */ 30 | 31 | #ifndef GSLIST_H_INCLUDED 32 | #define GSLIST_H_INCLUDED 33 | 34 | /* Generic Single linked-list */ 35 | typedef struct GSLList_ { 36 | void *data; 37 | struct GSLList_ *next; 38 | } GSLList; 39 | 40 | #define GSLIST_FOREACH(node, data, code) { \ 41 | GSLList *__tmp = node; \ 42 | while (__tmp) { \ 43 | (data) = __tmp->data; \ 44 | code; \ 45 | __tmp = __tmp->next; \ 46 | }} 47 | 48 | /* single linked-list */ 49 | GSLList *list_create (void *data); 50 | GSLList *list_find (GSLList * node, int (*func) (void *, void *), void *data); 51 | GSLList *list_insert_append (GSLList * node, void *data); 52 | GSLList *list_insert_prepend (GSLList * list, void *data); 53 | int list_count (GSLList * list); 54 | int list_foreach (GSLList * node, int (*func) (void *, void *), void *user_data); 55 | int list_remove_node (GSLList ** list, GSLList * node); 56 | int list_remove_nodes (GSLList * list); 57 | 58 | #endif // for #ifndef GSLIST_H 59 | -------------------------------------------------------------------------------- /src/log.h: -------------------------------------------------------------------------------- 1 | /** 2 | * _______ _______ __ __ 3 | * / ____/ | / / ___/____ _____/ /_____ / /_ 4 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 5 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 6 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 7 | * 8 | * The MIT License (MIT) 9 | * Copyright (c) 2009-2020 Gerardo Orellana 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | 30 | #ifndef ERROR_H_INCLUDED 31 | #define ERROR_H_INCLUDED 32 | 33 | #define FATAL(fmt, ...) do { \ 34 | fprintf (stderr, "\nFatal error has occurred"); \ 35 | fprintf (stderr, "\nError occured at: %s - %s - %d\n", __FILE__, \ 36 | __FUNCTION__, __LINE__); \ 37 | fprintf (stderr, fmt, ##__VA_ARGS__); \ 38 | fprintf (stderr, "\n\n"); \ 39 | exit(EXIT_FAILURE); \ 40 | } while (0) 41 | 42 | #ifdef DEBUG 43 | #define DEBUG_TEST 1 44 | #else 45 | #define DEBUG_TEST 0 46 | #endif 47 | 48 | #define LOG(x) do { if (DEBUG_TEST) dbg_printf x; } while (0) 49 | /* access requests log */ 50 | #define ACCESS_LOG(x, ...) do { access_fprintf x; } while (0) 51 | 52 | int access_log_open (const char *path); 53 | void access_fprintf (const char *fmt, ...); 54 | void access_log_close (void); 55 | void dbg_printf (const char *fmt, ...); 56 | 57 | #endif // for #ifndef ERROR_H 58 | -------------------------------------------------------------------------------- /src/base64.c: -------------------------------------------------------------------------------- 1 | /** 2 | * base64.c -- A basic base64 encode implementation 3 | * _______ _______ __ __ 4 | * / ____/ | / / ___/____ _____/ /_____ / /_ 5 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 6 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 7 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 8 | * 9 | * 10 | * The MIT License (MIT) 11 | * Copyright (c) 2009-2020 Gerardo Orellana 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in all 21 | * copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | #include 34 | 35 | #include "base64.h" 36 | 37 | /* Encodes the given data with base64.. 38 | * 39 | * On success, the encoded nul-terminated data, as a string is returned. */ 40 | char * 41 | base64_encode (const void *buf, size_t size) { 42 | static const char base64[] = 43 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 44 | 45 | char *str = (char *) malloc ((size + 3) * 4 / 3 + 1); 46 | 47 | char *p = str; 48 | const unsigned char *q = (const unsigned char *) buf; 49 | size_t i = 0; 50 | 51 | while (i < size) { 52 | int c = q[i++]; 53 | c *= 256; 54 | if (i < size) 55 | c += q[i]; 56 | i++; 57 | 58 | c *= 256; 59 | if (i < size) 60 | c += q[i]; 61 | i++; 62 | 63 | *p++ = base64[(c & 0x00fc0000) >> 18]; 64 | *p++ = base64[(c & 0x0003f000) >> 12]; 65 | 66 | if (i > size + 1) 67 | *p++ = '='; 68 | else 69 | *p++ = base64[(c & 0x00000fc0) >> 6]; 70 | 71 | if (i > size) 72 | *p++ = '='; 73 | else 74 | *p++ = base64[c & 0x0000003f]; 75 | } 76 | 77 | *p = 0; 78 | 79 | return str; 80 | } 81 | -------------------------------------------------------------------------------- /src/xmalloc.c: -------------------------------------------------------------------------------- 1 | /** 2 | * xmalloc.c -- *alloc functions with error handling. 3 | * _______ _______ __ __ 4 | * / ____/ | / / ___/____ _____/ /_____ / /_ 5 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 6 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 7 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 8 | * 9 | * The MIT License (MIT) 10 | * Copyright (c) 2009-2020 Gerardo Orellana 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | */ 30 | 31 | #include 32 | #if !defined __SUNPRO_C 33 | #include 34 | #endif 35 | #include 36 | #include 37 | 38 | #include "xmalloc.h" 39 | 40 | #include "log.h" 41 | 42 | /* Self-checking wrapper to malloc() */ 43 | void * 44 | xmalloc (size_t size) { 45 | void *ptr; 46 | 47 | if ((ptr = malloc (size)) == NULL) 48 | FATAL ("Unable to allocate memory - failed."); 49 | 50 | return (ptr); 51 | } 52 | 53 | char * 54 | xstrdup (const char *s) { 55 | char *ptr; 56 | size_t len; 57 | 58 | len = strlen (s) + 1; 59 | ptr = xmalloc (len); 60 | 61 | strncpy (ptr, s, len); 62 | return (ptr); 63 | } 64 | 65 | /* Self-checking wrapper to calloc() */ 66 | void * 67 | xcalloc (size_t nmemb, size_t size) { 68 | void *ptr; 69 | 70 | if ((ptr = calloc (nmemb, size)) == NULL) 71 | FATAL ("Unable to calloc memory - failed."); 72 | 73 | return (ptr); 74 | } 75 | 76 | /* Self-checking wrapper to realloc() */ 77 | void * 78 | xrealloc (void *oldptr, size_t size) { 79 | void *newptr; 80 | 81 | if ((newptr = realloc (oldptr, size)) == NULL) 82 | FATAL ("Unable to reallocate memory - failed"); 83 | 84 | return (newptr); 85 | } 86 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | /** 2 | * log.c -- log and error handling. 3 | * _______ _______ __ __ 4 | * / ____/ | / / ___/____ _____/ /_____ / /_ 5 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 6 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 7 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 8 | * 9 | * The MIT License (MIT) 10 | * Copyright (c) 2009-2020 Gerardo Orellana 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "log.h" 37 | #include "xmalloc.h" 38 | 39 | static FILE *log_file; 40 | 41 | /* Open a access file whose name is specified in the given path. */ 42 | int 43 | access_log_open (const char *path) { 44 | if (path == NULL) 45 | return 0; 46 | 47 | if (access (path, F_OK) != -1) 48 | log_file = fopen (path, "a"); 49 | else 50 | log_file = fopen (path, "w"); 51 | if (log_file == NULL) 52 | return 1; 53 | 54 | return 0; 55 | } 56 | 57 | /* Close the access log file. */ 58 | void 59 | access_log_close (void) { 60 | if (log_file != NULL) 61 | fclose (log_file); 62 | } 63 | 64 | #pragma GCC diagnostic ignored "-Wformat-nonliteral" 65 | /* Debug otuput */ 66 | void 67 | dbg_printf (const char *fmt, ...) { 68 | va_list args; 69 | va_start (args, fmt); 70 | vfprintf (stderr, fmt, args); 71 | va_end (args); 72 | } 73 | 74 | /* Write formatted access log data to the logfile. */ 75 | void 76 | access_fprintf (const char *fmt, ...) { 77 | va_list args; 78 | 79 | if (!log_file) 80 | return; 81 | 82 | va_start (args, fmt); 83 | vfprintf (log_file, fmt, args); 84 | fflush (log_file); 85 | va_end (args); 86 | } 87 | 88 | #pragma GCC diagnostic warning "-Wformat-nonliteral" 89 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # -*- Autoconf -*- 2 | # Process this file with autoconf to produce a configure script. 3 | 4 | AC_PREREQ([2.59]) 5 | AC_INIT([gwsocket], [0.4], [goaccess@prosoftcorp.com], [], [http://gwsocket.io]) 6 | AM_INIT_AUTOMAKE 7 | AC_CONFIG_SRCDIR([src/gwsocket.c]) 8 | AC_CONFIG_HEADERS([src/config.h]) 9 | 10 | # Use empty CFLAGS by default so autoconf does not add 11 | # CFLAGS="-O2 -g" 12 | : ${CFLAGS=""} 13 | 14 | # Checks for programs. 15 | AC_PROG_CC 16 | AM_PROG_CC_C_O 17 | 18 | # DEBUG 19 | AC_ARG_ENABLE(debug, [ --enable-debug Create a debug build. Default is disabled], 20 | [debug="$enableval"], debug=no) 21 | 22 | if test "$debug" = "yes"; then 23 | AC_DEFINE([_DEBUG], 1, [Debug option]) 24 | fi 25 | AM_CONDITIONAL([DEBUG], [test "x$debug" = "xyes"]) 26 | 27 | # Handle rdynamic only on systems using GNU ld 28 | AC_CANONICAL_HOST 29 | AC_MSG_CHECKING([whether to build with rdynamic for GNU ld]) 30 | with_rdyanimc=yes 31 | case "$host_os" in 32 | *darwin*|*cygwin*|*aix*|*mingw*) with_rdyanimc=no 33 | ;; 34 | esac 35 | AC_MSG_RESULT([$with_rdyanimc]) 36 | AM_CONDITIONAL([WITH_RDYNAMIC], [test "x$with_rdyanimc" = "xyes"]) 37 | 38 | # Checks for libraries. 39 | AC_ARG_WITH([openssl],AC_HELP_STRING([--with-openssl], [build with OpenSSL support]), 40 | [openssl="$withval"],[openssl="no"]) 41 | 42 | # Build with OpenSSL 43 | if test "$openssl" = 'yes'; then 44 | AC_CHECK_LIB([ssl], [SSL_SESSION_new],,[AC_MSG_ERROR([ssl library missing])]) 45 | AC_CHECK_LIB([crypto], [CRYPTO_free],,[AC_MSG_ERROR([crypto library missing])]) 46 | fi 47 | # For Makefile.am 48 | AM_CONDITIONAL(HAVE_OPENSSL, test "$openssl" = "yes") 49 | 50 | # Checks for header files. 51 | AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h netdb.h netinet/in.h stddef.h stdint.h stdlib.h string.h sys/socket.h sys/time.h unistd.h]) 52 | 53 | # Checks for typedefs, structures, and compiler characteristics. 54 | AC_TYPE_SIZE_T 55 | AC_TYPE_UINT16_T 56 | AC_TYPE_UINT32_T 57 | AC_TYPE_UINT64_T 58 | AC_TYPE_UINT8_T 59 | AC_CHECK_TYPES([ptrdiff_t]) 60 | 61 | # Checks for library functions. 62 | AC_FUNC_MALLOC 63 | AC_FUNC_REALLOC 64 | AC_CHECK_FUNCS([gettimeofday]) 65 | AC_CHECK_FUNCS([malloc]) 66 | AC_CHECK_FUNCS([memmove]) 67 | AC_CHECK_FUNCS([memset]) 68 | AC_CHECK_FUNCS([mkfifo]) 69 | AC_CHECK_FUNCS([poll]) 70 | AC_CHECK_FUNCS([realloc]) 71 | AC_CHECK_FUNCS([socket]) 72 | AC_CHECK_FUNCS([strcasecmp]) 73 | AC_CHECK_FUNCS([strchr]) 74 | AC_CHECK_FUNCS([strerror]) 75 | AC_CHECK_FUNCS([strcasecmp]) 76 | AC_CHECK_FUNCS([strpbrk]) 77 | AC_CHECK_FUNCS([strrchr]) 78 | AC_CHECK_FUNCS([strstr]) 79 | 80 | AC_CONFIG_FILES([Makefile]) 81 | AC_OUTPUT 82 | 83 | cat << EOF 84 | 85 | Your build configuration: 86 | 87 | Prefix : $prefix 88 | Package : $PACKAGE_NAME 89 | Version : $VERSION 90 | Compiler flags : $CFLAGS 91 | Linker flags : $LIBS $LDFLAGS 92 | TLS/SSL : $openssl 93 | Bugs : $PACKAGE_BUGREPORT 94 | 95 | EOF 96 | -------------------------------------------------------------------------------- /src/gslist.c: -------------------------------------------------------------------------------- 1 | /** 2 | * gslist.c -- A Singly link list implementation 3 | * _______ _______ __ __ 4 | * / ____/ | / / ___/____ _____/ /_____ / /_ 5 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 6 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 7 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 8 | * 9 | * 10 | * The MIT License (MIT) 11 | * Copyright (c) 2009-2020 Gerardo Orellana 12 | * 13 | * Permission is hereby granted, free of charge, to any person obtaining a copy 14 | * of this software and associated documentation files (the "Software"), to deal 15 | * in the Software without restriction, including without limitation the rights 16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | * copies of the Software, and to permit persons to whom the Software is 18 | * furnished to do so, subject to the following conditions: 19 | * 20 | * The above copyright notice and this permission notice shall be included in all 21 | * copies or substantial portions of the Software. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | * SOFTWARE. 30 | */ 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "gslist.h" 38 | #include "xmalloc.h" 39 | 40 | /* Instantiate a new Single linked-list node. 41 | * 42 | * On error, aborts if node can't be malloc'd. 43 | * On success, the GSLList node. */ 44 | GSLList * 45 | list_create (void *data) { 46 | GSLList *node = xmalloc (sizeof (GSLList)); 47 | node->data = data; 48 | node->next = NULL; 49 | 50 | return node; 51 | } 52 | 53 | /* Create and insert a node after a given node. 54 | * 55 | * On error, aborts if node can't be malloc'd. 56 | * On success, the newly created node. */ 57 | GSLList * 58 | list_insert_append (GSLList * node, void *data) { 59 | GSLList *newnode; 60 | newnode = list_create (data); 61 | newnode->next = node->next; 62 | node->next = newnode; 63 | 64 | return newnode; 65 | } 66 | 67 | /* Create and insert a node in front of the list. 68 | * 69 | * On error, aborts if node can't be malloc'd. 70 | * On success, the newly created node. */ 71 | GSLList * 72 | list_insert_prepend (GSLList * list, void *data) { 73 | GSLList *newnode; 74 | newnode = list_create (data); 75 | newnode->next = list; 76 | 77 | return newnode; 78 | } 79 | 80 | /* Find a node given a pointer to a function that compares them. 81 | * 82 | * If comparison fails, NULL is returned. 83 | * On success, the existing node is returned. */ 84 | GSLList * 85 | list_find (GSLList * node, int (*func) (void *, void *), void *data) { 86 | while (node) { 87 | if (func (node->data, data) > 0) 88 | return node; 89 | node = node->next; 90 | } 91 | 92 | return NULL; 93 | } 94 | 95 | /* Remove all nodes from the list. 96 | * 97 | * On success, 0 is returned. */ 98 | int 99 | list_remove_nodes (GSLList * list) { 100 | GSLList *tmp; 101 | while (list != NULL) { 102 | tmp = list->next; 103 | if (list->data) 104 | free (list->data); 105 | free (list); 106 | list = tmp; 107 | } 108 | 109 | return 0; 110 | } 111 | 112 | /* Remove the given node from the list. 113 | * 114 | * On error, 1 is returned. 115 | * On success, 0 is returned. */ 116 | int 117 | list_remove_node (GSLList ** list, GSLList * node) { 118 | GSLList **current = list, *next = NULL; 119 | for (; *current; current = &(*current)->next) { 120 | if ((*current) != node) 121 | continue; 122 | 123 | next = (*current)->next; 124 | if ((*current)->data) 125 | free ((*current)->data); 126 | free (*current); 127 | *current = next; 128 | return 0; 129 | } 130 | return 1; 131 | } 132 | 133 | /* Iterate over the single linked-list and call function pointer. 134 | * 135 | * If function pointer does not return 0, -1 is returned. 136 | * On success, 0 is returned. */ 137 | int 138 | list_foreach (GSLList * node, int (*func) (void *, void *), void *user_data) { 139 | while (node) { 140 | if (func (node->data, user_data) != 0) 141 | return -1; 142 | node = node->next; 143 | } 144 | 145 | return 0; 146 | } 147 | 148 | /* Count the number of elements on the linked-list. 149 | * 150 | * On success, the number of elements is returned. */ 151 | int 152 | list_count (GSLList * node) { 153 | int count = 0; 154 | while (node != 0) { 155 | count++; 156 | node = node->next; 157 | } 158 | return count; 159 | } 160 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gwsocket [![gwsocket](http://gwsocket.io/badge?v0.1)](http://gwsocket.io) 2 | ======== 3 | 4 | ## What is it? ## 5 | **gwsocket** is a simple, standalone, language-agnostic, RFC6455 compliant 6 | WebSocket Server, written in C. It sits between your application and the 7 | client's browser, giving fast bidirectional communication between these two 8 | with ease and flexibility. More info at: 9 | [https://gwsocket.io](https://gwsocket.io/?src=gh). 10 | 11 | ![gwsocket terminal](https://cloud.githubusercontent.com/assets/5005367/19279261/515b6ec6-8fa6-11e6-8bc5-e109710bfb56.gif) 12 | 13 | ## How it Works? ## 14 | Very simple, just redirect the output from your application **(stdout)** to 15 | `stdin` or to file (named pipe) and let **gwsocket** transfer the data to the 16 | browser — That's it. 17 | 18 | For example, tailing your server's logs into the browser couldn't be easier 19 | 20 | # tail -f /var/log/nginx/access.log > /tmp/wspipein.fifo 21 | 22 | OR 23 | 24 | # tail -f /var/log/nginx/access.log | gwsocket 25 | 26 | You can also get the client's data into **(stdin)** your application. In fact, 27 | you can even send your favorite ncurses program's output to the browser. See 28 | screencast above. 29 | 30 | ## Features ## 31 | * Message Fragmentation per section [5.4](https://tools.ietf.org/html/rfc6455#page-33) 32 | * UTF-8 Handling 33 | * Framing (Text & Binary messages) 34 | * Multiplexed non-blocking network I/O 35 | * Ability to pipe data in/out in two different modes (**stdin/stdout & strict mode**) 36 | * Origin-based restriction 37 | * It passes the [Autobahn Testsuite](https://gwsocket.io/autobahn/) :) 38 | * and of course, [Valgrind](http://valgrind.org/) tested 39 | * missing something?, please feel free to post it on Github. 40 | 41 | ## Why gwsocket? ## 42 | I needed a **fast**, **simple**, **no-dependencies**, **no libraries**, 43 | **RFC6455 compliant** WebSocket Server written in C that I could use for 44 | version 1.0 of [**GoAccess**](https://goaccess.io/) by simply piping data in 45 | and out — WebSockets made easy! 46 | 47 | ## More Examples? ## 48 | gwsocket is language agnostic, look at the [Man Page](https://gwsocket.io/man?src=gh) 49 | for more details and examples on how to receive data from the browser and how 50 | to send it to the browser. 51 | 52 | ## Installation ## 53 | Installing gwsocket is pretty easy. Just download, extract and compile it with: 54 | 55 | ``` 56 | $ wget https://tar.gwsocket.io/gwsocket-0.4.tar.gz 57 | $ tar -xzvf gwsocket-0.4.tar.gz 58 | $ cd gwsocket-0.4/ 59 | $ ./configure 60 | $ make 61 | # make install 62 | ``` 63 | No dependencies needed. How nice isn't it :), well almost, you need `gcc`, `make`, etc. 64 | 65 | ## Build from GitHub ## 66 | ``` 67 | $ git clone s://github.com/allinurl/gwsocket.git 68 | $ cd gwsocket 69 | $ autoreconf -fiv 70 | $ ./configure 71 | $ make 72 | # make install 73 | ``` 74 | 75 | ## Data Modes ## 76 | In order to establish a channel between your application and the client's 77 | browser, gwsocket provides two methods that allow the user to send data in and 78 | out. The first one is through the use of the standard input (stdin), and the 79 | standard output (stdout). The second method is through a fixed-size header 80 | followed by the payload. See options below for more details. 81 | 82 | ### STDIN/STDOUT ### 83 | The standard input/output is the simplest way of sending/receiving data to/from 84 | a client. However, it's limited to broadcasting messages to all clients. To 85 | send messages to or receive from a specific client, use the strict mode in the 86 | next section. See language specific examples [here](https://gwsocket.io/). 87 | 88 | ### Strict Mode ### 89 | gwsocket implements its own tiny protocol for sending/receiving data. In 90 | contrast to the **stdin/stdout** mode, the strict mode allows you to 91 | send/receive data to/from specific connected clients as well as to keep track 92 | of who opened/closed a WebSocket connection. It also gives you the ability to 93 | pack and send as much data as you would like on a single message. See language 94 | specific examples [here](https://gwsocket.io/). 95 | 96 | ## Command Line / Config Options ## 97 | The following options can be supplied to the command line. 98 | 99 | 100 | | Command Line Option | Description | 101 | | ---------------------------- | --------------------------------------------------------------------| 102 | | `-p --port` | Specifies the port to bind. | 103 | | `-h --help` | Command line help. | 104 | | `-V --version` | Display version information and exit. | 105 | | `--access-log=` | Specifies the path/file for the access log. | 106 | | `--addr=` | Specifies the address to bind. | 107 | | `--echo-mode` | Set the server to echo all received messages. | 108 | | `--max-frame-size=` | Maximum size of a websocket frame. | 109 | | `--origin=` | Ensure clients send the specified origin header upon handshake. | 110 | | `--pipein=` | Creates a named pipe (FIFO) that reads from on the given path/file. | 111 | | `--pipeout=` | Creates a named pipe (FIFO) that writes to the given path/file. | 112 | | `--std` | Enable `--stdin` and `--stdout`. | 113 | | `--stdin` | Send stdin to the websocket. | 114 | | `--stdout` | Send received websocket data to stdout. | 115 | | `--strict` | Parse messages using strict mode. See man page for more details. | 116 | | `--ssl-cert=` | Path to SSL certificate. | 117 | | `--ssl-key=` | Path to SSL private key. | 118 | | `--unix-socket=` | Specify UNIX-domain socket address to bind server to. | 119 | 120 | ## Roadmap ## 121 | * Support for `epoll` and `kqueue` 122 | * Add more command line options 123 | * Add configuration file 124 | * Please feel free to open an issue to discuss a new feature. 125 | 126 | ## License ## 127 | MIT Licensed 128 | 129 | ## Contributing ## 130 | 131 | Any help on gwsocket is welcome. The most helpful way is to try it out and give 132 | feedback. Feel free to use the Github issue tracker and pull requests to 133 | discuss and submit code changes. 134 | 135 | Enjoy! 136 | -------------------------------------------------------------------------------- /src/sha1.c: -------------------------------------------------------------------------------- 1 | /* 2 | SHA-1 in C 3 | By Steve Reid 4 | 100% Public Domain 5 | 6 | Test Vectors (from FIPS PUB 180-1) 7 | "abc" 8 | A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D 9 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 10 | 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 11 | A million repetitions of "a" 12 | 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F 13 | */ 14 | 15 | /* #define LITTLE_ENDIAN * This should be #define'd if true. */ 16 | #if __LITTLE_ENDIAN__ 17 | #define LITTLE_ENDIAN 18 | #endif 19 | /* #define SHA1HANDSOFF * Copies data before messing with it. */ 20 | 21 | #include 22 | #include 23 | 24 | #include "sha1.h" 25 | 26 | void SHA1Transform (uint32_t state[5], uint8_t buffer[64]); 27 | 28 | #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) 29 | 30 | /* blk0() and blk() perform the initial expand. */ 31 | /* I got the idea of expanding during the round function from SSLeay */ 32 | #ifdef LITTLE_ENDIAN 33 | #define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ 34 | |(rol(block->l[i],8)&0x00FF00FF)) 35 | #else 36 | #define blk0(i) block->l[i] 37 | #endif 38 | #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ 39 | ^block->l[(i+2)&15]^block->l[i&15],1)) 40 | 41 | /* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ 42 | #define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); 43 | #define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); 44 | #define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); 45 | #define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); 46 | #define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); 47 | 48 | 49 | /* Hash a single 512-bit block. This is the core of the algorithm. */ 50 | 51 | void 52 | SHA1Transform (uint32_t state[5], uint8_t buffer[64]) { 53 | uint32_t a, b, c, d, e; 54 | typedef union { 55 | uint8_t c[64]; 56 | uint32_t l[16]; 57 | } CHAR64LONG16; 58 | CHAR64LONG16 *block; 59 | #ifdef SHA1HANDSOFF 60 | static uint8_t workspace[64]; 61 | block = (CHAR64LONG16 *) workspace; 62 | memcpy (block, buffer, 64); 63 | #else 64 | block = (CHAR64LONG16 *) buffer; 65 | #endif 66 | /* Copy context->state[] to working vars */ 67 | a = state[0]; 68 | b = state[1]; 69 | c = state[2]; 70 | d = state[3]; 71 | e = state[4]; 72 | /* 4 rounds of 20 operations each. Loop unrolled. */ 73 | R0 (a, b, c, d, e, 0); 74 | R0 (e, a, b, c, d, 1); 75 | R0 (d, e, a, b, c, 2); 76 | R0 (c, d, e, a, b, 3); 77 | R0 (b, c, d, e, a, 4); 78 | R0 (a, b, c, d, e, 5); 79 | R0 (e, a, b, c, d, 6); 80 | R0 (d, e, a, b, c, 7); 81 | R0 (c, d, e, a, b, 8); 82 | R0 (b, c, d, e, a, 9); 83 | R0 (a, b, c, d, e, 10); 84 | R0 (e, a, b, c, d, 11); 85 | R0 (d, e, a, b, c, 12); 86 | R0 (c, d, e, a, b, 13); 87 | R0 (b, c, d, e, a, 14); 88 | R0 (a, b, c, d, e, 15); 89 | R1 (e, a, b, c, d, 16); 90 | R1 (d, e, a, b, c, 17); 91 | R1 (c, d, e, a, b, 18); 92 | R1 (b, c, d, e, a, 19); 93 | R2 (a, b, c, d, e, 20); 94 | R2 (e, a, b, c, d, 21); 95 | R2 (d, e, a, b, c, 22); 96 | R2 (c, d, e, a, b, 23); 97 | R2 (b, c, d, e, a, 24); 98 | R2 (a, b, c, d, e, 25); 99 | R2 (e, a, b, c, d, 26); 100 | R2 (d, e, a, b, c, 27); 101 | R2 (c, d, e, a, b, 28); 102 | R2 (b, c, d, e, a, 29); 103 | R2 (a, b, c, d, e, 30); 104 | R2 (e, a, b, c, d, 31); 105 | R2 (d, e, a, b, c, 32); 106 | R2 (c, d, e, a, b, 33); 107 | R2 (b, c, d, e, a, 34); 108 | R2 (a, b, c, d, e, 35); 109 | R2 (e, a, b, c, d, 36); 110 | R2 (d, e, a, b, c, 37); 111 | R2 (c, d, e, a, b, 38); 112 | R2 (b, c, d, e, a, 39); 113 | R3 (a, b, c, d, e, 40); 114 | R3 (e, a, b, c, d, 41); 115 | R3 (d, e, a, b, c, 42); 116 | R3 (c, d, e, a, b, 43); 117 | R3 (b, c, d, e, a, 44); 118 | R3 (a, b, c, d, e, 45); 119 | R3 (e, a, b, c, d, 46); 120 | R3 (d, e, a, b, c, 47); 121 | R3 (c, d, e, a, b, 48); 122 | R3 (b, c, d, e, a, 49); 123 | R3 (a, b, c, d, e, 50); 124 | R3 (e, a, b, c, d, 51); 125 | R3 (d, e, a, b, c, 52); 126 | R3 (c, d, e, a, b, 53); 127 | R3 (b, c, d, e, a, 54); 128 | R3 (a, b, c, d, e, 55); 129 | R3 (e, a, b, c, d, 56); 130 | R3 (d, e, a, b, c, 57); 131 | R3 (c, d, e, a, b, 58); 132 | R3 (b, c, d, e, a, 59); 133 | R4 (a, b, c, d, e, 60); 134 | R4 (e, a, b, c, d, 61); 135 | R4 (d, e, a, b, c, 62); 136 | R4 (c, d, e, a, b, 63); 137 | R4 (b, c, d, e, a, 64); 138 | R4 (a, b, c, d, e, 65); 139 | R4 (e, a, b, c, d, 66); 140 | R4 (d, e, a, b, c, 67); 141 | R4 (c, d, e, a, b, 68); 142 | R4 (b, c, d, e, a, 69); 143 | R4 (a, b, c, d, e, 70); 144 | R4 (e, a, b, c, d, 71); 145 | R4 (d, e, a, b, c, 72); 146 | R4 (c, d, e, a, b, 73); 147 | R4 (b, c, d, e, a, 74); 148 | R4 (a, b, c, d, e, 75); 149 | R4 (e, a, b, c, d, 76); 150 | R4 (d, e, a, b, c, 77); 151 | R4 (c, d, e, a, b, 78); 152 | R4 (b, c, d, e, a, 79); 153 | /* Add the working vars back into context.state[] */ 154 | state[0] += a; 155 | state[1] += b; 156 | state[2] += c; 157 | state[3] += d; 158 | state[4] += e; 159 | /* Wipe variables */ 160 | a = b = c = d = e = 0; 161 | } 162 | 163 | 164 | /* SHA1Init - Initialize new context */ 165 | 166 | void 167 | SHA1Init (SHA1_CTX * context) { 168 | /* SHA1 initialization constants */ 169 | context->state[0] = 0x67452301; 170 | context->state[1] = 0xEFCDAB89; 171 | context->state[2] = 0x98BADCFE; 172 | context->state[3] = 0x10325476; 173 | context->state[4] = 0xC3D2E1F0; 174 | context->count[0] = context->count[1] = 0; 175 | } 176 | 177 | 178 | /* Run your data through this. */ 179 | 180 | void 181 | SHA1Update (SHA1_CTX * context, uint8_t * data, unsigned int len) { 182 | unsigned int i, j; 183 | 184 | j = (context->count[0] >> 3) & 63; 185 | if ((context->count[0] += len << 3) < (len << 3)) 186 | context->count[1]++; 187 | context->count[1] += (len >> 29); 188 | if ((j + len) > 63) { 189 | memcpy (&context->buffer[j], data, (i = 64 - j)); 190 | SHA1Transform (context->state, context->buffer); 191 | for (; i + 63 < len; i += 64) { 192 | SHA1Transform (context->state, &data[i]); 193 | } 194 | j = 0; 195 | } else 196 | i = 0; 197 | memcpy (&context->buffer[j], &data[i], len - i); 198 | } 199 | 200 | 201 | /* Add padding and return the message digest. */ 202 | 203 | void 204 | SHA1Final (uint8_t digest[20], SHA1_CTX * context) { 205 | uint32_t i, j; 206 | uint8_t finalcount[8]; 207 | 208 | for (i = 0; i < 8; i++) { 209 | finalcount[i] = (uint8_t) ((context->count[(i >= 4 ? 0 : 1)] 210 | >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ 211 | } 212 | SHA1Update (context, (uint8_t *) "\200", 1); 213 | while ((context->count[0] & 504) != 448) { 214 | SHA1Update (context, (uint8_t *) "\0", 1); 215 | } 216 | SHA1Update (context, finalcount, 8); /* Should cause a SHA1Transform() */ 217 | for (i = 0; i < 20; i++) { 218 | digest[i] = (uint8_t) 219 | ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); 220 | } 221 | /* Wipe variables */ 222 | i = j = 0; 223 | memset (context->buffer, 0, 64); 224 | memset (context->state, 0, 20); 225 | memset (context->count, 0, 8); 226 | memset (&finalcount, 0, 8); 227 | #ifdef SHA1HANDSOFF /* make SHA1Transform overwrite it's own static vars */ 228 | SHA1Transform (context->state, context->buffer); 229 | #endif 230 | } 231 | -------------------------------------------------------------------------------- /src/websocket.h: -------------------------------------------------------------------------------- 1 | /** 2 | * _______ _______ __ __ 3 | * / ____/ | / / ___/____ _____/ /_____ / /_ 4 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 5 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 6 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 7 | * 8 | * The MIT License (MIT) 9 | * Copyright (c) 2009-2020 Gerardo Orellana 10 | * 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in all 19 | * copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | * SOFTWARE. 28 | */ 29 | 30 | #ifndef WEBSOCKET_H_INCLUDED 31 | #define WEBSOCKET_H_INCLUDED 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #if HAVE_LIBSSL 39 | #include 40 | #include 41 | #include 42 | #endif 43 | 44 | #if defined(__linux__) || defined(__CYGWIN__) 45 | # include 46 | #if ((__GLIBC__ == 2) && (__GLIBC_MINOR__ < 9)) 47 | #if defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN) 48 | # include 49 | # define htobe16(x) htons(x) 50 | # define htobe64(x) (((uint64_t)htonl(((uint32_t)(((uint64_t)(x)) >> 32)))) | \ 51 | (((uint64_t)htonl(((uint32_t)(x)))) << 32)) 52 | # define be16toh(x) ntohs(x) 53 | # define be32toh(x) ntohl(x) 54 | # define be64toh(x) (((uint64_t)ntohl(((uint32_t)(((uint64_t)(x)) >> 32)))) | \ 55 | (((uint64_t)ntohl(((uint32_t)(x)))) << 32)) 56 | #else 57 | # error Byte Order not supported! 58 | #endif 59 | #endif 60 | #elif defined(__sun__) 61 | # include 62 | # define htobe16(x) BE_16(x) 63 | # define htobe64(x) BE_64(x) 64 | # define be16toh(x) BE_IN16(x) 65 | # define be32toh(x) BE_IN32(x) 66 | # define be64toh(x) BE_IN64(x) 67 | #elif defined(__FreeBSD__) || defined(__NetBSD__) 68 | # include 69 | #elif defined(__OpenBSD__) 70 | # include 71 | # if !defined(be16toh) 72 | # define be16toh(x) betoh16(x) 73 | # endif 74 | # if !defined(be32toh) 75 | # define be32toh(x) betoh32(x) 76 | # endif 77 | # if !defined(be64toh) 78 | # define be64toh(x) betoh64(x) 79 | # endif 80 | #elif defined(__APPLE__) 81 | # include 82 | # define htobe16(x) OSSwapHostToBigInt16(x) 83 | # define htobe64(x) OSSwapHostToBigInt64(x) 84 | # define be16toh(x) OSSwapBigToHostInt16(x) 85 | # define be32toh(x) OSSwapBigToHostInt32(x) 86 | # define be64toh(x) OSSwapBigToHostInt64(x) 87 | #else 88 | # error Platform not supported! 89 | #endif 90 | 91 | #define MAX(a,b) (((a)>(b))?(a):(b)) 92 | #include "gslist.h" 93 | 94 | #define WS_PIPEIN "/tmp/wspipein.fifo" 95 | #define WS_PIPEOUT "/tmp/wspipeout.fifo" 96 | 97 | #define WS_BAD_REQUEST_STR "HTTP/1.1 400 Invalid Request\r\n\r\n" 98 | #define WS_SWITCH_PROTO_STR "HTTP/1.1 101 Switching Protocols" 99 | #define WS_TOO_BUSY_STR "HTTP/1.1 503 Service Unavailable\r\n\r\n" 100 | 101 | #define CRLF "\r\n" 102 | #define SHA_DIGEST_LENGTH 20 103 | 104 | /* packet header is 3 unit32_t : type, size, listener */ 105 | #define HDR_SIZE 3 * 4 106 | #define WS_MAX_FRM_SZ 1048576 /* 1 MiB max frame size */ 107 | #define WS_THROTTLE_THLD 2097152 /* 2 MiB throttle threshold */ 108 | #define WS_MAX_HEAD_SZ 8192 /* a reasonable size for request headers */ 109 | 110 | #define WS_MAGIC_STR "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 111 | #define WS_PAYLOAD_EXT16 126 112 | #define WS_PAYLOAD_EXT64 127 113 | #define WS_PAYLOAD_FULL 125 114 | #define WS_FRM_HEAD_SZ 16 /* frame header size */ 115 | 116 | #define WS_FRM_FIN(x) (((x) >> 7) & 0x01) 117 | #define WS_FRM_MASK(x) (((x) >> 7) & 0x01) 118 | #define WS_FRM_R1(x) (((x) >> 6) & 0x01) 119 | #define WS_FRM_R2(x) (((x) >> 5) & 0x01) 120 | #define WS_FRM_R3(x) (((x) >> 4) & 0x01) 121 | #define WS_FRM_OPCODE(x) ((x) & 0x0F) 122 | #define WS_FRM_PAYLOAD(x) ((x) & 0x7F) 123 | 124 | #define WS_CLOSE_NORMAL 1000 125 | #define WS_CLOSE_GOING_AWAY 1001 126 | #define WS_CLOSE_PROTO_ERR 1002 127 | #define WS_CLOSE_INVALID_UTF8 1007 128 | #define WS_CLOSE_TOO_LARGE 1009 129 | #define WS_CLOSE_UNEXPECTED 1011 130 | 131 | typedef enum WSSTATUS { 132 | WS_OK = 0, 133 | WS_ERR = (1 << 0), 134 | WS_CLOSE = (1 << 1), 135 | WS_READING = (1 << 2), 136 | WS_SENDING = (1 << 3), 137 | WS_THROTTLING = (1 << 4), 138 | WS_TLS_ACCEPTING = (1 << 5), 139 | WS_TLS_READING = (1 << 6), 140 | WS_TLS_WRITING = (1 << 7), 141 | WS_TLS_SHUTTING = (1 << 8), 142 | } WSStatus; 143 | 144 | typedef enum WSOPCODE { 145 | WS_OPCODE_CONTINUATION = 0x00, 146 | WS_OPCODE_TEXT = 0x01, 147 | WS_OPCODE_BIN = 0x02, 148 | WS_OPCODE_END = 0x03, 149 | WS_OPCODE_CLOSE = 0x08, 150 | WS_OPCODE_PING = 0x09, 151 | WS_OPCODE_PONG = 0x0A, 152 | } WSOpcode; 153 | 154 | typedef struct WSQueue_ { 155 | char *queued; /* queue data */ 156 | int qlen; /* queue length */ 157 | } WSQueue; 158 | 159 | typedef struct WSPacket_ { 160 | uint32_t type; /* packet type (fixed-size) */ 161 | uint32_t size; /* payload size in bytes (fixed-size) */ 162 | char *data; /* payload */ 163 | int len; /* payload buffer len */ 164 | } WSPacket; 165 | 166 | /* WS HTTP Headers */ 167 | typedef struct WSHeaders_ { 168 | int reading; 169 | int buflen; 170 | char buf[WS_MAX_HEAD_SZ + 1]; 171 | 172 | char *agent; 173 | char *path; 174 | char *method; 175 | char *protocol; 176 | char *host; 177 | char *origin; 178 | char *upgrade; 179 | char *referer; 180 | char *connection; 181 | char *ws_protocol; 182 | char *ws_key; 183 | char *ws_sock_ver; 184 | 185 | char *ws_accept; 186 | char *ws_resp; 187 | } WSHeaders; 188 | 189 | /* A WebSocket Message */ 190 | typedef struct WSFrame_ { 191 | /* frame format */ 192 | WSOpcode opcode; /* frame opcode */ 193 | unsigned char fin; /* frame fin flag */ 194 | unsigned char mask[4]; /* mask key */ 195 | uint8_t res; /* extensions */ 196 | int payload_offset; /* end of header/start of payload */ 197 | int payloadlen; /* payload length (for each frame) */ 198 | 199 | /* status flags */ 200 | int reading; /* still reading frame's header part? */ 201 | int masking; /* are we masking the frame? */ 202 | 203 | char buf[WS_FRM_HEAD_SZ + 1]; /* frame's header */ 204 | int buflen; /* recv'd buf length so far (for each frame) */ 205 | } WSFrame; 206 | 207 | /* A WebSocket Message */ 208 | typedef struct WSMessage_ { 209 | WSOpcode opcode; /* frame opcode */ 210 | int fragmented; /* reading a fragmented frame */ 211 | int mask_offset; /* for fragmented frames */ 212 | 213 | char *payload; /* payload message */ 214 | int payloadsz; /* total payload size (whole message) */ 215 | int buflen; /* recv'd buf length so far (for each frame) */ 216 | } WSMessage; 217 | 218 | /* A WebSocket Client */ 219 | typedef struct WSClient_ { 220 | /* socket data */ 221 | int listener; /* socket */ 222 | char remote_ip[INET6_ADDRSTRLEN]; /* client IP */ 223 | 224 | WSQueue *sockqueue; /* sending buffer */ 225 | WSHeaders *headers; /* HTTP headers */ 226 | WSFrame *frame; /* frame headers */ 227 | WSMessage *message; /* message */ 228 | WSStatus status; /* connection status */ 229 | 230 | struct timeval start_proc; 231 | struct timeval end_proc; 232 | 233 | #ifdef HAVE_LIBSSL 234 | SSL *ssl; 235 | WSStatus sslstatus; /* ssl connection status */ 236 | #endif 237 | } WSClient; 238 | 239 | /* Config OOptions */ 240 | typedef struct WSPipeIn_ { 241 | int fd; /* named pipe FD */ 242 | 243 | WSPacket *packet; /* FIFO data's buffer */ 244 | 245 | char hdr[HDR_SIZE]; /* FIFO header's buffer */ 246 | int hlen; 247 | } WSPipeIn; 248 | 249 | /* Pipe Out */ 250 | typedef struct WSPipeOut_ { 251 | int fd; /* named pipe FD */ 252 | WSQueue *fifoqueue; /* FIFO out queue */ 253 | WSStatus status; /* connection status */ 254 | } WSPipeOut; 255 | 256 | /* Config OOptions */ 257 | typedef struct WSConfig_ { 258 | /* Config Options */ 259 | const char *accesslog; 260 | const char *host; 261 | const char *origin; 262 | const char *pipein; 263 | const char *pipeout; 264 | const char *port; 265 | const char *sslcert; 266 | const char *sslkey; 267 | const char *unix_socket; 268 | int echomode; 269 | int strict; 270 | int max_frm_size; 271 | int use_ssl; 272 | int use_stdin; 273 | int use_stdout; 274 | } WSConfig; 275 | 276 | /* A WebSocket Instance */ 277 | typedef struct WSServer_ { 278 | /* Server Status */ 279 | int closing; 280 | 281 | /* Callbacks */ 282 | int (*onclose) (WSPipeOut * pipeout, WSClient * client); 283 | int (*onmessage) (WSPipeOut * pipeout, WSClient * client); 284 | int (*onopen) (WSPipeOut * pipeout, WSClient * client); 285 | 286 | /* self-pipe */ 287 | int self_pipe[2]; 288 | /* FIFO reader */ 289 | WSPipeIn *pipein; 290 | /* FIFO writer */ 291 | WSPipeOut *pipeout; 292 | /* Connected Clients */ 293 | GSLList *colist; 294 | 295 | #ifdef HAVE_LIBSSL 296 | SSL_CTX *ctx; 297 | #endif 298 | } WSServer; 299 | 300 | int ws_read_fifo (int fd, char *buf, int *buflen, int pos, int need); 301 | int ws_send_data (WSClient * client, WSOpcode opcode, const char *p, int sz); 302 | int ws_setfifo (const char *pipename); 303 | int ws_validate_string (const char *str, int len); 304 | int ws_write_fifo (WSPipeOut * pipeout, char *buffer, int len); 305 | size_t pack_uint32 (void *buf, uint32_t val); 306 | size_t unpack_uint32 (const void *buf, uint32_t * val); 307 | void set_nonblocking (int listener); 308 | void ws_set_config_accesslog (const char *accesslog); 309 | void ws_set_config_echomode (int echomode); 310 | void ws_set_config_frame_size (int max_frm_size); 311 | void ws_set_config_host (const char *host); 312 | void ws_set_config_unix_socket (const char *unix_socket); 313 | void ws_set_config_origin (const char *origin); 314 | void ws_set_config_pipein (const char *pipein); 315 | void ws_set_config_pipeout (const char *pipeout); 316 | void ws_set_config_port (const char *port); 317 | void ws_set_config_sslcert (const char *sslcert); 318 | void ws_set_config_sslkey (const char *sslkey); 319 | void ws_set_config_stdin (int use_stdin); 320 | void ws_set_config_stdout (int use_stdout); 321 | void ws_set_config_strict (int strict); 322 | void ws_start (WSServer * server); 323 | void ws_stop (WSServer * server); 324 | WSServer *ws_init (const char *host, const char *port, void (*initopts) (void)); 325 | 326 | #endif // for #ifndef WEBSOCKET_H 327 | -------------------------------------------------------------------------------- /src/gwsocket.c: -------------------------------------------------------------------------------- 1 | /** 2 | * gwsocket.c -- An rfc6455-complaint Web Socket Server 3 | * _______ _______ __ __ 4 | * / ____/ | / / ___/____ _____/ /_____ / /_ 5 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 6 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 7 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 8 | * 9 | * The MIT License (MIT) 10 | * Copyright (c) 2009-2020 Gerardo Orellana 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "gwsocket.h" 40 | #include "log.h" 41 | #include "websocket.h" 42 | #include "xmalloc.h" 43 | 44 | #if HAVE_CONFIG_H 45 | #include 46 | #endif 47 | 48 | static WSServer *server = NULL; 49 | static WSConfig gsconfig = { 0 }; 50 | 51 | /* *INDENT-OFF* */ 52 | static char short_options[] = "p:Vh"; 53 | static struct option long_opts[] = { 54 | {"port" , required_argument , 0 , 'p' } , 55 | {"addr" , required_argument , 0 , 0 } , 56 | {"echo-mode" , no_argument , 0 , 0 } , 57 | {"max-frame-size" , required_argument , 0 , 0 } , 58 | {"origin" , required_argument , 0 , 0 } , 59 | {"pipein" , required_argument , 0 , 0 } , 60 | {"pipeout" , required_argument , 0 , 0 } , 61 | {"std" , no_argument , 0 , 0 } , 62 | {"stdin" , no_argument , 0 , 0 } , 63 | {"stdout" , no_argument , 0 , 0 } , 64 | #if HAVE_LIBSSL 65 | {"ssl-cert" , required_argument , 0 , 0 } , 66 | {"ssl-key" , required_argument , 0 , 0 } , 67 | #endif 68 | {"access-log" , required_argument , 0 , 0 } , 69 | {"unix-socket" , required_argument , 0 , 0 } , 70 | {"strict" , no_argument , 0 , 0 } , 71 | {"version" , no_argument , 0 , 'V' } , 72 | {"help" , no_argument , 0 , 'h' } , 73 | {0, 0, 0, 0} 74 | }; 75 | 76 | /* Command line help. */ 77 | static void 78 | cmd_help (void) 79 | { 80 | printf ("\nGWSocket - %s\n\n", GW_VERSION); 81 | 82 | printf ( 83 | "Usage: " 84 | "gwsocket [ options ... ] -p [--addr][--origin][...]\n" 85 | "The following options can also be supplied to the command:\n\n" 86 | "" 87 | " -p --port= - Specifies the port to bind.\n" 88 | " -h --help - This help.\n" 89 | " -V --version - Display version information and exit.\n" 90 | " --access-log= - Specifies the path/file for the access log.\n" 91 | " --addr= - Specify an IP address to bind to.\n" 92 | " --echo-mode - Echo all received messages.\n" 93 | " --max-frame-size= - Maximum size of a websocket frame. This\n" 94 | " includes received frames from the client\n" 95 | " and messages through the named pipe.\n" 96 | " --origin= - Ensure clients send the specified origin\n" 97 | " header upon the WebSocket handshake.\n" 98 | " --pipein= - Creates a named pipe (FIFO) that reads\n" 99 | " from on the given path/file.\n" 100 | " --pipeout= - Creates a named pipe (FIFO) that writes\n" 101 | " to on the given path/file.\n" 102 | " --std - Enable --stdin and --stdout.\n" 103 | " --stdin - Send stdin to the websocket.\n" 104 | " --stdout - Send received websocket data to stdout.\n" 105 | " --strict - Parse messages using strict mode. See\n" 106 | " man page for more details.\n" 107 | " --ssl-cert= - Path to SSL certificate.\n" 108 | " --ssl-key= - Path to SSL private key.\n" 109 | " --unix-socket= - Specify UNIX-domain socket address to\n" 110 | " bind server to.\n" 111 | "\n" 112 | "See the man page for more information `man gwsocket`.\n\n" 113 | "For more details visit: http://gwsocket.io\n" 114 | "gwsocket Copyright (C) 2020 by Gerardo Orellana" 115 | "\n\n" 116 | ); 117 | ws_stop (server); 118 | exit (EXIT_FAILURE); 119 | } 120 | /* *INDENT-ON* */ 121 | 122 | static void 123 | handle_signal_action (int sig_number) { 124 | if (sig_number == SIGINT) { 125 | printf ("SIGINT caught!\n"); 126 | /* if it fails to write, force stop */ 127 | if ((write (server->self_pipe[1], "x", 1)) == -1 && errno != EAGAIN) 128 | ws_stop (server); 129 | } else if (sig_number == SIGPIPE) { 130 | printf ("SIGPIPE caught!\n"); 131 | } 132 | } 133 | 134 | static int 135 | setup_signals (void) { 136 | struct sigaction sa; 137 | memset (&sa, 0, sizeof (sa)); 138 | sa.sa_handler = handle_signal_action; 139 | if (sigaction (SIGINT, &sa, 0) != 0) { 140 | perror ("sigaction()"); 141 | return -1; 142 | } 143 | if (sigaction (SIGPIPE, &sa, 0) != 0) { 144 | perror ("sigaction()"); 145 | return -1; 146 | } 147 | return 0; 148 | } 149 | 150 | static int 151 | onopen (WSPipeOut * pipeout, WSClient * client) { 152 | uint32_t hsize = sizeof (uint32_t) * 3; 153 | char *hdr = calloc (hsize, sizeof (char)); 154 | char *ptr = hdr; 155 | 156 | ptr += pack_uint32 (ptr, client->listener); 157 | ptr += pack_uint32 (ptr, 0x10); 158 | ptr += pack_uint32 (ptr, INET6_ADDRSTRLEN); 159 | 160 | ws_write_fifo (pipeout, hdr, hsize); 161 | ws_write_fifo (pipeout, client->remote_ip, INET6_ADDRSTRLEN); 162 | free (hdr); 163 | 164 | return 0; 165 | } 166 | 167 | static int 168 | onclose (WSPipeOut * pipeout, WSClient * client) { 169 | uint32_t hsize = sizeof (uint32_t) * 3; 170 | char *hdr = calloc (hsize, sizeof (char)); 171 | char *ptr = hdr; 172 | 173 | ptr += pack_uint32 (ptr, client->listener); 174 | ptr += pack_uint32 (ptr, 0x08); 175 | ptr += pack_uint32 (ptr, 0); 176 | 177 | ws_write_fifo (pipeout, hdr, hsize); 178 | free (hdr); 179 | 180 | return 0; 181 | } 182 | 183 | static int 184 | onmessage (WSPipeOut * pipeout, WSClient * client) { 185 | WSMessage **msg = &client->message; 186 | uint32_t hsize = sizeof (uint32_t) * 3; 187 | char *hdr = NULL, *ptr = NULL; 188 | 189 | hdr = calloc (hsize, sizeof (char)); 190 | ptr = hdr; 191 | ptr += pack_uint32 (ptr, client->listener); 192 | ptr += pack_uint32 (ptr, (*msg)->opcode); 193 | ptr += pack_uint32 (ptr, (*msg)->payloadsz); 194 | 195 | ws_write_fifo (pipeout, hdr, hsize); 196 | ws_write_fifo (pipeout, (*msg)->payload, (*msg)->payloadsz); 197 | free (hdr); 198 | 199 | return 0; 200 | } 201 | 202 | static void 203 | parse_long_opt (const char *name, const char *oarg) { 204 | if (!strcmp ("addr", name)) 205 | gsconfig.host = oarg; 206 | if (!strcmp ("echo-mode", name)) 207 | gsconfig.echomode = 1; 208 | if (!strcmp ("max-frame-size", name)) 209 | gsconfig.max_frm_size = atoi (oarg); 210 | if (!strcmp ("origin", name)) 211 | gsconfig.origin = oarg; 212 | if (!strcmp ("pipein", name)) 213 | gsconfig.pipein = oarg; 214 | if (!strcmp ("pipeout", name)) 215 | gsconfig.pipeout = oarg; 216 | if (!strcmp ("strict", name)) 217 | gsconfig.strict = 1; 218 | if (!strcmp ("access-log", name)) 219 | gsconfig.accesslog = oarg; 220 | #if HAVE_LIBSSL 221 | if (!strcmp ("ssl-cert", name)) 222 | gsconfig.sslcert = oarg; 223 | if (!strcmp ("ssl-key", name)) 224 | gsconfig.sslkey = oarg; 225 | #endif 226 | if (!strcmp ("std", name)) { 227 | gsconfig.use_stdin = 1; 228 | gsconfig.use_stdout = 1; 229 | } 230 | if (!strcmp ("stdin", name)) 231 | gsconfig.use_stdin = 1; 232 | if (!strcmp ("stdout", name)) 233 | gsconfig.use_stdout = 1; 234 | if (!strcmp ("unix-socket", name)) 235 | gsconfig.unix_socket = oarg; 236 | } 237 | 238 | static void 239 | set_server_opts (void) { 240 | if (gsconfig.host) 241 | ws_set_config_host (gsconfig.host); 242 | if (gsconfig.port) 243 | ws_set_config_port (gsconfig.port); 244 | if (gsconfig.echomode) 245 | ws_set_config_echomode (1); 246 | if (gsconfig.max_frm_size) 247 | ws_set_config_frame_size (gsconfig.max_frm_size); 248 | if (gsconfig.origin) 249 | ws_set_config_origin (gsconfig.origin); 250 | if (gsconfig.pipein) 251 | ws_set_config_pipein (gsconfig.pipein); 252 | if (gsconfig.pipeout) 253 | ws_set_config_pipeout (gsconfig.pipeout); 254 | if (gsconfig.strict) 255 | ws_set_config_strict (1); 256 | if (gsconfig.accesslog) 257 | ws_set_config_accesslog (gsconfig.accesslog); 258 | #if HAVE_LIBSSL 259 | if (gsconfig.sslcert) 260 | ws_set_config_sslcert (gsconfig.sslcert); 261 | if (gsconfig.sslkey) 262 | ws_set_config_sslkey (gsconfig.sslkey); 263 | #endif 264 | if (gsconfig.use_stdin) 265 | ws_set_config_stdin (1); 266 | if (gsconfig.use_stdout) 267 | ws_set_config_stdout (1); 268 | if (gsconfig.unix_socket) 269 | ws_set_config_unix_socket (gsconfig.unix_socket); 270 | } 271 | 272 | /* Read the user's supplied command line options. */ 273 | static int 274 | read_option_args (int argc, char **argv) { 275 | int o, idx = 0; 276 | 277 | while ((o = getopt_long (argc, argv, short_options, long_opts, &idx)) >= 0) { 278 | if (-1 == o || EOF == o) 279 | break; 280 | switch (o) { 281 | case 'p': 282 | gsconfig.port = optarg; 283 | break; 284 | case 'h': 285 | cmd_help (); 286 | return 1; 287 | case 'V': 288 | fprintf (stdout, "GWSocket %s\n", GW_VERSION); 289 | return 1; 290 | case 0: 291 | parse_long_opt (long_opts[idx].name, optarg); 292 | break; 293 | case '?': 294 | return 1; 295 | default: 296 | return 1; 297 | } 298 | } 299 | 300 | for (idx = optind; idx < argc; idx++) 301 | cmd_help (); 302 | 303 | return 0; 304 | } 305 | 306 | static void 307 | set_self_pipe (void) { 308 | /* Initialize self pipe. */ 309 | if (pipe (server->self_pipe) == -1) 310 | FATAL ("Unable to create pipe: %s.", strerror (errno)); 311 | 312 | /* make the read and write pipe non-blocking */ 313 | set_nonblocking (server->self_pipe[0]); 314 | set_nonblocking (server->self_pipe[1]); 315 | } 316 | 317 | int 318 | main (int argc, char **argv) { 319 | if (read_option_args (argc, argv)) 320 | exit (EXIT_FAILURE); 321 | 322 | if ((server = ws_init ("0.0.0.0", "7890", set_server_opts)) == NULL) { 323 | perror ("Error during ws_init.\n"); 324 | exit (EXIT_FAILURE); 325 | } 326 | /* callbacks */ 327 | server->onclose = onclose; 328 | server->onmessage = onmessage; 329 | server->onopen = onopen; 330 | 331 | set_self_pipe (); 332 | if (setup_signals () != 0) 333 | exit (EXIT_FAILURE); 334 | 335 | ws_start (server); 336 | ws_stop (server); 337 | 338 | return EXIT_SUCCESS; 339 | } 340 | -------------------------------------------------------------------------------- /gwsocket.1: -------------------------------------------------------------------------------- 1 | .TH gwsocket 1 "MARCH 2023" Linux "User Manuals" 2 | .SH NAME 3 | gwsocket is a standalone, simple, yet powerful rfc6455 compliant WebSocket Server. 4 | .SH SYNOPSIS 5 | .LP 6 | .B gwsocket -p [--addr][--origin][gwsocket options ...] 7 | .SH DESCRIPTION 8 | .B gwsocket 9 | is a free (MIT Licensed), standalone, WebSocket Server. It sits 10 | between your application and the client's browser, giving the ability for 11 | bidirectional communication between these two with ease and flexibility. 12 | .SH Start the Server 13 | .P 14 | You can run gwsocket without passing any options to the command line. By 15 | default, it will listen on port 7890 on all the interfaces, e.g., 127.0.0.1, 16 | ::1, etc. For instance: 17 | .LP 18 | .B # gwsocket --access-log=/tmp/access.log 19 | .I Note: 20 | See a basic client-side example at the bottom of the man page. 21 | .SH OPTIONS 22 | .TP 23 | \fB\-p \-\-port 24 | Specifies the port to bind. 25 | .TP 26 | \fB\-h \-\-help 27 | Command line help. 28 | .TP 29 | \fB\-V \-\-version 30 | Display version information and exit. 31 | .TP 32 | \fB\-\-access-log= 33 | Specifies the path/file for the access log. 34 | .TP 35 | \fB\-\-addr= 36 | Specifies the address to bind. 37 | .TP 38 | \fB\-\-echo-mode 39 | Set the server to echo all received messages. 40 | .TP 41 | \fB\-\-max-frame-size= 42 | Maximum size of a websocket frame. This includes received frames from the 43 | client and messages through the named pipe. 44 | .TP 45 | \fB\-\-origin= 46 | Ensure clients send the specified origin header upon the WebSocket handshake. 47 | .TP 48 | \fB\-\-pipein= 49 | Creates a named pipe (FIFO) that reads from on the given path/file. 50 | .TP 51 | \fB\-\-pipeout= 52 | Creates a named pipe (FIFO) that writes to the given path/file. 53 | .TP 54 | \fB\-\-std 55 | Enable --stdin and --stdout. By default named pipes are used for I/O. This adds the option to use stdin / stdout as well. For example, this allows: 56 | 57 | ./gwsocket --std > log.txt 58 | 59 | tail -F /var/log/syslog | ./gwsocket --std 60 | 61 | --stdin and --stdout are added for fine grained control. 62 | .TP 63 | \fB\-\-stdin 64 | Send stdin to the websocket. 65 | .TP 66 | \fB\-\-stdout 67 | Send received websocket data to stdout. 68 | .TP 69 | \fB\-\-strict 70 | Parse messages using strict mode. See below for more details. 71 | .TP 72 | \fB\-\-ssl-cert= 73 | Path to TLS/SSL certificate. 74 | .TP 75 | \fB\-\-ssl-key= 76 | Path to TLS/SSL private key. 77 | .TP 78 | \fB\-\-unix-socket= 79 | Specify UNIX-domain socket address to bind server to. 80 | 81 | .SH CONFIGURATION 82 | .TP 83 | \fB\-\-enable-debug 84 | Compile with debugging symbols and turn off compiler optimizations. 85 | .TP 86 | \fB\-\-with-openssl 87 | Compile gwsocket with OpenSSL support for its WebSocket server. 88 | .SH DATA MODES 89 | .P 90 | In order to establish a channel between your application and the client's 91 | browser, gwsocket provides two methods that allow the user to send data in and 92 | out. The first one is through the use of the standard input 93 | .I (stdin) 94 | ,and the standard output 95 | .I (stdout) 96 | .The second method is through a fixed-size header 97 | followed by the payload. See options below for more details. 98 | .SS 99 | .I 100 | 1. stdin/stdout 101 | .P 102 | The standard input/output is the simplest way of sending/receiving data to/from 103 | a client. However, it's limited to broadcasting messages to all clients. To 104 | send messages to or receive from a specific client, use the strict mode in 105 | section 2. 106 | .SS 107 | .I 108 | 1.1 Sending Data to all Clients — stdout 109 | .P 110 | If you need to broadcast data from your application to all clients connected to 111 | gwsocket, then, the simplest way of doing it is by piping your application 112 | output into a named pipe (also known as FIFO) that gwsocket makes use of. Once 113 | gwsocket receives the payload, then it will automatically broadcast the message 114 | to all connected clients. 115 | .SS 116 | .BR 117 | 1.1. Examples 118 | 119 | #include 120 | #include 121 | #include 122 | #include 123 | 124 | int main() { 125 | int fd; 126 | char *myfifo = "/tmp/wspipein.fifo"; 127 | const char *msg = "Message to broadcast"; 128 | 129 | fd = open(myfifo, O_WRONLY); 130 | write(fd, msg, strlen(msg)); 131 | close(fd); 132 | 133 | return 0; 134 | } 135 | 136 | .P 137 | .I Note: 138 | You can send as many bytes 139 | .I PIPE_BUF 140 | can hold. If a message is greater than PIPE_BUF, it would send the rest on a 141 | second message or third, and so on. See strict mode below for more control over 142 | messages. 143 | .SS 144 | .I 145 | 1.2 Receiving Data from Clients — stdin 146 | .P 147 | When a client sends a message to the server, it is possible to capture that 148 | message in your application. To do this, your application simply needs to read 149 | from a named pipe. By default, gwsocket creates a FIFO under 150 | .I /tmp/wspipeout.fifo. 151 | .SS 152 | .BR 153 | 1.2. Examples 154 | .P 155 | Receiving data can be as simple as doing a 156 | .I # cat /tmp/wspipeout.fifo 157 | or you can do it in the language of your choice. See examples below. 158 | 159 | #include 160 | #include 161 | #include 162 | #include 163 | 164 | static void read_message (int fd, fd_set set) { 165 | int bytes = 0; 166 | char buf[PIPE_BUF] = { 0 }; 167 | 168 | FD_ZERO (&set); 169 | FD_SET (fd, &set); 170 | 171 | if ((select (fd + 1, &set, NULL, NULL, NULL)) < 1) 172 | exit (1); 173 | if (!FD_ISSET (fd, &set)) 174 | return; 175 | 176 | if (read (fd, buf, PIPE_BUF) > 0) 177 | printf ("%s\n", buf); 178 | } 179 | 180 | int main (void) { 181 | fd_set set; 182 | char *fifo = "/tmp/wspipeout.fifo"; 183 | int fd = 0; 184 | 185 | if ((fd = open (fifo, O_RDWR | O_NONBLOCK)) < 0) 186 | exit (1); 187 | while (1) 188 | read_message(fd, set); 189 | 190 | return 0; 191 | } 192 | 193 | .I 194 | Note: 195 | Make sure the reader in your application is set as non-blocking to get a 196 | constant feed. 197 | .P 198 | .I Tip 199 | If you need to know which client sent the message, for example, in a chat 200 | application, please see the strict mode below. 201 | 202 | .SS 203 | .I 204 | 2. Strict Mode 205 | .P 206 | gwsocket implements its own tiny protocol for sending/receiving data. In 207 | contrast to the stdin/stdout mode, the strict mode allows you to send/receive 208 | data to/from specific connected clients as well as to keep track of who 209 | opened/closed a WebSocket connection. It also gives you the ability to pack and 210 | send as much data as you would like on a single message. 211 | .P 212 | 2. Data Format 213 | .P 214 | The message header is a fixed-size header. The first 12 bytes (uint32_t) are 215 | packed in network byte order and contain the "meta-data" of the message we are 216 | sending/receiving. The rest of it is the actual message. 217 | .P 218 | 0 1 2 3 219 | +---------------------------------------------+ 220 | | Client Socket Id (listener) | 221 | +---------------------------------------------+ 222 | | Message Type (binary: 0x2 / text: 0x1) | 223 | +---------------------------------------------+ 224 | | Payload length | 225 | +---------------------------------------------+ 226 | | Payload Data | 227 | +---------------------------------------------+ 228 | .SS 229 | .I 230 | 2.1 Sending Data — Strict Mode 231 | .P 232 | If you need to send a message to a specific client, then you can do so by 233 | specifying the client id in the message header. If set to 0, the message will 234 | be broadcasted to all clients. The first 4 bytes are reserved for the client id 235 | or listener. The following 4 bytes are reserved for the message type. 0x01 for 236 | a text message, and 0x02 for a binary message. And the last 4 bytes are 237 | reserved for the payload's length. 238 | .P 239 | Once the header has been written to the pipe, you may now write the message. 240 | .SS 241 | .BR 242 | 2.1. Examples 243 | .P 244 | First, start the server in strict-mode. 245 | .LP 246 | .B # gwsocket --strict-mode 247 | #include 248 | #include 249 | #include 250 | #include 251 | 252 | size_t pack_uint32(void* buf, uint32_t val) { 253 | uint32_t v32 = htonl(val); 254 | memcpy(buf, &v32, sizeof(uint32_t)); 255 | return sizeof(uint32_t); 256 | } 257 | 258 | int main() { 259 | char *p = calloc (sizeof(uint32_t) * 3, sizeof(char)), *ptr; 260 | const char *msg = "Message to broadcast"; 261 | const char *fifo = "/tmp/wspipein.fifo"; 262 | int fd; 263 | 264 | ptr = p; 265 | ptr += pack_uint32(ptr, 0); 266 | ptr += pack_uint32(ptr, 0x01); 267 | ptr += pack_uint32(ptr, strlen(msg)); 268 | 269 | fd = open(fifo, O_WRONLY); 270 | write(fd, p, sizeof(uint32_t) * 3); 271 | write(fd, msg, strlen(msg)); 272 | close(fd); 273 | free (p); 274 | 275 | return 0; 276 | } 277 | .SS 278 | .I 279 | 2.2 Receiving Data from Clients — Strict Mode 280 | .P 281 | Now, to get a message from a specific client and route it to another client, 282 | you just need to do the opposite of sending data. First you unpack the header 283 | from network byte order to host byte order and then read the payload. 284 | .SS 285 | .BR 286 | 2.2. Examples 287 | .P 288 | First, start the server in strict-mode. 289 | .LP 290 | .B # gwsocket --strict-mode 291 | #include 292 | #include 293 | #include 294 | #include 295 | #include 296 | 297 | static size_t unpack_uint32 (const void *b, uint32_t * val) { 298 | uint32_t v32 = 0; 299 | memcpy (&v32, b, sizeof (uint32_t)); 300 | *val = ntohl (v32); 301 | return sizeof (uint32_t); 302 | } 303 | 304 | static void read_message (int fd, fd_set set) { 305 | int bytes = 0; 306 | uint32_t size = 0, listener = 0, type = 0; 307 | char hdr[PIPE_BUF] = { 0 }, buf[PIPE_BUF] = {0}; 308 | char *ptr = NULL; 309 | 310 | FD_ZERO (&set); 311 | FD_SET (fd, &set); 312 | 313 | if ((select (fd + 1, &set, NULL, NULL, NULL)) < 1) 314 | exit (1); 315 | if (!FD_ISSET (fd, &set)) 316 | return; 317 | 318 | if (hdr[0] == '\0') { 319 | if (read (fd, hdr, sizeof (uint32_t) * 3) < 1) 320 | return; 321 | } 322 | 323 | ptr = hdr; 324 | ptr += unpack_uint32(ptr, &listener); 325 | ptr += unpack_uint32(ptr, &type); 326 | ptr += unpack_uint32(ptr, &size); 327 | 328 | if (read (fd, buf, size) < 1) 329 | return; 330 | 331 | printf ("client: %d, msg: %s\n", listener, buf); 332 | } 333 | 334 | int main (void) { 335 | fd_set set; 336 | char *fifo = "/tmp/wspipeout.fifo"; 337 | int fd = 0; 338 | 339 | if ((fd = open (fifo, O_RDWR | O_NONBLOCK)) < 0) 340 | exit (1); 341 | while (1) 342 | read_message(fd, set); 343 | 344 | return 0; 345 | } 346 | .P 347 | .I Note: 348 | If you read/write to a stream, be aware that they do not necessarily read/write 349 | the full amount of data you have requested. Your application will need to 350 | handle the case where only a single byte is read or written. Examples above do 351 | not handle this. 352 | .SH OBLIGATORY CLIENT EXAMPLE 353 | .P 354 | Here's the basic example, client and server side. First start the server and 355 | set it in echo mode. 356 | .LP 357 | .B # gwsocket --echo-mode 358 | .P 359 | Now, let's create the client side. 360 | 361 | 362 | 363 | 375 | 397 | 398 |
399 |
Connecting...
400 | 401 | 402 |
403 | 404 | .SH BUGS 405 | .P 406 | If you think you have found a bug, please send me an email to hello [@at] 407 | goaccess.io. 408 | .SH AUTHOR 409 | .P 410 | Gerardo Orellana. For more details about it, or new releases, please visit 411 | http://gwsocket.io 412 | -------------------------------------------------------------------------------- /src/websocket.c: -------------------------------------------------------------------------------- 1 | /** 2 | * websocket.c -- An rfc6455-complaint Web Socket Server 3 | * _______ _______ __ __ 4 | * / ____/ | / / ___/____ _____/ /_____ / /_ 5 | * / / __ | | /| / /\__ \/ __ \/ ___/ //_/ _ \/ __/ 6 | * / /_/ / | |/ |/ /___/ / /_/ / /__/ ,< / __/ /_ 7 | * \____/ |__/|__//____/\____/\___/_/|_|\___/\__/ 8 | * 9 | * The MIT License (MIT) 10 | * Copyright (c) 2009-2020 Gerardo Orellana 11 | * 12 | * Permission is hereby granted, free of charge, to any person obtaining a copy 13 | * of this software and associated documentation files (the "Software"), to deal 14 | * in the Software without restriction, including without limitation the rights 15 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | * copies of the Software, and to permit persons to whom the Software is 17 | * furnished to do so, subject to the following conditions: 18 | * 19 | * The above copyright notice and this permission notice shall be included in all 20 | * copies or substantial portions of the Software. 21 | * 22 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | * SOFTWARE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #if HAVE_CONFIG_H 52 | #include 53 | #endif 54 | 55 | #include "websocket.h" 56 | 57 | #include "base64.h" 58 | #include "log.h" 59 | #include "gslist.h" 60 | #include "sha1.h" 61 | #include "xmalloc.h" 62 | 63 | /* *INDENT-OFF* */ 64 | 65 | /* UTF-8 Decoder */ 66 | /* Copyright (c) 2008-2009 Bjoern Hoehrmann 67 | * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. */ 68 | #define UTF8_VALID 0 69 | #define UTF8_INVAL 1 70 | static const uint8_t utf8d[] = { 71 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 00..1f */ 72 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 20..3f */ 73 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 40..5f */ 74 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 60..7f */ 75 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, /* 80..9f */ 76 | 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, /* a0..bf */ 77 | 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* c0..df */ 78 | 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, /* e0..ef */ 79 | 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, /* f0..ff */ 80 | 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, /* s0..s0 */ 81 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, /* s1..s2 */ 82 | 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, /* s3..s4 */ 83 | 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, /* s5..s6 */ 84 | 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* s7..s8 */ 85 | }; 86 | /* *INDENT-ON* */ 87 | 88 | static struct pollfd *fdstate = NULL; 89 | static nfds_t nfdstate = 0; 90 | static WSConfig wsconfig = { 0 }; 91 | 92 | static void handle_read_close (int *conn, WSClient * client, WSServer * server); 93 | static void handle_reads (int *conn, WSServer * server); 94 | static void handle_writes (int *conn, WSServer * server); 95 | #ifdef HAVE_LIBSSL 96 | static int shutdown_ssl (WSClient * client); 97 | #endif 98 | 99 | /* Determine if the given string is valid UTF-8. 100 | * 101 | * The state after the by has been processed is returned. */ 102 | static uint32_t 103 | verify_utf8 (uint32_t * state, const char *str, int len) { 104 | int i; 105 | uint32_t type; 106 | 107 | for (i = 0; i < len; ++i) { 108 | type = utf8d[(uint8_t) str[i]]; 109 | *state = utf8d[256 + (*state) * 16 + type]; 110 | 111 | if (*state == UTF8_INVAL) 112 | break; 113 | } 114 | 115 | return *state; 116 | } 117 | 118 | /* Decode a character maintaining state and a byte, and returns the 119 | * state achieved after processing the byte. 120 | * 121 | * The state after the by has been processed is returned. */ 122 | static uint32_t 123 | utf8_decode (uint32_t * state, uint32_t * p, uint32_t b) { 124 | uint32_t type = utf8d[(uint8_t) b]; 125 | 126 | *p = (*state != UTF8_VALID) ? (b & 0x3fu) | (*p << 6) : (0xff >> type) & (b); 127 | *state = utf8d[256 + *state * 16 + type]; 128 | 129 | return *state; 130 | } 131 | 132 | /* Replace malformed sequences with a substitute character. 133 | * 134 | * On success, it replaces the whole sequence and return a malloc'd buffer. */ 135 | static char * 136 | sanitize_utf8 (const char *str, int len) { 137 | char *buf = NULL; 138 | uint32_t state = UTF8_VALID, prev = UTF8_VALID, cp = 0; 139 | int i = 0, j = 0, k = 0, l = 0; 140 | 141 | buf = xcalloc (len + 1, sizeof (char)); 142 | for (; i < len; prev = state, ++i) { 143 | switch (utf8_decode (&state, &cp, (unsigned char) str[i])) { 144 | case UTF8_INVAL: 145 | /* replace the whole sequence */ 146 | if (k) { 147 | for (l = i - k; l < i; ++l) 148 | buf[j++] = '?'; 149 | } else { 150 | buf[j++] = '?'; 151 | } 152 | state = UTF8_VALID; 153 | if (prev != UTF8_VALID) 154 | i--; 155 | k = 0; 156 | break; 157 | case UTF8_VALID: 158 | /* fill i - k valid continuation bytes */ 159 | if (k) 160 | for (l = i - k; l < i; ++l) 161 | buf[j++] = str[l]; 162 | buf[j++] = str[i]; 163 | k = 0; 164 | break; 165 | default: 166 | /* UTF8_VALID + continuation bytes */ 167 | k++; 168 | break; 169 | } 170 | } 171 | 172 | return buf; 173 | } 174 | 175 | /* find a pollfd structure based on fd 176 | * this should only be called by set_pollfd and unset_pollfd 177 | * because the position in memory may change */ 178 | static struct pollfd * 179 | get_pollfd (int fd) { 180 | struct pollfd *pfd, *efd = fdstate + nfdstate; 181 | 182 | for (pfd = fdstate; pfd < efd; pfd++) { 183 | if (pfd->fd == fd) 184 | return pfd; 185 | } 186 | 187 | return NULL; 188 | } 189 | 190 | /* set flags for an existing pollfd structure based on fd, 191 | * otherwise malloc a new one */ 192 | static void 193 | set_pollfd (int fd, short flags) { 194 | struct pollfd *pfd; 195 | 196 | if (fd == -1) 197 | return; 198 | 199 | pfd = get_pollfd (fd); 200 | if (pfd == NULL) { 201 | struct pollfd *newstate = xrealloc (fdstate, sizeof (*pfd) * (nfdstate + 1)); 202 | 203 | fdstate = newstate; 204 | pfd = fdstate + nfdstate++; 205 | pfd->fd = fd; 206 | } 207 | pfd->events = flags; 208 | pfd->revents = 0; 209 | } 210 | 211 | /* free a pollfd structure based on fd */ 212 | static void 213 | unset_pollfd (int fd) { 214 | struct pollfd *pfd = get_pollfd (fd), *efd; 215 | struct pollfd *newstate; 216 | 217 | if (pfd == NULL) 218 | return; 219 | 220 | nfdstate--; 221 | 222 | /* avoid undefined behaviour with realloc with a size of zero */ 223 | if (nfdstate == 0) { 224 | free (fdstate); 225 | fdstate = NULL; 226 | return; 227 | } 228 | 229 | efd = fdstate + nfdstate; 230 | if (pfd != efd) 231 | memmove (pfd, pfd + 1, (char *) efd - (char *) pfd); 232 | 233 | /* realloc could fail, but that's ok, we don't mind. */ 234 | newstate = realloc (fdstate, sizeof (*pfd) * nfdstate); 235 | if (newstate != NULL) 236 | fdstate = newstate; 237 | } 238 | 239 | /* Allocate memory for a websocket server */ 240 | static WSServer * 241 | new_wsserver (void) { 242 | WSServer *server = xcalloc (1, sizeof (WSServer)); 243 | 244 | return server; 245 | } 246 | 247 | /* Allocate memory for a websocket client */ 248 | static WSClient * 249 | new_wsclient (void) { 250 | WSClient *client = xcalloc (1, sizeof (WSClient)); 251 | client->status = WS_OK; 252 | 253 | return client; 254 | } 255 | 256 | /* Allocate memory for a websocket header */ 257 | static WSHeaders * 258 | new_wsheader (void) { 259 | WSHeaders *headers = xcalloc (1, sizeof (WSHeaders)); 260 | memset (headers->buf, 0, sizeof (headers->buf)); 261 | headers->reading = 1; 262 | 263 | return headers; 264 | } 265 | 266 | /* Allocate memory for a websocket frame */ 267 | static WSFrame * 268 | new_wsframe (void) { 269 | WSFrame *frame = xcalloc (1, sizeof (WSFrame)); 270 | memset (frame->buf, 0, sizeof (frame->buf)); 271 | frame->reading = 1; 272 | 273 | return frame; 274 | } 275 | 276 | /* Allocate memory for a websocket message */ 277 | static WSMessage * 278 | new_wsmessage (void) { 279 | WSMessage *msg = xcalloc (1, sizeof (WSMessage)); 280 | 281 | return msg; 282 | } 283 | 284 | /* Allocate memory for a websocket pipeout */ 285 | static WSPipeOut * 286 | new_wspipeout (void) { 287 | WSPipeOut *pipeout = xcalloc (1, sizeof (WSPipeOut)); 288 | pipeout->fd = -1; 289 | 290 | return pipeout; 291 | } 292 | 293 | /* Allocate memory for a websocket pipein */ 294 | static WSPipeIn * 295 | new_wspipein (void) { 296 | WSPipeIn *pipein = xcalloc (1, sizeof (WSPipeIn)); 297 | pipein->fd = -1; 298 | 299 | return pipein; 300 | } 301 | 302 | /* Escapes the special characters, e.g., '\n', '\r', '\t', '\' 303 | * in the string source by inserting a '\' before them. 304 | * 305 | * On error NULL is returned. 306 | * On success the escaped string is returned */ 307 | static char * 308 | escape_http_request (const char *src) { 309 | char *dest, *q; 310 | const unsigned char *p; 311 | 312 | if (src == NULL || *src == '\0') 313 | return NULL; 314 | 315 | p = (const unsigned char *) src; 316 | q = dest = xmalloc (strlen (src) * 4 + 1); 317 | 318 | while (*p) { 319 | switch (*p) { 320 | case '\\': 321 | *q++ = '\\'; 322 | *q++ = '\\'; 323 | break; 324 | case '\n': 325 | *q++ = '\\'; 326 | *q++ = 'n'; 327 | break; 328 | case '\r': 329 | *q++ = '\\'; 330 | *q++ = 'r'; 331 | break; 332 | case '\t': 333 | *q++ = '\\'; 334 | *q++ = 't'; 335 | break; 336 | case '"': 337 | *q++ = '\\'; 338 | *q++ = '"'; 339 | break; 340 | default: 341 | if ((*p < ' ') || (*p >= 0177)) { 342 | /* not ASCII */ 343 | } else { 344 | *q++ = *p; 345 | } 346 | break; 347 | } 348 | p++; 349 | } 350 | *q = 0; 351 | return dest; 352 | } 353 | 354 | /* Make a string uppercase. 355 | * 356 | * On error the original string is returned. 357 | * On success, the uppercased string is returned. */ 358 | static char * 359 | strtoupper (char *str) { 360 | char *p = str; 361 | if (str == NULL || *str == '\0') 362 | return str; 363 | 364 | while (*p != '\0') { 365 | *p = toupper ((int) *p); 366 | p++; 367 | } 368 | 369 | return str; 370 | } 371 | 372 | /* Chop n characters from the beginning of the supplied buffer. 373 | * 374 | * The new length of the string is returned. */ 375 | static size_t 376 | chop_nchars (char *str, size_t n, size_t len) { 377 | if (n == 0 || str == 0) 378 | return 0; 379 | 380 | if (n > len) 381 | n = len; 382 | memmove (str, str + n, len - n); 383 | 384 | return (len - n); 385 | } 386 | 387 | /* Match a client given a socket id and an item from the list. 388 | * 389 | * On match, 1 is returned, else 0. */ 390 | static int 391 | ws_find_client_sock_in_list (void *data, void *needle) { 392 | WSClient *client = data; 393 | 394 | return client->listener == (*(int *) needle); 395 | } 396 | 397 | /* Find a client given a socket id. 398 | * 399 | * On success, an instance of a GSLList node is returned, else NULL. */ 400 | static GSLList * 401 | ws_get_list_node_from_list (int listener, GSLList ** colist) { 402 | GSLList *match = NULL; 403 | 404 | /* Find the client data for the socket in use */ 405 | if (!(match = list_find (*colist, ws_find_client_sock_in_list, &listener))) 406 | return NULL; 407 | return match; 408 | } 409 | 410 | /* Find a client given a socket id. 411 | * 412 | * On success, an instance of a WSClient is returned, else NULL. */ 413 | static WSClient * 414 | ws_get_client_from_list (int listener, GSLList ** colist) { 415 | GSLList *match = NULL; 416 | 417 | /* Find the client data for the socket in use */ 418 | if (!(match = list_find (*colist, ws_find_client_sock_in_list, &listener))) 419 | return NULL; 420 | return (WSClient *) match->data; 421 | } 422 | 423 | /* Free a frame structure and its data for the given client. */ 424 | static void 425 | ws_free_frame (WSClient * client) { 426 | if (client->frame) 427 | free (client->frame); 428 | client->frame = NULL; 429 | } 430 | 431 | /* Free a message structure and its data for the given client. */ 432 | static void 433 | ws_free_message (WSClient * client) { 434 | if (client->message && client->message->payload) 435 | free (client->message->payload); 436 | if (client->message) 437 | free (client->message); 438 | client->message = NULL; 439 | } 440 | 441 | /* Free all HTTP handshake headers data for the given client. */ 442 | static void 443 | ws_free_header_fields (WSHeaders * headers) { 444 | if (headers->connection) 445 | free (headers->connection); 446 | if (headers->host) 447 | free (headers->host); 448 | if (headers->agent) 449 | free (headers->agent); 450 | if (headers->method) 451 | free (headers->method); 452 | if (headers->origin) 453 | free (headers->origin); 454 | if (headers->path) 455 | free (headers->path); 456 | if (headers->protocol) 457 | free (headers->protocol); 458 | if (headers->upgrade) 459 | free (headers->upgrade); 460 | if (headers->ws_accept) 461 | free (headers->ws_accept); 462 | if (headers->ws_key) 463 | free (headers->ws_key); 464 | if (headers->ws_protocol) 465 | free (headers->ws_protocol); 466 | if (headers->ws_resp) 467 | free (headers->ws_resp); 468 | if (headers->ws_sock_ver) 469 | free (headers->ws_sock_ver); 470 | if (headers->referer) 471 | free (headers->referer); 472 | } 473 | 474 | /* A wrapper to close a socket. */ 475 | static void 476 | ws_close (int listener) { 477 | unset_pollfd (listener); 478 | close (listener); 479 | } 480 | 481 | /* Clear the client's sent queue and its data. */ 482 | static void 483 | ws_clear_queue (WSClient * client) { 484 | WSQueue **queue = &client->sockqueue; 485 | if (!(*queue)) 486 | return; 487 | 488 | if ((*queue)->queued) 489 | free ((*queue)->queued); 490 | (*queue)->queued = NULL; 491 | (*queue)->qlen = 0; 492 | 493 | free ((*queue)); 494 | (*queue) = NULL; 495 | 496 | /* done sending the whole queue, stop throttling */ 497 | client->status &= ~WS_THROTTLING; 498 | /* done sending, close connection if set to close */ 499 | if ((client->status & WS_CLOSE) && (client->status & WS_SENDING)) 500 | client->status = WS_CLOSE; 501 | } 502 | 503 | /* Free all HTTP handshake headers and structure. */ 504 | static void 505 | ws_clear_handshake_headers (WSHeaders * headers) { 506 | ws_free_header_fields (headers); 507 | free (headers); 508 | } 509 | 510 | /* Remove the given client from the list. */ 511 | static void 512 | ws_remove_client_from_list (WSClient * client, WSServer * server) { 513 | GSLList *node = NULL; 514 | 515 | if (!(node = ws_get_list_node_from_list (client->listener, &server->colist))) 516 | return; 517 | 518 | if (client->headers) 519 | ws_clear_handshake_headers (client->headers); 520 | list_remove_node (&server->colist, node); 521 | } 522 | 523 | #if HAVE_LIBSSL 524 | /* Attempt to send the TLS/SSL "close notify" shutdown and and removes 525 | * the SSL structure pointed to by ssl and frees up the allocated 526 | * memory. */ 527 | static void 528 | ws_shutdown_dangling_clients (WSClient * client) { 529 | shutdown_ssl (client); 530 | SSL_free (client->ssl); 531 | client->ssl = NULL; 532 | } 533 | 534 | /* Attempt to remove the SSL_CTX object pointed to by ctx and frees up 535 | * the allocated memory and cleans some more generally used TLS/SSL 536 | * memory. */ 537 | static void 538 | ws_ssl_cleanup (WSServer * server) { 539 | if (!wsconfig.use_ssl) 540 | return; 541 | 542 | if (server->ctx) 543 | SSL_CTX_free (server->ctx); 544 | 545 | CRYPTO_cleanup_all_ex_data (); 546 | CRYPTO_set_id_callback (NULL); 547 | CRYPTO_set_locking_callback (NULL); 548 | ERR_free_strings (); 549 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 550 | ERR_remove_state (0); 551 | #endif 552 | EVP_cleanup (); 553 | } 554 | #endif 555 | 556 | /* Remove all clients that are still hanging out. */ 557 | static int 558 | ws_remove_dangling_clients (void *value, void *user_data) { 559 | WSClient *client = value; 560 | (void) (user_data); 561 | 562 | if (client == NULL) 563 | return 1; 564 | 565 | if (client->headers) 566 | ws_clear_handshake_headers (client->headers); 567 | if (client->sockqueue) 568 | ws_clear_queue (client); 569 | #ifdef HAVE_LIBSSL 570 | if (client->ssl) 571 | ws_shutdown_dangling_clients (client); 572 | #endif 573 | 574 | return 0; 575 | } 576 | 577 | /* Do some housekeeping on the named pipe data packet. */ 578 | static void 579 | ws_clear_fifo_packet (WSPacket * packet) { 580 | if (!packet) 581 | return; 582 | 583 | if (packet->data) 584 | free (packet->data); 585 | free (packet); 586 | } 587 | 588 | /* Do some housekeeping on the named pipe. */ 589 | static void 590 | ws_clear_pipein (WSPipeIn * pipein) { 591 | WSPacket **packet = &pipein->packet; 592 | if (!pipein) 593 | return; 594 | 595 | if (pipein->fd != -1 && !wsconfig.use_stdin) 596 | ws_close (pipein->fd); 597 | 598 | ws_clear_fifo_packet (*packet); 599 | free (pipein); 600 | 601 | if (wsconfig.pipein && access (wsconfig.pipein, F_OK) != -1) 602 | unlink (wsconfig.pipein); 603 | } 604 | 605 | /* Do some housekeeping on the named pipe. */ 606 | static void 607 | ws_clear_pipeout (WSPipeOut * pipeout) { 608 | if (!pipeout) 609 | return; 610 | 611 | if (pipeout->fd != -1 && !wsconfig.use_stdout) 612 | ws_close (pipeout->fd); 613 | 614 | free (pipeout); 615 | 616 | if (wsconfig.pipeout && access (wsconfig.pipeout, F_OK) != -1) 617 | unlink (wsconfig.pipeout); 618 | } 619 | 620 | /* Stop the server and do some cleaning. */ 621 | void 622 | ws_stop (WSServer * server) { 623 | if (!server) { 624 | return; 625 | } else { 626 | WSPipeIn **pipein = &server->pipein; 627 | WSPipeOut **pipeout = &server->pipeout; 628 | 629 | ws_clear_pipein (*pipein); 630 | ws_clear_pipeout (*pipeout); 631 | 632 | /* close access log (if any) */ 633 | if (wsconfig.accesslog) 634 | access_log_close (); 635 | 636 | /* remove dangling clients */ 637 | if (list_count (server->colist) > 0) 638 | list_foreach (server->colist, ws_remove_dangling_clients, NULL); 639 | 640 | if (server->colist) 641 | list_remove_nodes (server->colist); 642 | 643 | #ifdef HAVE_LIBSSL 644 | ws_ssl_cleanup (server); 645 | #endif 646 | 647 | free (server); 648 | free (fdstate); 649 | fdstate = NULL; 650 | } 651 | } 652 | 653 | /* Set the connection status for the given client and return the given 654 | * bytes. 655 | * 656 | * The given number of bytes are returned. */ 657 | static int 658 | ws_set_status (WSClient * client, WSStatus status, int bytes) { 659 | client->status = status; 660 | return bytes; 661 | } 662 | 663 | /* Append the source string to destination and reallocates and 664 | * updating the destination buffer appropriately. */ 665 | static void 666 | ws_append_str (char **dest, const char *src) { 667 | size_t curlen = strlen (*dest); 668 | size_t srclen = strlen (src); 669 | size_t newlen = curlen + srclen; 670 | 671 | char *str = xrealloc (*dest, newlen + 1); 672 | memcpy (str + curlen, src, srclen + 1); 673 | *dest = str; 674 | } 675 | 676 | #if HAVE_LIBSSL 677 | /* Create a new SSL_CTX object as framework to establish TLS/SSL 678 | * enabled connections. 679 | * 680 | * On error 1 is returned. 681 | * On success, SSL_CTX object is malloc'd and 0 is returned. 682 | */ 683 | static int 684 | initialize_ssl_ctx (WSServer * server) { 685 | int ret = 1; 686 | SSL_CTX *ctx = NULL; 687 | 688 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 689 | SSL_library_init (); 690 | SSL_load_error_strings (); 691 | #endif 692 | 693 | /* Ciphers and message digests */ 694 | OpenSSL_add_ssl_algorithms (); 695 | 696 | /* ssl context */ 697 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 698 | if (!(ctx = SSL_CTX_new (SSLv23_server_method ()))) 699 | #else 700 | if (!(ctx = SSL_CTX_new (TLS_server_method ()))) 701 | #endif 702 | goto out; 703 | /* set certificate */ 704 | if (!SSL_CTX_use_certificate_file (ctx, wsconfig.sslcert, SSL_FILETYPE_PEM)) 705 | goto out; 706 | /* ssl private key */ 707 | if (!SSL_CTX_use_PrivateKey_file (ctx, wsconfig.sslkey, SSL_FILETYPE_PEM)) 708 | goto out; 709 | if (!SSL_CTX_check_private_key (ctx)) 710 | goto out; 711 | 712 | /* since we queued up the send data, a retry won't be the same buffer, 713 | * thus we need the following flags */ 714 | SSL_CTX_set_mode (ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE); 715 | 716 | server->ctx = ctx; 717 | ret = 0; 718 | out: 719 | if (ret) { 720 | SSL_CTX_free (ctx); 721 | LOG (("Error: %s\n", ERR_error_string (ERR_get_error (), NULL))); 722 | } 723 | 724 | return ret; 725 | } 726 | 727 | /* Log result code for TLS/SSL I/O operation */ 728 | static void 729 | log_return_message (int ret, int err, const char *fn) { 730 | unsigned long e; 731 | 732 | switch (err) { 733 | case SSL_ERROR_NONE: 734 | LOG (("SSL: %s -> SSL_ERROR_NONE\n", fn)); 735 | LOG (("SSL: TLS/SSL I/O operation completed\n")); 736 | break; 737 | case SSL_ERROR_WANT_READ: 738 | LOG (("SSL: %s -> SSL_ERROR_WANT_READ\n", fn)); 739 | LOG (("SSL: incomplete, data available for reading\n")); 740 | break; 741 | case SSL_ERROR_WANT_WRITE: 742 | LOG (("SSL: %s -> SSL_ERROR_WANT_WRITE\n", fn)); 743 | LOG (("SSL: incomplete, data available for writing\n")); 744 | break; 745 | case SSL_ERROR_ZERO_RETURN: 746 | LOG (("SSL: %s -> SSL_ERROR_ZERO_RETURN\n", fn)); 747 | LOG (("SSL: TLS/SSL connection has been closed\n")); 748 | break; 749 | case SSL_ERROR_WANT_X509_LOOKUP: 750 | LOG (("SSL: %s -> SSL_ERROR_WANT_X509_LOOKUP\n", fn)); 751 | break; 752 | case SSL_ERROR_SYSCALL: 753 | LOG (("SSL: %s -> SSL_ERROR_SYSCALL\n", fn)); 754 | 755 | e = ERR_get_error (); 756 | if (e > 0) 757 | LOG (("SSL: %s -> %s\n", fn, ERR_error_string (e, NULL))); 758 | 759 | /* call was not successful because a fatal error occurred either at the 760 | * protocol level or a connection failure occurred. */ 761 | if (ret != 0) { 762 | LOG (("SSL bogus handshake interrupt: %s\n", strerror (errno))); 763 | break; 764 | } 765 | /* call not yet finished. */ 766 | LOG (("SSL: handshake interrupted, got EOF\n")); 767 | if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) 768 | LOG (("SSL: %s -> not yet finished %s\n", fn, strerror (errno))); 769 | break; 770 | default: 771 | LOG (("SSL: %s -> failed fatal error code: %d\n", fn, err)); 772 | LOG (("SSL: %s\n", ERR_error_string (ERR_get_error (), NULL))); 773 | break; 774 | } 775 | } 776 | 777 | /* Shut down the client's TLS/SSL connection 778 | * 779 | * On fatal error, 1 is returned. 780 | * If data still needs to be read/written, -1 is returned. 781 | * On success, the TLS/SSL connection is closed and 0 is returned */ 782 | static int 783 | shutdown_ssl (WSClient * client) { 784 | int ret = -1, err = 0; 785 | 786 | /* all good */ 787 | if ((ret = SSL_shutdown (client->ssl)) > 0) 788 | return ws_set_status (client, WS_CLOSE, 0); 789 | 790 | err = SSL_get_error (client->ssl, ret); 791 | log_return_message (ret, err, "SSL_shutdown"); 792 | 793 | switch (err) { 794 | case SSL_ERROR_WANT_READ: 795 | case SSL_ERROR_WANT_WRITE: 796 | client->sslstatus = WS_TLS_SHUTTING; 797 | break; 798 | case SSL_ERROR_SYSCALL: 799 | if (ret == 0) { 800 | LOG (("SSL: SSL_shutdown, connection unexpectedly closed by peer.\n")); 801 | /* The shutdown is not yet finished. */ 802 | if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) 803 | client->sslstatus = WS_TLS_SHUTTING; 804 | break; 805 | } 806 | LOG (("SSL: SSL_shutdown, probably unrecoverable, forcing close.\n")); 807 | /* FALLTHRU */ 808 | case SSL_ERROR_ZERO_RETURN: 809 | case SSL_ERROR_WANT_X509_LOOKUP: 810 | default: 811 | return ws_set_status (client, WS_ERR | WS_CLOSE, 1); 812 | } 813 | 814 | return ret; 815 | } 816 | 817 | /* Wait for a TLS/SSL client to initiate a TLS/SSL handshake 818 | * 819 | * On fatal error, the connection is shut down. 820 | * If data still needs to be read/written, -1 is returned. 821 | * On success, the TLS/SSL connection is completed and 0 is returned */ 822 | static int 823 | accept_ssl (WSClient * client) { 824 | int ret = -1, err = 0; 825 | 826 | /* all good on TLS handshake */ 827 | if ((ret = SSL_accept (client->ssl)) > 0) { 828 | client->sslstatus &= ~WS_TLS_ACCEPTING; 829 | return 0; 830 | } 831 | 832 | err = SSL_get_error (client->ssl, ret); 833 | log_return_message (ret, err, "SSL_accept"); 834 | 835 | switch (err) { 836 | case SSL_ERROR_WANT_READ: 837 | case SSL_ERROR_WANT_WRITE: 838 | client->sslstatus = WS_TLS_ACCEPTING; 839 | break; 840 | case SSL_ERROR_SYSCALL: 841 | /* Wait for more activity else bail out, for instance if the socket is closed 842 | * during the handshake. */ 843 | if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) { 844 | client->sslstatus = WS_TLS_ACCEPTING; 845 | break; 846 | } 847 | /* The peer notified that it is shutting down through a SSL "close_notify" so 848 | * we shutdown too */ 849 | /* FALLTHRU */ 850 | case SSL_ERROR_ZERO_RETURN: 851 | case SSL_ERROR_WANT_X509_LOOKUP: 852 | default: 853 | client->sslstatus &= ~WS_TLS_ACCEPTING; 854 | return ws_set_status (client, WS_ERR | WS_CLOSE, 1); 855 | } 856 | 857 | return ret; 858 | } 859 | 860 | /* Create a new SSL structure for a connection and perform handshake */ 861 | static void 862 | handle_accept_ssl (WSClient * client, WSServer * server) { 863 | /* attempt to create SSL connection if we don't have one yet */ 864 | if (!client->ssl) { 865 | if (!(client->ssl = SSL_new (server->ctx))) { 866 | LOG (("SSL: SSL_new, new SSL structure failed.\n")); 867 | return; 868 | } 869 | if (!SSL_set_fd (client->ssl, client->listener)) { 870 | LOG (("SSL: unable to set file descriptor\n")); 871 | return; 872 | } 873 | } 874 | 875 | /* attempt to initiate the TLS/SSL handshake */ 876 | if (accept_ssl (client) == 0) { 877 | LOG (("SSL Accepted: %d %s\n", client->listener, client->remote_ip)); 878 | } 879 | } 880 | 881 | /* Given the current status of the SSL buffer, perform that action. 882 | * 883 | * On error or if no SSL pending status, 1 is returned. 884 | * On success, the TLS/SSL pending action is called and 0 is returned */ 885 | static int 886 | handle_ssl_pending_rw (int *conn, WSServer * server, WSClient * client) { 887 | if (!wsconfig.use_ssl) 888 | return 1; 889 | 890 | /* trying to write but still waiting for a successful SSL_accept */ 891 | if (client->sslstatus & WS_TLS_ACCEPTING) { 892 | handle_accept_ssl (client, server); 893 | return 0; 894 | } 895 | /* trying to read but still waiting for a successful SSL_read */ 896 | if (client->sslstatus & WS_TLS_READING) { 897 | handle_reads (conn, server); 898 | return 0; 899 | } 900 | /* trying to write but still waiting for a successful SSL_write */ 901 | if (client->sslstatus & WS_TLS_WRITING) { 902 | handle_writes (conn, server); 903 | return 0; 904 | } 905 | /* trying to write but still waiting for a successful SSL_shutdown */ 906 | if (client->sslstatus & WS_TLS_SHUTTING) { 907 | if (shutdown_ssl (client) == 0) 908 | handle_read_close (conn, client, server); 909 | return 0; 910 | } 911 | 912 | return 1; 913 | } 914 | 915 | /* Write bytes to a TLS/SSL connection for a given client. 916 | * 917 | * On error or if no write is performed <=0 is returned. 918 | * On success, the number of bytes actually written to the TLS/SSL 919 | * connection are returned */ 920 | static int 921 | send_ssl_buffer (WSClient * client, const char *buffer, int len) { 922 | int bytes = 0, err = 0; 923 | 924 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 925 | ERR_clear_error (); 926 | #endif 927 | if ((bytes = SSL_write (client->ssl, buffer, len)) > 0) 928 | return bytes; 929 | 930 | err = SSL_get_error (client->ssl, bytes); 931 | log_return_message (bytes, err, "SSL_write"); 932 | 933 | switch (err) { 934 | case SSL_ERROR_WANT_WRITE: 935 | break; 936 | case SSL_ERROR_WANT_READ: 937 | client->sslstatus = WS_TLS_WRITING; 938 | break; 939 | case SSL_ERROR_SYSCALL: 940 | if ((bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))) 941 | break; 942 | /* The connection was shut down cleanly */ 943 | /* FALLTHRU */ 944 | case SSL_ERROR_ZERO_RETURN: 945 | case SSL_ERROR_WANT_X509_LOOKUP: 946 | default: 947 | return ws_set_status (client, WS_ERR | WS_CLOSE, -1); 948 | } 949 | 950 | return bytes; 951 | } 952 | 953 | /* Read data from the given client's socket and set a connection 954 | * status given the output of recv(). 955 | * 956 | * On error, -1 is returned and the connection status is set. 957 | * On success, the number of bytes read is returned. */ 958 | static int 959 | read_ssl_socket (WSClient * client, char *buffer, int size) { 960 | int bytes = 0, done = 0, err = 0; 961 | do { 962 | #if OPENSSL_VERSION_NUMBER < 0x10100000L 963 | ERR_clear_error (); 964 | #endif 965 | 966 | done = 0; 967 | if ((bytes = SSL_read (client->ssl, buffer, size)) > 0) 968 | break; 969 | 970 | err = SSL_get_error (client->ssl, bytes); 971 | log_return_message (bytes, err, "SSL_read"); 972 | 973 | switch (err) { 974 | case SSL_ERROR_WANT_WRITE: 975 | client->sslstatus = WS_TLS_READING; 976 | done = 1; 977 | break; 978 | case SSL_ERROR_WANT_READ: 979 | done = 1; 980 | break; 981 | case SSL_ERROR_SYSCALL: 982 | if ((bytes < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))) 983 | break; 984 | /* FALLTHRU */ 985 | case SSL_ERROR_ZERO_RETURN: 986 | case SSL_ERROR_WANT_X509_LOOKUP: 987 | default: 988 | return ws_set_status (client, WS_ERR | WS_CLOSE, -1); 989 | } 990 | } while (SSL_pending (client->ssl) && !done); 991 | 992 | return bytes; 993 | } 994 | #endif 995 | 996 | /* Get sockaddr, either IPv4 or IPv6 */ 997 | static void * 998 | ws_get_raddr (struct sockaddr *sa) { 999 | if (sa->sa_family == AF_INET) 1000 | return &(((struct sockaddr_in *) (void *) sa)->sin_addr); 1001 | 1002 | return &(((struct sockaddr_in6 *) (void *) sa)->sin6_addr); 1003 | } 1004 | 1005 | /* Set the given file descriptor as NON BLOCKING. */ 1006 | void 1007 | set_nonblocking (int sock) { 1008 | if (fcntl (sock, F_SETFL, fcntl (sock, F_GETFL, 0) | O_NONBLOCK) == -1) 1009 | FATAL ("Unable to set socket as non-blocking: %s.", strerror (errno)); 1010 | } 1011 | 1012 | /* Accept a new connection on a socket and add it to the list of 1013 | * current connected clients. 1014 | * 1015 | * The newly assigned socket is returned. */ 1016 | static int 1017 | accept_client (int listener, GSLList ** colist) { 1018 | WSClient *client; 1019 | struct sockaddr_storage raddr; 1020 | int newfd; 1021 | const void *src = NULL; 1022 | socklen_t alen; 1023 | 1024 | alen = sizeof (raddr); 1025 | if ((newfd = accept (listener, (struct sockaddr *) &raddr, &alen)) == -1) 1026 | FATAL ("Unable to set accept: %s.", strerror (errno)); 1027 | 1028 | if (newfd == -1) { 1029 | LOG (("Unable to accept: %s.", strerror (errno))); 1030 | return newfd; 1031 | } 1032 | src = ws_get_raddr ((struct sockaddr *) &raddr); 1033 | 1034 | /* malloc a new client */ 1035 | client = new_wsclient (); 1036 | client->listener = newfd; 1037 | inet_ntop (raddr.ss_family, src, client->remote_ip, INET6_ADDRSTRLEN); 1038 | 1039 | /* add up our new client to keep track of */ 1040 | if (*colist == NULL) 1041 | *colist = list_create (client); 1042 | else 1043 | *colist = list_insert_prepend (*colist, client); 1044 | 1045 | /* make the socket non-blocking */ 1046 | set_nonblocking (client->listener); 1047 | 1048 | /* poll for the socket */ 1049 | set_pollfd (client->listener, POLLIN); 1050 | 1051 | return newfd; 1052 | } 1053 | 1054 | /* Extract the HTTP method. 1055 | * 1056 | * On error, or if not found, NULL is returned. 1057 | * On success, the HTTP method is returned. */ 1058 | static const char * 1059 | ws_get_method (const char *token) { 1060 | const char *lookfor = NULL; 1061 | 1062 | if ((lookfor = "GET", !memcmp (token, "GET ", 4)) || 1063 | (lookfor = "get", !memcmp (token, "get ", 4))) 1064 | return lookfor; 1065 | return NULL; 1066 | } 1067 | 1068 | /* Parse a request containing the method and protocol. 1069 | * 1070 | * On error, or unable to parse, NULL is returned. 1071 | * On success, the HTTP request is returned and the method and 1072 | * protocol are assigned to the corresponding buffers. */ 1073 | static char * 1074 | ws_parse_request (char *line, char **method, char **protocol) { 1075 | const char *meth; 1076 | char *req = NULL, *request = NULL, *proto = NULL; 1077 | ptrdiff_t rlen; 1078 | 1079 | if ((meth = ws_get_method (line)) == NULL) { 1080 | return NULL; 1081 | } else { 1082 | req = line + strlen (meth); 1083 | if ((proto = strstr (line, " HTTP/1.0")) == NULL && 1084 | (proto = strstr (line, " HTTP/1.1")) == NULL) 1085 | return NULL; 1086 | 1087 | req++; 1088 | if ((rlen = proto - req) <= 0) 1089 | return NULL; 1090 | 1091 | request = xmalloc (rlen + 1); 1092 | strncpy (request, req, rlen); 1093 | request[rlen] = 0; 1094 | 1095 | (*method) = strtoupper (xstrdup (meth)); 1096 | (*protocol) = strtoupper (xstrdup (++proto)); 1097 | } 1098 | 1099 | return request; 1100 | } 1101 | 1102 | /* Given a pair of key/values, assign it to our HTTP headers 1103 | * structure. */ 1104 | static void 1105 | ws_set_header_key_value (WSHeaders * headers, char *key, char *value) { 1106 | if (strcasecmp ("Host", key) == 0) 1107 | headers->host = xstrdup (value); 1108 | else if (strcasecmp ("Origin", key) == 0) 1109 | headers->origin = xstrdup (value); 1110 | else if (strcasecmp ("Upgrade", key) == 0) 1111 | headers->upgrade = xstrdup (value); 1112 | else if (strcasecmp ("Connection", key) == 0) 1113 | headers->connection = xstrdup (value); 1114 | else if (strcasecmp ("Sec-WebSocket-Protocol", key) == 0) 1115 | headers->ws_protocol = xstrdup (value); 1116 | else if (strcasecmp ("Sec-WebSocket-Key", key) == 0) 1117 | headers->ws_key = xstrdup (value); 1118 | else if (strcasecmp ("Sec-WebSocket-Version", key) == 0) 1119 | headers->ws_sock_ver = xstrdup (value); 1120 | else if (strcasecmp ("User-Agent", key) == 0) 1121 | headers->agent = xstrdup (value); 1122 | else if (strcasecmp ("Referer", key) == 0) 1123 | headers->referer = xstrdup (value); 1124 | } 1125 | 1126 | /* Verify that the given HTTP headers were passed upon doing the 1127 | * websocket handshake. 1128 | * 1129 | * On error, or header missing, 1 is returned. 1130 | * On success, 0 is returned. */ 1131 | static int 1132 | ws_verify_req_headers (WSHeaders * headers) { 1133 | if (!headers->host) 1134 | return 1; 1135 | if (!headers->method) 1136 | return 1; 1137 | if (!headers->protocol) 1138 | return 1; 1139 | if (!headers->path) 1140 | return 1; 1141 | if (wsconfig.origin && !headers->origin) 1142 | return 1; 1143 | if (wsconfig.origin && strcasecmp (wsconfig.origin, headers->origin) != 0) 1144 | return 1; 1145 | if (!headers->connection) 1146 | return 1; 1147 | if (!headers->ws_key) 1148 | return 1; 1149 | if (!headers->ws_sock_ver) 1150 | return 1; 1151 | return 0; 1152 | } 1153 | 1154 | /* From RFC2616, each header field consists of a name followed by a 1155 | * colon (":") and the field value. Field names are case-insensitive. 1156 | * The field value MAY be preceded by any amount of LWS, though a 1157 | * single SP is preferred */ 1158 | static int 1159 | ws_set_header_fields (char *line, WSHeaders * headers) { 1160 | char *path = NULL, *method = NULL, *proto = NULL, *p, *value; 1161 | 1162 | if (line[0] == '\n' || line[0] == '\r') 1163 | return 1; 1164 | 1165 | if ((strstr (line, "GET ")) || (strstr (line, "get "))) { 1166 | if ((path = ws_parse_request (line, &method, &proto)) == NULL) 1167 | return 1; 1168 | headers->path = path; 1169 | headers->method = method; 1170 | headers->protocol = proto; 1171 | 1172 | return 0; 1173 | } 1174 | 1175 | if ((p = strchr (line, ':')) == NULL) 1176 | return 1; 1177 | 1178 | value = p + 1; 1179 | while (p != line && isspace ((unsigned char) *(p - 1))) 1180 | p--; 1181 | 1182 | if (p == line) 1183 | return 1; 1184 | 1185 | *p = '\0'; 1186 | if (strpbrk (line, " \t") != NULL) { 1187 | *p = ' '; 1188 | return 1; 1189 | } 1190 | while (isspace ((unsigned char) *value)) 1191 | value++; 1192 | 1193 | ws_set_header_key_value (headers, line, value); 1194 | 1195 | return 0; 1196 | } 1197 | 1198 | /* Parse the given HTTP headers and set the expected websocket 1199 | * handshake. 1200 | * 1201 | * On error, or 1 is returned. 1202 | * On success, 0 is returned. */ 1203 | static int 1204 | parse_headers (WSHeaders * headers) { 1205 | char *tmp = NULL; 1206 | const char *buffer = headers->buf; 1207 | const char *line = buffer, *next = NULL; 1208 | int len = 0; 1209 | 1210 | while (line) { 1211 | if ((next = strstr (line, "\r\n")) != NULL) 1212 | len = (next - line); 1213 | else 1214 | len = strlen (line); 1215 | 1216 | if (len <= 0) 1217 | return 1; 1218 | 1219 | tmp = xmalloc (len + 1); 1220 | memcpy (tmp, line, len); 1221 | tmp[len] = '\0'; 1222 | 1223 | if (ws_set_header_fields (tmp, headers) == 1) { 1224 | free (tmp); 1225 | return 1; 1226 | } 1227 | 1228 | free (tmp); 1229 | line = next ? (next + 2) : NULL; 1230 | 1231 | if (next && strcmp (next, "\r\n\r\n") == 0) 1232 | break; 1233 | } 1234 | 1235 | return 0; 1236 | } 1237 | 1238 | /* Set into a queue the data that couldn't be sent. */ 1239 | static void 1240 | ws_queue_sockbuf (WSClient * client, const char *buffer, int len, int bytes) { 1241 | WSQueue *queue = xcalloc (1, sizeof (WSQueue)); 1242 | 1243 | if (bytes < 1) 1244 | bytes = 0; 1245 | 1246 | queue->queued = xcalloc (len - bytes, sizeof (char)); 1247 | memcpy (queue->queued, buffer + bytes, len - bytes); 1248 | queue->qlen = len - bytes; 1249 | client->sockqueue = queue; 1250 | 1251 | client->status |= WS_SENDING; 1252 | set_pollfd (client->listener, POLLIN | POLLOUT); 1253 | } 1254 | 1255 | /* Read data from the given client's socket and set a connection 1256 | * status given the output of recv(). 1257 | * 1258 | * On error, -1 is returned and the connection status is set. 1259 | * On success, the number of bytes read is returned. */ 1260 | static int 1261 | read_plain_socket (WSClient * client, char *buffer, int size) { 1262 | int bytes = 0; 1263 | 1264 | bytes = recv (client->listener, buffer, size, 0); 1265 | 1266 | if (bytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) 1267 | return ws_set_status (client, WS_READING, bytes); 1268 | else if (bytes == -1 || bytes == 0) 1269 | return ws_set_status (client, WS_ERR | WS_CLOSE, bytes); 1270 | 1271 | return bytes; 1272 | } 1273 | 1274 | /* Read data from the given client's socket and set a connection 1275 | * status given the output of recv(). 1276 | * 1277 | * On error, -1 is returned and the connection status is set. 1278 | * On success, the number of bytes read is returned. */ 1279 | static int 1280 | read_socket (WSClient * client, char *buffer, int size) { 1281 | #ifdef HAVE_LIBSSL 1282 | if (wsconfig.use_ssl) 1283 | return read_ssl_socket (client, buffer, size); 1284 | else 1285 | return read_plain_socket (client, buffer, size); 1286 | #else 1287 | return read_plain_socket (client, buffer, size); 1288 | #endif 1289 | } 1290 | 1291 | static int 1292 | send_plain_buffer (WSClient * client, const char *buffer, int len) { 1293 | return send (client->listener, buffer, len, 0); 1294 | } 1295 | 1296 | static int 1297 | send_buffer (WSClient * client, const char *buffer, int len) { 1298 | #ifdef HAVE_LIBSSL 1299 | if (wsconfig.use_ssl) 1300 | return send_ssl_buffer (client, buffer, len); 1301 | else 1302 | return send_plain_buffer (client, buffer, len); 1303 | #else 1304 | return send_plain_buffer (client, buffer, len); 1305 | #endif 1306 | } 1307 | 1308 | /* Attempt to send the given buffer to the given socket. 1309 | * 1310 | * On error, -1 is returned and the connection status is set. 1311 | * On success, the number of bytes sent is returned. */ 1312 | static int 1313 | ws_respond_data (WSClient * client, const char *buffer, int len) { 1314 | int bytes = 0; 1315 | 1316 | bytes = send_buffer (client, buffer, len); 1317 | if (bytes == -1 && errno == EPIPE) 1318 | return ws_set_status (client, WS_ERR | WS_CLOSE, bytes); 1319 | 1320 | /* did not send all of it... buffer it for a later attempt */ 1321 | if (bytes < len || (bytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK))) 1322 | ws_queue_sockbuf (client, buffer, len, bytes); 1323 | 1324 | return bytes; 1325 | } 1326 | 1327 | /* Attempt to send the queued up client's data to the given socket. 1328 | * 1329 | * On error, -1 is returned and the connection status is set. 1330 | * On success, the number of bytes sent is returned. */ 1331 | static int 1332 | ws_respond_cache (WSClient * client) { 1333 | WSQueue *queue = client->sockqueue; 1334 | int bytes = 0; 1335 | 1336 | bytes = send_buffer (client, queue->queued, queue->qlen); 1337 | if (bytes == -1 && errno == EPIPE) 1338 | return ws_set_status (client, WS_ERR | WS_CLOSE, bytes); 1339 | 1340 | if (bytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) 1341 | return bytes; 1342 | 1343 | if (chop_nchars (queue->queued, bytes, queue->qlen) == 0) 1344 | ws_clear_queue (client); 1345 | else 1346 | queue->qlen -= bytes; 1347 | 1348 | return bytes; 1349 | } 1350 | 1351 | /* Attempt to realloc the current sent queue. 1352 | * 1353 | * On error, 1 is returned and the connection status is set. 1354 | * On success, 0 is returned. */ 1355 | static int 1356 | ws_realloc_send_buf (WSClient * client, const char *buf, int len) { 1357 | WSQueue *queue = client->sockqueue; 1358 | char *tmp = NULL; 1359 | int newlen = 0; 1360 | 1361 | newlen = queue->qlen + len; 1362 | tmp = realloc (queue->queued, newlen); 1363 | if (tmp == NULL && newlen > 0) { 1364 | ws_clear_queue (client); 1365 | return ws_set_status (client, WS_ERR | WS_CLOSE, 1); 1366 | } 1367 | queue->queued = tmp; 1368 | memcpy (queue->queued + queue->qlen, buf, len); 1369 | queue->qlen += len; 1370 | 1371 | /* client probably too slow, so stop queueing until everything is 1372 | * sent */ 1373 | if (queue->qlen >= WS_THROTTLE_THLD) 1374 | client->status |= WS_THROTTLING; 1375 | 1376 | return 0; 1377 | } 1378 | 1379 | /* An entry point to attempt to send the client's data. 1380 | * 1381 | * On error, 1 is returned and the connection status is set. 1382 | * On success, the number of bytes sent is returned. */ 1383 | static int 1384 | ws_respond (WSClient * client, const char *buffer, int len) { 1385 | int bytes = 0; 1386 | 1387 | /* attempt to send the whole buffer buffer */ 1388 | if (client->sockqueue == NULL) 1389 | bytes = ws_respond_data (client, buffer, len); 1390 | /* buffer not empty, just append new data iff we're not throttling the 1391 | * client */ 1392 | else if (client->sockqueue != NULL && buffer != NULL && !(client->status & WS_THROTTLING)) { 1393 | if (ws_realloc_send_buf (client, buffer, len) == 1) 1394 | return bytes; 1395 | } 1396 | /* send from cache buffer */ 1397 | else { 1398 | bytes = ws_respond_cache (client); 1399 | } 1400 | 1401 | return bytes; 1402 | } 1403 | 1404 | /* Encode a websocket frame (header/message) and attempt to send it 1405 | * through the client's socket. 1406 | * 1407 | * On success, 0 is returned. */ 1408 | static int 1409 | ws_send_frame (WSClient * client, WSOpcode opcode, const char *p, int sz) { 1410 | unsigned char buf[32] = { 0 }; 1411 | char *frm = NULL; 1412 | uint64_t payloadlen = 0, u64; 1413 | int hsize = 2; 1414 | 1415 | if (sz < 126) { 1416 | payloadlen = sz; 1417 | } else if (sz < (1 << 16)) { 1418 | payloadlen = WS_PAYLOAD_EXT16; 1419 | hsize += 2; 1420 | } else { 1421 | payloadlen = WS_PAYLOAD_EXT64; 1422 | hsize += 8; 1423 | } 1424 | 1425 | buf[0] = 0x80 | ((uint8_t) opcode); 1426 | switch (payloadlen) { 1427 | case WS_PAYLOAD_EXT16: 1428 | buf[1] = WS_PAYLOAD_EXT16; 1429 | buf[2] = (sz & 0xff00) >> 8; 1430 | buf[3] = (sz & 0x00ff) >> 0; 1431 | break; 1432 | case WS_PAYLOAD_EXT64: 1433 | buf[1] = WS_PAYLOAD_EXT64; 1434 | u64 = htobe64 (sz); 1435 | memcpy (buf + 2, &u64, sizeof (uint64_t)); 1436 | break; 1437 | default: 1438 | buf[1] = (sz & 0xff); 1439 | } 1440 | frm = xcalloc (hsize + sz, sizeof (unsigned char)); 1441 | memcpy (frm, buf, hsize); 1442 | if (p != NULL && sz > 0) 1443 | memcpy (frm + hsize, p, sz); 1444 | 1445 | ws_respond (client, frm, hsize + sz); 1446 | free (frm); 1447 | 1448 | return 0; 1449 | } 1450 | 1451 | /* Send an error message to the given client. 1452 | * 1453 | * On success, the number of sent bytes is returned. */ 1454 | static int 1455 | ws_error (WSClient * client, unsigned short code, const char *err) { 1456 | unsigned int len; 1457 | unsigned short code_be; 1458 | char buf[128] = { 0 }; 1459 | 1460 | len = 2; 1461 | code_be = htobe16 (code); 1462 | memcpy (buf, &code_be, 2); 1463 | if (err) 1464 | len += snprintf (buf + 2, sizeof buf - 4, "%s", err); 1465 | 1466 | return ws_send_frame (client, WS_OPCODE_CLOSE, buf, len); 1467 | } 1468 | 1469 | /* Log hit to the access log. 1470 | * 1471 | * On success, the hit/entry is logged. */ 1472 | static void 1473 | access_log (WSClient * client, int status_code) { 1474 | WSHeaders *hdrs = client->headers; 1475 | char buf[64] = { 0 }; 1476 | uint32_t elapsed = 0; 1477 | struct timeval tv; 1478 | struct tm time; 1479 | char *req = NULL, *ref = NULL, *ua = NULL; 1480 | 1481 | gettimeofday (&tv, NULL); 1482 | strftime (buf, sizeof (buf) - 1, "[%d/%b/%Y:%H:%M:%S %z]", localtime_r (&tv.tv_sec, &time)); 1483 | 1484 | elapsed = (client->end_proc.tv_sec - client->start_proc.tv_sec) * 1000.0; 1485 | elapsed += (client->end_proc.tv_usec - client->start_proc.tv_usec) / 1000.0; 1486 | 1487 | req = escape_http_request (hdrs->path); 1488 | ref = escape_http_request (hdrs->referer); 1489 | ua = escape_http_request (hdrs->agent); 1490 | 1491 | ACCESS_LOG (("%s ", client->remote_ip)); 1492 | ACCESS_LOG (("- - ")); 1493 | ACCESS_LOG (("%s ", buf)); 1494 | ACCESS_LOG (("\"%s ", hdrs->method)); 1495 | ACCESS_LOG (("%s ", req ? req : "-")); 1496 | ACCESS_LOG (("%s\" ", hdrs->protocol)); 1497 | ACCESS_LOG (("%d ", status_code)); 1498 | ACCESS_LOG (("%d ", hdrs->buflen)); 1499 | ACCESS_LOG (("\"%s\" ", ref ? ref : "-")); 1500 | ACCESS_LOG (("\"%s\" ", ua ? ua : "-")); 1501 | ACCESS_LOG (("%u\n", elapsed)); 1502 | 1503 | if (req) 1504 | free (req); 1505 | if (ref) 1506 | free (ref); 1507 | if (ua) 1508 | free (ua); 1509 | } 1510 | 1511 | /* Send an HTTP error status to the given client. 1512 | * 1513 | * On success, the number of sent bytes is returned. */ 1514 | static int 1515 | http_error (WSClient * client, const char *buffer) { 1516 | /* do access logging */ 1517 | gettimeofday (&client->end_proc, NULL); 1518 | if (wsconfig.accesslog) 1519 | access_log (client, 400); 1520 | 1521 | return ws_respond (client, buffer, strlen (buffer)); 1522 | } 1523 | 1524 | /* Compute the SHA1 for the handshake. */ 1525 | static void 1526 | ws_sha1_digest (char *s, int len, unsigned char *digest) { 1527 | SHA1_CTX sha; 1528 | 1529 | SHA1Init (&sha); 1530 | SHA1Update (&sha, (uint8_t *) s, len); 1531 | SHA1Final (digest, &sha); 1532 | } 1533 | 1534 | /* Set the parsed websocket handshake headers. */ 1535 | static void 1536 | ws_set_handshake_headers (WSHeaders * headers) { 1537 | size_t klen = strlen (headers->ws_key); 1538 | size_t mlen = strlen (WS_MAGIC_STR); 1539 | size_t len = klen + mlen; 1540 | char *s = xmalloc (klen + mlen + 1); 1541 | uint8_t digest[SHA_DIGEST_LENGTH]; 1542 | 1543 | memset (digest, 0, sizeof *digest); 1544 | 1545 | memcpy (s, headers->ws_key, klen); 1546 | memcpy (s + klen, WS_MAGIC_STR, mlen + 1); 1547 | 1548 | ws_sha1_digest (s, len, digest); 1549 | 1550 | /* set response headers */ 1551 | headers->ws_accept = base64_encode ((unsigned char *) digest, sizeof (digest)); 1552 | headers->ws_resp = xstrdup (WS_SWITCH_PROTO_STR); 1553 | 1554 | if (!headers->upgrade) 1555 | headers->upgrade = xstrdup ("websocket"); 1556 | if (!headers->connection) 1557 | headers->connection = xstrdup ("Upgrade"); 1558 | 1559 | free (s); 1560 | } 1561 | 1562 | /* Send the websocket handshake headers to the given client. 1563 | * 1564 | * On success, the number of sent bytes is returned. */ 1565 | static int 1566 | ws_send_handshake_headers (WSClient * client, WSHeaders * headers) { 1567 | int bytes = 0; 1568 | char *str = xstrdup (""); 1569 | 1570 | ws_append_str (&str, headers->ws_resp); 1571 | ws_append_str (&str, CRLF); 1572 | 1573 | ws_append_str (&str, "Upgrade: "); 1574 | ws_append_str (&str, headers->upgrade); 1575 | ws_append_str (&str, CRLF); 1576 | 1577 | ws_append_str (&str, "Connection: "); 1578 | ws_append_str (&str, headers->connection); 1579 | ws_append_str (&str, CRLF); 1580 | 1581 | ws_append_str (&str, "Sec-WebSocket-Accept: "); 1582 | ws_append_str (&str, headers->ws_accept); 1583 | ws_append_str (&str, CRLF CRLF); 1584 | 1585 | bytes = ws_respond (client, str, strlen (str)); 1586 | free (str); 1587 | 1588 | return bytes; 1589 | } 1590 | 1591 | /* Given the HTTP connection headers, attempt to parse the web socket 1592 | * handshake headers. 1593 | * 1594 | * On success, the number of sent bytes is returned. */ 1595 | static int 1596 | ws_get_handshake (WSClient * client, WSServer * server) { 1597 | int bytes = 0, readh = 0; 1598 | char *buf = NULL; 1599 | 1600 | if (client->headers == NULL) 1601 | client->headers = new_wsheader (); 1602 | 1603 | buf = client->headers->buf; 1604 | readh = client->headers->buflen; 1605 | /* Probably the connection was closed before finishing handshake */ 1606 | if ((bytes = read_socket (client, buf + readh, WS_MAX_HEAD_SZ - readh)) < 1) { 1607 | if (client->status & WS_CLOSE) { 1608 | LOG (("Connection aborted %d [%s]...\n", client->listener, client->remote_ip)); 1609 | http_error (client, WS_BAD_REQUEST_STR); 1610 | } 1611 | LOG (("Can't establish handshake %d [%s]...\n", client->listener, client->remote_ip)); 1612 | return ws_set_status (client, WS_CLOSE, bytes); 1613 | } 1614 | client->headers->buflen += bytes; 1615 | 1616 | buf[client->headers->buflen] = '\0'; /* null-terminate */ 1617 | 1618 | /* Must have a \r\n\r\n */ 1619 | if (strstr (buf, "\r\n\r\n") == NULL) { 1620 | if (strlen (buf) < WS_MAX_HEAD_SZ) { 1621 | LOG (("Headers too long %d [%s]...\n", client->listener, client->remote_ip)); 1622 | return ws_set_status (client, WS_READING, bytes); 1623 | } 1624 | 1625 | LOG (("Invalid newlines for handshake %d [%s]...\n", client->listener, client->remote_ip)); 1626 | http_error (client, WS_BAD_REQUEST_STR); 1627 | return ws_set_status (client, WS_CLOSE, bytes); 1628 | } 1629 | 1630 | /* Ensure we have valid HTTP headers for the handshake */ 1631 | if (parse_headers (client->headers) != 0) { 1632 | LOG (("Invalid headers for handshake %d [%s]...\n", client->listener, client->remote_ip)); 1633 | http_error (client, WS_BAD_REQUEST_STR); 1634 | return ws_set_status (client, WS_CLOSE, bytes); 1635 | } 1636 | 1637 | /* Ensure we have the required headers */ 1638 | if (ws_verify_req_headers (client->headers) != 0) { 1639 | LOG (("Missing headers for handshake %d [%s]...\n", client->listener, client->remote_ip)); 1640 | http_error (client, WS_BAD_REQUEST_STR); 1641 | return ws_set_status (client, WS_CLOSE, bytes); 1642 | } 1643 | 1644 | ws_set_handshake_headers (client->headers); 1645 | 1646 | /* handshake response */ 1647 | ws_send_handshake_headers (client, client->headers); 1648 | 1649 | /* upon success, call onopen() callback */ 1650 | if (server->onopen && wsconfig.strict && !wsconfig.echomode) 1651 | server->onopen (server->pipeout, client); 1652 | client->headers->reading = 0; 1653 | 1654 | /* do access logging */ 1655 | gettimeofday (&client->end_proc, NULL); 1656 | if (wsconfig.accesslog) 1657 | access_log (client, 101); 1658 | LOG (("Active: %d\n", list_count (server->colist))); 1659 | 1660 | return ws_set_status (client, WS_OK, bytes); 1661 | } 1662 | 1663 | /* Send a data message to the given client. 1664 | * 1665 | * On success, 0 is returned. */ 1666 | int 1667 | ws_send_data (WSClient * client, WSOpcode opcode, const char *p, int sz) { 1668 | char *buf = NULL; 1669 | 1670 | if (opcode != WS_OPCODE_BIN) { 1671 | buf = sanitize_utf8 (p, sz); 1672 | } else { 1673 | buf = xmalloc (sz); 1674 | memcpy (buf, p, sz); 1675 | } 1676 | ws_send_frame (client, opcode, buf, sz); 1677 | free (buf); 1678 | 1679 | return 0; 1680 | } 1681 | 1682 | /* Read a websocket frame's header. 1683 | * 1684 | * On success, the number of bytesr read is returned. */ 1685 | static int 1686 | ws_read_header (WSClient * client, WSFrame * frm, int pos, int need) { 1687 | char *buf = frm->buf; 1688 | int bytes = 0; 1689 | 1690 | /* read the first 2 bytes for basic frame info */ 1691 | if ((bytes = read_socket (client, buf + pos, need)) < 1) { 1692 | if (client->status & WS_CLOSE) 1693 | ws_error (client, WS_CLOSE_UNEXPECTED, "Unable to read header"); 1694 | return bytes; 1695 | } 1696 | frm->buflen += bytes; 1697 | frm->buf[frm->buflen] = '\0'; /* null-terminate */ 1698 | 1699 | return bytes; 1700 | } 1701 | 1702 | /* Read a websocket frame's payload. 1703 | * 1704 | * On success, the number of bytesr read is returned. */ 1705 | static int 1706 | ws_read_payload (WSClient * client, WSMessage * msg, int pos, int need) { 1707 | char *buf = msg->payload; 1708 | int bytes = 0; 1709 | 1710 | /* read the first 2 bytes for basic frame info */ 1711 | if ((bytes = read_socket (client, buf + pos, need)) < 1) { 1712 | if (client->status & WS_CLOSE) 1713 | ws_error (client, WS_CLOSE_UNEXPECTED, "Unable to read payload"); 1714 | return bytes; 1715 | } 1716 | msg->buflen += bytes; 1717 | msg->payloadsz += bytes; 1718 | 1719 | return bytes; 1720 | } 1721 | 1722 | /* Set the basic frame headers on a frame structure. 1723 | * 1724 | * On success, 0 is returned. */ 1725 | static int 1726 | ws_set_front_header_fields (WSClient * client) { 1727 | WSFrame **frm = &client->frame; 1728 | char *buf = (*frm)->buf; 1729 | 1730 | (*frm)->fin = WS_FRM_FIN (*(buf)); 1731 | (*frm)->masking = WS_FRM_MASK (*(buf + 1)); 1732 | (*frm)->opcode = WS_FRM_OPCODE (*(buf)); 1733 | (*frm)->res = WS_FRM_R1 (*(buf)) || WS_FRM_R2 (*(buf)) || WS_FRM_R3 (*(buf)); 1734 | 1735 | /* should be masked and can't be using RESVd bits */ 1736 | if (!(*frm)->masking || (*frm)->res) 1737 | return ws_set_status (client, WS_ERR | WS_CLOSE, 1); 1738 | 1739 | return 0; 1740 | } 1741 | 1742 | /* Unmask the payload given the current frame's masking key. */ 1743 | static void 1744 | ws_unmask_payload (char *buf, int len, int offset, unsigned char mask[]) { 1745 | int i, j = 0; 1746 | 1747 | /* unmask data */ 1748 | for (i = offset; i < len; ++i, ++j) { 1749 | buf[i] ^= mask[j % 4]; 1750 | } 1751 | } 1752 | 1753 | /* Close a websocket connection. */ 1754 | static int 1755 | ws_handle_close (WSClient * client) { 1756 | client->status = WS_ERR | WS_CLOSE; 1757 | return ws_send_frame (client, WS_OPCODE_CLOSE, NULL, 0); 1758 | } 1759 | 1760 | /* Handle a websocket error. 1761 | * 1762 | * On success, the number of bytes sent is returned. */ 1763 | static int 1764 | ws_handle_err (WSClient * client, unsigned short code, WSStatus status, const char *m) { 1765 | client->status = status; 1766 | return ws_error (client, code, m); 1767 | } 1768 | 1769 | /* Handle a websocket pong. */ 1770 | static void 1771 | ws_handle_pong (WSClient * client) { 1772 | WSFrame **frm = &client->frame; 1773 | 1774 | if (!(*frm)->fin) { 1775 | ws_handle_err (client, WS_CLOSE_PROTO_ERR, WS_ERR | WS_CLOSE, NULL); 1776 | return; 1777 | } 1778 | ws_free_message (client); 1779 | } 1780 | 1781 | /* Handle a websocket ping from the client and it attempts to send 1782 | * back a pong as soon as possible. */ 1783 | static void 1784 | ws_handle_ping (WSClient * client) { 1785 | WSFrame **frm = &client->frame; 1786 | WSMessage **msg = &client->message; 1787 | char *buf = NULL, *tmp = NULL; 1788 | int pos = 0, len = (*frm)->payloadlen, newlen = 0; 1789 | 1790 | /* RFC states that Control frames themselves MUST NOT be 1791 | * fragmented. */ 1792 | if (!(*frm)->fin) { 1793 | ws_handle_err (client, WS_CLOSE_PROTO_ERR, WS_ERR | WS_CLOSE, NULL); 1794 | return; 1795 | } 1796 | 1797 | /* Control frames are only allowed to have payload up to and 1798 | * including 125 octets */ 1799 | if ((*frm)->payloadlen > 125) { 1800 | ws_handle_err (client, WS_CLOSE_PROTO_ERR, WS_ERR | WS_CLOSE, NULL); 1801 | return; 1802 | } 1803 | 1804 | /* No payload from ping */ 1805 | if (len == 0) { 1806 | ws_send_frame (client, WS_OPCODE_PONG, NULL, 0); 1807 | return; 1808 | } 1809 | 1810 | /* Copy the ping payload */ 1811 | pos = (*msg)->payloadsz - len; 1812 | buf = xcalloc (len, sizeof (char)); 1813 | memcpy (buf, (*msg)->payload + pos, len); 1814 | 1815 | /* Unmask it */ 1816 | ws_unmask_payload (buf, len, 0, (*frm)->mask); 1817 | 1818 | /* Resize the current payload (keep an eye on this realloc) */ 1819 | newlen = (*msg)->payloadsz - len; 1820 | tmp = realloc ((*msg)->payload, newlen); 1821 | if (tmp == NULL && newlen > 0) { 1822 | free ((*msg)->payload); 1823 | free (buf); 1824 | 1825 | (*msg)->payload = NULL; 1826 | client->status = WS_ERR | WS_CLOSE; 1827 | return; 1828 | } 1829 | 1830 | (*msg)->payload = tmp; 1831 | (*msg)->payloadsz -= len; 1832 | 1833 | ws_send_frame (client, WS_OPCODE_PONG, buf, len); 1834 | 1835 | (*msg)->buflen = 0; /* done with the current frame's payload */ 1836 | /* Control frame injected in the middle of a fragmented message. */ 1837 | if (!(*msg)->fragmented) { 1838 | ws_free_message (client); 1839 | } 1840 | free (buf); 1841 | } 1842 | 1843 | /* Ensure we have valid UTF-8 text payload. 1844 | * 1845 | * On error, or if the message is invalid, 1 is returned. 1846 | * On success, or if the message is valid, 0 is returned. */ 1847 | int 1848 | ws_validate_string (const char *str, int len) { 1849 | uint32_t state = UTF8_VALID; 1850 | 1851 | if (verify_utf8 (&state, str, len) == UTF8_INVAL) { 1852 | LOG (("Invalid UTF8 data!\n")); 1853 | return 1; 1854 | } 1855 | if (state != UTF8_VALID) { 1856 | LOG (("Invalid UTF8 data!\n")); 1857 | return 1; 1858 | } 1859 | 1860 | return 0; 1861 | } 1862 | 1863 | /* It handles a text or binary message frame from the client. */ 1864 | static void 1865 | ws_handle_text_bin (WSClient * client, WSServer * server) { 1866 | WSFrame **frm = &client->frame; 1867 | WSMessage **msg = &client->message; 1868 | int offset = (*msg)->mask_offset; 1869 | 1870 | /* All data frames after the initial data frame must have opcode 0 */ 1871 | if ((*msg)->fragmented && (*frm)->opcode != WS_OPCODE_CONTINUATION) { 1872 | client->status = WS_ERR | WS_CLOSE; 1873 | return; 1874 | } 1875 | 1876 | /* RFC states that there is a new masking key per frame, therefore, 1877 | * time to unmask... */ 1878 | ws_unmask_payload ((*msg)->payload, (*msg)->payloadsz, offset, (*frm)->mask); 1879 | /* Done with the current frame's payload */ 1880 | (*msg)->buflen = 0; 1881 | /* Reading a fragmented frame */ 1882 | (*msg)->fragmented = 1; 1883 | 1884 | if (!(*frm)->fin) 1885 | return; 1886 | 1887 | /* validate text data encoded as UTF-8 */ 1888 | if ((*msg)->opcode == WS_OPCODE_TEXT) { 1889 | if (ws_validate_string ((*msg)->payload, (*msg)->payloadsz) != 0) { 1890 | ws_handle_err (client, WS_CLOSE_INVALID_UTF8, WS_ERR | WS_CLOSE, NULL); 1891 | return; 1892 | } 1893 | } 1894 | 1895 | if ((*msg)->opcode != WS_OPCODE_CONTINUATION && server->onmessage) { 1896 | /* just echo the message to the client */ 1897 | if (wsconfig.echomode) 1898 | ws_send_data (client, (*msg)->opcode, (*msg)->payload, (*msg)->payloadsz); 1899 | /* just pipe out the message */ 1900 | else if (!wsconfig.strict) 1901 | ws_write_fifo (server->pipeout, (*msg)->payload, (*msg)->payloadsz); 1902 | else 1903 | server->onmessage (server->pipeout, client); 1904 | } 1905 | ws_free_message (client); 1906 | } 1907 | 1908 | /* Depending on the frame opcode, then we take certain decisions. */ 1909 | static void 1910 | ws_manage_payload_opcode (WSClient * client, WSServer * server) { 1911 | WSFrame **frm = &client->frame; 1912 | WSMessage **msg = &client->message; 1913 | 1914 | switch ((*frm)->opcode) { 1915 | case WS_OPCODE_CONTINUATION: 1916 | LOG (("CONTINUATION\n")); 1917 | /* first frame can't be a continuation frame */ 1918 | if (!(*msg)->fragmented) { 1919 | client->status = WS_ERR | WS_CLOSE; 1920 | break; 1921 | } 1922 | ws_handle_text_bin (client, server); 1923 | break; 1924 | case WS_OPCODE_TEXT: 1925 | case WS_OPCODE_BIN: 1926 | LOG (("TEXT\n")); 1927 | client->message->opcode = (*frm)->opcode; 1928 | ws_handle_text_bin (client, server); 1929 | break; 1930 | case WS_OPCODE_PONG: 1931 | LOG (("PONG\n")); 1932 | ws_handle_pong (client); 1933 | break; 1934 | case WS_OPCODE_PING: 1935 | LOG (("PING\n")); 1936 | ws_handle_ping (client); 1937 | break; 1938 | default: 1939 | LOG (("CLOSE\n")); 1940 | ws_handle_close (client); 1941 | } 1942 | } 1943 | 1944 | /* Set the extended payload length into the given pointer. */ 1945 | static void 1946 | ws_set_extended_header_size (const char *buf, int *extended) { 1947 | uint64_t payloadlen = 0; 1948 | /* determine the payload length, else read more data */ 1949 | payloadlen = WS_FRM_PAYLOAD (*(buf + 1)); 1950 | switch (payloadlen) { 1951 | case WS_PAYLOAD_EXT16: 1952 | *extended = 2; 1953 | break; 1954 | case WS_PAYLOAD_EXT64: 1955 | *extended = 8; 1956 | break; 1957 | } 1958 | } 1959 | 1960 | /* Set the extended payload length into our frame structure. */ 1961 | static void 1962 | ws_set_payloadlen (WSFrame * frm, const char *buf) { 1963 | uint64_t payloadlen = 0, len64; 1964 | uint16_t len16; 1965 | 1966 | /* determine the payload length, else read more data */ 1967 | payloadlen = WS_FRM_PAYLOAD (*(buf + 1)); 1968 | switch (payloadlen) { 1969 | case WS_PAYLOAD_EXT16: 1970 | memcpy (&len16, (buf + 2), sizeof (uint16_t)); 1971 | frm->payloadlen = ntohs (len16); 1972 | break; 1973 | case WS_PAYLOAD_EXT64: 1974 | memcpy (&len64, (buf + 2), sizeof (uint64_t)); 1975 | frm->payloadlen = be64toh (len64); 1976 | break; 1977 | default: 1978 | frm->payloadlen = payloadlen; 1979 | } 1980 | } 1981 | 1982 | /* Set the masking key into our frame structure. */ 1983 | static void 1984 | ws_set_masking_key (WSFrame * frm, const char *buf) { 1985 | uint64_t payloadlen = 0; 1986 | 1987 | /* determine the payload length, else read more data */ 1988 | payloadlen = WS_FRM_PAYLOAD (*(buf + 1)); 1989 | switch (payloadlen) { 1990 | case WS_PAYLOAD_EXT16: 1991 | memcpy (&frm->mask, buf + 4, sizeof (frm->mask)); 1992 | break; 1993 | case WS_PAYLOAD_EXT64: 1994 | memcpy (&frm->mask, buf + 10, sizeof (frm->mask)); 1995 | break; 1996 | default: 1997 | memcpy (&frm->mask, buf + 2, sizeof (frm->mask)); 1998 | } 1999 | } 2000 | 2001 | /* Attempt to read the frame's header and set the relevant data into 2002 | * our frame structure. 2003 | * 2004 | * On error, or if no data available to read, the number of bytes is 2005 | * returned and the appropriate connection status is set. 2006 | * On success, the number of bytes is returned. */ 2007 | static int 2008 | ws_get_frm_header (WSClient * client) { 2009 | WSFrame **frm = NULL; 2010 | int bytes = 0, readh = 0, need = 0, offset = 0, extended = 0; 2011 | 2012 | if (client->frame == NULL) 2013 | client->frame = new_wsframe (); 2014 | 2015 | frm = &client->frame; 2016 | 2017 | /* Read the first 2 bytes for basic frame info */ 2018 | readh = (*frm)->buflen; /* read from header so far */ 2019 | need = 2 - readh; /* need to read */ 2020 | if (need > 0) { 2021 | if ((bytes = ws_read_header (client, (*frm), readh, need)) < 1) 2022 | return bytes; 2023 | if (bytes != need) 2024 | return ws_set_status (client, WS_READING, bytes); 2025 | } 2026 | offset += 2; 2027 | 2028 | if (ws_set_front_header_fields (client) != 0) 2029 | return bytes; 2030 | 2031 | ws_set_extended_header_size ((*frm)->buf, &extended); 2032 | /* read the extended header */ 2033 | readh = (*frm)->buflen; /* read from header so far */ 2034 | need = (extended + offset) - readh; /* read from header field so far */ 2035 | if (need > 0) { 2036 | if ((bytes = ws_read_header (client, (*frm), readh, need)) < 1) 2037 | return bytes; 2038 | if (bytes != need) 2039 | return ws_set_status (client, WS_READING, bytes); 2040 | } 2041 | offset += extended; 2042 | 2043 | /* read the masking key */ 2044 | readh = (*frm)->buflen; /* read from header so far */ 2045 | need = (4 + offset) - readh; 2046 | if (need > 0) { 2047 | if ((bytes = ws_read_header (client, (*frm), readh, need)) < 1) 2048 | return bytes; 2049 | if (bytes != need) 2050 | return ws_set_status (client, WS_READING, bytes); 2051 | } 2052 | offset += 4; 2053 | 2054 | ws_set_payloadlen ((*frm), (*frm)->buf); 2055 | ws_set_masking_key ((*frm), (*frm)->buf); 2056 | 2057 | if ((*frm)->payloadlen > wsconfig.max_frm_size) { 2058 | ws_error (client, WS_CLOSE_TOO_LARGE, "Frame is too big"); 2059 | return ws_set_status (client, WS_ERR | WS_CLOSE, bytes); 2060 | } 2061 | 2062 | (*frm)->buflen = 0; 2063 | (*frm)->reading = 0; 2064 | (*frm)->payload_offset = offset; 2065 | 2066 | return ws_set_status (client, WS_OK, bytes); 2067 | } 2068 | 2069 | /* Attempt to realloc the message payload. 2070 | * 2071 | * On error, 1 is returned. 2072 | * On success, 0 is returned. */ 2073 | static int 2074 | ws_realloc_frm_payload (WSFrame * frm, WSMessage * msg) { 2075 | char *tmp = NULL; 2076 | uint64_t newlen = 0; 2077 | 2078 | newlen = msg->payloadsz + frm->payloadlen; 2079 | tmp = realloc (msg->payload, newlen); 2080 | if (tmp == NULL && newlen > 0) { 2081 | free (msg->payload); 2082 | msg->payload = NULL; 2083 | return 1; 2084 | } 2085 | msg->payload = tmp; 2086 | 2087 | return 0; 2088 | } 2089 | 2090 | /* Attempt to read the frame's payload and set the relevant data into 2091 | * our message structure. 2092 | * 2093 | * On error, or if no data available to read, the number of bytes is 2094 | * returned and the appropriate connection status is set. 2095 | * On success, the number of bytes is returned. */ 2096 | static int 2097 | ws_get_frm_payload (WSClient * client, WSServer * server) { 2098 | WSFrame **frm = NULL; 2099 | WSMessage **msg = NULL; 2100 | int bytes = 0, readh = 0, need = 0; 2101 | 2102 | if (client->message == NULL) 2103 | client->message = new_wsmessage (); 2104 | 2105 | frm = &client->frame; 2106 | msg = &client->message; 2107 | 2108 | /* message within the same frame */ 2109 | if ((*msg)->payload == NULL && (*frm)->payloadlen) 2110 | (*msg)->payload = xcalloc ((*frm)->payloadlen, sizeof (char)); 2111 | /* handle a new frame */ 2112 | else if ((*msg)->buflen == 0 && (*frm)->payloadlen) { 2113 | if (ws_realloc_frm_payload ((*frm), (*msg)) == 1) 2114 | return ws_set_status (client, WS_ERR | WS_CLOSE, 0); 2115 | } 2116 | 2117 | readh = (*msg)->buflen; /* read from so far */ 2118 | need = (*frm)->payloadlen - readh; /* need to read */ 2119 | if (need > 0) { 2120 | if ((bytes = ws_read_payload (client, (*msg), (*msg)->payloadsz, need)) < 0) 2121 | return bytes; 2122 | if (bytes != need) 2123 | return ws_set_status (client, WS_READING, bytes); 2124 | } 2125 | 2126 | (*msg)->mask_offset = (*msg)->payloadsz - (*msg)->buflen; 2127 | 2128 | ws_manage_payload_opcode (client, server); 2129 | ws_free_frame (client); 2130 | 2131 | return bytes; 2132 | } 2133 | 2134 | /* Determine if we need to read a frame's header or its payload. 2135 | * 2136 | * On success, the number of bytes is returned. */ 2137 | static int 2138 | ws_get_message (WSClient * client, WSServer * server) { 2139 | int bytes = 0; 2140 | if ((client->frame == NULL) || (client->frame->reading)) 2141 | if ((bytes = ws_get_frm_header (client)) < 1 || client->frame->reading) 2142 | return bytes; 2143 | return ws_get_frm_payload (client, server); 2144 | } 2145 | 2146 | /* Determine if we need to read an HTTP request or a websocket frame. 2147 | * 2148 | * On success, the number of bytes is returned. */ 2149 | static int 2150 | read_client_data (WSClient * client, WSServer * server) { 2151 | int bytes = 0; 2152 | 2153 | /* Handshake */ 2154 | if ((!(client->headers) || (client->headers->reading))) 2155 | bytes = ws_get_handshake (client, server); 2156 | /* Message */ 2157 | else 2158 | bytes = ws_get_message (client, server); 2159 | 2160 | return bytes; 2161 | } 2162 | 2163 | /* Handle a tcp close connection. */ 2164 | static void 2165 | handle_tcp_close (int conn, WSClient * client, WSServer * server) { 2166 | LOG (("Closing TCP %d [%s]\n", client->listener, client->remote_ip)); 2167 | 2168 | #ifdef HAVE_LIBSSL 2169 | if (client->ssl) 2170 | shutdown_ssl (client); 2171 | #endif 2172 | 2173 | shutdown (conn, SHUT_RDWR); 2174 | /* upon close, call onclose() callback */ 2175 | if (server->onclose && wsconfig.strict && !wsconfig.echomode) 2176 | (*server->onclose) (server->pipeout, client); 2177 | 2178 | /* do access logging */ 2179 | gettimeofday (&client->end_proc, NULL); 2180 | if (wsconfig.accesslog) 2181 | access_log (client, 200); 2182 | 2183 | /* errored out while parsing a frame or a message */ 2184 | if (client->status & WS_ERR) { 2185 | ws_clear_queue (client); 2186 | ws_free_frame (client); 2187 | ws_free_message (client); 2188 | } 2189 | 2190 | server->closing = 0; 2191 | ws_close (conn); 2192 | 2193 | #ifdef HAVE_LIBSSL 2194 | if (client->ssl) 2195 | SSL_free (client->ssl); 2196 | client->ssl = NULL; 2197 | #endif 2198 | 2199 | /* remove client from our list */ 2200 | ws_remove_client_from_list (client, server); 2201 | LOG (("Connection Closed.\nActive: %d\n", list_count (server->colist))); 2202 | } 2203 | 2204 | /* Handle a tcp read close connection. */ 2205 | static void 2206 | handle_read_close (int *conn, WSClient * client, WSServer * server) { 2207 | /* We can still try to send a message to the client if not forcing close, (nice 2208 | * goodbye), else proceed to close it */ 2209 | if (!(client->status & WS_CLOSE) && client->status & WS_SENDING) { 2210 | server->closing = 1; 2211 | set_pollfd (client->listener, POLLOUT); 2212 | return; 2213 | } 2214 | handle_tcp_close (*conn, client, server); 2215 | } 2216 | 2217 | /* Handle a new socket connection. */ 2218 | static void 2219 | handle_accept (int listener, WSServer * server) { 2220 | WSClient *client = NULL; 2221 | int newfd; 2222 | 2223 | newfd = accept_client (listener, &server->colist); 2224 | if (newfd == -1) 2225 | return; 2226 | 2227 | if (!(client = ws_get_client_from_list (newfd, &server->colist))) 2228 | return; 2229 | 2230 | #ifdef HAVE_LIBSSL 2231 | /* set flag to do TLS handshake */ 2232 | if (wsconfig.use_ssl) 2233 | client->sslstatus |= WS_TLS_ACCEPTING; 2234 | #endif 2235 | 2236 | LOG (("Accepted: %d [%s]\n", newfd, client->remote_ip)); 2237 | } 2238 | 2239 | /* Handle a tcp read. */ 2240 | static void 2241 | handle_reads (int *conn, WSServer * server) { 2242 | WSClient *client = NULL; 2243 | 2244 | if (!(client = ws_get_client_from_list (*conn, &server->colist))) 2245 | return; 2246 | 2247 | LOG (("Handling read %d [%s]...\n", client->listener, client->remote_ip)); 2248 | 2249 | #ifdef HAVE_LIBSSL 2250 | if (handle_ssl_pending_rw (conn, server, client) == 0) 2251 | return; 2252 | #endif 2253 | 2254 | /* *INDENT-OFF* */ 2255 | client->start_proc = client->end_proc = (struct timeval) {0}; 2256 | /* *INDENT-ON* */ 2257 | gettimeofday (&client->start_proc, NULL); 2258 | read_client_data (client, server); 2259 | /* An error occurred while reading data or connection closed */ 2260 | if ((client->status & WS_CLOSE)) { 2261 | handle_read_close (conn, client, server); 2262 | *conn = -1; 2263 | } 2264 | } 2265 | 2266 | /* Handle a tcp write close connection. */ 2267 | static void 2268 | handle_write_close (int conn, WSClient * client, WSServer * server) { 2269 | handle_tcp_close (conn, client, server); 2270 | } 2271 | 2272 | /* Handle a tcp write. */ 2273 | static void 2274 | handle_writes (int *conn, WSServer * server) { 2275 | WSClient *client = NULL; 2276 | 2277 | if (!(client = ws_get_client_from_list (*conn, &server->colist))) 2278 | return; 2279 | 2280 | #ifdef HAVE_LIBSSL 2281 | if (handle_ssl_pending_rw (conn, server, client) == 0) 2282 | return; 2283 | #endif 2284 | 2285 | ws_respond (client, NULL, 0); /* buffered data */ 2286 | /* done sending data */ 2287 | if (client->sockqueue == NULL) { 2288 | client->status &= ~WS_SENDING; 2289 | set_pollfd (client->listener, server->closing ? 0 : POLLIN); 2290 | } 2291 | 2292 | /* An error occurred while sending data or while reading data but still 2293 | * waiting from the last send() from the server to the client. e.g., 2294 | * sending status code */ 2295 | if ((client->status & WS_CLOSE) && !(client->status & WS_SENDING)) 2296 | handle_write_close (*conn, client, server); 2297 | } 2298 | 2299 | /* Create named pipe (FIFO) with the given pipe name. 2300 | * 2301 | * On error, 1 is returned. 2302 | * On success, 0 is returned. */ 2303 | int 2304 | ws_setfifo (const char *pipename) { 2305 | struct stat fistat; 2306 | const char *f = pipename; 2307 | 2308 | if (access (f, F_OK) == 0) 2309 | return 0; 2310 | 2311 | if (mkfifo (f, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) < 0) 2312 | FATAL ("Unable to set fifo: %s.", strerror (errno)); 2313 | if (stat (f, &fistat) < 0) 2314 | FATAL ("Unable to stat fifo: %s.", strerror (errno)); 2315 | if (!S_ISFIFO (fistat.st_mode)) 2316 | FATAL ("pipe is not a fifo: %s.", strerror (errno)); 2317 | 2318 | return 0; 2319 | } 2320 | 2321 | /* Open a named pipe (FIFO) for input to the server (reader). */ 2322 | static void 2323 | ws_openfifo_in (WSPipeIn * pipein) { 2324 | ws_setfifo (wsconfig.pipein); 2325 | /* we should be able to open it at as reader */ 2326 | if ((pipein->fd = open (wsconfig.pipein, O_RDWR | O_NONBLOCK)) < 0) 2327 | FATAL ("Unable to open fifo in: %s.", strerror (errno)); 2328 | } 2329 | 2330 | 2331 | /* Open a named pipe (FIFO) for output from the server (writer). */ 2332 | static int 2333 | ws_openfifo_out (WSPipeOut * pipeout) { 2334 | int status = 0; 2335 | 2336 | ws_setfifo (wsconfig.pipeout); 2337 | status = open (wsconfig.pipeout, O_WRONLY | O_NONBLOCK); 2338 | /* will attempt on the next write */ 2339 | if (status == -1 && errno == ENXIO) 2340 | LOG (("Unable to open fifo out: %s.\n", strerror (errno))); 2341 | else if (status < 0) 2342 | FATAL ("Unable to open fifo out: %s.", strerror (errno)); 2343 | pipeout->fd = status; 2344 | 2345 | return status; 2346 | } 2347 | 2348 | /* Set a new named pipe for incoming messages and one for outgoing 2349 | * messages from the client. */ 2350 | static void 2351 | ws_fifo (WSServer * server) { 2352 | if (wsconfig.use_stdin) 2353 | server->pipein->fd = STDIN_FILENO; 2354 | else { 2355 | wsconfig.pipein = wsconfig.pipein ? wsconfig.pipein : WS_PIPEIN; 2356 | ws_openfifo_in (server->pipein); 2357 | } 2358 | set_pollfd (server->pipein->fd, POLLIN); 2359 | 2360 | if (wsconfig.use_stdout) 2361 | server->pipeout->fd = STDOUT_FILENO; 2362 | else { 2363 | wsconfig.pipeout = wsconfig.pipeout ? wsconfig.pipeout : WS_PIPEOUT; 2364 | ws_openfifo_out (server->pipeout); 2365 | } 2366 | set_pollfd (server->pipeout->fd, POLLOUT); 2367 | } 2368 | 2369 | /* Clear the queue for an outgoing named pipe. */ 2370 | static void 2371 | clear_fifo_queue (WSPipeOut * pipeout) { 2372 | WSQueue **queue = &pipeout->fifoqueue; 2373 | if (!(*queue)) 2374 | return; 2375 | 2376 | if ((*queue)->queued) 2377 | free ((*queue)->queued); 2378 | (*queue)->queued = NULL; 2379 | (*queue)->qlen = 0; 2380 | 2381 | free ((*queue)); 2382 | (*queue) = NULL; 2383 | } 2384 | 2385 | /* Attempt to realloc the current sent queue for an outgoing named pip 2386 | * (FIFO). 2387 | * 2388 | * On error, 1 is returned and the connection status is closed and 2389 | * reopened. 2390 | * On success, 0 is returned. */ 2391 | static int 2392 | ws_realloc_fifobuf (WSPipeOut * pipeout, const char *buf, int len) { 2393 | WSQueue *queue = pipeout->fifoqueue; 2394 | char *tmp = NULL; 2395 | int newlen = 0; 2396 | 2397 | newlen = queue->qlen + len; 2398 | tmp = realloc (queue->queued, newlen); 2399 | if (tmp == NULL && newlen > 0) { 2400 | ws_close (pipeout->fd); 2401 | clear_fifo_queue (pipeout); 2402 | ws_openfifo_out (pipeout); 2403 | return 1; 2404 | } 2405 | 2406 | queue->queued = tmp; 2407 | memcpy (queue->queued + queue->qlen, buf, len); 2408 | queue->qlen += len; 2409 | 2410 | return 0; 2411 | } 2412 | 2413 | /* Set into a queue the data that couldn't be sent in the outgoing 2414 | * FIFO. */ 2415 | static void 2416 | ws_queue_fifobuf (WSPipeOut * pipeout, const char *buffer, int len, int bytes) { 2417 | WSQueue **queue = &pipeout->fifoqueue; 2418 | 2419 | if (bytes < 1) 2420 | bytes = 0; 2421 | 2422 | (*queue) = xcalloc (1, sizeof (WSQueue)); 2423 | (*queue)->queued = xcalloc (len - bytes, sizeof (char)); 2424 | memcpy ((*queue)->queued, buffer + bytes, len - bytes); 2425 | (*queue)->qlen = len - bytes; 2426 | 2427 | pipeout->status |= WS_SENDING; 2428 | set_pollfd (pipeout->fd, POLLOUT); 2429 | } 2430 | 2431 | /* Attempt to send the given buffer to the given outgoing FIFO. 2432 | * 2433 | * On error, the data is queued up. 2434 | * On success, the number of bytes sent is returned. */ 2435 | static int 2436 | ws_write_fifo_data (WSPipeOut * pipeout, char *buffer, int len) { 2437 | int bytes = 0; 2438 | 2439 | bytes = write (pipeout->fd, buffer, len); 2440 | 2441 | /* At this point, the reader probably closed the pipe, so a cheap *hack* for 2442 | * this is to close the pipe on our end and attempt to reopen it. If unable to 2443 | * do so, then let it be -1 and try on the next attempt to write. */ 2444 | if (bytes == -1 && errno == EPIPE) { 2445 | ws_close (pipeout->fd); 2446 | ws_openfifo_out (pipeout); 2447 | return bytes; 2448 | } 2449 | if (bytes < len || (bytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK))) 2450 | ws_queue_fifobuf (pipeout, buffer, len, bytes); 2451 | 2452 | return bytes; 2453 | } 2454 | 2455 | /* Attempt to send the queued up client's data through the outgoing 2456 | * named pipe (FIFO) . 2457 | * 2458 | * On error, 1 is returned and the connection status is set. 2459 | * On success, the number of bytes sent is returned. */ 2460 | static int 2461 | ws_write_fifo_cache (WSPipeOut * pipeout) { 2462 | WSQueue *queue = pipeout->fifoqueue; 2463 | int bytes = 0; 2464 | 2465 | bytes = write (pipeout->fd, queue->queued, queue->qlen); 2466 | 2467 | /* At this point, the reader probably closed the pipe, so a cheap *hack* for 2468 | * this is to close the pipe on our end and attempt to reopen it. If unable to 2469 | * do so, then let it be -1 and try on the next attempt to write. */ 2470 | if (bytes == -1 && errno == EPIPE) { 2471 | ws_close (pipeout->fd); 2472 | ws_openfifo_out (pipeout); 2473 | return bytes; 2474 | } 2475 | 2476 | if (chop_nchars (queue->queued, bytes, queue->qlen) == 0) 2477 | clear_fifo_queue (pipeout); 2478 | else 2479 | queue->qlen -= bytes; 2480 | 2481 | return bytes; 2482 | } 2483 | 2484 | /* An entry point to attempt to send the client's data into an 2485 | * outgoing named pipe (FIFO). 2486 | * 2487 | * On success, the number of bytes sent is returned. */ 2488 | int 2489 | ws_write_fifo (WSPipeOut * pipeout, char *buffer, int len) { 2490 | int bytes = 0; 2491 | 2492 | if (pipeout->fd == -1 && ws_openfifo_out (pipeout) == -1) 2493 | return bytes; 2494 | 2495 | /* attempt to send the whole buffer buffer */ 2496 | if (pipeout->fifoqueue == NULL) 2497 | bytes = ws_write_fifo_data (pipeout, buffer, len); 2498 | /* buffer not empty, just append new data */ 2499 | else if (pipeout->fifoqueue != NULL && buffer != NULL) { 2500 | if (ws_realloc_fifobuf (pipeout, buffer, len) == 1) 2501 | return bytes; 2502 | } 2503 | /* send from cache buffer */ 2504 | else { 2505 | bytes = ws_write_fifo_cache (pipeout); 2506 | } 2507 | 2508 | if (pipeout->fifoqueue == NULL) { 2509 | pipeout->status &= ~WS_SENDING; 2510 | set_pollfd (pipeout->fd, 0); 2511 | } 2512 | 2513 | return bytes; 2514 | } 2515 | 2516 | /* Clear an incoming FIFO packet and header data. */ 2517 | static void 2518 | clear_fifo_packet (WSPipeIn * pipein) { 2519 | memset (pipein->hdr, 0, sizeof (pipein->hdr)); 2520 | pipein->hlen = 0; 2521 | 2522 | if (pipein->packet == NULL) 2523 | return; 2524 | 2525 | if (pipein->packet->data) 2526 | free (pipein->packet->data); 2527 | free (pipein->packet); 2528 | pipein->packet = NULL; 2529 | } 2530 | 2531 | /* Broadcast to all connected clients the given message. */ 2532 | static int 2533 | ws_broadcast_fifo (WSClient * client, WSServer * server) { 2534 | WSPacket *packet = server->pipein->packet; 2535 | 2536 | LOG (("Broadcasting to %d [%s] ", client->listener, client->remote_ip)); 2537 | if (client == NULL) 2538 | return 1; 2539 | 2540 | if (client->headers == NULL || client->headers->ws_accept == NULL) { 2541 | /* no handshake for this client */ 2542 | LOG (("No headers. Closing %d [%s]\n", client->listener, client->remote_ip)); 2543 | return -1; 2544 | } 2545 | 2546 | LOG ((" - Sending...\n")); 2547 | ws_send_data (client, packet->type, packet->data, packet->size); 2548 | 2549 | return 0; 2550 | } 2551 | 2552 | static void 2553 | ws_broadcast_fifo_to_clients (WSServer * server) { 2554 | WSClient *client = NULL; 2555 | void *data = NULL; 2556 | uint32_t *close_list = NULL; 2557 | int n = 0, idx = 0, i = 0, listener = 0; 2558 | 2559 | if ((n = list_count (server->colist)) == 0) 2560 | return; 2561 | 2562 | close_list = xcalloc (n, sizeof (uint32_t)); 2563 | /* *INDENT-OFF* */ 2564 | GSLIST_FOREACH (server->colist, data, { 2565 | client = data; 2566 | if (ws_broadcast_fifo(client, server) == -1) 2567 | close_list[idx++] = client->listener; 2568 | }); 2569 | /* *INDENT-ON* */ 2570 | 2571 | client = NULL; 2572 | for (i = 0; i < idx; ++i) { 2573 | listener = close_list[i]; 2574 | if ((client = ws_get_client_from_list (listener, &server->colist))) 2575 | handle_tcp_close (listener, client, server); 2576 | } 2577 | 2578 | free (close_list); 2579 | } 2580 | 2581 | /* Send a message from the incoming named pipe to specific client 2582 | * given the socket id. */ 2583 | static void 2584 | ws_send_strict_fifo_to_client (WSServer * server, int listener, WSPacket * pa) { 2585 | WSClient *client = NULL; 2586 | 2587 | if (!(client = ws_get_client_from_list (listener, &server->colist))) 2588 | return; 2589 | /* no handshake for this client */ 2590 | if (client->headers == NULL || client->headers->ws_accept == NULL) { 2591 | LOG (("No headers. Closing %d [%s]\n", client->listener, client->remote_ip)); 2592 | 2593 | handle_tcp_close (client->listener, client, server); 2594 | return; 2595 | } 2596 | ws_send_data (client, pa->type, pa->data, pa->len); 2597 | } 2598 | 2599 | /* Attempt to read message from a named pipe (FIFO). 2600 | * 2601 | * On error, -1 is returned. 2602 | * On success, the number of bytes read is returned. */ 2603 | int 2604 | ws_read_fifo (int fd, char *buf, int *buflen, int pos, int need) { 2605 | int bytes = 0; 2606 | 2607 | bytes = read (fd, buf + pos, need); 2608 | if (bytes == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) 2609 | return bytes; 2610 | else if (bytes == -1) 2611 | return bytes; 2612 | *buflen += bytes; 2613 | 2614 | return bytes; 2615 | } 2616 | 2617 | /* Pack the given value into a network byte order. 2618 | * 2619 | * On success, the number size of uint32_t is returned. */ 2620 | size_t 2621 | pack_uint32 (void *buf, uint32_t val) { 2622 | uint32_t v32 = htonl (val); 2623 | memcpy (buf, &v32, sizeof (uint32_t)); 2624 | 2625 | return sizeof (uint32_t); 2626 | } 2627 | 2628 | /* Unpack the given value into a host byte order. 2629 | * 2630 | * On success, the number size of uint32_t is returned. */ 2631 | size_t 2632 | unpack_uint32 (const void *buf, uint32_t * val) { 2633 | uint32_t v32 = 0; 2634 | memcpy (&v32, buf, sizeof (uint32_t)); 2635 | *val = ntohl (v32); 2636 | 2637 | return sizeof (uint32_t); 2638 | } 2639 | 2640 | /* Ensure the fields coming from the named pipe are valid. 2641 | * 2642 | * On error, 1 is returned. 2643 | * On success, 0 is returned. */ 2644 | static int 2645 | validate_fifo_packet (uint32_t type, int size) { 2646 | if (type != WS_OPCODE_TEXT && type != WS_OPCODE_BIN) { 2647 | LOG (("Invalid fifo packet type\n")); 2648 | return 1; 2649 | } 2650 | 2651 | if (size > wsconfig.max_frm_size) { 2652 | LOG (("Invalid fifo packet size\n")); 2653 | return 1; 2654 | } 2655 | 2656 | return 0; 2657 | } 2658 | 2659 | /* Handle reading and sending the incoming data from the named pipe on 2660 | * strict mode. */ 2661 | static void 2662 | handle_strict_fifo (WSServer * server) { 2663 | WSPipeIn *pi = server->pipein; 2664 | WSPacket **pa = &pi->packet; 2665 | int bytes = 0, readh = 0, need = 0; 2666 | 2667 | char *ptr = NULL; 2668 | uint32_t listener = 0, type = 0, size = 0; 2669 | 2670 | readh = pi->hlen; /* read from header so far */ 2671 | need = HDR_SIZE - readh; /* need to read */ 2672 | if (need > 0) { 2673 | if ((bytes = ws_read_fifo (pi->fd, pi->hdr, &pi->hlen, readh, need)) < 0) 2674 | return; 2675 | if (bytes != need) 2676 | return; 2677 | } 2678 | 2679 | /* unpack size, and type */ 2680 | ptr = pi->hdr; 2681 | ptr += unpack_uint32 (ptr, &listener); 2682 | ptr += unpack_uint32 (ptr, &type); 2683 | ptr += unpack_uint32 (ptr, &size); 2684 | 2685 | if (validate_fifo_packet (type, size) == 1) { 2686 | ws_close (pi->fd); 2687 | clear_fifo_packet (pi); 2688 | ws_openfifo_in (pi); 2689 | return; 2690 | } 2691 | 2692 | if ((*pa) == NULL) { 2693 | (*pa) = xcalloc (1, sizeof (WSPacket)); 2694 | (*pa)->type = type; 2695 | (*pa)->size = size; 2696 | (*pa)->data = xcalloc (size, sizeof (char)); 2697 | } 2698 | 2699 | readh = (*pa)->len; /* read from payload so far */ 2700 | need = (*pa)->size - readh; /* need to read */ 2701 | if (need > 0) { 2702 | if ((bytes = ws_read_fifo (pi->fd, (*pa)->data, &(*pa)->len, readh, need)) < 0) 2703 | return; 2704 | if (bytes != need) 2705 | return; 2706 | } 2707 | 2708 | /* no clients to send data to */ 2709 | if (list_count (server->colist) == 0) { 2710 | clear_fifo_packet (pi); 2711 | return; 2712 | } 2713 | 2714 | /* Either send it to a specific client or broadcast message to all 2715 | * clients */ 2716 | if (listener != 0) 2717 | ws_send_strict_fifo_to_client (server, listener, *pa); 2718 | else 2719 | ws_broadcast_fifo_to_clients (server); 2720 | clear_fifo_packet (pi); 2721 | } 2722 | 2723 | /* Handle reading and sending the incoming data from the named pipe on 2724 | * a fixed buffer mode. */ 2725 | static void 2726 | handle_fixed_fifo (WSServer * server) { 2727 | WSPipeIn *pi = server->pipein; 2728 | WSPacket **pa = &pi->packet; 2729 | 2730 | int bytes = 0; 2731 | char buf[PIPE_BUF] = { 0 }; 2732 | 2733 | if ((bytes = read (pi->fd, buf, PIPE_BUF - 1)) < 0) 2734 | return; 2735 | 2736 | buf[bytes] = '\0'; /* null-terminate */ 2737 | if (ws_validate_string (buf, bytes) != 0) 2738 | return; 2739 | 2740 | (*pa) = xcalloc (1, sizeof (WSPacket)); 2741 | (*pa)->type = WS_OPCODE_TEXT; 2742 | (*pa)->size = bytes; 2743 | (*pa)->data = xstrdup (buf); 2744 | 2745 | /* no clients to send data to */ 2746 | if (list_count (server->colist) == 0) { 2747 | clear_fifo_packet (pi); 2748 | return; 2749 | } 2750 | 2751 | /* broadcast message to all clients */ 2752 | ws_broadcast_fifo_to_clients (server); 2753 | clear_fifo_packet (pi); 2754 | } 2755 | 2756 | /* Determine which mode should use the incoming message from the FIFO. */ 2757 | static void 2758 | handle_fifo (WSServer * server) { 2759 | if (wsconfig.strict) 2760 | handle_strict_fifo (server); 2761 | else 2762 | handle_fixed_fifo (server); 2763 | } 2764 | 2765 | /* Creates an endpoint for communication and start listening for 2766 | * connections on a socket */ 2767 | static void 2768 | ws_socket (int *listener) { 2769 | if (wsconfig.unix_socket) { 2770 | struct sockaddr_un servaddr; 2771 | 2772 | /* Create a TCP socket. */ 2773 | if ((*listener = socket (AF_UNIX, SOCK_STREAM, 0)) == -1) 2774 | FATAL ("Unable to open socket: %s.", strerror (errno)); 2775 | 2776 | memset (&servaddr, 0, sizeof (servaddr)); 2777 | servaddr.sun_family = AF_UNIX; 2778 | strncpy (servaddr.sun_path, wsconfig.unix_socket, sizeof (servaddr.sun_path) - 1); 2779 | 2780 | /* Bind the socket to the address. */ 2781 | if (bind (*listener, (struct sockaddr *) &servaddr, sizeof (servaddr)) != 0) 2782 | FATAL ("Unable to set bind: %s.", strerror (errno)); 2783 | } else { 2784 | int ov = 1; 2785 | struct addrinfo hints, *ai; 2786 | 2787 | /* get a socket and bind it */ 2788 | memset (&hints, 0, sizeof hints); 2789 | hints.ai_family = AF_UNSPEC; 2790 | hints.ai_socktype = SOCK_STREAM; 2791 | /*hints.ai_flags = AI_PASSIVE; */ 2792 | if (getaddrinfo (wsconfig.host, wsconfig.port, &hints, &ai) != 0) 2793 | FATAL ("Unable to set server: %s.", gai_strerror (errno)); 2794 | 2795 | /* Create a TCP socket. */ 2796 | if ((*listener = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1) 2797 | FATAL ("Unable to open socket: %s.", strerror (errno)); 2798 | 2799 | /* Options */ 2800 | if (setsockopt (*listener, SOL_SOCKET, SO_REUSEADDR, &ov, sizeof (ov)) == -1) 2801 | FATAL ("Unable to set setsockopt: %s.", strerror (errno)); 2802 | 2803 | /* Bind the socket to the address. */ 2804 | if (bind (*listener, ai->ai_addr, ai->ai_addrlen) != 0) 2805 | FATAL ("Unable to set bind: %s.", strerror (errno)); 2806 | freeaddrinfo (ai); 2807 | } 2808 | 2809 | /* Tell the socket to accept connections. */ 2810 | if (listen (*listener, SOMAXCONN) == -1) 2811 | FATAL ("Unable to listen: %s.", strerror (errno)); 2812 | } 2813 | 2814 | /* Start the websocket server and start to monitor multiple file 2815 | * descriptors until we have something to read or write. */ 2816 | void 2817 | ws_start (WSServer * server) { 2818 | int listener = -1, ret = 0; 2819 | struct pollfd *cfdstate = NULL, *pfd, *efd; 2820 | nfds_t ncfdstate = 0; 2821 | bool run = true; 2822 | 2823 | if (server->self_pipe[0] != -1) 2824 | set_pollfd (server->self_pipe[0], POLLIN); 2825 | 2826 | #ifdef HAVE_LIBSSL 2827 | if (wsconfig.sslcert && wsconfig.sslkey) { 2828 | LOG (("==Using TLS/SSL==\n")); 2829 | wsconfig.use_ssl = 1; 2830 | if (initialize_ssl_ctx (server)) { 2831 | LOG (("Unable to initialize_ssl_ctx\n")); 2832 | return; 2833 | } 2834 | } 2835 | #endif 2836 | 2837 | ws_socket (&listener); 2838 | set_pollfd (listener, POLLIN); 2839 | 2840 | while (run) { 2841 | /* take a copy of the fdstate and give that to poll to allow 2842 | * any dispatch to modify the real fdstate for the next pass */ 2843 | if (ncfdstate != nfdstate) { 2844 | free (cfdstate); 2845 | cfdstate = xmalloc (nfdstate * sizeof (*cfdstate)); 2846 | memset (cfdstate, 0, sizeof (*cfdstate) * nfdstate); 2847 | ncfdstate = nfdstate; 2848 | } 2849 | memcpy (cfdstate, fdstate, ncfdstate * sizeof (*cfdstate)); 2850 | 2851 | /* yep, wait patiently */ 2852 | if ((ret = poll (cfdstate, nfdstate, -1)) == -1) { 2853 | switch (errno) { 2854 | case EINTR: 2855 | LOG (("A signal was caught on select(2)\n")); 2856 | break; 2857 | default: 2858 | FATAL ("Unable to poll: %s.", strerror (errno)); 2859 | } 2860 | } 2861 | 2862 | /* iterate over existing connections */ 2863 | efd = cfdstate + nfdstate; 2864 | for (pfd = cfdstate; pfd < efd; pfd++) { 2865 | if (pfd->revents & POLLHUP) 2866 | LOG (("Got POLLHUP %d\n", pfd->fd)); 2867 | if (pfd->revents & POLLNVAL) 2868 | LOG (("Got POLLNVAL %d\n", pfd->fd)); 2869 | if (pfd->revents & POLLERR) { 2870 | LOG (("Got POLLERR %d\n", pfd->fd)); 2871 | if (pfd->fd == server->pipeout->fd) { 2872 | LOG (("Reopen pipeout\n")); 2873 | ws_close (pfd->fd); 2874 | ws_openfifo_out (server->pipeout); 2875 | } 2876 | } 2877 | 2878 | /* handle self-pipe trick */ 2879 | if (pfd->fd == server->self_pipe[0]) { 2880 | if (pfd->revents & POLLIN) { 2881 | LOG (("Handled self-pipe to close event loop.\n")); 2882 | run = false; 2883 | break; 2884 | } 2885 | } else if (pfd->fd == server->pipein->fd) { 2886 | /* handle pipein */ 2887 | if (pfd->revents & POLLIN) 2888 | handle_fifo (server); 2889 | } else if (pfd->fd == server->pipeout->fd) { 2890 | /* handle pipeout */ 2891 | if (pfd->revents & POLLOUT) 2892 | ws_write_fifo (server->pipeout, NULL, 0); 2893 | } else if (pfd->fd == listener) { 2894 | /* handle new connections */ 2895 | if (pfd->revents & POLLIN) 2896 | handle_accept (listener, server); 2897 | } else { 2898 | /* handle data from a client */ 2899 | if (pfd->revents & POLLIN) { 2900 | if (server->closing) { 2901 | struct pollfd *ffd = get_pollfd (pfd->fd); 2902 | if (ffd != NULL) 2903 | ffd->events &= ~POLLIN; 2904 | } else 2905 | handle_reads (&pfd->fd, server); 2906 | } 2907 | /* handle sending data to a client */ 2908 | if (pfd->revents & POLLOUT) 2909 | handle_writes (&pfd->fd, server); 2910 | } 2911 | } 2912 | } 2913 | 2914 | free (cfdstate); 2915 | ws_close (listener); 2916 | if (server->self_pipe[0] != -1) 2917 | unset_pollfd (server->self_pipe[0]); 2918 | 2919 | if (wsconfig.unix_socket) { 2920 | unlink (wsconfig.unix_socket); 2921 | } 2922 | } 2923 | 2924 | /* Set the origin so the server can force connections to have the 2925 | * given HTTP origin. */ 2926 | void 2927 | ws_set_config_origin (const char *origin) { 2928 | wsconfig.origin = origin; 2929 | } 2930 | 2931 | /* Set the the maximum websocket frame size. */ 2932 | void 2933 | ws_set_config_frame_size (int max_frm_size) { 2934 | wsconfig.max_frm_size = max_frm_size; 2935 | } 2936 | 2937 | /* Set specific name for the reader named pipe. */ 2938 | void 2939 | ws_set_config_pipein (const char *pipein) { 2940 | wsconfig.pipein = pipein; 2941 | } 2942 | 2943 | /* Set specific name for the writer named pipe. */ 2944 | void 2945 | ws_set_config_pipeout (const char *pipeout) { 2946 | wsconfig.pipeout = pipeout; 2947 | } 2948 | 2949 | /* Set a path and a file for the access log. */ 2950 | void 2951 | ws_set_config_accesslog (const char *accesslog) { 2952 | wsconfig.accesslog = accesslog; 2953 | 2954 | if (access_log_open (wsconfig.accesslog) == 1) 2955 | FATAL ("Unable to open access log: %s.", strerror (errno)); 2956 | } 2957 | 2958 | /* Set if the server should handle strict named pipe handling. */ 2959 | void 2960 | ws_set_config_strict (int strict) { 2961 | wsconfig.strict = strict; 2962 | } 2963 | 2964 | /* Set the server into echo mode. */ 2965 | void 2966 | ws_set_config_echomode (int echomode) { 2967 | wsconfig.echomode = echomode; 2968 | } 2969 | 2970 | /* Set the server host bind address. */ 2971 | void 2972 | ws_set_config_host (const char *host) { 2973 | wsconfig.host = host; 2974 | } 2975 | 2976 | /* Set the server unix socket bind address. */ 2977 | void 2978 | ws_set_config_unix_socket (const char *unix_socket) { 2979 | wsconfig.unix_socket = unix_socket; 2980 | } 2981 | 2982 | /* Set the server port bind address. */ 2983 | void 2984 | ws_set_config_port (const char *port) { 2985 | wsconfig.port = port; 2986 | } 2987 | 2988 | /* Set specific name for the SSL certificate. */ 2989 | void 2990 | ws_set_config_sslcert (const char *sslcert) { 2991 | wsconfig.sslcert = sslcert; 2992 | } 2993 | 2994 | /* Set specific name for the SSL key. */ 2995 | void 2996 | ws_set_config_sslkey (const char *sslkey) { 2997 | wsconfig.sslkey = sslkey; 2998 | } 2999 | 3000 | /* Use stdin instead of a pipe */ 3001 | void 3002 | ws_set_config_stdin (int use_stdin) { 3003 | wsconfig.use_stdin = use_stdin; 3004 | } 3005 | 3006 | 3007 | /* Use stdout instead of a pipe */ 3008 | void 3009 | ws_set_config_stdout (int use_stdout) { 3010 | wsconfig.use_stdout = use_stdout; 3011 | } 3012 | 3013 | /* Create a new websocket server context. */ 3014 | WSServer * 3015 | ws_init (const char *host, const char *port, void (*initopts) (void)) { 3016 | WSServer *server = new_wsserver (); 3017 | server->pipein = new_wspipein (); 3018 | server->pipeout = new_wspipeout (); 3019 | server->self_pipe[0] = server->self_pipe[1] = -1; 3020 | 3021 | wsconfig.accesslog = NULL; 3022 | wsconfig.host = host; 3023 | wsconfig.unix_socket = NULL; 3024 | wsconfig.max_frm_size = WS_MAX_FRM_SZ; 3025 | wsconfig.origin = NULL; 3026 | wsconfig.pipein = NULL; 3027 | wsconfig.pipeout = NULL; 3028 | wsconfig.sslcert = NULL; 3029 | wsconfig.sslkey = NULL; 3030 | wsconfig.port = port; 3031 | wsconfig.strict = 0; 3032 | wsconfig.use_ssl = 0; 3033 | 3034 | initopts (); 3035 | ws_fifo (server); 3036 | 3037 | return server; 3038 | } 3039 | --------------------------------------------------------------------------------