diff --git a/Makefile.am b/Makefile.am index bb676218..dec9704f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,2 +1,2 @@ -SUBDIRS=lib test-server +SUBDIRS=lib test-server diff --git a/Makefile.in b/Makefile.in index 02a4a846..20e9dbaf 100644 --- a/Makefile.in +++ b/Makefile.in @@ -181,6 +181,7 @@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ +clientcertdir = @clientcertdir@ datadir = @datadir@ datarootdir = @datarootdir@ docdir = @docdir@ @@ -214,7 +215,7 @@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -SUBDIRS = lib test-server +SUBDIRS = lib test-server all: config.h $(MAKE) $(AM_MAKEFLAGS) all-recursive diff --git a/README-test-server b/README-test-server index 9a48db2e..f10520e1 100644 --- a/README-test-server +++ b/README-test-server @@ -33,6 +33,10 @@ There are a couple of other possible configure options to disable the built-in ones and force use of the libcrypto (part of openssl) ones. +--with-client-cert-dir=dir tells the client ssl support where to + look for trust certificates to validate + the remote certificate against. + Testing server with a browser ----------------------------- @@ -44,11 +48,8 @@ It will fetch a script in the form of test.html, and then run the script in there on the browser to open a websocket connection. Incrementing numbers should appear in the browser display. -Using SSL ---------- - -The client side operation does not support SSL yet, but the -server side does. +Using SSL on the server side +---------------------------- To test it using SSL/WSS, just run the test server with @@ -68,6 +69,7 @@ same. test-server.c is all that is needed to use libwebsockets for serving both the script html over http and websockets. + Forkless operation ------------------ @@ -76,6 +78,7 @@ libwebsockets from your own main loop instead. Use the configure option --nofork and simply call libwebsocket_service() from your own main loop as shown in the test app sources. + Testing websocket client support -------------------------------- @@ -91,6 +94,19 @@ if you connect to the test server using a browser at the same time you will be able to see the circles being drawn. +Testing SSL on the client side +------------------------------ + +To test SSL/WSS client action, just run the client test with + +$ libwebsockets-test-client localhost --ssl + +By default the client test applet is set to accept selfsigned +certificates used by the test server, this is indicated by the +use_ssl var being set to 2. Set it to 1 to reject any server +certificate that it doesn't have a trusted CA cert for. + + Websocket version supported --------------------------- diff --git a/configure b/configure index 3e0ce284..4ff42aca 100755 --- a/configure +++ b/configure @@ -616,6 +616,7 @@ ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS +clientcertdir LIBCRYPTO_FALSE LIBCRYPTO_TRUE CPP @@ -739,6 +740,7 @@ enable_libtool_lock enable_openssl enable_nofork enable_libcrypto +with_client_cert_dir ' ac_precious_vars='build_alias host_alias @@ -1383,7 +1385,7 @@ Optional Features: --disable-libtool-lock avoid locking (might break parallel builds) --enable-openssl Enables https support and needs openssl libs --enable-nofork Disables fork-related options - --enable-libcrypto Use libcrypto MD5 and SHA1 implemntations + --enable-libcrypto Use libcrypto MD5 and SHA1 implementations Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -1393,6 +1395,8 @@ Optional Packages: --with-gnu-ld assume the C compiler uses GNU ld [default=no] --with-sysroot=DIR Search for dependent libraries within DIR (or the compiler's sysroot if not specified). + --with-client-cert-dir directory containing client certs, defaults to + /etc/pki/tls/certs/ Some influential environment variables: CC C compiler command @@ -11963,6 +11967,7 @@ fi + # # # @@ -12285,6 +12290,23 @@ fi +# +# +# + +# Check whether --with-client-cert-dir was given. +if test "${with_client_cert_dir+set}" = set; then : + withval=$with_client_cert_dir; clientcertdir=$withval +else + clientcertdir=/etc/pki/tls/certs/ + +fi + + + + + + # Checks for header files. for ac_header in fcntl.h netinet/in.h stdlib.h string.h sys/socket.h unistd.h diff --git a/configure.ac b/configure.ac index 019b3f96..00b69f4f 100644 --- a/configure.ac +++ b/configure.ac @@ -17,6 +17,7 @@ AC_PROG_INSTALL AC_PROG_MAKE_SET AC_CONFIG_MACRO_DIR([m4]) + # # # @@ -48,7 +49,7 @@ fi # # AC_ARG_ENABLE(libcrypto, - [ --enable-libcrypto Use libcrypto MD5 and SHA1 implemntations], + [ --enable-libcrypto Use libcrypto MD5 and SHA1 implementations], [ libcrypto=yes ]) @@ -59,6 +60,19 @@ fi AM_CONDITIONAL(LIBCRYPTO, test x$libcrypto = xyes) +# +# +# +AC_ARG_WITH([client-cert-dir], +[AS_HELP_STRING([--with-client-cert-dir],[directory containing client certs, defaults to /etc/pki/tls/certs/])], +[clientcertdir=$withval], +[clientcertdir=/etc/pki/tls/certs/] +) +AC_SUBST([clientcertdir]) + +AC_SUBST([CFLAGS]) + + # Checks for header files. AC_CHECK_HEADERS([fcntl.h netinet/in.h stdlib.h string.h sys/socket.h unistd.h]) diff --git a/lib/Makefile.am b/lib/Makefile.am index 2280bdcb..8328c291 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -12,7 +12,7 @@ else dist_libwebsockets_la_SOURCES += md5.c sha-1.c endif -libwebsockets_la_CFLAGS=-Wall -Werror -std=gnu99 -pedantic -rdynamic -fPIC -c +libwebsockets_la_CFLAGS:=-rdynamic -fPIC -Wall -Werror -std=gnu99 -pedantic -c -DDATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" libwebsockets_la_LDFLAGS=-version-info 0:2 all-local: diff --git a/lib/Makefile.in b/lib/Makefile.in index 9f58996b..775baba7 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -183,6 +183,7 @@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ +clientcertdir = @clientcertdir@ datadir = @datadir@ datarootdir = @datarootdir@ docdir = @docdir@ @@ -221,7 +222,7 @@ include_HEADERS = libwebsockets.h dist_libwebsockets_la_SOURCES = libwebsockets.c handshake.c parsers.c \ libwebsockets.h base64-decode.c client-handshake.c \ private-libwebsockets.h $(am__append_1) -libwebsockets_la_CFLAGS = -Wall -Werror -std=gnu99 -pedantic -rdynamic -fPIC -c +libwebsockets_la_CFLAGS := -rdynamic -fPIC -Wall -Werror -std=gnu99 -pedantic -c -DDATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" libwebsockets_la_LDFLAGS = -version-info 0:2 all: all-am diff --git a/lib/client-handshake.c b/lib/client-handshake.c index 64f72cbf..e93ce990 100644 --- a/lib/client-handshake.c +++ b/lib/client-handshake.c @@ -9,8 +9,10 @@ void strtolower(char *s) { - while (*s) - *s++ = tolower(*s); + while (*s) { + *s = tolower(*s); + s++; + } } void @@ -53,7 +55,7 @@ libwebsocket_client_close(struct libwebsocket *wsi) /* shut down reasonably cleanly */ #ifdef LWS_OPENSSL_SUPPORT - if (use_ssl) { + if (wsi->ssl) { n = SSL_get_fd(wsi->ssl); SSL_shutdown(wsi->ssl); close(n); @@ -67,10 +69,29 @@ libwebsocket_client_close(struct libwebsocket *wsi) #endif } + +/** + * libwebsocket_client_connect() - Connect to another websocket server + * @this: Websocket context + * @address: Remote server address, eg, "myserver.com" + * @port: Port to connect to on the remote server, eg, 80 + * @ssl_connection: 0 = ws://, 1 = wss:// encrypted, 2 = wss:// allow self + * signed certs + * @path: Websocket path on server + * @host: Hostname on server + * @origin: Socket origin name + * @protocol: Comma-separated list of protocols being asked for from + * the server, or just one. The server will pick the one it + * likes best. + * + * This function creates a connection to a remote server + */ + struct libwebsocket * -libwebsocket_client_connect(struct libwebsocket_context *clients, +libwebsocket_client_connect(struct libwebsocket_context *this, const char *address, int port, + int ssl_connection, const char *path, const char *host, const char *origin, @@ -94,12 +115,22 @@ libwebsocket_client_connect(struct libwebsocket_context *clients, int okay = 0; struct libwebsocket *wsi; int n; +#ifdef LWS_OPENSSL_SUPPORT + char ssl_err_buf[512]; +#else + if (ssl_connection) { + fprintf(stderr, "libwebsockets not configured for ssl\n"); + return NULL; + } +#endif wsi = malloc(sizeof(struct libwebsocket)); - if (wsi == NULL) + if (wsi == NULL) { + fprintf(stderr, "Out of memort allocing new connection\n"); return NULL; + } - clients->wsi[clients->fds_count] = wsi; + this->wsi[this->fds_count] = wsi; wsi->ietf_spec_revision = 4; wsi->name_buffer_pos = 0; @@ -137,10 +168,36 @@ libwebsocket_client_connect(struct libwebsocket_context *clients, if (connect(wsi->sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) { - fprintf(stderr, "Connect failed"); + fprintf(stderr, "Connect failed\n"); goto bail1; } +#ifdef LWS_OPENSSL_SUPPORT + if (ssl_connection) { + + wsi->ssl = SSL_new(this->ssl_client_ctx); + wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE); + SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); + + if (SSL_connect(wsi->ssl) <= 0) { + fprintf(stderr, "SSL connect error %s\n", + ERR_error_string(ERR_get_error(), ssl_err_buf)); + goto bail2; + } + + n = SSL_get_verify_result(wsi->ssl); + if (n != X509_V_OK) { + if (n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT && + ssl_connection == 2) + goto cert_okay; + + fprintf(stderr, "server's cert didn't look good %d\n", n); + goto bail2; + } + } else + wsi->ssl = NULL; +cert_okay: +#endif /* * create the random key */ @@ -199,7 +256,17 @@ libwebsocket_client_connect(struct libwebsocket_context *clients, /* send our request to the server */ - n = send(wsi->sock, pkt, p - pkt, 0); +#ifdef LWS_OPENSSL_SUPPORT + if (ssl_connection) + n = SSL_write(wsi->ssl, pkt, p - pkt); + else +#endif + n = send(wsi->sock, pkt, p - pkt, 0); + + if (n < 0) { + fprintf(stderr, "ERROR writing to client socket\n"); + goto bail2; + } wsi->parser_state = WSI_TOKEN_NAME_PART; @@ -230,7 +297,13 @@ libwebsocket_client_connect(struct libwebsocket_context *clients, * Sec-WebSocket-Protocol: chat */ - len = recv(wsi->sock, pkt, sizeof pkt, 0); +#ifdef LWS_OPENSSL_SUPPORT + if (ssl_connection) + len = SSL_read(wsi->ssl, pkt, sizeof pkt); + else +#endif + len = recv(wsi->sock, pkt, sizeof pkt, 0); + if (len < 0) { fprintf(stderr, "libwebsocket_client_handshake read error\n"); goto bail2; @@ -300,7 +373,7 @@ libwebsocket_client_connect(struct libwebsocket_context *clients, if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) { /* no protocol name to work from, default to first protocol */ - wsi->protocol = &clients->protocols[0]; + wsi->protocol = &this->protocols[0]; goto check_accept; } @@ -331,10 +404,10 @@ libwebsocket_client_connect(struct libwebsocket_context *clients, */ n = 0; wsi->protocol = NULL; - while (clients->protocols[n].callback) { + while (this->protocols[n].callback) { if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, - clients->protocols[n].name) == 0) - wsi->protocol = &clients->protocols[n]; + this->protocols[n].name) == 0) + wsi->protocol = &this->protocols[n]; n++; } @@ -373,15 +446,21 @@ check_accept: /* okay he is good to go */ - clients->fds[clients->fds_count].fd = wsi->sock; - clients->fds[clients->fds_count].revents = 0; - clients->fds[clients->fds_count++].events = POLLIN; + this->fds[this->fds_count].fd = wsi->sock; + this->fds[this->fds_count].revents = 0; + this->fds[this->fds_count++].events = POLLIN; wsi->state = WSI_STATE_ESTABLISHED; wsi->client_mode = 1; fprintf(stderr, "handshake OK for protocol %s\n", wsi->protocol->name); + /* call him back to inform him he is up */ + + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_ESTABLISHED, + wsi->user_space, + NULL, 0); return wsi; diff --git a/lib/handshake.c b/lib/handshake.c index d6928d75..d126809f 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -131,7 +131,7 @@ handshake_00(struct libwebsocket *wsi) strcpy(p, wsi->utf8_token[WSI_TOKEN_ORIGIN].token); p += wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len; #ifdef LWS_OPENSSL_SUPPORT - if (use_ssl) { + if (wsi->ssl) { strcpy(p, "\x0d\x0aSec-WebSocket-Location: wss://"); p += strlen("\x0d\x0aSec-WebSocket-Location: wss://"); } else { diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 1c4bd176..8d462095 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -21,11 +21,6 @@ #include "private-libwebsockets.h" -#ifdef LWS_OPENSSL_SUPPORT -SSL_CTX *ssl_ctx; -int use_ssl; -#endif - void libwebsocket_close_and_free_session(struct libwebsocket *wsi) { @@ -49,7 +44,7 @@ libwebsocket_close_and_free_session(struct libwebsocket *wsi) /* fprintf(stderr, "closing fd=%d\n", wsi->sock); */ #ifdef LWS_OPENSSL_SUPPORT - if (use_ssl) { + if (wsi->ssl) { n = SSL_get_fd(wsi->ssl); SSL_shutdown(wsi->ssl); close(n); @@ -93,6 +88,19 @@ libwebsocket_poll_connections(struct libwebsocket_context *this) goto nuke_this; } + /* the guy requested a callback when it was OK to write */ + + if ((unsigned long)this->wsi[client] > LWS_MAX_PROTOCOLS && + this->fds[client].revents & POLLOUT) { + + this->fds[client].events &= ~POLLOUT; + + this->wsi[client]->protocol->callback(this->wsi[client], + LWS_CALLBACK_CLIENT_WRITEABLE, + this->wsi[client]->user_space, + NULL, 0); + } + /* any incoming data ready? */ if (!(this->fds[client].revents & POLLIN)) @@ -158,7 +166,7 @@ libwebsocket_poll_connections(struct libwebsocket_context *this) } #ifdef LWS_OPENSSL_SUPPORT - if (this->use_ssl) + if (this->wsi[client]->ssl) n = SSL_read(this->wsi[client]->ssl, buf, sizeof buf); else #endif @@ -218,8 +226,8 @@ libwebsocket_context_destroy(struct libwebsocket_context *this) libwebsocket_close_and_free_session(this->wsi[client]); #ifdef LWS_OPENSSL_SUPPORT - if (ssl_ctx) - SSL_CTX_free(ssl_ctx); + if (this->ssl_ctx) + SSL_CTX_free(this->ssl_ctx); #endif if (this) @@ -336,9 +344,12 @@ libwebsocket_service(struct libwebsocket_context *this, int timeout_ms) } #ifdef LWS_OPENSSL_SUPPORT + this->wsi[this->fds_count]->ssl = NULL; + if (this->use_ssl) { - this->wsi[this->fds_count]->ssl = SSL_new(ssl_ctx); + this->wsi[this->fds_count]->ssl = + SSL_new(this->ssl_ctx); if (this->wsi[this->fds_count]->ssl == NULL) { fprintf(stderr, "SSL_new failed: %s\n", ERR_error_string(SSL_get_error( @@ -440,7 +451,8 @@ fatal: close(this->fds[0].fd); #ifdef LWS_OPENSSL_SUPPORT - SSL_CTX_free(ssl_ctx); + if (this->ssl_ctx) + SSL_CTX_free(this->ssl_ctx); #endif if (this) @@ -453,6 +465,91 @@ fatal: return 1; } +/** + * libwebsocket_callback_on_writable() - Request a callback when this socket + * becomes able to be written to without + * blocking + * + * @wsi: Websocket connection instance to get callback for + */ + +int +libwebsocket_callback_on_writable(struct libwebsocket *wsi) +{ + struct libwebsocket_context *this = wsi->protocol->owning_server; + int n; + + for (n = this->count_protocols + 1; n < this->fds_count; n++) + if (this->wsi[n] == wsi) { + this->fds[n].events |= POLLOUT; + return 0; + } + + fprintf(stderr, "libwebsocket_callback_on_writable " + "didn't find socket\n"); + return 1; +} + +/** + * libwebsocket_callback_on_writable_all_protocol() - Request a callback for + * all connections using the given protocol when it + * becomes possible to write to each socket without + * blocking in turn. + * + * @protocol: Protocol whose connections will get callbacks + */ + +int +libwebsocket_callback_on_writable_all_protocol( + const struct libwebsocket_protocols *protocol) +{ + struct libwebsocket_context *this = protocol->owning_server; + int n; + + for (n = this->count_protocols + 1; n < this->fds_count; n++) + if ((unsigned long)this->wsi[n] > LWS_MAX_PROTOCOLS) + if (this->wsi[n]->protocol == protocol) + this->fds[n].events |= POLLOUT; + + return 0; +} + +/** + * libwebsocket_rx_flow_control() - Enable and disable socket servicing for + * receieved packets. + * + * If the output side of a server process becomes choked, this allows flow + * control for the input side. + * + * @wsi: Websocket connection instance to get callback for + * @enable: 0 = disable read servicing for this connection, 1 = enable + */ + +int +libwebsocket_rx_flow_control(struct libwebsocket *wsi, int enable) +{ + struct libwebsocket_context *this = wsi->protocol->owning_server; + int n; + + for (n = this->count_protocols + 1; n < this->fds_count; n++) + if (this->wsi[n] == wsi) { + if (enable) + this->fds[n].events |= POLLIN; + else + this->fds[n].events &= ~POLLIN; + + return 0; + } + + fprintf(stderr, "libwebsocket_callback_on_writable " + "unable to find socket\n"); + return 1; +} + +static void sigpipe_handler(int x) +{ +} + /** * libwebsocket_create_context() - Create the websocket handler @@ -515,47 +612,103 @@ libwebsocket_create_context(int port, #ifdef LWS_OPENSSL_SUPPORT SSL_METHOD *method; char ssl_err_buf[512]; - - use_ssl = ssl_cert_filepath != NULL && ssl_private_key_filepath != NULL; - if (use_ssl) - fprintf(stderr, " Compiled with SSL support, using it\n"); - else - fprintf(stderr, " Compiled with SSL support, not using it\n"); - -#else - if (ssl_cert_filepath != NULL && ssl_private_key_filepath != NULL) { - fprintf(stderr, " Not compiled for OpenSSl support!\n"); - return NULL; - } - fprintf(stderr, " Compiled without SSL support, serving unencrypted\n"); #endif + this = malloc(sizeof(struct libwebsocket_context)); + if (!this) { + fprintf(stderr, "No memory for websocket context\n"); + return NULL; + } + this->protocols = protocols; + this->listen_port = port; + + if (port) { + #ifdef LWS_OPENSSL_SUPPORT - if (use_ssl) { - SSL_library_init(); + this->use_ssl = ssl_cert_filepath != NULL && + ssl_private_key_filepath != NULL; + if (this->use_ssl) + fprintf(stderr, " Compiled with SSL support, " + "using it\n"); + else + fprintf(stderr, " Compiled with SSL support, " + "not using it\n"); - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - - /* - * Firefox insists on SSLv23 not SSLv3 - * Konq disables SSLv2 by default now, SSLv23 works - */ - - method = (SSL_METHOD *)SSLv23_server_method(); - if (!method) { - fprintf(stderr, "problem creating ssl method: %s\n", - ERR_error_string(ERR_get_error(), ssl_err_buf)); - return NULL; - } - ssl_ctx = SSL_CTX_new(method); /* create context */ - if (!ssl_ctx) { - printf("problem creating ssl context: %s\n", - ERR_error_string(ERR_get_error(), ssl_err_buf)); +#else + if (ssl_cert_filepath != NULL && + ssl_private_key_filepath != NULL) { + fprintf(stderr, " Not compiled for OpenSSl support!\n"); return NULL; } + fprintf(stderr, " Compiled without SSL support, " + "serving unencrypted\n"); +#endif + } + + /* ignore SIGPIPE */ + + signal(SIGPIPE, sigpipe_handler); + + +#ifdef LWS_OPENSSL_SUPPORT + + /* basic openssl init */ + + SSL_library_init(); + + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + + /* + * Firefox insists on SSLv23 not SSLv3 + * Konq disables SSLv2 by default now, SSLv23 works + */ + + method = (SSL_METHOD *)SSLv23_server_method(); + if (!method) { + fprintf(stderr, "problem creating ssl method: %s\n", + ERR_error_string(ERR_get_error(), ssl_err_buf)); + return NULL; + } + this->ssl_ctx = SSL_CTX_new(method); /* create context */ + if (!this->ssl_ctx) { + fprintf(stderr, "problem creating ssl context: %s\n", + ERR_error_string(ERR_get_error(), ssl_err_buf)); + return NULL; + } + + /* client context */ + + method = (SSL_METHOD *)SSLv23_client_method(); + if (!method) { + fprintf(stderr, "problem creating ssl method: %s\n", + ERR_error_string(ERR_get_error(), ssl_err_buf)); + return NULL; + } + this->ssl_client_ctx = SSL_CTX_new(method); /* create context */ + if (!this->ssl_client_ctx) { + fprintf(stderr, "problem creating ssl context: %s\n", + ERR_error_string(ERR_get_error(), ssl_err_buf)); + return NULL; + } + + + /* openssl init for cert verification (used with client sockets) */ + + if (!SSL_CTX_load_verify_locations(this->ssl_client_ctx, NULL, + LWS_OPENSSL_CLIENT_CERTS)) { + fprintf(stderr, "Unable to load SSL Client certs from %s " + "(set by --with-client-cert-dir= in configure) -- " + " client ssl isn't going to work", + LWS_OPENSSL_CLIENT_CERTS); + } + + if (this->use_ssl) { + + /* openssl init for server sockets */ + /* set the local certificate from CertFile */ - n = SSL_CTX_use_certificate_file(ssl_ctx, + n = SSL_CTX_use_certificate_file(this->ssl_ctx, ssl_cert_filepath, SSL_FILETYPE_PEM); if (n != 1) { fprintf(stderr, "problem getting cert '%s': %s\n", @@ -564,7 +717,7 @@ libwebsocket_create_context(int port, return NULL; } /* set the private key from KeyFile */ - if (SSL_CTX_use_PrivateKey_file(ssl_ctx, + if (SSL_CTX_use_PrivateKey_file(this->ssl_ctx, ssl_private_key_filepath, SSL_FILETYPE_PEM) != 1) { fprintf(stderr, "ssl problem getting key '%s': %s\n", @@ -573,7 +726,7 @@ libwebsocket_create_context(int port, return NULL; } /* verify private key */ - if (!SSL_CTX_check_private_key(ssl_ctx)) { + if (!SSL_CTX_check_private_key(this->ssl_ctx)) { fprintf(stderr, "Private SSL key doesn't match cert\n"); return NULL; } @@ -587,12 +740,6 @@ libwebsocket_create_context(int port, if (lws_b64_selftest()) return NULL; - - this = malloc(sizeof(struct libwebsocket_context)); - - this->protocols = protocols; - this->listen_port = port; - /* set up our external listening socket we serve on */ if (port) { @@ -641,9 +788,6 @@ libwebsocket_create_context(int port, this->fds[0].fd = sockfd; this->fds[0].events = POLLIN; this->count_protocols = 0; -#ifdef LWS_OPENSSL_SUPPORT - this->use_ssl = use_ssl; -#endif if (port) { listen(sockfd, 5); diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 57ab4814..a4862217 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -26,9 +26,11 @@ enum libwebsocket_callback_reasons { LWS_CALLBACK_ESTABLISHED, + LWS_CALLBACK_CLIENT_ESTABLISHED, LWS_CALLBACK_CLOSED, LWS_CALLBACK_RECEIVE, LWS_CALLBACK_CLIENT_RECEIVE, + LWS_CALLBACK_CLIENT_WRITEABLE, LWS_CALLBACK_HTTP, LWS_CALLBACK_BROADCAST }; @@ -42,7 +44,9 @@ enum libwebsocket_write_protocol { LWS_WRITE_CLOSE, LWS_WRITE_PING, - LWS_WRITE_PONG + LWS_WRITE_PONG, + + LWS_WRITE_NO_FIN = 0x40 }; struct libwebsocket; @@ -67,15 +71,25 @@ struct libwebsocket_context; * You get an opportunity to initialize user data when called back with * LWS_CALLBACK_ESTABLISHED reason. * - * LWS_CALLBACK_ESTABLISHED: after successful websocket handshake + * LWS_CALLBACK_ESTABLISHED: after the server completes a handshake with + * an incoming client + * + * LWS_CALLBACK_CLIENT_ESTABLISHED: after your client connection completed + * a handshake with the remote server * * LWS_CALLBACK_CLOSED: when the websocket session ends * * LWS_CALLBACK_BROADCAST: signal to send to client (you would use * libwebsocket_write() taking care about the * special buffer requirements - * LWS_CALLBACK_RECEIVE: data has appeared for the server, it can be - * found at *in and is len bytes long + * + * LWS_CALLBACK_RECEIVE: data has appeared for this server endpoint from a + * remote client, it can be found at *in and is + * len bytes long + * + * LWS_CALLBACK_CLIENT_RECEIVE: data has appeared from the server for the + * client connection, it can be found at *in and + * is len bytes long * * LWS_CALLBACK_HTTP: an http request has come from a client that is not * asking to upgrade the connection to a websocket @@ -85,6 +99,13 @@ struct libwebsocket_context; * @in points to the URI path requested and * libwebsockets_serve_http_file() makes it very * simple to send back a file to the client. + * + * LWS_CALLBACK_CLIENT_WRITEABLE: if you call + * libwebsocket_callback_on_writable() on a connection, you will + * get this callback coming when the connection socket is able to + * accept another write packet without blocking. If it already + * was able to take another packet without blocking, you'll get + * this callback at the next call to the service loop function. */ extern int callback(struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, @@ -197,6 +218,17 @@ libwebsockets_broadcast(const struct libwebsocket_protocols *protocol, extern const struct libwebsocket_protocols * libwebsockets_get_protocol(struct libwebsocket *wsi); +extern int +libwebsocket_callback_on_writable(struct libwebsocket *wsi); + +extern int +libwebsocket_callback_on_writable_all_protocol( + const struct libwebsocket_protocols *protocol); + + +extern int +libwebsocket_rx_flow_control(struct libwebsocket *wsi, int enable); + extern size_t libwebsockets_remaining_packet_payload(struct libwebsocket *wsi); @@ -204,6 +236,7 @@ extern struct libwebsocket * libwebsocket_client_connect(struct libwebsocket_context *clients, const char *address, int port, + int ssl_connection, const char *path, const char *host, const char *origin, diff --git a/lib/parsers.c b/lib/parsers.c index 9d9efb3d..8d562219 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -977,8 +977,6 @@ libwebsocket_04_frame_mask_generate(struct libwebsocket *wsi) * packet while not burdening the user code with any protocol knowledge. */ - /* FIXME FIN bit */ - int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, size_t len, enum libwebsocket_write_protocol protocol) { @@ -987,6 +985,11 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, int post = 0; unsigned int shift = 7; + if (len == 0) { + fprintf(stderr, "zero length libwebsocket_write attempt\n"); + return 0; + } + if (protocol == LWS_WRITE_HTTP) goto send_raw; @@ -999,7 +1002,7 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, /* chrome likes this as of 30 Oct */ /* Firefox 4.0b6 likes this as of 30 Oct */ case 0: - if (protocol == LWS_WRITE_BINARY) { + if ((protocol & 0xf) == LWS_WRITE_BINARY) { /* in binary mode we send 7-bit used length blocks */ pre = 1; while (len & (127 << shift)) { @@ -1031,7 +1034,7 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, case 4: - switch (protocol) { + switch (protocol & 0xf) { case LWS_WRITE_TEXT: n = LWS_WS_OPCODE_04__TEXT_FRAME; break; @@ -1054,12 +1057,8 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, return -1; } - /* - * We don't really support the metaframe concept with FIN. - * Just set FIN on every packet for now - */ - - n |= 1 << 7; + if (!(protocol & LWS_WRITE_NO_FIN)) + n |= 1 << 7; if (len < 126) { buf[-2] = n; @@ -1130,10 +1129,9 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, memcpy(&buf[0 - pre], wsi->frame_masking_nonce_04, 4); } - send_raw: #ifdef LWS_OPENSSL_SUPPORT - if (use_ssl) { + if (wsi->ssl) { n = SSL_write(wsi->ssl, buf - pre, len + pre + post); if (n < 0) { fprintf(stderr, "ERROR writing to socket\n"); @@ -1149,6 +1147,7 @@ send_raw: #ifdef LWS_OPENSSL_SUPPORT } #endif + debug("written %d bytes to client\n", (int)len); return 0; @@ -1199,6 +1198,8 @@ int libwebsockets_serve_http_file(struct libwebsocket *wsi, const char *file, n = 1; while (n > 0) { n = read(fd, buf, 512); + if (n <= 0) + continue; libwebsocket_write(wsi, (unsigned char *)buf, n, LWS_WRITE_HTTP); } diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index a263d833..dfeb373e 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -67,11 +67,6 @@ static inline void debug(const char *format, ...) } #endif -#ifdef LWS_OPENSSL_SUPPORT -extern SSL_CTX *ssl_ctx; -extern int use_ssl; -#endif - #define MAX_CLIENTS 100 #define LWS_MAX_HEADER_NAME_LENGTH 64 @@ -79,7 +74,7 @@ extern int use_ssl; #define LWS_INITIAL_HDR_ALLOC 256 #define LWS_ADDITIONAL_HDR_ALLOC 64 #define MAX_USER_RX_BUFFER 512 -#define MAX_BROADCAST_PAYLOAD 1024 +#define MAX_BROADCAST_PAYLOAD 2048 #define LWS_MAX_PROTOCOLS 10 #define MAX_WEBSOCKET_04_KEY_LEN 128 @@ -175,6 +170,8 @@ struct libwebsocket_context { int listen_port; #ifdef LWS_OPENSSL_SUPPORT int use_ssl; + SSL_CTX *ssl_ctx; + SSL_CTX *ssl_client_ctx; #endif struct libwebsocket_protocols *protocols; int count_protocols; @@ -224,6 +221,7 @@ struct libwebsocket { #ifdef LWS_OPENSSL_SUPPORT SSL *ssl; + BIO *client_bio; #endif void *user_space; diff --git a/lib/sha-1.c b/lib/sha-1.c index 1bec3959..aea5d6a6 100644 --- a/lib/sha-1.c +++ b/lib/sha-1.c @@ -146,27 +146,31 @@ sha1_step(struct sha1_ctxt *ctxt) for (t = 0; t < 20; t++) { s = t & 0x0f; - if (t >= 16) { - W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); - } + if (t >= 16) + W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ + W((s+2) & 0x0f) ^ W(s)); + tmp = S(5, a) + F0(b, c, d) + e + W(s) + K(t); e = d; d = c; c = S(30, b); b = a; a = tmp; } for (t = 20; t < 40; t++) { s = t & 0x0f; - W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); + W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ + W((s+2) & 0x0f) ^ W(s)); tmp = S(5, a) + F1(b, c, d) + e + W(s) + K(t); e = d; d = c; c = S(30, b); b = a; a = tmp; } for (t = 40; t < 60; t++) { s = t & 0x0f; - W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); + W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ + W((s+2) & 0x0f) ^ W(s)); tmp = S(5, a) + F2(b, c, d) + e + W(s) + K(t); e = d; d = c; c = S(30, b); b = a; a = tmp; } for (t = 60; t < 80; t++) { s = t & 0x0f; - W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s)); + W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ + W((s+2) & 0x0f) ^ W(s)); tmp = S(5, a) + F3(b, c, d) + e + W(s) + K(t); e = d; d = c; c = S(30, b); b = a; a = tmp; } diff --git a/libwebsockets-api-doc.html b/libwebsockets-api-doc.html index 0016684d..f649d795 100644 --- a/libwebsockets-api-doc.html +++ b/libwebsockets-api-doc.html @@ -58,6 +58,45 @@ would call it with a timeout_ms of 0, so it returns immediately if nothing is pending, or as soon as it services whatever was pending.
+++If the output side of a server process becomes choked, this allows flow +control for the input side. +
+This function creates a connection to a remote server ++
-after successful websocket handshake +after the server completes a handshake with +an incoming client ++
+after your client connection completed +a handshake with the remote server
@@ -307,8 +390,15 @@ special buffer requirements
-data has appeared for the server, it can be -found at *in and is len bytes long +data has appeared for this server endpoint from a +remote client, it can be found at *in and is +len bytes long ++
+data has appeared from the server for the +client connection, it can be found at *in and +is len bytes long
@@ -321,6 +411,15 @@ which will then open the websockets connection. libwebsockets_serve_http_file makes it very simple to send back a file to the client.+
+if you call +libwebsocket_callback_on_writable on a connection, you will +get this callback coming when the connection socket is able to +accept another write packet without blocking. If it already +was able to take another packet without blocking, you'll get +this callback at the next call to the service loop function. +
libwebsockets-test-client spams circles on to this shared canvas when +run.