/*! * \file sccp_pbx.c * \brief SCCP PBX Asterisk Wrapper Class * \author Diederik de Groot * \note Reworked, but based on chan_sccp code. * The original chan_sccp driver that was made by Zozo which itself was derived from the chan_skinny driver. * Modified by Jan Czmok and Julien Goodwin * \note This program is free software and may be modified and distributed under the terms of the GNU Public License. * See the LICENSE file at the top of the source tree. * */ #include "config.h" #include "common.h" #include "sccp_pbx.h" #include "sccp_channel.h" #include "sccp_device.h" #include "sccp_conference.h" #include "sccp_feature.h" #include "sccp_line.h" #include "sccp_utils.h" #include "sccp_indicate.h" #include "sccp_linedevice.h" #include "sccp_rtp.h" #include "sccp_netsock.h" #include "sccp_session.h" #include "sccp_atomic.h" #include "sccp_labels.h" #include "sccp_threadpool.h" SCCP_FILE_VERSION(__FILE__, ""); #include #include // ast_update_use_count #include // AST_CAUSE_NORMAL_CLEARING /*! * \brief SCCP Request Channel * \param lineName Line Name as Char * \param autoanswer_type SCCP Auto Answer Type * \param autoanswer_cause SCCP Auto Answer Cause * \param ringermode Ringer Mode * \param channel SCCP Channel * \return SCCP Channel Request Status * * \called_from_asterisk */ sccp_channel_request_status_t sccp_requestChannel(const char * lineName, sccp_autoanswer_t autoanswer_type, uint8_t autoanswer_cause, skinny_ringtype_t ringermode, sccp_channel_t * const * channel) { if (!lineName) { return SCCP_REQUEST_STATUS_ERROR; } char mainId[SCCP_MAX_EXTENSION]; sccp_subscription_id_t subscriptionId; if(!sccp_parseComposedId(lineName, 80, &subscriptionId, mainId)) { sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "Could not parse lineName:%s !\n", lineName); return SCCP_REQUEST_STATUS_LINEUNKNOWN; }; AUTO_RELEASE(sccp_line_t, l, sccp_line_find_byname(mainId, FALSE)); if (!l) { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "SCCP/%s does not exist!\n", mainId); return SCCP_REQUEST_STATUS_LINEUNKNOWN; } sccp_log_and((DEBUGCAT_CORE + DEBUGCAT_HIGH)) (VERBOSE_PREFIX_1 "[SCCP] in file %s, line %d (%s)\n", __FILE__, __LINE__, __PRETTY_FUNCTION__); if (SCCP_RWLIST_GETSIZE(&l->devices) == 0) { sccp_log((DEBUGCAT_DEVICE + DEBUGCAT_LINE)) (VERBOSE_PREFIX_3 "SCCP/%s isn't currently registered anywhere.\n", l->name); return SCCP_REQUEST_STATUS_LINEUNAVAIL; } sccp_log_and((DEBUGCAT_CORE + DEBUGCAT_HIGH)) (VERBOSE_PREFIX_1 "[SCCP] in file %s, line %d (%s)\n", __FILE__, __LINE__, __PRETTY_FUNCTION__); /* call forward check */ // Allocate a new SCCP channel. /* on multiline phone we set the line when answering or switching lines */ AUTO_RELEASE(sccp_channel_t, my_sccp_channel, sccp_channel_allocate(l, NULL)); if (!my_sccp_channel) { return SCCP_REQUEST_STATUS_ERROR; } /* set subscriberId for individual device addressing */ if (!sccp_strlen_zero(subscriptionId.number)) { sccp_copy_string(my_sccp_channel->subscriptionId.number, subscriptionId.number, sizeof(my_sccp_channel->subscriptionId.number)); if (!sccp_strlen_zero(subscriptionId.name)) { sccp_copy_string(my_sccp_channel->subscriptionId.name, subscriptionId.name, sizeof(my_sccp_channel->subscriptionId.name)); } else { //pbx_log(LOG_NOTICE, "%s: calling subscriber id=%s\n", l->id, my_sccp_channel->subscriptionId.number); } } else { sccp_copy_string(my_sccp_channel->subscriptionId.number, l->defaultSubscriptionId.number, sizeof(my_sccp_channel->subscriptionId.number)); sccp_copy_string(my_sccp_channel->subscriptionId.name, l->defaultSubscriptionId.name, sizeof(my_sccp_channel->subscriptionId.name)); //pbx_log(LOG_NOTICE, "%s: calling all subscribers\n", l->id); } //memset(&channel->preferences.audio, 0, sizeof(channel->preferences.audio)); //memset(&channel->preferences.video, 0, sizeof(channel->preferences.video)); my_sccp_channel->autoanswer_type = autoanswer_type; my_sccp_channel->autoanswer_cause = autoanswer_cause; my_sccp_channel->ringermode = ringermode; my_sccp_channel->hangupRequest = sccp_astgenwrap_requestQueueHangup; (*(sccp_channel_t **)channel) = sccp_channel_retain(my_sccp_channel); return SCCP_REQUEST_STATUS_SUCCESS; } /*! * \brief SCCP Structure to pass data to the pbx answer thread */ struct sccp_answer_conveyor_struct { sccp_linedevice_t * ld; uint32_t callid; }; /*! * \brief Call Auto Answer Thead * \param data Data * * The Auto Answer thread is started by ref sccp_pbx_call if necessary */ static void *sccp_pbx_call_autoanswer_thread(void *data) { struct sccp_answer_conveyor_struct *conveyor = (struct sccp_answer_conveyor_struct *)data; sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "SCCP: autoanswer thread started\n"); sleep(GLOB(autoanswer_ring_time)); pthread_testcancel(); if (!conveyor) { pbx_log(LOG_ERROR, "SCCP: (autoanswer_thread) No conveyor\n"); return NULL; } if(!conveyor->ld) { pbx_log(LOG_ERROR, "SCCP: (autoanswer_thread) No linedevice\n"); goto FINAL; } { AUTO_RELEASE(sccp_device_t, device, sccp_device_retain(conveyor->ld->device)); if (!device) { pbx_log(LOG_ERROR, "SCCP: (autoanswer_thread) no device\n"); goto FINAL; } AUTO_RELEASE(sccp_channel_t, c , sccp_channel_find_byid(conveyor->callid)); if (!c || c->state != SCCP_CHANNELSTATE_RINGING) { pbx_log(LOG_WARNING, "%s: (autoanswer_thread) %s\n", device->id, c ? "not ringing" : "no channel"); goto FINAL; } if (c->pbx_callid) { pbx_callid_threadassoc_add(c->pbx_callid); } sccp_channel_answer(device, c); c->setTone(c, GLOB(autoanswer_tone), SKINNY_TONEDIRECTION_USER); if (c->autoanswer_type == SCCP_AUTOANSWER_1W) { sccp_dev_set_microphone(device, SKINNY_STATIONMIC_OFF); } if (c->pbx_callid) { pbx_callid_threadassoc_remove(); } } FINAL: if(conveyor->ld) { sccp_linedevice_release(&conveyor->ld); // retained in calling thread, explicit release required here } sccp_free(conveyor); return NULL; } /*! * \brief Incoming Calls by Asterisk SCCP_Request * \param c SCCP Channel * \param dest Destination as char * \param timeout Timeout after which incoming call is cancelled as int * \return Success as int * * \todo reimplement DNDMODES, ringermode=urgent, autoanswer * * \callgraph * \callergraph * * \called_from_asterisk * * \note called with c retained */ // improved sharedline handling // - calculate c->subscribers correctly (using when handling sccp_softkey_onhook, to define behaviour) // - handle dnd and callforward after calculating c->subscribers // - use asterisk local channel to resolve forward bei non-shared line, which does evade // * sccp_channel_callforward // * sccp_channel_end_forwarding_channel // * c->parentChannel // * masquerading in sccp_pbx_answer // in that case int sccp_pbx_call(channelPtr c, const char * dest, int timeout) { if (!c) { return -1; } int res = 0; AUTO_RELEASE(sccp_line_t, l , sccp_line_retain(c->line)); if (!l) { pbx_log(LOG_WARNING, "SCCP: The channel %08X has no line. giving up.\n", (c->callid)); return -1; } sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: Asterisk request to call %s\n", l->name, iPbx.getChannelName(c)); /* Reinstated this call instead of the following lines */ char cid_name[StationMaxNameSize] = {0}; char cid_num[StationMaxDirnumSize] = {0}; char suffixedNumber[StationMaxDirnumSize] = {0}; sccp_callerid_presentation_t presentation = CALLERID_PRESENTATION_ALLOWED; sccp_callinfo_t *ci = sccp_channel_getCallInfo(c); iCallInfo.Getter(ci, SCCP_CALLINFO_CALLINGPARTY_NAME, &cid_name, SCCP_CALLINFO_CALLINGPARTY_NUMBER, &cid_num, SCCP_CALLINFO_PRESENTATION, &presentation, SCCP_CALLINFO_KEY_SENTINEL); sccp_copy_string(suffixedNumber, cid_num, sizeof(suffixedNumber)); //! \todo implement dnid, ani, ani2 and rdnis sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "SCCP: (sccp_pbx_call) asterisk callerid='%s <%s>'\n", cid_name, cid_num); /* Set the channel callingParty Name and Number, called Party Name and Number, original CalledParty Name and Number, Presentation */ if (GLOB(recorddigittimeoutchar)) { /* The hack to add the # at the end of the incoming number is only applied for numbers beginning with a 0, which is appropriate for Germany and other countries using similar numbering plan. The option should be generalized, moved to the dialplan, or otherwise be replaced. */ /* Also, we require an option whether to add the timeout suffix to certain enbloc dialed numbers (such as via 7970 enbloc dialing) if they match a certain pattern. This would help users dial from call history lists on other phones, which do not have enbloc dialing, when using shared lines. */ int length = sccp_strlen(cid_num); if (length && (length + 2 < StationMaxDirnumSize) && ('\0' == cid_num[0])) { suffixedNumber[length + 0] = GLOB(digittimeoutchar); suffixedNumber[length + 1] = '\0'; } /* Set the channel calledParty Name and Number 7910 compatibility */ } //! \todo implement dnid, ani, ani2 and rdnis sccp_callerid_presentation_t pbx_presentation = iPbx.get_callerid_presentation ? iPbx.get_callerid_presentation(c->owner) : SCCP_CALLERID_PRESENTATION_SENTINEL; if ( (!sccp_strequals(suffixedNumber, cid_num)) || (pbx_presentation != SCCP_CALLERID_PRESENTATION_SENTINEL && pbx_presentation != presentation) ) { iCallInfo.Setter(ci, SCCP_CALLINFO_CALLINGPARTY_NUMBER, (!sccp_strlen_zero(suffixedNumber) ? suffixedNumber : NULL), SCCP_CALLINFO_PRESENTATION, (pbx_presentation != SCCP_CALLERID_PRESENTATION_SENTINEL) ? pbx_presentation : presentation, SCCP_CALLINFO_KEY_SENTINEL); } sccp_channel_display_callInfo(c); if (!c->ringermode) { c->ringermode = GLOB(ringtype); } boolean_t isRinging = FALSE; boolean_t isBusy = FALSE; boolean_t bypassCallForward = !sccp_strlen_zero(pbx_builtin_getvar_helper(c->owner, "BYPASS_CFWD")); sccp_linedevice_t * ForwardingLineDevice = NULL; sccp_linedevice_t * ld = NULL; sccp_channelstate_t previousstate = c->previousChannelState; SCCP_LIST_LOCK(&l->devices); int num_devices = SCCP_LIST_GETSIZE(&l->devices); c->subscribers = num_devices; pbx_str_t * cfwds_all = pbx_str_alloca(DEFAULT_PBX_STR_BUFFERSIZE); pbx_str_t * cfwds_busy = pbx_str_alloca(DEFAULT_PBX_STR_BUFFERSIZE); pbx_str_t * cfwds_noanswer = pbx_str_alloca(DEFAULT_PBX_STR_BUFFERSIZE); int cfwd_all = 0; int cfwd_busy = 0; int cfwd_noanswer = 0; SCCP_LIST_TRAVERSE(&l->devices, ld, list) { AUTO_RELEASE(sccp_channel_t, active_channel, sccp_device_getActiveChannel(ld->device)); // skip incoming call on a shared line from the originator. (sharedline calling same sharedline) if (active_channel && active_channel != c && sccp_strequals(iPbx.getChannelLinkedId(active_channel), iPbx.getChannelLinkedId(c))) { sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "SCCP: (sccp_pbx_call) skip ringing on %s\n", ld->device->id); c->subscribers--; continue; } /* do we have cfwd enabled? */ if(ld->cfwd[SCCP_CFWD_ALL].enabled) { ast_str_append(&cfwds_all, DEFAULT_PBX_STR_BUFFERSIZE, "%s%s", cfwd_all++ ? "," : "", ld->cfwd[SCCP_CFWD_ALL].number); } if(ld->cfwd[SCCP_CFWD_BUSY].enabled) { ast_str_append(&cfwds_busy, DEFAULT_PBX_STR_BUFFERSIZE, "%s%s", cfwd_busy++ ? "," : "", ld->cfwd[SCCP_CFWD_BUSY].number); } if(!bypassCallForward && (ld->cfwd[SCCP_CFWD_ALL].enabled || (ld->cfwd[SCCP_CFWD_BUSY].enabled && (sccp_device_getDeviceState(ld->device) != SCCP_DEVICESTATE_ONHOOK || sccp_device_getActiveAccessory(ld->device))))) { if (num_devices == 1) { /* when single line -> use asterisk functionality directly, without creating new channel + masquerade */ sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "%s: Call Forward active on line %s\n", ld->device->id, ld->line->name); ForwardingLineDevice = ld; } else { /* shared line -> create a temp channel to call forward destination and tie them together */ pbx_log(LOG_NOTICE, "%s: handle cfwd to %s for line %s\n", ld->device->id, ld->cfwd[SCCP_CFWD_ALL].enabled ? ld->cfwd[SCCP_CFWD_ALL].number : ld->cfwd[SCCP_CFWD_BUSY].number, l->name); if(sccp_channel_forward(c, ld, ld->cfwd[SCCP_CFWD_ALL].enabled ? ld->cfwd[SCCP_CFWD_ALL].number : ld->cfwd[SCCP_CFWD_BUSY].number) == 0) { sccp_device_sendcallstate(ld->device, ld->lineInstance, c->callid, SKINNY_CALLSTATE_INTERCOMONEWAY, SKINNY_CALLPRIORITY_NORMAL, SKINNY_CALLINFO_VISIBILITY_DEFAULT); sccp_channel_send_callinfo(ld->device, c); isRinging = TRUE; } }; c->subscribers--; continue; } if(ld->cfwd[SCCP_CFWD_NOANSWER].enabled && c) { sccp_channel_schedule_cfwd_noanswer(c, GLOB(cfwdnoanswer_timeout)); ast_str_append(&cfwds_noanswer, DEFAULT_PBX_STR_BUFFERSIZE, "%s%s", cfwd_noanswer++ ? "," : "", ld->cfwd[SCCP_CFWD_NOANSWER].number); } if(!ld->device->session) { sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "%s: line device has no session\n", DEV_ID_LOG(ld->device)); c->subscribers--; continue; } /* check if c->subscriptionId.number is matching deviceSubscriptionID */ /* This means that we call only those devices on a shared line which match the specified subscription id in the dial parameters. */ if(!sccp_util_matchSubscriptionId(c, ld->subscriptionId.number)) { sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "%s: device does not match subscriptionId.number c->subscriptionId.number: '%s', deviceSubscriptionID: '%s'\n", DEV_ID_LOG(ld->device), c->subscriptionId.number, ld->subscriptionId.number); c->subscribers--; continue; } /* reset channel state (because we are offering the same call to multiple (shared) lines)*/ c->previousChannelState = previousstate; int calls = sccp_getCallCount(ld); sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "%s: #calls:%d, Incoming calls limit:%d\n", l->name, calls, l->incominglimit); if(l->incominglimit && calls > l->incominglimit) { sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "%s: #calls:%d > Incoming calls limit:%d -> returning BUSY\n", l->name, calls, l->incominglimit); isBusy = TRUE; c->subscribers--; continue; } if (active_channel) { sccp_indicate(ld->device, c, SCCP_CHANNELSTATE_CALLWAITING); /* display the new call on prompt */ AUTO_RELEASE(sccp_linedevice_t, activeChannelLinedevice, active_channel->getLineDevice(active_channel)); if (activeChannelLinedevice) { char caller[100] = {0}; if(!sccp_strlen_zero(cid_name)) { if(!sccp_strlen_zero(cid_num)) { snprintf(caller,sizeof(caller), "%s %s <%s>", SKINNY_DISP_CALL_WAITING, cid_name, cid_num); } else { snprintf(caller,sizeof(caller), "%s %s", SKINNY_DISP_CALL_WAITING, cid_name); } } else { if (!sccp_strlen_zero(cid_num)) { snprintf(caller,sizeof(caller), "%s %s", SKINNY_DISP_CALL_WAITING, cid_num); } else { snprintf(caller,sizeof(caller), "%s %s", SKINNY_DISP_CALL_WAITING, SKINNY_DISP_UNKNOWN_NUMBER); } } // snprintf(prompt, sizeof(prompt), "%s: %s: %s", active_channel->line->name, SKINNY_DISP_FROM, cid_num); // sccp_dev_displayprompt(ld->device, activeChannelLinedevice->lineInstance, active_channel->callid, caller, SCCP_DISPLAYSTATUS_TIMEOUT); sccp_dev_set_message(ld->device, caller, SCCP_DISPLAYSTATUS_TIMEOUT, FALSE, FALSE); } ForwardingLineDevice = NULL; /* reset cfwd if shared */ isRinging = TRUE; } else { /** check if ringermode is not urgent and device enabled dnd in reject mode */ if(SKINNY_RINGTYPE_URGENT != c->ringermode && ld->device->dndFeature.enabled && ld->device->dndFeature.status == SCCP_DNDMODE_REJECT) { sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "%s: DND active on line %s, returning Busy\n", ld->device->id, ld->line->name); isBusy = TRUE; c->subscribers--; continue; } ForwardingLineDevice = NULL; /* reset cfwd if shared */ sccp_log(DEBUGCAT_PBX)(VERBOSE_PREFIX_3 "%s: Ringing %sLine: %s on device:%s using channel:%s, ringermode:%s\n", ld->device->id, SCCP_LIST_GETSIZE(&l->devices) > 1 ? "Shared" : "", ld->line->name, ld->device->id, c->designator, skinny_ringtype2str(c->ringermode)); sccp_indicate(ld->device, c, SCCP_CHANNELSTATE_RINGING); isRinging = TRUE; if (c->autoanswer_type) { struct sccp_answer_conveyor_struct *conveyor = (struct sccp_answer_conveyor_struct *)sccp_calloc(1, sizeof(struct sccp_answer_conveyor_struct)); if (conveyor) { sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "%s: Running the autoanswer thread on %s\n", DEV_ID_LOG(ld->device), iPbx.getChannelName(c)); conveyor->callid = c->callid; conveyor->ld = sccp_linedevice_retain(ld); sccp_threadpool_add_work(GLOB(general_threadpool), sccp_pbx_call_autoanswer_thread, (void *) conveyor); } else { pbx_log(LOG_ERROR, SS_Memory_Allocation_Error, c->designator); } } } } SCCP_LIST_UNLOCK(&l->devices); // sccp_log(DEBUGCAT_PBX)(VERBOSE_PREFIX_3 "%s: isRinging:%d, hadDNDParticipant:%d, ForwardingLineDevice:%p\n", c->designator, isRinging, isBusy, ForwardingLineDevice); if(cfwd_all) { pbx_builtin_setvar_helper(c->owner, "_CFWD_ALL", pbx_str_buffer(cfwds_all)); } if(cfwd_busy) { pbx_builtin_setvar_helper(c->owner, "_CFWD_BUSY", pbx_str_buffer(cfwds_busy)); } if(cfwd_noanswer) { pbx_builtin_setvar_helper(c->owner, "_CFWD_NOANSWER", pbx_str_buffer(cfwds_noanswer)); } if (isRinging) { sccp_channel_setChannelstate(c, SCCP_CHANNELSTATE_RINGING); iPbx.set_callstate(c, AST_STATE_RINGING); iPbx.queue_control(c->owner, AST_CONTROL_RINGING); } else if (ForwardingLineDevice) { /* when single line -> use asterisk functionality directly, without creating new channel + masquerade */ pbx_log(LOG_NOTICE, "%s: handle cfwd to %s for line %s\n", ForwardingLineDevice->device->id, ForwardingLineDevice->cfwd[SCCP_CFWD_ALL].enabled ? ForwardingLineDevice->cfwd[SCCP_CFWD_ALL].number : ForwardingLineDevice->cfwd[SCCP_CFWD_BUSY].number, l->name); #if CS_AST_CONTROL_REDIRECTING iPbx.queue_control(c->owner, AST_CONTROL_REDIRECTING); #endif pbx_channel_call_forward_set(c->owner, ForwardingLineDevice->cfwd[SCCP_CFWD_ALL].enabled ? ForwardingLineDevice->cfwd[SCCP_CFWD_ALL].number : ForwardingLineDevice->cfwd[SCCP_CFWD_BUSY].number); sccp_device_sendcallstate(ForwardingLineDevice->device, ForwardingLineDevice->lineInstance, c->callid, SKINNY_CALLSTATE_INTERCOMONEWAY, SKINNY_CALLPRIORITY_NORMAL, SKINNY_CALLINFO_VISIBILITY_DEFAULT); sccp_channel_send_callinfo(ForwardingLineDevice->device, c); } else if(isBusy) { iPbx.queue_control(c->owner, AST_CONTROL_BUSY); // iPbx.set_callstate(c, AST_STATE_BUSY); // pbx_channel_set_hangupcause(c->owner, AST_CAUSE_USER_BUSY); pbx_channel_set_hangupcause(c->owner, AST_CAUSE_BUSY); res = 0; } else { iPbx.queue_control(c->owner, AST_CONTROL_CONGESTION); res = -1; } /* set linevariables */ PBX_VARIABLE_TYPE *v = l->variables; while (c->owner && !pbx_check_hangup(c->owner) && l && v) { pbx_builtin_setvar_helper(c->owner, v->name, v->value); v = v->next; } sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_call) Returning: %d\n", c->designator, res); return res; } /*! * callback function to handle callforward when recipient does not answer within GLOB(cfwdnoanswer_timeout) * * this callback is scheduled in sccp_pbx_call / sccp_channel_schedule_cfwd_noanswer */ int sccp_pbx_cfwdnoanswer_cb(const void * data) { AUTO_RELEASE(sccp_channel_t, c, (channelPtr)data); // explicitly taken in sccp_channel_schedule_cfwd_noanswer, releasing on exit if(!c || !c->owner) { pbx_log(LOG_WARNING, "SCCP: No channel provided.\n"); return -1; } if((ATOMIC_FETCH(&c->scheduler.deny, &c->scheduler.lock) == 0)) { c->scheduler.cfwd_noanswer_id = -3; /* prevent further cfwd_noanswer_id scheduling */ } AUTO_RELEASE(sccp_line_t, l, sccp_line_retain(c->line)); if(!l) { pbx_log(LOG_WARNING, "%s: The channel has no line. giving up.\n", c->designator); return -2; } if (sccp_channel_isAnswering(c)) { sccp_log(DEBUGCAT_CHANNEL)(VERBOSE_PREFIX_2 "%s: The channel is already being answered. canceling forward\n", c->designator); return -3; } pbx_channel_lock(c->owner); PBX_CHANNEL_TYPE * forwarder = pbx_channel_ref(c->owner); pbx_channel_unlock(c->owner); if(!forwarder || c->isHangingUp || pbx_check_hangup_locked(forwarder)) { pbx_log(LOG_WARNING, "%s: The channel was already hungup. giving up.\n", c->designator); pbx_channel_unref(forwarder); return -4; } boolean_t bypassCallForward = !sccp_strlen_zero(pbx_builtin_getvar_helper(forwarder, "BYPASS_CFWD")); sccp_linedevice_t * ld = NULL; SCCP_LIST_LOCK(&l->devices); int num_devices = SCCP_LIST_GETSIZE(&l->devices); SCCP_LIST_TRAVERSE(&l->devices, ld, list) { /* do we have cfwd enabled? */ if(ld->cfwd[SCCP_CFWD_NOANSWER].enabled && !sccp_strlen_zero(ld->cfwd[SCCP_CFWD_NOANSWER].number) && !bypassCallForward) { if(num_devices == 1) { /* when single line -> use asterisk functionality directly, without creating new channel + masquerade */ sccp_log(DEBUGCAT_PBX)("%s: Redirecting call to callforward noanswer:%s\n", c->designator, ld->cfwd[SCCP_CFWD_NOANSWER].number); #if CS_AST_CONTROL_REDIRECTING iPbx.queue_control(forwarder, AST_CONTROL_REDIRECTING); #endif pbx_channel_call_forward_set(forwarder, ld->cfwd[SCCP_CFWD_NOANSWER].number); sccp_device_sendcallstate(ld->device, ld->lineInstance, c->callid, SKINNY_CALLSTATE_INTERCOMONEWAY, SKINNY_CALLPRIORITY_NORMAL, SKINNY_CALLINFO_VISIBILITY_DEFAULT); sccp_channel_send_callinfo(ld->device, c); iPbx.set_owner(c, NULL); break; //! \todo currently using only the first match. } else { /* shared line -> create a temp channel to call forward destination and tie them together */ pbx_log(LOG_NOTICE, "%s: handle cfwd to %s for line %s\n", ld->device->id, ld->cfwd[SCCP_CFWD_NOANSWER].number, l->name); sccp_channel_forward(c, ld, ld->cfwd[SCCP_CFWD_NOANSWER].number); } } } SCCP_LIST_UNLOCK(&l->devices); pbx_channel_unref(forwarder); return 0; } /*! * \brief Handle Hangup Request by Asterisk * \param channel SCCP Channel * \return Success as int * * \callgraph * \callergraph * * \called_from_asterisk via sccp_wrapper_asterisk.._hangup * * \note sccp_channel should be retained in calling function */ channelPtr sccp_pbx_hangup(constChannelPtr channel) { /* here the ast channel is locked */ // sccp_log((DEBUGCAT_CORE)) (;VERBOSE_PREFIX_3 "SCCP: Asterisk request to hangup channel %s\n", iPbx.getChannelName(c)); (void) ATOMIC_DECR(&GLOB(usecnt), 1, &GLOB(usecnt_lock)); pbx_update_use_count(); AUTO_RELEASE(sccp_channel_t, c , sccp_channel_retain(channel)); if (!c) { sccp_log_and((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: Asked to hangup channel. SCCP channel already deleted\n"); return NULL; } c->isHangingUp = TRUE; sccp_log_and((DEBUGCAT_PBX + DEBUGCAT_CHANNEL))(VERBOSE_PREFIX_3 "%s: Asked to hangup channel.\n", c->designator); AUTO_RELEASE(sccp_device_t, d , sccp_channel_getDevice(c)); if(d && d->session) { sccp_session_waitForPendingRequests(d->session); /* if ( GLOB(remotehangup_tone) && SKINNY_DEVICE_RS_OK == sccp_device_getRegistrationState(d) && SCCP_DEVICESTATE_OFFHOOK == sccp_device_getDeviceState(d) && SCCP_CHANNELSTATE_IsConnected(c->state) && c == d->active_channel ) { c->setTone(c, GLOB(remotehangup_tone), SKINNY_TONEDIRECTION_USER); }*/ } AUTO_RELEASE(sccp_line_t, l , sccp_line_retain(c->line)); #ifdef CS_SCCP_CONFERENCE if (c && c->conference) { sccp_conference_release(&c->conference); /* explicit release required here */ } if (d && d->conference) { sccp_conference_release(&d->conference); /* explicit release required here */ } #endif // CS_SCCP_CONFERENCE if (c->rtp.audio.instance || c->rtp.video.instance) { sccp_channel_closeAllMediaTransmitAndReceive(c); } // removing scheduled dialing sccp_channel_stop_schedule_digittimout(c); sccp_channel_stop_schedule_cfwd_noanswer(c); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL))(VERBOSE_PREFIX_3 "%s: Current pbxchannelstate %s(%d)\n", c->designator, sccp_channelstate2str(c->state), c->state); /* end callforwards */ sccp_channel_end_forwarding_channel(c); /* cancel transfer if in progress */ if(d) { sccp_channel_transfer_cancel(d, c); } if (l) { /* remove call from transferee, transferer */ sccp_linedevice_t * ld = NULL; SCCP_LIST_LOCK(&l->devices); SCCP_LIST_TRAVERSE(&l->devices, ld, list) { AUTO_RELEASE(sccp_device_t, tmpDevice, sccp_device_retain(ld->device)); if(!d && tmpDevice && SKINNY_DEVICE_RS_OK == sccp_device_getRegistrationState(tmpDevice)) { d = sccp_device_retain(tmpDevice); } if (tmpDevice) { sccp_channel_transfer_release(tmpDevice, c); /* explicit release required here */ } } SCCP_LIST_UNLOCK(&l->devices); sccp_line_removeChannel(l, c); } if (d) { if (d->monitorFeature.status & SCCP_FEATURE_MONITOR_STATE_ACTIVE) { d->monitorFeature.status &= ~SCCP_FEATURE_MONITOR_STATE_ACTIVE; sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "%s: Reset monitor state after hangup\n", DEV_ID_LOG(d)); sccp_feat_changed(d, NULL, SCCP_FEATURE_MONITOR); } if (SCCP_CHANNELSTATE_DOWN != c->state && SCCP_CHANNELSTATE_ONHOOK != c->state) { sccp_indicate(d, c, SCCP_CHANNELSTATE_ONHOOK); } /* requesting statistics */ sccp_channel_StatisticsRequest(c); sccp_channel_clean(c); return c; /* returning unretained so that sccp_wrapper_asterisk113_hangup can clear out the last reference */ } return NULL; } /*! * \brief Asterisk Channel as been answered by remote * \note we have no bridged channel at this point * * \param channel SCCCP channel * \return Success as int * * \callgraph * \callergraph * * \called_from_asterisk * * \todo masquarade does not succeed when forwarding to a dialplan extension which starts with PLAYBACK (Is this still the case, i think this might have been resolved ?? - DdG -) */ int sccp_pbx_remote_answer(constChannelPtr channel) { int res = -1; /* \todo perhaps we should lock channel here. */ AUTO_RELEASE(sccp_channel_t, c , sccp_channel_retain(channel)); if(!c || !c->owner) { return res; } sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_2 "%s: (%s) Remote end has answered the call.\n", c->designator, __func__); sccp_channel_stop_schedule_cfwd_noanswer(c); // sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "%s: sccp_pbx_answer checking parent channel\n", c->currentDeviceId); if (c->parentChannel) { // containing a retained channel, final release at the end /* we are a forwarded call, bridge me with my parent (the forwarded channel will take the place of the forwarder.) */ sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "%s: (%s) handling forwarded call\n", c->designator, __func__); pbx_channel_lock(c->parentChannel->owner); PBX_CHANNEL_TYPE * forwarder = pbx_channel_ref(c->parentChannel->owner); pbx_channel_unlock(c->parentChannel->owner); pbx_channel_lock(c->owner); PBX_CHANNEL_TYPE * tmp_channel = pbx_channel_ref(c->owner); pbx_channel_unlock(c->owner); const char * destinationChannelName = pbx_builtin_getvar_helper(tmp_channel, CS_BRIDGEPEERNAME); PBX_CHANNEL_TYPE * destination = NULL; if(sccp_strlen_zero(destinationChannelName) || !iPbx.getChannelByName(destinationChannelName, &destination)) { pbx_log(LOG_NOTICE, "%s: (%s) Could not retrieve channel for destination: %s", c->designator, __func__, destinationChannelName); return -2; } /* if (iPbx.getChannelAppl(c)) { sccp_log_and((DEBUGCAT_PBX + DEBUGCAT_HIGH)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_answer) %s bridging to dialplan application %s\n", c->currentDeviceId, iPbx.getChannelName(c), iPbx.getChannelAppl(c)); } */ sccp_log(DEBUGCAT_PBX)(VERBOSE_PREFIX_3 "\n" "\tendpoint1 | bridge | endpoint2 | comment\n" "\t=================== | ================== | ================== | =================\n" "\t%-20.20s| primary_call |%20.20s| creates tmp_channel in sccp_pbx_call to call forwarding destination\n" "\t%-20.20s| temp_bridge |%20.20s| masquerade on answer of endpoint 2\n" "\t------------------- | ------------------ | ------------------ | <-- masquerade temp_bridge:endpoint2 -> primary_call:endpoint2\n" "\t%-20.20s| primary call |%20.20s| after masquerading, hangup temp_bridge:temp_channel\n", "incoming", pbx_channel_name(forwarder), pbx_channel_name(tmp_channel), destinationChannelName, "incoming", destinationChannelName); do { // retrieve channel by name which will replace in the forwarded channel sccp_channel_release(&c->parentChannel); if(destination) { sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "%s: (%s) handling forwarded call. Replace %s with %s.\n", c->designator, __func__, pbx_channel_name(forwarder), pbx_channel_name(destination)); #if ASTERISK_VERSION_GROUP < 116 /* set the channel and the bridge to state UP to fix problem with fast pickup / autoanswer */ pbx_setstate(tmp_channel, AST_STATE_UP); pbx_setstate(destination, AST_STATE_UP); #endif if(!iPbx.masqueradeHelper(destination, forwarder)) { pbx_log(LOG_ERROR, "%s: (%s) Failed to masquerade bridge into forwarded channel\n", c->designator, __func__); if(destination) { pbx_channel_unref(destination); } res = -3; break; } #if ASTERISK_VERSION_GROUP > 106 // Note: destination has taken the place of forwarder pbx_indicate(forwarder, AST_CONTROL_CONNECTED_LINE); #endif sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_4 "%s: (%s) Masqueraded into %s\n", c->designator, __func__, pbx_channel_name(forwarder)); res = 0; } else { pbx_log(LOG_ERROR, "%s: (%s) Could not retrieve forwarding channel by name:%s: -> Hangup\n", c->designator, __func__, destinationChannelName); if(pbx_channel_state(tmp_channel) == AST_STATE_RING && pbx_channel_state(forwarder) == AST_STATE_DOWN && iPbx.getChannelPbx(c)) { sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_4 "SCCP: Receiver Hungup: (hasPBX: %s)\n", iPbx.getChannelPbx(c) ? "yes" : "no"); pbx_channel_set_hangupcause(forwarder, AST_CAUSE_CALL_REJECTED); } else { pbx_log(LOG_ERROR, "%s: (%s) We did not find bridge channel for call forwarding call. Hangup\n", c->currentDeviceId, __func__); pbx_channel_set_hangupcause(forwarder, AST_CAUSE_REQUESTED_CHAN_UNAVAIL); sccp_channel_endcall(c); } pbx_channel_set_hangupcause(forwarder, AST_CAUSE_REQUESTED_CHAN_UNAVAIL); pbx_channel_unref(forwarder); if(destination) { pbx_channel_unref(destination); } res = -4; } } while(0); if(tmp_channel) { pbx_channel_unref(tmp_channel); } } else { sccp_log((DEBUGCAT_CORE))(VERBOSE_PREFIX_3 "%s: (%s) Outgoing call %s being answered by remote party\n", c->currentDeviceId, __func__, iPbx.getChannelName(c)); AUTO_RELEASE(sccp_device_t, d , sccp_channel_getDevice(c)); if (d) { const char * application = ast_channel_appl (c->owner); if (application && sccp_strequals (application, "ParkedCall")) { sccp_log ((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: !! retrieving parked call !!\n", c->designator); sccp_channel_setChannelstate (c, SCCP_CHANNELSTATE_CALLPARK); pbx_builtin_setvar_helper (c->owner, "_PARK_RETRIEVER", c->designator); } #if CS_SCCP_CONFERENCE sccp_indicate(d, c, d->conference ? SCCP_CHANNELSTATE_CONNECTEDCONFERENCE : SCCP_CHANNELSTATE_CONNECTED); #else sccp_indicate(d, c, SCCP_CHANNELSTATE_CONNECTED); #endif /** check for monitor request */ if((d->monitorFeature.status & SCCP_FEATURE_MONITOR_STATE_REQUESTED) && !(d->monitorFeature.status & SCCP_FEATURE_MONITOR_STATE_ACTIVE)) { pbx_log(LOG_NOTICE, "%s: (%s) request monitor/record\n", d->id, __func__); sccp_feat_monitor(d, NULL, 0, c); } sccp_log(DEBUGCAT_PBX)(VERBOSE_PREFIX_3 "%s: (%s) Switching to STATE_UP\n", c->designator, __func__); iPbx.set_callstate(c, AST_STATE_UP); res = 0; } if((sccp_rtp_getState(&c->rtp.video, SCCP_RTP_RECEPTION) & SCCP_RTP_STATUS_ACTIVE)) { iPbx.queue_control(c->owner, AST_CONTROL_VIDUPDATE); } } return res; } /*! * \brief Allocate an Asterisk Channel * \param channel SCCP Channel * \param ids Void Character Pointer (either Empty / LinkedId / Channel ID, depending on the asterisk version) * \param parentChannel SCCP Channel for which the channel was created * \return 1 on Success as uint8_t * * \callgraph * \callergraph * * \lock * - usecnt_lock */ boolean_t sccp_pbx_channel_allocate(constChannelPtr channel, const void * ids, const PBX_CHANNEL_TYPE * parentChannel) { PBX_CHANNEL_TYPE * tmp = NULL; AUTO_RELEASE(sccp_channel_t, c , sccp_channel_retain(channel)); AUTO_RELEASE(sccp_device_t, d , NULL); if (!c) { return FALSE; } pbx_assert(c->owner == NULL); // prevent calling this function when the channel already has a pbx channel #ifndef CS_AST_CHANNEL_HAS_CID char cidtmp[256]; memset(&cidtmp, 0, sizeof(cidtmp)); #endif // CS_AST_CHANNEL_HAS_CID AUTO_RELEASE(sccp_line_t, l , sccp_line_retain(c->line)); if (!l) { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "SCCP: (sccp_pbx_channel_allocate) Unable to find line for channel %s\n", c->designator); pbx_log(LOG_ERROR, "SCCP: Unable to allocate asterisk channel... returning 0\n"); return FALSE; } sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: (pbx_channel_allocate) try to allocate %s channel on line: %s\n", skinny_calltype2str(c->calltype), l->name); /* Don't hold a sccp pvt lock while we allocate a channel */ char s1[512]; char s2[512]; char cid_name[StationMaxNameSize] = {0}; char cid_num[StationMaxDirnumSize] = {0}; { sccp_linedevice_t * ld = NULL; // if ((d = sccp_channel_getDevice(c))) { d = sccp_channel_getDevice(c) /*ref_replace*/; if(d) { SCCP_LIST_LOCK(&l->devices); SCCP_LIST_TRAVERSE(&l->devices, ld, list) { if(ld->device == d) { break; } } SCCP_LIST_UNLOCK(&l->devices); } else if(SCCP_LIST_GETSIZE(&l->devices) > 0) { SCCP_LIST_LOCK(&l->devices); ld = SCCP_LIST_FIRST(&l->devices); SCCP_LIST_UNLOCK(&l->devices); if(ld && ld->device) { d = sccp_device_retain(ld->device) /*ref_replace*/; // ugly hack just picking the first one ! } } if(!ld) { pbx_log(LOG_NOTICE, "%s: Could not find an appropriate ld to assign this channel to. Line:%s exists, but was not assigned to any device (yet). We should give up here.\n", c->designator, l->name); goto error_exit; } sccp_callinfo_t *ci = sccp_channel_getCallInfo(c); if(ld->subscriptionId.replaceCid) { snprintf(cid_num, StationMaxDirnumSize, "%s", sccp_strlen_zero(ld->subscriptionId.number) ? l->cid_num : ld->subscriptionId.number); snprintf(cid_name, StationMaxNameSize, "%s", sccp_strlen_zero(ld->subscriptionId.name) ? l->cid_name : ld->subscriptionId.name); } else { snprintf(cid_num, StationMaxDirnumSize, "%s%s", l->cid_num, sccp_strlen_zero(ld->subscriptionId.number) ? "" : ld->subscriptionId.number); snprintf(cid_name, StationMaxNameSize, "%s%s", l->cid_name, sccp_strlen_zero(ld->subscriptionId.name) ? "" : ld->subscriptionId.name); } switch (c->calltype) { case SKINNY_CALLTYPE_INBOUND: iCallInfo.Setter(ci, SCCP_CALLINFO_CALLEDPARTY_NAME, &cid_name, SCCP_CALLINFO_CALLEDPARTY_NUMBER, &cid_num, SCCP_CALLINFO_KEY_SENTINEL); break; case SKINNY_CALLTYPE_FORWARD: iCallInfo.Setter(ci, SCCP_CALLINFO_CALLINGPARTY_NAME, &cid_name, SCCP_CALLINFO_CALLINGPARTY_NUMBER, &cid_num, SCCP_CALLINFO_LAST_REDIRECTINGPARTY_NAME, &cid_name, SCCP_CALLINFO_LAST_REDIRECTINGPARTY_NUMBER, &cid_num, SCCP_CALLINFO_LAST_REDIRECT_REASON, 4, SCCP_CALLINFO_KEY_SENTINEL); break; case SKINNY_CALLTYPE_OUTBOUND: iCallInfo.Setter(ci, SCCP_CALLINFO_CALLINGPARTY_NAME, &cid_name, SCCP_CALLINFO_CALLINGPARTY_NUMBER, &cid_num, //SCCP_CALLINFO_ORIG_CALLINGPARTY_NAME, &cid_name, //SCCP_CALLINFO_ORIG_CALLINGPARTY_NUMBER, &cid_num, SCCP_CALLINFO_LAST_REDIRECTINGPARTY_NAME, &cid_name, SCCP_CALLINFO_LAST_REDIRECTINGPARTY_NUMBER, &cid_num, SCCP_CALLINFO_LAST_REDIRECT_REASON, 0, SCCP_CALLINFO_KEY_SENTINEL); break; case SKINNY_CALLTYPE_SENTINEL: break; } // make sure preferences only contains the codecs that this channel is capable of if (SCCP_LIST_GETSIZE(&l->devices) == 1 && d) { // singleline //sccp_line_copyCodecSetsFromLineToChannel(l, d, c); sccp_codec_reduceSet(c->preferences.audio, d->capabilities.audio); sccp_codec_reduceSet(c->preferences.video, d->capabilities.video); } else { //sccp_line_copyCodecSetsFromLineToChannel(l, NULL, c); // sharedline sccp_codec_reduceSet(c->preferences.audio, c->capabilities.audio); sccp_codec_reduceSet(c->preferences.video, c->capabilities.video); } if (c->preferences.audio[0] == SKINNY_CODEC_NONE || c->capabilities.audio[0] == SKINNY_CODEC_NONE) { pbx_log(LOG_ERROR, "%s: Expect trouble ahead.\n" "The audio preferences:%s of this channel have been reduced to nothing.\n" "Because they are not compatible with this %s capabilities:%s.\n" "Please fix your config. Ending Call !.\n", c->designator, sccp_codec_multiple2str(s1, sizeof(s1) - 1, c->preferences.audio, SKINNY_MAX_CAPABILITIES), l->preferences_set_on_line_level ? "line's" : "device's", sccp_codec_multiple2str(s2, sizeof(s2) - 1, c->capabilities.audio, SKINNY_MAX_CAPABILITIES)); goto error_exit; } } sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: cid_num: %s\n", cid_num); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: cid_name: %s\n", cid_name); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: accountcode: %s\n", l->accountcode); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: exten: %s\n", c->dialedNumber); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: context: %s\n", l->context); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: amaflags: %d\n", (int)l->amaflags); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: chan/call: %s\n", c->designator); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: combined audio caps: %s\n", sccp_codec_multiple2str(s1, sizeof(s1) - 1, c->capabilities.audio, SKINNY_MAX_CAPABILITIES)); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: reduced audio prefs: %s\n", sccp_codec_multiple2str(s1, sizeof(s1) - 1, c->preferences.audio, SKINNY_MAX_CAPABILITIES)); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: combined video caps: %s\n", sccp_codec_multiple2str(s1, sizeof(s1) - 1, c->capabilities.video, SKINNY_MAX_CAPABILITIES)); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: reduced video prefs: %s\n", sccp_codec_multiple2str(s1, sizeof(s1) - 1, c->preferences.video, SKINNY_MAX_CAPABILITIES)); /* // this should not be done here at this moment, leaving it to alloc_pbxChannel to sort out. if (c->calltype == SKINNY_CALLTYPE_INBOUND) { if (c->remoteCapabilities.audio[0] != SKINNY_CODEC_NONE) { sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: remote audio prefs: \"%s\"\n", sccp_codec_multiple2str(s1, sizeof(s1) - 1, c->remoteCapabilities.audio, SKINNY_MAX_CAPABILITIES)); skinny_codec_t ordered_audio_prefs[SKINNY_MAX_CAPABILITIES] = {SKINNY_CODEC_NONE}; memcpy(&ordered_audio_prefs, c->remoteCapabilities.audio, sizeof(ordered_audio_prefs)); sccp_codec_reduceSet(ordered_audio_prefs, c->preferences.audio); memcpy(&c->preferences.audio, ordered_audio_prefs, sizeof(c->preferences.audio)); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: set audio prefs: \"%s\"\n", sccp_codec_multiple2str(s2, sizeof(s2) - 1, c->preferences.audio, SKINNY_MAX_CAPABILITIES)); } if (c->remoteCapabilities.video[0] != SKINNY_CODEC_NONE && (d ? sccp_device_isVideoSupported(d) : TRUE)) { sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: remote video prefs: \"%s\"\n", sccp_codec_multiple2str(s1, sizeof(s1) - 1, c->remoteCapabilities.video, SKINNY_MAX_CAPABILITIES)); skinny_codec_t ordered_video_prefs[SKINNY_MAX_CAPABILITIES] = {SKINNY_CODEC_NONE}; memcpy(&ordered_video_prefs, c->remoteCapabilities.video, sizeof(ordered_video_prefs)); sccp_codec_reduceSet(ordered_video_prefs, c->preferences.video); memcpy(&c->preferences.video, ordered_video_prefs, sizeof(c->preferences.video)); sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "SCCP: set video prefs: \"%s\"\n", sccp_codec_multiple2str(s2, sizeof(s2) - 1, c->preferences.video, SKINNY_MAX_CAPABILITIES)); } } */ if (!c->pbx_callid && c->calltype != SKINNY_CALLTYPE_INBOUND) { c->pbx_callid = pbx_create_callid(); } /* This should definitely fix CDR */ iPbx.alloc_pbxChannel(c, ids, parentChannel, &tmp); if (!tmp || !c->owner) { pbx_log(LOG_ERROR, "%s: Unable to allocate asterisk channel on line %s\n", c->designator, l->name); goto error_exit; } iPbx.setChannelName(c, c->designator); // \todo: Bridge? // \todo: Transfer? (void) ATOMIC_INCR(&GLOB(usecnt), 1, &GLOB(usecnt_lock)); pbx_update_use_count(); if (iPbx.set_callerid_number) { iPbx.set_callerid_number(c->owner, cid_num); } if (iPbx.set_callerid_ani) { iPbx.set_callerid_ani(c->owner, cid_num); } if (iPbx.set_callerid_name) { iPbx.set_callerid_name(c->owner, cid_name); } /* call ast_channel_call_forward_set with the forward destination if this device is forwarded */ if (SCCP_LIST_GETSIZE(&l->devices) == 1) { sccp_linedevice_t * ld = NULL; SCCP_LIST_LOCK(&l->devices); SCCP_LIST_TRAVERSE(&l->devices, ld, list) { if(ld->line == l) { if(ld->cfwd[SCCP_CFWD_ALL].enabled) { sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "%s: ast call forward channel_set: %s\n", c->designator, ld->cfwd[SCCP_CFWD_ALL].number); iPbx.setChannelCallForward(c, ld->cfwd[SCCP_CFWD_ALL].number); } else if(ld->cfwd[SCCP_CFWD_BUSY].enabled && (sccp_device_getDeviceState(ld->device) != SCCP_DEVICESTATE_ONHOOK || sccp_device_getActiveAccessory(ld->device))) { sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "%s: ast call forward channel_set: %s\n", c->designator, ld->cfwd[SCCP_CFWD_BUSY].number); iPbx.setChannelCallForward(c, ld->cfwd[SCCP_CFWD_BUSY].number); } break; } } SCCP_LIST_UNLOCK(&l->devices); } #if CS_SCCP_VIDEO const char *VideoStr = pbx_builtin_getvar_helper(c->owner, "SCCP_VIDEO_MODE"); if (VideoStr && !sccp_strlen_zero(VideoStr)) { sccp_channel_setVideoMode(c, VideoStr); } #endif /* asterisk needs the native formats bevore dialout, otherwise the next channel gets the whole AUDIO_MASK as requested format * chan_sip dont like this do sdp processing */ //iPbx.set_nativeAudioFormats(c, c->preferences.audio, ARRAY_LEN(c->preferences.audio)); /* start audio rtp server early, to facilitate choosing codecs via sdp */ if (d) { if (c->calltype == SKINNY_CALLTYPE_OUTBOUND) { if (!c->rtp.audio.instance && !sccp_rtp_createServer(d, c, SCCP_RTP_AUDIO)) { pbx_log(LOG_WARNING, "%s: Error opening RTP instance for channel %s\n", d->id, c->designator); goto error_exit; } /* #if CS_SCCP_VIDEO if (sccp_channel_getVideoMode(c) != SCCP_VIDEO_MODE_OFF && sccp_device_isVideoSupported(d) && c->preferences.video[0] != SKINNY_CODEC_NONE && !c->rtp.video.instance && !sccp_rtp_createServer(d, c, SCCP_RTP_VIDEO)) { pbx_log(LOG_WARNING, "%s: Error opening VRTP instance for channel %s\n", d->id, c->designator); sccp_channel_setVideoMode(c, "off"); } #endif */ } // export sccp informations in asterisk dialplan pbx_builtin_setvar_helper(tmp, "SCCP_DEVICE_MAC", d->id); struct sockaddr_storage sas = { 0 }; sccp_session_getSas(d->session, &sas); pbx_builtin_setvar_helper(tmp, "SCCP_DEVICE_IP", d->session ? sccp_netsock_stringify_addr(&sas) : ""); pbx_builtin_setvar_helper(tmp, "SCCP_DEVICE_TYPE", skinny_devicetype2str(d->skinny_type)); } sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_3 "%s: Allocated asterisk channel %s\n", (l) ? l->id : "(null)", c->designator); return TRUE; error_exit: if(c) { pbx_log(LOG_WARNING, "%s: (pbx_channel_allocate) Unable to allocate a new channel for line %s\n -> Hanging up call.", DEV_ID_LOG(d), l->name); if(c->owner) { if(d) { sccp_indicate(d, c, SCCP_CHANNELSTATE_CONGESTION); } sccp_channel_endcall(c); } else { if(d) { sccp_indicate(d, channel, SCCP_CHANNELSTATE_ONHOOK); } if(c->line) { sccp_line_removeChannel(c->line, c); } sccp_channel_clean(c); sccp_channel_release(&c); } } return FALSE; } /*! * \brief Schedule Asterisk Dial * \param data Data as constant * \return Success as int * * \called_from_asterisk */ int sccp_pbx_sched_dial(const void * data) { AUTO_RELEASE(sccp_channel_t, channel, sccp_channel_retain(data)); if(channel) { if ((ATOMIC_FETCH(&channel->scheduler.deny, &channel->scheduler.lock) == 0) && channel->scheduler.hangup_id == -1) { channel->scheduler.digittimeout_id = -3; /* prevent further digittimeout scheduling */ if (channel->owner && !iPbx.getChannelPbx(channel) && !sccp_strlen_zero(channel->dialedNumber)) { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_1 "SCCP: Timeout for call '%s'. Going to dial '%s'\n", channel->designator, channel->dialedNumber); sccp_pbx_softswitch(channel); } else { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_1 "SCCP: Timeout for call '%s'. Nothing to dial -> INVALIDNUMBER\n", channel->designator); channel->dialedNumber[0] = '\0'; sccp_indicate(NULL, channel, SCCP_CHANNELSTATE_INVALIDNUMBER); } } sccp_channel_release((sccp_channel_t **)&data); // release channel retained in scheduled event } return 0; // return 0 to release schedule ! } /*! * \brief Asterisk Helper * \param c SCCP Channel as sccp_channel_t * \return Success as int */ sccp_extension_status_t sccp_pbx_helper(constChannelPtr c) { sccp_extension_status_t extensionStatus = 0; int dialedLen = sccp_strlen(c->dialedNumber); if (dialedLen > 1) { if (GLOB(recorddigittimeoutchar) && GLOB(digittimeoutchar) == c->dialedNumber[dialedLen - 1]) { /* we finished dialing with digit timeout char */ sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_2 "%s: We finished dialing with digit timeout char %s\n", c->designator, c->dialedNumber); return SCCP_EXTENSION_EXACTMATCH; } } if ((c->softswitch_action != SCCP_SOFTSWITCH_GETCBARGEROOM) && (c->softswitch_action != SCCP_SOFTSWITCH_GETMEETMEROOM) #ifdef CS_SCCP_CONFERENCE && (c->softswitch_action != SCCP_SOFTSWITCH_GETCONFERENCEROOM) #endif ) { //! \todo check overlap feature status -MC extensionStatus = iPbx.extension_status(c); AUTO_RELEASE(sccp_device_t, d , sccp_channel_getDevice(c)); if (d) { if (((d->overlapFeature.enabled && !extensionStatus) || (!d->overlapFeature.enabled && !extensionStatus))) { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: %s Matches More\n", c->designator, c->dialedNumber); return SCCP_EXTENSION_MATCHMORE; } sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: %s Matches %s\n", c->designator, c->dialedNumber, extensionStatus == SCCP_EXTENSION_EXACTMATCH ? "Exactly" : "More"); } return extensionStatus; } sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_2 "%s: %s Does Exists\n", c->designator, c->dialedNumber); return SCCP_EXTENSION_NOTEXISTS; } /*! * \brief Handle Soft Switch * \param channel SCCP Channel as sccp_channel_t * \todo clarify Soft Switch Function */ void * sccp_pbx_softswitch(constChannelPtr channel) { PBX_CHANNEL_TYPE * pbx_channel = NULL; PBX_VARIABLE_TYPE * v = NULL; { AUTO_RELEASE(sccp_channel_t, c , sccp_channel_retain(channel)); if (!c) { pbx_log(LOG_ERROR, "SCCP: (sccp_pbx_softswitch) No available. Returning from dial thread.\n"); goto EXIT_FUNC; } sccp_channel_stop_schedule_digittimout(c); /* Reset Enbloc Dial Emulation */ c->enbloc.deactivate = 0; c->enbloc.totaldigittime = 0; c->enbloc.totaldigittimesquared = 0; c->enbloc.digittimeout = GLOB(digittimeout); /* prevent softswitch from being executed twice (Pavel Troller / 15-Oct-2010) */ if (iPbx.getChannelPbx(c)) { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "SCCP: (sccp_pbx_softswitch) PBX structure already exists. Dialing instead of starting.\n"); /* If there are any digits, send them instead of starting the PBX */ if (!sccp_strlen_zero(c->dialedNumber)) { if (iPbx.send_digits) { iPbx.send_digits(channel, c->dialedNumber); } sccp_channel_set_calledparty(c, NULL, c->dialedNumber); } goto EXIT_FUNC; } if (!c->owner) { pbx_log(LOG_ERROR, "SCCP: (sccp_pbx_softswitch) No PBX available. Returning from dial thread.\n"); goto EXIT_FUNC; } pbx_channel = pbx_channel_ref(c->owner); /* we should just process outbound calls, let's check calltype */ if (c->calltype != SKINNY_CALLTYPE_OUTBOUND && c->softswitch_action == SCCP_SOFTSWITCH_DIAL) { pbx_log(LOG_ERROR, "SCCP: (sccp_pbx_softswitch) This function is for outbound calls only. Exiting\n"); goto EXIT_FUNC; } /* assume d is the channel's device */ /* does it exists ? */ AUTO_RELEASE(sccp_device_t, d , sccp_channel_getDevice(c)); if (!d) { pbx_log(LOG_ERROR, "SCCP: (sccp_pbx_softswitch) No available. Returning from dial thread. Exiting\n"); goto EXIT_FUNC; } if (pbx_check_hangup(pbx_channel)) { pbx_log(LOG_ERROR, "SCCP: (sccp_pbx_softswitch) Channel already hungup, no need to go any further. Exiting\n"); sccp_indicate(d, c, SCCP_CHANNELSTATE_ONHOOK); goto EXIT_FUNC; } /* we don't need to check for a device type but just if the device has an id, otherwise back home -FS */ if (sccp_strlen_zero(d->id)) { pbx_log(LOG_ERROR, "SCCP: (sccp_pbx_softswitch) No identifier available. Returning from dial thread. Exiting\n"); goto EXIT_FUNC; } AUTO_RELEASE(sccp_line_t, l , sccp_line_retain(c->line)); if (!l) { pbx_log(LOG_ERROR, "SCCP: (sccp_pbx_softswitch) No available. Returning from dial thread. Exiting\n"); c->hangupRequest(c); goto EXIT_FUNC; } uint8_t instance = sccp_device_find_index_for_line(d, l->name); sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) New call on line %s\n", DEV_ID_LOG(d), l->name); /* assign callerid name and number */ //sccp_channel_set_callingparty(c, l->cid_name, l->cid_num); // we use shortenedNumber but why ??? // If the timeout digit has been used to terminate the number // and this digit shall be included in the phone call history etc (recorddigittimeoutchar is true) // we still need to dial the number without the timeout char in the pbx // so that we don't dial strange extensions with a trailing characters. char shortenedNumber[256] = { '\0' }; sccp_copy_string(shortenedNumber, c->dialedNumber, sizeof(shortenedNumber)); unsigned int len = sccp_strlen(shortenedNumber); pbx_assert(sccp_strlen(c->dialedNumber) == len); if (len > 0 && GLOB(digittimeoutchar) == shortenedNumber[len - 1]) { shortenedNumber[len - 1] = '\0'; // If we don't record the timeoutchar in the logs, we remove it from the sccp channel structure // Later, the channel dialed number is used for directories, etc., // and the shortened number is used for dialing the actual call via asterisk pbx. if (!GLOB(recorddigittimeoutchar)) { c->dialedNumber[len - 1] = '\0'; } } /* This will choose what to do */ switch (c->softswitch_action) { case SCCP_SOFTSWITCH_GETFORWARDEXTEN: { sccp_cfwd_t type = (sccp_cfwd_t)c->ss_data; sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Get Forward %s Extension\n", d->id, sccp_cfwd2str(type)); if(!sccp_strlen_zero(shortenedNumber)) { c->setTone(c, SKINNY_TONE_ZIP, SKINNY_TONEDIRECTION_USER); sccp_line_cfwd(l, d, type, shortenedNumber); } sccp_channel_endcall(c); goto EXIT_FUNC; // leave simple switch without dial } case SCCP_SOFTSWITCH_ENDCALLFORWARD: { sccp_cfwd_t type = (sccp_cfwd_t)c->ss_data; sccp_log((DEBUGCAT_PBX))(VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Clear Forward %s\n", d->id, sccp_cfwd2str(type)); sccp_line_cfwd(l, d, type, NULL); switch(type) { case SCCP_CFWD_ALL: sccp_device_setLamp(d, SKINNY_STIMULUS_FORWARDALL, instance, SKINNY_LAMP_OFF); break; case SCCP_CFWD_BUSY: sccp_device_setLamp(d, SKINNY_STIMULUS_FORWARDBUSY, instance, SKINNY_LAMP_OFF); break; case SCCP_CFWD_NOANSWER: sccp_device_setLamp(d, SKINNY_STIMULUS_FORWARDNOANSWER, instance, SKINNY_LAMP_OFF); break; case SCCP_CFWD_NONE: case SCCP_CFWD_SENTINEL: default: pbx_log(LOG_ERROR, "%s: (sccp_pbx_softswitch) EndCallForward unknown CFWD_TYPE\n", d->id); } sccp_channel_endcall(c); goto EXIT_FUNC; // leave simple switch without dial } #ifdef CS_SCCP_PICKUP case SCCP_SOFTSWITCH_GETPICKUPEXTEN: sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Get Pickup Extension\n", d->id); sccp_dev_clearprompt(d, instance, c->callid); if (!sccp_strlen_zero(shortenedNumber)) { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "SCCP: (sccp_pbx_softswitch) Asterisk request to pickup exten '%s'\n", shortenedNumber); sccp_dev_displayprompt(d, instance, c->callid, SKINNY_DISP_PICKUP, GLOB(digittimeout)); if (sccp_feat_directed_pickup(d, c, instance, shortenedNumber) == 0) { goto EXIT_FUNC; } } sccp_dev_displayprinotify(d, SKINNY_DISP_NO_CALL_AVAILABLE_FOR_PICKUP, SCCP_MESSAGE_PRIORITY_TIMEOUT, 5); if (c->state == SCCP_CHANNELSTATE_ONHOOK || c->state == SCCP_CHANNELSTATE_DOWN) { c->setTone(c, SKINNY_TONE_BEEPBONK, SKINNY_TONEDIRECTION_USER); } sccp_channel_schedule_hangup(c, 500); goto EXIT_FUNC; // leave simpleswitch without dial #endif // CS_SCCP_PICKUP #ifdef CS_SCCP_CONFERENCE case SCCP_SOFTSWITCH_GETCONFERENCEROOM: sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Conference request\n", d->id); if (c->owner && !pbx_check_hangup(c->owner)) { sccp_channel_setChannelstate(c, SCCP_CHANNELSTATE_PROCEED); iPbx.set_callstate(channel, AST_STATE_UP); if (!d->conference) { if (!(d->conference = sccp_conference_create(d, c))) { goto EXIT_FUNC; } } else { pbx_log(LOG_NOTICE, "%s: There is already a conference running on this device.\n", DEV_ID_LOG(d)); sccp_channel_endcall(c); goto EXIT_FUNC; } sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Start Conference\n", d->id); sccp_feat_conference_start(d, instance, c); } goto EXIT_FUNC; #endif // CS_SCCP_CONFERENCE case SCCP_SOFTSWITCH_GETMEETMEROOM: sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Meetme request\n", d->id); if (!sccp_strlen_zero(shortenedNumber) && !sccp_strlen_zero(c->line->meetmenum)) { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Meetme request for room '%s' on extension '%s'\n", d->id, shortenedNumber, c->line->meetmenum); if (c->owner && !pbx_check_hangup(c->owner)) { pbx_builtin_setvar_helper(c->owner, "SCCP_MEETME_ROOM", shortenedNumber); } sccp_copy_string(shortenedNumber, c->line->meetmenum, sizeof(shortenedNumber)); //sccp_copy_string(c->dialedNumber, SKINNY_DISP_CONFERENCE, sizeof(c->dialedNumber)); sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Start Meetme Thread\n", d->id); sccp_feat_meetme_start(c); /* Copied from Federico Santulli */ sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Meetme Thread Started\n", d->id); goto EXIT_FUNC; } else { // without a number we can also close the call. Isn't it true ? sccp_channel_endcall(c); goto EXIT_FUNC; } break; case SCCP_SOFTSWITCH_GETBARGEEXTEN: sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Get Barge Extension\n", d->id); sccp_dev_clearprompt(d, instance, c->callid); if (!sccp_strlen_zero(shortenedNumber)) { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "SCCP: (sccp_pbx_softswitch) Asterisk request to barge exten '%s'\n", shortenedNumber); sccp_dev_displayprompt(d, instance, c->callid, SKINNY_DISP_BARGE, GLOB(digittimeout)); if (sccp_feat_singleline_barge(c, shortenedNumber)) { //sccp_indicate(d, c, SCCP_CHANNELSTATE_INVALIDNUMBER); goto EXIT_FUNC; } } sccp_dev_displayprinotify(d, SKINNY_DISP_FAILED_TO_SETUP_BARGE, SCCP_MESSAGE_PRIORITY_TIMEOUT, 5); if (c->state == SCCP_CHANNELSTATE_ONHOOK || c->state == SCCP_CHANNELSTATE_DOWN) { c->setTone(c, SKINNY_TONE_BEEPBONK, SKINNY_TONEDIRECTION_USER); } sccp_channel_endcall(c); goto EXIT_FUNC; // leave simpleswitch without dial case SCCP_SOFTSWITCH_GETCBARGEROOM: sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Get Conference Barge Extension\n", d->id); // like we're dialing but we're not :) sccp_indicate(d, c, SCCP_CHANNELSTATE_DIALING); sccp_device_sendcallstate(d, instance, c->callid, SKINNY_CALLSTATE_PROCEED, SKINNY_CALLPRIORITY_LOW, SKINNY_CALLINFO_VISIBILITY_DEFAULT); sccp_channel_send_callinfo(d, c); sccp_dev_clearprompt(d, instance, c->callid); sccp_dev_displayprompt(d, instance, c->callid, SKINNY_DISP_CALL_PROCEED, GLOB(digittimeout)); if (!sccp_strlen_zero(shortenedNumber)) { sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Device request to barge conference '%s'\n", d->id, shortenedNumber); if (sccp_feat_cbarge(c, shortenedNumber)) { sccp_indicate(d, c, SCCP_CHANNELSTATE_INVALIDNUMBER); } } else { // without a number we can also close the call. Isn't it true ? sccp_channel_endcall(c); } goto EXIT_FUNC; // leave simpleswitch without dial case SCCP_SOFTSWITCH_SENTINEL: sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Unknown Softswitch Request\n", d->id); goto EXIT_FUNC; case SCCP_SOFTSWITCH_DIAL: sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "%s: (sccp_pbx_softswitch) Dial Extension %s\n", d->id, shortenedNumber); sccp_indicate(d, c, SCCP_CHANNELSTATE_DIALING); /* fall through */ } /* set private variable */ if (pbx_channel && !pbx_check_hangup(pbx_channel)) { sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "SCCP: (sccp_pbx_softswitch) set variable SKINNY_PRIVATE to: %s\n", c->privacy ? "1" : "0"); if (c->privacy) { sccp_channel_set_calleridPresentation(c, CALLERID_PRESENTATION_FORBIDDEN); } uint32_t result = d->privacyFeature.status & SCCP_PRIVACYFEATURE_CALLPRESENT; result |= c->privacy; if (d->privacyFeature.enabled && result) { sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "SCCP: (sccp_pbx_softswitch) set variable SKINNY_PRIVATE to: %s\n", "1"); pbx_builtin_setvar_helper(pbx_channel, "SKINNY_PRIVATE", "1"); } else { sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_3 "SCCP: (sccp_pbx_softswitch) set variable SKINNY_PRIVATE to: %s\n", "0"); pbx_builtin_setvar_helper(pbx_channel, "SKINNY_PRIVATE", "0"); } } /* set devicevariables */ v = d->variables; while (pbx_channel && !pbx_check_hangup(pbx_channel) && d && v) { pbx_builtin_setvar_helper(pbx_channel, v->name, v->value); v = v->next; } /* set linevariables */ v = l->variables; while (pbx_channel && !pbx_check_hangup(pbx_channel) && l && v) { pbx_builtin_setvar_helper(pbx_channel, v->name, v->value); v = v->next; } iPbx.setChannelExten(c, shortenedNumber); /*! \todo DdG: Extra wait time is incurred when checking pbx_exists_extension, when a wrong number is dialed. storing extension_exists status for sccp_log use */ int extension_exists = SCCP_EXTENSION_NOTEXISTS; if (!sccp_strlen_zero(shortenedNumber) && ((extension_exists = iPbx.extension_status(c) != SCCP_EXTENSION_NOTEXISTS)) ) { if (pbx_channel && !pbx_check_hangup(pbx_channel)) { /* found an extension, let's dial it */ sccp_log((DEBUGCAT_PBX + DEBUGCAT_CHANNEL)) (VERBOSE_PREFIX_1 "%s: (sccp_pbx_softswitch) channel %s is dialing number %s\n", DEV_ID_LOG(d), c->designator, shortenedNumber); /* Answer dialplan command works only when in RINGING OR RING ast_state */ iPbx.set_callstate(c, AST_STATE_RING); enum ast_pbx_result pbxStartResult = pbx_pbx_start(pbx_channel); /* \todo replace AST_PBX enum using pbx_impl wrapper enum */ switch (pbxStartResult) { case AST_PBX_FAILED: pbx_log(LOG_ERROR, "%s: (sccp_pbx_softswitch) channel %s failed to start new thread to dial %s\n", DEV_ID_LOG(d), c->designator, shortenedNumber); /* \todo change indicate to something more suitable */ sccp_indicate(d, c, SCCP_CHANNELSTATE_CONGESTION); /* will auto hangup after SCCP_HANGUP_TIMEOUT */ break; case AST_PBX_CALL_LIMIT: pbx_log(LOG_WARNING, "%s: (sccp_pbx_softswitch) call limit reached for channel %s-%08x failed to start new thread to dial %s\n", DEV_ID_LOG(d), l->name, c->callid, shortenedNumber); sccp_indicate(d, c, SCCP_CHANNELSTATE_CONGESTION); /* will auto hangup after SCCP_HANGUP_TIMEOUT */ break; case AST_PBX_SUCCESS: sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_1 "%s: (sccp_pbx_softswitch) pbx started\n", DEV_ID_LOG(d)); #ifdef CS_MANAGER_EVENTS if (GLOB(callevents)) { manager_event(EVENT_FLAG_SYSTEM, "ChannelUpdate", "Channel: %s\r\nUniqueid: %s\r\nChanneltype: %s\r\nSCCPdevice: %s\r\nSCCPline: %s\r\nSCCPcallid: %08X\r\nSCCPCallDesignator: %s\r\n", (pbx_channel) ? pbx_channel_name(pbx_channel) : "(null)", (pbx_channel) ? pbx_channel_uniqueid(pbx_channel) : "(null)", "SCCP", (d) ? d->id : "(null)", (l) ? l->name : "(null)", (c && c->callid) ? c->callid : 0, (c) ? c->designator : "(null)"); } #endif // CS_MANAGER_EVENTS break; } AUTO_RELEASE(sccp_linedevice_t, ld, c->getLineDevice(c)); if(ld) { sccp_device_setLastNumberDialed(d, shortenedNumber, ld); } if(iPbx.set_dialed_number) { iPbx.set_dialed_number(c, shortenedNumber); } } else { sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_1 "%s: (sccp_pbx_softswitch) pbx_check_hangup(chan): %d on line %s\n", DEV_ID_LOG(d), (pbx_channel && pbx_check_hangup(pbx_channel)), l->name); } } else { sccp_log((DEBUGCAT_PBX)) (VERBOSE_PREFIX_1 "%s: (sccp_pbx_softswitch) channel:%s shortenedNumber:%s, extension exists:%s\n", DEV_ID_LOG(d), c->designator, shortenedNumber, (extension_exists != SCCP_EXTENSION_NOTEXISTS) ? "TRUE" : "FALSE"); pbx_log(LOG_NOTICE, "%s: Call from '%s' to extension '%s', rejected because the extension could not be found in context '%s'\n", DEV_ID_LOG(d), l->name, shortenedNumber, pbx_channel ? pbx_channel_context(pbx_channel) : "pbx_channel==NULL"); /* timeout and no extension match */ if (pbx_channel && !pbx_check_hangup(pbx_channel)) { pbx_log(LOG_NOTICE, "%s: Scheduling Hangup of Call: %s\n", DEV_ID_LOG(d), c->designator); sccp_indicate(d, c, SCCP_CHANNELSTATE_INVALIDNUMBER); /* will auto hangup after SCCP_HANGUP_TIMEOUT */ } } sccp_log((DEBUGCAT_PBX + DEBUGCAT_DEVICE)) (VERBOSE_PREFIX_1 "%s: (sccp_pbx_softswitch) finished\n", DEV_ID_LOG(d)); } EXIT_FUNC: sccp_log((DEBUGCAT_PBX + DEBUGCAT_DEVICE)) (VERBOSE_PREFIX_1 "SCCP: (sccp_pbx_softswitch) exit\n"); if (pbx_channel) { pbx_channel_unref(pbx_channel); } return NULL; } #if 0 /*! * \brief Handle Dialplan Transfer * * This will allow asterisk to transfer an SCCP Channel via the dialplan transfer function * * \param ast Asterisk Channel * \param dest Destination as char * * \return result as int * * \test Dialplan Transfer Needs to be tested * \todo pbx_transfer needs to be implemented correctly * * \called_from_asterisk */ int sccp_pbx_transfer(PBX_CHANNEL_TYPE * ast, const char *dest) { int res = 0; if (dest == NULL) { /* functions below do not take a NULL */ dest = ""; return -1; } AUTO_RELEASE(sccp_channel_t, c , get_sccp_channel_from_pbx_channel(ast)); if (!c) { return -1; } /* sccp_device_t *d = NULL; sccp_channel_t *newcall = NULL; */ sccp_log((DEBUGCAT_CORE)) (VERBOSE_PREFIX_1 "Transferring '%s' to '%s'\n", iPbx.getChannelName(c), dest); if (pbx_channel_state(ast) == AST_STATE_RING) { //! \todo Blindtransfer needs to be implemented correctly /* res = sccp_blindxfer(p, dest); */ res = -1; } else { //! \todo Transfer needs to be implemented correctly /* res=sccp_channel_transfer(p,dest); */ res = -1; } return res; } #endif // kate: indent-width 8; replace-tabs off; indent-mode cstyle; auto-insert-doxygen on; line-numbers on; tab-indents on; keep-extra-spaces off; auto-brackets off;