add-wss-ssl-openssl-support.patch

Signed-off-by: Andy Green <andy@warmcat.com>
diff --git a/Makefile b/Makefile
index 3aa2237..dda1115 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
-export CFLAGS= -Wall -Werror -rdynamic -fPIC -c
+export CFLAGS= -Wall -Werror -rdynamic -fPIC -c -DLWS_OPENSSL_SUPPORT
+export LFLAGS= -lssl
 all:
 	make -C lib
 	make -C test-server
@@ -14,4 +15,6 @@
 	make -C lib install
 	make -C test-server install
 
+gencert:
+	make -C test-server gencert
 	
diff --git a/lib/Makefile b/lib/Makefile
index be381c3..a84373a 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -3,7 +3,7 @@
 all:
 	gcc $(CFLAGS) libwebsockets.c
 	gcc $(CFLAGS) md5.c
-	gcc libwebsockets.o md5.o --shared -o libwebsockets.so
+	gcc $(LFLAGS) libwebsockets.o md5.o --shared -o libwebsockets.so
 	
 clean:
 	rm -f *.o *.so
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 065cb01..9c798e4 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -21,6 +21,17 @@
 #include <poll.h>
 #include <sys/mman.h>
 
+#ifdef LWS_OPENSSL_SUPPORT
+#include <openssl/ssl.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+
+SSL_CTX *ssl_ctx;
+int use_ssl;
+#endif
+
+//#define DEBUG
+
 #include "libwebsockets.h"
 
 #ifdef DEBUG
@@ -41,6 +52,7 @@
 #define LWS_ADDITIONAL_HDR_ALLOC 64
 
 
+
 enum lws_connection_states {
 	WSI_STATE_HTTP,
 	WSI_STATE_HTTP_HEADERS,
@@ -108,6 +120,26 @@
 	enum lws_rx_parse_state lws_rx_parse_state;
 	size_t rx_packet_length;
 	
+#ifdef LWS_OPENSSL_SUPPORT
+	char m_fOccupied;
+	struct sockaddr_in m_addr;
+	int m_addrlen;
+
+	SSL *ssl;
+
+		// these are valid if it is a POST
+
+	char m_fOngoingPost;
+	int m_nSessionID;
+
+	time_t m_timeStarted;
+	long long m_llTransferred;
+	long long m_llSizeIfKnown;
+
+	char m_szTitle[PATH_MAX];
+	char m_szStatus[PATH_MAX];
+#endif
+
 	/* last */
 	char user_space[0];
 };
@@ -142,8 +174,19 @@
 
 //	fprintf(stderr, "closing fd=%d\n", wsi->sock);
 
-	shutdown(wsi->sock, SHUT_RDWR);
-	close(wsi->sock);
+#ifdef LWS_OPENSSL_SUPPORT
+	if (use_ssl) {
+		n = SSL_get_fd(wsi->ssl);
+		SSL_shutdown(wsi->ssl);
+		close(n);
+		SSL_free(wsi->ssl);
+	} else {
+#endif
+		shutdown(wsi->sock, SHUT_RDWR);
+		close(wsi->sock);
+#ifdef LWS_OPENSSL_SUPPORT
+	}
+#endif
 	free(wsi);
 }
 
@@ -156,6 +199,13 @@
  * 			which will be used by the user application to store
  * 			per-session data.  A pointer to this space is given
  * 			when the user callback is called.
+ * @ssl_cert_filepath:	If libwebsockets was compiled to use ssl, and you want
+ * 			to listen using SSL, set to the filepath to fetch the
+ * 			server cert from, otherwise NULL for unencrypted
+ * @ssl_private_key_filepath: filepath to private key if wanting SSL mode,
+ * 			else ignored
+ * @gid:	group id to change to after setting listen socket, or -1.
+ * @uid:	user id to change to after setting listen socket, or -1.
  * 
  * 	This function forks to create the listening socket and takes care
  * 	of all initialization in one step.
@@ -177,7 +227,10 @@
 		int (*callback)(struct libwebsocket *,
 				enum libwebsocket_callback_reasons, 
 				void *, void *, size_t),
