*/
struct ast_http_auth *ast_http_get_auth(struct ast_variable *headers);
+/*!
+ * \brief Create an HTTP authorization header.
+ *
+ * The returned ast_variable must be freed with ast_variables_destroy()
+ *
+ * \param userid User ID or "<userid>:<password>".
+ * \param password Password if not in userid.
+ *
+ * \return ast_variable with name="Authorization" and value="Basic <base64enc>"
+ * \retval NULL if memory allocation failed.
+ */
+struct ast_variable *ast_http_create_basic_auth_header(const char *userid,
+ const char *password);
+
/*! \brief Register a URI handler */
int ast_http_uri_link(struct ast_http_uri *urihandler);
* \brief Result code for a websocket client.
*/
enum ast_websocket_result {
- WS_OK,
+ WS_OK = 0,
WS_ALLOCATE_ERROR,
WS_KEY_ERROR,
WS_URI_PARSE_ERROR,
WS_NOT_SUPPORTED,
WS_WRITE_ERROR,
WS_CLIENT_START_ERROR,
+ WS_UNAUTHORIZED,
};
/*!
* Secure websocket credentials
*/
struct ast_tls_config *tls_cfg;
+ const char *username; /*!< Auth username */
+ const char *password; /*!< Auth password */
+ int suppress_connection_msgs; /*!< Suppress connection log messages */
};
/*!
*/
AST_OPTIONAL_API(int, ast_websocket_set_timeout, (struct ast_websocket *session, int timeout), {return -1;});
+/*!
+ * \brief Convert a websocket result code to a string.
+ *
+ * \param result The result code to convert
+ *
+ * \return A string representation of the result code
+ */
+AST_OPTIONAL_API(const char *, ast_websocket_result_to_str, (enum ast_websocket_result result), {return "";});
+
#endif
#ifndef _ASTERISK_MD5_H
#define _ASTERISK_MD5_H
+#ifndef MD5_DIGEST_LENGTH
+#define MD5_DIGEST_LENGTH 16
+#endif
+
struct MD5Context {
uint32_t buf[4];
uint32_t bits[2];
void MD5Init(struct MD5Context *context);
void MD5Update(struct MD5Context *context, unsigned char const *buf,
unsigned len);
-void MD5Final(unsigned char digest[16], struct MD5Context *context);
+void MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], struct MD5Context *context);
void MD5Transform(uint32_t buf[4], uint32_t const in[16]);
#endif /* _ASTERISK_MD5_H */
*/
struct stasis_app *stasis_app_get_by_name(const char *name);
+/*!
+ * \brief Check if a Stasis application is registered.
+ *
+ * \param name The name of the registered Stasis application
+ *
+ * \return 1 if the application is registered.
+ * \return 0 if the application is not registered.
+ */
+int stasis_app_is_registered(const char *name);
+
/*!
* \brief Register a new Stasis application.
*
int stasis_app_register_all(const char *app_name, stasis_app_cb handler, void *data);
/*!
- * \brief Unregister a Stasis application.
+ * \brief Unregister a Stasis application and unsubscribe from all event sources.
* \param app_name Name of the application to unregister.
*/
void stasis_app_unregister(const char *app_name);
int stasis_app_control_is_done(
struct stasis_app_control *control);
+/*!
+ * \brief Set the failed flag on a control structure
+ *
+ * \param control Control object to be updated
+ */
+void stasis_app_control_mark_failed(struct stasis_app_control *control);
+
+/*!
+ * \brief Check if a control object is marked as "failed"
+ *
+ * \param control Control object to check
+ */
+int stasis_app_control_is_failed(const struct stasis_app_control *control);
+
/*!
* \brief Flush the control command queue.
* \since 13.9.0
void *(*worker_fn)(void *); /*!< the function in charge of doing the actual work */
const char *name;
struct ast_tls_config *old_tls_cfg; /*!< copy of the SSL configuration to determine whether changes have been made */
+ int suppress_connection_msgs; /*!< suppress connection messages to allow caller to manage logging */
};
/*! \brief
return NULL;
}
+struct ast_variable *ast_http_create_basic_auth_header(const char *userid,
+ const char *password)
+{
+ int encoded_size = 0;
+ int userinfo_len = 0;
+ RAII_VAR(char *, userinfo, NULL, ast_free);
+ char *encoded_userinfo = NULL;
+ struct ast_variable *auth_header = NULL;
+
+ if (ast_strlen_zero(userid)) {
+ return NULL;
+ }
+
+ if (strchr(userid, ':')) {
+ userinfo = ast_strdup(userid);
+ userinfo_len = strlen(userinfo);
+ } else {
+ if (ast_strlen_zero(password)) {
+ return NULL;
+ }
+ userinfo_len = ast_asprintf(&userinfo, "%s:%s", userid, password);
+ }
+ if (!userinfo) {
+ return NULL;
+ }
+
+ /*
+ * The header value is "Basic " + base64(userinfo).
+ * Doubling the userinfo length then adding the length
+ * of the "Basic " prefix is a conservative estimate of the
+ * final encoded size.
+ */
+ encoded_size = userinfo_len * 2 * sizeof(char) + 1 + BASIC_LEN;
+ encoded_userinfo = ast_alloca(encoded_size);
+ strcpy(encoded_userinfo, BASIC_PREFIX); /* Safe */
+ ast_base64encode(encoded_userinfo + BASIC_LEN, (unsigned char *)userinfo,
+ userinfo_len, encoded_size - BASIC_LEN);
+
+ auth_header = ast_variable_new("Authorization",
+ encoded_userinfo, "");
+
+ return auth_header;
+}
+
int ast_http_response_status_line(const char *buf, const char *version, int code)
{
int status_code;
* Final wrapup - pad to 64-byte boundary with the bit pattern
* 1 0* (64-bit count of bits processed, MSB-first)
*/
-void MD5Final(unsigned char digest[16], struct MD5Context *ctx)
+void MD5Final(unsigned char digest[MD5_DIGEST_LENGTH], struct MD5Context *ctx)
{
unsigned count;
unsigned char *p;
}
#endif
-static int __ssl_setup(struct ast_tls_config *cfg, int client)
+static int __ssl_setup(struct ast_tls_config *cfg, int client,
+ int suppress_progress_msgs)
{
#ifndef DO_SSL
if (cfg->enabled) {
if (SSL_CTX_set_tmp_dh(cfg->ssl_ctx, dh)) {
long options = SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_ECDH_USE;
options = SSL_CTX_set_options(cfg->ssl_ctx, options);
- ast_verb(2, "TLS/SSL DH initialized, PFS cipher-suites enabled\n");
+ if (!suppress_progress_msgs) {
+ ast_verb(2, "TLS/SSL DH initialized, PFS cipher-suites enabled\n");
+ }
}
DH_free(dh);
}
#endif
/* SSL_CTX_set_ecdh_auto(cfg->ssl_ctx, on); requires OpenSSL 1.0.2 which wraps: */
if (SSL_CTX_ctrl(cfg->ssl_ctx, SSL_CTRL_SET_ECDH_AUTO, 1, NULL)) {
- ast_verb(2, "TLS/SSL ECDH initialized (automatic), faster PFS ciphers enabled\n");
+ if (!suppress_progress_msgs) {
+ ast_verb(2, "TLS/SSL ECDH initialized (automatic), faster PFS ciphers enabled\n");
+ }
#if !defined(OPENSSL_NO_ECDH) && (OPENSSL_VERSION_NUMBER >= 0x10000000L) && (OPENSSL_VERSION_NUMBER < 0x10100000L)
} else {
/* enables AES-128 ciphers, to get AES-256 use NID_secp384r1 */
#endif
}
- ast_verb(2, "TLS/SSL certificate ok\n"); /* We should log which one that is ok. This message doesn't really make sense in production use */
+ if (!suppress_progress_msgs) {
+ ast_verb(2, "TLS/SSL certificate ok\n"); /* We should log which one that is ok. This message doesn't really make sense in production use */
+ }
return 1;
#endif
}
int ast_ssl_setup(struct ast_tls_config *cfg)
{
- return __ssl_setup(cfg, 0);
+ return __ssl_setup(cfg, 0, 0);
}
void ast_ssl_teardown(struct ast_tls_config *cfg)
}
if (socket_connect(desc->accept_fd, &desc->remote_address, timeout)) {
- ast_log(LOG_WARNING, "Unable to connect %s to %s: %s\n", desc->name,
- ast_sockaddr_stringify(&desc->remote_address), strerror(errno));
+ if (!desc->suppress_connection_msgs) {
+ ast_log(LOG_WARNING, "Unable to connect %s to %s: %s\n", desc->name,
+ ast_sockaddr_stringify(&desc->remote_address), strerror(errno));
+ }
ao2_ref(tcptls_session, -1);
return NULL;
ast_fd_clear_flags(desc->accept_fd, O_NONBLOCK);
if (desc->tls_cfg) {
- desc->tls_cfg->enabled = 1;
- __ssl_setup(desc->tls_cfg, 1);
+ __ssl_setup(desc->tls_cfg, 1, desc->suppress_connection_msgs);
}
return handle_tcptls_connection(tcptls_session);
* The returned host will contain the address and optional port while
* path will contain everything after the address/port if included.
*/
-static int websocket_client_parse_uri(const char *uri, char **host, struct ast_str **path)
+static int websocket_client_parse_uri(const char *uri, char **host,
+ struct ast_str **path, char **userinfo)
{
struct ast_uri *parsed_uri = ast_uri_parse_websocket(uri);
}
*host = ast_uri_make_host_with_port(parsed_uri);
+ *userinfo = ast_strdup(ast_uri_user_info(parsed_uri));
if (ast_uri_path(parsed_uri) || ast_uri_query(parsed_uri)) {
*path = ast_str_create(64);
struct ast_tcptls_session_args *args;
/*! tcptls connection instance */
struct ast_tcptls_session_instance *ser;
+ /*! Authentication userid:password */
+ char *userinfo;
+ /*! Suppress connection log messages */
+ int suppress_connection_msgs;
};
static void websocket_client_destroy(void *obj)
ast_free(client->key);
ast_free(client->resource_name);
ast_free(client->host);
+ ast_free(client->userinfo);
}
static struct ast_websocket * websocket_client_create(
return NULL;
}
+ if (!ast_uuid_generate_str(ws->session_id, sizeof(ws->session_id))) {
+ ast_log(LOG_ERROR, "Unable to allocate websocket session_id\n");
+ ao2_ref(ws, -1);
+ *result = WS_ALLOCATE_ERROR;
+ return NULL;
+ }
+
if (!(ws->client = ao2_alloc(
sizeof(*ws->client), websocket_client_destroy))) {
ast_log(LOG_ERROR, "Unable to allocate websocket client\n");
+ ao2_ref(ws, -1);
*result = WS_ALLOCATE_ERROR;
return NULL;
}
}
if (websocket_client_parse_uri(
- options->uri, &ws->client->host, &ws->client->resource_name)) {
+ options->uri, &ws->client->host, &ws->client->resource_name,
+ &ws->client->userinfo)) {
ao2_ref(ws, -1);
*result = WS_URI_PARSE_ERROR;
return NULL;
}
+ if (ast_strlen_zero(ws->client->userinfo)
+ && !ast_strlen_zero(options->username)
+ && !ast_strlen_zero(options->password)) {
+ ast_asprintf(&ws->client->userinfo, "%s:%s", options->username,
+ options->password);
+ }
+
if (!(ws->client->args = websocket_client_args_create(
ws->client->host, options->tls_cfg, result))) {
ao2_ref(ws, -1);
return NULL;
}
- ws->client->protocols = ast_strdup(options->protocols);
+ ws->client->suppress_connection_msgs = options->suppress_connection_msgs;
+ ws->client->args->suppress_connection_msgs = options->suppress_connection_msgs;
+ ws->client->protocols = ast_strdup(options->protocols);
ws->client->version = 13;
ws->opcode = -1;
ws->reconstruct = DEFAULT_RECONSTRUCTION_CEILING;
+ ws->timeout = options->timeout;
+
return ws;
}
case 101:
return 0;
case 400:
- ast_log(LOG_ERROR, "Received response 400 - Bad Request "
- "- from %s\n", client->host);
+ if (!client->suppress_connection_msgs) {
+ ast_log(LOG_ERROR, "Received response 400 - Bad Request "
+ "- from %s\n", client->host);
+ }
return WS_BAD_REQUEST;
+ case 401:
+ if (!client->suppress_connection_msgs) {
+ ast_log(LOG_ERROR, "Received response 401 - Unauthorized "
+ "- from %s\n", client->host);
+ }
+ return WS_UNAUTHORIZED;
case 404:
- ast_log(LOG_ERROR, "Received response 404 - Request URL not "
- "found - from %s\n", client->host);
+ if (!client->suppress_connection_msgs) {
+ ast_log(LOG_ERROR, "Received response 404 - Request URL not "
+ "found - from %s\n", client->host);
+ }
return WS_URL_NOT_FOUND;
}
- ast_log(LOG_ERROR, "Invalid HTTP response code %d from %s\n",
- response_code, client->host);
+ if (!client->suppress_connection_msgs) {
+ ast_log(LOG_ERROR, "Invalid HTTP response code %d from %s\n",
+ response_code, client->host);
+ }
return WS_INVALID_RESPONSE;
}
WS_OK : WS_HEADER_MISSING;
}
+#define optional_header_spec "%s%s%s"
+#define print_optional_header(test, name, value) \
+ test ? name : "", \
+ test ? value : "", \
+ test ? "\r\n" : ""
+
static enum ast_websocket_result websocket_client_handshake(
struct websocket_client *client)
{
- char protocols[100] = "";
-
- if (!ast_strlen_zero(client->protocols)) {
- sprintf(protocols, "Sec-WebSocket-Protocol: %s\r\n",
- client->protocols);
- }
-
- if (ast_iostream_printf(client->ser->stream,
- "GET /%s HTTP/1.1\r\n"
- "Sec-WebSocket-Version: %d\r\n"
- "Upgrade: websocket\r\n"
- "Connection: Upgrade\r\n"
- "Host: %s\r\n"
- "Sec-WebSocket-Key: %s\r\n"
- "%s\r\n",
- client->resource_name ? ast_str_buffer(client->resource_name) : "",
- client->version,
- client->host,
- client->key,
- protocols) < 0) {
+ size_t protocols_len = 0;
+ struct ast_variable *auth_header = NULL;
+ size_t res;
+
+ if (!ast_strlen_zero(client->userinfo)) {
+ auth_header = ast_http_create_basic_auth_header(client->userinfo, NULL);
+ if (!auth_header) {
+ ast_log(LOG_ERROR, "Unable to allocate client websocket userinfo\n");
+ return WS_ALLOCATE_ERROR;
+ }
+ }
+
+ protocols_len = client->protocols ? strlen(client->protocols) : 0;
+
+ res = ast_iostream_printf(client->ser->stream,
+ "GET /%s HTTP/1.1\r\n"
+ "Sec-WebSocket-Version: %d\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Host: %s\r\n"
+ optional_header_spec
+ optional_header_spec
+ "Sec-WebSocket-Key: %s\r\n"
+ "\r\n",
+ client->resource_name ? ast_str_buffer(client->resource_name) : "",
+ client->version,
+ client->host,
+ print_optional_header(auth_header, "Authorization: ", auth_header->value),
+ print_optional_header(protocols_len, "Sec-WebSocket-Protocol: ", client->protocols),
+ client->key
+ );
+
+ ast_variables_destroy(auth_header);
+ if (res < 0) {
ast_log(LOG_ERROR, "Failed to send handshake.\n");
return WS_WRITE_ERROR;
}
(char *)buf, len);
}
+const char *websocket_result_string_map[] = {
+ [WS_OK] = "OK",
+ [WS_ALLOCATE_ERROR] = "Allocation error",
+ [WS_KEY_ERROR] = "Key error",
+ [WS_URI_PARSE_ERROR] = "URI parse error",
+ [WS_URI_RESOLVE_ERROR] = "URI resolve error",
+ [WS_BAD_STATUS] = "Bad status line",
+ [WS_INVALID_RESPONSE] = "Invalid response code",
+ [WS_BAD_REQUEST] = "Bad request",
+ [WS_URL_NOT_FOUND] = "URL not found",
+ [WS_HEADER_MISMATCH] = "Header mismatch",
+ [WS_HEADER_MISSING] = "Header missing",
+ [WS_NOT_SUPPORTED] = "Not supported",
+ [WS_WRITE_ERROR] = "Write error",
+ [WS_CLIENT_START_ERROR] = "Client start error",
+ [WS_UNAUTHORIZED] = "Unauthorized"
+};
+
+const char *AST_OPTIONAL_API_NAME(ast_websocket_result_to_str)
+ (enum ast_websocket_result result)
+{
+ if (!ARRAY_IN_BOUNDS(result, websocket_result_string_map)) {
+ return "unknown";
+ }
+ return websocket_result_string_map[result];
+}
+
static int load_module(void)
{
websocketuri.data = websocket_server_internal_create();
*/
cleanup();
+ if (stasis_app_control_is_failed(control)) {
+ res = -1;
+ }
/* The control needs to be removed from the controls container in
* case a new PBX is started and ends up coming back into Stasis.
*/
return find_app_by_name(name);
}
+int stasis_app_is_registered(const char *name)
+{
+ struct stasis_app *app = find_app_by_name(name);
+
+ /*
+ * It's safe to unref app here because we're not actually
+ * using it or returning it.
+ */
+ ao2_cleanup(app);
+
+ return app != NULL;
+}
+
static int append_name(void *obj, void *arg, int flags)
{
struct stasis_app *app = obj;
void stasis_app_unregister(const char *app_name)
{
struct stasis_app *app;
+ struct stasis_app_event_source *source;
+ int res;
if (!app_name) {
return;
return;
}
+ /* Unsubscribe from all event sources. */
+ AST_RWLIST_RDLOCK(&event_sources);
+ AST_LIST_TRAVERSE(&event_sources, source, next) {
+ if (!source->unsubscribe || !source->is_subscribed
+ || !source->is_subscribed(app, NULL)) {
+ continue;
+ }
+
+ res = source->unsubscribe(app, NULL);
+ if (res) {
+ ast_log(LOG_WARNING, "%s: Error unsubscribing from event source '%s'\n",
+ app_name, source->scheme);
+ }
+ }
+ AST_RWLIST_UNLOCK(&event_sources);
+
app_deactivate(app);
/* There's a decent chance that app is ready for cleanup. Go ahead
int is_subscribed;
ao2_lock(device_state_subscriptions);
- is_subscribed = is_subscribed_device_state(app, name);
+ is_subscribed = is_subscribed_device_state(app, S_OR(name, DEVICE_STATE_ALL));
ao2_unlock(device_state_subscriptions);
return is_subscribed;
struct device_state_subscription *sub;
ao2_lock(device_state_subscriptions);
- sub = find_device_state_subscription(app, name);
+ sub = find_device_state_subscription(app, S_OR(name, DEVICE_STATE_ALL));
if (sub) {
remove_device_state_subscription(sub);
}
* When set, /c app_stasis should exit and continue in the dialplan.
*/
unsigned int is_done:1;
+ /*!
+ * When set, /c app_stasis should exit indicating failure and continue
+ * in the dialplan.
+ */
+ unsigned int failed:1;
};
static void control_dtor(void *obj)
ao2_unlock(control->command_queue);
}
+void stasis_app_control_mark_failed(struct stasis_app_control *control)
+{
+ control->failed = 1;
+}
+
+int stasis_app_control_is_failed(const struct stasis_app_control *control)
+{
+ return control->failed;
+}
+
+
struct stasis_app_control_continue_data {
char context[AST_MAX_CONTEXT];
char extension[AST_MAX_EXTENSION];