diff --git a/c/Makefile b/c/Makefile index 8b7f49a..407f082 100644 --- a/c/Makefile +++ b/c/Makefile @@ -1,8 +1,11 @@ -TARGETS=websockify +TARGETS=websockify echoserver CFLAGS += -fPIC all: $(TARGETS) +echoserver: echo.c + $(CC) $(LDFLAGS) $^ -lssl -lcrypto -o $@ + websockify: websockify.o websocket.o $(CC) $(LDFLAGS) $^ -lssl -lcrypto -o $@ @@ -10,5 +13,5 @@ websocket.o: websocket.c websocket.h websockify.o: websockify.c websocket.h clean: - rm -f websockify *.o + rm -f websockify *.o echoserver diff --git a/c/README.txt b/c/README.txt new file mode 100644 index 0000000..dbfcb26 --- /dev/null +++ b/c/README.txt @@ -0,0 +1,28 @@ + +This is the proof of concept websocket server. It runs +on the unmodified live server and bridges websockets. + +the basic configuration is to run + + websockify hostname:listensocket hostname:targetsocket + +listensocket is a random available socket (open it in the firewall) +targetsocket is the customary server port for the test server. + +for testing purposes, you can use echoserver as the target, or netcat + +in principle, whatever service you normally provide on targetsocket +can run unchanged. + + +echo.c is a simple echo server cribbed from a random internet page. + + +so summary: the server runs unmodified + websockify runs as a proxy between + +webchat.html is a simple test that connects to websockify and pings +a few lines, expecting them to be echoed. to run this test, set up +websockify to listen on port 12346 and connect to echoserver on any +port you wish. + diff --git a/c/echo.c b/c/echo.c new file mode 100644 index 0000000..27541d1 --- /dev/null +++ b/c/echo.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 1024 +#define on_error(...) { fprintf(stderr, __VA_ARGS__); fflush(stderr); exit(1); } + +int main (int argc, char *argv[]) { + if (argc < 2) on_error("Usage: %s [port]\n", argv[0]); + + int port = atoi(argv[1]); + + int server_fd, client_fd, err; + struct sockaddr_in server, client; + char buf[BUFFER_SIZE]; + + server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) on_error("Could not create socket\n"); + + server.sin_family = AF_INET; + server.sin_port = htons(port); + server.sin_addr.s_addr = htonl(INADDR_ANY); + + int opt_val = 1; + setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof opt_val); + + err = bind(server_fd, (struct sockaddr *) &server, sizeof(server)); + if (err < 0) on_error("Could not bind socket\n"); + + err = listen(server_fd, 128); + if (err < 0) on_error("Could not listen on socket\n"); + + printf("Server is listening on %d\n", port); + + while (1) { + socklen_t client_len = sizeof(client); + client_fd = accept(server_fd, (struct sockaddr *) &client, &client_len); + + if (client_fd < 0) on_error("Could not establish new connection\n"); + printf("listening\n"); + while (1) { + int read = recv(client_fd, buf, BUFFER_SIZE, 0); + + if (!read) + { printf("done reading\n"); break; // done reading + } + if (read < 0) on_error("Client read failed\n"); + buf[read] = (char)0; + printf("in: %s",buf); + + err = send(client_fd, buf, read, 0); + if (err < 0) on_error("Client write failed\n"); + } + } + + return 0; +} diff --git a/c/startserver b/c/startserver new file mode 100644 index 0000000..48c4c04 --- /dev/null +++ b/c/startserver @@ -0,0 +1,5 @@ +#!/bin/sh +# +# this script has to be executed as root to get access to the key files +# +./websockify -k /etc/letsencrypt/live/boardspace.net/privkey.pem -c /etc/letsencrypt/live/boardspace.net/cert.pem boardspace.net:12345 boardspace.net:2255 diff --git a/c/webchat.html b/c/webchat.html new file mode 100644 index 0000000..03e6e48 --- /dev/null +++ b/c/webchat.html @@ -0,0 +1,79 @@ + + + WebSocket client test + + + +

