You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
prosody/plugins/mod_sipwise_vjud.lua

337 lines
9.9 KiB

--
-- Copyright (C) 2013-2015 Sipwise GmbH <development@sipwise.com>
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
module:depends("disco");
local softreq = require "util.dependencies".softreq;
local ut_jid = require "util.jid";
local sql = require "util.sql";
local st = require "util.stanza";
local template = require "util.template";
local prosodyctl = require "util.prosodyctl"
local dataforms_new = require "util.dataforms".new;
local ut = require "ngcp.utils";
local hosts = prosody.hosts;
local rex = softreq "rex_pcre";
if not rex then
rex = require "rex_pcre2";
end
local form_layout = dataforms_new{
title= 'User Directory Search';
instructions = 'Please provide the following information to search for subscribers';
{
type = 'text-single',
label = 'e164 Phone number',
name = 'e164',
required = false,
},
{
type = 'text-single',
label = 'domain',
name = 'domain',
required = false,
},
};
local form_item = template[[
<item>
<field var='{name}'>
<value>{value}</value>
</field>
</item>
]];
local form_reply = {
domain = template[[
<query xmlns='jabber:iq:search'>
<x xmlns='jabber:x:data' type='result'>
<field type='hidden' var='FORM_TYPE'>
<value>jabber:iq:search</value>
</field>
<reported>
<field type='text-single'
label='domain'
var='domain'/>
</reported>
</x>
</query>
]],
e164 = template[[
<query xmlns='jabber:iq:search'>
<x xmlns='jabber:x:data' type='result'>
<field type='hidden' var='FORM_TYPE'>
<value>jabber:iq:search</value>
</field>
<reported>
<field type='text-single'
label='e164 Phone number'
var='e164'/>
</reported>
</x>
</query>
]],
form = template[[
<query xmlns='jabber:iq:search'>
<instructions>Use the enclosed form to search</instructions>
<x xmlns='jabber:x:data' type='form'>
<title>User Directory Search</title>
<instructions>Please provide the following information to search for subscribers</instructions>
<field type='hidden' var='FORM_TYPE'>
<value>jabber:iq:search</value>
</field>
<field type='text-single'
label='e164 Phone number'
var='e164'/>
<field type='text-single'
label='domain'
var='domain'/>
</x>
<nick/>
</query>
]]
};
local usr_replacements_query = [[
SELECT vrr.match_pattern, vrr.replace_pattern FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_usr_preferences vup ON vup.attribute_id = vp.id
LEFT JOIN provisioning.voip_subscribers vs ON vs.id = vup.subscriber_id
LEFT JOIN provisioning.voip_domains vd ON vd.id = vs.domain_id
LEFT JOIN provisioning.voip_rewrite_rule_sets vrrs ON vrrs.callee_in_dpid = vup.value
LEFT JOIN provisioning.voip_rewrite_rules vrr ON vrr.set_id = vrrs.id
AND vrr.direction = 'in'
AND vrr.field = 'callee'
WHERE vp.attribute = 'rewrite_callee_in_dpid' AND vs.username = ? AND vd.domain = ?
ORDER BY vrr.priority ASC;
]];
local dom_replacements_query = [[
SELECT vrr.match_pattern, vrr.replace_pattern FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_dom_preferences vdp ON vdp.attribute_id = vp.id
LEFT JOIN provisioning.voip_domains vd ON vd.id = vdp.domain_id
LEFT JOIN provisioning.voip_rewrite_rule_sets vrrs ON vrrs.callee_in_dpid = vdp.value
LEFT JOIN provisioning.voip_rewrite_rules vrr ON vrr.set_id = vrrs.id
AND vrr.direction = 'in' AND vrr.field = 'callee'
WHERE vp.attribute = 'rewrite_callee_in_dpid' AND vd.domain = ?
ORDER BY vrr.priority ASC;
]];
local locale_query = [[
SELECT vp.attribute, vup.value FROM provisioning.voip_preferences vp
LEFT JOIN provisioning.voip_usr_preferences vup ON vup.attribute_id = vp.id
LEFT JOIN provisioning.voip_subscribers vs ON vs.id = vup.subscriber_id
LEFT JOIN provisioning.voip_domains vd ON vd.id = vs.domain_id
WHERE (vp.attribute = 'ac' or vp.attribute = 'cc')
AND vs.username = ?
AND vd.domain = ?;
]];
local lookup_query = [[
SELECT username,domain FROM kamailio.dbaliases
WHERE alias_username=?;
]];
local default_params = module:get_option("sql");
local engine;
-- Reconnect to DB if necessary
local function reconect_check()
if not engine.conn:ping() then
engine.conn = nil;
engine:connect();
end
end
local function normalize_number(user, host, number)
local locale_info = {};
reconect_check();
for row in engine:select(locale_query, user, host) do
locale_info["caller_"..row[1]] = row[2];
end
local replacement_regexes = {};
local usr_pref = 0;
for row in engine:select(usr_replacements_query, user, host) do
usr_pref = 1;
module:log("debug", "user rewrite_callee_in_dpid preference found");
local patt, repl = row[1], row[2]
:gsub("%$avp%(s:([a-zA-Z_]+)%)", locale_info)
:gsub("\\", "%%"):gsub("%%%%", "\\");
table.insert(replacement_regexes, { patt, repl });
end
if usr_pref == 0 then
for row in engine:select(dom_replacements_query, host) do
module:log("debug", "domain rewrite_callee_in_dpid preference found");
local patt, repl = row[1], row[2]
:gsub("%$avp%(s:([a-zA-Z_]+)%)", locale_info)
:gsub("\\", "%%"):gsub("%%%%", "\\");
table.insert(replacement_regexes, { patt, repl });
end
end
for _, rule in ipairs(replacement_regexes) do
local new_number, n_matches = rex.gsub(number, rule[1], rule[2]);
if n_matches > 0 then
module:log("debug", "rule [%s] matched [%s]->[%s]",
tostring(rule[1]), tostring(number), tostring(new_number));
number = new_number;
break;
end
end
return number;
end
local function search_by_number(number)
local results = {};
reconect_check();
module:log("debug", "search jids with number:[%s]", tostring(number));
for result in engine:select(lookup_query, number) do
table.insert(results, result[1].."@"..result[2]);
end
return results;
end
local function get_dataform_items(vals, fieldname)
local res = {};
for _,v in ipairs(vals) do
table.insert(res, form_item.apply({ name = fieldname, value = v }));
end
return res;
end
local function search_domains(dom)
local hosts_keys = ut.table.keys(hosts);
local res = {};
module:log("debug", "search for domain: %s", tostring(dom));
if dom == '*' then
res = get_dataform_items(hosts_keys, 'domain');
elseif ut.table.contains(hosts_keys, dom) then
res = get_dataform_items({dom,}, 'domain');
end
return res;
end
function module.command(arg)
local warn = prosodyctl.show_warning;
local command = arg[1];
if not command then
warn("Valid subcommands: normalize");
return 0;
end
table.remove(arg, 1);
if command == "normalize" then
if #arg ~= 2 then
warn("Usage: normalize USER@HOST NUMBER");
return 1;
end
local user_jid, number = arg[1], arg[2];
local user, host = ut_jid.prepped_split(user_jid);
if not (user and host) then
warn("Invalid JID: "..user_jid);
return 1;
end
print(normalize_number(user, host, number));
elseif command == "query" then
if #arg ~= 1 then
warn("Usage: query NUMBER");
warn(" NUMBER must be normalized (see 'normalize' command)");
return 1;
end
local results = search_by_number(arg[1]);
for _, jid in ipairs(results) do
print("", jid);
end
end
return 0;
end
local function append_x_items(form, values, reply)
local x = form:get_child('x', 'jabber:x:data');
for _, v in ipairs(values) do
x:add_child(v):up();
end
reply:add_child(x);
end
if module:get_host_type() ~= "component" then
error("Don't load mod_sipwise_vjud manually,"..
" it should be for a component", 0);
end
module:add_feature("jabber:iq:search");
module:add_feature("jabber:x:data"); -- dataforms
module:hook("iq/host/jabber:iq:search:query", function(event)
local origin, stanza = event.origin, event.stanza;
module:log("debug", "stanza[%s]", tostring(stanza));
if stanza.attr.type == "get" then
return origin.send(st.reply(stanza):add_child(form_reply.form.apply({})));
else
local user, host = ut_jid.split(stanza.attr.from);
local query = stanza:get_child("query",'jabber:iq:search');
local form_stanza = query:get_child("x",'jabber:x:data');
local reply;
local search_number = stanza.tags[1]:get_child_text("nick");
local search_domain
if form_stanza then
local form_data, form_errors = form_layout:data(form_stanza);
if form_errors and form_errors['e164'] then
reply = st.error_reply(stanza, "modify",
"bad-request", "e164: "..form_errors['e164']);
return origin.send(reply);
else
search_number = form_data['e164'] or search_number;
search_domain = form_data['domain'];
end
end
if search_number then
local number = normalize_number(user, host, search_number);
local data = search_by_number(number);
reply = st.reply(stanza):query("jabber:iq:search");
if form_stanza then
append_x_items(form_reply.e164.apply({}),
get_dataform_items(data, 'e164'), reply);
else
for _, jid in ipairs(data) do
reply:tag("item", { jid = jid }):up();
end
end
elseif search_domain then
reply = st.reply(stanza):query("jabber:iq:search");
append_x_items(form_reply.domain.apply({}),
search_domains(search_domain), reply);
else
reply = st.error_reply(stanza, "modify",
"bad-request", "no domain, nick or e164 field found");
end
return origin.send(reply);
end
end);
local function normalize_params(params)
assert(params.driver and params.database,
"Configuration error: Both the SQL driver and the database need to be specified");
return params;
end
function module.load()
if prosody.prosodyctl then return; end
local engines = module:shared("/*/sql/connections");
local params = normalize_params(module:get_option("auth_sql", default_params));
engine = engines[sql.db2uri(params)];
if not engine then
module:log("debug", "Creating new engine");
engine = sql:create_engine(params);
engines[sql.db2uri(params)] = engine;
end
engine:connect();
module:log("debug", "load OK");
end