mirror of https://github.com/sipwise/prosody.git
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.
337 lines
9.9 KiB
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
|