add a new http.conf option, sslbindaddr.
[asterisk/asterisk.git] / main / http.c
index 10b8557..96cb8d1 100644 (file)
@@ -63,15 +63,16 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
  * 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.
+ * XXX this eventually goes into a generic header file.
  */
-#ifdef HAVE_OPENSSL
-// #define     DO_SSL  /* comment in/out if you want to support ssl */
+#if defined(HAVE_OPENSSL) && (defined(HAVE_FUNOPEN) || defined(HAVE_FOPENCOOKIE))
+#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;
+static SSL_CTX* ssl_ctx;
 #endif /* DO_SSL */
 
 /* SSL support */
@@ -91,7 +92,7 @@ struct server_instance {
        SSL *ssl;       /* ssl state */
 #endif
        struct sockaddr_in requestor;
-       ast_http_callback callback;
+       struct server_args *parent;
 };
 
 /*!
@@ -103,8 +104,14 @@ struct server_args {
        int is_ssl;             /* is this an SSL accept ? */
        int accept_fd;
        pthread_t master;
+       void *(*accept_fn)(void *);     /* the function in charge of doing the accept */
+       void *(*worker_fn)(void *);     /* the function in charge of doing the actual work */
+       const char *name;
 };
 
+static void *http_root(void *arg);
+static void *httpd_helper_thread(void *arg);
+
 /*!
  * we have up to two accepting threads, one for http, one for https
  */
@@ -112,12 +119,18 @@ static struct server_args http_desc = {
        .accept_fd = -1,
        .master = AST_PTHREADT_NULL,
        .is_ssl = 0,
+       .name = "http server",
+       .accept_fn = http_root,
+       .worker_fn = httpd_helper_thread,
 };
 
 static struct server_args https_desc = {
        .accept_fd = -1,
        .master = AST_PTHREADT_NULL,
        .is_ssl = 1,
+       .name = "https server",
+       .accept_fn = http_root,
+       .worker_fn = httpd_helper_thread,
 };
 
 static struct ast_http_uri *uris;      /*!< list of supported handlers */
@@ -428,30 +441,36 @@ static char *handle_uri(struct sockaddr_in *sin, char *uri, int *status, char **
 }
 
 #ifdef DO_SSL
+#if defined(HAVE_FUNOPEN)
+#define HOOK_T int
+#define LEN_T int
+#else
+#define HOOK_T ssize_t
+#define LEN_T size_t
+#endif
 /*!
  * 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)
+static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
 {
-       int i;
-       i = SSL_read(cookie, buf, len-1);
+       int 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);
+       ast_verbose("ssl read size %d returns %d <%s>\n", (int)len, i, buf);
 #endif
        return i;
 }
 
-static int ssl_write(void *cookie, const char *buf, int len)
+static HOOK_T ssl_write(void *cookie, const char *buf, LEN_T 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);
+       ast_verbose("ssl write size %d <%s>\n", (int)len, s);
 #endif
        return SSL_write(cookie, buf, len);
 }
@@ -463,36 +482,62 @@ static int ssl_close(void *cookie)
        SSL_free(cookie);
        return 0;
 }
-#endif
+#endif /* DO_SSL */
 
