Implement https support.
authorLuigi Rizzo <rizzo@icir.org>
Sun, 22 Oct 2006 12:02:35 +0000 (12:02 +0000)
committerLuigi Rizzo <rizzo@icir.org>
Sun, 22 Oct 2006 12:02:35 +0000 (12:02 +0000)
The changes are not large. Most of the diff comes from putting the
global variables describing an accept session into a structure, so
we can reuse the existing code for running multiple accept threads
on different ports.

Once this is done, and if your system has the funopen() library
function (and ssl, of course), it is just a matter of calling
the appropriate functions to set up the ssl connection on the
existing socket, and everything works on the secure channel now.

At the moment, the code is disabled because i have not implemented yet
the autoconf code to detect the presence of funopen(), and add -lssl
to main/Makefile if ssl libraries are present. And a bit of documentation
on the http.conf arguments, too.

If you want to manually enable https support, that is very simple
(step 0 1 2 will be eventually detected by ./configure, the
rest is something you will have to do anyways).

0. make sure your system has funopen(3). FreeBSD does, linux probably
   does too, not sure about other systems.

1. uncomment the following line in main/http.c
   // #define      DO_SSL  /* comment in/out if you want to support ssl */

2. add -lssl to AST_LIBS in main/Makefile

3. add the following options to http.conf

sslenable=yes
sslbindport=4433 ; pick one you like
sslcert=/tmp/foo.pem ; path to your certificate file.

