Skip to content
Snippets Groups Projects
chan_sip.c 1.06 MiB
Newer Older
  • Learn to ignore specific revisions
  • 			}
    			if (cur->retransid > -1) {
    				if (sipdebug)
    					ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
    			}
    			/* This odd section is designed to thwart a
    			 * race condition in the packet scheduler. There are
    			 * two conditions under which deleting the packet from the
    			 * scheduler can fail.
    			 *
    			 * 1. The packet has been removed from the scheduler because retransmission
    			 * is being attempted. The problem is that if the packet is currently attempting
    			 * retransmission and we are at this point in the code, then that MUST mean
    			 * that retrans_pkt is waiting on p's lock. Therefore we will relinquish the
    			 * lock temporarily to allow retransmission.
    			 *
    			 * 2. The packet has reached its maximum number of retransmissions and has
    			 * been permanently removed from the packet scheduler. If this is the case, then
    			 * the packet's retransid will be set to -1. The atomicity of the setting and checking
    			 * of the retransid to -1 is ensured since in both cases p's lock is held.
    
    			while (cur->retransid > -1 && ast_sched_del(sched, cur->retransid)) {
    				sip_pvt_unlock(p);
    				usleep(1);
    				sip_pvt_lock(p);
    
    			UNLINK(cur, p->packets, prev);
    			dialog_unref(cur->owner, "unref pkt cur->owner dialog from sip ack before freeing pkt");
    
    			if (cur->data) {
    
    				ast_free(cur->data);
    
    			ast_free(cur);
    			break;
    
    	ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
    		p->callid, resp ? "Response" : "Request", seqno, msg);
    	return res;
    }
    
    /*! \brief Pretend to ack all packets
     * called with p locked */
    void __sip_pretend_ack(struct sip_pvt *p)
    {
    	struct sip_pkt *cur = NULL;
    
    	while (p->packets) {
    		int method;
    		if (cur == p->packets) {
    			ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
    			return;
    
    		cur = p->packets;
    		method = (cur->method) ? cur->method : find_sip_method(cur->data->str);
    		__sip_ack(p, cur->seqno, cur->is_resp, method);
    
    /*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
    int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
    {
    	struct sip_pkt *cur;
    	int res = FALSE;
    
    	for (cur = p->packets; cur; cur = cur->next) {
    		if (cur->seqno == seqno && cur->is_resp == resp &&
    			(cur->is_resp || method_match(sipmethod, cur->data->str))) {
    			/* this is our baby */
    			if (cur->retransid > -1) {
    				if (sipdebug)
    					ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
    
    			AST_SCHED_DEL(sched, cur->retransid);
    			res = TRUE;
    			break;
    
    	ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res == -1 ? "Not Found" : "Found");
    	return res;
    }
    
    
    /*! \brief Copy SIP request, parse it */
    static void parse_copy(struct sip_request *dst, const struct sip_request *src)
    {
    	copy_request(dst, src);
    	parse_request(dst);
    }
    
    /*! \brief add a blank line if no body */
    static void add_blank(struct sip_request *req)
    {
    	if (!req->lines) {
    		/* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
    		ast_str_append(&req->data, 0, "\r\n");
    
    }
    
    static int send_provisional_keepalive_full(struct sip_pvt *pvt, int with_sdp)
    {
    	const char *msg = NULL;
    
    	if (!pvt->last_provisional || !strncasecmp(pvt->last_provisional, "100", 3)) {
    		msg = "183 Session Progress";
    	}
    
    	if (pvt->invitestate < INV_COMPLETED) {
    		if (with_sdp) {
    			transmit_response_with_sdp(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq, XMIT_UNRELIABLE, FALSE, FALSE);
    		} else {
    			transmit_response(pvt, S_OR(msg, pvt->last_provisional), &pvt->initreq);
    
    		return PROVIS_KEEPALIVE_TIMEOUT;
    
    static int send_provisional_keepalive(const void *data)
    {
    
    	struct sip_pvt *pvt = (struct sip_pvt *) data;
    
    	return send_provisional_keepalive_full(pvt, 0);
    
    static int send_provisional_keepalive_with_sdp(const void *data)
    {
    	struct sip_pvt *pvt = (void *) data;
    
    	return send_provisional_keepalive_full(pvt, 1);
    }
    
    static void update_provisional_keepalive(struct sip_pvt *pvt, int with_sdp)
    {
    	AST_SCHED_DEL_UNREF(sched, pvt->provisional_keepalive_sched_id, dialog_unref(pvt, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
    
    	pvt->provisional_keepalive_sched_id = ast_sched_add(sched, PROVIS_KEEPALIVE_TIMEOUT,
    		with_sdp ? send_provisional_keepalive_with_sdp : send_provisional_keepalive, dialog_ref(pvt, "Increment refcount to pass dialog pointer to sched callback"));
    
    /*! \brief Transmit response on SIP request*/
    static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
    
    	finalize_content(req);
    
    	add_blank(req);
    	if (sip_debug_test_pvt(p)) {
    
    Mark Michelson's avatar
    Mark Michelson committed
    		const struct ast_sockaddr *dst = sip_real_dst(p);
    
    Mark Michelson's avatar
    Mark Michelson committed
    		ast_verbose("\n<--- %sTransmitting (%s) to %s --->\n%s\n<------------>\n",
    
    			reliable ? "Reliably " : "", sip_nat_mode(p),
    
    Mark Michelson's avatar
    Mark Michelson committed
    			ast_sockaddr_stringify(dst),
    			req->data->str);
    
    	}
    	if (p->do_history) {
    		struct sip_request tmp = { .rlPart1 = 0, };
    		parse_copy(&tmp, req);
    
    		append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", tmp.data->str, sip_get_header(&tmp, "CSeq"),
    
    			(tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? REQ_OFFSET_TO_STR(&tmp, rlPart2) : sip_methods[tmp.method].text);
    
    	/* If we are sending a final response to an INVITE, stop retransmitting provisional responses */
    	if (p->initreq.method == SIP_INVITE && reliable == XMIT_CRITICAL) {
    		AST_SCHED_DEL_UNREF(sched, p->provisional_keepalive_sched_id, dialog_unref(p, "when you delete the provisional_keepalive_sched_id, you should dec the refcount for the stored dialog ptr"));
    
    	res = (reliable) ?
    
    		 __sip_reliable_xmit(p, seqno, 1, req->data, (reliable == XMIT_CRITICAL), req->method) :
    		__sip_xmit(p, req->data);
    
    /*!
     * \internal
     * \brief Send SIP Request to the other part of the dialogue
     * \return see \ref __sip_xmit
     */
    
    static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
    
    	/* If we have an outbound proxy, reset peer address
    		Only do this once.
    	*/
    	if (p->outboundproxy) {
    		p->sa = p->outboundproxy->ip;
    
    	finalize_content(req);
    
    	add_blank(req);
    	if (sip_debug_test_pvt(p)) {
    
    Mark Michelson's avatar
    Mark Michelson committed
    		if (ast_test_flag(&p->flags[0], SIP_NAT_FORCE_RPORT)) {
    			ast_verbose("%sTransmitting (NAT) to %s:\n%s\n---\n", reliable ? "Reliably " : "", ast_sockaddr_stringify(&p->recv), req->data->str);
    		} else {
    			ast_verbose("%sTransmitting (no NAT) to %s:\n%s\n---\n", reliable ? "Reliably " : "", ast_sockaddr_stringify(&p->sa), req->data->str);
    		}
    
    	if (p->do_history) {
    		struct sip_request tmp = { .rlPart1 = 0, };
    		parse_copy(&tmp, req);
    
    		append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", tmp.data->str, sip_get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
    
    	res = (reliable) ?
    
    		__sip_reliable_xmit(p, seqno, 0, req->data, (reliable == XMIT_CRITICAL), req->method) :
    		__sip_xmit(p, req->data);
    
    static void enable_dsp_detect(struct sip_pvt *p)
    
    	int features = 0;
    
    	if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
    
    	    (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
    		if (p->rtp) {
    			ast_rtp_instance_dtmf_mode_set(p->rtp, AST_RTP_DTMF_MODE_INBAND);
    		}
    		features |= DSP_FEATURE_DIGIT_DETECT;
    
    	if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) {
    		features |= DSP_FEATURE_FAX_DETECT;
    
    	if (!features) {
    		return;
    
    	if (!(p->dsp = ast_dsp_new())) {
    		return;
    
    	ast_dsp_set_features(p->dsp, features);
    	if (global_relaxdtmf) {
    		ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
    	}
    
    static void disable_dsp_detect(struct sip_pvt *p)
    
    	if (p->dsp) {
    		ast_dsp_free(p->dsp);
    		p->dsp = NULL;
    
    /*! \brief Set an option on a SIP dialog */
    static int sip_setoption(struct ast_channel *chan, int option, void *data, int datalen)
    {
    	int res = -1;
    	struct sip_pvt *p = chan->tech_pvt;
    
    		ast_log(LOG_ERROR, "Attempt to Ref a null pointer.  sip private structure is gone!\n");
    		return -1;
    
    	switch (option) {
    	case AST_OPTION_FORMAT_READ:
    
    		if (p->rtp) {
    			res = ast_rtp_instance_set_read_format(p->rtp, (struct ast_format *) data);
    		}
    
    		break;
    	case AST_OPTION_FORMAT_WRITE:
    
    		if (p->rtp) {
    			res = ast_rtp_instance_set_write_format(p->rtp, (struct ast_format *) data);
    		}
    
    		break;
    	case AST_OPTION_MAKE_COMPATIBLE:
    
    		if (p->rtp) {
    			res = ast_rtp_instance_make_compatible(chan, p->rtp, (struct ast_channel *) data);
    		}
    
    		break;
    	case AST_OPTION_DIGIT_DETECT:
    		if ((ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_INBAND) ||
    		    (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_AUTO)) {
    			char *cp = (char *) data;
    
    			ast_debug(1, "%sabling digit detection on %s\n", *cp ? "En" : "Dis", chan->name);
    			if (*cp) {
    				enable_dsp_detect(p);
    			} else {
    				disable_dsp_detect(p);
    			}
    			res = 0;
    		}
    		break;
    
    	case AST_OPTION_SECURE_SIGNALING:
    		p->req_secure_signaling = *(unsigned int *) data;
    		res = 0;
    		break;
    	case AST_OPTION_SECURE_MEDIA:
    		ast_set2_flag(&p->flags[1], *(unsigned int *) data, SIP_PAGE2_USE_SRTP);
    		res = 0;
    		break;
    
    	return res;
    }
    
    /*! \brief Query an option on a SIP dialog */
    static int sip_queryoption(struct ast_channel *chan, int option, void *data, int *datalen)
    {
    	int res = -1;
    	enum ast_t38_state state = T38_STATE_UNAVAILABLE;
    	struct sip_pvt *p = (struct sip_pvt *) chan->tech_pvt;
    	char *cp;
    
    
    	switch (option) {
    	case AST_OPTION_T38_STATE:
    		/* Make sure we got an ast_t38_state enum passed in */
    		if (*datalen != sizeof(enum ast_t38_state)) {
    			ast_log(LOG_ERROR, "Invalid datalen for AST_OPTION_T38_STATE option. Expected %d, got %d\n", (int)sizeof(enum ast_t38_state), *datalen);
    
    		/* Now if T38 support is enabled we need to look and see what the current state is to get what we want to report back */
    		if (ast_test_flag(&p->flags[1], SIP_PAGE2_T38SUPPORT)) {
    			switch (p->t38.state) {
    			case T38_LOCAL_REINVITE:
    			case T38_PEER_REINVITE:
    				state = T38_STATE_NEGOTIATING;
    				break;
    			case T38_ENABLED:
    				state = T38_STATE_NEGOTIATED;
    				break;
    
    			case T38_REJECTED:
    				state = T38_STATE_REJECTED;
    				break;
    
    			default:
    				state = T38_STATE_UNKNOWN;
    			}
    		}
    
    		*((enum ast_t38_state *) data) = state;
    		res = 0;
    
    		break;
    	case AST_OPTION_DIGIT_DETECT:
    		cp = (char *) data;
    		*cp = p->dsp ? 1 : 0;
    		ast_debug(1, "Reporting digit detection %sabled on %s\n", *cp ? "en" : "dis", chan->name);
    		break;
    
    	case AST_OPTION_SECURE_SIGNALING:
    		*((unsigned int *) data) = p->req_secure_signaling;
    		res = 0;
    		break;
    	case AST_OPTION_SECURE_MEDIA:
    		*((unsigned int *) data) = ast_test_flag(&p->flags[1], SIP_PAGE2_USE_SRTP) ? 1 : 0;
    		res = 0;
    		break;
    
    	case AST_OPTION_DEVICE_NAME:
    		if (p && p->outgoing_call) {
    			cp = (char *) data;
    			ast_copy_string(cp, p->dialstring, *datalen);
    			res = 0;
    		}
    		/* We purposely break with a return of -1 in the
    		 * implied else case here
    		 */
    		break;
    	default:
    		break;
    	}
    
    /*! \brief Locate closing quote in a string, skipping escaped quotes.
     * optionally with a limit on the search.
     * start must be past the first quote.
    
    const char *find_closing_quote(const char *start, const char *lim)
    
    	char last_char = '\0';
    	const char *s;
    	for (s = start; *s && s != lim; last_char = *s++) {
    		if (*s == '"' && last_char != '\\')
    			break;
    	}
    	return s;
    }
    
    /*! \brief Send message with Access-URL header, if this is an HTML URL only! */
    static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
    {
    	struct sip_pvt *p = chan->tech_pvt;
    
    	if (subclass != AST_HTML_URL)
    
    	ast_string_field_build(p, url, "<%s>;mode=active", data);
    
    	if (sip_debug_test_pvt(p))
    		ast_debug(1, "Send URL %s, state = %d!\n", data, chan->_state);
    
    	switch (chan->_state) {
    	case AST_STATE_RING:
    		transmit_response(p, "100 Trying", &p->initreq);
    		break;
    	case AST_STATE_RINGING:
    		transmit_response(p, "180 Ringing", &p->initreq);
    		break;
    	case AST_STATE_UP:
    		if (!p->pendinginvite) {		/* We are up, and have no outstanding invite */
    			transmit_reinvite_with_sdp(p, FALSE, FALSE);
    		} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
    
    			ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
    		}
    
    		break;
    	default:
    		ast_log(LOG_WARNING, "Don't know how to send URI when state is %d!\n", chan->_state);
    
    /*! \brief Deliver SIP call ID for the call */
    static const char *sip_get_callid(struct ast_channel *chan)
    {
    	return chan->tech_pvt ? ((struct sip_pvt *) chan->tech_pvt)->callid : "";
    }
    
    
    /*!
     * \internal
     * \brief Send SIP MESSAGE text within a call
     * \note Called from PBX core sendtext() application
     */
    
    static int sip_sendtext(struct ast_channel *ast, const char *text)
    {
    	struct sip_pvt *dialog = ast->tech_pvt;
    	int debug = sip_debug_test_pvt(dialog);
    
    	if (!dialog)
    
    		return -1;
    
    	/* NOT ast_strlen_zero, because a zero-length message is specifically
    	 * allowed by RFC 3428 (See section 10, Examples) */
    	if (!text)
    		return 0;
    	if(!is_method_allowed(&dialog->allowed_methods, SIP_MESSAGE)) {
    		ast_debug(2, "Trying to send MESSAGE to device that does not support it.\n");
    		return(0);
    
    	if (debug)
    		ast_verbose("Sending text %s on %s\n", text, ast->name);
    
    	transmit_message_with_text(dialog, text, 0, 0);
    
    /*! \brief Update peer object in realtime storage
    	If the Asterisk system name is set in asterisk.conf, we will use
    	that name and store that in the "regserver" field in the sippeers
    	table to facilitate multi-server setups.
    */
    
    Mark Michelson's avatar
    Mark Michelson committed
    static void realtime_update_peer(const char *peername, struct ast_sockaddr *addr, const char *defaultuser, const char *fullcontact, const char *useragent, int expirey, unsigned short deprecated_username, int lastms)
    
    	char ipaddr[INET6_ADDRSTRLEN];
    
    	char regseconds[20];
    	char *tablename = NULL;
    	char str_lastms[20];
    
    	const char *sysname = ast_config_AST_SYSTEM_NAME;
    	char *syslabel = NULL;
    
    	time_t nowtime = time(NULL) + expirey;
    	const char *fc = fullcontact ? "fullcontact" : NULL;
    
    	int realtimeregs = ast_check_realtime("sipregs");
    
    	tablename = realtimeregs ? "sipregs" : "sippeers";
    
    	snprintf(str_lastms, sizeof(str_lastms), "%d", lastms);
    	snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime);	/* Expiration time */
    
    Mark Michelson's avatar
    Mark Michelson committed
    	ast_copy_string(ipaddr, ast_sockaddr_stringify_addr(addr), sizeof(ipaddr));
    	ast_copy_string(port, ast_sockaddr_stringify_port(addr), sizeof(port));
    
    
    	if (ast_strlen_zero(sysname))	/* No system name, disable this */
    		sysname = NULL;
    	else if (sip_cfg.rtsave_sysname)
    		syslabel = "regserver";
    
    
    	/* XXX IMPORTANT: Anytime you add a new parameter to be updated, you
             *  must also add it to contrib/scripts/asterisk.ldap-schema,
             *  contrib/scripts/asterisk.ldif,
             *  and to configs/res_ldap.conf.sample as described in
    
             *  bugs 15156 and 15895
    
    	if (fc) {
    		ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
    			"port", port, "regseconds", regseconds,
    			deprecated_username ? "username" : "defaultuser", defaultuser,
    			"useragent", useragent, "lastms", str_lastms,
    			fc, fullcontact, syslabel, sysname, SENTINEL); /* note fc and syslabel _can_ be NULL */
    	} else {
    		ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
    			"port", port, "regseconds", regseconds,
    			"useragent", useragent, "lastms", str_lastms,
    			deprecated_username ? "username" : "defaultuser", defaultuser,
    			syslabel, sysname, SENTINEL); /* note syslabel _can_ be NULL */
    	}
    
    /*! \brief Automatically add peer extension to dial plan */
    static void register_peer_exten(struct sip_peer *peer, int onoff)
    
    	char multi[256];
    	char *stringp, *ext, *context;
    	struct pbx_find_info q = { .stacklen = 0 };
    
    	/* XXX note that sip_cfg.regcontext is both a global 'enable' flag and
    	 * the name of the global regexten context, if not specified
    	 * individually.
    	 */
    	if (ast_strlen_zero(sip_cfg.regcontext))
    		return;
    
    	ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi));
    	stringp = multi;
    	while ((ext = strsep(&stringp, "&"))) {
    		if ((context = strchr(ext, '@'))) {
    			*context++ = '\0';	/* split ext@context */
    			if (!ast_context_find(context)) {
    				ast_log(LOG_WARNING, "Context %s must exist in regcontext= in sip.conf!\n", context);
    				continue;
    
    			context = sip_cfg.regcontext;
    
    		if (onoff) {
    			if (!ast_exists_extension(NULL, context, ext, 1, NULL)) {
    				ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
    					 ast_strdup(peer->name), ast_free_ptr, "SIP");
    
    		} else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1, NULL, "", E_MATCH)) {
    			ast_context_remove_extension(context, ext, 1, NULL);
    
    Olle Johansson's avatar
    Olle Johansson committed
    	}
    
    /*! Destroy mailbox subscriptions */
    static void destroy_mailbox(struct sip_mailbox *mailbox)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	if (mailbox->event_sub)
    		ast_event_unsubscribe(mailbox->event_sub);
    	ast_free(mailbox);
    }
    
    /*! Destroy all peer-related mailbox subscriptions */
    static void clear_peer_mailboxes(struct sip_peer *peer)
    {
    	struct sip_mailbox *mailbox;
    
    	while ((mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, entry)))
    		destroy_mailbox(mailbox);
    
    static void sip_destroy_peer_fn(void *peer)
    {
    	sip_destroy_peer(peer);
    }
    
    /*! \brief Destroy peer object from memory */
    static void sip_destroy_peer(struct sip_peer *peer)
    
    	ast_debug(3, "Destroying SIP peer %s\n", peer->name);
    
    
    	/*
    	 * Remove any mailbox event subscriptions for this peer before
    	 * we destroy anything.  An event subscription callback may be
    	 * happening right now.
    	 */
    	clear_peer_mailboxes(peer);
    
    	if (peer->outboundproxy) {
    
    		ao2_ref(peer->outboundproxy, -1);
    
    	/* Delete it, it needs to disappear */
    	if (peer->call) {
    
    		dialog_unlink_all(peer->call);
    
    		peer->call = dialog_unref(peer->call, "peer->call is being unset");
    
    	if (peer->mwipvt) {	/* We have an active subscription, delete it */
    
    		dialog_unlink_all(peer->mwipvt);
    
    		peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt");
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	if (peer->chanvars) {
    		ast_variables_destroy(peer->chanvars);
    		peer->chanvars = NULL;
    
    	register_peer_exten(peer, FALSE);
    	ast_free_ha(peer->ha);
    
    	ast_free_ha(peer->directmediaha);
    
    	if (peer->selfdestruct)
    		ast_atomic_fetchadd_int(&apeerobjs, -1);
    
    	else if (!ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && peer->is_realtime) {
    
    		ast_atomic_fetchadd_int(&rpeerobjs, -1);
    		ast_debug(3, "-REALTIME- peer Destroyed. Name: %s. Realtime Peer objects: %d\n", peer->name, rpeerobjs);
    	} else
    		ast_atomic_fetchadd_int(&speerobjs, -1);
    
    	if (peer->auth) {
    		ao2_t_ref(peer->auth, -1, "Removing peer authentication");
    		peer->auth = NULL;
    	}
    
    	if (peer->dnsmgr)
    		ast_dnsmgr_release(peer->dnsmgr);
    
    	if (peer->socket.tcptls_session) {
    		ao2_ref(peer->socket.tcptls_session, -1);
    		peer->socket.tcptls_session = NULL;
    
    	ast_cc_config_params_destroy(peer->cc_params);
    
    	ast_string_field_free_memory(peer);
    
    
    	peer->caps = ast_format_cap_destroy(peer->caps);
    
    /*! \brief Update peer data in database (if used) */
    static void update_peer(struct sip_peer *p, int expire)
    
    	int rtcachefriends = ast_test_flag(&p->flags[1], SIP_PAGE2_RTCACHEFRIENDS);
    	if (sip_cfg.peer_rtupdate &&
    	    (p->is_realtime || rtcachefriends)) {
    
    		realtime_update_peer(p->name, &p->addr, p->username, p->fullcontact, p->useragent, expire, p->deprecated_username, p->lastms);
    
    static struct ast_variable *get_insecure_variable_from_config(struct ast_config *cfg)
    
    	struct ast_variable *var = NULL;
    	struct ast_flags flags = {0};
    	char *cat = NULL;
    	const char *insecure;
    	while ((cat = ast_category_browse(cfg, cat))) {
    		insecure = ast_variable_retrieve(cfg, cat, "insecure");
    		set_insecure_flags(&flags, insecure, -1);
    		if (ast_test_flag(&flags, SIP_INSECURE_PORT)) {
    			var = ast_category_root(cfg, cat);
    			break;
    		}
    
    static struct ast_variable *get_insecure_variable_from_sippeers(const char *column, const char *value)
    
    	struct ast_config *peerlist;
    	struct ast_variable *var = NULL;
    	if ((peerlist = ast_load_realtime_multientry("sippeers", column, value, "insecure LIKE", "%port%", SENTINEL))) {
    		if ((var = get_insecure_variable_from_config(peerlist))) {
    			/* Must clone, because var will get freed along with
    			 * peerlist. */
    			var = ast_variables_dup(var);
    		}
    		ast_config_destroy(peerlist);
    
    /* Yes.. the only column that makes sense to pass is "ipaddr", but for
     * consistency's sake, we require the column name to be passed. As extra
     * argument, we take a pointer to var. We already got the info, so we better
     * return it and save the caller a query. If return value is nonzero, then *var
     * is nonzero too (and the other way around). */
    static struct ast_variable *get_insecure_variable_from_sipregs(const char *column, const char *value, struct ast_variable **var)
    
    {
    	struct ast_variable *varregs = NULL;
    
    	struct ast_config *regs, *peers;
    	char *regscat;
    	const char *regname;
    
    	if (!(regs = ast_load_realtime_multientry("sipregs", column, value, SENTINEL))) {
    		return NULL;
    	}
    
    	/* Load *all* peers that are probably insecure=port */
    	if (!(peers = ast_load_realtime_multientry("sippeers", "insecure LIKE", "%port%", SENTINEL))) {
    		ast_config_destroy(regs);
    		return NULL;
    	}
    
    	/* Loop over the sipregs that match IP address and attempt to find an
    	 * insecure=port match to it in sippeers. */
    	regscat = NULL;
    	while ((regscat = ast_category_browse(regs, regscat)) && (regname = ast_variable_retrieve(regs, regscat, "name"))) {
    		char *peerscat;
    		const char *peername;
    
    		peerscat = NULL;
    		while ((peerscat = ast_category_browse(peers, peerscat)) && (peername = ast_variable_retrieve(peers, peerscat, "name"))) {
    			if (!strcasecmp(regname, peername)) {
    				/* Ensure that it really is insecure=port and
    				 * not something else. */
    				const char *insecure = ast_variable_retrieve(peers, peerscat, "insecure");
    				struct ast_flags flags = {0};
    				set_insecure_flags(&flags, insecure, -1);
    				if (ast_test_flag(&flags, SIP_INSECURE_PORT)) {
    					/* ENOMEM checks till the bitter end. */
    					if ((varregs = ast_variables_dup(ast_category_root(regs, regscat)))) {
    						if (!(*var = ast_variables_dup(ast_category_root(peers, peerscat)))) {
    							ast_variables_destroy(varregs);
    							varregs = NULL;
    
    done:
    	ast_config_destroy(regs);
    	ast_config_destroy(peers);
    	return varregs;
    }
    
    static const char *get_name_from_variable(const struct ast_variable *var)
    {
    
    	/* Don't expect this to return non-NULL. Both NULL and empty
    	 * values can cause the option to get removed from the variable
    	 * list. This is called on ast_variables gotten from both
    	 * ast_load_realtime and ast_load_realtime_multientry.
    	 * - ast_load_realtime removes options with empty values
    	 * - ast_load_realtime_multientry does not!
    	 * For consistent behaviour, we check for the empty name and
    	 * return NULL instead. */
    
    	const struct ast_variable *tmp;
    	for (tmp = var; tmp; tmp = tmp->next) {
    		if (!strcasecmp(tmp->name, "name")) {
    
    			if (!ast_strlen_zero(tmp->value)) {
    				return tmp->value;
    			}
    			break;
    
    	}
    	return NULL;
    }
    
    /* If varregs is NULL, we don't use sipregs.
     * Using empty if-bodies instead of goto's while avoiding unnecessary indents */
    static int realtime_peer_by_name(const char *const *name, struct ast_sockaddr *addr, const char *ipaddr, struct ast_variable **var, struct ast_variable **varregs)
    {
    	/* Peer by name and host=dynamic */
    	if ((*var = ast_load_realtime("sippeers", "name", *name, "host", "dynamic", SENTINEL))) {
    		;
    	/* Peer by name and host=IP */
    	} else if (addr && !(*var = ast_load_realtime("sippeers", "name", *name, "host", ipaddr, SENTINEL))) {
    		;
    	/* Peer by name and host=HOSTNAME */
    	} else if ((*var = ast_load_realtime("sippeers", "name", *name, SENTINEL))) {
    		/*!\note
    		 * If this one loaded something, then we need to ensure that the host
    		 * field matched.  The only reason why we can't have this as a criteria
    		 * is because we only have the IP address and the host field might be
    		 * set as a name (and the reverse PTR might not match).
    		 */
    		if (addr) {
    			struct ast_variable *tmp;
    			for (tmp = *var; tmp; tmp = tmp->next) {
    				if (!strcasecmp(tmp->name, "host")) {
    					struct ast_sockaddr *addrs = NULL;
    
    					if (ast_sockaddr_resolve(&addrs,
    								 tmp->value,
    								 PARSE_PORT_FORBID,
    								 get_address_family_filter(&bindaddr)) <= 0 ||
    								 ast_sockaddr_cmp(&addrs[0], addr)) {
    						/* No match */
    						ast_variables_destroy(*var);
    						*var = NULL;
    
    	/* Did we find anything? */
    	if (*var) {
    		if (varregs) {
    			*varregs = ast_load_realtime("sipregs", "name", *name, SENTINEL);
    		}
    		return 1;
    
    /* Another little helper function for backwards compatibility: this
     * checks/fetches the sippeer that belongs to the sipreg. If none is
     * found, we free the sipreg and return false. This way we can do the
     * check inside the if-condition below. In the old code, not finding
     * the sippeer also had it continue look for another match, so we do
     * the same. */
    static struct ast_variable *realtime_peer_get_sippeer_helper(const char **name, struct ast_variable **varregs) {
    
    	struct ast_variable *var = NULL;
    
    	const char *old_name = *name;
    	*name = get_name_from_variable(*varregs);
    
    	if (!*name || !(var = ast_load_realtime("sippeers", "name", *name, SENTINEL))) {
    		if (!*name) {
    			ast_log(LOG_WARNING, "Found sipreg but it has no name\n");
    		}
    
    		ast_variables_destroy(*varregs);
    		*varregs = NULL;
    		*name = old_name;
    	}
    	return var;
    }
    
    /* If varregs is NULL, we don't use sipregs. If we return true, then *name is
     * set. Using empty if-bodies instead of goto's while avoiding unnecessary
     * indents. */
    static int realtime_peer_by_addr(const char **name, struct ast_sockaddr *addr, const char *ipaddr, struct ast_variable **var, struct ast_variable **varregs)
    {
    	char portstring[6]; /* up to 5 digits plus null terminator */
    	ast_copy_string(portstring, ast_sockaddr_stringify_port(addr), sizeof(portstring));
    
    	/* We're not finding this peer by this name anymore. Reset it. */
    	*name = NULL;
    
    	/* First check for fixed IP hosts */
    	if ((*var = ast_load_realtime("sippeers", "host", ipaddr, "port", portstring, SENTINEL))) {
    		;
    	/* Check for registered hosts (in sipregs) */
    	} else if (varregs && (*varregs = ast_load_realtime("sipregs", "ipaddr", ipaddr, "port", portstring, SENTINEL)) &&
    			(*var = realtime_peer_get_sippeer_helper(name, varregs))) {
    		;
    	/* Check for registered hosts (in sippeers) */
    	} else if (!varregs && (*var = ast_load_realtime("sippeers", "ipaddr", ipaddr, "port", portstring, SENTINEL))) {
    		;
    	/* We couldn't match on ipaddress and port, so we need to check if port is insecure */
    	} else if ((*var = get_insecure_variable_from_sippeers("host", ipaddr))) {
    		;
    	/* Same as above, but try the IP address field (in sipregs)
    	 * Observe that it fetches the name/var at the same time, without the
    	 * realtime_peer_get_sippeer_helper. Also note that it is quite inefficient.
    	 * Avoid sipregs if possible. */
    	} else if (varregs && (*varregs = get_insecure_variable_from_sipregs("ipaddr", ipaddr, var))) {
    		;
    	/* Same as above, but try the IP address field (in sippeers) */
    	} else if (!varregs && (*var = get_insecure_variable_from_sippeers("ipaddr", ipaddr))) {
    		;
    	}
    
    
    	/* Nothing found? */
    	if (!*var) {
    		return 0;
    	}
    
    	/* Check peer name. It must not be empty. There may exist a
    	 * different match that does have a name, but it's too late for
    	 * that now. */
    	if (!*name && !(*name = get_name_from_variable(*var))) {
    		ast_log(LOG_WARNING, "Found peer for IP %s but it has no name\n", ipaddr);
    		ast_variables_destroy(*var);
    		*var = NULL;
    		if (varregs && *varregs) {
    			ast_variables_destroy(*varregs);
    			*varregs = NULL;
    
    
    	/* Make sure varregs is populated if var is. The inverse,
    	 * ensuring that var is set when varregs is, is taken
    	 * care of by realtime_peer_get_sippeer_helper(). */
    	if (varregs && !*varregs) {
    		*varregs = ast_load_realtime("sipregs", "name", *name, SENTINEL);
    	}
    	return 1;
    
    }
    
    /*! \brief  realtime_peer: Get peer from realtime storage
     * Checks the "sippeers" realtime family from extconfig.conf
     * Checks the "sipregs" realtime family from extconfig.conf if it's configured.
     * This returns a pointer to a peer and because we use build_peer, we can rest
     * assured that the refcount is bumped.
     * 
     * \note This is never called with both newpeername and addr at the same time.
     * If you do, be prepared to get a peer with a different name than newpeername.
     */
    static struct sip_peer *realtime_peer(const char *newpeername, struct ast_sockaddr *addr, int devstate_only, int which_objects)
    {
    	struct sip_peer *peer = NULL;
    	struct ast_variable *var = NULL;
    	struct ast_variable *varregs = NULL;
    	char ipaddr[INET6_ADDRSTRLEN];
    	int realtimeregs = ast_check_realtime("sipregs");
    
    	if (addr) {
    		ast_copy_string(ipaddr, ast_sockaddr_stringify_addr(addr), sizeof(ipaddr));
    	} else {
    		ipaddr[0] = '\0';
    
    	if (newpeername && realtime_peer_by_name(&newpeername, addr, ipaddr, &var, realtimeregs ? &varregs : NULL)) {
    		;
    	} else if (addr && realtime_peer_by_addr(&newpeername, addr, ipaddr, &var, realtimeregs ? &varregs : NULL)) {
    		;
    	} else {
    
    	/* If we're looking for users, don't return peers (although this check
    	 * should probably be done in realtime_peer_by_* instead...) */
    	if (which_objects == FINDUSERS) {
    		struct ast_variable *tmp;
    		for (tmp = var; tmp; tmp = tmp->next) {
    			if (!strcasecmp(tmp->name, "type") && (!strcasecmp(tmp->value, "peer"))) {
    				goto cleanup;
    			}
    		}
    	}
    
    
    	/* Peer found in realtime, now build it in memory */
    	peer = build_peer(newpeername, var, varregs, TRUE, devstate_only);
    	if (!peer) {
    
    	ast_debug(3, "-REALTIME- loading peer from database to memory. Name: %s. Peer objects: %d\n", peer->name, rpeerobjs);
    
    	if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS) && !devstate_only) {
    		/* Cache peer */
    		ast_copy_flags(&peer->flags[1], &global_flags[1], SIP_PAGE2_RTAUTOCLEAR|SIP_PAGE2_RTCACHEFRIENDS);
    		if (ast_test_flag(&global_flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
    			AST_SCHED_REPLACE_UNREF(peer->expire, sched, sip_cfg.rtautoclear * 1000, expire_register, peer,
    
    					sip_unref_peer(_data, "remove registration ref"),
    					sip_unref_peer(peer, "remove registration ref"),
    					sip_ref_peer(peer, "add registration ref"));
    
    		}
    		ao2_t_link(peers, peer, "link peer into peers table");
    
    Mark Michelson's avatar
    Mark Michelson committed
    		if (!ast_sockaddr_isnull(&peer->addr)) {
    
    			ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table");
    
    	peer->is_realtime = 1;
    
    cleanup:
    	ast_variables_destroy(var);
    	ast_variables_destroy(varregs);
    
    /* Function to assist finding peers by name only */
    static int find_by_name(void *obj, void *arg, void *data, int flags)
    {
    	struct sip_peer *search = obj, *match = arg;
    	int *which_objects = data;
    
    	/* Usernames in SIP uri's are case sensitive. Domains are not */
    	if (strcmp(search->name, match->name)) {
    		return 0;
    	}
    
    	switch (*which_objects) {
    	case FINDUSERS:
    		if (!(search->type & SIP_TYPE_USER)) {
    			return 0;
    		}
    		break;
    	case FINDPEERS:
    		if (!(search->type & SIP_TYPE_PEER)) {
    			return 0;
    		}
    		break;
    	case FINDALLDEVICES:
    		break;
    
    
    	return CMP_MATCH | CMP_STOP;