Mercurial > prosody-modules
view mod_voipms/mod_voipms.lua @ 6319:04c3273cb81f
mod_auth_cyrus: Add empty 'profile' table to SASL handler objects
This is for compatibility with Prosody's built-in util.sasl objects.
A SASL profile table usually includes methods supported by the backend, which
can be used by SASL mechanism handlers to perform operations (such as testing
the password). It also optionally contains a 'cb' field with channel binding
method handlers.
The Cyrus backend doesn't support channel binding, and doesn't have the same
concept of auth backend methods (it handles all that internally, and Prosody
has no insight or control over it).
Thus, we create an empty profile which informs Prosody that the SASL handler
does not support any of the auth or channel binding methods. Some features
will not work, but they didn't work anyway. This just makes it explicit.
This fixes a traceback in mod_sasl2_fast, which expected SASL handlers to
always contain a 'profile' field.
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Thu, 04 Sep 2025 10:14:46 +0100 |
| parents | 8f2de6652930 |
| children | fa976338928c |
line wrap: on
line source
local http = require "net.http" local json = require "util.json" local st = require "util.stanza" local api_username = module:get_option_string("voipms_api_username") local api_password = module:get_option_string("voipms_api_password") local query_key = module:get_option("voipms_query_key") local jid_map = module:get_option("voipms_jid_map") or {} local rest_endpoint = "https://voip.ms/api/v1/rest.php" if not api_username or not api_password or not query_key then module:log("error", "Missing required config values (voipms_api_username, voipms_api_password, voipms_query_key)") return end for jid, dids in pairs(jid_map) do if type(dids) ~= "table" then module:log("error", "Invalid voipms_jid_map entry for %s: must be a list of DIDs", jid) return end end module:depends("http") local function normalize_number(num) if not num then return nil end if num:sub(1, 1) ~= "+" then return "+1" .. num end return num end local function extract_query_key(event) return (event.request.url.query or ""):match("key=([^&]+)") end module:provides("http", { route = { ["POST"] = function(event) local req = event.request local body = req.body or "" if extract_query_key(event) ~= query_key then module:log("warn", "Unauthorized webhook: missing or invalid key") return { status_code = 403 } end local json_payload, err = json.decode(body) if not json_payload then module:log("warn", "Invalid JSON: %s", err or "unknown error") return { status_code = 400 } end local payload = json_payload.data and json_payload.data.payload if not payload then module:log("warn", "Missing payload in JSON") return { status_code = 400 } end local from = payload.from and payload.from.phone_number local to_list = payload.to or {} local to = #to_list > 0 and to_list[1].phone_number or nil if not from or not to then module:log("warn", "Missing phone numbers (from: %s, to: %s)", tostring(from), tostring(to)) return { status_code = 400 } end local normalized_from = normalize_number(from) local normalized_to = normalize_number(to) local target_jid = nil for jid, dids in pairs(jid_map) do for _, did in ipairs(dids) do if normalize_number(did) == normalized_to then target_jid = jid break end end if target_jid then break end end if not target_jid then module:log("warn", "No JID mapping for DID %s", normalized_to) return { status_code = 404 } end local message_text = payload.text or "" local from_jid = normalized_from .. "%" .. normalized_to .. "@" .. module.host if message_text ~= "" then local text_message = st.message({ from = from_jid, to = target_jid, type = "chat" }):tag("body"):text(message_text):up() module:send(text_message) module:log("info", "Delivered text SMS from %s to %s", normalized_from, target_jid) end if payload.media and #payload.media > 0 then for _, media_item in ipairs(payload.media) do if media_item.url then local media_message = st.message({ from = from_jid, to = target_jid, type = "chat" }) :tag("body"):text(media_item.url):up() :tag("x", { xmlns = "jabber:x:oob" }) :tag("url"):text(media_item.url):up() :up() module:send(media_message) module:log("info", "Delivered media URL from %s to %s: %s", normalized_from, target_jid, media_item.url) end end end return { status_code = 204 } end } }) module:hook("message/bare", function(event) local stanza = event.stanza if stanza.attr.type ~= "chat" then return end local from_jid = stanza.attr.from local to_jid = stanza.attr.to local body = stanza:get_child_text("body") if not body or body == "" then return end local to_node = to_jid:match("^(.-)@") if not to_node then module:log("warn", "Malformed JID in message.to: %s", to_jid) return end local dst_number, from_number = to_node:match("([^%%]+)%%(.+)") if not dst_number or not from_number then module:log("warn", "Malformed to JID node: %s", to_node) return end dst_number = normalize_number(dst_number) from_number = normalize_number(from_number) local media_urls = {} for line in body:gmatch("[^\r\n]+") do if line:match("^https?://") then table.insert(media_urls, line) end end local method = (#media_urls > 0) and "sendMMS" or "sendSMS" local query = { api_username = api_username, api_password = api_password, method = method, did = from_number, dst = dst_number, message = body } if method == "sendMMS" then for i, url in ipairs(media_urls) do query["media_url[" .. (i - 1) .. "]"] = url end end local query_str = http.formencode(query) http.request(rest_endpoint .. "?" .. query_str, { method = "GET"; }, function(response_body, code) if code == 200 then local resp, err = json.decode(response_body) if not resp or resp.status ~= "success" then local err_msg = string.format("Failed to send %s: %s", method, err or (resp and resp.status) or "unknown") module:log("error", "%s", err_msg) local err_reply = st.error_reply(stanza, "cancel", "remote-server-error", err_msg) module:send(err_reply) else module:log("info", "Sent %s from %s to %s", method, from_number, dst_number) end else local err_msg = string.format("HTTP error sending %s: code %s", method, tostring(code)) module:log("error", "%s", err_msg) local err_reply = st.error_reply(stanza, "cancel", "remote-server-error", err_msg) module:send(err_reply) end end) return true end)