-static void *ast_httpd_helper_thread(void *data)
+/*!
+ * creates a FILE * from the fd passed by the accept thread.
+ * This operation is potentially expensive (certificate verification),
+ * so we do it in the child thread context.
+ */
+static void *make_file_from_fd(void *data)
 {
-       char buf[4096];
-       char cookie[4096];
        struct server_instance *ser = data;
-       struct ast_variable *var, *prev=NULL, *vars=NULL;
-       char *uri, *c, *title=NULL;
-       int status = 200, contentlength = 0;
 
+       /*
+        * open a FILE * as appropriate.
+        */
+       if (!ser->is_ssl)
+               ser->f = fdopen(ser->fd, "w+");
 #ifdef DO_SSL
-       if (ser->is_ssl) {
-               ser->ssl = SSL_new(ssl_ctx);
+       else if ( (ser->ssl = SSL_new(ssl_ctx)) ) {
                SSL_set_fd(ser->ssl, ser->fd);
-               if (SSL_accept(ser->ssl) == 0) {
+               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
+               else {
+#if defined(HAVE_FUNOPEN)      /* the BSD interface */
+                       ser->f = funopen(ser->ssl, ssl_read, ssl_write, NULL, ssl_close);
+
+#elif defined(HAVE_FOPENCOOKIE)        /* the glibc/linux interface */
+                       static const cookie_io_functions_t cookie_funcs = {
+                               ssl_read, ssl_write, NULL, ssl_close
+                       };
+                       ser->f = fopencookie(ser->ssl, "w+", cookie_funcs);
+#else
+                       /* could add other methods here */
 #endif
-       ser->f = fdopen(ser->fd, "w+");
+               }
+               if (!ser->f)    /* no success opening descriptor stacking */
+                       SSL_free(ser->ssl);
+       }
+#endif /* DO_SSL */
 
        if (!ser->f) {
-               ast_log(LOG_WARNING, "fdopen/funopen failed!\n");
                close(ser->fd);
+               ast_log(LOG_WARNING, "FILE * open failed!\n");
                free(ser);
                return NULL;
        }
+       return ser->parent->worker_fn(ser);
+}
+
+static void *httpd_helper_thread(void *data)
+{
+       char buf[4096];
+       char cookie[4096];
+       struct server_instance *ser = data;
+       struct ast_variable *var, *prev=NULL, *vars=NULL;
+       char *uri, *c, *title=NULL;
+       int status = 200, contentlength = 0;
 
        if (!fgets(buf, sizeof(buf), ser->f))
                goto done;
@@ -605,7 +650,8 @@ static void *ast_httpd_helper_thread(void *data)
                free(title);
 
 done:
-       fclose(ser->f);
+       if (ser->f)
+               fclose(ser->f);
        free(ser);
        return NULL;
 }
@@ -641,12 +687,13 @@ static void *http_root(void *data)
                fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
                ser->fd = fd;
                ser->is_ssl = desc->is_ssl;
+               ser->parent = desc;
                memcpy(&ser->requestor, &sin, sizeof(ser->requestor));
 
                pthread_attr_init(&attr);
                pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
                        
-               if (ast_pthread_create_background(&launched, &attr, ast_httpd_helper_thread, ser)) {
+               if (ast_pthread_create_background(&launched, &attr, make_file_from_fd, ser)) {
                        ast_log(LOG_WARNING, "Unable to launch helper thread: %s\n", strerror(errno));
                        close(ser->fd);
                        free(ser);
@@ -700,7 +747,12 @@ static int ssl_setup(void)
 #endif
 }
 
-static void http_server_start(struct server_args *desc)
+/*!
+ * This is a generic (re)start routine for a TCP server,
+ * which does the socket/bind/listen and starts a thread for handling
+ * accept().
+ */
+static void server_start(struct server_args *desc)
 {
        int flags;
        int x = 1;
@@ -708,7 +760,7 @@ static void http_server_start(struct server_args *desc)
        /* Do nothing if nothing has changed */
        if (!memcmp(&desc->oldsin, &desc->sin, sizeof(desc->oldsin))) {
                if (option_debug)
-                       ast_log(LOG_DEBUG, "Nothing changed in http\n");
+                       ast_log(LOG_DEBUG, "Nothing changed in %s\n", desc->name);
                return;
        }
        
@@ -727,33 +779,33 @@ static void http_server_start(struct server_args *desc)
        /* If there's no new server, stop here */
        if (desc->sin.sin_family == 0)
                return;
-       
-       
+
        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));
+               ast_log(LOG_WARNING, "Unable to allocate socket for %s: %s\n",
+                       desc->name, strerror(errno));
                return;
        }
        
        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_log(LOG_NOTICE, "Unable to bind %s to %s:%d: %s\n",
+                       desc->name,
                        ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
                        strerror(errno));
                goto error;
        }
        if (listen(desc->accept_fd, 10)) {
-               ast_log(LOG_NOTICE, "Unable to listen!\n");
-               close(desc->accept_fd);
-               desc->accept_fd = -1;
-               return;
+               ast_log(LOG_NOTICE, "Unable to listen for %s!\n", desc->name);
+               goto error;
        }
        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(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
-                               strerror(errno));
+       if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {
+               ast_log(LOG_NOTICE, "Unable to launch %s on %s:%d: %s\n",
+                       desc->name,
+                       ast_inet_ntoa(desc->sin.sin_addr), ntohs(desc->sin.sin_port),
+                       strerror(errno));
                goto error;
        }
        return;
@@ -772,9 +824,12 @@ static int __ast_http_load(int reload)
        struct hostent *hp;
        struct ast_hostent ahp;
        char newprefix[MAX_PREFIX];
+       int have_sslbindaddr = 0;
 
+       /* default values */
        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);
@@ -808,10 +863,16 @@ static int __ast_http_load(int reload)
                                newenablestatic = ast_true(v->value);
                        else if (!strcasecmp(v->name, "bindport"))
                                http_desc.sin.sin_port = htons(atoi(v->value));
-                       else if (!strcasecmp(v->name, "bindaddr")) {
+                       else if (!strcasecmp(v->name, "sslbindaddr")) {
                                if ((hp = ast_gethostbyname(v->value, &ahp))) {
-                                       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));
+                                       have_sslbindaddr = 1;
+                               } else {
+                                       ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
+                               }
+                       } else if (!strcasecmp(v->name, "bindaddr")) {
+                               if ((hp = ast_gethostbyname(v->value, &ahp))) {
+                                       memcpy(&http_desc.sin.sin_addr, hp->h_addr, sizeof(http_desc.sin.sin_addr));
                                } else {
                                        ast_log(LOG_WARNING, "Invalid bind address '%s'\n", v->value);
                                }
@@ -828,14 +889,16 @@ static int __ast_http_load(int reload)
                }
                ast_config_destroy(cfg);
        }
+       if (!have_sslbindaddr)
+               https_desc.sin.sin_addr = http_desc.sin.sin_addr;
        if (enabled)
                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(&http_desc);
+       server_start(&http_desc);
        if (ssl_setup())
-               http_server_start(&https_desc);
+               server_start(&https_desc);
        return 0;
 }
 
@@ -874,11 +937,11 @@ int ast_http_reload(void)
 }
 
 static char show_http_help[] =
-"Usage: http list status\n"
+"Usage: http show status\n"
 "       Lists status of internal HTTP engine\n";
 
 static struct ast_cli_entry cli_http[] = {
-       { { "http", "list", "status", NULL },
+       { { "http", "show", "status", NULL },
        handle_show_http, "Display HTTP server status",
        show_http_help },
 };