Skip to content
Snippets Groups Projects
chan_sip.c 1.06 MiB
Newer Older
  • Learn to ignore specific revisions
  • 		}
    		cc_entry->core_id = monitor->core_id;
    		monitor_instance->suspension_entry->instance_data = cc_entry;
    		publish_type = SIP_PUBLISH_INITIAL;
    
    		publish_type = SIP_PUBLISH_MODIFY;
    		cc_entry = monitor_instance->suspension_entry->instance_data;
    	}
    
    	cc_entry->current_state = CC_CLOSED;
    
    	if (ast_strlen_zero(monitor_instance->notify_uri)) {
    		/* If we have no set notify_uri, then what this means is that we have
    		 * not received a NOTIFY from this destination stating that he is
    		 * currently available.
    		 *
    		 * This situation can arise when the core calls the suspend callbacks
    		 * of multiple destinations. If one of the other destinations aside
    		 * from this one notified Asterisk that he is available, then there
    		 * is no reason to take any suspension action on this device. Rather,
    		 * we should return now and if we receive a NOTIFY while monitoring
    		 * is still "suspended" then we can immediately respond with the
    		 * proper PUBLISH to let this endpoint know what is going on.
    		 */
    		return 0;
    
    	construct_pidf_body(CC_CLOSED, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
    	return transmit_publish(monitor_instance->suspension_entry, publish_type, monitor_instance->notify_uri);
    }
    
    static int sip_cc_monitor_unsuspend(struct ast_cc_monitor *monitor)
    {
    	struct sip_monitor_instance *monitor_instance = monitor->private_data;
    	struct cc_epa_entry *cc_entry;
    
    	if (!monitor_instance) {
    		return -1;
    	}
    
    	ast_assert(monitor_instance->suspension_entry != NULL);
    
    	cc_entry = monitor_instance->suspension_entry->instance_data;
    	cc_entry->current_state = CC_OPEN;
    	if (ast_strlen_zero(monitor_instance->notify_uri)) {
    		/* This means we are being asked to unsuspend a call leg we never
    		 * sent a PUBLISH on. As such, there is no reason to send another
    		 * PUBLISH at this point either. We can just return instead.
    		 */
    		return 0;
    	}
    	construct_pidf_body(CC_OPEN, monitor_instance->suspension_entry->body, sizeof(monitor_instance->suspension_entry->body), monitor_instance->peername);
    	return transmit_publish(monitor_instance->suspension_entry, SIP_PUBLISH_MODIFY, monitor_instance->notify_uri);
    }
    
    static int sip_cc_monitor_cancel_available_timer(struct ast_cc_monitor *monitor, int *sched_id)
    {
    	if (*sched_id != -1) {
    		AST_SCHED_DEL(sched, *sched_id);
    		ao2_t_ref(monitor, -1, "Removing scheduler's reference to the monitor");
    	}
    	return 0;
    }
    
    static void sip_cc_monitor_destructor(void *private_data)
    {
    	struct sip_monitor_instance *monitor_instance = private_data;
    	ao2_unlink(sip_monitor_instances, monitor_instance);
    	ast_module_unref(ast_module_info->self);
    }
    
    static int sip_get_cc_information(struct sip_request *req, char *subscribe_uri, size_t size, enum ast_cc_service_type *service)
    {
    
    	char *call_info = ast_strdupa(sip_get_header(req, "Call-Info"));
    
    	char *uri;
    	char *purpose;
    	char *service_str;
    	static const char cc_purpose[] = "purpose=call-completion";
    	static const int cc_purpose_len = sizeof(cc_purpose) - 1;
    
    	if (ast_strlen_zero(call_info)) {
    		/* No Call-Info present. Definitely no CC offer */
    		return -1;
    	}
    
    	uri = strsep(&call_info, ";");
    
    	while ((purpose = strsep(&call_info, ";"))) {
    		if (!strncmp(purpose, cc_purpose, cc_purpose_len)) {
    			break;
    		}
    	}
    	if (!purpose) {
    		/* We didn't find the appropriate purpose= parameter. Oh well */
    		return -1;
    	}
    
    	/* Okay, call-completion has been offered. Let's figure out what type of service this is */
    	while ((service_str = strsep(&call_info, ";"))) {
    		if (!strncmp(service_str, "m=", 2)) {
    			break;
    
    	}
    	if (!service_str) {
    		/* So they didn't offer a particular service, We'll just go with CCBS since it really
    		 * doesn't matter anyway
    		 */
    		service_str = "BS";
    	} else {
    		/* We already determined that there is an "m=" so no need to check
    		 * the result of this strsep
    		 */
    		strsep(&service_str, "=");
    	}
    
    	if ((*service = service_string_to_service_type(service_str)) == AST_CC_NONE) {
    		/* Invalid service offered */
    		return -1;
    	}
    
    	ast_copy_string(subscribe_uri, get_in_brackets(uri), size);
    
    /*
     * \brief Determine what, if any, CC has been offered and queue a CC frame if possible
     *
     * After taking care of some formalities to be sure that this call is eligible for CC,
     * we first try to see if we can make use of native CC. We grab the information from
     * the passed-in sip_request (which is always a response to an INVITE). If we can
     * use native CC monitoring for the call, then so be it.
     *
     * If native cc monitoring is not possible or not supported, then we will instead attempt
     * to use generic monitoring. Falling back to generic from a failed attempt at using native
     * monitoring will only work if the monitor policy of the endpoint is "always"
     *
     * \param pvt The current dialog. Contains CC parameters for the endpoint
     * \param req The response to the INVITE we want to inspect
     * \param service The service to use if generic monitoring is to be used. For native
     * monitoring, we get the service from the SIP response itself
     */
    static void sip_handle_cc(struct sip_pvt *pvt, struct sip_request *req, enum ast_cc_service_type service)
    {
    	enum ast_cc_monitor_policies monitor_policy = ast_get_cc_monitor_policy(pvt->cc_params);
    	int core_id;
    	char interface_name[AST_CHANNEL_NAME];
    
    	if (monitor_policy == AST_CC_MONITOR_NEVER) {
    		/* Don't bother, just return */
    		return;
    
    	if ((core_id = ast_cc_get_current_core_id(pvt->owner)) == -1) {
    		/* For some reason, CC is invalid, so don't try it! */
    		return;
    
    	ast_channel_get_device_name(pvt->owner, interface_name, sizeof(interface_name));
    
    	if (monitor_policy == AST_CC_MONITOR_ALWAYS || monitor_policy == AST_CC_MONITOR_NATIVE) {
    		char subscribe_uri[SIPBUFSIZE];
    		char device_name[AST_CHANNEL_NAME];
    		enum ast_cc_service_type offered_service;
    		struct sip_monitor_instance *monitor_instance;
    		if (sip_get_cc_information(req, subscribe_uri, sizeof(subscribe_uri), &offered_service)) {
    			/* If CC isn't being offered to us, or for some reason the CC offer is
    			 * not formatted correctly, then it may still be possible to use generic
    			 * call completion since the monitor policy may be "always"
    			 */
    			goto generic;
    
    		ast_channel_get_device_name(pvt->owner, device_name, sizeof(device_name));
    		if (!(monitor_instance = sip_monitor_instance_init(core_id, subscribe_uri, pvt->peername, device_name))) {
    			/* Same deal. We can try using generic still */
    			goto generic;
    
    		/* We bump the refcount of chan_sip because once we queue this frame, the CC core
    		 * will have a reference to callbacks in this module. We decrement the module
    		 * refcount once the monitor destructor is called
    		 */
    		ast_module_ref(ast_module_info->self);
    		ast_queue_cc_frame(pvt->owner, "SIP", pvt->dialstring, offered_service, monitor_instance);
    		ao2_ref(monitor_instance, -1);
    		return;
    	}
    
    generic:
    	if (monitor_policy == AST_CC_MONITOR_GENERIC || monitor_policy == AST_CC_MONITOR_ALWAYS) {
    		ast_queue_cc_frame(pvt->owner, AST_CC_GENERIC_MONITOR_TYPE, interface_name, service, NULL);
    
    /*! \brief Working TLS connection configuration */
    static struct ast_tls_config sip_tls_cfg;
    
    /*! \brief Default TLS connection configuration */
    static struct ast_tls_config default_tls_cfg;
    
    /*! \brief The TCP server definition */
    static struct ast_tcptls_session_args sip_tcp_desc = {
    	.accept_fd = -1,
    	.master = AST_PTHREADT_NULL,
    	.tls_cfg = NULL,
    	.poll_timeout = -1,
    	.name = "SIP TCP server",
    	.accept_fn = ast_tcptls_server_root,
    	.worker_fn = sip_tcp_worker_fn,
    };
    
    /*! \brief The TCP/TLS server definition */
    static struct ast_tcptls_session_args sip_tls_desc = {
    	.accept_fd = -1,
    	.master = AST_PTHREADT_NULL,
    	.tls_cfg = &sip_tls_cfg,
    	.poll_timeout = -1,
    	.name = "SIP TLS server",
    	.accept_fn = ast_tcptls_server_root,
    	.worker_fn = sip_tcp_worker_fn,
    };
    
    /*! \brief Append to SIP dialog history
    	\return Always returns 0 */
    #define append_history(p, event, fmt , args... )	append_history_full(p, "%-15s " fmt, event, ## args)
    
    
    struct sip_pvt *dialog_ref_debug(struct sip_pvt *p, const char *tag, char *file, int line, const char *func)
    
    	if (p)
    #ifdef REF_DEBUG
    		__ao2_ref_debug(p, 1, tag, file, line, func);
    #else
    		ao2_ref(p, 1);
    #endif
    	else
    		ast_log(LOG_ERROR, "Attempt to Ref a null pointer\n");
    	return p;
    
    struct sip_pvt *dialog_unref_debug(struct sip_pvt *p, const char *tag, char *file, int line, const char *func)
    
    	if (p)
    #ifdef REF_DEBUG
    		__ao2_ref_debug(p, -1, tag, file, line, func);
    #else
    		ao2_ref(p, -1);
    #endif
    	return NULL;
    
    /*! \brief map from an integer value to a string.
     * If no match is found, return errorstring
    
    static const char *map_x_s(const struct _map_x_s *table, int x, const char *errorstring)
    
    	const struct _map_x_s *cur;
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    	for (cur = table; cur->s; cur++) {
    		if (cur->x == x) {
    
    	return errorstring;
    
    /*! \brief map from a string to an integer value, case insensitive.
     * If no match is found, return errorvalue.
    
    static int map_s_x(const struct _map_x_s *table, const char *s, int errorvalue)
    
    	const struct _map_x_s *cur;
    
    Olle Johansson's avatar
    Olle Johansson committed
    	for (cur = table; cur->s; cur++) {
    		if (!strcasecmp(cur->s, s)) {
    
    	return errorvalue;
    }
    
    static enum AST_REDIRECTING_REASON sip_reason_str_to_code(const char *text)
    {
    	enum AST_REDIRECTING_REASON ast = AST_REDIRECTING_REASON_UNKNOWN;
    	int i;
    
    	for (i = 0; i < ARRAY_LEN(sip_reason_table); ++i) {
    		if (!strcasecmp(text, sip_reason_table[i].text)) {
    			ast = sip_reason_table[i].code;
    			break;
    
    static const char *sip_reason_code_to_str(enum AST_REDIRECTING_REASON code)
    {
    	if (code >= 0 && code < ARRAY_LEN(sip_reason_table)) {
    		return sip_reason_table[code].text;
    
    	return "unknown";
    
    /*!
     * \brief generic function for determining if a correct transport is being
     * used to contact a peer
     *
     * this is done as a macro so that the "tmpl" var can be passed either a
     * sip_request or a sip_peer
     */
    #define check_request_transport(peer, tmpl) ({ \
    	int ret = 0; \
    	if (peer->socket.type == tmpl->socket.type) \
    		; \
    	else if (!(peer->transports & tmpl->socket.type)) {\
    		ast_log(LOG_ERROR, \
    			"'%s' is not a valid transport for '%s'. we only use '%s'! ending call.\n", \
    
    			sip_get_transport(tmpl->socket.type), peer->name, get_transport_list(peer->transports) \
    
    			); \
    		ret = 1; \
    	} else if (peer->socket.type & SIP_TRANSPORT_TLS) { \
    		ast_log(LOG_WARNING, \
    			"peer '%s' HAS NOT USED (OR SWITCHED TO) TLS in favor of '%s' (but this was allowed in sip.conf)!\n", \
    
    			peer->name, sip_get_transport(tmpl->socket.type) \
    
    		); \
    	} else { \
    		ast_debug(1, \
    			"peer '%s' has contacted us over %s even though we prefer %s.\n", \
    
    			peer->name, sip_get_transport(tmpl->socket.type), sip_get_transport(peer->socket.type) \
    
    /*! \brief
     * duplicate a list of channel variables, \return the copy.
     */
    static struct ast_variable *copy_vars(struct ast_variable *src)
    
    	struct ast_variable *res = NULL, *tmp, *v = NULL;
    
    	for (v = src ; v ; v = v->next) {
    		if ((tmp = ast_variable_new(v->name, v->value, v->file))) {
    			tmp->next = res;
    			res = tmp;
    		}
    	}
    	return res;
    
    static void tcptls_packet_destructor(void *obj)
    
    	struct tcptls_packet *packet = obj;
    
    	ast_free(packet->data);
    
    static void sip_tcptls_client_args_destructor(void *obj)
    
    Olle Johansson's avatar
    Olle Johansson committed
    {
    
    	struct ast_tcptls_session_args *args = obj;
    	if (args->tls_cfg) {
    		ast_free(args->tls_cfg->certfile);
    		ast_free(args->tls_cfg->pvtfile);
    		ast_free(args->tls_cfg->cipher);
    		ast_free(args->tls_cfg->cafile);
    		ast_free(args->tls_cfg->capath);
    	}
    	ast_free(args->tls_cfg);
    	ast_free((char *) args->name);
    
    static void sip_threadinfo_destructor(void *obj)
    
    	struct sip_threadinfo *th = obj;
    	struct tcptls_packet *packet;
    
    Olle Johansson's avatar
    Olle Johansson committed
    
    
    	if (th->alert_pipe[1] > -1) {
    		close(th->alert_pipe[0]);
    
    	if (th->alert_pipe[1] > -1) {
    		close(th->alert_pipe[1]);
    	}
    	th->alert_pipe[0] = th->alert_pipe[1] = -1;
    
    	while ((packet = AST_LIST_REMOVE_HEAD(&th->packet_q, entry))) {
    		ao2_t_ref(packet, -1, "thread destruction, removing packet from frame queue");
    
    	if (th->tcptls_session) {
    		ao2_t_ref(th->tcptls_session, -1, "remove tcptls_session for sip_threadinfo object");
    	}
    
    /*! \brief creates a sip_threadinfo object and links it into the threadt table. */
    static struct sip_threadinfo *sip_threadinfo_create(struct ast_tcptls_session_instance *tcptls_session, int transport)
    
    	struct sip_threadinfo *th;
    
    	if (!tcptls_session || !(th = ao2_alloc(sizeof(*th), sip_threadinfo_destructor))) {
    
    	th->alert_pipe[0] = th->alert_pipe[1] = -1;
    
    	if (pipe(th->alert_pipe) == -1) {
    		ao2_t_ref(th, -1, "Failed to open alert pipe on sip_threadinfo");
    		ast_log(LOG_ERROR, "Could not create sip alert pipe in tcptls thread, error %s\n", strerror(errno));
    		return NULL;
    
    	ao2_t_ref(tcptls_session, +1, "tcptls_session ref for sip_threadinfo object");
    	th->tcptls_session = tcptls_session;
    	th->type = transport ? transport : (tcptls_session->ssl ? SIP_TRANSPORT_TLS: SIP_TRANSPORT_TCP);
    	ao2_t_link(threadt, th, "Adding new tcptls helper thread");
    	ao2_t_ref(th, -1, "Decrementing threadinfo ref from alloc, only table ref remains");
    	return th;
    
    /*! \brief used to indicate to a tcptls thread that data is ready to be written */
    static int sip_tcptls_write(struct ast_tcptls_session_instance *tcptls_session, const void *buf, size_t len)
    
    	int res = len;
    	struct sip_threadinfo *th = NULL;
    	struct tcptls_packet *packet = NULL;
    	struct sip_threadinfo tmp = {
    		.tcptls_session = tcptls_session,
    	};
    	enum sip_tcptls_alert alert = TCPTLS_ALERT_DATA;
    
    	if (!tcptls_session) {
    		return XMIT_ERROR;
    
    	ast_mutex_lock(&tcptls_session->lock);
    
    	if ((tcptls_session->fd == -1) ||
    		!(th = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread")) ||
    		!(packet = ao2_alloc(sizeof(*packet), tcptls_packet_destructor)) ||
    		!(packet->data = ast_str_create(len))) {
    		goto tcptls_write_setup_error;
    	}
    
    	/* goto tcptls_write_error should _NOT_ be used beyond this point */
    	ast_str_set(&packet->data, 0, "%s", (char *) buf);
    	packet->len = len;
    
    	/* alert tcptls thread handler that there is a packet to be sent.
    	 * must lock the thread info object to guarantee control of the
    	 * packet queue */
    	ao2_lock(th);
    	if (write(th->alert_pipe[1], &alert, sizeof(alert)) == -1) {
    		ast_log(LOG_ERROR, "write() to alert pipe failed: %s\n", strerror(errno));
    		ao2_t_ref(packet, -1, "could not write to alert pipe, remove packet");
    		packet = NULL;
    		res = XMIT_ERROR;
    	} else { /* it is safe to queue the frame after issuing the alert when we hold the threadinfo lock */
    		AST_LIST_INSERT_TAIL(&th->packet_q, packet, entry);
    
    	ao2_unlock(th);
    
    	ast_mutex_unlock(&tcptls_session->lock);
    	ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo object after finding it");
    	return res;
    
    tcptls_write_setup_error:
    	if (th) {
    		ao2_t_ref(th, -1, "In sip_tcptls_write, unref threadinfo obj, could not create packet");
    
    	if (packet) {
    		ao2_t_ref(packet, -1, "could not allocate packet's data");
    	}
    	ast_mutex_unlock(&tcptls_session->lock);
    
    	return XMIT_ERROR;
    
    /*! \brief SIP TCP connection handler */
    static void *sip_tcp_worker_fn(void *data)
    
    	struct ast_tcptls_session_instance *tcptls_session = data;
    
    	return _sip_tcp_helper_thread(NULL, tcptls_session);
    
    /*! \brief Check if the authtimeout has expired.
     * \param start the time when the session started
     *
     * \retval 0 the timeout has expired
     * \retval -1 error
     * \return the number of milliseconds until the timeout will expire
     */
    static int sip_check_authtimeout(time_t start)
    {
    	int timeout;
    	time_t now;
    	if(time(&now) == -1) {
    		ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno));
    		return -1;
    	}
    
    	timeout = (authtimeout - (now - start)) * 1000;
    	if (timeout < 0) {
    		/* we have timed out */
    		return 0;
    	}
    
    	return timeout;
    }
    
    
    /*! \brief SIP TCP thread management function
    	This function reads from the socket, parses the packet into a request
    */
    static void *_sip_tcp_helper_thread(struct sip_pvt *pvt, struct ast_tcptls_session_instance *tcptls_session)
    
    	int res, cl, timeout = -1, authenticated = 0, flags, after_poll = 0, need_poll = 1;
    
    	struct sip_request req = { 0, } , reqcpy = { 0, };
    	struct sip_threadinfo *me = NULL;
    	char buf[1024] = "";
    	struct pollfd fds[2] = { { 0 }, { 0 }, };
    	struct ast_tcptls_session_args *ca = NULL;
    
    	/* If this is a server session, then the connection has already been
    	 * setup. Check if the authlimit has been reached and if not create the
    	 * threadinfo object so we can access this thread for writing.
    	 *
    
    	 * if this is a client connection more work must be done.
    	 * 1. We own the parent session args for a client connection.  This pointer needs
    	 *    to be held on to so we can decrement it's ref count on thread destruction.
    	 * 2. The threadinfo object was created before this thread was launched, however
    	 *    it must be found within the threadt table.
    	 * 3. Last, the tcptls_session must be started.
    	 */
    	if (!tcptls_session->client) {
    
    		if (ast_atomic_fetchadd_int(&unauth_sessions, +1) >= authlimit) {
    			/* unauth_sessions is decremented in the cleanup code */
    			goto cleanup;
    		}
    
    		if ((flags = fcntl(tcptls_session->fd, F_GETFL)) == -1) {
    			ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno));
    			goto cleanup;
    		}
    
    		flags |= O_NONBLOCK;
    		if (fcntl(tcptls_session->fd, F_SETFL, flags) == -1) {
    			ast_log(LOG_ERROR, "error setting socket to non blocking mode, fcntl() failed: %s\n", strerror(errno));
    			goto cleanup;
    		}
    
    
    		if (!(me = sip_threadinfo_create(tcptls_session, tcptls_session->ssl ? SIP_TRANSPORT_TLS : SIP_TRANSPORT_TCP))) {
    			goto cleanup;
    		}
    		ao2_t_ref(me, +1, "Adding threadinfo ref for tcp_helper_thread");
    	} else {
    		struct sip_threadinfo tmp = {
    			.tcptls_session = tcptls_session,
    		};
    
    		if ((!(ca = tcptls_session->parent)) ||
    			(!(me = ao2_t_find(threadt, &tmp, OBJ_POINTER, "ao2_find, getting sip_threadinfo in tcp helper thread"))) ||
    			(!(tcptls_session = ast_tcptls_client_start(tcptls_session)))) {
    			goto cleanup;
    		}
    
    	flags = 1;
    	if (setsockopt(tcptls_session->fd, SOL_SOCKET, SO_KEEPALIVE, &flags, sizeof(flags))) {
    		ast_log(LOG_ERROR, "error enabling TCP keep-alives on sip socket: %s\n", strerror(errno));
    		goto cleanup;
    	}
    
    
    	me->threadid = pthread_self();
    	ast_debug(2, "Starting thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
    
    	/* set up pollfd to watch for reads on both the socket and the alert_pipe */
    	fds[0].fd = tcptls_session->fd;
    	fds[1].fd = me->alert_pipe[0];
    	fds[0].events = fds[1].events = POLLIN | POLLPRI;
    
    	if (!(req.data = ast_str_create(SIP_MIN_PACKET))) {
    
    	}
    	if (!(reqcpy.data = ast_str_create(SIP_MIN_PACKET))) {
    
    	if(time(&start) == -1) {
    		ast_log(LOG_ERROR, "error executing time(): %s\n", strerror(errno));
    		goto cleanup;
    	}
    
    
    	for (;;) {
    		struct ast_str *str_save;
    
    		if (!tcptls_session->client && req.authenticated && !authenticated) {
    			authenticated = 1;
    			ast_atomic_fetchadd_int(&unauth_sessions, -1);
    		}
    
    		/* calculate the timeout for unauthenticated server sessions */
    		if (!tcptls_session->client && !authenticated ) {
    			if ((timeout = sip_check_authtimeout(start)) < 0) {
    				goto cleanup;
    			}
    
    			if (timeout == 0) {
    				ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
    				goto cleanup;
    			}
    		} else {
    			timeout = -1;
    		}
    
    		res = ast_poll(fds, 2, timeout); /* polls for both socket and alert_pipe */
    
    		if (res < 0) {
    			ast_debug(2, "SIP %s server :: ast_wait_for_input returned %d\n", tcptls_session->ssl ? "SSL": "TCP", res);
    			goto cleanup;
    
    		} else if (res == 0) {
    			/* timeout */
    			ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
    			goto cleanup;
    
    		/* handle the socket event, check for both reads from the socket fd,
    		 * and writes from alert_pipe fd */
    		if (fds[0].revents) { /* there is data on the socket to be read */
    
    			fds[0].revents = 0;
    
    			/* clear request structure */
    			str_save = req.data;
    			memset(&req, 0, sizeof(req));
    			req.data = str_save;
    			ast_str_reset(req.data);
    
    			str_save = reqcpy.data;
    			memset(&reqcpy, 0, sizeof(reqcpy));
    			reqcpy.data = str_save;
    			ast_str_reset(reqcpy.data);
    
    			memset(buf, 0, sizeof(buf));
    
    			if (tcptls_session->ssl) {
    				set_socket_transport(&req.socket, SIP_TRANSPORT_TLS);
    				req.socket.port = htons(ourport_tls);
    			} else {
    				set_socket_transport(&req.socket, SIP_TRANSPORT_TCP);
    				req.socket.port = htons(ourport_tcp);
    			}
    			req.socket.fd = tcptls_session->fd;
    
    			/* Read in headers one line at a time */
    
    			while (ast_str_strlen(req.data) < 4 || strncmp(REQ_OFFSET_TO_STR(&req, data->used - 4), "\r\n\r\n", 4)) {
    
    				if (!tcptls_session->client && !authenticated ) {
    					if ((timeout = sip_check_authtimeout(start)) < 0) {
    						goto cleanup;
    					}
    
    					if (timeout == 0) {
    						ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
    						goto cleanup;
    					}
    				} else {
    					timeout = -1;
    				}
    
    
    				/* special polling behavior is required for TLS
    				 * sockets because of the buffering done in the
    				 * TLS layer */
    				if (!tcptls_session->ssl || need_poll) {
    					need_poll = 0;
    					after_poll = 1;
    					res = ast_wait_for_input(tcptls_session->fd, timeout);
    					if (res < 0) {
    						ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
    						goto cleanup;
    					} else if (res == 0) {
    						/* timeout */
    						ast_debug(2, "SIP TCP server timed out\n");
    						goto cleanup;
    					}
    
    				ast_mutex_lock(&tcptls_session->lock);
    				if (!fgets(buf, sizeof(buf), tcptls_session->f)) {
    					ast_mutex_unlock(&tcptls_session->lock);
    
    					if (after_poll) {
    						goto cleanup;
    					} else {
    						need_poll = 1;
    						continue;
    					}
    
    				}
    				ast_mutex_unlock(&tcptls_session->lock);
    
    				ast_str_append(&req.data, 0, "%s", buf);
    			}
    			copy_request(&reqcpy, &req);
    			parse_request(&reqcpy);
    			/* In order to know how much to read, we need the content-length header */
    
    			if (sscanf(sip_get_header(&reqcpy, "Content-Length"), "%30d", &cl)) {
    
    				while (cl > 0) {
    					size_t bytes_read;
    
    					if (!tcptls_session->client && !authenticated ) {
    						if ((timeout = sip_check_authtimeout(start)) < 0) {
    							goto cleanup;
    						}
    
    						if (timeout == 0) {
    
    							ast_debug(2, "SIP %s server timed out\n", tcptls_session->ssl ? "SSL": "TCP");
    
    					if (!tcptls_session->ssl || need_poll) {
    						need_poll = 0;
    						after_poll = 1;
    						res = ast_wait_for_input(tcptls_session->fd, timeout);
    						if (res < 0) {
    							ast_debug(2, "SIP TCP server :: ast_wait_for_input returned %d\n", res);
    							goto cleanup;
    						} else if (res == 0) {
    							/* timeout */
    							ast_debug(2, "SIP TCP server timed out\n");
    							goto cleanup;
    						}
    
    					ast_mutex_lock(&tcptls_session->lock);
    					if (!(bytes_read = fread(buf, 1, MIN(sizeof(buf) - 1, cl), tcptls_session->f))) {
    						ast_mutex_unlock(&tcptls_session->lock);
    
    						if (after_poll) {
    							goto cleanup;
    						} else {
    							need_poll = 1;
    							continue;
    						}
    
    					}
    					buf[bytes_read] = '\0';
    					ast_mutex_unlock(&tcptls_session->lock);
    
    					cl -= strlen(buf);
    					ast_str_append(&req.data, 0, "%s", buf);
    				}
    			}
    			/*! \todo XXX If there's no Content-Length or if the content-length and what
    					we receive is not the same - we should generate an error */
    
    			req.socket.tcptls_session = tcptls_session;
    			handle_request_do(&req, &tcptls_session->remote_address);
    		}
    
    		if (fds[1].revents) { /* alert_pipe indicates there is data in the send queue to be sent */
    			enum sip_tcptls_alert alert;
    			struct tcptls_packet *packet;
    
    			fds[1].revents = 0;
    
    			if (read(me->alert_pipe[0], &alert, sizeof(alert)) == -1) {
    				ast_log(LOG_ERROR, "read() failed: %s\n", strerror(errno));
    				continue;
    			}
    
    			switch (alert) {
    			case TCPTLS_ALERT_STOP:
    				goto cleanup;
    			case TCPTLS_ALERT_DATA:
    				ao2_lock(me);
    				if (!(packet = AST_LIST_REMOVE_HEAD(&me->packet_q, entry))) {
    					ast_log(LOG_WARNING, "TCPTLS thread alert_pipe indicated packet should be sent, but frame_q is empty");
    				}
    
    					if (ast_tcptls_server_write(tcptls_session, ast_str_buffer(packet->data), packet->len) == -1) {
    						ast_log(LOG_WARNING, "Failure to write to tcp/tls socket\n");
    					}
    
    					ao2_t_ref(packet, -1, "tcptls packet sent, this is no longer needed");
    				}
    
    				ast_log(LOG_ERROR, "Unknown tcptls thread alert '%d'\n", alert);
    
    	ast_debug(2, "Shutting down thread for %s server\n", tcptls_session->ssl ? "SSL" : "TCP");
    
    	if (tcptls_session && !tcptls_session->client && !authenticated) {
    
    		ast_atomic_fetchadd_int(&unauth_sessions, -1);
    	}
    
    
    	if (me) {
    		ao2_t_unlink(threadt, me, "Removing tcptls helper thread, thread is closing");
    		ao2_t_ref(me, -1, "Removing tcp_helper_threads threadinfo ref");
    
    	deinit_req(&reqcpy);
    	deinit_req(&req);
    
    	/* if client, we own the parent session arguments and must decrement ref */
    	if (ca) {
    		ao2_t_ref(ca, -1, "closing tcptls thread, getting rid of client tcptls_session arguments");
    	}
    
    	if (tcptls_session) {
    		ast_mutex_lock(&tcptls_session->lock);
    
    		ast_tcptls_close_session_file(tcptls_session);
    
    		tcptls_session->parent = NULL;
    		ast_mutex_unlock(&tcptls_session->lock);
    
    		ao2_ref(tcptls_session, -1);
    		tcptls_session = NULL;
    	}
    	return NULL;
    
    #ifdef REF_DEBUG
    
    #define sip_ref_peer(arg1,arg2) _ref_peer((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__)
    #define sip_unref_peer(arg1,arg2) _unref_peer((arg1),(arg2), __FILE__, __LINE__, __PRETTY_FUNCTION__)
    
    static struct sip_peer *_ref_peer(struct sip_peer *peer, char *tag, char *file, int line, const char *func)
    
    	if (peer)
    		__ao2_ref_debug(peer, 1, tag, file, line, func);
    	else
    		ast_log(LOG_ERROR, "Attempt to Ref a null peer pointer\n");
    	return peer;
    
    static struct sip_peer *_unref_peer(struct sip_peer *peer, char *tag, char *file, int line, const char *func)
    
    	if (peer)
    		__ao2_ref_debug(peer, -1, tag, file, line, func);
    	return NULL;
    
    /*!
     * helper functions to unreference various types of objects.
     * By handling them this way, we don't have to declare the
     * destructor on each call, which removes the chance of errors.
     */
    
    void *sip_unref_peer(struct sip_peer *peer, char *tag)
    
    	ao2_t_ref(peer, -1, tag);
    	return NULL;
    }
    
    struct sip_peer *sip_ref_peer(struct sip_peer *peer, char *tag)
    
    {
    	ao2_t_ref(peer, 1, tag);
    	return peer;
    }
    
    #endif /* REF_DEBUG */
    
    static void peer_sched_cleanup(struct sip_peer *peer)
    {
    	if (peer->pokeexpire != -1) {
    		AST_SCHED_DEL_UNREF(sched, peer->pokeexpire,
    
    				sip_unref_peer(peer, "removing poke peer ref"));
    
    	}
    	if (peer->expire != -1) {
    		AST_SCHED_DEL_UNREF(sched, peer->expire,
    
    				sip_unref_peer(peer, "remove register expire ref"));
    
    	}
    }
    
    typedef enum {
    	SIP_PEERS_MARKED,
    	SIP_PEERS_ALL,
    } peer_unlink_flag_t;
    
    /* this func is used with ao2_callback to unlink/delete all marked or linked
       peers, depending on arg */
    static int match_and_cleanup_peer_sched(void *peerobj, void *arg, int flags)
    {
    	struct sip_peer *peer = peerobj;
    	peer_unlink_flag_t which = *(peer_unlink_flag_t *)arg;
    
    	if (which == SIP_PEERS_ALL || peer->the_mark) {
    		peer_sched_cleanup(peer);
    		return CMP_MATCH;
    	}
    	return 0;
    }
    
    static void unlink_peers_from_tables(peer_unlink_flag_t flag)
    {
    	ao2_t_callback(peers, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE,
    		match_and_cleanup_peer_sched, &flag, "initiating callback to remove marked peers");
    	ao2_t_callback(peers_by_ip, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE,
    		match_and_cleanup_peer_sched, &flag, "initiating callback to remove marked peers");
    }
    
    /* \brief Unlink all marked peers from ao2 containers */
    static void unlink_marked_peers_from_tables(void)
    {
    	unlink_peers_from_tables(SIP_PEERS_MARKED);
    }
    
    static void unlink_all_peers_from_tables(void)
    {
    	unlink_peers_from_tables(SIP_PEERS_ALL);
    }
    
    /* \brief Unlink single peer from all ao2 containers */
    static void unlink_peer_from_tables(struct sip_peer *peer)
    {
    	ao2_t_unlink(peers, peer, "ao2_unlink of peer from peers table");
    	if (!ast_sockaddr_isnull(&peer->addr)) {
    		ao2_t_unlink(peers_by_ip, peer, "ao2_unlink of peer from peers_by_ip table");
    	}
    }
    
    /*! \brief maintain proper refcounts for a sip_pvt's outboundproxy
     *
     * This function sets pvt's outboundproxy pointer to the one referenced
     * by the proxy parameter. Because proxy may be a refcounted object, and
     * because pvt's old outboundproxy may also be a refcounted object, we need
     * to maintain the proper refcounts.
     *
     * \param pvt The sip_pvt for which we wish to set the outboundproxy
     * \param proxy The sip_proxy which we will point pvt towards.
     * \return Returns void
     */
    static void ref_proxy(struct sip_pvt *pvt, struct sip_proxy *proxy)
    {
    	struct sip_proxy *old_obproxy = pvt->outboundproxy;
    	/* The sip_cfg.outboundproxy is statically allocated, and so
    	 * we don't ever need to adjust refcounts for it
    	 */
    	if (proxy && proxy != &sip_cfg.outboundproxy) {
    		ao2_ref(proxy, +1);
    	}
    	pvt->outboundproxy = proxy;
    	if (old_obproxy && old_obproxy != &sip_cfg.outboundproxy) {
    		ao2_ref(old_obproxy, -1);
    	}
    }
    
    /*!
     * \brief Unlink a dialog from the dialogs container, as well as any other places
     * that it may be currently stored.
     *
     * \note A reference to the dialog must be held before calling this function, and this
     * function does not release that reference.
     */
    
    void dialog_unlink_all(struct sip_pvt *dialog)
    
    {
    	struct sip_pkt *cp;
    
    	struct ast_channel *owner;
    
    	dialog_ref(dialog, "Let's bump the count in the unlink so it doesn't accidentally become dead before we are done");
    
    	ao2_t_unlink(dialogs, dialog, "unlinking dialog via ao2_unlink");
    
    	ao2_t_unlink(dialogs_needdestroy, dialog, "unlinking dialog_needdestroy via ao2_unlink");
    	ao2_t_unlink(dialogs_rtpcheck, dialog, "unlinking dialog_rtpcheck via ao2_unlink");
    
    	/* Unlink us from the owner (channel) if we have one */
    
    	owner = sip_pvt_lock_full(dialog);
    	if (owner) {
    		ast_debug(1, "Detaching from channel %s\n", owner->name);
    		owner->tech_pvt = dialog_unref(owner->tech_pvt, "resetting channel dialog ptr in unlink_all");
    		ast_channel_unlock(owner);
    		ast_channel_unref(owner);
    		dialog->owner = NULL;
    
    	if (dialog->registry) {
    
    		if (dialog->registry->call == dialog) {
    
    			dialog->registry->call = dialog_unref(dialog->registry->call, "nulling out the registry's call dialog field in unlink_all");
    
    		dialog->registry = registry_unref(dialog->registry, "delete dialog->registry");
    	}
    
    	if (dialog->stateid != -1) {
    		ast_extension_state_del(dialog->stateid, cb_extensionstate);
    		dialog->stateid = -1;
    
    	}
    	/* Remove link from peer to subscription of MWI */
    
    	if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog) {
    
    		dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt");
    
    	}
    	if (dialog->relatedpeer && dialog->relatedpeer->call == dialog) {
    
    		dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself");
    
    	/* remove all current packets in this dialog */
    	while((cp = dialog->packets)) {
    		dialog->packets = dialog->packets->next;
    		AST_SCHED_DEL(sched, cp->retransid);
    		dialog_unref(cp->owner, "remove all current packets in this dialog, and the pointer to the dialog too as part of __sip_destroy");
    		if (cp->data) {