WebSocket Client Test

+
+ + + + \ No newline at end of file diff --git a/c/websocket.c b/c/websocket.c index c282986..4c907de 100644 --- a/c/websocket.c +++ b/c/websocket.c @@ -6,6 +6,7 @@ * You can make a cert/key with openssl using: * openssl req -new -x509 -days 365 -nodes -out self.pem -keyout self.pem * as taken from http://docs.python.org/dev/library/ssl.html#certificates + * */ #include #include @@ -27,6 +28,53 @@ #include /* sha1 hash */ #include "websocket.h" +// +// this was TLS_server_method in the original, and that is recommended, except +// it doesn't seem to exist in older versions of SSL +// including this allows the project to link, but still more mystery +// to be solved involing getting a signed certificate and self.pem +#define TLS_SERVER_METHOD TLSv1_2_server_method + + +// the other mystery is the "self.pem" needed to provide ssl keys. Back in +// the day it was common to have openssl generate a self-signed certificate, +// but that is no longer acceptable. +// one apparently correct answer is to catenate the site private key and public key +// as self.pem, but that has the undesirable effect of making a copy of rthe private +// key. A better solution is to use -k -c where the +// beracketed items are the paths to the actual keys for the server, which are +// typically found in /etc/letsencrypt/live +// + +/* +https://www.openmymind.net/WebSocket-Framing-Masking-Fragmentation-and-More/ + +May 29, 2022 + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-------+-+-------------+-------------------------------+ +|F|R|R|R| opcode|M| Payload len | Extended payload length | +|I|S|S|S| (4) |A| (7) | (16/64) | +|N|V|V|V| |S| | (if payload len==126/127) | +| |1|2|3| |K| | | ++-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + +| Extended payload length continued, if payload len == 127 | ++ - - - - - - - - - - - - - - - +-------------------------------+ +| |Masking-key, if MASK set to 1 | ++-------------------------------+-------------------------------+ +| Masking-key (continued) | Payload Data | ++-------------------------------- - - - - - - - - - - - - - - - + +: Payload Data continued ... : ++ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +| Payload Data continued ... | ++---------------------------------------------------------------+ + + + + */ + + /* * Global state * @@ -38,7 +86,7 @@ settings_t settings; void traffic(const char * token) { - if ((settings.verbose) && (! settings.daemon)) { + if ((settings.veryverbose) && (! settings.daemon)) { fprintf(stdout, "%s", token); fflush(stdout); } @@ -157,7 +205,7 @@ ws_ctx_t *ws_socket_ssl(ws_ctx_t *ctx, int socket, char * certfile, char * keyfi } - ctx->ssl_ctx = SSL_CTX_new(TLS_server_method()); + ctx->ssl_ctx = SSL_CTX_new(TLS_SERVER_METHOD()); if (ctx->ssl_ctx == NULL) { ERR_print_errors_fp(stderr); fatal("Failed to configure SSL context"); @@ -208,6 +256,7 @@ void ws_socket_free(ws_ctx_t *ctx) { } } + int ws_b64_ntop(const unsigned char const * src, size_t srclen, char * dst, size_t dstlen) { int len = 0; int total_len = 0; @@ -274,6 +323,7 @@ int ws_b64_pton(const char const * src, unsigned char * dst, size_t dstlen) { return len; } + /* ------------------------------------------------------- */ @@ -281,7 +331,11 @@ int encode_hixie(u_char const *src, size_t srclength, char *target, size_t targsize) { int sz = 0, len = 0; target[sz++] = '\x00'; +#ifdef BASE64 len = ws_b64_ntop(src, srclength, target+sz, targsize-sz); +#else + len = targsize-sz; +#endif if (len < 0) { return len; } @@ -349,12 +403,15 @@ int encode_hybi(u_char const *src, size_t srclength, return 0; } +#ifdef BASE64 if (opcode & OPCODE_TEXT) { - len = ((srclength - 1) / 3) * 4 + 4; - } else { + len = ((srclength - 1) / 3) * 4 + 4; + } else +#endif + { len = srclength; } - + if(settings.veryverbose) {printf("enc opcode %d len %d\n",opcode,len); } if (len <= 125) { target[1] = (char) len; payload_offset = 2; @@ -370,9 +427,12 @@ int encode_hybi(u_char const *src, size_t srclength, //payload_offset = 10; } +#if BASE64 if (opcode & OPCODE_TEXT) { len = ws_b64_ntop(src, srclength, target+payload_offset, targsize-payload_offset); - } else { + } else +#endif +{ memcpy(target+payload_offset, src, srclength); len = srclength; } @@ -430,6 +490,7 @@ int decode_hybi(unsigned char *src, size_t srclength, } payload_length = frame[1] & 0x7f; + if(settings.veryverbose) { printf("payload len = %d",payload_length);} if (payload_length < 126) { hdr_length = 2; //frame += 2 * sizeof(char); @@ -445,6 +506,7 @@ int decode_hybi(unsigned char *src, size_t srclength, } //printf(" payload_length: %u, raw remaining: %u\n", payload_length, remaining); payload = frame + hdr_length + 4*masked; + if(settings.veryverbose) { printf(" opcode %d %s\n",*opcode,*opcode==OPCODE_TEXT?"text":"binary"); } if (*opcode != OPCODE_TEXT && *opcode != OPCODE_BINARY) { handler_msg("Ignoring non-data frame, opcode 0x%x\n", *opcode); @@ -455,7 +517,6 @@ int decode_hybi(unsigned char *src, size_t srclength, handler_msg("Ignoring empty frame\n"); continue; } - if ((payload_length > 0) && (!masked)) { handler_emsg("Received unmasked payload from client\n"); return -1; @@ -471,10 +532,16 @@ int decode_hybi(unsigned char *src, size_t srclength, payload[i] ^= mask[i%4]; } - if (*opcode & OPCODE_TEXT) { - // base64 decode the data - len = ws_b64_pton((const char*)payload, target+target_offset, targsize); - } else { +#ifdef BASE64 + if (*opcode == OPCODE_TEXT) { + // base64 decode the data + if(settings.veryverbose) { printf("base64 decode\n"); } + len = ws_b64_pton((const char*)payload, target+target_offset, targsize); + } + else +#endif + { // binary + if(settings.veryverbose) { printf("binary decode\n"); } memcpy(target+target_offset, payload, payload_length); len = payload_length; } @@ -657,13 +724,19 @@ ws_ctx_t *do_handshake(int sock) { headers_t *headers; int len, ret, i, offset; ws_ctx_t * ws_ctx; - char *response_protocol; + char *response_protocol=NULL; const char *response_protocol_header = "Sec-WebSocket-Protocol: "; const char *response_protocol_crlf = "\r\n"; + printf("doing handshake\n"); + // Peek, but don't read the data len = recv(sock, handshake, 1024, MSG_PEEK); + handshake[len] = 0; + + printf("in %s\n",handshake); + if (len == 0) { handler_msg("ignoring empty handshake\n"); return NULL; @@ -732,15 +805,18 @@ ws_ctx_t *do_handshake(int sock) { if (headers->protocols == NULL || headers->protocols[0] == 0) { ws_ctx->opcode = OPCODE_BINARY; + if(settings.verbose) { printf("Opcode default set to binary %d\n",ws_ctx->opcode); } response_protocol_header = ""; response_protocol = ""; response_protocol_crlf = ""; } else { response_protocol = strtok(headers->protocols, ","); + printf("response protocol %s\n",response_protocol); if (!response_protocol || !strlen(response_protocol)) { ws_ctx->opcode = OPCODE_BINARY; response_protocol = "null"; } else if (!strcmp(response_protocol, "base64")) { + printf("using base64 %s\n",response_protocol); ws_ctx->opcode = OPCODE_TEXT; } else { ws_ctx->opcode = OPCODE_BINARY; diff --git a/c/websocket.h b/c/websocket.h index 45acc21..41cde27 100644 --- a/c/websocket.h +++ b/c/websocket.h @@ -57,6 +57,7 @@ typedef struct { typedef struct { int verbose; + int veryverbose; char listen_host[256]; int listen_port; void (*handler)(ws_ctx_t*); diff --git a/c/websockify.c b/c/websockify.c index fa4a637..834bcbf 100644 --- a/c/websockify.c +++ b/c/websockify.c @@ -20,7 +20,6 @@ #include #include #include "websocket.h" - char traffic_legend[] = "\n\ Traffic Legend:\n\ } - Client receive\n\ @@ -36,6 +35,7 @@ Traffic Legend:\n\ char USAGE[] = "Usage: [options] " \ "[source_addr:]source_port target_addr:target_port\n\n" \ " --verbose|-v verbose messages and per frame traffic\n" \ + " --veryverbose|-V very verbose messages and per frame traffic\n" \ " --daemon|-D become a daemon (background process)\n" \ " --run-once handle a single WebSocket connection and exit\n" \ " --cert CERT SSL certificate file\n" \ @@ -57,11 +57,12 @@ void do_proxy(ws_ctx_t *ws_ctx, int target) { fd_set rlist, wlist, elist; struct timeval tv; int i, maxfd, client = ws_ctx->sockfd; - unsigned int opcode, left, ret; + unsigned int left, ret; unsigned int tout_start, tout_end, cout_start, cout_end; unsigned int tin_start, tin_end; ssize_t len, bytes; - + unsigned int opcode = ws_ctx->opcode; + if(settings.veryverbose) { printf("proxy opcode %d",opcode); } tout_start = tout_end = cout_start = cout_end; tin_start = tin_end = 0; maxfd = client > target ? client+1 : target+1; @@ -115,6 +116,12 @@ void do_proxy(ws_ctx_t *ws_ctx, int target) { if (FD_ISSET(target, &wlist)) { len = tout_end-tout_start; bytes = send(target, ws_ctx->tout_buf + tout_start, len, 0); + if(settings.verbose) + { char ch = *(ws_ctx->tout_buf + tout_start + len); + *(ws_ctx->tout_buf + tout_start + len) = 0; + printf("send target: %s\n", ws_ctx->tout_buf + tout_start); + *(ws_ctx->tout_buf + tout_start + len) = ch; + } if (pipe_error) { break; } if (bytes < 0) { handler_emsg("target connection error: %s\n", @@ -132,7 +139,8 @@ void do_proxy(ws_ctx_t *ws_ctx, int target) { if (FD_ISSET(client, &wlist)) { len = cout_end-cout_start; - bytes = ws_send(ws_ctx, ws_ctx->cout_buf + cout_start, len); + bytes = ws_send( +ws_ctx, ws_ctx->cout_buf + cout_start, len); if (pipe_error) { break; } if (len < 3) { handler_emsg("len: %d, bytes: %d: %d\n", @@ -140,6 +148,10 @@ void do_proxy(ws_ctx_t *ws_ctx, int target) { (int) *(ws_ctx->cout_buf + cout_start)); } cout_start += bytes; + if(settings.veryverbose) + { + printf("sent %d\n",bytes); + } if (cout_start >= cout_end) { cout_start = cout_end = 0; traffic("<"); @@ -156,20 +168,27 @@ void do_proxy(ws_ctx_t *ws_ctx, int target) { break; } cout_start = 0; + if(settings.veryverbose) { printf("received %d from target\n",bytes); } + ws_ctx->cin_buf[bytes]=(char)0; + if(settings.verbose) { printf("echo: %s\n",ws_ctx->cin_buf); } + if (ws_ctx->hybi) { + if(settings.veryverbose) { printf("encode hybi\n"); } cout_end = encode_hybi(ws_ctx->cin_buf, bytes, - ws_ctx->cout_buf, BUFSIZE, ws_ctx->opcode); + ws_ctx->cout_buf, BUFSIZE, opcode); } else { + if(settings.veryverbose) { printf("encode hixie\n"); } cout_end = encode_hixie(ws_ctx->cin_buf, bytes, ws_ctx->cout_buf, BUFSIZE); } - /* - printf("encoded: "); + if(settings.veryverbose) + { + printf("encoded: "); for (i=0; i< cout_end; i++) { printf("%u,", (unsigned char) *(ws_ctx->cout_buf+i)); } printf("\n"); - */ + } if (cout_end < 0) { handler_emsg("encoding error\n"); break; @@ -179,25 +198,31 @@ void do_proxy(ws_ctx_t *ws_ctx, int target) { if (FD_ISSET(client, &rlist)) { bytes = ws_recv(ws_ctx, ws_ctx->tin_buf + tin_end, BUFSIZE-1); + if(settings.veryverbose) { printf("got %d bytes\n",bytes); } if (pipe_error) { break; } if (bytes <= 0) { handler_emsg("client closed connection\n"); break; } tin_end += bytes; - /* - printf("before decode: "); - for (i=0; i< bytes; i++) { - printf("%u,", (unsigned char) *(ws_ctx->tin_buf+i)); - } - printf("\n"); - */ + + if(settings.veryverbose) + { + printf("before decode: "); + for (i=0; i< bytes; i++) { + printf("%u,", (unsigned char) *(ws_ctx->tin_buf+i)); + } + printf("\n"); + } if (ws_ctx->hybi) { + if(settings.veryverbose) { printf("decode hybi %d\n",opcode); } len = decode_hybi(ws_ctx->tin_buf + tin_start, tin_end-tin_start, ws_ctx->tout_buf, BUFSIZE-1, &opcode, &left); + if(settings.veryverbose) { printf("len %d op %d\n",len,opcode); } } else { + if(settings.veryverbose) { printf("decode hixie\n"); } len = decode_hixie(ws_ctx->tin_buf + tin_start, tin_end-tin_start, ws_ctx->tout_buf, BUFSIZE-1, @@ -209,13 +234,14 @@ void do_proxy(ws_ctx_t *ws_ctx, int target) { break; } - /* + if(settings.veryverbose) + { printf("decoded: "); for (i=0; i< len; i++) { printf("%u,", (unsigned char) *(ws_ctx->tout_buf+i)); } printf("\n"); - */ + } if (len < 0) { handler_emsg("decoding error\n"); break; @@ -277,10 +303,11 @@ void proxy_handler(ws_ctx_t *ws_ctx) { int main(int argc, char *argv[]) { int fd, c, option_index = 0; - static int ssl_only = 0, daemon = 0, run_once = 0, verbose = 0; + static int ssl_only = 0, daemon = 0, run_once = 0, verbose = 0,veryverbose = 0; char *found; static struct option long_options[] = { {"verbose", no_argument, &verbose, 'v'}, + {"veryverbose", no_argument, &veryverbose, 'V'}, {"ssl-only", no_argument, &ssl_only, 1 }, {"daemon", no_argument, &daemon, 'D'}, /* ---- */ @@ -298,12 +325,11 @@ int main(int argc, char *argv[]) settings.key = ""; while (1) { - c = getopt_long (argc, argv, "vDrc:k:", + c = getopt_long (argc, argv, "vVDrc:k:", long_options, &option_index); /* Detect the end */ if (c == -1) { break; } - switch (c) { case 0: break; // ignore @@ -312,6 +338,9 @@ int main(int argc, char *argv[]) case 'v': verbose = 1; break; + case 'V': + verbose = veryverbose = 1; + break; case 'D': daemon = 1; break; @@ -335,6 +364,7 @@ int main(int argc, char *argv[]) } } settings.verbose = verbose; + settings.veryverbose = veryverbose; settings.ssl_only = ssl_only; settings.daemon = daemon; settings.run_once = run_once;