init
This commit is contained in:
commit
883f31932e
169 changed files with 5676 additions and 0 deletions
144
ansible/roles/prosody/templates/mod_offline_email.lua.j2
Normal file
144
ansible/roles/prosody/templates/mod_offline_email.lua.j2
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
local socket = require "socket";
|
||||
local ssl = require "ssl";
|
||||
local b64 = require "prosody.util.encodings".base64.encode;
|
||||
|
||||
local smtp_server = module:get_option_string("offline_email_smtp_server");
|
||||
local smtp_port = module:get_option_number("offline_email_smtp_port", 587);
|
||||
local smtp_username = module:get_option_string("offline_email_smtp_username");
|
||||
local smtp_password = module:get_option_string("offline_email_smtp_password");
|
||||
local smtp_from = module:get_option_string("offline_email_smtp_from", smtp_username);
|
||||
|
||||
local accounts = module:open_store("accounts");
|
||||
|
||||
local function read_response(sock)
|
||||
local lines = {};
|
||||
while true do
|
||||
local line, err = sock:receive("*l");
|
||||
if not line then return nil, err; end
|
||||
table.insert(lines, line);
|
||||
if line:sub(4, 4) == " " then break; end
|
||||
end
|
||||
local code = tonumber(lines[1]:sub(1, 3));
|
||||
return code, table.concat(lines, "\n");
|
||||
end
|
||||
|
||||
local function send_command(sock, cmd)
|
||||
sock:send(cmd .. "\r\n");
|
||||
return read_response(sock);
|
||||
end
|
||||
|
||||
local function send_email(to_email, subject, body_text)
|
||||
local sock = socket.tcp();
|
||||
sock:settimeout(10);
|
||||
|
||||
local ok, err = sock:connect(smtp_server, smtp_port);
|
||||
if not ok then return nil, "connect: " .. tostring(err); end
|
||||
|
||||
local code, resp = read_response(sock);
|
||||
if not code or code ~= 220 then
|
||||
sock:close();
|
||||
return nil, "greeting: " .. tostring(resp);
|
||||
end
|
||||
|
||||
code = send_command(sock, "EHLO localhost");
|
||||
if code ~= 250 then sock:close(); return nil, "EHLO failed"; end
|
||||
|
||||
code = send_command(sock, "STARTTLS");
|
||||
if code ~= 220 then sock:close(); return nil, "STARTTLS rejected"; end
|
||||
|
||||
sock = ssl.wrap(sock, { mode = "client", protocol = "any", verify = "none" });
|
||||
ok = sock:dohandshake();
|
||||
if not ok then sock:close(); return nil, "TLS handshake failed"; end
|
||||
|
||||
code = send_command(sock, "EHLO localhost");
|
||||
if code ~= 250 then sock:close(); return nil, "EHLO after TLS failed"; end
|
||||
|
||||
local auth_str = b64("\0" .. smtp_username .. "\0" .. smtp_password);
|
||||
code = send_command(sock, "AUTH PLAIN " .. auth_str);
|
||||
if code ~= 235 then sock:close(); return nil, "AUTH failed"; end
|
||||
|
||||
code = send_command(sock, "MAIL FROM:<" .. smtp_from .. ">");
|
||||
if code ~= 250 then sock:close(); return nil, "MAIL FROM failed"; end
|
||||
|
||||
code = send_command(sock, "RCPT TO:<" .. to_email .. ">");
|
||||
if code ~= 250 then sock:close(); return nil, "RCPT TO failed"; end
|
||||
|
||||
code = send_command(sock, "DATA");
|
||||
if code ~= 354 then sock:close(); return nil, "DATA failed"; end
|
||||
|
||||
local message = "From: " .. smtp_from .. "\r\n" ..
|
||||
"To: " .. to_email .. "\r\n" ..
|
||||
"Subject: " .. subject .. "\r\n" ..
|
||||
"Content-Type: text/plain; charset=UTF-8\r\n" ..
|
||||
"\r\n" ..
|
||||
body_text .. "\r\n.\r\n";
|
||||
sock:send(message);
|
||||
code = read_response(sock);
|
||||
if code ~= 250 then sock:close(); return nil, "message rejected"; end
|
||||
|
||||
send_command(sock, "QUIT");
|
||||
sock:close();
|
||||
return true;
|
||||
end
|
||||
|
||||
local function notify_offline(to_user, stanza)
|
||||
local body = stanza:get_child_text("body");
|
||||
local is_encrypted = stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") ~= nil;
|
||||
|
||||
if not is_encrypted and (not body or body == "") then return; end
|
||||
|
||||
local account = accounts:get(to_user);
|
||||
if not account or not account.email then return; end
|
||||
|
||||
local from_jid = stanza.attr.from or "unknown";
|
||||
local from_name = from_jid:match("^([^@]+)") or from_jid;
|
||||
local subject = "New message from " .. from_name;
|
||||
local email_body;
|
||||
if is_encrypted then
|
||||
email_body = "Ahoy,\r\n\r\n" ..
|
||||
"while offline, you've received an encrypted message from " .. from_name .. ".\r\n\r\n" ..
|
||||
"This message was sent using end-to-end encryption (OMEMO).\r\n" ..
|
||||
"To view it, log in from the same device where your encryption keys are stored. " ..
|
||||
"The message cannot be read on a different device or client.\r\n\r\n" ..
|
||||
"Kind regards,\r\n\r\n" ..
|
||||
"Tiara";
|
||||
else
|
||||
email_body = "Ahoy,\r\n\r\n" ..
|
||||
"while offline, you received a plain-text message from " .. from_name .. ":\r\n\r\n" ..
|
||||
">> " .. body .. "\r\n\r\n" ..
|
||||
"Kind regards,\r\n\r\n" ..
|
||||
"Tiara";
|
||||
end
|
||||
|
||||
local ok, err = send_email(account.email, subject, email_body);
|
||||
if ok then
|
||||
module:log("info", "Sent offline email notification to %s for user %s", account.email, to_user);
|
||||
else
|
||||
module:log("warn", "Failed to send offline email to %s: %s", account.email, tostring(err));
|
||||
end
|
||||
end
|
||||
|
||||
local bare_sessions = prosody.bare_sessions;
|
||||
local jid = require "util.jid";
|
||||
|
||||
local function has_hibernating_session(username)
|
||||
local bare = username .. "@" .. module.host;
|
||||
local user = bare_sessions[bare];
|
||||
if not user then return false; end
|
||||
for _, session in pairs(user.sessions) do
|
||||
if session.hibernating then return true; end
|
||||
end
|
||||
return false;
|
||||
end
|
||||
|
||||
-- Fired when user is offline (no sessions) or when smacks gives up on a hibernating session
|
||||
module:hook("message/offline/handle", function(event)
|
||||
local to_user = event.username or (event.stanza.attr.to and event.stanza.attr.to:match("^([^@]+)"));
|
||||
if not to_user then return; end
|
||||
-- Skip if smacks is still hibernating — we'll get called again when it expires
|
||||
if has_hibernating_session(to_user) then
|
||||
module:log("debug", "Skipping email for %s — smacks session still hibernating", to_user);
|
||||
return;
|
||||
end
|
||||
notify_offline(to_user, event.stanza);
|
||||
end, 1);
|
||||
Loading…
Add table
Add a link
Reference in a new issue