mirror of
https://github.com/jambonz/sbc-inbound.git
synced 2026-10-04 02:04:22 +00:00
195 lines
7.4 KiB
JavaScript
195 lines
7.4 KiB
JavaScript
const assert = require('assert');
|
|
const CIDRMatcher = require('cidr-matcher');
|
|
const {parseUri} = require('drachtio-srf');
|
|
const {normalizeDID} = require('./utils');
|
|
|
|
const sqlSelectAllCarriersForAccountByRealm =
|
|
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
|
|
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
|
FROM sip_gateways sg, voip_carriers vc, accounts acc
|
|
WHERE acc.sip_realm = ?
|
|
AND vc.account_sid = acc.account_sid
|
|
AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
|
|
|
|
const sqlSelectAllCarriersForSPByRealm =
|
|
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
|
|
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
|
FROM sip_gateways sg, voip_carriers vc, accounts acc
|
|
WHERE acc.sip_realm = ?
|
|
AND vc.service_provider_sid = acc.service_provider_sid
|
|
AND vc.account_sid IS NULL
|
|
AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
|
|
|
|
const sqlSelectAllGatewaysForSP =
|
|
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.service_provider_sid,
|
|
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
|
FROM sip_gateways sg, voip_carriers vc
|
|
WHERE sg.voip_carrier_sid = vc.voip_carrier_sid
|
|
AND vc.service_provider_sid IS NOT NULL
|
|
AND vc.is_active = 1`;
|
|
|
|
const sqlCarriersForAccountBySid =
|
|
`SELECT sg.sip_gateway_sid, sg.voip_carrier_sid, vc.name, vc.account_sid,
|
|
vc.application_sid, sg.inbound, sg.outbound, sg.is_active, sg.ipv4, sg.netmask
|
|
FROM sip_gateways sg, voip_carriers vc, accounts acc
|
|
WHERE acc.account_sid = ?
|
|
AND vc.account_sid = acc.account_sid
|
|
AND sg.voip_carrier_sid = vc.voip_carrier_sid`;
|
|
|
|
const sqlAccountByRealm = 'SELECT * from accounts WHERE sip_realm = ?';
|
|
const sqlAccountBySid = 'SELECT * from accounts WHERE account_sid = ?';
|
|
|
|
const sqlQueryApplicationByDid = `
|
|
SELECT * FROM phone_numbers
|
|
WHERE number = ?
|
|
AND voip_carrier_sid = ?`;
|
|
|
|
const sqlQueryAllDidsForCarrier = `
|
|
SELECT * FROM phone_numbers
|
|
WHERE voip_carrier_sid = ?`;
|
|
|
|
const sqlSelectOutboundGatewayForCarrier = `
|
|
SELECT ipv4, port, e164_leading_plus
|
|
FROM sip_gateways sg, voip_carriers vc
|
|
WHERE sg.voip_carrier_sid = ?
|
|
AND sg.voip_carrier_sid = vc.voip_carrier_sid
|
|
AND outbound = 1`;
|
|
|
|
const gatewayMatchesSourceAddress = (source_address, gw) => {
|
|
if (32 === gw.netmask && gw.ipv4 === source_address) return true;
|
|
if (gw.netmask < 32) {
|
|
const matcher = new CIDRMatcher([`${gw.ipv4}/${gw.netmask}`]);
|
|
return matcher.contains(source_address);
|
|
}
|
|
return false;
|
|
};
|
|
|
|
module.exports = (srf, logger) => {
|
|
const {pool} = srf.locals.dbHelpers;
|
|
const pp = pool.promise();
|
|
|
|
const getOutboundGatewayForRefer = async(voip_carrier_sid) => {
|
|
try {
|
|
const [r] = await pp.query(sqlSelectOutboundGatewayForCarrier, [voip_carrier_sid]);
|
|
if (0 === r.length) return null;
|
|
|
|
/* if multiple, prefer a DNS name */
|
|
const hasDns = r.find((row) => row.ipv4.match(/^[A-Za-z]/));
|
|
return hasDns || r[0];
|
|
} catch (err) {
|
|
logger.error({err}, 'getOutboundGatewayForRefer');
|
|
}
|
|
};
|
|
|
|
const getApplicationForDidAndCarrier = async(req, voip_carrier_sid) => {
|
|
const did = normalizeDID(req.calledNumber);
|
|
|
|
try {
|
|
/* straight DID match */
|
|
const [r] = await pp.query(sqlQueryApplicationByDid, [did, voip_carrier_sid]);
|
|
if (r.length) return r[0].application_sid;
|
|
|
|
/* wildcard / regex match */
|
|
const [r2] = await pp.query(sqlQueryAllDidsForCarrier, [voip_carrier_sid]);
|
|
const match = r2
|
|
.filter((o) => o.number.match(/\D/)) // look at anything with non-digit characters
|
|
.sort((a, b) => b.number.length - a.number.length) // prefer longest match
|
|
.find((o) => did.match(new RegExp(o.number.endsWith('*') ? `${o.number.slice(0, -1)}\\d*` : o.number)));
|
|
if (match) return match.application_sid;
|
|
return null;
|
|
} catch (err) {
|
|
logger.error({err}, 'getApplicationForDidAndCarrier');
|
|
}
|
|
};
|
|
|
|
const wasOriginatedFromCarrier = async(req) => {
|
|
const failure = {fromCarrier: false};
|
|
const uri = parseUri(req.uri);
|
|
const isDotDecimal = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(uri.host);
|
|
|
|
if (isDotDecimal) {
|
|
if (process.env.JAMBONES_HOSTING) {
|
|
if (!process.env.SBC_ACCOUNT_SID) return failure;
|
|
|
|
/* look for carrier only within that account */
|
|
const [r] = await pp.query(sqlCarriersForAccountBySid,
|
|
[process.env.SBC_ACCOUNT_SID, req.source_address, req.source_port]);
|
|
if (0 === r.length) return failure;
|
|
return {
|
|
fromCarrier: true,
|
|
gateway: r[0],
|
|
account_sid: process.env.SBC_ACCOUNT_SID
|
|
};
|
|
}
|
|
else {
|
|
/* we may have a carrier at the service provider level */
|
|
const [gw] = await pp.query(sqlSelectAllGatewaysForSP);
|
|
const matches = gw.filter(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
|
if (matches.length) {
|
|
/* we have one or more carriers that match. Now we need to find one with a provisioned phone number */
|
|
const vc_sids = matches.map((m) => `'${m.voip_carrier_sid}'`).join(',');
|
|
const did = normalizeDID(req.calledNumber);
|
|
const sql = `SELECT * FROM phone_numbers WHERE number = ${did} AND voip_carrier_sid IN (${vc_sids})`;
|
|
logger.debug({matches, sql, did, vc_sids}, 'looking up DID');
|
|
|
|
const [r] = await pp.query(sql);
|
|
if (0 === r.length) {
|
|
/* came from a carrier, but number is not provisioned..
|
|
check if we only have a single account, otherwise we have no
|
|
way of knowing which account this is for
|
|
*/
|
|
const [r] = await pp.query('SELECT count(*) as count from accounts where service_provider_sid = ?',
|
|
matches[0].service_provider_sid);
|
|
if (r[0].count === 0) return {fromCarrier: true};
|
|
else {
|
|
const [accounts] = await pp.query('SELECT * from accounts where service_provider_sid = ?',
|
|
matches[0].service_provider_sid);
|
|
return {
|
|
fromCarrier: true,
|
|
gateway: matches[0],
|
|
account_sid: accounts[0].account_sid,
|
|
account: accounts[0]
|
|
};
|
|
}
|
|
}
|
|
const gateway = matches.find((m) => m.voip_carrier_sid === r[0].voip_carrier_sid);
|
|
const [accounts] = await pp.query(sqlAccountBySid, r[0].account_sid);
|
|
assert(accounts.length);
|
|
return {
|
|
fromCarrier: true,
|
|
gateway,
|
|
account_sid: r[0].account_sid,
|
|
application_sid: r[0].application_sid,
|
|
account: accounts[0]
|
|
};
|
|
}
|
|
return failure;
|
|
}
|
|
}
|
|
|
|
/* get all the carriers and gateways for the account owning this sip realm */
|
|
const [gwAcc] = await pp.query(sqlSelectAllCarriersForAccountByRealm, uri.host);
|
|
const [gwSP] = gwAcc.length ? [[]] : await pp.query(sqlSelectAllCarriersForSPByRealm, uri.host);
|
|
const gw = gwAcc.concat(gwSP);
|
|
const selected = gw.find(gatewayMatchesSourceAddress.bind(null, req.source_address));
|
|
if (selected) {
|
|
const [a] = await pp.query(sqlAccountByRealm, uri.host);
|
|
if (0 === a.length) return failure;
|
|
return {
|
|
fromCarrier: true,
|
|
gateway: selected,
|
|
account_sid: a[0].account_sid,
|
|
application_sid: selected.application_sid,
|
|
account: a[0]
|
|
};
|
|
}
|
|
return failure;
|
|
};
|
|
|
|
return {
|
|
wasOriginatedFromCarrier,
|
|
getApplicationForDidAndCarrier,
|
|
getOutboundGatewayForRefer
|
|
};
|
|
};
|