add-wss-ssl-openssl-support.patch
authorAndy Green <andy@warmcat.com>
Mon, 8 Nov 2010 17:03:03 +0000 (17:03 +0000)
committerAndy Green <andy@warmcat.com>
Mon, 8 Nov 2010 17:03:03 +0000 (17:03 +0000)
Signed-off-by: Andy Green <andy@warmcat.com>
Makefile
lib/Makefile
lib/libwebsockets.c
lib/libwebsockets.h
libwebsockets-api-doc.html
test-server/Makefile
test-server/test-server.c
test-server/test.html

index 3aa2237..dda1115 100644 (file)
--- 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 @@ install:
        make -C lib install
        make -C test-server install
 
+gencert:
+       make -C test-server gencert
        
index be381c3..a84373a 100644 (file)
@@ -3,7 +3,7 @@ export LDIR=$(shell if [ -z "gcc --print-search-dirs | grep libraries | sed s/\\
 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
index 065cb01..9c798e4 100644 (file)
 #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 @@ libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len);
 #define LWS_ADDITIONAL_HDR_ALLOC 64
 
 
+
 enum lws_connection_states {
        WSI_STATE_HTTP,
        WSI_STATE_HTTP_HEADERS,
@@ -108,6 +120,26 @@ struct libwebsocket {
        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 @@ libwebsocket_close_and_free_session(struct libwebsocket *wsi)
 
 //     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 @@ libwebsocket_close_and_free_session(struct libwebsocket *wsi)
  *                     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 libwebsocket_create_server(int port,
                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 @@ int libwebsocket_create_server(int port,
        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 @@ int libwebsocket_create_server(int port,
        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 @@ int libwebsocket_create_server(int port,
 
                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 @@ int libwebsocket_create_server(int port,
                                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 @@ poll_out:
        }
        
 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 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c)
                                "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 @@ libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len)
                            "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 @@ libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len)
 #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 @@ int libwebsocket_write(struct libwebsocket * wsi, unsigned char *buf,
 #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;
index df351ac..a4a0111 100644 (file)
@@ -22,7 +22,10 @@ extern int libwebsocket_create_server(int port,
                  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!
index a75ddb2..0c2d8eb 100644 (file)
@@ -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>
 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>
index 18dc56d..493f7b2 100644 (file)
@@ -6,11 +6,20 @@ all:
                -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
 
index ae8deed..dfef268 100644 (file)
@@ -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 @@ static int websocket_callback(struct libwebsocket * wsi,
         */
        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 @@ static int websocket_callback(struct libwebsocket * wsi,
        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 @@ static struct option options[] = {
        { "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 @@ int main(int argc, char **argv)
                if (n < 0)
                        continue;
                switch (n) {
+               case 's':
+                       use_ssl = 1;
+                       break;
                case 'p':
                        port = atoi(optarg);
                        break;
@@ -167,9 +177,13 @@ int main(int argc, char **argv)
                        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;
        }
index 905a0c5..57e335e 100644 (file)
@@ -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);