[asterisk-commits] bebuild: tag certified-1.8.15-cert6 r415973 - in /certified/tags/1.8.15-cert6...
SVN commits to the Asterisk project
asterisk-commits at lists.digium.com
Thu Jun 12 13:54:03 CDT 2014
Author: bebuild
Date: Thu Jun 12 13:53:58 2014
New Revision: 415973
URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=415973
Log:
Merge changes for AST-2014-007
Modified:
certified/tags/1.8.15-cert6/ (props changed)
certified/tags/1.8.15-cert6/ChangeLog
certified/tags/1.8.15-cert6/UPGRADE.txt
certified/tags/1.8.15-cert6/channels/chan_sip.c
certified/tags/1.8.15-cert6/configs/http.conf.sample
certified/tags/1.8.15-cert6/include/asterisk/tcptls.h
certified/tags/1.8.15-cert6/include/asterisk/time.h
certified/tags/1.8.15-cert6/include/asterisk/utils.h
certified/tags/1.8.15-cert6/main/http.c
certified/tags/1.8.15-cert6/main/manager.c
certified/tags/1.8.15-cert6/main/tcptls.c
certified/tags/1.8.15-cert6/main/utils.c
Propchange: certified/tags/1.8.15-cert6/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Thu Jun 12 13:53:58 2014
@@ -1,3 +1,3 @@
/branches/1.8:371919
/certified/branches/1.8.11:376302
-/certified/branches/1.8.15:397754,397761,403858,403914,410358,410427
+/certified/branches/1.8.15:397754,397761,403858,403914,410358,410427,415972
Modified: certified/tags/1.8.15-cert6/ChangeLog
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/ChangeLog?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/ChangeLog (original)
+++ certified/tags/1.8.15-cert6/ChangeLog Thu Jun 12 13:53:58 2014
@@ -1,3 +1,46 @@
+2014-06-12 Asterisk Development Team <asteriskteam at digium.com>
+
+ * AST-2014-007: Fix DOS by consuming the number of allowed HTTP
+ connections.
+
+ Simply establishing a TCP connection and never sending anything to
+ the configured HTTP port in http.conf will tie up a HTTP connection.
+ Since there is a maximum number of open HTTP sessions allowed at a
+ time you can block legitimate connections.
+
+ A similar problem exists if a HTTP request is started but never
+ finished.
+
+ * Added http.conf session_inactivity timer option to close HTTP
+ connections that aren't doing anything. Defaults to 30000 ms.
+
+ * Removed the undocumented manager.conf block-sockets option. It
+ interferes with TCP/TLS inactivity timeouts.
+
+ * AMI and SIP TLS connections now have better authentication timeout
+ protection. Though I didn't remove the bizzare TLS timeout polling
+ code from chan_sip.
+
+ * chan_sip can now handle SSL certificate renegotiations in the
+ middle of a session. It couldn't do that before because the socket
+ was non-blocking and the SSL calls were not restarted as documented
+ by the OpenSSL documentation.
+
+ * Fixed an off nominal leak of the ssl struct in
+ handle_tcptls_connection() if the FILE stream failed to open and
+ the SSL certificate negotiations failed.
+
+ The patch creates a custom FILE stream handler to give the created FILE
+ streams inactivity timeout and timeout after a specific moment in time
+ capability. This approach eliminates the need for code using the FILE
+ stream to be redesigned to deal with the timeouts.
+
+ This patch indirectly fixes most of ASTERISK-18345 by fixing the usage
+ of the SSL_read/SSL_write operations.
+
+ ASTERISK-23673 #close
+ Reported by: Richard Mudgett
+
2014-03-10 Asterisk Development Team <asteriskteam at digium.com>
* Certified Asterisk 1.8.15-cert5 Released.
Modified: certified/tags/1.8.15-cert6/UPGRADE.txt
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/UPGRADE.txt?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/UPGRADE.txt (original)
+++ certified/tags/1.8.15-cert6/UPGRADE.txt Thu Jun 12 13:53:58 2014
@@ -18,6 +18,14 @@
===
===========================================================
+from 1.8.15-cert5 to 1.8.15-cert6
+
+* Added http.conf session_inactivity timer option to close HTTP connections
+ that aren't doing anything.
+
+* Removed the undocumented manager.conf block-sockets option. It interferes with
+ TCP/TLS inactivity timeouts.
+
from 1.8.15-cert3 to 1.8.15-cert4
* Certain dialplan functions have been marked as 'dangerous', and may only be
executed from the dialplan. Execution from extenal sources (AMI's GetVar and
Modified: certified/tags/1.8.15-cert6/channels/chan_sip.c
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/channels/chan_sip.c?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/channels/chan_sip.c (original)
+++ certified/tags/1.8.15-cert6/channels/chan_sip.c Thu Jun 12 13:53:58 2014
@@ -2614,11 +2614,15 @@
goto cleanup;
}
+ ast_tcptls_stream_set_timeout_sequence(tcptls_session->stream_cookie, ast_tvnow(),
+ tcptls_session->client ? -1 : (authtimeout * 1000));
+
for (;;) {
struct ast_str *str_save;
if (!tcptls_session->client && req.authenticated && !authenticated) {
authenticated = 1;
+ ast_tcptls_stream_set_timeout_disable(tcptls_session->stream_cookie);
ast_atomic_fetchadd_int(&unauth_sessions, -1);
}
Modified: certified/tags/1.8.15-cert6/configs/http.conf.sample
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/configs/http.conf.sample?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/configs/http.conf.sample (original)
+++ certified/tags/1.8.15-cert6/configs/http.conf.sample Thu Jun 12 13:53:58 2014
@@ -36,6 +36,12 @@
; allowed to exist at any given time. (default: 100)
;
;sessionlimit=100
+;
+; session_inactivity specifies the number of milliseconds to wait for
+; more data over the HTTP connection before closing it.
+;
+; Default: 30000
+;session_inactivity=30000
;
; Whether Asterisk should serve static content from http-static
; Default is no.
Modified: certified/tags/1.8.15-cert6/include/asterisk/tcptls.h
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/include/asterisk/tcptls.h?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/include/asterisk/tcptls.h (original)
+++ certified/tags/1.8.15-cert6/include/asterisk/tcptls.h Thu Jun 12 13:53:58 2014
@@ -136,6 +136,51 @@
const char *name;
};
+struct ast_tcptls_stream;
+
+/*!
+ * \brief Disable the TCP/TLS stream timeout timer.
+ *
+ * \param stream TCP/TLS stream control data.
+ *
+ * \return Nothing
+ */
+void ast_tcptls_stream_set_timeout_disable(struct ast_tcptls_stream *stream);
+
+/*!
+ * \brief Set the TCP/TLS stream inactivity timeout timer.
+ *
+ * \param stream TCP/TLS stream control data.
+ * \param timeout Number of milliseconds to wait for data transfer with the peer.
+ *
+ * \details This is basically how much time we are willing to spend
+ * in an I/O call before we declare the peer unresponsive.
+ *
+ * \note Setting timeout to -1 disables the timeout.
+ * \note Setting this timeout replaces the I/O sequence timeout timer.
+ *
+ * \return Nothing
+ */
+void ast_tcptls_stream_set_timeout_inactivity(struct ast_tcptls_stream *stream, int timeout);
+
+/*!
+ * \brief Set the TCP/TLS stream I/O sequence timeout timer.
+ *
+ * \param stream TCP/TLS stream control data.
+ * \param start Time the I/O sequence timer starts.
+ * \param timeout Number of milliseconds from the start time before timeout.
+ *
+ * \details This is how much time are we willing to allow the peer
+ * to complete an operation that can take several I/O calls. The
+ * main use is as an authentication timer with us.
+ *
+ * \note Setting timeout to -1 disables the timeout.
+ * \note Setting this timeout replaces the inactivity timeout timer.
+ *
+ * \return Nothing
+ */
+void ast_tcptls_stream_set_timeout_sequence(struct ast_tcptls_stream *stream, struct timeval start, int timeout);
+
/*
* describes a server instance
*/
@@ -149,6 +194,8 @@
struct ast_tcptls_session_args *parent;
/*! XXX Why do we still use this lock when this struct is allocated as an ao2 object which has its own lock? */
ast_mutex_t lock;
+ /*! ao2 FILE stream cookie object associated with f. */
+ struct ast_tcptls_stream *stream_cookie;
};
#if defined(HAVE_FUNOPEN)
Modified: certified/tags/1.8.15-cert6/include/asterisk/time.h
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/include/asterisk/time.h?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/include/asterisk/time.h (original)
+++ certified/tags/1.8.15-cert6/include/asterisk/time.h Thu Jun 12 13:53:58 2014
@@ -152,6 +152,20 @@
struct timeval ast_tvsub(struct timeval a, struct timeval b);
/*!
+ * \brief Calculate remaining milliseconds given a starting timestamp
+ * and upper bound
+ *
+ * If the upper bound is negative, then this indicates that there is no
+ * upper bound on the amount of time to wait. This will result in a
+ * negative return.
+ *
+ * \param start When timing started being calculated
+ * \param max_ms The maximum number of milliseconds to wait from start. May be negative.
+ * \return The number of milliseconds left to wait for. May be negative.
+ */
+int ast_remaining_ms(struct timeval start, int max_ms);
+
+/*!
* \brief Returns a timeval from sec, usec
*/
AST_INLINE_API(
Modified: certified/tags/1.8.15-cert6/include/asterisk/utils.h
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/include/asterisk/utils.h?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/include/asterisk/utils.h (original)
+++ certified/tags/1.8.15-cert6/include/asterisk/utils.h Thu Jun 12 13:53:58 2014
@@ -351,6 +351,7 @@
int ast_utils_init(void);
int ast_wait_for_input(int fd, int ms);
+int ast_wait_for_output(int fd, int ms);
/*!
\brief Try to write string, but wait no more than ms milliseconds
Modified: certified/tags/1.8.15-cert6/main/http.c
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/main/http.c?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/main/http.c (original)
+++ certified/tags/1.8.15-cert6/main/http.c Thu Jun 12 13:53:58 2014
@@ -60,6 +60,7 @@
#define MAX_PREFIX 80
#define DEFAULT_SESSION_LIMIT 100
+#define DEFAULT_SESSION_INACTIVITY 30000 /* (ms) Idle time waiting for data. */
#define DEFAULT_HTTP_PORT 8088
#define DEFAULT_HTTPS_PORT 8089
@@ -70,6 +71,7 @@
#endif
static int session_limit = DEFAULT_SESSION_LIMIT;
+static int session_inactivity = DEFAULT_SESSION_INACTIVITY;
static int session_count = 0;
static struct ast_tls_config http_tls_cfg;
@@ -260,12 +262,12 @@
goto out404;
}
+ if (strstr(path, "/private/") && !astman_is_authed(ast_http_manid_from_vars(headers))) {
+ goto out403;
+ }
+
fd = open(path, O_RDONLY);
if (fd < 0) {
- goto out403;
- }
-
- if (strstr(path, "/private/") && !astman_is_authed(ast_http_manid_from_vars(headers))) {
goto out403;
}
@@ -288,6 +290,7 @@
}
if ( (http_header = ast_str_create(255)) == NULL) {
+ close(fd);
return -1;
}
@@ -404,7 +407,7 @@
/* calc content length */
if (out) {
- content_length += strlen(ast_str_buffer(out));
+ content_length += ast_str_strlen(out);
}
if (fd) {
@@ -431,8 +434,10 @@
/* send content */
if (method != AST_HTTP_HEAD || status_code >= 400) {
- if (out) {
- fprintf(ser->f, "%s", ast_str_buffer(out));
+ if (out && ast_str_strlen(out)) {
+ if (fwrite(ast_str_buffer(out), ast_str_strlen(out), 1, ser->f) != 1) {
+ ast_log(LOG_ERROR, "fwrite() failed: %s\n", strerror(errno));
+ }
}
if (fd) {
@@ -454,8 +459,7 @@
ast_free(out);
}
- fclose(ser->f);
- ser->f = 0;
+ ast_tcptls_close_session_file(ser);
return;
}
@@ -880,12 +884,20 @@
char *uri, *method;
enum ast_http_method http_method = AST_HTTP_UNKNOWN;
int remaining_headers;
+ int flags;
if (ast_atomic_fetchadd_int(&session_count, +1) >= session_limit) {
goto done;
}
- if (!fgets(buf, sizeof(buf), ser->f)) {
+ /* make sure socket is non-blocking */
+ flags = fcntl(ser->fd, F_GETFL);
+ flags |= O_NONBLOCK;
+ fcntl(ser->fd, F_SETFL, flags);
+
+ ast_tcptls_stream_set_timeout_inactivity(ser->stream_cookie, session_inactivity);
+
+ if (!fgets(buf, sizeof(buf), ser->f) || feof(ser->f)) {
goto done;
}
@@ -921,12 +933,19 @@
/* process "Request Headers" lines */
remaining_headers = MAX_HTTP_REQUEST_HEADERS;
- while (fgets(header_line, sizeof(header_line), ser->f)) {
- char *name, *value;
+ for (;;) {
+ char *name;
+ char *value;
+
+ if (!fgets(header_line, sizeof(header_line), ser->f) || feof(ser->f)) {
+ ast_http_error(ser, 400, "Bad Request", "Timeout");
+ goto done;
+ }
/* Trim trailing characters */
ast_trim_blanks(header_line);
if (ast_strlen_zero(header_line)) {
+ /* A blank line ends the request header section. */
break;
}
@@ -977,7 +996,7 @@
ast_variables_destroy(headers);
if (ser->f) {
- fclose(ser->f);
+ ast_tcptls_close_session_file(ser);
}
ao2_ref(ser, -1);
ser = NULL;
@@ -1091,6 +1110,9 @@
AST_RWLIST_UNLOCK(&uri_redirects);
ast_sockaddr_setnull(&https_desc.local_address);
+
+ session_limit = DEFAULT_SESSION_LIMIT;
+ session_inactivity = DEFAULT_SESSION_INACTIVITY;
if (cfg) {
v = ast_variable_browse(cfg, "general");
@@ -1133,6 +1155,12 @@
ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
v->name, v->value, v->lineno);
}
+ } else if (!strcasecmp(v->name, "session_inactivity")) {
+ if (ast_parse_arg(v->value, PARSE_INT32 |PARSE_DEFAULT | PARSE_IN_RANGE,
+ &session_inactivity, DEFAULT_SESSION_INACTIVITY, 1, INT_MAX)) {
+ ast_log(LOG_WARNING, "Invalid %s '%s' at line %d of http.conf\n",
+ v->name, v->value, v->lineno);
+ }
} else {
ast_log(LOG_WARNING, "Ignoring unknown option '%s' in http.conf\n", v->name);
}
Modified: certified/tags/1.8.15-cert6/main/manager.c
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/main/manager.c?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/main/manager.c (original)
+++ certified/tags/1.8.15-cert6/main/manager.c Thu Jun 12 13:53:58 2014
@@ -879,7 +879,6 @@
static const int DEFAULT_ENABLED = 0; /*!< Default setting for manager to be enabled */
static const int DEFAULT_WEBENABLED = 0; /*!< Default setting for the web interface to be enabled */
-static const int DEFAULT_BLOCKSOCKETS = 0; /*!< Default setting for block-sockets */
static const int DEFAULT_DISPLAYCONNECTS = 1; /*!< Default setting for displaying manager connections */
static const int DEFAULT_TIMESTAMPEVENTS = 0; /*!< Default setting for timestampevents */
static const int DEFAULT_HTTPTIMEOUT = 60; /*!< Default manager http timeout */
@@ -903,7 +902,6 @@
#define DEFAULT_REALM "asterisk"
static char global_realm[MAXHOSTNAMELEN]; /*!< Default realm */
-static int block_sockets;
static int unauth_sessions = 0;
@@ -1324,6 +1322,7 @@
}
if (session->f != NULL) {
+ fflush(session->f);
fclose(session->f);
}
if (eqe) {
@@ -5002,12 +5001,9 @@
ast_log(LOG_WARNING, "Failed to set manager tcp connection to TCP_NODELAY, getprotobyname(\"tcp\") failed\nSome manager actions may be slow to respond.\n");
}
+ /* make sure socket is non-blocking */
flags = fcntl(ser->fd, F_GETFL);
- if (!block_sockets) { /* make sure socket is non-blocking */
- flags |= O_NONBLOCK;
- } else {
- flags &= ~O_NONBLOCK;
- }
+ flags |= O_NONBLOCK;
fcntl(ser->fd, F_SETFL, flags);
ao2_lock(session);
@@ -5033,10 +5029,16 @@
}
ao2_unlock(session);
+ ast_tcptls_stream_set_timeout_sequence(ser->stream_cookie,
+ ast_tvnow(), authtimeout * 1000);
+
astman_append(&s, "Asterisk Call Manager/%s\r\n", AMI_VERSION); /* welcome prompt */
for (;;) {
if ((res = do_message(&s)) < 0 || s.write_error) {
break;
+ }
+ if (session->authenticated) {
+ ast_tcptls_stream_set_timeout_disable(ser->stream_cookie);
}
}
/* session is over, explain why and terminate */
@@ -5799,6 +5801,30 @@
}
}
+static void close_mansession_file(struct mansession *s)
+{
+ if (s->f) {
+ if (fclose(s->f)) {
+ ast_log(LOG_ERROR, "fclose() failed: %s\n", strerror(errno));
+ }
+ s->f = NULL;
+ s->fd = -1;
+ } else if (s->fd != -1) {
+ /*
+ * Issuing shutdown() is necessary here to avoid a race
+ * condition where the last data written may not appear
+ * in the TCP stream. See ASTERISK-23548
+ */
+ shutdown(s->fd, SHUT_RDWR);
+ if (close(s->fd)) {
+ ast_log(LOG_ERROR, "close() failed: %s\n", strerror(errno));
+ }
+ s->fd = -1;
+ } else {
+ ast_log(LOG_ERROR, "Attempted to close file/file descriptor on mansession without a valid file or file descriptor.\n");
+ }
+}
+
static void process_output(struct mansession *s, struct ast_str **out, struct ast_variable *params, enum output_format format)
{
char *buf;
@@ -5826,20 +5852,7 @@
xml_translate(out, "", params, format);
}
- if (s->f) {
- if (fclose(s->f)) {
- ast_log(LOG_ERROR, "fclose() failed: %s\n", strerror(errno));
- }
- s->f = NULL;
- s->fd = -1;
- } else if (s->fd != -1) {
- if (close(s->fd)) {
- ast_log(LOG_ERROR, "close() failed: %s\n", strerror(errno));
- }
- s->fd = -1;
- } else {
- ast_log(LOG_ERROR, "process output attempted to close file/file descriptor on mansession without a valid file or file descriptor.\n");
- }
+ close_mansession_file(s);
}
static int generic_http_callback(struct ast_tcptls_session_instance *ser,
@@ -6557,7 +6570,6 @@
ast_cli(a->fd, FORMAT, "Timestamp events:", AST_CLI_YESNO(timestampevents));
ast_cli(a->fd, FORMAT, "Channel vars:", S_OR(manager_channelvars, ""));
ast_cli(a->fd, FORMAT, "Debug:", AST_CLI_YESNO(manager_debug));
- ast_cli(a->fd, FORMAT, "Block sockets:", AST_CLI_YESNO(block_sockets));
#undef FORMAT
#undef FORMAT2
@@ -6679,7 +6691,6 @@
manager_debug = DEFAULT_MANAGERDEBUG;
displayconnects = DEFAULT_DISPLAYCONNECTS;
broken_events_action = DEFAULT_BROKENEVENTSACTION;
- block_sockets = DEFAULT_BLOCKSOCKETS;
timestampevents = DEFAULT_TIMESTAMPEVENTS;
httptimeout = DEFAULT_HTTPTIMEOUT;
authtimeout = DEFAULT_AUTHTIMEOUT;
@@ -6727,8 +6738,6 @@
if (!strcasecmp(var->name, "enabled")) {
manager_enabled = ast_true(val);
- } else if (!strcasecmp(var->name, "block-sockets")) {
- block_sockets = ast_true(val);
} else if (!strcasecmp(var->name, "webenabled")) {
webmanager_enabled = ast_true(val);
} else if (!strcasecmp(var->name, "port")) {
Modified: certified/tags/1.8.15-cert6/main/tcptls.c
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/main/tcptls.c?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/main/tcptls.c (original)
+++ certified/tags/1.8.15-cert6/main/tcptls.c Thu Jun 12 13:53:58 2014
@@ -50,98 +50,482 @@
#include "asterisk/astobj2.h"
#include "asterisk/pbx.h"
-/*! \brief
- * replacement read/write functions for SSL support.
- * We use wrappers rather than SSL_read/SSL_write directly so
- * we can put in some debugging.
+/*! ao2 object used for the FILE stream fopencookie()/funopen() cookie. */
+struct ast_tcptls_stream {
+ /*! SSL state if not NULL */
+ SSL *ssl;
+ /*!
+ * \brief Start time from when an I/O sequence must complete
+ * by struct ast_tcptls_stream.timeout.
+ *
+ * \note If struct ast_tcptls_stream.start.tv_sec is zero then
+ * start time is the current I/O request.
+ */
+ struct timeval start;
+ /*!
+ * \brief The socket returned by accept().
+ *
+ * \note Set to -1 if the stream is closed.
+ */
+ int fd;
+ /*!
+ * \brief Timeout in ms relative to struct ast_tcptls_stream.start
+ * to wait for an event on struct ast_tcptls_stream.fd.
+ *
+ * \note Set to -1 to disable timeout.
+ * \note The socket needs to be set to non-blocking for the timeout
+ * feature to work correctly.
+ */
+ int timeout;
+};
+
+void ast_tcptls_stream_set_timeout_disable(struct ast_tcptls_stream *stream)
+{
+ ast_assert(stream != NULL);
+
+ stream->timeout = -1;
+}
+
+void ast_tcptls_stream_set_timeout_inactivity(struct ast_tcptls_stream *stream, int timeout)
+{
+ ast_assert(stream != NULL);
+
+ stream->start.tv_sec = 0;
+ stream->timeout = timeout;
+}
+
+void ast_tcptls_stream_set_timeout_sequence(struct ast_tcptls_stream *stream, struct timeval start, int timeout)
+{
+ ast_assert(stream != NULL);
+
+ stream->start = start;
+ stream->timeout = timeout;
+}
+
+/*!
+ * \internal
+ * \brief fopencookie()/funopen() stream read function.
+ *
+ * \param cookie Stream control data.
+ * \param buf Where to put read data.
+ * \param size Size of the buffer.
+ *
+ * \retval number of bytes put into buf.
+ * \retval 0 on end of file.
+ * \retval -1 on error.
*/
-
-#ifdef DO_SSL
-static HOOK_T ssl_read(void *cookie, char *buf, LEN_T len)
-{
- int i = SSL_read(cookie, buf, len-1);
-#if 0
- if (i >= 0)
- buf[i] = '\0';
- ast_verb(0, "ssl read size %d returns %d <%s>\n", (int)len, i, buf);
+static HOOK_T tcptls_stream_read(void *cookie, char *buf, LEN_T size)
+{
+ struct ast_tcptls_stream *stream = cookie;
+ struct timeval start;
+ int ms;
+ int res;
+
+ if (!size) {
+ /* You asked for no data you got no data. */
+ return 0;
+ }
+
+ if (!stream || stream->fd == -1) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (stream->start.tv_sec) {
+ start = stream->start;
+ } else {
+ start = ast_tvnow();
+ }
+
+#if defined(DO_SSL)
+ if (stream->ssl) {
+ for (;;) {
+ res = SSL_read(stream->ssl, buf, size);
+ if (0 < res) {
+ /* We read some payload data. */
+ return res;
+ }
+ switch (SSL_get_error(stream->ssl, res)) {
+ case SSL_ERROR_ZERO_RETURN:
+ /* Report EOF for a shutdown */
+ ast_debug(1, "TLS clean shutdown alert reading data\n");
+ return 0;
+ case SSL_ERROR_WANT_READ:
+ while ((ms = ast_remaining_ms(start, stream->timeout))) {
+ res = ast_wait_for_input(stream->fd, ms);
+ if (0 < res) {
+ /* Socket is ready to be read. */
+ break;
+ }
+ if (res < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ /* Try again. */
+ continue;
+ }
+ ast_debug(1, "TLS socket error waiting for read data: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ }
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ while ((ms = ast_remaining_ms(start, stream->timeout))) {
+ res = ast_wait_for_output(stream->fd, ms);
+ if (0 < res) {
+ /* Socket is ready to be written. */
+ break;
+ }
+ if (res < 0) {
+ if (errno == EINTR || errno == EAGAIN) {
+ /* Try again. */
+ continue;
+ }
+ ast_debug(1, "TLS socket error waiting for write space: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ }
+ break;
+ default:
+ /* Report EOF for an undecoded SSL or transport error. */
+ ast_debug(1, "TLS transport or SSL error reading data\n");
+ return 0;
+ }
+ if (!ms) {
+ /* Report EOF for a timeout */
+ ast_debug(1, "TLS timeout reading data\n");
+ return 0;
+ }
+ }
+ }
+#endif /* defined(DO_SSL) */
+
+ for (;;) {
+ res = read(stream->fd, buf, size);
+ if (0 <= res) {
+ return res;
+ }
+ if (errno != EINTR && errno != EAGAIN) {
+ /* Not a retryable error. */
+ ast_debug(1, "TCP socket error reading data: %s\n",
+ strerror(errno));
+ return -1;
+ }
+ ms = ast_remaining_ms(start, stream->timeout);
+ if (!ms) {
+ /* Report EOF for a timeout */
+ ast_debug(1, "TCP timeout reading data\n");
+ return 0;
+ }
+ ast_wait_for_input(stream->fd, ms);
+ }
+}
+
+/*!
+ * \internal
+ * \brief fopencookie()/funopen() stream write function.
+ *
+ * \param cookie Stream control data.
+ * \param buf Where to get data to write.
+ * \param size Size of the buffer.
+ *
+ * \retval number of bytes written from buf.
+ * \retval -1 on error.
+ */
+static HOOK_T tcptls_stream_write(void *cookie, const char *buf, LEN_T size)
+{
+ struct ast_tcptls_stream *stream = cookie;
+ struct timeval start;
+ int ms;
+ int res;
+ int written;
+ int remaining;
+
+ if (!size) {
+ /* You asked to write no data you wrote no data. */
+ return 0;
+ }
+
+ if (!stream || stream->fd == -1) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (stream->start.tv_sec) {
+ start = stream->start;
+ } else {
+ start = ast_tvnow();
+ }
+
+#if defined(DO_SSL)
+ if (stream->ssl) {
+ written = 0;
+ remaining = size;
+ for (;;) {
+ res = SSL_write(stream->ssl, buf + written, remaining);
+ if (res == remaining) {
+ /* Everything was written. */
+ return size;
+ }
+ if (0 < res) {
+ /* Successfully wrote part of the buffer. Try to write the rest. */
+ written += res;
+ remaining -= res;
+ continue;
+ }
+ switch (SSL_get_error(stream->ssl, res)) {
+ case SSL_ERROR_ZERO_RETURN:
+ ast_debug(1, "TLS clean shutdown alert writing data\n");
+ if (written) {
+ /* Report partial write. */
+ return written;
+ }
+ errno = EBADF;
+ return -1;
+ case SSL_ERROR_WANT_READ:
+ ms = ast_remaining_ms(start, stream->timeout);
+ if (!ms) {
+ /* Report partial write. */
+ ast_debug(1, "TLS timeout writing data (want read)\n");
+ return written;
+ }
+ ast_wait_for_input(stream->fd, ms);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ ms = ast_remaining_ms(start, stream->timeout);
+ if (!ms) {
+ /* Report partial write. */
+ ast_debug(1, "TLS timeout writing data (want write)\n");
+ return written;
+ }
+ ast_wait_for_output(stream->fd, ms);
+ break;
+ default:
+ /* Undecoded SSL or transport error. */
+ ast_debug(1, "TLS transport or SSL error writing data\n");
+ if (written) {
+ /* Report partial write. */
+ return written;
+ }
+ errno = EBADF;
+ return -1;
+ }
+ }
+ }
+#endif /* defined(DO_SSL) */
+
+ written = 0;
+ remaining = size;
+ for (;;) {
+ res = write(stream->fd, buf + written, remaining);
+ if (res == remaining) {
+ /* Yay everything was written. */
+ return size;
+ }
+ if (0 < res) {
+ /* Successfully wrote part of the buffer. Try to write the rest. */
+ written += res;
+ remaining -= res;
+ continue;
+ }
+ if (errno != EINTR && errno != EAGAIN) {
+ /* Not a retryable error. */
+ ast_debug(1, "TCP socket error writing: %s\n", strerror(errno));
+ if (written) {
+ return written;
+ }
+ return -1;
+ }
+ ms = ast_remaining_ms(start, stream->timeout);
+ if (!ms) {
+ /* Report partial write. */
+ ast_debug(1, "TCP timeout writing data\n");
+ return written;
+ }
+ ast_wait_for_output(stream->fd, ms);
+ }
+}
+
+/*!
+ * \internal
+ * \brief fopencookie()/funopen() stream close function.
+ *
+ * \param cookie Stream control data.
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int tcptls_stream_close(void *cookie)
+{
+ struct ast_tcptls_stream *stream = cookie;
+
+ if (!stream) {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (stream->fd != -1) {
+#if defined(DO_SSL)
+ if (stream->ssl) {
+ int res;
+
+ /*
+ * According to the TLS standard, it is acceptable for an
+ * application to only send its shutdown alert and then
+ * close the underlying connection without waiting for
+ * the peer's response (this way resources can be saved,
+ * as the process can already terminate or serve another
+ * connection).
+ */
+ res = SSL_shutdown(stream->ssl);
+ if (res < 0) {
+ ast_log(LOG_ERROR, "SSL_shutdown() failed: %d\n",
+ SSL_get_error(stream->ssl, res));
+ }
+
+ if (!stream->ssl->server) {
+ /* For client threads, ensure that the error stack is cleared */
+ ERR_remove_state(0);
+ }
+
+ SSL_free(stream->ssl);
+ stream->ssl = NULL;
+ }
+#endif /* defined(DO_SSL) */
+
+ /*
+ * Issuing shutdown() is necessary here to avoid a race
+ * condition where the last data written may not appear
+ * in the TCP stream. See ASTERISK-23548
+ */
+ shutdown(stream->fd, SHUT_RDWR);
+ if (close(stream->fd)) {
+ ast_log(LOG_ERROR, "close() failed: %s\n", strerror(errno));
+ }
+ stream->fd = -1;
+ }
+ ao2_t_ref(stream, -1, "Closed tcptls stream cookie");
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief fopencookie()/funopen() stream destructor function.
+ *
+ * \param cookie Stream control data.
+ *
+ * \return Nothing
+ */
+static void tcptls_stream_dtor(void *cookie)
+{
+ struct ast_tcptls_stream *stream = cookie;
+
+ ast_assert(stream->fd == -1);
+}
+
+/*!
+ * \internal
+ * \brief fopencookie()/funopen() stream allocation function.
+ *
+ * \retval stream_cookie on success.
+ * \retval NULL on error.
+ */
+static struct ast_tcptls_stream *tcptls_stream_alloc(void)
+{
+ struct ast_tcptls_stream *stream;
+
+ stream = ao2_alloc(sizeof(*stream), tcptls_stream_dtor);
+ if (stream) {
+ stream->fd = -1;
+ stream->timeout = -1;
+ }
+ return stream;
+}
+
+/*!
+ * \internal
+ * \brief Open a custom FILE stream for tcptls.
+ *
+ * \param stream Stream cookie control data.
+ * \param ssl SSL state if not NULL.
+ * \param fd Socket file descriptor.
+ * \param timeout ms to wait for an event on fd. -1 if timeout disabled.
+ *
+ * \retval fp on success.
+ * \retval NULL on error.
+ */
+static FILE *tcptls_stream_fopen(struct ast_tcptls_stream *stream, SSL *ssl, int fd, int timeout)
+{
+ FILE *fp;
+
+#if defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */
+ static const cookie_io_functions_t cookie_funcs = {
+ tcptls_stream_read,
+ tcptls_stream_write,
+ NULL,
+ tcptls_stream_close
+ };
+#endif /* defined(HAVE_FOPENCOOKIE) */
+
+ if (fd == -1) {
+ /* Socket not open. */
+ return NULL;
+ }
+
+ stream->ssl = ssl;
+ stream->fd = fd;
+ stream->timeout = timeout;
+ ao2_t_ref(stream, +1, "Opening tcptls stream cookie");
+
+#if defined(HAVE_FUNOPEN) /* the BSD interface */
+ fp = funopen(stream, tcptls_stream_read, tcptls_stream_write, NULL,
+ tcptls_stream_close);
+#elif defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */
+ fp = fopencookie(stream, "w+", cookie_funcs);
+#else
+ /* could add other methods here */
+ ast_debug(2, "No stream FILE methods attempted!\n");
+ fp = NULL;
#endif
- return i;
-}
-
-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_verb(0, "ssl write size %d <%s>\n", (int)len, s);
-#endif
- return SSL_write(cookie, buf, len);
-}
-
-static int ssl_close(void *cookie)
-{
- int cookie_fd = SSL_get_fd(cookie);
- int ret;
-
- if (cookie_fd > -1) {
- /*
- * According to the TLS standard, it is acceptable for an application to only send its shutdown
- * alert and then close the underlying connection without waiting for the peer's response (this
- * way resources can be saved, as the process can already terminate or serve another connection).
- */
- if ((ret = SSL_shutdown(cookie)) < 0) {
- ast_log(LOG_ERROR, "SSL_shutdown() failed: %d\n", SSL_get_error(cookie, ret));
- }
-
- if (!((SSL*)cookie)->server) {
- /* For client threads, ensure that the error stack is cleared */
- ERR_remove_state(0);
- }
-
- SSL_free(cookie);
- /* adding shutdown(2) here has no added benefit */
- if (close(cookie_fd)) {
- ast_log(LOG_ERROR, "close() failed: %s\n", strerror(errno));
- }
- }
- return 0;
-}
-#endif /* DO_SSL */
+
+ if (!fp) {
+ stream->fd = -1;
+ ao2_t_ref(stream, -1, "Failed to open tcptls stream cookie");
+ }
+ return fp;
+}
HOOK_T ast_tcptls_server_read(struct ast_tcptls_session_instance *tcptls_session, void *buf, size_t count)
{
- if (tcptls_session->fd == -1) {
- ast_log(LOG_ERROR, "server_read called with an fd of -1\n");
+ if (!tcptls_session->stream_cookie || tcptls_session->stream_cookie->fd == -1) {
+ ast_log(LOG_ERROR, "TCP/TLS read called on invalid stream.\n");
errno = EIO;
return -1;
}
-#ifdef DO_SSL
- if (tcptls_session->ssl)
- return ssl_read(tcptls_session->ssl, buf, count);
-#endif
- return read(tcptls_session->fd, buf, count);
+ return tcptls_stream_read(tcptls_session->stream_cookie, buf, count);
}
HOOK_T ast_tcptls_server_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t count)
{
- if (tcptls_session->fd == -1) {
- ast_log(LOG_ERROR, "server_write called with an fd of -1\n");
+ if (!tcptls_session->stream_cookie || tcptls_session->stream_cookie->fd == -1) {
+ ast_log(LOG_ERROR, "TCP/TLS write called on invalid stream.\n");
errno = EIO;
return -1;
}
-#ifdef DO_SSL
- if (tcptls_session->ssl)
- return ssl_write(tcptls_session->ssl, buf, count);
-#endif
- return write(tcptls_session->fd, buf, count);
+ return tcptls_stream_write(tcptls_session->stream_cookie, buf, count);
}
static void session_instance_destructor(void *obj)
{
struct ast_tcptls_session_instance *i = obj;
+
+ if (i->stream_cookie) {
+ ao2_t_ref(i->stream_cookie, -1, "Destroying tcptls session instance");
+ i->stream_cookie = NULL;
+ }
ast_mutex_destroy(&i->lock);
}
@@ -168,6 +552,15 @@
*/
if (ast_thread_inhibit_escalations()) {
ast_log(LOG_ERROR, "Failed to inhibit privilege escalations; killing connection\n");
+ ast_tcptls_close_session_file(tcptls_session);
+ ao2_ref(tcptls_session, -1);
+ return NULL;
+ }
+
+ tcptls_session->stream_cookie = tcptls_stream_alloc();
+ if (!tcptls_session->stream_cookie) {
+ ast_tcptls_close_session_file(tcptls_session);
+ ao2_ref(tcptls_session, -1);
return NULL;
}
@@ -175,8 +568,10 @@
* open a FILE * as appropriate.
*/
if (!tcptls_session->parent->tls_cfg) {
- if ((tcptls_session->f = fdopen(tcptls_session->fd, "w+"))) {
- if(setvbuf(tcptls_session->f, NULL, _IONBF, 0)) {
+ tcptls_session->f = tcptls_stream_fopen(tcptls_session->stream_cookie, NULL,
+ tcptls_session->fd, -1);
+ if (tcptls_session->f) {
+ if (setvbuf(tcptls_session->f, NULL, _IONBF, 0)) {
ast_tcptls_close_session_file(tcptls_session);
}
}
@@ -186,29 +581,28 @@
SSL_set_fd(tcptls_session->ssl, tcptls_session->fd);
if ((ret = ssl_setup(tcptls_session->ssl)) <= 0) {
ast_verb(2, "Problem setting up ssl connection: %s\n", ERR_error_string(ERR_get_error(), err));
- } else {
-#if defined(HAVE_FUNOPEN) /* the BSD interface */
- tcptls_session->f = funopen(tcptls_session->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
- };
- tcptls_session->f = fopencookie(tcptls_session->ssl, "w+", cookie_funcs);
-#else
- /* could add other methods here */
- ast_debug(2, "no tcptls_session->f methods attempted!\n");
-#endif
+ } else if ((tcptls_session->f = tcptls_stream_fopen(tcptls_session->stream_cookie,
+ tcptls_session->ssl, tcptls_session->fd, -1))) {
if ((tcptls_session->client && !ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_DONT_VERIFY_SERVER))
|| (!tcptls_session->client && ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_VERIFY_CLIENT))) {
X509 *peer;
long res;
peer = SSL_get_peer_certificate(tcptls_session->ssl);
- if (!peer)
- ast_log(LOG_WARNING, "No peer SSL certificate\n");
+ if (!peer) {
+ ast_log(LOG_ERROR, "No peer SSL certificate to verify\n");
+ ast_tcptls_close_session_file(tcptls_session);
+ ao2_ref(tcptls_session, -1);
+ return NULL;
+ }
+
res = SSL_get_verify_result(tcptls_session->ssl);
- if (res != X509_V_OK)
+ if (res != X509_V_OK) {
ast_log(LOG_ERROR, "Certificate did not verify: %s\n", X509_verify_cert_error_string(res));
+ X509_free(peer);
+ ast_tcptls_close_session_file(tcptls_session);
+ ao2_ref(tcptls_session, -1);
+ return NULL;
+ }
if (!ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_IGNORE_COMMON_NAME)) {
ASN1_STRING *str;
unsigned char *str2;
@@ -235,16 +629,13 @@
}
if (!found) {
ast_log(LOG_ERROR, "Certificate common name did not match (%s)\n", tcptls_session->parent->hostname);
- if (peer) {
- X509_free(peer);
- }
+ X509_free(peer);
ast_tcptls_close_session_file(tcptls_session);
ao2_ref(tcptls_session, -1);
return NULL;
}
}
- if (peer)
- X509_free(peer);
+ X509_free(peer);
}
}
if (!tcptls_session->f) /* no success opening descriptor stacking */
@@ -595,12 +986,19 @@
void ast_tcptls_close_session_file(struct ast_tcptls_session_instance *tcptls_session)
{
if (tcptls_session->f) {
+ fflush(tcptls_session->f);
if (fclose(tcptls_session->f)) {
ast_log(LOG_ERROR, "fclose() failed: %s\n", strerror(errno));
}
tcptls_session->f = NULL;
tcptls_session->fd = -1;
} else if (tcptls_session->fd != -1) {
+ /*
+ * Issuing shutdown() is necessary here to avoid a race
+ * condition where the last data written may not appear
+ * in the TCP stream. See ASTERISK-23548
+ */
+ shutdown(tcptls_session->fd, SHUT_RDWR);
if (close(tcptls_session->fd)) {
ast_log(LOG_ERROR, "close() failed: %s\n", strerror(errno));
}
Modified: certified/tags/1.8.15-cert6/main/utils.c
URL: http://svnview.digium.com/svn/asterisk/certified/tags/1.8.15-cert6/main/utils.c?view=diff&rev=415973&r1=415972&r2=415973
==============================================================================
--- certified/tags/1.8.15-cert6/main/utils.c (original)
+++ certified/tags/1.8.15-cert6/main/utils.c Thu Jun 12 13:53:58 2014
@@ -859,7 +859,7 @@
ast_mutex_t *lock;
struct ast_lock_track *lt;
- ast_str_append(str, 0, "=== ---> %sLock #%d (%s): %s %d %s %s %p (%d)\n",
+ ast_str_append(str, 0, "=== ---> %sLock #%d (%s): %s %d %s %s %p (%d)\n",
lock_info->locks[i].pending > 0 ? "Waiting for " :
lock_info->locks[i].pending < 0 ? "Tried and failed to get " : "", i,
lock_info->locks[i].file,
@@ -1154,13 +1154,24 @@
int ast_wait_for_input(int fd, int ms)
{
struct pollfd pfd[1];
+
memset(pfd, 0, sizeof(pfd));
pfd[0].fd = fd;
- pfd[0].events = POLLIN|POLLPRI;
+ pfd[0].events = POLLIN | POLLPRI;
return ast_poll(pfd, 1, ms);
}
-static int ast_wait_for_output(int fd, int timeoutms)
+int ast_wait_for_output(int fd, int ms)
+{
+ struct pollfd pfd[1];
+
+ memset(pfd, 0, sizeof(pfd));
+ pfd[0].fd = fd;
+ pfd[0].events = POLLOUT;
+ return ast_poll(pfd, 1, ms);
+}
+
+static int wait_for_output(int fd, int timeoutms)
{
struct pollfd pfd = {
.fd = fd,
@@ -1220,7 +1231,7 @@
int elapsed = 0;
while (len) {
- if (ast_wait_for_output(fd, timeoutms - elapsed)) {
+ if (wait_for_output(fd, timeoutms - elapsed)) {
return -1;
}
@@ -1261,7 +1272,7 @@
int elapsed = 0;
while (len) {
- if (ast_wait_for_output(fd, timeoutms - elapsed)) {
+ if (wait_for_output(fd, timeoutms - elapsed)) {
/* poll returned a fatal error, so bail out immediately. */
return -1;
}
@@ -1492,6 +1503,23 @@
}
return a;
}
+
+int ast_remaining_ms(struct timeval start, int max_ms)
+{
+ int ms;
+
+ if (max_ms < 0) {
+ ms = max_ms;
+ } else {
+ ms = max_ms - ast_tvdiff_ms(ast_tvnow(), start);
+ if (ms < 0) {
+ ms = 0;
+ }
+ }
+
+ return ms;
+}
+
#undef ONE_MILLION
/*! \brief glibc puts a lock inside random(3), so that the results are thread-safe.
More information about the asterisk-commits
mailing list