-					    int protocol, size_t user_area_size)
+					int protocol, size_t user_area_size,
+				const char * ssl_cert_filepath,
+				const char * ssl_private_key_filepath,
+				int gid, int uid)
 {
 	int n;
 	int client;
@@ -188,9 +241,74 @@
 	struct libwebsocket *wsi[MAX_CLIENTS + 1];
 	struct pollfd fds[MAX_CLIENTS + 1];
 	int fds_count = 0;
-	unsigned char buf[256];
+	unsigned char buf[1024];
 	int opt = 1;
 
+#ifdef LWS_OPENSSL_SUPPORT
+	const 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, but not using it\n");
+
+#else
+	if (ssl_cert_filepath != NULL && ssl_private_key_filepath != NULL) {
+		fprintf(stderr, " Not compiled for OpenSSl support!\n");
+		return -1;
+	}
+	fprintf(stderr, " Compiled without SSL support, listening unencrypted\n");
+#endif
+
+#ifdef LWS_OPENSSL_SUPPORT
+	if (use_ssl) {
+		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 = SSLv23_server_method();   // create server instance
+		if (!method) {
+			fprintf(stderr, "problem creating ssl method: %s\n",
+				ERR_error_string(ERR_get_error(), ssl_err_buf));
+			return -1;
+		}
+		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));
+			return -1;
+		}
+		/* set the local certificate from CertFile */
+		n = SSL_CTX_use_certificate_file(ssl_ctx,
+					ssl_cert_filepath, SSL_FILETYPE_PEM);
+		if (n != 1) {
+			fprintf(stderr, "problem getting cert '%s': %s\n",
+				ssl_cert_filepath,
+				ERR_error_string(ERR_get_error(), ssl_err_buf));
+			return -1;
+		}
+		/* set the private key from KeyFile */
+		if (SSL_CTX_use_PrivateKey_file(ssl_ctx, ssl_private_key_filepath,
+		    SSL_FILETYPE_PEM) != 1) {
+			fprintf(stderr, "ssl problem getting key '%s': %s\n", ssl_private_key_filepath, ERR_error_string(ERR_get_error(), ssl_err_buf));
+			return (-1);
+		}
+		/* verify private key */
+		if (!SSL_CTX_check_private_key(ssl_ctx)) {
+			fprintf(stderr, "Private SSL key does not match cert\n");
+			return (-1);
+		}
+
+		/* SSL is happy and has a cert it's content with */
+	}
+#endif
+
 	/* sanity check */
 
 	switch (protocol) {
@@ -245,6 +363,15 @@
 	if (n)
 		return sockfd;
  
+	// drop any root privs for this thread
+
+	if (gid != -1)
+		if (setgid(gid))
+			fprintf(stderr, "setgid: %s\n", strerror(errno));
+	if (uid != -1)
+		if (setuid(uid))
+			fprintf(stderr, "setuid: %s\n", strerror(errno));
+
 	/* we are running in a forked subprocess now */
  
 	listen(sockfd, 5);
@@ -266,32 +393,64 @@
 
 		if (fds[0].revents & POLLIN) {
 
-			/* listen socket got a new connection... */
-		
+			/* listen socket got an unencrypted connection... */
+
 			clilen = sizeof(cli_addr);
-			fd  = accept(sockfd, (struct sockaddr *)&cli_addr,
-								       &clilen);
+			fd  = accept(sockfd,
+				     (struct sockaddr *)&cli_addr,
+							       &clilen);
 			if (fd < 0) {
 				fprintf(stderr, "ERROR on accept");
 				continue;
 			}
-			
+
 			if (fds_count >= MAX_CLIENTS) {
 				fprintf(stderr, "too busy");
 				close(fd);
 				continue;
 			}
-			
-//			fprintf(stderr, "accepted new conn  port %u on fd=%d\n",
-//						  ntohs(cli_addr.sin_port), fd);
-			
-			/* intialize the instance struct */
-			
+
 			wsi[fds_count] = malloc(sizeof(struct libwebsocket) +
 								user_area_size);
 			if (!wsi[fds_count])
 				return -1;
-		     
+
+
+#ifdef LWS_OPENSSL_SUPPORT
+			if (use_ssl) {
+
+				wsi[fds_count]->ssl = SSL_new(ssl_ctx);  // get new SSL state with context
+				if (wsi[fds_count]->ssl == NULL) {
+					fprintf(stderr, "SSL_new failed: %s\n",
+					    ERR_error_string(SSL_get_error(wsi[fds_count]->ssl, 0), NULL));
+					free(wsi[fds_count]);
+					continue;
+				}
+
+				SSL_set_fd(wsi[fds_count]->ssl, fd);    // set SSL socket
+
+				n = SSL_accept(wsi[fds_count]->ssl);
+				if (n != 1) {
+					/* browsers seem to probe with various ssl params which fail then retry */
+					debug("SSL_accept failed for socket %u: %s\n",
+						fd,
+						ERR_error_string(SSL_get_error(wsi[fds_count]->ssl, n),
+						NULL));
+					SSL_free(wsi[fds_count]->ssl);
+					free(wsi[fds_count]);
+					continue;
+				}
+				debug("accepted new SSL conn  port %u on fd=%d SSL ver %s\n",
+						  ntohs(cli_addr.sin_port), fd, SSL_get_version(wsi[fds_count]->ssl));
+				
+			} else {
+//			fprintf(stderr, "accepted new conn  port %u on fd=%d\n",
+//						  ntohs(cli_addr.sin_port), fd);
+			}
+#endif
+			
+			/* intialize the instance struct */
+
 			wsi[fds_count]->sock = fd;
 			wsi[fds_count]->state = WSI_STATE_HTTP;
 			wsi[fds_count]->name_buffer_pos = 0;
@@ -328,8 +487,16 @@
 				continue;
 				
 //			fprintf(stderr, "POLLIN\n");
-			
-			n = recv(fds[client].fd, buf, sizeof(buf), 0);
+
+#ifdef LWS_OPENSSL_SUPPORT
+			if (use_ssl)
+				n = SSL_read(wsi[client]->ssl, buf, sizeof buf);
+			else
+#endif
+				n = recv(fds[client].fd, buf, sizeof(buf), 0);
+
+//			fprintf(stderr, "read returned %d\n", n);
+
 			if (n < 0) {
 				fprintf(stderr, "Socket read returned %d\n", n);
 				continue;
@@ -372,10 +539,14 @@
 	}
 	
 fatal:
+	/* listening socket */
 	close(fds[0].fd);
 	for (client = 1; client < fds_count; client++)
 		libwebsocket_close_and_free_session(wsi[client]);
 
+#ifdef LWS_OPENSSL_SUPPORT
+	SSL_CTX_free(ssl_ctx);
+#endif
 	kill(0, SIGTERM);
 	
 	return 0;
@@ -598,7 +769,7 @@
 				"a v76 close, sending ack\n");
 		buf[0] = 0xff;
 		buf[1] = 0;
-		n = write(wsi->sock, buf, 2);
+		n = libwebsocket_write(wsi, buf, 2, LWS_WRITE_HTTP);
 		if (n < 0) {
 			fprintf(stderr, "ERROR writing to socket");
 			return -1;
@@ -724,8 +895,17 @@
 			    "Sec-WebSocket-Origin: ");
 		strcpy(p, wsi->utf8_token[WSI_TOKEN_ORIGIN].token);
 		p += wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len;
-		strcpy(p,   "\x0d\x0aSec-WebSocket-Location: ws://");
-		p += strlen("\x0d\x0aSec-WebSocket-Location: ws://");
+#ifdef LWS_OPENSSL_SUPPORT
+		if (use_ssl) {
+			strcpy(p,   "\x0d\x0aSec-WebSocket-Location: wss://");
+			p += strlen("\x0d\x0aSec-WebSocket-Location: wss://");
+		} else {
+#endif
+			strcpy(p,   "\x0d\x0aSec-WebSocket-Location: ws://");
+			p += strlen("\x0d\x0aSec-WebSocket-Location: ws://");
+#ifdef LWS_OPENSSL_SUPPORT
+		}
+#endif
 		strcpy(p, wsi->utf8_token[WSI_TOKEN_HOST].token);
 		p += wsi->utf8_token[WSI_TOKEN_HOST].token_len;
 		strcpy(p, wsi->utf8_token[WSI_TOKEN_GET_URI].token);
@@ -778,7 +958,8 @@
 #ifdef DEBUG
 		fwrite(response, 1,  p - response, stderr);
 #endif
-		n = write(wsi->sock, response, p - response);
+		n = libwebsocket_write(wsi, (unsigned char *)response, p - response,
+								LWS_WRITE_HTTP);
 		if (n < 0) {
 			fprintf(stderr, "ERROR writing to socket");
 			goto bail;
@@ -955,13 +1136,23 @@
 #endif
 
 send_raw:
-
-	n = send(wsi->sock, buf - pre, len + pre + post, 0);
-	if (n < 0) {
-		fprintf(stderr, "ERROR writing to socket");
-		return -1;
+#ifdef LWS_OPENSSL_SUPPORT
+	if (use_ssl) {
+		n = SSL_write(wsi->ssl, buf - pre, len + pre + post);
+		if (n < 0) {
+			fprintf(stderr, "ERROR writing to socket");
+			return -1;
+		}
+	} else {
+#endif
+		n = send(wsi->sock, buf - pre, len + pre + post, 0);
+		if (n < 0) {
+			fprintf(stderr, "ERROR writing to socket");
+			return -1;
+		}
+#ifdef LWS_OPENSSL_SUPPORT
 	}
-
+#endif
 //	fprintf(stderr, "written %d bytes to client\n", (int)len);
 	
 	return 0;
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index df351ac..a4a0111 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -22,7 +22,10 @@
 		  int (*callback)(struct libwebsocket *wsi,
 				  enum libwebsocket_callback_reasons reason,
 				  void *user, void *in, size_t len),
-					       int protocol, size_t user_space);
+					       int protocol, size_t user_space,
+					       const char * ssl_cert_filepath,
+					const char * ssl_private_key_filepath,
+							      int gid, int uid);
 
 /*
  * IMPORTANT NOTICE!
diff --git a/libwebsockets-api-doc.html b/libwebsockets-api-doc.html
index a75ddb2..0c2d8eb 100644
--- a/libwebsockets-api-doc.html
+++ b/libwebsockets-api-doc.html
@@ -4,7 +4,11 @@
 (<i>int</i> <b>port</b>,
 <i>int (*</i><b>callback</b>) <i>(struct libwebsocket *, 				enum libwebsocket_callback_reasons,  				void *, void *, size_t)</i>,
 <i>int</i> <b>protocol</b>,
-<i>size_t</i> <b>user_area_size</b>)
+<i>size_t</i> <b>user_area_size</b>,
+<i>const char *</i> <b>ssl_cert_filepath</b>,
+<i>const char *</i> <b>ssl_private_key_filepath</b>,
+<i>int</i> <b>gid</b>,
+<i>int</i> <b>uid</b>)
 <h3>Arguments</h3>
 <dl>
 <dt><b>port</b>
@@ -18,6 +22,17 @@
 which will be used by the user application to store
 per-session data.  A pointer to this space is given
 when the user callback is called.
+<dt><b>ssl_cert_filepath</b>
+<dd>If libwebsockets was compiled to use ssl, and you want
+to listen using SSL, set to the filepath to fetch the
+server cert from, otherwise NULL for unencrypted
+<dt><b>ssl_private_key_filepath</b>
+<dd>filepath to private key if wanting SSL mode,
+else ignored
+<dt><b>gid</b>
+<dd>group id to change to after setting listen socket, or -1.
+<dt><b>uid</b>
+<dd>user id to change to after setting listen socket, or -1.
 </dl>
 <h3>Description</h3>
 <blockquote>
diff --git a/test-server/Makefile b/test-server/Makefile
index 18dc56d..493f7b2 100644
--- a/test-server/Makefile
+++ b/test-server/Makefile
@@ -6,11 +6,20 @@
 		-o libwebsockets-test-server
 
 clean:
-	rm -f *.o libwebsockets-test-server
+	rm -f *.o libwebsockets-test-server *.pem
 
-install:
+install:	./libwebsockets-test-server.pem
 	cp -f libwebsockets-test-server $(DESTDIR)/usr/bin
 	mkdir -p $(DESTDIR)/usr/share/libwebsockets-test-server
-	cp -f favicon.ico test.html $(DESTDIR)/usr/share/libwebsockets-test-server
+	cp -a favicon.ico test.html libwebsockets-test-server.key.pem libwebsockets-test-server.pem $(DESTDIR)/usr/share/libwebsockets-test-server
 
+# notice!  You would want the key chmod 600 not 644 for real install!
+# this is just for test so you can run it without root easily
+
+./libwebsockets-test-server.pem:
+	printf "GB\nErewhon\nAll around\nlibwebsockets-test\n\nlocalhost\nnone@invalid.org\n" | \
+	openssl req -new -newkey rsa:1024 -days 10000 -nodes -x509 -keyout \
+	./libwebsockets-test-server.key.pem -out ./libwebsockets-test-server.pem >/dev/null 2>&1  && \
+	chmod 644 	./libwebsockets-test-server.key.pem \
+			./libwebsockets-test-server.pem
 
diff --git a/test-server/test-server.c b/test-server/test-server.c
index ae8deed..dfef268 100644
--- a/test-server/test-server.c
+++ b/test-server/test-server.c
@@ -17,6 +17,7 @@
 #define LOCAL_RESOURCE_PATH "/usr/share/libwebsockets-test-server"
 static int port = 7681;
 static int ws_protocol = 76;
+static int use_ssl = 0;
 
 struct per_session_data {
 	int number;
@@ -90,7 +91,7 @@
 	 */
 	case LWS_CALLBACK_SEND:	
 		n = sprintf(p, "%d", pss->number++);
-		n = libwebsocket_write(wsi, (unsigned char *)p, n, 0);
+		n = libwebsocket_write(wsi, (unsigned char *)p, n, LWS_WRITE_TEXT);
 		if (n < 0) {
 			fprintf(stderr, "ERROR writing to socket");
 			exit(1);
@@ -116,6 +117,9 @@
 	case LWS_CALLBACK_HTTP:
 
 		uri = libwebsocket_get_uri(wsi);
+
+		fprintf(stderr, "serving HTTP URI %s\n", uri);
+		
 		if (uri && strcmp(uri, "/favicon.ico") == 0) {
 			if (libwebsockets_serve_http_file(wsi,
 			     LOCAL_RESOURCE_PATH"/favicon.ico", "image/x-icon"))
@@ -139,12 +143,15 @@
 	{ "help", 	no_argument, NULL, 'h' },
 	{ "port", 	required_argument, NULL, 'p' },
 	{ "protocol", 	required_argument, NULL, 'r' },
+	{ "ssl", 	no_argument, NULL, 's' },
 	{ NULL, 0, 0, 0 }
 };
 
 int main(int argc, char **argv)
 {
 	int n = 0;
+	const char * cert_path = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem";
+	const char * key_path = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem";
 
 	fprintf(stderr, "libwebsockets test server\n"
 			"Copyright 2010 Andy Green <andy@warmcat.com> "
@@ -155,6 +162,9 @@
 		if (n < 0)
 			continue;
 		switch (n) {
+		case 's':
+			use_ssl = 1;
+			break;
 		case 'p':
 			port = atoi(optarg);
 			break;
@@ -167,9 +177,13 @@
 			exit(1);
 		}
 	}
+
+	if (!use_ssl)
+		cert_path = key_path = NULL;
 	
 	if (libwebsocket_create_server(port, websocket_callback, ws_protocol,
-					 sizeof(struct per_session_data)) < 0) {
+					 sizeof(struct per_session_data),
+					     cert_path, key_path, -1, -1) < 0) {
 		fprintf(stderr, "libwebsocket init failed\n");
 		return -1;
 	}
diff --git a/test-server/test.html b/test-server/test.html
index 905a0c5..57e335e 100644
--- a/test-server/test.html
+++ b/test-server/test.html
@@ -8,8 +8,19 @@
 <body>
 <script>
 	var pos = 0;
+	var websocket_ads;
 
-    var socket = new WebSocket("ws://127.0.0.1:7681");  
+	/*
+	 * We open the websocket encrypted if this page came on an
+	 * https:// url itself, otherwise unencrypted
+	 */
+
+	if (document.URL.substring(0, 5) == "https")
+		websocket_ads = "wss://127.0.0.1:7681";
+	else
+		websocket_ads = "ws://127.0.0.1:7681"
+	
+    var socket = new WebSocket(websocket_ads);  
 
 	try {
 //        alert('<p class="event">Socket Status: '+socket.readyState);