1
0
Fork 0

Initial import

master
Matthieu Lalonde 12 years ago
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 = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
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…
Cancel
Save