diff --git a/debian/control b/debian/control index 86fd4de..1f32e78 100644 --- a/debian/control +++ b/debian/control @@ -11,6 +11,7 @@ Architecture: amd64 Depends: lua-dbi-mysql, lua-dbi-common, lua-sec, + lua-rex-pcre, ${misc:Depends}, ${shlibs:Depends} Description: ngcp modules for the prosody Jabber/XMPP server This package provides modules for the Prosody Jabber/XMPP server @@ -24,3 +25,5 @@ Description: ngcp modules for the prosody Jabber/XMPP server for phone/video of the requested user * mod_sipwise_vhosts_sql.lua: load vhosts from DB on startup of Prosody XMPP server + * mod_sipwise_vjud.lua: implement Jabber Search with implicit number + normalization using the NGCP rewrite rules diff --git a/plugins/mod_sipwise_vjud.lua b/plugins/mod_sipwise_vjud.lua new file mode 100644 index 0000000..a8405db --- /dev/null +++ b/plugins/mod_sipwise_vjud.lua @@ -0,0 +1,168 @@ +local nodeprep = require "util.encodings".stringprep.nodeprep; +local jid_split = require "util.jid".split; +local sql = module:require("sql"); +local st = require "util.stanza"; +local template = require "util.template"; +local array = require "util.array"; +local rex = require "rex_pcre"; + +local get_reply = template[[ + + Please enter a phone number + + +]].apply({}); + +local usr_replacements_query = [[ +SELECT vrr.match_pattern, vrr.replace_pattern FROM voip_preferences vp + LEFT JOIN voip_usr_preferences vup ON vup.attribute_id = vp.id + LEFT JOIN voip_subscribers vs ON vs.id = vup.subscriber_id + LEFT JOIN voip_domains vd ON vd.id = vs.domain_id + LEFT JOIN voip_rewrite_rule_sets vrrs ON vrrs.callee_in_dpid = vup.value + LEFT JOIN 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 voip_preferences vp + LEFT JOIN voip_dom_preferences vdp ON vdp.attribute_id = vp.id + LEFT JOIN voip_domains vd ON vd.id = vdp.domain_id + LEFT JOIN voip_rewrite_rule_sets vrrs ON vrrs.callee_in_dpid = vdp.value + LEFT JOIN 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 voip_preferences vp + LEFT JOIN voip_usr_preferences vup ON vup.attribute_id = vp.id + LEFT JOIN voip_subscribers vs ON vs.id = vup.subscriber_id + LEFT JOIN 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 mod_sql = module:require("sql"); +local params = module:get_option("voip_sql", { + driver = "MySQL", + database = "provisioning", + username = "prosody", + password = "PW_PROSODY", + host = "localhost" +}); +engine = mod_sql:create_engine(params); +engine:execute("SET NAMES 'utf8' COLLATE 'utf8_bin';"); + +function normalize_number(user, host, number) + local locale_info = {}; + 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; + 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 + 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 + number = new_number; + break; + end + end + return number; +end + +function search_by_number(number) + local results = {}; + for result in engine:select(lookup_query, number) do + table.insert(results, result[1].."@"..result[2]); + end + return results; +end + +module:depends("disco"); +module:add_feature("jabber:iq:search"); + +module:hook("iq/host/jabber:iq:search:query", function(event) + local origin, stanza = event.origin, event.stanza; + + if stanza.attr.type == "get" then + return origin.send(st.reply(stanza):add_child(get_reply)); + else + local user, host = jid_split(stanza.attr.from); + local number = stanza.tags[1]:get_child_text("nick"); + + -- Reconnect to DB if necessary + if not engine.conn:ping() then + engine.conn = nil; + engine:connect(); + end + + number = normalize_number(user, host, number); + + local reply = st.reply(stanza):query("jabber:iq:search"); + + for _, jid in ipairs(search_by_number(number)) do + reply:tag("item", { jid = jid }):up(); + end + + return origin.send(reply); + end +end); + +function module.command(arg) + local jid = require "util.jid"; + 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 = 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