Newer
Older
}
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");
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;
Mark Spencer
committed
}
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;
Mark Spencer
committed
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;
Mark Spencer
committed
static int send_provisional_keepalive(const void *data)
{
struct sip_pvt *pvt = (struct sip_pvt *) data;
Mark Spencer
committed
return send_provisional_keepalive_full(pvt, 0);
static int send_provisional_keepalive_with_sdp(const void *data)
{
struct sip_pvt *pvt = (void *) data;
Sean Bright
committed
return send_provisional_keepalive_full(pvt, 1);
}
Sean Bright
committed
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"));
Sean Bright
committed
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"));
Sean Bright
committed
}
/*! \brief Transmit response on SIP request*/
static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
Kevin P. Fleming
committed
add_blank(req);
if (sip_debug_test_pvt(p)) {
const struct ast_sockaddr *dst = sip_real_dst(p);
ast_verbose("\n<--- %sTransmitting (%s) to %s --->\n%s\n<------------>\n",
reliable ? "Reliably " : "", sip_nat_mode(p),
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"));
__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;
add_blank(req);
if (sip_debug_test_pvt(p)) {
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);
__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)
Joshua Colp
committed
{
Joshua Colp
committed
Joshua Colp
committed
return;
David Vossel
committed
}
Joshua Colp
committed
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;
Joshua Colp
committed
if (ast_test_flag(&p->flags[1], SIP_PAGE2_FAX_DETECT_CNG)) {
features |= DSP_FEATURE_FAX_DETECT;
Joshua Colp
committed
}
if (!features) {
return;
Brett Bryant
committed
}
if (!(p->dsp = ast_dsp_new())) {
return;
Brett Bryant
committed
}
ast_dsp_set_features(p->dsp, features);
if (global_relaxdtmf) {
ast_dsp_set_digitmode(p->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
}
Brett Bryant
committed
}
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;
}
Olle Johansson
committed
/*! \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)
return -1;
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)
/* 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.
*/
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;
Olle Johansson
committed
time_t nowtime = time(NULL) + expirey;
const char *fc = fullcontact ? "fullcontact" : NULL;
int realtimeregs = ast_check_realtime("sipregs");
tablename = realtimeregs ? "sipregs" : "sippeers";
Olle Johansson
committed
snprintf(str_lastms, sizeof(str_lastms), "%d", lastms);
snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime); /* Expiration time */
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
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 */
}
Tilghman Lesher
committed
/*! \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;
Jeff Peeler
committed
} else {
context = sip_cfg.regcontext;
Jeff Peeler
committed
}
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");
Jeff Peeler
committed
}
} else if (pbx_find_extension(NULL, NULL, &q, context, ext, 1, NULL, "", E_MATCH)) {
ast_context_remove_extension(context, ext, 1, NULL);
/*! Destroy mailbox subscriptions */
static void destroy_mailbox(struct sip_mailbox *mailbox)
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);
peer->outboundproxy = NULL;
}
/* 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");
Kevin P. Fleming
committed
if (peer->mwipvt) { /* We have an active subscription, delete it */
dialog_unlink_all(peer->mwipvt);
peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt");
if (peer->chanvars) {
ast_variables_destroy(peer->chanvars);
peer->chanvars = NULL;
register_peer_exten(peer, FALSE);
ast_free_ha(peer->ha);
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);
Olle Johansson
committed
if (peer->socket.tcptls_session) {
ao2_ref(peer->socket.tcptls_session, -1);
peer->socket.tcptls_session = NULL;
Olle Johansson
committed
ast_cc_config_params_destroy(peer->cc_params);
Mark Michelson
committed
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)
Mark Spencer
committed
{
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);
Mark Spencer
committed
}
}
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);
Tilghman Lesher
committed
}
return var;
Tilghman Lesher
committed
/* 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;
}
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
/* 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;
goto done;
Doug Bailey
committed
}
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;
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
4797
4798
4799
}
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;
ast_free(addrs);
break;
/* Did we find anything? */
if (*var) {
if (varregs) {
*varregs = ast_load_realtime("sipregs", "name", *name, SENTINEL);
}
return 1;
return 0;
}
/* 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");
}
4832
4833
4834
4835
4836
4837
4838
4839
4840
4841
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
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;
4900
4901
4902
4903
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
}
/*! \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 {
Kevin P. Fleming
committed
/* 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) {
goto cleanup;
Russell Bryant
committed
}
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");
ao2_t_link(peers_by_ip, peer, "link peer into peers_by_ip table");
Mark Spencer
committed
}
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;
Brett Bryant
committed
/* 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;
Brett Bryant
committed
}
return CMP_MATCH | CMP_STOP;