commit
491af71329
@ -0,0 +1,218 @@
|
|||||||
|
-- Prosody IM
|
||||||
|
-- Copyright (C) 2008-2010 Matthew Wild
|
||||||
|
-- Copyright (C) 2008-2010 Waqas Hussain
|
||||||
|
--
|
||||||
|
-- This project is MIT/X11 licensed. Please see the
|
||||||
|
-- COPYING file in the source package for more information.
|
||||||
|
--
|
||||||
|
|
||||||
|
local log = require "util.logger".init("stanzarouter")
|
||||||
|
|
||||||
|
local hosts = _G.prosody.hosts;
|
||||||
|
local tostring = tostring;
|
||||||
|
local st = require "util.stanza";
|
||||||
|
local send_s2s = require "core.s2smanager".send_to_host;
|
||||||
|
local jid_split = require "util.jid".split;
|
||||||
|
local jid_prepped_split = require "util.jid".prepped_split;
|
||||||
|
|
||||||
|
local full_sessions = _G.prosody.full_sessions;
|
||||||
|
local bare_sessions = _G.prosody.bare_sessions;
|
||||||
|
|
||||||
|
local function handle_unhandled_stanza(host, origin, stanza)
|
||||||
|
local name, xmlns, origin_type = stanza.name, stanza.attr.xmlns or "jabber:client", origin.type;
|
||||||
|
if name == "iq" and xmlns == "jabber:client" then
|
||||||
|
if stanza.attr.type == "get" or stanza.attr.type == "set" then
|
||||||
|
xmlns = stanza.tags[1].attr.xmlns or "jabber:client";
|
||||||
|
log("debug", "Stanza of type %s from %s has xmlns: %s", name, origin_type, xmlns);
|
||||||
|
else
|
||||||
|
log("debug", "Discarding %s from %s of type: %s", name, origin_type, stanza.attr.type);
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if stanza.attr.xmlns == nil then
|
||||||
|
log("debug", "Unhandled %s stanza: %s; xmlns=%s", origin.type, stanza.name, xmlns); -- we didn't handle it
|
||||||
|
if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
|
||||||
|
origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
|
||||||
|
end
|
||||||
|
elseif not((name == "features" or name == "error") and xmlns == "http://etherx.jabber.org/streams") then -- FIXME remove check once we handle S2S features
|
||||||
|
log("warn", "Unhandled %s stream element: %s; xmlns=%s: %s", origin.type, stanza.name, xmlns, tostring(stanza)); -- we didn't handle it
|
||||||
|
origin:close("unsupported-stanza-type");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local iq_types = { set=true, get=true, result=true, error=true };
|
||||||
|
function core_process_stanza(origin, stanza)
|
||||||
|
if stanza.attr and stanza.attr["xml:lang"] then
|
||||||
|
stanza.attr["xml:lang"] = nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
(origin.log or log)("debug", "Received[%s]: %s", origin.type, stanza:top_tag())
|
||||||
|
|
||||||
|
-- TODO verify validity of stanza (as well as JID validity)
|
||||||
|
if stanza.attr.type == "error" and #stanza.tags == 0 then return; end -- TODO invalid stanza, log
|
||||||
|
if stanza.name == "iq" then
|
||||||
|
if not stanza.attr.id then stanza.attr.id = ""; end -- COMPAT Jabiru doesn't send the id attribute on roster requests
|
||||||
|
if not iq_types[stanza.attr.type] or ((stanza.attr.type == "set" or stanza.attr.type == "get") and (#stanza.tags ~= 1)) then
|
||||||
|
origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid IQ type or incorrect number of children"));
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if origin.type == "c2s" and not stanza.attr.xmlns then
|
||||||
|
if not origin.full_jid
|
||||||
|
and not(stanza.name == "iq" and stanza.attr.type == "set" and stanza.tags[1] and stanza.tags[1].name == "bind"
|
||||||
|
and stanza.tags[1].attr.xmlns == "urn:ietf:params:xml:ns:xmpp-bind") then
|
||||||
|
-- authenticated client isn't bound and current stanza is not a bind request
|
||||||
|
if stanza.attr.type ~= "result" and stanza.attr.type ~= "error" then
|
||||||
|
origin.send(st.error_reply(stanza, "auth", "not-authorized")); -- FIXME maybe allow stanzas to account or server
|
||||||
|
end
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- TODO also, stanzas should be returned to their original state before the function ends
|
||||||
|
stanza.attr.from = origin.full_jid;
|
||||||
|
end
|
||||||
|
local to, xmlns = stanza.attr.to, stanza.attr.xmlns;
|
||||||
|
local from = stanza.attr.from;
|
||||||
|
local node, host, resource;
|
||||||
|
local from_node, from_host, from_resource;
|
||||||
|
local to_bare, from_bare;
|
||||||
|
if to then
|
||||||
|
if full_sessions[to] or bare_sessions[to] or hosts[to] then
|
||||||
|
node, host = jid_split(to); -- TODO only the host is needed, optimize
|
||||||
|
else
|
||||||
|
node, host, resource = jid_prepped_split(to);
|
||||||
|
if not host then
|
||||||
|
log("warn", "Received stanza with invalid destination JID: %s", to);
|
||||||
|
if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
|
||||||
|
origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The destination address is invalid: "..to));
|
||||||
|
end
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
to_bare = node and (node.."@"..host) or host; -- bare JID
|
||||||
|
if resource then to = to_bare.."/"..resource; else to = to_bare; end
|
||||||
|
stanza.attr.to = to;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if from and not origin.full_jid then
|
||||||
|
-- We only stamp the 'from' on c2s stanzas, so we still need to check validity
|
||||||
|
from_node, from_host, from_resource = jid_prepped_split(from);
|
||||||
|
if not from_host then
|
||||||
|
log("warn", "Received stanza with invalid source JID: %s", from);
|
||||||
|
if stanza.attr.type ~= "error" and stanza.attr.type ~= "result" then
|
||||||
|
origin.send(st.error_reply(stanza, "modify", "jid-malformed", "The source address is invalid: "..from));
|
||||||
|
end
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
from_bare = from_node and (from_node.."@"..from_host) or from_host; -- bare JID
|
||||||
|
if from_resource then from = from_bare.."/"..from_resource; else from = from_bare; end
|
||||||
|
stanza.attr.from = from;
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[if to and not(hosts[to]) and not(hosts[to_bare]) and (hosts[host] and hosts[host].type ~= "local") then -- not for us?
|
||||||
|
log("warn", "stanza recieved for a non-local server");
|
||||||
|
return; -- FIXME what should we do here?
|
||||||
|
end]] -- FIXME
|
||||||
|
|
||||||
|
if (origin.type == "s2sin" or origin.type == "c2s" or origin.type == "component") and xmlns == nil then
|
||||||
|
if origin.type == "s2sin" and not origin.dummy then
|
||||||
|
local host_status = origin.hosts[from_host];
|
||||||
|
if not host_status or not host_status.authed then -- remote server trying to impersonate some other server?
|
||||||
|
log("warn", "Received a stanza claiming to be from %s, over a stream authed for %s!", from_host, origin.from_host);
|
||||||
|
return; -- FIXME what should we do here? does this work with subdomains?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
core_post_stanza(origin, stanza, origin.full_jid);
|
||||||
|
else
|
||||||
|
local h = hosts[stanza.attr.to or origin.host or origin.to_host];
|
||||||
|
if h then
|
||||||
|
local event;
|
||||||
|
if xmlns == nil then
|
||||||
|
if stanza.name == "iq" and (stanza.attr.type == "set" or stanza.attr.type == "get") then
|
||||||
|
event = "stanza/iq/"..stanza.tags[1].attr.xmlns..":"..stanza.tags[1].name;
|
||||||
|
else
|
||||||
|
event = "stanza/"..stanza.name;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
event = "stanza/"..xmlns..":"..stanza.name;
|
||||||
|
end
|
||||||
|
if h.events.fire_event(event, {origin = origin, stanza = stanza}) then return; end
|
||||||
|
end
|
||||||
|
if host and not hosts[host] then host = nil; end -- COMPAT: workaround for a Pidgin bug which sets 'to' to the SRV result
|
||||||
|
handle_unhandled_stanza(host or origin.host or origin.to_host, origin, stanza);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function core_post_stanza(origin, stanza, preevents)
|
||||||
|
local to = stanza.attr.to;
|
||||||
|
local node, host, resource = jid_split(to);
|
||||||
|
local to_bare = node and (node.."@"..host) or host; -- bare JID
|
||||||
|
|
||||||
|
local to_type, to_self;
|
||||||
|
if node then
|
||||||
|
if resource then
|
||||||
|
to_type = '/full';
|
||||||
|
else
|
||||||
|
to_type = '/bare';
|
||||||
|
if node == origin.username and host == origin.host then
|
||||||
|
stanza.attr.to = nil;
|
||||||
|
to_self = true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if host then
|
||||||
|
to_type = '/host';
|
||||||
|
else
|
||||||
|
to_type = '/bare';
|
||||||
|
to_self = true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local event_data = {origin=origin, stanza=stanza};
|
||||||
|
if preevents then -- c2s connection
|
||||||
|
if hosts[origin.host].events.fire_event('pre-'..stanza.name..to_type, event_data) then return; end -- do preprocessing
|
||||||
|
end
|
||||||
|
local h = hosts[to_bare] or hosts[host or origin.host];
|
||||||
|
if h then
|
||||||
|
if h.events.fire_event(stanza.name..to_type, event_data) then return; end -- do processing
|
||||||
|
if to_self and h.events.fire_event(stanza.name..'/self', event_data) then return; end -- do processing
|
||||||
|
handle_unhandled_stanza(h.host, origin, stanza);
|
||||||
|
else
|
||||||
|
core_route_stanza(origin, stanza);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function core_route_stanza(origin, stanza)
|
||||||
|
local node, host, resource = jid_split(stanza.attr.to);
|
||||||
|
local from_node, from_host, from_resource = jid_split(stanza.attr.from);
|
||||||
|
|
||||||
|
-- Auto-detect origin if not specified
|
||||||
|
origin = origin or hosts[from_host];
|
||||||
|
if not origin then return false; end
|
||||||
|
|
||||||
|
if hosts[host] then
|
||||||
|
-- old stanza routing code removed
|
||||||
|
core_post_stanza(origin, stanza);
|
||||||
|
elseif origin.type == "c2s" then
|
||||||
|
-- Remote host
|
||||||
|
if not hosts[from_host] then
|
||||||
|
log("error", "No hosts[from_host] (please report): %s", tostring(stanza));
|
||||||
|
end
|
||||||
|
if (not hosts[from_host]) or (not hosts[from_host].disallow_s2s) then
|
||||||
|
local xmlns = stanza.attr.xmlns;
|
||||||
|
--stanza.attr.xmlns = "jabber:server";
|
||||||
|
stanza.attr.xmlns = nil;
|
||||||
|
log("debug", "sending s2s stanza: %s", tostring(stanza.top_tag and stanza:top_tag()) or stanza);
|
||||||
|
send_s2s(origin.host, host, stanza); -- TODO handle remote routing errors
|
||||||
|
stanza.attr.xmlns = xmlns; -- reset
|
||||||
|
else
|
||||||
|
core_route_stanza(hosts[from_host], st.error_reply(stanza, "cancel", "not-allowed", "Communication with remote servers is not allowed"));
|
||||||
|
end
|
||||||
|
elseif origin.type == "component" or origin.type == "local" then
|
||||||
|
-- Route via s2s for components and modules
|
||||||
|
log("debug", "Routing outgoing stanza for %s to %s", from_host, host);
|
||||||
|
send_s2s(from_host, host, stanza);
|
||||||
|
else
|
||||||
|
log("warn", "received %s stanza from unhandled connection type: %s", tostring(stanza.name), tostring(origin.type));
|
||||||
|
end
|
||||||
|
end
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,724 @@
|
|||||||
|
Hahn
|
||||||
|
tamarao
|
||||||
|
Vogel
|
||||||
|
flamefish
|
||||||
|
buffalo
|
||||||
|
Schakal
|
||||||
|
Tintenfisch
|
||||||
|
Schwan
|
||||||
|
javelina
|
||||||
|
blacktail
|
||||||
|
cottontail
|
||||||
|
maiger
|
||||||
|
sphingid
|
||||||
|
Wurm
|
||||||
|
capibara
|
||||||
|
sciara
|
||||||
|
sheepdog
|
||||||
|
Holstein
|
||||||
|
malemute
|
||||||
|
guib
|
||||||
|
Kanguru
|
||||||
|
vetchworm
|
||||||
|
Neandertal
|
||||||
|
reedbird
|
||||||
|
Kuh
|
||||||
|
angler
|
||||||
|
nutria
|
||||||
|
jewfish
|
||||||
|
bonnethead
|
||||||
|
chrysopid
|
||||||
|
mirid
|
||||||
|
Pfeffer
|
||||||
|
deathwatch
|
||||||
|
langouste
|
||||||
|
Hengst
|
||||||
|
gar
|
||||||
|
unau
|
||||||
|
Ameise
|
||||||
|
Giraffe
|
||||||
|
Kopfsalat
|
||||||
|
pig
|
||||||
|
pekan
|
||||||
|
ounce
|
||||||
|
Orang-Utang
|
||||||
|
dory
|
||||||
|
dunlin
|
||||||
|
Fink
|
||||||
|
surmullet
|
||||||
|
Wiesel
|
||||||
|
Geier
|
||||||
|
pipistrel
|
||||||
|
Ungeziefer
|
||||||
|
Hundin
|
||||||
|
aurochs
|
||||||
|
tsetse
|
||||||
|
cockatiel
|
||||||
|
dogy
|
||||||
|
Wiese
|
||||||
|
Schlange
|
||||||
|
mollusc
|
||||||
|
sika
|
||||||
|
Lipizzan
|
||||||
|
scarabaeid
|
||||||
|
pond-skater
|
||||||
|
Lowin
|
||||||
|
Kaulquappe
|
||||||
|
trematode
|
||||||
|
branchiopod
|
||||||
|
dayfly
|
||||||
|
Wal
|
||||||
|
babiroussa
|
||||||
|
takahe
|
||||||
|
Kodiak
|
||||||
|
camelopard
|
||||||
|
Gans
|
||||||
|
sailor's-choice
|
||||||
|
Eisbar
|
||||||
|
brittle-star
|
||||||
|
caracul
|
||||||
|
mound-bird
|
||||||
|
merlin
|
||||||
|
man-of-war
|
||||||
|
surffish
|
||||||
|
Kafer
|
||||||
|
grindle
|
||||||
|
muttonfish
|
||||||
|
allice
|
||||||
|
deathwatch
|
||||||
|
rockchuck
|
||||||
|
blackfish
|
||||||
|
blacksnake
|
||||||
|
hawksbill
|
||||||
|
purebred
|
||||||
|
urochordate
|
||||||
|
conceptus
|
||||||
|
snakefish
|
||||||
|
raptor
|
||||||
|
dipteran
|
||||||
|
panda
|
||||||
|
Krahe
|
||||||
|
chum
|
||||||
|
Lamm
|
||||||
|
tomtit
|
||||||
|
splitworm
|
||||||
|
slowworm
|
||||||
|
Qualle
|
||||||
|
Ganserich
|
||||||
|
hippo
|
||||||
|
coati-mondi
|
||||||
|
mocker
|
||||||
|
lobefin
|
||||||
|
lammergeier
|
||||||
|
Habicht
|
||||||
|
queenfish
|
||||||
|
warrigal
|
||||||
|
thread-fish
|
||||||
|
snapper
|
||||||
|
thick-knee
|
||||||
|
polliwog
|
||||||
|
madrepore
|
||||||
|
doggie
|
||||||
|
suckerfish
|
||||||
|
Keks
|
||||||
|
grackle
|
||||||
|
hogget
|
||||||
|
Fisch
|
||||||
|
doctor-fish
|
||||||
|
tichodrome
|
||||||
|
jabiru
|
||||||
|
Pelikan
|
||||||
|
blackcap
|
||||||
|
gallfly
|
||||||
|
cabassous
|
||||||
|
punky
|
||||||
|
teleost
|
||||||
|
redhead
|
||||||
|
narwal
|
||||||
|
chetah
|
||||||
|
Truthahn
|
||||||
|
Jaguar
|
||||||
|
sprat
|
||||||
|
Pferd
|
||||||
|
Schokolade
|
||||||
|
bearcat
|
||||||
|
horsefly
|
||||||
|
whistler
|
||||||
|
budgereegah
|
||||||
|
cimarron
|
||||||
|
chigger
|
||||||
|
mavis
|
||||||
|
tortrix
|
||||||
|
killer
|
||||||
|
ai
|
||||||
|
moufflon
|
||||||
|
Schlange
|
||||||
|
seasnail
|
||||||
|
hadrosaurus
|
||||||
|
Schabe
|
||||||
|
rinkhals
|
||||||
|
Leopard
|
||||||
|
Pinguin
|
||||||
|
redwing
|
||||||
|
chimp
|
||||||
|
trichopteran
|
||||||
|
cohoe
|
||||||
|
Karotte
|
||||||
|
redbreast
|
||||||
|
sheep-tick
|
||||||
|
Ente
|
||||||
|
mongrel
|
||||||
|
piggy
|
||||||
|
Sau
|
||||||
|
Fliege
|
||||||
|
stint
|
||||||
|
partridge
|
||||||
|
dolphin
|
||||||
|
tropicbird
|
||||||
|
koodoo
|
||||||
|
bonobo
|
||||||
|
Wolf
|
||||||
|
mudcat
|
||||||
|
pork-fish
|
||||||
|
Gans
|
||||||
|
jenny
|
||||||
|
butterball
|
||||||
|
Eule
|
||||||
|
Rentier
|
||||||
|
hoactzin
|
||||||
|
potto
|
||||||
|
polychete
|
||||||
|
springbuck
|
||||||
|
doggie
|
||||||
|
mademoiselle
|
||||||
|
dromedary
|
||||||
|
Schaf
|
||||||
|
pufferfish
|
||||||
|
turaco
|
||||||
|
perissodactyl
|
||||||
|
Schnecke
|
||||||
|
chalcidfly
|
||||||
|
Tiger
|
||||||
|
markhoor
|
||||||
|
surmullet
|
||||||
|
tatou
|
||||||
|
gobbler
|
||||||
|
seriema
|
||||||
|
nemertean
|
||||||
|
Gemuse
|
||||||
|
malemute
|
||||||
|
Rube
|
||||||
|
steamer
|
||||||
|
hanuman
|
||||||
|
scollop
|
||||||
|
Paprika
|
||||||
|
hemerobiid
|
||||||
|
Wurst
|
||||||
|
mongrel
|
||||||
|
gemsbuck
|
||||||
|
greyback
|
||||||
|
Schwein
|
||||||
|
rudderfish
|
||||||
|
kildeer
|
||||||
|
seagull
|
||||||
|
bandtail
|
||||||
|
rattail
|
||||||
|
tortoiseshell-cat
|
||||||
|
anteater
|
||||||
|
sandhopper
|
||||||
|
Giraffe
|
||||||
|
Hamster
|
||||||
|
saurel
|
||||||
|
Ziege
|
||||||
|
dickey-bird
|
||||||
|
Krauter
|
||||||
|
Hummer
|
||||||
|
Insekt
|
||||||
|
whooper
|
||||||
|
heathfowl
|
||||||
|
Rennmaus
|
||||||
|
Papagei
|
||||||
|
bottlenose
|
||||||
|
man
|
||||||
|
hackee
|
||||||
|
Geback
|
||||||
|
pintado
|
||||||
|
urus
|
||||||
|
chenfish
|
||||||
|
gooney
|
||||||
|
angelfish
|
||||||
|
tarantula
|
||||||
|
Thunfisch
|
||||||
|
thrip
|
||||||
|
emmet
|
||||||
|
eelpout
|
||||||
|
merl
|
||||||
|
Milch
|
||||||
|
unau
|
||||||
|
walrus
|
||||||
|
Murmeltier
|
||||||
|
Schaf
|
||||||
|
Vieh
|
||||||
|
Kastanie
|
||||||
|
Fasan
|
||||||
|
Ananas
|
||||||
|
petchary
|
||||||
|
poyou
|
||||||
|
Rotkehlchen
|
||||||
|
duckbill
|
||||||
|
musquash
|
||||||
|
Lowe
|
||||||
|
panda
|
||||||
|
graylag
|
||||||
|
seahorse
|
||||||
|
kitty-cat
|
||||||
|
rasher
|
||||||
|
Heuschrecke
|
||||||
|
megatheriid
|
||||||
|
whidah
|
||||||
|
elk
|
||||||
|
milcher
|
||||||
|
ferret
|
||||||
|
peewee
|
||||||
|
kestrel
|
||||||
|
Hyane
|
||||||
|
ringdove
|
||||||
|
Hai
|
||||||
|
pudding-wife
|
||||||
|
Pekingese
|
||||||
|
sprat
|
||||||
|
Wolf
|
||||||
|
Reh
|
||||||
|
Bohne
|
||||||
|
cheewink
|
||||||
|
Kren
|
||||||
|
Garnele
|
||||||
|
Stinktier
|
||||||
|
poriferan
|
||||||
|
peewit
|
||||||
|
swift
|
||||||
|
skipjack
|
||||||
|
Klapperschlange
|
||||||
|
thysanopteron
|
||||||
|
toad
|
||||||
|
button-quail
|
||||||
|
cob
|
||||||
|
Pfirsich
|
||||||
|
Zwiebel
|
||||||
|
gopher
|
||||||
|
soupfin
|
||||||
|
angleworm
|
||||||
|
pacemaker
|
||||||
|
heathfowl
|
||||||
|
moose
|
||||||
|
Saft
|
||||||
|
Heuschrecker
|
||||||
|
striper
|
||||||
|
gallfly
|
||||||
|
redstart
|
||||||
|
nautilus
|
||||||
|
kinkajou
|
||||||
|
partridge
|
||||||
|
grayback
|
||||||
|
leaf-cutter
|
||||||
|
jabiru
|
||||||
|
lepidopteron
|
||||||
|
panther
|
||||||
|
apatosaurus
|
||||||
|
goldfinch
|
||||||
|
adjutant
|
||||||
|
whistler
|
||||||
|
pelecypod
|
||||||
|
Kolibri
|
||||||
|
tamandu
|
||||||
|
Strauss
|
||||||
|
Stachelschwein
|
||||||
|
sewellel
|
||||||
|
serpent
|
||||||
|
fitch
|
||||||
|
pen-tail
|
||||||
|
Katzchen
|
||||||
|
torsk
|
||||||
|
man-eater
|
||||||
|
Stier
|
||||||
|
Senf
|
||||||
|
horsefish
|
||||||
|
scincid
|
||||||
|
cacomistle
|
||||||
|
cows
|
||||||
|
iguana
|
||||||
|
pye-dog
|
||||||
|
flycatcher
|
||||||
|
Butter
|
||||||
|
canecutter
|
||||||
|
seriema
|
||||||
|
dobsonfly
|
||||||
|
Brahma
|
||||||
|
Nilpferd
|
||||||
|
elater
|
||||||
|
bullbat
|
||||||
|
Pampelmuse
|
||||||
|
argal
|
||||||
|
ladybeetle
|
||||||
|
Bock
|
||||||
|
pollock
|
||||||
|
barnacle
|
||||||
|
medfly
|
||||||
|
sandfish
|
||||||
|
Spinne
|
||||||
|
elk
|
||||||
|
steinbok
|
||||||
|
Banane
|
||||||
|
platyrrhine
|
||||||
|
noctuid
|
||||||
|
inchworm
|
||||||
|
archeopteryx
|
||||||
|
Nashorn
|
||||||
|
prongbuck
|
||||||
|
baranduki
|
||||||
|
Birne
|
||||||
|
peckerwood
|
||||||
|
allice
|
||||||
|
stiltbird
|
||||||
|
nightjar
|
||||||
|
broadbill
|
||||||
|
Specht
|
||||||
|
puma
|
||||||
|
jaguarondi
|
||||||
|
blackfish
|
||||||
|
Heuschrecke
|
||||||
|
Spargel
|
||||||
|
stock
|
||||||
|
pye-dog
|
||||||
|
Kirsche
|
||||||
|
mithan
|
||||||
|
lug
|
||||||
|
sunfish
|
||||||
|
sambur
|
||||||
|
Johannisbeere
|
||||||
|
gerfalcon
|
||||||
|
pinniped
|
||||||
|
crotonbug
|
||||||
|
Katze
|
||||||
|
nandu
|
||||||
|
Kase
|
||||||
|
moorfowl
|
||||||
|
donkey
|
||||||
|
longicorn
|
||||||
|
psylla
|
||||||
|
dotrel
|
||||||
|
Kreichwacholder
|
||||||
|
scrub-bird
|
||||||
|
taira
|
||||||
|
ribbonfish
|
||||||
|
cochineal
|
||||||
|
pirana
|
||||||
|
woodchuck
|
||||||
|
Igel
|
||||||
|
butterfish
|
||||||
|
oldwench
|
||||||
|
caddis-fly
|
||||||
|
Eichhornchen
|
||||||
|
Grille
|
||||||
|
Panda
|
||||||
|
reindeer
|
||||||
|
bloodsucker
|
||||||
|
blackbeetle
|
||||||
|
shiner
|
||||||
|
medusoid
|
||||||
|
mynah
|
||||||
|
tyrannosaurus
|
||||||
|
shoebird
|
||||||
|
Hase
|
||||||
|
andrenid
|
||||||
|
coon
|
||||||
|
Honigbiene
|
||||||
|
homoiotherm
|
||||||
|
kitty-cat
|
||||||
|
Knoblauch
|
||||||
|
convictfish
|
||||||
|
Gorilla
|
||||||
|
polecat
|
||||||
|
potamogale
|
||||||
|
Hornisse
|
||||||
|
pasang
|
||||||
|
chinchillon
|
||||||
|
cerastes
|
||||||
|
tamanoir
|
||||||
|
Hund
|
||||||
|
Seeschwalbe
|
||||||
|
surffish
|
||||||
|
titlark
|
||||||
|
artiodactyl
|
||||||
|
muttonfish
|
||||||
|
bug
|
||||||
|
duckbill
|
||||||
|
Ziege
|
||||||
|
devilfish
|
||||||
|
souslik
|
||||||
|
hornpout
|
||||||
|
Taube
|
||||||
|
Kiefer
|
||||||
|
Hahn
|
||||||
|
Ochs
|
||||||
|
Blaubeere
|
||||||
|
Kabeljau
|
||||||
|
angelfish
|
||||||
|
sea-ear
|
||||||
|
silvertip
|
||||||
|
Huhn
|
||||||
|
cigarfish
|
||||||
|
Kartoffel
|
||||||
|
Hahnchen
|
||||||
|
Otter
|
||||||
|
hangbird
|
||||||
|
sheepdog
|
||||||
|
oldwife
|
||||||
|
saurel
|
||||||
|
lintwhite
|
||||||
|
Meerschweinchen
|
||||||
|
Limone
|
||||||
|
sparrow
|
||||||
|
millepede
|
||||||
|
Lowe
|
||||||
|
indris
|
||||||
|
crab
|
||||||
|
orthopteron
|
||||||
|
redfish
|
||||||
|
cootie
|
||||||
|
snowbird
|
||||||
|
Hirsch
|
||||||
|
Kaninchen
|
||||||
|
Brot
|
||||||
|
Tiger
|
||||||
|
Waschbar
|
||||||
|
boar
|
||||||
|
anteater
|
||||||
|
Esel
|
||||||
|
fowl
|
||||||
|
capercailzie
|
||||||
|
sprigtail
|
||||||
|
Walross
|
||||||
|
Stachelrochen
|
||||||
|
Nussbaum
|
||||||
|
Mowe
|
||||||
|
Leu
|
||||||
|
coney
|
||||||
|
actinian
|
||||||
|
Falke
|
||||||
|
Hai
|
||||||
|
Ratte
|
||||||
|
grayhen
|
||||||
|
crampfish
|
||||||
|
stumpknocker
|
||||||
|
leatherback
|
||||||
|
silversides
|
||||||
|
hausen
|
||||||
|
nylghai
|
||||||
|
Robbe
|
||||||
|
tortoiseshell-cat
|
||||||
|
Forelle
|
||||||
|
jaybird
|
||||||
|
anhinga
|
||||||
|
Speck
|
||||||
|
nanny-goat
|
||||||
|
hymenopteran
|
||||||
|
capercailzie
|
||||||
|
Sahne
|
||||||
|
bruin
|
||||||
|
sandfly
|
||||||
|
Salamander
|
||||||
|
hogfish
|
||||||
|
Himbeere
|
||||||
|
Floh
|
||||||
|
catamountain
|
||||||
|
dachsie
|
||||||
|
Wachtel
|
||||||
|
mayfish
|
||||||
|
anole
|
||||||
|
antlion
|
||||||
|
ridgling
|
||||||
|
Huhnchen
|
||||||
|
Taube
|
||||||
|
Moosbeere
|
||||||
|
billfish
|
||||||
|
goggle-eye
|
||||||
|
chum
|
||||||
|
cachalot
|
||||||
|
Schildkrote
|
||||||
|
bushbaby
|
||||||
|
redbelly
|
||||||
|
Zitrone
|
||||||
|
Strauss
|
||||||
|
ridgling
|
||||||
|
Schildkrote
|
||||||
|
Rude
|
||||||
|
ghostfish
|
||||||
|
chigger
|
||||||
|
whitethroat
|
||||||
|
parrakeet
|
||||||
|
bilby
|
||||||
|
moonfish
|
||||||
|
brent
|
||||||
|
Aue
|
||||||
|
hag
|
||||||
|
harlequin-snake
|
||||||
|
wreckfish
|
||||||
|
neuropteran
|
||||||
|
cirriped
|
||||||
|
Fohlen
|
||||||
|
ricebird
|
||||||
|
symphilid
|
||||||
|
Eis
|
||||||
|
elaphure
|
||||||
|
bergall
|
||||||
|
cottonmouth
|
||||||
|
banting
|
||||||
|
Pekingese
|
||||||
|
Schweinswal
|
||||||
|
Angus
|
||||||
|
pichiciego
|
||||||
|
crawfish
|
||||||
|
Kohlrabi
|
||||||
|
orang
|
||||||
|
opossum
|
||||||
|
Tomate
|
||||||
|
Luchs
|
||||||
|
tercel
|
||||||
|
Puma
|
||||||
|
Lowenzahn
|
||||||
|
gopher
|
||||||
|
Henne
|
||||||
|
arui
|
||||||
|
Eibe
|
||||||
|
beluga
|
||||||
|
capiz
|
||||||
|
Panther
|
||||||
|
sawbill
|
||||||
|
Eidechse
|
||||||
|
sheatfish
|
||||||
|
Rabe
|
||||||
|
Apfelsine
|
||||||
|
erne
|
||||||
|
Eiche
|
||||||
|
bladdernose
|
||||||
|
Amsel
|
||||||
|
carcajou
|
||||||
|
Pferd
|
||||||
|
Rebhuhn
|
||||||
|
Koala
|
||||||
|
walkingstick
|
||||||
|
percoid
|
||||||
|
baldpate
|
||||||
|
topi
|
||||||
|
tinker
|
||||||
|
daw
|
||||||
|
mustelid
|
||||||
|
guinea
|
||||||
|
Walnuss
|
||||||
|
tortrix
|
||||||
|
humanity
|
||||||
|
Reiher
|
||||||
|
guacharo
|
||||||
|
dachsie
|
||||||
|
Kiwi
|
||||||
|
whitetail
|
||||||
|
sardine
|
||||||
|
Geier
|
||||||
|
helleri
|
||||||
|
tusseh
|
||||||
|
Henne
|
||||||
|
tenpounder
|
||||||
|
Zebra
|
||||||
|
wreckfish
|
||||||
|
polyzoan
|
||||||
|
Seelowe
|
||||||
|
Wespe
|
||||||
|
Gottesanbeterin
|
||||||
|
oyster-fish
|
||||||
|
Echse
|
||||||
|
grayhen
|
||||||
|
Fuchs
|
||||||
|
leipoa
|
||||||
|
jacksnipe
|
||||||
|
Ochse
|
||||||
|
whitethroat
|
||||||
|
roughleg
|
||||||
|
flinthead
|
||||||
|
Marienkafer
|
||||||
|
sprigtail
|
||||||
|
Kater
|
||||||
|
Mowe
|
||||||
|
bluebill
|
||||||
|
garfish
|
||||||
|
stegosaurus
|
||||||
|
Erdnuss
|
||||||
|
cohoe
|
||||||
|
ricebird
|
||||||
|
scarabaeus
|
||||||
|
Adler
|
||||||
|
snowbird
|
||||||
|
Krote
|
||||||
|
massasauga
|
||||||
|
whalesucker
|
||||||
|
Beutelratte
|
||||||
|
linnet
|
||||||
|
shoveller
|
||||||
|
Tintenfisch
|
||||||
|
tumbler
|
||||||
|
ringtail
|
||||||
|
Haher
|
||||||
|
whitethroat
|
||||||
|
jade
|
||||||
|
threadworm
|
||||||
|
blackcap
|
||||||
|
eutherian
|
||||||
|
redbird
|
||||||
|
Frosch
|
||||||
|
poeciliid
|
||||||
|
chinch
|
||||||
|
Erdbeere
|
||||||
|
Sau
|
||||||
|
Schwarzbeere
|
||||||
|
cleg
|
||||||
|
Bar
|
||||||
|
post-horse
|
||||||
|
Pinguin
|
||||||
|
Papagei
|
||||||
|
Frettchen
|
||||||
|
broody
|
||||||
|
weaverbird
|
||||||
|
marabout
|
||||||
|
Pflaume
|
||||||
|
cedarbird
|
||||||
|
griffon
|
||||||
|
Elefant
|
||||||
|
Rind
|
||||||
|
turtledove
|
||||||
|
Haselnuss
|
||||||
|
coney
|
||||||
|
skilletfish
|
||||||
|
capelan
|
||||||
|
archosaurian
|
||||||
|
coney
|
||||||
|
Lachs
|
||||||
|
Schwein
|
||||||
|
quahaug
|
||||||
|
Quark
|
||||||
|
wigeon
|
||||||
|
Seepferdchen
|
||||||
|
Kamel
|
||||||
|
Aar
|
||||||
|
Lamm
|
||||||
|
Storch
|
||||||
|
dovekie
|
||||||
|
dziggetai
|
||||||
|
cavalla
|
||||||
|
Kaschunuss
|
||||||
|
firebird
|
||||||
|
thrasher
|
||||||
|
moorfowl
|
||||||
|
woodcreeper
|
||||||
|
bronc
|
||||||
|
yellowbird
|
||||||
|
striper
|
@ -0,0 +1,397 @@
|
|||||||
|
malt
|
||||||
|
citrine
|
||||||
|
rose
|
||||||
|
emerald
|
||||||
|
ketchup
|
||||||
|
forestgreen
|
||||||
|
azul
|
||||||
|
envy
|
||||||
|
clay
|
||||||
|
tea
|
||||||
|
lemontwist
|
||||||
|
marine
|
||||||
|
kaki
|
||||||
|
wine
|
||||||
|
slime
|
||||||
|
palegreen
|
||||||
|
rod
|
||||||
|
olive
|
||||||
|
gourd
|
||||||
|
parfait
|
||||||
|
pasture
|
||||||
|
pickle
|
||||||
|
magenta
|
||||||
|
cerise
|
||||||
|
darkslategray
|
||||||
|
tobacco
|
||||||
|
sepia
|
||||||
|
base
|
||||||
|
purple
|
||||||
|
caramel
|
||||||
|
blueberry
|
||||||
|
bloodstone
|
||||||
|
umber
|
||||||
|
sea
|
||||||
|
deepurple
|
||||||
|
midnightblue
|
||||||
|
ping
|
||||||
|
white
|
||||||
|
sand
|
||||||
|
indigo
|
||||||
|
hulkcredible
|
||||||
|
fuschia
|
||||||
|
choclae
|
||||||
|
dirt
|
||||||
|
mediumspringgreen
|
||||||
|
lightblue
|
||||||
|
yellowbrown
|
||||||
|
pine
|
||||||
|
cranberry
|
||||||
|
steel
|
||||||
|
drab
|
||||||
|
vegas
|
||||||
|
ash
|
||||||
|
sage
|
||||||
|
bronze
|
||||||
|
beige
|
||||||
|
celery
|
||||||
|
spring
|
||||||
|
persimmon
|
||||||
|
cyan
|
||||||
|
darkcyan
|
||||||
|
charcoal
|
||||||
|
puce
|
||||||
|
redishorange
|
||||||
|
sapphire
|
||||||
|
khaki
|
||||||
|
amaranth
|
||||||
|
mediumaquamarine
|
||||||
|
stone
|
||||||
|
light
|
||||||
|
navy
|
||||||
|
keylime
|
||||||
|
aquamarine
|
||||||
|
space
|
||||||
|
arcotic
|
||||||
|
fuzz
|
||||||
|
avocado
|
||||||
|
atlantis
|
||||||
|
darkolivegreen
|
||||||
|
stain
|
||||||
|
pumpkin
|
||||||
|
royale
|
||||||
|
puff
|
||||||
|
russet
|
||||||
|
yellowishbrown
|
||||||
|
lipstick
|
||||||
|
mud
|
||||||
|
amazon
|
||||||
|
jean
|
||||||
|
ebony
|
||||||
|
kiwi
|
||||||
|
plub
|
||||||
|
pum
|
||||||
|
olivegreen
|
||||||
|
fog
|
||||||
|
poop
|
||||||
|
julep
|
||||||
|
pink
|
||||||
|
grey
|
||||||
|
violetred
|
||||||
|
darkorchid
|
||||||
|
brown-green
|
||||||
|
deeppink
|
||||||
|
peru
|
||||||
|
royal
|
||||||
|
quince
|
||||||
|
violet
|
||||||
|
sorbet
|
||||||
|
smoke
|
||||||
|
saffron
|
||||||
|
orchard
|
||||||
|
prune
|
||||||
|
pastel
|
||||||
|
odeole
|
||||||
|
daffodil
|
||||||
|
chokato
|
||||||
|
haze
|
||||||
|
tourlamine
|
||||||
|
gem
|
||||||
|
beet
|
||||||
|
oak
|
||||||
|
butter
|
||||||
|
kelly
|
||||||
|
aubergine
|
||||||
|
copper
|
||||||
|
breeze
|
||||||
|
sierra
|
||||||
|
mediumturquoise
|
||||||
|
drabe
|
||||||
|
grassclippings
|
||||||
|
apricot
|
||||||
|
plumeria
|
||||||
|
lightskyblue
|
||||||
|
berry
|
||||||
|
soup
|
||||||
|
supreme
|
||||||
|
merrigold
|
||||||
|
plum
|
||||||
|
clover
|
||||||
|
blackberry
|
||||||
|
darkbrown
|
||||||
|
lampshade
|
||||||
|
cream
|
||||||
|
algae
|
||||||
|
denim
|
||||||
|
royalblue
|
||||||
|
glue
|
||||||
|
red
|
||||||
|
crush
|
||||||
|
bluegreen
|
||||||
|
azure
|
||||||
|
hotpink
|
||||||
|
paleblue
|
||||||
|
deep
|
||||||
|
cyanicle
|
||||||
|
mustard
|
||||||
|
oatmeal
|
||||||
|
obsidian
|
||||||
|
deepskyblue
|
||||||
|
sherbert
|
||||||
|
bark
|
||||||
|
highlighter
|
||||||
|
tomato
|
||||||
|
rain
|
||||||
|
cocoa
|
||||||
|
velvet
|
||||||
|
grape
|
||||||
|
mediumblue
|
||||||
|
forest
|
||||||
|
gum
|
||||||
|
yellow
|
||||||
|
azalea
|
||||||
|
mediumseagreen
|
||||||
|
tan
|
||||||
|
brass
|
||||||
|
greenyellow
|
||||||
|
darkblue
|
||||||
|
shipwrecked
|
||||||
|
pear
|
||||||
|
cobalt
|
||||||
|
coneflower
|
||||||
|
cinnamon
|
||||||
|
ocean
|
||||||
|
peacock
|
||||||
|
pistachio
|
||||||
|
lemon
|
||||||
|
gold
|
||||||
|
time
|
||||||
|
earthcore
|
||||||
|
bple
|
||||||
|
moss
|
||||||
|
mint
|
||||||
|
evergreen
|
||||||
|
slate
|
||||||
|
celadon
|
||||||
|
scab
|
||||||
|
pinkishred
|
||||||
|
chestnut
|
||||||
|
limey
|
||||||
|
apple
|
||||||
|
limeade
|
||||||
|
goldenyellow
|
||||||
|
snake
|
||||||
|
brunette
|
||||||
|
petal
|
||||||
|
darkviolet
|
||||||
|
ocher
|
||||||
|
brown
|
||||||
|
sandlewood
|
||||||
|
darkcoral
|
||||||
|
teal
|
||||||
|
jade
|
||||||
|
mediumorchid
|
||||||
|
sprite
|
||||||
|
strawberry
|
||||||
|
egg
|
||||||
|
fern
|
||||||
|
taupe
|
||||||
|
sangria
|
||||||
|
saddlebrown
|
||||||
|
lightgreen
|
||||||
|
aster
|
||||||
|
cheeks
|
||||||
|
ruddy
|
||||||
|
peuce
|
||||||
|
brandywine
|
||||||
|
earth
|
||||||
|
mulberry
|
||||||
|
redorange
|
||||||
|
crimson
|
||||||
|
indianred
|
||||||
|
seagreen
|
||||||
|
merlot
|
||||||
|
ultramarine
|
||||||
|
maroonish
|
||||||
|
lilac
|
||||||
|
lettuce
|
||||||
|
redbrown
|
||||||
|
petrol
|
||||||
|
puke
|
||||||
|
boysenberry
|
||||||
|
cherry
|
||||||
|
bloom
|
||||||
|
toffee
|
||||||
|
rust
|
||||||
|
cadetblue
|
||||||
|
twilight
|
||||||
|
lavender
|
||||||
|
flashypurple
|
||||||
|
blueviolet
|
||||||
|
jungle
|
||||||
|
seablue
|
||||||
|
melon
|
||||||
|
auburn
|
||||||
|
goldengold
|
||||||
|
noon
|
||||||
|
springgreen
|
||||||
|
butternut
|
||||||
|
pea
|
||||||
|
wax
|
||||||
|
fulcrum
|
||||||
|
mauve
|
||||||
|
harding
|
||||||
|
coral
|
||||||
|
tangerine
|
||||||
|
chili
|
||||||
|
lawngreen
|
||||||
|
leftovers
|
||||||
|
mascot
|
||||||
|
goldenrod
|
||||||
|
muave
|
||||||
|
darkslateblue
|
||||||
|
hyacinth
|
||||||
|
petunia
|
||||||
|
booger
|
||||||
|
royalty
|
||||||
|
lemongrass
|
||||||
|
seaweed
|
||||||
|
dookie
|
||||||
|
blue
|
||||||
|
water
|
||||||
|
putty
|
||||||
|
kiss
|
||||||
|
flower
|
||||||
|
darkgoldenred
|
||||||
|
cardinal
|
||||||
|
ultrablue
|
||||||
|
brand
|
||||||
|
eggplant
|
||||||
|
mahogany
|
||||||
|
bluevoilet
|
||||||
|
sienna
|
||||||
|
distemper
|
||||||
|
gray
|
||||||
|
cotta
|
||||||
|
princess
|
||||||
|
skyblue
|
||||||
|
chartreuse
|
||||||
|
wasabi
|
||||||
|
dull
|
||||||
|
fresh
|
||||||
|
olivedrab
|
||||||
|
kelp
|
||||||
|
burgundy
|
||||||
|
colour
|
||||||
|
fancy
|
||||||
|
mezantha
|
||||||
|
watermelon
|
||||||
|
almond
|
||||||
|
periwinkle
|
||||||
|
dark
|
||||||
|
cinnabar
|
||||||
|
blossom
|
||||||
|
snorkeling
|
||||||
|
raspberry
|
||||||
|
darkpink
|
||||||
|
lightcoral
|
||||||
|
green
|
||||||
|
burn
|
||||||
|
slateblue
|
||||||
|
peach
|
||||||
|
turquoise
|
||||||
|
forrest
|
||||||
|
aqua
|
||||||
|
scarlet
|
||||||
|
amber
|
||||||
|
mocha
|
||||||
|
sucker
|
||||||
|
seafoam
|
||||||
|
thistle
|
||||||
|
currant
|
||||||
|
mediumpurple
|
||||||
|
leaf
|
||||||
|
greenishbrown
|
||||||
|
rosemary
|
||||||
|
chocolate
|
||||||
|
bright
|
||||||
|
uberpink
|
||||||
|
grass
|
||||||
|
black
|
||||||
|
rusty
|
||||||
|
candy
|
||||||
|
darkgreen
|
||||||
|
choc
|
||||||
|
butterscotch
|
||||||
|
yellowgreen
|
||||||
|
yellowishgreen
|
||||||
|
darkmagenta
|
||||||
|
marigold
|
||||||
|
limegreen
|
||||||
|
ecru
|
||||||
|
spice
|
||||||
|
darkred
|
||||||
|
goldenbrown
|
||||||
|
wisteria
|
||||||
|
shade
|
||||||
|
bean
|
||||||
|
koolaid
|
||||||
|
wood
|
||||||
|
kac
|
||||||
|
carmine
|
||||||
|
oxblood
|
||||||
|
aqua-green
|
||||||
|
fuchsia
|
||||||
|
brick
|
||||||
|
terracotta
|
||||||
|
lime
|
||||||
|
cerulean
|
||||||
|
purplish
|
||||||
|
mediumvioletred
|
||||||
|
foam
|
||||||
|
salmon
|
||||||
|
dune
|
||||||
|
night
|
||||||
|
orange
|
||||||
|
grapejuice
|
||||||
|
firebrick
|
||||||
|
blackmaroon
|
||||||
|
vermilion
|
||||||
|
dandelion
|
||||||
|
redishbrown
|
||||||
|
cornflower
|
||||||
|
midnight
|
||||||
|
amethyst
|
||||||
|
fleshtone
|
||||||
|
maroon
|
||||||
|
browntown
|
||||||
|
asparagus
|
||||||
|
sky
|
||||||
|
bipolar
|
||||||
|
steelblue
|
||||||
|
orchid
|
||||||
|
juniper
|
||||||
|
amazonish
|
||||||
|
hot
|
||||||
|
bridesmaid
|
||||||
|
sunset
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,89 @@
|
|||||||
|
-- Copyleft Matthieu Lalonde 2012 LGPL
|
||||||
|
|
||||||
|
admins = { "admin@domain.tld" }
|
||||||
|
|
||||||
|
--use_libevent = true;
|
||||||
|
|
||||||
|
-- This is the list of modules Prosody will load on startup.
|
||||||
|
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
|
||||||
|
-- Documentation on modules can be found at: http://prosody.im/doc/modules
|
||||||
|
modules_enabled = {
|
||||||
|
-- Generally required
|
||||||
|
"roster"; -- Allow users to have a roster. Recommended ;)
|
||||||
|
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
|
||||||
|
"tls"; -- Add support for secure TLS on c2s/s2s connections
|
||||||
|
--"dialback"; -- s2s dialback support
|
||||||
|
--"disco"; -- Service discovery
|
||||||
|
|
||||||
|
-- Not essential, but recommended
|
||||||
|
--"private"; -- Private XML storage (for room bookmarks, etc.)
|
||||||
|
--"vcard"; -- Allow users to set vCards
|
||||||
|
--"privacy"; -- Support privacy lists
|
||||||
|
"compression"; -- Stream compression
|
||||||
|
|
||||||
|
-- Nice to have
|
||||||
|
--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
|
||||||
|
--"version"; -- Replies to server version requests
|
||||||
|
--"uptime"; -- Report how long server has been running
|
||||||
|
--"time"; -- Let others know the time here on this server
|
||||||
|
"ping"; -- Replies to XMPP pings with pongs
|
||||||
|
--"pep"; -- Enables users to publish their mood, activity, playing music and more
|
||||||
|
--"register"; -- Allow users to register on this server using a client and change passwords
|
||||||
|
--"adhoc"; -- Support for "ad-hoc commands" that can be executed with an XMPP client
|
||||||
|
|
||||||
|
-- Admin interfaces
|
||||||
|
--"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
|
||||||
|
--"admin_telnet"; -- Opens telnet console interface on localhost port 5582
|
||||||
|
|
||||||
|
-- Other specific functionality
|
||||||
|
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
|
||||||
|
--"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
|
||||||
|
--"httpserver"; -- Serve static files from a directory over HTTP
|
||||||
|
--"groups"; -- Shared roster support
|
||||||
|
--"announce"; -- Send announcement to all online users
|
||||||
|
--"welcome"; -- Welcome users who register accounts
|
||||||
|
--"watchregistrations"; -- Alert admins of registrations
|
||||||
|
--"motd"; -- Send a message to users when they log in
|
||||||
|
};
|
||||||
|
|
||||||
|
modules_disabled = {
|
||||||
|
-- "presence"; -- Route user/contact status information
|
||||||
|
-- "message"; -- Route messages
|
||||||
|
-- "iq"; -- Route info queries
|
||||||
|
--"offline"; -- Store offline messages
|
||||||
|
};
|
||||||
|
|
||||||
|
allow_registration = false;
|
||||||
|
|
||||||
|
|
||||||
|
ssl = {
|
||||||
|
key = "/opt/prosody/etc/prosody/certs/domain.tld.key";
|
||||||
|
certificate = "/opt/prosody/etc/prosody/certs/domain.tld.cert";
|
||||||
|
};
|
||||||
|
|
||||||
|
c2s_require_encryption = true
|
||||||
|
s2s_require_encryption = true
|
||||||
|
|
||||||
|
--log = {
|
||||||
|
-- error = "*syslog"; -- Send error and higher to the syslog sink
|
||||||
|
--};
|
||||||
|
log = "/var/log/prosody/prosody.log"
|
||||||
|
|
||||||
|
compression_level = 7
|
||||||
|
|
||||||
|
authentication = "anon"
|
||||||
|
|
||||||
|
anons = {
|
||||||
|
salt = "GIVEMEAGOODSALT";
|
||||||
|
expiry_time = 30;
|
||||||
|
};
|
||||||
|
|
||||||
|
storage = "internal"
|
||||||
|
|
||||||
|
|
||||||
|
daemonize = true
|
||||||
|
|
||||||
|
VirtualHost "domain.tld"
|
||||||
|
Component "conference.domain.tld" "muc"
|
||||||
|
Component "proxy.domain.tld" "proxy65"
|
||||||
|
|
@ -0,0 +1,704 @@
|
|||||||
|
-- mod_auth_anon v0.1
|
||||||
|
-- Copyright (C) 2011 Matthieu Lalonde
|
||||||
|
--
|
||||||
|
-- All rights reserved.
|
||||||
|
--
|
||||||
|
-- Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
--
|
||||||
|
-- * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
-- * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
-- * Neither the name of Tobias Markmann nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
|
--
|
||||||
|
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
-- TODO: Minimum length username+pass
|
||||||
|
-- TODO: Add pretty nicknames to users (must be random and unique!)
|
||||||
|
-- TODO: Drop client that are idle for X time in order to help protect their privacy on Tor
|
||||||
|
-- DONE: On new signup, publish to the roster so no login cycle is needed
|
||||||
|
-- TODO: When a new user is pushed to the roster of online users, they don't seem to be authorized (or at least presence is not pushed)
|
||||||
|
-- TODO?: Change all Version IQ to INFOXMPP-APPROVED
|
||||||
|
-- TODO?: Add <sub xmlns='urn:xmpp:features:pre-approval'/> to stream negociation
|
||||||
|
-- TODO?: Change to a whitelisting system for IQ filtering in case there is more than bytestreams and jingle/content/transport that leak ips
|
||||||
|
-- TODO?: Move the datastore callbacks to the new mechanism?! (Need to figure out what that mechanism is but callbacks are deprecated apparently)
|
||||||
|
-- TODO?: Block locale from leaking (done in core/stanza_router.lua but needs to move to filters in here see: http://prosody-modules.googlecode.com/hg/mod_smacks/mod_smacks.lua )
|
||||||
|
|
||||||
|
local mod_auth_anon_changelog = {
|
||||||
|
{0.3, [[
|
||||||
|
]]
|
||||||
|
},
|
||||||
|
{0.2, [[
|
||||||
|
- Added: Changelog and version
|
||||||
|
- Added: Generated user nicknames
|
||||||
|
- Fixed: Bytestreams filtering cleaned up and fixed for Psi
|
||||||
|
- Fixed: resource binding in Psi]]},
|
||||||
|
{0.1, [[
|
||||||
|
- First versions before the changelog, a brief of current functionalities
|
||||||
|
- Anonymous login with user ids based on hmac:sha256(salt, username+password),
|
||||||
|
once logged in a user will receive their unique hashed username, resources are also anonymized.
|
||||||
|
Users exist so long as they login every X days (default 1 day);
|
||||||
|
after a user expires any of its stored data is deleted.
|
||||||
|
- Automatic roster of all current users
|
||||||
|
- Offline messages permitted only for encrypted messages (detects OTR, RSA and x:encrypted GPG)
|
||||||
|
- Filtering of bytestream and jingle (at least libpurple) negotiation leaking a users IP
|
||||||
|
- Filtering of a user's locale leaked by some clients (namely Tkabber)]]}
|
||||||
|
}
|
||||||
|
|
||||||
|
local hmac_sha256 = require "util.hmac".sha256
|
||||||
|
local hashes = require "util.hashes"
|
||||||
|
local uuid_generate = require "util.uuid".generate
|
||||||
|
local datamanager = require "util.datamanager"
|
||||||
|
local usermanager_create_user = require "core.usermanager".create_user
|
||||||
|
local json = require "util.json"
|
||||||
|
local st = require "util.stanza"
|
||||||
|
|
||||||
|
require "luasql.sqlite3"
|
||||||
|
local sqlite3 = luasql.sqlite3()
|
||||||
|
local connection
|
||||||
|
|
||||||
|
local resolve_relative_path = require "core.configmanager".resolve_relative_path
|
||||||
|
local sm_bind_resource = require "core.sessionmanager".bind_resource
|
||||||
|
|
||||||
|
local full_sessions = full_sessions
|
||||||
|
|
||||||
|
local data_path = (prosody and prosody.paths and prosody.paths.data) or "."
|
||||||
|
|
||||||
|
local xmlns_bind ='urn:ietf:params:xml:ns:xmpp-bind'
|
||||||
|
|
||||||
|
local module_host = module:get_host()
|
||||||
|
|
||||||
|
local params = module:get_option("anons")
|
||||||
|
if not params then -- Set defaults
|
||||||
|
params = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
params.expiry_time = (params.expiry_time or 1) -- Days
|
||||||
|
params.salt = (params.salt or "iamsalt")
|
||||||
|
|
||||||
|
local expiry_time = params.expiry_time * 24 * 3600
|
||||||
|
|
||||||
|
local name_list_path = (prosody and prosody.paths and prosody.paths.config) or "."
|
||||||
|
|
||||||
|
local function open_list(file)
|
||||||
|
local list = {}
|
||||||
|
local fd = assert(io.open(file, "r"))
|
||||||
|
for line in fd:lines() do
|
||||||
|
table.insert(list, line)
|
||||||
|
end
|
||||||
|
|
||||||
|
return list
|
||||||
|
end
|
||||||
|
|
||||||
|
local animals = open_list(resolve_relative_path(name_list_path, "names/animals.list"))
|
||||||
|
local colors = open_list(resolve_relative_path(name_list_path, "names/colors.list"))
|
||||||
|
local locations = open_list(resolve_relative_path(name_list_path, "names/locations.list"))
|
||||||
|
local adjectives = open_list(resolve_relative_path(name_list_path, "names/adjectives.list"))
|
||||||
|
|
||||||
|
function round(num, idp)
|
||||||
|
return tonumber(string.format("%." .. (idp or 0) .. "f", num))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function tchelper(first, rest)
|
||||||
|
return string.upper(first) .. string.lower(rest)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function capitalize(str)
|
||||||
|
return string.gsub(str, "(%a)([%w_']*)", tchelper)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function trim(str)
|
||||||
|
return string.gsub(str, "^%s*(.-)%s*$", "%1")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function num_superscript(nbr)
|
||||||
|
local str_nbr = tostring(nbr):sub(-1)
|
||||||
|
local suffix = ""
|
||||||
|
|
||||||
|
if string.len(tostring(nbr)) > 1 and tostring(nbr):sub(-2) == "13" then
|
||||||
|
suffix = "th"
|
||||||
|
elseif str_nbr == "1" then
|
||||||
|
suffix = "st"
|
||||||
|
elseif str_nbr == "2" then
|
||||||
|
suffix = "nd"
|
||||||
|
elseif str_nbr == "3" then
|
||||||
|
suffix = "rd"
|
||||||
|
else
|
||||||
|
suffix = "th"
|
||||||
|
end
|
||||||
|
|
||||||
|
return suffix
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function get_hash_name(hash)
|
||||||
|
local number = tonumber(string.format("0x%s", hash))
|
||||||
|
local animal = math.fmod(number, table.getn(animals)) +1
|
||||||
|
number = round(number / table.getn(animals))
|
||||||
|
|
||||||
|
if number == 0 then
|
||||||
|
return capitalize(trim(animals[animal]))
|
||||||
|
end
|
||||||
|
|
||||||
|
local color = math.fmod(number, table.getn(colors)) + 1
|
||||||
|
number = round(number / table.getn(colors))
|
||||||
|
|
||||||
|
if number == 0 then
|
||||||
|
return capitalize(trim(colors[color])) .. " " .. capitalize(trim(animals[animal]))
|
||||||
|
end
|
||||||
|
|
||||||
|
local location = math.fmod(number, table.getn(locations)) + 1
|
||||||
|
number = round(number / table.getn(locations))
|
||||||
|
|
||||||
|
if number == 0 then
|
||||||
|
return capitalize(trim(colors[color])) .. " " .. capitalize(trim(animals[animal])) .. " of " .. capitalize(trim(locations[location]))
|
||||||
|
end
|
||||||
|
|
||||||
|
local adjective = math.fmod(number, table.getn(adjectives)) + 1
|
||||||
|
number = round(number / table.getn(adjectives))
|
||||||
|
|
||||||
|
if number == 0 then
|
||||||
|
return capitalize(trim(colors[color])) .. " " .. capitalize(trim(animals[animal])) .. " `The " .. capitalize(trim(adjectives[adjective])) .. "` of " .. capitalize(trim(locations[location]))
|
||||||
|
end
|
||||||
|
|
||||||
|
local str_nbr = "The " .. number .. num_superscript(number)
|
||||||
|
|
||||||
|
return capitalize(trim(colors[color])) ..
|
||||||
|
" " .. capitalize(trim(animals[animal])) ..
|
||||||
|
" " .. str_nbr ..
|
||||||
|
" `The " .. capitalize(trim(adjectives[adjective])) ..
|
||||||
|
"` of " .. capitalize(trim(locations[location]))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- URL Encode
|
||||||
|
local function encode(s)
|
||||||
|
return s and (s:gsub("%W", function (c) return string.format("%%%02x", c:byte()); end))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Initiate SQL connections
|
||||||
|
local function connect()
|
||||||
|
local database = resolve_relative_path(data_path, (module_host and encode(module_host)) .. "/anons.sqlite")
|
||||||
|
|
||||||
|
assert(datamanager.getpath("", module_host, "", "", true))
|
||||||
|
|
||||||
|
local dbh = assert(sqlite3:connect(database))
|
||||||
|
|
||||||
|
if not dbh then
|
||||||
|
module:log("error", "Database connection failed: %s", tostring(err))
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
module:log("debug", "Successfully connected to database")
|
||||||
|
|
||||||
|
dbh:setautocommit(true);
|
||||||
|
connection = dbh;
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Drop SQL Connection
|
||||||
|
local function disconnect()
|
||||||
|
connection:close();
|
||||||
|
sqlite3:close();
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create anon table
|
||||||
|
local function create_table()
|
||||||
|
local res = assert(connection:execute[[
|
||||||
|
CREATE TABLE IF NOT EXISTS `anon_users` (TEXT, `user` TEXT, `name` TEXT, `last` TIMESTAMP, `stores` TEXT);
|
||||||
|
]]);
|
||||||
|
|
||||||
|
if not res then
|
||||||
|
module:log("error", "Failed to create SQLite Anon Database");
|
||||||
|
else
|
||||||
|
module:log("info", "Initialized new %s database with anon_users", "SQLite");
|
||||||
|
local res = connection:execute[[
|
||||||
|
CREATE INDEX IF NOT EXISTS `anon_users_index` ON `anon_users` (`user`, `last`, `name`);
|
||||||
|
]];
|
||||||
|
|
||||||
|
if not res then
|
||||||
|
module:log("warn", "Failed to create anon indexes, lookups may not be optimised");
|
||||||
|
end
|
||||||
|
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Lookup a user
|
||||||
|
local function is_anon_user(username, fields)
|
||||||
|
if fields == nil then
|
||||||
|
fields = "`user`"
|
||||||
|
end
|
||||||
|
|
||||||
|
local sql = string.format("SELECT %s FROM `anon_users` WHERE user = %q LIMIT 1;", fields, username)
|
||||||
|
local res = connection:execute(sql)
|
||||||
|
|
||||||
|
if not res then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return res:fetch({}, "a");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Log a user datastore so we can purge them later on
|
||||||
|
local function log_user_datastore(username, datastore)
|
||||||
|
local user_result = is_anon_user(username, "`user`, `stores`")
|
||||||
|
|
||||||
|
if user_result ~= nil then
|
||||||
|
local user_stores = json.decode(user_result.stores)
|
||||||
|
|
||||||
|
if not user_stores[datastore] then
|
||||||
|
user_stores[datastore] = true
|
||||||
|
local sql_str = "UPDATE `anon_users` SET stores = '%s', last = %q WHERE user = %q;";
|
||||||
|
local sql = string.format(sql_str, json.encode(user_stores), tostring(os.time()), username);
|
||||||
|
|
||||||
|
if not connection:execute(sql) then
|
||||||
|
module:log("error", "Failed to write stores %q status for %q: %s", datastore, username, sql);
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
module:log("debug", "User %q doesn't exist, not storing datastore!", username)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Purges expired users by deleting any datastores they might have and their database entry
|
||||||
|
local function purge_users()
|
||||||
|
-- Fetch all the stale users
|
||||||
|
sql = string.format("SELECT * FROM `anon_users` WHERE last < %s;", (os.time() - expiry_time));
|
||||||
|
local res = connection:execute(sql);
|
||||||
|
|
||||||
|
if not res then
|
||||||
|
module:log("error", "Error fetching stale users: %s", sql);
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
local row = res:fetch ({}, "a");
|
||||||
|
local errors = false;
|
||||||
|
|
||||||
|
-- There are no expired users
|
||||||
|
if not row then
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
while row do
|
||||||
|
-- First make sure we update any expired logins that are still connected
|
||||||
|
if prosody.bare_sessions[row.user .. "@" .. module_host] ~= nil then
|
||||||
|
sql = string.format("UPDATE `anon_users` SET last = %s WHERE user = %q;",
|
||||||
|
tostring(os.time()), row.user);
|
||||||
|
|
||||||
|
if not connection:execute(sql) then
|
||||||
|
errors = true;
|
||||||
|
end
|
||||||
|
-- Otherwise we delete the user's datastores
|
||||||
|
else
|
||||||
|
local user_stores = json.decode(row.stores);
|
||||||
|
local str_stores = ""
|
||||||
|
|
||||||
|
if string.len(row.stores) > 0 then
|
||||||
|
for t, v in pairs(user_stores) do
|
||||||
|
-- Remove stores and list stores
|
||||||
|
datamanager.store(row.user, module_host, tostring(t), nil)
|
||||||
|
datamanager.list_store(row.user, module_host, tostring(t), nil)
|
||||||
|
str_stores = str_stores .. tostring(t) .. " "
|
||||||
|
end
|
||||||
|
|
||||||
|
module:log("debug", "Removed datastores %q for stale user %q", str_stores, row.user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
row = res:fetch (row, "a")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Deleting any expired users left in the DB
|
||||||
|
if not errors then
|
||||||
|
sql = string.format("DELETE FROM `anon_users` WHERE last < %s;", (os.time() - expiry_time));
|
||||||
|
res = connection:execute(sql)
|
||||||
|
|
||||||
|
if not res then
|
||||||
|
module:log("error", "Failed to purge anon users, check the database!")
|
||||||
|
else
|
||||||
|
module:log("info", "Purged %s user(s) from anons!", res)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
module:log("error", "There was an error updating current expired users, purging failed!")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Log an anon to the anon DB and clean the old ones.
|
||||||
|
local function log_user(username)
|
||||||
|
local sql = string.format("SELECT `user` FROM `anon_users` WHERE user = %q;", username);
|
||||||
|
local res = connection:execute(sql);
|
||||||
|
|
||||||
|
-- The entry doesn't exist, let's create it
|
||||||
|
if not res:fetch() then
|
||||||
|
local user_longname = get_hash_name(username)
|
||||||
|
sql = string.format("INSERT INTO `anon_users` (user, last, name, stores) VALUES (%q, %s, %q, \"{}\");",
|
||||||
|
username, os.time(), user_longname);
|
||||||
|
module:log("debug", "Inserting new anon %s", username);
|
||||||
|
-- The user already exists, update his last login time
|
||||||
|
else
|
||||||
|
sql = string.format("UPDATE `anon_users` SET last = %s WHERE user = %q;", os.time(), username);
|
||||||
|
module:log("debug", "Updating current anon %s", username);
|
||||||
|
end
|
||||||
|
|
||||||
|
if not connection:execute(sql) then
|
||||||
|
module:log("error", "Failed to write anon login to database: %s", sql)
|
||||||
|
else -- Let's purge the old users here
|
||||||
|
purge_users()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Inject ALL THE anons into the roster
|
||||||
|
local function inject_roster_contacts(username, host, roster)
|
||||||
|
local res = connection:execute("SELECT `user`, `name` FROM `anon_users`;");
|
||||||
|
local row = res:fetch ({}, "a")
|
||||||
|
|
||||||
|
while row do
|
||||||
|
if username ~= row.user then
|
||||||
|
local jid = row.user .. "@" .. module_host;
|
||||||
|
module:log("debug", "Adding user %s to roster %s.", jid, username);
|
||||||
|
if not roster[jid] then roster[jid] = {}; end
|
||||||
|
roster[jid].subscription = "both";
|
||||||
|
roster[jid].published = true;
|
||||||
|
roster[jid].name = row.name;
|
||||||
|
roster[jid].persist = false;
|
||||||
|
roster[jid].groups = { [module_host] = true };
|
||||||
|
end
|
||||||
|
|
||||||
|
row = res:fetch (row, "a")
|
||||||
|
end
|
||||||
|
|
||||||
|
roster[module_host] = {}
|
||||||
|
roster[module_host].subscription = "both"
|
||||||
|
roster[module_host].published = true
|
||||||
|
roster[module_host].name = module_host
|
||||||
|
roster[module_host].persist = false
|
||||||
|
roster[module_host].groups = { [module_host] = true }
|
||||||
|
end
|
||||||
|
|
||||||
|
local function push_roster_contact(username, full_jid)
|
||||||
|
local user_longname = get_hash_name(username)
|
||||||
|
local roster_attr = {jid = username.."@"..module_host, name = user_longname,
|
||||||
|
subscription = "both", approved = "true"}
|
||||||
|
module:log("debug", "Sending new roster item for user: %s", username)
|
||||||
|
|
||||||
|
for _, session in pairs(prosody.bare_sessions) do
|
||||||
|
if _ ~= username.."@"..module_host then
|
||||||
|
for __, current_session in pairs(session.sessions) do
|
||||||
|
local roster_item =
|
||||||
|
st.iq({to = current_session.full_jid, type = "set"})
|
||||||
|
:tag("query", {xmlns = "jabber:iq:roster", ver = os.time()})
|
||||||
|
:tag("item", roster_attr)
|
||||||
|
:tag("group"):text(module_host)
|
||||||
|
:up()
|
||||||
|
:up()
|
||||||
|
|
||||||
|
core_route_stanza(module_host, roster_item)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Catching the remove requests for data to block roster access
|
||||||
|
local function handle_datamanager(username, host, datastore, data)
|
||||||
|
if host ~= module_host then
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
|
||||||
|
if datastore == "roster" then
|
||||||
|
-- Don't allow users to actually save rosters
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
-- Register all datastores for a user so we can purge them later
|
||||||
|
if data ~= nil then
|
||||||
|
if log_user_datastore(username, datastore) ~= nil then
|
||||||
|
return username, host, datastore, data;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return username, host, datastore, data;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Anon Auth Provider, let ALL THE anons in!
|
||||||
|
module:add_item("auth-provider", {
|
||||||
|
name = module.name:gsub("^auth_",""),
|
||||||
|
test_password = function() return true end,
|
||||||
|
user_exists = function(node, host) return (is_anon_user(node) ~= nil) end,
|
||||||
|
get_sasl_handler = function () return {
|
||||||
|
mechanisms = function() return { PLAIN = true, } end,
|
||||||
|
plain = function(self, message)
|
||||||
|
if string.len(message) < 18 then
|
||||||
|
return "failure", "malformed-request", "Invalid username or password must be 18 characters or more.";
|
||||||
|
else
|
||||||
|
self.username = hmac_sha256(params.salt, message:match("%z(.*)$"), true);
|
||||||
|
self.username = self.username:sub(-10);
|
||||||
|
|
||||||
|
return "success"
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
select = function(self, mech) self.process = self[mech:lower()] return true end,
|
||||||
|
clean_clone = function(self) self.username = nil; self.process = nil; return self end,
|
||||||
|
} end,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Override the IQ Bind hook from saslauth in order to send the welcome message
|
||||||
|
module:hook("resource-bind", function(event)
|
||||||
|
local session = event.session
|
||||||
|
local bare_jid = session.username .. "@" .. session.host
|
||||||
|
|
||||||
|
if is_anon_user(session.username) == nil then
|
||||||
|
prosody.bare_sessions[bare_jid].is_new_user = true
|
||||||
|
end
|
||||||
|
|
||||||
|
log_user(session.username)
|
||||||
|
|
||||||
|
local user_longname = is_anon_user(session.username, "`name`")
|
||||||
|
user_longname = user_longname.name
|
||||||
|
|
||||||
|
local welcome_stanza =
|
||||||
|
st.message({ to = bare_jid, from = session.host })
|
||||||
|
:tag("body")
|
||||||
|
:text(string.format("Welcome in %s, your username is: %s@%s",
|
||||||
|
user_longname, session.username, session.host))
|
||||||
|
:up()
|
||||||
|
|
||||||
|
module:log("debug", "Welcomed user %s %q", session.full_jid, tostring(welcome_stanza))
|
||||||
|
core_route_stanza(session.host, welcome_stanza)
|
||||||
|
|
||||||
|
return nil -- Go on with the original hook if there is any
|
||||||
|
end, 9000) -- Priority over 9000, nothing should be over 9000, really!
|
||||||
|
|
||||||
|
module:hook("presence/host", function(event)
|
||||||
|
local origin, stanza = event.origin, event.stanza;
|
||||||
|
|
||||||
|
-- Catch the fist time a user comes online
|
||||||
|
if stanza and stanza.attr and stanza.attr.type == "probe" and stanza.attr.to == module_host
|
||||||
|
and prosody.bare_sessions[origin.username.."@"..origin.host].is_new_user ~= nil then
|
||||||
|
|
||||||
|
module:log("debug", "***** PRESENCE PUSHING NEW USER TO USERS ******")
|
||||||
|
prosody.bare_sessions[origin.username.."@"..origin.host].is_new_user = nil
|
||||||
|
push_roster_contact(origin.username, origin.full_jid)
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end, 9000)
|
||||||
|
|
||||||
|
-- Override the IQ Bind hook from saslauth in order to anonymize resource
|
||||||
|
module:hook("iq/self/urn:ietf:params:xml:ns:xmpp-bind:bind", function(event)
|
||||||
|
local origin, stanza = event.origin, event.stanza;
|
||||||
|
|
||||||
|
if stanza.attr.type == "set" then
|
||||||
|
local resource_tag = stanza.tags[1]:child_with_name("resource")
|
||||||
|
local hashed_resource = tostring(hmac_sha256((resource_tag and #resource_tag.tags == 0 and resource_tag[1] or "wearelegions"), (resource_tag and #resource_tag.tags == 0 and resource_tag[1] or uuid_generate()), true)):sub(-16);
|
||||||
|
|
||||||
|
if resource_tag == nil then
|
||||||
|
stanza.tags[1]:tag("resource"):text(hashed_resource);
|
||||||
|
else
|
||||||
|
stanza.tags[1]:child_with_name("resource")[1] = hashed_resource
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end, 9000) -- Priority over 9000, nothing should be over 9000, really!
|
||||||
|
|
||||||
|
module:hook("iq/full", function(data)
|
||||||
|
-- IQ to full JID recieved
|
||||||
|
local origin, stanza = data.origin, data.stanza;
|
||||||
|
local xmlns_bytestream = "http://jabber.org/protocol/bytestreams"
|
||||||
|
|
||||||
|
-- Sanitize bytestreams
|
||||||
|
if stanza:get_child("query", xmlns_bytestream)
|
||||||
|
and stanza:get_child("query", xmlns_bytestream):get_child("streamhost") then
|
||||||
|
local sanitize_streamhost = function(tag)
|
||||||
|
if tag.name == "streamhost" then
|
||||||
|
local is_proxy = false
|
||||||
|
|
||||||
|
if tag.attr and tag.attr.jid and string.find(tag.attr.jid, "proxy.") ~= nil then
|
||||||
|
is_proxy = true
|
||||||
|
elseif tag.attr and tag.attr.host and string.find(tag.attr.host, "proxy.") ~= nil then
|
||||||
|
is_proxy = true
|
||||||
|
elseif tag:get_child("proxy") ~= nil then
|
||||||
|
is_proxy = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_proxy == false then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return tag
|
||||||
|
end
|
||||||
|
|
||||||
|
stanza:get_child("query", xmlns_bytestream):maptags(sanitize_streamhost)
|
||||||
|
|
||||||
|
local num_bytestreams = 0
|
||||||
|
for child in stanza:get_child("query", xmlns_bytestream):childtags("bytestream") do
|
||||||
|
num_bytestreams = num_bytestreams + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local num_tags = #stanza:get_child("query", xmlns_bytestream).tags
|
||||||
|
if num_bytestreams < num_tags then
|
||||||
|
local error_stanza = st.error_reply(stanza, "cancel", "service-unavailable")
|
||||||
|
error_stanza.attr.to = stanza.attr.to
|
||||||
|
error_stanza.attr.from = stanza.attr.from
|
||||||
|
core_route_stanza(module_host, error_stanza);
|
||||||
|
core_route_stanza(module_host, st.error_reply(stanza, "cancel", "service-unavailable")) --"error", nil, "Couldn't connect to any streamhosts"));
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
-- Sanitize Jingle
|
||||||
|
-- FIXME: This should use maptags as above
|
||||||
|
elseif stanza and stanza.tags and stanza.tags[1] then
|
||||||
|
local child = stanza.tags[1]
|
||||||
|
|
||||||
|
if child and child.name == "jingle" and child.attr.xmlns == "urn:xmpp:jingle:1" then
|
||||||
|
for a, b in ipairs(stanza.tags[1]) do
|
||||||
|
if b.name == "content" then
|
||||||
|
for i, v in ipairs(stanza.tags[1].tags[a]) do
|
||||||
|
if v.name == "transport" then
|
||||||
|
local remove_ids = {}
|
||||||
|
for ii, vv in ipairs(v) do
|
||||||
|
if vv.name == "candidate" and vv.attr.type and vv.attr.type ~= "relay" then
|
||||||
|
table.insert(remove_ids, ii)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local ib = #remove_ids
|
||||||
|
while ib > 0 do
|
||||||
|
table.remove(stanza.tags[1].tags[a].tags[i], ib)
|
||||||
|
ib = ib-1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil; -- Let the original hook go through
|
||||||
|
end, 9000);
|
||||||
|
|
||||||
|
-- Catch offline messages and allow only those that are encrypted
|
||||||
|
module:hook("message/offline/handle", function(event)
|
||||||
|
local origin, stanza = event.origin, event.stanza;
|
||||||
|
|
||||||
|
if not stanza:child_with_name("body") then
|
||||||
|
return true; -- Only log actual messages
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Let host and bare messages go through anyway
|
||||||
|
if not (stanza.attr.from == "" or stanza.attr.from == module_host or stanza.attr.to == module_host) then
|
||||||
|
if stanza and stanza.name and stanza.name == "message" then
|
||||||
|
if stanza.tags and stanza.tags[2] then
|
||||||
|
local is_encrypted = false
|
||||||
|
|
||||||
|
if stanza.tags[2].name == "body" then
|
||||||
|
if string.find(stanza.tags[2]:get_text(), "?OTR:") == 1 then
|
||||||
|
is_encrypted = true;
|
||||||
|
end
|
||||||
|
if string.find(stanza.tags[2]:get_text(), "*** Encrypted with the Gaim-Encryption plugin", 1, true) == 1 then
|
||||||
|
is_encrypted = true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if stanza.tags[2].name == "x" and stanza.tags[2].attr.xmlns == "jabber:x:encrypted" then
|
||||||
|
is_encrypted = true;
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_encrypted == false then
|
||||||
|
local error_stanza =
|
||||||
|
st.message({ to = origin.full_jid, from = stanza.attr.to })
|
||||||
|
:tag("body"):text("User offline; offline messages are only allowed when you use encryption");
|
||||||
|
|
||||||
|
origin.send( error_stanza);
|
||||||
|
|
||||||
|
return true; -- Block the message from getting stored
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil; -- let the hook go along
|
||||||
|
end, 9000); -- Priority over 9000, nothing should be over 9000, really!
|
||||||
|
|
||||||
|
module:hook("message/host", function(event)
|
||||||
|
local origin, stanza = event.origin, event.stanza;
|
||||||
|
local message_body = stanza:child_with_name("body"):get_text()
|
||||||
|
|
||||||
|
if string.find(message_body, "#!help") == 1 then
|
||||||
|
local help_str = [[#!help This message
|
||||||
|
#!version Returns mod_auth_anon's current version
|
||||||
|
#!changelog Returns the full changelog (one message per version)]]
|
||||||
|
|
||||||
|
local reply_stanza = st.message({to = origin.full_jid, from = module_host, type = "chat"})
|
||||||
|
:tag("body"):text(help_str)
|
||||||
|
|
||||||
|
origin.send(reply_stanza)
|
||||||
|
|
||||||
|
return true
|
||||||
|
elseif string.find(message_body, "#!version") == 1 then
|
||||||
|
local current_version = mod_auth_anon_changelog[1][1]
|
||||||
|
local reply_stanza = st.message({to = origin.full_jid, from = module_host, type = "chat"})
|
||||||
|
:tag("body"):text("Current version of Mod Auth Anon: " .. current_version )
|
||||||
|
|
||||||
|
origin.send(reply_stanza)
|
||||||
|
|
||||||
|
return true
|
||||||
|
elseif string.find(message_body, "#!changelog") == 1 then
|
||||||
|
local changelog = ""
|
||||||
|
|
||||||
|
for i, v in ipairs(mod_auth_anon_changelog) do
|
||||||
|
local version_body = "Version "..v[1].."\n"..v[2]
|
||||||
|
local reply_stanza = st.message({to = origin.full_jid, from = module_host, type = "chat"})
|
||||||
|
:tag("body"):text(version_body)
|
||||||
|
|
||||||
|
origin.send(reply_stanza)
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
-- elseif string.find(message_body, "#!hash") == 1 then
|
||||||
|
-- local file_path = string.sub(debug.getinfo(1).source, 2)
|
||||||
|
-- module:log("debug", file_path)
|
||||||
|
-- local file = assert(io.open(file_path, "r"))
|
||||||
|
-- local x = ""
|
||||||
|
-- for line in file:lines() do
|
||||||
|
-- x = x..line
|
||||||
|
-- end
|
||||||
|
-- file:close()
|
||||||
|
--
|
||||||
|
-- local file_hash = hashes.md5(x, true)
|
||||||
|
--
|
||||||
|
-- module:log("debug", file_path .. " " .. file_hash)
|
||||||
|
--
|
||||||
|
-- return true
|
||||||
|
else
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end, 9000)
|
||||||
|
|
||||||
|
function module.load()
|
||||||
|
assert (connect() ~= nil)
|
||||||
|
|
||||||
|
assert (create_table() ~= nil)
|
||||||
|
|
||||||
|
assert (purge_users() ~= nil)
|
||||||
|
|
||||||
|
module:hook("roster-load", inject_roster_contacts);
|
||||||
|
datamanager.add_callback(handle_datamanager);
|
||||||
|
end
|
||||||
|
|
||||||
|
function module.unload()
|
||||||
|
datamanager.remove_callback(handle_datamanager);
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,78 @@
|
|||||||
|
-- Prosody IM
|
||||||
|
-- Copyright (C) 2008-2010 Matthew Wild
|
||||||
|
-- Copyright (C) 2008-2010 Waqas Hussain
|
||||||
|
--
|
||||||
|
-- This project is MIT/X11 licensed. Please see the
|
||||||
|
-- COPYING file in the source package for more information.
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
local st = require "util.stanza";
|
||||||
|
local jid_split = require "util.jid".split;
|
||||||
|
|
||||||
|
local full_sessions = full_sessions;
|
||||||
|
local bare_sessions = bare_sessions;
|
||||||
|
|
||||||
|
if module:get_host_type() == "local" then
|
||||||
|
module:hook("iq/full", function(data)
|
||||||
|
-- IQ to full JID recieved
|
||||||
|
local origin, stanza = data.origin, data.stanza;
|
||||||
|
|
||||||
|
local session = full_sessions[stanza.attr.to];
|
||||||
|
if session then
|
||||||
|
-- TODO fire post processing event
|
||||||
|
session.send(stanza);
|
||||||
|
else -- resource not online
|
||||||
|
if stanza.attr.type == "get" or stanza.attr.type == "set" then
|
||||||
|
origin.send(st.error_reply(stanza, "cancel", "service-unavailable"));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true;
|
||||||
|
end);
|
||||||
|
end
|
||||||
|
|
||||||
|
module:hook("iq/bare", function(data)
|
||||||
|
-- IQ to bare JID recieved
|
||||||
|
local origin, stanza = data.origin, data.stanza;
|
||||||
|
local type = stanza.attr.type;
|
||||||
|
|
||||||
|
-- TODO fire post processing events
|
||||||
|
if type == "get" or type == "set" then
|
||||||
|
local child = stanza.tags[1];
|
||||||
|
local ret = module:fire_event("iq/bare/"..child.attr.xmlns..":"..child.name, data);
|
||||||
|
if ret ~= nil then return ret; end
|
||||||
|
return module:fire_event("iq-"..type.."/bare/"..child.attr.xmlns..":"..child.name, data);
|
||||||
|
else
|
||||||
|
return module:fire_event("iq-"..type.."/bare/"..stanza.attr.id, data);
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
|
||||||
|
module:hook("iq/self", function(data)
|
||||||
|
-- IQ to self JID recieved
|
||||||
|
local origin, stanza = data.origin, data.stanza;
|
||||||
|
local type = stanza.attr.type;
|
||||||
|
|
||||||
|
if type == "get" or type == "set" then
|
||||||
|
local child = stanza.tags[1];
|
||||||
|
local ret = module:fire_event("iq/self/"..child.attr.xmlns..":"..child.name, data);
|
||||||
|
if ret ~= nil then return ret; end
|
||||||
|
return module:fire_event("iq-"..type.."/self/"..child.attr.xmlns..":"..child.name, data);
|
||||||
|
else
|
||||||
|
return module:fire_event("iq-"..type.."/self/"..stanza.attr.id, data);
|
||||||
|
end
|
||||||
|
end);
|
||||||
|
|
||||||
|
module:hook("iq/host", function(data)
|
||||||
|
-- IQ to a local host recieved
|
||||||
|
local origin, stanza = data.origin, data.stanza;
|
||||||
|
local type = stanza.attr.type;
|
||||||
|
|
||||||
|
if type == "get" or type == "set" then
|
||||||
|
local child = stanza.tags[1];
|
||||||
|
local ret = module:fire_event("iq/host/"..child.attr.xmlns..":"..child.name, data);
|
||||||
|
if ret ~= nil then return ret; end
|
||||||
|
return module:fire_event("iq-"..type.."/host/"..child.attr.xmlns..":"..child.name, data);
|
||||||
|
else
|
||||||
|
return module:fire_event("iq-"..type.."/host/"..stanza.attr.id, data);
|
||||||
|
end
|
||||||
|
end);
|
@ -0,0 +1,228 @@
|
|||||||
|
-- Prosody IM
|
||||||
|
-- Copyright (C) 2008-2010 Matthew Wild
|
||||||
|
-- Copyright (C) 2008-2010 Waqas Hussain
|
||||||
|
--
|
||||||
|
-- This project is MIT/X11 licensed. Please see the
|
||||||
|
-- COPYING file in the source package for more information.
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
local format = string.format;
|
||||||
|
local setmetatable, type = setmetatable, type;
|
||||||
|
local pairs, ipairs = pairs, ipairs;
|
||||||
|
local char = string.char;
|
||||||
|
local loadfile, setfenv, pcall = loadfile, setfenv, pcall;
|
||||||
|
local log = require "util.logger".init("datamanager");
|
||||||
|
local io_open = io.open;
|
||||||
|
local os_remove = os.remove;
|
||||||
|
local tostring, tonumber = tostring, tonumber;
|
||||||
|
local error = error;
|
||||||
|
local next = next;
|
||||||
|
local t_insert = table.insert;
|
||||||
|
local append = require "util.serialization".append;
|
||||||
|
local path_separator = assert ( package.config:match ( "^([^\n]+)" ) , "package.config not in standard form" ) -- Extract directory seperator from package.config (an undocumented string that comes with lua)
|
||||||
|
local lfs = require "lfs";
|
||||||
|
local prosody = prosody;
|
||||||
|
local raw_mkdir;
|
||||||
|
|
||||||
|
if prosody.platform == "posix" then
|
||||||
|
raw_mkdir = require "util.pposix".mkdir; -- Doesn't trample on umask
|
||||||
|
else
|
||||||
|
raw_mkdir = lfs.mkdir;
|
||||||
|
end
|
||||||
|
|
||||||
|
module "datamanager"
|
||||||
|
|
||||||
|
---- utils -----
|
||||||
|
local encode, decode;
|
||||||
|
do
|
||||||
|
local urlcodes = setmetatable({}, { __index = function (t, k) t[k] = char(tonumber("0x"..k)); return t[k]; end });
|
||||||
|
|
||||||
|
decode = function (s)
|
||||||
|
return s and (s:gsub("+", " "):gsub("%%([a-fA-F0-9][a-fA-F0-9])", urlcodes));
|
||||||
|
end
|
||||||
|
|
||||||
|
encode = function (s)
|
||||||
|
return s and (s:gsub("%W", function (c) return format("%%%02x", c:byte()); end));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local _mkdir = {};
|
||||||
|
local function mkdir(path)
|
||||||
|
path = path:gsub("/", path_separator); -- TODO as an optimization, do this during path creation rather than here
|
||||||
|
if not _mkdir[path] then
|
||||||
|
raw_mkdir(path);
|
||||||
|
_mkdir[path] = true;
|
||||||
|
end
|
||||||
|
return path;
|
||||||
|
end
|
||||||
|
|
||||||
|
local data_path = (prosody and prosody.paths and prosody.paths.data) or ".";
|
||||||
|
local callbacks = {};
|
||||||
|
|
||||||
|
------- API -------------
|
||||||
|
|
||||||
|
function set_data_path(path)
|
||||||
|
log("debug", "Setting data path to: %s", path);
|
||||||
|
data_path = path;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function callback(username, host, datastore, data)
|
||||||
|
for _, f in ipairs(callbacks) do
|
||||||
|
username, host, datastore, data = f(username, host, datastore, data);
|
||||||
|
if username == false then break; end
|
||||||
|
end
|
||||||
|
|
||||||
|
return username, host, datastore, data;
|
||||||
|
end
|
||||||
|
function add_callback(func)
|
||||||
|
if not callbacks[func] then -- Would you really want to set the same callback more than once?
|
||||||
|
callbacks[func] = true;
|
||||||
|
callbacks[#callbacks+1] = func;
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function remove_callback(func)
|
||||||
|
if callbacks[func] then
|
||||||
|
for i, f in ipairs(callbacks) do
|
||||||
|
if f == func then
|
||||||
|
callbacks[i] = nil;
|
||||||
|
callbacks[f] = nil;
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function getpath(username, host, datastore, ext, create)
|
||||||
|
ext = ext or "dat";
|
||||||
|
host = (host and encode(host)) or "_global";
|
||||||
|
username = username and encode(username);
|
||||||
|
if username then
|
||||||
|
if create then mkdir(mkdir(mkdir(data_path).."/"..host).."/"..datastore); end
|
||||||
|
return format("%s/%s/%s/%s.%s", data_path, host, datastore, username, ext);
|
||||||
|
elseif host then
|
||||||
|
if create then mkdir(mkdir(data_path).."/"..host); end
|
||||||
|
return format("%s/%s/%s.%s", data_path, host, datastore, ext);
|
||||||
|
else
|
||||||
|
if create then mkdir(data_path); end
|
||||||
|
return format("%s/%s.%s", data_path, datastore, ext);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function load(username, host, datastore)
|
||||||
|
local data, ret = loadfile(getpath(username, host, datastore));
|
||||||
|
if not data then
|
||||||
|
local mode = lfs.attributes(getpath(username, host, datastore), "mode");
|
||||||
|
if not mode then
|
||||||
|
log("debug", "Assuming empty "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
|
||||||
|
return nil;
|
||||||
|
else -- file exists, but can't be read
|
||||||
|
-- TODO more detailed error checking and logging?
|
||||||
|
log("error", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
|
||||||
|
return nil, "Error reading storage";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
setfenv(data, {});
|
||||||
|
local success, ret = pcall(data);
|
||||||
|
if not success then
|
||||||
|
log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
|
||||||
|
return nil, "Error reading storage";
|
||||||
|
end
|
||||||
|
return ret;
|
||||||
|
end
|
||||||
|
|
||||||
|
function store(username, host, datastore, data)
|
||||||
|
if not data then
|
||||||
|
data = {};
|
||||||
|
end
|
||||||
|
|
||||||
|
username, host, datastore, data = callback(username, host, datastore, data);
|
||||||
|
if username == false then
|
||||||
|
return true; -- Don't save this data at all
|
||||||
|
end
|
||||||
|
|
||||||
|
-- save the datastore
|
||||||
|
local f, msg = io_open(getpath(username, host, datastore, nil, true), "w+");
|
||||||
|
if not f then
|
||||||
|
log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
|
||||||
|
return nil, "Error saving to storage";
|
||||||
|
end
|
||||||
|
f:write("return ");
|
||||||
|
append(f, data);
|
||||||
|
f:close();
|
||||||
|
if next(data) == nil then -- try to delete empty datastore
|
||||||
|
log("debug", "Removing empty %s datastore for user %s@%s at %q", datastore, username or "nil", host or "nil", getpath(username, host, datastore));
|
||||||
|
os_remove(getpath(username, host, datastore));
|
||||||
|
end
|
||||||
|
-- we write data even when we are deleting because lua doesn't have a
|
||||||
|
-- platform independent way of checking for non-exisitng files
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
function list_append(username, host, datastore, data)
|
||||||
|
if not data then return; end
|
||||||
|
if callback(username, host, datastore, data) == false then return true; end
|
||||||
|
-- save the datastore
|
||||||
|
local f, msg = io_open(getpath(username, host, datastore, "list", true), "a+");
|
||||||
|
if not f then
|
||||||
|
log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
f:write("item(");
|
||||||
|
append(f, data);
|
||||||
|
f:write(");\n");
|
||||||
|
f:close();
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
function list_store(username, host, datastore, data)
|
||||||
|
if not data then
|
||||||
|
data = {};
|
||||||
|
end
|
||||||
|
if callback(username, host, datastore, data) == false then return true; end
|
||||||
|
-- save the datastore
|
||||||
|
local f, msg = io_open(getpath(username, host, datastore, "list", true), "w+");
|
||||||
|
if not f then
|
||||||
|
log("error", "Unable to write to "..datastore.." storage ('"..msg.."') for user: "..(username or "nil").."@"..(host or "nil"));
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
for _, d in ipairs(data) do
|
||||||
|
f:write("item(");
|
||||||
|
append(f, d);
|
||||||
|
f:write(");\n");
|
||||||
|
end
|
||||||
|
f:close();
|
||||||
|
if next(data) == nil then -- try to delete empty datastore
|
||||||
|
log("debug", "Removing empty %s datastore for user %s@%s", datastore, username or "nil", host or "nil");
|
||||||
|
os_remove(getpath(username, host, datastore, "list"));
|
||||||
|
end
|
||||||
|
-- we write data even when we are deleting because lua doesn't have a
|
||||||
|
-- platform independent way of checking for non-exisitng files
|
||||||
|
return true;
|
||||||
|
end
|
||||||
|
|
||||||
|
function list_load(username, host, datastore)
|
||||||
|
local data, ret = loadfile(getpath(username, host, datastore, "list"));
|
||||||
|
if not data then
|
||||||
|
local mode = lfs.attributes(getpath(username, host, datastore, "list"), "mode");
|
||||||
|
if not mode then
|
||||||
|
log("debug", "Assuming empty "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
|
||||||
|
return nil;
|
||||||
|
else -- file exists, but can't be read
|
||||||
|
-- TODO more detailed error checking and logging?
|
||||||
|
log("error", "Failed to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
|
||||||
|
return nil, "Error reading storage";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local items = {};
|
||||||
|
setfenv(data, {item = function(i) t_insert(items, i); end});
|
||||||
|
local success, ret = pcall(data);
|
||||||
|
if not success then
|
||||||
|
log("error", "Unable to load "..datastore.." storage ('"..ret.."') for user: "..(username or "nil").."@"..(host or "nil"));
|
||||||
|
return nil, "Error reading storage";
|
||||||
|
end
|
||||||
|
return items;
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M;
|
@ -0,0 +1,433 @@
|
|||||||
|
-- Prosody IM
|
||||||
|
-- Copyright (C) 2008-2010 Matthew Wild
|
||||||
|
-- Copyright (C) 2008-2010 Waqas Hussain
|
||||||
|
--
|
||||||
|
-- This project is MIT/X11 licensed. Please see the
|
||||||
|
-- COPYING file in the source package for more information.
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
local t_insert = table.insert;
|
||||||
|
local t_concat = table.concat;
|
||||||
|
local t_remove = table.remove;
|
||||||
|
local t_concat = table.concat;
|
||||||
|
local s_format = string.format;
|
||||||
|
local s_match = string.match;
|
||||||
|
local tostring = tostring;
|
||||||
|
local setmetatable = setmetatable;
|
||||||
|
local getmetatable = getmetatable;
|
||||||
|
local pairs = pairs;
|
||||||
|
local ipairs = ipairs;
|
||||||
|
local type = type;
|
||||||
|
local next = next;
|
||||||
|
local print = print;
|
||||||
|
local unpack = unpack;
|
||||||
|
local s_gsub = string.gsub;
|
||||||
|
local s_char = string.char;
|
||||||
|
local s_find = string.find;
|
||||||
|
local os = os;
|
||||||
|
local log = require "util.logger".init("stanzarouter")
|
||||||
|
|
||||||
|
local do_pretty_printing = not os.getenv("WINDIR");
|
||||||
|
local getstyle, getstring;
|
||||||
|
if do_pretty_printing then
|
||||||
|
local ok, termcolours = pcall(require, "util.termcolours");
|
||||||
|
if ok then
|
||||||
|
getstyle, getstring = termcolours.getstyle, termcolours.getstring;
|
||||||
|
else
|
||||||
|
do_pretty_printing = nil;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local xmlns_stanzas = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||||
|
|
||||||
|
module "stanza"
|
||||||
|
|
||||||
|
stanza_mt = { __type = "stanza" };
|
||||||
|
stanza_mt.__index = stanza_mt;
|
||||||
|
local stanza_mt = stanza_mt;
|
||||||
|
|
||||||
|
function stanza(name, attr)
|
||||||
|
local stanza = { name = name, attr = attr or {}, tags = {} };
|
||||||
|
return setmetatable(stanza, stanza_mt);
|
||||||
|
end
|
||||||
|
local stanza = stanza;
|
||||||
|
|
||||||
|
function stanza_mt:query(xmlns)
|
||||||
|
return self:tag("query", { xmlns = xmlns });
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:body(text, attr)
|
||||||
|
return self:tag("body", attr):text(text);
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:tag(name, attrs)
|
||||||
|
local s = stanza(name, attrs);
|
||||||
|
local last_add = self.last_add;
|
||||||
|
if not last_add then last_add = {}; self.last_add = last_add; end
|
||||||
|
(last_add[#last_add] or self):add_direct_child(s);
|
||||||
|
t_insert(last_add, s);
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:text(text)
|
||||||
|
local last_add = self.last_add;
|
||||||
|
(last_add and last_add[#last_add] or self):add_direct_child(text);
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:up()
|
||||||
|
local last_add = self.last_add;
|
||||||
|
if last_add then t_remove(last_add); end
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:reset()
|
||||||
|
self.last_add = nil;
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:add_direct_child(child)
|
||||||
|
if type(child) == "table" then
|
||||||
|
t_insert(self.tags, child);
|
||||||
|
end
|
||||||
|
t_insert(self, child);
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:add_child(child)
|
||||||
|
local last_add = self.last_add;
|
||||||
|
(last_add and last_add[#last_add] or self):add_direct_child(child);
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:get_child(name, xmlns)
|
||||||
|
for _, child in ipairs(self.tags) do
|
||||||
|
if (not name or child.name == name)
|
||||||
|
and ((not xmlns and self.attr.xmlns == child.attr.xmlns)
|
||||||
|
or child.attr.xmlns == xmlns) then
|
||||||
|
|
||||||
|
return child;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:get_child_text(name, xmlns)
|
||||||
|
local tag = self:get_child(name, xmlns);
|
||||||
|
if tag then
|
||||||
|
return tag:get_text();
|
||||||
|
end
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:child_with_name(name)
|
||||||
|
for _, child in ipairs(self.tags) do
|
||||||
|
if child.name == name then return child; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:child_with_ns(ns)
|
||||||
|
for _, child in ipairs(self.tags) do
|
||||||
|
if child.attr.xmlns == ns then return child; end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:children()
|
||||||
|
local i = 0;
|
||||||
|
return function (a)
|
||||||
|
i = i + 1
|
||||||
|
return a[i];
|
||||||
|
end, self, i;
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:childtags(name, xmlns)
|
||||||
|
xmlns = xmlns or self.attr.xmlns;
|
||||||
|
local tags = self.tags;
|
||||||
|
local start_i, max_i = 1, #tags;
|
||||||
|
return function ()
|
||||||
|
for i = start_i, max_i do
|
||||||
|
local v = tags[i];
|
||||||
|
if (not name or v.name == name)
|
||||||
|
and (not xmlns or xmlns == v.attr.xmlns) then
|
||||||
|
start_i = i+1;
|
||||||
|
return v;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt:maptags(callback)
|
||||||
|
local tags = self.tags;
|
||||||
|
local n_children, n_tags, curr_tag = #self, #tags, #tags;
|
||||||
|
local i = curr_tag
|
||||||
|
|
||||||
|
while i > 0 do
|
||||||
|
if self[curr_tag] == tags[curr_tag] then
|
||||||
|
local ret = callback(self[curr_tag])
|
||||||
|
if ret == nil then
|
||||||
|
t_remove(self, curr_tag);
|
||||||
|
t_remove(tags, curr_tag);
|
||||||
|
n_children = n_children - 1;
|
||||||
|
n_tags = n_tags - 1;
|
||||||
|
else
|
||||||
|
self[curr_tag] = ret;
|
||||||
|
tags[curr_tag] = ret;
|
||||||
|
end
|
||||||
|
curr_tag = curr_tag - 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
i = i - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- local i = 1;
|
||||||
|
-- while curr_tag <= n_tags do
|
||||||
|
-- log("debug", tostring(self[i]))
|
||||||
|
-- log("debug", tostring(tags[curr_tag]))
|
||||||
|
-- if self[i] == tags[curr_tag] then
|
||||||
|
-- local ret = callback(self[i]);
|
||||||
|
-- if ret == nil then
|
||||||
|
-- t_remove(self, i);
|
||||||
|
-- t_remove(tags, curr_tag);
|
||||||
|
-- n_children = n_children - 1;
|
||||||
|
-- n_tags = n_tags - 1;
|
||||||
|
-- else
|
||||||
|
-- self[i] = ret;
|
||||||
|
-- tags[i] = ret;
|
||||||
|
-- end
|
||||||
|
-- i = i + 1;
|
||||||
|
-- curr_tag = curr_tag + 1;
|
||||||
|
-- end
|
||||||
|
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
|
||||||
|
local xml_escape
|
||||||
|
do
|
||||||
|
local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" };
|
||||||
|
function xml_escape(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
|
||||||
|
_M.xml_escape = xml_escape;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _dostring(t, buf, self, xml_escape, parentns)
|
||||||
|
local nsid = 0;
|
||||||
|
local name = t.name
|
||||||
|
t_insert(buf, "<"..name);
|
||||||
|
for k, v in pairs(t.attr) do
|
||||||
|
if s_find(k, "\1", 1, true) then
|
||||||
|
local ns, attrk = s_match(k, "^([^\1]*)\1?(.*)$");
|
||||||
|
nsid = nsid + 1;
|
||||||
|
t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'");
|
||||||
|
elseif not(k == "xmlns" and v == parentns) then
|
||||||
|
t_insert(buf, " "..k.."='"..xml_escape(v).."'");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local len = #t;
|
||||||
|
if len == 0 then
|
||||||
|
t_insert(buf, "/>");
|
||||||
|
else
|
||||||
|
t_insert(buf, ">");
|
||||||
|
for n=1,len do
|
||||||
|
local child = t[n];
|
||||||
|
if child.name then
|
||||||
|
self(child, buf, self, xml_escape, t.attr.xmlns);
|
||||||
|
else
|
||||||
|
t_insert(buf, xml_escape(child));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
t_insert(buf, "</"..name..">");
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function stanza_mt.__tostring(t)
|
||||||
|
local buf = {};
|
||||||
|
_dostring(t, buf, _dostring, xml_escape, nil);
|
||||||
|
return t_concat(buf);
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt.top_tag(t)
|
||||||
|
local attr_string = "";
|
||||||
|
if t.attr then
|
||||||
|
for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, xml_escape(tostring(v))); end end
|
||||||
|
end
|
||||||
|
return s_format("<%s%s>", t.name, attr_string);
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt.get_text(t)
|
||||||
|
if #t.tags == 0 then
|
||||||
|
return t_concat(t);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt.get_error(stanza)
|
||||||
|
local type, condition, text;
|
||||||
|
|
||||||
|
local error_tag = stanza:get_child("error");
|
||||||
|
if not error_tag then
|
||||||
|
return nil, nil, nil;
|
||||||
|
end
|
||||||
|
type = error_tag.attr.type;
|
||||||
|
|
||||||
|
for child in error_tag:childtags() do
|
||||||
|
if child.attr.xmlns == xmlns_stanzas then
|
||||||
|
if not text and child.name == "text" then
|
||||||
|
text = child:get_text();
|
||||||
|
elseif not condition then
|
||||||
|
condition = child.name;
|
||||||
|
end
|
||||||
|
if condition and text then
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return type, condition or "undefined-condition", text;
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt.__add(s1, s2)
|
||||||
|
return s1:add_direct_child(s2);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
do
|
||||||
|
local id = 0;
|
||||||
|
function new_id()
|
||||||
|
id = id + 1;
|
||||||
|
return "lx"..id;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function preserialize(stanza)
|
||||||
|
local s = { name = stanza.name, attr = stanza.attr };
|
||||||
|
for _, child in ipairs(stanza) do
|
||||||
|
if type(child) == "table" then
|
||||||
|
t_insert(s, preserialize(child));
|
||||||
|
else
|
||||||
|
t_insert(s, child);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return s;
|
||||||
|
end
|
||||||
|
|
||||||
|
function deserialize(stanza)
|
||||||
|
-- Set metatable
|
||||||
|
if stanza then
|
||||||
|
local attr = stanza.attr;
|
||||||
|
for i=1,#attr do attr[i] = nil; end
|
||||||
|
local attrx = {};
|
||||||
|
for att in pairs(attr) do
|
||||||
|
if s_find(att, "|", 1, true) and not s_find(att, "\1", 1, true) then
|
||||||
|
local ns,na = s_match(att, "^([^|]+)|(.+)$");
|
||||||
|
attrx[ns.."\1"..na] = attr[att];
|
||||||
|
attr[att] = nil;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for a,v in pairs(attrx) do
|
||||||
|
attr[a] = v;
|
||||||
|
end
|
||||||
|
setmetatable(stanza, stanza_mt);
|
||||||
|
for _, child in ipairs(stanza) do
|
||||||
|
if type(child) == "table" then
|
||||||
|
deserialize(child);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not stanza.tags then
|
||||||
|
-- Rebuild tags
|
||||||
|
local tags = {};
|
||||||
|
for _, child in ipairs(stanza) do
|
||||||
|
if type(child) == "table" then
|
||||||
|
t_insert(tags, child);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
stanza.tags = tags;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return stanza;
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _clone(stanza)
|
||||||
|
local attr, tags = {}, {};
|
||||||
|
for k,v in pairs(stanza.attr) do attr[k] = v; end
|
||||||
|
local new = { name = stanza.name, attr = attr, tags = tags };
|
||||||
|
for i=1,#stanza do
|
||||||
|
local child = stanza[i];
|
||||||
|
if child.name then
|
||||||
|
child = _clone(child);
|
||||||
|
t_insert(tags, child);
|
||||||
|
end
|
||||||
|
t_insert(new, child);
|
||||||
|
end
|
||||||
|
return setmetatable(new, stanza_mt);
|
||||||
|
end
|
||||||
|
clone = _clone;
|
||||||
|
|
||||||
|
function message(attr, body)
|
||||||
|
if not body then
|
||||||
|
return stanza("message", attr);
|
||||||
|
else
|
||||||
|
return stanza("message", attr):tag("body"):text(body):up();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function iq(attr)
|
||||||
|
if attr and not attr.id then attr.id = new_id(); end
|
||||||
|
return stanza("iq", attr or { id = new_id() });
|
||||||
|
end
|
||||||
|
|
||||||
|
function reply(orig)
|
||||||
|
return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) });
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
local xmpp_stanzas_attr = { xmlns = xmlns_stanzas };
|
||||||
|
function error_reply(orig, type, condition, message)
|
||||||
|
local t = reply(orig);
|
||||||
|
t.attr.type = "error";
|
||||||
|
t:tag("error", {type = type}) --COMPAT: Some day xmlns:stanzas goes here
|
||||||
|
:tag(condition, xmpp_stanzas_attr):up();
|
||||||
|
if (message) then t:tag("text", xmpp_stanzas_attr):text(message):up(); end
|
||||||
|
return t; -- stanza ready for adding app-specific errors
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function presence(attr)
|
||||||
|
return stanza("presence", attr);
|
||||||
|
end
|
||||||
|
|
||||||
|
if do_pretty_printing then
|
||||||
|
local style_attrk = getstyle("yellow");
|
||||||
|
local style_attrv = getstyle("red");
|
||||||
|
local style_tagname = getstyle("red");
|
||||||
|
local style_punc = getstyle("magenta");
|
||||||
|
|
||||||
|
local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'");
|
||||||
|
local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">");
|
||||||
|
--local tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">").."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
|
||||||
|
local tag_format = top_tag_format.."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
|
||||||
|
function stanza_mt.pretty_print(t)
|
||||||
|
local children_text = "";
|
||||||
|
for n, child in ipairs(t) do
|
||||||
|
if type(child) == "string" then
|
||||||
|
children_text = children_text .. xml_escape(child);
|
||||||
|
else
|
||||||
|
children_text = children_text .. child:pretty_print();
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local attr_string = "";
|
||||||
|
if t.attr then
|
||||||
|
for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end
|
||||||
|
end
|
||||||
|
return s_format(tag_format, t.name, attr_string, children_text, t.name);
|
||||||
|
end
|
||||||
|
|
||||||
|
function stanza_mt.pretty_top_tag(t)
|
||||||
|
local attr_string = "";
|
||||||
|
if t.attr then
|
||||||
|
for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end
|
||||||
|
end
|
||||||
|
return s_format(top_tag_format, t.name, attr_string);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Sorry, fresh out of colours for you guys ;)
|
||||||
|
stanza_mt.pretty_print = stanza_mt.__tostring;
|
||||||
|
stanza_mt.pretty_top_tag = stanza_mt.top_tag;
|
||||||
|
end
|
||||||
|
|
||||||
|
return _M;
|
Loading…
Reference in new issue