diff options
81 files changed, 4033 insertions, 637 deletions
@@ -9,9 +9,41 @@ ============================================================================== ------------------------------------------------------------------------------ +--- Functionality changes from Asterisk 13.9.0 to Asterisk 13.10.0 ----------- +------------------------------------------------------------------------------ + +res_fax +------------------ + * Added FAXMODE variable to let dialplan know what fax transport was used. + FAXMODE variable is set to either "audio" or "T38". + +res_pjsip +------------------ + * Added new status Updated to AMI event ContactStatus on update registration + + * Added "reg_server" to contacts. + If the Asterisk system name is set in asterisk.conf, it will be stored + into the "reg_server" field in the ps_contacts table to facilitate + multi-server setups. + +app_confbridge +------------------ + * Added a bridge profile option called regcontext that allows you to + dynamically register the conference bridge name as an extension into + the specified context. This allows tracking down conferences on multi- + server installations via alternate means (DUNDI for example). By default + this feature is not used. + +------------------------------------------------------------------------------ --- Functionality changes from Asterisk 13.8.0 to Asterisk 13.9.0 ------------ ------------------------------------------------------------------------------ +res_pjsip +------------------ + * Added new global option (disable_multi_domain) to pjsip. + Disabling Multi Domain can improve realtime performace by reducing + number of database requsts. + chan_pjsip ------------------ * Added 'pjsip show channelstats' CLI command. @@ -124,6 +156,20 @@ res_pjsip dynamically create and destroy a NoOp priority 1 extension for a given endpoint who registers or unregisters with us. + * Endpoints and aors can now be identified by the username and realm in an + incoming Authorization header. To use this feature, add "auth_username" + to your endpoint's "identify_by" list. You can combine "auth_username" + and the original "username" to test both the From/To and Authorization + headers. For endpoints, the order is controlled by the global + "endpoint_identifier_order" setting. For matching aors to an endpoint + for inbound registration, the order is controlled by this option. + + * In conjunction with the "auth_username" change, 3 new options have been + added to the global configuration object that control how many unidentified + requests over a certain period from the same IP address can be received + before a security altert is generated. A new CLI command + "pjsip show unidentified_requests" will list the current candidates. + res_pjsip_history ------------------ * A new module, res_pjsip_history, has been added that provides SIP history diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c index 55b7b1240..991b3a307 100644 --- a/apps/app_confbridge.c +++ b/apps/app_confbridge.c @@ -1253,9 +1253,17 @@ void conf_handle_second_active(struct confbridge_conference *conference) void conf_ended(struct confbridge_conference *conference) { + struct pbx_find_info q = { .stacklen = 0 }; + /* Called with a reference to conference */ ao2_unlink(conference_bridges, conference); send_conf_end_event(conference); + if (!ast_strlen_zero(conference->b_profile.regcontext) && + pbx_find_extension(NULL, NULL, &q, conference->b_profile.regcontext, + conference->name, 1, NULL, "", E_MATCH)) { + ast_context_remove_extension(conference->b_profile.regcontext, + conference->name, 1, NULL); + } ao2_lock(conference); conf_stop_record(conference); ao2_unlock(conference); @@ -1360,6 +1368,13 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen } send_conf_start_event(conference); + + if (!ast_strlen_zero(conference->b_profile.regcontext)) { + if (!ast_exists_extension(NULL, conference->b_profile.regcontext, conference->name, 1, NULL)) { + ast_add_extension(conference->b_profile.regcontext, 1, conference->name, 1, NULL, NULL, "Noop", NULL, NULL, "ConfBridge"); + } + } + ast_debug(1, "Created conference '%s' and linked to container.\n", conference_name); } diff --git a/apps/app_queue.c b/apps/app_queue.c index 939a0e2ad..dbd83938d 100644 --- a/apps/app_queue.c +++ b/apps/app_queue.c @@ -1510,7 +1510,6 @@ struct member { struct call_queue *lastqueue; /*!< Last queue we received a call */ unsigned int dead:1; /*!< Used to detect members deleted in realtime */ unsigned int delme:1; /*!< Flag to delete entry on reload */ - unsigned int call_pending:1; /*!< TRUE if the Q is attempting to place a call to the member. */ char rt_uniqueid[80]; /*!< Unique id of realtime member entry */ unsigned int ringinuse:1; /*!< Flag to ring queue members even if their status is 'inuse' */ }; @@ -2267,6 +2266,70 @@ static int get_member_status(struct call_queue *q, int max_penalty, int min_pena return -1; } +/* + * A "pool" of member objects that calls are currently pending on. If an + * agent is a member of multiple queues it's possible for that agent to be + * called by each of the queues at the same time. This happens because device + * state is slow to notify the queue app of one of it's member's being rung. + * This "pool" allows us to track which members are currently being rung while + * we wait on the device state change. + */ +static struct ao2_container *pending_members; +#define MAX_CALL_ATTEMPT_BUCKETS 353 + +static int pending_members_hash(const void *obj, const int flags) +{ + const struct member *object; + const char *key; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_KEY: + key = obj; + break; + case OBJ_SEARCH_OBJECT: + object = obj; + key = object->interface; + break; + default: + ast_assert(0); + return 0; + } + return ast_str_case_hash(key); +} + +static int pending_members_cmp(void *obj, void *arg, int flags) +{ + const struct member *object_left = obj; + const struct member *object_right = arg; + const char *right_key = arg; + int cmp; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + right_key = object_right->interface; + /* Fall through */ + case OBJ_SEARCH_KEY: + cmp = strcasecmp(object_left->interface, right_key); + break; + case OBJ_SEARCH_PARTIAL_KEY: + /* Not supported by container. */ + ast_assert(0); + return 0; + default: + cmp = 0; + break; + } + if (cmp) { + return 0; + } + return CMP_MATCH; +} + +static void pending_members_remove(struct member *mem) +{ + ao2_find(pending_members, mem, OBJ_POINTER | OBJ_NODATA | OBJ_UNLINK); +} + /*! \brief set a member's status based on device state of that member's state_interface. * * Lock interface list find sc, iterate through each queues queue_member list for member to @@ -2276,6 +2339,9 @@ static void update_status(struct call_queue *q, struct member *m, const int stat { m->status = status; + /* Whatever the status is clear the member from the pending members pool */ + pending_members_remove(m); + queue_publish_member_blob(queue_member_status_type(), queue_member_blob_create(q, m)); } @@ -3132,6 +3198,7 @@ static void member_add_to_queue(struct call_queue *queue, struct member *mem) */ static void member_remove_from_queue(struct call_queue *queue, struct member *mem) { + pending_members_remove(mem); ao2_lock(queue->members); ast_devstate_changed(QUEUE_UNKNOWN_PAUSED_DEVSTATE, AST_DEVSTATE_CACHABLE, "Queue:%s_pause_%s", queue->name, mem->interface); queue_member_follower_removal(queue, mem); @@ -4110,41 +4177,6 @@ static int member_status_available(int status) /*! * \internal - * \brief Clear the member call pending flag. - * - * \param mem Queue member. - * - * \return Nothing - */ -static void member_call_pending_clear(struct member *mem) -{ - ao2_lock(mem); - mem->call_pending = 0; - ao2_unlock(mem); -} - -/*! - * \internal - * \brief Set the member call pending flag. - * - * \param mem Queue member. - * - * \retval non-zero if call pending flag was already set. - */ -static int member_call_pending_set(struct member *mem) -{ - int old_pending; - - ao2_lock(mem); - old_pending = mem->call_pending; - mem->call_pending = 1; - ao2_unlock(mem); - - return old_pending; -} - -/*! - * \internal * \brief Determine if can ring a queue entry. * * \param qe Queue entry to check. @@ -4164,7 +4196,7 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call) return 0; } - if (call->member->in_call && call->lastqueue->wrapuptime) { + if (call->member->in_call && call->lastqueue && call->lastqueue->wrapuptime) { ast_debug(1, "%s is in call, so not available (wrapuptime %d)\n", call->interface, call->lastqueue->wrapuptime); return 0; @@ -4185,13 +4217,32 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call) } if (!call->member->ringinuse) { - if (member_call_pending_set(call->member)) { - ast_debug(1, "%s has another call pending, can't receive call\n", - call->interface); + struct member *mem; + + ao2_lock(pending_members); + + mem = ao2_find(pending_members, call->member, + OBJ_SEARCH_OBJECT | OBJ_NOLOCK); + if (mem) { + /* + * If found that means this member is currently being attempted + * from another calling thread, so stop trying from this thread + */ + ast_debug(1, "%s has another call trying, can't receive call\n", + call->interface); + ao2_ref(mem, -1); + ao2_unlock(pending_members); return 0; } /* + * If not found add it to the container so another queue + * won't attempt to call this member at the same time. + */ + ao2_link(pending_members, call->member); + ao2_unlock(pending_members); + + /* * The queue member is available. Get current status to be sure * because the device state and extension state callbacks may * not have updated the status yet. @@ -4199,7 +4250,7 @@ static int can_ring_entry(struct queue_ent *qe, struct callattempt *call) if (!member_status_available(get_queue_member_status(call->member))) { ast_debug(1, "%s actually not available, can't receive call\n", call->interface); - member_call_pending_clear(call->member); + pending_members_remove(call->member); return 0; } } @@ -4236,7 +4287,6 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies ++*busies; return 0; } - ast_assert(tmp->member->ringinuse || tmp->member->call_pending); ast_copy_string(tech, tmp->interface, sizeof(tech)); if ((location = strchr(tech, '/'))) { @@ -4253,7 +4303,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies qe->linpos++; ao2_unlock(qe->parent); - member_call_pending_clear(tmp->member); + pending_members_remove(tmp->member); publish_dial_end_event(qe->chan, tmp, NULL, "BUSY"); tmp->stillgoing = 0; @@ -4324,7 +4374,7 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies /* Again, keep going even if there's an error */ ast_verb(3, "Couldn't call %s\n", tmp->interface); do_hang(tmp); - member_call_pending_clear(tmp->member); + pending_members_remove(tmp->member); ++*busies; return 0; } @@ -4344,7 +4394,6 @@ static int ring_entry(struct queue_ent *qe, struct callattempt *tmp, int *busies ast_verb(3, "Called %s\n", tmp->interface); - member_call_pending_clear(tmp->member); return 1; } @@ -9472,7 +9521,7 @@ static int manager_queues_summary(struct mansession *s, const struct message *m) ao2_lock(q); /* List queue properties */ - if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) { + if (ast_strlen_zero(queuefilter) || !strcasecmp(q->name, queuefilter)) { /* Reset the necessary local variables if no queuefilter is set*/ qmemcount = 0; qmemavail = 0; @@ -9550,7 +9599,7 @@ static int manager_queues_status(struct mansession *s, const struct message *m) ao2_lock(q); /* List queue properties */ - if (ast_strlen_zero(queuefilter) || !strcmp(q->name, queuefilter)) { + if (ast_strlen_zero(queuefilter) || !strcasecmp(q->name, queuefilter)) { sl = ((q->callscompleted > 0) ? 100 * ((float)q->callscompletedinsl / (float)q->callscompleted) : 0); astman_append(s, "Event: QueueParams\r\n" "Queue: %s\r\n" @@ -10806,6 +10855,8 @@ static int unload_module(void) ast_unload_realtime("queue_members"); ao2_cleanup(queues); + ao2_cleanup(pending_members); + queues = NULL; return 0; } @@ -10833,6 +10884,13 @@ static int load_module(void) return AST_MODULE_LOAD_DECLINE; } + pending_members = ao2_container_alloc( + MAX_CALL_ATTEMPT_BUCKETS, pending_members_hash, pending_members_cmp); + if (!pending_members) { + unload_module(); + return AST_MODULE_LOAD_DECLINE; + } + use_weight = 0; if (reload_handler(0, &mask, NULL)) { diff --git a/apps/app_talkdetect.c b/apps/app_talkdetect.c index a021252de..f7086fdd9 100644 --- a/apps/app_talkdetect.c +++ b/apps/app_talkdetect.c @@ -26,7 +26,7 @@ */ /*** MODULEINFO - <support_level>extended</support_level> + <support_level>core</support_level> ***/ #include "asterisk.h" diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c index 798f844fa..c94513d28 100644 --- a/apps/app_voicemail.c +++ b/apps/app_voicemail.c @@ -622,12 +622,12 @@ static AST_LIST_HEAD_STATIC(vmstates, vmstate); #define OPERATOR_EXIT 300 enum vm_box { - NEW_FOLDER, - OLD_FOLDER, - WORK_FOLDER, - FAMILY_FOLDER, - FRIENDS_FOLDER, - GREETINGS_FOLDER + NEW_FOLDER = 0, + OLD_FOLDER = 1, + WORK_FOLDER = 2, + FAMILY_FOLDER = 3, + FRIENDS_FOLDER = 4, + GREETINGS_FOLDER = -1 }; enum vm_option_flags { @@ -1725,13 +1725,14 @@ static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *contex } if (cur) { /* Make a copy, so that on a reload, we have no race */ - if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) { + if ((vmu = (ivm ? ivm : ast_calloc(1, sizeof(*vmu))))) { + ast_free(vmu->email); + ast_free(vmu->emailbody); + ast_free(vmu->emailsubject); *vmu = *cur; - if (!ivm) { - vmu->email = ast_strdup(cur->email); - vmu->emailbody = ast_strdup(cur->emailbody); - vmu->emailsubject = ast_strdup(cur->emailsubject); - } + vmu->email = ast_strdup(cur->email); + vmu->emailbody = ast_strdup(cur->emailbody); + vmu->emailsubject = ast_strdup(cur->emailsubject); ast_set2_flag(vmu, !ivm, VM_ALLOCED); AST_LIST_NEXT(vmu, list) = NULL; } @@ -2009,17 +2010,18 @@ static int get_folder_by_name(const char *name) static void free_user(struct ast_vm_user *vmu) { - if (ast_test_flag(vmu, VM_ALLOCED)) { - - ast_free(vmu->email); - vmu->email = NULL; - - ast_free(vmu->emailbody); - vmu->emailbody = NULL; + if (!vmu) { + return; + } - ast_free(vmu->emailsubject); - vmu->emailsubject = NULL; + ast_free(vmu->email); + vmu->email = NULL; + ast_free(vmu->emailbody); + vmu->emailbody = NULL; + ast_free(vmu->emailsubject); + vmu->emailsubject = NULL; + if (ast_test_flag(vmu, VM_ALLOCED)) { ast_free(vmu); } } @@ -2457,14 +2459,17 @@ static int __messagecount(const char *context, const char *mailbox, const char * return 0; /* We have to get the user before we can open the stream! */ + memset(&vmus, 0, sizeof(vmus)); vmu = find_user(&vmus, context, mailbox); if (!vmu) { ast_log(AST_LOG_WARNING, "Couldn't find mailbox %s in context %s\n", mailbox, context); + free_user(vmu); return -1; } else { /* No IMAP account available */ if (vmu->imapuser[0] == '\0') { ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox); + free_user(vmu); return -1; } } @@ -2484,9 +2489,11 @@ static int __messagecount(const char *context, const char *mailbox, const char * if (vms_p) { ast_debug(3, "Returning before search - user is logged in\n"); if (fold == 0) { /* INBOX */ + free_user(vmu); return urgent ? vms_p->urgentmessages : vms_p->newmessages; } if (fold == 1) { /* Old messages */ + free_user(vmu); return vms_p->oldmessages; } } @@ -2503,6 +2510,7 @@ static int __messagecount(const char *context, const char *mailbox, const char * ret = init_mailstream(vms_p, fold); if (!vms_p->mailstream) { ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n"); + free_user(vmu); return -1; } if (ret == 0) { @@ -2546,6 +2554,7 @@ static int __messagecount(const char *context, const char *mailbox, const char * /*Freeing the searchpgm also frees the searchhdr*/ mail_free_searchpgm(&pgm); ast_mutex_unlock(&vms_p->lock); + free_user(vmu); vms_p->updated = 0; return vms_p->vmArrayIndex; } else { @@ -2553,6 +2562,7 @@ static int __messagecount(const char *context, const char *mailbox, const char * mail_ping(vms_p->mailstream); ast_mutex_unlock(&vms_p->lock); } + free_user(vmu); return 0; } @@ -6185,6 +6195,7 @@ static int msg_create_from_file(struct ast_vm_recording_data *recdata) return -1; } + memset(&svm, 0, sizeof(svm)); if (!(recipient = find_user(&svm, recdata->context, recdata->mailbox))) { ast_log(LOG_ERROR, "No entry in voicemail config file for '%s@%s'\n", recdata->mailbox, recdata->context); return -1; @@ -6500,6 +6511,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ } ast_debug(3, "Before find_user\n"); + memset(&svm, 0, sizeof(svm)); if (!(vmu = find_user(&svm, context, ext))) { ast_log(AST_LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext); pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED"); @@ -6529,6 +6541,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext); if ((res = create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp"))) { ast_log(AST_LOG_WARNING, "Failed to make directory (%s)\n", tempfile); + free_user(vmu); ast_free(tmp); return -1; } @@ -6672,9 +6685,9 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ } ast_play_and_wait(chan, "transfer"); ast_channel_priority_set(chan, 0); - free_user(vmu); pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT"); } + free_user(vmu); ast_free(tmp); return OPERATOR_EXIT; } @@ -6708,6 +6721,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ res = inboxcount(ext_context, &newmsgs, &oldmsgs); if (res < 0) { ast_log(AST_LOG_NOTICE, "Can not leave voicemail, unable to count messages\n"); + free_user(vmu); ast_free(tmp); return -1; } @@ -6718,6 +6732,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ */ if (!(vms = create_vm_state_from_user(vmu))) { ast_log(AST_LOG_ERROR, "Couldn't allocate necessary space\n"); + free_user(vmu); ast_free(tmp); return -1; } @@ -6912,6 +6927,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_ *cntx = '\0'; cntx++; } + memset(&recipu, 0, sizeof(recipu)); if ((recip = find_user(&recipu, cntx, exten))) { copy_message(chan, vmu, 0, msgnum, duration, recip, fmt, dir, flag, NULL); free_user(recip); @@ -10939,6 +10955,7 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ } ast_debug(1, "Before find user for mailbox %s\n", mailbox); + memset(&vmus, 0, sizeof(vmus)); vmu = find_user(&vmus, context, mailbox); if (vmu && (vmu->password[0] == '\0' || (vmu->password[0] == '-' && vmu->password[1] == '\0'))) { /* saved password is blank, so don't bother asking */ @@ -10946,10 +10963,12 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ } else { if (ast_streamfile(chan, vm_password, ast_channel_language(chan))) { ast_log(AST_LOG_WARNING, "Unable to stream password file\n"); + free_user(vmu); return -1; } if (ast_readstring(chan, password, sizeof(password) - 1, 2000, 10000, "#") < 0) { ast_log(AST_LOG_WARNING, "Unable to read password\n"); + free_user(vmu); return -1; } else if (password[0] == '*') { /* user entered '*' */ @@ -10957,11 +10976,13 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1, S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) { mailbox[0] = '*'; + free_user(vmu); return -1; } ast_verb(4, "Jump to extension 'a' failed; setting mailbox and user to NULL\n"); mailbox[0] = '\0'; /* if the password entered was '*', do not let a user mailbox be created if the extension 'a' is not defined */ + free_user(vmu); vmu = NULL; } } @@ -10982,6 +11003,7 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ if (skipuser || logretries >= max_logins) { if (ast_streamfile(chan, "vm-incorrect", ast_channel_language(chan))) { ast_log(AST_LOG_WARNING, "Unable to stream incorrect message\n"); + free_user(vmu); return -1; } } else { @@ -10989,16 +11011,20 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_ adsi_login(chan); if (ast_streamfile(chan, "vm-incorrect-mailbox", ast_channel_language(chan))) { ast_log(AST_LOG_WARNING, "Unable to stream incorrect mailbox message\n"); + free_user(vmu); return -1; } } - if (ast_waitstream(chan, "")) /* Channel is hung up */ + if (ast_waitstream(chan, "")) { /* Channel is hung up */ + free_user(vmu); return -1; + } } } if (!valid && (logretries >= max_logins)) { ast_stopstream(chan); ast_play_and_wait(chan, "vm-goodbye"); + free_user(vmu); return -1; } if (vmu && !skipuser) { @@ -11106,6 +11132,8 @@ play_msg_cleanup: } #endif + free_user(vmu); + return res; } @@ -12301,7 +12329,7 @@ AST_TEST_DEFINE(test_voicemail_vmuser) static int vm_box_exists(struct ast_channel *chan, const char *data) { - struct ast_vm_user svm; + struct ast_vm_user svm, *vmu; char *context, *box; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(mbox); @@ -12331,8 +12359,10 @@ static int vm_box_exists(struct ast_channel *chan, const char *data) context++; } - if (find_user(&svm, context, args.mbox)) { + vmu = find_user(&svm, context, args.mbox); + if (vmu) { pbx_builtin_setvar_helper(chan, "VMBOXEXISTSSTATUS", "SUCCESS"); + free_user(vmu); } else pbx_builtin_setvar_helper(chan, "VMBOXEXISTSSTATUS", "FAILED"); @@ -12341,7 +12371,7 @@ static int vm_box_exists(struct ast_channel *chan, const char *data) static int acf_mailbox_exists(struct ast_channel *chan, const char *cmd, char *args, char *buf, size_t len) { - struct ast_vm_user svm; + struct ast_vm_user svm, *vmu; AST_DECLARE_APP_ARGS(arg, AST_APP_ARG(mbox); AST_APP_ARG(context); @@ -12360,7 +12390,10 @@ static int acf_mailbox_exists(struct ast_channel *chan, const char *cmd, char *a ast_log(AST_LOG_WARNING, "MAILBOX_EXISTS is deprecated. Please use ${VM_INFO(%s,exists)} instead.\n", args); } - ast_copy_string(buf, find_user(&svm, ast_strlen_zero(arg.context) ? "default" : arg.context, arg.mbox) ? "1" : "0", len); + vmu = find_user(&svm, ast_strlen_zero(arg.context) ? "default" : arg.context, arg.mbox); + ast_copy_string(buf, vmu ? "1" : "0", len); + free_user(vmu); + return 0; } @@ -12396,10 +12429,12 @@ static int acf_vm_info(struct ast_channel *chan, const char *cmd, char *args, ch return -1; } + memset(&svm, 0, sizeof(svm)); vmu = find_user(&svm, context, mailbox); if (!strncasecmp(arg.attribute, "exists", 5)) { ast_copy_string(buf, vmu ? "1" : "0", len); + free_user(vmu); return 0; } @@ -12428,13 +12463,16 @@ static int acf_vm_info(struct ast_channel *chan, const char *cmd, char *args, ch res = messagecount(mailbox_id, arg.folder); if (res < 0) { ast_log(LOG_ERROR, "Unable to retrieve message count for mailbox %s\n", arg.mailbox_context); + free_user(vmu); return -1; } snprintf(buf, len, "%d", res); } else { ast_log(LOG_ERROR, "Unknown attribute '%s' for VM_INFO\n", arg.attribute); + free_user(vmu); return -1; } + free_user(vmu); } return 0; @@ -14248,6 +14286,7 @@ AST_TEST_DEFINE(test_voicemail_msgcount) } #endif + memset(&svm, 0, sizeof(svm)); if (!(vmu = find_user(&svm, testcontext, testmailbox)) && !(vmu = find_or_create(testcontext, testmailbox))) { ast_test_status_update(test, "Cannot create vmu structure\n"); @@ -14277,6 +14316,7 @@ AST_TEST_DEFINE(test_voicemail_msgcount) #ifdef IMAP_STORAGE chan = ast_channel_unref(chan); #endif + free_user(vmu); return AST_TEST_FAIL; } } @@ -14360,6 +14400,7 @@ AST_TEST_DEFINE(test_voicemail_msgcount) syserr > 0 ? strerror(syserr) : "unable to fork()"); } + free_user(vmu); return res; } @@ -14469,6 +14510,7 @@ AST_TEST_DEFINE(test_voicemail_notify_endl) } } fclose(file); + free_user(vmu); return res; } @@ -14629,6 +14671,7 @@ AST_TEST_DEFINE(test_voicemail_vm_info) } chan = ast_channel_unref(chan); + free_user(vmu); return res; } #endif /* defined(TEST_FRAMEWORK) */ @@ -15020,8 +15063,10 @@ static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, s ast_config_destroy(msg_cfg); return res; } else { - struct ast_vm_user vmu2; - if (find_user(&vmu2, vmu->context, num)) { + struct ast_vm_user vmu2, *vmu3; + memset(&vmu2, 0, sizeof(vmu2)); + vmu3 = find_user(&vmu2, vmu->context, num); + if (vmu3) { struct leave_vm_options leave_options; char mailbox[AST_MAX_EXTENSION * 2 + 2]; snprintf(mailbox, sizeof(mailbox), "%s@%s", num, vmu->context); @@ -15034,6 +15079,7 @@ static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, s if (!res) res = 't'; ast_config_destroy(msg_cfg); + free_user(vmu3); return res; } else { /* Sender has no mailbox, can't reply */ @@ -15528,11 +15574,13 @@ static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_create(const char *ma if (!(mailbox_snapshot = ast_calloc(1, sizeof(*mailbox_snapshot)))) { ast_log(AST_LOG_ERROR, "Failed to allocate memory for mailbox snapshot\n"); + free_user(vmu); return NULL; } if (!(mailbox_snapshot->snapshots = ast_calloc(ARRAY_LEN(mailbox_folders), sizeof(*mailbox_snapshot->snapshots)))) { ast_free(mailbox_snapshot); + free_user(vmu); return NULL; } @@ -15593,6 +15641,7 @@ snapshot_cleanup: } #endif + free_user(vmu); return mailbox_snapshot; } @@ -15747,6 +15796,7 @@ static int vm_msg_forward(const char *from_mailbox, if (!(to_vmu = find_user(&to_vmus, to_context, to_mailbox))) { ast_log(LOG_WARNING, "Can't find voicemail user to forward to (%s@%s)\n", to_mailbox, to_context); + free_user(vmu); return -1; } @@ -15827,6 +15877,8 @@ vm_forward_cleanup: notify_new_state(to_vmu); } + free_user(vmu); + free_user(to_vmu); return res; } @@ -15930,6 +15982,7 @@ vm_move_cleanup: notify_new_state(vmu); } + free_user(vmu); return res; } @@ -16027,6 +16080,7 @@ vm_remove_cleanup: notify_new_state(vmu); } + free_user(vmu); return res; } @@ -16140,6 +16194,7 @@ play2_msg_cleanup: notify_new_state(vmu); } + free_user(vmu); return res; } diff --git a/apps/confbridge/conf_chan_announce.c b/apps/confbridge/conf_chan_announce.c index 6596a8537..ee4660687 100644 --- a/apps/confbridge/conf_chan_announce.c +++ b/apps/confbridge/conf_chan_announce.c @@ -199,7 +199,6 @@ int conf_announce_channel_push(struct ast_channel *ast) /* Impart the output channel into the bridge */ if (ast_bridge_impart(p->bridge, chan, NULL, features, AST_BRIDGE_IMPART_CHAN_DEPARTABLE)) { - ast_bridge_features_destroy(features); ast_channel_unref(chan); return -1; } diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c index b8b1e2a9c..f5bb7eb57 100644 --- a/apps/confbridge/conf_config_parser.c +++ b/apps/confbridge/conf_config_parser.c @@ -317,6 +317,22 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") on a conference. </para></description> </configOption> + <configOption name="regcontext"> + <synopsis>The name of the context into which to register the name of the conference bridge as NoOP() at priority 1</synopsis> + <description><para> + When set this will cause the name of the created conference to be registered + into the named context at priority 1 with an operation of NoOP(). This can + then be used in other parts of the dialplan to test for the existence of a + specific conference bridge. + You should be aware that there are potential races between testing for the + existence of a bridge, and taking action upon that information, consider + for example two callers executing the check simultaniously, and then taking + special action as "first caller" into the bridge. The same for exiting, + directly after the check the bridge can be destroyed before the new caller + enters (creating a new bridge), for example, and the "first member" actions + could thus be missed. + </para></description> + </configOption> <configOption name="video_mode"> <synopsis>Sets how confbridge handles video distribution to the conference participants</synopsis> <description><para> @@ -1563,6 +1579,8 @@ static char *handle_cli_confbridge_show_bridge_profile(struct ast_cli_entry *e, ast_cli(a->fd,"Max Members: No Limit\n"); } + ast_cli(a->fd,"Registration context: %s\n", b_profile.regcontext); + switch (b_profile.flags & (BRIDGE_OPT_VIDEO_SRC_LAST_MARKED | BRIDGE_OPT_VIDEO_SRC_FIRST_MARKED | BRIDGE_OPT_VIDEO_SRC_FOLLOW_TALKER)) { @@ -2128,6 +2146,7 @@ int conf_load_config(void) aco_option_register(&cfg_info, "record_file_append", ACO_EXACT, bridge_types, "yes", OPT_BOOLFLAG_T, 1, FLDSET(struct bridge_profile, flags), BRIDGE_OPT_RECORD_FILE_APPEND); aco_option_register(&cfg_info, "max_members", ACO_EXACT, bridge_types, "0", OPT_UINT_T, 0, FLDSET(struct bridge_profile, max_members)); aco_option_register(&cfg_info, "record_file", ACO_EXACT, bridge_types, NULL, OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, rec_file)); + aco_option_register(&cfg_info, "regcontext", ACO_EXACT, bridge_types, NULL, OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, regcontext)); aco_option_register(&cfg_info, "language", ACO_EXACT, bridge_types, "en", OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, language)); aco_option_register_custom(&cfg_info, "^sound_", ACO_REGEX, bridge_types, NULL, sound_option_handler, 0); /* This option should only be used with the CONFBRIDGE dialplan function */ diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h index 8d2dffb1c..a1fa5a2b7 100644 --- a/apps/confbridge/include/confbridge.h +++ b/apps/confbridge/include/confbridge.h @@ -207,6 +207,7 @@ struct bridge_profile { unsigned int internal_sample_rate; /*!< The internal sample rate of the bridge. 0 when set to auto adjust mode. */ unsigned int mix_interval; /*!< The internal mixing interval used by the bridge. When set to 0 the bridgewill use a default interval. */ struct bridge_profile_sounds *sounds; + char regcontext[AST_MAX_CONTEXT]; }; /*! \brief The structure that represents a conference bridge */ diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c index e3df18fe5..fe058e4e6 100644 --- a/bridges/bridge_softmix.c +++ b/bridges/bridge_softmix.c @@ -359,6 +359,9 @@ static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_ch struct ast_format *slin_format; int setup_fail; + /* The callers have already ensured that sc is never NULL. */ + ast_assert(sc != NULL); + slin_format = ast_format_cache_get_slin_by_rate(rate); ast_mutex_lock(&sc->lock); @@ -714,7 +717,7 @@ static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_cha { int res = 0; - if (!bridge->tech_pvt || (bridge_channel && !bridge_channel->tech_pvt)) { + if (!bridge->tech_pvt || !bridge_channel || !bridge_channel->tech_pvt) { /* "Accept" the frame and discard it. */ return 0; } @@ -984,6 +987,11 @@ static int softmix_mixing_loop(struct ast_bridge *bridge) AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { struct softmix_channel *sc = bridge_channel->tech_pvt; + if (!sc) { + /* This channel failed to join successfully. */ + continue; + } + /* Update the sample rate to match the bridge's native sample rate if necessary. */ if (update_all_rates) { set_softmix_bridge_data(softmix_data->internal_rate, softmix_data->internal_mixing_interval, bridge_channel, 1); @@ -1019,7 +1027,8 @@ static int softmix_mixing_loop(struct ast_bridge *bridge) AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { struct softmix_channel *sc = bridge_channel->tech_pvt; - if (bridge_channel->suspended) { + if (!sc || bridge_channel->suspended) { + /* This channel failed to join successfully or is suspended. */ continue; } diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 91fb0b546..7cc91486a 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -17669,6 +17669,10 @@ static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sock if (!peer && sip_cfg.autocreatepeer != AUTOPEERS_DISABLED) { /* Create peer if we have autocreate mode enabled */ peer = temp_peer(name); + if (peer && !(peer->endpoint = ast_endpoint_create("SIP", name))) { + ao2_t_ref(peer, -1, "failed to allocate Stasis endpoint, drop peer"); + peer = NULL; + } if (peer) { ao2_t_link(peers, peer, "link peer into peer table"); if (!ast_sockaddr_isnull(&peer->addr)) { @@ -35170,17 +35174,19 @@ static int load_module(void) /* And start the monitor for the first time */ restart_monitor(); - ast_realtime_require_field(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", - "name", RQ_CHAR, 10, - "ipaddr", RQ_CHAR, INET6_ADDRSTRLEN - 1, - "port", RQ_UINTEGER2, 5, - "regseconds", RQ_INTEGER4, 11, - "defaultuser", RQ_CHAR, 10, - "fullcontact", RQ_CHAR, 35, - "regserver", RQ_CHAR, 20, - "useragent", RQ_CHAR, 20, - "lastms", RQ_INTEGER4, 11, - SENTINEL); + if (sip_cfg.peer_rtupdate) { + ast_realtime_require_field(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", + "name", RQ_CHAR, 10, + "ipaddr", RQ_CHAR, INET6_ADDRSTRLEN - 1, + "port", RQ_UINTEGER2, 5, + "regseconds", RQ_INTEGER4, 11, + "defaultuser", RQ_CHAR, 10, + "fullcontact", RQ_CHAR, 35, + "regserver", RQ_CHAR, 20, + "useragent", RQ_CHAR, 20, + "lastms", RQ_INTEGER4, 11, + SENTINEL); + } sip_register_tests(); @@ -35199,7 +35205,7 @@ static int unload_module(void) struct sip_pvt *p; struct sip_threadinfo *th; struct ao2_iterator i; - int wait_count; + struct timeval start; ast_sip_api_provider_unregister(); @@ -35349,11 +35355,11 @@ static int unload_module(void) * joinable. They can die on their own and remove themselves * from the container thus resulting in a huge memory leak. */ - wait_count = 1000; - while (ao2_container_count(threadt) && --wait_count) { + start = ast_tvnow(); + while (ao2_container_count(threadt) && (ast_tvdiff_sec(ast_tvnow(), start) < 5)) { sched_yield(); } - if (!wait_count) { + if (ao2_container_count(threadt)) { ast_debug(2, "TCP/TLS thread container did not become empty :(\n"); } diff --git a/configs/basic-pbx/asterisk.conf b/configs/basic-pbx/asterisk.conf index 3ee7b99b8..576cc976b 100644 --- a/configs/basic-pbx/asterisk.conf +++ b/configs/basic-pbx/asterisk.conf @@ -1,15 +1,15 @@ [directories] -astetcdir = /etc/asterisk -astmoddir = /usr/lib/asterisk/modules -astvarlibdir = /var/lib/asterisk -astdbdir = /var/lib/asterisk -astkeydir = /var/lib/asterisk -astdatadir = /var/lib/asterisk -astagidir = /var/lib/asterisk/agi-bin -astspooldir = /var/spool/asterisk -astrundir = /var/run/asterisk -astlogdir = /var/log/asterisk -astsbindir = /usr/sbin +astetcdir => /etc/asterisk +astmoddir => /usr/lib/asterisk/modules +astvarlibdir => /var/lib/asterisk +astdbdir => /var/lib/asterisk +astkeydir => /var/lib/asterisk +astdatadir => /var/lib/asterisk +astagidir => /var/lib/asterisk/agi-bin +astspooldir => /var/spool/asterisk +astrundir => /var/run/asterisk +astlogdir => /var/log/asterisk +astsbindir => /usr/sbin [options] ; If we want to start Asterisk with a default verbosity for the verbose diff --git a/configs/samples/confbridge.conf.sample b/configs/samples/confbridge.conf.sample index d0bdd6fd9..49208c31b 100644 --- a/configs/samples/confbridge.conf.sample +++ b/configs/samples/confbridge.conf.sample @@ -211,6 +211,8 @@ type=bridge ;language=en ; Set the language used for announcements to the conference. ; Default is en (English). +;regcontext=conferences ; The name of the context into which to register conference names as extensions. + ; All sounds in the conference are customizable using the bridge profile options below. ; Simply state the option followed by the filename or full path of the filename after ; the option. Example: sound_had_joined=conf-hasjoin This will play the conf-hasjoin diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample index 0b321b43c..a17dab7a7 100644 --- a/configs/samples/pjsip.conf.sample +++ b/configs/samples/pjsip.conf.sample @@ -620,8 +620,13 @@ ; the specified address. (default: "no") ;force_rport=yes ; Force use of return port (default: "yes") ;ice_support=no ; Enable the ICE mechanism to help traverse NAT (default: "no") -;identify_by=username ; Way s for Endpoint to be identified (default: - ; "username") +;identify_by=username ; A comma-separated list of ways the Endpoint or AoR can be + ; identified. + ; "username": Identify by the From or To username and domain + ; "auth_username": Identify by the Authorization username and realm + : In all cases, if an exact match on username and domain/realm fails, + ; the match will be retried with just the username. + ; (default: "username") ;redirect_method=user ; How redirects received from an endpoint are handled ; (default: "user") ;mailboxes= ; NOTIFY the endpoint when state changes for any of the specified mailboxes. @@ -899,10 +904,19 @@ ; (default: "0") ;contact_expiration_check_interval=30 ; The interval (in seconds) to check for expired contacts. +;disable_multi_domain=no + ; Disable Multi Domain support. + ; If disabled it can improve realtime performace by reducing + ; number of database requsts + ; (default: "no") ;endpoint_identifier_order=ip,username,anonymous ; The order by which endpoint identifiers are given priority. - ; Identifier names are derived from res_pjsip_endpoint_identifier_* - ; modules. (default: ip,username,anonymous) + ; Currently, "ip", "username", "auth_username" and "anonymous" are valid + ; identifiers as registered by the res_pjsip_endpoint_identifier_* modules. + ; Some modules like res_pjsip_endpoint_identifier_user register more than + ; one identifier. Use the CLI command "pjsip show identifiers" to see the + ; identifiers currently available. + ; (default: ip,username,anonymous) ;max_initial_qualify_time=4 ; The maximum amount of time (in seconds) from ; startup that qualifies should be attempted on all ; contacts. If greater than the qualify_frequency @@ -915,7 +929,28 @@ ; The voicemail extension to send in the NOTIFY Message-Account header ; if not set on endpoint or aor. ; (default: "") - +; +; The following unidentified_request options are only used when "auth_username" +; matching is enabled in "endpoint_identifier_order". +; +;unidentified_request_count=5 ; The number of unidentified requests that can be + ; received from a single IP address in + ; unidentified_request_period seconds before a security + ; event is generated. (default: 5) +;unidentified_request_period=5 ; See above. (default: 5 seconds) +;unidentified_request_prune_interval=30 + ; The interval at which unidentified requests + ; are check to see if they can be pruned. If they're + ; older than twice the unidentified_request_period, + ; they're pruned. +; +;default_from_user=asterisk ; When Asterisk generates an outgoing SIP request, the + ; From header username will be set to this value if + ; there is no better option (such as CallerID or + ; endpoint/from_user) to be used +;default_realm=asterisk ; When Asterisk generates a challenge, the realm will be + ; set to this value if there is no better option (such as + ; auth/realm) to be used ; MODULE PROVIDING BELOW SECTION(S): res_pjsip_acl ;==========================ACL SECTION OPTIONS========================= diff --git a/configs/samples/sip.conf.sample b/configs/samples/sip.conf.sample index a24ab30a6..5c3238e2a 100644 --- a/configs/samples/sip.conf.sample +++ b/configs/samples/sip.conf.sample @@ -1479,7 +1479,6 @@ srvlookup=yes ; Enable DNS SRV lookups on outbound calls ;allow=ulaw ;allow=alaw ;mailbox=1234@default,1233@default ; Subscribe to status of multiple mailboxes -;registertrying=yes ; Send a 100 Trying when the device registers. ;[snom] ;type=friend ; Friends place calls and receive calls @@ -663,6 +663,10 @@ PWLIB_LIBDIR PWLIB_INCDIR PWLIBDIR PTLIB_CONFIG +PYTHONDEV_LIBS +PYTHONDEV_CFLAGS +PYTHONDEV_INCLUDE +PYTHONDEV_LIB PJPROJECT_LIBS PJPROJECT_CFLAGS PG_CONFIG @@ -1416,6 +1420,8 @@ LIBEDIT_CFLAGS LIBEDIT_LIBS PJPROJECT_CFLAGS PJPROJECT_LIBS +PYTHONDEV_CFLAGS +PYTHONDEV_LIBS GMIME_CFLAGS GMIME_LIBS GTK2_CFLAGS @@ -2158,6 +2164,10 @@ Some influential environment variables: C compiler flags for PJPROJECT, overriding pkg-config PJPROJECT_LIBS linker flags for PJPROJECT, overriding pkg-config + PYTHONDEV_CFLAGS + C compiler flags for PYTHONDEV, overriding pkg-config + PYTHONDEV_LIBS + linker flags for PYTHONDEV, overriding pkg-config GMIME_CFLAGS C compiler flags for GMIME, overriding pkg-config GMIME_LIBS linker flags for GMIME, overriding pkg-config @@ -24221,16 +24231,16 @@ if test "$USE_PJPROJECT" != "no" ; then $as_echo_n "checking for embedded pjproject (may have to download)... " >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: configuring" >&5 $as_echo "configuring" >&6; } - make --quiet --no-print-directory -C $PJPROJECT_DIR configure + ${GNU_MAKE} --quiet --no-print-directory -C $PJPROJECT_DIR configure if test $? -ne 0 ; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5 $as_echo "failed" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: Unable to configure $PJPROJECT_DIR" >&5 $as_echo "$as_me: Unable to configure $PJPROJECT_DIR" >&6;} - as_fn_error $? "Run \"make -C $PJPROJECT_DIR NOISY_BUILD=yes configure\" to see error details." "$LINENO" 5 + as_fn_error $? "Run \"${GNU_MAKE} -C $PJPROJECT_DIR NOISY_BUILD=yes configure\" to see error details." "$LINENO" 5 fi - PJPROJECT_INCLUDE=$(make --quiet --no-print-directory -C $PJPROJECT_DIR echo_cflags) + PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C $PJPROJECT_DIR echo_cflags) PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE" PBX_PJPROJECT=1 PJPROJECT_BUNDLED=yes @@ -25172,6 +25182,97 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi + + + if test "x${PBX_PYTHONDEV}" != "x1" -a "${USE_PYTHONDEV}" != "no"; then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for PYTHONDEV" >&5 +$as_echo_n "checking for PYTHONDEV... " >&6; } + +if test -n "$PYTHONDEV_CFLAGS"; then + pkg_cv_PYTHONDEV_CFLAGS="$PYTHONDEV_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python\""; } >&5 + ($PKG_CONFIG --exists --print-errors "python") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_PYTHONDEV_CFLAGS=`$PKG_CONFIG --cflags "python" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$PYTHONDEV_LIBS"; then + pkg_cv_PYTHONDEV_LIBS="$PYTHONDEV_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"python\""; } >&5 + ($PKG_CONFIG --exists --print-errors "python") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_PYTHONDEV_LIBS=`$PKG_CONFIG --libs "python" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "python" 2>&1` + else + PYTHONDEV_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "python" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$PYTHONDEV_PKG_ERRORS" >&5 + + + PBX_PYTHONDEV=0 + + +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + + PBX_PYTHONDEV=0 + + +else + PYTHONDEV_CFLAGS=$pkg_cv_PYTHONDEV_CFLAGS + PYTHONDEV_LIBS=$pkg_cv_PYTHONDEV_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + + PBX_PYTHONDEV=1 + PYTHONDEV_INCLUDE="$PYTHONDEV_CFLAGS" + PYTHONDEV_LIB="$PYTHONDEV_LIBS" + +$as_echo "#define HAVE_PYTHONDEV 1" >>confdefs.h + + +fi + fi + + + if test "x${PBX_POPT}" != "x1" -a "${USE_POPT}" != "no"; then pbxlibdir="" # if --with-POPT=DIR has been specified, use it. diff --git a/configure.ac b/configure.ac index 7571f7fd5..822303708 100644 --- a/configure.ac +++ b/configure.ac @@ -2197,6 +2197,10 @@ if test "$USE_PJPROJECT" != "no" ; then fi fi +AC_SUBST([PYTHONDEV_LIB]) +AC_SUBST([PYTHONDEV_INCLUDE]) +AST_PKG_CONFIG_CHECK([PYTHONDEV], [python]) + AST_EXT_LIB_CHECK([POPT], [popt], [poptStrerror], [popt.h]) AST_EXT_LIB_CHECK([PORTAUDIO], [portaudio], [Pa_GetDeviceCount], [portaudio.h]) diff --git a/contrib/ast-db-manage/config/versions/65eb22eb195_add_unidentified_request_options_to_.py b/contrib/ast-db-manage/config/versions/65eb22eb195_add_unidentified_request_options_to_.py new file mode 100644 index 000000000..e0453a57c --- /dev/null +++ b/contrib/ast-db-manage/config/versions/65eb22eb195_add_unidentified_request_options_to_.py @@ -0,0 +1,27 @@ +"""Add unidentified request options to global + +Revision ID: 65eb22eb195 +Revises: 8d478ab86e29 +Create Date: 2016-03-11 11:58:51.567959 + +""" + +# revision identifiers, used by Alembic. +revision = '65eb22eb195' +down_revision = '8d478ab86e29' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('ps_globals', sa.Column('unidentified_request_count', sa.Integer)) + op.add_column('ps_globals', sa.Column('unidentified_request_period', sa.Integer)) + op.add_column('ps_globals', sa.Column('unidentified_request_prune_interval', sa.Integer)) + op.add_column('ps_globals', sa.Column('default_realm', sa.String(40))) + +def downgrade(): + op.drop_column('ps_globals', 'unidentified_request_count') + op.drop_column('ps_globals', 'unidentified_request_period') + op.drop_column('ps_globals', 'unidentified_request_prune_interval') + op.drop_column('ps_globals', 'default_realm') diff --git a/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py b/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py new file mode 100644 index 000000000..c25fc7233 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/81b01a191a46_pjsip_add_contact_reg_server.py @@ -0,0 +1,25 @@ +"""pjsip: add contact reg_server + +Revision ID: 81b01a191a46 +Revises: 65eb22eb195 +Create Date: 2016-04-15 15:00:35.024525 + +""" + +# revision identifiers, used by Alembic. +revision = '81b01a191a46' +down_revision = '65eb22eb195' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('ps_contacts', sa.Column('reg_server', sa.String(20))) + op.drop_constraint(UniqueConstraint('id'), 'ps_contacts', type_='unique') + op.create_unique_constraint('ps_contacts_uq', 'ps_contacts', ['id','reg_server']) + +def downgrade(): + op.drop_constraint('ps_contacts_uq', 'ps_contacts', type_='unique') + op.drop_column('ps_contacts', 'reg_server') + op.create_unique_constraint(None, 'ps_contacts', 'id') diff --git a/contrib/ast-db-manage/config/versions/8d478ab86e29_pjsip_add_disable_multi_domain.py b/contrib/ast-db-manage/config/versions/8d478ab86e29_pjsip_add_disable_multi_domain.py new file mode 100644 index 000000000..a78268584 --- /dev/null +++ b/contrib/ast-db-manage/config/versions/8d478ab86e29_pjsip_add_disable_multi_domain.py @@ -0,0 +1,31 @@ +"""pjsip_add_disable_multi_domain + +Revision ID: 8d478ab86e29 +Revises: 1c688d9a003c +Create Date: 2016-04-15 11:41:26.988997 + +""" + +# revision identifiers, used by Alembic. +revision = '8d478ab86e29' +down_revision = '1c688d9a003c' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import ENUM + +YESNO_NAME = 'yesno_values' +YESNO_VALUES = ['yes', 'no'] + +def upgrade(): + ############################# Enums ############################## + + # yesno_values have already been created, so use postgres enum object + # type to get around "already created" issue - works okay with mysql + yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False) + + op.add_column('ps_globals', sa.Column('disable_multi_domain', yesno_values)) + + +def downgrade(): + op.drop_column('ps_globals', 'disable_multi_domain') diff --git a/contrib/scripts/install_prereq b/contrib/scripts/install_prereq index 1682558ac..bda28e9f7 100755 --- a/contrib/scripts/install_prereq +++ b/contrib/scripts/install_prereq @@ -29,13 +29,15 @@ PACKAGES_DEBIAN="$PACKAGES_DEBIAN libopenh323-dev libvpb-dev libgtk2.0-dev libmy PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev libjack-dev" PACKAGES_DEBIAN="$PACKAGES_DEBIAN libresample-dev libc-client-dev binutils-dev libsrtp-dev libgsm1-dev libedit-dev doxygen libjansson-dev libldap-dev" PACKAGES_DEBIAN="$PACKAGES_DEBIAN subversion git libxslt1-dev automake libsrtp-dev libncurses5-dev python-dev" -PACKAGES_RH="automake gcc gcc-c++ ncurses-devel openssl-devel libxml2-devel unixODBC-devel libcurl-devel libogg-devel libvorbis-devel speex-devel" +PACKAGES_RH="automake bzip2 gcc gcc-c++ patch ncurses-devel openssl-devel libxml2-devel unixODBC-devel libcurl-devel libogg-devel libvorbis-devel speex-devel" PACKAGES_RH="$PACKAGES_RH spandsp-devel freetds-devel net-snmp-devel iksemel-devel corosynclib-devel newt-devel popt-devel libtool-ltdl-devel lua-devel" PACKAGES_RH="$PACKAGES_RH sqlite-devel libsqlite3x-devel radiusclient-ng-devel portaudio-devel postgresql-devel libresample-devel neon-devel libical-devel" PACKAGES_RH="$PACKAGES_RH openldap-devel gmime22-devel sqlite2-devel mysql-devel bluez-libs-devel jack-audio-connection-kit-devel gsm-devel libedit-devel libuuid-devel" PACKAGES_RH="$PACKAGES_RH jansson-devel libsrtp-devel pjproject-devel subversion git libxslt-devel python-devel" PACKAGES_OBSD="popt gmake wget libxml libogg libvorbis curl iksemel spandsp speex iodbc freetds-0.63p1-msdblib mysql-client gmime sqlite sqlite3 jack libxslt" +PACKAGES_FBSD="autoconf gcc binutils popt gmake wget libxml2 libogg libvorbis curl iksemel spandsp speex unixODBC freetds-devel mysql55-client gmime2 sqlite" +PACKAGES_FBSD="$PACKAGES_FBSD sqlite3 libxslt jansson e2fsprogs-libuuid gsm libsrtp libsamplerate" KVERS=`uname -r` @@ -95,23 +97,45 @@ check_installed_pkgs() { done } +check_installed_fpkgs() { + for pack in "$@" + do + if [ `pkg info -a | grep $pack | wc -l` = 0 ]; then + echo $pack + fi + done +} + handle_debian() { if ! [ -x "$(command -v aptitude)" ]; then apt-get install aptitude fi extra_packs=`check_installed_debs $PACKAGES_DEBIAN` $testcmd aptitude update - $testcmd aptitude install -y $extra_packs + if [ x"$extra_packs" != "x" ] ; then + $testcmd aptitude install -y $extra_packs + fi } handle_rh() { extra_packs=`check_installed_rpms $PACKAGES_RH` - $testcmd yum install -y $extra_packs + if [ x"$extra_packs" != "x" ] ; then + $testcmd yum install -y $extra_packs + fi } handle_obsd() { extra_packs=`check_installed_pkgs $PACKAGES_OBSD` - $testcmd pkg_add $extra_packs + if [ x"$extra_packs" != "x" ] ; then + $testcmd pkg_add $extra_packs + fi +} + +handle_fbsd() { + extra_packs=`check_installed_fpkgs $PACKAGES_FBSD` + if [ x"$extra_packs" != "x" ] ; then + $testcmd pkg install -y $extra_packs + fi } install_unpackaged() { @@ -188,7 +212,7 @@ OS=`uname -s` unsupported_distro='' # A number of distributions we don't (yet?) support. -if [ "$OS" != 'Linux' -a "$OS" != 'OpenBSD' ]; then +if [ "$OS" != 'Linux' -a "$OS" != 'OpenBSD' -a "$OS" != 'FreeBSD' ]; then echo >&2 "$0: Your OS ($OS) is currently not supported. Aborting." exit 1 fi @@ -221,6 +245,8 @@ elif [ -r /etc/redhat-release ]; then handle_rh elif [ "$OS" = 'OpenBSD' ]; then handle_obsd +elif [ "$OS" = 'FreeBSD' ]; then + handle_fbsd fi if ! in_test_mode; then diff --git a/funcs/func_odbc.c b/funcs/func_odbc.c index 0af3fd1c8..aa84c00c7 100644 --- a/funcs/func_odbc.c +++ b/funcs/func_odbc.c @@ -137,6 +137,218 @@ struct odbc_datastore { char names[0]; }; +/* \brief Data source name + * + * This holds data that pertains to a DSN + */ +struct dsn { + /*! A connection to the database */ + struct odbc_obj *connection; + /*! The name of the DSN as defined in res_odbc.conf */ + char name[0]; +}; + +#define DSN_BUCKETS 37 + +struct ao2_container *dsns; + +static int dsn_hash(const void *obj, const int flags) +{ + const struct dsn *object; + const char *key; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_KEY: + key = obj; + break; + case OBJ_SEARCH_OBJECT: + object = obj; + key = object->name; + break; + default: + ast_assert(0); + return 0; + } + return ast_str_hash(key); +} + +static int dsn_cmp(void *obj, void *arg, int flags) +{ + const struct dsn *object_left = obj; + const struct dsn *object_right = arg; + const char *right_key = arg; + int cmp; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + right_key = object_right->name; + /* Fall through */ + case OBJ_SEARCH_KEY: + cmp = strcmp(object_left->name, right_key); + break; + case OBJ_SEARCH_PARTIAL_KEY: + cmp = strncmp(object_left->name, right_key, strlen(right_key)); + break; + default: + cmp = 0; + break; + } + + if (cmp) { + return 0; + } + + return CMP_MATCH; +} + +static void dsn_destructor(void *obj) +{ + struct dsn *dsn = obj; + + if (dsn->connection) { + ast_odbc_release_obj(dsn->connection); + } +} + +/*! + * \brief Create a DSN and connect to the database + * + * \param name The name of the DSN as found in res_odbc.conf + * \retval NULL Fail + * \retval non-NULL The newly-created structure + */ +static struct dsn *create_dsn(const char *name) +{ + struct dsn *dsn; + + dsn = ao2_alloc(sizeof(*dsn) + strlen(name) + 1, dsn_destructor); + if (!dsn) { + return NULL; + } + + /* Safe */ + strcpy(dsn->name, name); + + dsn->connection = ast_odbc_request_obj(name, 0); + if (!dsn->connection) { + ao2_ref(dsn, -1); + return NULL; + } + + if (!ao2_link_flags(dsns, dsn, OBJ_NOLOCK)) { + ao2_ref(dsn, -1); + return NULL; + } + + return dsn; +} + +static SQLHSTMT silent_execute(struct odbc_obj *obj, void *data); + +/*! + * \brief Determine if the connection has died. + * + * \param connection The connection to check + * \retval 1 Yep, it's dead + * \retval 0 It's alive and well + */ +static int connection_dead(struct odbc_obj *connection) +{ + SQLINTEGER dead; + SQLRETURN res; + SQLHSTMT stmt; + + if (!connection) { + return 1; + } + + res = SQLGetConnectAttr(connection->con, SQL_ATTR_CONNECTION_DEAD, &dead, 0, 0); + if (SQL_SUCCEEDED(res)) { + return dead == SQL_CD_TRUE ? 1 : 0; + } + + /* If the Driver doesn't support SQL_ATTR_CONNECTION_DEAD do a direct + * execute of a probing statement and see if that succeeds instead + */ + stmt = ast_odbc_direct_execute(connection, silent_execute, "SELECT 1"); + if (!stmt) { + return 1; + } + + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return 0; +} + +/*! + * \brief Retrieve a DSN, or create it if it does not exist. + * + * The created DSN is returned locked. This should be inconsequential + * to callers in most cases. + * + * When finished with the returned structure, the caller must call + * \ref release_dsn + * + * \param name Name of the DSN as found in res_odbc.conf + * \retval NULL Unable to retrieve or create the DSN + * \retval non-NULL The retrieved/created locked DSN + */ +static struct dsn *get_dsn(const char *name) +{ + struct dsn *dsn; + + ao2_lock(dsns); + dsn = ao2_find(dsns, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (!dsn) { + dsn = create_dsn(name); + } + ao2_unlock(dsns); + + if (!dsn) { + return NULL; + } + + ao2_lock(dsn); + if (!dsn->connection) { + dsn->connection = ast_odbc_request_obj(name, 0); + if (!dsn->connection) { + ao2_unlock(dsn); + ao2_ref(dsn, -1); + return NULL; + } + return dsn; + } + + if (connection_dead(dsn->connection)) { + ast_odbc_release_obj(dsn->connection); + dsn->connection = ast_odbc_request_obj(name, 0); + if (!dsn->connection) { + ao2_unlock(dsn); + ao2_ref(dsn, -1); + return NULL; + } + } + + return dsn; +} + +/*! + * \brief Unlock and unreference a DSN + * + * \param dsn The dsn to unlock and unreference + * \return NULL + */ +static void *release_dsn(struct dsn *dsn) +{ + if (!dsn) { + return NULL; + } + + ao2_unlock(dsn); + ao2_ref(dsn, -1); + + return NULL; +} + static AST_RWLIST_HEAD_STATIC(queries, acf_odbc_query); static int resultcount = 0; @@ -166,7 +378,16 @@ static void odbc_datastore_free(void *data) ast_free(result); } -static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data) +/*! + * \brief Common execution function for SQL queries. + * + * \param obj DB connection + * \param data The query to execute + * \param silent If true, do not print warnings on failure + * \retval NULL Failed to execute query + * \retval non-NULL The executed statement + */ +static SQLHSTMT execute(struct odbc_obj *obj, void *data, int silent) { int res; char *sql = data; @@ -180,7 +401,7 @@ static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data) res = SQLExecDirect(stmt, (unsigned char *)sql, SQL_NTS); if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO) && (res != SQL_NO_DATA)) { - if (res == SQL_ERROR) { + if (res == SQL_ERROR && !silent) { int i; SQLINTEGER nativeerror=0, numfields=0; SQLSMALLINT diagbytes=0; @@ -197,7 +418,9 @@ static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data) } } - ast_log(LOG_WARNING, "SQL Exec Direct failed (%d)![%s]\n", res, sql); + if (!silent) { + ast_log(LOG_WARNING, "SQL Exec Direct failed (%d)![%s]\n", res, sql); + } SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); return NULL; @@ -206,6 +429,16 @@ static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data) return stmt; } +static SQLHSTMT generic_execute(struct odbc_obj *obj, void *data) +{ + return execute(obj, data, 0); +} + +static SQLHSTMT silent_execute(struct odbc_obj *obj, void *data) +{ + return execute(obj, data, 1); +} + /* * Master control routine */ @@ -214,7 +447,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co struct odbc_obj *obj = NULL; struct acf_odbc_query *query; char *t, varname[15]; - int i, dsn, bogus_chan = 0; + int i, dsn_num, bogus_chan = 0; int transactional = 0; AST_DECLARE_APP_ARGS(values, AST_APP_ARG(field)[100]; @@ -227,6 +460,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co struct ast_str *buf = ast_str_thread_get(&sql_buf, 16); struct ast_str *insertbuf = ast_str_thread_get(&sql2_buf, 16); const char *status = "FAILURE"; + struct dsn *dsn = NULL; if (!buf || !insertbuf) { return -1; @@ -324,17 +558,21 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co * to multiple DSNs. We MUST have a single handle all the way through the * transaction, or else we CANNOT enforce atomicity. */ - for (dsn = 0; dsn < 5; dsn++) { - if (!ast_strlen_zero(query->writehandle[dsn])) { + for (dsn_num = 0; dsn_num < 5; dsn_num++) { + if (!ast_strlen_zero(query->writehandle[dsn_num])) { if (transactional) { /* This can only happen second time through or greater. */ ast_log(LOG_WARNING, "Transactions do not work well with multiple DSNs for 'writehandle'\n"); } - if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn]))) { + if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn_num]))) { transactional = 1; } else { - obj = ast_odbc_request_obj(query->writehandle[dsn], 0); + dsn = get_dsn(query->writehandle[dsn_num]); + if (!dsn) { + continue; + } + obj = dsn->connection; transactional = 0; } @@ -342,10 +580,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co break; } - if (obj && !transactional) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); } } @@ -358,25 +593,25 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co status = "SUCCESS"; } else if (query->sql_insert) { - if (obj && !transactional) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); - for (transactional = 0, dsn = 0; dsn < 5; dsn++) { - if (!ast_strlen_zero(query->writehandle[dsn])) { + for (transactional = 0, dsn_num = 0; dsn_num < 5; dsn_num++) { + if (!ast_strlen_zero(query->writehandle[dsn_num])) { if (transactional) { /* This can only happen second time through or greater. */ ast_log(LOG_WARNING, "Transactions do not work well with multiple DSNs for 'writehandle'\n"); } else if (obj) { - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); } - if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn]))) { + if ((obj = ast_odbc_retrieve_transaction_obj(chan, query->writehandle[dsn_num]))) { transactional = 1; } else { - obj = ast_odbc_request_obj(query->writehandle[dsn], 0); + dsn = get_dsn(query->writehandle[dsn_num]); + if (!dsn) { + continue; + } + obj = dsn->connection; transactional = 0; } if (obj) { @@ -406,10 +641,7 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status); } - if (obj && !transactional) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); if (!bogus_chan) { ast_autoservice_stop(chan); @@ -420,11 +652,10 @@ static int acf_odbc_write(struct ast_channel *chan, const char *cmd, char *s, co static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, char *buf, size_t len) { - struct odbc_obj *obj = NULL; struct acf_odbc_query *query; char varname[15], rowcount[12] = "-1"; struct ast_str *colnames = ast_str_thread_get(&colnames_buf, 16); - int res, x, y, buflen = 0, escapecommas, rowlimit = 1, multirow = 0, dsn, bogus_chan = 0; + int res, x, y, buflen = 0, escapecommas, rowlimit = 1, multirow = 0, dsn_num, bogus_chan = 0; AST_DECLARE_APP_ARGS(args, AST_APP_ARG(field)[100]; ); @@ -436,6 +667,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha struct odbc_datastore_row *row = NULL; struct ast_str *sql = ast_str_thread_get(&sql_buf, 16); const char *status = "FAILURE"; + struct dsn *dsn = NULL; if (!sql || !colnames) { if (chan) { @@ -523,28 +755,23 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha } AST_RWLIST_UNLOCK(&queries); - for (dsn = 0; dsn < 5; dsn++) { - if (!ast_strlen_zero(query->readhandle[dsn])) { - obj = ast_odbc_request_obj(query->readhandle[dsn], 0); - if (obj) { - stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)); + for (dsn_num = 0; dsn_num < 5; dsn_num++) { + if (!ast_strlen_zero(query->readhandle[dsn_num])) { + dsn = get_dsn(query->readhandle[dsn_num]); + if (!dsn) { + continue; } + stmt = ast_odbc_direct_execute(dsn->connection, generic_execute, ast_str_buffer(sql)); } if (stmt) { break; } - if (obj) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); } if (!stmt) { ast_log(LOG_ERROR, "Unable to execute query [%s]\n", ast_str_buffer(sql)); - if (obj) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); ast_autoservice_stop(chan); @@ -558,8 +785,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", ast_str_buffer(sql)); SQLCloseCursor(stmt); SQLFreeHandle (SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); ast_autoservice_stop(chan); @@ -583,8 +809,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha } SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); pbx_builtin_setvar_helper(chan, "ODBCSTATUS", status); @@ -607,8 +832,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha odbc_datastore_free(resultset); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR"); ast_autoservice_stop(chan); @@ -640,8 +864,7 @@ static int acf_odbc_read(struct ast_channel *chan, const char *cmd, char *s, cha odbc_datastore_free(resultset); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (!bogus_chan) { pbx_builtin_setvar_helper(chan, "ODBCROWS", rowcount); pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR"); @@ -750,8 +973,7 @@ end_acf_read: odbc_datastore_free(resultset); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); pbx_builtin_setvar_helper(chan, "ODBCSTATUS", "MEMERROR"); ast_autoservice_stop(chan); return -1; @@ -764,8 +986,7 @@ end_acf_read: } SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (resultset && !multirow) { /* Fetch the first resultset */ if (!acf_fetch(chan, "", buf, buf, len)) { @@ -1192,8 +1413,8 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args if (a->argc == 5 && !strcmp(a->argv[4], "exec")) { /* Execute the query */ - struct odbc_obj *obj = NULL; - int dsn, executed = 0; + struct dsn *dsn = NULL; + int dsn_num, executed = 0; SQLHSTMT stmt; int rows = 0, res, x; SQLSMALLINT colcount = 0, collength; @@ -1207,19 +1428,18 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args return CLI_SUCCESS; } - for (dsn = 0; dsn < 5; dsn++) { - if (ast_strlen_zero(query->readhandle[dsn])) { + for (dsn_num = 0; dsn_num < 5; dsn_num++) { + if (ast_strlen_zero(query->readhandle[dsn_num])) { continue; } - ast_debug(1, "Found handle %s\n", query->readhandle[dsn]); - if (!(obj = ast_odbc_request_obj(query->readhandle[dsn], 0))) { + dsn = get_dsn(query->readhandle[dsn_num]); + if (!dsn) { continue; } + ast_debug(1, "Found handle %s\n", query->readhandle[dsn_num]); - ast_debug(1, "Got obj\n"); - if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) { - ast_odbc_release_obj(obj); - obj = NULL; + if (!(stmt = ast_odbc_direct_execute(dsn->connection, generic_execute, ast_str_buffer(sql)))) { + dsn = release_dsn(dsn); continue; } @@ -1230,8 +1450,7 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args ast_cli(a->fd, "SQL Column Count error!\n[%s]\n\n", ast_str_buffer(sql)); SQLCloseCursor(stmt); SQLFreeHandle (SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); AST_RWLIST_UNLOCK(&queries); return CLI_SUCCESS; } @@ -1240,10 +1459,9 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); if (res == SQL_NO_DATA) { - ast_cli(a->fd, "Returned %d rows. Query executed on handle %d:%s [%s]\n", rows, dsn, query->readhandle[dsn], ast_str_buffer(sql)); + ast_cli(a->fd, "Returned %d rows. Query executed on handle %d:%s [%s]\n", rows, dsn_num, query->readhandle[dsn_num], ast_str_buffer(sql)); break; } else { ast_cli(a->fd, "Error %d in FETCH [%s]\n", res, ast_str_buffer(sql)); @@ -1270,8 +1488,7 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args ast_cli(a->fd, "SQL Get Data error %d!\n[%s]\n\n", res, ast_str_buffer(sql)); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; + dsn = release_dsn(dsn); AST_RWLIST_UNLOCK(&queries); return CLI_SUCCESS; } @@ -1289,15 +1506,11 @@ static char *cli_odbc_read(struct ast_cli_entry *e, int cmd, struct ast_cli_args } SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; - ast_cli(a->fd, "Returned %d row%s. Query executed on handle %d [%s]\n", rows, rows == 1 ? "" : "s", dsn, query->readhandle[dsn]); + dsn = release_dsn(dsn); + ast_cli(a->fd, "Returned %d row%s. Query executed on handle %d [%s]\n", rows, rows == 1 ? "" : "s", dsn_num, query->readhandle[dsn_num]); break; } - if (obj) { - ast_odbc_release_obj(obj); - obj = NULL; - } + dsn = release_dsn(dsn); if (!executed) { ast_cli(a->fd, "Failed to execute query. [%s]\n", ast_str_buffer(sql)); @@ -1420,30 +1633,29 @@ static char *cli_odbc_write(struct ast_cli_entry *e, int cmd, struct ast_cli_arg if (a->argc == 6 && !strcmp(a->argv[5], "exec")) { /* Execute the query */ - struct odbc_obj *obj = NULL; - int dsn, executed = 0; + struct dsn *dsn; + int dsn_num, executed = 0; SQLHSTMT stmt; SQLLEN rows = -1; - for (dsn = 0; dsn < 5; dsn++) { - if (ast_strlen_zero(query->writehandle[dsn])) { + for (dsn_num = 0; dsn_num < 5; dsn_num++) { + if (ast_strlen_zero(query->writehandle[dsn_num])) { continue; } - if (!(obj = ast_odbc_request_obj(query->writehandle[dsn], 0))) { + dsn = get_dsn(query->writehandle[dsn_num]); + if (!dsn) { continue; } - if (!(stmt = ast_odbc_direct_execute(obj, generic_execute, ast_str_buffer(sql)))) { - ast_odbc_release_obj(obj); - obj = NULL; + if (!(stmt = ast_odbc_direct_execute(dsn->connection, generic_execute, ast_str_buffer(sql)))) { + dsn = release_dsn(dsn); continue; } SQLRowCount(stmt, &rows); SQLCloseCursor(stmt); SQLFreeHandle(SQL_HANDLE_STMT, stmt); - ast_odbc_release_obj(obj); - obj = NULL; - ast_cli(a->fd, "Affected %d rows. Query executed on handle %d [%s]\n", (int)rows, dsn, query->writehandle[dsn]); + dsn = release_dsn(dsn); + ast_cli(a->fd, "Affected %d rows. Query executed on handle %d [%s]\n", (int)rows, dsn_num, query->writehandle[dsn_num]); executed = 1; break; } @@ -1470,6 +1682,11 @@ static int load_module(void) char *catg; struct ast_flags config_flags = { 0 }; + dsns = ao2_container_alloc(DSN_BUCKETS, dsn_hash, dsn_cmp); + if (!dsns) { + return AST_MODULE_LOAD_DECLINE; + } + res |= ast_custom_function_register(&fetch_function); res |= ast_register_application_xml(app_odbcfinish, exec_odbcfinish); AST_RWLIST_WRLOCK(&queries); @@ -1478,6 +1695,7 @@ static int load_module(void) if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) { ast_log(LOG_NOTICE, "Unable to load config for func_odbc: %s\n", config); AST_RWLIST_UNLOCK(&queries); + ao2_ref(dsns, -1); return AST_MODULE_LOAD_DECLINE; } @@ -1531,6 +1749,8 @@ static int unload_module(void) AST_RWLIST_WRLOCK(&queries); AST_RWLIST_UNLOCK(&queries); + + ao2_ref(dsns, -1); return res; } diff --git a/include/asterisk/astobj2.h b/include/asterisk/astobj2.h index 692cc7cb4..4bd44db76 100644 --- a/include/asterisk/astobj2.h +++ b/include/asterisk/astobj2.h @@ -19,6 +19,7 @@ #include "asterisk/compat.h" #include "asterisk/lock.h" +#include "asterisk/inline_api.h" /*! \file * \ref AstObj2 @@ -638,6 +639,46 @@ int __ao2_trylock(void *a, enum ao2_lock_req lock_how, const char *file, const c void *ao2_object_get_lockaddr(void *obj); +/*! + * \brief Increment reference count on an object and lock it + * \since 13.9.0 + * + * \param[in] obj A pointer to the ao2 object + * \retval 0 The object is not an ao2 object or wasn't locked successfully + * \retval 1 The object's reference count was incremented and was locked + */ +AST_INLINE_API( +int ao2_ref_and_lock(void *obj), +{ + ao2_ref(obj, +1); + if (ao2_lock(obj)) { + ao2_ref(obj, -1); + return 0; + } + return 1; +} +) + +/*! + * \brief Unlock an object and decrement its reference count + * \since 13.9.0 + * + * \param[in] obj A pointer to the ao2 object + * \retval 0 The object is not an ao2 object or wasn't unlocked successfully + * \retval 1 The object was unlocked and it's reference count was decremented + */ +AST_INLINE_API( +int ao2_unlock_and_unref(void *obj), +{ + if (ao2_unlock(obj)) { + return 0; + } + ao2_ref(obj, -1); + + return 1; +} +) + /*! Global ao2 object holder structure. */ struct ao2_global_obj { /*! Access lock to the held ao2 object. */ @@ -1985,4 +2026,97 @@ void ao2_iterator_cleanup(struct ao2_iterator *iter); */ int ao2_iterator_count(struct ao2_iterator *iter); +/*! + * \brief Creates a hash function for a structure string field. + * \param stype The structure type + * \param field The string field in the structure to hash + * + * AO2_STRING_FIELD_HASH_CB(mystruct, myfield) will produce a function + * named mystruct_hash_fn which hashes mystruct->myfield. + */ +#define AO2_STRING_FIELD_HASH_FN(stype, field) \ +static int stype ## _hash_fn(const void *obj, const int flags) \ +{ \ + const struct stype *object = obj; \ + const char *key; \ + switch (flags & OBJ_SEARCH_MASK) { \ + case OBJ_SEARCH_KEY: \ + key = obj; \ + break; \ + case OBJ_SEARCH_OBJECT: \ + key = object->field; \ + break; \ + default: \ + ast_assert(0); \ + return 0; \ + } \ + return ast_str_hash(key); \ +} + +/*! + * \brief Creates a compare function for a structure string field. + * \param stype The structure type + * \param field The string field in the structure to compare + * + * AO2_STRING_FIELD_CMP_FN(mystruct, myfield) will produce a function + * named mystruct_cmp_fn which compares mystruct->myfield. + */ +#define AO2_STRING_FIELD_CMP_FN(stype, field) \ +static int stype ## _cmp_fn(void *obj, void *arg, int flags) \ +{ \ + const struct stype *object_left = obj, *object_right = arg; \ + const char *right_key = arg; \ + int cmp; \ + switch (flags & OBJ_SEARCH_MASK) { \ + case OBJ_SEARCH_OBJECT: \ + right_key = object_right->field; \ + case OBJ_SEARCH_KEY: \ + cmp = strcmp(object_left->field, right_key); \ + break; \ + case OBJ_SEARCH_PARTIAL_KEY: \ + cmp = strncmp(object_left->field, right_key, strlen(right_key)); \ + break; \ + default: \ + cmp = 0; \ + break; \ + } \ + if (cmp) { \ + return 0; \ + } \ + return CMP_MATCH; \ +} + +/*! + * \brief Creates a sort function for a structure string field. + * \param stype The structure type + * \param field The string field in the structure to compare + * + * AO2_STRING_FIELD_SORT_FN(mystruct, myfield) will produce a function + * named mystruct_sort_fn which compares mystruct->myfield. + */ +#define AO2_STRING_FIELD_SORT_FN(stype, field) \ +static int stype ## _sort_fn(const void *obj, const void *arg, int flags) \ +{ \ + const struct stype *object_left = obj; \ + const struct stype *object_right = arg; \ + const char *right_key = arg; \ + int cmp; \ +\ + switch (flags & OBJ_SEARCH_MASK) { \ + case OBJ_SEARCH_OBJECT: \ + right_key = object_right->field; \ + /* Fall through */ \ + case OBJ_SEARCH_KEY: \ + cmp = strcmp(object_left->field, right_key); \ + break; \ + case OBJ_SEARCH_PARTIAL_KEY: \ + cmp = strncmp(object_left->field, right_key, strlen(right_key)); \ + break; \ + default: \ + cmp = 0; \ + break; \ + } \ + return cmp; \ +} + #endif /* _ASTERISK_ASTOBJ2_H */ diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index 80780986a..a01131cc3 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -731,6 +731,9 @@ /* Define if your system has the PWLib libraries. */ #undef HAVE_PWLIB +/* Define if your system has the PYTHONDEV libraries. */ +#undef HAVE_PYTHONDEV + /* Define to 1 if you have the Radius Client library. */ #undef HAVE_RADIUS diff --git a/include/asterisk/bridge_channel_internal.h b/include/asterisk/bridge_channel_internal.h index 7f7d5a88b..fb8e781e8 100644 --- a/include/asterisk/bridge_channel_internal.h +++ b/include/asterisk/bridge_channel_internal.h @@ -151,47 +151,20 @@ int bridge_channel_internal_push_full(struct ast_bridge_channel *bridge_channel, void bridge_channel_internal_pull(struct ast_bridge_channel *bridge_channel); /*! - * \brief Internal bridge channel wait condition and associated result. - */ -struct bridge_channel_internal_cond { - /*! Lock for the data structure */ - ast_mutex_t lock; - /*! Wait condition */ - ast_cond_t cond; - /*! Wait until done */ - int done; - /*! The bridge channel */ - struct ast_bridge_channel *bridge_channel; -}; - -/*! - * \internal - * \brief Wait for the expected signal. - * \since 13.5.0 - * - * \param cond the wait object - * - * \return Nothing - */ -void bridge_channel_internal_wait(struct bridge_channel_internal_cond *cond); - -/*! - * \internal - * \brief Signal the condition wait. - * \since 13.5.0 + * \brief Signal imparting threads to wake up. + * \since 13.9.0 * - * \param cond the wait object + * \param chan Channel imparted that we need to signal. * * \return Nothing */ -void bridge_channel_internal_signal(struct bridge_channel_internal_cond *cond); +void bridge_channel_impart_signal(struct ast_channel *chan); /*! * \internal * \brief Join the bridge_channel to the bridge (blocking) * * \param bridge_channel The Channel in the bridge - * \param cond data used for signaling * * \note The bridge_channel->swap holds a channel reference for the swap * channel going into the bridging system. The ref ensures that the swap @@ -206,8 +179,7 @@ void bridge_channel_internal_signal(struct bridge_channel_internal_cond *cond); * \retval 0 bridge channel successfully joined the bridge * \retval -1 bridge channel failed to join the bridge */ -int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel, - struct bridge_channel_internal_cond *cond); +int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel); /*! * \internal diff --git a/include/asterisk/bridge_technology.h b/include/asterisk/bridge_technology.h index 7de573a23..7f5d746f8 100644 --- a/include/asterisk/bridge_technology.h +++ b/include/asterisk/bridge_technology.h @@ -107,6 +107,9 @@ struct ast_bridge_technology { * \retval -1 on failure * * \note On entry, bridge is already locked. + * + * \note The bridge technology must tollerate a failed to join channel + * until it can be kicked from the bridge. */ int (*join)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel); /*! diff --git a/include/asterisk/features.h b/include/asterisk/features.h index b63124c2f..a4aed5d18 100644 --- a/include/asterisk/features.h +++ b/include/asterisk/features.h @@ -51,6 +51,7 @@ int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, struct a /*! * \brief Bridge a call, and add additional flags to the bridge * + * \details * This does the same thing as \ref ast_bridge_call, except that once the bridge * is created, the provided flags are set on the bridge. The provided flags are * added to the bridge's flags; they will not clear any flags already set. @@ -70,6 +71,7 @@ int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *pee * \brief Add an arbitrary channel to a bridge * \since 12.0.0 * + * \details * The channel that is being added to the bridge can be in any state: unbridged, * bridged, answered, unanswered, etc. The channel will be added asynchronously, * meaning that when this function returns once the channel has been added to @@ -87,11 +89,16 @@ int ast_bridge_call_with_flags(struct ast_channel *chan, struct ast_channel *pee * \param features Features for this channel in the bridge * \param play_tone Indicates if a tone should be played to the channel * \param xfersound Sound that should be used to indicate transfer with play_tone + * + * \note The features parameter must be NULL or obtained by + * ast_bridge_features_new(). You must not dereference features + * after calling even if the call fails. + * * \retval 0 Success * \retval -1 Failure */ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan, - struct ast_bridge_features *features, int play_tone, const char *xfersound); + struct ast_bridge_features *features, int play_tone, const char *xfersound); diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 3a9d61e4c..f985e3254 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -19,6 +19,13 @@ #ifndef _RES_PJSIP_H #define _RES_PJSIP_H +#include <pjsip.h> +/* Needed for SUBSCRIBE, NOTIFY, and PUBLISH method definitions */ +#include <pjsip_simple.h> +#include <pjsip/sip_transaction.h> +#include <pj/timer.h> +#include <pjlib.h> + #include "asterisk/stringfields.h" /* Needed for struct ast_sockaddr */ #include "asterisk/netsock2.h" @@ -241,6 +248,8 @@ struct ast_sip_contact { struct ast_sip_endpoint *endpoint; /*! The name of the aor this contact belongs to */ char *aor; + /*! Asterisk Server name */ + AST_STRING_FIELD_EXTENDED(reg_server); }; #define CONTACT_STATUS "contact_status" @@ -254,6 +263,7 @@ enum ast_sip_contact_status_type { UNKNOWN, CREATED, REMOVED, + UPDATED, }; /*! @@ -389,7 +399,10 @@ AST_VECTOR(ast_sip_auth_vector, const char *); enum ast_sip_endpoint_identifier_type { /*! Identify based on user name in From header */ AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME = (1 << 0), + /*! Identify based on user name in Auth header first, then From header */ + AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME = (1 << 1), }; +AST_VECTOR(ast_sip_identify_by_vector, enum ast_sip_endpoint_identifier_type); enum ast_sip_session_refresh_method { /*! Use reinvite to negotiate direct media */ @@ -701,6 +714,8 @@ struct ast_sip_endpoint { enum ast_sip_dtmf_mode dtmf; /*! Method(s) by which the endpoint should be identified. */ enum ast_sip_endpoint_identifier_type ident_method; + /*! Order of the method(s) by which the endpoint should be identified. */ + struct ast_sip_identify_by_vector ident_method_order; /*! Boolean indicating if ringing should be sent as inband progress */ unsigned int inband_progress; /*! Pointer to the persistent Asterisk endpoint */ @@ -1174,8 +1189,9 @@ struct ast_sip_auth *ast_sip_get_artificial_auth(void); */ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void); -/*! - * \page Threading model for SIP +/*! \defgroup pjsip_threading PJSIP Threading Model + * @{ + * \page PJSIP PJSIP Threading Model * * There are three major types of threads that SIP will have to deal with: * \li Asterisk threads @@ -1224,6 +1240,19 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void); * previous tasks pushed with the same serializer have completed. For more information * on serializers and the benefits they provide, see \ref ast_threadpool_serializer * + * \par Scheduler + * + * Some situations require that a task run periodically or at a future time. Normally + * the ast_sched functionality would be used but ast_sched only uses 1 thread for all + * tasks and that thread isn't registered with PJLIB and therefore can't do any PJSIP + * related work. + * + * ast_sip_sched uses ast_sched only as a scheduled queue. When a task is ready to run, + * it's pushed to a Serializer to be invoked asynchronously by a Servant. This ensures + * that the task is executed in a PJLIB registered thread and allows the ast_sched thread + * to immediately continue processing the queue. The Serializer used by ast_sip_sched + * is one of your choosing or a random one from the res_pjsip pool if you don't choose one. + * * \note * * Do not make assumptions about individual threads based on a corresponding serializer. @@ -1232,6 +1261,8 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void); * tasks, even though they are all guaranteed to be executed in sequence. */ +typedef int (*ast_sip_task)(void *user_data); + /*! * \brief Create a new serializer for SIP tasks * @@ -1369,6 +1400,214 @@ int ast_sip_push_task_synchronous(struct ast_taskprocessor *serializer, int (*si int ast_sip_thread_is_servant(void); /*! + * \brief Task flags for the res_pjsip scheduler + * + * The default is AST_SIP_SCHED_TASK_FIXED + * | AST_SIP_SCHED_TASK_DATA_NOT_AO2 + * | AST_SIP_SCHED_TASK_DATA_NO_CLEANUP + * | AST_SIP_SCHED_TASK_PERIODIC + */ +enum ast_sip_scheduler_task_flags { + /*! + * The defaults + */ + AST_SIP_SCHED_TASK_DEFAULTS = (0 << 0), + + /*! + * Run at a fixed interval. + * Stop scheduling if the callback returns 0. + * Any other value is ignored. + */ + AST_SIP_SCHED_TASK_FIXED = (0 << 0), + /*! + * Run at a variable interval. + * Stop scheduling if the callback returns 0. + * Any other return value is used as the new interval. + */ + AST_SIP_SCHED_TASK_VARIABLE = (1 << 0), + + /*! + * The task data is not an AO2 object. + */ + AST_SIP_SCHED_TASK_DATA_NOT_AO2 = (0 << 1), + /*! + * The task data is an AO2 object. + * A reference count will be held by the scheduler until + * after the task has run for the final time (if ever). + */ + AST_SIP_SCHED_TASK_DATA_AO2 = (1 << 1), + + /*! + * Don't take any cleanup action on the data + */ + AST_SIP_SCHED_TASK_DATA_NO_CLEANUP = (0 << 3), + /*! + * If AST_SIP_SCHED_TASK_DATA_AO2 is set, decrement the reference count + * otherwise call ast_free on it. + */ + AST_SIP_SCHED_TASK_DATA_FREE = ( 1 << 3 ), + + /*! \brief AST_SIP_SCHED_TASK_PERIODIC + * The task is scheduled at multiples of interval + * \see Interval + */ + AST_SIP_SCHED_TASK_PERIODIC = (0 << 4), + /*! \brief AST_SIP_SCHED_TASK_DELAY + * The next invocation of the task is at last finish + interval + * \see Interval + */ + AST_SIP_SCHED_TASK_DELAY = (1 << 4), +}; + +/*! + * \brief Scheduler task data structure + */ +struct ast_sip_sched_task; + +/*! + * \brief Schedule a task to run in the res_pjsip thread pool + * \since 13.9.0 + * + * \param serializer The serializer to use. If NULL, don't use a serializer (see note below) + * \param interval The invocation interval in milliseconds (see note below) + * \param sip_task The task to invoke + * \param name An optional name to associate with the task + * \param task_data Optional data to pass to the task + * \param flags One of enum ast_sip_scheduler_task_type + * + * \returns Pointer to \ref ast_sip_sched_task ao2 object which must be dereferenced when done. + * + * \paragraph Serialization + * + * Specifying a serializer guarantees serialized execution but NOT specifying a serializer + * may still result in tasks being effectively serialized if the thread pool is busy. + * The point of the serializer BTW is not to prevent parallel executions of the SAME task. + * That happens automatically (see below). It's to prevent the task from running at the same + * time as other work using the same serializer, whether or not it's being run by the scheduler. + * + * \paragraph Interval + * + * The interval is used to calculate the next time the task should run. There are two models. + * + * \ref AST_SIP_SCHED_TASK_PERIODIC specifies that the invocations of the task occur at the + * specific interval. That is, every \ref "interval" milliseconds, regardless of how long the task + * takes. If the task takes longer than \ref interval, it will be scheduled at the next available + * multiple of \ref interval. For exmaple: If the task has an interval of 60 seconds and the task + * takes 70 seconds, the next invocation will happen at 120 seconds. + * + * \ref AST_SIP_SCHED_TASK_DELAY specifies that the next invocation of the task should start + * at \ref interval milliseconds after the current invocation has finished. + * + */ +struct ast_sip_sched_task *ast_sip_schedule_task(struct ast_taskprocessor *serializer, + int interval, ast_sip_task sip_task, char *name, void *task_data, + enum ast_sip_scheduler_task_flags flags); + +/*! + * \brief Cancels the next invocation of a task + * \since 13.9.0 + * + * \param schtd The task structure pointer + * \retval 0 Success + * \retval -1 Failure + * \note Only cancels future invocations not the currently running invocation. + */ +int ast_sip_sched_task_cancel(struct ast_sip_sched_task *schtd); + +/*! + * \brief Cancels the next invocation of a task by name + * \since 13.9.0 + * + * \param name The task name + * \retval 0 Success + * \retval -1 Failure + * \note Only cancels future invocations not the currently running invocation. + */ +int ast_sip_sched_task_cancel_by_name(const char *name); + +/*! + * \brief Gets the last start and end times of the task + * \since 13.9.0 + * + * \param schtd The task structure pointer + * \param[out] when_queued Pointer to a timeval structure to contain the time when queued + * \param[out] last_start Pointer to a timeval structure to contain the time when last started + * \param[out] last_end Pointer to a timeval structure to contain the time when last ended + * \retval 0 Success + * \retval -1 Failure + * \note Any of the pointers can be NULL if you don't need them. + */ +int ast_sip_sched_task_get_times(struct ast_sip_sched_task *schtd, + struct timeval *when_queued, struct timeval *last_start, struct timeval *last_end); + +/*! + * \brief Gets the last start and end times of the task by name + * \since 13.9.0 + * + * \param name The task name + * \param[out] when_queued Pointer to a timeval structure to contain the time when queued + * \param[out] last_start Pointer to a timeval structure to contain the time when last started + * \param[out] last_end Pointer to a timeval structure to contain the time when last ended + * \retval 0 Success + * \retval -1 Failure + * \note Any of the pointers can be NULL if you don't need them. + */ +int ast_sip_sched_task_get_times_by_name(const char *name, + struct timeval *when_queued, struct timeval *last_start, struct timeval *last_end); + +/*! + * \brief Gets the number of milliseconds until the next invocation + * \since 13.9.0 + * + * \param schtd The task structure pointer + * \return The number of milliseconds until the next invocation or -1 if the task isn't scheduled + */ +int ast_sip_sched_task_get_next_run(struct ast_sip_sched_task *schtd); + +/*! + * \brief Gets the number of milliseconds until the next invocation + * \since 13.9.0 + * + * \param name The task name + * \return The number of milliseconds until the next invocation or -1 if the task isn't scheduled + */ +int ast_sip_sched_task_get_next_run_by_name(const char *name); + +/*! + * \brief Checks if the task is currently running + * \since 13.9.0 + * + * \param schtd The task structure pointer + * \retval 0 not running + * \retval 1 running + */ +int ast_sip_sched_is_task_running(struct ast_sip_sched_task *schtd); + +/*! + * \brief Checks if the task is currently running + * \since 13.9.0 + * + * \param name The task name + * \retval 0 not running or not found + * \retval 1 running + */ +int ast_sip_sched_is_task_running_by_name(const char *name); + +/*! + * \brief Gets the task name + * \since 13.9.0 + * + * \param schtd The task structure pointer + * \retval 0 success + * \retval 1 failure + */ +int ast_sip_sched_task_get_name(struct ast_sip_sched_task *schtd, char *name, size_t maxlen); + +/*! + * @} + */ + +/*! * \brief SIP body description * * This contains a type and subtype that will be added as @@ -2223,6 +2462,18 @@ char *ast_sip_get_endpoint_identifier_order(void); char *ast_sip_get_default_voicemail_extension(void); /*! + * \brief Retrieve the global default realm. + * + * This is the value placed in outbound challenges' realm if there + * is no better option (such as an auth-configured realm). + * + * \param[out] realm The default realm + * \param size The buffer size of realm + * \return nothing + */ +void ast_sip_get_default_realm(char *realm, size_t size); + +/*! * \brief Retrieve the global default from user. * * This is the value placed in outbound requests' From header if there @@ -2258,6 +2509,13 @@ unsigned int ast_sip_get_keep_alive_interval(void); */ unsigned int ast_sip_get_contact_expiration_check_interval(void); +/*! + * \brief Retrieve the system setting 'disable multi domain'. + * \since 13.9.0 + * + * \retval non zero if disable multi domain. + */ +unsigned int ast_sip_get_disable_multi_domain(void); /*! * \brief Retrieve the system max initial qualify time. @@ -2361,5 +2619,15 @@ int ast_sip_set_tpselector_from_transport_name(const char *transport_name, pjsip void ast_sip_modify_id_header(pj_pool_t *pool, pjsip_fromto_hdr *id_hdr, const struct ast_party_id *id); +/*! + * \brief Retrieve the unidentified request security event thresholds + * \since 13.8.0 + * + * \param count The maximum number of unidentified requests per source ip to accumulate before emitting a security event + * \param period The period in seconds over which to accumulate unidentified requests + * \param prune_interval The interval in seconds at which expired entries will be pruned + */ +void ast_sip_get_unidentified_request_thresholds(unsigned int *count, unsigned int *period, + unsigned int *prune_interval); #endif /* _RES_PJSIP_H */ diff --git a/include/asterisk/res_pjsip_pubsub.h b/include/asterisk/res_pjsip_pubsub.h index 84d86fb9e..94576d38b 100644 --- a/include/asterisk/res_pjsip_pubsub.h +++ b/include/asterisk/res_pjsip_pubsub.h @@ -214,9 +214,9 @@ enum ast_sip_subscription_notify_reason { AST_SIP_SUBSCRIPTION_NOTIFY_REASON_OTHER }; -/*! Type used for conveying mailbox state */ -#define AST_SIP_EXTEN_STATE_DATA "ast_sip_exten_state_data" /*! Type used for extension state/presence */ +#define AST_SIP_EXTEN_STATE_DATA "ast_sip_exten_state_data" +/*! Type used for conveying mailbox state */ #define AST_SIP_MESSAGE_ACCUMULATOR "ast_sip_message_accumulator" /*! diff --git a/include/asterisk/stasis.h b/include/asterisk/stasis.h index 16b30ccb3..4fc295bc4 100644 --- a/include/asterisk/stasis.h +++ b/include/asterisk/stasis.h @@ -416,14 +416,14 @@ const struct timeval *stasis_message_timestamp(const struct stasis_message *msg) * May return \c NULL, to indicate no representation. The returned object should * be ast_json_unref()'ed. * - * \param message Message to convert to JSON string. + * \param msg Message to convert to JSON string. * \param sanitize Snapshot sanitization callback. * * \return Newly allocated string with JSON message. * \return \c NULL on error. * \return \c NULL if JSON format is not supported. */ -struct ast_json *stasis_message_to_json(struct stasis_message *message, struct stasis_message_sanitizer *sanitize); +struct ast_json *stasis_message_to_json(struct stasis_message *msg, struct stasis_message_sanitizer *sanitize); /*! * \brief Build the AMI representation of the message. @@ -431,12 +431,21 @@ struct ast_json *stasis_message_to_json(struct stasis_message *message, struct s * May return \c NULL, to indicate no representation. The returned object should * be ao2_cleanup()'ed. * - * \param message Message to convert to AMI. + * \param msg Message to convert to AMI. * \return \c NULL on error. * \return \c NULL if AMI format is not supported. */ -struct ast_manager_event_blob *stasis_message_to_ami( - struct stasis_message *message); +struct ast_manager_event_blob *stasis_message_to_ami(struct stasis_message *msg); + +/*! + * \brief Determine if the given message can be converted to AMI. + * + * \param msg Message to see if can be converted to AMI. + * + * \retval 0 Cannot be converted + * \retval non-zero Can be converted + */ +int stasis_message_can_be_ami(struct stasis_message *msg); /*! * \brief Build the \ref AstGenericEvents representation of the message. @@ -444,12 +453,11 @@ struct ast_manager_event_blob *stasis_message_to_ami( * May return \c NULL, to indicate no representation. The returned object should * be disposed of via \ref ast_event_destroy. * - * \param message Message to convert to AMI. + * \param msg Message to convert to AMI. * \return \c NULL on error. * \return \c NULL if AMI format is not supported. */ -struct ast_event *stasis_message_to_event( - struct stasis_message *message); +struct ast_event *stasis_message_to_event(struct stasis_message *msg); /*! @} */ diff --git a/main/Makefile b/main/Makefile index a64eabcde..13f1c9cf5 100644 --- a/main/Makefile +++ b/main/Makefile @@ -224,11 +224,7 @@ endif $(ASTSSL_LIB): $(ASTSSL_LIB).$(ASTSSL_SO_VERSION) $(ECHO_PREFIX) echo " [LN] $< -> $@" - $(CMD_PREFIX) if [ -x "$(LDCONFIG)" ] ; then \ - $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null ;\ - else \ - $(LN) -sf $< $@ ;\ - fi + $(LN) -sf $< $@ ;\ else # Darwin ASTSSL_LIB:=libasteriskssl.dylib @@ -304,11 +300,7 @@ $(ASTPJ_LIB).$(ASTPJ_SO_VERSION): libasteriskpj.o libasteriskpj.exports $(ASTPJ_LIB): $(ASTPJ_LIB).$(ASTPJ_SO_VERSION) $(ECHO_PREFIX) echo " [LN] $< -> $@" - $(CMD_PREFIX) if [ -x "$(LDCONFIG)" ] ; then \ - $(LDCONFIG) $(LDCONFIG_FLAGS) . 2>/dev/null ;\ - else \ - $(LN) -sf $< $@ ;\ - fi + $(LN) -sf $< $@ ;\ else # Darwin ASTPJ_LIB:=libasteriskpj.dylib diff --git a/main/asterisk.exports.in b/main/asterisk.exports.in index 364b3b05e..f997587c9 100644 --- a/main/asterisk.exports.in +++ b/main/asterisk.exports.in @@ -49,6 +49,8 @@ LINKER_SYMBOL_PREFIXres_srtp; LINKER_SYMBOL_PREFIXres_srtp_policy; LINKER_SYMBOL_PREFIXsecure_call_info; + LINKER_SYMBOL_PREFIX__progname; + LINKER_SYMBOL_PREFIXenviron; /* If _IO_stdin_used is not exported, stdout/stderr may not get diff --git a/main/bridge.c b/main/bridge.c index fd83cfb7b..ee5ad735b 100644 --- a/main/bridge.c +++ b/main/bridge.c @@ -420,10 +420,12 @@ static void bridge_channel_complete_join(struct ast_bridge *bridge, struct ast_b bridge->technology->name); if (bridge->technology->join && bridge->technology->join(bridge, bridge_channel)) { - ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology\n", + /* We cannot leave the channel partially in the bridge so we must kick it out */ + ast_debug(1, "Bridge %s: %p(%s) failed to join %s technology (Kicking it out)\n", bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan), bridge->technology->name); bridge_channel->just_joined = 1; + ast_bridge_channel_leave_bridge(bridge_channel, BRIDGE_CHANNEL_STATE_END, 0); return; } @@ -1483,6 +1485,150 @@ void ast_bridge_notify_masquerade(struct ast_channel *chan) ao2_ref(bridge_channel, -1); } +/*! + * \brief Internal bridge impart wait condition and associated conditional. + */ +struct bridge_channel_impart_cond { + AST_LIST_ENTRY(bridge_channel_impart_cond) node; + /*! Lock for the data structure */ + ast_mutex_t lock; + /*! Wait condition */ + ast_cond_t cond; + /*! Wait until done */ + int done; +}; + +AST_LIST_HEAD_NOLOCK(bridge_channel_impart_ds_head, bridge_channel_impart_cond); + +/*! + * \internal + * \brief Signal imparting threads to wake up. + * \since 13.9.0 + * + * \param ds_head List of imparting threads to wake up. + * + * \return Nothing + */ +static void bridge_channel_impart_ds_head_signal(struct bridge_channel_impart_ds_head *ds_head) +{ + if (ds_head) { + struct bridge_channel_impart_cond *cond; + + while ((cond = AST_LIST_REMOVE_HEAD(ds_head, node))) { + ast_mutex_lock(&cond->lock); + cond->done = 1; + ast_cond_signal(&cond->cond); + ast_mutex_unlock(&cond->lock); + } + } +} + +static void bridge_channel_impart_ds_head_dtor(void *doomed) +{ + bridge_channel_impart_ds_head_signal(doomed); + ast_free(doomed); +} + +/*! + * \internal + * \brief Fixup the bridge impart datastore. + * \since 13.9.0 + * + * \param data Bridge impart datastore data to fixup from old_chan. + * \param old_chan The datastore is moving from this channel. + * \param new_chan The datastore is moving to this channel. + * + * \return Nothing + */ +static void bridge_channel_impart_ds_head_fixup(void *data, struct ast_channel *old_chan, struct ast_channel *new_chan) +{ + /* + * Signal any waiting impart threads. The masquerade is going to kill + * old_chan and we don't need to be waiting on new_chan. + */ + bridge_channel_impart_ds_head_signal(data); +} + +static const struct ast_datastore_info bridge_channel_impart_ds_info = { + .type = "bridge-impart-ds", + .destroy = bridge_channel_impart_ds_head_dtor, + .chan_fixup = bridge_channel_impart_ds_head_fixup, +}; + +/*! + * \internal + * \brief Add impart wait datastore conditional to channel. + * \since 13.9.0 + * + * \param chan Channel to add the impart wait conditional. + * \param cond Imparting conditional to add. + * + * \retval 0 on success. + * \retval -1 on error. + */ +static int bridge_channel_impart_add(struct ast_channel *chan, struct bridge_channel_impart_cond *cond) +{ + struct ast_datastore *datastore; + struct bridge_channel_impart_ds_head *ds_head; + + ast_channel_lock(chan); + + datastore = ast_channel_datastore_find(chan, &bridge_channel_impart_ds_info, NULL); + if (!datastore) { + datastore = ast_datastore_alloc(&bridge_channel_impart_ds_info, NULL); + if (!datastore) { + ast_channel_unlock(chan); + return -1; + } + ds_head = ast_calloc(1, sizeof(*ds_head)); + if (!ds_head) { + ast_channel_unlock(chan); + ast_datastore_free(datastore); + return -1; + } + datastore->data = ds_head; + ast_channel_datastore_add(chan, datastore); + } else { + ds_head = datastore->data; + ast_assert(ds_head != NULL); + } + + AST_LIST_INSERT_TAIL(ds_head, cond, node); + + ast_channel_unlock(chan); + return 0; +} + +void bridge_channel_impart_signal(struct ast_channel *chan) +{ + struct ast_datastore *datastore; + + ast_channel_lock(chan); + datastore = ast_channel_datastore_find(chan, &bridge_channel_impart_ds_info, NULL); + if (datastore) { + bridge_channel_impart_ds_head_signal(datastore->data); + } + ast_channel_unlock(chan); +} + +/*! + * \internal + * \brief Block imparting channel thread until signaled. + * \since 13.9.0 + * + * \param cond Imparting conditional to wait for. + * + * \return Nothing + */ +static void bridge_channel_impart_wait(struct bridge_channel_impart_cond *cond) +{ + ast_mutex_lock(&cond->lock); + while (!cond->done) { + ast_cond_wait(&cond->cond, &cond->lock); + } + ast_mutex_unlock(&cond->lock); +} + /* * XXX ASTERISK-21271 make ast_bridge_join() require features to be allocated just like ast_bridge_impart() and not expect the struct back. * @@ -1551,7 +1697,7 @@ int ast_bridge_join(struct ast_bridge *bridge, } if (!res) { - res = bridge_channel_internal_join(bridge_channel, NULL); + res = bridge_channel_internal_join(bridge_channel); } /* Cleanup all the data in the bridge channel after it leaves the bridge. */ @@ -1568,6 +1714,7 @@ int ast_bridge_join(struct ast_bridge *bridge, join_exit:; ast_bridge_run_after_callback(chan); + bridge_channel_impart_signal(chan); if (!(ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO) && !ast_bridge_setup_after_goto(chan)) { /* Claim the after bridge goto is an async goto destination. */ @@ -1581,14 +1728,13 @@ join_exit:; /*! \brief Thread responsible for imparted bridged channels to be departed */ static void *bridge_channel_depart_thread(void *data) { - struct bridge_channel_internal_cond *cond = data; - struct ast_bridge_channel *bridge_channel = cond->bridge_channel; + struct ast_bridge_channel *bridge_channel = data; if (bridge_channel->callid) { ast_callid_threadassoc_add(bridge_channel->callid); } - bridge_channel_internal_join(bridge_channel, cond); + bridge_channel_internal_join(bridge_channel); /* * cleanup @@ -1601,6 +1747,8 @@ static void *bridge_channel_depart_thread(void *data) bridge_channel->features = NULL; ast_bridge_discard_after_callback(bridge_channel->chan, AST_BRIDGE_AFTER_CB_REASON_DEPART); + /* If join failed there will be impart threads waiting. */ + bridge_channel_impart_signal(bridge_channel->chan); ast_bridge_discard_after_goto(bridge_channel->chan); return NULL; @@ -1609,15 +1757,14 @@ static void *bridge_channel_depart_thread(void *data) /*! \brief Thread responsible for independent imparted bridged channels */ static void *bridge_channel_ind_thread(void *data) { - struct bridge_channel_internal_cond *cond = data; - struct ast_bridge_channel *bridge_channel = cond->bridge_channel; + struct ast_bridge_channel *bridge_channel = data; struct ast_channel *chan; if (bridge_channel->callid) { ast_callid_threadassoc_add(bridge_channel->callid); } - bridge_channel_internal_join(bridge_channel, cond); + bridge_channel_internal_join(bridge_channel); chan = bridge_channel->chan; /* cleanup */ @@ -1634,15 +1781,18 @@ static void *bridge_channel_ind_thread(void *data) ao2_ref(bridge_channel, -1); ast_bridge_run_after_callback(chan); + /* If join failed there will be impart threads waiting. */ + bridge_channel_impart_signal(chan); ast_bridge_run_after_goto(chan); return NULL; } -int ast_bridge_impart(struct ast_bridge *bridge, +static int bridge_impart_internal(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features, - enum ast_bridge_impart_flags flags) + enum ast_bridge_impart_flags flags, + struct bridge_channel_impart_cond *cond) { int res = 0; struct ast_bridge_channel *bridge_channel; @@ -1701,27 +1851,20 @@ int ast_bridge_impart(struct ast_bridge *bridge, /* Actually create the thread that will handle the channel */ if (!res) { - struct bridge_channel_internal_cond cond = { - .done = 0, - .bridge_channel = bridge_channel - }; - ast_mutex_init(&cond.lock); - ast_cond_init(&cond.cond, NULL); - + res = bridge_channel_impart_add(chan, cond); + } + if (!res) { if ((flags & AST_BRIDGE_IMPART_CHAN_MASK) == AST_BRIDGE_IMPART_CHAN_INDEPENDENT) { res = ast_pthread_create_detached(&bridge_channel->thread, NULL, - bridge_channel_ind_thread, &cond); + bridge_channel_ind_thread, bridge_channel); } else { res = ast_pthread_create(&bridge_channel->thread, NULL, - bridge_channel_depart_thread, &cond); + bridge_channel_depart_thread, bridge_channel); } if (!res) { - bridge_channel_internal_wait(&cond); + bridge_channel_impart_wait(cond); } - - ast_cond_destroy(&cond.cond); - ast_mutex_destroy(&cond.lock); } if (res) { @@ -1742,6 +1885,32 @@ int ast_bridge_impart(struct ast_bridge *bridge, return 0; } +int ast_bridge_impart(struct ast_bridge *bridge, + struct ast_channel *chan, + struct ast_channel *swap, + struct ast_bridge_features *features, + enum ast_bridge_impart_flags flags) +{ + struct bridge_channel_impart_cond cond = { + .done = 0, + }; + int res; + + ast_mutex_init(&cond.lock); + ast_cond_init(&cond.cond, NULL); + + res = bridge_impart_internal(bridge, chan, swap, features, flags, &cond); + if (res) { + /* Impart failed. Signal any other waiting impart threads */ + bridge_channel_impart_signal(chan); + } + + ast_cond_destroy(&cond.cond); + ast_mutex_destroy(&cond.lock); + + return res; +} + int ast_bridge_depart(struct ast_channel *chan) { struct ast_bridge_channel *bridge_channel; @@ -2318,6 +2487,9 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan, if (chan_bridge) { struct ast_bridge_channel *bridge_channel; + /* The channel is in a bridge so it is not getting any new features. */ + ast_bridge_features_destroy(features); + ast_bridge_lock_both(bridge, chan_bridge); bridge_channel = bridge_find_channel(chan_bridge, chan); @@ -2340,9 +2512,6 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan, bridge_dissolve_check_stolen(chan_bridge, bridge_channel); ast_bridge_unlock(chan_bridge); ast_bridge_unlock(bridge); - - /* The channel was in a bridge so it is not getting any new features. */ - ast_bridge_features_destroy(features); } else { /* Slightly less easy case. We need to yank channel A from * where he currently is and impart him into our bridge. @@ -2350,6 +2519,7 @@ int ast_bridge_add_channel(struct ast_bridge *bridge, struct ast_channel *chan, yanked_chan = ast_channel_yank(chan); if (!yanked_chan) { ast_log(LOG_WARNING, "Could not gain control of channel %s\n", ast_channel_name(chan)); + ast_bridge_features_destroy(features); return -1; } if (ast_channel_state(yanked_chan) != AST_STATE_UP) { diff --git a/main/bridge_channel.c b/main/bridge_channel.c index c9262a84a..4baae3cc5 100644 --- a/main/bridge_channel.c +++ b/main/bridge_channel.c @@ -2117,13 +2117,14 @@ int bridge_channel_internal_push_full(struct ast_bridge_channel *bridge_channel, if (bridge->dissolved || bridge_channel->state != BRIDGE_CHANNEL_STATE_WAIT || (swap && swap->state != BRIDGE_CHANNEL_STATE_WAIT) - || bridge->v_table->push(bridge, bridge_channel, swap) - || ast_bridge_channel_establish_roles(bridge_channel)) { + || bridge->v_table->push(bridge, bridge_channel, swap)) { ast_debug(1, "Bridge %s: pushing %p(%s) into bridge failed\n", bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan)); return -1; } + ast_bridge_channel_establish_roles(bridge_channel); + if (swap) { int dissolve = ast_test_flag(&bridge->feature_flags, AST_BRIDGE_FLAG_DISSOLVE_EMPTY); @@ -2636,27 +2637,7 @@ static void bridge_channel_event_join_leave(struct ast_bridge_channel *bridge_ch ao2_iterator_destroy(&iter); } -void bridge_channel_internal_wait(struct bridge_channel_internal_cond *cond) -{ - ast_mutex_lock(&cond->lock); - while (!cond->done) { - ast_cond_wait(&cond->cond, &cond->lock); - } - ast_mutex_unlock(&cond->lock); -} - -void bridge_channel_internal_signal(struct bridge_channel_internal_cond *cond) -{ - if (cond) { - ast_mutex_lock(&cond->lock); - cond->done = 1; - ast_cond_signal(&cond->cond); - ast_mutex_unlock(&cond->lock); - } -} - -int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel, - struct bridge_channel_internal_cond *cond) +int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel) { int res = 0; struct ast_bridge_features *channel_features; @@ -2686,7 +2667,6 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel, bridge_channel->bridge->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan)); - bridge_channel_internal_signal(cond); return -1; } ast_channel_internal_bridge_set(bridge_channel->chan, bridge_channel->bridge); @@ -2721,8 +2701,6 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel, } bridge_reconfigured(bridge_channel->bridge, !bridge_channel->inhibit_colp); - bridge_channel_internal_signal(cond); - if (bridge_channel->state == BRIDGE_CHANNEL_STATE_WAIT) { /* * Indicate a source change since this channel is entering the @@ -2734,6 +2712,7 @@ int bridge_channel_internal_join(struct ast_bridge_channel *bridge_channel, ast_indicate(bridge_channel->chan, AST_CONTROL_SRCCHANGE); } + bridge_channel_impart_signal(bridge_channel->chan); ast_bridge_unlock(bridge_channel->bridge); /* Must release any swap ref after unlocking the bridge. */ diff --git a/main/config.c b/main/config.c index 2a6baa133..ffada0bf3 100644 --- a/main/config.c +++ b/main/config.c @@ -36,6 +36,9 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/paths.h" /* use ast_config_AST_CONFIG_DIR */ #include "asterisk/network.h" /* we do some sockaddr manipulation here */ + +#include <string.h> +#include <libgen.h> #include <time.h> #include <sys/stat.h> @@ -2512,6 +2515,25 @@ int ast_config_text_file_save(const char *configfile, const struct ast_config *c return ast_config_text_file_save2(configfile, cfg, generator, CONFIG_SAVE_FLAG_PRESERVE_EFFECTIVE_CONTEXT); } +static int is_writable(const char *fn) +{ + if (access(fn, F_OK)) { + char *dn = dirname(ast_strdupa(fn)); + + if (access(dn, R_OK | W_OK)) { + ast_log(LOG_ERROR, "Unable to write to directory %s (%s)\n", dn, strerror(errno)); + return 0; + } + } else { + if (access(fn, R_OK | W_OK)) { + ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno)); + return 0; + } + } + + return 1; +} + int ast_config_text_file_save2(const char *configfile, const struct ast_config *cfg, const char *generator, uint32_t flags) { FILE *f; @@ -2534,20 +2556,20 @@ int ast_config_text_file_save2(const char *configfile, const struct ast_config * for (incl = cfg->includes; incl; incl = incl->next) { /* reset all the output flags in case this isn't our first time saving this data */ incl->output = 0; - /* now make sure we have write access */ + if (!incl->exec) { + /* now make sure we have write access to the include file or its parent directory */ make_fn(fn, sizeof(fn), incl->included_file, configfile); - if (access(fn, R_OK | W_OK)) { - ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno)); + /* If the file itself doesn't exist, make sure we have write access to the directory */ + if (!is_writable(fn)) { return -1; } } } - /* now make sure we have write access to the main config file */ + /* now make sure we have write access to the main config file or its parent directory */ make_fn(fn, sizeof(fn), 0, configfile); - if (access(fn, R_OK | W_OK)) { - ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno)); + if (!is_writable(fn)) { return -1; } diff --git a/main/config_options.c b/main/config_options.c index db99a441d..c8988c984 100644 --- a/main/config_options.c +++ b/main/config_options.c @@ -198,8 +198,8 @@ static int link_option_to_types(struct aco_info *info, struct aco_type **types, #ifdef AST_DEVMODE opt->doc_unavailable = 1; #endif -#endif } +#endif } /* The container(s) should hold the only ref to opt */ ao2_ref(opt, -1); diff --git a/main/core_unreal.c b/main/core_unreal.c index e9b7a8d66..f2404dfca 100644 --- a/main/core_unreal.c +++ b/main/core_unreal.c @@ -808,7 +808,6 @@ int ast_unreal_channel_push_to_bridge(struct ast_channel *ast, struct ast_bridge /* Impart the semi2 channel into the bridge */ if (ast_bridge_impart(bridge, chan, NULL, features, AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) { - ast_bridge_features_destroy(features); ast_channel_unref(chan); return -1; } diff --git a/main/features.c b/main/features.c index 1810b1556..b96cbd68c 100644 --- a/main/features.c +++ b/main/features.c @@ -1104,7 +1104,6 @@ static int bridge_exec(struct ast_channel *chan, const char *data) xfer_cfg ? xfer_cfg->xfersound : NULL); ao2_cleanup(xfer_cfg); if (bridge_add_failed) { - ast_bridge_features_destroy(peer_features); ast_bridge_features_cleanup(&chan_features); ast_bridge_destroy(bridge, 0); goto done; diff --git a/main/file.c b/main/file.c index 7ce021340..1c51177c2 100644 --- a/main/file.c +++ b/main/file.c @@ -799,7 +799,7 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *fil /* As above, but for video. But here we don't have translators * so we must enforce a format. */ - struct ast_format_cap *tmp_cap; + struct ast_format_cap *nativeformats, *tmp_cap; char *buf; int buflen; int i, fd; @@ -810,16 +810,23 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *fil buflen = strlen(preflang) + strlen(filename) + 4; buf = ast_alloca(buflen); + ast_channel_lock(chan); + nativeformats = ao2_bump(ast_channel_nativeformats(chan)); + ast_channel_unlock(chan); + /* is the channel capable of video without translation ?*/ - if (!ast_format_cap_has_type(ast_channel_nativeformats(chan), AST_MEDIA_TYPE_VIDEO)) { + if (!ast_format_cap_has_type(nativeformats, AST_MEDIA_TYPE_VIDEO)) { + ao2_cleanup(nativeformats); return NULL; } if (!(tmp_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { + ao2_cleanup(nativeformats); return NULL; } /* Video is supported, so see what video formats exist for this file */ if (!fileexists_core(filename, NULL, preflang, buf, buflen, tmp_cap)) { ao2_ref(tmp_cap, -1); + ao2_cleanup(nativeformats); return NULL; } @@ -828,7 +835,7 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *fil struct ast_format *format = ast_format_cap_get_format(tmp_cap, i); if ((ast_format_get_type(format) != AST_MEDIA_TYPE_VIDEO) || - !ast_format_cap_iscompatible(ast_channel_nativeformats(chan), tmp_cap)) { + !ast_format_cap_iscompatible(nativeformats, tmp_cap)) { ao2_ref(format, -1); continue; } @@ -837,12 +844,14 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *fil if (fd >= 0) { ao2_ref(format, -1); ao2_ref(tmp_cap, -1); + ao2_cleanup(nativeformats); return ast_channel_vstream(chan); } ast_log(LOG_WARNING, "File %s has video but couldn't be opened\n", filename); ao2_ref(format, -1); } ao2_ref(tmp_cap, -1); + ao2_cleanup(nativeformats); return NULL; } @@ -1097,8 +1106,10 @@ int ast_streamfile(struct ast_channel *chan, const char *filename, const char *p fs = ast_openstream(chan, filename, preflang); if (!fs) { struct ast_str *codec_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN); + ast_channel_lock(chan); ast_log(LOG_WARNING, "Unable to open %s (format %s): %s\n", filename, ast_format_cap_get_names(ast_channel_nativeformats(chan), &codec_buf), strerror(errno)); + ast_channel_unlock(chan); return -1; } @@ -1133,7 +1144,12 @@ int ast_streamfile(struct ast_channel *chan, const char *filename, const char *p res = ast_playstream(fs); if (!res && vfs) res = ast_playstream(vfs); - ast_verb(3, "<%s> Playing '%s.%s' (language '%s')\n", ast_channel_name(chan), filename, ast_format_get_name(ast_channel_writeformat(chan)), preflang ? preflang : "default"); + + if (VERBOSITY_ATLEAST(3)) { + ast_channel_lock(chan); + ast_verb(3, "<%s> Playing '%s.%s' (language '%s')\n", ast_channel_name(chan), filename, ast_format_get_name(ast_channel_writeformat(chan)), preflang ? preflang : "default"); + ast_channel_unlock(chan); + } return res; } diff --git a/main/format_cap.c b/main/format_cap.c index 17ae18cd4..bf3bd1c4b 100644 --- a/main/format_cap.c +++ b/main/format_cap.c @@ -376,7 +376,7 @@ int ast_format_cap_update_by_allow_disallow(struct ast_format_cap *cap, const ch } - while ((this = strsep(&parse, ",|"))) { + while ((this = ast_strip(strsep(&parse, ",|")))) { int framems = 0; struct ast_format *format = NULL; diff --git a/main/lock.c b/main/lock.c index dd90d7bd9..03f1cd974 100644 --- a/main/lock.c +++ b/main/lock.c @@ -286,17 +286,19 @@ int __ast_pthread_mutex_lock(const char *filename, int lineno, const char *func, if (wait_time > reported_wait && (wait_time % 5) == 0) { __ast_mutex_logger("%s line %d (%s): Deadlock? waited %d sec for mutex '%s'?\n", filename, lineno, func, (int) wait_time, mutex_name); - ast_reentrancy_lock(lt); + if (lt) { + ast_reentrancy_lock(lt); #ifdef HAVE_BKTR - __dump_backtrace(<->backtrace[lt->reentrancy], canlog); + __dump_backtrace(<->backtrace[lt->reentrancy], canlog); #endif - __ast_mutex_logger("%s line %d (%s): '%s' was locked here.\n", - lt->file[ROFFSET], lt->lineno[ROFFSET], - lt->func[ROFFSET], mutex_name); + __ast_mutex_logger("%s line %d (%s): '%s' was locked here.\n", + lt->file[ROFFSET], lt->lineno[ROFFSET], + lt->func[ROFFSET], mutex_name); #ifdef HAVE_BKTR - __dump_backtrace(<->backtrace[ROFFSET], canlog); + __dump_backtrace(<->backtrace[ROFFSET], canlog); #endif - ast_reentrancy_unlock(lt); + ast_reentrancy_unlock(lt); + } reported_wait = wait_time; } usleep(200); diff --git a/main/manager.c b/main/manager.c index 7c2155015..ba261e8e9 100644 --- a/main/manager.c +++ b/main/manager.c @@ -1541,6 +1541,17 @@ static AST_RWLIST_HEAD_STATIC(manager_hooks, manager_custom_hook); /*! \brief A container of event documentation nodes */ static AO2_GLOBAL_OBJ_STATIC(event_docs); +static int __attribute__((format(printf, 9, 0))) __manager_event_sessions( + struct ao2_container *sessions, + int category, + const char *event, + int chancount, + struct ast_channel **chans, + const char *file, + int line, + const char *func, + const char *fmt, + ...); static enum add_filter_result manager_add_filter(const char *filter_pattern, struct ao2_container *whitefilters, struct ao2_container *blackfilters); static int match_filter(struct mansession *s, char *eventdata); @@ -1679,37 +1690,75 @@ struct ast_str *ast_manager_str_from_json_object(struct ast_json *blob, key_excl return res; } +#define manager_event_sessions(sessions, category, event, contents , ...) \ + __manager_event_sessions(sessions, category, event, 0, NULL, __FILE__, __LINE__, __PRETTY_FUNCTION__, contents , ## __VA_ARGS__) + +#define any_manager_listeners(sessions) \ + ((sessions && ao2_container_count(sessions)) || !AST_RWLIST_EMPTY(&manager_hooks)) + static void manager_default_msg_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { - RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup); + struct ao2_container *sessions; + struct ast_manager_event_blob *ev; - ev = stasis_message_to_ami(message); + if (!stasis_message_can_be_ami(message)) { + /* Not an AMI message; disregard */ + return; + } + + sessions = ao2_global_obj_ref(mgr_sessions); + if (!any_manager_listeners(sessions)) { + /* Nobody is listening */ + ao2_cleanup(sessions); + return; + } - if (ev == NULL) { - /* Not and AMI message; disregard */ + ev = stasis_message_to_ami(message); + if (!ev) { + /* Conversion failure */ + ao2_cleanup(sessions); return; } - manager_event(ev->event_flags, ev->manager_event, "%s", - ev->extra_fields); + manager_event_sessions(sessions, ev->event_flags, ev->manager_event, + "%s", ev->extra_fields); + ao2_ref(ev, -1); + ao2_cleanup(sessions); } static void manager_generic_msg_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) { - struct ast_json_payload *payload = stasis_message_data(message); - int class_type = ast_json_integer_get(ast_json_object_get(payload->json, "class_type")); - const char *type = ast_json_string_get(ast_json_object_get(payload->json, "type")); - struct ast_json *event = ast_json_object_get(payload->json, "event"); - RAII_VAR(struct ast_str *, event_buffer, NULL, ast_free); + struct ast_json_payload *payload; + int class_type; + const char *type; + struct ast_json *event; + struct ast_str *event_buffer; + struct ao2_container *sessions; + + sessions = ao2_global_obj_ref(mgr_sessions); + if (!any_manager_listeners(sessions)) { + /* Nobody is listening */ + ao2_cleanup(sessions); + return; + } + + payload = stasis_message_data(message); + class_type = ast_json_integer_get(ast_json_object_get(payload->json, "class_type")); + type = ast_json_string_get(ast_json_object_get(payload->json, "type")); + event = ast_json_object_get(payload->json, "event"); event_buffer = ast_manager_str_from_json_object(event, NULL); if (!event_buffer) { ast_log(AST_LOG_WARNING, "Error while creating payload for event %s\n", type); + ao2_cleanup(sessions); return; } - manager_event(class_type, type, "%s", ast_str_buffer(event_buffer)); + manager_event_sessions(sessions, class_type, type, + "%s", ast_str_buffer(event_buffer)); + ast_free(event_buffer); + ao2_cleanup(sessions); } void ast_manager_publish_event(const char *type, int class_type, struct ast_json *obj) @@ -4698,7 +4747,7 @@ static int action_blind_transfer(struct mansession *s, const struct message *m) const char *name = astman_get_header(m, "Channel"); const char *exten = astman_get_header(m, "Exten"); const char *context = astman_get_header(m, "Context"); - RAII_VAR(struct ast_channel *, chan, NULL, ao2_cleanup); + struct ast_channel *chan; if (ast_strlen_zero(name)) { astman_send_error(s, m, "No channel specified"); @@ -4735,6 +4784,7 @@ static int action_blind_transfer(struct mansession *s, const struct message *m) break; } + ast_channel_unref(chan); return 0; } @@ -5907,7 +5957,7 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m const char *actionid = astman_get_header(m, "ActionID"); char idText[256]; int numchans = 0; - RAII_VAR(struct ao2_container *, channels, NULL, ao2_cleanup); + struct ao2_container *channels; struct ao2_iterator it_chans; struct stasis_message *msg; @@ -5917,7 +5967,8 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m idText[0] = '\0'; } - if (!(channels = stasis_cache_dump(ast_channel_cache_by_name(), ast_channel_snapshot_type()))) { + channels = stasis_cache_dump(ast_channel_cache_by_name(), ast_channel_snapshot_type()); + if (!channels) { astman_send_error(s, m, "Could not get cached channels"); return 0; } @@ -5969,6 +6020,7 @@ static int action_coreshowchannels(struct mansession *s, const struct message *m astman_send_list_complete_start(s, m, "CoreShowChannelsComplete", numchans); astman_send_list_complete_end(s); + ao2_ref(channels, -1); return 0; } @@ -6597,11 +6649,10 @@ static int append_event(const char *str, int category) static void append_channel_vars(struct ast_str **pbuf, struct ast_channel *chan) { - RAII_VAR(struct varshead *, vars, NULL, ao2_cleanup); + struct varshead *vars; struct ast_var_t *var; vars = ast_channel_get_manager_vars(chan); - if (!vars) { return; } @@ -6609,62 +6660,67 @@ static void append_channel_vars(struct ast_str **pbuf, struct ast_channel *chan) AST_LIST_TRAVERSE(vars, var, entries) { ast_str_append(pbuf, 0, "ChanVariable(%s): %s=%s\r\n", ast_channel_name(chan), var->name, var->value); } + ao2_ref(vars, -1); } /* XXX see if can be moved inside the function */ AST_THREADSTORAGE(manager_event_buf); #define MANAGER_EVENT_BUF_INITSIZE 256 -int __ast_manager_event_multichan(int category, const char *event, int chancount, - struct ast_channel **chans, const char *file, int line, const char *func, - const char *fmt, ...) +static int __attribute__((format(printf, 9, 0))) __manager_event_sessions_va( + struct ao2_container *sessions, + int category, + const char *event, + int chancount, + struct ast_channel **chans, + const char *file, + int line, + const char *func, + const char *fmt, + va_list ap) { - RAII_VAR(struct ao2_container *, sessions, ao2_global_obj_ref(mgr_sessions), ao2_cleanup); - struct mansession_session *session; - struct manager_custom_hook *hook; struct ast_str *auth = ast_str_alloca(MAX_AUTH_PERM_STRING); const char *cat_str; - va_list ap; struct timeval now; struct ast_str *buf; int i; - if (!(sessions && ao2_container_count(sessions)) && AST_RWLIST_EMPTY(&manager_hooks)) { - return 0; - } - - if (!(buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE))) { + buf = ast_str_thread_get(&manager_event_buf, MANAGER_EVENT_BUF_INITSIZE); + if (!buf) { return -1; } cat_str = authority_to_str(category, &auth); ast_str_set(&buf, 0, - "Event: %s\r\nPrivilege: %s\r\n", - event, cat_str); + "Event: %s\r\n" + "Privilege: %s\r\n", + event, cat_str); if (timestampevents) { now = ast_tvnow(); ast_str_append(&buf, 0, - "Timestamp: %ld.%06lu\r\n", - (long)now.tv_sec, (unsigned long) now.tv_usec); + "Timestamp: %ld.%06lu\r\n", + (long)now.tv_sec, (unsigned long) now.tv_usec); } if (manager_debug) { static int seq; + ast_str_append(&buf, 0, - "SequenceNumber: %d\r\n", - ast_atomic_fetchadd_int(&seq, 1)); + "SequenceNumber: %d\r\n", + ast_atomic_fetchadd_int(&seq, 1)); ast_str_append(&buf, 0, - "File: %s\r\nLine: %d\r\nFunc: %s\r\n", file, line, func); + "File: %s\r\n" + "Line: %d\r\n" + "Func: %s\r\n", + file, line, func); } if (!ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) { ast_str_append(&buf, 0, - "SystemName: %s\r\n", - ast_config_AST_SYSTEM_NAME); + "SystemName: %s\r\n", + ast_config_AST_SYSTEM_NAME); } - va_start(ap, fmt); ast_str_append_va(&buf, 0, fmt, ap); - va_end(ap); for (i = 0; i < chancount; i++) { append_channel_vars(&buf, chans[i]); } @@ -6675,9 +6731,11 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount /* Wake up any sleeping sessions */ if (sessions) { - struct ao2_iterator i; - i = ao2_iterator_init(sessions, 0); - while ((session = ao2_iterator_next(&i))) { + struct ao2_iterator iter; + struct mansession_session *session; + + iter = ao2_iterator_init(sessions, 0); + while ((session = ao2_iterator_next(&iter))) { ao2_lock(session); if (session->waiting_thread != AST_PTHREADT_NULL) { pthread_kill(session->waiting_thread, SIGURG); @@ -6692,10 +6750,12 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount ao2_unlock(session); unref_mansession(session); } - ao2_iterator_destroy(&i); + ao2_iterator_destroy(&iter); } if (category != EVENT_FLAG_SHUTDOWN && !AST_RWLIST_EMPTY(&manager_hooks)) { + struct manager_custom_hook *hook; + AST_RWLIST_RDLOCK(&manager_hooks); AST_RWLIST_TRAVERSE(&manager_hooks, hook, list) { hook->helper(category, event, ast_str_buffer(buf)); @@ -6706,6 +6766,50 @@ int __ast_manager_event_multichan(int category, const char *event, int chancount return 0; } +static int __attribute__((format(printf, 9, 0))) __manager_event_sessions( + struct ao2_container *sessions, + int category, + const char *event, + int chancount, + struct ast_channel **chans, + const char *file, + int line, + const char *func, + const char *fmt, + ...) +{ + va_list ap; + int res; + + va_start(ap, fmt); + res = __manager_event_sessions_va(sessions, category, event, chancount, chans, + file, line, func, fmt, ap); + va_end(ap); + return res; +} + +int __ast_manager_event_multichan(int category, const char *event, int chancount, + struct ast_channel **chans, const char *file, int line, const char *func, + const char *fmt, ...) +{ + struct ao2_container *sessions = ao2_global_obj_ref(mgr_sessions); + va_list ap; + int res; + + if (!any_manager_listeners(sessions)) { + /* Nobody is listening */ + ao2_cleanup(sessions); + return 0; + } + + va_start(ap, fmt); + res = __manager_event_sessions_va(sessions, category, event, chancount, chans, + file, line, func, fmt, ap); + va_end(ap); + ao2_cleanup(sessions); + return res; +} + /*! \brief * support functions to register/unregister AMI action handlers, */ @@ -9184,6 +9288,7 @@ int ast_str_append_event_header(struct ast_str **fields_string, static void manager_event_blob_dtor(void *obj) { struct ast_manager_event_blob *ev = obj; + ast_string_field_free_memory(ev); } @@ -9195,18 +9300,19 @@ ast_manager_event_blob_create( const char *extra_fields_fmt, ...) { - RAII_VAR(struct ast_manager_event_blob *, ev, NULL, ao2_cleanup); + struct ast_manager_event_blob *ev; va_list argp; ast_assert(extra_fields_fmt != NULL); ast_assert(manager_event != NULL); - ev = ao2_alloc(sizeof(*ev), manager_event_blob_dtor); + ev = ao2_alloc_options(sizeof(*ev), manager_event_blob_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); if (!ev) { return NULL; } if (ast_string_field_init(ev, 20)) { + ao2_ref(ev, -1); return NULL; } @@ -9214,10 +9320,8 @@ ast_manager_event_blob_create( 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); + ast_string_field_ptr_build_va(ev, &ev->extra_fields, extra_fields_fmt, argp); va_end(argp); - ao2_ref(ev, +1); return ev; } diff --git a/main/manager_channels.c b/main/manager_channels.c index adef639e8..ef71c65b1 100644 --- a/main/manager_channels.c +++ b/main/manager_channels.c @@ -697,28 +697,33 @@ static void channel_hangup_request_cb(void *data, struct stasis_message *message) { struct ast_channel_blob *obj = stasis_message_data(message); - RAII_VAR(struct ast_str *, extra, NULL, ast_free); - RAII_VAR(struct ast_str *, channel_event_string, NULL, ast_free); + struct ast_str *extra; + struct ast_str *channel_event_string; struct ast_json *cause; int is_soft; char *manager_event = "HangupRequest"; + if (!obj->snapshot) { + /* No snapshot? Likely an earlier allocation failure creating it. */ + return; + } + extra = ast_str_create(20); if (!extra) { return; } channel_event_string = ast_manager_build_channel_state_string(obj->snapshot); - if (!channel_event_string) { + ast_free(extra); 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)); + "Cause: %jd\r\n", + ast_json_integer_get(cause)); } is_soft = ast_json_is_true(ast_json_object_get(obj->blob, "soft")); @@ -727,9 +732,12 @@ static void channel_hangup_request_cb(void *data, } manager_event(EVENT_FLAG_CALL, manager_event, - "%s%s", - ast_str_buffer(channel_event_string), - ast_str_buffer(extra)); + "%s%s", + ast_str_buffer(channel_event_string), + ast_str_buffer(extra)); + + ast_free(channel_event_string); + ast_free(extra); } static void channel_chanspy_stop_cb(void *data, struct stasis_subscription *sub, diff --git a/main/stasis_endpoints.c b/main/stasis_endpoints.c index fbca2ad9b..a91cd9f6b 100644 --- a/main/stasis_endpoints.c +++ b/main/stasis_endpoints.c @@ -86,6 +86,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <enum name="Reachable"/> <enum name="Created"/> <enum name="Removed"/> + <enum name="Updated"/> </enumlist> </parameter> <parameter name="AOR"> @@ -97,6 +98,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <parameter name="RoundtripUsec"> <para>The RTT measured during the last qualify.</para> </parameter> + <parameter name="UserAgent"> + <para>Content of the User-Agent header in REGISTER request</para> + </parameter> + <parameter name="RegExpire"> + <para>Absolute time that this contact is no longer valid after</para> + </parameter> </syntax> </managerEventInstance> </managerEvent> diff --git a/main/stasis_message.c b/main/stasis_message.c index c797cdfa0..99721ef3c 100644 --- a/main/stasis_message.c +++ b/main/stasis_message.c @@ -170,17 +170,17 @@ const struct timeval *stasis_message_timestamp(const struct stasis_message *msg) return &msg->timestamp; } -#define INVOKE_VIRTUAL(fn, ...) \ - ({ \ - if (msg == NULL) { \ - return NULL; \ - } \ - ast_assert(msg->type != NULL); \ +#define INVOKE_VIRTUAL(fn, ...) \ + ({ \ + if (!msg) { \ + return NULL; \ + } \ + ast_assert(msg->type != NULL); \ ast_assert(msg->type->vtable != NULL); \ - if (msg->type->vtable->fn == NULL) { \ - return NULL; \ - } \ - msg->type->vtable->fn(__VA_ARGS__); \ + if (!msg->type->vtable->fn) { \ + return NULL; \ + } \ + msg->type->vtable->fn(__VA_ARGS__); \ }) struct ast_manager_event_blob *stasis_message_to_ami(struct stasis_message *msg) @@ -199,3 +199,18 @@ struct ast_event *stasis_message_to_event(struct stasis_message *msg) { return INVOKE_VIRTUAL(to_event, msg); } + +#define HAS_VIRTUAL(fn, msg) \ + ({ \ + if (!msg) { \ + return 0; \ + } \ + ast_assert(msg->type != NULL); \ + ast_assert(msg->type->vtable != NULL); \ + !!msg->type->vtable->fn; \ + }) + +int stasis_message_can_be_ami(struct stasis_message *msg) +{ + return HAS_VIRTUAL(to_ami, msg); +} diff --git a/makeopts.in b/makeopts.in index bfc965de4..9fa49bc04 100644 --- a/makeopts.in +++ b/makeopts.in @@ -243,6 +243,9 @@ PORTAUDIO_LIB=@PORTAUDIO_LIB@ PRI_INCLUDE=@PRI_INCLUDE@ PRI_LIB=@PRI_LIB@ +PYTHONDEV_INCLUDE=@PYTHONDEV_INCLUDE@ +PYTHONDEV_LIB=@PYTHONDEV_LIB@ + RESAMPLE_INCLUDE=@RESAMPLE_INCLUDE@ RESAMPLE_LIB=@RESAMPLE_LIB@ diff --git a/res/res_agi.c b/res/res_agi.c index ff3358062..e3839dd6d 100644 --- a/res/res_agi.c +++ b/res/res_agi.c @@ -3736,6 +3736,24 @@ static enum agi_result agi_handle_command(struct ast_channel *chan, AGI *agi, ch return AGI_RESULT_SUCCESS; } + +AST_LIST_HEAD_NOLOCK(deferred_frames, ast_frame); + +static void queue_deferred_frames(struct deferred_frames *deferred_frames, + struct ast_channel *chan) +{ + struct ast_frame *f; + + if (!AST_LIST_EMPTY(deferred_frames)) { + ast_channel_lock(chan); + while ((f = AST_LIST_REMOVE_HEAD(deferred_frames, frame_list))) { + ast_queue_frame_head(chan, f); + ast_frfree(f); + } + ast_channel_unlock(chan); + } +} + static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi, int pid, int *status, int dead, int argc, char *argv[]) { struct ast_channel *c; @@ -3754,6 +3772,9 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi const char *sighup_str; const char *exit_on_hangup_str; int exit_on_hangup; + struct deferred_frames deferred_frames; + + AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames); ast_channel_lock(chan); sighup_str = pbx_builtin_getvar_helper(chan, "AGISIGHUP"); @@ -3815,8 +3836,20 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi /* Write, ignoring errors */ if (write(agi->audio, f->data.ptr, f->datalen) < 0) { } + ast_frfree(f); + } else if (ast_is_deferrable_frame(f)) { + struct ast_frame *dup_f; + + if ((dup_f = ast_frisolate(f))) { + AST_LIST_INSERT_HEAD(&deferred_frames, dup_f, frame_list); + } + + if (dup_f != f) { + ast_frfree(f); + } + } else { + ast_frfree(f); } - ast_frfree(f); } } else if (outfd > -1) { size_t len = sizeof(buf); @@ -3864,6 +3897,8 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi buf[buflen - 1] = '\0'; } + queue_deferred_frames(&deferred_frames, chan); + if (agidebug) ast_verbose("<%s>AGI Rx << %s\n", ast_channel_name(chan), buf); cmd_status = agi_handle_command(chan, agi, buf, dead); @@ -3885,6 +3920,9 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi } } } + + queue_deferred_frames(&deferred_frames, chan); + if (agi->speech) { ast_speech_destroy(agi->speech); } diff --git a/res/res_fax.c b/res/res_fax.c index ef0e27696..6282b13d7 100644 --- a/res/res_fax.c +++ b/res/res_fax.c @@ -626,6 +626,8 @@ static const struct ast_datastore_info fax_datastore = { static int fax_gateway_attach(struct ast_channel *chan, struct ast_fax_session_details *details); static int fax_detect_attach(struct ast_channel *chan, int timeout, int flags); static struct ast_fax_session_details *find_or_create_details(struct ast_channel *chan); +static struct ast_fax_session *fax_v21_session_new (struct ast_channel *chan); + /*! \brief Copies fax detection and gateway framehooks during masquerades * @@ -1447,6 +1449,12 @@ static void set_channel_variables(struct ast_channel *chan, struct ast_fax_sessi pbx_builtin_setvar_helper(chan, "FAXBITRATE", S_OR(details->transfer_rate, NULL)); pbx_builtin_setvar_helper(chan, "FAXRESOLUTION", S_OR(details->resolution, NULL)); + if (ast_channel_get_t38_state(chan) == T38_STATE_NEGOTIATED) { + pbx_builtin_setvar_helper(chan, "FAXMODE", "T38"); + } else { + pbx_builtin_setvar_helper(chan, "FAXMODE", "audio"); + } + snprintf(buf, sizeof(buf), "%u", details->pages_transferred); pbx_builtin_setvar_helper(chan, "FAXPAGES", buf); } @@ -2071,6 +2079,7 @@ static int receivefax_exec(struct ast_channel *chan, const char *data) pbx_builtin_setvar_helper(chan, "FAXPAGES", "0"); pbx_builtin_setvar_helper(chan, "FAXBITRATE", NULL); pbx_builtin_setvar_helper(chan, "FAXRESOLUTION", NULL); + pbx_builtin_setvar_helper(chan, "FAXMODE", NULL); /* Get a FAX session details structure from the channel's FAX datastore and create one if * it does not already exist. */ @@ -2578,6 +2587,7 @@ static int sendfax_exec(struct ast_channel *chan, const char *data) pbx_builtin_setvar_helper(chan, "FAXPAGES", "0"); pbx_builtin_setvar_helper(chan, "FAXBITRATE", NULL); pbx_builtin_setvar_helper(chan, "FAXRESOLUTION", NULL); + pbx_builtin_setvar_helper(chan, "FAXMODE", NULL); /* Get a requirement structure and set it. This structure is used * to tell the FAX technology module about the higher level FAX session */ @@ -2827,6 +2837,23 @@ static void destroy_gateway(void *data) ao2_cleanup(gateway->peer_write_format); } +static struct ast_fax_session *fax_v21_session_new (struct ast_channel *chan) { + struct ast_fax_session_details *v21_details; + struct ast_fax_session *v21_session; + + if (!chan || !(v21_details = session_details_new())) { + return NULL; + } + + v21_details->caps = AST_FAX_TECH_V21_DETECT; + if (!(v21_session = fax_session_new(v21_details, chan, NULL, NULL))) { + ao2_ref(v21_details, -1); + return NULL; + } + + return v21_session; +} + /*! \brief Create a new fax gateway object. * \param chan the channel the gateway object will be attached to * \param details the fax session details @@ -2835,29 +2862,15 @@ static void destroy_gateway(void *data) static struct fax_gateway *fax_gateway_new(struct ast_channel *chan, struct ast_fax_session_details *details) { struct fax_gateway *gateway = ao2_alloc(sizeof(*gateway), destroy_gateway); - struct ast_fax_session_details *v21_details; if (!gateway) { return NULL; } - if (!(v21_details = session_details_new())) { - ao2_ref(gateway, -1); - return NULL; - } - - v21_details->caps = AST_FAX_TECH_V21_DETECT; - if (!(gateway->chan_v21_session = fax_session_new(v21_details, chan, NULL, NULL))) { - ao2_ref(v21_details, -1); - ao2_ref(gateway, -1); - return NULL; - } - - if (!(gateway->peer_v21_session = fax_session_new(v21_details, chan, NULL, NULL))) { - ao2_ref(v21_details, -1); + if (!(gateway->chan_v21_session = fax_v21_session_new(chan))) { + ast_log(LOG_ERROR, "Can't create V21 session on chan %s for T.38 gateway session\n", ast_channel_name(chan)); ao2_ref(gateway, -1); return NULL; } - ao2_ref(v21_details, -1); gateway->framehook = -1; @@ -3352,6 +3365,11 @@ static struct ast_frame *fax_gateway_framehook(struct ast_channel *chan, struct ast_channel_unlock(peer); gateway->bridged = 1; + if (!(gateway->peer_v21_session = fax_v21_session_new(peer))) { + ast_log(LOG_ERROR, "Can't create V21 session on chan %s for T.38 gateway session\n", ast_channel_name(peer)); + ast_framehook_detach(chan, gateway->framehook); + return f; + } } if (gateway->bridged && !ast_tvzero(gateway->timeout_start)) { @@ -3478,6 +3496,10 @@ static int fax_gateway_attach(struct ast_channel *chan, struct ast_fax_session_d .disable_inheritance = 1, /* Masquerade inheritance is handled through the datastore fixup */ }; + if (global_fax_debug) { + details->option.debug = AST_FAX_OPTFLAG_TRUE; + } + ast_string_field_set(details, result, "SUCCESS"); ast_string_field_set(details, resultstr, "gateway operation started successfully"); ast_string_field_set(details, error, "NO_ERROR"); diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 0437a652e..61d26302d 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -252,18 +252,35 @@ <configOption name="identify_by" default="username,location"> <synopsis>Way(s) for Endpoint to be identified</synopsis> <description><para> - An endpoint can be identified in multiple ways. Currently, the only supported - option is <literal>username</literal>, which matches the endpoint based on the - username in the From header. + Endpoints and aors can be identified in multiple ways. Currently, the supported + options are <literal>username</literal>, which matches the endpoint or aor id based on + the username and domain in the From header (or To header for aors), and + <literal>auth_username</literal>, which matches the endpoint or aor id based on the + username and realm in the Authentication header. In all cases, if an exact match + on both username and domain/realm fails, the match will be retried with just the username. </para> + <note><para> + Identification by auth_username has some security considerations because an + Authentication header is not present on the first message of a dialog when + digest authentication is used. The client can't generate it until the server + sends the challenge in a 401 response. Since Asterisk normally sends a security + event when an incoming request can't be matched to an endpoint, using auth_username + requires that the security event be deferred until a request is received with + the Authentication header and only generated if the username doesn't result in a + match. This may result in a delay before an attack is recognized. You can control + how many unmatched requests are received from a single ip address before a security + event is generated using the unidentified_request parameters in the "global" + configuration object. + </para></note> <note><para>Endpoints can also be identified by IP address; however, that method of identification is not handled by this configuration option. See the documentation for the <literal>identify</literal> configuration section for more details on that - method of endpoint identification. If this option is set to <literal>username</literal> - and an <literal>identify</literal> configuration section exists for the endpoint, then - the endpoint can be identified in multiple ways.</para></note> + method of endpoint identification. If this option is set and an <literal>identify</literal> + configuration section exists for the endpoint, then the endpoint can be identified in + multiple ways.</para></note> <enumlist> <enum name="username" /> + <enum name="auth_username" /> </enumlist> </description> </configOption> @@ -1099,6 +1116,12 @@ REGISTER requests and is not intended to be configured manually. </para></description> </configOption> + <configOption name="reg_server"> + <synopsis>Asterisk Server name</synopsis> + <description><para> + Asterisk Server name on which SIP endpoint registered. + </para></description> + </configOption> </configObject> <configObject name="aor"> <synopsis>The configuration for a location of an endpoint</synopsis> @@ -1298,10 +1321,34 @@ <configOption name="contact_expiration_check_interval" default="30"> <synopsis>The interval (in seconds) to check for expired contacts.</synopsis> </configOption> + <configOption name="disable_multi_domain" default="no"> + <synopsis>Disable Multi Domain support</synopsis> + <description><para> + If disabled it can improve realtime performace by reducing number of database requsts. + </para></description> + </configOption> <configOption name="max_initial_qualify_time" default="0"> <synopsis>The maximum amount of time from startup that qualifies should be attempted on all contacts. If greater than the qualify_frequency for an aor, qualify_frequency will be used instead.</synopsis> </configOption> + <configOption name="unidentified_request_period" default="5"> + <synopsis>The number of seconds over which to accumulate unidentified requests.</synopsis> + <description><para> + If <literal>unidentified_request_count</literal> unidentified requests are received + during <literal>unidentified_request_period</literal>, a security event will be generated. + </para></description> + </configOption> + <configOption name="unidentified_request_count" default="5"> + <synopsis>The number of unidentified requests from a single IP to allow.</synopsis> + <description><para> + If <literal>unidentified_request_count</literal> unidentified requests are received + during <literal>unidentified_request_period</literal>, a security event will be generated. + </para></description> + </configOption> + <configOption name="unidentified_request_prune_interval" default="30"> + <synopsis>The interval at which unidentified requests are older than + twice the unidentified_request_period are pruned.</synopsis> + </configOption> <configOption name="type"> <synopsis>Must be of type 'global'.</synopsis> </configOption> @@ -1325,13 +1372,35 @@ <configOption name="endpoint_identifier_order" default="ip,username,anonymous"> <synopsis>The order by which endpoint identifiers are processed and checked. Identifier names are usually derived from and can be found in the endpoint - identifier module itself (res_pjsip_endpoint_identifier_*)</synopsis> + identifier module itself (res_pjsip_endpoint_identifier_*). + You can use the CLI command "pjsip show identifiers" to see the + identifiers currently available.</synopsis> + <description> + <note><para> + One of the identifiers is "auth_username" which matches on the username in + an Authentication header. This method has some security considerations because an + Authentication header is not present on the first message of a dialog when + digest authentication is used. The client can't generate it until the server + sends the challenge in a 401 response. Since Asterisk normally sends a security + event when an incoming request can't be matched to an endpoint, using auth_username + requires that the security event be deferred until a request is received with + the Authentication header and only generated if the username doesn't result in a + match. This may result in a delay before an attack is recognized. You can control + how many unmatched requests are received from a single ip address before a security + event is generated using the unidentified_request parameters. + </para></note> + </description> </configOption> <configOption name="default_from_user" default="asterisk"> <synopsis>When Asterisk generates an outgoing SIP request, the From header username will be set to this value if there is no better option (such as CallerID) to be used.</synopsis> </configOption> + <configOption name="default_realm" default="asterisk"> + <synopsis>When Asterisk generates an challenge, the digest will be + set to this value if there is no better option (such as auth/realm) to be + used.</synopsis> + </configOption> </configObject> </configFile> </configInfo> @@ -3609,11 +3678,7 @@ int ast_sip_push_task(struct ast_taskprocessor *serializer, int (*sip_task)(void serializer = serializer_pool[pos]; } - if (serializer) { - return ast_taskprocessor_push(serializer, sip_task, task_data); - } else { - return ast_threadpool_push(sip_threadpool, sip_task, task_data); - } + return ast_taskprocessor_push(serializer, sip_task, task_data); } struct sync_task_data { @@ -4132,6 +4197,11 @@ static int load_module(void) goto error; } + if (ast_sip_initialize_scheduler()) { + ast_log(LOG_ERROR, "Failed to start scheduler. Aborting load\n"); + goto error; + } + /* Now load all the pjproject infrastructure. */ if (load_pjsip()) { goto error; @@ -4172,8 +4242,10 @@ static int load_module(void) return AST_MODULE_LOAD_SUCCESS; error: - /* These functions all check for NULLs and are safe to call at any time */ unload_pjsip(NULL); + + /* These functions all check for NULLs and are safe to call at any time */ + ast_sip_destroy_scheduler(); serializer_pool_shutdown(); ast_threadpool_shutdown(sip_threadpool); @@ -4204,7 +4276,7 @@ static int unload_module(void) * so we have to push the work to the threadpool to handle */ ast_sip_push_task_synchronous(NULL, unload_pjsip, NULL); - + ast_sip_destroy_scheduler(); serializer_pool_shutdown(); ast_threadpool_shutdown(sip_threadpool); diff --git a/res/res_pjsip/config_global.c b/res/res_pjsip/config_global.c index 8348a1eb5..6bb688804 100644 --- a/res/res_pjsip/config_global.c +++ b/res/res_pjsip/config_global.c @@ -35,9 +35,14 @@ #define DEFAULT_ENDPOINT_IDENTIFIER_ORDER "ip,username,anonymous" #define DEFAULT_MAX_INITIAL_QUALIFY_TIME 0 #define DEFAULT_FROM_USER "asterisk" +#define DEFAULT_REALM "asterisk" #define DEFAULT_REGCONTEXT "" #define DEFAULT_CONTACT_EXPIRATION_CHECK_INTERVAL 30 +#define DEFAULT_DISABLE_MULTI_DOMAIN 0 #define DEFAULT_VOICEMAIL_EXTENSION "" +#define DEFAULT_UNIDENTIFIED_REQUEST_COUNT 5 +#define DEFAULT_UNIDENTIFIED_REQUEST_PERIOD 5 +#define DEFAULT_UNIDENTIFIED_REQUEST_PRUNE_INTERVAL 30 static char default_useragent[256]; @@ -55,6 +60,8 @@ struct global_config { AST_STRING_FIELD(default_from_user); /*! Default voicemail extension */ AST_STRING_FIELD(default_voicemail_extension); + /*! Realm to use in challenges before an endpoint is identified */ + AST_STRING_FIELD(default_realm); ); /* Value to put in Max-Forwards header */ unsigned int max_forwards; @@ -64,6 +71,14 @@ struct global_config { unsigned int max_initial_qualify_time; /* The interval at which to check for expired contacts */ unsigned int contact_expiration_check_interval; + /*! Nonzero to disable multi domain support */ + unsigned int disable_multi_domain; + /* The maximum number of unidentified requests per source IP address before a security event is logged */ + unsigned int unidentified_request_count; + /* The period during which unidentified requests are accumulated */ + unsigned int unidentified_request_period; + /* Interval at which expired unidentifed requests will be pruned */ + unsigned int unidentified_request_prune_interval; }; static void global_destructor(void *obj) @@ -222,6 +237,21 @@ unsigned int ast_sip_get_contact_expiration_check_interval(void) return interval; } +unsigned int ast_sip_get_disable_multi_domain(void) +{ + unsigned int disable_multi_domain; + struct global_config *cfg; + + cfg = get_global_cfg(); + if (!cfg) { + return DEFAULT_DISABLE_MULTI_DOMAIN; + } + + disable_multi_domain = cfg->disable_multi_domain; + ao2_ref(cfg, -1); + return disable_multi_domain; +} + unsigned int ast_sip_get_max_initial_qualify_time(void) { unsigned int time; @@ -237,6 +267,40 @@ unsigned int ast_sip_get_max_initial_qualify_time(void) return time; } +void ast_sip_get_unidentified_request_thresholds(unsigned int *count, unsigned int *period, + unsigned int *prune_interval) +{ + struct global_config *cfg; + + cfg = get_global_cfg(); + if (!cfg) { + *count = DEFAULT_UNIDENTIFIED_REQUEST_COUNT; + *period = DEFAULT_UNIDENTIFIED_REQUEST_PERIOD; + *prune_interval = DEFAULT_UNIDENTIFIED_REQUEST_PRUNE_INTERVAL; + return; + } + + *count = cfg->unidentified_request_count; + *period = cfg->unidentified_request_period; + *prune_interval = cfg->unidentified_request_prune_interval; + + ao2_ref(cfg, -1); + return; +} + +void ast_sip_get_default_realm(char *realm, size_t size) +{ + struct global_config *cfg; + + cfg = get_global_cfg(); + if (!cfg) { + ast_copy_string(realm, DEFAULT_REALM, size); + } else { + ast_copy_string(realm, cfg->default_realm, size); + ao2_ref(cfg, -1); + } +} + void ast_sip_get_default_from_user(char *from_user, size_t size) { struct global_config *cfg; @@ -373,6 +437,19 @@ int ast_sip_initialize_sorcery_global(void) ast_sorcery_object_field_register(sorcery, "global", "contact_expiration_check_interval", __stringify(DEFAULT_CONTACT_EXPIRATION_CHECK_INTERVAL), OPT_UINT_T, 0, FLDSET(struct global_config, contact_expiration_check_interval)); + ast_sorcery_object_field_register(sorcery, "global", "disable_multi_domain", "no", + OPT_BOOL_T, 1, FLDSET(struct global_config, disable_multi_domain)); + ast_sorcery_object_field_register(sorcery, "global", "unidentified_request_count", + __stringify(DEFAULT_UNIDENTIFIED_REQUEST_COUNT), + OPT_UINT_T, 0, FLDSET(struct global_config, unidentified_request_count)); + ast_sorcery_object_field_register(sorcery, "global", "unidentified_request_period", + __stringify(DEFAULT_UNIDENTIFIED_REQUEST_PERIOD), + OPT_UINT_T, 0, FLDSET(struct global_config, unidentified_request_period)); + ast_sorcery_object_field_register(sorcery, "global", "unidentified_request_prune_interval", + __stringify(DEFAULT_UNIDENTIFIED_REQUEST_PRUNE_INTERVAL), + OPT_UINT_T, 0, FLDSET(struct global_config, unidentified_request_prune_interval)); + ast_sorcery_object_field_register(sorcery, "global", "default_realm", DEFAULT_REALM, + OPT_STRINGFIELD_T, 0, STRFLDSET(struct global_config, default_realm)); if (ast_sorcery_instance_observer_add(sorcery, &observer_callbacks_global)) { return -1; diff --git a/res/res_pjsip/include/res_pjsip_private.h b/res/res_pjsip/include/res_pjsip_private.h index 72a4387f1..04cd85408 100644 --- a/res/res_pjsip/include/res_pjsip_private.h +++ b/res/res_pjsip/include/res_pjsip_private.h @@ -325,4 +325,23 @@ struct ast_sip_contact_status *ast_res_pjsip_find_or_create_contact_status(const */ int ast_sip_validate_uri_length(const char *uri); +/*! + * \brief Initialize scheduler + * \since 13.9.0 + * + * \retval -1 failure + * \retval 0 success + */ +int ast_sip_initialize_scheduler(void); + +/*! + * \internal + * \brief Destroy scheduler + * \since 13.9.0 + * + * \retval -1 failure + * \retval 0 success + */ +int ast_sip_destroy_scheduler(void); + #endif /* RES_PJSIP_PRIVATE_H_ */ diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c index c30acc7b8..ef06456a7 100644 --- a/res/res_pjsip/location.c +++ b/res/res_pjsip/location.c @@ -23,6 +23,7 @@ #include "asterisk/res_pjsip.h" #include "asterisk/logger.h" #include "asterisk/astobj2.h" +#include "asterisk/paths.h" #include "asterisk/sorcery.h" #include "include/res_pjsip_private.h" #include "asterisk/res_pjsip_cli.h" @@ -120,6 +121,8 @@ static void *contact_alloc(const char *name) return NULL; } + ast_string_field_init_extended(contact, reg_server); + /* Dynamic contacts are delimited with ";@" and static ones with "@@" */ if ((aor_separator = strstr(id, ";@")) || (aor_separator = strstr(id, "@@"))) { *aor_separator = '\0'; @@ -335,6 +338,10 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri ast_string_field_set(contact, user_agent, user_agent); } + if (!ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) { + ast_string_field_set(contact, reg_server, ast_config_AST_SYSTEM_NAME); + } + contact->endpoint = ao2_bump(endpoint); return ast_sorcery_create(ast_sip_get_sorcery(), contact); @@ -417,38 +424,64 @@ static int permanent_uri_sort_fn(const void *obj_left, const void *obj_right, in int ast_sip_validate_uri_length(const char *contact_uri) { - pjsip_uri *uri; - pjsip_sip_uri *sip_uri; - pj_pool_t *pool; int max_length = pj_max_hostname - 1; + char *contact = ast_strdupa(contact_uri); + char *host; + char *at; + int theres_a_port = 0; if (strlen(contact_uri) > pjsip_max_url_size - 1) { return -1; } - if (!(pool = pjsip_endpt_create_pool(ast_sip_get_pjsip_endpoint(), "uri validation", 512, 512))) { - ast_log(LOG_ERROR, "Unable to allocate pool for uri validation\n"); + contact = ast_strip_quoted(contact, "<", ">"); + + if (!strncasecmp(contact, "sip:", 4)) { + host = contact + 4; + } else if (!strncasecmp(contact, "sips:", 5)) { + host = contact + 5; + } else { + /* Not a SIP URI */ return -1; } - if (!(uri = pjsip_parse_uri(pool, (char *)contact_uri, strlen(contact_uri), 0)) || - (!PJSIP_URI_SCHEME_IS_SIP(uri) && !PJSIP_URI_SCHEME_IS_SIPS(uri))) { - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); - return -1; + at = strchr(contact, '@'); + if (at) { + /* sip[s]:user@host */ + host = at + 1; } - sip_uri = pjsip_uri_get_uri(uri); - if (sip_uri->port == 0) { + if (host[0] == '[') { + /* Host is an IPv6 address. Just get up to the matching bracket */ + char *close_bracket; + + close_bracket = strchr(host, ']'); + if (!close_bracket) { + return -1; + } + close_bracket++; + if (*close_bracket == ':') { + theres_a_port = 1; + } + *close_bracket = '\0'; + } else { + /* uri parameters could contain ';' so trim them off first */ + host = strsep(&host, ";?"); + /* Host is FQDN or IPv4 address. Need to find closing delimiter */ + if (strchr(host, ':')) { + theres_a_port = 1; + host = strsep(&host, ":"); + } + } + + if (!theres_a_port) { max_length -= strlen("_sips.tcp."); } - if (sip_uri->host.slen > max_length) { - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); + if (strlen(host) > max_length) { return -1; } - pjsip_endpt_release_pool(ast_sip_get_pjsip_endpoint(), pool); - return 0; } @@ -1091,6 +1124,7 @@ int ast_sip_initialize_sorcery_location(void) ast_sorcery_object_field_register(sorcery, "contact", "authenticate_qualify", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_contact, authenticate_qualify)); ast_sorcery_object_field_register(sorcery, "contact", "outbound_proxy", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, outbound_proxy)); ast_sorcery_object_field_register(sorcery, "contact", "user_agent", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, user_agent)); + ast_sorcery_object_field_register(sorcery, "contact", "reg_server", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, reg_server)); ast_sorcery_object_field_register(sorcery, "aor", "type", "", OPT_NOOP_T, 0, 0); ast_sorcery_object_field_register(sorcery, "aor", "minimum_expiration", "60", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, minimum_expiration)); diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 06588337c..8e7e95a5d 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -479,6 +479,16 @@ static int ident_handler(const struct aco_option *opt, struct ast_variable *var, struct ast_sip_endpoint *endpoint = obj; char *idents = ast_strdupa(var->value); char *val; + enum ast_sip_endpoint_identifier_type method; + + /* + * If there's already something in the vector when we get here, + * it's the default value so we need to clean it out. + */ + if (AST_VECTOR_SIZE(&endpoint->ident_method_order)) { + AST_VECTOR_RESET(&endpoint->ident_method_order, AST_VECTOR_ELEM_CLEANUP_NOOP); + endpoint->ident_method = 0; + } while ((val = ast_strip(strsep(&idents, ",")))) { if (ast_strlen_zero(val)) { @@ -486,27 +496,55 @@ static int ident_handler(const struct aco_option *opt, struct ast_variable *var, } if (!strcasecmp(val, "username")) { - endpoint->ident_method |= AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME; + method = AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME; + } else if (!strcasecmp(val, "auth_username")) { + method = AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME; } else { ast_log(LOG_ERROR, "Unrecognized identification method %s specified for endpoint %s\n", val, ast_sorcery_object_get_id(endpoint)); + AST_VECTOR_RESET(&endpoint->ident_method_order, AST_VECTOR_ELEM_CLEANUP_NOOP); + endpoint->ident_method = 0; return -1; } + + endpoint->ident_method |= method; + AST_VECTOR_APPEND(&endpoint->ident_method_order, method); } + return 0; } static int ident_to_str(const void *obj, const intptr_t *args, char **buf) { const struct ast_sip_endpoint *endpoint = obj; - switch (endpoint->ident_method) { - case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME : - *buf = "username"; break; - default: + int methods; + char *method; + int i; + int j = 0; + + methods = AST_VECTOR_SIZE(&endpoint->ident_method_order); + if (!methods) { return 0; } - *buf = ast_strdup(*buf); + if (!(*buf = ast_calloc(MAX_OBJECT_FIELD, sizeof(char)))) { + return -1; + } + + for (i = 0; i < methods; i++) { + switch (AST_VECTOR_GET(&endpoint->ident_method_order, i)) { + case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME : + method = "username"; + break; + case AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME : + method = "auth_username"; + break; + default: + continue; + } + j = sprintf(*buf + j, "%s%s", method, i < methods - 1 ? "," : ""); + } + return 0; } @@ -1851,6 +1889,7 @@ static void endpoint_destructor(void* obj) endpoint->pickup.named_pickupgroups = ast_unref_namedgroups(endpoint->pickup.named_pickupgroups); ao2_cleanup(endpoint->persistent); ast_variables_destroy(endpoint->channel_vars); + AST_VECTOR_FREE(&endpoint->ident_method_order); } static int init_subscription_configuration(struct ast_sip_endpoint_subscription_configuration *subscription) @@ -1895,6 +1934,11 @@ void *ast_sip_endpoint_alloc(const char *name) return NULL; } ast_party_id_init(&endpoint->id.self); + + if (AST_VECTOR_INIT(&endpoint->ident_method_order, 1)) { + return NULL; + } + return endpoint; } diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c index 834ca10d3..cbe955728 100644 --- a/res/res_pjsip/pjsip_distributor.c +++ b/res/res_pjsip/pjsip_distributor.c @@ -24,6 +24,7 @@ #include "include/res_pjsip_private.h" #include "asterisk/taskprocessor.h" #include "asterisk/threadpool.h" +#include "asterisk/res_pjsip_cli.h" static int distribute(void *data); static pj_bool_t distributor(pjsip_rx_data *rdata); @@ -37,6 +38,26 @@ static pjsip_module distributor_mod = { .on_rx_response = distributor, }; +struct ast_sched_context *prune_context; + +/* From the auth/realm realtime column size */ +#define MAX_REALM_LENGTH 40 +static char default_realm[MAX_REALM_LENGTH + 1]; + +#define DEFAULT_SUSPECTS_BUCKETS 53 + +static struct ao2_container *unidentified_requests; +static unsigned int unidentified_count; +static unsigned int unidentified_period; +static unsigned int unidentified_prune_interval; +static int using_auth_username; + +struct unidentified_request{ + struct timeval first_seen; + int count; + char src_name[]; +}; + /*! * \internal * \brief Record the task's serializer name on the tdata structure. @@ -322,7 +343,7 @@ static int create_artificial_auth(void) return -1; } - ast_string_field_set(artificial_auth, realm, "asterisk"); + ast_string_field_set(artificial_auth, realm, default_realm); ast_string_field_set(artificial_auth, auth_user, ""); ast_string_field_set(artificial_auth, auth_pass, ""); artificial_auth->type = AST_SIP_AUTH_TYPE_ARTIFICIAL; @@ -359,27 +380,65 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void) return artificial_endpoint; } -static void log_unidentified_request(pjsip_rx_data *rdata) +static void log_unidentified_request(pjsip_rx_data *rdata, unsigned int count, unsigned int period) { char from_buf[PJSIP_MAX_URL_SIZE]; char callid_buf[PJSIP_MAX_URL_SIZE]; pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, rdata->msg_info.from->uri, from_buf, PJSIP_MAX_URL_SIZE); ast_copy_pj_str(callid_buf, &rdata->msg_info.cid->id, PJSIP_MAX_URL_SIZE); - ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found\n", - from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf); + if (count) { + ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found" + " after %u tries in %.3f ms\n", + from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf, count, period / 1000.0); + } else { + ast_log(LOG_NOTICE, "Request from '%s' failed for '%s:%d' (callid: %s) - No matching endpoint found", + from_buf, rdata->pkt_info.src_name, rdata->pkt_info.src_port, callid_buf); + } +} + +static void check_endpoint(pjsip_rx_data *rdata, struct unidentified_request *unid, + const char *name) +{ + int64_t ms = ast_tvdiff_ms(ast_tvnow(), unid->first_seen); + + ao2_wrlock(unid); + unid->count++; + + if (ms < (unidentified_period * 1000) && unid->count >= unidentified_count) { + log_unidentified_request(rdata, unid->count, ms); + ast_sip_report_invalid_endpoint(name, rdata); + } + ao2_unlock(unid); } static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata) { struct ast_sip_endpoint *endpoint; + struct unidentified_request *unid; int is_ack = rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD; endpoint = rdata->endpt_info.mod_data[endpoint_mod.id]; if (endpoint) { + /* + * ao2_find with OBJ_UNLINK always write locks the container before even searching + * for the object. Since the majority case is that the object won't be found, do + * the find without OBJ_UNLINK to prevent the unnecessary write lock, then unlink + * if needed. + */ + if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) { + ao2_unlink(unidentified_requests, unid); + ao2_ref(unid, -1); + } return PJ_FALSE; } endpoint = ast_sip_identify_endpoint(rdata); + if (endpoint) { + if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) { + ao2_unlink(unidentified_requests, unid); + ao2_ref(unid, -1); + } + } if (!endpoint && !is_ack) { char name[AST_UUID_STR_LEN] = ""; @@ -397,8 +456,32 @@ static pj_bool_t endpoint_lookup(pjsip_rx_data *rdata) ast_copy_pj_str(name, &sip_from->user, sizeof(name)); } - log_unidentified_request(rdata); - ast_sip_report_invalid_endpoint(name, rdata); + if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) { + check_endpoint(rdata, unid, name); + ao2_ref(unid, -1); + } else if (using_auth_username) { + ao2_wrlock(unidentified_requests); + /* The check again with the write lock held allows us to eliminate the DUPS_REPLACE and sort_fn */ + if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY | OBJ_NOLOCK))) { + check_endpoint(rdata, unid, name); + } else { + unid = ao2_alloc_options(sizeof(*unid) + strlen(rdata->pkt_info.src_name) + 1, NULL, + AO2_ALLOC_OPT_LOCK_RWLOCK); + if (!unid) { + ao2_unlock(unidentified_requests); + return PJ_TRUE; + } + strcpy(unid->src_name, rdata->pkt_info.src_name); /* Safe */ + unid->first_seen = ast_tvnow(); + unid->count = 1; + ao2_link_flags(unidentified_requests, unid, OBJ_NOLOCK); + } + ao2_ref(unid, -1); + ao2_unlock(unidentified_requests); + } else { + log_unidentified_request(rdata, 0, 0); + ast_sip_report_invalid_endpoint(name, rdata); + } } rdata->endpt_info.mod_data[endpoint_mod.id] = endpoint; return PJ_FALSE; @@ -413,6 +496,8 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata) if (!is_ack && ast_sip_requires_authentication(endpoint, rdata)) { pjsip_tx_data *tdata; + struct unidentified_request *unid; + pjsip_endpt_create_response(ast_sip_get_pjsip_endpoint(), rdata, 401, NULL, &tdata); switch (ast_sip_check_authentication(endpoint, rdata, tdata)) { case AST_SIP_AUTHENTICATION_CHALLENGE: @@ -421,6 +506,11 @@ static pj_bool_t authenticate(pjsip_rx_data *rdata) pjsip_endpt_send_response2(ast_sip_get_pjsip_endpoint(), rdata, tdata, NULL, NULL); return PJ_TRUE; case AST_SIP_AUTHENTICATION_SUCCESS: + /* See note in endpoint_lookup about not holding an unnecessary write lock */ + if ((unid = ao2_find(unidentified_requests, rdata->pkt_info.src_name, OBJ_SEARCH_KEY))) { + ao2_unlink(unidentified_requests, unid); + ao2_ref(unid, -1); + } ast_sip_report_auth_success(endpoint, rdata); pjsip_tx_data_dec_ref(tdata); return PJ_FALSE; @@ -480,31 +570,287 @@ struct ast_sip_endpoint *ast_pjsip_rdata_get_endpoint(pjsip_rx_data *rdata) return endpoint; } +static int suspects_sort(const void *obj, const void *arg, int flags) +{ + const struct unidentified_request *object_left = obj; + const struct unidentified_request *object_right = arg; + const char *right_key = arg; + int cmp; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + right_key = object_right->src_name; + /* Fall through */ + case OBJ_SEARCH_KEY: + cmp = strcmp(object_left->src_name, right_key); + break; + case OBJ_SEARCH_PARTIAL_KEY: + cmp = strncmp(object_left->src_name, right_key, strlen(right_key)); + break; + default: + cmp = 0; + break; + } + return cmp; +} + +static int suspects_compare(void *obj, void *arg, int flags) +{ + const struct unidentified_request *object_left = obj; + const struct unidentified_request *object_right = arg; + const char *right_key = arg; + int cmp = 0; + + switch (flags & OBJ_SEARCH_MASK) { + case OBJ_SEARCH_OBJECT: + right_key = object_right->src_name; + /* Fall through */ + case OBJ_SEARCH_KEY: + if (strcmp(object_left->src_name, right_key) == 0) { + cmp = CMP_MATCH | CMP_STOP; + } + break; + case OBJ_SEARCH_PARTIAL_KEY: + if (strncmp(object_left->src_name, right_key, strlen(right_key)) == 0) { + cmp = CMP_MATCH; + } + break; + default: + cmp = 0; + break; + } + return cmp; +} + +static int suspects_hash(const void *obj, int flags) { + const struct unidentified_request *object_left = obj; + + if (flags & OBJ_SEARCH_OBJECT) { + return ast_str_hash(object_left->src_name); + } else if (flags & OBJ_SEARCH_KEY) { + return ast_str_hash(obj); + } + return -1; +} + +static struct ao2_container *cli_unid_get_container(const char *regex) +{ + struct ao2_container *s_container; + + s_container = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, + suspects_sort, suspects_compare); + if (!s_container) { + return NULL; + } + + if (ao2_container_dup(s_container, unidentified_requests, 0)) { + ao2_ref(s_container, -1); + return NULL; + } + + return s_container; +} + +static int cli_unid_iterate(void *container, ao2_callback_fn callback, void *args) +{ + ao2_callback(container, 0, callback, args); + + return 0; +} + +static void *cli_unid_retrieve_by_id(const char *id) +{ + return ao2_find(unidentified_requests, id, OBJ_SEARCH_KEY); +} + +static const char *cli_unid_get_id(const void *obj) +{ + const struct unidentified_request *unid = obj; + + return unid->src_name; +} + +static int cli_unid_print_header(void *obj, void *arg, int flags) +{ + struct ast_sip_cli_context *context = arg; + RAII_VAR(struct ast_sip_cli_formatter_entry *, formatter_entry, NULL, ao2_cleanup); + + int indent = CLI_INDENT_TO_SPACES(context->indent_level); + int filler = CLI_LAST_TABSTOP - indent - 7; + + ast_assert(context->output_buffer != NULL); + + ast_str_append(&context->output_buffer, 0, + "%*s: <IP Address%*.*s> <Count> <Age(sec)>\n", + indent, "Request", filler, filler, CLI_HEADER_FILLER); + + return 0; +} +static int cli_unid_print_body(void *obj, void *arg, int flags) +{ + struct unidentified_request *unid = obj; + struct ast_sip_cli_context *context = arg; + int indent; + int flexwidth; + int64_t ms = ast_tvdiff_ms(ast_tvnow(), unid->first_seen); + + ast_assert(context->output_buffer != NULL); + + indent = CLI_INDENT_TO_SPACES(context->indent_level); + flexwidth = CLI_LAST_TABSTOP - 4; + + ast_str_append(&context->output_buffer, 0, "%*s: %-*.*s %7d %10.3f\n", + indent, + "Request", + flexwidth, flexwidth, + unid->src_name, unid->count, ms / 1000.0); + + return 0; +} + +static struct ast_cli_entry cli_commands[] = { + AST_CLI_DEFINE(ast_sip_cli_traverse_objects, "Show PJSIP Unidentified Requests", + .command = "pjsip show unidentified_requests", + .usage = "Usage: pjsip show unidentified_requests\n" + " Show the PJSIP Unidentified Requests\n"), +}; + +struct ast_sip_cli_formatter_entry *unid_formatter; + +static int expire_requests(void *object, void *arg, int flags) +{ + struct unidentified_request *unid = object; + int *maxage = arg; + int64_t ms = ast_tvdiff_ms(ast_tvnow(), unid->first_seen); + + if (ms > (*maxage) * 2 * 1000) { + return CMP_MATCH; + } + + return 0; +} + +static int prune_task(const void *data) +{ + unsigned int maxage; + + ast_sip_get_unidentified_request_thresholds(&unidentified_count, &unidentified_period, &unidentified_prune_interval); + maxage = unidentified_period * 2; + ao2_callback(unidentified_requests, OBJ_MULTIPLE | OBJ_NODATA | OBJ_UNLINK, expire_requests, &maxage); + + return unidentified_prune_interval * 1000; +} + +static int clean_task(const void *data) +{ + return 0; +} + +static void global_loaded(const char *object_type) +{ + char *identifier_order = ast_sip_get_endpoint_identifier_order(); + char *io_copy = ast_strdupa(identifier_order); + char *identify_method; + + ast_free(identifier_order); + using_auth_username = 0; + while ((identify_method = ast_strip(strsep(&io_copy, ",")))) { + if (!strcmp(identify_method, "auth_username")) { + using_auth_username = 1; + break; + } + } + + ast_sip_get_default_realm(default_realm, sizeof(default_realm)); + ast_sip_get_unidentified_request_thresholds(&unidentified_count, &unidentified_period, &unidentified_prune_interval); + + /* Clean out the old task, if any */ + ast_sched_clean_by_callback(prune_context, prune_task, clean_task); + if (ast_sched_add_variable(prune_context, unidentified_prune_interval * 1000, prune_task, NULL, 1) < 0) { + return; + } +} + +/*! \brief Observer which is used to update our interval and default_realm when the global setting changes */ +static struct ast_sorcery_observer global_observer = { + .loaded = global_loaded, +}; + + int ast_sip_initialize_distributor(void) { + unidentified_requests = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0, + DEFAULT_SUSPECTS_BUCKETS, suspects_hash, NULL, suspects_compare); + if (!unidentified_requests) { + return -1; + } + + prune_context = ast_sched_context_create(); + if (!prune_context) { + ast_sip_destroy_distributor(); + return -1; + } + + if (ast_sched_start_thread(prune_context)) { + ast_sip_destroy_distributor(); + return -1; + } + + ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer); + ast_sorcery_reload_object(ast_sip_get_sorcery(), "global"); + if (create_artificial_endpoint() || create_artificial_auth()) { + ast_sip_destroy_distributor(); return -1; } if (internal_sip_register_service(&distributor_mod)) { + ast_sip_destroy_distributor(); return -1; } if (internal_sip_register_service(&endpoint_mod)) { + ast_sip_destroy_distributor(); return -1; } if (internal_sip_register_service(&auth_mod)) { + ast_sip_destroy_distributor(); return -1; } + unid_formatter = ao2_alloc(sizeof(struct ast_sip_cli_formatter_entry), NULL); + if (!unid_formatter) { + ast_log(LOG_ERROR, "Unable to allocate memory for unid_formatter\n"); + return -1; + } + unid_formatter->name = "unidentified_request"; + unid_formatter->print_header = cli_unid_print_header; + unid_formatter->print_body = cli_unid_print_body; + unid_formatter->get_container = cli_unid_get_container; + unid_formatter->iterate = cli_unid_iterate; + unid_formatter->get_id = cli_unid_get_id; + unid_formatter->retrieve_by_id = cli_unid_retrieve_by_id; + ast_sip_register_cli_formatter(unid_formatter); + ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands)); + return 0; } void ast_sip_destroy_distributor(void) { + ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands)); + ast_sip_unregister_cli_formatter(unid_formatter); + internal_sip_unregister_service(&distributor_mod); internal_sip_unregister_service(&endpoint_mod); internal_sip_unregister_service(&auth_mod); ao2_cleanup(artificial_auth); ao2_cleanup(artificial_endpoint); + ao2_cleanup(unidentified_requests); + + ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer); + + if (prune_context) { + ast_sched_context_destroy(prune_context); + } } diff --git a/res/res_pjsip/pjsip_options.c b/res/res_pjsip/pjsip_options.c index f7233a89c..b9339f60b 100644 --- a/res/res_pjsip/pjsip_options.c +++ b/res/res_pjsip/pjsip_options.c @@ -42,6 +42,7 @@ static const char *status_map [] = { [UNKNOWN] = "Unknown", [CREATED] = "Created", [REMOVED] = "Removed", + [UPDATED] = "Updated", }; static const char *short_status_map [] = { @@ -50,6 +51,7 @@ static const char *short_status_map [] = { [UNKNOWN] = "Unknown", [CREATED] = "Created", [REMOVED] = "Removed", + [UPDATED] = "Updated", }; const char *ast_sip_get_contact_status_label(const enum ast_sip_contact_status_type status) @@ -543,6 +545,16 @@ static void contact_created(const void *obj) /*! * \internal + * \brief A contact has been updated. + */ +static void contact_updated(const void *obj) +{ + update_contact_status((struct ast_sip_contact *) obj, UPDATED); + qualify_and_schedule((struct ast_sip_contact *) obj); +} + +/*! + * \internal * \brief A contact has been deleted remove status tracking. */ static void contact_deleted(const void *obj) @@ -567,7 +579,8 @@ static void contact_deleted(const void *obj) static const struct ast_sorcery_observer contact_observer = { .created = contact_created, - .deleted = contact_deleted + .deleted = contact_deleted, + .updated = contact_updated }; static pj_bool_t options_start(void) diff --git a/res/res_pjsip/pjsip_scheduler.c b/res/res_pjsip/pjsip_scheduler.c new file mode 100644 index 000000000..a5d406cb5 --- /dev/null +++ b/res/res_pjsip/pjsip_scheduler.c @@ -0,0 +1,495 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2016, Fairview 5 Engineering, LLC + * + * George Joseph <george.joseph@fairview5.com> + * + * 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 res_pjsip Scheduler + * + * \author George Joseph <george.joseph@fairview5.com> + */ + +#include "asterisk.h" + +ASTERISK_REGISTER_FILE() + +#include "asterisk/res_pjsip.h" +#include "include/res_pjsip_private.h" +#include "asterisk/res_pjsip_cli.h" + +#define TASK_BUCKETS 53 + +static struct ast_sched_context *scheduler_context; +static struct ao2_container *tasks; +static int task_count; + +struct ast_sip_sched_task { + /*! ast_sip_sched task id */ + uint32_t task_id; + /*! ast_sched scheudler id */ + int current_scheduler_id; + /*! task is currently running */ + int is_running; + /*! task */ + ast_sip_task task; + /*! task data */ + void *task_data; + /*! reschedule interval in milliseconds */ + int interval; + /*! the time the task was queued */ + struct timeval when_queued; + /*! the last time the task was started */ + struct timeval last_start; + /*! the last time the task was ended */ + struct timeval last_end; + /*! times run */ + int run_count; + /*! the task reschedule, cleanup and policy flags */ + enum ast_sip_scheduler_task_flags flags; + /*! the serializer to be used (if any) */ + struct ast_taskprocessor *serializer; + /* A name to be associated with the task */ + char name[0]; +}; + +AO2_STRING_FIELD_HASH_FN(ast_sip_sched_task, name); +AO2_STRING_FIELD_CMP_FN(ast_sip_sched_task, name); +AO2_STRING_FIELD_SORT_FN(ast_sip_sched_task, name); + +static int push_to_serializer(const void *data); + +/* + * This function is run in the context of the serializer. + * It runs the task with a simple call and reschedules based on the result. + */ +static int run_task(void *data) +{ + RAII_VAR(struct ast_sip_sched_task *, schtd, ao2_bump(data), ao2_cleanup); + int res; + int delay; + + ao2_lock(schtd); + schtd->last_start = ast_tvnow(); + schtd->is_running = 1; + schtd->run_count++; + ao2_unlock(schtd); + + res = schtd->task(schtd->task_data); + + ao2_lock(schtd); + schtd->is_running = 0; + schtd->last_end = ast_tvnow(); + + /* + * Don't restart if the task returned 0 or if the interval + * was set to 0 while the task was running + */ + if (!res || !schtd->interval) { + schtd->interval = 0; + ao2_unlock(schtd); + ao2_unlink(tasks, schtd); + return -1; + } + + if (schtd->flags & AST_SIP_SCHED_TASK_VARIABLE) { + schtd->interval = res; + } + + if (schtd->flags & AST_SIP_SCHED_TASK_DELAY) { + delay = schtd->interval; + } else { + delay = schtd->interval - (ast_tvdiff_ms(schtd->last_end, schtd->last_start) % schtd->interval); + } + + schtd->current_scheduler_id = ast_sched_add(scheduler_context, delay, push_to_serializer, (const void *)schtd); + if (schtd->current_scheduler_id < 0) { + schtd->interval = 0; + ao2_unlock(schtd); + ao2_unlink(tasks, schtd); + return -1; + } + + ao2_unlock(schtd); + + return 0; +} + +/* + * This function is run by the scheduler thread. Its only job is to push the task + * to the serialize and return. It returns 0 so it's not rescheduled. + */ +static int push_to_serializer(const void *data) +{ + struct ast_sip_sched_task *schtd = (struct ast_sip_sched_task *)data; + + if (ast_sip_push_task(schtd->serializer, run_task, schtd)) { + ao2_ref(schtd, -1); + } + + return 0; +} + +int ast_sip_sched_task_cancel(struct ast_sip_sched_task *schtd) +{ + int res; + + if (!ao2_ref_and_lock(schtd)) { + return -1; + } + + if (schtd->current_scheduler_id < 0 || schtd->interval <= 0) { + ao2_unlock_and_unref(schtd); + return 0; + } + + schtd->interval = 0; + ao2_unlock_and_unref(schtd); + ao2_unlink(tasks, schtd); + res = ast_sched_del(scheduler_context, schtd->current_scheduler_id); + + return res; +} + +int ast_sip_sched_task_cancel_by_name(const char *name) +{ + RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup); + + if (ast_strlen_zero(name)) { + return -1; + } + + schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (!schtd) { + return -1; + } + + return ast_sip_sched_task_cancel(schtd); +} + + +int ast_sip_sched_task_get_times(struct ast_sip_sched_task *schtd, + struct timeval *queued, struct timeval *last_start, struct timeval *last_end) +{ + if (!ao2_ref_and_lock(schtd)) { + return -1; + } + + if (queued) { + memcpy(queued, &schtd->when_queued, sizeof(struct timeval)); + } + if (last_start) { + memcpy(last_start, &schtd->last_start, sizeof(struct timeval)); + } + if (last_end) { + memcpy(last_end, &schtd->last_end, sizeof(struct timeval)); + } + + ao2_unlock_and_unref(schtd); + + return 0; +} + +int ast_sip_sched_task_get_times_by_name(const char *name, + struct timeval *queued, struct timeval *last_start, struct timeval *last_end) +{ + RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup); + + if (ast_strlen_zero(name)) { + return -1; + } + + schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (!schtd) { + return -1; + } + + return ast_sip_sched_task_get_times(schtd, queued, last_start, last_end); +} + +int ast_sip_sched_task_get_name(struct ast_sip_sched_task *schtd, char *name, size_t maxlen) +{ + if (maxlen <= 0) { + return -1; + } + + if (!ao2_ref_and_lock(schtd)) { + return -1; + } + + ast_copy_string(name, schtd->name, maxlen); + + ao2_unlock_and_unref(schtd); + + return 0; +} + +int ast_sip_sched_task_get_next_run(struct ast_sip_sched_task *schtd) +{ + int delay; + struct timeval since_when; + struct timeval now; + + if (!ao2_ref_and_lock(schtd)) { + return -1; + } + + if (schtd->interval) { + delay = schtd->interval; + now = ast_tvnow(); + + if (schtd->flags & AST_SIP_SCHED_TASK_DELAY) { + since_when = schtd->is_running ? now : schtd->last_end; + } else { + since_when = schtd->last_start.tv_sec ? schtd->last_start : schtd->when_queued; + } + + delay -= ast_tvdiff_ms(now, since_when); + + delay = delay < 0 ? 0 : delay; + } else { + delay = -1; + } + + ao2_unlock_and_unref(schtd); + + return delay; +} + +int ast_sip_sched_task_get_next_run_by_name(const char *name) +{ + RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup); + + if (ast_strlen_zero(name)) { + return -1; + } + + schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (!schtd) { + return -1; + } + + return ast_sip_sched_task_get_next_run(schtd); +} + +int ast_sip_sched_is_task_running(struct ast_sip_sched_task *schtd) +{ + if (!schtd) { + return 0; + } + + return schtd->is_running; +} + +int ast_sip_sched_is_task_running_by_name(const char *name) +{ + RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup); + + if (ast_strlen_zero(name)) { + return 0; + } + + schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + if (!schtd) { + return 0; + } + + return schtd->is_running; +} + +static void schtd_destructor(void *data) +{ + struct ast_sip_sched_task *schtd = data; + + if (schtd->flags & AST_SIP_SCHED_TASK_DATA_AO2) { + /* release our own ref, then release the callers if asked to do so */ + ao2_ref(schtd->task_data, (schtd->flags & AST_SIP_SCHED_TASK_DATA_FREE) ? -2 : -1); + } else if (schtd->task_data && (schtd->flags & AST_SIP_SCHED_TASK_DATA_FREE)) { + ast_free(schtd->task_data); + } +} + +struct ast_sip_sched_task *ast_sip_schedule_task(struct ast_taskprocessor *serializer, + int interval, ast_sip_task sip_task, char *name, void *task_data, enum ast_sip_scheduler_task_flags flags) +{ +#define ID_LEN 13 /* task_deadbeef */ + struct ast_sip_sched_task *schtd; + int res; + + if (interval < 0) { + return NULL; + } + + schtd = ao2_alloc((sizeof(*schtd) + (!ast_strlen_zero(name) ? strlen(name) : ID_LEN) + 1), schtd_destructor); + if (!schtd) { + return NULL; + } + + schtd->task_id = ast_atomic_fetchadd_int(&task_count, 1); + schtd->serializer = serializer; + schtd->task = sip_task; + if (!ast_strlen_zero(name)) { + strcpy(schtd->name, name); /* Safe */ + } else { + sprintf(schtd->name, "task_%08x", schtd->task_id); + } + schtd->task_data = task_data; + schtd->flags = flags; + schtd->interval = interval; + schtd->when_queued = ast_tvnow(); + + if (flags & AST_SIP_SCHED_TASK_DATA_AO2) { + ao2_ref(task_data, +1); + } + res = ast_sched_add(scheduler_context, interval, push_to_serializer, (const void *)schtd); + if (res < 0) { + ao2_ref(schtd, -1); + return NULL; + } else { + schtd->current_scheduler_id = res; + ao2_link(tasks, schtd); + } + + return schtd; +#undef ID_LEN +} + +static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct ao2_iterator i; + struct ast_sip_sched_task *schtd; + const char *log_format = ast_logger_get_dateformat(); + struct ast_tm tm; + char queued[32]; + char last_start[32]; + char last_end[32]; + int datelen; + struct timeval now = ast_tvnow(); + const char *separator = "======================================"; + + switch (cmd) { + case CLI_INIT: + e->command = "pjsip show scheduled_tasks"; + e->usage = "Usage: pjsip show scheduled_tasks\n" + " Show all scheduled tasks\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc != 3) { + return CLI_SHOWUSAGE; + } + + ast_localtime(&now, &tm, NULL); + datelen = ast_strftime(queued, sizeof(queued), log_format, &tm); + + ast_cli(a->fd, "PJSIP Scheduled Tasks:\n\n"); + + ast_cli(a->fd, " %1$-24s %2$-8s %3$-9s %4$-7s %6$-*5$s %7$-*5$s %8$-*5$s\n", + "Task Name", "Interval", "Times Run", "State", + datelen, "Queued", "Last Started", "Last Ended"); + + ast_cli(a->fd, " %1$-24.24s %2$-8.8s %3$-9.9s %4$-7.7s %6$-*5$.*5$s %7$-*5$.*5$s %8$-*5$.*5$s\n", + separator, separator, separator, separator, + datelen, separator, separator, separator); + + + ao2_ref(tasks, +1); + ao2_rdlock(tasks); + i = ao2_iterator_init(tasks, 0); + while ((schtd = ao2_iterator_next(&i))) { + + ast_localtime(&schtd->when_queued, &tm, NULL); + ast_strftime(queued, sizeof(queued), log_format, &tm); + + if (ast_tvzero(schtd->last_start)) { + strcpy(last_start, "not yet started"); + } else { + ast_localtime(&schtd->last_start, &tm, NULL); + ast_strftime(last_start, sizeof(last_start), log_format, &tm); + } + + if (ast_tvzero(schtd->last_end)) { + if (ast_tvzero(schtd->last_start)) { + strcpy(last_end, "not yet started"); + } else { + strcpy(last_end, "running"); + } + } else { + ast_localtime(&schtd->last_end, &tm, NULL); + ast_strftime(last_end, sizeof(last_end), log_format, &tm); + } + + ast_cli(a->fd, " %1$-24.24s %2$-8.3f %3$-9d %4$-7s %6$-*5$s %7$-*5$s %8$-*5$s\n", + schtd->name, + schtd->interval / 1000.0, + schtd->run_count, + schtd->is_running ? "running" : "waiting", + datelen, queued, last_start, last_end); + ao2_cleanup(schtd); + } + ao2_iterator_destroy(&i); + ao2_unlock(tasks); + ao2_ref(tasks, -1); + ast_cli(a->fd, "\n"); + + return CLI_SUCCESS; +} + +static struct ast_cli_entry cli_commands[] = { + AST_CLI_DEFINE(cli_show_tasks, "Show all scheduled tasks"), +}; + +int ast_sip_initialize_scheduler(void) +{ + if (!(scheduler_context = ast_sched_context_create())) { + ast_log(LOG_ERROR, "Failed to create scheduler. Aborting load\n"); + return -1; + } + + if (ast_sched_start_thread(scheduler_context)) { + ast_log(LOG_ERROR, "Failed to start scheduler. Aborting load\n"); + ast_sched_context_destroy(scheduler_context); + return -1; + } + + tasks = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, + TASK_BUCKETS, ast_sip_sched_task_hash_fn, ast_sip_sched_task_sort_fn, ast_sip_sched_task_cmp_fn); + if (!tasks) { + ast_log(LOG_ERROR, "Failed to allocate task container. Aborting load\n"); + ast_sched_context_destroy(scheduler_context); + return -1; + } + + ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands)); + + return 0; +} + +int ast_sip_destroy_scheduler(void) +{ + ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands)); + + if (scheduler_context) { + ast_sched_context_destroy(scheduler_context); + } + + ao2_cleanup(tasks); + tasks = NULL; + + return 0; +} diff --git a/res/res_pjsip_authenticator_digest.c b/res/res_pjsip_authenticator_digest.c index 8a781254c..59e9738fd 100644 --- a/res/res_pjsip_authenticator_digest.c +++ b/res/res_pjsip_authenticator_digest.c @@ -31,6 +31,10 @@ <support_level>core</support_level> ***/ +/* From the auth/realm realtime column size */ +#define MAX_REALM_LENGTH 40 +static char default_realm[MAX_REALM_LENGTH + 1]; + AO2_GLOBAL_OBJ_STATIC(entity_id); /*! @@ -202,9 +206,12 @@ static int build_nonce(struct ast_str **nonce, const char *timestamp, const pjsi RAII_VAR(char *, eid, ao2_global_obj_ref(entity_id), ao2_cleanup); char hash[33]; + /* + * Note you may be tempted to think why not include the port. The reason + * is that when using TCP the port can potentially differ from before. + */ ast_str_append(&str, 0, "%s", timestamp); ast_str_append(&str, 0, ":%s", rdata->pkt_info.src_name); - ast_str_append(&str, 0, ":%d", rdata->pkt_info.src_port); ast_str_append(&str, 0, ":%s", eid); ast_str_append(&str, 0, ":%s", realm); ast_md5_hash(hash, ast_str_buffer(str)); @@ -409,7 +416,7 @@ static enum ast_sip_check_auth_result digest_check_auth(struct ast_sip_endpoint for (i = 0; i < auth_size; ++i) { if (ast_strlen_zero(auths[i]->realm)) { - ast_string_field_set(auths[i], realm, "asterisk"); + ast_string_field_set(auths[i], realm, default_realm); } verify_res[i] = verify(auths[i], rdata, tdata->pool); if (verify_res[i] == AUTH_SUCCESS) { @@ -456,6 +463,16 @@ static int build_entity_id(void) return 0; } +static void global_loaded(const char *object_type) +{ + ast_sip_get_default_realm(default_realm, sizeof(default_realm)); +} + +/*! \brief Observer which is used to update our default_realm when the global setting changes */ +static struct ast_sorcery_observer global_observer = { + .loaded = global_loaded, +}; + static int reload_module(void) { if (build_entity_id()) { @@ -471,6 +488,10 @@ static int load_module(void) if (build_entity_id()) { return AST_MODULE_LOAD_DECLINE; } + + ast_sorcery_observer_add(ast_sip_get_sorcery(), "global", &global_observer); + ast_sorcery_reload_object(ast_sip_get_sorcery(), "global"); + if (ast_sip_register_authenticator(&digest_authenticator)) { ao2_global_obj_release(entity_id); return AST_MODULE_LOAD_DECLINE; @@ -480,6 +501,7 @@ static int load_module(void) static int unload_module(void) { + ast_sorcery_observer_remove(ast_sip_get_sorcery(), "global", &global_observer); ast_sip_unregister_authenticator(&digest_authenticator); ao2_global_obj_release(entity_id); return 0; @@ -490,5 +512,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP authentication .load = load_module, .unload = unload_module, .reload = reload_module, - .load_pri = AST_MODPRI_CHANNEL_DEPEND, + .load_pri = AST_MODPRI_CHANNEL_DEPEND - 5, ); diff --git a/res/res_pjsip_caller_id.c b/res/res_pjsip_caller_id.c index 9af2a8a64..efa1b89a8 100644 --- a/res/res_pjsip_caller_id.c +++ b/res/res_pjsip_caller_id.c @@ -424,6 +424,12 @@ static pjsip_fromto_hdr *create_new_id_hdr(const pj_str_t *hdr_name, pjsip_fromt ast_escape_quoted(id->name.str, name_buf, name_buf_len); pj_strdup2(tdata->pool, &id_name_addr->display, name_buf); + } else { + /* + * We need to clear the remnants of the clone or it'll be left set. + * pj_strdup2 is safe to call with a NULL src and it resets both slen and ptr. + */ + pj_strdup2(tdata->pool, &id_name_addr->display, NULL); } pj_strdup2(tdata->pool, &id_uri->user, id->number.str); diff --git a/res/res_pjsip_endpoint_identifier_anonymous.c b/res/res_pjsip_endpoint_identifier_anonymous.c index b39c8632c..b93133824 100644 --- a/res/res_pjsip_endpoint_identifier_anonymous.c +++ b/res/res_pjsip_endpoint_identifier_anonymous.c @@ -69,28 +69,30 @@ static struct ast_sip_endpoint *anonymous_identify(pjsip_rx_data *rdata) return NULL; } - /* Attempt to find the endpoint given the name and domain provided */ - snprintf(id, sizeof(id), "anonymous@%s", domain_name); - if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { - goto done; - } - - /* See if an alias exists for the domain provided */ - if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) { - snprintf(id, sizeof(id), "anonymous@%s", alias->domain); + if (!ast_sip_get_disable_multi_domain()) { + /* Attempt to find the endpoint given the name and domain provided */ + snprintf(id, sizeof(id), "anonymous@%s", domain_name); if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { goto done; } - } - /* See if the transport this came in on has a provided domain */ - if ((transport_states = ast_sip_get_transport_states()) - && (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata)) - && (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id)) - && !ast_strlen_zero(transport->domain)) { - snprintf(id, sizeof(id), "anonymous@%s", transport->domain); - if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { - goto done; + /* See if an alias exists for the domain provided */ + if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) { + snprintf(id, sizeof(id), "anonymous@%s", alias->domain); + if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { + goto done; + } + } + + /* See if the transport this came in on has a provided domain */ + if ((transport_states = ast_sip_get_transport_states()) + && (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata)) + && (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id)) + && !ast_strlen_zero(transport->domain)) { + snprintf(id, sizeof(id), "anonymous@%s", transport->domain); + if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { + goto done; + } } } diff --git a/res/res_pjsip_endpoint_identifier_ip.c b/res/res_pjsip_endpoint_identifier_ip.c index 7f4858af0..b1ffd2cc3 100644 --- a/res/res_pjsip_endpoint_identifier_ip.c +++ b/res/res_pjsip_endpoint_identifier_ip.c @@ -515,5 +515,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP IP endpoint ide .load = load_module, .reload = reload_module, .unload = unload_module, - .load_pri = AST_MODPRI_APP_DEPEND, + .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4, ); diff --git a/res/res_pjsip_endpoint_identifier_user.c b/res/res_pjsip_endpoint_identifier_user.c index aa6d398cf..e018abd61 100644 --- a/res/res_pjsip_endpoint_identifier_user.c +++ b/res/res_pjsip_endpoint_identifier_user.c @@ -29,7 +29,7 @@ #include "asterisk/res_pjsip.h" #include "asterisk/module.h" -static int get_endpoint_details(pjsip_rx_data *rdata, char *endpoint, size_t endpoint_size, char *domain, size_t domain_size) +static int get_from_header(pjsip_rx_data *rdata, char *username, size_t username_size, char *domain, size_t domain_size) { pjsip_uri *from = rdata->msg_info.from->uri; pjsip_sip_uri *sip_from; @@ -37,11 +37,28 @@ static int get_endpoint_details(pjsip_rx_data *rdata, char *endpoint, size_t end return -1; } sip_from = (pjsip_sip_uri *) pjsip_uri_get_uri(from); - ast_copy_pj_str(endpoint, &sip_from->user, endpoint_size); + ast_copy_pj_str(username, &sip_from->user, username_size); ast_copy_pj_str(domain, &sip_from->host, domain_size); return 0; } +static pjsip_authorization_hdr *get_auth_header(pjsip_rx_data *rdata, char *username, + size_t username_size, char *realm, size_t realm_size, pjsip_authorization_hdr *start) +{ + pjsip_authorization_hdr *header; + + header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, start); + + if (!header || pj_stricmp2(&header->scheme, "digest")) { + return NULL; + } + + ast_copy_pj_str(username, &header->credential.digest.username, username_size); + ast_copy_pj_str(realm, &header->credential.digest.realm, realm_size); + + return header; +} + static int find_transport_state_in_use(void *obj, void *arg, int flags) { struct ast_sip_transport_state *transport_state = obj; @@ -56,74 +73,126 @@ static int find_transport_state_in_use(void *obj, void *arg, int flags) return 0; } -static struct ast_sip_endpoint *username_identify(pjsip_rx_data *rdata) +static struct ast_sip_endpoint *find_endpoint(pjsip_rx_data *rdata, char *endpoint_name, + char *domain_name) { - char endpoint_name[64], domain_name[64], id[AST_UUID_STR_LEN]; + char id[AST_UUID_STR_LEN]; struct ast_sip_endpoint *endpoint; RAII_VAR(struct ast_sip_domain_alias *, alias, NULL, ao2_cleanup); RAII_VAR(struct ao2_container *, transport_states, NULL, ao2_cleanup); RAII_VAR(struct ast_sip_transport_state *, transport_state, NULL, ao2_cleanup); RAII_VAR(struct ast_sip_transport *, transport, NULL, ao2_cleanup); - if (get_endpoint_details(rdata, endpoint_name, sizeof(endpoint_name), domain_name, sizeof(domain_name))) { - return NULL; - } - - /* Attempt to find the endpoint given the name and domain provided */ - snprintf(id, sizeof(id), "%s@%s", endpoint_name, domain_name); - if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { - goto done; - } - - /* See if an alias exists for the domain provided */ - if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) { - snprintf(id, sizeof(id), "%s@%s", endpoint_name, alias->domain); + if (!ast_sip_get_disable_multi_domain()) { + /* Attempt to find the endpoint given the name and domain provided */ + snprintf(id, sizeof(id), "%s@%s", endpoint_name, domain_name); if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { - goto done; + return endpoint; } - } - /* See if the transport this came in on has a provided domain */ - if ((transport_states = ast_sip_get_transport_states()) - && (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata)) - && (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id)) - && !ast_strlen_zero(transport->domain)) { - snprintf(id, sizeof(id), "anonymous@%s", transport->domain); - if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { - goto done; + /* See if an alias exists for the domain provided */ + if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) { + snprintf(id, sizeof(id), "%s@%s", endpoint_name, alias->domain); + if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { + return endpoint; + } + } + /* See if the transport this came in on has a provided domain */ + if ((transport_states = ast_sip_get_transport_states()) + && (transport_state = ao2_callback(transport_states, 0, find_transport_state_in_use, rdata)) + && (transport = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "transport", transport_state->id)) + && !ast_strlen_zero(transport->domain)) { + snprintf(id, sizeof(id), "anonymous@%s", transport->domain); + if ((endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", id))) { + return endpoint; + } } } /* Fall back to no domain */ - endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name); + return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", endpoint_name); +} -done: - if (endpoint) { - if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME)) { - ao2_ref(endpoint, -1); - return NULL; - } - ast_debug(3, "Retrieved endpoint %s\n", ast_sorcery_object_get_id(endpoint)); - } else { - ast_debug(3, "Could not identify endpoint by username '%s'\n", endpoint_name); +static struct ast_sip_endpoint *username_identify(pjsip_rx_data *rdata) +{ + char username[64], domain[64]; + struct ast_sip_endpoint *endpoint; + + if (get_from_header(rdata, username, sizeof(username), domain, sizeof(domain))) { + return NULL; + } + ast_debug(3, "Attempting identify by From username '%s' domain '%s'\n", username, domain); + + endpoint = find_endpoint(rdata, username, domain); + if (!endpoint) { + ast_debug(3, "Endpoint not found for From username '%s' domain '%s'\n", username, domain); + ao2_cleanup(endpoint); + return NULL; } + if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME)) { + ast_debug(3, "Endpoint found for '%s' but 'username' method not supported'\n", username); + ao2_cleanup(endpoint); + return NULL; + } + ast_debug(3, "Identified by From username '%s' domain '%s'\n", username, domain); + return endpoint; } +static struct ast_sip_endpoint *auth_username_identify(pjsip_rx_data *rdata) +{ + char username[64], realm[64]; + struct ast_sip_endpoint *endpoint; + pjsip_authorization_hdr *auth_header = NULL; + + while ((auth_header = get_auth_header(rdata, username, sizeof(username), realm, sizeof(realm), + auth_header ? auth_header->next : NULL))) { + ast_debug(3, "Attempting identify by Authorization username '%s' realm '%s'\n", username, + realm); + + endpoint = find_endpoint(rdata, username, realm); + if (!endpoint) { + ast_debug(3, "Endpoint not found for Authentication username '%s' realm '%s'\n", + username, realm); + ao2_cleanup(endpoint); + continue; + } + if (!(endpoint->ident_method & AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME)) { + ast_debug(3, "Endpoint found for '%s' but 'auth_username' method not supported'\n", + username); + ao2_cleanup(endpoint); + continue; + } + ast_debug(3, "Identified by Authorization username '%s' realm '%s'\n", username, realm); + + return endpoint; + } + + return NULL; +} + + static struct ast_sip_endpoint_identifier username_identifier = { .identify_endpoint = username_identify, }; +static struct ast_sip_endpoint_identifier auth_username_identifier = { + .identify_endpoint = auth_username_identify, +}; + + static int load_module(void) { CHECK_PJSIP_MODULE_LOADED(); ast_sip_register_endpoint_identifier_with_name(&username_identifier, "username"); + ast_sip_register_endpoint_identifier_with_name(&auth_username_identifier, "auth_username"); return AST_MODULE_LOAD_SUCCESS; } static int unload_module(void) { + ast_sip_unregister_endpoint_identifier(&auth_username_identifier); ast_sip_unregister_endpoint_identifier(&username_identifier); return 0; } @@ -132,5 +201,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP username endpoi .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, - .load_pri = AST_MODPRI_APP_DEPEND, + .load_pri = AST_MODPRI_CHANNEL_DEPEND - 4, ); diff --git a/res/res_pjsip_exten_state.c b/res/res_pjsip_exten_state.c index 4e225dd1a..69a458993 100644 --- a/res/res_pjsip_exten_state.c +++ b/res/res_pjsip_exten_state.c @@ -517,8 +517,8 @@ static int unload_module(void) } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Extension State Notifications", - .support_level = AST_MODULE_SUPPORT_CORE, - .load = load_module, - .unload = unload_module, - .load_pri = AST_MODPRI_CHANNEL_DEPEND, + .support_level = AST_MODULE_SUPPORT_CORE, + .load = load_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND + 5, ); diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c index 64d3c84ea..9eba335b5 100644 --- a/res/res_pjsip_mwi.c +++ b/res/res_pjsip_mwi.c @@ -1227,9 +1227,9 @@ static int unload_module(void) } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP MWI resource", - .support_level = AST_MODULE_SUPPORT_CORE, - .load = load_module, - .unload = unload_module, - .reload = reload, - .load_pri = AST_MODPRI_CHANNEL_DEPEND, + .support_level = AST_MODULE_SUPPORT_CORE, + .load = load_module, + .unload = unload_module, + .reload = reload, + .load_pri = AST_MODPRI_CHANNEL_DEPEND + 5, ); diff --git a/res/res_pjsip_outbound_publish.c b/res/res_pjsip_outbound_publish.c index 74b06c2ef..60f9bbb17 100644 --- a/res/res_pjsip_outbound_publish.c +++ b/res/res_pjsip_outbound_publish.c @@ -389,7 +389,7 @@ static void sip_outbound_publish_synchronize(struct ast_sip_event_publisher_hand } else { state->client->started = 1; } - } else if (state->client->started && !handler && removed && !strcmp(publish->event, removed->event_name)) { + } else if (!handler && removed && !strcmp(publish->event, removed->event_name)) { /* If the publisher client has been started but it is going away stop it */ removed->stop_publishing(state->client); state->client->started = 0; diff --git a/res/res_pjsip_publish_asterisk.c b/res/res_pjsip_publish_asterisk.c index c0d3b90cf..3218b0a0c 100644 --- a/res/res_pjsip_publish_asterisk.c +++ b/res/res_pjsip_publish_asterisk.c @@ -923,8 +923,8 @@ static int unload_module(void) } AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Asterisk Event PUBLISH Support", - .load = load_module, - .reload = reload_module, - .unload = unload_module, - .load_pri = AST_MODPRI_CHANNEL_DEPEND, + .load = load_module, + .reload = reload_module, + .unload = unload_module, + .load_pri = AST_MODPRI_CHANNEL_DEPEND + 5, ); diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index 670047567..7ed804acf 100644 --- a/res/res_pjsip_pubsub.c +++ b/res/res_pjsip_pubsub.c @@ -2541,20 +2541,28 @@ void ast_sip_unregister_subscription_handler(struct ast_sip_subscription_handler AST_RWLIST_TRAVERSE_SAFE_END; } -static struct ast_sip_pubsub_body_generator *find_body_generator_type_subtype(const char *content_type, - const char *content_subtype) +static struct ast_sip_pubsub_body_generator *find_body_generator_type_subtype_nolock(const char *type, const char *subtype) { - struct ast_sip_pubsub_body_generator *iter; - SCOPED_LOCK(lock, &body_generators, AST_RWLIST_RDLOCK, AST_RWLIST_UNLOCK); + struct ast_sip_pubsub_body_generator *gen; - AST_LIST_TRAVERSE(&body_generators, iter, list) { - if (!strcmp(iter->type, content_type) && - !strcmp(iter->subtype, content_subtype)) { + AST_LIST_TRAVERSE(&body_generators, gen, list) { + if (!strcmp(gen->type, type) + && !strcmp(gen->subtype, subtype)) { break; } - }; + } - return iter; + return gen; +} + +static struct ast_sip_pubsub_body_generator *find_body_generator_type_subtype(const char *type, const char *subtype) +{ + struct ast_sip_pubsub_body_generator *gen; + + AST_RWLIST_RDLOCK(&body_generators); + gen = find_body_generator_type_subtype_nolock(type, subtype); + AST_RWLIST_UNLOCK(&body_generators); + return gen; } static struct ast_sip_pubsub_body_generator *find_body_generator_accept(const char *accept) @@ -3092,14 +3100,14 @@ int ast_sip_pubsub_register_body_generator(struct ast_sip_pubsub_body_generator pj_str_t accept; pj_size_t accept_len; - existing = find_body_generator_type_subtype(generator->type, generator->subtype); + AST_RWLIST_WRLOCK(&body_generators); + existing = find_body_generator_type_subtype_nolock(generator->type, generator->subtype); if (existing) { - ast_log(LOG_WARNING, "Cannot register body generator of %s/%s." - "One is already registered.\n", generator->type, generator->subtype); + AST_RWLIST_UNLOCK(&body_generators); + ast_log(LOG_WARNING, "A body generator for %s/%s is already registered.\n", + generator->type, generator->subtype); return -1; } - - AST_RWLIST_WRLOCK(&body_generators); AST_LIST_INSERT_HEAD(&body_generators, generator, list); AST_RWLIST_UNLOCK(&body_generators); @@ -3179,14 +3187,15 @@ int ast_sip_pubsub_generate_body_content(const char *type, const char *subtype, } if (strcmp(data->body_type, generator->body_type)) { - ast_log(LOG_WARNING, "Body generator does not accept the type of data provided\n"); + ast_log(LOG_WARNING, "%s/%s body generator does not accept the type of data provided\n", + type, subtype); return -1; } body = generator->allocate_body(data->body_data); if (!body) { - ast_log(LOG_WARNING, "Unable to allocate a NOTIFY body of type %s/%s\n", - type, subtype); + ast_log(LOG_WARNING, "%s/%s body generator could not to allocate a body\n", + type, subtype); return -1; } diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c index 776700490..a8144fc05 100644 --- a/res/res_pjsip_registrar.c +++ b/res/res_pjsip_registrar.c @@ -30,6 +30,7 @@ #include "asterisk/res_pjsip.h" #include "asterisk/module.h" +#include "asterisk/paths.h" #include "asterisk/test.h" #include "asterisk/taskprocessor.h" #include "asterisk/manager.h" @@ -555,6 +556,9 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co if (user_agent) { ast_string_field_set(contact_update, user_agent, user_agent); } + if (!ast_strlen_zero(ast_config_AST_SYSTEM_NAME)) { + ast_string_field_set(contact_update, reg_server, ast_config_AST_SYSTEM_NAME); + } if (ast_sip_location_update_contact(contact_update)) { ast_log(LOG_ERROR, "Failed to update contact '%s' expiration time to %d seconds.\n", @@ -576,7 +580,6 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co ao2_cleanup(contact_update); } else { /* We want to report the user agent that was actually in the removed contact */ - user_agent = ast_strdupa(contact->user_agent); ast_sip_location_delete_contact(contact); ast_verb(3, "Removed contact '%s' from AOR '%s' due to request\n", contact_uri, aor_name); ast_test_suite_event_notify("AOR_CONTACT_REMOVED", @@ -585,7 +588,7 @@ static int rx_task_core(struct rx_task_data *task_data, struct ao2_container *co "UserAgent: %s", contact_uri, aor_name, - user_agent); + contact->user_agent); } } @@ -658,6 +661,65 @@ static int rx_task(void *data) return res; } +static int match_aor(const char *aor_name, const char *id) +{ + if (ast_strlen_zero(aor_name)) { + return 0; + } + + if (!strcmp(aor_name, id)) { + ast_debug(3, "Matched id '%s' to aor '%s'\n", id, aor_name); + return 1; + } + + return 0; +} + +static char *find_aor_name(const char *username, const char *domain, const char *aors) +{ + char *configured_aors; + char *aor_name; + char *id_domain; + struct ast_sip_domain_alias *alias; + + id_domain = ast_alloca(strlen(username) + strlen(domain) + 2); + sprintf(id_domain, "%s@%s", username, domain); + + /* Look for exact match on username@domain */ + configured_aors = ast_strdupa(aors); + while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) { + if (match_aor(aor_name, id_domain)) { + return ast_strdup(aor_name); + } + } + + /* If there's a domain alias, look for exact match on username@domain_alias */ + alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain); + if (alias) { + char *id_domain_alias = ast_alloca(strlen(username) + strlen(alias->domain) + 2); + + sprintf(id_domain, "%s@%s", username, alias->domain); + ao2_cleanup(alias); + + configured_aors = ast_strdupa(aors); + while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) { + if (match_aor(aor_name, id_domain_alias)) { + return ast_strdup(aor_name); + } + } + } + + /* Look for exact match on username only */ + configured_aors = ast_strdupa(aors); + while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) { + if (match_aor(aor_name, username)) { + return ast_strdup(aor_name); + } + } + + return NULL; +} + static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) { RAII_VAR(struct serializer *, ser, NULL, ao2_cleanup); @@ -666,10 +728,10 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) RAII_VAR(struct ast_sip_endpoint *, endpoint, ast_pjsip_rdata_get_endpoint(rdata), ao2_cleanup); RAII_VAR(struct ast_sip_aor *, aor, NULL, ao2_cleanup); - pjsip_sip_uri *uri; - char *domain_name; - char *configured_aors, *aor_name; - RAII_VAR(struct ast_str *, id, NULL, ast_free); + char *domain_name = NULL; + char *username = NULL; + RAII_VAR(char *, aor_name, NULL, ast_free); + int i; if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_register_method) || !endpoint) { return PJ_FALSE; @@ -690,38 +752,46 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) return PJ_TRUE; } - uri = pjsip_uri_get_uri(rdata->msg_info.to->uri); - domain_name = ast_alloca(uri->host.slen + 1); - ast_copy_pj_str(domain_name, &uri->host, uri->host.slen + 1); + for (i = 0; i < AST_VECTOR_SIZE(&endpoint->ident_method_order); i++) { + pjsip_sip_uri *uri; + pjsip_authorization_hdr *header = NULL; - configured_aors = ast_strdupa(endpoint->aors); - - /* Iterate the configured AORs to see if the user or the user+domain match */ - while ((aor_name = ast_strip(strsep(&configured_aors, ",")))) { - struct ast_sip_domain_alias *alias = NULL; + switch (AST_VECTOR_GET(&endpoint->ident_method_order, i)) { + case AST_SIP_ENDPOINT_IDENTIFY_BY_USERNAME : + uri = pjsip_uri_get_uri(rdata->msg_info.to->uri); - if (ast_strlen_zero(aor_name)) { - continue; - } + domain_name = ast_alloca(uri->host.slen + 1); + ast_copy_pj_str(domain_name, &uri->host, uri->host.slen + 1); + username = ast_alloca(uri->user.slen + 1); + ast_copy_pj_str(username, &uri->user, uri->user.slen + 1); - if (!pj_strcmp2(&uri->user, aor_name)) { + aor_name = find_aor_name(username, domain_name, endpoint->aors); + if (aor_name) { + ast_debug(3, "Matched aor '%s' by To username\n", aor_name); + } break; + case AST_SIP_ENDPOINT_IDENTIFY_BY_AUTH_USERNAME : + while ((header = pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_AUTHORIZATION, + header ? header->next : NULL))) { + if (header && !pj_stricmp2(&header->scheme, "digest")) { + username = ast_alloca(header->credential.digest.username.slen + 1); + ast_copy_pj_str(username, &header->credential.digest.username, header->credential.digest.username.slen + 1); + domain_name = ast_alloca(header->credential.digest.realm.slen + 1); + ast_copy_pj_str(domain_name, &header->credential.digest.realm, header->credential.digest.realm.slen + 1); + + aor_name = find_aor_name(username, domain_name, endpoint->aors); + if (aor_name) { + ast_debug(3, "Matched aor '%s' by Authentication username\n", aor_name); + break; + } + } + } + break; + default: + continue; } - if (!id && !(id = ast_str_create(uri->user.slen + uri->host.slen + 2))) { - return PJ_TRUE; - } - - ast_str_set(&id, 0, "%.*s@", (int)uri->user.slen, uri->user.ptr); - if ((alias = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "domain_alias", domain_name))) { - ast_str_append(&id, 0, "%s", alias->domain); - ao2_cleanup(alias); - } else { - ast_str_append(&id, 0, "%s", domain_name); - } - - if (!strcmp(aor_name, ast_str_buffer(id))) { - ast_free(id); + if (aor_name) { break; } } @@ -730,7 +800,7 @@ static pj_bool_t registrar_on_rx_request(struct pjsip_rx_data *rdata) /* The provided AOR name was not found (be it within the configuration or sorcery itself) */ pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata, 404, NULL, NULL, NULL); ast_sip_report_req_no_support(endpoint, rdata, "registrar_requested_aor_not_found"); - ast_log(LOG_WARNING, "AOR '%.*s' not found for endpoint '%s'\n", (int)uri->user.slen, uri->user.ptr, ast_sorcery_object_get_id(endpoint)); + ast_log(LOG_WARNING, "AOR '%s' not found for endpoint '%s'\n", username, ast_sorcery_object_get_id(endpoint)); return PJ_TRUE; } @@ -887,5 +957,5 @@ AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "PJSIP Registrar Suppo .support_level = AST_MODULE_SUPPORT_CORE, .load = load_module, .unload = unload_module, - .load_pri = AST_MODPRI_APP_DEPEND, + .load_pri = AST_MODPRI_CHANNEL_DEPEND - 3, ); diff --git a/res/res_pjsip_transport_management.c b/res/res_pjsip_transport_management.c index eb0240438..afd94eb1f 100644 --- a/res/res_pjsip_transport_management.c +++ b/res/res_pjsip_transport_management.c @@ -24,6 +24,8 @@ #include "asterisk.h" +#include <signal.h> + #include <pjsip.h> #include <pjsip_ua.h> @@ -93,7 +95,7 @@ static void *keepalive_transport_thread(void *data) /* Once loaded this module just keeps on going as it is unsafe to stop and change the underlying * callback for the transport manager. */ - while (1) { + while (keepalive_interval) { sleep(keepalive_interval); ao2_callback(transports, OBJ_NODATA, keepalive_transport_cb, NULL); } @@ -347,7 +349,19 @@ static int load_module(void) static int unload_module(void) { - /* This will never get called */ + pjsip_tpmgr *tpmgr = pjsip_endpt_get_tpmgr(ast_sip_get_pjsip_endpoint()); + + if (keepalive_interval) { + keepalive_interval = 0; + pthread_kill(keepalive_thread, SIGURG); + pthread_join(keepalive_thread, NULL); + } + + ast_sched_context_destroy(sched); + ao2_ref(transports, -1); + + ast_sip_unregister_service(&idle_monitor_module); + pjsip_tpmgr_set_state_cb(tpmgr, tpmgr_state_callback); return 0; } diff --git a/res/stasis/control.c b/res/stasis/control.c index 97b0b8809..b2b076b73 100644 --- a/res/stasis/control.c +++ b/res/stasis/control.c @@ -323,7 +323,7 @@ static int app_control_dial(struct stasis_app_control *control, AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) { ast_hangup(new_chan); } else { - control_add_channel_to_bridge(control, chan, bridge); + control_swap_channel_in_bridge(control, bridge, chan, NULL); } return 0; @@ -982,11 +982,8 @@ static void bridge_after_cb_failed(enum ast_bridge_after_cb_reason reason, ast_bridge_after_cb_reason_string(reason)); } -int control_add_channel_to_bridge( - struct stasis_app_control *control, - struct ast_channel *chan, void *data) +int control_swap_channel_in_bridge(struct stasis_app_control *control, struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap) { - struct ast_bridge *bridge = data; int res; if (!control || !bridge) { @@ -1039,7 +1036,7 @@ int control_add_channel_to_bridge( res = ast_bridge_impart(bridge, chan, - NULL, /* swap channel */ + swap, NULL, /* features */ AST_BRIDGE_IMPART_CHAN_DEPARTABLE); if (res != 0) { @@ -1055,6 +1052,11 @@ int control_add_channel_to_bridge( return 0; } +int control_add_channel_to_bridge(struct stasis_app_control *control, struct ast_channel *chan, void *data) +{ + return control_swap_channel_in_bridge(control, data, chan, NULL); +} + int stasis_app_control_add_channel_to_bridge( struct stasis_app_control *control, struct ast_bridge *bridge) { diff --git a/res/stasis/control.h b/res/stasis/control.h index 1d37a494a..868a8091b 100644 --- a/res/stasis/control.h +++ b/res/stasis/control.h @@ -111,12 +111,20 @@ struct stasis_app *control_app(struct stasis_app_control *control); * \brief Command callback for adding a channel to a bridge * * \param control The control for chan - * \param channel The channel on which commands should be executed - * \param bridge Data to be passed to the callback + * \param chan The channel on which commands should be executed + * \param data Bridge to be passed to the callback + */ +int control_add_channel_to_bridge(struct stasis_app_control *control, struct ast_channel *chan, void *data); + +/*! + * \brief Command for swapping a channel in a bridge + * + * \param control The control for chan + * \param chan The channel on which commands should be executed + * \param bridge Bridge to be passed to the callback + * \param swap Channel to swap with when joining the bridge */ -int control_add_channel_to_bridge( - struct stasis_app_control *control, - struct ast_channel *chan, void *obj); +int control_swap_channel_in_bridge(struct stasis_app_control *control, struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap); /*! * \brief Stop playing silence to a channel right now. diff --git a/res/stasis/stasis_bridge.c b/res/stasis/stasis_bridge.c index e41088134..9ffc2d7be 100644 --- a/res/stasis/stasis_bridge.c +++ b/res/stasis/stasis_bridge.c @@ -76,24 +76,54 @@ static void bridge_stasis_run_cb(struct ast_channel *chan, void *data) pbx_exec(chan, app_stasis, app_name); } -static int add_channel_to_bridge( +struct defer_bridge_add_obj { + /*! Bridge to join (has ref) */ + struct ast_bridge *bridge; + /*! + * \brief Channel to swap with in the bridge. (has ref) + * + * \note NULL if not swapping with a channel. + */ + struct ast_channel *swap; +}; + +static void defer_bridge_add_dtor(void *obj) +{ + struct defer_bridge_add_obj *defer = obj; + + ao2_cleanup(defer->bridge); + ast_channel_cleanup(defer->swap); +} + +static int defer_bridge_add( struct stasis_app_control *control, struct ast_channel *chan, void *obj) { - struct ast_bridge *bridge = obj; - int res; + struct defer_bridge_add_obj *defer = obj; - res = control_add_channel_to_bridge(control, - chan, bridge); - return res; + return control_swap_channel_in_bridge(control, defer->bridge, chan, defer->swap); } static void bridge_stasis_queue_join_action(struct ast_bridge *self, - struct ast_bridge_channel *bridge_channel) + struct ast_bridge_channel *bridge_channel, struct ast_bridge_channel *swap) { + struct defer_bridge_add_obj *defer; + + defer = ao2_alloc_options(sizeof(*defer), defer_bridge_add_dtor, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!defer) { + return; + } + ao2_ref(self, +1); + defer->bridge = self; + if (swap) { + ast_channel_ref(swap->chan); + defer->swap = swap->chan; + } + ast_channel_lock(bridge_channel->chan); - command_prestart_queue_command(bridge_channel->chan, add_channel_to_bridge, - ao2_bump(self), __ao2_cleanup); + command_prestart_queue_command(bridge_channel->chan, defer_bridge_add, + defer, __ao2_cleanup); ast_channel_unlock(bridge_channel->chan); } @@ -167,18 +197,19 @@ static int bridge_stasis_push(struct ast_bridge *self, struct ast_bridge_channel if (!control && !stasis_app_channel_is_internal(bridge_channel->chan)) { /* channel not in Stasis(), get it there */ + ast_debug(1, "Bridge %s: pushing non-stasis %p(%s) setup to come back in under stasis\n", + self->uniqueid, bridge_channel, ast_channel_name(bridge_channel->chan)); + /* Attach after-bridge callback and pass ownership of swap_app to it */ if (ast_bridge_set_after_callback(bridge_channel->chan, bridge_stasis_run_cb, NULL, NULL)) { - ast_log(LOG_ERROR, "Failed to set after bridge callback\n"); + ast_log(LOG_ERROR, + "Failed to set after bridge callback for bridge %s non-stasis push of %s\n", + self->uniqueid, ast_channel_name(bridge_channel->chan)); return -1; } - bridge_stasis_queue_join_action(self, bridge_channel); - if (swap) { - /* nudge the swap channel out of the bridge */ - ast_bridge_channel_leave_bridge(swap, BRIDGE_CHANNEL_STATE_END_NO_DISSOLVE, 0); - } + bridge_stasis_queue_join_action(self, bridge_channel, swap); /* Return -1 so the push fails and the after-bridge callback gets called * This keeps the bridging framework from putting the channel into the bridge diff --git a/tests/test_channel_feature_hooks.c b/tests/test_channel_feature_hooks.c index fbc9786cc..c5d3b9b86 100644 --- a/tests/test_channel_feature_hooks.c +++ b/tests/test_channel_feature_hooks.c @@ -40,6 +40,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/bridge.h" #include "asterisk/bridge_basic.h" #include "asterisk/features.h" +#include "asterisk/format_cache.h" #define TEST_CATEGORY "/channels/features/" @@ -47,6 +48,8 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #define TEST_BACKEND_NAME "Features Test Logging" +#define TEST_CHANNEL_FORMAT ast_format_slin + /*! \brief A channel technology used for the unit tests */ static struct ast_channel_tech test_features_chan_tech = { .type = CHANNEL_TECH_NAME, @@ -94,6 +97,11 @@ static void wait_for_unbridged(struct ast_channel *channel) #define START_CHANNEL(channel, name, number) do { \ channel = ast_channel_alloc(0, AST_STATE_UP, number, name, number, number, \ "default", NULL, NULL, 0, CHANNEL_TECH_NAME "/" name); \ + ast_channel_nativeformats_set(channel, test_features_chan_tech.capabilities); \ + ast_channel_set_rawwriteformat(channel, TEST_CHANNEL_FORMAT); \ + ast_channel_set_rawreadformat(channel, TEST_CHANNEL_FORMAT); \ + ast_channel_set_writeformat(channel, TEST_CHANNEL_FORMAT); \ + ast_channel_set_readformat(channel, TEST_CHANNEL_FORMAT); \ ast_channel_unlock(channel); \ } while (0) @@ -329,12 +337,19 @@ static int unload_module(void) AST_TEST_UNREGISTER(test_features_channel_interval); ast_channel_unregister(&test_features_chan_tech); + ao2_cleanup(test_features_chan_tech.capabilities); + test_features_chan_tech.capabilities = NULL; return 0; } static int load_module(void) { + test_features_chan_tech.capabilities = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!test_features_chan_tech.capabilities) { + return AST_MODULE_LOAD_FAILURE; + } + ast_format_cap_append(test_features_chan_tech.capabilities, TEST_CHANNEL_FORMAT, 0); ast_channel_register(&test_features_chan_tech); AST_TEST_REGISTER(test_features_channel_dtmf); diff --git a/tests/test_config.c b/tests/test_config.c index 40de2652b..fd14908b6 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -34,6 +34,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); #include <math.h> /* HUGE_VAL */ +#include <sys/stat.h> #include "asterisk/config.h" #include "asterisk/module.h" @@ -49,6 +50,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); #include "asterisk/format_cap.h" #define CONFIG_FILE "test_config.conf" +#define CONFIG_INCLUDE_FILE "test_config_include.conf" /* * This builds the folowing config: @@ -881,6 +883,77 @@ static int hook_cb(struct ast_config *cfg) return 0; } +AST_TEST_DEFINE(config_save) +{ + enum ast_test_result_state res = AST_TEST_FAIL; + struct ast_flags config_flags = { 0 }; + struct ast_config *cfg; + char config_filename[PATH_MAX]; + char include_filename[PATH_MAX]; + struct stat config_stat; + off_t before_save; + + switch (cmd) { + case TEST_INIT: + info->name = "config_save"; + info->category = "/main/config/"; + info->summary = "Test config save"; + info->description = + "Test configuration save."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (write_config_file()) { + ast_test_status_update(test, "Could not write initial config files\n"); + return res; + } + + snprintf(config_filename, PATH_MAX, "%s/%s", ast_config_AST_CONFIG_DIR, CONFIG_FILE); + snprintf(include_filename, PATH_MAX, "%s/%s", ast_config_AST_CONFIG_DIR, CONFIG_INCLUDE_FILE); + + cfg = ast_config_load(CONFIG_FILE, config_flags); + if (!cfg) { + ast_test_status_update(test, "Could not load config\n"); + goto out; + } + + /* We need to re-save to get the generator header */ + if (ast_config_text_file_save(CONFIG_FILE, cfg, "TEST")) { + ast_test_status_update(test, "Unable to write files\n"); + goto out; + } + + stat(config_filename, &config_stat); + before_save = config_stat.st_size; + + if (!ast_include_new(cfg, CONFIG_FILE, CONFIG_INCLUDE_FILE, 0, NULL, 4, include_filename, PATH_MAX)) { + ast_test_status_update(test, "Could not create include\n"); + goto out; + } + + if (ast_config_text_file_save(CONFIG_FILE, cfg, "TEST")) { + ast_test_status_update(test, "Unable to write files\n"); + goto out; + } + + stat(config_filename, &config_stat); + if (config_stat.st_size <= before_save) { + ast_test_status_update(test, "Did not save config file with #include\n"); + goto out; + } + + res = AST_TEST_PASS; + +out: + ast_config_destroy(cfg); + unlink(config_filename); + unlink(include_filename); + + return res; +} + AST_TEST_DEFINE(config_hook) { enum ast_test_result_state res = AST_TEST_FAIL; @@ -1734,6 +1807,7 @@ AST_TEST_DEFINE(variable_lists_match) static int unload_module(void) { + AST_TEST_UNREGISTER(config_save); AST_TEST_UNREGISTER(config_basic_ops); AST_TEST_UNREGISTER(config_filtered_ops); AST_TEST_UNREGISTER(config_template_ops); @@ -1748,6 +1822,7 @@ static int unload_module(void) static int load_module(void) { + AST_TEST_REGISTER(config_save); AST_TEST_REGISTER(config_basic_ops); AST_TEST_REGISTER(config_filtered_ops); AST_TEST_REGISTER(config_template_ops); diff --git a/tests/test_message.c b/tests/test_message.c index f7ee02730..f73901ea6 100644 --- a/tests/test_message.c +++ b/tests/test_message.c @@ -232,8 +232,8 @@ static int user_event_hook_cb(int category, const char *event, char *body) static int handler_wait_for_message(struct ast_test *test) { int error = 0; - struct timeval wait_now = ast_tvnow(); - struct timespec wait_time = { .tv_sec = wait_now.tv_sec + 1, .tv_nsec = wait_now.tv_usec * 1000 }; + struct timeval wait = ast_tvadd(ast_tvnow(), ast_tv(5 /* seconds */, 0)); + struct timespec wait_time = { .tv_sec = wait.tv_sec, .tv_nsec = wait.tv_usec * 1000 }; ast_mutex_lock(&handler_lock); while (!handler_received_message) { @@ -253,8 +253,8 @@ static int handler_wait_for_message(struct ast_test *test) static int user_event_wait_for_events(struct ast_test *test, int expected_events) { int error; - struct timeval wait_now = ast_tvnow(); - struct timespec wait_time = { .tv_sec = wait_now.tv_sec + 1, .tv_nsec = wait_now.tv_usec * 1000 }; + struct timeval wait = ast_tvadd(ast_tvnow(), ast_tv(5 /* seconds */, 0)); + struct timespec wait_time = { .tv_sec = wait.tv_sec, .tv_nsec = wait.tv_usec * 1000 }; expected_user_events = expected_events; diff --git a/tests/test_res_pjsip_scheduler.c b/tests/test_res_pjsip_scheduler.c new file mode 100644 index 000000000..f9a1633ac --- /dev/null +++ b/tests/test_res_pjsip_scheduler.c @@ -0,0 +1,400 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2016, Fairview 5 Engineering, LLC + * + * George Joseph <george.joseph@fairview5.com> + * + * 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 res_pjsip scheduler tests + * + * \author George Joseph <george.joseph@fairview5.com> + * + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <depend>res_pjsip</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_REGISTER_FILE() + +#include <pjsip.h> +#include "asterisk/test.h" +#include "asterisk/module.h" +#include "asterisk/taskprocessor.h" +#include "asterisk/res_pjsip.h" +#include "asterisk/utils.h" + +#define CATEGORY "/res/res_pjsip/scheduler/" + +struct test_data { + ast_mutex_t lock; + ast_cond_t cond; + pthread_t tid; + struct timeval test_start; + struct timeval task_start; + struct timeval task_end; + int is_servant; + int interval; + int sleep; + int done; + struct ast_test *test; +}; + +#define S2U(x) (long int)(x * 1000 * 1000) +#define M2U(x) (long int)(x * 1000) + +static int task_1(void *data) +{ + struct test_data *test = data; + + test->done = 0; + test->task_start = ast_tvnow(); + test->tid = pthread_self(); + test->is_servant = ast_sip_thread_is_servant(); + usleep(M2U(test->sleep)); + test->task_end = ast_tvnow(); + + ast_mutex_lock(&test->lock); + test->done = 1; + ast_mutex_unlock(&test->lock); + ast_cond_signal(&test->cond); + + return test->interval; +} + + +static void data_cleanup(void *data) +{ + struct test_data *test_data = data; + ast_mutex_destroy(&test_data->lock); + ast_cond_destroy(&test_data->cond); +} + +#define waitfor(x) \ +{ \ + ast_mutex_lock(&(x)->lock); \ + while (!(x)->done) { \ + ast_cond_wait(&(x)->cond, &(x)->lock); \ + } \ + (x)->done = 0; \ + ast_mutex_unlock(&(x)->lock); \ +} + +static int scheduler(struct ast_test *test, int serialized) +{ + RAII_VAR(struct ast_taskprocessor *, tp1, NULL, ast_taskprocessor_unreference); + RAII_VAR(struct test_data *, test_data1, ao2_alloc(sizeof(*test_data1), data_cleanup), ao2_cleanup); + RAII_VAR(struct test_data *, test_data2, ao2_alloc(sizeof(*test_data2), data_cleanup), ao2_cleanup); + RAII_VAR(struct ast_sip_sched_task *, task1, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_sched_task *, task2, NULL, ao2_cleanup); + int duration; + int delay; + struct timeval task1_start; + + ast_test_validate(test, test_data1 != NULL); + ast_test_validate(test, test_data2 != NULL); + + test_data1->test = test; + test_data1->test_start = ast_tvnow(); + test_data1->interval = 2000; + test_data1->sleep = 1000; + ast_mutex_init(&test_data1->lock); + ast_cond_init(&test_data1->cond, NULL); + + test_data2->test = test; + test_data2->test_start = ast_tvnow(); + test_data2->interval = 2000; + test_data2->sleep = 1000; + ast_mutex_init(&test_data2->lock); + ast_cond_init(&test_data2->cond, NULL); + + if (serialized) { + ast_test_status_update(test, "This test will take about %3.1f seconds\n", + (test_data1->interval + test_data1->sleep + (MAX(test_data1->interval - test_data2->interval, 0)) + test_data2->sleep) / 1000.0); + tp1 = ast_sip_create_serializer(); + ast_test_validate(test, (tp1 != NULL)); + } else { + ast_test_status_update(test, "This test will take about %3.1f seconds\n", + ((MAX(test_data1->interval, test_data2->interval) + MAX(test_data1->sleep, test_data2->sleep)) / 1000.0)); + } + + task1 = ast_sip_schedule_task(tp1, test_data1->interval, task_1, NULL, test_data1, AST_SIP_SCHED_TASK_FIXED); + ast_test_validate(test, task1 != NULL); + + task2 = ast_sip_schedule_task(tp1, test_data2->interval, task_1, NULL, test_data2, AST_SIP_SCHED_TASK_FIXED); + ast_test_validate(test, task2 != NULL); + + waitfor(test_data1); + ast_sip_sched_task_cancel(task1); + ast_test_validate(test, test_data1->is_servant); + + duration = ast_tvdiff_ms(test_data1->task_end, test_data1->test_start); + ast_test_validate(test, (duration > ((test_data1->interval + test_data1->sleep) * 0.9)) + && (duration < ((test_data1->interval + test_data1->sleep) * 1.1))); + + ast_sip_sched_task_get_times(task1, NULL, &task1_start, NULL); + delay = ast_tvdiff_ms(task1_start, test_data1->test_start); + ast_test_validate(test, (delay > (test_data1->interval * 0.9) + && (delay < (test_data1->interval * 1.1)))); + + waitfor(test_data2); + ast_sip_sched_task_cancel(task2); + ast_test_validate(test, test_data2->is_servant); + + if (serialized) { + ast_test_validate(test, test_data1->tid == test_data2->tid); + ast_test_validate(test, ast_tvdiff_ms(test_data2->task_start, test_data1->task_end) >= 0); + } else { + ast_test_validate(test, test_data1->tid != test_data2->tid); + } + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(serialized_scheduler) +{ + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = CATEGORY; + info->summary = "Test res_pjsip serialized scheduler"; + info->description = "Test res_pjsip serialized scheduler"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return scheduler(test, 1); +} + +AST_TEST_DEFINE(unserialized_scheduler) +{ + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = CATEGORY; + info->summary = "Test res_pjsip unserialized scheduler"; + info->description = "Test res_pjsip unserialized scheduler"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return scheduler(test, 0); +} + +static int run_count; +static int destruct_count; + +static int dummy_task(void *data) +{ + int *sleep = data; + + usleep(M2U(*sleep)); + run_count++; + + return 0; +} + +static void test_destructor(void *data) +{ + destruct_count++; +} + +AST_TEST_DEFINE(scheduler_cleanup) +{ + RAII_VAR(int *, sleep, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_sched_task *, task, NULL, ao2_cleanup); + int interval; + int when; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = CATEGORY; + info->summary = "Test res_pjsip scheduler cleanup"; + info->description = "Test res_pjsip scheduler cleanup"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + destruct_count = 0; + interval = 1000; + + sleep = ao2_alloc(sizeof(*sleep), test_destructor); + ast_test_validate(test, sleep != NULL); + *sleep = 500; + + ast_test_status_update(test, "This test will take about %3.1f seconds\n", + ((interval * 1.1) + *sleep) / 1000.0); + + task = ast_sip_schedule_task(NULL, interval, dummy_task, "dummy", sleep, + AST_SIP_SCHED_TASK_DATA_AO2 | AST_SIP_SCHED_TASK_DATA_FREE); + ast_test_validate(test, task != NULL); + usleep(M2U(interval * 0.5)); + when = ast_sip_sched_task_get_next_run(task); + ast_test_validate(test, (when > (interval * 0.4) && when < (interval * 0.6))); + usleep(M2U(interval * 0.6)); + ast_test_validate(test, ast_sip_sched_is_task_running(task)); + + usleep(M2U(*sleep)); + + ast_test_validate(test, (ast_sip_sched_is_task_running(task) == 0)); + when = ast_sip_sched_task_get_next_run(task); + ast_test_validate(test, (when < 0), res, error); + ast_test_validate(test, (ao2_ref(task, 0) == 1)); + ao2_ref(task, -1); + task = NULL; + ast_test_validate(test, (destruct_count == 1)); + sleep = NULL; + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(scheduler_cancel) +{ + RAII_VAR(int *, sleep, NULL, ao2_cleanup); + RAII_VAR(struct ast_sip_sched_task *, task, NULL, ao2_cleanup); + int interval; + int when; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = CATEGORY; + info->summary = "Test res_pjsip scheduler cancel task"; + info->description = "Test res_pjsip scheduler cancel task"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + destruct_count = 0; + interval = 1000; + + sleep = ao2_alloc(sizeof(*sleep), test_destructor); + ast_test_validate(test, sleep != NULL); + *sleep = 500; + + ast_test_status_update(test, "This test will take about %3.1f seconds\n", + (interval + *sleep) / 1000.0); + + task = ast_sip_schedule_task(NULL, interval, dummy_task, "dummy", sleep, AST_SIP_SCHED_TASK_DATA_NO_CLEANUP); + ast_test_validate(test, task != NULL); + + usleep(M2U(interval * 0.5)); + when = ast_sip_sched_task_get_next_run_by_name("dummy"); + ast_test_validate(test, (when > (interval * 0.4) && when < (interval * 0.6))); + ast_test_validate(test, !ast_sip_sched_is_task_running_by_name("dummy")); + ast_test_validate(test, ao2_ref(task, 0) == 2); + + ast_sip_sched_task_cancel_by_name("dummy"); + + when = ast_sip_sched_task_get_next_run(task); + ast_test_validate(test, when < 0); + + usleep(M2U(interval)); + ast_test_validate(test, run_count == 0); + ast_test_validate(test, destruct_count == 0); + ast_test_validate(test, ao2_ref(task, 0) == 1); + + return AST_TEST_PASS; +} + +AST_TEST_DEFINE(scheduler_policy) +{ + RAII_VAR(struct test_data *, test_data1, ao2_alloc(sizeof(*test_data1), data_cleanup), ao2_cleanup); + RAII_VAR(struct ast_sip_sched_task *, task, NULL, ao2_cleanup); + int when; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = CATEGORY; + info->summary = "Test res_pjsip scheduler cancel task"; + info->description = "Test res_pjsip scheduler cancel task"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_validate(test, test_data1 != NULL); + + destruct_count = 0; + run_count = 0; + test_data1->test = test; + test_data1->test_start = ast_tvnow(); + test_data1->interval = 1000; + test_data1->sleep = 500; + ast_mutex_init(&test_data1->lock); + ast_cond_init(&test_data1->cond, NULL); + + ast_test_status_update(test, "This test will take about %3.1f seconds\n", + ((test_data1->interval * 3) + test_data1->sleep) / 1000.0); + + task = ast_sip_schedule_task(NULL, test_data1->interval, task_1, "test_1", test_data1, + AST_SIP_SCHED_TASK_DATA_NO_CLEANUP | AST_SIP_SCHED_TASK_PERIODIC); + ast_test_validate(test, task != NULL); + + waitfor(test_data1); + when = ast_tvdiff_ms(test_data1->task_start, test_data1->test_start); + ast_test_validate(test, when > test_data1->interval * 0.9 && when < test_data1->interval * 1.1); + + waitfor(test_data1); + when = ast_tvdiff_ms(test_data1->task_start, test_data1->test_start); + ast_test_validate(test, when > test_data1->interval * 2 * 0.9 && when < test_data1->interval * 2 * 1.1); + + waitfor(test_data1); + when = ast_tvdiff_ms(test_data1->task_start, test_data1->test_start); + ast_test_validate(test, when > test_data1->interval * 3 * 0.9 && when < test_data1->interval * 3 * 1.1); + + ast_sip_sched_task_cancel(task); + ao2_ref(task, -1); + task = NULL; + + return AST_TEST_PASS; +} + +static int load_module(void) +{ + CHECK_PJSIP_MODULE_LOADED(); + + AST_TEST_REGISTER(serialized_scheduler); + AST_TEST_REGISTER(unserialized_scheduler); + AST_TEST_REGISTER(scheduler_cleanup); + AST_TEST_REGISTER(scheduler_cancel); + AST_TEST_REGISTER(scheduler_policy); + return AST_MODULE_LOAD_SUCCESS; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(scheduler_cancel); + AST_TEST_UNREGISTER(scheduler_cleanup); + AST_TEST_UNREGISTER(unserialized_scheduler); + AST_TEST_UNREGISTER(serialized_scheduler); + AST_TEST_UNREGISTER(scheduler_policy); + return 0; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "res_pjsip scheduler test module"); diff --git a/third-party/pjproject/Makefile b/third-party/pjproject/Makefile index 5810a65dc..7349db62f 100644 --- a/third-party/pjproject/Makefile +++ b/third-party/pjproject/Makefile @@ -45,6 +45,13 @@ ifeq ($(SPECIAL_TARGETS),) CF := $(filter-out -I%,$(CF)) export CFLAGS += $(CF) export LDFLAGS += $(CC_LDFLAGS) + TARGETS := pjproject.symbols + ifeq ($(findstring TEST_FRAMEWORK,$(MENUSELECT_CFLAGS)),TEST_FRAMEWORK) + TARGETS += source/pjsip-apps/bin/pjsua-$(TARGET_NAME) + ifneq ($(PYTHONDEV_LIB),) + TARGETS += source/pjsip-apps/src/python/build/_pjsua.so + endif + endif else all install: endif @@ -95,12 +102,10 @@ source/pjlib/build/.pjlib-$(TARGET_NAME).depend: build.mak $(ECHO_PREFIX) "Making dependencies" +$(CMD_PREFIX) $(SUBMAKE) -C source dep - menuselect: ../../menuselect.makeopts ../../makeopts -$(CMD_PREFIX) test -d source && ($(SUBMAKE) -C source clean ; find source -name *.a -delete ; rm -rf source/pjsip-apps/src/python/build) || : -$(CMD_PREFIX) rm -rf pjproject.symbols - source/pjlib/lib/libpj-$(TARGET_NAME).a: menuselect source/pjlib/build/.pjlib-$(TARGET_NAME).depend $(ECHO_PREFIX) Compiling libs +$(CMD_PREFIX) $(SUBMAKE) -C source lib $(REALLY_QUIET) @@ -115,20 +120,24 @@ source/pjsip-apps/bin/pjsua-$(TARGET_NAME): source/pjlib/lib/libpj-$(TARGET_NAME source/pjsip-apps/src/python/build/_pjsua.so: source/pjlib/lib/libpj-$(TARGET_NAME).a $(ECHO_PREFIX) Compiling python bindings - $(CMD_PREFIX) (cd source/pjsip-apps/src/python ; python setup.py build --build-platlib=./build $(REALLY_QUIET)) - + $(CMD_PREFIX) (cd source/pjsip-apps/src/python ; MAKE=$(MAKE) python setup.py build --build-platlib=./build $(REALLY_QUIET)) -_all: pjproject.symbols source/pjsip-apps/bin/pjsua-$(TARGET_NAME) source/pjsip-apps/src/python/build/_pjsua.so +_all: $(TARGETS) _install: _all - $(ECHO_PREFIX) Installing apps and python bindings @if [ ! -d "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject" ]; then \ $(INSTALL) -d "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject"; \ fi; +ifneq ($(findstring source/pjsip-apps/bin/pjsua-$(TARGET_NAME),$(TARGETS)),) + $(ECHO_PREFIX) Installing apps $(CMD_PREFIX) $(INSTALL) -m 755 source/pjsip-apps/bin/pjsua-$(TARGET_NAME) "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/pjsua" $(CMD_PREFIX) $(INSTALL) -m 755 source/pjsip-apps/bin/pjsystest-$(TARGET_NAME) "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/pjsystest" +endif +ifneq ($(findstring source/pjsip-apps/src/python/build/_pjsua.so,$(TARGETS)),) + $(ECHO_PREFIX) Installing python bindings $(CMD_PREFIX) $(INSTALL) -m 755 source/pjsip-apps/src/python/build/_pjsua.so "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/" $(CMD_PREFIX) $(INSTALL) -m 644 source/pjsip-apps/src/python/build/pjsua.py "$(DESTDIR)$(ASTDATADIR)/third-party/pjproject/" +endif uninstall: $(ECHO_PREFIX) Uninstalling apps and python bindings @@ -136,7 +145,7 @@ uninstall: clean: $(ECHO_PREFIX) Cleaning - -$(CMD_PREFIX) test -d source && ($(SUBMAKE) -C source clean ; find source -name *.a -delete ; rm -rf source/pjsip-apps/src/python/build) || : + -$(CMD_PREFIX) test -d source && ($(SUBMAKE) -C source clean ; find source -name *.a -delete ; rm -rf source/pjsip-apps/src/python/build ; rm -rf source/pjsip-apps/bin/* ) || : -$(CMD_PREFIX) rm -rf pjproject.symbols distclean: diff --git a/third-party/pjproject/Makefile.rules b/third-party/pjproject/Makefile.rules index 062793f12..d2e7d25a4 100644 --- a/third-party/pjproject/Makefile.rules +++ b/third-party/pjproject/Makefile.rules @@ -1,7 +1,10 @@ PJPROJECT_URL = http://www.pjsip.org/release/$(PJPROJECT_VERSION) # Even though we're not installing pjproject, we're setting prefix to /opt/pjproject to be safe -PJPROJECT_CONFIG_OPTS = --prefix=/opt/pjproject --with-external-speex --with-external-gsm --with-external-srtp \ - --disable-video --disable-v4l2 --disable-sound --disable-opencore-amr --disable-ilbc-codec \ - --without-libyuv --disable-g7221-codec \ - --enable-epoll +PJPROJECT_CONFIG_OPTS = --prefix=/opt/pjproject --disable-speex-codec --disable-speex-aec \ + --disable-gsm-codec --disable-video --disable-v4l2 --disable-sound --disable-opencore-amr \ + --disable-ilbc-codec --without-libyuv --disable-g7221-codec --disable-resample + +ifeq ($(shell uname -s),Linux) + PJPROJECT_CONFIG_OPTS += --enable-epoll +endif diff --git a/third-party/pjproject/apply_patches b/third-party/pjproject/apply_patches index 1b72d14b0..5dfdd2a3c 100755 --- a/third-party/pjproject/apply_patches +++ b/third-party/pjproject/apply_patches @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh if [ "$1" = "-q" ] ; then quiet=1 @@ -27,7 +27,7 @@ if [ ! "$(ls -A $patchdir/*.patch 2>/dev/null)" ] ; then fi for patchfile in $patchdir/*.patch ; do - patch -d $sourcedir -p1 -s -r- -f -N --dry-run -i "$patchfile" || (echo "Patchfile $(basename $patchfile) failed to apply >&2" ; exit 1) || exit 1 + patch -d $sourcedir -p1 -s -r- -f -N --dry-run -i "$patchfile" || (echo "Patchfile $(basename $patchfile) failed to apply" >&2 ; exit 1) || exit 1 done for patchfile in "$patchdir"/*.patch ; do diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4 index 743222774..2cc18bfa8 100644 --- a/third-party/pjproject/configure.m4 +++ b/third-party/pjproject/configure.m4 @@ -21,14 +21,14 @@ AC_DEFUN([PJPROJECT_CONFIGURE], [ AC_MSG_CHECKING(for embedded pjproject (may have to download)) AC_MSG_RESULT(configuring) - make --quiet --no-print-directory -C $1 configure + ${GNU_MAKE} --quiet --no-print-directory -C $1 configure if test $? -ne 0 ; then AC_MSG_RESULT(failed) AC_MSG_NOTICE(Unable to configure $1) - AC_MSG_ERROR(Run "make -C $1 NOISY_BUILD=yes configure" to see error details.) + AC_MSG_ERROR(Run "${GNU_MAKE} -C $1 NOISY_BUILD=yes configure" to see error details.) fi - PJPROJECT_INCLUDE=$(make --quiet --no-print-directory -C $1 echo_cflags) + PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C $1 echo_cflags) PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE" PBX_PJPROJECT=1 PJPROJECT_BUNDLED=yes diff --git a/third-party/pjproject/patches/0001-pjsip-apps-src-python-setup.py-Take-make-from-the-en.patch b/third-party/pjproject/patches/0001-pjsip-apps-src-python-setup.py-Take-make-from-the-en.patch new file mode 100644 index 000000000..80f8bc0b3 --- /dev/null +++ b/third-party/pjproject/patches/0001-pjsip-apps-src-python-setup.py-Take-make-from-the-en.patch @@ -0,0 +1,51 @@ +From 61668b8fcaa0f2a8a05100097284c0c427600033 Mon Sep 17 00:00:00 2001 +From: George Joseph <george.joseph@fairview5.com> +Date: Mon, 2 May 2016 17:08:15 -0600 +Subject: [PATCH] pjsip-apps/src/python/setup.py: Take "make" from the + environment + +With "make" hard coded in setup.py, it chokes on FreeBSD because the system +make command isn't GNU compatibile. This patch allows setup.py to take the +name of the make command from the MAKE environment variable if it exists. +If it doesn't, it defaults to "make". +--- + pjsip-apps/src/python/setup.py | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/pjsip-apps/src/python/setup.py b/pjsip-apps/src/python/setup.py +index 69a9859..ea1427d 100644 +--- a/pjsip-apps/src/python/setup.py ++++ b/pjsip-apps/src/python/setup.py +@@ -60,25 +60,25 @@ if pj_version_suffix: + pj_version += "-" + pj_version_suffix + + #print 'PJ_VERSION = "'+ pj_version + '"' +- ++MAKE = os.environ.get('MAKE') or "make" + + # Fill in pj_inc_dirs + pj_inc_dirs = [] +-f = os.popen("make -f helper.mak inc_dir") ++f = os.popen("%s -f helper.mak inc_dir" % MAKE) + for line in f: + pj_inc_dirs.append(line.rstrip("\r\n")) + f.close() + + # Fill in pj_lib_dirs + pj_lib_dirs = [] +-f = os.popen("make -f helper.mak lib_dir") ++f = os.popen("%s -f helper.mak lib_dir" % MAKE) + for line in f: + pj_lib_dirs.append(line.rstrip("\r\n")) + f.close() + + # Fill in pj_libs + pj_libs = [] +-f = os.popen("make -f helper.mak libs") ++f = os.popen("%s -f helper.mak libs" % MAKE) + for line in f: + pj_libs.append(line.rstrip("\r\n")) + f.close() +-- +2.5.5 + diff --git a/third-party/pjproject/patches/0001-sip_parser.c-Remove-wholesale-strip-from-parse_param.patch b/third-party/pjproject/patches/0001-sip_parser.c-Remove-wholesale-strip-from-parse_param.patch new file mode 100644 index 000000000..e0bd9129c --- /dev/null +++ b/third-party/pjproject/patches/0001-sip_parser.c-Remove-wholesale-strip-from-parse_param.patch @@ -0,0 +1,55 @@ +From ce426249ec1270f27560919791f3e13eaeea9152 Mon Sep 17 00:00:00 2001 +From: George Joseph <george.joseph@fairview5.com> +Date: Tue, 12 Apr 2016 14:09:53 -0600 +Subject: [PATCH] sip_parser.c: Remove wholesale '[]' strip from + parse_param_impl + +The wholesale stripping of '[]' from header parameters causes issues if +something (like a port) occurrs after the final ']'. + +'[2001:a::b]' will correctly parse to '2001:a::b' +'[2001:a::b]:8080' will correctly parse to '2001:a::b' but the scanner is left +with ':8080' and parsing stops with a syntax error. + +I can't even find a case where stripping the '[]' is a good thing anyway. Even +if you continued to parse and resulted in a string that looks like this... +'2001:a::b:8080', it's not valid. + +This came up in Asterisk because Kamailio sends us a Contact with an alias +URI parameter that has an IPv6 address in it like this: +Contact: <sip:1171@127.0.0.1:5080;alias=[2001:1:2::3]~43691~6> +which should be legal but causes a syntax error because of the characters +after the final ']'. Even if it didn't, the '[]' should still not be stripped. + +I've run the Asterisk Test Suite for PJSIP (252 tests) many of which are IPv6 +enabled. No issues were caused by removing the code that strips the '[]'. + +I tried running 'make pjsip-test' but that fails even without my change. :) + +The Asterisk ticket is: https://issues.asterisk.org/jira/browse/ASTERISK-25123 +--- + pjsip/src/pjsip/sip_parser.c | 8 -------- + 1 file changed, 8 deletions(-) + +diff --git a/pjsip/src/pjsip/sip_parser.c b/pjsip/src/pjsip/sip_parser.c +index c18faa3..98eb5ea 100644 +--- a/pjsip/src/pjsip/sip_parser.c ++++ b/pjsip/src/pjsip/sip_parser.c +@@ -1149,14 +1149,6 @@ static void parse_param_imp( pj_scanner *scanner, pj_pool_t *pool, + pvalue->ptr++; + pvalue->slen -= 2; + } +- } else if (*scanner->curptr == '[') { +- /* pvalue can be a quoted IPv6; in this case, the +- * '[' and ']' quote characters are to be removed +- * from the pvalue. +- */ +- pj_scan_get_char(scanner); +- pj_scan_get_until_ch(scanner, ']', pvalue); +- pj_scan_get_char(scanner); + } else if(pj_cis_match(spec, *scanner->curptr)) { + parser_get_and_unescape(scanner, pool, spec, esc_spec, pvalue); + } +-- +2.5.5 + diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h index 840d8b279..8e854b723 100644 --- a/third-party/pjproject/patches/config_site.h +++ b/third-party/pjproject/patches/config_site.h @@ -28,7 +28,11 @@ #define PJSIP_SAFE_MODULE 0 #define PJ_HAS_STRICMP_ALNUM 0 #define PJ_HASH_USE_OWN_TOLOWER 1 -#define PJSIP_UNESCAPE_IN_PLACE 1 +/* + It is imperative that PJSIP_UNESCAPE_IN_PLACE remain 0 or undefined. + Enabling it will result in SEGFAULTS when URIs containing escape sequences are encountered. +*/ +#undef PJSIP_UNESCAPE_IN_PLACE #define PJSIP_MAX_PKT_LEN 6000 #undef PJ_TODO |