From 28b5fc52a5bee737a0f1b59d240a36d2f0225306 Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Wed, 7 Aug 2013 22:35:13 +0200 Subject: [PATCH] Implement XEP-0055 with implicit rewrite rules. This allows for searching for denormalized phone numbers via Jabber Search, and the module will use the rewrite rules of the searching user to normalize to E164. Using this number, the subscriber is looked up in dbaliases table and is returned as JID. --- debian/control | 3 + plugins/mod_sipwise_vjud.lua | 168 +++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 plugins/mod_sipwise_vjud.lua 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