/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2013, Digium, Inc. * * David M. Lee, II * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /*! \file * * \brief The Asterisk Management Interface - AMI (channel event handling) * * \author David M. Lee, II * * AMI generated many per-channel and global-channel events by converting Stasis * messages to AMI events. It makes sense to simply put them into a single file. */ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/callerid.h" #include "asterisk/channel.h" #include "asterisk/manager.h" #include "asterisk/stasis_message_router.h" #include "asterisk/pbx.h" #include "asterisk/stasis_channels.h" static struct stasis_message_router *channel_state_router; /*** DOCUMENTATION Raised when a new channel is created. A numeric code for the channel's current state, related to ChannelStateDesc Raised when a channel's state changes. Raised when a channel is hung up. A numeric cause code for why the channel was hung up. A description of why the channel was hung up. Raised when a hangup is requested. Raised when a soft hangup is requested with a specific cause code. Raised when a channel enters a new context, extension, priority. Deprecated in 12, but kept for backward compatability. Please use 'Exten' instead. The application about to be executed. The data to be passed to the application. Raised when a channel receives new Caller ID information. A description of the Caller ID presentation. Raised when a dial action has started. A numeric code for the channel's current state, related to ChannelStateDescDest The non-technology specific device being dialed. Dial Raised when a dial action has completed. The result of the dial operation. Dial ***/ struct ast_str *ast_manager_build_channel_state_string_suffix( const struct ast_channel_snapshot *snapshot, const char *suffix) { struct ast_str *out = ast_str_create(1024); int res = 0; if (!out) { return NULL; } res = ast_str_set(&out, 0, "Channel%s: %s\r\n" "ChannelState%s: %d\r\n" "ChannelStateDesc%s: %s\r\n" "CallerIDNum%s: %s\r\n" "CallerIDName%s: %s\r\n" "ConnectedLineNum%s: %s\r\n" "ConnectedLineName%s: %s\r\n" "AccountCode%s: %s\r\n" "Context%s: %s\r\n" "Exten%s: %s\r\n" "Priority%s: %d\r\n" "Uniqueid%s: %s\r\n", suffix, snapshot->name, suffix, snapshot->state, suffix, ast_state2str(snapshot->state), suffix, S_OR(snapshot->caller_number, ""), suffix, S_OR(snapshot->caller_name, ""), suffix, S_OR(snapshot->connected_number, ""), suffix, S_OR(snapshot->connected_name, ""), suffix, snapshot->accountcode, suffix, snapshot->context, suffix, snapshot->exten, suffix, snapshot->priority, suffix, snapshot->uniqueid); if (!res) { return NULL; } if (snapshot->manager_vars) { struct ast_var_t *var; AST_LIST_TRAVERSE(snapshot->manager_vars, var, entries) { ast_str_append(&out, 0, "ChanVariable%s: %s=%s\r\n", suffix, var->name, var->value); } } return out; } struct ast_str *ast_manager_build_channel_state_string( const struct ast_channel_snapshot *snapshot) { return ast_manager_build_channel_state_string_suffix(snapshot, ""); } /*! \brief Struct containing info for an AMI channel event to send out. */ struct snapshot_manager_event { /*! event_flags manager_event() flags parameter. */ int event_flags; /*! manager_event manager_event() category. */ const char *manager_event; AST_DECLARE_STRING_FIELDS( /* extra fields to include in the event. */ AST_STRING_FIELD(extra_fields); ); }; static void snapshot_manager_event_dtor(void *obj) { struct snapshot_manager_event *ev = obj; ast_string_field_free_memory(ev); } /*! * \brief Construct a \ref snapshot_manager_event. * \param event_flags manager_event() flags parameter. * \param manager_event manager_event() category. * \param extra_fields_fmt Format string for extra fields to include. * Or NO_EXTRA_FIELDS for no extra fields. * \return New \ref snapshot_manager_event object. * \return \c NULL on error. */ static struct snapshot_manager_event * __attribute__((format(printf, 3, 4))) snapshot_manager_event_create( int event_flags, const char *manager_event, const char *extra_fields_fmt, ...) { RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup); va_list argp; ast_assert(extra_fields_fmt != NULL); ast_assert(manager_event != NULL); ev = ao2_alloc(sizeof(*ev), snapshot_manager_event_dtor); if (!ev) { return NULL; } if (ast_string_field_init(ev, 20)) { return NULL; } ev->manager_event = manager_event; ev->event_flags = event_flags; va_start(argp, extra_fields_fmt); ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt, argp); va_end(argp); ao2_ref(ev, +1); return ev; } /*! GCC warns about blank or NULL format strings. So, shenanigans! */ #define NO_EXTRA_FIELDS "%s", "" /*! \brief Typedef for callbacks that get called on channel snapshot updates */ typedef struct snapshot_manager_event *(*snapshot_monitor)( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot); /*! \brief Handle channel state changes */ static struct snapshot_manager_event *channel_state_change( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot) { int is_hungup, was_hungup; if (!new_snapshot) { /* Ignore cache clearing events; we'll see the hangup first */ return NULL; } /* The Newchannel, Newstate and Hangup events are closely related, in * in that they are mutually exclusive, basically different flavors * of a new channel state event. */ if (!old_snapshot) { return snapshot_manager_event_create( EVENT_FLAG_CALL, "Newchannel", NO_EXTRA_FIELDS); } was_hungup = ast_test_flag(&old_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0; is_hungup = ast_test_flag(&new_snapshot->flags, AST_FLAG_ZOMBIE) ? 1 : 0; if (!was_hungup && is_hungup) { return snapshot_manager_event_create( EVENT_FLAG_CALL, "Hangup", "Cause: %d\r\n" "Cause-txt: %s\r\n", new_snapshot->hangupcause, ast_cause2str(new_snapshot->hangupcause)); } if (old_snapshot->state != new_snapshot->state) { return snapshot_manager_event_create( EVENT_FLAG_CALL, "Newstate", NO_EXTRA_FIELDS); } /* No event */ return NULL; } /*! * \brief Compares the context, exten and priority of two snapshots. * \param old_snapshot Old snapshot * \param new_snapshot New snapshot * \return True (non-zero) if context, exten or priority are identical. * \return False (zero) if context, exten and priority changed. */ static inline int cep_equal( const struct ast_channel_snapshot *old_snapshot, const struct ast_channel_snapshot *new_snapshot) { ast_assert(old_snapshot != NULL); ast_assert(new_snapshot != NULL); /* We actually get some snapshots with CEP set, but before the * application is set. Since empty application is invalid, we treat * setting the application from nothing as a CEP change. */ if (ast_strlen_zero(old_snapshot->appl) && !ast_strlen_zero(new_snapshot->appl)) { return 0; } return old_snapshot->priority == new_snapshot->priority && strcmp(old_snapshot->context, new_snapshot->context) == 0 && strcmp(old_snapshot->exten, new_snapshot->exten) == 0; } static struct snapshot_manager_event *channel_newexten( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot) { /* No Newexten event on cache clear */ if (!new_snapshot) { return NULL; } /* Empty application is not valid for a Newexten event */ if (ast_strlen_zero(new_snapshot->appl)) { return NULL; } if (old_snapshot && cep_equal(old_snapshot, new_snapshot)) { return NULL; } /* DEPRECATED: Extension field deprecated in 12; remove in 14 */ return snapshot_manager_event_create( EVENT_FLAG_CALL, "Newexten", "Extension: %s\r\n" "Application: %s\r\n" "AppData: %s\r\n", new_snapshot->exten, new_snapshot->appl, new_snapshot->data); } /*! * \brief Compares the callerid info of two snapshots. * \param old_snapshot Old snapshot * \param new_snapshot New snapshot * \return True (non-zero) if callerid are identical. * \return False (zero) if callerid changed. */ static inline int caller_id_equal( const struct ast_channel_snapshot *old_snapshot, const struct ast_channel_snapshot *new_snapshot) { ast_assert(old_snapshot != NULL); ast_assert(new_snapshot != NULL); return strcmp(old_snapshot->caller_number, new_snapshot->caller_number) == 0 && strcmp(old_snapshot->caller_name, new_snapshot->caller_name) == 0; } static struct snapshot_manager_event *channel_new_callerid( struct ast_channel_snapshot *old_snapshot, struct ast_channel_snapshot *new_snapshot) { /* No NewCallerid event on cache clear or first event */ if (!old_snapshot || !new_snapshot) { return NULL; } if (caller_id_equal(old_snapshot, new_snapshot)) { return NULL; } return snapshot_manager_event_create( EVENT_FLAG_CALL, "NewCallerid", "CID-CallingPres: %d (%s)\r\n", new_snapshot->caller_pres, ast_describe_caller_presentation(new_snapshot->caller_pres)); } snapshot_monitor monitors[] = { channel_state_change, channel_newexten, channel_new_callerid }; static void channel_snapshot_update(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message) { RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); struct stasis_cache_update *update; struct ast_channel_snapshot *old_snapshot; struct ast_channel_snapshot *new_snapshot; size_t i; update = stasis_message_data(message); if (ast_channel_snapshot_type() != update->type) { return; } old_snapshot = stasis_message_data(update->old_snapshot); new_snapshot = stasis_message_data(update->new_snapshot); for (i = 0; i < ARRAY_LEN(monitors); ++i) { RAII_VAR(struct snapshot_manager_event *, ev, NULL, ao2_cleanup); ev = monitors[i](old_snapshot, new_snapshot); if (!ev) { continue; } /* If we haven't already, build the channel event string */ if (!channel_event_string) { channel_event_string = ast_manager_build_channel_state_string(new_snapshot); if (!channel_event_string) { return; } } manager_event(ev->event_flags, ev->manager_event, "%s%s", ast_str_buffer(channel_event_string), ev->extra_fields); } } static void channel_varset(struct ast_channel_blob *obj) { RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); const char *variable = ast_json_string_get(ast_json_object_get(obj->blob, "variable")); const char *value = ast_json_string_get(ast_json_object_get(obj->blob, "value")); if (obj->snapshot) { channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); } else { channel_event_string = ast_str_create(35); ast_str_set(&channel_event_string, 0, "Channel: none\r\n" "Uniqueid: none\r\n"); } if (!channel_event_string) { return; } /*** DOCUMENTATION Raised when a variable is set to a particular value. The variable being set. The new value of the variable. ***/ manager_event(EVENT_FLAG_DIALPLAN, "VarSet", "%s" "Variable: %s\r\n" "Value: %s\r\n", ast_str_buffer(channel_event_string), variable, value); } static void channel_userevent(struct ast_channel_blob *obj) { RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); const char *eventname; const char *body; eventname = ast_json_string_get(ast_json_object_get(obj->blob, "eventname")); body = ast_json_string_get(ast_json_object_get(obj->blob, "body")); channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { return; } /*** DOCUMENTATION A user defined event raised from the dialplan. The event name, as specified in the dialplan. UserEvent ***/ manager_event(EVENT_FLAG_USER, "UserEvent", "%s" "UserEvent: %s\r\n" "%s", ast_str_buffer(channel_event_string), eventname, body); } static void channel_hangup_request(struct ast_channel_blob *obj) { RAII_VAR(struct ast_str *, extra, NULL, ast_free); RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); struct ast_json *cause; int is_soft; char *manager_event = "HangupRequest"; extra = ast_str_create(20); if (!extra) { return; } channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { return; } cause = ast_json_object_get(obj->blob, "cause"); if (cause) { ast_str_append(&extra, 0, "Cause: %jd\r\n", ast_json_integer_get(cause)); } is_soft = ast_json_is_true(ast_json_object_get(obj->blob, "soft")); if (is_soft) { manager_event = "SoftHangupRequest"; } manager_event(EVENT_FLAG_CALL, manager_event, "%s%s", ast_str_buffer(channel_event_string), ast_str_buffer(extra)); } static void channel_dtmf_begin(struct ast_channel_blob *obj) { RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); const char *digit = ast_json_string_get(ast_json_object_get(obj->blob, "digit")); const char *direction = ast_json_string_get(ast_json_object_get(obj->blob, "direction")); channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { return; } /*** DOCUMENTATION Raised when a DTMF digit has started on a channel. DTMF digit received or transmitted (0-9, A-E, # or * ***/ manager_event(EVENT_FLAG_DTMF, "DTMFBegin", "%s" "Digit: %s\r\n" "Direction: %s\r\n", ast_str_buffer(channel_event_string), digit, direction); } static void channel_dtmf_end(struct ast_channel_blob *obj) { RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); const char *digit = ast_json_string_get(ast_json_object_get(obj->blob, "digit")); const char *direction = ast_json_string_get(ast_json_object_get(obj->blob, "direction")); long duration_ms = ast_json_integer_get(ast_json_object_get(obj->blob, "duration_ms")); channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); if (!channel_event_string) { return; } /*** DOCUMENTATION Raised when a DTMF digit has ended on a channel. DTMF digit received or transmitted (0-9, A-E, # or * Duration (in milliseconds) DTMF was sent/received ***/ manager_event(EVENT_FLAG_DTMF, "DTMFEnd", "%s" "Digit: %s\r\n" "DurationMs: %ld\r\n" "Direction: %s\r\n", ast_str_buffer(channel_event_string), digit, duration_ms, direction); } /*! * \brief Callback processing messages on the channel topic. */ static void channel_blob_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message) { struct ast_channel_blob *obj = stasis_message_data(message); if (strcmp("varset", ast_channel_blob_json_type(obj)) == 0) { channel_varset(obj); } else if (strcmp("userevent", ast_channel_blob_json_type(obj)) == 0) { channel_userevent(obj); } else if (strcmp("hangup_request", ast_channel_blob_json_type(obj)) == 0) { channel_hangup_request(obj); } else if (strcmp("dtmf_begin", ast_channel_blob_json_type(obj)) == 0) { channel_dtmf_begin(obj); } else if (strcmp("dtmf_end", ast_channel_blob_json_type(obj)) == 0) { channel_dtmf_end(obj); } } /*! * \brief Callback processing messages for channel dialing */ static void channel_dial_cb(void *data, struct stasis_subscription *sub, struct stasis_topic *topic, struct stasis_message *message) { struct ast_multi_channel_blob *obj = stasis_message_data(message); const char *dialstatus; const char *dialstring; struct ast_channel_snapshot *caller; struct ast_channel_snapshot *peer; RAII_VAR(struct ast_str *, caller_event_string, NULL, ast_free); RAII_VAR(struct ast_str *, peer_event_string, NULL, ast_free); if (strcmp("dial", ast_multi_channel_blob_get_type(obj))) { ast_assert(0); return; } caller = ast_multi_channel_blob_get_channel(obj, "caller"); peer = ast_multi_channel_blob_get_channel(obj, "peer"); /* Peer is required - otherwise, who are we dialing? */ ast_assert(peer != NULL); peer_event_string = ast_manager_build_channel_state_string_suffix(peer, "Dest"); if (!peer_event_string) { return; } if (caller) { caller_event_string = ast_manager_build_channel_state_string(caller); if (!caller_event_string) { return; } dialstatus = ast_json_string_get(ast_json_object_get(ast_multi_channel_blob_get_json(obj), "dialstatus")); dialstring = ast_json_string_get(ast_json_object_get(ast_multi_channel_blob_get_json(obj), "dialstring")); if (ast_strlen_zero(dialstatus)) { manager_event(EVENT_FLAG_CALL, "DialBegin", "%s" "%s" "DialString: %s\r\n", ast_str_buffer(caller_event_string), ast_str_buffer(peer_event_string), S_OR(dialstring, "unknown")); } else { manager_event(EVENT_FLAG_CALL, "DialEnd", "%s" "%s" "DialStatus: %s\r\n", ast_str_buffer(caller_event_string), ast_str_buffer(peer_event_string), S_OR(dialstatus, "unknown")); } } else { /* TODO: If we don't have a caller, this should be treated as an Originate */ } } static void manager_channels_shutdown(void) { stasis_message_router_unsubscribe(channel_state_router); channel_state_router = NULL; } int manager_channels_init(void) { int ret = 0; if (channel_state_router) { /* Already initialized */ return 0; } ast_register_atexit(manager_channels_shutdown); channel_state_router = stasis_message_router_create( stasis_caching_get_topic(ast_channel_topic_all_cached())); if (!channel_state_router) { return -1; } ret |= stasis_message_router_add(channel_state_router, stasis_cache_update_type(), channel_snapshot_update, NULL); ret |= stasis_message_router_add(channel_state_router, ast_channel_blob_type(), channel_blob_cb, NULL); ret |= stasis_message_router_add(channel_state_router, ast_channel_dial_type(), channel_dial_cb, NULL); /* If somehow we failed to add any routes, just shut down the whole * thing and fail it. */ if (ret) { manager_channels_shutdown(); return -1; } return 0; }