4. generate a suitable certificate e.g. (example from mini_httpd's Makefile:

openssl req -new -x509 -days 365 -nodes -out /tmp/foo.pem -keyout /tmp/foo.pem

and here you go:

https://localhost:4433/asterisk/manager

now works.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@45869 65c4cc65-6c06-0410-ace0-fbb531ad65f3

main/http.c

index 5bb1df2..10b8557 100644 (file)
@@ -57,21 +57,73 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #define MAX_PREFIX 80
 #define DEFAULT_PREFIX "/asterisk"
 
-struct ast_http_server_instance {
-       FILE *f;
-       int fd;
+/*!
+ * In order to have SSL support, we need the openssl libraries.
+ * Still we can decide whether or not to use them by commenting
+ * in or out the DO_SSL macro.
+ * We declare most of ssl support variables unconditionally,
+ * because their number is small and this simplifies the code.
+ */
+#ifdef HAVE_OPENSSL
+// #define     DO_SSL  /* comment in/out if you want to support ssl */
+#endif
+
+#ifdef DO_SSL
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+SSL_CTX* ssl_ctx;
+#endif /* DO_SSL */
+
+/* SSL support */
+#define AST_CERTFILE "asterisk.pem"
+static int do_ssl;
+static char *certfile;
+static char *cipher;
+
+/*!
+ * describes a server instance
+ */
+struct server_instance {
+       FILE *f;        /* fopen/funopen result */
+       int fd;         /* the socket returned by accept() */
+       int is_ssl;     /* is this an ssl session ? */
+#ifdef DO_SSL
+       SSL *ssl;       /* ssl state */
+#endif
        struct sockaddr_in requestor;
        ast_http_callback callback;
 };
 
-static struct ast_http_uri *uris;
+/*!
+ * arguments for the accepting thread
+ */
+struct server_args {
+       struct sockaddr_in sin;
+       struct sockaddr_in oldsin;
+       int is_ssl;             /* is this an SSL accept ? */
+       int accept_fd;
+       pthread_t master;
+};
+
+/*!
+ * we have up to two accepting threads, one for http, one for https
+ */
+static struct server_args http_desc = {
+       .accept_fd = -1,
+       .master = AST_PTHREADT_NULL,
+       .is_ssl = 0,
+};
+
+static struct server_args https_desc = {
+       .accept_fd = -1,
+       .master = AST_PTHREADT_NULL,
+       .is_ssl = 1,
+};
 
-static int httpfd = -1;
-static pthread_t master = AST_PTHREADT_NULL;
+static struct ast_http_uri *uris;      /*!< list of supported handlers */
 
 /* all valid URIs must be prepended by the string in prefix. */
 static char prefix[MAX_PREFIX];
-static struct sockaddr_in oldsin;
 static int enablestatic=0;
 
 /*! \brief Limit the kinds of files we're willing to serve up */
@@ -196,9 +248,12 @@ static char *httpstatus_callback(struct sockaddr_in *req, const char *uri, struc
 
        ast_build_string(&c, &reslen, "<tr><td><i>Prefix</i></td><td><b>%s</b></td></tr>\r\n", prefix);
        ast_build_string(&c, &reslen, "<tr><td><i>Bind Address</i></td><td><b>%s</b></td></tr>\r\n",
-                       ast_inet_ntoa(oldsin.sin_addr));
+                       ast_inet_ntoa(http_desc.oldsin.sin_addr));
        ast_build_string(&c, &reslen, "<tr><td><i>Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
-                       ntohs(oldsin.sin_port));
+                       ntohs(http_desc.oldsin.sin_port));
+       if (do_ssl)
+               ast_build_string(&c, &reslen, "<tr><td><i>SSL Bind Port</i></td><td><b>%d</b></td></tr>\r\n",
+                       ntohs(https_desc.oldsin.sin_port));
        ast_build_string(&c, &reslen, "<tr><td colspan=\"2\"><hr></td></tr>\r\n");
        v = vars;
        while(v) {
@@ -372,15 +427,73 @@ static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **
        return c;
 }
 
+#ifdef DO_SSL
+/*!
+ * replacement read/write functions for SSL support.
+ * We use wrappers rather than SSL_read/SSL_write directly so
+ * we can put in some debugging.
+ */
+static int ssl_read(void *cookie, char *buf, int len)
+{
+       int i;
+       i = SSL_read(cookie, buf, len-1);
+#if 0
+       if (i >= 0)
+               buf[i] = '\0';
+       ast_verbose("ssl read size %d returns %d <%s>\n", len, i, buf);
+#endif
+       return i;
+}
+
+static int ssl_write(void *cookie, const char *buf, int len)
+{
+#if 0
+       char *s = alloca(len+1);
+       strncpy(s, buf, len);
+       s[len] = '\0';
+       ast_verbose("ssl write size %d <%s>\n", len, s);
+#endif
+       return SSL_write(cookie, buf, len);
+}
+
+static int ssl_close(void *cookie)
+{
+       close(SSL_get_fd(cookie));
+       SSL_shutdown(cookie);
+       SSL_free(cookie);
+       return 0;
+}
+#endif
+
 static void *ast_httpd_helper_thread(void *data)
 {
        char buf[4096];
        char cookie[4096];
-       struct ast_http_server_instance *ser = data;
+       struct server_instance *ser = data;
        struct ast_variable *var, *prev=NULL, *vars=NULL;
        char *uri, *c, *title=NULL;
        int status = 200, contentlength = 0;
 
+#ifdef DO_SSL
+       if (ser->is_ssl) {
+               ser->ssl = SSL_new(ssl_ctx);
+               SSL_set_fd(ser->ssl, ser->fd);
+               if (SSL_accept(ser->ssl) == 0) {
+                       ast_verbose(" error setting up ssl connection");
+                       goto done;
+               }
+               ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close);
+       } else
+#endif
+       ser->f = fdopen(ser->fd, "w+");
+
+       if (!ser->f) {
+               ast_log(LOG_WARNING, "fdopen/funopen failed!\n");
+               close(ser->fd);
+               free(ser);
+               return NULL;
+       }
+
        if (!fgets(buf, sizeof(buf), ser->f))
                goto done;
 
@@ -499,19 +612,20 @@ done:
 
 static void *http_root(void *data)
 {
+       struct server_args *desc = data;
        int fd;
        struct sockaddr_in sin;
        socklen_t sinlen;
-       struct ast_http_server_instance *ser;
+       struct server_instance *ser;
        pthread_t launched;
        pthread_attr_t attr;
        
        for (;;) {
                int flags;
 
-               ast_wait_for_input(httpfd, -1);
+               ast_wait_for_input(desc->accept_fd, -1);
                sinlen = sizeof(sin);
-               fd = accept(httpfd, (struct sockaddr *)&sin, &sinlen);
+               fd = accept(desc->accept_fd, (struct sockaddr *)&sin, &sinlen);
                if (fd < 0) {
                        if ((errno != EAGAIN) && (errno != EINTR))
                                ast_log(LOG_WARNING, "Accept failed: %s\n", strerror(errno));
@@ -526,18 +640,14 @@ static void *http_root(void *data)
                flags = fcntl(fd, F_GETFL);
                fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
                ser->fd = fd;
+               ser->is_ssl = desc->is_ssl;
                memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
-               if ((ser->f = fdopen(ser->fd, "w+"))) {
-                       pthread_attr_init(&attr);
-                       pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+               pthread_attr_init(&attr);
+               pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
                        
-                       if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
-                               ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
-                               fclose(ser->f);
-                               free(ser);
-                       }
-               } else {
-                       ast_log(LOG_WARNING, "fdopen failed!\n");
+               if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
+                       ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
                        close(ser->fd);
                        free(ser);
                }
@@ -556,66 +666,101 @@ char *ast_http_setcookie(const char *var, const char *val, int expires, char *bu
        return buf;
 }
 
+static int ssl_setup(void)
+{
+#ifndef DO_SSL
+       do_ssl = 0;
+       return 0;
+#else
+       if (!do_ssl)
+               return 0;
+       SSL_load_error_strings();
+       SSLeay_add_ssl_algorithms();
+       ssl_ctx = SSL_CTX_new( SSLv23_server_method() );
+       if (!ast_strlen_zero(certfile)) {
+               if (SSL_CTX_use_certificate_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) == 0 ||
+                   SSL_CTX_use_PrivateKey_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) == 0 ||
+                   SSL_CTX_check_private_key(ssl_ctx) == 0 ) {
+                       ast_verbose("ssl cert error <%s>", certfile);
+                       sleep(2);
+                       do_ssl = 0;
+                       return 0;
+               }
+       }
+       if (!ast_strlen_zero(cipher)) {
+               if (SSL_CTX_set_cipher_list(ssl_ctx, cipher) == 0 ) {
+                       ast_verbose("ssl cipher error <%s>", cipher);
+                       sleep(2);
+                       do_ssl = 0;
+                       return 0;
+               }
+       }
+       ast_verbose("ssl cert ok");
+       return 1;
+#endif
+}
 
-static void http_server_start(struct sockaddr_in *sin)
+static void http_server_start(struct server_args *desc)
 {
        int flags;
        int x = 1;
        
        /* Do nothing if nothing has changed */
-       if (!memcmp(&oldsin, sin, sizeof(oldsin))) {
+       if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
                if (option_debug)
                        ast_log(LOG_DEBUG, "Nothing changed in http\n");
                return;
        }
        
-       memcpy(&oldsin, sin, sizeof(oldsin));
+       desc->oldsin = desc->sin;
        
        /* Shutdown a running server if there is one */
-       if (master != AST_PTHREADT_NULL) {
-               pthread_cancel(master);
-               pthread_kill(master, SIGURG);
-               pthread_join(master, NULL);
+       if (desc->master != AST_PTHREADT_NULL) {
+               pthread_cancel(desc->master);
+               pthread_kill(desc->master, SIGURG);
+               pthread_join(desc->master, NULL);
        }
        
-       if (httpfd != -1)
-               close(httpfd);
+       if (desc->accept_fd != -1)
+               close(desc->accept_fd);
 
        /* If there's no new server, stop here */
-       if (!sin->sin_family)
+       if (desc->sin.sin_family == 0)
                return;
        
        
-       httpfd = socket(AF_INET, SOCK_STREAM, 0);
-       if (httpfd < 0) {
+       desc->accept_fd = socket(AF_INET, SOCK_STREAM, 0);
+       if (desc->accept_fd < 0) {
                ast_log(LOG_WARNING, "Unable to allocate socket: %s\n", strerror(errno));
                return;
        }
        
-       setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
-       if (bind(httpfd, (struct sockaddr *)sin, sizeof(*sin))) {
+       setsockopt(desc->accept_fd, SOL_SOCKET, SO_REUSEADDR, &x, sizeof(x));
+       if (bind(desc->accept_fd, (struct sockaddr *)&desc->sin, sizeof(desc->sin))) {
                ast_log(LOG_NOTICE, "Unable to bind http server to %s:%d: %s\n",
-                       ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
+                       ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
                        strerror(errno));
-               close(httpfd);
-               httpfd = -1;
-               return;
+               goto error;
        }
-       if (listen(httpfd, 10)) {
+       if (listen(desc->accept_fd, 10)) {
                ast_log(LOG_NOTICE, "Unable to listen!\n");
-               close(httpfd);
-               httpfd = -1;
+               close(desc->accept_fd);
+               desc->accept_fd = -1;
                return;
        }
-       flags = fcntl(httpfd, F_GETFL);
-       fcntl(httpfd, F_SETFL, flags | O_NONBLOCK);
-       if (ast_pthread_create_background(&master, NULL, http_root, NULL)) {
+       flags = fcntl(desc->accept_fd, F_GETFL);
+       fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
+       if (ast_pthread_create_background(&desc->master, NULL, http_root, desc)) {
                ast_log(LOG_NOTICE, "Unable to launch http server on %s:%d: %s\n",
-                               ast_inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
+                               ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
                                strerror(errno));
-               close(httpfd);
-               httpfd = -1;
+               goto error;
        }
+       return;
+
+error:
+       close(desc->accept_fd);
+       desc->accept_fd = -1;
 }
 
 static int __ast_http_load(int reload)
@@ -624,27 +769,49 @@ static int __ast_http_load(int reload)
        struct ast_variable *v;
        int enabled=0;
        int newenablestatic=0;
-       struct sockaddr_in sin;
        struct hostent *hp;
        struct ast_hostent ahp;
        char newprefix[MAX_PREFIX];
 
-       memset(&sin, 0, sizeof(sin));
-       sin.sin_port = htons(8088);
+       memset(&http_desc.sin, 0, sizeof(http_desc.sin));
+       http_desc.sin.sin_port = htons(8088);
+       memset(&https_desc.sin, 0, sizeof(https_desc.sin));
+       https_desc.sin.sin_port = htons(8089);
        strcpy(newprefix, DEFAULT_PREFIX);
        cfg = ast_config_load("http.conf");
+
+       do_ssl = 0;
+       if (certfile)
+               free(certfile);
+       certfile = ast_strdup(AST_CERTFILE);
+       if (cipher)
+               free(cipher);
+       cipher = ast_strdup("");
+
        if (cfg) {
                v = ast_variable_browse(cfg, "general");
                while(v) {
                        if (!strcasecmp(v->name, "enabled"))
                                enabled = ast_true(v->value);
+                       else if (!strcasecmp(v->name, "sslenable"))
+                               do_ssl = ast_true(v->value);
+                       else if (!strcasecmp(v->name, "sslbindport"))
+                               https_desc.sin.sin_port = htons(atoi(v->value));
+                       else if (!strcasecmp(v->name, "sslcert")) {
+                               free(certfile);
+                               certfile = ast_strdup(v->value);
+                       } else if (!strcasecmp(v->name, "sslcipher")) {
+                               free(cipher);
+                               cipher = ast_strdup(v->value);
+                       }
                        else if (!strcasecmp(v->name, "enablestatic"))
                                newenablestatic = ast_true(v->value);
                        else if (!strcasecmp(v->name, "bindport"))
-                               sin.sin_port = htons(atoi(v->value));
+                               http_desc.sin.sin_port = htons(atoi(v->value));
                        else if (!strcasecmp(v->name, "bindaddr")) {
                                if ((hp = ast_gethostbyname(v->value, &ahp))) {
-                                       memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr));
+                                       memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
+                                       memcpy(&https_desc.sin.sin_addr, hp->h_addr, sizeof(https_desc.sin.sin_addr));
                                } else {
                                        ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
                                }
@@ -662,11 +829,13 @@ static int __ast_http_load(int reload)
                ast_config_destroy(cfg);
        }
        if (enabled)
-               sin.sin_family = AF_INET;
+               http_desc.sin.sin_family = https_desc.sin.sin_family = AF_INET;
        if (strcmp(prefix, newprefix))
                ast_copy_string(prefix, newprefix, sizeof(prefix));
        enablestatic = newenablestatic;
-       http_server_start(&sin);
+       http_server_start(&http_desc);
+       if (ssl_setup())
+               http_server_start(&https_desc);
        return 0;
 }
 
@@ -677,12 +846,17 @@ static int handle_show_http(int fd, int argc, char *argv[])
                return RESULT_SHOWUSAGE;
        ast_cli(fd, "HTTP Server Status:\n");
        ast_cli(fd, "Prefix: %s\n", prefix);
-       if (oldsin.sin_family)
-               ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
-                       ast_inet_ntoa(oldsin.sin_addr),
-                       ntohs(oldsin.sin_port));
-       else
+       if (!http_desc.oldsin.sin_family)
                ast_cli(fd, "Server Disabled\n\n");
+       else {
+               ast_cli(fd, "Server Enabled and Bound to %s:%d\n\n",
+                       ast_inet_ntoa(http_desc.oldsin.sin_addr),
+                       ntohs(http_desc.oldsin.sin_port));
+               if (do_ssl)
+                       ast_cli(fd, "HTTPS Server Enabled and Bound to %s:%d\n\n",
+                               ast_inet_ntoa(https_desc.oldsin.sin_addr),
+                               ntohs(https_desc.oldsin.sin_port));
+       }
        ast_cli(fd, "Enabled URI's:\n");
        urih = uris;
        while(urih){